- 粘贴 JSON 或 XML 响应示例,点击「解析并导入」生成响应参数树。
+ 粘贴 JSON 或 XML 响应示例,点击「解析并导入」将全量替换当前响应参数树(先清空再导入)。
diff --git a/src/locale/en-US.ts b/src/locale/en-US.ts
index a532149..fff87ab 100644
--- a/src/locale/en-US.ts
+++ b/src/locale/en-US.ts
@@ -2152,6 +2152,9 @@ export default {
'order.myOrders.drawer.operationLog.after': 'After',
'order.myOrders.drawer.operationLog.noChanges': 'No field changes',
'order.myOrders.drawer.operationLog.operator': 'Operator',
+ 'order.myOrders.drawer.operationLog.time': 'Time',
+ 'order.myOrders.drawer.operationLog.action': 'Action',
+ 'order.myOrders.drawer.operationLog.content': 'Content',
'order.myOrders.drawer.tabs.issueHandling': 'Issue handling',
'order.myOrders.drawer.issueHandling.pendingCount': 'Pending {count}',
'order.myOrders.drawer.issueHandling.processedCount': 'Processed {count}',
@@ -2384,6 +2387,8 @@ export default {
'order.myOrders.drawer.actions.edit': 'Edit',
'order.myOrders.drawer.actions.save': 'Save',
'order.myOrders.drawer.actions.cancel': 'Cancel',
+ 'order.myOrders.drawer.editAfterInboundForbidden':
+ 'Order details cannot be edited after the parcel is received',
'order.myOrders.drawer.actions.recalculateReceivable': 'Recalc Receivable',
'order.myOrders.drawer.actions.recalculatePayable': 'Recalc Payable',
'order.myOrders.drawer.actions.bindSnapshotVersions': 'Switch Versions',
@@ -2406,7 +2411,7 @@ export default {
'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.actions.additionalServices': 'Add service',
'order.myOrders.drawer.editBasicTitle': 'Edit Order Details',
'order.myOrders.drawer.editWeightTitle': 'Edit Weight & Dimensions',
'order.myOrders.drawer.message.saveSuccess': 'Saved successfully',
@@ -3956,9 +3961,9 @@ export default {
'operation.inbound.pageTitle': 'Waybill Information',
'operation.inbound.broadcastSettings': 'Broadcast Settings',
'operation.inbound.scan.title': 'Scan inbound',
- 'operation.inbound.tip': 'Scan to verify product and destCountry, then enter received weight and dimensions before inbound.',
+ 'operation.inbound.tip': 'Scan to load order info, enter weight and dimensions, then inbound (Enter submits when metrics are filled).',
'operation.inbound.preview.title': 'Verify',
- 'operation.inbound.preview.empty': 'Scan a number to show order info and enter weight / dimensions',
+ 'operation.inbound.preview.empty': 'Scan or enter a number and press Enter to load order info',
'operation.inbound.form.scanNumber': 'Scan Number:',
'operation.inbound.form.receivedWeight': 'Received Weight',
'operation.inbound.form.dimensions': 'L × W × H',
@@ -4008,9 +4013,9 @@ export default {
'operation.outbound.pageTitle': 'Waybill Information',
'operation.outbound.broadcastSettings': 'Broadcast Settings',
'operation.outbound.scan.title': 'Scan outbound',
- 'operation.outbound.tip': 'Scan waybill to verify product, destCountry and channel. Weight and dimensions are optional.',
+ 'operation.outbound.tip': 'Scan to load product, destCountry and channel, enter weight and dimensions, then outbound (Enter submits when metrics are filled).',
'operation.outbound.preview.title': 'Verify',
- 'operation.outbound.preview.empty': 'Scan a number to show order info; weight / dimensions are optional',
+ 'operation.outbound.preview.empty': 'Scan or enter a number and press Enter to load order info',
'operation.outbound.form.scanNumber': 'Scan Number:',
'operation.outbound.form.shipWeight': 'Shipped weight',
'operation.outbound.form.dimensions': 'L × W × H',
@@ -4042,6 +4047,7 @@ export default {
'operation.outbound.table.operateTime': 'Operate Time',
'operation.outbound.table.operator': 'Operator',
'operation.outbound.message.waybillNumberRequired': 'Please enter or scan waybill number',
+ 'operation.outbound.message.metricsRequired': 'Please enter ship weight and dimensions',
'operation.outbound.message.outboundSuccess': 'Outbound successful',
'operation.outbound.message.outboundFailed': 'Outbound failed',
'operation.outbound.message.loadRecentFailed': 'Failed to load recent outbound records',
@@ -5040,6 +5046,8 @@ export default {
'channel.labelDesign.actions.search': 'Search',
'channel.labelDesign.actions.create': 'New template',
'channel.labelDesign.actions.edit': 'Design',
+ 'channel.labelDesign.actions.rename': 'Rename',
+ 'channel.labelDesign.actions.editTemplate': 'Edit',
'channel.labelDesign.actions.delete': 'Delete',
'channel.labelDesign.actions.refresh': 'Refresh',
'channel.labelDesign.actions.back': 'Back',
@@ -5049,6 +5057,7 @@ export default {
'channel.labelDesign.actions.openTemplate': 'Open template',
'channel.labelDesign.actions.open': 'Open',
'channel.labelDesign.actions.startDesign': 'Start design',
+ 'channel.labelDesign.actions.confirm': 'OK',
'channel.labelDesign.actions.cancel': 'Cancel',
'channel.labelDesign.loadingList': 'Loading templates…',
'channel.labelDesign.emptySelect': 'Select a template on the left, or create one',
@@ -5059,7 +5068,12 @@ export default {
'channel.labelDesign.message.saveSuccess': 'Template saved',
'channel.labelDesign.message.saveFailed': 'Save failed',
'channel.labelDesign.message.deleteSuccess': 'Deleted',
+ 'channel.labelDesign.message.renameSuccess': 'Name updated',
+ 'channel.labelDesign.message.renameFailed': 'Failed to rename',
+ 'channel.labelDesign.message.editSuccess': 'Updated',
+ 'channel.labelDesign.message.editFailed': 'Update failed',
'channel.labelDesign.message.nameRequired': 'Template name is required',
+ 'channel.labelDesign.message.typeRequired': 'Please select a template type',
'channel.labelDesign.message.alreadyOpen': 'This template is already open',
'channel.labelDesign.message.openSuccess': 'Opened: {name}',
'channel.labelDesign.fieldDictionary': 'Order fields',
diff --git a/src/locale/zh-CN.ts b/src/locale/zh-CN.ts
index 4b1a7c6..6f3c072 100644
--- a/src/locale/zh-CN.ts
+++ b/src/locale/zh-CN.ts
@@ -2013,6 +2013,9 @@ export default {
'order.myOrders.drawer.operationLog.after': '修改后',
'order.myOrders.drawer.operationLog.noChanges': '无字段变更',
'order.myOrders.drawer.operationLog.operator': '操作人',
+ 'order.myOrders.drawer.operationLog.time': '时间',
+ 'order.myOrders.drawer.operationLog.action': '操作动作',
+ 'order.myOrders.drawer.operationLog.content': '内容',
'order.myOrders.drawer.tabs.issueHandling': '问题处理',
'order.myOrders.drawer.issueHandling.pendingCount': '待处理 {count}',
'order.myOrders.drawer.issueHandling.processedCount': '已处理 {count}',
@@ -2245,11 +2248,12 @@ export default {
'order.myOrders.drawer.actions.edit': '编辑',
'order.myOrders.drawer.actions.save': '保存',
'order.myOrders.drawer.actions.cancel': '取消',
+ 'order.myOrders.drawer.editAfterInboundForbidden': '包裹入库后不可再编辑订单详情',
'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.actions.additionalServices': '添加服务',
'order.myOrders.drawer.fee.rulesVersion': '规则版本',
'order.myOrders.drawer.fee.priceVersion': '价格版本',
'order.myOrders.drawer.bindSnapshot.title': '切换价格/规则版本',
@@ -3985,9 +3989,9 @@ export default {
'operation.inbound.pageTitle': '运单信息',
'operation.inbound.broadcastSettings': '播报设置',
'operation.inbound.scan.title': '扫码入库',
- 'operation.inbound.tip': '扫描单号核对产品与国家后,填写收货实重与尺寸再入库,避免录错。',
+ 'operation.inbound.tip': '扫描单号自动带出订单信息,填写收货实重与尺寸后点入库(件重尺已填时可回车直接入库)。',
'operation.inbound.preview.title': '核对信息',
- 'operation.inbound.preview.empty': '扫描单号后显示订单信息,并填写收货实重与尺寸',
+ 'operation.inbound.preview.empty': '扫描或输入单号并回车,自动带出订单信息',
'operation.inbound.form.scanNumber': '扫描单号:',
'operation.inbound.form.receivedWeight': '收货实重',
'operation.inbound.form.dimensions': '长*宽*高',
@@ -4037,9 +4041,9 @@ export default {
'operation.outbound.pageTitle': '运单信息',
'operation.outbound.broadcastSettings': '播报设置',
'operation.outbound.scan.title': '扫码出库',
- 'operation.outbound.tip': '扫描运单号核对产品、国家与渠道后,可按需补录发货实重尺寸再出库。',
+ 'operation.outbound.tip': '扫描单号自动带出产品、国家与渠道,填写发货实重与尺寸后点出库(件重尺已填时可回车直接出库)。',
'operation.outbound.preview.title': '核对信息',
- 'operation.outbound.preview.empty': '扫描单号后显示订单信息,可填写发货实重与尺寸',
+ 'operation.outbound.preview.empty': '扫描或输入单号并回车,自动带出订单信息',
'operation.outbound.form.scanNumber': '扫描单号:',
'operation.outbound.form.shipWeight': '发货实重',
'operation.outbound.form.dimensions': '长*宽*高',
@@ -4071,6 +4075,7 @@ export default {
'operation.outbound.table.operateTime': '操作时间',
'operation.outbound.table.operator': '操作人',
'operation.outbound.message.waybillNumberRequired': '请输入或扫描运单号',
+ 'operation.outbound.message.metricsRequired': '请填写发货实重与长宽高',
'operation.outbound.message.outboundSuccess': '出库成功',
'operation.outbound.message.outboundFailed': '出库失败',
'operation.outbound.message.loadRecentFailed': '加载最近出库记录失败',
@@ -5068,6 +5073,8 @@ export default {
'channel.labelDesign.actions.search': '查询',
'channel.labelDesign.actions.create': '新建模板',
'channel.labelDesign.actions.edit': '设计',
+ 'channel.labelDesign.actions.rename': '编辑名称',
+ 'channel.labelDesign.actions.editTemplate': '编辑',
'channel.labelDesign.actions.delete': '删除',
'channel.labelDesign.actions.refresh': '刷新列表',
'channel.labelDesign.actions.back': '返回列表',
@@ -5077,6 +5084,7 @@ export default {
'channel.labelDesign.actions.openTemplate': '打开模板',
'channel.labelDesign.actions.open': '打开',
'channel.labelDesign.actions.startDesign': '开始设计',
+ 'channel.labelDesign.actions.confirm': '确定',
'channel.labelDesign.actions.cancel': '取消',
'channel.labelDesign.loadingList': '正在加载模板…',
'channel.labelDesign.emptySelect': '请在左侧选择模板,或新建一个',
@@ -5087,7 +5095,12 @@ export default {
'channel.labelDesign.message.saveSuccess': '模板已保存',
'channel.labelDesign.message.saveFailed': '保存失败',
'channel.labelDesign.message.deleteSuccess': '已删除',
+ 'channel.labelDesign.message.renameSuccess': '名称已更新',
+ 'channel.labelDesign.message.renameFailed': '修改名称失败',
+ 'channel.labelDesign.message.editSuccess': '已更新',
+ 'channel.labelDesign.message.editFailed': '修改失败',
'channel.labelDesign.message.nameRequired': '请填写模板名称',
+ 'channel.labelDesign.message.typeRequired': '请选择标签类型',
'channel.labelDesign.message.alreadyOpen': '该模板已在设计器中',
'channel.labelDesign.message.openSuccess': '已打开:{name}',
'channel.labelDesign.fieldDictionary': '订单字段',
diff --git a/src/main.ts b/src/main.ts
index af0cf6b..05d3af2 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -3,11 +3,15 @@ import ArcoVue from '@arco-design/web-vue';
import ArcoVueIcon from '@arco-design/web-vue/es/icon';
// eslint-disable-next-line import/no-unresolved
import 'uno.css';
+import { installGlobalMessageDefaults } from '@/utils/message';
import router from './router';
import store, { useAppStore } from './store';
import i18n, { initPreferredLocale, ensureFullLocaleMessages } from './locale';
import directive from './directive';
import globalProperties from './plugins/globalProperties';
+
+// 全局 Message:可手动关闭;错误/警告默认 8s
+installGlobalMessageDefaults();
import {
bindHeavyPluginsContext,
ensureHeavyPlugins,
diff --git a/src/router/dynamicRouter.ts b/src/router/dynamicRouter.ts
index 3fc6f12..dae77fe 100644
--- a/src/router/dynamicRouter.ts
+++ b/src/router/dynamicRouter.ts
@@ -41,10 +41,7 @@ export const initDynamicRouter = async (options?: {
/** 路由初始化错误 */
const routerError = (isNotice: boolean = true, title: string = '无权限访问', message: string = '当前账号无任何菜单权限,请联系系统管理员!') => {
if (isNotice) {
- Message.warning({
- content: message,
- duration: 3000
- });
+ Message.warning(message);
}
const loginType = rbacUserStore.resolveLoginTypeForRedirect();
const currentPath = router.currentRoute.value.fullPath;
diff --git a/src/utils/interfaceParameterModel.ts b/src/utils/interfaceParameterModel.ts
index ccf2971..93f0316 100644
--- a/src/utils/interfaceParameterModel.ts
+++ b/src/utils/interfaceParameterModel.ts
@@ -70,14 +70,16 @@ function flattenTree(arr: any[]): any[] {
return flattenTreeTableRows(arr);
}
-/** 读取树表完整扁平数据(避免 fullData 仅返回根节点导致子级丢失) */
+/**
+ * 读取树表完整扁平数据。
+ * 已传入表格 fullData(含清空后的 [])时必须以表格为准;
+ * 旧逻辑在 grid 为空时回退 props,会把已删除行「复活」,导致清空后保存仍报重名。
+ */
export function getTreeTableFlatRows(fromGrid?: any[], propsData?: any[]): any[] {
- const flatFromGrid = flattenTreeTableRows(fromGrid || []);
- const propsFlat = Array.isArray(propsData) ? propsData : [];
- if (flatFromGrid.length >= propsFlat.length) {
- return flatFromGrid.length ? flatFromGrid : [...propsFlat];
+ if (Array.isArray(fromGrid)) {
+ return flattenTreeTableRows(fromGrid);
}
- return propsFlat.length ? [...propsFlat] : flatFromGrid;
+ return flattenTreeTableRows(Array.isArray(propsData) ? propsData : []);
}
function getRowSeq(row: any, tableRef: any): string {
@@ -278,11 +280,33 @@ function mapTreeChangeRows(
return [...install, ...remove, ...update];
}
+function mapTreeDeleteFromBaseline(
+ serverId: number,
+ baseline: NormalizedParamRow,
+ paraKind: number,
+) {
+ return {
+ optionType: 2,
+ id: serverId,
+ pId: baseline.pId,
+ alias: baseline.alias || baseline.name,
+ name: baseline.name,
+ cnName: baseline.cnName,
+ type: baseline.type,
+ isRequired: baseline.isRequired,
+ sort: baseline.sort || '0',
+ description: baseline.description,
+ paraKind,
+ };
+}
+
/**
* 编辑保存时对齐后端 MaintainParametersAsync:
* - 仅提交增/删/改增量
* - 未变更的已有参数不传(后端会自动补全 DB 节点)
* - 假 id + 已存在路径 → 按服务端 id 走修改,避免与 DB 补全节点重复
+ * - 解析全量导入会 reloadData(清空 removeRecords)且行 id 全是新假 id:走「删光快照 + 整树新增」
+ * - VXE removeRecords 丢失时,按「快照有、当前表无」补删(与 FormKV 一致)
*/
function buildTreeParameterList(
actionRows: { insertRecords?: any[]; removeRecords?: any[]; updateRecords?: any[] },
@@ -298,55 +322,90 @@ function buildTreeParameterList(
changes.filter((c) => c.optionType === 2).map((c) => Number(c.id)),
);
+ const flatCurrent = flattenTree(treeData).filter(
+ (item) => item?.name && String(item.name).trim() !== '',
+ );
+
if (!isEditMode) {
if (changes.length > 0) return changes;
- return flattenTree(treeData)
- .filter((item) => item?.name && String(item.name).trim() !== '')
- .map((item) => mapTreeRow(item, 1, tableRef, paraKind));
+ return flatCurrent.map((item) => mapTreeRow(item, 1, tableRef, paraKind));
+ }
+
+ // 全量替换:当前行没有任何加载时的服务端 id(解析导入假 id / 或已清空整表)
+ const preservesAnyServerId = flatCurrent.some((item) =>
+ original.ids.has(normalizeRowId(item)),
+ );
+ if (original.ids.size > 0 && !preservesAnyServerId) {
+ const result: any[] = [];
+ original.ids.forEach((serverId) => {
+ const baseline = original.rowById.get(serverId);
+ if (!baseline?.name) return;
+ result.push(mapTreeDeleteFromBaseline(serverId, baseline, paraKind));
+ });
+ flatCurrent.forEach((item) => {
+ result.push(mapTreeRow(item, 1, tableRef, paraKind));
+ });
+ return result;
}
const result: any[] = [];
changes.filter((c) => c.optionType === 2).forEach((c) => result.push(c));
+ const presentServerIds = new Set
();
- flattenTree(treeData)
- .filter((item) => item?.name && String(item.name).trim() !== '')
- .forEach((item) => {
- const id = normalizeRowId(item);
- const pId = Number(item?.pId ?? item?.PId ?? 0);
- const name = String(item?.name ?? item?.Name ?? '').trim();
- const pathKey = buildParamPathKey(pId, name);
+ flatCurrent.forEach((item) => {
+ const id = normalizeRowId(item);
+ const pId = Number(item?.pId ?? item?.PId ?? 0);
+ const name = String(item?.name ?? item?.Name ?? '').trim();
+ const pathKey = buildParamPathKey(pId, name);
- if (removedIds.has(id)) return;
+ if (removedIds.has(id)) return;
- const tracked = changeById.get(id);
- if (tracked && tracked.optionType !== 2) {
- result.push(tracked);
- return;
+ const tracked = changeById.get(id);
+ if (tracked && tracked.optionType !== 2) {
+ result.push(tracked);
+ const trackedServerId = Number(tracked.id);
+ if (trackedServerId > 0 && original.ids.has(trackedServerId)) {
+ presentServerIds.add(trackedServerId);
}
+ return;
+ }
- const serverIdByPath = original.pathToId.get(pathKey);
- const serverId = original.ids.has(id) ? id : serverIdByPath;
- const baseline = serverId ? original.rowById.get(serverId) : undefined;
+ const serverIdByPath = original.pathToId.get(pathKey);
+ const serverId = original.ids.has(id) ? id : serverIdByPath;
+ const baseline = serverId ? original.rowById.get(serverId) : undefined;
- if (serverId && baseline) {
- if (!isSameTreeParamRow(item, baseline)) {
- result.push(mapTreeRow(item, 3, tableRef, paraKind, serverId));
- }
- return;
+ if (serverId && baseline) {
+ presentServerIds.add(serverId);
+ if (!isSameTreeParamRow(item, baseline)) {
+ result.push(mapTreeRow(item, 3, tableRef, paraKind, serverId));
}
+ return;
+ }
- if (!serverId) {
- result.push(mapTreeRow(item, 1, tableRef, paraKind));
- }
- });
+ if (!serverId) {
+ result.push(mapTreeRow(item, 1, tableRef, paraKind));
+ }
+ });
+
+ // VXE removeRecords 丢失时,仍按「原始快照有、当前表无」补删,避免库里残留
+ original.ids.forEach((serverId) => {
+ if (removedIds.has(serverId) || presentServerIds.has(serverId)) return;
+ const baseline = original.rowById.get(serverId);
+ if (!baseline?.name) return;
+ result.push(mapTreeDeleteFromBaseline(serverId, baseline, paraKind));
+ });
return result;
}
-/** 客户端校验:同一 pId 下参数名不可重复(对齐后端 ValidateParameter) */
+/** 客户端校验:同一 pId 下参数名不可重复(对齐后端 ValidateParameter,跳过删除项) */
export function validateTreeParameterPaths(rows: any[]): string | null {
const seen = new Set();
for (const item of flattenTree(rows)) {
+ // 后端 ValidateParameter 会先排除 OptionType.删除;全量替换时「删旧 + 增同名」不能当重复
+ const optionType = Number(item?.optionType ?? item?.OptionType ?? 0);
+ if (optionType === 2) continue;
+
const name = String(item?.name ?? item?.Name ?? '').trim();
if (!name) continue;
const pId = Number(item?.pId ?? item?.PId ?? 0);
diff --git a/src/utils/message.ts b/src/utils/message.ts
index 81f510b..e08adc8 100644
--- a/src/utils/message.ts
+++ b/src/utils/message.ts
@@ -1,18 +1,94 @@
import { Message } from '@arco-design/web-vue';
+export type BaseMessageType = 'success' | 'error' | 'warning' | 'info';
+
+export type BaseMessageOptions = {
+ /** 显示时长 ms */
+ duration?: number;
+ /** 是否显示关闭按钮 */
+ closable?: boolean;
+ /** 悬停时重置倒计时 */
+ resetOnHover?: boolean;
+};
+
+type MessageConfigLike = {
+ content: any;
+ duration?: number;
+ closable?: boolean;
+ resetOnHover?: boolean;
+ [key: string]: any;
+};
+
+/** 全局默认:可手动关闭;错误/警告多停留约 5s(合计 8s) */
+const GLOBAL_MESSAGE_DEFAULTS: Record<
+ 'info' | 'success' | 'warning' | 'error' | 'normal',
+ { duration: number; closable: boolean; resetOnHover: boolean }
+> = {
+ info: { duration: 3000, closable: true, resetOnHover: true },
+ success: { duration: 3000, closable: true, resetOnHover: true },
+ warning: { duration: 8000, closable: true, resetOnHover: true },
+ error: { duration: 8000, closable: true, resetOnHover: true },
+ normal: { duration: 3000, closable: true, resetOnHover: true },
+};
+
+type PatchableType = keyof typeof GLOBAL_MESSAGE_DEFAULTS;
+
+let messageDefaultsInstalled = false;
+
+function mergeMessageConfig(
+ type: PatchableType,
+ config: string | MessageConfigLike,
+): MessageConfigLike {
+ const defaults = GLOBAL_MESSAGE_DEFAULTS[type];
+ if (typeof config === 'string') {
+ return {
+ content: config,
+ ...defaults,
+ };
+ }
+ return {
+ ...defaults,
+ ...config,
+ duration: config.duration ?? defaults.duration,
+ closable: config.closable ?? defaults.closable,
+ resetOnHover: config.resetOnHover ?? defaults.resetOnHover,
+ };
+}
+
+/** 安装全局 Message 默认行为(幂等)。覆盖 Message.* 与 $baseMessage。 */
+export function installGlobalMessageDefaults() {
+ if (messageDefaultsInstalled) return;
+ messageDefaultsInstalled = true;
+
+ (Object.keys(GLOBAL_MESSAGE_DEFAULTS) as PatchableType[]).forEach((type) => {
+ const original = (Message[type] as any).bind(Message);
+
+ (Message as any)[type] = (config: string | MessageConfigLike, appContext?: any) => {
+ return original(mergeMessageConfig(type, config), appContext);
+ };
+ });
+}
+
+// 被任何路径 import 时即安装,保证 request 拦截器等直接 Message.error 也生效
+installGlobalMessageDefaults();
+
/**
- * 全局消息提示工具函数
+ * 全局消息提示
* @param message 消息内容
- * @param type 消息类型 'success' | 'error' | 'warning' | 'info'
+ * @param type 消息类型
+ * @param options 可覆盖全局默认(duration / closable / resetOnHover)
*/
export const baseMessage = (
message: string,
- type: 'success' | 'error' | 'warning' | 'info' = 'info'
+ type: BaseMessageType = 'info',
+ options?: BaseMessageOptions,
) => {
- Message[type](message);
+ Message[type]({
+ content: message,
+ ...options,
+ });
};
-// Export as a global function for direct use
export const $baseMessage = baseMessage;
-export default baseMessage;
\ No newline at end of file
+export default baseMessage;
diff --git a/src/utils/request.ts b/src/utils/request.ts
index 5195767..6354643 100644
--- a/src/utils/request.ts
+++ b/src/utils/request.ts
@@ -12,6 +12,8 @@ import { useUserStore } from '@/store';
import { useRbacUserStore } from '@/store/modules/rbac-user';
import { resolvePostLogoutLogin } from '@/utils/login-portal';
import { rewriteBrowserApiPath } from '@/utils/rewriteBrowserApiPath';
+// 安装全局 Message 默认(可关闭 / 错误加长),须在本文件 Message.* 调用前生效
+import '@/utils/message';
const service: AxiosInstance = axios.create({
baseURL: '/',
@@ -149,10 +151,7 @@ service.interceptors.response.use(
// 根据不同的状态码处理不同的错误
switch (status) {
case 400:
- Message.error({
- content: `请求参数错误: ${data?.message || statusText}`,
- duration: 3000,
- });
+ Message.error(`请求参数错误: ${data?.message || statusText}`);
break;
case 401:
// 401 未授权,登录过期
@@ -162,10 +161,7 @@ service.interceptors.response.use(
if (isRbacApi) {
// RBAC API的401,说明token过期,需要重新登录
- Message.error({
- content: errorMessage,
- duration: 3000,
- });
+ Message.error(errorMessage);
const rbacUserStore = useRbacUserStore();
const loginType = rbacUserStore.resolveLoginTypeForRedirect();
const currentPath = router.currentRoute.value.fullPath;
@@ -184,10 +180,7 @@ service.interceptors.response.use(
}
} else {
// 非RBAC API的401,统一处理为登录失效,跳转到登录页
- Message.error({
- content: errorMessage || '登录已过期,请重新登录',
- duration: 3000,
- });
+ Message.error(errorMessage || '登录已过期,请重新登录');
const rbacUserStore = useRbacUserStore();
const loginType = rbacUserStore.resolveLoginTypeForRedirect();
const currentPath = router.currentRoute.value.fullPath;
@@ -202,41 +195,23 @@ service.interceptors.response.use(
}
break;
case 403:
- Message.error({
- content: `禁止访问: ${data?.message || statusText}`,
- duration: 3000,
- });
+ Message.error(`禁止访问: ${data?.message || statusText}`);
break;
case 404:
- Message.error({
- content: `请求的资源不存在: ${data?.message || statusText}`,
- duration: 3000,
- });
+ Message.error(`请求的资源不存在: ${data?.message || statusText}`);
break;
case 500:
- Message.error({
- content: `服务器内部错误: ${data?.message || statusText}`,
- duration: 3000,
- });
+ Message.error(`服务器内部错误: ${data?.message || statusText}`);
break;
default:
- Message.error({
- content: `请求失败 (${status}): ${data?.message || statusText}`,
- duration: 3000,
- });
+ Message.error(`请求失败 (${status}): ${data?.message || statusText}`);
}
} else if (error.request) {
// 请求已经发出,但没有收到响应
- Message.error({
- content: '网络连接失败,请检查网络设置',
- duration: 3000,
- });
+ Message.error('网络连接失败,请检查网络设置');
} else {
// 请求配置出错
- Message.error({
- content: `请求配置错误: ${error.message}`,
- duration: 3000,
- });
+ Message.error(`请求配置错误: ${error.message}`);
}
return Promise.reject(error);
diff --git a/src/views/api/downstreamInterface/components/ServiceProviderInterfaceModal.vue b/src/views/api/downstreamInterface/components/ServiceProviderInterfaceModal.vue
index 803f641..033ae87 100644
--- a/src/views/api/downstreamInterface/components/ServiceProviderInterfaceModal.vue
+++ b/src/views/api/downstreamInterface/components/ServiceProviderInterfaceModal.vue
@@ -230,6 +230,10 @@
if (!forceRefresh && isInitializing.value && lastInitId.value === currentId) {
return;
}
+ // 已加载同一接口且非强制刷新(来回切 tab):跳过重拉,避免误报未保存
+ if (!forceRefresh && lastInitId.value === currentId && shouldRenderForm.value && !isInitializing.value) {
+ return;
+ }
// 标记正在初始化和当前ID
isInitializing.value = true;
diff --git a/src/views/api/downstreamInterface/comps/ReturnConfig.vue b/src/views/api/downstreamInterface/comps/ReturnConfig.vue
index 41d7491..87b9c41 100644
--- a/src/views/api/downstreamInterface/comps/ReturnConfig.vue
+++ b/src/views/api/downstreamInterface/comps/ReturnConfig.vue
@@ -53,17 +53,29 @@
return row;
}
+ /** SelectPro / 字典 key 常把 number 写成 string,脏检查必须宽松比较 */
+ function sameScalar(a: unknown, b: unknown) {
+ if (a == null && b == null) return true;
+ if (a == null || b == null) return false;
+ return String(a) === String(b);
+ }
+
+ function rowSourceId(row: any): number | null {
+ const id = Number(row?.id);
+ return Number.isFinite(id) && id > 0 ? id : null;
+ }
+
function snapshotSource(rows: any[]) {
const map = new Map();
rows.forEach((row) => {
- if (row.id != null) {
- map.set(row.id, {
- id: row.id,
- configType: row.configType,
- parameterId: row.parameterId,
- value: row.value == null ? '' : String(row.value),
- });
- }
+ const id = rowSourceId(row);
+ if (id == null) return;
+ map.set(id, {
+ id,
+ configType: row.configType == null ? '' : String(row.configType),
+ parameterId: row.parameterId == null ? '' : String(row.parameterId),
+ value: row.value == null ? '' : String(row.value),
+ });
});
sourceMap.value = map;
removedRecords.value = [];
@@ -74,19 +86,20 @@
const updateRecords: any[] = [];
listData.value.forEach((row) => {
- if (row._isNew || row.id == null) {
+ const id = rowSourceId(row);
+ if (row._isNew || id == null) {
insertRecords.push(row);
return;
}
- const src = sourceMap.value.get(row.id);
+ const src = sourceMap.value.get(id);
if (!src) {
insertRecords.push(row);
return;
}
const currentValue = row.value == null ? '' : String(row.value);
if (
- src.configType !== row.configType ||
- src.parameterId !== row.parameterId ||
+ !sameScalar(src.configType, row.configType) ||
+ !sameScalar(src.parameterId, row.parameterId) ||
src.value !== currentValue
) {
updateRecords.push(row);
@@ -141,12 +154,27 @@
});
const tableData = [...addData, ...removeData, ...updataData];
+ // map 里若因类型空而 push 了 Message 返回值,拦截掉
+ const items = tableData.filter((i) => i && typeof i === 'object' && i.optionType != null);
+ if (items.length !== tableData.length) {
+ return false;
+ }
await postInterfaceReturnConfigSave({
interfaceId: addId.value,
- items: tableData,
+ items,
});
+ // 保存成功后以当前表为准重新采基线,避免仍按旧快照误报脏
+ listData.value.forEach((row) => {
+ if (row._isNew) delete row._isNew;
+ });
+ snapshotSource(listData.value);
+ // 保存后需重新拉 id(新增行),否则下次会当 insert
+ if (resolvedInterfaceId.value) {
+ await loadData(resolvedInterfaceId.value);
+ }
+
if (!options?.skipSuccessToast) {
$baseMessage('保存成功,如需生效请去发布', 'success');
}
@@ -171,8 +199,9 @@
if (idx >= 0) {
listData.value.splice(idx, 1);
}
- if (currRow?.id != null && !currRow._isNew) {
- if (!removedRecords.value.some((r) => r.id === currRow.id)) {
+ const removedId = rowSourceId(currRow);
+ if (removedId != null && !currRow._isNew) {
+ if (!removedRecords.value.some((r) => rowSourceId(r) === removedId)) {
removedRecords.value.push(currRow);
}
}
@@ -181,12 +210,15 @@
function handleTypeChange(e: any, row: any) {
row.parameterId = undefined;
const fullData = getFullData();
+ const nextType = Number(e?.value ?? e);
- if (e.value === 2 || e.value === 3) {
- const flag = fullData?.filter((item: any) => item.configType === e.value);
+ if (nextType === 2 || nextType === 3) {
+ const flag = fullData?.filter(
+ (item: any) => Number(item.configType) === nextType,
+ );
if (flag && flag?.length >= 2) {
const msg =
- e.value === 2
+ nextType === 2
? '标签上传只能保存一条数据'
: '错误返回只能保存一条数据';
row.configType = undefined;
@@ -232,6 +264,9 @@
);
snapshotSource(listData.value);
await nextTick();
+ // SelectPro 异步回写 key 类型后再采一次基线,避免刚打开就误报脏
+ await nextTick();
+ snapshotSource(listData.value);
}
watch(
@@ -244,9 +279,11 @@
{ immediate: false }
);
+ /** 与后端 onlyLeafs 一致:排除 Object/Array。错误返回(3)需要整树,其余只要叶子 */
function formatNameType(row: any) {
- if (row.configType === 1 || row.configType === 2) return true;
- if (row.configType === 3) return false;
+ const type = Number(row?.configType);
+ if (type === 3) return false;
+ return true;
}
const valueList = ref([]);
@@ -325,7 +362,7 @@
diff --git a/src/views/api/downstreamInterface/index.vue b/src/views/api/downstreamInterface/index.vue
index a8e7cca..6c40742 100644
--- a/src/views/api/downstreamInterface/index.vue
+++ b/src/views/api/downstreamInterface/index.vue
@@ -1437,9 +1437,19 @@
addClick();
}
+ /** 刷新左侧服务商列表:清空当前选中 */
function handleRefresh() {
+ selectedProvider.value = null;
+ selectedInterface.value = null;
+ interfaceList.value = [];
loadServiceProviderList();
}
+
+ /** 刷新中间接口列表:清空当前接口选中 */
+ function handleRefreshInterfaceList() {
+ selectedInterface.value = null;
+ loadInterfaceList();
+ }
// 接口弹窗关闭
function handleInterfaceModalClose() {
@@ -1787,7 +1797,7 @@
actions-title="操作"
delete-label="删除"
@search="handleInterfaceSearch"
- @refresh="loadInterfaceList"
+ @refresh="handleRefreshInterfaceList"
@add="addInterface"
@select="handleInterfaceSelectFromTree"
@node-click="handleInterfaceNodeClick"
diff --git a/src/views/api/upstreamInterface/components/EnteredMap.vue b/src/views/api/upstreamInterface/components/EnteredMap.vue
index 930b887..b986528 100644
--- a/src/views/api/upstreamInterface/components/EnteredMap.vue
+++ b/src/views/api/upstreamInterface/components/EnteredMap.vue
@@ -7,6 +7,7 @@
getEditMapDetails,
getGetMapParameterCascadedList,
} from '@/api/InterfaceMappingServices/interfaceMap';
+ import { ParameterType } from '@/constants/itfxEnums';
import AddParameter from './AddParameter.vue';
import MapAiAssistModal from './MapAiAssistModal.vue';
@@ -290,11 +291,36 @@
);
};
}
+ /** 与 GetParameterList?onlyLeafs=true 一致:排除 Object/Array */
+ function isLeafParamType(type: unknown) {
+ const t = Number(type);
+ return t !== ParameterType.Object && t !== ParameterType.Array;
+ }
+
+ /** 接口根保留,参数列表只留叶子(服务商出参接口原先未过滤) */
+ function prepareCascaderRoots(roots: any[]) {
+ return (roots || []).map((root) => ({
+ ...root,
+ parameters: (Array.isArray(root.parameters) ? root.parameters : []).filter(
+ (p: any) => isLeafParamType(p?.type),
+ ),
+ }));
+ }
+
function setlabel(data: any) {
data.forEach((item: any) => {
- if (item.parameters) item.parameters = setlabel(item.parameters);
+ const children = Array.isArray(item.parameters) ? item.parameters : null;
+ if (children?.length) {
+ item.parameters = setlabel(children);
+ } else if (item.interfaceId != null && item.alias == null) {
+ // 接口根无参数:保留空 children,避免被当成可选叶子
+ item.parameters = [];
+ } else {
+ // 参数叶子:去掉空 parameters,否则 Arco 会当成不可选的非叶子
+ delete item.parameters;
+ }
- item.getlabel = item.parameters
+ item.getlabel = children?.length
? item.name
: item.description
? `${item.alias}-${item.description}`
@@ -304,6 +330,13 @@
});
return data;
}
+
+ /** Arco Cascader 无 show-all-levels,用 formatLabel 只展示叶子文案 */
+ function formatCascaderLeafLabel(options: any[]) {
+ if (!options?.length) return '';
+ const leaf = options[options.length - 1];
+ return leaf?.getlabel ?? leaf?.alias ?? leaf?.name ?? '';
+ }
const mapAliasEditRef = ref();
/** 级联 options 按步骤缓存,避免每行渲染都深拷贝/打标 */
const cascaderOptionsCache = ref
@@ -798,6 +859,8 @@
v-model="row.mapId"
:options="outputCascaderOptions"
:filter-option="filterMethod"
+ :format-label="formatCascaderLeafLabel"
+ search-option-only-label
allow-search
allow-clear
:field-names="{
@@ -805,17 +868,12 @@
label: 'getlabel',
children: 'parameters',
}"
- :show-all-levels="false"
@change="
(value) => {
mapAliasEditCheng(value, row);
}
"
- >
-
- {{ _data.label }}
-
-
+ />
diff --git a/src/views/api/upstreamInterface/components/UpstreamInterfaceModal.vue b/src/views/api/upstreamInterface/components/UpstreamInterfaceModal.vue
index 11fadc9..c54f3ea 100644
--- a/src/views/api/upstreamInterface/components/UpstreamInterfaceModal.vue
+++ b/src/views/api/upstreamInterface/components/UpstreamInterfaceModal.vue
@@ -100,8 +100,12 @@
// 上游接口的验证规则(使用 form-config.ts 中的规则)
const upstreamFormRules = formRules;
- // 初始化函数
- async function init(data?: { id?: number | string; interfaceId?: number | string }) {
+ // 初始化函数;forceRefresh 用于保存后强制重拉,切 tab 回来不重复 init
+ async function init(data?: {
+ id?: number | string;
+ interfaceId?: number | string;
+ forceRefresh?: boolean;
+ }) {
// 优先使用传入的参数
let idRaw: any = null;
@@ -118,6 +122,7 @@
tableForm.isUpStream = true; // 上游接口默认设置为 true
tableForm.requestMethod = 2; // 默认 POST
tableForm.qps = 1;
+ lastInitId.value = null;
await nextTick();
if (interfaceDetailRef.value) {
interfaceDetailRef.value.setBasicFormValues({ ...tableForm });
@@ -127,11 +132,16 @@
}
const id = Number(idRaw);
+ const forceRefresh = !!data?.forceRefresh;
- // 防止重复调用:如果正在加载相同的接口,直接返回
+ // 正在加载同一接口:忽略重复调用
if (isInitializing.value && lastInitId.value === id) {
return;
}
+ // 已加载同一接口且非强制刷新(例如明细 ↔ 调试来回切):跳过重拉,避免误报未保存
+ if (!forceRefresh && lastInitId.value === id && shouldRenderForm.value) {
+ return;
+ }
// 标记正在初始化和当前ID
isInitializing.value = true;
diff --git a/src/views/api/upstreamInterface/components/map-service-provider.vue b/src/views/api/upstreamInterface/components/map-service-provider.vue
index 6f9a93c..07bf4d7 100644
--- a/src/views/api/upstreamInterface/components/map-service-provider.vue
+++ b/src/views/api/upstreamInterface/components/map-service-provider.vue
@@ -125,7 +125,10 @@
async function loadList(options?: { keepSelection?: boolean }) {
if (!props.id) {
list.value = [];
- selectedId.value = null;
+ if (selectedId.value != null) {
+ selectedId.value = null;
+ emit('row-click', null);
+ }
return;
}
try {
@@ -142,8 +145,10 @@
const data: any = raw.items ?? raw.data ?? raw.list ?? raw.records ?? raw;
list.value = Array.isArray(data) ? data : [];
if (!options?.keepSelection) {
- // 切换上游接口时不默认选中第一条映射
+ // 刷新 / 切换上游接口:清空选中
+ const hadSelection = selectedId.value != null;
selectedId.value = null;
+ if (hadSelection) emit('row-click', null);
} else if (
selectedId.value !== null &&
!list.value.some(
@@ -151,10 +156,12 @@
)
) {
selectedId.value = null;
+ emit('row-click', null);
}
} catch (e) {
list.value = [];
selectedId.value = null;
+ emit('row-click', null);
} finally {
loading.value = false;
}
@@ -398,7 +405,7 @@
:show-node-index="true"
:show-node-code="true"
@search="handleSearch"
- @refresh="() => loadList({ keepSelection: true })"
+ @refresh="() => loadList({ keepSelection: false })"
@add="handleAdd"
@select="handleMenuSelect"
@node-click="handleNodeClick"
diff --git a/src/views/api/upstreamInterface/index.vue b/src/views/api/upstreamInterface/index.vue
index d801750..b7c1ab1 100644
--- a/src/views/api/upstreamInterface/index.vue
+++ b/src/views/api/upstreamInterface/index.vue
@@ -998,7 +998,17 @@
}
function rowClick(row: any) {
- MapServiceProviderROW.value = row;
+ MapServiceProviderROW.value = row || null;
+ if (!row) {
+ serviceProviderId.value = 0;
+ serviceProviderName.value = '';
+ mappingPanelReady.value = false;
+ if (activeContentTab.value === 'mapping' || activeContentTab.value === 'debugger') {
+ activeContentTab.value = 'detail';
+ currentActiveTab.value = 'detail';
+ }
+ return;
+ }
// 联动右侧映射明细:根据左侧选中映射行初始化 EnteredMap
serviceProviderId.value = row.serviceProviderId;
serviceProviderName.value = row.serviceProviderName;
@@ -1462,6 +1472,22 @@
loadInterfaceTree();
}
+ /** 刷新左侧列表:清空当前选中,回到未选中状态 */
+ async function handleRefreshInterfaceTree() {
+ selectedKeys.value = [];
+ selectedInterface.value = null;
+ upStreamIdRef.value = undefined;
+ serviceProviderId.value = 0;
+ serviceProviderName.value = '';
+ MapServiceProviderROW.value = null;
+ mappingPanelReady.value = false;
+ if (activeContentTab.value === 'mapping' || activeContentTab.value === 'debugger') {
+ activeContentTab.value = 'detail';
+ currentActiveTab.value = 'detail';
+ }
+ await loadInterfaceTree();
+ }
+
// 处理下拉菜单选择
function handleActionSelect(value: string, option: any) {
try {
@@ -1519,7 +1545,7 @@
:actions-title="$t('system.interface.table.actions')"
method-label="POST"
@search="handleSearch"
- @refresh="loadInterfaceTree"
+ @refresh="handleRefreshInterfaceTree"
@add="interfaceClick"
@select="handleInterfaceSelect"
@expand="handleTreeExpand"
diff --git a/src/views/channel/labelDesign/LabelDesignerPanel.vue b/src/views/channel/labelDesign/LabelDesignerPanel.vue
index 7460451..bb5194d 100644
--- a/src/views/channel/labelDesign/LabelDesignerPanel.vue
+++ b/src/views/channel/labelDesign/LabelDesignerPanel.vue
@@ -494,8 +494,138 @@ onBeforeUnmount(() => {
flex: 1;
min-height: 0;
display: flex;
- --vg-header-bg: var(--app-primary-color, rgb(var(--arcoblue-6)));
+ /* vg-print 大量写死 #580df8;同步系统主色 + Element Plus 主色 */
+ --vg-primary: var(--app-primary-color, rgb(var(--arcoblue-6)));
+ --vg-primary-soft: color-mix(in srgb, var(--app-primary-color) 12%, #fff);
+ --vg-header-bg: var(--vg-primary);
--vg-header-text: #fff;
+ --el-color-primary: var(--vg-primary);
+ --el-color-primary-light-3: color-mix(in srgb, var(--app-primary-color) 70%, #fff);
+ --el-color-primary-light-5: color-mix(in srgb, var(--app-primary-color) 50%, #fff);
+ --el-color-primary-light-7: color-mix(in srgb, var(--app-primary-color) 30%, #fff);
+ --el-color-primary-light-8: color-mix(in srgb, var(--app-primary-color) 20%, #fff);
+ --el-color-primary-light-9: color-mix(in srgb, var(--app-primary-color) 12%, #fff);
+ --el-color-primary-dark-2: var(--app-primary-color-hover, var(--app-primary-color));
+}
+
+/* 顶栏 / 左栏标题 / 属性头 / 结构树头 / 历史头 */
+.ld-designer__body :deep(.primary-toolbar),
+.ld-designer__body :deep(.left-sider-header),
+.ld-designer__body :deep(.prop-header),
+.ld-designer__body :deep(.hip-struct-header),
+.ld-designer__body :deep(.hip-history-header) {
+ background-color: var(--vg-primary) !important;
+ background: var(--vg-primary) !important;
+}
+
+.ld-designer__body :deep(.primary-toolbar) {
+ border-bottom-color: color-mix(in srgb, var(--app-primary-color) 40%, #dcdfe6) !important;
+}
+
+/* 顶栏按钮:主色底上用浅色按钮,避免与主色 primary 按钮糊在一起 */
+.ld-designer__body :deep(.primary-toolbar .el-button) {
+ --el-button-bg-color: rgba(255, 255, 255, 0.95);
+ --el-button-border-color: rgba(255, 255, 255, 0.85);
+ --el-button-text-color: var(--vg-primary);
+ --el-button-hover-bg-color: #fff;
+ --el-button-hover-border-color: #fff;
+ --el-button-hover-text-color: var(--app-primary-color-hover, var(--vg-primary));
+ --el-button-active-bg-color: #fff;
+ --el-button-active-border-color: #fff;
+ --el-button-active-text-color: var(--app-primary-color-hover, var(--vg-primary));
+}
+
+.ld-designer__body :deep(.primary-toolbar .el-button--primary) {
+ --el-button-bg-color: #fff;
+ --el-button-border-color: #fff;
+ --el-button-text-color: var(--vg-primary);
+ --el-button-hover-bg-color: rgba(255, 255, 255, 0.92);
+ --el-button-hover-border-color: #fff;
+ --el-button-hover-text-color: var(--app-primary-color-hover, var(--vg-primary));
+}
+
+.ld-designer__body :deep(.secondary-toolbar) {
+ background-color: color-mix(in srgb, var(--app-primary-color) 6%, #fff) !important;
+}
+
+.ld-designer__body :deep(.secondary-toolbar .scrollbar-track) {
+ background: color-mix(in srgb, var(--app-primary-color) 12%, transparent) !important;
+}
+
+.ld-designer__body :deep(.secondary-toolbar .scrollbar-thumb) {
+ background-color: var(--vg-primary) !important;
+}
+
+/* 文字 / 勾选强调色 */
+.ld-designer__body :deep(.template-name),
+.ld-designer__body :deep(.hiprint-option-item-iconBtn),
+.ld-designer__body :deep(.template-item.is-active .template-check) {
+ color: var(--vg-primary) !important;
+}
+
+/* 左侧模板选中态 */
+.ld-designer__body :deep(.template-item.is-active) {
+ background: linear-gradient(
+ 90deg,
+ var(--vg-primary-soft),
+ #fff 60%
+ ) !important;
+ border-color: var(--vg-primary) !important;
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--app-primary-color) 12%, transparent) !important;
+}
+
+/* 画布缩放把手 / 表格把手 */
+.ld-designer__body :deep(.resize-handle:hover),
+.ld-designer__body :deep(.hiprint-printElement-table-handle),
+.ld-designer__body :deep(.hiprint-printElement-table .resize-panel .n),
+.ld-designer__body :deep(.hiprint-printElement-table .resize-panel .s),
+.ld-designer__body :deep(.hiprint-printElement-table .resize-panel .e),
+.ld-designer__body :deep(.hiprint-printElement-table .resize-panel .w),
+.ld-designer__body :deep(.hiprint-printElement-table .resize-panel .ne),
+.ld-designer__body :deep(.hiprint-printElement-table .resize-panel .nw),
+.ld-designer__body :deep(.hiprint-printElement-table .resize-panel .se),
+.ld-designer__body :deep(.hiprint-printElement-table .resize-panel .sw) {
+ background: var(--vg-primary) !important;
+ background-color: var(--vg-primary) !important;
+}
+
+/* 左侧组件拖拽项(库内边框写死 #590df8) */
+.ld-designer__body :deep(.ep-draggable-item),
+.ld-designer__body :deep(.rect-printElement-types .hiprint-printElement-type > li > ul > li > a),
+.ld-designer__body :deep(.small-printElement-types .hiprint-printElement-type > li > ul > li > a) {
+ border-color: var(--vg-primary) !important;
+ color: var(--color-text-1, #303133) !important;
+}
+
+.ld-designer__body :deep(.ep-draggable-item:hover),
+.ld-designer__body :deep(.rect-printElement-types .hiprint-printElement-type > li > ul > li > a:hover),
+.ld-designer__body :deep(.small-printElement-types .hiprint-printElement-type > li > ul > li > a:hover) {
+ background: var(--vg-primary-soft) !important;
+ border-color: var(--vg-primary) !important;
+ color: var(--vg-primary) !important;
+}
+
+/* 左栏折叠钮(库内写死 #590df8) */
+.ld-designer__body :deep(.left-sider-toggle) {
+ background: var(--vg-primary) !important;
+ background-color: var(--vg-primary) !important;
+ color: #fff !important;
+}
+
+/* 属性 Tab / 分页选中(库内 #2196f3) */
+.ld-designer__body :deep(.prop-tabs .prop-tab-items li.active) {
+ border-bottom-color: var(--vg-primary) !important;
+ color: var(--vg-primary) !important;
+}
+
+.ld-designer__body :deep(.hiprint-pagination .selected) {
+ border-color: var(--vg-primary) !important;
+}
+
+/* 移动端 sheet tab(库内 #409eff) */
+.ld-designer__body :deep(.mobile-bottom-sheet .mobile-sheet-tabs .tab-item.active) {
+ color: var(--vg-primary) !important;
+ border-bottom-color: var(--vg-primary) !important;
}
.ld-spin {
diff --git a/src/views/channel/labelDesign/index.vue b/src/views/channel/labelDesign/index.vue
index 7781cb2..75cc9b6 100644
--- a/src/views/channel/labelDesign/index.vue
+++ b/src/views/channel/labelDesign/index.vue
@@ -25,7 +25,18 @@
@add="openCreateModal"
@select="handleMenuSelect"
@delete-node="handleDeleteNode"
- />
+ >
+
+
+
+ {{ $t('channel.labelDesign.actions.editTemplate') }}
+
+
+
+ {{ $t('channel.labelDesign.actions.delete') }}
+
+
+
@@ -90,12 +140,14 @@ import { computed, defineAsyncComponent, onMounted, reactive, ref } from 'vue'
import { h } from 'vue'
import { useI18n } from 'vue-i18n'
import { Modal, Message, Spin } from '@arco-design/web-vue'
+import { IconDelete, IconEdit } from '@arco-design/web-vue/es/icon'
import CustomLeftMenu from '@/components/CustomLeftMenu/index.vue'
import type { CustomLeftMenuTreeNode } from '@/components/CustomLeftMenu/types'
import {
deleteLabelTemplate,
getLabelTemplate,
getLabelTemplateList,
+ saveLabelTemplate,
type LabelTemplateListItem,
} from '@/api/channel/labelTemplate'
@@ -126,6 +178,12 @@ const createForm = reactive({
name: '',
templateType: 'lastMile',
})
+const renameModalVisible = ref(false)
+const renameForm = reactive({
+ id: 0,
+ name: '',
+ templateType: 'lastMile',
+})
const editing = reactive<{
id?: number
name?: string
@@ -261,6 +319,49 @@ function handleMenuSelect(keys: string[]) {
openTemplate(id)
}
+function openRenameModal(nodeData: any) {
+ const id = Number(nodeData?.id)
+ if (!id) return
+ renameForm.id = id
+ renameForm.name = String(nodeData?.name || nodeData?.title || '')
+ renameForm.templateType = String(nodeData?.templateType || 'lastMile')
+ renameModalVisible.value = true
+}
+
+async function confirmRename() {
+ const id = renameForm.id
+ const name = renameForm.name?.trim()
+ const templateType = renameForm.templateType?.trim() || 'lastMile'
+ if (!id) return false
+ if (!name) {
+ Message.warning(t('channel.labelDesign.message.nameRequired'))
+ return false
+ }
+ if (!templateType) {
+ Message.warning(t('channel.labelDesign.message.typeRequired'))
+ return false
+ }
+ try {
+ const detail = await getLabelTemplate(id)
+ await saveLabelTemplate({
+ id,
+ name,
+ templateType,
+ templateJson: detail.templateJson,
+ })
+ Message.success(t('channel.labelDesign.message.editSuccess'))
+ if (editing.id === id) {
+ editing.name = name
+ editing.templateType = templateType
+ }
+ await loadList()
+ return true
+ } catch (e: any) {
+ Message.error(e?.message || t('channel.labelDesign.message.editFailed'))
+ return false
+ }
+}
+
function handleDeleteNode(nodeData: any) {
const id = Number(nodeData?.id)
const name = String(nodeData?.name || nodeData?.title || '')
diff --git a/src/views/channel/salesProducts/components/PriceListHotTable.vue b/src/views/channel/salesProducts/components/PriceListHotTable.vue
index 68865bb..41449ba 100644
--- a/src/views/channel/salesProducts/components/PriceListHotTable.vue
+++ b/src/views/channel/salesProducts/components/PriceListHotTable.vue
@@ -367,9 +367,27 @@
enterBeginsEditing: true,
fragmentSelection: false,
selectionMode: 'multiple',
+ // 点表格外的工具栏/弹窗按钮时不要清选中,否则「批量填价」等会在 mousedown 时先丢选中再点不到
outsideClickDeselects(event: MouseEvent) {
const target = event.target as HTMLElement | null;
- return !target?.closest('.sales-price-config-modal, .arco-modal, .arco-trigger-popup');
+ if (!target) return true;
+ if (
+ target.closest(
+ [
+ '.price-list-actions',
+ '.price-list-header',
+ '.price-list-content',
+ '.sales-price-config-modal',
+ '.arco-modal',
+ '.arco-modal-wrapper',
+ '.arco-trigger-popup',
+ '.arco-btn',
+ ].join(','),
+ )
+ ) {
+ return false;
+ }
+ return true;
},
allowInsertRow: false,
allowInsertColumn: false,
diff --git a/src/views/channel/salesProducts/index.vue b/src/views/channel/salesProducts/index.vue
index 54418e8..da97570 100644
--- a/src/views/channel/salesProducts/index.vue
+++ b/src/views/channel/salesProducts/index.vue
@@ -3243,10 +3243,12 @@
/** 当前价格表行对应的客户组编码顺序(用于同步时按编码保留价格) */
const priceListCustomerCodesSnapshot = ref