From 8e6f0d45ea525a6b83818f701e4a0ec90bb9ebb1 Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Wed, 15 Jul 2026 12:46:06 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=8C=85=E8=A3=B9=E5=A4=9A?= =?UTF-8?q?=E5=95=86=E5=93=81=E5=88=97=E8=A1=A8=E4=B8=8E=E6=98=8E=E7=BB=86?= =?UTF-8?q?=E4=BE=A7=E7=AA=97=EF=BC=8C=E4=BF=AE=E5=A4=8D=E7=8A=B6=E6=80=81?= =?UTF-8?q?Tab=E7=AD=9B=E9=80=89=EF=BC=8C=E5=B9=B6=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E5=88=B0=E4=BB=93=E9=A1=B5=E4=B8=8E=E6=8F=92=E4=BB=B6=E7=A1=AE?= =?UTF-8?q?=E8=AE=A4=E9=A1=B5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .eslintrc-auto-import.json | 2 + nginx-default.conf | 10 +- nginx-docker.conf | 4 +- src/api/plugin/index.ts | 24 + src/api/warehouse/index.ts | 20 + src/auto-imports.d.ts | 4 + src/components/ContactService/index.vue | 29 +- src/components/HandsontablePro/index.vue | 48 +- src/components/top-nav/index.vue | 1 + src/constants/pageButtonCatalog.ts | 9 + src/layout/default-layout.vue | 7 +- src/locale/en-US.ts | 35 +- src/locale/zh-CN.ts | 35 +- src/router/index.ts | 9 + src/router/routes/newModules/operation.ts | 17 + src/store/index.ts | 10 +- src/store/modules/current-warehouse.ts | 92 ++++ src/utils/menu-icon.ts | 6 + src/utils/rapidPackagePluginHost.ts | 110 ++++ src/utils/warehouseListMapper.ts | 63 ++- src/views/order/platformStoreOrder/index.vue | 25 +- src/views/plugin/AddPackage.vue | 494 ++++++++++++++++++ src/views/warehouse/inboundNotice/index.vue | 54 +- src/views/warehouse/parcel/index.vue | 280 ++++++++++- src/views/warehouse/parcelArrival/index.vue | 497 +++++++++++++++++++ src/views/warehouse/sku/index.vue | 124 ++++- vite.config.ts | 48 +- 27 files changed, 1973 insertions(+), 84 deletions(-) create mode 100644 src/api/plugin/index.ts create mode 100644 src/store/modules/current-warehouse.ts create mode 100644 src/utils/rapidPackagePluginHost.ts create mode 100644 src/views/plugin/AddPackage.vue create mode 100644 src/views/warehouse/parcelArrival/index.vue diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json index 6a93a31..6ad6d69 100644 --- a/.eslintrc-auto-import.json +++ b/.eslintrc-auto-import.json @@ -163,6 +163,7 @@ "useCssVar": true, "useCssVars": true, "useCurrentElement": true, + "useCurrentWarehouseStore": true, "useCycleList": true, "useDark": true, "useDateFormat": true, @@ -294,6 +295,7 @@ "useVibrate": true, "useVirtualList": true, "useWakeLock": true, + "useWarehouseAdminCustomer": true, "useWarehouseList": true, "useWebNotification": true, "useWebSocket": true, diff --git a/nginx-default.conf b/nginx-default.conf index b6de830..ca21b2d 100644 --- a/nginx-default.conf +++ b/nginx-default.conf @@ -102,6 +102,9 @@ server { location ^~ /operation/printLabel { try_files /index.html =200; } + location ^~ /operation/parcelArrival { + try_files /index.html =200; + } # ============ 订单前端路由(^~ 须比下方 /order/ API 正则代理更优先)============ location ^~ /order/priceCheck { @@ -144,6 +147,11 @@ server { try_files /index.html =200; } + # 采集插件确认页(/Plugin/AddPackage);API 走 /plugin/browser/* + location ^~ /Plugin/ { + try_files /index.html =200; + } + # ============ 系统管理前端路由(^~ 须比下方 /system/ API 正则代理更优先)============ # 页面路径为 camelCase;API 为 kebab(如 /system/dict/list、/system/batch-email/…),勿用宽泛前缀匹配 dict location = /system/dict { @@ -222,7 +230,7 @@ server { } # ============ Admin API 同源反代(/rbac /order /xhs … → api.nanafa.com/admin)============ - location ~ ^/(rbac|order|monitor|customer|recharge|channel|system|warehouse)/ { + location ~ ^/(rbac|order|monitor|customer|recharge|channel|system|warehouse|plugin)/ { rewrite ^/(.*)$ /admin/$1 break; proxy_pass http://api.nanafa.com; proxy_http_version 1.1; diff --git a/nginx-docker.conf b/nginx-docker.conf index f55b230..b26d721 100644 --- a/nginx-docker.conf +++ b/nginx-docker.conf @@ -39,6 +39,7 @@ server { location ^~ /operation/inboundScan { try_files /index.html =200; } location ^~ /operation/outboundScan { try_files /index.html =200; } location ^~ /operation/printLabel { try_files /index.html =200; } + location ^~ /operation/parcelArrival { try_files /index.html =200; } # 订单前端路由(^~ 须比下方 /order/ API 正则代理更优先) location ^~ /order/priceCheck { try_files /index.html =200; } @@ -54,6 +55,7 @@ server { location = /warehouse/sku { try_files /index.html =200; } location = /warehouse/binLocation { try_files /index.html =200; } location ^~ /warehouse/inboundNotice { try_files /index.html =200; } + location ^~ /Plugin/ { try_files /index.html =200; } location ^~ /channel/salesProducts { try_files /index.html =200; } location ^~ /channel/serviceChannels { try_files /index.html =200; } @@ -109,7 +111,7 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } - location ~ ^/(rbac|order|monitor|customer|recharge|channel|system|warehouse)/ { + location ~ ^/(rbac|order|monitor|customer|recharge|channel|system|warehouse|plugin)/ { rewrite ^/(.*)$ /admin/$1 break; proxy_pass http://api.nanafa.com; proxy_http_version 1.1; diff --git a/src/api/plugin/index.ts b/src/api/plugin/index.ts new file mode 100644 index 0000000..6c8c7eb --- /dev/null +++ b/src/api/plugin/index.ts @@ -0,0 +1,24 @@ +import { axiosInstance } from '@/api/rbac' +import { assertBusinessOk, omsUrl } from '@/api/oms/common' + +function unwrapData(body: any): T { + assertBusinessOk(body) + // 兼容 {code,data} 包裹,以及直接返回 JSON 字符串/对象 + if (body != null && typeof body === 'object' && ('data' in body || 'Data' in body)) { + const nested = body.data ?? body.Data + if (nested !== undefined) return nested as T + } + return body as T +} + +/** 读取采集草稿(key 来自插件 add) */ +export async function getPluginPackage(key: string) { + const res = await axiosInstance.get(omsUrl('/plugin/browser/getPackage'), { params: { key } }) + return unwrapData(res.data) +} + +/** 插件确认页默认仓库 */ +export async function getPluginWhseInfo() { + const res = await axiosInstance.get(omsUrl('/plugin/browser/getPluginWhseInfo')) + return unwrapData<{ id: number; name: string } | null>(res.data) +} diff --git a/src/api/warehouse/index.ts b/src/api/warehouse/index.ts index 5ae1206..656c8ee 100644 --- a/src/api/warehouse/index.ts +++ b/src/api/warehouse/index.ts @@ -25,6 +25,18 @@ export const deleteWarehouseSku = (id: number) => postOk('/warehouse/sku/delete' export const bindWarehouseSkuMapping = (body: Record) => postOk('/warehouse/sku/bind-mapping', body) +/** 上传 SKU 图片,返回相对路径 url */ +export async function uploadWarehouseSkuImage(file: File): Promise { + const form = new FormData() + form.append('file', file) + const res = await axiosInstance.post(omsUrl('/warehouse/sku/upload-image'), form) + assertBusinessOk(res.data) + const data = unwrapData(res.data) + const url = data?.url ?? data?.fileUrl ?? data?.Url ?? data?.FileUrl + if (!url) throw new Error('上传成功但未返回图片地址') + return String(url) +} + // Bin location export const getWarehouseBinLocationList = (body: Record) => postList('/warehouse/bin-location/list', body).then((r) => ({ items: r.items, total: r.items.length })) @@ -48,6 +60,14 @@ export const saveWarehouseParcel = (body: Record) => postOk('/w export const deleteWarehouseParcel = (id: number) => postOk('/warehouse/parcel/delete', { id }) export const putawayWarehouseParcel = (body: Record) => postOk('/warehouse/parcel/putaway', body) +// Parcel arrival(快递到仓) +export const getWarehouseParcelArrivalList = (body: Record) => + postList('/warehouse/parcel-arrival/list', body) +export const createWarehouseParcelArrival = (body: Record) => + postOk('/warehouse/parcel-arrival/create', body) +export const deleteWarehouseParcelArrival = (id: number) => + postOk('/warehouse/parcel-arrival/delete', { id }) + // Inventory export const getWarehouseInventoryList = (body: Record) => postList('/warehouse/inventory/list', body) diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts index 6ea6eb7..1468bec 100644 --- a/src/auto-imports.d.ts +++ b/src/auto-imports.d.ts @@ -151,6 +151,7 @@ declare global { const useCssVar: typeof import('@vueuse/core')['useCssVar'] const useCssVars: typeof import('vue')['useCssVars'] const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement'] + const useCurrentWarehouseStore: typeof import('./store/index')['useCurrentWarehouseStore'] const useCycleList: typeof import('@vueuse/core')['useCycleList'] const useDark: typeof import('@vueuse/core')['useDark'] const useDateFormat: typeof import('@vueuse/core')['useDateFormat'] @@ -282,6 +283,7 @@ declare global { const useVibrate: typeof import('@vueuse/core')['useVibrate'] const useVirtualList: typeof import('@vueuse/core')['useVirtualList'] const useWakeLock: typeof import('@vueuse/core')['useWakeLock'] + const useWarehouseAdminCustomer: typeof import('./composables/useWarehouseAdminCustomer')['useWarehouseAdminCustomer'] const useWarehouseList: typeof import('./composables/useWarehouseList')['useWarehouseList'] const useWebNotification: typeof import('@vueuse/core')['useWebNotification'] const useWebSocket: typeof import('@vueuse/core')['useWebSocket'] @@ -469,6 +471,7 @@ declare module 'vue' { readonly useCssVar: UnwrapRef readonly useCssVars: UnwrapRef readonly useCurrentElement: UnwrapRef + readonly useCurrentWarehouseStore: UnwrapRef readonly useCycleList: UnwrapRef readonly useDark: UnwrapRef readonly useDateFormat: UnwrapRef @@ -600,6 +603,7 @@ declare module 'vue' { readonly useVibrate: UnwrapRef readonly useVirtualList: UnwrapRef readonly useWakeLock: UnwrapRef + readonly useWarehouseAdminCustomer: UnwrapRef readonly useWarehouseList: UnwrapRef readonly useWebNotification: UnwrapRef readonly useWebSocket: UnwrapRef diff --git a/src/components/ContactService/index.vue b/src/components/ContactService/index.vue index 674c0f0..bf58240 100644 --- a/src/components/ContactService/index.vue +++ b/src/components/ContactService/index.vue @@ -1,9 +1,10 @@ + + + + diff --git a/src/views/warehouse/inboundNotice/index.vue b/src/views/warehouse/inboundNotice/index.vue index 4e233d1..138b907 100644 --- a/src/views/warehouse/inboundNotice/index.vue +++ b/src/views/warehouse/inboundNotice/index.vue @@ -29,6 +29,7 @@ import { } from '@/utils/warehouseListMapper' import { renderWarehouseActionBtn, wrapWarehouseActions } from '@/utils/warehouseActionRenderer' import { useWarehouseAdminCustomer } from '@/composables/useWarehouseAdminCustomer' +import { useCurrentWarehouseStore } from '@/store' defineOptions({ name: 'WarehouseInboundNotice' }) @@ -74,8 +75,11 @@ const { ensureMutationCustomerId, resolveSelectedCustomerId, } = useWarehouseAdminCustomer() +const currentWarehouseStore = useCurrentWarehouseStore() -const receiptStatus = ref() +/** Tab active-key;全部用 'all',请求时再转成不传 receiptStatus */ +const STATUS_TAB_ALL = 'all' +const receiptStatus = ref(STATUS_TAB_ALL) const summary = ref({ forecasted: 0, received: 0, completed: 0 }) const keyword = ref('') const warehouseId = ref() @@ -91,7 +95,7 @@ const selectedBoxKeys = ref([]) const activeBoxKey = ref('') const statusTabs = [ - { value: undefined, labelKey: 'warehouse.inbound.status.all', countKey: null }, + { value: STATUS_TAB_ALL, labelKey: 'warehouse.inbound.status.all', countKey: null }, { value: 'forecasted', labelKey: 'warehouse.inbound.status.forecasted', countKey: 'forecasted' as const }, { value: 'received', labelKey: 'warehouse.inbound.status.received', countKey: 'received' as const }, { value: 'completed', labelKey: 'warehouse.inbound.status.completed', countKey: 'completed' as const }, @@ -267,13 +271,19 @@ async function loadSummary() { } } +function resolveReceiptStatusFilter(): string | undefined { + const status = receiptStatus.value + if (!status || status === STATUS_TAB_ALL) return undefined + return status +} + async function loadList() { listLoading.value = true try { const { items, total } = await getWarehouseInboundList(withCustomerId({ page: currentPage.value, pageSize: pageSize.value, - receiptStatus: receiptStatus.value, + receiptStatus: resolveReceiptStatusFilter(), keyword: keyword.value || undefined, warehouseId: warehouseId.value, })) @@ -286,6 +296,12 @@ async function loadList() { } } +function handleStatusTabChange(key: string | number) { + receiptStatus.value = String(key) + currentPage.value = 1 + loadList() +} + function handlePageChange(page: number) { currentPage.value = page loadList() @@ -428,11 +444,15 @@ function handleAdd() { if (!hasPermission('Warehouse.InboundNotice.Save')) return if (isAdmin.value && ensureMutationCustomerId() == null) return formModel.id = 0 - formModel.warehouseId = warehouseId.value + formModel.warehouseId = currentWarehouseStore.resolveDefaultWarehouseId(warehouseId.value) formModel.markReference = '' formModel.remark = '' formModel.inboundType = 'normal' resetBoxes() + if (!formModel.warehouseId) { + $baseMessage(t('warehouse.common.noCurrentWarehouse'), 'warning') + return + } openModal(true) } @@ -536,9 +556,10 @@ onMounted(async () => { - +