From fed257a72070a0c44d120d3b3236ec58d59af5eb Mon Sep 17 00:00:00 2001
From: liuyihe <2513762445@qq.com>
Date: Tue, 22 Sep 2026 13:51:58 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/modules/iam/api/platformIam.ts | 3 +
src/modules/iam/pages/InternalUsersView.vue | 50 +++++++++++++
.../api/platformLoadingLists.test.ts | 4 +-
.../loading-lists/api/platformLoadingLists.ts | 16 ++++-
.../components/LoadingListDetailDrawer.vue | 40 +++++++----
.../components/LoadingListsView.test.ts | 6 +-
.../pages/PreloadPlanDetailView.test.ts | 28 ++++++--
.../pages/PreloadPlanDetailView.vue | 72 ++++++++++++++-----
8 files changed, 174 insertions(+), 45 deletions(-)
diff --git a/src/modules/iam/api/platformIam.ts b/src/modules/iam/api/platformIam.ts
index e6f4cf31..29ab981e 100644
--- a/src/modules/iam/api/platformIam.ts
+++ b/src/modules/iam/api/platformIam.ts
@@ -267,6 +267,7 @@ export interface PlatformManagedUser {
lastLoginAt: string | null
protectedAccount: boolean
overseasWarehouseId: number | string | null
+ domesticWarehouseId?: number | string | null
}
export interface PlatformManagedUserPage {
@@ -309,6 +310,7 @@ export interface CreatePlatformManagedUserRequest {
tenantId: number | string
roleIds: Array
overseasWarehouseId: number | string | null
+ domesticWarehouseId?: number | string | null
password: string
status: 'ACTIVE' | 'DISABLED'
}
@@ -320,6 +322,7 @@ export interface UpdatePlatformManagedUserRequest {
tenantId: number | string
roleIds: Array
overseasWarehouseId: number | string | null
+ domesticWarehouseId?: number | string | null
}
async function getData(path: string): Promise {
diff --git a/src/modules/iam/pages/InternalUsersView.vue b/src/modules/iam/pages/InternalUsersView.vue
index dbb66ecd..6506d2e7 100644
--- a/src/modules/iam/pages/InternalUsersView.vue
+++ b/src/modules/iam/pages/InternalUsersView.vue
@@ -29,6 +29,7 @@ import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { useAuthStore } from '@/core/auth/auth'
+import { listAllPlatformWarehouses, type PlatformWarehouseSummaryResource } from '@/modules/warehouses'
const access = useAccessPreviewStore()
const auth = useAuthStore()
@@ -37,6 +38,9 @@ const options = ref({ tenants: [], roles: [] })
const overseasWarehouses = ref([])
const overseasWarehousesLoading = ref(false)
const overseasWarehousesError = ref(null)
+const domesticWarehouses = ref([])
+const domesticWarehousesLoading = ref(false)
+const domesticWarehousesError = ref(null)
const loading = ref(false)
const saving = ref(false)
const loadError = ref(null)
@@ -81,6 +85,7 @@ const emptyForm = () => ({
tenantId: '0',
roleIds: [] as string[],
overseasWarehouseId: '',
+ domesticWarehouseId: '',
password: '',
confirmPassword: '',
status: 'ACTIVE' as 'ACTIVE' | 'DISABLED',
@@ -111,6 +116,10 @@ const overseasPdaRoleIds = computed(() => new Set(
availableRoles.value.filter((role) => role.roleCode === 'OVERSEAS_WAREHOUSE_PDA_OPERATOR').map((role) => String(role.id)),
))
const hasOverseasPdaRole = computed(() => form.roleIds.some((roleId) => overseasPdaRoleIds.value.has(roleId)))
+const domesticPdaRoleIds = computed(() => new Set(
+ availableRoles.value.filter((role) => role.roleCode === 'CN_FIRSTLEG_PDA_OPERATOR').map((role) => String(role.id)),
+))
+const hasDomesticPdaRole = computed(() => form.roleIds.some((roleId) => domesticPdaRoleIds.value.has(roleId)))
const formRules = computed(() => ({
username: editingUser.value ? [] : [
@@ -127,6 +136,9 @@ const formRules = computed(() => ({
overseasWarehouseId: !hasOverseasPdaRole.value || (editingUser.value && !canEdit.value) ? [] : [
{ required: true, message: '请选择实际海外收货仓', trigger: 'change' },
],
+ domesticWarehouseId: !hasDomesticPdaRole.value || (editingUser.value && !canEdit.value) ? [] : [
+ { required: true, message: '请选择国内作业仓', trigger: 'change' },
+ ],
password: editingUser.value && !form.resetPassword ? [] : [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ pattern: /^(?=.*[A-Za-z])(?=.*\d).{8,64}$/, message: '密码为 8-64 位,且必须包含字母和数字', trigger: 'blur' },
@@ -217,6 +229,7 @@ function openEdit(row: PlatformManagedUser) {
tenantId: '0',
roleIds: row.roleIds.map(String),
overseasWarehouseId: row.overseasWarehouseId == null ? '' : String(row.overseasWarehouseId),
+ domesticWarehouseId: row.domesticWarehouseId == null ? '' : String(row.domesticWarehouseId),
password: '',
confirmPassword: '',
status: row.status,
@@ -243,6 +256,19 @@ async function loadOverseasWarehouses() {
}
}
+async function loadDomesticWarehouses() {
+ domesticWarehousesLoading.value = true
+ domesticWarehousesError.value = null
+ try {
+ domesticWarehouses.value = await listAllPlatformWarehouses({ warehouseType: 'DOMESTIC', status: 'ACTIVE' })
+ } catch (error) {
+ domesticWarehouses.value = []
+ domesticWarehousesError.value = getApiErrorDetails(error, '国内作业仓加载失败,请重试')
+ } finally {
+ domesticWarehousesLoading.value = false
+ }
+}
+
function isCurrentUser(row: PlatformManagedUser | null) {
if (!row) return false
return String(row.userId) === auth.user?.id || String(row.authUserId) === auth.user?.id
@@ -266,6 +292,7 @@ async function saveUser() {
tenantId: '0',
roleIds: form.roleIds,
overseasWarehouseId: hasOverseasPdaRole.value ? form.overseasWarehouseId : null,
+ ...(hasDomesticPdaRole.value ? { domesticWarehouseId: form.domesticWarehouseId } : {}),
password: form.password,
status: form.status,
})
@@ -280,6 +307,7 @@ async function saveUser() {
tenantId: '0',
roleIds: form.roleIds,
overseasWarehouseId: hasOverseasPdaRole.value ? form.overseasWarehouseId : null,
+ ...(hasDomesticPdaRole.value ? { domesticWarehouseId: form.domesticWarehouseId } : {}),
})
}
if (canChangeStatus.value && form.status !== row.status) {
@@ -340,6 +368,14 @@ watch(hasOverseasPdaRole, (selected) => {
}
void loadOverseasWarehouses()
})
+watch(hasDomesticPdaRole, (selected) => {
+ if (!selected) {
+ form.domesticWarehouseId = ''
+ domesticWarehousesError.value = null
+ return
+ }
+ void loadDomesticWarehouses()
+})
onMounted(loadInitialData)
@@ -435,6 +471,20 @@ onMounted(loadInitialData)
仓库将写入登录 Token,海外 PDA 不提供仓库切换。
+
+
+
+
+
+ {{ domesticWarehousesError.message }}
+ 重新加载
+
+ 该人员只会出现在此仓库的国内头程 PDA 任务候选中。
+
diff --git a/src/modules/loading-lists/api/platformLoadingLists.test.ts b/src/modules/loading-lists/api/platformLoadingLists.test.ts
index fd6a5f27..83037d75 100644
--- a/src/modules/loading-lists/api/platformLoadingLists.test.ts
+++ b/src/modules/loading-lists/api/platformLoadingLists.test.ts
@@ -146,11 +146,11 @@ describe('platform loading lists API', () => {
},
})
- const result = await confirmPreloadPlan('1900000000000000001', 3, 'confirm-operation-1')
+ const result = await confirmPreloadPlan('1900000000000000001', 3, '1900000000000000007', 'confirm-operation-1')
expect(post).toHaveBeenCalledWith(
'/platform/v1/first-leg/loading-plans/1900000000000000001/confirm',
- { expectedVersion: 3 },
+ { expectedVersion: 3, assigneeId: '1900000000000000007' },
{ headers: { 'Idempotency-Key': 'confirm-operation-1' } },
)
expect(result).toMatchObject({
diff --git a/src/modules/loading-lists/api/platformLoadingLists.ts b/src/modules/loading-lists/api/platformLoadingLists.ts
index 6248fe26..8c41c447 100644
--- a/src/modules/loading-lists/api/platformLoadingLists.ts
+++ b/src/modules/loading-lists/api/platformLoadingLists.ts
@@ -186,6 +186,11 @@ export interface PreloadConfirmationResponse {
readiness: PreloadReadiness
}
+export interface DomesticPdaOperator {
+ userId: string
+ displayName: string
+}
+
type RawId = string | number | null | undefined
type RawPage = Omit, 'page' | 'size' | 'total' | 'pages'> & {
page: string | number
@@ -359,10 +364,17 @@ export async function updateLoadingItemRemarks(planId: string, itemId: string, r
}
}
-export async function confirmPreloadPlan(planId: string, expectedVersion: number, operationId: string) {
+export async function listDomesticPdaOperators(warehouseId: string, keyword?: string): Promise {
+ const response = await http.get, ApiEnvelope>(
+ '/platform/v1/iam/users/domestic-pda-operators', { params: { warehouseId, keyword } },
+ )
+ return unwrapApiResponse(response).map((operator) => ({ ...operator, userId: requiredId(operator.userId) }))
+}
+
+export async function confirmPreloadPlan(planId: string, expectedVersion: number, assigneeId: string, operationId: string) {
const response = await http.post, ApiEnvelope>(
`${basePath}/loading-plans/${encodeURIComponent(planId)}/confirm`,
- { expectedVersion },
+ { expectedVersion, assigneeId },
{ headers: { 'Idempotency-Key': operationId } },
)
const result = unwrapApiResponse(response)
diff --git a/src/modules/loading-lists/components/LoadingListDetailDrawer.vue b/src/modules/loading-lists/components/LoadingListDetailDrawer.vue
index 00c9f868..7278b72d 100644
--- a/src/modules/loading-lists/components/LoadingListDetailDrawer.vue
+++ b/src/modules/loading-lists/components/LoadingListDetailDrawer.vue
@@ -1,7 +1,7 @@
-
+
+
+
+
+
+
+
+
+
+
@@ -278,7 +310,7 @@ watch([planId, warehouseId], () => void loadDetail())
-
+
@@ -286,6 +318,8 @@ watch([planId, warehouseId], () => void loadDetail())
{{ detail.summary.cabinetNo || '—' }}
{{ detail.summary.containerNo || '待补录' }}
+ {{ selectedAssignee?.displayName || '未选择' }}
+
@@ -296,6 +330,10 @@ watch([planId, warehouseId], () => void loadDetail())
.transport-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--admin-space-6, 16px); }
.transport-form :deep(.el-form-item:last-child) { grid-column: 1 / -1; }
.confirm-content { display: grid; gap: var(--admin-space-5, 12px); }
+.pda-operator-picker { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; }
+.pda-operator-picker label { flex: 0 0 auto; font-size: var(--admin-font-body, 13px); white-space: nowrap; }
+.pda-operator-picker label span { color: var(--el-color-warning-dark-2); font-size: var(--admin-font-caption, 12px); }
+.pda-operator-picker :deep(.el-select) { width: 180px; }
.remarks-form { margin-top: 14px; }
.remarks-form small { display: block; margin-top: var(--admin-space-1, 4px); color: var(--cc-muted); }
:global(.loading-transport-dialog.el-dialog) { max-height: 90vh; overflow: hidden; }