From 0791ea968a5e5af9aa19005c20fec251087f4ca1 Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Wed, 28 Oct 2026 14:15:57 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=A1=B5=E7=AD=BE=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E5=90=8D=E5=AF=B9=E9=BD=90=E3=80=81=E7=8A=B6=E6=80=81=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E5=8E=BB=E8=89=B2=E6=9D=A1=EF=BC=8C=E5=B9=B6=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E8=AE=A2=E5=8D=95=E8=AF=A6=E6=83=85=E5=B5=8C=E5=A5=97?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E4=B8=8D=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- src/store/modules/tab-bar/index.ts | 16 +- src/utils/statusTag.ts | 5 +- src/views/home/workplace/index.vue | 3 +- .../components/orderDetailFieldConfig.ts | 140 ++++++++++++++++++ .../myOrders/components/OrderDetailDrawer.vue | 68 ++++----- src/views/order/myOrders/index.vue | 5 +- .../components/OrderDetailDrawer.vue | 66 ++++----- src/views/order/waybillManage/index.vue | 25 +++- src/views/system/aiAssistant/index.vue | 5 + src/views/system/mcpTools/index.vue | 5 + 10 files changed, 253 insertions(+), 85 deletions(-) diff --git a/src/store/modules/tab-bar/index.ts b/src/store/modules/tab-bar/index.ts index a50866c..6de97d8 100644 --- a/src/store/modules/tab-bar/index.ts +++ b/src/store/modules/tab-bar/index.ts @@ -106,13 +106,19 @@ const useTabBarStore = defineStore('tabBar', { } }, - /** LRU:访问时挪到末尾,并裁剪超量缓存 */ + /** + * LRU:访问时挪到末尾,并裁剪超量缓存。 + * 必须一次性重赋值 Set,禁止先 delete 再 add: + * keep-alive 会监听 include,名字从列表里消失会立刻 prune 掉缓存实例, + * 表现为「切换标签就整页刷新」。 + */ touchCache(name: string) { if (!isString(name) || name === '') return; - if (this.cacheTabList.has(name)) { - this.cacheTabList.delete(name); - } - this.cacheTabList.add(name); + const next = new Set( + [...this.cacheTabList].filter((item) => item !== name), + ); + next.add(name); + this.cacheTabList = next; this.pruneCache(name); }, diff --git a/src/utils/statusTag.ts b/src/utils/statusTag.ts index 61eb3a4..866ad12 100644 --- a/src/utils/statusTag.ts +++ b/src/utils/statusTag.ts @@ -76,7 +76,7 @@ function escapeAttr(text: string): string { .replace(/"/g, '"') } -/** Vue / a-tag 内联样式(软底 + 左侧色条,无硬框) */ +/** Vue / a-tag 内联样式(软底圆角,无硬框、无左侧色条) */ export function getStatusTagStyle( toneOrColors: StatusTagTone | StatusTagColors | null | undefined, variant: StatusTagVariant = 'solid', @@ -102,11 +102,10 @@ export function getStatusTagStyle( background: colors.tagBgColor, border: 'none', borderRadius: '6px', - boxShadow: `inset 3px 0 0 ${colors.tagColor}`, fontWeight: '500', fontSize: '12px', lineHeight: '22px', - padding: '0 10px 0 12px', + padding: '0 10px', display: 'inline-flex', alignItems: 'center', letterSpacing: '0.02em', diff --git a/src/views/home/workplace/index.vue b/src/views/home/workplace/index.vue index 826377c..98a7209 100644 --- a/src/views/home/workplace/index.vue +++ b/src/views/home/workplace/index.vue @@ -10,7 +10,8 @@ import AdminWorkplace from './components/AdminWorkplace.vue' import CustomerWorkplace from './components/CustomerWorkplace.vue' defineOptions({ - name: 'Home', + // 必须与路由 name `Workplace` 一致,否则 keep-alive include 对不上,切标签会整页重挂 + name: 'Workplace', }) const rbacUserStore = useRbacUserStore() diff --git a/src/views/order/components/orderDetailFieldConfig.ts b/src/views/order/components/orderDetailFieldConfig.ts index 0a220bd..d707df3 100644 --- a/src/views/order/components/orderDetailFieldConfig.ts +++ b/src/views/order/components/orderDetailFieldConfig.ts @@ -5,6 +5,50 @@ export interface OrderDetailFieldDef { labelKey: string; } +/** 订单详情基本信息中按「是/否」展示的布尔字段 */ +export const ORDER_DETAIL_BOOLEAN_FIELD_KEYS = [ + 'isCod', + 'isIntercept', + 'isRemote', + 'isPaid', +] as const; + +export type OrderDetailBooleanFieldKey = (typeof ORDER_DETAIL_BOOLEAN_FIELD_KEYS)[number]; + +export function isOrderDetailBooleanField(key: string): boolean { + return (ORDER_DETAIL_BOOLEAN_FIELD_KEYS as readonly string[]).includes(key); +} + +/** 兼容 true/false、0/1、是/否 等后端写法 */ +export function coerceOrderDetailBoolean(raw: unknown): boolean | null { + if (raw === true || raw === 1 || raw === '1') return true; + if (raw === false || raw === 0 || raw === '0') return false; + if (raw == null || raw === '') return null; + const s = String(raw).trim().toLowerCase(); + if (!s || s === '--') return null; + if (['true', 'yes', 'y', '是'].includes(s)) return true; + if (['false', 'no', 'n', '否'].includes(s)) return false; + return null; +} + +export function formatOrderDetailBooleanLabel( + raw: unknown, + t: (key: string, ...args: any[]) => string, +): string { + const flag = coerceOrderDetailBoolean(raw); + // 缺省按「否」,避免详情里出现裸文本/「--」与同组 tag 不一致 + return flag ? t('order.myOrders.drawer.yes') : t('order.myOrders.drawer.no'); +} + +export function isOrderDetailYesNoLabel( + text: string, + t: (key: string, ...args: any[]) => string, +): boolean { + return ( + text === t('order.myOrders.drawer.yes') || text === t('order.myOrders.drawer.no') + ); +} + export const ORDER_DETAIL_EXTRA_BASIC_FIELDS: OrderDetailFieldDef[] = [ { key: 'quantity', labelKey: 'order.myOrders.drawer.quantity' }, { key: 'exportCustomsMode', labelKey: 'order.myOrders.drawer.exportCustomsMode' }, @@ -34,8 +78,30 @@ export const ORDER_DETAIL_VALUE_FIELD_MAP: Record = { additionalServices: ['additionalServices', 'AdditionalServices'], downstreamSupplierName: ['downstreamSupplierName', 'DownstreamSupplierName'], downstreamSupplierCode: ['downstreamSupplierCode', 'DownstreamSupplierCode'], + isPaid: ['isPaid', 'IsPaid'], + isCod: ['isCod', 'IsCod'], + isIntercept: ['isIntercept', 'IsIntercept'], + isRemote: ['isRemote', 'IsRemote'], }; +/** 读取布尔字段原始值(保留 false/0,不因 falsy 被跳过) */ +export function pickOrderDetailBooleanRaw( + order: Record | null | undefined, + key: string, +): unknown { + if (!order) return undefined; + const keys = ORDER_DETAIL_VALUE_FIELD_MAP[key] || [key]; + for (const k of keys) { + if (!Object.prototype.hasOwnProperty.call(order, k) && order[k] === undefined) { + continue; + } + const v = order[k]; + if (v === undefined) continue; + return v; + } + return undefined; +} + export interface OrderDetailFieldGroup { key: string; labelKey: string; @@ -97,6 +163,80 @@ export function pickOrderDetailRawValue(order: Record | null | unde return undefined; } +/** 兼容 camelCase / PascalCase 嵌套字段(列表轻量映射会留下空 items/weightList) */ +export function pickOrderDetailNested( + order: Record | null | undefined, + ...keys: string[] +): T | undefined { + if (!order) return undefined; + for (const key of keys) { + if (order[key] !== undefined && order[key] !== null) { + return order[key] as T; + } + } + return undefined; +} + +/** 多个候选数组里取「有内容」的那个,避免列表空数组盖住详情 */ +export function pickOrderDetailArray( + order: Record | null | undefined, + ...keys: string[] +): T[] { + if (!order) return []; + let fallback: T[] = []; + for (const key of keys) { + const raw = order[key]; + if (!Array.isArray(raw)) continue; + if (raw.length > 0) return raw as T[]; + fallback = raw as T[]; + } + return fallback; +} + +/** + * 打开详情时合并列表行 + /order/detail: + * 列表故意 Items/WeightList/FeeList=[],不能让空数组盖住详情里的完整嵌套数据。 + */ +export function mergeOrderListRowWithDetail(listRow: any, detail: any) { + const base = listRow && typeof listRow === 'object' ? listRow : {}; + const d = detail && typeof detail === 'object' ? detail : {}; + const merged = { ...base, ...d }; + + const sender = pickOrderDetailNested(d, 'sender', 'Sender') + ?? pickOrderDetailNested(base, 'sender', 'Sender'); + const receiver = pickOrderDetailNested(d, 'receiver', 'Receiver') + ?? pickOrderDetailNested(base, 'receiver', 'Receiver'); + const items = pickOrderDetailArray(d, 'items', 'Items'); + const itemsResolved = items.length + ? items + : pickOrderDetailArray(base, 'items', 'Items'); + const weightList = pickOrderDetailArray(d, 'weightList', 'WeightList'); + const weightResolved = weightList.length + ? weightList + : pickOrderDetailArray(base, 'weightList', 'WeightList'); + const feeList = pickOrderDetailArray( + d, + 'feeList', + 'FeeList', + 'orderFees', + 'fees', + ); + const feeResolved = feeList.length + ? feeList + : pickOrderDetailArray(base, 'feeList', 'FeeList', 'orderFees', 'fees'); + + if (sender && typeof sender === 'object') { + merged.sender = sender; + } + if (receiver && typeof receiver === 'object') { + merged.receiver = receiver; + } + merged.items = itemsResolved; + merged.weightList = weightResolved; + merged.feeList = feeResolved; + return merged; +} + export interface DictRowLike { dictCode?: string | null; dictName?: string | null; diff --git a/src/views/order/myOrders/components/OrderDetailDrawer.vue b/src/views/order/myOrders/components/OrderDetailDrawer.vue index 696212a..4c2106d 100644 --- a/src/views/order/myOrders/components/OrderDetailDrawer.vue +++ b/src/views/order/myOrders/components/OrderDetailDrawer.vue @@ -216,10 +216,10 @@ {{ getOrderValue(fieldKey) }} -