From d5a23a7c65c206357ea2d2b0e7cfa1d2467e7823 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 25 Aug 2026 15:08:31 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 2914efa0..dc9c53f0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,6 +41,7 @@ | 供应商、国内整柜 | `modules/suppliers/`、`modules/domestic-containers/` | Backend `warehouse/` 及对应 OpenAPI | | 平台仓库与仓储资源 | `modules/warehouses/`、`modules/warehouse-resources/` | `warehouse/` | | 平台用户、RBAC、导航 | `modules/iam/`、`core/access/`、`core/navigation/` | `auth/`、`iam/`、平台权限契约 | +| 海外 PDA 拣货员与实际收货仓绑定 | `modules/iam/pages/InternalUsersView.vue`、`modules/iam/api/platformIam.ts` | `WAREHOUSE_PICKER` 必须且只能绑定一个 `overseasWarehouseId`;候选来自 `modules/warehouses/` 的实际海外仓,服务端约束在 `iam/`,Claim 与实时校验联查 `common-security/`、`overseas-security-service/` | | 消息中心 | `modules/notifications/` | Backend `iam/.../notification/`;仅顶部消息中心,不进入左侧菜单 | | 订单列表、详情与入库费用 | `modules/unified-orders/`、`modules/first-leg-orders/` | `order/`、`firstleg/`、Finance | | 入库、测量与计价主数据 | `modules/inbounds/`、`modules/fba-pricing/`、`modules/dimension-config/`、`modules/fee-items/` | `firstleg/`、`order/`、`file/`、`integration-service/` | @@ -76,6 +77,7 @@ - 当前只有平台、客户两级;Admin 不显示“租户”。平台仓库是公共主数据,不与客户绑定;客户筛选不得缩小仓库候选。完整语义见平台客户层级与权限契约。 - 菜单、列表路由和详情只读统一使用 `*:list`,写操作使用独立权限;前端守卫不能替代服务端鉴权。供应商固定使用 `platform:supplier:list`。 +- 平台用户选择 `WAREHOUSE_PICKER` 时必须选择唯一实际海外收货仓,其他角色不得提交 `overseasWarehouseId`;编辑时保留已绑定仓库并按字符串透传长整型 ID,前端校验不能替代 IAM 服务端约束。 - `core/navigation/adminNavigation.ts`、`app/router/index.ts` 和模块 `routes.ts` 是共享装配文件;并行任务不得覆盖总控已确认语义,重叠时保留现有改动并回传总控。 - 认证统一经过 `core/auth/auth.ts`、`shared/api/http.ts` 和 `shared/api/overseasHttp.ts`。登录/刷新原子替换国内、海外 Token,海外 401 统一刷新并最多重放一次;页面不读写 Token 或手工拼 `Authorization`。 - 接入真实接口时删除对应模拟路径,不允许生产逻辑静默回退假数据。客户专属写操作显式选择客户,长整型 ID 按字符串透传,不从本地存储推断。 From 3545ffa5221ecdf1683eb1bdd4235a3e3767f048 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 25 Aug 2026 16:40:44 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index dc9c53f0..b5a9937c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,7 +41,7 @@ | 供应商、国内整柜 | `modules/suppliers/`、`modules/domestic-containers/` | Backend `warehouse/` 及对应 OpenAPI | | 平台仓库与仓储资源 | `modules/warehouses/`、`modules/warehouse-resources/` | `warehouse/` | | 平台用户、RBAC、导航 | `modules/iam/`、`core/access/`、`core/navigation/` | `auth/`、`iam/`、平台权限契约 | -| 海外 PDA 拣货员与实际收货仓绑定 | `modules/iam/pages/InternalUsersView.vue`、`modules/iam/api/platformIam.ts` | `WAREHOUSE_PICKER` 必须且只能绑定一个 `overseasWarehouseId`;候选来自 `modules/warehouses/` 的实际海外仓,服务端约束在 `iam/`,Claim 与实时校验联查 `common-security/`、`overseas-security-service/` | +| 海外 PDA 拣货员与实际收货仓绑定 | `modules/iam/pages/InternalUsersView.vue`、`modules/iam/api/platformIam.ts` | `WAREHOUSE_PICKER` 必须且只能绑定一个 `overseasWarehouseId`;候选来自 `modules/warehouses/` 的实际海外仓,服务端约束在 `iam/`,Claim 与实时校验联查 `common-security/`、`lastleg-service/security/` | | 消息中心 | `modules/notifications/` | Backend `iam/.../notification/`;仅顶部消息中心,不进入左侧菜单 | | 订单列表、详情与入库费用 | `modules/unified-orders/`、`modules/first-leg-orders/` | `order/`、`firstleg/`、Finance | | 入库、测量与计价主数据 | `modules/inbounds/`、`modules/fba-pricing/`、`modules/dimension-config/`、`modules/fee-items/` | `firstleg/`、`order/`、`file/`、`integration-service/` | From f702d1b988706f803301b37a8814e821b3008849 Mon Sep 17 00:00:00 2001 From: wujinliang <2318309077@qq.com> Date: Tue, 25 Aug 2026 17:43:00 +0800 Subject: [PATCH 3/6] 1 --- .../pages/ContainersView.vue | 4 ++-- .../measurement/InboundMeasurementPanel.vue | 23 ++++++++++++++++++- .../pages/OceanBookingDetailView.vue | 10 ++++++-- .../pages/OceanBookingsView.vue | 5 ++-- .../pallets/pages/PalletsView.vue | 2 -- 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/modules/container-loading/pages/ContainersView.vue b/src/modules/container-loading/pages/ContainersView.vue index f0165d05..b259e8e8 100644 --- a/src/modules/container-loading/pages/ContainersView.vue +++ b/src/modules/container-loading/pages/ContainersView.vue @@ -161,8 +161,8 @@ const destinationWarehouseValue = computed(() => form.destinationWarehouseCode | const queryFields = computed(() => [ { key: 'customerId', type: 'select', label: '客户', width: 220, placeholder: '全部客户', options: customers.value.filter((customer) => customer.status === 'ACTIVE').map((customer) => ({ label: customer.tenantName, value: String(customer.id) })), props: { filterable: true, loading: customersLoading.value } }, { key: 'keyword', type: 'input', label: '关键词', width: 220, placeholder: '柜次号、集装箱号或提单号', props: { 'aria-label': '关键词', maxlength: 128 } }, - { key: 'containerType', type: 'select', label: '柜型', width: 140, options: CONTAINER_SPECIFICATIONS.map((item) => ({ label: item.type, value: item.type })), props: { 'aria-label': '柜型' } }, - { key: 'status', type: 'select', label: '排柜状态', width: 150, options: [{ label: '待装柜', value: 'WAIT_LOADING' }, { label: '预装柜中', value: 'EDITING' }, { label: '拣货中', value: 'LOADING' }, { label: '装柜完成', value: 'COMPLETED' }], props: { 'aria-label': '排柜状态' } }, + { key: 'containerType', type: 'select', label: '柜型', width: 140, placeholder: '请选择箱型', options: CONTAINER_SPECIFICATIONS.map((item) => ({ label: item.type, value: item.type })), props: { 'aria-label': '柜型' } }, + { key: 'status', type: 'select', label: '排柜状态', width: 150, placeholder: '请选择货柜状态', options: [{ label: '待装柜', value: 'WAIT_LOADING' }, { label: '预装柜中', value: 'EDITING' }, { label: '拣货中', value: 'LOADING' }, { label: '装柜完成', value: 'COMPLETED' }], props: { 'aria-label': '排柜状态' } }, { key: 'sailingRange', type: 'daterange', label: '预计开船', width: 260, props: { 'aria-label': '预计开船日期范围' } }, ]) const columns: DataTableColumn[] = [ diff --git a/src/modules/inbounds/measurement/InboundMeasurementPanel.vue b/src/modules/inbounds/measurement/InboundMeasurementPanel.vue index ae2e9e66..0177d691 100644 --- a/src/modules/inbounds/measurement/InboundMeasurementPanel.vue +++ b/src/modules/inbounds/measurement/InboundMeasurementPanel.vue @@ -294,6 +294,16 @@ function validateDraft() { form.items.forEach((item, index) => { const itemErrors: string[] = [] if (!Number.isInteger(item.packageCount) || item.packageCount < 1 || item.packageCount > 1_000_000) itemErrors.push('箱数必须是 1 至 1,000,000 的整数') + const dimensions = [ + ['长', item.lengthCm], + ['宽', item.widthCm], + ['高', item.heightCm], + ] as const + for (const [label, value] of dimensions) { + if (!Number.isFinite(value) || value <= 0) itemErrors.push(`${label}必须是大于 0 的有效数值`) + else if (value > 999_999_999.999) itemErrors.push(`${label}超出系统允许上限 999,999,999.999 cm`) + else if (Math.abs(value * 1000 - Math.round(value * 1000)) > 1e-6) itemErrors.push(`${label}最多保留 3 位小数`) + } if (itemErrors.length) errors[item.clientKey] = itemErrors.map((message) => `第 ${index + 1} 条:${message}`) }) if (general.length) errors.general = general @@ -310,7 +320,17 @@ function validateDraft() { } function setOperationError(error: unknown, fallback: string) { - const details = getApiErrorDetails(error, fallback) + const source = getApiErrorDetails(error, fallback) + const numericOverflow = /data truncation|out of range|numeric value|decimal|数值.*(过大|超出)|尺寸.*(过大|超限)/i.test(source.message) + const largestItem = numericOverflow + ? [...form.items].sort((left, right) => Math.max(right.lengthCm, right.widthCm, right.heightCm) - Math.max(left.lengthCm, left.widthCm, left.heightCm))[0] + : undefined + const details = numericOverflow ? { + ...source, + message: largestItem + ? `箱规尺寸或箱数超出系统允许范围,请检查箱号 ${largestItem.boxCode || largestItem.itemNo}(${formatNumber(largestItem.lengthCm)} × ${formatNumber(largestItem.widthCm)} × ${formatNumber(largestItem.heightCm)} cm)后重试` + : '箱规尺寸或箱数超出系统允许范围,请检查长、宽、高和箱数后重试', + } : source const conflict = details.message.includes('版本已变化') || details.message.includes('并发修改') conflictDetected.value = conflict @@ -361,6 +381,7 @@ async function saveDraft() { async function submitForReview() { if (!maySubmit.value || !form.measurementId) return + if (!validateDraft()) return if (isDirty.value) { ElMessage.warning('请先保存当前修改,再提交审核') return diff --git a/src/modules/ocean-bookings/pages/OceanBookingDetailView.vue b/src/modules/ocean-bookings/pages/OceanBookingDetailView.vue index 005d2535..f9f586b5 100644 --- a/src/modules/ocean-bookings/pages/OceanBookingDetailView.vue +++ b/src/modules/ocean-bookings/pages/OceanBookingDetailView.vue @@ -4,6 +4,7 @@ import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'elem import { useRoute, useRouter } from 'vue-router' import { ArrowLeft, Plus, Save, Trash2 } from '@lucide/vue' import { useAccessPreviewStore } from '@/core/access/accessPreview' +import { useWorkspaceTabsStore } from '@/core/workspace-tabs/workspaceTabs' import { getApiErrorDetails } from '@/shared/api/http' import LoadErrorState from '@/shared/components/LoadErrorState.vue' import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue' @@ -52,6 +53,7 @@ const containerKindOptions = [ const route = useRoute() const router = useRouter() const access = useAccessPreviewStore() +const workspace = useWorkspaceTabsStore() const id = computed(() => String(route.params.bookingId || '')) const isNew = computed(() => route.name === 'ocean-booking-create' || id.value === 'new') const detail = ref(null) @@ -220,8 +222,12 @@ async function save() { const saved = isNew.value ? await createOceanBooking({ expectedVersion: 0, data: payload }) : await updateOceanBookingDraft(id.value, { expectedVersion: detail.value?.version || 0, data: payload }) - ElMessage.success('订舱草稿已保存') - if (isNew.value) await router.replace(`/ocean-bookings/${saved.id}`) + ElMessage.success(isNew.value ? `订舱创建成功:${saved.bookingNo || saved.id}` : '订舱草稿已保存') + if (isNew.value) { + workspace.removeByIds(['ocean-booking-create']) + await router.replace('/ocean-bookings') + return + } applyDetail(saved) } catch (cause) { actionError.value = getApiErrorDetails(cause) } finally { saving.value = false } diff --git a/src/modules/ocean-bookings/pages/OceanBookingsView.vue b/src/modules/ocean-bookings/pages/OceanBookingsView.vue index e2fb1a7b..29b218b7 100644 --- a/src/modules/ocean-bookings/pages/OceanBookingsView.vue +++ b/src/modules/ocean-bookings/pages/OceanBookingsView.vue @@ -1,5 +1,5 @@