From 640562654d593221b75acc5bd8f591dcf7115300 Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Fri, 9 Oct 2026 14:12:32 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E4=BB=93=E5=BA=93=E5=9C=B0?= =?UTF-8?q?=E5=9D=80=E6=8E=92=E5=BA=8F=E5=A4=87=E6=B3=A8=E4=B8=8E=E6=98=A0?= =?UTF-8?q?=E5=B0=84=E6=98=8E=E7=BB=86=E6=80=A7=E8=83=BD=E3=80=81=E8=84=8F?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E8=AF=AF=E6=8A=A5=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 下单/H5 仓库地址展示与排序备注;映射明细加载优化与发布后保留选中;接口明细加载期不再误报未保存。 Co-authored-by: Cursor --- src/api/system/warehouse.ts | 4 + src/components/InterfaceDetail/index.vue | 94 ++++++- src/locale/en-US.ts | 6 + src/locale/zh-CN.ts | 6 + src/utils/warehouseShipAddress.ts | 49 ++++ .../downstreamInterface/comps/AccountList.vue | 14 +- .../components/EnteredMap.vue | 254 +++++++++++------ .../components/map-service-provider.vue | 38 ++- src/views/api/upstreamInterface/index.vue | 43 ++- .../components/H5PlaceOrderSheet.vue | 220 ++++++++++++--- .../components/H5WarehouseAddressSheet.vue | 52 +++- src/views/h5/customer-center/locale/en-US.ts | 11 +- src/views/h5/customer-center/locale/zh-CN.ts | 9 +- src/views/login/locale/zh-CN.ts | 2 +- src/views/order/placeOrder/index.vue | 262 ++++++++++++++---- src/views/system/warehouseAddress/index.vue | 68 ++++- 16 files changed, 876 insertions(+), 256 deletions(-) create mode 100644 src/utils/warehouseShipAddress.ts diff --git a/src/api/system/warehouse.ts b/src/api/system/warehouse.ts index ea0e347..f8d43fe 100644 --- a/src/api/system/warehouse.ts +++ b/src/api/system/warehouse.ts @@ -9,6 +9,10 @@ export interface WarehouseDto { recipient?: string contactPhone?: string shippingAddress?: string + /** 展示排序,越小越靠前;可不填 */ + sort?: number | null + /** 备注(选填) */ + remark?: string createTime?: string updateTime?: string createBy?: string diff --git a/src/components/InterfaceDetail/index.vue b/src/components/InterfaceDetail/index.vue index bfde355..bfa0679 100644 --- a/src/components/InterfaceDetail/index.vue +++ b/src/components/InterfaceDetail/index.vue @@ -220,7 +220,8 @@ originalParamIds.value = source ? buildOriginalParamIdsFromHydrated(source) : createEmptyOriginalParamIds(); - nextTick(() => captureDirtyBaseline()); + // 等表格/公共参数/表单回填落稳后再采基线,避免「刚打开就提示未保存」 + void settleDirtyBaseline(); } watch( @@ -288,6 +289,10 @@ const exampleDirtyBaseline = ref({ request: '', response: '' }); const manualDirty = ref(false); const isDirty = ref(false); + /** 接口明细加载/回填中:不算未保存,避免误报 */ + const detailHydrating = ref(false); + let baselineSettleSeq = 0; + const dirtyBaselineReady = ref(false); function pickFormDirtySnapshot() { const d: any = props.basicFormData || {}; @@ -318,15 +323,24 @@ } function hasUnsavedChanges() { + // 加载未完成时不提示「有未保存更改」 + if (detailHydrating.value || !dirtyBaselineReady.value) return false; if (manualDirty.value) return true; if (fixedParamsHasChanges.value) return true; if (returnConfigHasChanges.value) return true; if (formDirtyBaseline.value && pickFormDirtySnapshot() !== formDirtyBaseline.value) return true; if ((requestExample.value || '') !== (exampleDirtyBaseline.value.request || '')) return true; if ((responseExample.value || '') !== (exampleDirtyBaseline.value.response || '')) return true; - if (actionRowsDirty(() => queryTableRef.value?.getActionRows?.())) return true; - if (actionRowsDirty(() => headerTableRef.value?.getActionRows?.())) return true; - if (actionRowsDirty(() => bodyFormTableRef.value?.getActionRows?.())) return true; + // 公共参数行不参与脏检查 + if (actionRowsDirty(() => filterCommonFromActionRows(queryTableRef.value?.getActionRows?.()))) { + return true; + } + if (actionRowsDirty(() => filterCommonFromActionRows(headerTableRef.value?.getActionRows?.()))) { + return true; + } + if (actionRowsDirty(() => filterCommonFromActionRows(bodyFormTableRef.value?.getActionRows?.()))) { + return true; + } if ( actionRowsDirty(() => stripBodyRootFromActionRows(requestTableRef.value?.getActionRows?.() || {}), @@ -344,6 +358,7 @@ } function markDirty() { + if (detailHydrating.value) return; manualDirty.value = true; isDirty.value = true; } @@ -356,6 +371,60 @@ response: responseExample.value || '', }; isDirty.value = false; + dirtyBaselineReady.value = true; + } + + /** 用当前 props.data 再 reload 一次,清掉 VXE keep-source 在回填过程中留下的假 insert/update */ + function reseatParamTables() { + const reload = (refLike: { value?: { reloadData?: (rows: any[]) => void } }, rows: any[]) => { + if (!refLike?.value?.reloadData) return; + refLike.value.reloadData(Array.isArray(rows) ? [...rows] : []); + }; + reload(queryTableRef, queryTableProps.data); + reload(headerTableRef, headerTableProps.data); + reload(bodyFormTableRef, bodyFormTableProps.data); + reload(mappingBodyTableRef, mappingBodyTableProps.data); + reload(requestTableRef, requestTableProps.data); + reload(responseTableRef, responseTableProps.data); + } + + /** + * 等待公共参数合并、表格挂载、FormPro/SelectPro 回填后再采脏检查基线。 + * 解决:选中接口后立刻切 API 调试误报「有未保存的更改」。 + */ + async function settleDirtyBaseline() { + const seq = ++baselineSettleSeq; + detailHydrating.value = true; + dirtyBaselineReady.value = false; + manualDirty.value = false; + isDirty.value = false; + + await nextTick(); + await nextTick(); + if (seq !== baselineSettleSeq) return; + + // 等服务商公共参数拉取并合并进 Query/Header/Body + const waitStart = Date.now(); + while (inheritedCommonParamsLoading.value && Date.now() - waitStart < 4000) { + await new Promise((r) => setTimeout(r, 40)); + if (seq !== baselineSettleSeq) return; + } + + await nextTick(); + reseatParamTables(); + await nextTick(); + if (seq !== baselineSettleSeq) return; + + captureDirtyBaseline(); + detailHydrating.value = false; + + // SelectPro/字典异步回写可能再改一帧表单值,二次采基线(仅无手动编辑时) + await new Promise((r) => setTimeout(r, 120)); + if (seq !== baselineSettleSeq || manualDirty.value) return; + reseatParamTables(); + await nextTick(); + if (seq !== baselineSettleSeq || manualDirty.value) return; + captureDirtyBaseline(); } /** 浏览器关闭/刷新:有未保存更改时提示 */ @@ -370,6 +439,8 @@ * resolve true = 可离开(丢弃更改),false = 留下继续编辑。 */ async function confirmLeaveUnsaved(options?: { content?: string }): Promise { + // 仍在回填:用户不可能已编辑,直接放行 + if (detailHydrating.value || !dirtyBaselineReady.value) return true; refreshDirtyState(); if (!hasUnsavedChanges()) return true; return new Promise((resolve) => { @@ -562,11 +633,19 @@ (id, oldId) => { if (oldId) saveTabMemory(oldId); if (id) { + // 切换接口:进入回填态,避免沿用上一接口的脏标记 + detailHydrating.value = true; + dirtyBaselineReady.value = false; + manualDirty.value = false; + isDirty.value = false; nextTick(() => { restoreTabMemory(id); - // 加载完成后由父组件 hydrate 再 capture;此处先清 manual - manualDirty.value = false; }); + } else { + detailHydrating.value = false; + dirtyBaselineReady.value = false; + manualDirty.value = false; + isDirty.value = false; } }, ); @@ -3283,7 +3362,8 @@ } } else { fixedParams.value = payload.fixedParams; - fixedParamsHasChanges.value = payload.fixedParams.length > 0; + // 有固定配置数据 ≠ 未保存更改;加载后应视为干净 + fixedParamsHasChanges.value = false; await nextTick(); queryFixedTableRef.value?.reloadData?.(payload.fixedParams); } diff --git a/src/locale/en-US.ts b/src/locale/en-US.ts index 4f7adff..0d2986c 100644 --- a/src/locale/en-US.ts +++ b/src/locale/en-US.ts @@ -3046,6 +3046,8 @@ export default { 'system.interface.mapping.table.providerParamName': 'Provider parameter name', 'system.interface.mapping.table.providerParamNameCn': 'Provider parameter description', 'system.interface.mapping.table.paramValue': 'Parameter value', + 'system.interface.mapping.table.paramValueTip': + 'If a value looks like both a number and a string (e.g. phone 075525131031), wrap it in single quotes as \'075525131031\' to mark it as a string. Entering it without quotes treats it as a number and may drop leading zeros or cause type errors.', 'system.interface.mapping.table.inputParam': 'Input parameter', 'system.interface.mapping.table.inputParamCn': 'Input parameter description', 'system.interface.mapping.actions.prefixScript': 'Prefix script', @@ -4085,8 +4087,12 @@ export default { 'system.warehouseAddress.fields.recipient': 'Recipient', 'system.warehouseAddress.fields.contactPhone': 'Contact Phone', 'system.warehouseAddress.fields.shippingAddress': 'Shipping Address', + 'system.warehouseAddress.fields.sort': 'Sort', + 'system.warehouseAddress.fields.remark': 'Remark', 'system.warehouseAddress.fields.servicePhone': 'Service Phone', 'system.warehouseAddress.fields.serviceWechat': 'Service WeChat', + 'system.warehouseAddress.placeholder.sort': 'Smaller first (optional)', + 'system.warehouseAddress.placeholder.remark': 'Optional note shown to customers', 'system.warehouseAddress.actions.add': 'Add', 'system.warehouseAddress.actions.edit': 'Edit', 'system.warehouseAddress.actions.copy': 'Copy', diff --git a/src/locale/zh-CN.ts b/src/locale/zh-CN.ts index 82036bb..c98f79a 100644 --- a/src/locale/zh-CN.ts +++ b/src/locale/zh-CN.ts @@ -2908,6 +2908,8 @@ export default { 'system.interface.mapping.table.providerParamName': '服务商参数名', 'system.interface.mapping.table.providerParamNameCn': '服务商参数描述', 'system.interface.mapping.table.paramValue': '参数值', + 'system.interface.mapping.table.paramValueTip': + '若填写的值既像数字又像字符串(例如电话号 075525131031),请用单引号包起来写成 \'075525131031\',明确为字符串。直接填写会被当成数字解析,可能导致前导零丢失或类型错误。', 'system.interface.mapping.table.inputParam': '入参', 'system.interface.mapping.table.inputParamCn': '入参描述', 'system.interface.mapping.actions.prefixScript': '前置脚本', @@ -4113,8 +4115,12 @@ export default { 'system.warehouseAddress.fields.recipient': '收件人', 'system.warehouseAddress.fields.contactPhone': '联系电话', 'system.warehouseAddress.fields.shippingAddress': '收货地址', + 'system.warehouseAddress.fields.sort': '排序', + 'system.warehouseAddress.fields.remark': '备注', 'system.warehouseAddress.fields.servicePhone': '客服电话', 'system.warehouseAddress.fields.serviceWechat': '客服微信', + 'system.warehouseAddress.placeholder.sort': '越小越靠前,可不填', + 'system.warehouseAddress.placeholder.remark': '选填,展示给客户的说明', 'system.warehouseAddress.actions.add': '新增', 'system.warehouseAddress.actions.edit': '编辑', 'system.warehouseAddress.actions.copy': '复制', diff --git a/src/utils/warehouseShipAddress.ts b/src/utils/warehouseShipAddress.ts new file mode 100644 index 0000000..bb382aa --- /dev/null +++ b/src/utils/warehouseShipAddress.ts @@ -0,0 +1,49 @@ +/** + * 客户登录时展示/复制仓库寄件信息: + * - 收件人后拼接客户编号(空格分隔) + * - 地址后拼接客户编号 +「号」(无空格) + * 例:张志坚 AI0A1 / …二楼AI0A1号 + */ + +/** 有 Sort 的升序靠前,无 Sort 的靠后,再按 id */ +export function compareWarehouseSort( + a: { sort?: number | null; id?: number | string | null; value?: string | number | null }, + b: { sort?: number | null; id?: number | string | null; value?: string | number | null }, +): number { + const aHas = a.sort != null && Number.isFinite(Number(a.sort)); + const bHas = b.sort != null && Number.isFinite(Number(b.sort)); + if (aHas !== bHas) return aHas ? -1 : 1; + if (aHas && bHas) { + const diff = Number(a.sort) - Number(b.sort); + if (diff !== 0) return diff; + } + const aId = Number(a.id ?? a.value ?? 0); + const bId = Number(b.id ?? b.value ?? 0); + return aId - bId; +} + +export function formatWarehouseShipRecipient( + recipient?: string | null, + customerCode?: string | null, +): string { + const base = String(recipient ?? '').trim(); + const code = String(customerCode ?? '').trim(); + if (!code) return base || '-'; + if (!base) return code; + if (base === code || base.endsWith(` ${code}`) || base.endsWith(code)) return base; + return `${base} ${code}`; +} + +export function formatWarehouseShipAddress( + address?: string | null, + customerCode?: string | null, +): string { + const base = String(address ?? '').trim(); + const code = String(customerCode ?? '').trim(); + if (!code) return base || '-'; + const withHao = `${code}号`; + if (!base) return withHao; + if (base.endsWith(withHao)) return base; + if (base.endsWith(code)) return `${base}号`; + return `${base}${withHao}`; +} diff --git a/src/views/api/downstreamInterface/comps/AccountList.vue b/src/views/api/downstreamInterface/comps/AccountList.vue index 68d00f7..706092c 100644 --- a/src/views/api/downstreamInterface/comps/AccountList.vue +++ b/src/views/api/downstreamInterface/comps/AccountList.vue @@ -1,5 +1,6 @@ + + + + diff --git a/src/views/order/waybillManage/components/OrderDetailDrawer.vue b/src/views/order/waybillManage/components/OrderDetailDrawer.vue index 724af2e..3702c44 100644 --- a/src/views/order/waybillManage/components/OrderDetailDrawer.vue +++ b/src/views/order/waybillManage/components/OrderDetailDrawer.vue @@ -48,12 +48,14 @@ @@ -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(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([]); + +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; +} diff --git a/src/views/order/waybillManage/index.vue b/src/views/order/waybillManage/index.vue index 4384c8a..693d31c 100644 --- a/src/views/order/waybillManage/index.vue +++ b/src/views/order/waybillManage/index.vue @@ -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)) { From 2612a225ade6b08c3545e2e204d27d53cd8dab64 Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Fri, 9 Oct 2026 18:48:27 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E6=8E=A5=E5=8F=A3=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=8D=A0=E4=BD=8D=E7=AC=A6=20$Token=20=E5=A4=A7=E5=B0=8F?= =?UTF-8?q?=E5=86=99=EF=BC=8C=E5=B9=B6=E4=BF=AE=E5=A4=8D=20Query/Header=20?= =?UTF-8?q?=E7=A9=BA=E5=80=BC=E5=88=A0=E9=99=A4=E6=97=A0=E6=B3=95=E8=90=BD?= =?UTF-8?q?=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- src/components/InterfaceDetail/index.vue | 2 +- src/constants/itfxEnums.ts | 2 +- src/utils/interfaceParameterModel.ts | 41 ++++++++++++++++++------ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/components/InterfaceDetail/index.vue b/src/components/InterfaceDetail/index.vue index bfa0679..ce5205e 100644 --- a/src/components/InterfaceDetail/index.vue +++ b/src/components/InterfaceDetail/index.vue @@ -3905,7 +3905,7 @@
  • 参数名:与服务商文档、curl、Mapping Body / Body 参数树、前置脚本写入字段名保持一致(如 _aop_signature、notifyBizEventDTO)。
  • 参数值:使用占位符或留空,不要填写 curl 中的真实签名/业务样例;转发前由 Like API 替换。
  • $FromMappingBody(FormUrlEncoded):Query 上承载业务 JSON 的字段(如 *DTO)填此占位符,映射结果来自 Mapping Body 参数树;POST 参数仅在 URL、无 Body 时,DTO 应配置在 Query 而非 Body 表单。
  • -
  • $FromAccount:按参数名从服务商账户取值;$TimeStamp / $DateTime:时间;$token:令牌;$Url:当前请求地址。
  • +
  • $FromAccount:按参数名从服务商账户取值;$TimeStamp / $DateTime:时间;$Token:令牌;$Url:当前请求地址。
  • 留空:签名类参数(如 sign、_aop_signature)由前置脚本写入。
  • 参数描述:可记录备注或 curl 片段,便于对照映射,不参与转发。
  • diff --git a/src/constants/itfxEnums.ts b/src/constants/itfxEnums.ts index 06ebc3c..b616203 100644 --- a/src/constants/itfxEnums.ts +++ b/src/constants/itfxEnums.ts @@ -100,7 +100,7 @@ export const ParameterPlaceholderText: Record = { [ParameterPlaceholder.TimeStamp]: '$TimeStamp', [ParameterPlaceholder.DateTime]: '$DateTime', [ParameterPlaceholder.Url]: '$Url', - [ParameterPlaceholder.Token]: '$token', + [ParameterPlaceholder.Token]: '$Token', [ParameterPlaceholder.FromMappingBody]: '$FromMappingBody', }; diff --git a/src/utils/interfaceParameterModel.ts b/src/utils/interfaceParameterModel.ts index 89b139d..ccf2971 100644 --- a/src/utils/interfaceParameterModel.ts +++ b/src/utils/interfaceParameterModel.ts @@ -399,6 +399,14 @@ function isSameFormKvRow(row: any, baseline: NormalizedParamRow): boolean { ); } +function isPersistableFormKvRow(p: any) { + // 删除只依赖 id;参数值允许留空(签名类由前置脚本写入) + if (Number(p?.optionType) === 2) { + return Number(p?.id) > 0; + } + return !!(p.name && String(p.name).trim() !== ''); +} + function buildFormKvParameterList( changes: any[], tableData: any[], @@ -406,24 +414,23 @@ function buildFormKvParameterList( isEditMode: boolean, original: OriginalParamSnapshot, ) { - const isValid = (p: any) => - p.name && String(p.name).trim() !== '' && p.value && String(p.value).trim() !== ''; - const changeById = new Map(changes.map((c) => [Number(c.id), c])); const removedIds = new Set( changes.filter((c) => c.optionType === 2).map((c) => Number(c.id)), ); if (!isEditMode) { - if (changes.length) return changes; + if (changes.length) return changes.filter(isPersistableFormKvRow); return (tableData || []) .map((item) => mapFormKvRow(stripFormKvMeta(item), 1, fieldPosition)) - .filter(isValid); + .filter(isPersistableFormKvRow); } const result: any[] = []; changes.filter((c) => c.optionType === 2).forEach((c) => result.push(c)); + const presentServerIds = new Set(); + (tableData || []).forEach((item) => { const rest = stripFormKvMeta(item); const id = normalizeRowId(rest); @@ -435,6 +442,8 @@ function buildFormKvParameterList( const tracked = changeById.get(id); if (tracked && tracked.optionType !== 2) { result.push(tracked); + const trackedServerId = Number(tracked.id); + if (trackedServerId > 0) presentServerIds.add(trackedServerId); return; } @@ -443,6 +452,7 @@ function buildFormKvParameterList( const baseline = serverId ? original.rowById.get(serverId) : undefined; if (serverId && baseline) { + presentServerIds.add(serverId); if (!isSameFormKvRow(rest, baseline)) { result.push(mapFormKvRow(rest, 3, fieldPosition, serverId)); } @@ -454,7 +464,22 @@ function buildFormKvParameterList( } }); - return result.filter(isValid); + // 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({ + id: serverId, + name: baseline.name, + value: baseline.alias || '', + description: baseline.description || '', + optionType: 2, + fieldPosition, + }); + }); + + return result.filter(isPersistableFormKvRow); } function mapFormKvChanges( @@ -476,9 +501,7 @@ function mapFormKvChanges( ...(actionRows.updateRecords || []).map((r) => mapRow(r, 3)), ]; - return list.filter( - (p) => p.name && String(p.name).trim() !== '' && p.value && String(p.value).trim() !== '', - ); + return list.filter(isPersistableFormKvRow); } export interface InterfaceDetailSnapshot {