All checks were successful
Build and Deploy Front / deploy (push) Successful in 5m42s
保留核心 InterfaceDetail/ApiTest 的 vxe;去掉死代码映射页,预警/账户等次要表迁 a-table;构建默认跳过 brotli、mermaid 分包;面单按需 EP;修复标签刷新竞态与 COS 打印鉴权。 Co-authored-by: Cursor <cursoragent@cursor.com>
2118 lines
60 KiB
Vue
2118 lines
60 KiB
Vue
<script lang="ts" setup>
|
||
import { ref, reactive, computed, nextTick, onMounted, onActivated, unref, watch } from 'vue';
|
||
import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router';
|
||
import useModal from '@/components/ModalPro/useModal';
|
||
import ModalPro from '@/components/ModalPro/index.vue';
|
||
import {
|
||
getServiceProviderGetServiceProvider,
|
||
postServiceProviderAdd,
|
||
postServiceProviderUpdate,
|
||
postServiceProviderDeleteByIds,
|
||
} from '@/api/interface-serviceProvider/serviceProvider';
|
||
import { Message, Spin, Empty, Button, Input, Select, Option, Checkbox, Textarea, InputNumber, Space, Dropdown, Doption } from '@arco-design/web-vue';
|
||
import { $baseMessage } from '@/utils/message';
|
||
import { $baseConfirm } from '@/utils/confirm';
|
||
import {
|
||
postServiceProviderCopy,
|
||
postServiceProviderExport,
|
||
postServiceProviderImport,
|
||
} from '@/api/interface-serviceProvider/serviceProviderImportExport';
|
||
import ParameterConfig from '@/views/api/downstreamInterface/comps/ParameterConfig.vue';
|
||
import FormPro from '@/components/FormPro/index.vue';
|
||
import TableButtons from '@/components/TableButtons/index.vue';
|
||
import {
|
||
postServiceProviderPaginatedList,
|
||
} from '@/api/ServiceProviderInterface/serviceProvider';
|
||
import {
|
||
postInterfaceBatchDeleteServiceProviderInterfaceByIds,
|
||
} from '@/api/InterfaceServices/serviceProviderInterface';
|
||
import {
|
||
postInterfaceInterfacePaginatedList,
|
||
} from '@/api/InterfaceServices/interface';
|
||
import dayjs from 'dayjs';
|
||
import { getDictionaryGetList } from '@/api/ApplicationServices/dictionary';
|
||
import { getAppGetDropDownList } from '@/api/ApplicationServices/apps';
|
||
import ServiceProviderInterfaceModal from './components/ServiceProviderInterfaceModal.vue';
|
||
import AccountList from './comps/AccountList.vue';
|
||
import ApiDebugger from '@/views/api/upstreamInterface/components/ApiDebugger.vue';
|
||
import CustomLeftMenu from '@/components/CustomLeftMenu/index.vue';
|
||
import {
|
||
IconEdit,
|
||
IconSettings,
|
||
IconCopy,
|
||
IconUser,
|
||
IconDownload,
|
||
IconSearch,
|
||
IconPlus,
|
||
IconRefresh,
|
||
IconMore,
|
||
IconDelete,
|
||
} from '@arco-design/web-vue/es/icon';
|
||
import { useI18n } from 'vue-i18n';
|
||
|
||
defineOptions({
|
||
name: 'DownstreamInterface',
|
||
});
|
||
|
||
const { t } = useI18n();
|
||
|
||
/** 保存成功统一提示(含发布提醒,避免 success + warning 连弹) */
|
||
function tipSaveOk(action = '保存') {
|
||
Message.success(`${action}成功,如需生效请去发布`);
|
||
}
|
||
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
const useModalResult = useModal();
|
||
const register = useModalResult[0];
|
||
const modal = useModalResult[1] as any;
|
||
const applicationType = ref<number | undefined>(0);
|
||
|
||
const appList = ref<API.AppDropDownItem[]>([]);
|
||
|
||
// 新增的响应式数据
|
||
const searchKeyword = ref('');
|
||
const serviceProviderList = ref<any[]>([]);
|
||
const selectedProvider = ref<any>(null);
|
||
const loading = ref(false);
|
||
|
||
// 接口弹窗相关
|
||
const interfaceModalVisible = ref(false);
|
||
const interfaceModalRef = ref();
|
||
const interfaceModalTitle = ref('服务商接口配置');
|
||
|
||
// 接口列表相关
|
||
const interfaceList = ref<any[]>([]);
|
||
const selectedInterface = ref<any>(null);
|
||
const interfaceLoading = ref<boolean>(false);
|
||
|
||
// 接口详情表单和表格相关
|
||
const interfaceDetailFormRef = ref();
|
||
const interfaceDetailTableRef = ref();
|
||
const interfaceDetailTableRef1 = ref();
|
||
const interfaceDetailModalRef = ref();
|
||
const apiDebuggerRefreshKey = ref(0);
|
||
|
||
// 接口明细tab切换
|
||
const interfaceDetailTab = ref('detail');
|
||
|
||
const interfaceDetailTabItems = computed(() => [
|
||
{ key: 'detail', label: t('system.serviceProviders.detail.tab.detail') },
|
||
{ key: 'debug', label: t('system.serviceProviders.detail.tab.debug') },
|
||
]);
|
||
const interfaceDetailLoading = ref(false); // 接口详情加载状态
|
||
const detailRefreshKey = ref(0); // 用于控制组件重新渲染的key
|
||
|
||
// 初始化接口明细组件的函数
|
||
async function initInterfaceDetail() {
|
||
if (!selectedInterface.value || interfaceDetailTab.value !== 'detail') {
|
||
return;
|
||
}
|
||
|
||
// 等待组件完全挂载(v-if 条件渲染需要时间)
|
||
await nextTick();
|
||
await nextTick();
|
||
|
||
// 使用轮询方式等待组件实例创建,最多等待 2 秒
|
||
let retryCount = 0;
|
||
const maxRetries = 20;
|
||
const checkInterval = 100;
|
||
|
||
const checkAndInit = () => {
|
||
// 检查组件是否还存在且处于 detail tab
|
||
if (interfaceDetailTab.value !== 'detail' || !selectedInterface.value) {
|
||
return; // 如果已经切换了 tab,停止初始化
|
||
}
|
||
|
||
if (interfaceDetailModalRef.value && interfaceDetailModalRef.value.init) {
|
||
try {
|
||
const interfaceId = selectedInterface.value.interfaceId || selectedInterface.value.id;
|
||
interfaceDetailModalRef.value.init({
|
||
id: selectedInterface.value.id,
|
||
interfaceId: interfaceId,
|
||
serviceProviderId: selectedProvider.value?.id,
|
||
serviceProviderName: selectedProvider.value?.name,
|
||
mode: 'edit',
|
||
forceRefresh: false
|
||
});
|
||
} catch {
|
||
// 初始化失败,静默处理
|
||
}
|
||
} else {
|
||
retryCount++;
|
||
if (retryCount < maxRetries) {
|
||
setTimeout(checkAndInit, checkInterval);
|
||
}
|
||
}
|
||
};
|
||
|
||
// 延迟一下再开始检查,给组件更多时间挂载
|
||
setTimeout(checkAndInit, 200);
|
||
}
|
||
|
||
// 监听tab切换,切换回detail tab时检查是否需要初始化组件
|
||
watch(interfaceDetailTab, async (newTab) => {
|
||
if (newTab === 'detail' && selectedInterface.value) {
|
||
// 等待 DOM 更新完成
|
||
await nextTick();
|
||
await nextTick();
|
||
|
||
// 由于使用了 v-show,组件不会被销毁,数据会保留
|
||
// 但如果组件还没有初始化(比如第一次切换到 detail tab),需要初始化
|
||
// 检查组件是否存在,如果不存在则初始化
|
||
setTimeout(() => {
|
||
if (!interfaceDetailModalRef.value && selectedInterface.value) {
|
||
// 组件不存在,需要初始化
|
||
initInterfaceDetail();
|
||
}
|
||
// 如果组件已存在,说明可能是切换tab回来的,v-show 会保留数据,不需要重新初始化
|
||
}, 100);
|
||
}
|
||
});
|
||
|
||
const interfaceDetailForm = reactive<any>({
|
||
optionType: 1,
|
||
id: 0,
|
||
name: '',
|
||
abbreviation: '',
|
||
requestProtocol: '',
|
||
requestMethod: 'POST',
|
||
requestAddress: '',
|
||
contentType: '',
|
||
responseContentType: '',
|
||
isDisableAuth: false,
|
||
qps: 1,
|
||
mappedParamType: '',
|
||
description: '',
|
||
serviceProviderId: 0,
|
||
inParameterList: [],
|
||
outParameterList: [],
|
||
});
|
||
|
||
// 参数类型列表
|
||
const ParameterTypeList = ref<any[]>([]);
|
||
|
||
// 导入:打开导入弹窗(复用已有新建接口弹窗或实现你有的导入逻辑)
|
||
function openImportModal() {
|
||
// 直接选择文件并调用导入接口
|
||
const input = document.createElement('input');
|
||
input.type = 'file';
|
||
input.accept = '.json,.xlsx,.xls,.csv,.txt';
|
||
input.onchange = async () => {
|
||
const file = input.files && input.files[0];
|
||
if (!file) return;
|
||
try {
|
||
loading.value = true;
|
||
const ok = await postServiceProviderImport({}, file);
|
||
if (ok) {
|
||
$baseMessage(t('system.serviceProviders.interfaceList.refresh'), 'success');
|
||
await loadServiceProviderList();
|
||
} else {
|
||
$baseMessage(t('system.serviceProviders.list.deleteFail'), 'error');
|
||
}
|
||
} catch (e) {
|
||
$baseMessage(t('system.serviceProviders.list.deleteFail'), 'error');
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
input.click();
|
||
}
|
||
|
||
// 表格配置
|
||
const interfaceDetailTableProps = reactive({
|
||
data: [] as any[],
|
||
columns: [
|
||
{ field: 'drag', title: '按住拖动', width: 80, slots: { default: 'drag' } },
|
||
{ field: 'name', title: '参数名', width: 120, slots: { default: 'name' } },
|
||
{ field: 'cnName', title: '参数名(中文)', width: 120, slots: { default: 'cnName' } },
|
||
{ field: 'type', title: '参数类型', width: 100, slots: { default: 'type' } },
|
||
{ field: 'description', title: '参数描述', width: 150, slots: { default: 'description' } },
|
||
{ field: 'isRequired', title: '必填', width: 80, slots: { default: 'isRequired' } },
|
||
{ field: 'action', title: '操作', width: 120, slots: { default: 'action' } },
|
||
],
|
||
});
|
||
|
||
const interfaceDetailTableProps1 = reactive({
|
||
data: [] as any[],
|
||
columns: [
|
||
{ field: 'drag', title: '按住拖动', width: 80, slots: { default: 'drag' } },
|
||
{ field: 'name', title: '参数名', width: 120, slots: { default: 'name' } },
|
||
{ field: 'cnName', title: '参数名(中文)', width: 120, slots: { default: 'cnName' } },
|
||
{ field: 'type', title: '参数类型', width: 100, slots: { default: 'type' } },
|
||
{ field: 'description', title: '参数描述', width: 150, slots: { default: 'description' } },
|
||
{ field: 'isRequired', title: '必填', width: 80, slots: { default: 'isRequired' } },
|
||
{ field: 'action', title: '操作', width: 120, slots: { default: 'action' } },
|
||
],
|
||
});
|
||
|
||
// 编辑状态
|
||
const editingRows = ref<Set<number>>(new Set());
|
||
const editingRows1 = ref<Set<number>>(new Set());
|
||
|
||
// 表单配置
|
||
const serviceProviderFormItems = [
|
||
{
|
||
comp: 'a-input',
|
||
field: 'name',
|
||
label: '',
|
||
span: 5,
|
||
compProps: {
|
||
placeholder: '请填写接口中文名称',
|
||
},
|
||
},
|
||
{
|
||
comp: 'a-input',
|
||
field: 'abbreviation',
|
||
label: '',
|
||
span: 5,
|
||
required: true,
|
||
compProps: {
|
||
placeholder: '请填写接口code',
|
||
},
|
||
},
|
||
{
|
||
comp: 'SelectPro',
|
||
field: 'requestProtocol',
|
||
label: '',
|
||
span: 4,
|
||
compProps: {
|
||
placeholder: '请选择请求协议',
|
||
api: getDictionaryGetList,
|
||
extra: { type: 'RequestProtocol' },
|
||
labelKeys: ['value'],
|
||
idKey: 'key',
|
||
isPage: false,
|
||
showHeader: false,
|
||
immediate: true, // 立即加载字典数据,确保能显示字典值
|
||
},
|
||
},
|
||
{
|
||
comp: 'SelectPro',
|
||
field: 'requestMethod',
|
||
label: '',
|
||
span: 4,
|
||
compProps: {
|
||
placeholder: '请选择请求方式',
|
||
api: getDictionaryGetList,
|
||
extra: { type: 'RequestMethod' },
|
||
labelKeys: ['value'],
|
||
idKey: 'key',
|
||
isPage: false,
|
||
showHeader: false,
|
||
immediate: true, // 立即加载字典数据,确保能显示字典值
|
||
},
|
||
},
|
||
{
|
||
comp: 'a-input',
|
||
field: 'requestAddress',
|
||
label: '',
|
||
span: 6,
|
||
compProps: {
|
||
placeholder: '例: http://xx/api/CreateOrder',
|
||
},
|
||
},
|
||
{
|
||
comp: 'SelectPro',
|
||
field: 'contentType',
|
||
label: '',
|
||
span: 4,
|
||
compProps: {
|
||
placeholder: '请选择Content-Type',
|
||
api: getDictionaryGetList,
|
||
extra: { type: 'ContentType' },
|
||
labelKeys: ['value'],
|
||
idKey: 'key',
|
||
isPage: false,
|
||
showHeader: false,
|
||
immediate: true, // 立即加载字典数据,确保能显示字典值
|
||
},
|
||
},
|
||
{
|
||
comp: 'SelectPro',
|
||
field: 'responseContentType',
|
||
label: '',
|
||
span: 4,
|
||
compProps: {
|
||
placeholder: '请选择Response-Content-Type',
|
||
api: getDictionaryGetList,
|
||
extra: { type: 'ResponseContentType' },
|
||
labelKeys: ['value'],
|
||
idKey: 'key',
|
||
isPage: false,
|
||
showHeader: false,
|
||
immediate: true, // 立即加载字典数据,确保能显示字典值
|
||
},
|
||
},
|
||
{
|
||
comp: 'SelectPro',
|
||
field: 'isDisableAuth',
|
||
label: '',
|
||
span: 4,
|
||
compProps: {
|
||
options: [
|
||
{ value: true, label: '是' },
|
||
{ value: false, label: '否' },
|
||
],
|
||
labelKeys: ['label'],
|
||
idKey: 'value',
|
||
isPage: false,
|
||
showHeader: false,
|
||
},
|
||
},
|
||
{
|
||
comp: 'a-input-number',
|
||
field: 'qps',
|
||
label: '',
|
||
span: 4,
|
||
compProps: {
|
||
min: 0,
|
||
precision: 0,
|
||
controlsPosition: 'right',
|
||
valueOnClear: 0,
|
||
controls: false,
|
||
placeholder: 'QPS',
|
||
},
|
||
},
|
||
{
|
||
comp: 'SelectPro',
|
||
field: 'mappedParamType',
|
||
label: '',
|
||
span: 4,
|
||
compProps: {
|
||
api: getDictionaryGetList,
|
||
extra: { type: 'ParameterType' },
|
||
placeholder: '请选择根节点',
|
||
labelKeys: ['value'],
|
||
idKey: 'key',
|
||
isPage: false,
|
||
showHeader: false,
|
||
immediate: true, // 立即加载字典数据,确保能显示字典值
|
||
},
|
||
},
|
||
{
|
||
comp: 'a-input',
|
||
field: 'description',
|
||
label: '',
|
||
span: 6,
|
||
compProps: {
|
||
placeholder: '请输入接口备注',
|
||
allowClear: true,
|
||
},
|
||
},
|
||
];
|
||
|
||
const serviceProviderFormRules = {
|
||
name: [{ required: true, message: '输入不能为空', trigger: 'change' }],
|
||
abbreviation: [
|
||
{ required: true, message: '输入不能为空', trigger: 'change' },
|
||
{
|
||
max: 30,
|
||
message: '请控制长度在30个字符之内',
|
||
trigger: 'change',
|
||
},
|
||
],
|
||
requestAddress: [
|
||
{ required: true, message: '输入不能为空', trigger: 'change' },
|
||
],
|
||
requestProtocol: [
|
||
{ required: true, message: '请选择请求协议', trigger: 'change' },
|
||
],
|
||
requestMethod: [
|
||
{ required: true, message: '请选择请求方式', trigger: 'change' },
|
||
],
|
||
contentType: [
|
||
{ required: true, message: '请选择Content-Type', trigger: 'change' },
|
||
],
|
||
description: [
|
||
{
|
||
max: 200,
|
||
message: '请控制长度在200个字符之内',
|
||
trigger: 'change',
|
||
},
|
||
],
|
||
};
|
||
const tableProps = computed(() => {
|
||
return {
|
||
isToolsExport: false,
|
||
tableConfig: {
|
||
immediate: true,
|
||
columns: [
|
||
{ field: 'checkbox', type: 'checkbox', width: 60 },
|
||
{ field: 'seq', type: 'seq', width: 60, title: '序号' },
|
||
{
|
||
field: 'name',
|
||
title: '服务商名称',
|
||
},
|
||
{
|
||
field: 'type',
|
||
title: '服务商类型',
|
||
},
|
||
{
|
||
field: 'code',
|
||
title: '服务商代码',
|
||
},
|
||
|
||
{
|
||
field: 'interfaceCount',
|
||
title: '接口数量',
|
||
slots: { default: 'interfaceCount' },
|
||
},
|
||
{
|
||
field: 'description',
|
||
title: '描述',
|
||
},
|
||
{
|
||
field: 'accountCount',
|
||
title: '账户数量',
|
||
slots: { default: 'accountCount' },
|
||
},
|
||
{
|
||
field: 'updateTime',
|
||
title: '更新时间',
|
||
slots: {
|
||
default: 'updateTime',
|
||
},
|
||
},
|
||
{
|
||
field: 'action',
|
||
title: '操作',
|
||
fixed: 'right',
|
||
width: 180,
|
||
slots: {
|
||
default: 'action',
|
||
},
|
||
},
|
||
],
|
||
},
|
||
searchInfo: {
|
||
appId: applicationType.value,
|
||
},
|
||
api: postServiceProviderPaginatedList,
|
||
formConfig: {
|
||
layout: 'inline',
|
||
itemList: [
|
||
{
|
||
label: '',
|
||
field: 'nameOrCode',
|
||
comp: 'AInput',
|
||
componentProps: {
|
||
placeholder: '请输入服务商名称或者服务商代码',
|
||
allowClear: true,
|
||
size: 'small',
|
||
},
|
||
compProps: {
|
||
placeholder: '请输入服务商名称或者服务商代码',
|
||
allowClear: true,
|
||
size: 'small',
|
||
},
|
||
span: 6,
|
||
},
|
||
],
|
||
buttonConfig: {
|
||
showBtn: true,
|
||
showReset: false,
|
||
showOk: true,
|
||
okText: '查询',
|
||
span: 2,
|
||
},
|
||
},
|
||
};
|
||
});
|
||
const dialogFormRef = ref();
|
||
const dialogConfig = reactive({
|
||
itemList: [
|
||
{
|
||
label: '服务商名称',
|
||
field: 'name',
|
||
comp: 'AInput',
|
||
compProps: {
|
||
placeholder: '* 请填写服务商名称',
|
||
},
|
||
rules: [
|
||
{
|
||
required: true,
|
||
message: '输入不能为空',
|
||
trigger: 'change',
|
||
},
|
||
{
|
||
max: 200,
|
||
message: '请控制长度在200个字符之内',
|
||
trigger: 'change',
|
||
},
|
||
],
|
||
span: 6,
|
||
},
|
||
{
|
||
label: '服务商类型',
|
||
field: 'types',
|
||
comp: 'SelectPro',
|
||
compProps: {
|
||
api: getDictionaryGetList,
|
||
placeholder: '* 请选择服务商类型',
|
||
extra: { type: 'ServiceProviderType' },
|
||
labelKeys: ['value'],
|
||
idKey: 'key',
|
||
isPage: false,
|
||
multiple: true,
|
||
showHeader: false,
|
||
immediate: true, // 立即加载字典数据,确保能显示字典值
|
||
},
|
||
rules: [
|
||
{
|
||
required: true,
|
||
message: '请选择服务商类型',
|
||
trigger: 'change',
|
||
},
|
||
],
|
||
span: 6,
|
||
},
|
||
{
|
||
label: '服务商代码',
|
||
field: 'code',
|
||
comp: 'AInput',
|
||
compProps: {
|
||
placeholder: '* 请填写服务商代码',
|
||
// disabled: !flag.value,
|
||
},
|
||
rules: [
|
||
{
|
||
required: true,
|
||
message: '输入不能为空',
|
||
trigger: 'change',
|
||
},
|
||
{
|
||
max: 30,
|
||
message: '请控制长度在30个字符之内',
|
||
trigger: 'change',
|
||
},
|
||
{
|
||
pattern: /^[a-z,A-Z,0-9]+$/,
|
||
message: '只能输入字母和数字组合',
|
||
trigger: 'change',
|
||
},
|
||
],
|
||
span: 6,
|
||
},
|
||
{
|
||
label: '时区',
|
||
field: 'traceTimeZone',
|
||
comp: 'AInput',
|
||
compProps: {
|
||
maxlength: 50,
|
||
placeholder: '请输入时区',
|
||
},
|
||
span: 6,
|
||
},
|
||
{
|
||
label: '流程Code',
|
||
field: 'flowCode',
|
||
comp: 'AInput',
|
||
compProps: {
|
||
placeholder: '请输入流程Code',
|
||
},
|
||
span: 6,
|
||
},
|
||
{
|
||
label: '最大超时',
|
||
field: 'timeout',
|
||
comp: 'AInput',
|
||
compProps: {
|
||
placeholder: '请输入最大超时(秒)',
|
||
maxlength: 50,
|
||
},
|
||
rules: [
|
||
{
|
||
pattern: /^[1-9]\d*$/,
|
||
message: '只能输入正整数',
|
||
trigger: 'change',
|
||
},
|
||
],
|
||
span: 6,
|
||
},
|
||
{
|
||
label: '描述',
|
||
field: 'description',
|
||
comp: 'AInput',
|
||
compProps: {
|
||
placeholder: '请输入接口备注',
|
||
type: 'textarea',
|
||
rows: 4,
|
||
allowClear: true,
|
||
},
|
||
rules: [
|
||
{
|
||
max: 200,
|
||
message: '请控制长度在200个字符之内',
|
||
trigger: 'change',
|
||
},
|
||
],
|
||
span: 6,
|
||
},
|
||
],
|
||
formLayout: 'vertical',
|
||
buttonConfig: {
|
||
showBtn: false,
|
||
},
|
||
});
|
||
|
||
|
||
const edId = ref(0);
|
||
const isCopy = ref(false);
|
||
// 用于存储待设置的表单数据
|
||
const pendingEditFormData = ref<any>(null);
|
||
|
||
// 监听 Modal 打开状态,当 Modal 打开且 FormPro 已挂载时,设置表单值
|
||
watch(() => modal.getVisible, async (visible) => {
|
||
if (visible && pendingEditFormData.value) {
|
||
// 等待组件初始化
|
||
await nextTick();
|
||
await nextTick();
|
||
|
||
// 重试机制:最多尝试 10 次,每次间隔 100ms
|
||
let retries = 0;
|
||
const maxRetries = 10;
|
||
const checkAndSet = async () => {
|
||
if (dialogFormRef.value && pendingEditFormData.value) {
|
||
try {
|
||
if (dialogFormRef.value.setFormValue) {
|
||
dialogFormRef.value.setFormValue(pendingEditFormData.value);
|
||
} else if (dialogFormRef.value.setValues) {
|
||
dialogFormRef.value.setValues(pendingEditFormData.value);
|
||
}
|
||
pendingEditFormData.value = null;
|
||
return true;
|
||
} catch {
|
||
// 静默忽略设置表单值失败
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
|
||
while (retries < maxRetries) {
|
||
if (await checkAndSet()) {
|
||
break;
|
||
}
|
||
await new Promise(resolve => setTimeout(resolve, 100));
|
||
retries++;
|
||
}
|
||
}
|
||
});
|
||
|
||
function addClick() {
|
||
modal.setModalProps({ title: '新增' });
|
||
isCopy.value = false;
|
||
edId.value = 0;
|
||
modal.openModal();
|
||
}
|
||
|
||
async function saveFn() {
|
||
const d = await dialogFormRef.value.validate();
|
||
if (!d) {
|
||
const fromValue = dialogFormRef.value.getValues();
|
||
if (!isCopy.value) {
|
||
if (!edId.value) {
|
||
await postServiceProviderAdd({
|
||
...fromValue,
|
||
id: 0,
|
||
Id: undefined,
|
||
}).then(() => {
|
||
tipSaveOk('新增');
|
||
loadServiceProviderList();
|
||
modal.closeModal();
|
||
});
|
||
} else {
|
||
await postServiceProviderUpdate({
|
||
...fromValue,
|
||
Id: undefined,
|
||
id: edId.value,
|
||
}).then(() => {
|
||
tipSaveOk('修改');
|
||
loadServiceProviderList();
|
||
modal.closeModal();
|
||
});
|
||
}
|
||
} else {
|
||
await postServiceProviderCopy({
|
||
...fromValue,
|
||
Id: undefined,
|
||
id: edId.value,
|
||
}).then(() => {
|
||
tipSaveOk('创建');
|
||
loadServiceProviderList();
|
||
modal.closeModal();
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
async function deleteClick() {
|
||
// 由于新布局没有表格选择功能,这里暂时禁用删除功能
|
||
// 或者可以添加一个删除按钮到每个服务商项目中
|
||
Message.warning(t('system.serviceProviders.list.deleteTip'));
|
||
}
|
||
|
||
// 删除服务商
|
||
async function deleteProviderClick(provider: any) {
|
||
if (!provider || !provider.id) {
|
||
$baseMessage(t('system.serviceProviders.list.invalid'), 'warning');
|
||
return;
|
||
}
|
||
|
||
$baseConfirm({
|
||
title: '温馨提示',
|
||
content: `确定要删除服务商"${provider.name}"吗?删除后该服务商下的所有接口也将被删除。`,
|
||
onOk: async () => {
|
||
try {
|
||
await postServiceProviderDeleteByIds([provider.id]);
|
||
$baseMessage(t('system.serviceProviders.list.deleteSuccess'), 'success');
|
||
// 重新加载服务商列表
|
||
loadServiceProviderList();
|
||
// 如果删除的是当前选中的服务商,清空选中状态
|
||
if (selectedProvider.value?.id === provider.id) {
|
||
selectedProvider.value = null;
|
||
interfaceList.value = [];
|
||
selectedInterface.value = null;
|
||
}
|
||
} catch (error) {
|
||
$baseMessage(t('system.serviceProviders.list.deleteFail'), 'error');
|
||
}
|
||
},
|
||
});
|
||
}
|
||
|
||
async function editClick(row: any, isCopy2: boolean) {
|
||
if (!row || !row.id) {
|
||
$baseMessage('请先选择服务商', 'warning');
|
||
return;
|
||
}
|
||
isCopy.value = isCopy2;
|
||
edId.value = row.id;
|
||
|
||
// 创建副本模式
|
||
if (isCopy2) {
|
||
modal.setModalProps({ title: '创建服务商副本' });
|
||
modal.openModal();
|
||
await nextTick();
|
||
await nextTick();
|
||
setTimeout(() => {
|
||
if (dialogFormRef.value) {
|
||
dialogFormRef.value.setValues({ name: row.name });
|
||
}
|
||
}, 300);
|
||
return;
|
||
}
|
||
|
||
// 编辑模式:先加载数据,再打开 Modal
|
||
try {
|
||
const response = await getServiceProviderGetServiceProvider({
|
||
serviceProviderId: row.id,
|
||
});
|
||
|
||
// request.ts 的响应拦截器已经处理了数据,直接使用返回的数据
|
||
// 如果返回的是 { data: {...} } 格式,则使用 data;否则直接使用 response
|
||
const apiData = (response as any)?.data || response;
|
||
// 仅设置表单存在的字段,避免无效字段导致 setValues 抛错(例如已删除的“时区”)
|
||
const formData = {
|
||
name: apiData?.name || '',
|
||
types: apiData?.types || [],
|
||
code: apiData?.code || '',
|
||
flowCode: apiData?.flowCode || '',
|
||
traceTimeZone: apiData?.traceTimeZone || '',
|
||
timeout: apiData?.timeout || '',
|
||
description: apiData?.description || '',
|
||
};
|
||
|
||
// 存储待设置的数据
|
||
pendingEditFormData.value = formData;
|
||
|
||
// 打开 Modal,watch 会自动处理数据设置
|
||
modal.setModalProps({ title: '修改服务商列表' });
|
||
modal.openModal();
|
||
|
||
// 备用方案:如果 watch 没有及时触发,这里也会设置
|
||
await nextTick();
|
||
await nextTick();
|
||
await new Promise(resolve => setTimeout(resolve, 500));
|
||
if (dialogFormRef.value && pendingEditFormData.value) {
|
||
if (dialogFormRef.value.setFormValue) {
|
||
dialogFormRef.value.setFormValue(pendingEditFormData.value);
|
||
} else if (dialogFormRef.value.setValues) {
|
||
dialogFormRef.value.setValues(pendingEditFormData.value);
|
||
}
|
||
pendingEditFormData.value = null;
|
||
}
|
||
} catch (error) {
|
||
pendingEditFormData.value = null;
|
||
$baseMessage('加载服务商数据失败', 'error');
|
||
modal.closeModal();
|
||
}
|
||
}
|
||
|
||
const parameterConfigFlagShow = ref(false);
|
||
const parameterConfigFlag = ref();
|
||
|
||
// 包装函数:先选中服务商,然后延迟执行编辑操作
|
||
function handleEditWithSelect(provider: any, isCopy: boolean) {
|
||
handleProviderSelect(provider);
|
||
setTimeout(() => {
|
||
editClick(provider, isCopy);
|
||
}, 150);
|
||
}
|
||
|
||
// 包装函数:先选中服务商,然后延迟执行授权配置操作
|
||
function handleParameterConfigWithSelect(provider: any) {
|
||
handleProviderSelect(provider);
|
||
setTimeout(() => {
|
||
parameterConfigClick(provider);
|
||
}, 150);
|
||
}
|
||
|
||
async function parameterConfigClick(row: any) {
|
||
if (!row || !row.id) {
|
||
$baseMessage('请先选择服务商', 'warning');
|
||
return;
|
||
}
|
||
// 注意:选中操作已经在模板的 @click 中处理了
|
||
parameterConfigFlagShow.value = true;
|
||
// 等待组件完全挂载,使用多个 nextTick 确保 ModalPro 也完全初始化
|
||
await nextTick();
|
||
await nextTick();
|
||
// 额外等待一小段时间,确保 ModalPro 的 useModal 完全初始化
|
||
setTimeout(() => {
|
||
if (parameterConfigFlag.value) {
|
||
parameterConfigFlag.value.init(row);
|
||
} else {
|
||
$baseMessage('组件初始化失败,请重试', 'error');
|
||
}
|
||
}, 100);
|
||
}
|
||
|
||
const accountList = ref();
|
||
|
||
function accountManageClick(row: any) {
|
||
accountList.value.init(row.id);
|
||
}
|
||
|
||
function mapAccountClick(row: { id: any; code: any }) {
|
||
accountList.value.init(row.id);
|
||
}
|
||
|
||
function exportFileJSON(data = {}, filename = 'dataJSON.json') {
|
||
if (typeof data === 'object') data = JSON.stringify(data, null, 4);
|
||
|
||
// 导出数据
|
||
const blob = new Blob([data as any], { type: 'text/json' });
|
||
const e = new MouseEvent('click');
|
||
const a = document.createElement('a');
|
||
|
||
a.download = filename;
|
||
a.href = window.URL.createObjectURL(blob);
|
||
a.dataset.downloadurl = ['text/json', a.download, a.href].join(':');
|
||
a.dispatchEvent(e);
|
||
}
|
||
|
||
// 导出数据
|
||
function onDerive(row: any) {
|
||
postServiceProviderExport({ serviceProviderId: row.id }).then(
|
||
(res: any) => {
|
||
exportFileJSON(res, `${row.name}.json`);
|
||
}
|
||
);
|
||
}
|
||
|
||
function getAppList() {
|
||
getAppGetDropDownList({ keyword: '' }).then((response: any) => {
|
||
const data = response?.data || response || [];
|
||
appList.value = Array.isArray(data) ? data : [];
|
||
}).catch(() => {
|
||
appList.value = [];
|
||
});
|
||
}
|
||
|
||
async function tabClick(key: number) {
|
||
applicationType.value = key;
|
||
await nextTick();
|
||
// 重新加载服务商列表
|
||
loadServiceProviderList();
|
||
}
|
||
|
||
// 其他页面跳转过来如果query中有type就打开服务商列表弹窗
|
||
onActivated(() => {
|
||
const { type, id } = route.query;
|
||
// type === 'closeReturnConfig')
|
||
if (type && type === 'goRouter') {
|
||
// 重新加载服务商列表
|
||
loadServiceProviderList();
|
||
}
|
||
if (type && type === 'closeReturnConfig') {
|
||
// 重新加载服务商列表
|
||
loadServiceProviderList();
|
||
}
|
||
});
|
||
// 离开页面就把type删掉,不弹框
|
||
onBeforeRouteLeave(() => {
|
||
// route.query.type = ''
|
||
delete route.query.type;
|
||
delete route.query.id;
|
||
});
|
||
|
||
// 左侧菜单:将服务商列表转为树数据(单分类 + 子节点)
|
||
const providerTreeData = computed(() => {
|
||
const list = serviceProviderList.value || [];
|
||
if (list.length === 0) return [];
|
||
return [
|
||
{
|
||
key: 'category-1',
|
||
title: '服务商',
|
||
children: list.map((p: any) => ({
|
||
key: `item-${p.id}`,
|
||
title: p.name,
|
||
isLeaf: true,
|
||
id: p.id,
|
||
name: p.name,
|
||
code: p.code || '-',
|
||
...p,
|
||
})),
|
||
},
|
||
];
|
||
});
|
||
|
||
const providerSelectedKeys = computed(() =>
|
||
selectedProvider.value ? [`item-${selectedProvider.value.id}`] : []
|
||
);
|
||
const providerExpandedKeys = ref(['category-1']);
|
||
const interfaceSearchKeyword = ref('');
|
||
|
||
/** 中间接口列表:转成 CustomLeftMenu 树数据(展示结构与左侧/上游映射列表一致) */
|
||
const interfaceTreeData = computed(() => {
|
||
const list = interfaceList.value || [];
|
||
if (!selectedProvider.value || list.length === 0) return [];
|
||
const kw = interfaceSearchKeyword.value.trim().toLowerCase();
|
||
return list
|
||
.filter((item: any) => {
|
||
if (!kw) return true;
|
||
const name = String(item.name || '').toLowerCase();
|
||
const code = String(item.abbreviation || item.code || item.requestAddress || '').toLowerCase();
|
||
return name.includes(kw) || code.includes(kw);
|
||
})
|
||
.map((item: any, index: number) => ({
|
||
...item,
|
||
key: `iface-${item.id}`,
|
||
title: item.name,
|
||
isLeaf: true,
|
||
id: item.id,
|
||
name: item.name,
|
||
code: item.abbreviation || item.code || item.requestAddress || '-',
|
||
method: formatRequestMethod(item.requestMethod),
|
||
index,
|
||
}));
|
||
});
|
||
|
||
function handleInterfaceSearch() {
|
||
// 本地过滤,interfaceTreeData 已依赖 interfaceSearchKeyword
|
||
}
|
||
|
||
const interfaceSelectedKeys = computed(() =>
|
||
selectedInterface.value?.id != null ? [`iface-${selectedInterface.value.id}`] : []
|
||
);
|
||
|
||
function handleInterfaceSelectFromTree(keys: string[]) {
|
||
const key = keys?.[0];
|
||
if (!key?.startsWith('iface-')) return;
|
||
const id = Number(key.slice(6));
|
||
const item = interfaceList.value.find((x: any) => x.id === id);
|
||
if (item) selectInterface(item);
|
||
}
|
||
|
||
function handleInterfaceNodeClick(key: string) {
|
||
handleInterfaceSelectFromTree([key]);
|
||
}
|
||
|
||
function handleInterfaceDeleteNode(nodeData: any) {
|
||
if (!nodeData) return;
|
||
const id = nodeData.id;
|
||
const item = interfaceList.value.find((x: any) => x.id === id) || nodeData;
|
||
deleteInterfaceClick(item);
|
||
}
|
||
|
||
function handleProviderSelectFromTree(keys: string[], info: any) {
|
||
const node = info?.node;
|
||
if (!node || !node.isLeaf) return;
|
||
const key = node.key;
|
||
const id = key.startsWith('item-') ? Number(key.slice(5)) : null;
|
||
if (id == null) return;
|
||
const provider = serviceProviderList.value.find((p: any) => p.id === id);
|
||
if (provider) handleProviderSelect(provider);
|
||
}
|
||
|
||
// 新增的函数
|
||
async function loadServiceProviderList() {
|
||
try {
|
||
loading.value = true;
|
||
const params = {
|
||
nameOrCode: searchKeyword.value,
|
||
appId: applicationType.value,
|
||
pageIndex: 1,
|
||
pageSize: 1000
|
||
} as any;
|
||
const response = await postServiceProviderPaginatedList(params);
|
||
const data = (response as any)?.items || [];
|
||
serviceProviderList.value = data;
|
||
} catch (error) {
|
||
$baseMessage(t('system.serviceProviders.list.loadFail'), 'error');
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
// 加载服务商接口列表
|
||
async function loadInterfaceList() {
|
||
if (!selectedProvider.value) return;
|
||
|
||
interfaceLoading.value = true;
|
||
try {
|
||
const response = await postInterfaceInterfacePaginatedList({
|
||
pageIndex: 1,
|
||
pageSize: 1000,
|
||
serviceProviderId: selectedProvider.value.id,
|
||
isUpstream: false,
|
||
});
|
||
const data = (response as any)?.items || (response as any)?.data?.items || (response as any)?.list || [];
|
||
interfaceList.value = data;
|
||
|
||
} catch (error) {
|
||
$baseMessage(t('system.serviceProviders.interfaceList.loadFail'), 'error');
|
||
} finally {
|
||
interfaceLoading.value = false;
|
||
}
|
||
}
|
||
|
||
function handleProviderSelect(provider: any) {
|
||
selectedProvider.value = provider;
|
||
selectedInterface.value = null; // 清空选中的接口
|
||
loadInterfaceList(); // 加载接口列表
|
||
|
||
// 使用 requestAnimationFrame 确保在下一帧渲染时选中效果已应用
|
||
nextTick(() => {
|
||
requestAnimationFrame(() => {
|
||
// 再次等待一帧,确保 CSS 过渡效果已应用
|
||
requestAnimationFrame(() => {
|
||
const selectedElement = document.querySelector(`.provider-item.selected`);
|
||
if (selectedElement) {
|
||
selectedElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||
}
|
||
});
|
||
});
|
||
});
|
||
}
|
||
|
||
// 选择接口
|
||
async function selectInterface(interfaceItem: any) {
|
||
// 检查是否选择了不同的接口
|
||
const isDifferentInterface = !selectedInterface.value || selectedInterface.value.id !== interfaceItem.id;
|
||
|
||
if (isDifferentInterface && interfaceDetailTab.value === 'detail') {
|
||
const canLeave = await confirmLeaveInterfaceDetail();
|
||
if (!canLeave) return;
|
||
}
|
||
|
||
selectedInterface.value = interfaceItem;
|
||
|
||
// 确保切换到详情tab
|
||
interfaceDetailTab.value = 'detail';
|
||
|
||
// 只有在选择不同接口时才重新初始化组件
|
||
if (isDifferentInterface) {
|
||
interfaceDetailLoading.value = true;
|
||
detailRefreshKey.value++; // 改变key触发组件重新渲染
|
||
|
||
try {
|
||
// 等待组件渲染完成
|
||
await nextTick();
|
||
await nextTick();
|
||
|
||
setTimeout(() => {
|
||
if (interfaceDetailModalRef.value && interfaceDetailTab.value === 'detail') {
|
||
const interfaceId = interfaceItem.interfaceId || interfaceItem.id;
|
||
|
||
interfaceDetailModalRef.value.init({
|
||
id: interfaceItem.id,
|
||
interfaceId: interfaceId,
|
||
serviceProviderId: selectedProvider.value?.id,
|
||
serviceProviderName: selectedProvider.value?.name,
|
||
mode: 'edit',
|
||
forceRefresh: false
|
||
});
|
||
}
|
||
}, 100);
|
||
} finally {
|
||
interfaceDetailLoading.value = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
async function confirmLeaveInterfaceDetail(): Promise<boolean> {
|
||
if (interfaceDetailModalRef.value?.confirmLeaveUnsaved) {
|
||
return interfaceDetailModalRef.value.confirmLeaveUnsaved();
|
||
}
|
||
if (!interfaceDetailModalRef.value?.hasUnsavedChanges?.()) return true;
|
||
return new Promise((resolve) => {
|
||
$baseConfirm({
|
||
title: '提示',
|
||
content: '当前接口明细有未保存的更改,离开后将丢失。保存成功后如需生效还需发布。是否继续离开?',
|
||
okText: '继续离开',
|
||
cancelText: '继续编辑',
|
||
onOk: () => resolve(true),
|
||
onCancel: () => resolve(false),
|
||
});
|
||
});
|
||
}
|
||
|
||
// 中间接口列表切换且当前在调试 tab:刷新调试面板并重新导入 cURL
|
||
watch(
|
||
() => selectedInterface.value?.id,
|
||
async (newId, oldId) => {
|
||
if (!newId || newId === oldId) return;
|
||
if (interfaceDetailTab.value === 'debug') {
|
||
apiDebuggerRefreshKey.value++;
|
||
await nextTick();
|
||
importCurlToDebugger();
|
||
}
|
||
}
|
||
);
|
||
|
||
// 接口列表操作函数
|
||
function addInterface() {
|
||
if (!selectedProvider.value) {
|
||
$baseMessage(t('system.serviceProviders.interface.needProvider'), 'warning');
|
||
return;
|
||
}
|
||
|
||
// 设置弹窗标题
|
||
interfaceModalTitle.value = '新增服务商接口';
|
||
|
||
// 打开新增接口弹窗
|
||
interfaceModalVisible.value = true;
|
||
nextTick(() => {
|
||
interfaceModalRef.value?.init({
|
||
serviceProviderId: selectedProvider.value.id,
|
||
serviceProviderName: selectedProvider.value.name,
|
||
mode: 'add'
|
||
});
|
||
});
|
||
}
|
||
|
||
function deleteInterface() {
|
||
// 在新的布局中,删除功能通过单个接口的删除按钮实现
|
||
$baseMessage('请通过接口列表中的删除按钮进行删除', 'warning');
|
||
}
|
||
|
||
function editInterface(row: any) {
|
||
if (!selectedProvider.value) {
|
||
$baseMessage(t('system.serviceProviders.interface.needProvider'), 'warning');
|
||
return;
|
||
}
|
||
|
||
// 设置弹窗标题
|
||
interfaceModalTitle.value = '修改服务商接口';
|
||
|
||
// 打开修改接口弹窗
|
||
interfaceModalVisible.value = true;
|
||
nextTick(() => {
|
||
interfaceModalRef.value?.init({
|
||
id: row.id,
|
||
serviceProviderId: selectedProvider.value.id,
|
||
serviceProviderName: selectedProvider.value.name,
|
||
mode: 'edit'
|
||
});
|
||
});
|
||
}
|
||
|
||
function deleteSingleInterface(row: any) {
|
||
$baseConfirm({
|
||
title: t('system.serviceProviders.interface.deleteConfirmTitle'),
|
||
content: t('system.serviceProviders.interface.deleteConfirmContent', { name: row.name }),
|
||
onOk: async () => {
|
||
try {
|
||
await postInterfaceBatchDeleteServiceProviderInterfaceByIds([row.id]);
|
||
$baseMessage('删除成功', 'success');
|
||
// 重新加载接口列表
|
||
loadInterfaceList();
|
||
} catch (error) {
|
||
$baseMessage('删除失败', 'error');
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 删除接口
|
||
async function deleteInterfaceClick(interfaceItem: any) {
|
||
if (!interfaceItem || !interfaceItem.id) {
|
||
$baseMessage(t('system.serviceProviders.interface.invalid'), 'warning');
|
||
return;
|
||
}
|
||
|
||
$baseConfirm({
|
||
title: t('system.serviceProviders.interface.deleteConfirmTitle'),
|
||
content: t('system.serviceProviders.interface.deleteConfirmContent', { name: interfaceItem.name }),
|
||
onOk: async () => {
|
||
try {
|
||
await postInterfaceBatchDeleteServiceProviderInterfaceByIds([interfaceItem.id]);
|
||
$baseMessage('删除成功', 'success');
|
||
// 重新加载接口列表
|
||
loadInterfaceList();
|
||
// 如果删除的是当前选中的接口,清空选中状态
|
||
if (selectedInterface.value?.id === interfaceItem.id) {
|
||
selectedInterface.value = null;
|
||
}
|
||
} catch (error) {
|
||
$baseMessage('删除失败', 'error');
|
||
}
|
||
},
|
||
});
|
||
}
|
||
|
||
// 编辑接口
|
||
function editInterfaceClick(interfaceItem: any) {
|
||
selectInterface(interfaceItem);
|
||
}
|
||
|
||
function handleSearch() {
|
||
loadServiceProviderList();
|
||
}
|
||
|
||
function handleAddProvider() {
|
||
addClick();
|
||
}
|
||
|
||
function handleRefresh() {
|
||
loadServiceProviderList();
|
||
}
|
||
|
||
// 接口弹窗关闭
|
||
function handleInterfaceModalClose() {
|
||
interfaceModalVisible.value = false;
|
||
}
|
||
|
||
// 接口保存成功(提示由子组件统一发出,这里只关弹窗并刷新)
|
||
function handleInterfaceSaved() {
|
||
interfaceModalVisible.value = false;
|
||
loadInterfaceList();
|
||
loadServiceProviderList();
|
||
if (selectedInterface.value && interfaceDetailTab.value === 'detail' && interfaceDetailModalRef.value) {
|
||
nextTick(() => {
|
||
const interfaceId = selectedInterface.value.interfaceId || selectedInterface.value.id;
|
||
interfaceDetailModalRef.value.init({
|
||
id: selectedInterface.value.id,
|
||
interfaceId: interfaceId,
|
||
serviceProviderId: selectedProvider.value?.id,
|
||
serviceProviderName: selectedProvider.value?.name,
|
||
mode: 'edit',
|
||
forceRefresh: true,
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
// ModalPro确定按钮点击
|
||
async function handleInterfaceSave() {
|
||
if (interfaceModalRef.value) {
|
||
try {
|
||
await interfaceModalRef.value.saveInterface();
|
||
return true; // 返回 true 表示成功,允许弹窗关闭
|
||
} catch (error) {
|
||
return false; // 返回 false 表示失败,阻止弹窗关闭
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 表格操作方法
|
||
function isRowEditing(row: any) {
|
||
return editingRows.value.has(row.id);
|
||
}
|
||
|
||
function isRowEditing1(row: any) {
|
||
return editingRows1.value.has(row.id);
|
||
}
|
||
|
||
function editRow(row: any) {
|
||
editingRows.value.add(row.id);
|
||
}
|
||
|
||
function editRow1(row: any) {
|
||
editingRows1.value.add(row.id);
|
||
}
|
||
|
||
function saveRow(row: any) {
|
||
editingRows.value.delete(row.id);
|
||
}
|
||
|
||
function saveRow1(row: any) {
|
||
editingRows1.value.delete(row.id);
|
||
}
|
||
|
||
function deleteRow(row: any) {
|
||
const index = interfaceDetailTableProps.data.findIndex((item: any) => item.id === row.id);
|
||
if (index > -1) {
|
||
interfaceDetailTableProps.data.splice(index, 1);
|
||
}
|
||
}
|
||
|
||
function deleteRow1(row: any) {
|
||
const index = interfaceDetailTableProps1.data.findIndex((item: any) => item.id === row.id);
|
||
if (index > -1) {
|
||
interfaceDetailTableProps1.data.splice(index, 1);
|
||
}
|
||
}
|
||
|
||
function addNewRequestParam() {
|
||
const newParam: any = {
|
||
id: Date.now(),
|
||
name: '',
|
||
cnName: '',
|
||
type: 'String',
|
||
description: '',
|
||
isRequired: false,
|
||
};
|
||
interfaceDetailTableProps.data.push(newParam);
|
||
editingRows.value.add(newParam.id);
|
||
}
|
||
|
||
function addNewResponseParam() {
|
||
const newParam: any = {
|
||
id: Date.now(),
|
||
name: '',
|
||
cnName: '',
|
||
type: 'String',
|
||
description: '',
|
||
isRequired: false,
|
||
};
|
||
interfaceDetailTableProps1.data.push(newParam);
|
||
editingRows1.value.add(newParam.id);
|
||
}
|
||
|
||
function getParameterTypeName(type: string) {
|
||
const typeMap: { [key: string]: string } = {
|
||
'String': '字符串',
|
||
'Integer': '整数',
|
||
'Boolean': '布尔值',
|
||
'Array': '数组',
|
||
'Object': '对象',
|
||
};
|
||
return typeMap[type] || type;
|
||
}
|
||
|
||
function drawerClick() {
|
||
// 解析请求参数
|
||
}
|
||
|
||
function drawerClick1() {
|
||
// 解析返回参数
|
||
}
|
||
|
||
// 加载参数类型列表
|
||
async function loadParameterTypes() {
|
||
try {
|
||
const response: any = await getDictionaryGetList({ type: 'ParameterType' });
|
||
const responseData = response?.data || response;
|
||
ParameterTypeList.value = Array.isArray(responseData) ? responseData : [];
|
||
} catch (error) {
|
||
}
|
||
}
|
||
|
||
// 接口详情操作函数
|
||
function handleInterfaceCancel() {
|
||
// 重置表单数据
|
||
Object.assign(interfaceDetailForm, {
|
||
optionType: 1,
|
||
id: 0,
|
||
name: '',
|
||
abbreviation: '',
|
||
requestProtocol: '',
|
||
requestMethod: 'POST',
|
||
requestAddress: '',
|
||
contentType: '',
|
||
responseContentType: '',
|
||
isDisableAuth: false,
|
||
qps: 1,
|
||
mappedParamType: '',
|
||
description: '',
|
||
serviceProviderId: 0,
|
||
inParameterList: [],
|
||
outParameterList: [],
|
||
});
|
||
|
||
// 清空表格数据
|
||
interfaceDetailTableProps.data = [];
|
||
interfaceDetailTableProps1.data = [];
|
||
|
||
// 清空编辑状态
|
||
editingRows.value.clear();
|
||
editingRows1.value.clear();
|
||
|
||
// 清空选中的接口
|
||
selectedInterface.value = null;
|
||
}
|
||
|
||
// 格式化请求方式
|
||
function formatRequestMethod(method: any) {
|
||
if (!method) return 'POST';
|
||
|
||
// 如果是数字,转换为对应的HTTP方法
|
||
if (typeof method === 'number') {
|
||
const methodMap: { [key: number]: string } = {
|
||
1: 'GET',
|
||
2: 'POST',
|
||
3: 'PUT',
|
||
4: 'DELETE',
|
||
5: 'PATCH',
|
||
6: 'HEAD',
|
||
7: 'OPTIONS'
|
||
};
|
||
return methodMap[method] || 'POST';
|
||
}
|
||
|
||
// 如果是字符串,直接返回大写形式
|
||
return String(method).toUpperCase();
|
||
}
|
||
|
||
// 获取请求方式的CSS类名
|
||
function getMethodClass(method: any) {
|
||
const formattedMethod = formatRequestMethod(method);
|
||
return `method-${formattedMethod.toLowerCase()}`;
|
||
}
|
||
|
||
async function handleInterfaceDetailSave() {
|
||
if (!interfaceDetailModalRef.value) return;
|
||
// 成功提示 / 列表刷新由 saveInterface → @saved 统一处理,避免连弹 2~3 次
|
||
try {
|
||
await interfaceDetailModalRef.value.saveInterface();
|
||
} catch {
|
||
/* 子组件已提示错误 */
|
||
}
|
||
}
|
||
|
||
function handleInterfaceDetailCancel() {
|
||
// 取消编辑,重新加载接口详情
|
||
if (selectedInterface.value && interfaceDetailModalRef.value) {
|
||
nextTick(() => {
|
||
interfaceDetailModalRef.value.init({
|
||
id: selectedInterface.value.id,
|
||
serviceProviderId: selectedProvider.value?.id,
|
||
serviceProviderName: selectedProvider.value?.name,
|
||
mode: 'edit'
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
// 点击调试 / 切换 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);
|
||
});
|
||
}
|
||
}
|
||
async function handleDebugClick() {
|
||
const canLeave = await confirmLeaveInterfaceDetail();
|
||
if (!canLeave) return;
|
||
interfaceDetailTab.value = 'debug';
|
||
}
|
||
async function switchInterfaceDetailTab(tab: string) {
|
||
if (tab === interfaceDetailTab.value) return;
|
||
if (interfaceDetailTab.value === 'detail' && tab !== 'detail') {
|
||
const canLeave = await confirmLeaveInterfaceDetail();
|
||
if (!canLeave) return;
|
||
}
|
||
interfaceDetailTab.value = tab;
|
||
}
|
||
// 切换至 API 调试 tab 时也解析 cURL
|
||
watch(interfaceDetailTab, (tab) => {
|
||
if (tab === 'debug') importCurlToDebugger();
|
||
});
|
||
|
||
onMounted(() => {
|
||
getAppList();
|
||
loadServiceProviderList();
|
||
loadParameterTypes();
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<div class="service-provider-layout">
|
||
<!-- 左侧服务商列表:自定义左菜单 -->
|
||
<CustomLeftMenu
|
||
class="left-sidebar nav-panel"
|
||
:tree-data="providerTreeData"
|
||
:selected-keys="providerSelectedKeys"
|
||
v-model:expanded-keys="providerExpandedKeys"
|
||
:loading="loading"
|
||
v-model:search-keyword="searchKeyword"
|
||
:search-placeholder="t('system.serviceProviders.search.placeholder')"
|
||
:refresh-title="t('system.serviceProviders.search.refresh')"
|
||
:add-title="t('system.serviceProviders.search.addProvider')"
|
||
loading-text="正在加载服务商列表..."
|
||
:actions-title="t('common.actions')"
|
||
:show-method="false"
|
||
:show-map-count="false"
|
||
@search="handleSearch"
|
||
@refresh="handleRefresh"
|
||
@add="handleAddProvider"
|
||
@select="handleProviderSelectFromTree"
|
||
@node-click="(key) => { const id = key.startsWith('item-') ? Number(key.slice(5)) : null; if (id != null) { const p = serviceProviderList.find((x: any) => x.id === id); if (p) handleProviderSelect(p); } }"
|
||
>
|
||
<template #node-menu="{ nodeData }">
|
||
<a-doption @click="handleEditWithSelect(nodeData, false)">
|
||
<IconEdit style="margin-right: 8px;" />
|
||
{{ t('system.serviceProviders.dropdown.edit') }}
|
||
</a-doption>
|
||
<a-doption @click="handleEditWithSelect(nodeData, true)">
|
||
<IconCopy style="margin-right: 8px;" />
|
||
{{ t('system.serviceProviders.dropdown.copy') }}
|
||
</a-doption>
|
||
<a-doption @click="handleParameterConfigWithSelect(nodeData)">
|
||
<IconSettings style="margin-right: 8px;" />
|
||
{{ t('system.serviceProviders.dropdown.authConfig') }}
|
||
</a-doption>
|
||
<a-doption @click="accountManageClick(nodeData)">
|
||
<IconUser style="margin-right: 8px;" />
|
||
{{ t('system.serviceProviders.dropdown.accountManage') }}
|
||
</a-doption>
|
||
<a-doption @click="onDerive(nodeData)">
|
||
<IconDownload style="margin-right: 8px;" />
|
||
{{ t('system.serviceProviders.dropdown.export') }}
|
||
</a-doption>
|
||
<a-doption @click="deleteProviderClick(nodeData)">
|
||
<IconDelete style="margin-right: 8px;" />
|
||
{{ t('system.serviceProviders.dropdown.delete') }}
|
||
</a-doption>
|
||
</template>
|
||
</CustomLeftMenu>
|
||
|
||
<!-- 中间接口列表:壳常驻,未选服务商时列表区为空提示 -->
|
||
<div class="middle-content">
|
||
<CustomLeftMenu
|
||
class="middle-sidebar"
|
||
width="100%"
|
||
:tree-data="interfaceTreeData"
|
||
:selected-keys="interfaceSelectedKeys"
|
||
:loading="interfaceLoading"
|
||
v-model:search-keyword="interfaceSearchKeyword"
|
||
:search-placeholder="'请输入接口名称'"
|
||
:show-method="false"
|
||
:show-map-count="false"
|
||
:show-pagination="false"
|
||
:show-node-index="true"
|
||
:show-node-code="true"
|
||
:refresh-title="t('system.serviceProviders.interfaceList.refresh')"
|
||
:add-title="t('system.serviceProviders.interfaceList.add')"
|
||
:loading-text="t('system.serviceProviders.interfaceList.loading')"
|
||
:empty-text="
|
||
selectedProvider
|
||
? '暂无数据'
|
||
: t('system.serviceProviders.interfaceList.selectProvider')
|
||
"
|
||
actions-title="操作"
|
||
delete-label="删除"
|
||
@search="handleInterfaceSearch"
|
||
@refresh="loadInterfaceList"
|
||
@add="addInterface"
|
||
@select="handleInterfaceSelectFromTree"
|
||
@node-click="handleInterfaceNodeClick"
|
||
@delete-node="handleInterfaceDeleteNode"
|
||
>
|
||
<template #node-menu="{ nodeData }">
|
||
<a-doption @click="handleInterfaceDeleteNode(nodeData)">
|
||
<IconDelete style="margin-right: 8px;" />
|
||
删除
|
||
</a-doption>
|
||
</template>
|
||
</CustomLeftMenu>
|
||
</div>
|
||
|
||
<!-- 右侧接口详情区域 -->
|
||
<div class="right-content content-panel">
|
||
<div class="interface-detail-container">
|
||
<!-- Tab 壳常驻,未选接口时内容区仍为空提示 -->
|
||
<div class="interface-detail-tabs">
|
||
<div class="tabs-header">
|
||
<SlidingTabs
|
||
:model-value="interfaceDetailTab"
|
||
:items="interfaceDetailTabItems"
|
||
@change="switchInterfaceDetailTab"
|
||
/>
|
||
<div class="tabs-extra" v-if="selectedInterface && interfaceDetailTab !== 'detail'"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="interface-detail-tab-content">
|
||
<template v-if="selectedInterface">
|
||
<!-- 接口明细内容 -->
|
||
<div v-show="interfaceDetailTab === 'detail'" class="detail-content">
|
||
<a-spin :loading="interfaceDetailLoading" tip="加载接口详情中..." class="detail-spin-wrapper">
|
||
<ServiceProviderInterfaceModal
|
||
:key="`interface-detail-${selectedInterface?.id}-${detailRefreshKey}`"
|
||
ref="interfaceDetailModalRef"
|
||
@saved="handleInterfaceSaved"
|
||
>
|
||
<template #headerActions>
|
||
<a-button
|
||
size="small"
|
||
title="将当前 cURL 带入 API 调试"
|
||
@click="handleDebugClick"
|
||
>
|
||
调试
|
||
</a-button>
|
||
<a-button size="small" type="primary" @click="handleInterfaceDetailSave">
|
||
{{ t('system.serviceProviders.detail.actions.save') }}
|
||
</a-button>
|
||
</template>
|
||
</ServiceProviderInterfaceModal>
|
||
</a-spin>
|
||
</div>
|
||
|
||
<!-- API调试组件 -->
|
||
<div v-show="interfaceDetailTab === 'debug'" class="debug-content">
|
||
<ApiDebugger
|
||
:key="`api-debugger-${selectedProvider?.id || ''}-${selectedInterface?.id || ''}-${apiDebuggerRefreshKey}`"
|
||
:selected-interface="selectedInterface"
|
||
:selected-service-provider="selectedProvider"
|
||
:enable-defaults="false"
|
||
:curl-to-import="curlToImport"
|
||
/>
|
||
</div>
|
||
</template>
|
||
<div v-else class="empty-state">
|
||
<a-empty :description="t('system.serviceProviders.interfaceList.empty')" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 模态框 -->
|
||
<ModalPro width="50%" @ok="saveFn" @register="register">
|
||
<div class="sp-modal-form">
|
||
<FormPro ref="dialogFormRef" v-bind="dialogConfig" :required-position="'none'" />
|
||
</div>
|
||
</ModalPro>
|
||
|
||
<!-- 服务商接口弹窗 -->
|
||
<ModalPro
|
||
class="interface-modal-tall"
|
||
:title="interfaceModalTitle"
|
||
:width="1500"
|
||
:modal-style="{ height: '900px' }"
|
||
:visible="interfaceModalVisible"
|
||
:footer="true"
|
||
@ok="handleInterfaceSave"
|
||
@cancel="handleInterfaceModalClose"
|
||
@close="handleInterfaceModalClose"
|
||
>
|
||
<ServiceProviderInterfaceModal
|
||
ref="interfaceModalRef"
|
||
@close="handleInterfaceModalClose"
|
||
@saved="handleInterfaceSaved"
|
||
/>
|
||
</ModalPro>
|
||
<ParameterConfig v-if="parameterConfigFlagShow" ref="parameterConfigFlag" />
|
||
<AccountList ref="accountList" />
|
||
</div>
|
||
</template>
|
||
|
||
<style lang="less" scoped>
|
||
.sp-modal-form {
|
||
:deep(.arco-form-item-label-col) {
|
||
.arco-form-item-label-required,
|
||
.arco-form-item-label-asterisk,
|
||
.arco-form-item-label-required-symbol {
|
||
display: none !important;
|
||
}
|
||
.arco-form-item-label {
|
||
padding-left: 0 !important;
|
||
}
|
||
}
|
||
|
||
/* 使用上游接口弹窗的样式风格 - 排除左侧搜索框 */
|
||
:deep(.arco-input-wrapper):not(.sidebar-search-input-wrapper),
|
||
:deep(.arco-select-view):not(.sidebar-search-input-wrapper),
|
||
:deep(.arco-input-number):not(.sidebar-search-input-wrapper),
|
||
:deep(.arco-textarea-wrapper):not(.sidebar-search-input-wrapper) {
|
||
height: 32px;
|
||
}
|
||
|
||
:deep(.arco-textarea-wrapper):not(.sidebar-search-input-wrapper) {
|
||
min-height: 32px;
|
||
}
|
||
|
||
:deep(.arco-input):not(.sidebar-search-input) {
|
||
border-color: var(--color-neutral-3);
|
||
|
||
&:hover,
|
||
&:focus {
|
||
border-color: var(--app-primary-color);
|
||
}
|
||
}
|
||
|
||
:deep(.arco-select):not(.sidebar-search-input-wrapper) {
|
||
width: 100%;
|
||
|
||
.arco-select-view {
|
||
border-color: var(--color-neutral-3);
|
||
|
||
&:hover {
|
||
border-color: var(--app-primary-color);
|
||
}
|
||
}
|
||
}
|
||
|
||
:deep(.arco-input-number):not(.sidebar-search-input-wrapper) {
|
||
border-color: var(--color-neutral-3);
|
||
|
||
&:hover,
|
||
&:focus-within {
|
||
border-color: var(--app-primary-color);
|
||
}
|
||
}
|
||
}
|
||
|
||
.service-provider-layout {
|
||
display: flex;
|
||
height: calc(100vh - 86px);
|
||
max-height: calc(100vh - 86px);
|
||
padding: 16px;
|
||
gap: 16px;
|
||
background-color: var(--app-page-bg);
|
||
overflow: hidden;
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
}
|
||
|
||
/* 左侧使用自定义左菜单组件,仅保留布局 */
|
||
.left-sidebar {
|
||
width: 280px;
|
||
flex-shrink: 0;
|
||
height: 100%;
|
||
}
|
||
|
||
.action-btn {
|
||
width: 28px;
|
||
height: 28px;
|
||
padding: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.vertical-dots-icon {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
|
||
span {
|
||
width: 3px;
|
||
height: 3px;
|
||
background: rgba(255, 255, 255, 0.7);
|
||
border-radius: 50%;
|
||
}
|
||
}
|
||
|
||
.middle-content {
|
||
flex: 1;
|
||
max-width: 280px;
|
||
min-width: 0;
|
||
height: 100%;
|
||
min-height: 0;
|
||
align-self: stretch;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
background: transparent;
|
||
border: none;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.middle-sidebar {
|
||
width: 100% !important;
|
||
flex: 1 1 auto !important;
|
||
flex-shrink: 1 !important;
|
||
min-height: 0 !important;
|
||
height: 100% !important;
|
||
}
|
||
|
||
.middle-empty {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--app-card-bg);
|
||
border: 1px solid rgba(15, 16, 17, 0.08);
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.right-content {
|
||
flex: 1;
|
||
background-color: var(--app-card-bg);
|
||
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;
|
||
gap: 12px;
|
||
padding: 10px 20px;
|
||
min-height: 48px;
|
||
|
||
.tabs-extra {
|
||
margin-left: auto;
|
||
display: flex;
|
||
align-items: center;
|
||
flex-shrink: 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
.interface-detail-tab-content {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.detail-content {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
min-height: 0;
|
||
|
||
.detail-spin-wrapper {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
|
||
:deep(.arco-spin) {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
}
|
||
|
||
:deep(.arco-spin-container) {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
}
|
||
}
|
||
}
|
||
|
||
.debug-content {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 12px;
|
||
box-sizing: border-box;
|
||
min-height: 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
.tab-actions {
|
||
padding: 16px 0;
|
||
border-bottom: 1px solid var(--color-border-2);
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.detail-content {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
min-height: 0;
|
||
}
|
||
|
||
.debug-content {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
padding: 12px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.empty-state {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 40px 20px;
|
||
width: 100%;
|
||
}
|
||
|
||
.loading-state {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 200px;
|
||
color: var(--color-text-3);
|
||
|
||
p {
|
||
margin-top: 12px;
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
|
||
:deep(.arco-modal-body) {
|
||
padding: 12px;
|
||
height: auto !important;
|
||
max-height: calc(100vh - 200px);
|
||
overflow-y: auto;
|
||
}
|
||
|
||
:deep(.arco-form) {
|
||
height: 100%;
|
||
overflow: visible;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* Form items in details */
|
||
.interface-detail-container :deep(.arco-form-item) {
|
||
margin-bottom: 16px; /* Standard spacing */
|
||
overflow: visible;
|
||
flex-shrink: 0; /* Prevent item compression */
|
||
}
|
||
|
||
/* Ensure form fields are visible */
|
||
:deep(.arco-form-item-label-col) {
|
||
overflow: visible;
|
||
}
|
||
|
||
:deep(.arco-form-item-control) {
|
||
overflow: visible;
|
||
min-height: auto;
|
||
}
|
||
|
||
|
||
/* 使用上游接口弹窗的样式风格 */
|
||
.detail-content :deep(.arco-input-wrapper),
|
||
.detail-content :deep(.arco-select-view),
|
||
.detail-content :deep(.arco-input-number),
|
||
.detail-content :deep(.arco-textarea-wrapper) {
|
||
height: 32px;
|
||
}
|
||
|
||
.detail-content :deep(.arco-textarea-wrapper) {
|
||
min-height: 32px;
|
||
}
|
||
|
||
.detail-content :deep(.arco-input) {
|
||
border-color: var(--color-neutral-3);
|
||
|
||
&:hover,
|
||
&:focus {
|
||
border-color: var(--app-primary-color);
|
||
}
|
||
}
|
||
|
||
.detail-content :deep(.arco-select) {
|
||
width: 100%;
|
||
|
||
.arco-select-view {
|
||
border-color: var(--color-neutral-3);
|
||
|
||
&:hover {
|
||
border-color: var(--app-primary-color);
|
||
}
|
||
}
|
||
}
|
||
|
||
.detail-content :deep(.arco-input-number) {
|
||
border-color: var(--color-neutral-3);
|
||
|
||
&:hover,
|
||
&:focus-within {
|
||
border-color: var(--app-primary-color);
|
||
}
|
||
}
|
||
|
||
|
||
/* 使用上游接口弹窗的样式风格 */
|
||
:deep(.ServiceProviderInterfaceModal .arco-input-wrapper),
|
||
:deep(.ServiceProviderInterfaceModal .arco-select-view),
|
||
:deep(.ServiceProviderInterfaceModal .arco-input-number),
|
||
:deep(.ServiceProviderInterfaceModal .arco-textarea-wrapper) {
|
||
height: 32px;
|
||
}
|
||
|
||
:deep(.ServiceProviderInterfaceModal .arco-textarea-wrapper) {
|
||
min-height: 32px;
|
||
}
|
||
|
||
:deep(.ServiceProviderInterfaceModal .arco-input) {
|
||
border-color: var(--color-neutral-3);
|
||
|
||
&:hover,
|
||
&:focus {
|
||
border-color: var(--app-primary-color);
|
||
}
|
||
}
|
||
|
||
:deep(.ServiceProviderInterfaceModal .arco-select) {
|
||
width: 100%;
|
||
|
||
.arco-select-view {
|
||
border-color: var(--color-neutral-3);
|
||
|
||
&:hover {
|
||
border-color: var(--app-primary-color);
|
||
}
|
||
}
|
||
}
|
||
|
||
:deep(.ServiceProviderInterfaceModal .arco-input-number) {
|
||
border-color: var(--color-neutral-3);
|
||
|
||
&:hover,
|
||
&:focus-within {
|
||
border-color: var(--app-primary-color);
|
||
}
|
||
}
|
||
</style>
|