feat: 运单费用版本切换、服务商名称与列配置中文,我的运单隐藏操作日志
All checks were successful
Build and Deploy Front / deploy (push) Successful in 5m40s
All checks were successful
Build and Deploy Front / deploy (push) Successful in 5m40s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
5b6cb43457
commit
50b3eac99c
@ -229,8 +229,10 @@ flowchart TD
|
||||
| `POST /operation/outbound/create` | 出库:不写费用 |
|
||||
| `GET /order/weight/downstream-import-template` | 对账件重尺导入模板 |
|
||||
| `POST /order/weight/downstream-import` | 导入 downstream 重尺并锁定渠道重算应付 |
|
||||
| `POST /order/fee/recalculate-receivable` | 重算应收(已支付拒绝) |
|
||||
| `POST /order/fee/recalculate-payable` | 重算应付(已有渠道时锁定渠道,不换渠道) |
|
||||
| `POST /order/fee/recalculate-receivable` | 重算应收(已支付拒绝;沿用订单绑定价格/规则版本) |
|
||||
| `POST /order/fee/recalculate-payable` | 重算应付(已有渠道时锁定渠道;允许渠道不在旧规则排货池,只重算买价) |
|
||||
| `POST /order/fee/bind-snapshot-versions` | 显式切换订单绑定的规则/价格版本(可选随后重算应收/应付) |
|
||||
| `POST /order/fee/reallocate` | 重新排货(取当前有效规则;成功后写回订单规则版本) |
|
||||
| `POST /order/fee/rerun-inbound-billing` | 重跑入库计费(已支付拒绝,应收已锁) |
|
||||
| `POST /order/fee/add` | 手工加费(应收已支付拒绝) |
|
||||
| `POST /order/additional-services/update` | 未支付改附加服务并重算 |
|
||||
|
||||
@ -2152,6 +2152,25 @@ export async function postOrderRecalculatePayable(body: OrderIdRequest): Promise
|
||||
return res.data?.data ?? res.data?.Data ?? res.data
|
||||
}
|
||||
|
||||
/** POST /order/fee/bind-snapshot-versions — 切换订单绑定的规则/价格版本 */
|
||||
export async function postOrderBindSnapshotVersions(body: {
|
||||
id: number | string
|
||||
rulesVersion?: string
|
||||
priceVersion?: string
|
||||
recalculateReceivable?: boolean
|
||||
recalculatePayable?: boolean
|
||||
}): Promise<any> {
|
||||
const res = await axiosInstance.post(orderUrl('/order/fee/bind-snapshot-versions'), {
|
||||
id: Number(body.id),
|
||||
rulesVersion: body.rulesVersion || undefined,
|
||||
priceVersion: body.priceVersion || undefined,
|
||||
recalculateReceivable: !!body.recalculateReceivable,
|
||||
recalculatePayable: !!body.recalculatePayable,
|
||||
})
|
||||
assertBusinessOk(res.data)
|
||||
return res.data?.data ?? res.data?.Data ?? res.data
|
||||
}
|
||||
|
||||
/** POST /order/fee/rerun-inbound-billing — 重跑入库计费(应收+排货应付) */
|
||||
export async function postOrderRerunInboundBilling(body: OrderIdRequest): Promise<any> {
|
||||
const res = await axiosInstance.post(orderUrl('/order/fee/rerun-inbound-billing'), { id: body.id })
|
||||
|
||||
@ -175,6 +175,7 @@ export const PERMISSION_ACTION_LABELS: Record<string, string> = {
|
||||
AddFee: '添加费用',
|
||||
RecalculateReceivable: '重算应收',
|
||||
RecalculatePayable: '重算应付',
|
||||
BindSnapshotVersions: '切换版本',
|
||||
ImportDownstreamWeight: '导入对账件重尺',
|
||||
AddDeclarationRow: '添加申报行',
|
||||
DownstreamForecastResult: '下游预报结果',
|
||||
@ -537,6 +538,7 @@ const CORE_GROUPS: PageButtonGroup[] = [
|
||||
{ code: 'AddFee', name: '添加费用' },
|
||||
{ code: 'RecalculateReceivable', name: '重算应收' },
|
||||
{ code: 'RecalculatePayable', name: '重算应付' },
|
||||
{ code: 'BindSnapshotVersions', name: '切换版本' },
|
||||
{ code: 'AddDeclarationRow', name: '添加申报行' },
|
||||
]),
|
||||
B('Order.FulfillOrders', [
|
||||
@ -573,6 +575,7 @@ const CORE_GROUPS: PageButtonGroup[] = [
|
||||
{ code: 'AddFee', name: '添加费用' },
|
||||
{ code: 'RecalculateReceivable', name: '重算应收' },
|
||||
{ code: 'RecalculatePayable', name: '重算应付' },
|
||||
{ code: 'BindSnapshotVersions', name: '切换版本' },
|
||||
{ code: 'ImportDownstreamWeight', name: '导入对账件重尺' },
|
||||
{ code: 'AddDeclarationRow', name: '添加申报行' },
|
||||
]),
|
||||
|
||||
@ -1876,7 +1876,20 @@ export default {
|
||||
'order.waybillManage.table.id': 'Order ID',
|
||||
'order.waybillManage.table.productCode': 'Product code',
|
||||
'order.waybillManage.table.channelCode': 'Channel code',
|
||||
'order.waybillManage.table.downstreamSupplierName': 'Service provider',
|
||||
'order.waybillManage.table.downstreamSupplierCode': 'Downstream supplier',
|
||||
'order.waybillManage.table.maxTimeliness': 'Max transit days',
|
||||
'order.waybillManage.table.salesProductRulesVersion': 'Rules version',
|
||||
'order.waybillManage.table.salesProductPriceVersion': 'Price version',
|
||||
'order.waybillManage.table.forecastReceivableAmount': 'Forecast receivable',
|
||||
'order.waybillManage.table.forecastPayableAmount': 'Forecast payable',
|
||||
'order.waybillManage.table.hasInsurance': 'Insured',
|
||||
'order.waybillManage.table.hasAllocationTrace': 'Has allocation trace',
|
||||
'order.waybillManage.table.allocationScheme': 'Allocation scheme',
|
||||
'order.waybillManage.table.exportQualificationId': 'Export qualification ID',
|
||||
'order.waybillManage.table.pickupOrderId': 'Pickup order ID',
|
||||
'order.waybillManage.table.stockBindLineCount': 'Bind line count',
|
||||
'order.waybillManage.table.primarySkuCodes': 'Bound SKUs',
|
||||
'order.waybillManage.table.downstreamAppKey': 'Downstream AppKey',
|
||||
'order.waybillManage.table.downstreamAppId': 'Downstream AppId',
|
||||
'order.waybillManage.table.channelForecastCode': 'Forecast code',
|
||||
@ -2205,6 +2218,8 @@ export default {
|
||||
'order.myOrders.drawer.productName': 'Sales Product',
|
||||
'order.myOrders.drawer.channelCode': 'Channel Code',
|
||||
'order.myOrders.drawer.channelName': 'Service Channel',
|
||||
'order.myOrders.drawer.downstreamSupplierName': 'Service provider',
|
||||
'order.myOrders.drawer.downstreamSupplierCode': 'Provider code',
|
||||
'order.myOrders.allocationTrace.view': 'View allocation process',
|
||||
'order.myOrders.allocationTrace.title': 'Allocation process',
|
||||
'order.myOrders.allocationTrace.fromSnapshot': 'Historical snapshot (at allocation time)',
|
||||
@ -2371,6 +2386,25 @@ export default {
|
||||
'order.myOrders.drawer.actions.cancel': 'Cancel',
|
||||
'order.myOrders.drawer.actions.recalculateReceivable': 'Recalc Receivable',
|
||||
'order.myOrders.drawer.actions.recalculatePayable': 'Recalc Payable',
|
||||
'order.myOrders.drawer.actions.bindSnapshotVersions': 'Switch Versions',
|
||||
'order.myOrders.drawer.fee.rulesVersion': 'Rules version',
|
||||
'order.myOrders.drawer.fee.priceVersion': 'Price version',
|
||||
'order.myOrders.drawer.bindSnapshot.title': 'Switch price/rules versions',
|
||||
'order.myOrders.drawer.bindSnapshot.hint':
|
||||
'Explicitly switch the publish versions bound to this order. Recalculation follows these bindings.',
|
||||
'order.myOrders.drawer.bindSnapshot.rulesVersion': 'Rules version',
|
||||
'order.myOrders.drawer.bindSnapshot.priceVersion': 'Price version',
|
||||
'order.myOrders.drawer.bindSnapshot.boundAs': 'Bound: {version}',
|
||||
'order.myOrders.drawer.bindSnapshot.keepUnchanged': 'Keep current',
|
||||
'order.myOrders.drawer.bindSnapshot.useCurrent': 'Current effective publish',
|
||||
'order.myOrders.drawer.bindSnapshot.currentBound': 'current binding',
|
||||
'order.myOrders.drawer.bindSnapshot.recalcReceivable': 'Recalc receivable after bind',
|
||||
'order.myOrders.drawer.bindSnapshot.recalcPayable': 'Recalc payable after bind',
|
||||
'order.myOrders.drawer.bindSnapshot.needVersion': 'Select at least a rules or price version',
|
||||
'order.myOrders.drawer.bindSnapshot.noProduct': 'Order has no sales product',
|
||||
'order.myOrders.drawer.bindSnapshot.success': 'Versions updated',
|
||||
'order.myOrders.drawer.bindSnapshot.failed': 'Failed to switch versions',
|
||||
'order.myOrders.drawer.bindSnapshot.loadFailed': 'Failed to load publish versions',
|
||||
'order.myOrders.drawer.actions.addFee': 'Add Fee',
|
||||
'order.myOrders.drawer.actions.additionalServices': 'Additional services',
|
||||
'order.myOrders.drawer.editBasicTitle': 'Edit Order Details',
|
||||
@ -2402,7 +2436,24 @@ export default {
|
||||
'order.myOrders.drawer.fee.diff': 'Delta',
|
||||
'order.myOrders.drawer.fee.negativeGrossProfitWarn': 'Negative gross profit detected — review cost and pricing',
|
||||
'order.myOrders.drawer.fee.additionalServicesTitle': 'Additional services',
|
||||
'order.myOrders.drawer.fee.additionalServicesManageTitle': 'Manage additional services',
|
||||
'order.myOrders.drawer.fee.additionalServicesViewTitle': 'View additional services',
|
||||
'order.myOrders.drawer.fee.additionalServicesEmpty': 'No additional services for this product',
|
||||
'order.myOrders.drawer.fee.additionalServicesNone': 'None',
|
||||
'order.myOrders.drawer.fee.additionalServicesManage': 'Manage',
|
||||
'order.myOrders.drawer.fee.additionalServicesView': 'View',
|
||||
'order.myOrders.drawer.fee.additionalServicesSave': 'Save & recalc',
|
||||
'order.myOrders.drawer.fee.additionalServicesClose': 'Close',
|
||||
'order.myOrders.drawer.fee.additionalServicesHint':
|
||||
'Check or uncheck product-allowed services. Saving recalculates receivable (unpaid only).',
|
||||
'order.myOrders.drawer.fee.additionalServicesPaidHint':
|
||||
'Order is paid; additional services cannot be changed.',
|
||||
'order.myOrders.drawer.fee.additionalServicesCurrent': 'Currently bound',
|
||||
'order.myOrders.drawer.fee.additionalServicesPick': 'Available services',
|
||||
'order.myOrders.drawer.fee.additionalServicesOrphan':
|
||||
'These are no longer on the product and will be removed on save',
|
||||
'order.myOrders.drawer.fee.additionalServicesOrphanCleared':
|
||||
'Removed out-of-scope services: {names}',
|
||||
'order.myOrders.drawer.fee.additionalServicesSuccess': 'Additional services updated',
|
||||
'order.myOrders.drawer.fee.additionalServicesFailed': 'Failed to update additional services',
|
||||
'order.myOrders.drawer.fee.addTitle': 'Add Fee',
|
||||
|
||||
@ -1808,10 +1808,23 @@ export default {
|
||||
'order.waybillManage.table.id': '订单ID',
|
||||
'order.waybillManage.table.productCode': '产品编码',
|
||||
'order.waybillManage.table.channelCode': '渠道编码',
|
||||
'order.waybillManage.table.downstreamSupplierName': '服务商名称',
|
||||
'order.waybillManage.table.downstreamSupplierCode': '下游服务商编码',
|
||||
'order.waybillManage.table.downstreamAppKey': '下游AppKey',
|
||||
'order.waybillManage.table.downstreamAppId': '下游AppId',
|
||||
'order.waybillManage.table.channelForecastCode': '渠道预报编码',
|
||||
'order.waybillManage.table.maxTimeliness': '最大时效(天)',
|
||||
'order.waybillManage.table.salesProductRulesVersion': '规则版本',
|
||||
'order.waybillManage.table.salesProductPriceVersion': '价格版本',
|
||||
'order.waybillManage.table.forecastReceivableAmount': '预报应收',
|
||||
'order.waybillManage.table.forecastPayableAmount': '预报应付',
|
||||
'order.waybillManage.table.hasInsurance': '是否投保',
|
||||
'order.waybillManage.table.hasAllocationTrace': '有排货快照',
|
||||
'order.waybillManage.table.allocationScheme': '排货方案',
|
||||
'order.waybillManage.table.exportQualificationId': '出口资质ID',
|
||||
'order.waybillManage.table.pickupOrderId': '揽收单ID',
|
||||
'order.waybillManage.table.stockBindLineCount': '绑定行数',
|
||||
'order.waybillManage.table.primarySkuCodes': '绑定SKU',
|
||||
'order.waybillManage.table.consigneeCountryName': '收件国家名称',
|
||||
'order.waybillManage.table.warehouseId': '仓库ID',
|
||||
'order.waybillManage.table.customerId': '客户ID',
|
||||
@ -2066,6 +2079,8 @@ export default {
|
||||
'order.myOrders.drawer.productName': '销售产品',
|
||||
'order.myOrders.drawer.channelCode': '渠道code',
|
||||
'order.myOrders.drawer.channelName': '服务渠道',
|
||||
'order.myOrders.drawer.downstreamSupplierName': '服务商名称',
|
||||
'order.myOrders.drawer.downstreamSupplierCode': '服务商编码',
|
||||
'order.myOrders.allocationTrace.view': '查看排货过程',
|
||||
'order.myOrders.allocationTrace.title': '排货过程',
|
||||
'order.myOrders.allocationTrace.fromSnapshot': '历史快照(排货当时结果)',
|
||||
@ -2232,8 +2247,27 @@ export default {
|
||||
'order.myOrders.drawer.actions.cancel': '取消',
|
||||
'order.myOrders.drawer.actions.recalculateReceivable': '重算应收',
|
||||
'order.myOrders.drawer.actions.recalculatePayable': '重算应付',
|
||||
'order.myOrders.drawer.actions.bindSnapshotVersions': '切换版本',
|
||||
'order.myOrders.drawer.actions.addFee': '添加费用',
|
||||
'order.myOrders.drawer.actions.additionalServices': '附加服务',
|
||||
'order.myOrders.drawer.fee.rulesVersion': '规则版本',
|
||||
'order.myOrders.drawer.fee.priceVersion': '价格版本',
|
||||
'order.myOrders.drawer.bindSnapshot.title': '切换价格/规则版本',
|
||||
'order.myOrders.drawer.bindSnapshot.hint':
|
||||
'显式切换订单绑定的发布版本;默认仍保护下单契约。重算应收/应付会沿用此处绑定。',
|
||||
'order.myOrders.drawer.bindSnapshot.rulesVersion': '规则版本',
|
||||
'order.myOrders.drawer.bindSnapshot.priceVersion': '价格版本',
|
||||
'order.myOrders.drawer.bindSnapshot.boundAs': '当前绑定:{version}',
|
||||
'order.myOrders.drawer.bindSnapshot.keepUnchanged': '不修改(保持当前)',
|
||||
'order.myOrders.drawer.bindSnapshot.useCurrent': '当前有效发布',
|
||||
'order.myOrders.drawer.bindSnapshot.currentBound': '当前绑定',
|
||||
'order.myOrders.drawer.bindSnapshot.recalcReceivable': '绑定后重算应收',
|
||||
'order.myOrders.drawer.bindSnapshot.recalcPayable': '绑定后重算应付',
|
||||
'order.myOrders.drawer.bindSnapshot.needVersion': '请至少选择规则版本或价格版本',
|
||||
'order.myOrders.drawer.bindSnapshot.noProduct': '订单未绑定销售产品',
|
||||
'order.myOrders.drawer.bindSnapshot.success': '版本已切换',
|
||||
'order.myOrders.drawer.bindSnapshot.failed': '切换版本失败',
|
||||
'order.myOrders.drawer.bindSnapshot.loadFailed': '加载发布版本失败',
|
||||
'order.myOrders.drawer.editBasicTitle': '编辑订单详情',
|
||||
'order.myOrders.drawer.editWeightTitle': '编辑件重尺',
|
||||
'order.myOrders.drawer.message.saveSuccess': '保存成功',
|
||||
@ -2263,7 +2297,23 @@ export default {
|
||||
'order.myOrders.drawer.fee.diff': '差额',
|
||||
'order.myOrders.drawer.fee.negativeGrossProfitWarn': '存在负毛利,请关注成本与报价',
|
||||
'order.myOrders.drawer.fee.additionalServicesTitle': '附加服务',
|
||||
'order.myOrders.drawer.fee.additionalServicesManageTitle': '管理附加服务',
|
||||
'order.myOrders.drawer.fee.additionalServicesViewTitle': '查看附加服务',
|
||||
'order.myOrders.drawer.fee.additionalServicesEmpty': '该产品暂无可选附加服务',
|
||||
'order.myOrders.drawer.fee.additionalServicesNone': '未绑定',
|
||||
'order.myOrders.drawer.fee.additionalServicesManage': '管理',
|
||||
'order.myOrders.drawer.fee.additionalServicesView': '查看',
|
||||
'order.myOrders.drawer.fee.additionalServicesSave': '保存并重算',
|
||||
'order.myOrders.drawer.fee.additionalServicesClose': '关闭',
|
||||
'order.myOrders.drawer.fee.additionalServicesHint':
|
||||
'勾选增删产品允许的附加服务;保存后按当前阶段重算应收(未支付可改)。',
|
||||
'order.myOrders.drawer.fee.additionalServicesPaidHint': '订单已支付,附加服务不可修改。',
|
||||
'order.myOrders.drawer.fee.additionalServicesCurrent': '订单当前绑定',
|
||||
'order.myOrders.drawer.fee.additionalServicesPick': '可选附加服务',
|
||||
'order.myOrders.drawer.fee.additionalServicesOrphan':
|
||||
'以下服务已不在产品可选范围,保存后将自动移除',
|
||||
'order.myOrders.drawer.fee.additionalServicesOrphanCleared':
|
||||
'已移除不在产品范围的附加服务:{names}',
|
||||
'order.myOrders.drawer.fee.additionalServicesSuccess': '附加服务已更新',
|
||||
'order.myOrders.drawer.fee.additionalServicesFailed': '附加服务更新失败',
|
||||
'order.myOrders.drawer.fee.addTitle': '添加费用',
|
||||
|
||||
@ -145,7 +145,7 @@
|
||||
apply: [payload: { activeId: string; schemes: ColumnScheme[] }];
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const { t, te } = useI18n();
|
||||
const [registerModal, { openModal, closeModal, setModalProps }] = useModal();
|
||||
|
||||
const editingSchemeId = ref('');
|
||||
@ -173,8 +173,9 @@
|
||||
selectedKeys.value
|
||||
.map((key) => {
|
||||
const col = catalogMap.value.get(key);
|
||||
if (!col) return null;
|
||||
return { key: col.key, label: resolveColumnLabel(col, t) };
|
||||
// 方案里可能有目录尚未收录的 key:仍用中文兜底,避免表头/配置显示英文字段名
|
||||
const def = col || { key };
|
||||
return { key, label: resolveColumnLabel(def, t, te) };
|
||||
})
|
||||
.filter(Boolean) as { key: string; label: string }[],
|
||||
);
|
||||
@ -183,7 +184,7 @@
|
||||
const selected = new Set(selectedKeys.value);
|
||||
return props.catalog
|
||||
.filter((col) => !selected.has(col.key))
|
||||
.map((col) => ({ key: col.key, label: resolveColumnLabel(col, t) }));
|
||||
.map((col) => ({ key: col.key, label: resolveColumnLabel(col, t, te) }));
|
||||
});
|
||||
|
||||
function readSelectedKeysFromDom(): string[] {
|
||||
|
||||
@ -32,6 +32,8 @@ export const ORDER_DETAIL_VALUE_FIELD_MAP: Record<string, string[]> = {
|
||||
declaredCurrency: ['declaredCurrency', 'DeclaredCurrency'],
|
||||
declaredSum: ['declaredSum', 'DeclaredSum'],
|
||||
additionalServices: ['additionalServices', 'AdditionalServices'],
|
||||
downstreamSupplierName: ['downstreamSupplierName', 'DownstreamSupplierName'],
|
||||
downstreamSupplierCode: ['downstreamSupplierCode', 'DownstreamSupplierCode'],
|
||||
};
|
||||
|
||||
export interface OrderDetailFieldGroup {
|
||||
@ -58,7 +60,9 @@ export function patchOrderBasicFieldGroups(
|
||||
const fields = [
|
||||
'productCode',
|
||||
'productName',
|
||||
...(includeChannel ? (['channelCode', 'channelName'] as const) : []),
|
||||
...(includeChannel
|
||||
? (['channelCode', 'channelName', 'downstreamSupplierName', 'downstreamSupplierCode'] as const)
|
||||
: []),
|
||||
'warehouse',
|
||||
'consigneeCountry',
|
||||
'consigneeCountryName',
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
/** 我的订单列表字段配置(本地命名方案,最多显示 30 列) */
|
||||
|
||||
import { filterMyOrdersFieldKeys, isMyOrdersHiddenField, MY_ORDERS_HIDDEN_FIELD_KEYS } from './customerFieldMask';
|
||||
import { getOrderColumnLabelZh } from '../orderColumnLabelsZh';
|
||||
|
||||
export const MAX_VISIBLE_COLUMNS = 30;
|
||||
|
||||
@ -182,29 +183,67 @@ export function getActiveScheme(state: ColumnConfigState): ColumnScheme {
|
||||
);
|
||||
}
|
||||
|
||||
function isUsableLabel(text: string | undefined | null, fieldKey: string): text is string {
|
||||
if (!text) return false;
|
||||
const s = String(text).trim();
|
||||
if (!s) return false;
|
||||
// 翻译失败时 vue-i18n 常回传 i18n key 或字段 key
|
||||
if (s === fieldKey) return false;
|
||||
if (s.startsWith('order.') && s.includes('.')) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function resolveColumnLabel(
|
||||
col: OrderListColumnDef,
|
||||
t: (key: string) => string,
|
||||
t: (key: string, ...args: any[]) => string,
|
||||
_te?: (key: string) => boolean,
|
||||
): string {
|
||||
if (col.labelKey) return t(col.labelKey);
|
||||
if (col.label) return col.label;
|
||||
return col.key;
|
||||
const fieldKey = col.key;
|
||||
const tryI18n = (i18nKey: string) => {
|
||||
try {
|
||||
const text = t(i18nKey);
|
||||
return isUsableLabel(text, fieldKey) ? text : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
if (col.labelKey) {
|
||||
const byLabelKey = tryI18n(col.labelKey);
|
||||
if (byLabelKey) return byLabelKey;
|
||||
}
|
||||
if (isUsableLabel(col.label, fieldKey)) return col.label!.trim();
|
||||
|
||||
const autoKeys = [
|
||||
`order.waybillManage.table.${fieldKey}`,
|
||||
`order.myOrders.table.${fieldKey}`,
|
||||
`order.myOrders.drawer.${fieldKey}`,
|
||||
];
|
||||
for (const k of autoKeys) {
|
||||
const hit = tryI18n(k);
|
||||
if (hit) return hit;
|
||||
}
|
||||
// 最终兜底:中文列名表,绝不直接返回 camelCase key
|
||||
return getOrderColumnLabelZh(fieldKey);
|
||||
}
|
||||
|
||||
export function buildHotColumns(
|
||||
catalog: OrderListColumnDef[],
|
||||
keys: string[],
|
||||
t: (key: string) => string,
|
||||
t: (key: string, ...args: any[]) => string,
|
||||
te?: (key: string) => boolean,
|
||||
) {
|
||||
const map = new Map(catalog.map((c) => [c.key, c]));
|
||||
return keys
|
||||
.map((key) => map.get(key))
|
||||
.filter(Boolean)
|
||||
.map((col) => ({
|
||||
key: col!.key,
|
||||
label: resolveColumnLabel(col!, t),
|
||||
width: col!.width ?? 120,
|
||||
}));
|
||||
.map((key) => {
|
||||
const col = map.get(key) || { key, width: 120 };
|
||||
return {
|
||||
key,
|
||||
label: resolveColumnLabel(col, t, te),
|
||||
width: col.width ?? 120,
|
||||
};
|
||||
})
|
||||
.filter((col) => !!col.key);
|
||||
}
|
||||
|
||||
/** 将接口行中的标量字段并入可选列目录(跳过对象/数组与内部字段) */
|
||||
@ -228,10 +267,19 @@ export function mergeDiscoveredColumns(
|
||||
for (const [key, value] of Object.entries(row)) {
|
||||
if (!key || skip.has(key) || map.has(key)) continue;
|
||||
if (value != null && typeof value === 'object') continue;
|
||||
map.set(key, { key, label: key, width: 120 });
|
||||
map.set(key, {
|
||||
key,
|
||||
label: getOrderColumnLabelZh(key),
|
||||
labelKey: `order.waybillManage.table.${key}`,
|
||||
width: 120,
|
||||
});
|
||||
}
|
||||
}
|
||||
return Array.from(map.values());
|
||||
return Array.from(map.values()).map((col) =>
|
||||
isUsableLabel(col.label, col.key)
|
||||
? col
|
||||
: { ...col, label: getOrderColumnLabelZh(col.key) },
|
||||
);
|
||||
}
|
||||
|
||||
export function createSchemeId() {
|
||||
|
||||
@ -1053,7 +1053,6 @@ const tabList = computed(() => [
|
||||
{ key: 'track', label: t('order.myOrders.drawer.tabs.track') },
|
||||
{ key: 'attachments', label: t('order.myOrders.drawer.tabs.attachments') },
|
||||
{ key: 'stockBind', label: t('order.myOrders.drawer.tabs.stockBind') },
|
||||
{ key: 'operationLog', label: t('order.myOrders.drawer.tabs.operationLog') },
|
||||
]);
|
||||
|
||||
const operationLogList = ref<OrderOperationLogItem[]>([]);
|
||||
@ -1067,6 +1066,8 @@ const slidingTabItems = computed(() =>
|
||||
function resolveDrawerTab(tab?: string) {
|
||||
const next = tab || 'basic';
|
||||
if (next === 'weight' || next === 'fee') return 'weightFee';
|
||||
// 我的运单不展示操作日志(管理端运单管理保留)
|
||||
if (next === 'operationLog') return 'basic';
|
||||
return next;
|
||||
}
|
||||
|
||||
@ -2150,7 +2151,7 @@ watch(
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
openDrawer: (opts?: { tab?: 'basic' | 'weightFee' | 'weight' | 'fee' | 'track' | 'attachments' | 'stockBind' | 'operationLog' }) => {
|
||||
openDrawer: (opts?: { tab?: 'basic' | 'weightFee' | 'weight' | 'fee' | 'track' | 'attachments' | 'stockBind' }) => {
|
||||
const tab = resolveDrawerTab(opts?.tab);
|
||||
activeTab.value = tab;
|
||||
basicViewMode.value = 'all';
|
||||
|
||||
@ -7,6 +7,7 @@ export const MY_ORDERS_HIDDEN_FIELD_KEYS = new Set([
|
||||
'channelName',
|
||||
'downstreamProvider',
|
||||
'downstreamSupplierCode',
|
||||
'downstreamSupplierName',
|
||||
'downstreamAppKey',
|
||||
'downstreamAppId',
|
||||
'channelForecastCode',
|
||||
|
||||
@ -122,7 +122,7 @@
|
||||
name: 'MyOrders',
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const { t, te } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const isFulfillList = computed(() => route.meta.listMode === 'fulfill');
|
||||
@ -1623,7 +1623,7 @@
|
||||
'trackNo',
|
||||
]);
|
||||
const base = withServerFilterColumns(
|
||||
buildHotColumns(listColumnCatalog.value, activeColumnKeys.value, t),
|
||||
buildHotColumns(listColumnCatalog.value, activeColumnKeys.value, t, te),
|
||||
ORDER_SERVER_FILTER_KEYS,
|
||||
(col) => {
|
||||
if (numberKeys.has(col.key)) {
|
||||
|
||||
161
src/views/order/orderColumnLabelsZh.ts
Normal file
161
src/views/order/orderColumnLabelsZh.ts
Normal file
@ -0,0 +1,161 @@
|
||||
/**
|
||||
* 运单/订单列表列中文名(表头 + 字段配置兜底)。
|
||||
* 优先保证中文展示,避免出现 maxTimeliness 等英文字段名。
|
||||
*/
|
||||
export const ORDER_COLUMN_LABEL_ZH: Record<string, string> = {
|
||||
// 常用别名
|
||||
customerName: '客户名称',
|
||||
customerCode: '客户编码',
|
||||
waybillNo: '运单号',
|
||||
customerOrderNo: '客户单号',
|
||||
trackingNo: '跟踪号',
|
||||
downstreamServiceNo: '服务商单号',
|
||||
shippingMethod: '销售产品',
|
||||
serviceChannel: '服务渠道',
|
||||
consignee: '收件人',
|
||||
consigneePhone: '收件电话',
|
||||
consigneeCity: '收件城市',
|
||||
consigneeAddress: '收件地址',
|
||||
consigneePostcode: '收件邮编',
|
||||
consigneeState: '收件省州',
|
||||
ioss: 'IOSS',
|
||||
eori: 'EORI',
|
||||
|
||||
// OrderOutput 标量
|
||||
id: '订单ID',
|
||||
clientNo: '客户单号',
|
||||
trackNo: '跟踪号',
|
||||
serviceNo: '运单号',
|
||||
productCode: '产品编码',
|
||||
productName: '销售产品',
|
||||
maxTimeliness: '最大时效(天)',
|
||||
channelCode: '渠道编码',
|
||||
channelName: '服务渠道',
|
||||
downstreamSupplierName: '服务商名称',
|
||||
downstreamSupplierCode: '服务商编码',
|
||||
downstreamAppKey: '下游AppKey',
|
||||
downstreamAppId: '下游AppId',
|
||||
channelForecastCode: '渠道预报编码',
|
||||
consigneeCountry: '收件国家',
|
||||
consigneeCountryName: '收件国家名称',
|
||||
cargoAttribute: '货物属性',
|
||||
warehouseId: '仓库ID',
|
||||
warehouse: '仓库',
|
||||
customerId: '客户ID',
|
||||
orderSource: '订单来源',
|
||||
pickupTime: '期望揽收时间',
|
||||
carrierWaybillNo: '承运商单号',
|
||||
pickupNo: '揽收单号',
|
||||
inboundMethod: '到仓方式',
|
||||
fulfillmentType: '履约类型',
|
||||
fulfillProgress: '代发进度',
|
||||
stockBindStatus: '绑定状态',
|
||||
domesticTrackingNos: '国内快递单号',
|
||||
stockBindQtyTotal: '绑定件数',
|
||||
stockBindLineCount: '绑定行数',
|
||||
primaryBinCode: '主库位',
|
||||
primarySkuCodes: '绑定SKU',
|
||||
fulfillmentSource: '代发来源',
|
||||
customerSenderAddressId: '发件地址簿ID',
|
||||
requestId: '请求标识',
|
||||
exportCustomsMode: '出口报关方式',
|
||||
exportQualificationId: '出口资质ID',
|
||||
predeclareFailReason: '预报失败原因',
|
||||
allocationPendingReason: '待排货原因',
|
||||
allocationScheme: '排货方案',
|
||||
hasAllocationTrace: '有排货快照',
|
||||
declaredCurrency: '申报币种',
|
||||
declaredSum: '申报总金额',
|
||||
quantity: '包裹数量',
|
||||
isCod: '是否COD',
|
||||
codCurrency: 'COD币种',
|
||||
codSum: 'COD金额',
|
||||
orderStatus: '订单状态',
|
||||
status: '状态码',
|
||||
handleStatus: '处理状态码',
|
||||
handleStatusName: '处理状态',
|
||||
issueType: '问题类型码',
|
||||
issueTypeName: '问题类型',
|
||||
issueTitle: '问题标题',
|
||||
issueContent: '问题内容',
|
||||
issueTemplateId: '问题模板ID',
|
||||
issueRemark: '问题备注',
|
||||
issueTime: '问题时间',
|
||||
claimType: '理赔类型码',
|
||||
claimTypeName: '理赔类型',
|
||||
claimAmount: '理赔金额',
|
||||
claimCurrency: '理赔币种',
|
||||
claimReason: '理赔原因',
|
||||
claimTime: '理赔时间',
|
||||
previousOrderStatus: '原订单状态',
|
||||
receiveTime: '收货时间',
|
||||
shipTime: '发货时间',
|
||||
payTime: '支付时间',
|
||||
isPaid: '是否已支付',
|
||||
signTime: '签收时间',
|
||||
isIntercept: '是否拦截',
|
||||
isRemote: '是否偏远',
|
||||
hasInsurance: '是否投保',
|
||||
forecastReceivableAmount: '预报应收',
|
||||
forecastPayableAmount: '预报应付',
|
||||
salesProductRulesVersion: '规则版本',
|
||||
salesProductPriceVersion: '价格版本',
|
||||
createTime: '创建时间',
|
||||
createBy: '创建人',
|
||||
updateTime: '更新时间',
|
||||
updateBy: '更新人',
|
||||
chargeableWeight: '计费重',
|
||||
receivableAmount: '应收金额',
|
||||
inboundTime: '入库时间',
|
||||
outboundTime: '出库时间',
|
||||
pickupOrderId: '揽收单ID',
|
||||
|
||||
// 寄件人展平
|
||||
senderName: '寄件人',
|
||||
senderCompany: '寄件公司',
|
||||
senderPhone: '寄件手机',
|
||||
senderTelePhone: '寄件电话',
|
||||
senderEmail: '寄件邮箱',
|
||||
senderCountryCode: '寄件国家代码',
|
||||
senderProvince: '寄件省州',
|
||||
senderCity: '寄件城市',
|
||||
senderZipCode: '寄件邮编',
|
||||
senderStreetAddress1: '寄件地址1',
|
||||
senderStreetAddress2: '寄件地址2',
|
||||
senderStreetAddress3: '寄件地址3',
|
||||
senderHouseNumber: '寄件门牌号',
|
||||
|
||||
// 收件人展平
|
||||
receiverRecipientsName: '收件人',
|
||||
receiverCompanyName: '收件公司',
|
||||
receiverPhone: '收件手机',
|
||||
receiverTelePhone: '收件电话',
|
||||
receiverEmail: '收件邮箱',
|
||||
receiverCountryCode: '收件国家代码',
|
||||
receiverProvince: '收件省州',
|
||||
receiverCity: '收件城市',
|
||||
receiverStreetNumber: '收件门牌号',
|
||||
receiverStreetAddress1: '收件地址1',
|
||||
receiverStreetAddress2: '收件地址2',
|
||||
receiverStreetAddress3: '收件地址3',
|
||||
receiverZipCode: '收件邮编',
|
||||
receiverTaxType: '税号类型',
|
||||
receiverTaxNo: '税号',
|
||||
receiverCertificateType: '证件类型',
|
||||
receiverCertificateNo: '证件号码',
|
||||
receiverEori: '收件EORI',
|
||||
receiverVat: '收件VAT',
|
||||
};
|
||||
|
||||
/** 按字段 key 取中文列名;未知字段做可读化,避免直接甩 camelCase。 */
|
||||
export function getOrderColumnLabelZh(key: string): string {
|
||||
const k = String(key || '').trim();
|
||||
if (!k) return '';
|
||||
if (ORDER_COLUMN_LABEL_ZH[k]) return ORDER_COLUMN_LABEL_ZH[k];
|
||||
// fooBarBaz → Foo Bar Baz,再包一层中文前缀,避免纯英文 key 当表头
|
||||
const spaced = k
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
||||
.replace(/[_-]+/g, ' ')
|
||||
.trim();
|
||||
return spaced || k;
|
||||
}
|
||||
@ -4,6 +4,7 @@
|
||||
*/
|
||||
import type { OrderListColumnDef } from '../myOrders/columnConfig';
|
||||
import { MY_ORDERS_COLUMN_CATALOG } from '../myOrders/columnConfig';
|
||||
import { getOrderColumnLabelZh } from '../orderColumnLabelsZh';
|
||||
|
||||
export const WAYBILL_MANAGE_COLUMN_STORAGE_KEY = 'oms.order.waybillManage.columnSchemes';
|
||||
|
||||
@ -40,10 +41,23 @@ export const WAYBILL_MANAGE_COLUMN_CATALOG: OrderListColumnDef[] = [
|
||||
{ key: 'productName', labelKey: M('shippingMethod'), width: 120 },
|
||||
{ key: 'channelCode', labelKey: L('channelCode'), width: 110 },
|
||||
{ key: 'channelName', labelKey: M('serviceChannel'), width: 120 },
|
||||
{ key: 'downstreamSupplierName', labelKey: L('downstreamSupplierName'), width: 140 },
|
||||
{ key: 'downstreamSupplierCode', labelKey: L('downstreamSupplierCode'), width: 140 },
|
||||
{ key: 'downstreamAppKey', labelKey: L('downstreamAppKey'), width: 140 },
|
||||
{ key: 'downstreamAppId', labelKey: L('downstreamAppId'), width: 110 },
|
||||
{ key: 'channelForecastCode', labelKey: L('channelForecastCode'), width: 130 },
|
||||
{ key: 'maxTimeliness', labelKey: L('maxTimeliness'), width: 100 },
|
||||
{ key: 'salesProductRulesVersion', labelKey: L('salesProductRulesVersion'), width: 150 },
|
||||
{ key: 'salesProductPriceVersion', labelKey: L('salesProductPriceVersion'), width: 150 },
|
||||
{ key: 'forecastReceivableAmount', labelKey: L('forecastReceivableAmount'), width: 120 },
|
||||
{ key: 'forecastPayableAmount', labelKey: L('forecastPayableAmount'), width: 120 },
|
||||
{ key: 'hasInsurance', labelKey: L('hasInsurance'), width: 90 },
|
||||
{ key: 'hasAllocationTrace', labelKey: L('hasAllocationTrace'), width: 110 },
|
||||
{ key: 'allocationScheme', labelKey: L('allocationScheme'), width: 120 },
|
||||
{ key: 'exportQualificationId', labelKey: L('exportQualificationId'), width: 120 },
|
||||
{ key: 'pickupOrderId', labelKey: L('pickupOrderId'), width: 110 },
|
||||
{ key: 'stockBindLineCount', labelKey: L('stockBindLineCount'), width: 100 },
|
||||
{ key: 'primarySkuCodes', labelKey: L('primarySkuCodes'), width: 140 },
|
||||
{ key: 'consigneeCountry', labelKey: M('consigneeCountry'), width: 110 },
|
||||
{ key: 'consigneeCountryName', labelKey: L('consigneeCountryName'), width: 120 },
|
||||
{ key: 'cargoAttribute', labelKey: M('cargoAttribute'), width: 100 },
|
||||
@ -182,7 +196,16 @@ export function dedupeCatalog(list: OrderListColumnDef[]): OrderListColumnDef[]
|
||||
return Array.from(map.values());
|
||||
}
|
||||
|
||||
/** 基础目录 = 运单全量 + 我的订单目录中可能遗漏的项 */
|
||||
export function getWaybillBaseCatalog(): OrderListColumnDef[] {
|
||||
return dedupeCatalog([...WAYBILL_MANAGE_COLUMN_CATALOG, ...MY_ORDERS_COLUMN_CATALOG]);
|
||||
function withZhLabels(list: OrderListColumnDef[]): OrderListColumnDef[] {
|
||||
return list.map((col) => ({
|
||||
...col,
|
||||
label: col.label && col.label !== col.key ? col.label : getOrderColumnLabelZh(col.key),
|
||||
}));
|
||||
}
|
||||
|
||||
/** 基础目录 = 运单全量 + 我的订单目录中可能遗漏的项(带中文 label) */
|
||||
export function getWaybillBaseCatalog(): OrderListColumnDef[] {
|
||||
return withZhLabels(
|
||||
dedupeCatalog([...WAYBILL_MANAGE_COLUMN_CATALOG, ...MY_ORDERS_COLUMN_CATALOG]),
|
||||
);
|
||||
}
|
||||
|
||||
@ -0,0 +1,243 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import ModalPro from '@/components/ModalPro/index.vue'
|
||||
import useModal from '@/components/ModalPro/useModal'
|
||||
import {
|
||||
getSalesProductPublishPriceList,
|
||||
getSalesProductPublishRulesList,
|
||||
} from '@/api/channel/salesProduct'
|
||||
import { postOrderBindSnapshotVersions } from '@/api/order'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
orderId: number
|
||||
salesProductId: number | string | null
|
||||
currentRulesVersion?: string | null
|
||||
currentPriceVersion?: string | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
success: [order: any]
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const [registerModal] = useModal()
|
||||
const submitting = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
const formState = reactive({
|
||||
rulesVersion: undefined as string | undefined,
|
||||
priceVersion: undefined as string | undefined,
|
||||
recalculateReceivable: true,
|
||||
recalculatePayable: true,
|
||||
})
|
||||
|
||||
const rulesOptions = ref<Array<{ label: string; value: string }>>([])
|
||||
const priceOptions = ref<Array<{ label: string; value: string }>>([])
|
||||
|
||||
const title = computed(() => t('order.myOrders.drawer.bindSnapshot.title'))
|
||||
|
||||
function mapVersionOptions(items: any[], current?: string | null) {
|
||||
const rows = (items || []).map((row) => {
|
||||
const version = String(row.version ?? row.Version ?? '').trim()
|
||||
const publishTime = row.publishTime ?? row.PublishTime ?? ''
|
||||
const desc = String(row.description ?? row.Description ?? '').trim()
|
||||
const parts = [version]
|
||||
if (publishTime) parts.push(String(publishTime))
|
||||
if (desc) parts.push(desc)
|
||||
const isCurrent = current && version === current
|
||||
return {
|
||||
value: version,
|
||||
label: isCurrent
|
||||
? `${parts.join(' · ')}(${t('order.myOrders.drawer.bindSnapshot.currentBound')})`
|
||||
: parts.join(' · '),
|
||||
}
|
||||
}).filter((x) => x.value)
|
||||
return [
|
||||
{
|
||||
value: 'CURRENT',
|
||||
label: t('order.myOrders.drawer.bindSnapshot.useCurrent'),
|
||||
},
|
||||
...rows,
|
||||
]
|
||||
}
|
||||
|
||||
async function loadVersionOptions() {
|
||||
rulesOptions.value = [
|
||||
{ value: 'CURRENT', label: t('order.myOrders.drawer.bindSnapshot.useCurrent') },
|
||||
]
|
||||
priceOptions.value = [
|
||||
{ value: 'CURRENT', label: t('order.myOrders.drawer.bindSnapshot.useCurrent') },
|
||||
]
|
||||
if (props.salesProductId == null || props.salesProductId === '') return
|
||||
loading.value = true
|
||||
try {
|
||||
const [rules, price] = await Promise.all([
|
||||
getSalesProductPublishRulesList({
|
||||
salesProductId: props.salesProductId,
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
includeContent: false,
|
||||
}),
|
||||
getSalesProductPublishPriceList({
|
||||
salesProductId: props.salesProductId,
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
includeContent: false,
|
||||
}),
|
||||
])
|
||||
rulesOptions.value = mapVersionOptions(rules.items, props.currentRulesVersion)
|
||||
priceOptions.value = mapVersionOptions(price.items, props.currentPriceVersion)
|
||||
} catch (e: any) {
|
||||
Message.error(e?.message || t('order.myOrders.drawer.bindSnapshot.loadFailed'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
formState.rulesVersion = undefined
|
||||
formState.priceVersion = undefined
|
||||
formState.recalculateReceivable = true
|
||||
formState.recalculatePayable = true
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
async (v) => {
|
||||
if (!v) return
|
||||
resetForm()
|
||||
await loadVersionOptions()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function onUpdateVisible(val: boolean) {
|
||||
if (val === props.visible) return
|
||||
emit('update:visible', val)
|
||||
}
|
||||
|
||||
async function handleOk() {
|
||||
if (!formState.rulesVersion && !formState.priceVersion) {
|
||||
Message.warning(t('order.myOrders.drawer.bindSnapshot.needVersion'))
|
||||
return
|
||||
}
|
||||
submitting.value = true
|
||||
try {
|
||||
const result = await postOrderBindSnapshotVersions({
|
||||
id: props.orderId,
|
||||
rulesVersion: formState.rulesVersion,
|
||||
priceVersion: formState.priceVersion,
|
||||
recalculateReceivable: formState.recalculateReceivable,
|
||||
recalculatePayable: formState.recalculatePayable,
|
||||
})
|
||||
Message.success(t('order.myOrders.drawer.bindSnapshot.success'))
|
||||
emit('success', result)
|
||||
emit('update:visible', false)
|
||||
} catch (e: any) {
|
||||
Message.error(e?.message || t('order.myOrders.drawer.bindSnapshot.failed'))
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
emit('update:visible', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ModalPro
|
||||
:visible="visible"
|
||||
:title="title"
|
||||
:width="520"
|
||||
:mask-closable="false"
|
||||
:confirm-loading="submitting"
|
||||
@register="registerModal"
|
||||
@update:visible="onUpdateVisible"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<p class="bind-snapshot-hint">{{ t('order.myOrders.drawer.bindSnapshot.hint') }}</p>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<div class="bind-snapshot-modal">
|
||||
<div class="bind-snapshot-modal__field">
|
||||
<div class="bind-snapshot-modal__label">
|
||||
{{ t('order.myOrders.drawer.bindSnapshot.rulesVersion') }}
|
||||
</div>
|
||||
<div class="bind-snapshot-modal__current">
|
||||
{{ t('order.myOrders.drawer.bindSnapshot.boundAs', {
|
||||
version: currentRulesVersion || '--',
|
||||
}) }}
|
||||
</div>
|
||||
<a-select
|
||||
v-model="formState.rulesVersion"
|
||||
allow-clear
|
||||
allow-search
|
||||
:options="rulesOptions"
|
||||
:placeholder="t('order.myOrders.drawer.bindSnapshot.keepUnchanged')"
|
||||
/>
|
||||
</div>
|
||||
<div class="bind-snapshot-modal__field">
|
||||
<div class="bind-snapshot-modal__label">
|
||||
{{ t('order.myOrders.drawer.bindSnapshot.priceVersion') }}
|
||||
</div>
|
||||
<div class="bind-snapshot-modal__current">
|
||||
{{ t('order.myOrders.drawer.bindSnapshot.boundAs', {
|
||||
version: currentPriceVersion || '--',
|
||||
}) }}
|
||||
</div>
|
||||
<a-select
|
||||
v-model="formState.priceVersion"
|
||||
allow-clear
|
||||
allow-search
|
||||
:options="priceOptions"
|
||||
:placeholder="t('order.myOrders.drawer.bindSnapshot.keepUnchanged')"
|
||||
/>
|
||||
</div>
|
||||
<div class="bind-snapshot-modal__checks">
|
||||
<a-checkbox v-model="formState.recalculateReceivable">
|
||||
{{ t('order.myOrders.drawer.bindSnapshot.recalcReceivable') }}
|
||||
</a-checkbox>
|
||||
<a-checkbox v-model="formState.recalculatePayable">
|
||||
{{ t('order.myOrders.drawer.bindSnapshot.recalcPayable') }}
|
||||
</a-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</ModalPro>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.bind-snapshot-hint {
|
||||
margin: 0 0 12px;
|
||||
color: var(--color-text-3);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.bind-snapshot-modal__field {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.bind-snapshot-modal__label {
|
||||
margin-bottom: 4px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
.bind-snapshot-modal__current {
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
.bind-snapshot-modal__checks {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
@ -48,12 +48,14 @@
|
||||
</template>
|
||||
<template v-else-if="activeTab === 'fee'">
|
||||
<a-button
|
||||
v-if="canEditAdditionalServices"
|
||||
class="app-btn-search"
|
||||
:loading="additionalServicesLoading"
|
||||
@click="openAdditionalServicesModal"
|
||||
>
|
||||
{{ $t('order.myOrders.drawer.actions.additionalServices') }}
|
||||
<template v-if="orderAdditionalServices.length">
|
||||
({{ orderAdditionalServices.length }})
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button class="app-btn-search" @click="openAddFeeModal">
|
||||
{{ $t('order.myOrders.drawer.actions.addFee') }}
|
||||
@ -72,6 +74,9 @@
|
||||
>
|
||||
{{ $t('order.myOrders.drawer.actions.recalculatePayable') }}
|
||||
</a-button>
|
||||
<a-button class="app-btn-search" @click="openBindSnapshotVersionsModal">
|
||||
{{ $t('order.myOrders.drawer.actions.bindSnapshotVersions') }}
|
||||
</a-button>
|
||||
</template>
|
||||
<template v-else-if="activeTab === 'track'">
|
||||
<span class="toolbar-meta">
|
||||
@ -494,6 +499,44 @@
|
||||
|
||||
<!-- 应收应付 -->
|
||||
<div v-if="activeTab === 'fee'" class="tab-panel fee-info">
|
||||
<div v-if="boundRulesVersion || boundPriceVersion" class="fee-snapshot-versions">
|
||||
<span class="fee-snapshot-versions__item">
|
||||
{{ $t('order.myOrders.drawer.fee.rulesVersion') }}:{{ boundRulesVersion || '--' }}
|
||||
</span>
|
||||
<span class="fee-snapshot-versions__item">
|
||||
{{ $t('order.myOrders.drawer.fee.priceVersion') }}:{{ boundPriceVersion || '--' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="fee-additional-services">
|
||||
<span class="fee-additional-services__label">
|
||||
{{ $t('order.myOrders.drawer.additionalServices') }}:
|
||||
</span>
|
||||
<a-space v-if="orderAdditionalServices.length" wrap :size="4">
|
||||
<a-tag
|
||||
v-for="code in orderAdditionalServices"
|
||||
:key="code"
|
||||
size="small"
|
||||
color="arcoblue"
|
||||
>
|
||||
{{ labelAdditionalService(code) }}
|
||||
</a-tag>
|
||||
</a-space>
|
||||
<span v-else class="fee-additional-services__empty">
|
||||
{{ $t('order.myOrders.drawer.fee.additionalServicesNone') }}
|
||||
</span>
|
||||
<a-button
|
||||
type="text"
|
||||
size="mini"
|
||||
class="fee-additional-services__edit"
|
||||
@click="openAdditionalServicesModal"
|
||||
>
|
||||
{{
|
||||
canEditAdditionalServices
|
||||
? $t('order.myOrders.drawer.fee.additionalServicesManage')
|
||||
: $t('order.myOrders.drawer.fee.additionalServicesView')
|
||||
}}
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="fee-summary">
|
||||
<button
|
||||
type="button"
|
||||
@ -826,6 +869,16 @@
|
||||
@success="handleAddFeeSuccess"
|
||||
/>
|
||||
|
||||
<BindSnapshotVersionsModal
|
||||
v-if="bindSnapshotVersionsVisible && orderId != null"
|
||||
v-model:visible="bindSnapshotVersionsVisible"
|
||||
:order-id="Number(orderId)"
|
||||
:sales-product-id="bindSnapshotSalesProductId"
|
||||
:current-rules-version="boundRulesVersion"
|
||||
:current-price-version="boundPriceVersion"
|
||||
@success="handleBindSnapshotVersionsSuccess"
|
||||
/>
|
||||
|
||||
<a-modal
|
||||
v-model:visible="declarationSkuPreviewVisible"
|
||||
:title="$t('order.myOrders.drawer.declarationSkuImage')"
|
||||
@ -850,28 +903,89 @@
|
||||
|
||||
<a-modal
|
||||
v-model:visible="additionalServicesModalVisible"
|
||||
:title="$t('order.myOrders.drawer.fee.additionalServicesTitle')"
|
||||
:title="
|
||||
canEditAdditionalServices
|
||||
? $t('order.myOrders.drawer.fee.additionalServicesManageTitle')
|
||||
: $t('order.myOrders.drawer.fee.additionalServicesViewTitle')
|
||||
"
|
||||
:ok-loading="additionalServicesSaving"
|
||||
:ok-text="
|
||||
canEditAdditionalServices
|
||||
? $t('order.myOrders.drawer.fee.additionalServicesSave')
|
||||
: $t('order.myOrders.drawer.fee.additionalServicesClose')
|
||||
"
|
||||
:hide-cancel="!canEditAdditionalServices"
|
||||
unmount-on-close
|
||||
@ok="handleAdditionalServicesSubmit"
|
||||
>
|
||||
<a-spin :loading="additionalServicesLoading">
|
||||
<a-checkbox-group
|
||||
v-if="productAdditionalServiceOptions.length"
|
||||
v-model="selectedAdditionalServices"
|
||||
>
|
||||
<a-checkbox
|
||||
v-for="opt in productAdditionalServiceOptions"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
<p class="additional-services-hint">
|
||||
{{
|
||||
canEditAdditionalServices
|
||||
? $t('order.myOrders.drawer.fee.additionalServicesHint')
|
||||
: $t('order.myOrders.drawer.fee.additionalServicesPaidHint')
|
||||
}}
|
||||
</p>
|
||||
<div class="additional-services-current">
|
||||
<div class="additional-services-current__label">
|
||||
{{ $t('order.myOrders.drawer.fee.additionalServicesCurrent') }}
|
||||
</div>
|
||||
<a-space v-if="orderAdditionalServices.length" wrap :size="4">
|
||||
<a-tag
|
||||
v-for="code in orderAdditionalServices"
|
||||
:key="`cur-${code}`"
|
||||
size="small"
|
||||
color="arcoblue"
|
||||
>
|
||||
{{ labelAdditionalService(code) }}
|
||||
</a-tag>
|
||||
</a-space>
|
||||
<span v-else class="additional-services-current__empty">
|
||||
{{ $t('order.myOrders.drawer.fee.additionalServicesNone') }}
|
||||
</span>
|
||||
</div>
|
||||
<template v-if="canEditAdditionalServices">
|
||||
<div
|
||||
v-if="visibleOrphanAdditionalServices.length"
|
||||
class="additional-services-orphan"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
<a-empty
|
||||
v-else
|
||||
:description="$t('order.myOrders.drawer.fee.additionalServicesEmpty')"
|
||||
/>
|
||||
<div class="additional-services-orphan__label">
|
||||
{{ $t('order.myOrders.drawer.fee.additionalServicesOrphan') }}
|
||||
</div>
|
||||
<a-space wrap :size="4">
|
||||
<a-tag
|
||||
v-for="code in visibleOrphanAdditionalServices"
|
||||
:key="`orphan-${code}`"
|
||||
size="small"
|
||||
color="orangered"
|
||||
closable
|
||||
@close="dismissOrphanAdditionalService(code)"
|
||||
>
|
||||
{{ labelAdditionalService(code) }}
|
||||
</a-tag>
|
||||
</a-space>
|
||||
</div>
|
||||
<div class="additional-services-options-label">
|
||||
{{ $t('order.myOrders.drawer.fee.additionalServicesPick') }}
|
||||
</div>
|
||||
<a-checkbox-group
|
||||
v-if="productAdditionalServiceOptions.length"
|
||||
v-model="selectedAdditionalServices"
|
||||
class="additional-services-checkbox-group"
|
||||
>
|
||||
<a-checkbox
|
||||
v-for="opt in productAdditionalServiceOptions"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
<a-empty
|
||||
v-else
|
||||
:description="$t('order.myOrders.drawer.fee.additionalServicesEmpty')"
|
||||
/>
|
||||
</template>
|
||||
</a-spin>
|
||||
</a-modal>
|
||||
</template>
|
||||
@ -900,6 +1014,7 @@ import { getBooleanTagStyle } from '@/utils/statusTag';
|
||||
import { resolveOmsAssetUrl } from '@/api/oms/common';
|
||||
import SlidingTabs from '@/components/SlidingTabs/index.vue';
|
||||
import AddOrderFeeModal from './AddOrderFeeModal.vue';
|
||||
import BindSnapshotVersionsModal from './BindSnapshotVersionsModal.vue';
|
||||
import AllocationTraceModal from './AllocationTraceModal.vue';
|
||||
import OrderStockBindTab from '../../myOrders/components/OrderStockBindTab.vue';
|
||||
import OrderAttachmentsTab from '../../components/OrderAttachmentsTab.vue';
|
||||
@ -949,6 +1064,8 @@ const weightSaving = ref(false);
|
||||
const recalcReceivableLoading = ref(false);
|
||||
const recalcPayableLoading = ref(false);
|
||||
const addFeeModalVisible = ref(false);
|
||||
const bindSnapshotVersionsVisible = ref(false);
|
||||
const bindSnapshotSalesProductId = ref<number | string | null>(null);
|
||||
const allocationTraceVisible = ref(false);
|
||||
const addFeeDefaultType = ref<'receivable' | 'payable'>('receivable');
|
||||
const additionalServicesModalVisible = ref(false);
|
||||
@ -1288,6 +1405,8 @@ const orderBasicFields = mergeOrderBasicFields([
|
||||
{ key: 'productName', labelKey: 'order.myOrders.drawer.productName' },
|
||||
{ key: 'channelCode', labelKey: 'order.myOrders.drawer.channelCode' },
|
||||
{ key: 'channelName', labelKey: 'order.myOrders.drawer.channelName' },
|
||||
{ key: 'downstreamSupplierName', labelKey: 'order.myOrders.drawer.downstreamSupplierName' },
|
||||
{ key: 'downstreamSupplierCode', labelKey: 'order.myOrders.drawer.downstreamSupplierCode' },
|
||||
{ key: 'consigneeCountry', labelKey: 'order.myOrders.drawer.consigneeCountry' },
|
||||
{ key: 'consigneeCountryName', labelKey: 'order.myOrders.drawer.consigneeCountryName' },
|
||||
{ key: 'cargoAttribute', labelKey: 'order.myOrders.drawer.cargoAttribute' },
|
||||
@ -1849,26 +1968,27 @@ const orderAdditionalServices = computed(() =>
|
||||
)
|
||||
);
|
||||
|
||||
const orderHasAdditionalServicesField = computed(() => {
|
||||
const o = detail.value;
|
||||
if (!o) return false;
|
||||
return (
|
||||
Object.prototype.hasOwnProperty.call(o, 'additionalServices') ||
|
||||
Object.prototype.hasOwnProperty.call(o, 'AdditionalServices') ||
|
||||
o.additionalServices != null ||
|
||||
o.AdditionalServices != null
|
||||
);
|
||||
});
|
||||
|
||||
const isOrderUnpaid = computed(() => {
|
||||
const v = detail.value?.isPaid ?? detail.value?.IsPaid;
|
||||
return !(v === true || v === 1 || v === '1');
|
||||
});
|
||||
|
||||
const canEditAdditionalServices = computed(
|
||||
() =>
|
||||
isOrderUnpaid.value &&
|
||||
(productHasAdditionalServices.value || orderHasAdditionalServicesField.value)
|
||||
/** 未支付可勾选/取消附加服务;已支付仅可查看。 */
|
||||
const canEditAdditionalServices = computed(() => isOrderUnpaid.value);
|
||||
|
||||
/** 订单上有、但当前产品配置已不包含的附加服务(保存时只能去掉,不能再写回)。 */
|
||||
const orphanAdditionalServices = computed(() => {
|
||||
const allowed = new Set(productAdditionalServiceOptions.value.map((o) => o.value));
|
||||
if (!allowed.size) {
|
||||
return [...orderAdditionalServices.value];
|
||||
}
|
||||
return orderAdditionalServices.value.filter((code) => !allowed.has(code));
|
||||
});
|
||||
|
||||
const dismissedOrphanServices = ref<string[]>([]);
|
||||
|
||||
const visibleOrphanAdditionalServices = computed(() =>
|
||||
orphanAdditionalServices.value.filter((code) => !dismissedOrphanServices.value.includes(code))
|
||||
);
|
||||
|
||||
const grossProfitMarginText = computed(() => {
|
||||
@ -2057,25 +2177,45 @@ async function refreshProductAdditionalServicesCapability() {
|
||||
async function openAdditionalServicesModal() {
|
||||
additionalServicesModalVisible.value = true;
|
||||
additionalServicesLoading.value = true;
|
||||
dismissedOrphanServices.value = [];
|
||||
try {
|
||||
await refreshProductAdditionalServicesCapability();
|
||||
const allowed = new Set(productAdditionalServiceOptions.value.map((o) => o.value));
|
||||
selectedAdditionalServices.value = orderAdditionalServices.value.filter((c) =>
|
||||
allowed.size ? allowed.has(c) : true
|
||||
allowed.size ? allowed.has(c) : false
|
||||
);
|
||||
} finally {
|
||||
additionalServicesLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function dismissOrphanAdditionalService(code: string) {
|
||||
if (!dismissedOrphanServices.value.includes(code)) {
|
||||
dismissedOrphanServices.value = [...dismissedOrphanServices.value, code];
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAdditionalServicesSubmit() {
|
||||
if (!canEditAdditionalServices.value) {
|
||||
additionalServicesModalVisible.value = false;
|
||||
return;
|
||||
}
|
||||
const id = orderId.value;
|
||||
if (id == null) return Promise.reject(new Error('missing order'));
|
||||
additionalServicesSaving.value = true;
|
||||
try {
|
||||
const allowed = new Set(productAdditionalServiceOptions.value.map((o) => o.value));
|
||||
const next = selectedAdditionalServices.value.filter((c) => allowed.has(c));
|
||||
if (orphanAdditionalServices.value.length) {
|
||||
Message.info(
|
||||
t('order.myOrders.drawer.fee.additionalServicesOrphanCleared', {
|
||||
names: orphanAdditionalServices.value.map((c) => labelAdditionalService(c)).join('、'),
|
||||
})
|
||||
);
|
||||
}
|
||||
const result = await postOrderAdditionalServicesUpdate({
|
||||
id,
|
||||
additionalServices: [...selectedAdditionalServices.value],
|
||||
additionalServices: next,
|
||||
});
|
||||
applyDetailFromApi(result);
|
||||
additionalServicesModalVisible.value = false;
|
||||
@ -2437,6 +2577,36 @@ async function handleRecalculatePayable() {
|
||||
}
|
||||
}
|
||||
|
||||
const boundRulesVersion = computed(() => {
|
||||
const o = detail.value;
|
||||
if (!o) return '';
|
||||
return String(o.salesProductRulesVersion ?? o.SalesProductRulesVersion ?? '').trim();
|
||||
});
|
||||
|
||||
const boundPriceVersion = computed(() => {
|
||||
const o = detail.value;
|
||||
if (!o) return '';
|
||||
return String(o.salesProductPriceVersion ?? o.SalesProductPriceVersion ?? '').trim();
|
||||
});
|
||||
|
||||
async function openBindSnapshotVersionsModal() {
|
||||
const code = String(detail.value?.productCode ?? detail.value?.ProductCode ?? '').trim();
|
||||
if (!code) {
|
||||
Message.warning(t('order.myOrders.drawer.bindSnapshot.noProduct'));
|
||||
return;
|
||||
}
|
||||
bindSnapshotSalesProductId.value = await resolveProductIdByCode(code);
|
||||
if (bindSnapshotSalesProductId.value == null) {
|
||||
Message.warning(t('order.myOrders.drawer.bindSnapshot.noProduct'));
|
||||
return;
|
||||
}
|
||||
bindSnapshotVersionsVisible.value = true;
|
||||
}
|
||||
|
||||
function handleBindSnapshotVersionsSuccess(result: any) {
|
||||
applyDetailFromApi(result);
|
||||
}
|
||||
|
||||
async function loadOperationLogs() {
|
||||
const id = orderId.value;
|
||||
if (!id) {
|
||||
@ -3146,6 +3316,49 @@ defineExpose({
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.fee-additional-services {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
background: var(--color-fill-1);
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.fee-additional-services__label {
|
||||
color: var(--color-text-2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fee-additional-services__empty {
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
.fee-additional-services__edit {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.fee-snapshot-versions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px 20px;
|
||||
margin-bottom: 12px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
background: var(--color-fill-1);
|
||||
font-size: 12px;
|
||||
color: var(--color-text-2);
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.fee-snapshot-versions__item {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.fee-summary {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
@ -3733,4 +3946,45 @@ defineExpose({
|
||||
color: var(--app-primary-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.additional-services-hint {
|
||||
margin: 0 0 12px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
.additional-services-current {
|
||||
margin-bottom: 16px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 4px;
|
||||
background: var(--color-fill-1);
|
||||
}
|
||||
|
||||
.additional-services-current__label,
|
||||
.additional-services-orphan__label,
|
||||
.additional-services-options-label {
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
.additional-services-current__empty {
|
||||
font-size: 13px;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
.additional-services-orphan {
|
||||
margin-bottom: 16px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 125, 0, 0.08);
|
||||
}
|
||||
|
||||
.additional-services-checkbox-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -131,7 +131,7 @@
|
||||
name: 'WaybillManage',
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const { t, te } = useI18n();
|
||||
const { tableContainerRef, tableHeight } = useTableContainerHeight();
|
||||
const { customerOptions, withCustomerId } = useWarehouseAdminCustomer();
|
||||
|
||||
@ -248,6 +248,10 @@
|
||||
productName: productName ?? productCode ?? '',
|
||||
channelCode: channelCode ?? '',
|
||||
channelName: channelName ?? channelCode ?? '',
|
||||
downstreamSupplierName:
|
||||
pick(raw, 'downstreamSupplierName', 'DownstreamSupplierName') ?? '',
|
||||
downstreamSupplierCode:
|
||||
pick(raw, 'downstreamSupplierCode', 'DownstreamSupplierCode') ?? '',
|
||||
isPaid: boolish(isPaidRaw),
|
||||
isCod: boolish(pick(raw, 'isCod', 'IsCod')),
|
||||
isIntercept: boolish(pick(raw, 'isIntercept', 'IsIntercept')),
|
||||
@ -1567,7 +1571,7 @@
|
||||
]);
|
||||
return withSortableColumns(
|
||||
withServerFilterColumns(
|
||||
buildHotColumns(columnCatalog.value, activeColumnKeys.value, t),
|
||||
buildHotColumns(columnCatalog.value, activeColumnKeys.value, t, te),
|
||||
ORDER_SERVER_FILTER_KEYS,
|
||||
(col) => {
|
||||
if (numberKeys.has(col.key)) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user