1.02 #3
@ -9,6 +9,10 @@ export interface WarehouseDto {
|
|||||||
recipient?: string
|
recipient?: string
|
||||||
contactPhone?: string
|
contactPhone?: string
|
||||||
shippingAddress?: string
|
shippingAddress?: string
|
||||||
|
/** 展示排序,越小越靠前;可不填 */
|
||||||
|
sort?: number | null
|
||||||
|
/** 备注(选填) */
|
||||||
|
remark?: string
|
||||||
createTime?: string
|
createTime?: string
|
||||||
updateTime?: string
|
updateTime?: string
|
||||||
createBy?: string
|
createBy?: string
|
||||||
|
|||||||
@ -220,7 +220,8 @@
|
|||||||
originalParamIds.value = source
|
originalParamIds.value = source
|
||||||
? buildOriginalParamIdsFromHydrated(source)
|
? buildOriginalParamIdsFromHydrated(source)
|
||||||
: createEmptyOriginalParamIds();
|
: createEmptyOriginalParamIds();
|
||||||
nextTick(() => captureDirtyBaseline());
|
// 等表格/公共参数/表单回填落稳后再采基线,避免「刚打开就提示未保存」
|
||||||
|
void settleDirtyBaseline();
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@ -288,6 +289,10 @@
|
|||||||
const exampleDirtyBaseline = ref({ request: '', response: '' });
|
const exampleDirtyBaseline = ref({ request: '', response: '' });
|
||||||
const manualDirty = ref(false);
|
const manualDirty = ref(false);
|
||||||
const isDirty = ref(false);
|
const isDirty = ref(false);
|
||||||
|
/** 接口明细加载/回填中:不算未保存,避免误报 */
|
||||||
|
const detailHydrating = ref(false);
|
||||||
|
let baselineSettleSeq = 0;
|
||||||
|
const dirtyBaselineReady = ref(false);
|
||||||
|
|
||||||
function pickFormDirtySnapshot() {
|
function pickFormDirtySnapshot() {
|
||||||
const d: any = props.basicFormData || {};
|
const d: any = props.basicFormData || {};
|
||||||
@ -318,15 +323,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function hasUnsavedChanges() {
|
function hasUnsavedChanges() {
|
||||||
|
// 加载未完成时不提示「有未保存更改」
|
||||||
|
if (detailHydrating.value || !dirtyBaselineReady.value) return false;
|
||||||
if (manualDirty.value) return true;
|
if (manualDirty.value) return true;
|
||||||
if (fixedParamsHasChanges.value) return true;
|
if (fixedParamsHasChanges.value) return true;
|
||||||
if (returnConfigHasChanges.value) return true;
|
if (returnConfigHasChanges.value) return true;
|
||||||
if (formDirtyBaseline.value && pickFormDirtySnapshot() !== formDirtyBaseline.value) return true;
|
if (formDirtyBaseline.value && pickFormDirtySnapshot() !== formDirtyBaseline.value) return true;
|
||||||
if ((requestExample.value || '') !== (exampleDirtyBaseline.value.request || '')) return true;
|
if ((requestExample.value || '') !== (exampleDirtyBaseline.value.request || '')) return true;
|
||||||
if ((responseExample.value || '') !== (exampleDirtyBaseline.value.response || '')) 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(() => filterCommonFromActionRows(queryTableRef.value?.getActionRows?.()))) {
|
||||||
if (actionRowsDirty(() => bodyFormTableRef.value?.getActionRows?.())) return true;
|
return true;
|
||||||
|
}
|
||||||
|
if (actionRowsDirty(() => filterCommonFromActionRows(headerTableRef.value?.getActionRows?.()))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (actionRowsDirty(() => filterCommonFromActionRows(bodyFormTableRef.value?.getActionRows?.()))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
actionRowsDirty(() =>
|
actionRowsDirty(() =>
|
||||||
stripBodyRootFromActionRows(requestTableRef.value?.getActionRows?.() || {}),
|
stripBodyRootFromActionRows(requestTableRef.value?.getActionRows?.() || {}),
|
||||||
@ -344,6 +358,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function markDirty() {
|
function markDirty() {
|
||||||
|
if (detailHydrating.value) return;
|
||||||
manualDirty.value = true;
|
manualDirty.value = true;
|
||||||
isDirty.value = true;
|
isDirty.value = true;
|
||||||
}
|
}
|
||||||
@ -356,6 +371,60 @@
|
|||||||
response: responseExample.value || '',
|
response: responseExample.value || '',
|
||||||
};
|
};
|
||||||
isDirty.value = false;
|
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 = 留下继续编辑。
|
* resolve true = 可离开(丢弃更改),false = 留下继续编辑。
|
||||||
*/
|
*/
|
||||||
async function confirmLeaveUnsaved(options?: { content?: string }): Promise<boolean> {
|
async function confirmLeaveUnsaved(options?: { content?: string }): Promise<boolean> {
|
||||||
|
// 仍在回填:用户不可能已编辑,直接放行
|
||||||
|
if (detailHydrating.value || !dirtyBaselineReady.value) return true;
|
||||||
refreshDirtyState();
|
refreshDirtyState();
|
||||||
if (!hasUnsavedChanges()) return true;
|
if (!hasUnsavedChanges()) return true;
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@ -562,11 +633,19 @@
|
|||||||
(id, oldId) => {
|
(id, oldId) => {
|
||||||
if (oldId) saveTabMemory(oldId);
|
if (oldId) saveTabMemory(oldId);
|
||||||
if (id) {
|
if (id) {
|
||||||
|
// 切换接口:进入回填态,避免沿用上一接口的脏标记
|
||||||
|
detailHydrating.value = true;
|
||||||
|
dirtyBaselineReady.value = false;
|
||||||
|
manualDirty.value = false;
|
||||||
|
isDirty.value = false;
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
restoreTabMemory(id);
|
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 {
|
} else {
|
||||||
fixedParams.value = payload.fixedParams;
|
fixedParams.value = payload.fixedParams;
|
||||||
fixedParamsHasChanges.value = payload.fixedParams.length > 0;
|
// 有固定配置数据 ≠ 未保存更改;加载后应视为干净
|
||||||
|
fixedParamsHasChanges.value = false;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
queryFixedTableRef.value?.reloadData?.(payload.fixedParams);
|
queryFixedTableRef.value?.reloadData?.(payload.fixedParams);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3046,6 +3046,8 @@ export default {
|
|||||||
'system.interface.mapping.table.providerParamName': 'Provider parameter name',
|
'system.interface.mapping.table.providerParamName': 'Provider parameter name',
|
||||||
'system.interface.mapping.table.providerParamNameCn': 'Provider parameter description',
|
'system.interface.mapping.table.providerParamNameCn': 'Provider parameter description',
|
||||||
'system.interface.mapping.table.paramValue': 'Parameter value',
|
'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.inputParam': 'Input parameter',
|
||||||
'system.interface.mapping.table.inputParamCn': 'Input parameter description',
|
'system.interface.mapping.table.inputParamCn': 'Input parameter description',
|
||||||
'system.interface.mapping.actions.prefixScript': 'Prefix script',
|
'system.interface.mapping.actions.prefixScript': 'Prefix script',
|
||||||
@ -4085,8 +4087,12 @@ export default {
|
|||||||
'system.warehouseAddress.fields.recipient': 'Recipient',
|
'system.warehouseAddress.fields.recipient': 'Recipient',
|
||||||
'system.warehouseAddress.fields.contactPhone': 'Contact Phone',
|
'system.warehouseAddress.fields.contactPhone': 'Contact Phone',
|
||||||
'system.warehouseAddress.fields.shippingAddress': 'Shipping Address',
|
'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.servicePhone': 'Service Phone',
|
||||||
'system.warehouseAddress.fields.serviceWechat': 'Service WeChat',
|
'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.add': 'Add',
|
||||||
'system.warehouseAddress.actions.edit': 'Edit',
|
'system.warehouseAddress.actions.edit': 'Edit',
|
||||||
'system.warehouseAddress.actions.copy': 'Copy',
|
'system.warehouseAddress.actions.copy': 'Copy',
|
||||||
|
|||||||
@ -2908,6 +2908,8 @@ export default {
|
|||||||
'system.interface.mapping.table.providerParamName': '服务商参数名',
|
'system.interface.mapping.table.providerParamName': '服务商参数名',
|
||||||
'system.interface.mapping.table.providerParamNameCn': '服务商参数描述',
|
'system.interface.mapping.table.providerParamNameCn': '服务商参数描述',
|
||||||
'system.interface.mapping.table.paramValue': '参数值',
|
'system.interface.mapping.table.paramValue': '参数值',
|
||||||
|
'system.interface.mapping.table.paramValueTip':
|
||||||
|
'若填写的值既像数字又像字符串(例如电话号 075525131031),请用单引号包起来写成 \'075525131031\',明确为字符串。直接填写会被当成数字解析,可能导致前导零丢失或类型错误。',
|
||||||
'system.interface.mapping.table.inputParam': '入参',
|
'system.interface.mapping.table.inputParam': '入参',
|
||||||
'system.interface.mapping.table.inputParamCn': '入参描述',
|
'system.interface.mapping.table.inputParamCn': '入参描述',
|
||||||
'system.interface.mapping.actions.prefixScript': '前置脚本',
|
'system.interface.mapping.actions.prefixScript': '前置脚本',
|
||||||
@ -4113,8 +4115,12 @@ export default {
|
|||||||
'system.warehouseAddress.fields.recipient': '收件人',
|
'system.warehouseAddress.fields.recipient': '收件人',
|
||||||
'system.warehouseAddress.fields.contactPhone': '联系电话',
|
'system.warehouseAddress.fields.contactPhone': '联系电话',
|
||||||
'system.warehouseAddress.fields.shippingAddress': '收货地址',
|
'system.warehouseAddress.fields.shippingAddress': '收货地址',
|
||||||
|
'system.warehouseAddress.fields.sort': '排序',
|
||||||
|
'system.warehouseAddress.fields.remark': '备注',
|
||||||
'system.warehouseAddress.fields.servicePhone': '客服电话',
|
'system.warehouseAddress.fields.servicePhone': '客服电话',
|
||||||
'system.warehouseAddress.fields.serviceWechat': '客服微信',
|
'system.warehouseAddress.fields.serviceWechat': '客服微信',
|
||||||
|
'system.warehouseAddress.placeholder.sort': '越小越靠前,可不填',
|
||||||
|
'system.warehouseAddress.placeholder.remark': '选填,展示给客户的说明',
|
||||||
'system.warehouseAddress.actions.add': '新增',
|
'system.warehouseAddress.actions.add': '新增',
|
||||||
'system.warehouseAddress.actions.edit': '编辑',
|
'system.warehouseAddress.actions.edit': '编辑',
|
||||||
'system.warehouseAddress.actions.copy': '复制',
|
'system.warehouseAddress.actions.copy': '复制',
|
||||||
|
|||||||
49
src/utils/warehouseShipAddress.ts
Normal file
49
src/utils/warehouseShipAddress.ts
Normal file
@ -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}`;
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, nextTick } from 'vue';
|
import { ref, computed, nextTick } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import {
|
import {
|
||||||
getServiceProviderAccountGetAccountPageList,
|
getServiceProviderAccountGetAccountPageList,
|
||||||
getServiceProviderAccountGetFieldList,
|
getServiceProviderAccountGetFieldList,
|
||||||
@ -18,6 +19,8 @@
|
|||||||
name: 'AccountList',
|
name: 'AccountList',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'saved', payload: { id: number; name?: string; code?: string }): void;
|
(e: 'saved', payload: { id: number; name?: string; code?: string }): void;
|
||||||
}>();
|
}>();
|
||||||
@ -487,7 +490,8 @@
|
|||||||
>
|
>
|
||||||
<div class="account-list-body">
|
<div class="account-list-body">
|
||||||
<a-alert type="info" :show-icon="false" class="modal-form-tip">
|
<a-alert type="info" :show-icon="false" class="modal-form-tip">
|
||||||
配置服务商账户凭证;可先自定义表头再录入。修改后点确定保存,发布后才生效。
|
<p>配置服务商账户凭证;可先自定义表头再录入。修改后点确定保存,发布后才生效。</p>
|
||||||
|
<p>{{ t('system.interface.mapping.table.paramValueTip') }}</p>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
|
|
||||||
<div class="form-section-title">账户凭证</div>
|
<div class="form-section-title">账户凭证</div>
|
||||||
@ -670,6 +674,14 @@
|
|||||||
color: var(--color-text-2);
|
color: var(--color-text-2);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p + p {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-section-title {
|
.form-section-title {
|
||||||
|
|||||||
@ -8,7 +8,6 @@
|
|||||||
getGetMapParameterCascadedList,
|
getGetMapParameterCascadedList,
|
||||||
} from '@/api/InterfaceMappingServices/interfaceMap';
|
} from '@/api/InterfaceMappingServices/interfaceMap';
|
||||||
|
|
||||||
import { getParameterGetParameterList } from '@/api/ParameterServices/parameter';
|
|
||||||
import AddParameter from './AddParameter.vue';
|
import AddParameter from './AddParameter.vue';
|
||||||
import MapAiAssistModal from './MapAiAssistModal.vue';
|
import MapAiAssistModal from './MapAiAssistModal.vue';
|
||||||
|
|
||||||
@ -41,52 +40,21 @@
|
|||||||
const tableRef1 = ref();
|
const tableRef1 = ref();
|
||||||
const active = ref(0);
|
const active = ref(0);
|
||||||
const AddParameterRef = ref();
|
const AddParameterRef = ref();
|
||||||
const formsTypeList_new = ref<any[]>([]);
|
|
||||||
const maxCompletedIndex = ref<number>(-1);
|
const maxCompletedIndex = ref<number>(-1);
|
||||||
const outputAttention = ref(false);
|
const outputAttention = ref(false);
|
||||||
const outputStepRef = ref<HTMLElement | null>(null);
|
const outputStepRef = ref<HTMLElement | null>(null);
|
||||||
let outputAttentionTimer: ReturnType<typeof setTimeout> | null = null;
|
let outputAttentionTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
/** 当前已加载的映射上下文,避免切 tab 重复拉取 */
|
||||||
|
const loadedContextKey = ref('');
|
||||||
|
let initSeq = 0;
|
||||||
|
let inflightInit: Promise<void> | null = null;
|
||||||
|
let inflightInitKey = '';
|
||||||
|
const detailLoading = ref(false);
|
||||||
|
|
||||||
interface Indexable {
|
interface Indexable {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 请求去重:记录正在请求的接口ID
|
|
||||||
const loadingParameterId = ref<number | null>(null);
|
|
||||||
|
|
||||||
async function formsValueInfoGetList() {
|
|
||||||
// 如果正在加载相同的接口,直接返回
|
|
||||||
if (loadingParameterId.value === props.interfaceId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有接口ID,直接返回
|
|
||||||
if (!props.interfaceId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadingParameterId.value = props.interfaceId;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { data } = await getParameterGetParameterList({
|
|
||||||
isInPara: true,
|
|
||||||
id: Number(props.interfaceId),
|
|
||||||
onlyLeafs: true,
|
|
||||||
});
|
|
||||||
formsTypeList_new.value = data;
|
|
||||||
formsTypeList.value = data;
|
|
||||||
} catch (error) {
|
|
||||||
} finally {
|
|
||||||
loadingParameterId.value = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听 interfaceId 变化,只在变化时请求
|
|
||||||
watch(() => props.interfaceId, (newId) => {
|
|
||||||
if (newId) {
|
|
||||||
formsValueInfoGetList();
|
|
||||||
}
|
|
||||||
}, { immediate: true });
|
|
||||||
const tableProps1 = computed(() => {
|
const tableProps1 = computed(() => {
|
||||||
return {
|
return {
|
||||||
columnsKey: 'mapDetails111',
|
columnsKey: 'mapDetails111',
|
||||||
@ -168,6 +136,7 @@
|
|||||||
title: t('system.interface.mapping.table.paramValue'),
|
title: t('system.interface.mapping.table.paramValue'),
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
slots: {
|
slots: {
|
||||||
|
header: 'fixedValueHeader',
|
||||||
edit: 'fixedValueEdit',
|
edit: 'fixedValueEdit',
|
||||||
},
|
},
|
||||||
editRender: {},
|
editRender: {},
|
||||||
@ -285,9 +254,17 @@
|
|||||||
id: e.interfaceId,
|
id: e.interfaceId,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
invalidateCascaderOptionsCache();
|
||||||
};
|
};
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
restaurants.value = loadAll();
|
restaurants.value = loadAll();
|
||||||
|
// 面板首次挂载时自行拉数,避免依赖父级 setTimeout
|
||||||
|
if (props.serviceProviderId && props.interfaceId) {
|
||||||
|
void initData({
|
||||||
|
serviceProviderId: props.serviceProviderId,
|
||||||
|
interfaceId: props.interfaceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
type RestaurantItem = {
|
type RestaurantItem = {
|
||||||
value: string;
|
value: string;
|
||||||
@ -328,24 +305,48 @@
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
const mapAliasEditRef = ref();
|
const mapAliasEditRef = ref();
|
||||||
function getOptions(item: any, isReverse = false) {
|
/** 级联 options 按步骤缓存,避免每行渲染都深拷贝/打标 */
|
||||||
if (isReverse)
|
const cascaderOptionsCache = ref<Map<string, any[]>>(new Map());
|
||||||
return setlabel(cascadedList.value.slice(1, cascadedList.value.length));
|
|
||||||
|
|
||||||
|
function invalidateCascaderOptionsCache() {
|
||||||
|
cascaderOptionsCache.value = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOptions(item: any, isReverse = false) {
|
||||||
|
const cacheKey = isReverse
|
||||||
|
? 'reverse'
|
||||||
|
: `fwd-${item?.interfaceId ?? 'unknown'}`;
|
||||||
|
const cached = cascaderOptionsCache.value.get(cacheKey);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
|
let result: any[];
|
||||||
|
if (isReverse) {
|
||||||
|
result = setlabel(cascadedList.value.slice(1, cascadedList.value.length));
|
||||||
|
} else {
|
||||||
const ids: number[] = inParaMapList.value.map((t) => t.interfaceId!);
|
const ids: number[] = inParaMapList.value.map((t) => t.interfaceId!);
|
||||||
const findI = ids.findIndex((i) => i === item.interfaceId);
|
const findI = ids.findIndex((i) => i === item.interfaceId);
|
||||||
let data: any[] = [];
|
let data: any[] = [];
|
||||||
if (findI === -1 || findI === 0) {
|
if (findI === -1 || findI === 0) {
|
||||||
data = [cascadedList.value[0]];
|
data = cascadedList.value[0] ? [cascadedList.value[0]] : [];
|
||||||
} else {
|
} else {
|
||||||
data = [cascadedList.value[0]].concat(
|
data = [cascadedList.value[0]].concat(
|
||||||
cascadedList.value.filter((t) => {
|
cascadedList.value.filter((t) => {
|
||||||
return ids.slice(0, findI).includes(t.interfaceId!);
|
return ids.slice(0, findI).includes(t.interfaceId!);
|
||||||
})
|
}),
|
||||||
);
|
).filter(Boolean);
|
||||||
}
|
}
|
||||||
return setlabel(data);
|
result = setlabel(data);
|
||||||
}
|
}
|
||||||
|
cascaderOptionsCache.value.set(cacheKey, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当前入参步骤 / 返参步骤共用一份 options,模板里不要对每行再算 */
|
||||||
|
const activeInputCascaderOptions = computed(() => {
|
||||||
|
if (!activeInParaItem.value) return [];
|
||||||
|
return getOptions(activeInParaItem.value, false);
|
||||||
|
});
|
||||||
|
const outputCascaderOptions = computed(() => getOptions({ interfaceId: 0 }, true));
|
||||||
function mapAliasEditCheng(
|
function mapAliasEditCheng(
|
||||||
e: any,
|
e: any,
|
||||||
row: API.InterfaceParameterMapOutputValueObject
|
row: API.InterfaceParameterMapOutputValueObject
|
||||||
@ -424,7 +425,6 @@
|
|||||||
temporaryItem.value = undefined;
|
temporaryItem.value = undefined;
|
||||||
emits('save-model-enter');
|
emits('save-model-enter');
|
||||||
}
|
}
|
||||||
const formsTypeList = ref<any[]>([]);
|
|
||||||
// 支持多关键词(空格分隔)、大小写不敏感,匹配别名/中文名/名称/代码等字段
|
// 支持多关键词(空格分隔)、大小写不敏感,匹配别名/中文名/名称/代码等字段
|
||||||
function filterMethod(val: string, node: any) {
|
function filterMethod(val: string, node: any) {
|
||||||
const query = (val ?? '').toString().trim().toLowerCase();
|
const query = (val ?? '').toString().trim().toLowerCase();
|
||||||
@ -471,7 +471,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getVali(row: Indexable) {
|
function getVali(row: Indexable) {
|
||||||
tableRef.value?.forEach((element: any) => {
|
const refs = tableRef.value;
|
||||||
|
const tables = Array.isArray(refs) ? refs : refs ? [refs] : [];
|
||||||
|
tables.forEach((element: any) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
element?.editRowCellEvent(row, 'mapAlias');
|
element?.editRowCellEvent(row, 'mapAlias');
|
||||||
element?.xGrid.scrollToRow(row, 'mapAlias');
|
element?.xGrid.scrollToRow(row, 'mapAlias');
|
||||||
@ -485,36 +487,77 @@
|
|||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resultsFn() {
|
function buildContextKey(data: { serviceProviderId?: number; interfaceId?: number }) {
|
||||||
formsTypeList_new.value = [];
|
return `${data?.serviceProviderId ?? 0}:${data?.interfaceId ?? 0}`;
|
||||||
formsTypeList.value = [];
|
|
||||||
mapDetails.value = [];
|
|
||||||
active.value = 0;
|
|
||||||
maxCompletedIndex.value = -1;
|
|
||||||
inParaMapList.value = [];
|
|
||||||
}
|
|
||||||
async function initData(data: any, options?: { activeIndex?: number }) {
|
|
||||||
const nextActive = options?.activeIndex;
|
|
||||||
resultsFn();
|
|
||||||
if (typeof nextActive === 'number') {
|
|
||||||
active.value = nextActive;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getEditMapDetails(data).then((resp) => {
|
function hasLoadedData() {
|
||||||
const detail = (resp as any)?.data || resp || {};
|
return !!(inParaMapList.value.length || mapDetails.value.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initData(
|
||||||
|
data: any,
|
||||||
|
options?: { activeIndex?: number; force?: boolean },
|
||||||
|
) {
|
||||||
|
const ctx = buildContextKey(data);
|
||||||
|
const nextActive = options?.activeIndex;
|
||||||
|
const force = !!options?.force || typeof nextActive === 'number';
|
||||||
|
|
||||||
|
// 同服务商/接口已加载过:切回映射明细 tab 时跳过重复请求
|
||||||
|
if (!force && loadedContextKey.value === ctx && hasLoadedData()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 挂载与父级同时触发时合并为一次请求
|
||||||
|
if (!force && inflightInit && inflightInitKey === ctx) {
|
||||||
|
await inflightInit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const seq = ++initSeq;
|
||||||
|
detailLoading.value = true;
|
||||||
|
|
||||||
|
const run = (async () => {
|
||||||
|
try {
|
||||||
|
const [detailResp] = await Promise.all([
|
||||||
|
getEditMapDetails(data),
|
||||||
|
getCascadedList(data),
|
||||||
|
]);
|
||||||
|
if (seq !== initSeq) return;
|
||||||
|
|
||||||
|
const detail = (detailResp as any)?.data || detailResp || {};
|
||||||
inParaMapList.value = detail.inParaMapList || [];
|
inParaMapList.value = detail.inParaMapList || [];
|
||||||
mapDetails.value = detail.mapDetails || [];
|
mapDetails.value = detail.mapDetails || [];
|
||||||
});
|
loadedContextKey.value = ctx;
|
||||||
|
invalidateCascaderOptionsCache();
|
||||||
|
|
||||||
getCascadedList(data);
|
if (typeof nextActive === 'number') {
|
||||||
|
active.value = nextActive;
|
||||||
|
} else {
|
||||||
|
active.value = 0;
|
||||||
|
maxCompletedIndex.value = -1;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (seq === initSeq) detailLoading.value = false;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
setActive(active.value);
|
inflightInit = run;
|
||||||
|
inflightInitKey = ctx;
|
||||||
|
try {
|
||||||
|
await run;
|
||||||
|
} finally {
|
||||||
|
if (inflightInit === run) {
|
||||||
|
inflightInit = null;
|
||||||
|
inflightInitKey = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
tableRef,
|
tableRef,
|
||||||
getdata,
|
getdata,
|
||||||
initData,
|
initData,
|
||||||
|
hasLoadedData,
|
||||||
getVali,
|
getVali,
|
||||||
getReturnVali,
|
getReturnVali,
|
||||||
advanceAfterSave,
|
advanceAfterSave,
|
||||||
@ -640,20 +683,38 @@
|
|||||||
<span class="output-hint-action">{{ t('system.interface.mapping.outputMappingGoFill') }}</span>
|
<span class="output-hint-action">{{ t('system.interface.mapping.outputMappingGoFill') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="content-pane">
|
<div class="content-pane">
|
||||||
|
<a-spin :loading="detailLoading" class="map-detail-spin">
|
||||||
<div class="content-scroll">
|
<div class="content-scroll">
|
||||||
<div class="content-body">
|
<div class="content-body">
|
||||||
<template v-for="(item, index) in inParaMapList" :key="index">
|
<!-- 只挂载当前步骤表格,避免多步骤×多参数同时渲染 -->
|
||||||
<div v-show="active === index" class="map-table-wrapper">
|
<div
|
||||||
|
v-if="activeInParaItem && active >= 0"
|
||||||
|
:key="`in-${activeInParaItem.interfaceMapId || active}`"
|
||||||
|
class="map-table-wrapper"
|
||||||
|
>
|
||||||
<div class="map-scroll">
|
<div class="map-scroll">
|
||||||
<TablePro
|
<TablePro
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
v-bind="tableProps"
|
v-bind="tableProps"
|
||||||
class="map-table"
|
class="map-table"
|
||||||
:table-config="{ data: item?.mapDetails || [] }"
|
:table-config="{ data: activeInParaItem?.mapDetails || [] }"
|
||||||
>
|
>
|
||||||
<template #toolbar_right>
|
<template #toolbar_right>
|
||||||
<slot name="toolbar_right" />
|
<slot name="toolbar_right" />
|
||||||
</template>
|
</template>
|
||||||
|
<template #fixedValueHeader>
|
||||||
|
<span class="param-value-header">
|
||||||
|
{{ t('system.interface.mapping.table.paramValue') }}
|
||||||
|
<a-tooltip position="top" :content-style="{ maxWidth: '320px' }">
|
||||||
|
<template #content>
|
||||||
|
<div class="param-value-help">
|
||||||
|
{{ t('system.interface.mapping.table.paramValueTip') }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<IconQuestionCircle class="param-value-help-icon" @click.stop />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<template #fixedValueEdit="{ row }">
|
<template #fixedValueEdit="{ row }">
|
||||||
<a-auto-complete
|
<a-auto-complete
|
||||||
v-model="row.fixedValue"
|
v-model="row.fixedValue"
|
||||||
@ -677,7 +738,7 @@
|
|||||||
<a-cascader
|
<a-cascader
|
||||||
ref="mapAliasEditRef"
|
ref="mapAliasEditRef"
|
||||||
v-model="row.mapId"
|
v-model="row.mapId"
|
||||||
:options="getOptions(item, false)"
|
:options="activeInputCascaderOptions"
|
||||||
:filter-option="filterMethod"
|
:filter-option="filterMethod"
|
||||||
allow-clear
|
allow-clear
|
||||||
allow-search
|
allow-search
|
||||||
@ -712,9 +773,7 @@
|
|||||||
</TablePro>
|
</TablePro>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<div v-else-if="active === -1" class="map-table-wrapper">
|
||||||
<template v-if="active === -1">
|
|
||||||
<div class="map-table-wrapper">
|
|
||||||
<a-alert
|
<a-alert
|
||||||
v-if="outputNeedsAttention"
|
v-if="outputNeedsAttention"
|
||||||
type="warning"
|
type="warning"
|
||||||
@ -734,11 +793,10 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template #mapAlias="{ row }">
|
<template #mapAlias="{ row }">
|
||||||
|
|
||||||
<a-cascader
|
<a-cascader
|
||||||
ref="mapAliasEditRef"
|
ref="mapAliasEditRef"
|
||||||
v-model="row.mapId"
|
v-model="row.mapId"
|
||||||
:options="getOptions({ interfaceId: 0 }, true)"
|
:options="outputCascaderOptions"
|
||||||
:filter-option="filterMethod"
|
:filter-option="filterMethod"
|
||||||
allow-search
|
allow-search
|
||||||
allow-clear
|
allow-clear
|
||||||
@ -766,9 +824,9 @@
|
|||||||
</TablePro>
|
</TablePro>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</a-spin>
|
||||||
</div>
|
</div>
|
||||||
<AddParameter ref="AddParameterRef" @seva="save" />
|
<AddParameter ref="AddParameterRef" @seva="save" />
|
||||||
<MapAiAssistModal
|
<MapAiAssistModal
|
||||||
@ -783,6 +841,13 @@
|
|||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.entered-map { display: flex; flex-direction: column; width: 100%; height: 100%; min-height: 0; overflow: hidden; }
|
.entered-map { display: flex; flex-direction: column; width: 100%; height: 100%; min-height: 0; overflow: hidden; }
|
||||||
|
.map-detail-spin { display: block; width: 100%; height: 100%; min-height: 0; }
|
||||||
|
.map-detail-spin :deep(.arco-spin-children) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
.steps-bar {
|
.steps-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -821,13 +886,24 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.unequal-help-icon,
|
.unequal-help-icon,
|
||||||
.script-help-icon {
|
.script-help-icon,
|
||||||
|
.param-value-help-icon {
|
||||||
color: var(--color-text-3);
|
color: var(--color-text-3);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
}
|
}
|
||||||
|
.param-value-header {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.param-value-help {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
.unequal-help-icon:hover,
|
.unequal-help-icon:hover,
|
||||||
.script-help-icon:hover {
|
.script-help-icon:hover,
|
||||||
|
.param-value-help-icon:hover {
|
||||||
color: var(--app-primary-color);
|
color: var(--app-primary-color);
|
||||||
}
|
}
|
||||||
.step-item { position: relative; display: flex; align-items: center; color: var(--color-text-3); cursor: pointer; flex-shrink: 0; }
|
.step-item { position: relative; display: flex; align-items: center; color: var(--color-text-3); cursor: pointer; flex-shrink: 0; }
|
||||||
|
|||||||
@ -119,10 +119,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleSearch() {
|
function handleSearch() {
|
||||||
loadList();
|
loadList({ keepSelection: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadList() {
|
async function loadList(options?: { keepSelection?: boolean }) {
|
||||||
if (!props.id) {
|
if (!props.id) {
|
||||||
list.value = [];
|
list.value = [];
|
||||||
selectedId.value = null;
|
selectedId.value = null;
|
||||||
@ -141,10 +141,14 @@
|
|||||||
const raw: any = response || {};
|
const raw: any = response || {};
|
||||||
const data: any = raw.items ?? raw.data ?? raw.list ?? raw.records ?? raw;
|
const data: any = raw.items ?? raw.data ?? raw.list ?? raw.records ?? raw;
|
||||||
list.value = Array.isArray(data) ? data : [];
|
list.value = Array.isArray(data) ? data : [];
|
||||||
// 仅当当前选中项在新列表中不存在时才清空,避免“点击一次又掉选中”
|
if (!options?.keepSelection) {
|
||||||
if (
|
// 切换上游接口时不默认选中第一条映射
|
||||||
|
selectedId.value = null;
|
||||||
|
} else if (
|
||||||
selectedId.value !== null &&
|
selectedId.value !== null &&
|
||||||
!list.value.some((row: any, index: number) => getRowKey(row, index) === selectedId.value)
|
!list.value.some(
|
||||||
|
(row: any, index: number) => String(getRowKey(row, index)) === String(selectedId.value),
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
selectedId.value = null;
|
selectedId.value = null;
|
||||||
}
|
}
|
||||||
@ -157,19 +161,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
watch(() => props.id, (id) => {
|
watch(() => props.id, (id) => {
|
||||||
if (id) loadList();
|
// 换接口先清选中,避免沿用上一次的服务商高亮
|
||||||
else {
|
|
||||||
list.value = [];
|
|
||||||
selectedId.value = null;
|
selectedId.value = null;
|
||||||
}
|
if (id) loadList({ keepSelection: false });
|
||||||
|
else list.value = [];
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
// 暴露与旧用法兼容的 reload 接口
|
// 暴露与旧用法兼容的 reload:默认保留当前选中(发布/保存后刷新不丢高亮)
|
||||||
tableRef.value = { reload: loadList };
|
tableRef.value = {
|
||||||
|
reload: (options?: { keepSelection?: boolean }) =>
|
||||||
|
loadList({ keepSelection: options?.keepSelection !== false }),
|
||||||
|
};
|
||||||
|
|
||||||
function setSearchKeyword(val: string) {
|
function setSearchKeyword(val: string) {
|
||||||
searchKeyword.value = val || '';
|
searchKeyword.value = val || '';
|
||||||
loadList();
|
loadList({ keepSelection: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 右侧内容
|
// 右侧内容
|
||||||
@ -191,7 +197,7 @@
|
|||||||
serviceProviderId: row.serviceProviderId,
|
serviceProviderId: row.serviceProviderId,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
$baseMessage(t('system.interface.message.deleteSuccess'), 'success');
|
$baseMessage(t('system.interface.message.deleteSuccess'), 'success');
|
||||||
loadList();
|
loadList({ keepSelection: false });
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -350,6 +356,8 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
registerReleaseMethods.closeModal();
|
registerReleaseMethods.closeModal();
|
||||||
|
// 发布后刷新列表,保留当前服务商选中
|
||||||
|
void loadList({ keepSelection: true });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -390,7 +398,7 @@
|
|||||||
:show-node-index="true"
|
:show-node-index="true"
|
||||||
:show-node-code="true"
|
:show-node-code="true"
|
||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
@refresh="loadList"
|
@refresh="() => loadList({ keepSelection: true })"
|
||||||
@add="handleAdd"
|
@add="handleAdd"
|
||||||
@select="handleMenuSelect"
|
@select="handleMenuSelect"
|
||||||
@node-click="handleNodeClick"
|
@node-click="handleNodeClick"
|
||||||
|
|||||||
@ -53,6 +53,8 @@
|
|||||||
const active = ref(0);
|
const active = ref(0);
|
||||||
const activeContentTab = ref('detail'); // 接口明细、API调试、映射明细
|
const activeContentTab = ref('detail'); // 接口明细、API调试、映射明细
|
||||||
const currentActiveTab = ref('detail'); // 用于保存按钮判断的当前tab
|
const currentActiveTab = ref('detail'); // 用于保存按钮判断的当前tab
|
||||||
|
/** 映射明细首次打开后保持挂载,避免反复销毁重建 */
|
||||||
|
const mappingPanelReady = ref(false);
|
||||||
const apiDebuggerRef = ref<any>();
|
const apiDebuggerRef = ref<any>();
|
||||||
const apiDebuggerRefreshKey = ref(0);
|
const apiDebuggerRefreshKey = ref(0);
|
||||||
|
|
||||||
@ -1147,10 +1149,11 @@
|
|||||||
// 将上游接口ID同步给右侧明细区域
|
// 将上游接口ID同步给右侧明细区域
|
||||||
upStreamIdRef.value = Number(nodeData.id);
|
upStreamIdRef.value = Number(nodeData.id);
|
||||||
|
|
||||||
// 重置映射明细区域,确保EnteredMap组件重新渲染
|
// 重置映射明细区域
|
||||||
serviceProviderId.value = 0;
|
serviceProviderId.value = 0;
|
||||||
serviceProviderName.value = '';
|
serviceProviderName.value = '';
|
||||||
MapServiceProviderROW.value = null;
|
MapServiceProviderROW.value = null;
|
||||||
|
mappingPanelReady.value = false;
|
||||||
// 未选服务商时映射/调试不可用,回到接口明细
|
// 未选服务商时映射/调试不可用,回到接口明细
|
||||||
if (activeContentTab.value === 'mapping' || activeContentTab.value === 'debugger') {
|
if (activeContentTab.value === 'mapping' || activeContentTab.value === 'debugger') {
|
||||||
activeContentTab.value = 'detail';
|
activeContentTab.value = 'detail';
|
||||||
@ -1159,7 +1162,8 @@
|
|||||||
// 刷新右侧映射服务商列表
|
// 刷新右侧映射服务商列表
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (MapServiceProviderRef.value && MapServiceProviderRef.value.tableRef) {
|
if (MapServiceProviderRef.value && MapServiceProviderRef.value.tableRef) {
|
||||||
MapServiceProviderRef.value.tableRef.reload();
|
// 切换上游接口:清空中间映射选中,不默认第一条
|
||||||
|
MapServiceProviderRef.value.tableRef.reload({ keepSelection: false });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1262,17 +1266,22 @@
|
|||||||
initInterfaceConfig();
|
initInterfaceConfig();
|
||||||
}, 100);
|
}, 100);
|
||||||
} else if (newTab === 'mapping') {
|
} else if (newTab === 'mapping') {
|
||||||
// 切换回映射明细tab时,重新初始化EnteredMap组件
|
mappingPanelReady.value = true;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
await nextTick();
|
const map = unref(enteredMapRef) as any;
|
||||||
setTimeout(() => {
|
// 已缓存同一服务商映射时不再重复拉数;首次挂载或未加载时再 init
|
||||||
if (enteredMapRef.value && selectedInterface.value && upStreamIdRef.value) {
|
if (
|
||||||
(unref(enteredMapRef) as any)?.initData?.({
|
map &&
|
||||||
|
selectedInterface.value &&
|
||||||
|
upStreamIdRef.value &&
|
||||||
|
serviceProviderId.value &&
|
||||||
|
!map.hasLoadedData?.()
|
||||||
|
) {
|
||||||
|
await map.initData?.({
|
||||||
serviceProviderId: serviceProviderId.value,
|
serviceProviderId: serviceProviderId.value,
|
||||||
interfaceId: upStreamIdRef.value,
|
interfaceId: upStreamIdRef.value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 100);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1566,10 +1575,14 @@
|
|||||||
|
|
||||||
<div class="interface-detail-tab-content">
|
<div class="interface-detail-tab-content">
|
||||||
<template v-if="selectedInterface">
|
<template v-if="selectedInterface">
|
||||||
<!-- 映射明细内容 -->
|
<!-- 映射明细:首次打开后 v-show 保活,避免切 tab 整表重挂 -->
|
||||||
<div v-if="activeContentTab === 'mapping'" class="mapping-content">
|
<div
|
||||||
|
v-if="mappingPanelReady && serviceProviderId"
|
||||||
|
v-show="activeContentTab === 'mapping'"
|
||||||
|
class="mapping-content"
|
||||||
|
>
|
||||||
<EnteredMap
|
<EnteredMap
|
||||||
:key="`entered-map-${selectedInterface?.id}-${serviceProviderId}-${activeContentTab}`"
|
:key="`entered-map-${selectedInterface?.id}-${serviceProviderId}`"
|
||||||
ref="enteredMapRef"
|
ref="enteredMapRef"
|
||||||
class="h-full w-full"
|
class="h-full w-full"
|
||||||
:service-provider-id="serviceProviderId"
|
:service-provider-id="serviceProviderId"
|
||||||
|
|||||||
@ -325,40 +325,67 @@
|
|||||||
</p>
|
</p>
|
||||||
<div v-if="form.inboundMethod === 'selfShip'" class="h5-po__wh-card">
|
<div v-if="form.inboundMethod === 'selfShip'" class="h5-po__wh-card">
|
||||||
<div class="h5-po__wh-head">
|
<div class="h5-po__wh-head">
|
||||||
|
<strong>{{ t('h5.placeOrder.selfShipAddressTitle') }}</strong>
|
||||||
|
</div>
|
||||||
|
<p v-if="!warehouseOptions.length" class="h5-po__hint">
|
||||||
|
{{ t('h5.placeOrder.selfShipNoWarehouse') }}
|
||||||
|
</p>
|
||||||
|
<p v-else-if="!warehousesWithShipAddress.length" class="h5-po__hint">
|
||||||
|
{{ t('h5.placeOrder.selfShipNoAddressConfigured') }}
|
||||||
|
</p>
|
||||||
|
<template v-else>
|
||||||
|
<div class="h5-po__wh-list">
|
||||||
|
<div
|
||||||
|
v-for="opt in warehouseOptions"
|
||||||
|
:key="opt.value"
|
||||||
|
class="h5-po__wh-item"
|
||||||
|
:class="{
|
||||||
|
'is-selected': String(opt.value) === String(form.warehouseId ?? ''),
|
||||||
|
'is-empty': !warehouseHasAddress(opt),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="h5-po__wh-item-head">
|
||||||
<strong>
|
<strong>
|
||||||
{{ t('h5.placeOrder.selfShipAddressTitle') }}
|
{{ opt.label || opt.value }}
|
||||||
<template v-if="selectedWarehouseOption?.label">
|
<span
|
||||||
· {{ selectedWarehouseOption.label }}
|
v-if="String(opt.value) === String(form.warehouseId ?? '')"
|
||||||
</template>
|
class="h5-po__wh-badge"
|
||||||
|
>
|
||||||
|
{{ t('h5.placeOrder.selfShipCurrent') }}
|
||||||
|
</span>
|
||||||
</strong>
|
</strong>
|
||||||
<button
|
<button
|
||||||
v-if="selectedWarehouseHasAddress"
|
v-if="warehouseHasAddress(opt)"
|
||||||
type="button"
|
type="button"
|
||||||
class="h5-po__wh-copy"
|
class="h5-po__wh-copy"
|
||||||
@click="copyWarehouseShipAddress"
|
@click="copyWarehouseShipAddress(opt)"
|
||||||
>
|
>
|
||||||
{{ t('h5.placeOrder.selfShipCopy') }}
|
{{ t('h5.placeOrder.selfShipCopy') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="!selectedWarehouseOption" class="h5-po__hint">
|
<p v-if="!warehouseHasAddress(opt)" class="h5-po__hint">
|
||||||
{{ t('h5.placeOrder.selfShipSelectWarehouse') }}
|
{{ t('h5.placeOrder.selfShipAddressEmpty') }}
|
||||||
</p>
|
</p>
|
||||||
<template v-else-if="!selectedWarehouseHasAddress">
|
|
||||||
<p class="h5-po__hint">{{ t('h5.placeOrder.selfShipAddressEmpty') }}</p>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<p>
|
<p>
|
||||||
<span>{{ t('h5.placeOrder.selfShipRecipient') }}</span>
|
<span>{{ t('h5.placeOrder.selfShipRecipient') }}</span>
|
||||||
{{ selectedWarehouseOption.recipient || '-' }}
|
{{ displayWarehouseRecipient(opt) }}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span>{{ t('h5.placeOrder.selfShipPhone') }}</span>
|
<span>{{ t('h5.placeOrder.selfShipPhone') }}</span>
|
||||||
{{ selectedWarehouseOption.contactPhone || '-' }}
|
{{ opt.contactPhone || '-' }}
|
||||||
</p>
|
</p>
|
||||||
<p class="is-addr">
|
<p class="is-addr">
|
||||||
<span>{{ t('h5.placeOrder.selfShipAddress') }}</span>
|
<span>{{ t('h5.placeOrder.selfShipAddress') }}</span>
|
||||||
{{ selectedWarehouseOption.shippingAddress || '-' }}
|
{{ displayWarehouseShippingAddress(opt) }}
|
||||||
</p>
|
</p>
|
||||||
|
<p v-if="opt.remark">
|
||||||
|
<span>{{ t('h5.placeOrder.selfShipRemark') }}</span>
|
||||||
|
{{ opt.remark }}
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<p class="h5-po__hint">{{ t('h5.placeOrder.selfShipAddressHint') }}</p>
|
<p class="h5-po__hint">{{ t('h5.placeOrder.selfShipAddressHint') }}</p>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -1153,6 +1180,7 @@ import { getSalesProductDetail, getSalesProductList } from '@/api/channel/salesP
|
|||||||
import { getDictListAll } from '@/api/system/dict';
|
import { getDictListAll } from '@/api/system/dict';
|
||||||
import { getCountryList } from '@/api/system/country';
|
import { getCountryList } from '@/api/system/country';
|
||||||
import { getWarehouseList } from '@/api/system/warehouse';
|
import { getWarehouseList } from '@/api/system/warehouse';
|
||||||
|
import { resolveCurrentCustomer } from '@/api/customer/resolve';
|
||||||
import { resolveOmsAssetUrl } from '@/api/oms/common';
|
import { resolveOmsAssetUrl } from '@/api/oms/common';
|
||||||
import { normalizeCargoAttributeCode } from '@/utils/cargoAttribute';
|
import { normalizeCargoAttributeCode } from '@/utils/cargoAttribute';
|
||||||
import {
|
import {
|
||||||
@ -1160,6 +1188,13 @@ import {
|
|||||||
normalizeSkuImageForStorage,
|
normalizeSkuImageForStorage,
|
||||||
pickField,
|
pickField,
|
||||||
} from '@/utils/warehouseListMapper';
|
} from '@/utils/warehouseListMapper';
|
||||||
|
import {
|
||||||
|
compareWarehouseSort,
|
||||||
|
formatWarehouseShipAddress,
|
||||||
|
formatWarehouseShipRecipient,
|
||||||
|
} from '@/utils/warehouseShipAddress';
|
||||||
|
import { getMapIdFromAccessToken } from '@/utils/jwt';
|
||||||
|
import { useRbacUserStore } from '@/store/modules/rbac-user';
|
||||||
import { h5Navigate } from '@/utils/device';
|
import { h5Navigate } from '@/utils/device';
|
||||||
import { isCustomerContactRequiredError } from '@/utils/customerContactGate';
|
import { isCustomerContactRequiredError } from '@/utils/customerContactGate';
|
||||||
import {
|
import {
|
||||||
@ -1306,6 +1341,8 @@ const warehouseOptions = ref<
|
|||||||
recipient?: string;
|
recipient?: string;
|
||||||
contactPhone?: string;
|
contactPhone?: string;
|
||||||
shippingAddress?: string;
|
shippingAddress?: string;
|
||||||
|
sort?: number | null;
|
||||||
|
remark?: string;
|
||||||
}[]
|
}[]
|
||||||
>([]);
|
>([]);
|
||||||
const countryOptions = ref<{ value: string; label: string }[]>([]);
|
const countryOptions = ref<{ value: string; label: string }[]>([]);
|
||||||
@ -1526,25 +1563,49 @@ function skuImageSrc(url?: string | null) {
|
|||||||
return resolveOmsAssetUrl(url || '') || '';
|
return resolveOmsAssetUrl(url || '') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedWarehouseOption = computed(() => {
|
type WarehouseOption = (typeof warehouseOptions.value)[number];
|
||||||
const id = String(form.warehouseId ?? '').trim();
|
|
||||||
if (!id) return undefined;
|
const rbacUserStore = useRbacUserStore();
|
||||||
return warehouseOptions.value.find((opt) => String(opt.value) === id);
|
const currentCustomerCode = ref('');
|
||||||
});
|
|
||||||
const selectedWarehouseHasAddress = computed(() => {
|
async function loadCurrentCustomerCode() {
|
||||||
const opt = selectedWarehouseOption.value;
|
try {
|
||||||
|
const mapId = getMapIdFromAccessToken(rbacUserStore.accessToken);
|
||||||
|
const account = rbacUserStore.userInfo?.account?.trim() || '';
|
||||||
|
const data = await resolveCurrentCustomer({ mapId, account });
|
||||||
|
currentCustomerCode.value = String(data?.customerCode ?? '').trim();
|
||||||
|
} catch {
|
||||||
|
currentCustomerCode.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayWarehouseRecipient(opt?: WarehouseOption) {
|
||||||
|
return formatWarehouseShipRecipient(opt?.recipient, currentCustomerCode.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayWarehouseShippingAddress(opt?: WarehouseOption) {
|
||||||
|
return formatWarehouseShipAddress(opt?.shippingAddress, currentCustomerCode.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function warehouseHasAddress(opt?: WarehouseOption) {
|
||||||
if (!opt) return false;
|
if (!opt) return false;
|
||||||
return Boolean(opt.recipient || opt.contactPhone || opt.shippingAddress);
|
return Boolean(opt.recipient || opt.contactPhone || opt.shippingAddress);
|
||||||
});
|
}
|
||||||
|
|
||||||
async function copyWarehouseShipAddress() {
|
const warehousesWithShipAddress = computed(() =>
|
||||||
const opt = selectedWarehouseOption.value;
|
warehouseOptions.value.filter((opt) => warehouseHasAddress(opt)),
|
||||||
if (!opt) return;
|
);
|
||||||
|
|
||||||
|
async function copyWarehouseShipAddress(opt?: WarehouseOption) {
|
||||||
|
if (!opt || !warehouseHasAddress(opt)) {
|
||||||
|
Message.warning(t('h5.placeOrder.selfShipAddressEmpty'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
const text = [
|
const text = [
|
||||||
opt.label || '',
|
opt.label || '',
|
||||||
`${t('h5.placeOrder.selfShipRecipient')}:${opt.recipient || '-'}`,
|
`${t('h5.placeOrder.selfShipRecipient')}:${displayWarehouseRecipient(opt)}`,
|
||||||
`${t('h5.placeOrder.selfShipPhone')}:${opt.contactPhone || '-'}`,
|
`${t('h5.placeOrder.selfShipPhone')}:${opt.contactPhone || '-'}`,
|
||||||
`${t('h5.placeOrder.selfShipAddress')}:${opt.shippingAddress || '-'}`,
|
`${t('h5.placeOrder.selfShipAddress')}:${displayWarehouseShippingAddress(opt)}`,
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
@ -3172,15 +3233,20 @@ async function loadOptions(
|
|||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
const sortRaw = row?.sort ?? row?.Sort;
|
||||||
|
const sortNum = sortRaw == null || sortRaw === '' ? null : Number(sortRaw);
|
||||||
return {
|
return {
|
||||||
value: String(id ?? ''),
|
value: String(id ?? ''),
|
||||||
label: name || String(id),
|
label: name || String(id),
|
||||||
recipient: pick('recipient', 'Recipient'),
|
recipient: pick('recipient', 'Recipient'),
|
||||||
contactPhone: pick('contactPhone', 'ContactPhone'),
|
contactPhone: pick('contactPhone', 'ContactPhone'),
|
||||||
shippingAddress: pick('shippingAddress', 'ShippingAddress'),
|
shippingAddress: pick('shippingAddress', 'ShippingAddress'),
|
||||||
|
sort: sortNum != null && Number.isFinite(sortNum) ? sortNum : null,
|
||||||
|
remark: pick('remark', 'Remark'),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((x) => x.value);
|
.filter((x) => x.value)
|
||||||
|
.sort(compareWarehouseSort);
|
||||||
if (!opts?.skipDefaults && warehouseOptions.value.length && !form.warehouseId) {
|
if (!opts?.skipDefaults && warehouseOptions.value.length && !form.warehouseId) {
|
||||||
form.warehouseId = warehouseOptions.value[0].value;
|
form.warehouseId = warehouseOptions.value[0].value;
|
||||||
}
|
}
|
||||||
@ -3342,6 +3408,7 @@ watch(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resetForm();
|
resetForm();
|
||||||
|
void loadCurrentCustomerCode();
|
||||||
if (isEditMode.value) {
|
if (isEditMode.value) {
|
||||||
await loadOptions(null, { skipDefaults: true, skipEnsureClientNo: true });
|
await loadOptions(null, { skipDefaults: true, skipEnsureClientNo: true });
|
||||||
await loadSenders({ skipAutoSelect: true });
|
await loadSenders({ skipAutoSelect: true });
|
||||||
@ -3838,6 +3905,61 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h5-po__wh-list {
|
||||||
|
margin-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
max-height: 320px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-po__wh-item {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #e5e6eb;
|
||||||
|
background: #f7f8fa;
|
||||||
|
|
||||||
|
&.is-selected {
|
||||||
|
border-color: var(--app-primary-color);
|
||||||
|
background: color-mix(in srgb, var(--app-primary-color) 8%, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-empty {
|
||||||
|
opacity: 0.72;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-po__wh-item-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1d2129;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-po__wh-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 18px;
|
||||||
|
padding: 0 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--app-primary-color);
|
||||||
|
background: color-mix(in srgb, var(--app-primary-color) 14%, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
.h5-po__wh-copy {
|
.h5-po__wh-copy {
|
||||||
border: none;
|
border: none;
|
||||||
background: color-mix(in srgb, var(--app-primary-color) 12%, #fff);
|
background: color-mix(in srgb, var(--app-primary-color) 12%, #fff);
|
||||||
@ -3847,9 +3969,11 @@ watch(
|
|||||||
height: 28px;
|
height: 28px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h5-po__wh-card p {
|
.h5-po__wh-card p,
|
||||||
|
.h5-po__wh-item p {
|
||||||
margin: 8px 0 0;
|
margin: 8px 0 0;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #1d2129;
|
color: #1d2129;
|
||||||
@ -3863,6 +3987,10 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h5-po__wh-item .h5-po__hint {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.h5-po__more-panel {
|
.h5-po__more-panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@ -35,11 +35,15 @@
|
|||||||
{{ t('h5.journey.warehouse.copy') }}
|
{{ t('h5.journey.warehouse.copy') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p><span>{{ t('h5.journey.warehouse.recipient') }}</span>{{ item.recipient || '-' }}</p>
|
<p><span>{{ t('h5.journey.warehouse.recipient') }}</span>{{ displayRecipient(item) }}</p>
|
||||||
<p><span>{{ t('h5.journey.warehouse.phone') }}</span>{{ item.contactPhone || '-' }}</p>
|
<p><span>{{ t('h5.journey.warehouse.phone') }}</span>{{ item.contactPhone || '-' }}</p>
|
||||||
<p class="is-addr">
|
<p class="is-addr">
|
||||||
<span>{{ t('h5.journey.warehouse.address') }}</span>
|
<span>{{ t('h5.journey.warehouse.address') }}</span>
|
||||||
{{ item.shippingAddress || '-' }}
|
{{ displayShippingAddress(item) }}
|
||||||
|
</p>
|
||||||
|
<p v-if="item.remark">
|
||||||
|
<span>{{ t('h5.journey.warehouse.remark') }}</span>
|
||||||
|
{{ item.remark }}
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
@ -53,6 +57,14 @@ import { useI18n } from 'vue-i18n';
|
|||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import { IconClose, IconHome } from '@arco-design/web-vue/es/icon';
|
import { IconClose, IconHome } from '@arco-design/web-vue/es/icon';
|
||||||
import { getWarehouseList, type WarehouseDto } from '@/api/system/warehouse';
|
import { getWarehouseList, type WarehouseDto } from '@/api/system/warehouse';
|
||||||
|
import { resolveCurrentCustomer } from '@/api/customer/resolve';
|
||||||
|
import {
|
||||||
|
compareWarehouseSort,
|
||||||
|
formatWarehouseShipAddress,
|
||||||
|
formatWarehouseShipRecipient,
|
||||||
|
} from '@/utils/warehouseShipAddress';
|
||||||
|
import { getMapIdFromAccessToken } from '@/utils/jwt';
|
||||||
|
import { useRbacUserStore } from '@/store/modules/rbac-user';
|
||||||
import H5EmptyState from './H5EmptyState.vue';
|
import H5EmptyState from './H5EmptyState.vue';
|
||||||
|
|
||||||
defineOptions({ name: 'H5WarehouseAddressSheet' });
|
defineOptions({ name: 'H5WarehouseAddressSheet' });
|
||||||
@ -61,8 +73,10 @@ const props = defineProps<{ visible: boolean }>();
|
|||||||
const emit = defineEmits<{ 'update:visible': [boolean] }>();
|
const emit = defineEmits<{ 'update:visible': [boolean] }>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const rbacUserStore = useRbacUserStore();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const list = ref<WarehouseDto[]>([]);
|
const list = ref<WarehouseDto[]>([]);
|
||||||
|
const currentCustomerCode = ref('');
|
||||||
const drawerStyle = {
|
const drawerStyle = {
|
||||||
borderRadius: '20px 20px 0 0',
|
borderRadius: '20px 20px 0 0',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
@ -82,20 +96,46 @@ function pick(raw: any, ...keys: string[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalize(row: any): WarehouseDto {
|
function normalize(row: any): WarehouseDto {
|
||||||
|
const sortRaw = row?.sort ?? row?.Sort;
|
||||||
|
const sortNum = sortRaw == null || sortRaw === '' ? null : Number(sortRaw);
|
||||||
return {
|
return {
|
||||||
id: row?.id ?? row?.Id,
|
id: row?.id ?? row?.Id,
|
||||||
warehouseName: pick(row, 'warehouseName', 'WarehouseName'),
|
warehouseName: pick(row, 'warehouseName', 'WarehouseName'),
|
||||||
recipient: pick(row, 'recipient', 'Recipient'),
|
recipient: pick(row, 'recipient', 'Recipient'),
|
||||||
contactPhone: pick(row, 'contactPhone', 'ContactPhone'),
|
contactPhone: pick(row, 'contactPhone', 'ContactPhone'),
|
||||||
shippingAddress: pick(row, 'shippingAddress', 'ShippingAddress'),
|
shippingAddress: pick(row, 'shippingAddress', 'ShippingAddress'),
|
||||||
|
sort: sortNum != null && Number.isFinite(sortNum) ? sortNum : null,
|
||||||
|
remark: pick(row, 'remark', 'Remark') || undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function displayRecipient(item: WarehouseDto) {
|
||||||
|
return formatWarehouseShipRecipient(item.recipient, currentCustomerCode.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayShippingAddress(item: WarehouseDto) {
|
||||||
|
return formatWarehouseShipAddress(item.shippingAddress, currentCustomerCode.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCurrentCustomerCode() {
|
||||||
|
try {
|
||||||
|
const mapId = getMapIdFromAccessToken(rbacUserStore.accessToken);
|
||||||
|
const account = rbacUserStore.userInfo?.account?.trim() || '';
|
||||||
|
const data = await resolveCurrentCustomer({ mapId, account });
|
||||||
|
currentCustomerCode.value = String(data?.customerCode ?? '').trim();
|
||||||
|
} catch {
|
||||||
|
currentCustomerCode.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadList() {
|
async function loadList() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await getWarehouseList({ page: 1, pageSize: 100 });
|
const [res] = await Promise.all([
|
||||||
list.value = (res.items ?? []).map(normalize);
|
getWarehouseList({ page: 1, pageSize: 100 }),
|
||||||
|
loadCurrentCustomerCode(),
|
||||||
|
]);
|
||||||
|
list.value = (res.items ?? []).map(normalize).sort(compareWarehouseSort);
|
||||||
} catch {
|
} catch {
|
||||||
list.value = [];
|
list.value = [];
|
||||||
} finally {
|
} finally {
|
||||||
@ -106,9 +146,9 @@ async function loadList() {
|
|||||||
async function copyItem(item: WarehouseDto) {
|
async function copyItem(item: WarehouseDto) {
|
||||||
const text = [
|
const text = [
|
||||||
item.warehouseName,
|
item.warehouseName,
|
||||||
`${t('h5.journey.warehouse.recipient')}${item.recipient || '-'}`,
|
`${t('h5.journey.warehouse.recipient')}${displayRecipient(item)}`,
|
||||||
`${t('h5.journey.warehouse.phone')}${item.contactPhone || '-'}`,
|
`${t('h5.journey.warehouse.phone')}${item.contactPhone || '-'}`,
|
||||||
`${t('h5.journey.warehouse.address')}${item.shippingAddress || '-'}`,
|
`${t('h5.journey.warehouse.address')}${displayShippingAddress(item)}`,
|
||||||
].join('\n');
|
].join('\n');
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
|
|||||||
@ -406,13 +406,17 @@ export default {
|
|||||||
'h5.placeOrder.selfShipRecipient': 'Recipient',
|
'h5.placeOrder.selfShipRecipient': 'Recipient',
|
||||||
'h5.placeOrder.selfShipPhone': 'Phone',
|
'h5.placeOrder.selfShipPhone': 'Phone',
|
||||||
'h5.placeOrder.selfShipAddress': 'Address',
|
'h5.placeOrder.selfShipAddress': 'Address',
|
||||||
|
'h5.placeOrder.selfShipRemark': 'Remark',
|
||||||
'h5.placeOrder.selfShipCopy': 'Copy',
|
'h5.placeOrder.selfShipCopy': 'Copy',
|
||||||
'h5.placeOrder.selfShipCopySuccess': 'Warehouse shipping info copied',
|
'h5.placeOrder.selfShipCopySuccess': 'Warehouse shipping info copied',
|
||||||
'h5.placeOrder.selfShipSelectWarehouse': 'Select a warehouse first',
|
'h5.placeOrder.selfShipSelectWarehouse': 'Select a warehouse first',
|
||||||
'h5.placeOrder.selfShipAddressEmpty':
|
'h5.placeOrder.selfShipCurrent': 'Current',
|
||||||
'This warehouse has no shipping address yet. Contact support or choose another warehouse.',
|
'h5.placeOrder.selfShipNoWarehouse': 'No warehouse info yet. Retry later or contact support.',
|
||||||
|
'h5.placeOrder.selfShipNoAddressConfigured':
|
||||||
|
'No warehouse has a shipping address configured. Contact support.',
|
||||||
|
'h5.placeOrder.selfShipAddressEmpty': 'No shipping address configured',
|
||||||
'h5.placeOrder.selfShipAddressHint':
|
'h5.placeOrder.selfShipAddressHint':
|
||||||
'Ship to this address yourself. Put the client number on the courier label so the warehouse can match it.',
|
'Ship to the selected warehouse address yourself. Put the client number on the courier label so the warehouse can match it.',
|
||||||
'h5.placeOrder.pickupAddress': 'Pickup address',
|
'h5.placeOrder.pickupAddress': 'Pickup address',
|
||||||
'h5.placeOrder.pickupAddressPh': 'Select a saved sender address',
|
'h5.placeOrder.pickupAddressPh': 'Select a saved sender address',
|
||||||
'h5.placeOrder.expectPickupTime': 'Expected pickup time',
|
'h5.placeOrder.expectPickupTime': 'Expected pickup time',
|
||||||
@ -792,6 +796,7 @@ export default {
|
|||||||
'h5.journey.warehouse.recipient': 'Recipient: ',
|
'h5.journey.warehouse.recipient': 'Recipient: ',
|
||||||
'h5.journey.warehouse.phone': 'Phone: ',
|
'h5.journey.warehouse.phone': 'Phone: ',
|
||||||
'h5.journey.warehouse.address': 'Address: ',
|
'h5.journey.warehouse.address': 'Address: ',
|
||||||
|
'h5.journey.warehouse.remark': 'Remark: ',
|
||||||
|
|
||||||
'h5.journey.ship.title': 'Pickup / ship to warehouse',
|
'h5.journey.ship.title': 'Pickup / ship to warehouse',
|
||||||
'h5.journey.ship.lead': 'Create a logistics order first. Then either self-ship to the warehouse, or add a sender address and request pickup.',
|
'h5.journey.ship.lead': 'Create a logistics order first. Then either self-ship to the warehouse, or add a sender address and request pickup.',
|
||||||
|
|||||||
@ -404,11 +404,15 @@ export default {
|
|||||||
'h5.placeOrder.selfShipRecipient': '收件人',
|
'h5.placeOrder.selfShipRecipient': '收件人',
|
||||||
'h5.placeOrder.selfShipPhone': '电话',
|
'h5.placeOrder.selfShipPhone': '电话',
|
||||||
'h5.placeOrder.selfShipAddress': '地址',
|
'h5.placeOrder.selfShipAddress': '地址',
|
||||||
|
'h5.placeOrder.selfShipRemark': '备注',
|
||||||
'h5.placeOrder.selfShipCopy': '复制',
|
'h5.placeOrder.selfShipCopy': '复制',
|
||||||
'h5.placeOrder.selfShipCopySuccess': '仓库寄件信息已复制',
|
'h5.placeOrder.selfShipCopySuccess': '仓库寄件信息已复制',
|
||||||
'h5.placeOrder.selfShipSelectWarehouse': '请先选择仓库',
|
'h5.placeOrder.selfShipSelectWarehouse': '请先选择仓库',
|
||||||
'h5.placeOrder.selfShipAddressEmpty': '该仓库暂未配置收货地址,请联系客服或更换仓库',
|
'h5.placeOrder.selfShipCurrent': '当前',
|
||||||
'h5.placeOrder.selfShipAddressHint': '按此地址自行寄出。快递面单建议备注客户单号,方便仓库认领。',
|
'h5.placeOrder.selfShipNoWarehouse': '暂无仓库信息,请稍后重试或联系客服',
|
||||||
|
'h5.placeOrder.selfShipNoAddressConfigured': '暂无已配置收货地址的仓库,请联系客服',
|
||||||
|
'h5.placeOrder.selfShipAddressEmpty': '暂未配置收货地址',
|
||||||
|
'h5.placeOrder.selfShipAddressHint': '按所选仓库地址自行寄出。快递面单建议备注客户单号,方便仓库认领。',
|
||||||
'h5.placeOrder.pickupAddress': '取件地址',
|
'h5.placeOrder.pickupAddress': '取件地址',
|
||||||
'h5.placeOrder.pickupAddressPh': '请选择发件地址簿',
|
'h5.placeOrder.pickupAddressPh': '请选择发件地址簿',
|
||||||
'h5.placeOrder.expectPickupTime': '期望揽收时间',
|
'h5.placeOrder.expectPickupTime': '期望揽收时间',
|
||||||
@ -778,6 +782,7 @@ export default {
|
|||||||
'h5.journey.warehouse.recipient': '收件人:',
|
'h5.journey.warehouse.recipient': '收件人:',
|
||||||
'h5.journey.warehouse.phone': '电话:',
|
'h5.journey.warehouse.phone': '电话:',
|
||||||
'h5.journey.warehouse.address': '地址:',
|
'h5.journey.warehouse.address': '地址:',
|
||||||
|
'h5.journey.warehouse.remark': '备注:',
|
||||||
|
|
||||||
'h5.journey.ship.title': '揽收寄仓',
|
'h5.journey.ship.title': '揽收寄仓',
|
||||||
'h5.journey.ship.lead': '请先创建物流订单。两种方式任选:自己快递寄到仓库,或填写寄件地址后预约上门揽收。',
|
'h5.journey.ship.lead': '请先创建物流订单。两种方式任选:自己快递寄到仓库,或填写寄件地址后预约上门揽收。',
|
||||||
|
|||||||
@ -36,7 +36,7 @@
|
|||||||
'login.hero.title': 'IDE',
|
'login.hero.title': 'IDE',
|
||||||
'login.hero.tagline': 'Faster · Simpler · Professional',
|
'login.hero.tagline': 'Faster · Simpler · Professional',
|
||||||
'login.hero.desc.customer':
|
'login.hero.desc.customer':
|
||||||
'查价说得清、下单走得顺、轨迹跟得上。AI 帮你看风险、提建议、少踩坑——从揽收到签收,IDE 即配陪你把每一票发好、盯紧、交代清楚。',
|
'查价说得清、下单走得顺、轨迹跟得上。AI 帮你看风险、提建议、少踩坑——从揽收到签收,IDE 陪你把每一票发好、盯紧、交代清楚。',
|
||||||
'login.hero.admin.brief': '开工前对齐三件事,并让 AI 成为搭档',
|
'login.hero.admin.brief': '开工前对齐三件事,并让 AI 成为搭档',
|
||||||
'login.hero.admin.pillar.care.title': '更专业',
|
'login.hero.admin.pillar.care.title': '更专业',
|
||||||
'login.hero.admin.pillar.care.desc': '线路选对 · 异常不丢 · 全程说得清',
|
'login.hero.admin.pillar.care.desc': '线路选对 · 异常不丢 · 全程说得清',
|
||||||
|
|||||||
@ -41,8 +41,15 @@ import SelectDeclarationSkuModal from './components/SelectDeclarationSkuModal.vu
|
|||||||
import { getDictListAll } from '@/api/system/dict';
|
import { getDictListAll } from '@/api/system/dict';
|
||||||
import { getCountryList } from '@/api/system/country';
|
import { getCountryList } from '@/api/system/country';
|
||||||
import { getWarehouseList } from '@/api/system/warehouse';
|
import { getWarehouseList } from '@/api/system/warehouse';
|
||||||
|
import { resolveCurrentCustomer } from '@/api/customer/resolve';
|
||||||
import { mapWarehouseSkuToDeclarationFields, normalizeListRow, normalizeSkuImageForStorage, pickField } from '@/utils/warehouseListMapper';
|
import { mapWarehouseSkuToDeclarationFields, normalizeListRow, normalizeSkuImageForStorage, pickField } from '@/utils/warehouseListMapper';
|
||||||
import { normalizeCargoAttributeCode } from '@/utils/cargoAttribute';
|
import { normalizeCargoAttributeCode } from '@/utils/cargoAttribute';
|
||||||
|
import {
|
||||||
|
compareWarehouseSort,
|
||||||
|
formatWarehouseShipAddress,
|
||||||
|
formatWarehouseShipRecipient,
|
||||||
|
} from '@/utils/warehouseShipAddress';
|
||||||
|
import { getMapIdFromAccessToken } from '@/utils/jwt';
|
||||||
import { useRbacUserStore } from '@/store/modules/rbac-user';
|
import { useRbacUserStore } from '@/store/modules/rbac-user';
|
||||||
import { useWarehouseAdminCustomer } from '@/composables/useWarehouseAdminCustomer';
|
import { useWarehouseAdminCustomer } from '@/composables/useWarehouseAdminCustomer';
|
||||||
import { useNanaBatchPanel } from '@/composables/useNanaBatchPanel';
|
import { useNanaBatchPanel } from '@/composables/useNanaBatchPanel';
|
||||||
@ -319,6 +326,8 @@ import SelectDeclarationSkuModal from './components/SelectDeclarationSkuModal.vu
|
|||||||
recipient?: string;
|
recipient?: string;
|
||||||
contactPhone?: string;
|
contactPhone?: string;
|
||||||
shippingAddress?: string;
|
shippingAddress?: string;
|
||||||
|
sort?: number | null;
|
||||||
|
remark?: string;
|
||||||
};
|
};
|
||||||
const singleWarehouseOptions = ref<WarehouseSelectOption[]>([]);
|
const singleWarehouseOptions = ref<WarehouseSelectOption[]>([]);
|
||||||
const singleCurrencyOptions = ref<SingleSelectOption[]>([]);
|
const singleCurrencyOptions = ref<SingleSelectOption[]>([]);
|
||||||
@ -362,12 +371,16 @@ import SelectDeclarationSkuModal from './components/SelectDeclarationSkuModal.vu
|
|||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
const sortRaw = row?.sort ?? row?.Sort;
|
||||||
|
const sortNum = sortRaw == null || sortRaw === '' ? null : Number(sortRaw);
|
||||||
return {
|
return {
|
||||||
id: id != null && id !== '' ? String(id) : '',
|
id: id != null && id !== '' ? String(id) : '',
|
||||||
name,
|
name,
|
||||||
recipient: pick('recipient', 'Recipient'),
|
recipient: pick('recipient', 'Recipient'),
|
||||||
contactPhone: pick('contactPhone', 'ContactPhone'),
|
contactPhone: pick('contactPhone', 'ContactPhone'),
|
||||||
shippingAddress: pick('shippingAddress', 'ShippingAddress'),
|
shippingAddress: pick('shippingAddress', 'ShippingAddress'),
|
||||||
|
sort: sortNum != null && Number.isFinite(sortNum) ? sortNum : null,
|
||||||
|
remark: pick('remark', 'Remark'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2135,20 +2148,51 @@ import SelectDeclarationSkuModal from './components/SelectDeclarationSkuModal.vu
|
|||||||
return singleWarehouseOptions.value.find((opt) => String(opt.value) === id);
|
return singleWarehouseOptions.value.find((opt) => String(opt.value) === id);
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedWarehouseHasAddress = computed(() => {
|
/** 客户登录时拼接到仓库收件人/地址后的客户编号 */
|
||||||
const opt = selectedWarehouseOption.value;
|
const currentCustomerCode = ref('');
|
||||||
|
|
||||||
|
async function loadCurrentCustomerCode() {
|
||||||
|
if (!rbacUserStore.isCustomerPortal()) {
|
||||||
|
currentCustomerCode.value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const mapId = getMapIdFromAccessToken(rbacUserStore.accessToken);
|
||||||
|
const account = rbacUserStore.userInfo?.account?.trim() || '';
|
||||||
|
const data = await resolveCurrentCustomer({ mapId, account });
|
||||||
|
currentCustomerCode.value = String(data?.customerCode ?? '').trim();
|
||||||
|
} catch {
|
||||||
|
currentCustomerCode.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayWarehouseRecipient(opt?: WarehouseSelectOption) {
|
||||||
|
return formatWarehouseShipRecipient(opt?.recipient, currentCustomerCode.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayWarehouseShippingAddress(opt?: WarehouseSelectOption) {
|
||||||
|
return formatWarehouseShipAddress(opt?.shippingAddress, currentCustomerCode.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function warehouseHasAddress(opt?: WarehouseSelectOption) {
|
||||||
if (!opt) return false;
|
if (!opt) return false;
|
||||||
return Boolean(opt.recipient || opt.contactPhone || opt.shippingAddress);
|
return Boolean(opt.recipient || opt.contactPhone || opt.shippingAddress);
|
||||||
});
|
}
|
||||||
|
|
||||||
function buildWarehouseShipCopyText() {
|
const warehouseShipAddressList = computed(() => singleWarehouseOptions.value);
|
||||||
const opt = selectedWarehouseOption.value;
|
|
||||||
if (!opt) return '';
|
const warehousesWithShipAddress = computed(() =>
|
||||||
|
singleWarehouseOptions.value.filter((opt) => warehouseHasAddress(opt)),
|
||||||
|
);
|
||||||
|
|
||||||
|
function buildWarehouseShipCopyText(opt?: WarehouseSelectOption) {
|
||||||
|
const target = opt ?? selectedWarehouseOption.value;
|
||||||
|
if (!target) return '';
|
||||||
const lines = [
|
const lines = [
|
||||||
opt.label || '',
|
target.label || '',
|
||||||
`收件人:${opt.recipient || '-'}`,
|
`收件人:${displayWarehouseRecipient(target)}`,
|
||||||
`电话:${opt.contactPhone || '-'}`,
|
`电话:${target.contactPhone || '-'}`,
|
||||||
`地址:${opt.shippingAddress || '-'}`,
|
`地址:${displayWarehouseShippingAddress(target)}`,
|
||||||
];
|
];
|
||||||
const clientNo = String(singleForm.clientNo ?? '').trim();
|
const clientNo = String(singleForm.clientNo ?? '').trim();
|
||||||
if (clientNo) {
|
if (clientNo) {
|
||||||
@ -2157,15 +2201,10 @@ import SelectDeclarationSkuModal from './components/SelectDeclarationSkuModal.vu
|
|||||||
return lines.filter(Boolean).join('\n');
|
return lines.filter(Boolean).join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyWarehouseShipAddress() {
|
async function writeClipboardText(text: string, successMsg: string) {
|
||||||
const text = buildWarehouseShipCopyText();
|
|
||||||
if (!text.trim()) {
|
|
||||||
Message.warning('该仓库暂未配置收货地址');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
Message.success('仓库寄件信息已复制');
|
Message.success(successMsg);
|
||||||
} catch {
|
} catch {
|
||||||
const area = document.createElement('textarea');
|
const area = document.createElement('textarea');
|
||||||
area.value = text;
|
area.value = text;
|
||||||
@ -2175,7 +2214,7 @@ import SelectDeclarationSkuModal from './components/SelectDeclarationSkuModal.vu
|
|||||||
area.select();
|
area.select();
|
||||||
try {
|
try {
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
Message.success('仓库寄件信息已复制');
|
Message.success(successMsg);
|
||||||
} catch {
|
} catch {
|
||||||
Message.error('复制失败');
|
Message.error('复制失败');
|
||||||
}
|
}
|
||||||
@ -2183,6 +2222,15 @@ import SelectDeclarationSkuModal from './components/SelectDeclarationSkuModal.vu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function copyWarehouseShipAddress(opt?: WarehouseSelectOption) {
|
||||||
|
const text = buildWarehouseShipCopyText(opt);
|
||||||
|
if (!text.trim()) {
|
||||||
|
Message.warning('该仓库暂未配置收货地址');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await writeClipboardText(text, '仓库寄件信息已复制');
|
||||||
|
}
|
||||||
|
|
||||||
function resolveDefaultDeclaredCurrency() {
|
function resolveDefaultDeclaredCurrency() {
|
||||||
const opts = singleCurrencyOptions.value;
|
const opts = singleCurrencyOptions.value;
|
||||||
const usd = opts.find((o) => String(o.value).trim().toUpperCase() === 'USD');
|
const usd = opts.find((o) => String(o.value).trim().toUpperCase() === 'USD');
|
||||||
@ -2446,7 +2494,10 @@ import SelectDeclarationSkuModal from './components/SelectDeclarationSkuModal.vu
|
|||||||
recipient: w.recipient,
|
recipient: w.recipient,
|
||||||
contactPhone: w.contactPhone,
|
contactPhone: w.contactPhone,
|
||||||
shippingAddress: w.shippingAddress,
|
shippingAddress: w.shippingAddress,
|
||||||
}));
|
sort: w.sort,
|
||||||
|
remark: w.remark,
|
||||||
|
}))
|
||||||
|
.sort(compareWarehouseSort);
|
||||||
singleWarehouseOptions.value = whOpts;
|
singleWarehouseOptions.value = whOpts;
|
||||||
if (whOpts.length && !String(singleForm.warehouseId ?? '').trim()) {
|
if (whOpts.length && !String(singleForm.warehouseId ?? '').trim()) {
|
||||||
singleForm.warehouseId = whOpts[0].value;
|
singleForm.warehouseId = whOpts[0].value;
|
||||||
@ -2592,6 +2643,7 @@ import SelectDeclarationSkuModal from './components/SelectDeclarationSkuModal.vu
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadSingleFormReferenceOptions();
|
loadSingleFormReferenceOptions();
|
||||||
|
void loadCurrentCustomerCode();
|
||||||
loadReceiverAiPrefs();
|
loadReceiverAiPrefs();
|
||||||
void Promise.all([loadReceiverAiProviders(), loadReceiverAiSkills()]);
|
void Promise.all([loadReceiverAiProviders(), loadReceiverAiSkills()]);
|
||||||
void (async () => {
|
void (async () => {
|
||||||
@ -3926,49 +3978,85 @@ import SelectDeclarationSkuModal from './components/SelectDeclarationSkuModal.vu
|
|||||||
<template #content>
|
<template #content>
|
||||||
<div class="warehouse-ship-popover__body">
|
<div class="warehouse-ship-popover__body">
|
||||||
<div class="warehouse-ship-popover__head">
|
<div class="warehouse-ship-popover__head">
|
||||||
|
<strong>寄往仓库</strong>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
v-if="!warehouseShipAddressList.length"
|
||||||
|
class="warehouse-ship-popover__empty"
|
||||||
|
>
|
||||||
|
暂无仓库信息,请稍后重试或联系客服
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
v-else-if="!warehousesWithShipAddress.length"
|
||||||
|
class="warehouse-ship-popover__empty"
|
||||||
|
>
|
||||||
|
暂无已配置收货地址的仓库,请联系客服
|
||||||
|
</p>
|
||||||
|
<template v-else>
|
||||||
|
<div class="warehouse-ship-popover__list">
|
||||||
|
<div
|
||||||
|
v-for="opt in warehouseShipAddressList"
|
||||||
|
:key="opt.value"
|
||||||
|
class="warehouse-ship-popover__item"
|
||||||
|
:class="{
|
||||||
|
'is-selected':
|
||||||
|
String(opt.value) ===
|
||||||
|
String(singleForm.warehouseId ?? ''),
|
||||||
|
'is-empty': !warehouseHasAddress(opt),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="warehouse-ship-popover__item-head">
|
||||||
<strong>
|
<strong>
|
||||||
寄往仓库
|
{{ opt.label || opt.value }}
|
||||||
<template v-if="selectedWarehouseOption?.label">
|
<span
|
||||||
· {{ selectedWarehouseOption.label }}
|
v-if="
|
||||||
</template>
|
String(opt.value) ===
|
||||||
|
String(singleForm.warehouseId ?? '')
|
||||||
|
"
|
||||||
|
class="warehouse-ship-popover__badge"
|
||||||
|
>
|
||||||
|
当前
|
||||||
|
</span>
|
||||||
</strong>
|
</strong>
|
||||||
<a-button
|
<a-button
|
||||||
v-if="selectedWarehouseHasAddress"
|
v-if="warehouseHasAddress(opt)"
|
||||||
type="text"
|
type="text"
|
||||||
size="mini"
|
size="mini"
|
||||||
class="warehouse-ship-popover__copy"
|
class="warehouse-ship-popover__copy"
|
||||||
@click="copyWarehouseShipAddress"
|
@click="copyWarehouseShipAddress(opt)"
|
||||||
>
|
>
|
||||||
<template #icon><IconCopy /></template>
|
<template #icon><IconCopy /></template>
|
||||||
复制
|
复制
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="!selectedWarehouseOption" class="warehouse-ship-popover__empty">
|
|
||||||
请先选择上方仓库
|
|
||||||
</p>
|
|
||||||
<p
|
<p
|
||||||
v-else-if="!selectedWarehouseHasAddress"
|
v-if="!warehouseHasAddress(opt)"
|
||||||
class="warehouse-ship-popover__empty"
|
class="warehouse-ship-popover__empty"
|
||||||
>
|
>
|
||||||
该仓库暂未配置收货地址,请联系客服或更换仓库
|
暂未配置收货地址
|
||||||
</p>
|
</p>
|
||||||
<template v-else>
|
<dl v-else class="warehouse-ship-popover__rows">
|
||||||
<dl class="warehouse-ship-popover__rows">
|
|
||||||
<div>
|
<div>
|
||||||
<dt>收件人</dt>
|
<dt>收件人</dt>
|
||||||
<dd>{{ selectedWarehouseOption.recipient || '-' }}</dd>
|
<dd>{{ displayWarehouseRecipient(opt) }}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt>电话</dt>
|
<dt>电话</dt>
|
||||||
<dd>{{ selectedWarehouseOption.contactPhone || '-' }}</dd>
|
<dd>{{ opt.contactPhone || '-' }}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="is-addr">
|
<div class="is-addr">
|
||||||
<dt>地址</dt>
|
<dt>地址</dt>
|
||||||
<dd>{{ selectedWarehouseOption.shippingAddress || '-' }}</dd>
|
<dd>{{ displayWarehouseShippingAddress(opt) }}</dd>
|
||||||
|
</div>
|
||||||
|
<div v-if="opt.remark">
|
||||||
|
<dt>备注</dt>
|
||||||
|
<dd>{{ opt.remark }}</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<p class="warehouse-ship-popover__hint">
|
<p class="warehouse-ship-popover__hint">
|
||||||
按此地址自行寄出。快递面单建议备注客户单号,方便仓库认领。
|
按所选仓库地址自行寄出。快递面单建议备注客户单号,方便仓库认领。
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -7210,7 +7298,7 @@ import SelectDeclarationSkuModal from './components/SelectDeclarationSkuModal.vu
|
|||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.warehouse-ship-popover {
|
.warehouse-ship-popover {
|
||||||
max-width: 360px;
|
max-width: 400px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
.arco-popover-content {
|
.arco-popover-content {
|
||||||
@ -7219,8 +7307,8 @@ import SelectDeclarationSkuModal from './components/SelectDeclarationSkuModal.vu
|
|||||||
}
|
}
|
||||||
|
|
||||||
.warehouse-ship-popover__body {
|
.warehouse-ship-popover__body {
|
||||||
min-width: 240px;
|
min-width: 260px;
|
||||||
max-width: 320px;
|
max-width: 360px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warehouse-ship-popover__head {
|
.warehouse-ship-popover__head {
|
||||||
@ -7244,8 +7332,62 @@ import SelectDeclarationSkuModal from './components/SelectDeclarationSkuModal.vu
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.warehouse-ship-popover__list {
|
||||||
|
margin-top: 8px;
|
||||||
|
max-height: 320px;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warehouse-ship-popover__item {
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid var(--color-border-2);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--color-fill-1);
|
||||||
|
|
||||||
|
&.is-selected {
|
||||||
|
border-color: var(--app-primary-color);
|
||||||
|
background: color-mix(in srgb, var(--app-primary-color) 8%, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-empty {
|
||||||
|
opacity: 0.72;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.warehouse-ship-popover__item-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.warehouse-ship-popover__badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 18px;
|
||||||
|
padding: 0 6px;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--app-primary-color);
|
||||||
|
background: color-mix(in srgb, var(--app-primary-color) 14%, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
.warehouse-ship-popover__rows {
|
.warehouse-ship-popover__rows {
|
||||||
margin: 8px 0 0;
|
margin: 6px 0 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
@ -7280,4 +7422,8 @@ import SelectDeclarationSkuModal from './components/SelectDeclarationSkuModal.vu
|
|||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
color: var(--color-text-3);
|
color: var(--color-text-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.warehouse-ship-popover__item .warehouse-ship-popover__empty {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -18,6 +18,13 @@
|
|||||||
<icon-home />
|
<icon-home />
|
||||||
</span>
|
</span>
|
||||||
<span class="warehouse-card-title-text" :title="item.warehouseName">{{ item.warehouseName }}</span>
|
<span class="warehouse-card-title-text" :title="item.warehouseName">{{ item.warehouseName }}</span>
|
||||||
|
<a-tag
|
||||||
|
v-if="item.sort != null && item.sort !== ''"
|
||||||
|
size="small"
|
||||||
|
class="warehouse-card-sort-tag"
|
||||||
|
>
|
||||||
|
{{ $t('system.warehouseAddress.fields.sort') }} {{ item.sort }}
|
||||||
|
</a-tag>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
@ -58,6 +65,10 @@
|
|||||||
<span class="label">{{ $t('system.warehouseAddress.fields.shippingAddress') }}:</span>
|
<span class="label">{{ $t('system.warehouseAddress.fields.shippingAddress') }}:</span>
|
||||||
<span class="value value-address" :title="displayShippingAddress(item)">{{ displayShippingAddress(item) }}</span>
|
<span class="value value-address" :title="displayShippingAddress(item)">{{ displayShippingAddress(item) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="item.remark" class="card-row card-row--remark">
|
||||||
|
<span class="label">{{ $t('system.warehouseAddress.fields.remark') }}:</span>
|
||||||
|
<span class="value" :title="item.remark">{{ item.remark }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
@ -105,6 +116,18 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item :label="$t('system.warehouseAddress.fields.sort')">
|
||||||
|
<a-input-number
|
||||||
|
v-model="formData.sort"
|
||||||
|
:placeholder="$t('system.warehouseAddress.placeholder.sort')"
|
||||||
|
:precision="0"
|
||||||
|
allow-clear
|
||||||
|
hide-button
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
|
||||||
<div class="form-section-title">{{ $t('system.warehouseAddress.section.contact') }}</div>
|
<div class="form-section-title">{{ $t('system.warehouseAddress.section.contact') }}</div>
|
||||||
@ -134,6 +157,17 @@
|
|||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item :label="$t('system.warehouseAddress.fields.remark')">
|
||||||
|
<a-textarea
|
||||||
|
v-model="formData.remark"
|
||||||
|
:placeholder="$t('system.warehouseAddress.placeholder.remark')"
|
||||||
|
:max-length="512"
|
||||||
|
show-word-limit
|
||||||
|
:auto-size="{ minRows: 2, maxRows: 4 }"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@ -156,6 +190,11 @@ import {
|
|||||||
} from '@/api/system/warehouse';
|
} from '@/api/system/warehouse';
|
||||||
import { useRbacUserStore } from '@/store/modules/rbac-user';
|
import { useRbacUserStore } from '@/store/modules/rbac-user';
|
||||||
import { getMapIdFromAccessToken } from '@/utils/jwt';
|
import { getMapIdFromAccessToken } from '@/utils/jwt';
|
||||||
|
import {
|
||||||
|
compareWarehouseSort,
|
||||||
|
formatWarehouseShipAddress,
|
||||||
|
formatWarehouseShipRecipient,
|
||||||
|
} from '@/utils/warehouseShipAddress';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const rbacUserStore = useRbacUserStore();
|
const rbacUserStore = useRbacUserStore();
|
||||||
@ -172,22 +211,12 @@ const editingId = ref<string | number | null>(null);
|
|||||||
const warehouseTypeOptions = ref<Array<{ value: string; label: string }>>([]);
|
const warehouseTypeOptions = ref<Array<{ value: string; label: string }>>([]);
|
||||||
const currentCustomerCode = ref('');
|
const currentCustomerCode = ref('');
|
||||||
|
|
||||||
function appendCustomerCode(value?: string, codeSuffix = ''): string {
|
|
||||||
const base = value?.trim() || '';
|
|
||||||
const code = currentCustomerCode.value.trim();
|
|
||||||
if (!code) return base || '-';
|
|
||||||
const tail = `${code}${codeSuffix}`;
|
|
||||||
if (!base) return tail;
|
|
||||||
if (base.endsWith(tail)) return base;
|
|
||||||
return `${base} ${tail}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function displayRecipient(item: { recipient?: string }) {
|
function displayRecipient(item: { recipient?: string }) {
|
||||||
return appendCustomerCode(item.recipient);
|
return formatWarehouseShipRecipient(item.recipient, currentCustomerCode.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayShippingAddress(item: { shippingAddress?: string }) {
|
function displayShippingAddress(item: { shippingAddress?: string }) {
|
||||||
return appendCustomerCode(item.shippingAddress, '号');
|
return formatWarehouseShipAddress(item.shippingAddress, currentCustomerCode.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadCurrentCustomerCode() {
|
async function loadCurrentCustomerCode() {
|
||||||
@ -242,9 +271,11 @@ function getWarehouseTypeDisplay(item: {
|
|||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
warehouseName: '',
|
warehouseName: '',
|
||||||
warehouseType: undefined as string | undefined,
|
warehouseType: undefined as string | undefined,
|
||||||
|
sort: undefined as number | undefined,
|
||||||
recipient: '',
|
recipient: '',
|
||||||
contactPhone: '',
|
contactPhone: '',
|
||||||
shippingAddress: '',
|
shippingAddress: '',
|
||||||
|
remark: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleDelete(item: any) {
|
function handleDelete(item: any) {
|
||||||
@ -268,7 +299,7 @@ function handleDelete(item: any) {
|
|||||||
async function loadList() {
|
async function loadList() {
|
||||||
try {
|
try {
|
||||||
const { items } = await getWarehouseList({ page: 1, pageSize: 200 });
|
const { items } = await getWarehouseList({ page: 1, pageSize: 200 });
|
||||||
list.value = items || [];
|
list.value = [...(items || [])].sort(compareWarehouseSort);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
$baseMessage(e?.message || '加载仓库列表失败', 'error');
|
$baseMessage(e?.message || '加载仓库列表失败', 'error');
|
||||||
list.value = [];
|
list.value = [];
|
||||||
@ -281,9 +312,11 @@ function handleAdd() {
|
|||||||
Object.assign(formData, {
|
Object.assign(formData, {
|
||||||
warehouseName: '',
|
warehouseName: '',
|
||||||
warehouseType: undefined,
|
warehouseType: undefined,
|
||||||
|
sort: undefined,
|
||||||
recipient: '',
|
recipient: '',
|
||||||
contactPhone: '',
|
contactPhone: '',
|
||||||
shippingAddress: '',
|
shippingAddress: '',
|
||||||
|
remark: '',
|
||||||
});
|
});
|
||||||
modalVisible.value = true;
|
modalVisible.value = true;
|
||||||
}
|
}
|
||||||
@ -304,12 +337,15 @@ function handleCopy(item: any) {
|
|||||||
function handleEdit(item: any) {
|
function handleEdit(item: any) {
|
||||||
isEdit.value = true;
|
isEdit.value = true;
|
||||||
editingId.value = item.id ?? null;
|
editingId.value = item.id ?? null;
|
||||||
|
const sortRaw = item.sort ?? item.Sort;
|
||||||
Object.assign(formData, {
|
Object.assign(formData, {
|
||||||
warehouseName: item.warehouseName,
|
warehouseName: item.warehouseName,
|
||||||
warehouseType: item.warehouseType,
|
warehouseType: item.warehouseType,
|
||||||
|
sort: sortRaw == null || sortRaw === '' ? undefined : Number(sortRaw),
|
||||||
recipient: item.recipient,
|
recipient: item.recipient,
|
||||||
contactPhone: item.contactPhone,
|
contactPhone: item.contactPhone,
|
||||||
shippingAddress: item.shippingAddress,
|
shippingAddress: item.shippingAddress,
|
||||||
|
remark: item.remark || '',
|
||||||
});
|
});
|
||||||
modalVisible.value = true;
|
modalVisible.value = true;
|
||||||
}
|
}
|
||||||
@ -322,9 +358,11 @@ async function handleModalOk() {
|
|||||||
const payload = {
|
const payload = {
|
||||||
warehouseName: formData.warehouseName.trim(),
|
warehouseName: formData.warehouseName.trim(),
|
||||||
warehouseType: formData.warehouseType,
|
warehouseType: formData.warehouseType,
|
||||||
|
sort: formData.sort == null || Number.isNaN(Number(formData.sort)) ? null : Number(formData.sort),
|
||||||
recipient: formData.recipient,
|
recipient: formData.recipient,
|
||||||
contactPhone: formData.contactPhone,
|
contactPhone: formData.contactPhone,
|
||||||
shippingAddress: formData.shippingAddress,
|
shippingAddress: formData.shippingAddress,
|
||||||
|
remark: formData.remark?.trim() || null,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
if (isEdit.value && editingId.value != null) {
|
if (isEdit.value && editingId.value != null) {
|
||||||
@ -505,6 +543,10 @@ onMounted(() => {
|
|||||||
color: var(--color-text-1);
|
color: var(--color-text-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.warehouse-card-sort-tag {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.card-extra {
|
.card-extra {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user