front/src/views/system/interface/index.vue
2025-11-03 19:39:54 +08:00

2250 lines
64 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, ref, watch, computed } from 'vue';
import {
postInterfaceSystemInterfacePaginatedList,
postInterfaceBatchDeleteSystemInterfaceByIds,
} from '@/api/InterfaceServices/systemInterface';
import CascaderPro from '@/components/CascaderPro/Cascader.vue';
import { getServiceProviderGetServiceProviderInterfaceCascadedList } from '@/api/interface-serviceProvider/serviceProvider';
import { IconClose, IconEdit, IconPlus, IconSearch, IconMore, IconMinus, IconRefresh, IconDelete } 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 InterfaceConfig from './interface-config.vue';
const { t } = useI18n();
defineOptions({
name: 'SystemInterface',
});
const active = ref(0);
const activeContentTab = ref('mapping');
const apiDebuggerRef = ref<any>();
const drawerList = ref<any[]>([]);
// 左侧接口树相关数据
const searchKeyword = 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: '接口名称/代码',
allowClear: true,
size: 'small',
},
span: 12,
},
],
buttonConfig: {
showBtn: true,
span: 12,
showOk: true,
okText: '查询',
},
});
const tableRef = ref<any>();
const tableProps = reactive({
isToolsExport: false,
isToolsImport: false,
api: postInterfaceSystemInterfacePaginatedList,
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: '请输入接口名称或接口代码',
allowClear: true,
size: 'small',
},
},
],
layout: 'inline',
labelColProps: { span: 0 },
wrapperColProps: { span: 24 },
buttonConfig: {
showBtn: true,
showOk: true,
okText: '查询',
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 }) {
console.log('compileClick called with:', row);
editId.value = row.id;
editVisible.value = true;
console.log('editVisible set to:', editVisible.value);
}
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('系统接口存在映射不允许删除', 'error');
return
}
const ids = list?.map((node: any) => node.id);
if (ids) {
$baseConfirm({
title: '提示',
content: '确定是否删除该条数据',
okText: '确定',
cancelText: '取消',
async onOk() {
await postInterfaceBatchDeleteSystemInterfaceByIds(ids);
tableRef.value?.removeCheckboxRow();
$baseMessage('删除成功', 'success');
},
});
}
}
// 删除单个接口
async function deleteInterfaceClick(nodeData: any) {
if (!nodeData || !nodeData.isLeaf) {
$baseMessage('只能删除接口节点', 'warning');
return;
}
if (nodeData.mapCount > 0) {
$baseMessage('系统接口存在映射不允许删除', 'error');
return;
}
$baseConfirm({
title: '温馨提示',
content: `确定要删除接口"${nodeData.title}"吗?`,
onOk: async () => {
try {
await postInterfaceBatchDeleteSystemInterfaceByIds([nodeData.id]);
$baseMessage('删除成功', 'success');
loadInterfaceTree();
// 如果删除的是当前选中的接口,清空选中状态
if (selectedInterface.value?.id === nodeData.id) {
selectedInterface.value = null;
selectedKeys.value = [];
}
} catch (error) {
$baseMessage('删除失败', 'error');
}
},
});
}
// 新增映射
const [registerAddMap, registerAddMapMethods] = useModal() as any;
const newmapClickROW = ref();
// 级联选择器相关数据
const cascaderOptions = ref<any[]>([]);
const mappingList = ref<any[]>([{ value: [] }]);
async function newmapClick(row: any) {
console.log('newmapClick called with:', row);
drawerVisible.value = true;
mapId.value = row.id;
registerAddMapMethods.setModalProps({
title: '新增映射',
width: 1000,
});
registerAddMapMethods.openModal();
newmapClickROW.value = row;
upStreamIdRef.value = Number(row.id);
mapName.value = row.name;
console.log('新增映射弹窗已打开');
// 重置映射列表,初始化为1个空映射
mappingList.value = [{ value: [] }];
// 初始化时加载所有服务商数据
await loadInitialServiceProviderData();
}
// 初始化加载服务商数据
async function loadInitialServiceProviderData() {
try {
console.log('开始加载初始服务商数据');
const response = await getServiceProviderGetServiceProviderInterfaceCascadedList({
serviceProviderName: '',
interfaceId: upStreamIdRef.value || 0,
});
console.log('初始API返回的原始数据:', response);
console.log('response.data:', response.data);
console.log('response 本身:', response);
// 处理数据,去掉服务商类型的一级菜单,直接显示服务商和服务商接口
// API返回的数据结构可能是 response 本身就是数组,或者 response.data 是数组
const rawData = Array.isArray(response) ? response : (response.data || []);
console.log('使用的原始数据:', rawData);
const processedData = processServiceProviderData(rawData);
console.log('初始处理后的数据:', processedData);
console.log('processedData 长度:', processedData.length);
drawerList.value = processedData;
// 构建级联选择器数据 - 直接使用处理后数据的label字段
buildCascaderOptions(processedData);
await nextTick();
console.log('设置初始panelRef选项');
panelRef.value?.SetOptions(processedData);
const arr = processedData?.filter((node: { children: any; }) => {
return node.children && node.children.length > 0;
});
cascaderValue.value = filterValue(arr);
} catch (error) {
console.error('加载初始服务商数据失败:', error);
cascaderOptions.value = [];
}
}
// 构建级联选择器数据
function buildCascaderOptions(data: any[]) {
console.log('buildCascaderOptions 输入数据:', data);
if (!data || data.length === 0) {
console.log('没有数据,设置空数组');
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
})) || []
};
console.log('处理后的服务商数据:', providerData);
return providerData;
});
console.log('buildCascaderOptions 输出数据:', cascaderOptions.value);
}
// 级联选择器过滤函数
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) {
// 如果切换到API调试tab,检查是否选择了映射服务商
if (tabName === 'debugger') {
if (!MapServiceProviderROW.value) {
$baseMessage('请选择服务商进行调试', 'warning');
// 切换回映射列表tab
activeContentTab.value = 'mapping';
return;
}
}
// 如果切换到接口明细tab,检查是否选择了接口
if (tabName === 'detail') {
if (!selectedInterface.value) {
$baseMessage('请选择左侧接口', 'warning');
activeContentTab.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) {
console.log('onDetaCasc 被调用,参数:', formValue);
cascaderValue.value = [];
const response =
await getServiceProviderGetServiceProviderInterfaceCascadedList({
serviceProviderName: formValue?.serviceProviderName,
interfaceId: upStreamIdRef.value || 0,
});
console.log('API返回的原始数据:', response);
// 处理数据,去掉服务商类型的一级菜单,直接显示服务商和服务商接口
const processedData = processServiceProviderData(response.data || []);
console.log('处理后的数据:', processedData);
drawerList.value = processedData;
await nextTick();
console.log('设置panelRef选项');
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[] = [];
console.log('开始处理数据,原始数据结构:', data);
data.forEach((category: any) => {
console.log('处理分类:', category);
if (category && category.children && Array.isArray(category.children)) {
// 遍历每个分类下的服务商
category.children.forEach((provider: any) => {
console.log('处理服务商:', provider);
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;
console.log('创建新的服务商节点:', newProvider);
result.push(newProvider);
}
});
}
});
console.log('最终处理结果:', result);
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[]) {
console.log('选中的项目:', selectedItems);
// 这里可以处理选中的项目,比如更新UI状态等
}
// 处理树形搜索
function handleTreeSearch(keyword: string) {
console.log('搜索关键词:', keyword);
// 调用原来的搜索逻辑
onDetaCasc({ serviceProviderName: keyword });
}
const InputParameterMappingList = ref<any[]>([
{
value: '创建袋子',
label: '入参映射',
data: 1,
},
{
value: '获取带牌标签',
label: '入参映射',
data: 2,
},
{
value: '袋子绑定包裹',
label: '入参映射',
data: 3,
},
{
value: '出参映射',
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('请选择服务商的接口');
InputParameterMappingList.value = arr
.map((t: any, i: number) => (i = { ...t, data: i }))
.concat({
value: '',
label: '出参映射',
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: '新增',
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) {
console.log(data);
editMapIds.value = data;
serviceProviderId.value = data.serviceProviderId;
serviceProviderName.value = data.serviceProviderName;
upStreamIdRef.value = data.addId;
InputParameterMappingList.value = data.inputParameterMappingList;
registerEnterMethods.setModalProps({
title: '修改',
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 interfaceConfigRef = ref<any>();
const detailRefreshKey = ref(0);
const selectedProvider = ref<any>();
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;
postSaveParameterMaps(data)
.then(() => {
$baseMessage(t('保存成功'), 'success');
loadingSubmit.value = false;
(unref(enteredMapRef) as any)?.initData?.(
{
serviceProviderId: serviceProviderId.value,
interfaceId: upStreamIdRef.value,
});
$baseConfirm({
title: t('温馨提示'),
content: t('是否去发布!'),
onOk: async () => {
// 保留发布确认,但不再弹“映射服务商/发布”窗口,仅关闭并刷新
await registerEnterMethods.closeModal?.();
await (unref(MapServiceProviderRef) as any)?.tableRef?.reload?.();
await (unref(tableRef) as any)?.reload?.();
},
});
})
.catch(() => {
loadingSubmit.value = false;
});
}
function rowClick(row: any) {
console.log(row);
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,
});
});
}
function handleInterfaceDetailCancel() {
// 取消编辑,重新加载接口详情
if (selectedInterface.value) {
detailRefreshKey.value++;
}
}
// 点击跳转
function goRouter(id: number | undefined, address: string, type?: string) {
router.replace({
name: address,
query: { id, type },
});
}
// 处理接口树选择
function handleInterfaceSelect(keys: string[], info: any) {
try {
if (info && info.selected && info.node && info.node.isLeaf) {
// 更新选中的keys,保持选中状态
selectedKeys.value = keys;
const nodeData = getNodeData(info.node.key);
if (nodeData) {
selectedInterface.value = nodeData;
// 将上游接口ID同步给右侧明细区域
upStreamIdRef.value = Number(nodeData.id);
console.log('选择的接口:', nodeData);
// 重置映射明细区域,确保EnteredMap组件重新渲染
serviceProviderId.value = 0;
serviceProviderName.value = '';
MapServiceProviderROW.value = null;
// 刷新右侧映射服务商列表
nextTick(() => {
if (MapServiceProviderRef.value && MapServiceProviderRef.value.tableRef) {
console.log('重新加载映射服务商列表');
MapServiceProviderRef.value.tableRef.reload();
}
});
// 选择接口后,加载右侧嵌入的"新增映射"所需的服务商数据
loadInitialServiceProviderData();
}
}
} catch (error) {
console.error('处理接口选择时出错:', error);
}
}
// 处理树展开
function handleTreeExpand(keys: string[]) {
try {
expandedKeys.value = keys;
} catch (error) {
console.error('处理树展开时出错:', error);
}
}
// 加载接口树数据
async function loadInterfaceTree() {
loading.value = true;
try {
console.log('开始加载接口树数据...');
// 调用获取接口列表的API
const params: any = {
pageIndex: 1,
pageSize: 1000, // 获取所有接口
};
// 如果有搜索关键词,添加到参数中
if (searchKeyword.value) {
params.nameOrCode = searchKeyword.value;
}
const response = await postInterfaceSystemInterfacePaginatedList(params);
console.log('API完整响应:', response);
// 从API响应中获取数据
const data = (response as any)?.items || (response as any)?.data || (response as any)?.list || [];
console.log('提取的接口数据:', data);
if (Array.isArray(data) && data.length > 0) {
// 将接口数据转换为树形结构
const treeData = convertToTreeData(data);
console.log('转换后的树形数据:', treeData);
interfaceTreeData.value = treeData;
// 默认展开所有分类节点
const categoryKeys = treeData.map(item => item.key);
expandedKeys.value = categoryKeys;
} else {
interfaceTreeData.value = [];
}
} catch (error) {
console.error('加载接口树数据失败:', error);
$baseMessage('加载接口列表失败', 'error');
} finally {
loading.value = false;
}
}
// 将接口数据转换为树形结构
function convertToTreeData(interfaces: any[]) {
console.log('开始转换数据:', interfaces);
// 按分类分组
const categoryMap = new Map();
interfaces.forEach((item) => {
console.log('处理接口项:', 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
};
console.log('创建的树项:', treeItem);
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
};
console.log('创建分类节点:', categoryNode);
treeData.push(categoryNode);
categoryIndex++;
}
console.log('最终树形数据:', treeData);
return treeData;
}
// 处理搜索
function handleSearch() {
loadInterfaceTree();
}
// 处理下拉菜单选择
function handleActionSelect(value: string, option: any) {
try {
console.log('handleActionSelect called:', { value, option });
if (option && option.data) {
const dataRef = option.data;
console.log('dataRef:', dataRef);
if (value === 'edit') {
console.log('调用 compileClick');
compileClick(dataRef);
} else if (value === 'map') {
console.log('调用 newmapClick');
newmapClick(dataRef);
}
} else {
console.log('option 或 option.data 为空:', { option });
}
} catch (error) {
console.error('处理下拉菜单选择时出错:', 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);
}
// 获取节点在接口列表中的索引
function getNodeIndex(key: string) {
if (!key || !interfaceTreeData.value.length) return '';
let index = 1;
// 递归查找节点索引
function findNodeIndex(nodes: any[]): number | null {
for (const node of nodes) {
if (node.key === key) {
return index;
}
if (node.children && node.children.length > 0) {
for (const child of node.children) {
if (child.key === key) {
return index;
}
index++;
}
}
}
return null;
}
// 遍历所有分类,计算接口索引
for (const category of interfaceTreeData.value) {
if (category.children && category.children.length > 0) {
for (const child of category.children) {
if (child.key === key) {
return index;
}
index++;
}
}
}
return '';
}
</script>
<template>
<div class="interface-layout">
<!-- 左侧上游接口列表 -->
<div class="left-sidebar">
<div class="sidebar-header">
<div class="search-box">
<a-input
v-model="searchKeyword"
placeholder="请输入接口名称"
allow-clear
size="small"
@change="handleSearch"
@clear="handleSearch"
>
<template #prefix>
<icon-search />
</template>
</a-input>
<div class="search-actions">
<a-button
type="text"
size="small"
class="action-icon-btn refresh-btn"
@click="loadInterfaceTree"
title="刷新列表"
>
↻
</a-button>
<a-button
type="text"
size="small"
class="action-icon-btn add-btn"
@click="interfaceClick"
title="新增接口"
>
+
</a-button>
</div>
</div>
</div>
<div class="interface-tree">
<div v-if="loading" class="loading-state">
<a-spin />
<p>正在加载接口列表...</p>
</div>
<a-tree
v-else
:data="interfaceTreeData"
:selected-keys="selectedKeys"
:expanded-keys="expandedKeys"
@select="handleInterfaceSelect"
@expand="handleTreeExpand"
show-line
block-node
>
<template #title="{ key, title }">
<div
class="tree-node"
:class="{ selected: selectedKeys.includes(key) }"
@click="console.log('节点数据:', { key, title, nodeData: getNodeData(key) })"
>
<table class="node-table">
<tr>
<td class="node-content-cell">
<span v-if="selectedKeys.includes(key)" class="selected-dot"></span>
<span v-if="getNodeData(key)?.isLeaf" class="node-number">{{ getNodeIndex(key) }}</span>
<span v-if="getNodeData(key)?.isLeaf" class="post-method">POST</span>
<span class="node-title">{{ title }}</span>
<span v-if="getNodeData(key)?.isLeaf" class="map-count">
<span class="bracket">[</span>
<span class="count-num">{{ getNodeData(key)?.mapCount || 0 }}</span>
<span class="bracket">]</span>
</span>
<span v-if="getNodeData(key)?.isLeaf && (getNodeData(key)?.mapCount || 0) > 0" class="status-dot"></span>
</td>
<td class="node-actions-cell" v-if="getNodeData(key)?.isLeaf">
<a-dropdown trigger="click" @select="handleActionSelect">
<a-button
type="text"
size="small"
class="action-btn vertical-dots"
title="操作"
>
<div class="vertical-dots-icon">
<span></span>
<span></span>
<span></span>
</div>
</a-button>
<template #content>
<a-doption value="delete" :data="getNodeData(key)" @click="deleteInterfaceClick(getNodeData(key))">
<icon-delete />
删除
</a-doption>
</template>
</a-dropdown>
</td>
</tr>
<tr v-if="getNodeData(key)?.isLeaf && getNodeData(key)?.code">
<td class="node-code-cell">
<span class="interface-code">{{ getNodeData(key)?.code }}</span>
</td>
<td class="node-actions-cell"></td>
</tr>
</table>
</div>
</template>
</a-tree>
</div>
</div>
<!-- 右侧内容区域 -->
<div class="right-content-area">
<div class="content-tabs">
<a-tabs v-model="activeContentTab" class="content-tabs-nav" @change="handleTabChange">
<a-tab-pane key="mapping" title="映射列表">
<div class="mapping-split">
<div class="mapping-split-content">
<!-- 左侧:映射的服务商列表 -->
<div class="left-content">
<div class="pane-header">
<div class="search-box">
<a-input
v-model="searchKeyword"
placeholder="请输入服务商名称"
allow-clear
size="small"
@change="() => MapServiceProviderRef?.setSearchKeyword?.(searchKeyword)"
@clear="() => MapServiceProviderRef?.setSearchKeyword?.('')"
>
<template #prefix>
<icon-search />
</template>
</a-input>
<div class="search-actions">
<a-button
type="text"
size="small"
class="action-icon-btn refresh-btn"
@click="MapServiceProviderRef?.tableRef?.reload?.()"
title="刷新列表"
>
↻
</a-button>
<a-button
type="text"
size="small"
class="action-icon-btn add-btn"
@click="() => {
if (selectedInterface) {
newmapClick(selectedInterface);
} else {
$baseMessage('请先选择接口', 'warning');
}
}"
title="新增映射"
>
+
</a-button>
</div>
</div>
</div>
<div class="pane-body">
<MapServiceProvider
v-if="selectedInterface"
:id="selectedInterface.id"
ref="MapServiceProviderRef"
@edit-map="handlerEditMap"
@row-click="rowClick"
/>
<div v-else class="empty-state">
<a-empty description="请选择左侧接口查看映射服务商列表" />
</div>
</div>
</div>
<!-- 右侧:嵌入"映射明细"(原编辑弹窗内容) -->
<div class="pane-body">
<EnteredMap
v-if="selectedInterface"
:key="`entered-map-${selectedInterface?.id}-${serviceProviderId}`"
ref="enteredMapRef"
class="h-full w-full"
:service-provider-id="serviceProviderId"
:interface-id="upStreamIdRef"
:service-provider-name="serviceProviderName"
@save-model-enter="saveModelEnter"
>
<template #toolbar_right>
<a-space :size="8">
<a-button size="small" @click="handleInterfaceDetailCancel">取消</a-button>
<a-button size="small" type="primary" @click="saveModelEnter">确定</a-button>
</a-space>
</template>
</EnteredMap>
</div>
</div>
</div>
</a-tab-pane>
<a-tab-pane key="debugger" title="API调试" :disabled="!MapServiceProviderROW">
<div class="api-debugger-wrapper">
<ApiDebugger
ref="apiDebuggerRef"
:selected-interface="selectedInterface"
:selected-service-provider="MapServiceProviderROW"
/>
</div>
</a-tab-pane>
<a-tab-pane key="detail" title="接口明细" :disabled="!selectedInterface">
<div class="mapping-split">
<div class="mapping-split-content">
<div class="pane-body">
<div class="pane-header">
<div class="pane-header-actions">
<a-space :size="8">
<a-button size="small" @click="handleInterfaceDetailCancel">取消</a-button>
<a-button size="small" type="primary" @click="interfaceConfigRef?.submit?.()">确定</a-button>
</a-space>
</div>
</div>
<div class="mapping-content">
<div v-if="selectedInterface" class="h-full w-full">
<InterfaceConfig
:key="`${selectedInterface?.id}-${detailRefreshKey}`"
ref="interfaceConfigRef"
:modal-mode="true"
:modal-id="selectedInterface?.id"
@saved="() => { /* 保留在当前tab,按需刷新左侧树 */ }"
/>
</div>
<div v-else class="empty-state">
<a-empty description="请选择左侧接口查看接口明细" />
</div>
</div>
</div>
</div>
</div>
</a-tab-pane>
</a-tabs>
</div>
</div>
</div>
<ModalPro
v-model:visible="editVisible"
:width="1500"
:title="editId ? '修改上游接口' : '新增上游接口'"
unmount-on-close
:footer="false"
>
<InterfaceConfig
ref="modalForm"
:modal-mode="true"
:modal-id="editId"
@close="editVisible = false"
@saved="editVisible = false; tableRef?.reload()"
/>
<div class="modal-footer">
<a-space :size="16">
<a-button @click="editVisible = false">取消</a-button>
<a-button type="primary" @click="$refs.modalForm?.submit?.()">保存</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="请选择服务商和接口"
: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="添加映射"
>
+
</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: 100vh;
background-color: #ffffff; /* 改为白色,便于观察是否有空白 */
/* 避免页面层级滚动条出现/消失导致整体水平抖动 */
overflow: hidden;
scrollbar-gutter: stable both-edges;
/* 移除margin-left,使用flex布局 */
}
.left-sidebar {
width: 250px; /* 与下游接口页面保持一致 */
background-color: #fff;
border-right: 1px solid #e8e8e8; /* 只保留右边框,与mapping-split分隔 */
display: flex;
flex-direction: column;
flex-shrink: 0; /* 禁止收缩 */
height: 100vh; /* 使用flex布局,不需要固定定位 */
}
.sidebar-header {
padding: 10px 12px !important; /* 与下游接口的 search-section 保持一致 */
border-bottom: 1px solid #e8e8e8;
min-height: 48px !important; /* 确保最小高度与下游接口对齐 */
display: flex !important;
align-items: center !important;
}
.search-box {
display: flex;
align-items: center;
gap: 8px;
}
.search-actions {
display: flex;
gap: 4px;
}
.button-row {
display: flex;
gap: 8px;
}
.add-btn {
flex: 1;
}
.refresh-btn {
flex: 1;
}
.interface-tree {
flex: 1;
padding: 0;
overflow-y: auto;
// 隐藏树节点的文档图标 - 使用更精确的选择器
:deep(.arco-tree-node) {
.arco-tree-node-icon {
display: none !important;
}
.arco-tree-node-indent {
display: none !important;
}
.arco-tree-node-switcher {
display: none !important;
}
}
// 隐藏所有可能的图标元素
:deep(.arco-tree-node-icon),
:deep(.arco-tree-node-indent),
:deep(.arco-tree-node-switcher),
:deep(.arco-icon),
:deep(.arco-tree-node-title .arco-icon) {
display: none !important;
}
}
.tree-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
margin-bottom: 8px;
background-color: #f8f9fa;
border-radius: 4px;
border: 1px solid #e8e8e8;
}
.tree-title {
display: flex;
align-items: center;
font-weight: 600;
color: #262626;
.arco-icon {
color: #8c8c8c;
}
}
.tree-actions {
display: flex;
gap: 4px;
}
.action-icon-btn {
width: 28px;
height: 28px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
color: #8c8c8c;
border: 1px solid transparent;
border-radius: 4px;
font-size: 16px;
font-weight: bold;
&:hover {
background-color: #f0f8ff;
border-color: #d6e4ff;
}
&.add-btn {
color: #2D8C8C !important;
background-color: #e6f7f7 !important;
border: 1px solid #2D8C8C !important;
border-radius: 4px !important;
&:hover {
color: #ffffff !important;
background-color: #2D8C8C !important;
border-color: #2D8C8C !important;
}
}
&.refresh-btn {
&:hover {
color: #2D8C8C;
}
}
}
.tree-node {
width: 100%;
box-sizing: border-box;
border: 1px solid transparent; /* 预占位,避免状态切换导致宽度变化 */
padding: 6px 8px; /* 固定内边距,避免hover/selected时变化 */
margin: 2px 0; /* 固定外边距 */
border-radius: 6px;
transition: background-color 0.2s ease, border-color 0.2s ease;
cursor: pointer;
&:hover {
background-color: #f0f8ff;
border-color: #d6e4ff; /* 仅改颜色,不改宽度 */
}
}
.tree-node.selected {
background-color: #e6f7f7 !important; /* 青色浅底 */
box-shadow: none !important;
border-color: transparent !important;
}
.node-table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
.node-content-cell {
text-align: left;
padding: 0;
width: auto;
}
.node-code-cell {
text-align: left;
padding: 0;
width: auto;
padding-left: 20px; /* 与POST标签对齐 */
}
.node-actions-cell {
text-align: right;
padding: 0;
width: 30px;
vertical-align: middle;
}
.node-content {
display: flex;
align-items: center;
flex: 1;
gap: 8px;
}
.node-title {
font-size: 14px;
color: #262626;
}
.map-count {
font-size: 12px;
font-weight: 600;
color: #2D8C8C; /* 数字青色 */
padding: 0;
min-width: auto;
text-align: left;
.bracket {
color: #8c8c8c; /* 方括号灰色 */
}
.count-num {
color: #2D8C8C; /* 数字青色 */
}
}
.status-dot {
width: 6px;
height: 6px;
background-color: #00b42a;
border-radius: 50%;
flex-shrink: 0;
}
.selected-dot {
width: 8px;
height: 8px;
background-color: #2D8C8C;
border-radius: 50%;
flex-shrink: 0;
margin-right: 6px;
display: inline-block;
}
.node-actions {
display: flex;
justify-content: center;
align-items: center;
opacity: 1;
}
.action-btn {
padding: 0;
min-width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
:deep(.arco-icon) {
font-size: 12px;
}
}
.node-number {
color: #8c8c8c;
font-size: 12px;
font-weight: normal;
margin-right: 8px;
display: inline-block;
min-width: 16px;
text-align: center;
}
.post-method {
color: #ff7d00;
font-weight: bold;
font-size: 10px;
margin-right: 6px;
display: inline-block;
background-color: transparent;
padding: 0;
border-radius: 0;
border: none;
}
.interface-code {
color: #8c8c8c;
font-size: 12px;
font-weight: normal;
display: inline-block;
}
.vertical-dots-icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
width: 6px;
height: 14px;
}
.vertical-dots-icon span {
width: 3px;
height: 3px;
background-color: #666;
border-radius: 50%;
display: block;
}
.content-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px !important; /* 与下游接口保持一致 */
border-bottom: 1px solid #e8e8e8;
background-color: #fafafa;
border-radius: 4px 4px 0 0;
min-height: 48px !important; /* 确保最小高度与下游接口对齐 */
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #262626;
}
}
/* 新增:右侧拆分布局 */
.right-content-area {
flex: 1;
display: flex;
flex-direction: column;
background-color: #fff;
height: 100vh;
overflow: hidden;
}
.content-tabs {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
}
.content-tabs-nav {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
:deep(.arco-tabs-nav) {
background-color: #f7f8fa;
border-bottom: 1px solid #e5e6eb;
margin: 0;
padding: 0 20px;
flex-shrink: 0;
min-height: 48px !important; /* 确保最小高度与下游接口对齐 */
display: flex !important;
align-items: center !important;
}
:deep(.arco-tabs-content) {
flex: 1;
padding: 0;
height: 100%;
overflow: hidden;
}
:deep(.arco-tabs-pane) {
height: 100%;
overflow: hidden;
}
}
.api-debugger-wrapper {
height: 100%;
display: flex;
flex-direction: column;
}
.mapping-split {
flex: 1;
display: flex;
gap: 0; /* 移除gap,让两个pane紧贴 */
padding: 0; /* 移除padding,让pane填满区域 */
margin: 0; /* 移除margin,确保没有空白区域 */
overflow: hidden;
}
.mapping-split-content {
flex: 1;
display: flex;
gap: 0; /* 确保没有gap */
padding: 0; /* 确保没有padding */
margin: 0; /* 确保没有margin */
overflow: hidden;
}
/* 右侧内容区域占据剩余空间 */
.mapping-split-content > .pane-body {
flex: 1;
display: flex;
flex-direction: column;
background: #fff;
border: none;
border-radius: 0;
overflow: hidden;
}
/* 左侧内容区域固定为与下游服务商列表一致的宽度(250px) */
.left-content {
flex: 0 0 250px;
max-width: 250px;
min-width: 250px;
flex-shrink: 0; /* 禁止收缩,避免左右抖动 */
gap: 0; /* 确保没有gap */
padding: 0; /* 确保没有padding */
margin: 0; /* 确保没有margin */
display: flex;
flex-direction: column;
background: #fff;
border: none;
border-radius: 0;
overflow: hidden;
}
.pane-header {
padding: 10px 12px !important; /* 与 steps-bar 保持一致 */
border-bottom: 1px solid #e8e8e8;
background: #fafafa;
font-weight: 600;
color: #262626;
display: flex;
align-items: center;
justify-content: space-between;
min-height: 48px !important; /* 确保最小高度与下游接口对齐 */
}
.pane-header .search-box { display: flex; align-items: center; gap: 8px; }
.pane-header .search-actions { display: flex; gap: 4px; }
.pane-header-actions { margin-left: auto; display: flex; }
.pane-header :deep(.arco-input-wrapper) { height: 28px !important; }
.pane-header-actions :deep(.arco-btn) { height: 28px !important; padding: 0 10px; }
.action-icon-btn { width: 28px; height: 28px !important; padding: 0; display: flex; align-items: center; justify-content: center; color: #8c8c8c; border: 1px solid transparent; border-radius: 4px; font-size: 16px; font-weight: bold; }
.action-icon-btn.add-btn { color: #2D8C8C !important; background-color: #e6f7f7 !important; border: 1px solid #2D8C8C !important; border-radius: 4px !important; }
.action-icon-btn.add-btn:hover { color: #ffffff !important; background-color: #2D8C8C !important; border-color: #2D8C8C !important; }
.pane-body {
flex: 1;
display: flex; /* 让 empty-state 能占满高度以便垂直居中 */
flex-direction: column;
padding: 6px 0; /* 左右改为 0,由子项控制 2px 外边距 */
overflow: auto; /* 固定内部滚动条 */
scrollbar-gutter: stable both-edges; /* 出现/消失不改变布局宽度 */
min-height: 0; /* 防止子元素撑开引发父容器高度变化 */
}
.pane-footer {
display: flex;
justify-content: flex-end;
padding: 8px 12px;
border-top: 1px solid #e8e8e8;
background: #fff;
}
/* 右下角悬浮按钮样式 */
.floating-actions { position: sticky; top: 8px; display: flex; justify-content: flex-end; pointer-events: none; z-index: 2; }
.floating-actions.fixed { position: absolute; top: 8px; right: 8px; }
.floating-actions :deep(.arco-space) {
pointer-events: auto; /* 只让按钮可点 */
background: #ffffffcc;
backdrop-filter: blur(2px);
padding: 6px 8px;
border-radius: 6px;
border: 1px solid #e8e8e8;
}
.mapping-content {
flex: 1;
padding: 8px;
overflow: hidden;
}
.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: #2D8C8C !important;
background-color: #e6f7f7 !important;
border: 1px solid #2D8C8C !important;
border-radius: 4px !important;
}
:deep(.action-icon-btn.add-btn:hover) {
color: #ffffff !important;
background-color: #2D8C8C !important;
border-color: #2D8C8C !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;
}
</style>