front/src/views/api/upstreamInterface/index.vue
2026-03-21 15:20:52 +08:00

2236 lines
66 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts" setup>
import { useTabBarStore } from '@/store';
import { Modal, Message as $message } from '@arco-design/web-vue';
import { useI18n } from 'vue-i18n';
import { nextTick, onMounted, onActivated, ref, watch, computed, unref } from 'vue';
import {
postInterfaceInterfacePaginatedList,
postInterfaceBatchDeleteInterfaceByIds,
} from '@/api/InterfaceServices/interface';
import CascaderPro from '@/components/CascaderPro/Cascader.vue';
import { getServiceProviderGetServiceProviderInterfaceCascadedList } from '@/api/interface-serviceProvider/serviceProvider';
import { getDictionaryGetList } from '@/api/ApplicationServices/dictionary';
import { IconClose, IconEdit, IconPlus, IconSearch, IconMore, IconMinus, IconRefresh } from '@arco-design/web-vue/es/icon';
import dayjs from 'dayjs';
import { useRouter, useRoute } from 'vue-router';
import {
postCreate,
postSaveParameterMaps,
} from '@/api/InterfaceMappingServices/interfaceMap';
import useModal from '@/components/ModalPro/useModal';
import { $baseMessage, $baseConfirm } from '@/utils';
import EnteredMap from './components/EnteredMap.vue';
import MapServiceProvider from './components/map-service-provider.vue';
import ApiDebugger from './components/ApiDebugger.vue';
import { systemInterfaceColumns } from './models/tableColumns';
import UpstreamInterfaceModal from './components/UpstreamInterfaceModal.vue';
import CustomLeftMenu from '@/components/CustomLeftMenu/index.vue';
const { t } = useI18n();
defineOptions({
name: 'UpstreamInterface',
});
const active = ref(0);
const activeContentTab = ref('mapping'); // 映射明细、API调试、接口明细
const currentActiveTab = ref('mapping'); // 用于保存按钮判断的当前tab
const apiDebuggerRef = ref<any>();
const apiDebuggerRefreshKey = ref(0);
const drawerList = ref<any[]>([]);
// 左侧接口树相关数据
const searchKeyword = ref('');
// 中间映射列表搜索关键词
const mappingSearchKeyword = ref('');
const interfaceTreeData = ref<any[]>([]);
const selectedKeys = ref<string[]>([]);
const expandedKeys = ref<string[]>([]);
const selectedInterface = ref<any>(null);
const loading = ref(false);
async function ServiceProviderGetList() {
const data =
await getServiceProviderGetServiceProviderInterfaceCascadedList({
serviceProviderName: '',
interfaceId: upStreamIdRef.value || 0,
});
drawerList.value = data as any;
setTimeout(() => {
panelRef.value?.SetOptions(data);
}, 300);
}
const panelRef = ref<any>();
const drawerVisible = ref(false); // 参数弹窗
const cascaderValue = ref<number[]>([]);
const drawerFormRef = ref<any>();
const upStreamIdRef = ref<number>();
const enteredMapRef = ref<any>();
const drawerConfig = reactive({
itemList: [
{
comp: 'a-input',
field: 'serviceProviderName',
label: '',
compProps: {
placeholder: t('system.interface.search.placeholder.nameOrCode'),
allowClear: true,
size: 'small',
},
span: 12,
},
],
buttonConfig: {
showBtn: true,
span: 12,
showOk: true,
okText: t('system.interface.search.button.query'),
},
});
const tableRef = ref<any>();
const tableProps = reactive({
isToolsExport: false,
isToolsImport: false,
api: postInterfaceInterfacePaginatedList,
searchInfo: {
isUpstream: true,
},
tableConfig: {
columns: systemInterfaceColumns,
showIndex: true,
autoHeight: true,
showCheckbox: true,
isColumnsDrag: false,
toolbarConfig: {
enabled: true,
perfect: true,
slots: {
tools: 'toolbar_right',
buttons: 'toolbar_buttons',
},
},
},
formConfig: {
itemList: [
{
comp: 'a-input',
field: 'nameOrCode',
label: '',
compProps: {
placeholder: t('system.interface.search.placeholder.nameOrCode'),
allowClear: true,
size: 'small',
},
},
],
layout: 'inline',
labelColProps: { span: 0 },
wrapperColProps: { span: 24 },
buttonConfig: {
showBtn: true,
showOk: true,
okText: t('system.interface.search.button.query'),
showReset: false,
className: 'flex items-center',
span: 2,
},
},
});
const router = useRouter();
// 弹窗相关
const editVisible = ref(false);
const editId = ref<number | null>(null);
function interfaceClick() {
editId.value = null;
editVisible.value = true;
}
// 重置表单
const route = useRoute();
onActivated(() => {
if (route?.query?.openId) {
mapName.value = route?.query?.openName;
mapId.value = route?.query?.openId;
mapClick({
id: route.query.openId,
name: route.query.openName,
code: route.query.openCode,
});
}
});
// 组件挂载时加载数据
onMounted(() => {
loadInterfaceTree();
});
function compileClick(row: { id: any }) {
editId.value = row.id;
editVisible.value = true;
}
const [register, registerMethods] = useModal() as any;
const [registerEnter, registerEnterMethods] = useModal() as any;
const mapId = ref();
// 映射服务商
const mapName = ref();
async function mapClick(row: any) {
newmapClickROW.value = row;
mapName.value = row.name;
mapId.value = row.id;
await registerMethods.openModal();
await (unref(MapServiceProviderRef) as any)?.tableRef?.reload();
}
// 删除
async function deleteClick() {
const list = (unref(tableRef) as any)?.checkAllRows?.();
const isHasmapCount = list.some((i: any) => i.mapCount > 0) as boolean;
if (isHasmapCount) {
$baseMessage(t('system.interface.message.cannotDeleteWithMapping'), 'error');
return
}
const ids = list?.map((node: any) => node.id);
if (ids) {
$baseConfirm({
title: t('system.interface.modal.confirmTitle'),
content: t('system.interface.modal.deleteConfirm'),
okText: t('system.interface.modal.ok'),
cancelText: t('system.interface.modal.cancel'),
async onOk() {
await postInterfaceBatchDeleteInterfaceByIds({}, ids);
tableRef.value?.removeCheckboxRow();
$baseMessage(t('system.interface.message.deleteSuccess'), 'success');
},
});
}
}
// 删除单个接口
async function deleteInterfaceClick(nodeData: any) {
if (!nodeData || !nodeData.isLeaf) {
$baseMessage(t('system.interface.message.canOnlyDeleteInterface'), 'warning');
return;
}
if (nodeData.mapCount > 0) {
$baseMessage(t('system.interface.message.cannotDeleteWithMapping'), 'error');
return;
}
$baseConfirm({
title: t('system.interface.modal.warningTitle'),
content: t('system.interface.modal.deleteInterfaceConfirm', { name: nodeData.title }),
onOk: async () => {
try {
await postInterfaceBatchDeleteInterfaceByIds({}, [nodeData.id]);
$baseMessage(t('system.interface.message.deleteSuccess'), 'success');
loadInterfaceTree();
// 如果删除的是当前选中的接口,清空选中状态
if (selectedInterface.value?.id === nodeData.id) {
selectedInterface.value = null;
selectedKeys.value = [];
}
} catch (error) {
$baseMessage(t('system.interface.message.deleteFailed'), 'error');
}
},
});
}
// 新增映射
const [registerAddMap, registerAddMapMethods] = useModal() as any;
const newmapClickROW = ref();
// 级联选择器相关数据
const cascaderOptions = ref<any[]>([]);
const mappingList = ref<any[]>([{ value: [] }]);
async function newmapClick(row: any) {
drawerVisible.value = true;
mapId.value = row.id;
registerAddMapMethods.setModalProps({
title: t('system.interface.modal.addMapping'),
width: 1000,
});
registerAddMapMethods.openModal();
newmapClickROW.value = row;
upStreamIdRef.value = Number(row.id);
mapName.value = row.name;
// 重置映射列表,初始化为1个空映射
mappingList.value = [{ value: [] }];
// 初始化时加载所有服务商数据
await loadInitialServiceProviderData();
}
// 请求去重:记录正在请求的服务商接口ID
const loadingServiceProviderInterfaceId = ref<number | null>(null);
// 初始化加载服务商数据
async function loadInitialServiceProviderData() {
const interfaceId = upStreamIdRef.value || 0;
// 如果正在加载相同的接口,直接返回
if (loadingServiceProviderInterfaceId.value === interfaceId) {
return;
}
// 如果没有接口ID,直接返回
if (!interfaceId) {
return;
}
loadingServiceProviderInterfaceId.value = interfaceId;
try {
const response = await getServiceProviderGetServiceProviderInterfaceCascadedList({
serviceProviderName: '',
interfaceId: interfaceId,
});
// 处理数据,去掉服务商类型的一级菜单,直接显示服务商和服务商接口
// API返回的数据结构可能是 response 本身就是数组,或者 response.data 是数组
const rawData = Array.isArray(response) ? response : (response.data || []);
const processedData = processServiceProviderData(rawData);
drawerList.value = processedData;
// 构建级联选择器数据 - 直接使用处理后数据的label字段
buildCascaderOptions(processedData);
await nextTick();
panelRef.value?.SetOptions(processedData);
const arr = processedData?.filter((node: { children: any; }) => {
return node.children && node.children.length > 0;
});
cascaderValue.value = filterValue(arr);
} catch (error) {
cascaderOptions.value = [];
} finally {
loadingServiceProviderInterfaceId.value = null;
}
}
// 构建级联选择器数据
function buildCascaderOptions(data: any[]) {
if (!data || data.length === 0) {
cascaderOptions.value = [];
return;
}
// 直接使用处理后的数据,因为它已经包含了label和value字段
cascaderOptions.value = data.map((provider: any) => {
const providerData = {
value: provider.value,
label: provider.label,
isDisable: provider.isDisable || false,
children: provider.children?.map((interfaceItem: any) => ({
value: interfaceItem.value,
label: interfaceItem.label,
isDisable: interfaceItem.isDisable || false,
providerId: provider.value,
providerName: provider.label,
interfaceId: interfaceItem.value,
interfaceName: interfaceItem.label
})) || []
};
return providerData;
});
}
// 级联选择器过滤函数
function filterCascaderOption(inputValue: string, option: any) {
return option.label.toLowerCase().includes(inputValue.toLowerCase());
}
// 获取第一个映射的服务商ID和接口ID
const firstMapping = computed(() => {
if (mappingList.value.length > 0 && mappingList.value[0].value) {
// 如果 value 是数组且长度为2(完全选择)
if (Array.isArray(mappingList.value[0].value) && mappingList.value[0].value.length === 2) {
return {
providerId: mappingList.value[0].value[0],
interfaceId: mappingList.value[0].value[1]
};
}
// 如果 value 是数组但长度为1(可能只选择了服务商)
if (Array.isArray(mappingList.value[0].value) && mappingList.value[0].value.length === 1) {
return {
providerId: mappingList.value[0].value[0],
interfaceId: null
};
}
// 如果 value 是数字,需要通过接口ID找到对应的服务商ID
if (typeof mappingList.value[0].value === 'number') {
// 遍历级联选项找到包含该接口ID的服务商
for (const provider of cascaderOptions.value) {
if (provider.children) {
for (const interfaceItem of provider.children) {
if (interfaceItem.value === mappingList.value[0].value) {
return {
providerId: provider.value,
interfaceId: interfaceItem.value
};
}
}
}
}
return null;
}
}
return null;
});
// 获取第一个映射的服务商ID(向后兼容)
const firstProviderId = computed(() => {
return firstMapping.value?.providerId || null;
});
// 获取已选择的接口ID列表(包括第一个映射)
const selectedInterfaceIds = computed(() => {
const selectedIds: number[] = [];
// 遍历所有映射
for (let i = 0; i < mappingList.value.length; i++) {
const mappingValue = mappingList.value[i]?.value;
// 如果 value 是数组且长度为2
if (Array.isArray(mappingValue) && mappingValue.length === 2) {
selectedIds.push(mappingValue[1]);
}
// 如果 value 是数字,可能是接口ID
else if (typeof mappingValue === 'number') {
// 通过 firstMapping 确认这是接口ID
if (firstMapping.value && firstMapping.value.interfaceId === mappingValue) {
selectedIds.push(mappingValue);
}
}
}
return selectedIds;
});
// 获取每个映射项使用的级联选项
function getCascaderOptionsForIndex(index: number) {
// 如果是第一个映射,返回所有选项,但需要处理 isDisable
if (index === 0) {
return cascaderOptions.value.map((provider: any) => {
// 如果服务商的 isDisable 是 true,整个服务商及其接口都置灰
if (provider.isDisable) {
return {
...provider,
disabled: true,
children: provider.children?.map((interfaceItem: any) => ({
...interfaceItem,
disabled: true
})) || []
};
}
return provider;
});
}
// 第二个及之后的映射:只能选择第一个映射选择的服务商
if (!firstMapping.value || !firstMapping.value.providerId) {
return cascaderOptions.value;
}
return cascaderOptions.value.map((provider: any) => {
// 如果服务商的 isDisable 是 true,整个服务商及其接口都置灰
if (provider.isDisable) {
return {
...provider,
disabled: true,
children: provider.children?.map((interfaceItem: any) => ({
...interfaceItem,
disabled: true
})) || []
};
}
// 如果不是第一个映射选择的服务商,整个服务商都置灰
if (provider.value != firstMapping.value?.providerId) {
return {
...provider,
disabled: true,
children: provider.children?.map((interfaceItem: any) => ({
...interfaceItem,
disabled: true
})) || []
};
}
// 是第一个映射选择的服务商,需要禁用已选择的接口
return {
...provider,
children: provider.children?.map((interfaceItem: any) => ({
...interfaceItem,
disabled: selectedInterfaceIds.value.includes(interfaceItem.value)
})) || []
};
});
}
// 添加映射
function addMapping() {
mappingList.value.push({ value: [] });
}
// Tab 切换事件处理
function handleTabChange(tabName: string) {
// 更新当前激活的tab(用于保存按钮判断)
currentActiveTab.value = tabName;
// 如果切换到API调试tab,检查是否选择了映射服务商
if (tabName === 'debugger') {
if (!MapServiceProviderROW.value) {
$baseMessage(t('system.interface.message.selectProviderForDebug'), 'warning');
// 切换回映射明细tab
activeContentTab.value = 'mapping';
currentActiveTab.value = 'mapping';
return;
}
}
// 如果切换到接口明细tab,检查是否选择了接口
if (tabName === 'detail') {
if (!selectedInterface.value) {
$baseMessage(t('system.interface.message.selectLeftInterface'), 'warning');
activeContentTab.value = 'mapping';
currentActiveTab.value = 'mapping';
return;
}
}
}
// 监听第一个映射的变化,如果改变了服务商,清空后续映射
watch(
() => mappingList.value[0]?.value,
(newValue, oldValue) => {
let newProviderId: any = null;
let oldProviderId: any = null;
// 获取新的服务商ID
if (Array.isArray(newValue) && newValue.length >= 1) {
newProviderId = newValue[0];
} else if (typeof newValue === 'number') {
newProviderId = newValue;
}
// 获取旧的服务商ID
if (Array.isArray(oldValue) && oldValue.length >= 1) {
oldProviderId = oldValue[0];
} else if (typeof oldValue === 'number') {
oldProviderId = oldValue;
}
// 如果服务商改变了,清空所有后续映射的选择
if (oldProviderId && newProviderId && newProviderId !== oldProviderId) {
for (let i = 1; i < mappingList.value.length; i++) {
mappingList.value[i].value = [];
}
}
},
{ deep: true }
);
// 处理映射提交
async function handleMappingSubmit() {
// 过滤掉没有选择值的映射
const validMappings = mappingList.value.filter(m => {
// 如果是数组且长度为2,或者是一个数字(接口ID)
return (Array.isArray(m.value) && m.value.length === 2) || typeof m.value === 'number';
});
if (validMappings.length === 0) {
$baseMessage('请至少选择一个接口', 'warning');
return;
}
// 提取接口ID数组(按顺序)
const interfaceIds: number[] = [];
for (const m of validMappings) {
if (Array.isArray(m.value) && m.value.length === 2) {
interfaceIds.push(m.value[1]); // 接口ID是数组的第二个元素
} else if (typeof m.value === 'number') {
interfaceIds.push(m.value); // 数字本身就是接口ID
}
}
try {
// 调用API创建映射
await postCreate({
upStreamId: upStreamIdRef.value || 0,
downStreamIds: interfaceIds
});
$baseMessage('映射创建成功', 'success');
registerAddMapMethods.closeModal();
// 刷新映射列表
(unref(MapServiceProviderRef) as any)?.tableRef?.reload();
} catch (error) {
$baseMessage('创建映射失败', 'error');
}
}
// 抽屉查询按钮
async function onDetaCasc(formValue: any) {
cascaderValue.value = [];
const response =
await getServiceProviderGetServiceProviderInterfaceCascadedList({
serviceProviderName: formValue?.serviceProviderName,
interfaceId: upStreamIdRef.value || 0,
});
// 处理数据,去掉服务商类型的一级菜单,直接显示服务商和服务商接口
const processedData = processServiceProviderData(response.data || []);
drawerList.value = processedData;
await nextTick();
panelRef.value?.SetOptions(processedData);
const arr = processedData?.filter((node: { children: any; }) => {
return node.children && node.children.length > 0;
});
cascaderValue.value = filterValue(arr);
}
// 处理服务商数据,去掉一级分类,直接显示服务商和服务商接口
function processServiceProviderData(data: any[]) {
if (!data || !Array.isArray(data)) return [];
const result: any[] = [];
data.forEach((category: any) => {
if (category && category.children && Array.isArray(category.children)) {
// 遍历每个分类下的服务商
category.children.forEach((provider: any) => {
if (provider && provider.children && Array.isArray(provider.children)) {
// 创建新的服务商节点,直接包含接口
const newProvider = {
value: provider.value || provider.id,
label: provider.label || provider.name,
key: provider.key || provider.value || provider.id,
isDisable: provider.isDisable || false,
// 其他属性
...provider
};
// 将接口作为服务商的直接子项
const interfaceItems = provider.children
.filter((interfaceItem: any) => interfaceItem) // 过滤掉空值
.map((interfaceItem: any) => ({
value: interfaceItem.value || interfaceItem.id,
label: interfaceItem.label || interfaceItem.name,
key: interfaceItem.key || interfaceItem.value || interfaceItem.id,
isDisable: interfaceItem.isDisable || false,
// 其他属性
...interfaceItem
}));
// 重新构建,让服务商直接包含接口
newProvider.children = interfaceItems;
result.push(newProvider);
}
});
}
});
return result;
}
function filterValue(arr: any[], arrValue: any[] = []): number[] {
if (!arr.length) return arrValue;
arrValue.push(arr[0]?.value);
if (arr[0].children && arr[0].children?.length > 0)
return filterValue(arr[0].children, arrValue);
return arrValue;
}
// 抽屉取消按钮
async function cancelCascader() {
drawerFormRef.value?.setFormValue('serviceProviderName', '');
const { data } =
await getServiceProviderGetServiceProviderInterfaceCascadedList({
serviceProviderName: '',
interfaceId: upStreamIdRef.value || 0,
});
// 处理数据,去掉一级分类
const processedData = processServiceProviderData(data);
cascaderValue.value = [];
drawerList.value = [];
setTimeout(() => {
drawerList.value = processedData;
}, 100);
}
// 处理树形选择变化
function handleTreeSelectionChange(selectedItems: any[]) {
// 这里可以处理选中的项目,比如更新UI状态等
}
// 处理树形搜索
function handleTreeSearch(keyword: string) {
// 调用原来的搜索逻辑
onDetaCasc({ serviceProviderName: keyword });
}
const InputParameterMappingList = ref<any[]>([
{
value: '创建袋子',
label: '入参映射',
data: 1,
},
{
value: '获取带牌标签',
label: '入参映射',
data: 2,
},
{
value: '袋子绑定包裹',
label: '入参映射',
data: 3,
},
{
value: t('system.interface.mapping.outputMapping'),
label: '',
data: 4,
},
]);
const confirmLoading = ref(false);
const serviceProviderId = ref(0);
// 抽屉确认按钮
async function detCascader() {
const arr: any[] = panelRef.value?.getActivaList();
serviceProviderId.value = panelRef.value?.getCheckedList().value;
serviceProviderName.value = panelRef.value?.getCheckedList().label;
if (!arr.length) return $message.warning(t('system.interface.message.selectProviderInterface'));
InputParameterMappingList.value = arr
.map((t: any, i: number) => (i = { ...t, data: i }))
.concat({
value: '',
label: t('system.interface.mapping.outputMapping'),
data: arr.length,
});
const id = arr.map((item: any) => item.value);
confirmLoading.value = true;
const data = await postCreate({
upStreamId: Number(upStreamIdRef.value),
downStreamIds: id,
}).finally(() => {
confirmLoading.value = false;
});
if (!data) return;
(unref(tableRef) as any)?.reload?.();
editMapIds.value = {
id: serviceProviderId.value,
addId: Number(upStreamIdRef.value),
intId: id,
};
registerEnterMethods.openModal();
await nextTick();
registerEnterMethods.setModalProps({
title: t('system.interface.modal.add'),
width: 1600,
height: 1600,
});
(unref(enteredMapRef) as any)?.initData?.({
serviceProviderId: serviceProviderId.value,
interfaceId: upStreamIdRef.value,
});
active.value = 1;
const gb = drawerList.value;
cascaderValue.value = [];
drawerList.value = [];
setTimeout(() => {
drawerList.value = gb;
}, 100);
confirmLoading.value = false;
registerAddMapMethods.closeModal();
}
const checkRowsLength = computed(() => {
return (unref(tableRef) as any)?.checkAllRows?.()?.length || 0;
});
// 映射服务商弹窗点击修改打开入参映射弹窗
const editMapIds = ref();
// 是否全屏弹框
const fullScreen = ref(false);
const serviceProviderName = ref('');
async function handlerEditMap(data: any) {
editMapIds.value = data;
serviceProviderId.value = data.serviceProviderId;
serviceProviderName.value = data.serviceProviderName;
upStreamIdRef.value = data.addId;
InputParameterMappingList.value = data.inputParameterMappingList;
registerEnterMethods.setModalProps({
title: t('system.interface.modal.edit'),
width: 1600,
height: 1600,
});
registerEnterMethods.openModal();
await nextTick();
await (unref(enteredMapRef) as any)?.initData?.({
serviceProviderId: data.serviceProviderId,
interfaceId: data.addId,
}
);
}
const MapServiceProviderROW = ref<any>();
const MapServiceProviderRef = ref<any>();
const loadingSubmit = ref(false);
const interfaceDetailModalRef = ref<any>();
const detailRefreshKey = ref(0);
const selectedProvider = ref<any>();
const tabsRef = ref<any>();
async function saveModelEnter() {
const data = (unref(enteredMapRef) as any)?.getdata?.();
// 兜底系统接口 code,避免 undefined 读属性报错
const sysCode =
(newmapClickROW?.value && newmapClickROW.value.code) ||
(MapServiceProviderROW?.value && MapServiceProviderROW.value.systemInterfaceCode) ||
(selectedInterface?.value && selectedInterface.value.code) ||
'';
MapServiceProviderROW.value = {
serviceProviderId: serviceProviderId.value,
serviceProviderName: serviceProviderName.value,
systemInterfaceCode: sysCode,
addId: upStreamIdRef.value,
inputParameterMappingList: InputParameterMappingList.value,
};
loadingSubmit.value = true;
try {
await postSaveParameterMaps(data);
$baseMessage(t('system.interface.message.saveSuccess'), 'success');
loadingSubmit.value = false;
(unref(enteredMapRef) as any)?.initData?.(
{
serviceProviderId: serviceProviderId.value,
interfaceId: upStreamIdRef.value,
});
$baseConfirm({
title: t('system.interface.modal.warningTitle'),
content: t('system.interface.modal.publishConfirm'),
onOk: async () => {
await registerEnterMethods.closeModal?.();
const currentRow = MapServiceProviderROW.value;
if (currentRow) {
(unref(MapServiceProviderRef) as any)?.onRelease?.(currentRow);
}
await (unref(MapServiceProviderRef) as any)?.tableRef?.reload?.();
await (unref(tableRef) as any)?.reload?.();
},
});
} catch (error) {
loadingSubmit.value = false;
throw error;
}
}
function rowClick(row: any) {
MapServiceProviderROW.value = row;
// 联动右侧映射明细:根据左侧选中映射行初始化 EnteredMap
serviceProviderId.value = row.serviceProviderId;
serviceProviderName.value = row.serviceProviderName;
upStreamIdRef.value = row.systemInterfaceId || upStreamIdRef.value;
nextTick(() => {
(unref(enteredMapRef) as any)?.initData?.({
serviceProviderId: serviceProviderId.value,
interfaceId: upStreamIdRef.value,
});
});
}
// 中间映射列表(服务商映射行)切换时:若当前在 API 调试 tab,则强制刷新调试面板并重新导入 cURL
watch(
() => MapServiceProviderROW.value?.id || MapServiceProviderROW.value?.serviceProviderId || MapServiceProviderROW.value,
async (newVal, oldVal) => {
if (!newVal || newVal === oldVal) return;
apiDebuggerRefreshKey.value++;
if (activeContentTab.value === 'debugger') {
await nextTick();
importCurlToDebugger();
}
}
);
function handleInterfaceDetailCancel() {
// 取消编辑,重新加载接口详情
if (selectedInterface.value) {
detailRefreshKey.value++;
}
}
// 点击调试 / 切换 API 调试 tab:获取请求容器的 cURL 并导入到 API 调试
const curlToImport = ref('');
function importCurlToDebugger() {
const curl = interfaceDetailModalRef.value?.getRequestExample?.() || '';
if (curl && curl.trim()) {
curlToImport.value = curl;
nextTick(() => {
setTimeout(() => { curlToImport.value = ''; }, 500);
});
}
}
function handleDetailDebugClick() {
handleTabChange('debugger');
if (!MapServiceProviderROW.value) return;
importCurlToDebugger();
activeContentTab.value = 'debugger';
}
// 统一的保存函数,根据当前tab调用不同的接口
async function handleTabSave() {
// 使用独立的currentActiveTab变量,在handleTabChange中实时更新
const currentTab = currentActiveTab.value;
try {
if (currentTab === 'mapping') {
await saveModelEnter();
} else if (currentTab === 'detail') {
if (interfaceDetailModalRef.value && typeof interfaceDetailModalRef.value.saveInterface === 'function') {
await interfaceDetailModalRef.value.saveInterface();
// 保存成功后刷新接口明细
if (selectedInterface.value && interfaceDetailModalRef.value) {
await nextTick();
interfaceDetailModalRef.value.init({
id: selectedInterface.value.id,
interfaceId: selectedInterface.value.id,
forceRefresh: true // 强制刷新
});
}
} else {
$baseMessage('接口配置组件未初始化', 'warning');
}
} else {
$baseMessage('当前tab不支持保存操作', 'warning');
}
} catch (error) {
$baseMessage('保存失败', 'error');
throw error;
}
}
// 点击跳转
function goRouter(id: number | undefined, address: string, type?: string) {
router.replace({
name: address,
query: { id, type },
});
}
// 预加载接口明细所需的字典数据
async function preloadDictionaryData() {
const dictionaryTypes = [
'RequestProtocol', // 请求协议
'RequestMethod', // 请求方式
'ContentType', // Content-Type
'ResponseContentType', // Response-Content-Type
'ParameterType', // 参数类型(根节点)
];
// 并行请求所有字典数据
const promises = dictionaryTypes.map(type =>
getDictionaryGetList({ type }).catch(() => {
return null;
})
);
// 不等待结果,后台预加载即可
Promise.all(promises);
}
// 选中接口的辅助函数
function selectInterfaceByKey(key: string) {
const nodeData = getNodeData(key);
if (nodeData && nodeData.isLeaf) {
// 更新选中的keys,保持选中状态
selectedKeys.value = [key];
// 如果选择了不同的接口,更新 detailRefreshKey 以确保 InterfaceConfig 组件重新初始化
if (selectedInterface.value?.id !== nodeData.id) {
detailRefreshKey.value++;
}
selectedInterface.value = nodeData;
// 将上游接口ID同步给右侧明细区域
upStreamIdRef.value = Number(nodeData.id);
// 重置映射明细区域,确保EnteredMap组件重新渲染
serviceProviderId.value = 0;
serviceProviderName.value = '';
MapServiceProviderROW.value = null;
// 刷新右侧映射服务商列表
nextTick(() => {
if (MapServiceProviderRef.value && MapServiceProviderRef.value.tableRef) {
MapServiceProviderRef.value.tableRef.reload();
}
});
// 选择接口后,加载右侧嵌入的"新增映射"所需的服务商数据
loadInitialServiceProviderData();
// 预加载接口明细所需的字典数据
preloadDictionaryData();
// 如果当前在 detail tab,确保 InterfaceConfig 组件能正确初始化
if (activeContentTab.value === 'detail') {
initInterfaceConfig();
}
}
}
// 处理接口树选择
function handleInterfaceSelect(keys: string[], info: any) {
try {
if (info && info.selected && info.node && info.node.isLeaf) {
selectInterfaceByKey(info.node.key);
}
} catch (error) {
}
}
// 初始化接口明细组件的函数
async function initInterfaceConfig() {
if (!selectedInterface.value || activeContentTab.value !== 'detail') {
return;
}
// 等待组件完全挂载(v-if 条件渲染需要时间)
await nextTick();
await nextTick();
await nextTick();
// 使用轮询方式等待组件实例创建,最多等待 2 秒
let retryCount = 0;
const maxRetries = 20;
const checkInterval = 100;
const checkAndInit = () => {
// 检查组件是否还存在且处于 detail tab
if (activeContentTab.value !== 'detail' || !selectedInterface.value) {
return; // 如果已经切换了 tab,停止初始化
}
if (interfaceDetailModalRef.value && interfaceDetailModalRef.value.init) {
try {
interfaceDetailModalRef.value.init({
id: selectedInterface.value.id,
interfaceId: selectedInterface.value.id,
});
} catch {
// 初始化失败,静默处理
}
} else {
retryCount++;
if (retryCount < maxRetries) {
setTimeout(checkAndInit, checkInterval);
}
}
};
// 延迟一下再开始检查,给组件更多时间挂载
setTimeout(checkAndInit, 200);
}
// 监听 interfaceDetailModalRef 变化,当组件挂载后自动初始化(detail 用 v-show 保持挂载,需在有 selectedInterface 时初始化以便切换调试 tab 时能获取 cURL)
watch(interfaceDetailModalRef, async (newRef, oldRef) => {
if (newRef && !oldRef && selectedInterface.value && newRef.init) {
await nextTick();
await nextTick();
setTimeout(() => {
if (selectedInterface.value && newRef && newRef.init) {
try {
newRef.init({
id: selectedInterface.value.id,
interfaceId: selectedInterface.value.id,
});
} catch {
// 初始化失败,静默处理
}
}
}, 200);
}
});
// 监听 activeContentTab 变化,同步更新 currentActiveTab
watch(activeContentTab, async (newTab) => {
currentActiveTab.value = newTab;
if (newTab === 'debugger') {
importCurlToDebugger();
} else if (newTab === 'detail' && selectedInterface.value) {
// 等待 DOM 更新完成后再初始化
await nextTick();
await nextTick();
setTimeout(() => {
initInterfaceConfig();
}, 100);
} else if (newTab === 'mapping') {
// 切换回映射明细tab时,重新初始化EnteredMap组件
await nextTick();
await nextTick();
setTimeout(() => {
if (enteredMapRef.value && selectedInterface.value && upStreamIdRef.value) {
(unref(enteredMapRef) as any)?.initData?.({
serviceProviderId: serviceProviderId.value,
interfaceId: upStreamIdRef.value,
});
}
}, 100);
}
});
// 监听 selectedInterface 变化,初始化组件(detail 用 v-show 保持挂载,需在切换接口时初始化以便切换调试 tab 时能获取 cURL)
watch(selectedInterface, async (newInterface) => {
if (newInterface) {
await nextTick();
await nextTick();
setTimeout(() => {
if (interfaceDetailModalRef.value?.init && selectedInterface.value) {
try {
interfaceDetailModalRef.value.init({
id: selectedInterface.value.id,
interfaceId: selectedInterface.value.id,
});
} catch {
// 初始化失败,静默处理
}
}
}, activeContentTab.value === 'detail' ? 100 : 300);
}
}, { immediate: false });
// 监听模态框打开,初始化模态框中的组件
const modalForm = ref<any>();
watch([editVisible, editId], async ([visible, id]) => {
if (visible && modalForm.value) {
await nextTick();
await nextTick();
setTimeout(() => {
if (modalForm.value && modalForm.value.init) {
if (id) {
// 编辑模式
modalForm.value.init({ id, interfaceId: id });
} else {
// 新增模式
modalForm.value.init();
}
}
}, 200);
}
});
// 处理上游接口保存成功事件
function handleUpstreamInterfaceSaved() {
editVisible.value = false;
(unref(tableRef) as any)?.reload?.();
// 如果当前有选中的接口且处于 detail tab,刷新接口明细
if (selectedInterface.value && activeContentTab.value === 'detail' && interfaceDetailModalRef.value) {
nextTick(() => {
interfaceDetailModalRef.value.init({
id: selectedInterface.value.id,
interfaceId: selectedInterface.value.id,
forceRefresh: true // 强制刷新
});
});
}
}
// 处理模态框保存
async function handleModalSave() {
if (modalForm.value && modalForm.value.saveInterface) {
try {
await modalForm.value.saveInterface();
editVisible.value = false;
(unref(tableRef) as any)?.reload?.();
// 如果当前有选中的接口且处于 detail tab,刷新接口明细
if (selectedInterface.value && activeContentTab.value === 'detail' && interfaceDetailModalRef.value) {
await nextTick();
interfaceDetailModalRef.value.init({
id: selectedInterface.value.id,
interfaceId: selectedInterface.value.id,
forceRefresh: true // 强制刷新
});
}
} catch (error) {
// 错误已在组件内部处理
}
}
}
// 处理树展开
function handleTreeExpand(keys: string[]) {
try {
expandedKeys.value = keys;
} catch (error) {
}
}
// 加载接口树数据
async function loadInterfaceTree() {
loading.value = true;
try {
// 调用获取接口列表的API
const params: any = {
pageIndex: 1,
pageSize: 1000, // 获取所有接口
isUpstream: true, // 上游接口
};
// 如果有搜索关键词,添加到参数中
if (searchKeyword.value) {
params.nameOrCode = searchKeyword.value;
}
const response = await postInterfaceInterfacePaginatedList(params);
// 从API响应中获取数据
const data = (response as any)?.items || (response as any)?.data || (response as any)?.list || [];
if (Array.isArray(data) && data.length > 0) {
// 将接口数据转换为树形结构
const treeData = convertToTreeData(data);
interfaceTreeData.value = treeData;
// 默认展开所有分类节点
const categoryKeys = treeData.map(item => item.key);
expandedKeys.value = categoryKeys;
} else {
interfaceTreeData.value = [];
}
} catch (error) {
$baseMessage('加载接口列表失败', 'error');
} finally {
loading.value = false;
}
}
// 将接口数据转换为树形结构
function convertToTreeData(interfaces: any[]) {
// 按分类分组
const categoryMap = new Map();
interfaces.forEach((item) => {
// 使用categoryName作为分类,如果没有则使用category
const category = item.categoryName || `分类${item.category}` || '未分类';
if (!categoryMap.has(category)) {
categoryMap.set(category, []);
}
const treeItem = {
key: `item-${item.id}`,
title: item.name,
isLeaf: true,
id: item.id,
name: item.name,
code: item.code || item.name,
category: category,
mapCount: item.mapCount || 0
};
categoryMap.get(category).push(treeItem);
});
// 转换为树形结构
const treeData = [];
let categoryIndex = 1;
for (const [category, items] of categoryMap) {
const categoryNode = {
key: `category-${categoryIndex}`,
title: category,
children: items
};
treeData.push(categoryNode);
categoryIndex++;
}
return treeData;
}
// 处理搜索
function handleSearch() {
loadInterfaceTree();
}
// 处理下拉菜单选择
function handleActionSelect(value: string, option: any) {
try {
if (option && option.data) {
const dataRef = option.data;
if (value === 'edit') {
compileClick(dataRef);
} else if (value === 'map') {
newmapClick(dataRef);
}
}
} catch (error) {
}
}
// 根据key获取节点数据
function getNodeData(key: string) {
if (!key || !interfaceTreeData.value.length) return null;
// 递归查找节点
function findNode(nodes: any[]): any {
for (const node of nodes) {
if (node.key === key) {
return node;
}
if (node.children && node.children.length > 0) {
const found = findNode(node.children);
if (found) return found;
}
}
return null;
}
return findNode(interfaceTreeData.value);
}
</script>
<template>
<div class="interface-layout">
<!-- 左侧上游接口列表:自定义左菜单 -->
<CustomLeftMenu
class="left-sidebar"
:tree-data="interfaceTreeData"
v-model:selected-keys="selectedKeys"
v-model:expanded-keys="expandedKeys"
:loading="loading"
v-model:search-keyword="searchKeyword"
:search-placeholder="$t('system.interface.search.placeholder.interfaceName')"
:refresh-title="$t('system.interface.actions.refresh')"
:add-title="$t('system.interface.actions.addInterface')"
:loading-text="$t('system.interface.loading.interfaceList')"
:delete-label="$t('system.interface.actions.delete')"
:actions-title="$t('system.interface.table.actions')"
method-label="POST"
@search="handleSearch"
@refresh="loadInterfaceTree"
@add="interfaceClick"
@select="handleInterfaceSelect"
@expand="handleTreeExpand"
@action-select="handleActionSelect"
@delete-node="deleteInterfaceClick"
@node-click="(key) => selectInterfaceByKey(key)"
/>
<!-- 中间映射列表区域 -->
<div class="middle-content nav-panel">
<div class="content-header sidebar-header">
<div class="mapping-search-box">
<a-input
v-model="mappingSearchKeyword"
:placeholder="$t('system.interface.search.placeholder.providerName')"
allow-clear
size="small"
class="sidebar-search-input"
@change="() => MapServiceProviderRef?.setSearchKeyword?.(mappingSearchKeyword)"
@clear="() => MapServiceProviderRef?.setSearchKeyword?.('')"
>
<template #prefix>
<icon-search />
</template>
</a-input>
</div>
<div class="header-actions">
<a-button
type="text"
size="small"
class="action-icon-btn refresh-btn"
@click="MapServiceProviderRef?.tableRef?.reload?.()"
:disabled="!selectedInterface"
:title="$t('system.interface.actions.refresh')"
>
↻
</a-button>
<a-button
type="text"
size="small"
class="action-icon-btn add-btn"
@click="() => {
if (selectedInterface) {
newmapClick(selectedInterface);
} else {
$baseMessage($t('system.interface.message.selectInterfaceFirst'), 'warning');
}
}"
:disabled="!selectedInterface"
:title="$t('system.interface.modal.addMapping')"
>
+
</a-button>
</div>
</div>
<div class="content-body">
<div v-if="selectedInterface" class="mapping-list-container tree-scroll">
<MapServiceProvider
:id="selectedInterface.id"
ref="MapServiceProviderRef"
@edit-map="handlerEditMap"
@row-click="rowClick"
/>
</div>
<div v-else class="empty-state">
<a-empty :description="$t('system.interface.empty.selectLeftInterfaceForMapping')" />
</div>
</div>
</div>
<!-- 右侧内容区域 -->
<div class="right-content content-panel">
<div v-if="selectedInterface" class="interface-detail-container">
<!-- Tab切换 - 完全独立 -->
<div class="interface-detail-tabs">
<div class="tabs-header">
<div class="tabs-list">
<div
class="tab-item"
:class="{ active: activeContentTab === 'mapping' }"
@click="handleTabChange('mapping'); activeContentTab = 'mapping'"
>
{{ $t('system.interface.tabs.mappingDetail') }}
</div>
<div
class="tab-item"
:class="{ active: activeContentTab === 'debugger' }"
:style="{ opacity: MapServiceProviderROW ? 1 : 0.5, cursor: MapServiceProviderROW ? 'pointer' : 'not-allowed' }"
@click="if (MapServiceProviderROW) { handleTabChange('debugger'); activeContentTab = 'debugger' }"
>
{{ $t('system.interface.tabs.apiDebugger') }}
</div>
<div
class="tab-item"
:class="{ active: activeContentTab === 'detail' }"
@click="handleTabChange('detail'); activeContentTab = 'detail'"
>
{{ $t('system.interface.tabs.interfaceDetail') }}
</div>
</div>
<div class="tabs-extra">
<a-button
v-if="activeContentTab === 'mapping'"
size="small"
type="primary"
@click="handleTabSave"
>
{{ $t('system.interface.actions.save') }}
</a-button>
</div>
</div>
</div>
<!-- Tab内容 - 完全独立 -->
<div class="interface-detail-tab-content">
<!-- 映射明细内容 -->
<div v-if="activeContentTab === 'mapping' && selectedInterface" class="mapping-content">
<EnteredMap
:key="`entered-map-${selectedInterface?.id}-${serviceProviderId}-${activeContentTab}`"
ref="enteredMapRef"
class="h-full w-full"
:service-provider-id="serviceProviderId"
:interface-id="upStreamIdRef"
:service-provider-name="serviceProviderName"
@save-model-enter="saveModelEnter"
/>
</div>
<!-- API调试组件 -->
<div v-if="activeContentTab === 'debugger'" class="debug-content">
<ApiDebugger
:key="`api-debugger-${selectedInterface?.id || ''}-${MapServiceProviderROW?.id || MapServiceProviderROW?.serviceProviderId || ''}-${apiDebuggerRefreshKey}`"
ref="apiDebuggerRef"
:selected-interface="selectedInterface"
:selected-service-provider="MapServiceProviderROW"
:enable-defaults="true"
:curl-to-import="curlToImport"
/>
</div>
<!-- 接口明细内容(v-show 保持挂载,便于切换至调试 tab 时获取 cURL) -->
<div v-show="activeContentTab === 'detail' && selectedInterface" class="detail-content">
<UpstreamInterfaceModal
:key="`${selectedInterface?.id}-${detailRefreshKey}`"
ref="interfaceDetailModalRef"
@saved="() => { /* 保留在当前tab,按需刷新左侧树 */ }"
>
<template #headerActions>
<a-button size="small" type="primary" @click="handleTabSave">
{{ $t('system.interface.actions.save') }}
</a-button>
<a-button size="small" @click="handleDetailDebugClick">
调试
</a-button>
</template>
</UpstreamInterfaceModal>
</div>
</div>
</div>
<div v-else class="empty-state">
<a-empty :description="$t('system.interface.empty.selectInterfaceForDetail')" />
</div>
</div>
</div>
<ModalPro
v-model:visible="editVisible"
:width="1500"
:modal-style="{ height: '900px' }"
:title="editId ? $t('system.interface.modal.editUpstreamInterface') : $t('system.interface.modal.addUpstreamInterface')"
unmount-on-close
:footer="false"
class="upstream-modal-tall"
>
<template v-if="editVisible">
<UpstreamInterfaceModal
ref="modalForm"
@saved="handleUpstreamInterfaceSaved"
/>
</template>
<div class="modal-footer">
<a-space :size="8">
<a-button @click="editVisible = false">取消</a-button>
<a-button type="primary" @click="handleModalSave">保存</a-button>
</a-space>
</div>
</ModalPro>
<ModalPro
@register="registerAddMap"
@ok="handleMappingSubmit"
@cancel="cancelCascader"
:width="1000"
:title="`新增映射 - ${mapName || ''}`"
>
<div class="mapping-modal-content">
<!-- 映射列表 -->
<div class="mapping-selector">
<div class="selector-tip">
<span class="tip-text">需要映射多个接口的,请注意排序</span>
</div>
<div class="mapping-list">
<div
v-for="(mapping, index) in mappingList"
:key="index"
class="mapping-item"
>
<span class="mapping-label">第{{ index + 1 }}个映射</span>
<a-cascader
v-model="mapping.value"
:options="getCascaderOptionsForIndex(index)"
:field-names="{ label: 'label', value: 'value', children: 'children' }"
:placeholder="$t('system.interface.search.placeholder.selectProviderAndInterface')"
:show-search="true"
:filter-option="filterCascaderOption"
:change-on-select="false"
style="flex: 1"
/>
<a-button
v-if="index === mappingList.length - 1"
type="text"
size="small"
class="action-icon-btn add-btn"
@click="addMapping"
:title="$t('system.interface.actions.addMapping')"
>
+
</a-button>
</div>
</div>
</div>
</div>
</ModalPro>
<ModalPro title="映射服务商列表" :width="1200" @register="register">
<MapServiceProvider
:id="mapId"
ref="MapServiceProviderRef"
@edit-map="handlerEditMap"
@row-click="rowClick"
/>
</ModalPro>
<ModalPro
before-close
:width="1600"
:height="1600"
class="mapping-modal"
@register="registerEnter"
@ok="saveModelEnter"
@reset="() => {}"
>
<div >
<EnteredMap
ref="enteredMapRef"
class="h-full w-full"
:service-provider-id="serviceProviderId"
:interface-id="upStreamIdRef"
:service-provider-name="serviceProviderName"
@save-model-enter="saveModelEnter"
/>
</div>
</ModalPro>
</template>
<style scoped lang="less">
.interface-layout {
display: flex;
height: calc(100vh - 86px);
max-height: calc(100vh - 86px);
padding: 16px;
gap: 16px;
background-color: #f0f3f4;
overflow: hidden;
box-sizing: border-box;
width: 100%;
}
/* 左侧使用自定义左菜单组件,仅保留布局 */
.left-sidebar {
width: 280px;
flex-shrink: 0;
height: 100%;
}
.content-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px !important;
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
background: transparent;
min-height: 48px !important;
color: #000;
}
/* 中间映射列表区域 */
.middle-content.nav-panel {
flex: 1;
max-width: 250px;
background: #fff;
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 8px;
display: flex;
flex-direction: column;
overflow: hidden;
color: #000;
box-shadow: none;
}
.mapping-list-title {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #000;
}
.mapping-list-container {
flex: 1;
padding: 8px 4px 0 4px;
overflow-y: auto;
color: #000;
background: transparent;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
> * {
width: 100%;
max-width: 100%;
}
}
.mapping-list-container::-webkit-scrollbar {
width: 6px;
}
.mapping-list-container::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.08);
border-radius: 8px;
}
.mapping-list-container::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.15);
border-radius: 8px;
}
.mapping-list-container::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.25);
}
.mapping-search-box {
flex: 1 1 calc(100% - 22px);
padding: 0;
margin-right: 4px;
box-sizing: border-box;
min-width: 0;
}
.mapping-search-box :deep(.arco-input-wrapper) {
background: #fff !important;
border: 1px solid var(--color-border-2) !important;
border-radius: 4px !important;
height: auto !important;
}
.mapping-search-box :deep(.arco-input) {
background: #fff !important;
border: none !important;
border-radius: 4px !important;
color: var(--color-text-1) !important;
height: auto !important;
}
.mapping-search-box :deep(.arco-input::placeholder) {
color: var(--color-text-3) !important;
}
.mapping-search-box :deep(.arco-input:hover),
.mapping-search-box :deep(.arco-input:focus),
.mapping-search-box :deep(.arco-input-wrapper:hover),
.mapping-search-box :deep(.arco-input-wrapper:focus-within) {
background: #fff !important;
border-color: var(--app-primary-color) !important;
}
.mapping-search-box :deep(.arco-input-prefix) {
color: var(--color-text-3) !important;
}
/* 右侧内容区域 */
.right-content.content-panel {
flex: 1;
background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 8px;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.interface-detail-container {
display: flex;
flex-direction: column;
height: 100%;
.interface-detail-tabs {
margin-bottom: 16px;
flex-shrink: 0;
border-bottom: 1px solid var(--color-border-2);
.tabs-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
height: 48px;
.tabs-list {
display: flex;
align-items: center;
gap: 32px;
flex: 1;
justify-content: flex-start;
.tab-item {
padding: 8px 0;
font-size: 14px;
color: var(--color-text-2);
cursor: pointer;
position: relative;
transition: color 0.2s;
font-weight: 600;
white-space: nowrap;
flex-shrink: 0;
&:hover {
color: var(--color-text-1);
}
&.active {
color: var(--app-primary-color) !important;
font-weight: 600 !important;
&::after {
content: '' !important;
position: absolute !important;
bottom: -1px !important;
left: 0 !important;
right: 0 !important;
height: 2px !important;
background-color: var(--app-primary-color) !important;
}
}
}
}
.tabs-extra {
flex-shrink: 0;
margin-left: auto;
}
}
}
.interface-detail-tab-content {
flex: 1;
overflow: hidden;
min-height: 0;
.mapping-content {
height: 100%;
display: flex;
flex-direction: column;
min-height: 0;
}
.detail-content {
height: 100%;
display: flex;
flex-direction: column;
padding: 20px;
box-sizing: border-box;
min-height: 0;
}
.debug-content {
height: 100%;
display: flex;
flex-direction: column;
padding: 20px;
min-height: 0;
}
}
}
.tab-content {
height: 100%;
display: flex;
flex-direction: column;
flex: 1;
}
.tab-actions {
padding: 16px 20px;
border-bottom: 1px solid var(--color-border-2);
display: flex;
justify-content: flex-end;
align-items: center;
flex-shrink: 0;
}
.tab-body {
flex: 1;
height: 100%;
overflow-y: auto;
padding: 20px;
box-sizing: border-box;
}
.middle-content.nav-panel .content-header {
padding: 10px 12px !important;
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
background: transparent;
display: flex;
justify-content: space-between;
align-items: center;
min-height: 48px !important;
color: #000;
}
.header-actions {
display: flex;
gap: var(--app-button-gap, 8px);
flex-shrink: 0;
margin-left: auto;
}
/* 映射列表区域:刷新、新增按钮为黑色纯符号(无背景无框),两按钮紧挨 */
.middle-content.nav-panel .header-actions .action-icon-btn.refresh-btn {
color: #000 !important;
background-color: transparent !important;
border-color: transparent !important;
&:hover:not(:disabled) {
color: #262626 !important;
background-color: transparent !important;
border-color: transparent !important;
}
&:disabled {
color: #d9d9d9 !important;
background-color: transparent !important;
border-color: transparent !important;
}
}
.middle-content.nav-panel .header-actions .action-icon-btn.add-btn {
color: #000 !important;
background-color: transparent !important;
border-color: transparent !important;
&:hover:not(:disabled) {
color: #262626 !important;
background-color: transparent !important;
border-color: transparent !important;
}
&:disabled {
color: #d9d9d9 !important;
background-color: transparent !important;
border-color: transparent !important;
}
}
.content-body {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
}
.empty-state {
flex: 1; /* 占满父容器剩余空间 */
min-height: 0; /* 防止高度塌陷 */
display: flex;
align-items: center;
justify-content: center;
width: 100%; /* 确保占满宽度 */
:deep(.arco-empty) {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
}
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 120px;
color: #666;
p {
margin-top: 8px;
font-size: 12px;
}
}
// 新增映射弹窗样式
.mapping-modal-content {
padding: 0;
}
.mapping-selector {
padding: 20px;
// 强制设置弹窗内按钮为正方形
:deep(.arco-btn) {
width: 28px !important;
height: 28px !important;
min-width: 28px !important;
max-width: 28px !important;
padding: 0 !important;
}
}
.selector-tip {
margin-bottom: 20px;
.tip-text {
color: #ff7800;
font-size: 14px;
}
}
.mapping-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.mapping-item {
display: flex;
align-items: center;
gap: 12px;
.mapping-label {
font-size: 14px;
color: #262626;
min-width: 80px;
}
:deep(.action-icon-btn) {
width: 28px !important;
height: 28px !important;
min-width: 28px !important;
max-width: 28px !important;
padding: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
color: #8c8c8c !important;
border: 1px solid transparent !important;
border-radius: 4px !important;
font-size: 16px !important;
font-weight: bold !important;
}
:deep(.action-icon-btn.add-btn) {
color: var(--app-primary-color) !important;
background-color: var(--app-primary-color-light) !important;
border: 1px solid var(--app-primary-color) !important;
border-radius: 50% !important;
width: 28px !important;
height: 28px !important;
min-width: 28px !important;
max-width: 28px !important;
min-height: 28px !important;
max-height: 28px !important;
padding: 0 !important;
line-height: 28px !important;
}
:deep(.action-icon-btn.add-btn:hover) {
color: #ffffff !important;
background-color: var(--app-primary-color) !important;
border-color: var(--app-primary-color) !important;
}
}
.mapping-tip {
margin-bottom: 20px;
}
.mapping-search {
margin-bottom: 20px;
padding: 16px;
background-color: #fafafa;
border-radius: 6px;
border: 1px solid #e8e8e8;
}
.mapping-cascader {
border: 1px solid #e8e8e8;
border-radius: 6px;
overflow: hidden;
}
.cascader-header {
padding: 12px 16px;
background-color: #f5f5f5;
border-bottom: 1px solid #e8e8e8;
h4 {
margin: 0 0 4px 0;
font-size: 14px;
font-weight: 600;
color: #262626;
}
.cascader-desc {
font-size: 12px;
color: #8c8c8c;
}
}
.cascader-content {
min-height: 300px;
max-height: 400px;
overflow-y: auto;
}
.cascader-empty {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
:deep(.arco-empty) {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
}
.span-style1 {
font-size: 17px;
font-weight: 700;
color: #222;
}
.query-box {
padding: 12px 16px;
border-radius: 2px;
}
.table-box {
padding: 0 16px;
border-radius: 2px;
}
:deep(.vxe-table--header) {
background-color: #f2f3f5;
font-weight: bold;
display: table-header-group !important;
}
// :deep(.vxe-header--column) {
// height: 48px;
// padding: 8px;
// }
:deep(.mapping-modal) {
.arco-modal-body {
height: 1500px !important;
max-height: 1500px !important;
padding: 0 !important;
}
.arco-modal {
border-radius: 8px;
overflow: hidden;
}
.arco-modal-header {
padding: 16px 20px;
border-bottom: 1px solid #e8e8e8;
}
.arco-modal-footer {
padding: 12px 20px;
border-top: 1px solid #e8e8e8;
}
}
/* toolbar spacing and control sizes */
:deep(.toolbarBtn .arco-btn) {
height: 28px;
line-height: 26px;
padding: 0 10px;
margin-left: 2px;
}
/* remove outer rectangular wrapper of search (form inline) */
:deep(.arco-form-item-label-col) {
display: none !important;
}
:deep(.arco-form-item) { margin: 0 8px 0 0; }
:deep(.arco-input-wrapper) {
height: 28px;
}
:deep(.arco-form-item-content-wrapper) {
padding-right: 0 !important;
padding-left: 0 !important;
margin: 0 !important;
background: transparent !important;
border: none !important;
box-shadow: none !important;
outline: none !important;
}
:deep(.arco-form-item-content) { margin-right: 0; }
/* kill double-layer look: remove any outer box/background around inline form items */
:deep(.arco-form-inline) {
background: transparent !important;
border: 0 !important;
}
:deep(.vxe-toolbar .arco-form) {
background: transparent !important;
border: none !important;
box-shadow: none !important;
}
/* override inline paddings/margins added by arco row */
:deep(.vxe-toolbar .arco-row[style]) {
padding-left: 0 !important;
padding-right: 0 !important;
margin-left: 0 !important;
margin-right: 0 !important;
}
:deep(.vxe-toolbar .arco-form[style]) {
padding-left: 0 !important;
padding-right: 0 !important;
margin-left: 0 !important;
margin-right: 0 !important;
border: none !important;
box-shadow: none !important;
background: transparent !important;
}
/* remove the extra rounded rectangle on form item content */
:deep(.vxe-toolbar .arco-form-item-content),
:deep(.vxe-toolbar .arco-form-item-content-flex) {
background: transparent !important;
border: none !important;
box-shadow: none !important;
outline: none !important;
}
:deep(.arco-form-inline .arco-form-item) {
background: transparent !important;
border: 0 !important;
padding: 0 !important;
}
:deep(.arco-form-item-content) {
background: transparent !important;
border: none !important;
box-shadow: none !important;
}
/* remove input focus halo so only一个边框可见 */
:deep(.arco-input-wrapper.arco-input-focus),
:deep(.arco-input-wrapper:hover) {
box-shadow: none !important;
}
:deep(.arco-input) { outline: none !important; }
.modal-footer {
margin-top: 8px;
display: flex;
justify-content: flex-end;
padding: 6px 0 0 0;
gap: var(--app-button-gap, 8px);
}
</style>