From fb1e566a2162746109b72b3a24c5c83a7343017e Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Sat, 11 Jul 2026 18:25:48 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=93=E5=82=A8=E4=B8=8E?= =?UTF-8?q?=E5=BA=97=E9=93=BA=E6=96=B0=E5=A2=9E=E5=BC=B9=E7=AA=97=E5=B8=83?= =?UTF-8?q?=E5=B1=80=EF=BC=8C=E4=BF=AE=E5=A4=8D=20SKU=20=E8=A1=A8=E5=8D=95?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E9=94=99=E8=AF=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 统一 OMS 表单样式与交互,补齐 i18n 文案。 Co-authored-by: Cursor --- src/locale/en-US.ts | 57 +++- src/locale/zh-CN.ts | 57 +++- src/styles/warehouse-form-modal.less | 108 ++++++ src/styles/warehouse-modal.less | 175 ++++++++++ src/views/order/platformStore/index.vue | 353 +++++++++++++++++--- src/views/warehouse/inboundNotice/index.vue | 298 ++++++++++++++--- src/views/warehouse/inventory/index.vue | 96 +++++- src/views/warehouse/parcel/index.vue | 159 +++++++-- src/views/warehouse/sku/index.vue | 159 +++++++-- 9 files changed, 1299 insertions(+), 163 deletions(-) create mode 100644 src/styles/warehouse-form-modal.less create mode 100644 src/styles/warehouse-modal.less diff --git a/src/locale/en-US.ts b/src/locale/en-US.ts index 0466b26..b1ff871 100644 --- a/src/locale/en-US.ts +++ b/src/locale/en-US.ts @@ -113,6 +113,17 @@ export default { 'warehouse.sku.form.skuName': 'SKU Name', 'warehouse.sku.form.weightG': 'Weight(g)', 'warehouse.sku.form.remark': 'Remark', + 'warehouse.sku.form.imageUrl': 'Image URL', + 'warehouse.sku.form.dimensions': 'Dimensions', + 'warehouse.sku.form.length': 'L', + 'warehouse.sku.form.width': 'W', + 'warehouse.sku.form.height': 'H', + 'warehouse.sku.form.clearSize': 'Clear size', + 'warehouse.sku.form.volumeEstimate': 'Volume estimate', + 'warehouse.modal.noWarehouse': 'No warehouse selected', + 'warehouse.modal.section.basic': 'Basic Info', + 'warehouse.modal.section.weightSize': 'Weight & Size', + 'warehouse.modal.section.products': 'Products', 'warehouse.sku.modal.add': 'Add SKU', 'warehouse.sku.modal.edit': 'Edit SKU', 'warehouse.sku.actions.delete': 'Delete SKU', @@ -137,14 +148,16 @@ export default { 'warehouse.inventory.actions.relocate': 'Relocate', 'warehouse.inventory.drawer.binDetail': 'Bin Detail', 'warehouse.inventory.modal.relocate': 'Relocate', - 'warehouse.inventory.form.sourceBin': 'Source Bin ID', - 'warehouse.inventory.form.targetBin': 'Target Bin ID', + 'warehouse.inventory.form.sourceBin': 'Source Bin', + 'warehouse.inventory.form.targetBin': 'Target Bin', 'warehouse.inventory.form.qty': 'Qty', + 'warehouse.inventory.form.selectBin': 'Select bin', 'warehouse.inventory.message.loadFailed': 'Failed to load inventory', 'warehouse.inventory.message.binDetailFailed': 'Failed to load bin detail', 'warehouse.inventory.message.relocateRequired': 'Fill relocate fields', 'warehouse.inventory.message.relocateSuccess': 'Relocated', 'warehouse.inventory.message.relocateFailed': 'Relocate failed', + 'warehouse.inventory.message.sameBin': 'Source and target bin must differ', 'warehouse.parcel.status.all': 'All', 'warehouse.parcel.status.inTransit': 'In Transit', 'warehouse.parcel.status.arrived': 'Arrived', @@ -163,6 +176,10 @@ export default { 'warehouse.parcel.form.carrierName': 'Carrier', 'warehouse.parcel.form.productName': 'Product', 'warehouse.parcel.form.quantity': 'Qty', + 'warehouse.parcel.form.remark': 'Remark', + 'warehouse.parcel.form.itemRemark': 'Item remark', + 'warehouse.parcel.form.currentWarehouse': 'Current warehouse', + 'warehouse.parcel.form.autoAddRow': 'A new row is added after you fill product name', 'warehouse.parcel.modal.add': 'Add Parcel', 'warehouse.parcel.actions.delete': 'Delete Parcel', 'warehouse.parcel.message.loadFailed': 'Failed to load parcels', @@ -170,6 +187,7 @@ export default { 'warehouse.parcel.message.saveFailed': 'Save failed', 'warehouse.parcel.message.deleteSuccess': 'Deleted', 'warehouse.parcel.message.required': 'Warehouse and tracking no required', + 'warehouse.parcel.message.itemsRequired': 'Add at least one product', 'warehouse.inbound.status.all': 'All', 'warehouse.inbound.status.forecasted': 'Forecasted', 'warehouse.inbound.status.completed': 'Completed', @@ -190,6 +208,20 @@ export default { 'warehouse.inbound.form.markBoxNo': 'Box No', 'warehouse.inbound.form.sku': 'SKU', 'warehouse.inbound.form.plannedQty': 'Planned Qty', + 'warehouse.inbound.form.markPlaceholder': 'Usually the tracking number', + 'warehouse.inbound.form.boxSkuDetail': 'Boxes & SKU lines', + 'warehouse.inbound.form.boxCount': 'Boxes {n}', + 'warehouse.inbound.form.skuCount': 'SKU {n}', + 'warehouse.inbound.form.totalQty': 'Total qty {n}', + 'warehouse.inbound.form.boxWeight': 'Weight/g', + 'warehouse.inbound.form.boxLength': 'L/cm', + 'warehouse.inbound.form.boxWidth': 'W/cm', + 'warehouse.inbound.form.boxHeight': 'H/cm', + 'warehouse.inbound.form.selectSku': 'Select SKU', + 'warehouse.inbound.actions.addBox': 'Add box', + 'warehouse.inbound.actions.addBox5': 'Add 5 boxes', + 'warehouse.inbound.actions.removeBox': 'Remove boxes', + 'warehouse.inbound.actions.addSku': 'Add SKU line', 'warehouse.inbound.modal.add': 'Add Inbound', 'warehouse.inbound.drawer.detail': 'Inbound Detail', 'warehouse.inbound.box': 'Box', @@ -201,14 +233,22 @@ export default { 'warehouse.inbound.message.saveSuccess': 'Saved', 'warehouse.inbound.message.saveFailed': 'Save failed', 'warehouse.inbound.message.deleteSuccess': 'Deleted', - 'warehouse.inbound.message.required': 'Select warehouse and SKU', + 'warehouse.inbound.message.required': 'Select warehouse', + 'warehouse.inbound.message.boxRequired': 'Add SKU lines to at least one box', 'warehouse.inbound.message.detailFailed': 'Failed to load detail', 'order.platformStore.platform.manual': 'Manual', 'order.platformStore.table.siteCountry': 'Site Country', 'order.platformStore.table.autoFulfill': 'Auto Fulfill', 'order.platformStore.table.authTime': 'Auth Time', 'order.platformStore.table.createTime': 'Created At', - 'order.platformStore.form.siteCountry': 'Site Country', + 'order.platformStore.form.siteCountry': 'Site', + 'order.platformStore.form.authCode': 'Auth code', + 'order.platformStore.form.tip': 'Note', + 'order.platformStore.form.authRenewTip': 'Re-authorizing the same store renews binding without changing the nickname.', + 'order.platformStore.form.manualTip': 'Enter any name; the system creates a mock manual store.', + 'order.platformStore.site.crossBorder': 'Cross-border', + 'order.platformStore.actions.bind': 'Bind store', + 'order.platformStore.actions.startAuth': 'Start authorization', 'order.platformStore.auth.active': 'Active', 'order.platformStore.auth.expired': 'Expired', 'order.platformStore.auth.revoked': 'Revoked', @@ -224,6 +264,12 @@ export default { 'order.platformStore.form.account': 'Account', 'order.platformStore.form.autoFulfill': 'Auto Fulfill', 'order.platformStore.modal.add': 'Add Store', + 'order.platformStore.modal.bind': 'Bind Store', + 'order.platformStore.form.nicknamePlaceholder': 'Store nickname (custom)', + 'order.platformStore.form.authCodePlaceholder': 'Auth code from store management', + 'order.platformStore.form.sitePlaceholder': 'Select site', + 'order.platformStore.form.selectPlatformHint': 'Select a platform to continue', + 'order.platformStore.actions.close': 'Close', 'order.platformStore.modal.edit': 'Edit Store', 'order.platformStore.actions.auth': 'Authorize', 'order.platformStore.actions.delete': 'Delete Store', @@ -232,7 +278,10 @@ export default { 'order.platformStore.message.saveFailed': 'Save failed', 'order.platformStore.message.deleteSuccess': 'Deleted', 'order.platformStore.message.required': 'Nickname is required', + 'order.platformStore.message.platformRequired': 'Select a platform', + 'order.platformStore.message.authCodeRequired': 'Auth code is required', 'order.platformStore.message.authPlaceholder': 'OAuth placeholder', + 'order.platformStore.message.authStarted': 'Authorization started; complete it in the new window', 'order.platformStore.message.authFailed': 'Failed to get auth URL', 'order.platformStoreOrder.status.all': 'All', 'order.platformStoreOrder.status.pending': 'Pending', diff --git a/src/locale/zh-CN.ts b/src/locale/zh-CN.ts index 04f3bcd..8c1117f 100644 --- a/src/locale/zh-CN.ts +++ b/src/locale/zh-CN.ts @@ -113,6 +113,17 @@ export default { 'warehouse.sku.form.skuName': 'SKU 名称', 'warehouse.sku.form.weightG': '重量(g)', 'warehouse.sku.form.remark': '备注', + 'warehouse.sku.form.imageUrl': '图片地址', + 'warehouse.sku.form.dimensions': '尺寸', + 'warehouse.sku.form.length': '长', + 'warehouse.sku.form.width': '宽', + 'warehouse.sku.form.height': '高', + 'warehouse.sku.form.clearSize': '清空尺寸', + 'warehouse.sku.form.volumeEstimate': '体积估算', + 'warehouse.modal.noWarehouse': '未选择仓库', + 'warehouse.modal.section.basic': '基础信息', + 'warehouse.modal.section.weightSize': '重量与尺寸', + 'warehouse.modal.section.products': '商品信息', 'warehouse.sku.modal.add': '新增 SKU', 'warehouse.sku.modal.edit': '编辑 SKU', 'warehouse.sku.actions.delete': '删除 SKU', @@ -137,14 +148,16 @@ export default { 'warehouse.inventory.actions.relocate': '换库位', 'warehouse.inventory.drawer.binDetail': '库位明细', 'warehouse.inventory.modal.relocate': '换库位', - 'warehouse.inventory.form.sourceBin': '源库位 ID', - 'warehouse.inventory.form.targetBin': '目标库位 ID', + 'warehouse.inventory.form.sourceBin': '源库位', + 'warehouse.inventory.form.targetBin': '目标库位', 'warehouse.inventory.form.qty': '数量', + 'warehouse.inventory.form.selectBin': '请选择库位', 'warehouse.inventory.message.loadFailed': '加载库存失败', 'warehouse.inventory.message.binDetailFailed': '加载库位明细失败', 'warehouse.inventory.message.relocateRequired': '请填写换库位信息', 'warehouse.inventory.message.relocateSuccess': '换库位成功', 'warehouse.inventory.message.relocateFailed': '换库位失败', + 'warehouse.inventory.message.sameBin': '源库位与目标库位不能相同', 'warehouse.parcel.status.all': '全部', 'warehouse.parcel.status.inTransit': '在途', 'warehouse.parcel.status.arrived': '已到仓', @@ -163,6 +176,10 @@ export default { 'warehouse.parcel.form.carrierName': '快递公司', 'warehouse.parcel.form.productName': '商品名称', 'warehouse.parcel.form.quantity': '数量', + 'warehouse.parcel.form.remark': '备注', + 'warehouse.parcel.form.itemRemark': '商品备注', + 'warehouse.parcel.form.currentWarehouse': '当前仓库', + 'warehouse.parcel.form.autoAddRow': '填写商品名称后会自动新增下一行', 'warehouse.parcel.modal.add': '新增包裹', 'warehouse.parcel.actions.delete': '删除包裹', 'warehouse.parcel.message.loadFailed': '加载包裹失败', @@ -170,6 +187,7 @@ export default { 'warehouse.parcel.message.saveFailed': '保存失败', 'warehouse.parcel.message.deleteSuccess': '删除成功', 'warehouse.parcel.message.required': '请填写仓库和快递单号', + 'warehouse.parcel.message.itemsRequired': '请至少填写一条商品信息', 'warehouse.inbound.status.all': '全部', 'warehouse.inbound.status.forecasted': '预报', 'warehouse.inbound.status.completed': '已完成', @@ -190,6 +208,20 @@ export default { 'warehouse.inbound.form.markBoxNo': '箱号', 'warehouse.inbound.form.sku': 'SKU', 'warehouse.inbound.form.plannedQty': '计划数量', + 'warehouse.inbound.form.markPlaceholder': '一般填写物流的快递单号', + 'warehouse.inbound.form.boxSkuDetail': '箱子与 SKU 明细', + 'warehouse.inbound.form.boxCount': '箱子 {n}', + 'warehouse.inbound.form.skuCount': 'SKU {n}', + 'warehouse.inbound.form.totalQty': '总入库 {n}', + 'warehouse.inbound.form.boxWeight': '重量/g', + 'warehouse.inbound.form.boxLength': '长/cm', + 'warehouse.inbound.form.boxWidth': '宽/cm', + 'warehouse.inbound.form.boxHeight': '高/cm', + 'warehouse.inbound.form.selectSku': '请选择库内 SKU', + 'warehouse.inbound.actions.addBox': '增加箱子', + 'warehouse.inbound.actions.addBox5': '一次新增 5 箱', + 'warehouse.inbound.actions.removeBox': '移除箱子', + 'warehouse.inbound.actions.addSku': '选择库内 SKU', 'warehouse.inbound.modal.add': '新增入库预报', 'warehouse.inbound.drawer.detail': '入库详情', 'warehouse.inbound.box': '箱', @@ -201,14 +233,22 @@ export default { 'warehouse.inbound.message.saveSuccess': '保存成功', 'warehouse.inbound.message.saveFailed': '保存失败', 'warehouse.inbound.message.deleteSuccess': '删除成功', - 'warehouse.inbound.message.required': '请选择仓库和 SKU', + 'warehouse.inbound.message.required': '请选择仓库', + 'warehouse.inbound.message.boxRequired': '请至少为一个箱子选择 SKU', 'warehouse.inbound.message.detailFailed': '加载详情失败', 'order.platformStore.platform.manual': '手工店铺', 'order.platformStore.table.siteCountry': '站点国家', 'order.platformStore.table.autoFulfill': '自动发货', 'order.platformStore.table.authTime': '授权时间', 'order.platformStore.table.createTime': '创建时间', - 'order.platformStore.form.siteCountry': '站点国家', + 'order.platformStore.form.siteCountry': '开户站', + 'order.platformStore.form.authCode': '授权码', + 'order.platformStore.form.tip': '提示', + 'order.platformStore.form.authRenewTip': '授权相同的店铺会识别为店铺续期,当店铺准备过期或已过期时可进行重复绑定(续期不会改变名称)', + 'order.platformStore.form.manualTip': '可以随便取个名字,系统会自动创建一个店铺模拟绑定', + 'order.platformStore.site.crossBorder': '跨境店铺', + 'order.platformStore.actions.bind': '绑定店铺', + 'order.platformStore.actions.startAuth': '开始授权', 'order.platformStore.auth.active': '已授权', 'order.platformStore.auth.expired': '已过期', 'order.platformStore.auth.revoked': '已撤销', @@ -224,6 +264,12 @@ export default { 'order.platformStore.form.account': '平台账号', 'order.platformStore.form.autoFulfill': '自动发货', 'order.platformStore.modal.add': '新增店铺', + 'order.platformStore.modal.bind': '添加店铺', + 'order.platformStore.form.nicknamePlaceholder': '店铺名称(自定义)', + 'order.platformStore.form.authCodePlaceholder': '授权码,可在店铺管理中获取', + 'order.platformStore.form.sitePlaceholder': '请选择开户站', + 'order.platformStore.form.selectPlatformHint': '请先选择要绑定的平台', + 'order.platformStore.actions.close': '关闭', 'order.platformStore.modal.edit': '编辑店铺', 'order.platformStore.actions.auth': '授权', 'order.platformStore.actions.delete': '删除店铺', @@ -232,7 +278,10 @@ export default { 'order.platformStore.message.saveFailed': '保存失败', 'order.platformStore.message.deleteSuccess': '删除成功', 'order.platformStore.message.required': '请填写店铺昵称', + 'order.platformStore.message.platformRequired': '请选择平台类型', + 'order.platformStore.message.authCodeRequired': '请填写授权码', 'order.platformStore.message.authPlaceholder': 'OAuth 占位,待平台 SDK 接入', + 'order.platformStore.message.authStarted': '已发起授权,请在新窗口完成授权', 'order.platformStore.message.authFailed': '获取授权地址失败', 'order.platformStoreOrder.status.all': '全部', 'order.platformStoreOrder.status.pending': '待处理', diff --git a/src/styles/warehouse-form-modal.less b/src/styles/warehouse-form-modal.less new file mode 100644 index 0000000..54b0752 --- /dev/null +++ b/src/styles/warehouse-form-modal.less @@ -0,0 +1,108 @@ +.wh-form-modal { + padding: 4px 0 0; +} + +.wh-form-section__title { + margin-bottom: 12px; + font-size: 14px; + font-weight: 500; + color: var(--color-text-1); +} + +.wh-form-section__head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 12px; +} + +.wh-form-section__head .wh-form-section__title { + margin-bottom: 0; +} + +.wh-form-divider { + margin: 16px 0; +} + +.wh-form-hint { + font-size: 12px; + color: var(--color-text-3); +} + +.wh-form-meta { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.wh-form-toolbar { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 12px; +} + +.wh-form-readonly { + color: var(--color-text-1); + font-weight: 500; +} + +.wh-form-sku-preview { + display: flex; + align-items: flex-start; + gap: 12px; +} + +.wh-form-sku-preview__image { + flex: none; + width: 72px; + height: 72px; + border: 1px dashed var(--color-border-2); + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + background: var(--color-fill-1); + + img { + width: 100%; + height: 100%; + object-fit: cover; + } +} + +.wh-form-sku-preview__fields { + flex: 1; + min-width: 0; +} + +.wh-form-inline-size { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; +} + +.wh-form-volume { + margin-top: 4px; + font-size: 12px; + color: var(--color-text-3); +} + +.wh-form-box-panel { + padding: 12px; + background: var(--color-fill-1); + border-radius: 4px; +} + +.wh-form-info-bar { + margin-bottom: 12px; + padding: 10px 12px; + background: var(--color-fill-1); + border-radius: 4px; + font-size: 13px; + color: var(--color-text-2); + line-height: 1.5; +} diff --git a/src/styles/warehouse-modal.less b/src/styles/warehouse-modal.less new file mode 100644 index 0000000..b4c182e --- /dev/null +++ b/src/styles/warehouse-modal.less @@ -0,0 +1,175 @@ +.warehouse-modal-body { + max-height: 72vh; + overflow: auto; + padding: 4px 4px 0; +} + +.warehouse-modal-footer { + display: flex; + justify-content: flex-end; + gap: 8px; +} + +.warehouse-modal-title-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; + width: 100%; + padding-right: 24px; +} + +.warehouse-modal-title-text { + font-size: 16px; + font-weight: 600; +} + +.warehouse-modal-whse { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; +} + +.warehouse-modal-whse-label { + font-size: 12px; + color: var(--color-text-3); + white-space: nowrap; +} + +.warehouse-modal-card { + background: #fff; + border: 1px solid var(--color-border-2); + border-radius: 8px; + padding: 14px; + margin-bottom: 12px; +} + +.warehouse-modal-card-title { + font-size: 14px; + font-weight: 600; + margin-bottom: 12px; + color: var(--color-text-1); +} + +.warehouse-modal-card-head { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + margin-bottom: 10px; +} + +.warehouse-modal-card-head .warehouse-modal-card-title { + margin-bottom: 0; +} + +.warehouse-modal-whse-badge { + padding: 4px 10px; + border-radius: 14px; + background: rgb(var(--green-1)); + border: 1px solid rgb(var(--green-3)); + color: rgb(var(--green-7)); + font-size: 12px; + line-height: 20px; +} + +.warehouse-modal-meta { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.warehouse-modal-toolbar { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin: 8px 0 10px; + padding: 8px 0; + border-bottom: 1px dashed var(--color-border-2); +} + +.warehouse-modal-hint { + font-size: 12px; + color: var(--color-text-3); +} + +.warehouse-modal-size-row { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 12px; + margin: 12px 0; +} + +.warehouse-modal-size-label { + min-width: 40px; + color: var(--color-text-2); +} + +.warehouse-modal-volume { + margin-top: 4px; + font-size: 12px; + color: var(--color-text-3); +} + +.warehouse-modal-basic-layout { + display: flex; + gap: 16px; +} + +.warehouse-modal-image-col { + flex: none; + width: 120px; +} + +.warehouse-modal-form-col { + flex: 1; + min-width: 0; +} + +.warehouse-modal-image-preview { + width: 104px; + height: 104px; + border: 1px dashed var(--color-border-2); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + background: var(--color-fill-1); +} + +.warehouse-modal-image-preview img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.warehouse-modal-table-wrap { + :deep(.arco-table) { + border-radius: 6px; + } +} + +.warehouse-modal-box-panel { + margin-bottom: 8px; +} + +.warehouse-modal-platform-tags { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.warehouse-modal-platform-tag { + cursor: pointer; + user-select: none; +} + +.warehouse-modal-tip { + color: rgb(var(--red-6)); + font-size: 13px; + line-height: 1.6; +} diff --git a/src/views/order/platformStore/index.vue b/src/views/order/platformStore/index.vue index be0ac4e..815574c 100644 --- a/src/views/order/platformStore/index.vue +++ b/src/views/order/platformStore/index.vue @@ -6,7 +6,6 @@ import HandsontablePro from '@/components/HandsontablePro/index.vue' import PaginationPro from '@/components/PaginationPro/index.vue' import ModalPro from '@/components/ModalPro/index.vue' import useModal from '@/components/ModalPro/useModal' -import FormPro from '@/components/FormPro/index.vue' import { useTableContainerHeight } from '@/composables/useTableContainerHeight' import { deletePlatformStore, getPlatformStoreAuthUrl, getPlatformStoreList, savePlatformStore } from '@/api/order/platformStore' import { $baseMessage } from '@/utils' @@ -26,24 +25,56 @@ const currentPage = ref(1) const pageSize = ref(30) const totalCount = ref(0) -const platformOptions = computed(() => [ - { label: 'Shopee', value: 'shopee' }, - { label: 'Lazada', value: 'lazada' }, - { label: 'TikTok', value: 'tiktok' }, - { label: 'Temu', value: 'temu' }, - { label: t('order.platformStore.platform.manual'), value: 'manual' }, +const platformDefs = [ + { code: 'shopee', label: 'Shopee' }, + { code: 'lazada', label: 'Lazada' }, + { code: 'tiktok', label: 'TikTok' }, + { code: 'temu', label: 'Temu' }, + { code: 'manual', labelKey: 'order.platformStore.platform.manual' }, +] as const + +const platformOptions = computed(() => + platformDefs.map((p) => ({ + label: 'labelKey' in p ? t(p.labelKey) : p.label, + value: p.code, + })), +) + +const lazadaSiteOptions = computed(() => [ + { value: 'cb', label: t('order.platformStore.site.crossBorder') }, + { value: 'sg', label: '新加坡' }, + { value: 'my', label: '马来西亚' }, + { value: 'th', label: '泰国' }, + { value: 'vn', label: '越南' }, + { value: 'ph', label: '菲律宾' }, + { value: 'id', label: '印度尼西亚' }, ]) -const { visible: modalVisible, open: openModal, close: closeModal } = useModal() +const [registerFormModal, { openModal, closeModal }] = useModal() as any const saving = ref(false) const formModel = reactive({ id: 0, - platformCode: 'manual', + platformCode: '', storeNickname: '', platformAccount: '', - siteCountry: '', + siteCountry: 'cb', isAutoFulfill: false, remark: '', + authorizationCode: '', +}) + +const isEdit = computed(() => formModel.id > 0) +const isManualPlatform = computed(() => formModel.platformCode === 'manual') +const isOAuthPlatform = computed(() => !!formModel.platformCode && !isManualPlatform.value) +const showStoreFields = computed(() => !!formModel.platformCode || isEdit.value) + +const modalTitle = computed(() => + isEdit.value ? t('order.platformStore.modal.edit') : t('order.platformStore.modal.add'), +) + +const primaryActionLabel = computed(() => { + if (isEdit.value || isManualPlatform.value) return t('warehouse.common.save') + return t('order.platformStore.actions.startAuth') }) const hotColumns = computed(() => [ @@ -58,6 +89,12 @@ const hotColumns = computed(() => [ { key: 'createTime', label: t('order.platformStore.table.createTime'), width: 160 }, ]) +function platformLabel(code: string) { + const found = platformDefs.find((p) => p.code === code) + if (!found) return code + return 'labelKey' in found ? t(found.labelKey) : found.label +} + function mapRow(raw: Record) { const row = normalizeListRow(raw) const authStatus = String(pickField(row, 'authStatus', 'AuthStatus') ?? '') @@ -111,18 +148,23 @@ function handleSearch() { loadList() } -function handleAdd() { - if (!hasPermission('Order.PlatformStore.Save')) return +function resetForm() { Object.assign(formModel, { id: 0, - platformCode: 'manual', + platformCode: '', storeNickname: '', platformAccount: '', - siteCountry: '', + siteCountry: 'cb', isAutoFulfill: false, remark: '', + authorizationCode: '', }) - openModal() +} + +function handleAdd() { + if (!hasPermission('Order.PlatformStore.Save')) return + resetForm() + openModal(true) } function handleEdit(row: any) { @@ -130,21 +172,42 @@ function handleEdit(row: any) { const raw = row._raw || row Object.assign(formModel, { id: Number(pickField(raw, 'id', 'Id') ?? 0), - platformCode: pickField(raw, 'platformCode', 'PlatformCode') ?? 'manual', - storeNickname: pickField(raw, 'storeNickname', 'StoreNickname') ?? '', - platformAccount: pickField(raw, 'platformAccount', 'PlatformAccount') ?? '', - siteCountry: pickField(raw, 'siteCountry', 'SiteCountry') ?? '', + platformCode: String(pickField(raw, 'platformCode', 'PlatformCode') ?? 'manual'), + storeNickname: String(pickField(raw, 'storeNickname', 'StoreNickname') ?? ''), + platformAccount: String(pickField(raw, 'platformAccount', 'PlatformAccount') ?? ''), + siteCountry: String(pickField(raw, 'siteCountry', 'SiteCountry') ?? 'cb'), isAutoFulfill: Boolean(pickField(raw, 'isAutoFulfill', 'IsAutoFulfill')), - remark: pickField(raw, 'remark', 'Remark') ?? '', + remark: String(pickField(raw, 'remark', 'Remark') ?? ''), + authorizationCode: '', }) - openModal() + openModal(true) +} + +function selectPlatform(code: string) { + if (isEdit.value) return + formModel.platformCode = code + if (code === 'lazada') formModel.siteCountry = 'cb' + if (code === 'temu') formModel.siteCountry = 'US' +} + +function validateForm() { + if (!formModel.platformCode) { + $baseMessage(t('order.platformStore.message.platformRequired'), 'warning') + return false + } + if (!formModel.storeNickname.trim()) { + $baseMessage(t('order.platformStore.message.required'), 'warning') + return false + } + if (formModel.platformCode === 'temu' && !formModel.authorizationCode.trim()) { + $baseMessage(t('order.platformStore.message.authCodeRequired'), 'warning') + return false + } + return true } async function handleSave() { - if (!formModel.storeNickname.trim()) { - $baseMessage(t('order.platformStore.message.required'), 'warning') - return - } + if (!validateForm()) return saving.value = true try { await savePlatformStore({ ...formModel }) @@ -158,12 +221,46 @@ async function handleSave() { } } +async function handleStartAuth() { + if (!validateForm()) return + saving.value = true + try { + const result = await getPlatformStoreAuthUrl({ + platformCode: formModel.platformCode, + storeNickname: formModel.storeNickname, + siteCountry: formModel.siteCountry, + authorizationCode: formModel.authorizationCode || undefined, + }) + const authUrl = result?.authUrl || result?.AuthUrl + if (authUrl && authUrl !== '/') { + window.open(String(authUrl), '_blank') + } + $baseMessage(result?.message || t('order.platformStore.message.authStarted'), 'success') + closeModal() + await loadList() + } catch (e: any) { + $baseMessage(e?.message || t('order.platformStore.message.authFailed'), 'error') + } finally { + saving.value = false + } +} + +async function handlePrimaryAction() { + if (isEdit.value || isManualPlatform.value) { + await handleSave() + return + } + await handleStartAuth() +} + async function handleAuth(row: any) { if (!hasPermission('Order.PlatformStore.Auth')) return const raw = row._raw || row try { const result = await getPlatformStoreAuthUrl({ platformCode: pickField(raw, 'platformCode', 'PlatformCode') }) - $baseMessage(result?.message || result?.authUrl || t('order.platformStore.message.authPlaceholder'), 'info') + const authUrl = result?.authUrl || result?.AuthUrl + if (authUrl && authUrl !== '/') window.open(String(authUrl), '_blank') + $baseMessage(result?.message || t('order.platformStore.message.authPlaceholder'), 'info') } catch (e: any) { $baseMessage(e?.message || t('order.platformStore.message.authFailed'), 'error') } @@ -257,31 +354,120 @@ onMounted(loadList) - - - - - - - - - - - - - - - - - +
+
+
+ {{ t('order.platformStore.form.platform') }} + * +
+ +
+ {{ platformLabel(formModel.platformCode) }} +
+
+ +
+
+ + + +
+ {{ t('order.platformStore.form.selectPlatformHint') }} +
+
+
@@ -289,4 +475,75 @@ onMounted(loadList) diff --git a/src/views/warehouse/inboundNotice/index.vue b/src/views/warehouse/inboundNotice/index.vue index 316b6ee..9aa6e94 100644 --- a/src/views/warehouse/inboundNotice/index.vue +++ b/src/views/warehouse/inboundNotice/index.vue @@ -6,7 +6,6 @@ import HandsontablePro from '@/components/HandsontablePro/index.vue' import PaginationPro from '@/components/PaginationPro/index.vue' import ModalPro from '@/components/ModalPro/index.vue' import useModal from '@/components/ModalPro/useModal' -import FormPro from '@/components/FormPro/index.vue' import { useTableContainerHeight } from '@/composables/useTableContainerHeight' import { deleteWarehouseInbound, @@ -29,6 +28,23 @@ import { defineOptions({ name: 'WarehouseInboundNotice' }) +interface InboundLineForm { + skuId?: number + skuCode: string + plannedQty: number +} + +interface InboundBoxForm { + key: string + boxSeqNo: string + markBoxNo: string + weightG?: number + lengthCm?: number + widthCm?: number + heightCm?: number + lines: InboundLineForm[] +} + const { t } = useI18n() const { tableContainerRef, tableHeight } = useTableContainerHeight() @@ -44,6 +60,8 @@ const tableRows = ref([]) const currentPage = ref(1) const pageSize = ref(30) const totalCount = ref(0) +const selectedBoxKeys = ref([]) +const activeBoxKey = ref('') const statusTabs = [ { value: undefined, labelKey: 'warehouse.inbound.status.all', countKey: null }, @@ -55,7 +73,7 @@ const statusTabs = [ const detailVisible = ref(false) const detailData = ref(null) -const { visible: modalVisible, open: openModal, close: closeModal } = useModal() +const [registerFormModal, { openModal, closeModal }] = useModal() as any const saving = ref(false) const formModel = reactive({ id: 0, @@ -63,10 +81,24 @@ const formModel = reactive({ markReference: '', inboundType: 'normal', remark: '', - skuId: undefined as number | undefined, - plannedQty: 1, - markBoxNo: '', }) +const boxRows = ref([]) + +const modalWarehouseName = computed(() => { + const wh = warehouseOptions.value.find((w) => w.value === formModel.warehouseId) + return wh?.label || '' +}) + +const boxCount = computed(() => boxRows.value.length) +const totalSkuCount = computed(() => + boxRows.value.reduce((sum, box) => sum + box.lines.filter((l) => l.skuId).length, 0), +) +const totalPlannedQty = computed(() => + boxRows.value.reduce( + (sum, box) => sum + box.lines.reduce((s, line) => s + (Number(line.plannedQty) || 0), 0), + 0, + ), +) const hotColumns = computed(() => [ { key: 'noticeNo', label: t('warehouse.inbound.table.noticeNo'), width: 180 }, @@ -80,6 +112,71 @@ const hotColumns = computed(() => [ { key: 'createTime', label: t('warehouse.inbound.table.createTime'), width: 160 }, ]) +function createBox(seq: number): InboundBoxForm { + return { + key: `box-${Date.now()}-${seq}`, + boxSeqNo: String(seq), + markBoxNo: `BOX-${String(seq).padStart(3, '0')}`, + lines: [{ skuId: undefined, skuCode: '', plannedQty: 1 }], + } +} + +function resetBoxes() { + const first = createBox(1) + boxRows.value = [first] + selectedBoxKeys.value = [] + activeBoxKey.value = first.key +} + +function addBox() { + const next = boxRows.value.length + 1 + const box = createBox(next) + boxRows.value = [...boxRows.value, box] + activeBoxKey.value = box.key +} + +function addBoxes(count: number) { + const start = boxRows.value.length + const added = Array.from({ length: count }, (_, i) => createBox(start + i + 1)) + boxRows.value = [...boxRows.value, ...added] + if (added.length) activeBoxKey.value = added[added.length - 1].key +} + +function removeActiveBox() { + if (boxRows.value.length <= 1) return + const key = activeBoxKey.value + const remain = boxRows.value.filter((b) => b.key !== key) + boxRows.value = remain + selectedBoxKeys.value = selectedBoxKeys.value.filter((k) => k !== key) + activeBoxKey.value = remain[remain.length - 1]?.key || '' +} + +function removeSelectedBoxes() { + if (!selectedBoxKeys.value.length) { + removeActiveBox() + return + } + const remain = boxRows.value.filter((b) => !selectedBoxKeys.value.includes(b.key)) + boxRows.value = remain.length ? remain : [createBox(1)] + selectedBoxKeys.value = [] + activeBoxKey.value = boxRows.value[boxRows.value.length - 1].key +} + +function addLine(box: InboundBoxForm) { + box.lines.push({ skuId: undefined, skuCode: '', plannedQty: 1 }) +} + +function removeLine(box: InboundBoxForm, index: number) { + if (box.lines.length <= 1) return + box.lines.splice(index, 1) +} + +function onSkuChange(line: InboundLineForm, skuId?: number) { + const sku = skuOptions.value.find((s) => s.value === skuId) + line.skuId = skuId + line.skuCode = sku?.code || '' +} + function mapRow(raw: Record) { const row = normalizeListRow(raw) const status = String(pickField(row, 'receiptStatus', 'ReceiptStatus') ?? '') @@ -181,18 +278,39 @@ function handleAdd() { formModel.warehouseId = warehouseId.value formModel.markReference = '' formModel.remark = '' - formModel.skuId = undefined - formModel.plannedQty = 1 - formModel.markBoxNo = '' - openModal() + formModel.inboundType = 'normal' + resetBoxes() + openModal(true) } async function handleSave() { - if (!formModel.warehouseId || !formModel.skuId) { + if (!formModel.warehouseId) { $baseMessage(t('warehouse.inbound.message.required'), 'warning') return } - const sku = skuOptions.value.find((s) => s.value === formModel.skuId) + const boxes = boxRows.value + .map((box) => ({ + boxSeqNo: box.boxSeqNo, + markBoxNo: box.markBoxNo, + weightG: box.weightG, + lengthCm: box.lengthCm, + widthCm: box.widthCm, + heightCm: box.heightCm, + lines: box.lines + .filter((line) => line.skuId) + .map((line) => ({ + skuId: line.skuId!, + skuCode: line.skuCode, + plannedQty: Number(line.plannedQty) || 1, + })), + })) + .filter((box) => box.lines.length > 0) + + if (!boxes.length) { + $baseMessage(t('warehouse.inbound.message.boxRequired'), 'warning') + return + } + saving.value = true try { await saveWarehouseInbound({ @@ -201,13 +319,7 @@ async function handleSave() { markReference: formModel.markReference, inboundType: formModel.inboundType, remark: formModel.remark, - boxes: [ - { - boxSeqNo: '1', - markBoxNo: formModel.markBoxNo || 'BOX-001', - lines: [{ skuId: formModel.skuId, skuCode: sku?.code || '', plannedQty: formModel.plannedQty }], - }, - ], + boxes, }) $baseMessage(t('warehouse.inbound.message.saveSuccess'), 'success') closeModal() @@ -336,27 +448,136 @@ onMounted(async () => { - - - - - - - - - - - - - - - - - - + +
+ + + + + + + + + + {{ t('warehouse.inbound.type.normal') }} + {{ t('warehouse.inbound.type.return') }} + + + + + + + + + +
+
{{ t('warehouse.inbound.form.boxSkuDetail') }}
+
+ {{ t('warehouse.inbound.form.boxCount', { n: boxCount }) }} + {{ t('warehouse.inbound.form.skuCount', { n: totalSkuCount }) }} + {{ t('warehouse.inbound.form.totalQty', { n: totalPlannedQty }) }} +
+
+ +
+ {{ t('warehouse.inbound.actions.addBox') }} + {{ t('warehouse.inbound.actions.addBox5') }} + {{ t('warehouse.inbound.actions.removeBox') }} +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{ t('warehouse.inbound.actions.addSku') }} +
+ + + + +
+
+
+ + + {{ t('warehouse.common.warehouse') }}:{{ modalWarehouseName }} + +
+
@@ -364,6 +585,7 @@ onMounted(async () => { diff --git a/src/views/warehouse/parcel/index.vue b/src/views/warehouse/parcel/index.vue index acbfbb2..02aba56 100644 --- a/src/views/warehouse/parcel/index.vue +++ b/src/views/warehouse/parcel/index.vue @@ -1,12 +1,11 @@