599 lines
20 KiB
Vue
599 lines
20 KiB
Vue
<script setup lang="ts">
|
||
import { computed, onMounted, ref, unref, watch, type PropType } from 'vue';
|
||
import { useI18n } from 'vue-i18n';
|
||
|
||
import {
|
||
getEditMapDetails,
|
||
getGetMapParameterCascadedList,
|
||
} from '@/api/InterfaceMappingServices/interfaceMap';
|
||
|
||
import { getParameterGetParameterList } from '@/api/ParameterServices/parameter';
|
||
import AddParameter from './AddParameter.vue';
|
||
|
||
const { t } = useI18n();
|
||
|
||
const props = defineProps({
|
||
data: {
|
||
type: Object as PropType<any>,
|
||
default: () => {
|
||
return {};
|
||
},
|
||
},
|
||
serviceProviderName: {
|
||
type: String,
|
||
default: '',
|
||
},
|
||
|
||
serviceProviderId: {
|
||
type: Number,
|
||
default: 0,
|
||
},
|
||
interfaceId: {
|
||
type: Number,
|
||
default: 0,
|
||
},
|
||
});
|
||
const emits = defineEmits(['save-model-enter']);
|
||
const tableRef = ref();
|
||
const tableRef1 = ref();
|
||
const active = ref(0);
|
||
const AddParameterRef = ref();
|
||
const formsTypeList_new = ref<any[]>([]);
|
||
const maxCompletedIndex = ref<number>(-1);
|
||
|
||
interface Indexable {
|
||
[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(() => {
|
||
return {
|
||
columnsKey: 'mapDetails111',
|
||
showPager: false,
|
||
useForm: false,
|
||
autoHeight: false,
|
||
tableClassName: 'px-0!',
|
||
editConfig: { trigger: 'click', mode: 'cell', autoClear: false },
|
||
columns: [
|
||
{
|
||
field: 'alias',
|
||
title: t('system.interface.mapping.table.systemParamName'),
|
||
minWidth: 120,
|
||
},
|
||
{
|
||
field: 'cnName',
|
||
title: t('system.interface.mapping.table.systemParamNameCn'),
|
||
minWidth: 120,
|
||
},
|
||
{
|
||
field: 'type',
|
||
title: t('system.interface.mapping.table.paramType'),
|
||
minWidth: 120,
|
||
slots: { default: 'typeResp' },
|
||
},
|
||
{
|
||
field: 'mapAlias',
|
||
title: t('system.interface.mapping.table.providerParamName'),
|
||
minWidth: 120,
|
||
slots: {
|
||
edit: 'mapAlias',
|
||
},
|
||
editRender: {},
|
||
},
|
||
{
|
||
field: 'mapCnName',
|
||
title: t('system.interface.mapping.table.providerParamNameCn'),
|
||
minWidth: 130,
|
||
},
|
||
// 去掉"必填/参数描述"列
|
||
],
|
||
tableConfig: {
|
||
data: mapDetails.value,
|
||
// 让表格根据内容自适应,由外层容器负责滚动
|
||
height: undefined,
|
||
maxHeight: undefined,
|
||
},
|
||
};
|
||
});
|
||
const tableProps = computed(() => {
|
||
return {
|
||
columnsKey: 'mapDetails2',
|
||
showPager: false,
|
||
useForm: false,
|
||
autoHeight: false,
|
||
showToolbar: true,
|
||
tableClassName: 'px-0!',
|
||
editConfig: { trigger: 'click', mode: 'cell', autoClear: false },
|
||
columns: [
|
||
{
|
||
field: 'alias',
|
||
title: t('system.interface.mapping.table.providerParamName'),
|
||
minWidth: 120,
|
||
},
|
||
{
|
||
field: 'cnName',
|
||
title: t('system.interface.mapping.table.providerParamNameCn'),
|
||
minWidth: 130,
|
||
},
|
||
{
|
||
field: 'type',
|
||
title: t('system.interface.mapping.table.paramType'),
|
||
minWidth: 60,
|
||
slots: { default: 'typeReq' },
|
||
},
|
||
// 去掉"必填/参数描述"列
|
||
{
|
||
field: 'fixedValue',
|
||
title: t('system.interface.mapping.table.paramValue'),
|
||
minWidth: 120,
|
||
slots: {
|
||
edit: 'fixedValueEdit',
|
||
},
|
||
editRender: {},
|
||
},
|
||
{
|
||
field: 'mapAlias',
|
||
title: t('system.interface.mapping.table.inputParam'),
|
||
slots: {
|
||
edit: 'mapAliasEdit',
|
||
},
|
||
minWidth: 120,
|
||
editRender: {},
|
||
},
|
||
{
|
||
field: 'mapCnName',
|
||
title: t('system.interface.mapping.table.inputParamCn'),
|
||
minWidth: 130,
|
||
},
|
||
],
|
||
tableConfig: {
|
||
// 让表格根据内容自适应,由外层容器负责滚动
|
||
height: undefined,
|
||
maxHeight: undefined,
|
||
},
|
||
};
|
||
});
|
||
const inParaMapList = ref<API.MapSummary[]>([]);
|
||
const mapDetails = ref<API.InterfaceParameterMapOutputValueObject[]>([]);
|
||
function setActive(index: number) {
|
||
active.value = index;
|
||
}
|
||
|
||
function loadAll() {
|
||
return [{ value: '$FromAccount', key: 1 }];
|
||
}
|
||
|
||
const cascadedList = ref<API.MapParameterCascadedOutput[]>([]);
|
||
|
||
const getCascadedList = async (parms: any) => {
|
||
const resp = await getGetMapParameterCascadedList(parms);
|
||
const list = Array.isArray((resp as any)?.data) ? (resp as any).data : Array.isArray(resp) ? (resp as any) : [];
|
||
|
||
cascadedList.value = list.map((e: API.MapParameterCascadedOutput) => {
|
||
return {
|
||
...e,
|
||
id: e.interfaceId,
|
||
};
|
||
});
|
||
};
|
||
onMounted(() => {
|
||
restaurants.value = loadAll();
|
||
});
|
||
type RestaurantItem = {
|
||
value: string;
|
||
key: number;
|
||
};
|
||
const restaurants = ref<RestaurantItem[]>([]);
|
||
// 统一判断"必填"真值
|
||
function isRequiredFlag(v: any): boolean {
|
||
return v === true || v === t('common.yes') || v === 1 || v === '1';
|
||
}
|
||
function querySearch(queryString: string, cb: any) {
|
||
const results =
|
||
queryString?.length && queryString !== 'null'
|
||
? restaurants.value.filter(createFilter(queryString))
|
||
: restaurants.value;
|
||
// call callback function to return suggestions
|
||
cb(results);
|
||
}
|
||
function createFilter(queryString: string) {
|
||
return (restaurant: RestaurantItem) => {
|
||
return (
|
||
restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
|
||
);
|
||
};
|
||
}
|
||
function setlabel(data: any) {
|
||
data.forEach((item: any) => {
|
||
if (item.parameters) item.parameters = setlabel(item.parameters);
|
||
|
||
item.getlabel = item.parameters
|
||
? item.name
|
||
: item.cnName
|
||
? `${item.alias}-${item.cnName}`
|
||
: item.alias;
|
||
});
|
||
return data;
|
||
}
|
||
const mapAliasEditRef = ref();
|
||
function getOptions(item: any, isReverse = false) {
|
||
if (isReverse)
|
||
return setlabel(cascadedList.value.slice(1, cascadedList.value.length));
|
||
|
||
const ids: number[] = inParaMapList.value.map((t) => t.interfaceId!);
|
||
const findI = ids.findIndex((i) => i === item.interfaceId);
|
||
let data: any[] = [];
|
||
if (findI === -1 || findI === 0) {
|
||
data = [cascadedList.value[0]];
|
||
} else {
|
||
data = [cascadedList.value[0]].concat(
|
||
cascadedList.value.filter((t) => {
|
||
return ids.slice(0, findI).includes(t.interfaceId!);
|
||
})
|
||
);
|
||
}
|
||
return setlabel(data);
|
||
}
|
||
function mapAliasEditCheng(
|
||
e: any,
|
||
row: API.InterfaceParameterMapOutputValueObject
|
||
) {
|
||
if (e) {
|
||
let arr: any[] = [];
|
||
cascadedList.value.forEach((t: any) => {
|
||
if (t.parameters) arr = arr.concat(t.parameters);
|
||
});
|
||
const item = arr.find((t: any) => t.id === e);
|
||
row.mapAlias = item?.alias;
|
||
row.mapId = item?.id;
|
||
row.mapCnName = item?.cnName;
|
||
row.mapDescription = item?.description;
|
||
} else {
|
||
row.mapAlias = '';
|
||
row.mapId = 0;
|
||
row.mapCnName = '';
|
||
row.mapDescription = '';
|
||
}
|
||
}
|
||
/** 临时储存item */
|
||
const temporaryItem = ref<any>({});
|
||
const temporaryItemType = ref<number>(1);
|
||
|
||
// 前置-后置脚本点击事件
|
||
function interfaceClick(item: any, type: number) {
|
||
if (item) {
|
||
temporaryItemType.value = type;
|
||
temporaryItem.value = item;
|
||
unref(AddParameterRef)?.open(
|
||
type === 0 ? item.prefixScript : item.postfixScript
|
||
);
|
||
}
|
||
}
|
||
// 前置-后置脚本保存
|
||
function save(data: string) {
|
||
inParaMapList.value.forEach((t: any) => {
|
||
if (t.interfaceId === temporaryItem.value.interfaceId) {
|
||
if (temporaryItemType.value === 0) t.prefixScript = data;
|
||
if (temporaryItemType.value === 1) t.postfixScript = data;
|
||
}
|
||
});
|
||
temporaryItem.value = undefined;
|
||
emits('save-model-enter');
|
||
}
|
||
const formsTypeList = ref<any[]>([]);
|
||
// 支持多关键词(空格分隔)、大小写不敏感,匹配别名/中文名/名称/代码等字段
|
||
function filterMethod(val: string, node: any) {
|
||
const query = (val ?? '').toString().trim().toLowerCase();
|
||
if (!query) return true;
|
||
const data = node?.data || node || {};
|
||
const haystack = [
|
||
data.getlabel,
|
||
data.alias,
|
||
data.cnName,
|
||
data.name,
|
||
data.code,
|
||
]
|
||
.filter(Boolean)
|
||
.join(' ')
|
||
.toLowerCase();
|
||
const parts = query.split(/\s+/).filter(Boolean);
|
||
return parts.every((p: string) => haystack.includes(p));
|
||
}
|
||
function getdata() {
|
||
return {
|
||
inParaMapList: inParaMapList.value.map((t: any) => {
|
||
if (t.mapDetails.length) {
|
||
t.mapDetails = t.mapDetails.map(
|
||
(item: any) =>
|
||
(item = {
|
||
...item,
|
||
mappedParaId: item.mapId || 0,
|
||
paraId: item.id || 0,
|
||
})
|
||
);
|
||
}
|
||
return t;
|
||
}),
|
||
mapDetails: mapDetails.value.map(
|
||
(item: any) =>
|
||
(item = {
|
||
...item,
|
||
mappedParaId: item.mapId || 0,
|
||
paraId: item.id || 0,
|
||
})
|
||
),
|
||
};
|
||
}
|
||
|
||
function getVali(row: Indexable) {
|
||
tableRef.value?.forEach((element: any) => {
|
||
setTimeout(() => {
|
||
element?.editRowCellEvent(row, 'mapAlias');
|
||
element?.xGrid.scrollToRow(row, 'mapAlias');
|
||
}, 300);
|
||
});
|
||
}
|
||
function getReturnVali(index: number) {
|
||
setTimeout(() => {
|
||
tableRef1.value?.editRowCellEvent(mapDetails.value[index], 'mapAlias');
|
||
tableRef1.value?.xGrid.scrollToRow(mapDetails.value[index], 'mapAlias');
|
||
}, 300);
|
||
}
|
||
|
||
function resultsFn() {
|
||
formsTypeList_new.value = [];
|
||
formsTypeList.value = [];
|
||
mapDetails.value = [];
|
||
active.value = 0;
|
||
maxCompletedIndex.value = -1;
|
||
inParaMapList.value = [];
|
||
}
|
||
async function initData(data: any) {
|
||
resultsFn();
|
||
|
||
getEditMapDetails(data).then((resp) => {
|
||
const detail = (resp as any)?.data || resp || {};
|
||
inParaMapList.value = detail.inParaMapList || [];
|
||
mapDetails.value = detail.mapDetails || [];
|
||
});
|
||
|
||
getCascadedList(data);
|
||
|
||
setActive(active.value);
|
||
}
|
||
|
||
defineExpose({ tableRef, getdata, initData, getVali, getReturnVali, advanceAfterSave });
|
||
function advanceAfterSave() {
|
||
const currentStepIndex = active.value === -1 ? inParaMapList.value.length : active.value;
|
||
maxCompletedIndex.value = Math.max(maxCompletedIndex.value, currentStepIndex);
|
||
const nextIndex = currentStepIndex + 1;
|
||
if (nextIndex < inParaMapList.value.length) {
|
||
active.value = nextIndex;
|
||
} else if (nextIndex === inParaMapList.value.length) {
|
||
active.value = -1;
|
||
}
|
||
}
|
||
|
||
</script>
|
||
|
||
<template>
|
||
<div class="entered-map w-full h-full">
|
||
<div class="steps-bar">
|
||
<div
|
||
v-for="(item, index) in inParaMapList"
|
||
:key="item?.interfaceMapId || index"
|
||
class="step-item"
|
||
:class="{ active: active === index, done: index <= maxCompletedIndex }"
|
||
@click="setActive(index)"
|
||
>
|
||
<div class="circle">{{ index + 1 }}</div>
|
||
<div class="label">{{ t('system.interface.mapping.inputMapping') }}【{{ item.interfaceName }}】</div>
|
||
<!-- 始终在入参映射步骤后画连接线,最后一条连接到返参映射步骤 -->
|
||
<div class="connector"></div>
|
||
</div>
|
||
<div class="step-item" :class="{ active: active === -1, done: maxCompletedIndex >= inParaMapList.length }" @click="setActive(-1)">
|
||
<div class="circle">{{ inParaMapList.length + 1 }}</div>
|
||
<div class="label">{{ t('system.interface.mapping.outputMappingLabel') }}</div>
|
||
</div>
|
||
</div>
|
||
<div class="content-pane">
|
||
<div class="content-scroll">
|
||
<div class="content-body">
|
||
<template v-for="(item, index) in inParaMapList" :key="index">
|
||
<div v-show="active === index" class="map-table-wrapper">
|
||
<div class="map-scroll">
|
||
<TablePro
|
||
ref="tableRef"
|
||
v-bind="tableProps"
|
||
class="map-table"
|
||
:table-config="{ data: item?.mapDetails || [] }"
|
||
>
|
||
<template #toolbar_right>
|
||
<slot name="toolbar_right" />
|
||
</template>
|
||
<template #toolbar_buttons>
|
||
<div class="px-12px flex py-12px pl-0!">
|
||
<a-button @click="interfaceClick(item, 0)">{{ t('system.interface.mapping.actions.prefixScript') }} </a-button>
|
||
<a-button class="ml-5px" @click="interfaceClick(item, 1)"
|
||
>{{ t('system.interface.mapping.actions.postfixScript') }}
|
||
</a-button>
|
||
<div class="flex items-center ml-12px">
|
||
{{ t('system.interface.mapping.actions.allowUnequalMapping') }}
|
||
<a-switch v-model="item.freeMap" />
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<template #fixedValueEdit="{ row }">
|
||
<a-auto-complete
|
||
v-model="row.fixedValue"
|
||
:data="restaurants"
|
||
:filter-option="
|
||
(inputValue, option) =>
|
||
option.value
|
||
.toLowerCase()
|
||
.includes(inputValue.toLowerCase())
|
||
"
|
||
allow-clear
|
||
@select="(value) => (row.fixedValue = value)"
|
||
@change="
|
||
(value) => {
|
||
row.fixedValue = value;
|
||
}
|
||
"
|
||
/>
|
||
</template>
|
||
<template #mapAliasEdit="{ row }">
|
||
<a-cascader
|
||
ref="mapAliasEditRef"
|
||
v-model="row.mapId"
|
||
:options="getOptions(item, false)"
|
||
:filter-option="filterMethod"
|
||
allow-clear
|
||
allow-search
|
||
:field-names="{
|
||
value: 'id',
|
||
label: 'getlabel',
|
||
children: 'parameters',
|
||
}"
|
||
:show-all-levels="false"
|
||
@change="
|
||
(value) => {
|
||
mapAliasEditCheng(value, row);
|
||
}
|
||
"
|
||
>
|
||
<template #label="{ data: _data }">
|
||
{{ _data.label }}
|
||
</template>
|
||
</a-cascader>
|
||
</template>
|
||
|
||
<!-- 参数类型(请求侧)在必填时加红色星号 -->
|
||
<template #typeReq="{ row }">
|
||
<span :class="isRequiredFlag(row.isRequired) ? 'type-required' : ''">{{ row.type }}</span>
|
||
</template>
|
||
|
||
<template #isRequired="{ row }">
|
||
<span :class="{ 'text-red-500': row.isRequired === t('common.yes') || row.isRequired === true }">
|
||
{{ row.isRequired === t('common.yes') || row.isRequired === true ? t('common.yes') : t('common.no') }}
|
||
</span>
|
||
</template>
|
||
</TablePro>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<template v-if="active === -1">
|
||
<div class="map-table-wrapper">
|
||
<div class="map-scroll">
|
||
<TablePro ref="tableRef1" v-bind="tableProps1" class="map-table">
|
||
<template #toolbar_right>
|
||
<slot name="toolbar_right" />
|
||
</template>
|
||
<template #isRequired="{ row }">
|
||
<span :class="{ 'text-red-500': row.isRequired === t('common.yes') || row.isRequired === true }">
|
||
{{ row.isRequired === t('common.yes') || row.isRequired === true ? t('common.yes') : t('common.no') }}
|
||
</span>
|
||
</template>
|
||
<template #mapAlias="{ row }">
|
||
|
||
<a-cascader
|
||
ref="mapAliasEditRef"
|
||
v-model="row.mapId"
|
||
:options="getOptions({ interfaceId: 0 }, true)"
|
||
:filter-option="filterMethod"
|
||
allow-search
|
||
allow-clear
|
||
:field-names="{
|
||
value: 'id',
|
||
label: 'getlabel',
|
||
children: 'parameters',
|
||
}"
|
||
:show-all-levels="false"
|
||
@change="
|
||
(value) => {
|
||
mapAliasEditCheng(value, row);
|
||
}
|
||
"
|
||
>
|
||
<template #label="{ data: _data }">
|
||
{{ _data.label }}
|
||
</template>
|
||
</a-cascader>
|
||
</template>
|
||
<!-- 参数类型(响应侧)在必填时加红色星号 -->
|
||
<template #typeResp="{ row }">
|
||
<span :class="isRequiredFlag(row.isRequired) ? 'type-required' : ''">{{ row.type }}</span>
|
||
</template>
|
||
</TablePro>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<AddParameter ref="AddParameterRef" @seva="save" />
|
||
</div>
|
||
</template>
|
||
<style scoped>
|
||
.entered-map { display: flex; flex-direction: column; width: 100%; height: 100%; min-height: 0; overflow: hidden; }
|
||
.steps-bar { display: flex; align-items: center; padding: 10px 12px; gap: 12px; border-bottom: 1px solid #eee; overflow-x: auto; }
|
||
.step-item { position: relative; display: flex; align-items: center; color: #8c8c8c; cursor: pointer; }
|
||
.step-item .circle { width: 22px; height: 22px; border-radius: 50%; background: #f0f0f0; color: #8c8c8c; display: flex; align-items: center; justify-content: center; font-size: 12px; margin-right: 6px; }
|
||
.step-item.active .circle { background: var(--app-primary-color); color: #fff; }
|
||
.step-item.done .circle { background: var(--app-primary-color); color: #fff; }
|
||
.step-item .label { font-size: 12px; white-space: nowrap; }
|
||
.step-item .connector { width: 48px; height: 2px; background: #e5e5e5; margin-left: 12px; margin-right: 0; }
|
||
.content-pane { padding: 12px 16px; flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
||
.content-scroll { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
||
.content-body { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 12px; overflow: hidden; }
|
||
.map-table-wrapper { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
||
.map-scroll { flex: 1; min-height: 0; max-height: calc(100vh - 260px); overflow: auto; }
|
||
.map-table { flex: 1; min-height: 0; }
|
||
|
||
/* 在“参数类型”后显示必填星号 */
|
||
:deep(.vxe-table) .type-required {
|
||
position: relative;
|
||
padding-right: 8px;
|
||
}
|
||
:deep(.vxe-table) .type-required::after {
|
||
content: '*';
|
||
color: #ff4d4f;
|
||
margin-left: 4px;
|
||
}
|
||
</style>
|