fix: 页签缓存名对齐、状态标签去色条,并修复订单详情嵌套字段不显示
All checks were successful
Build and Deploy Front / deploy (push) Successful in 7m38s
All checks were successful
Build and Deploy Front / deploy (push) Successful in 7m38s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
9fe829c6a9
commit
0791ea968a
@ -106,13 +106,19 @@ const useTabBarStore = defineStore('tabBar', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** LRU:访问时挪到末尾,并裁剪超量缓存 */
|
/**
|
||||||
|
* LRU:访问时挪到末尾,并裁剪超量缓存。
|
||||||
|
* 必须一次性重赋值 Set,禁止先 delete 再 add:
|
||||||
|
* keep-alive 会监听 include,名字从列表里消失会立刻 prune 掉缓存实例,
|
||||||
|
* 表现为「切换标签就整页刷新」。
|
||||||
|
*/
|
||||||
touchCache(name: string) {
|
touchCache(name: string) {
|
||||||
if (!isString(name) || name === '') return;
|
if (!isString(name) || name === '') return;
|
||||||
if (this.cacheTabList.has(name)) {
|
const next = new Set(
|
||||||
this.cacheTabList.delete(name);
|
[...this.cacheTabList].filter((item) => item !== name),
|
||||||
}
|
);
|
||||||
this.cacheTabList.add(name);
|
next.add(name);
|
||||||
|
this.cacheTabList = next;
|
||||||
this.pruneCache(name);
|
this.pruneCache(name);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -76,7 +76,7 @@ function escapeAttr(text: string): string {
|
|||||||
.replace(/"/g, '"')
|
.replace(/"/g, '"')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Vue / a-tag 内联样式(软底 + 左侧色条,无硬框) */
|
/** Vue / a-tag 内联样式(软底圆角,无硬框、无左侧色条) */
|
||||||
export function getStatusTagStyle(
|
export function getStatusTagStyle(
|
||||||
toneOrColors: StatusTagTone | StatusTagColors | null | undefined,
|
toneOrColors: StatusTagTone | StatusTagColors | null | undefined,
|
||||||
variant: StatusTagVariant = 'solid',
|
variant: StatusTagVariant = 'solid',
|
||||||
@ -102,11 +102,10 @@ export function getStatusTagStyle(
|
|||||||
background: colors.tagBgColor,
|
background: colors.tagBgColor,
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
boxShadow: `inset 3px 0 0 ${colors.tagColor}`,
|
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
lineHeight: '22px',
|
lineHeight: '22px',
|
||||||
padding: '0 10px 0 12px',
|
padding: '0 10px',
|
||||||
display: 'inline-flex',
|
display: 'inline-flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
letterSpacing: '0.02em',
|
letterSpacing: '0.02em',
|
||||||
|
|||||||
@ -10,7 +10,8 @@ import AdminWorkplace from './components/AdminWorkplace.vue'
|
|||||||
import CustomerWorkplace from './components/CustomerWorkplace.vue'
|
import CustomerWorkplace from './components/CustomerWorkplace.vue'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'Home',
|
// 必须与路由 name `Workplace` 一致,否则 keep-alive include 对不上,切标签会整页重挂
|
||||||
|
name: 'Workplace',
|
||||||
})
|
})
|
||||||
|
|
||||||
const rbacUserStore = useRbacUserStore()
|
const rbacUserStore = useRbacUserStore()
|
||||||
|
|||||||
@ -5,6 +5,50 @@ export interface OrderDetailFieldDef {
|
|||||||
labelKey: string;
|
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[] = [
|
export const ORDER_DETAIL_EXTRA_BASIC_FIELDS: OrderDetailFieldDef[] = [
|
||||||
{ key: 'quantity', labelKey: 'order.myOrders.drawer.quantity' },
|
{ key: 'quantity', labelKey: 'order.myOrders.drawer.quantity' },
|
||||||
{ key: 'exportCustomsMode', labelKey: 'order.myOrders.drawer.exportCustomsMode' },
|
{ key: 'exportCustomsMode', labelKey: 'order.myOrders.drawer.exportCustomsMode' },
|
||||||
@ -34,8 +78,30 @@ export const ORDER_DETAIL_VALUE_FIELD_MAP: Record<string, string[]> = {
|
|||||||
additionalServices: ['additionalServices', 'AdditionalServices'],
|
additionalServices: ['additionalServices', 'AdditionalServices'],
|
||||||
downstreamSupplierName: ['downstreamSupplierName', 'DownstreamSupplierName'],
|
downstreamSupplierName: ['downstreamSupplierName', 'DownstreamSupplierName'],
|
||||||
downstreamSupplierCode: ['downstreamSupplierCode', 'DownstreamSupplierCode'],
|
downstreamSupplierCode: ['downstreamSupplierCode', 'DownstreamSupplierCode'],
|
||||||
|
isPaid: ['isPaid', 'IsPaid'],
|
||||||
|
isCod: ['isCod', 'IsCod'],
|
||||||
|
isIntercept: ['isIntercept', 'IsIntercept'],
|
||||||
|
isRemote: ['isRemote', 'IsRemote'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 读取布尔字段原始值(保留 false/0,不因 falsy 被跳过) */
|
||||||
|
export function pickOrderDetailBooleanRaw(
|
||||||
|
order: Record<string, any> | 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 {
|
export interface OrderDetailFieldGroup {
|
||||||
key: string;
|
key: string;
|
||||||
labelKey: string;
|
labelKey: string;
|
||||||
@ -97,6 +163,80 @@ export function pickOrderDetailRawValue(order: Record<string, any> | null | unde
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 兼容 camelCase / PascalCase 嵌套字段(列表轻量映射会留下空 items/weightList) */
|
||||||
|
export function pickOrderDetailNested<T = unknown>(
|
||||||
|
order: Record<string, any> | 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<T = any>(
|
||||||
|
order: Record<string, any> | 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 {
|
export interface DictRowLike {
|
||||||
dictCode?: string | null;
|
dictCode?: string | null;
|
||||||
dictName?: string | null;
|
dictName?: string | null;
|
||||||
|
|||||||
@ -216,10 +216,10 @@
|
|||||||
{{ getOrderValue(fieldKey) }}
|
{{ getOrderValue(fieldKey) }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="isOrderBooleanField(fieldKey)">
|
<template v-else-if="shouldRenderOrderBooleanTag(fieldKey)">
|
||||||
<a-tag
|
<a-tag
|
||||||
size="small"
|
size="small"
|
||||||
:style="getOrderBooleanTagStyle(fieldKey)"
|
:style="getBooleanTagStyle(getOrderValue(fieldKey) === $t('order.myOrders.drawer.yes'))"
|
||||||
>
|
>
|
||||||
{{ getOrderValue(fieldKey) }}
|
{{ getOrderValue(fieldKey) }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
@ -761,10 +761,16 @@ import {
|
|||||||
ORDER_DETAIL_VALUE_FIELD_MAP,
|
ORDER_DETAIL_VALUE_FIELD_MAP,
|
||||||
buildDeclarationPayloadFromRows,
|
buildDeclarationPayloadFromRows,
|
||||||
cloneDeclarationItemRows,
|
cloneDeclarationItemRows,
|
||||||
|
formatOrderDetailBooleanLabel,
|
||||||
formatOrderDetailExtendedValue,
|
formatOrderDetailExtendedValue,
|
||||||
|
isOrderDetailBooleanField,
|
||||||
|
isOrderDetailYesNoLabel,
|
||||||
mergeOrderBasicFields,
|
mergeOrderBasicFields,
|
||||||
parseAdditionalServiceCodes,
|
parseAdditionalServiceCodes,
|
||||||
patchOrderBasicFieldGroups,
|
patchOrderBasicFieldGroups,
|
||||||
|
pickOrderDetailArray,
|
||||||
|
pickOrderDetailBooleanRaw,
|
||||||
|
pickOrderDetailNested,
|
||||||
} from '../../components/orderDetailFieldConfig';
|
} from '../../components/orderDetailFieldConfig';
|
||||||
import {
|
import {
|
||||||
filterMyOrdersColumns,
|
filterMyOrdersColumns,
|
||||||
@ -1120,14 +1126,9 @@ function getOrderFieldLabelKey(fieldKey: string) {
|
|||||||
return orderBasicFields.find((field) => field.key === fieldKey)?.labelKey ?? fieldKey;
|
return orderBasicFields.find((field) => field.key === fieldKey)?.labelKey ?? fieldKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOrderBooleanField(fieldKey: string) {
|
function shouldRenderOrderBooleanTag(fieldKey: string) {
|
||||||
return ['isCod', 'isIntercept', 'isRemote', 'isPaid'].includes(fieldKey);
|
if (isOrderDetailBooleanField(fieldKey)) return true;
|
||||||
}
|
return isOrderDetailYesNoLabel(getOrderValue(fieldKey), t);
|
||||||
|
|
||||||
function getOrderBooleanTagStyle(fieldKey: string) {
|
|
||||||
const val = getOrderValue(fieldKey);
|
|
||||||
const yes = t('order.myOrders.drawer.yes');
|
|
||||||
return getBooleanTagStyle(val === yes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const declarationSkuPreviewVisible = ref(false);
|
const declarationSkuPreviewVisible = ref(false);
|
||||||
@ -1586,18 +1587,15 @@ function getOrderValue(key: string) {
|
|||||||
shipTime: ['shipTime'],
|
shipTime: ['shipTime'],
|
||||||
payTime: ['payTime'],
|
payTime: ['payTime'],
|
||||||
signTime: ['signTime'],
|
signTime: ['signTime'],
|
||||||
isPaid: ['isPaid', 'IsPaid'],
|
|
||||||
isCod: ['isCod'],
|
|
||||||
codCurrency: ['codCurrency'],
|
|
||||||
codSum: ['codSum'],
|
|
||||||
isIntercept: ['isIntercept'],
|
|
||||||
isRemote: ['isRemote'],
|
|
||||||
createTime: ['createTime'],
|
createTime: ['createTime'],
|
||||||
createBy: ['createBy'],
|
createBy: ['createBy'],
|
||||||
updateTime: ['updateTime'],
|
updateTime: ['updateTime'],
|
||||||
updateBy: ['updateBy'],
|
updateBy: ['updateBy'],
|
||||||
...ORDER_DETAIL_VALUE_FIELD_MAP,
|
...ORDER_DETAIL_VALUE_FIELD_MAP,
|
||||||
};
|
};
|
||||||
|
if (isOrderDetailBooleanField(key)) {
|
||||||
|
return formatOrderDetailBooleanLabel(pickOrderDetailBooleanRaw(o, key), t);
|
||||||
|
}
|
||||||
const keys = fieldMap[key] || [key];
|
const keys = fieldMap[key] || [key];
|
||||||
let val: any;
|
let val: any;
|
||||||
for (const k of keys) {
|
for (const k of keys) {
|
||||||
@ -1607,9 +1605,6 @@ function getOrderValue(key: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (val == null || val === '') return '--';
|
if (val == null || val === '') return '--';
|
||||||
if (key === 'isCod' || key === 'isIntercept' || key === 'isRemote' || key === 'isPaid') {
|
|
||||||
return val ? t('order.myOrders.drawer.yes') : t('order.myOrders.drawer.no');
|
|
||||||
}
|
|
||||||
if (key === 'orderStatus') {
|
if (key === 'orderStatus') {
|
||||||
const code = resolveOrderStatusCode(o);
|
const code = resolveOrderStatusCode(o);
|
||||||
const meta = getOrderStatusMeta(code);
|
const meta = getOrderStatusMeta(code);
|
||||||
@ -1634,7 +1629,7 @@ function getSenderValue(key: string) {
|
|||||||
}
|
}
|
||||||
const val = sender.value[key];
|
const val = sender.value[key];
|
||||||
if (key === 'isDefault') {
|
if (key === 'isDefault') {
|
||||||
return val == null ? '--' : (val ? t('order.myOrders.drawer.yes') : t('order.myOrders.drawer.no'));
|
return formatOrderDetailBooleanLabel(val, t);
|
||||||
}
|
}
|
||||||
return val ?? '--';
|
return val ?? '--';
|
||||||
}
|
}
|
||||||
@ -1967,21 +1962,27 @@ async function loadTrackTimeline() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载详情数据(优先使用 list 接口返回的 sender/receiver/items/weightList,与 parcel/create 对应)
|
// 加载详情数据(兼容 camel/Pascal;列表空 items/weightList 不能盖住详情)
|
||||||
function loadDetailData() {
|
function loadDetailData() {
|
||||||
const o = detail.value;
|
const o = detail.value;
|
||||||
if (!o) return;
|
if (!o) return;
|
||||||
|
|
||||||
// 发件人
|
const senderRaw = pickOrderDetailNested<Record<string, any>>(o, 'sender', 'Sender');
|
||||||
if (o.sender && typeof o.sender === 'object') {
|
if (senderRaw && typeof senderRaw === 'object') {
|
||||||
sender.value = { ...o.sender, country: o.sender.country ?? o.sender.countryCode };
|
sender.value = {
|
||||||
|
...senderRaw,
|
||||||
|
country: senderRaw.country ?? senderRaw.countryCode ?? senderRaw.CountryCode,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
sender.value = {};
|
sender.value = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收件人:优先使用 list 返回的 receiver
|
const receiverRaw = pickOrderDetailNested<Record<string, any>>(o, 'receiver', 'Receiver');
|
||||||
if (o.receiver && typeof o.receiver === 'object') {
|
if (receiverRaw && typeof receiverRaw === 'object') {
|
||||||
recipient.value = { ...o.receiver, country: o.receiver.country ?? o.receiver.countryCode };
|
recipient.value = {
|
||||||
|
...receiverRaw,
|
||||||
|
country: receiverRaw.country ?? receiverRaw.countryCode ?? receiverRaw.CountryCode,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
recipient.value = {
|
recipient.value = {
|
||||||
recipientsName: o.consignee || '',
|
recipientsName: o.consignee || '',
|
||||||
@ -2006,17 +2007,12 @@ function loadDetailData() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 申报信息
|
const itemsRaw = pickOrderDetailArray(o, 'items', 'Items');
|
||||||
declarationList.value = Array.isArray(o.items) && o.items.length > 0
|
declarationList.value = itemsRaw.length > 0 ? cloneDeclarationItemRows(itemsRaw) : [];
|
||||||
? cloneDeclarationItemRows(o.items)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
// 件重尺
|
weightList.value = pickOrderDetailArray(o, 'weightList', 'WeightList');
|
||||||
weightList.value = Array.isArray(o.weightList) && o.weightList.length > 0 ? o.weightList : [];
|
|
||||||
|
|
||||||
// 费用:接口字段可为 feeList / orderFees / FeeList
|
feeList.value = pickOrderDetailArray(o, 'feeList', 'FeeList', 'orderFees', 'fees');
|
||||||
const fees = o.feeList ?? o.FeeList ?? o.orderFees ?? o.fees;
|
|
||||||
feeList.value = Array.isArray(fees) ? fees : [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyDetailFromApi(raw: any) {
|
function applyDetailFromApi(raw: any) {
|
||||||
|
|||||||
@ -62,6 +62,7 @@
|
|||||||
type ColumnScheme,
|
type ColumnScheme,
|
||||||
} from './columnConfig';
|
} from './columnConfig';
|
||||||
import { filterMyOrdersColumns, filterMyOrdersFieldKeys } from './customerFieldMask';
|
import { filterMyOrdersColumns, filterMyOrdersFieldKeys } from './customerFieldMask';
|
||||||
|
import { mergeOrderListRowWithDetail } from '../components/orderDetailFieldConfig';
|
||||||
import { useHiprintClientState } from '@/utils/hiprint/hiprintClientState';
|
import { useHiprintClientState } from '@/utils/hiprint/hiprintClientState';
|
||||||
import { fetchPrintUrlsForOrders, getCachedPrintUrl } from '@/utils/printSessionCache';
|
import { fetchPrintUrlsForOrders, getCachedPrintUrl } from '@/utils/printSessionCache';
|
||||||
|
|
||||||
@ -274,7 +275,7 @@
|
|||||||
if (id != null && id !== '') {
|
if (id != null && id !== '') {
|
||||||
try {
|
try {
|
||||||
const detail = await getOrderDetail(id);
|
const detail = await getOrderDetail(id);
|
||||||
nextRow = normalizeOrderRow({ ...row, ...detail });
|
nextRow = normalizeOrderRow(mergeOrderListRowWithDetail(row, detail));
|
||||||
} catch {
|
} catch {
|
||||||
/* 保留列表行数*/
|
/* 保留列表行数*/
|
||||||
}
|
}
|
||||||
@ -1528,7 +1529,7 @@
|
|||||||
if (rows.length === 1 && id != null) {
|
if (rows.length === 1 && id != null) {
|
||||||
try {
|
try {
|
||||||
const detail = await getOrderDetail(id);
|
const detail = await getOrderDetail(id);
|
||||||
ctxRow = normalizeOrderRow({ ...ctxRow, ...detail });
|
ctxRow = normalizeOrderRow(mergeOrderListRowWithDetail(ctxRow, detail));
|
||||||
} catch {
|
} catch {
|
||||||
/* keep list row */
|
/* keep list row */
|
||||||
}
|
}
|
||||||
|
|||||||
@ -207,10 +207,10 @@
|
|||||||
{{ getOrderValue(fieldKey) }}
|
{{ getOrderValue(fieldKey) }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="isOrderBooleanField(fieldKey)">
|
<template v-else-if="shouldRenderOrderBooleanTag(fieldKey)">
|
||||||
<a-tag
|
<a-tag
|
||||||
size="small"
|
size="small"
|
||||||
:style="getOrderBooleanTagStyle(fieldKey)"
|
:style="getBooleanTagStyle(getOrderValue(fieldKey) === $t('order.myOrders.drawer.yes'))"
|
||||||
>
|
>
|
||||||
{{ getOrderValue(fieldKey) }}
|
{{ getOrderValue(fieldKey) }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
@ -1037,10 +1037,16 @@ import {
|
|||||||
ORDER_DETAIL_VALUE_FIELD_MAP,
|
ORDER_DETAIL_VALUE_FIELD_MAP,
|
||||||
buildDeclarationPayloadFromRows,
|
buildDeclarationPayloadFromRows,
|
||||||
cloneDeclarationItemRows,
|
cloneDeclarationItemRows,
|
||||||
|
formatOrderDetailBooleanLabel,
|
||||||
formatOrderDetailExtendedValue,
|
formatOrderDetailExtendedValue,
|
||||||
|
isOrderDetailBooleanField,
|
||||||
|
isOrderDetailYesNoLabel,
|
||||||
mergeOrderBasicFields,
|
mergeOrderBasicFields,
|
||||||
parseAdditionalServiceCodes,
|
parseAdditionalServiceCodes,
|
||||||
patchOrderBasicFieldGroups,
|
patchOrderBasicFieldGroups,
|
||||||
|
pickOrderDetailArray,
|
||||||
|
pickOrderDetailBooleanRaw,
|
||||||
|
pickOrderDetailNested,
|
||||||
} from '../../components/orderDetailFieldConfig';
|
} from '../../components/orderDetailFieldConfig';
|
||||||
import { openAskAdvisor } from '@/composables/useAskAdvisor';
|
import { openAskAdvisor } from '@/composables/useAskAdvisor';
|
||||||
import { buildFailGuideFromReason } from '@/utils/parcelCreateFailGuide';
|
import { buildFailGuideFromReason } from '@/utils/parcelCreateFailGuide';
|
||||||
@ -1393,14 +1399,9 @@ function getOrderFieldLabelKey(fieldKey: string) {
|
|||||||
return orderBasicFields.find((field) => field.key === fieldKey)?.labelKey ?? fieldKey;
|
return orderBasicFields.find((field) => field.key === fieldKey)?.labelKey ?? fieldKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOrderBooleanField(fieldKey: string) {
|
function shouldRenderOrderBooleanTag(fieldKey: string) {
|
||||||
return ['isCod', 'isIntercept', 'isRemote', 'isPaid'].includes(fieldKey);
|
if (isOrderDetailBooleanField(fieldKey)) return true;
|
||||||
}
|
return isOrderDetailYesNoLabel(getOrderValue(fieldKey), t);
|
||||||
|
|
||||||
function getOrderBooleanTagStyle(fieldKey: string) {
|
|
||||||
const val = getOrderValue(fieldKey);
|
|
||||||
const yes = t('order.myOrders.drawer.yes');
|
|
||||||
return getBooleanTagStyle(val === yes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const declarationSkuPreviewVisible = ref(false);
|
const declarationSkuPreviewVisible = ref(false);
|
||||||
@ -1819,18 +1820,15 @@ function getOrderValue(key: string) {
|
|||||||
shipTime: ['shipTime'],
|
shipTime: ['shipTime'],
|
||||||
payTime: ['payTime'],
|
payTime: ['payTime'],
|
||||||
signTime: ['signTime'],
|
signTime: ['signTime'],
|
||||||
isPaid: ['isPaid', 'IsPaid'],
|
|
||||||
isCod: ['isCod'],
|
|
||||||
codCurrency: ['codCurrency'],
|
|
||||||
codSum: ['codSum'],
|
|
||||||
isIntercept: ['isIntercept'],
|
|
||||||
isRemote: ['isRemote'],
|
|
||||||
createTime: ['createTime'],
|
createTime: ['createTime'],
|
||||||
createBy: ['createBy'],
|
createBy: ['createBy'],
|
||||||
updateTime: ['updateTime'],
|
updateTime: ['updateTime'],
|
||||||
updateBy: ['updateBy'],
|
updateBy: ['updateBy'],
|
||||||
...ORDER_DETAIL_VALUE_FIELD_MAP,
|
...ORDER_DETAIL_VALUE_FIELD_MAP,
|
||||||
};
|
};
|
||||||
|
if (isOrderDetailBooleanField(key)) {
|
||||||
|
return formatOrderDetailBooleanLabel(pickOrderDetailBooleanRaw(o, key), t);
|
||||||
|
}
|
||||||
const keys = fieldMap[key] || [key];
|
const keys = fieldMap[key] || [key];
|
||||||
let val: any;
|
let val: any;
|
||||||
for (const k of keys) {
|
for (const k of keys) {
|
||||||
@ -1840,9 +1838,6 @@ function getOrderValue(key: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (val == null || val === '') return '--';
|
if (val == null || val === '') return '--';
|
||||||
if (key === 'isCod' || key === 'isIntercept' || key === 'isRemote' || key === 'isPaid') {
|
|
||||||
return val ? t('order.myOrders.drawer.yes') : t('order.myOrders.drawer.no');
|
|
||||||
}
|
|
||||||
if (key === 'orderStatus') {
|
if (key === 'orderStatus') {
|
||||||
const code = resolveOrderStatusCode(o);
|
const code = resolveOrderStatusCode(o);
|
||||||
const meta = getOrderStatusMeta(code);
|
const meta = getOrderStatusMeta(code);
|
||||||
@ -1867,7 +1862,7 @@ function getSenderValue(key: string) {
|
|||||||
}
|
}
|
||||||
const val = sender.value[key];
|
const val = sender.value[key];
|
||||||
if (key === 'isDefault') {
|
if (key === 'isDefault') {
|
||||||
return val == null ? '--' : (val ? t('order.myOrders.drawer.yes') : t('order.myOrders.drawer.no'));
|
return formatOrderDetailBooleanLabel(val, t);
|
||||||
}
|
}
|
||||||
return val ?? '--';
|
return val ?? '--';
|
||||||
}
|
}
|
||||||
@ -2462,16 +2457,22 @@ function loadDetailData() {
|
|||||||
const o = detail.value;
|
const o = detail.value;
|
||||||
if (!o) return;
|
if (!o) return;
|
||||||
|
|
||||||
// 发件人
|
const senderRaw = pickOrderDetailNested<Record<string, any>>(o, 'sender', 'Sender');
|
||||||
if (o.sender && typeof o.sender === 'object') {
|
if (senderRaw && typeof senderRaw === 'object') {
|
||||||
sender.value = { ...o.sender, country: o.sender.country ?? o.sender.countryCode };
|
sender.value = {
|
||||||
|
...senderRaw,
|
||||||
|
country: senderRaw.country ?? senderRaw.countryCode ?? senderRaw.CountryCode,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
sender.value = {};
|
sender.value = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收件人:优先使用 list 返回的 receiver
|
const receiverRaw = pickOrderDetailNested<Record<string, any>>(o, 'receiver', 'Receiver');
|
||||||
if (o.receiver && typeof o.receiver === 'object') {
|
if (receiverRaw && typeof receiverRaw === 'object') {
|
||||||
recipient.value = { ...o.receiver, country: o.receiver.country ?? o.receiver.countryCode };
|
recipient.value = {
|
||||||
|
...receiverRaw,
|
||||||
|
country: receiverRaw.country ?? receiverRaw.countryCode ?? receiverRaw.CountryCode,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
recipient.value = {
|
recipient.value = {
|
||||||
recipientsName: o.consignee || '',
|
recipientsName: o.consignee || '',
|
||||||
@ -2496,17 +2497,12 @@ function loadDetailData() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 申报信息
|
const itemsRaw = pickOrderDetailArray(o, 'items', 'Items');
|
||||||
declarationList.value = Array.isArray(o.items) && o.items.length > 0
|
declarationList.value = itemsRaw.length > 0 ? cloneDeclarationItemRows(itemsRaw) : [];
|
||||||
? cloneDeclarationItemRows(o.items)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
// 件重尺
|
weightList.value = pickOrderDetailArray(o, 'weightList', 'WeightList');
|
||||||
weightList.value = Array.isArray(o.weightList) && o.weightList.length > 0 ? o.weightList : [];
|
|
||||||
|
|
||||||
// 费用:接口字段可为 feeList / orderFees / FeeList
|
feeList.value = pickOrderDetailArray(o, 'feeList', 'FeeList', 'orderFees', 'fees');
|
||||||
const fees = o.feeList ?? o.FeeList ?? o.orderFees ?? o.fees;
|
|
||||||
feeList.value = Array.isArray(fees) ? fees : [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyDetailFromApi(raw: any) {
|
function applyDetailFromApi(raw: any) {
|
||||||
|
|||||||
@ -74,6 +74,7 @@
|
|||||||
WAYBILL_MANAGE_DEFAULT_KEYS,
|
WAYBILL_MANAGE_DEFAULT_KEYS,
|
||||||
getWaybillBaseCatalog,
|
getWaybillBaseCatalog,
|
||||||
} from './columnConfig';
|
} from './columnConfig';
|
||||||
|
import { mergeOrderListRowWithDetail } from '../components/orderDetailFieldConfig';
|
||||||
import {
|
import {
|
||||||
fetchPrintUrlForOrder,
|
fetchPrintUrlForOrder,
|
||||||
fetchPrintUrlsForOrders,
|
fetchPrintUrlsForOrders,
|
||||||
@ -209,11 +210,17 @@
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 列表行与表格列对齐:只保留标量/展平字段,不保留嵌套树(Items/FeeList 等) */
|
/**
|
||||||
|
* 列表行与表格列对齐:展平寄收件人等列字段;
|
||||||
|
* 同时保留 sender/receiver/items/weightList/feeList,供详情抽屉使用
|
||||||
|
*(勿再整棵丢掉,否则 /order/detail 嵌套数据会被 normalize 冲掉)。
|
||||||
|
*/
|
||||||
function normalizeOrderRow(raw: any, customerLabelMap?: Map<number, string>) {
|
function normalizeOrderRow(raw: any, customerLabelMap?: Map<number, string>) {
|
||||||
const r = raw?.receiver || raw?.Receiver || {};
|
const r = raw?.receiver || raw?.Receiver || {};
|
||||||
const s = raw?.sender || raw?.Sender || {};
|
const s = raw?.sender || raw?.Sender || {};
|
||||||
const wl = raw?.weightList || raw?.WeightList;
|
const wl = raw?.weightList || raw?.WeightList;
|
||||||
|
const itemsRaw = raw?.items || raw?.Items;
|
||||||
|
const feeRaw = raw?.feeList || raw?.FeeList || raw?.orderFees || raw?.fees;
|
||||||
const lastW =
|
const lastW =
|
||||||
Array.isArray(wl) && wl.length ? wl[wl.length - 1] : undefined;
|
Array.isArray(wl) && wl.length ? wl[wl.length - 1] : undefined;
|
||||||
|
|
||||||
@ -466,6 +473,12 @@
|
|||||||
consigneeCountry && consigneeCountryName
|
consigneeCountry && consigneeCountryName
|
||||||
? `${consigneeCountry} ${consigneeCountryName}`
|
? `${consigneeCountry} ${consigneeCountryName}`
|
||||||
: consigneeCountryName || consigneeCountry || null,
|
: consigneeCountryName || consigneeCountry || null,
|
||||||
|
// 详情抽屉依赖嵌套对象;列表接口 items/weightList 常为空数组,有内容时以 raw 为准
|
||||||
|
sender: s && typeof s === 'object' && Object.keys(s).length ? s : undefined,
|
||||||
|
receiver: r && typeof r === 'object' && Object.keys(r).length ? r : undefined,
|
||||||
|
items: Array.isArray(itemsRaw) ? itemsRaw : [],
|
||||||
|
weightList: Array.isArray(wl) ? wl : [],
|
||||||
|
feeList: Array.isArray(feeRaw) ? feeRaw : [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -478,7 +491,10 @@
|
|||||||
if (id != null && id !== '') {
|
if (id != null && id !== '') {
|
||||||
try {
|
try {
|
||||||
const detail = await getOrderDetail(id);
|
const detail = await getOrderDetail(id);
|
||||||
nextRow = normalizeOrderRow({ ...row, ...detail }, buildCustomerLabelMap());
|
nextRow = normalizeOrderRow(
|
||||||
|
mergeOrderListRowWithDetail(row, detail),
|
||||||
|
buildCustomerLabelMap(),
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
/* 保留列表行数据 */
|
/* 保留列表行数据 */
|
||||||
}
|
}
|
||||||
@ -1735,7 +1751,10 @@
|
|||||||
if (rows.length === 1 && id != null) {
|
if (rows.length === 1 && id != null) {
|
||||||
try {
|
try {
|
||||||
const detail = await getOrderDetail(id);
|
const detail = await getOrderDetail(id);
|
||||||
ctxRow = normalizeOrderRow({ ...ctxRow, ...detail });
|
ctxRow = normalizeOrderRow(
|
||||||
|
mergeOrderListRowWithDetail(ctxRow, detail),
|
||||||
|
buildCustomerLabelMap(),
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
/* keep list row */
|
/* keep list row */
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,6 +68,11 @@ import {
|
|||||||
} from './providerCatalog';
|
} from './providerCatalog';
|
||||||
import { buildAiViewTree, type AiViewKey } from './viewsCatalog';
|
import { buildAiViewTree, type AiViewKey } from './viewsCatalog';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
// 与路由 name 对齐;index.vue 默认推断为 index,会导致切标签无法缓存
|
||||||
|
name: 'SystemAiAssistant',
|
||||||
|
});
|
||||||
|
|
||||||
const DEFAULT_VIEW: AiViewKey = 'providers';
|
const DEFAULT_VIEW: AiViewKey = 'providers';
|
||||||
|
|
||||||
const selectedViewKeys = ref<string[]>([DEFAULT_VIEW]);
|
const selectedViewKeys = ref<string[]>([DEFAULT_VIEW]);
|
||||||
|
|||||||
@ -147,6 +147,11 @@ import {
|
|||||||
type AiProviderItem,
|
type AiProviderItem,
|
||||||
} from '@/views/system/aiAssistant/providerCatalog';
|
} from '@/views/system/aiAssistant/providerCatalog';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
// 与路由 name 对齐;index.vue 默认推断为 index,会导致切标签无法缓存
|
||||||
|
name: 'SystemMcpTools',
|
||||||
|
});
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user