feat(接口管理): 接口定义-导入&前后置数据源切换
This commit is contained in:
parent
0c8103011b
commit
4332f78df3
|
@ -142,7 +142,7 @@
|
||||||
"stylelint-less": "^1.0.8",
|
"stylelint-less": "^1.0.8",
|
||||||
"stylelint-order": "^5.0.0",
|
"stylelint-order": "^5.0.0",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^5.4.2",
|
||||||
"unplugin-auto-import": "^0.16.7",
|
"unplugin-auto-import": "^0.16.7",
|
||||||
"unplugin-vue-components": "^0.24.1",
|
"unplugin-vue-components": "^0.24.1",
|
||||||
"vite": "^3.2.7",
|
"vite": "^3.2.7",
|
||||||
|
|
|
@ -1,7 +1,21 @@
|
||||||
import MSR from '@/api/http/index';
|
import MSR from '@/api/http/index';
|
||||||
import { GetPluginOptionsUrl, GetPluginScriptUrl, GetProtocolListUrl } from '@/api/requrls/api-test/common';
|
import {
|
||||||
|
GetEnvironmentUrl,
|
||||||
|
GetEnvListUrl,
|
||||||
|
GetPluginOptionsUrl,
|
||||||
|
GetPluginScriptUrl,
|
||||||
|
GetProtocolListUrl,
|
||||||
|
LocalExecuteApiDebugUrl,
|
||||||
|
} from '@/api/requrls/api-test/common';
|
||||||
|
|
||||||
import { GetPluginOptionsParams, PluginConfig, PluginOption, ProtocolItem } from '@/models/apiTest/common';
|
import {
|
||||||
|
ExecuteRequestParams,
|
||||||
|
GetPluginOptionsParams,
|
||||||
|
PluginConfig,
|
||||||
|
PluginOption,
|
||||||
|
ProtocolItem,
|
||||||
|
} from '@/models/apiTest/common';
|
||||||
|
import { EnvConfig, EnvironmentItem } from '@/models/projectManagement/environmental';
|
||||||
|
|
||||||
// 获取协议列表
|
// 获取协议列表
|
||||||
export function getProtocolList(organizationId: string) {
|
export function getProtocolList(organizationId: string) {
|
||||||
|
@ -17,3 +31,18 @@ export function getPluginOptions(data: GetPluginOptionsParams) {
|
||||||
export function getPluginScript(pluginId: string) {
|
export function getPluginScript(pluginId: string) {
|
||||||
return MSR.get<PluginConfig>({ url: GetPluginScriptUrl, params: pluginId });
|
return MSR.get<PluginConfig>({ url: GetPluginScriptUrl, params: pluginId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 本地执行调试
|
||||||
|
export function localExecuteApiDebug(host: string, data: ExecuteRequestParams) {
|
||||||
|
return MSR.post<ExecuteRequestParams>({ url: `${host}${LocalExecuteApiDebugUrl}`, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取环境列表
|
||||||
|
export function getEnvList(projectId: string) {
|
||||||
|
return MSR.get<EnvironmentItem[]>({ url: GetEnvListUrl, params: projectId });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取环境详情
|
||||||
|
export function getEnvironment(envId: string) {
|
||||||
|
return MSR.get<EnvConfig>({ url: GetEnvironmentUrl, params: envId });
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import {
|
||||||
GetApiDebugDetailUrl,
|
GetApiDebugDetailUrl,
|
||||||
GetDebugModuleCountUrl,
|
GetDebugModuleCountUrl,
|
||||||
GetDebugModulesUrl,
|
GetDebugModulesUrl,
|
||||||
LocalExecuteApiDebugUrl,
|
|
||||||
MoveDebugModuleUrl,
|
MoveDebugModuleUrl,
|
||||||
TestMockUrl,
|
TestMockUrl,
|
||||||
TransferFileUrl,
|
TransferFileUrl,
|
||||||
|
@ -69,11 +68,6 @@ export function executeDebug(data: ExecuteRequestParams) {
|
||||||
return MSR.post<ExecuteRequestParams>({ url: ExecuteApiDebugUrl, data });
|
return MSR.post<ExecuteRequestParams>({ url: ExecuteApiDebugUrl, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 本地执行调试
|
|
||||||
export function localExecuteApiDebug(host: string, data: ExecuteRequestParams) {
|
|
||||||
return MSR.post<ExecuteRequestParams>({ url: `${host}${LocalExecuteApiDebugUrl}`, data });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增调试
|
// 新增调试
|
||||||
export function addDebug(data: SaveDebugParams) {
|
export function addDebug(data: SaveDebugParams) {
|
||||||
return MSR.post({ url: AddApiDebugUrl, data });
|
return MSR.post({ url: AddApiDebugUrl, data });
|
||||||
|
|
|
@ -1,28 +1,43 @@
|
||||||
import MSR from '@/api/http/index';
|
import MSR from '@/api/http/index';
|
||||||
import {
|
import {
|
||||||
|
AddDefinitionScheduleUrl,
|
||||||
AddDefinitionUrl,
|
AddDefinitionUrl,
|
||||||
AddModuleUrl,
|
AddModuleUrl,
|
||||||
BatchDeleteDefinitionUrl,
|
BatchDeleteDefinitionUrl,
|
||||||
|
BatchMoveDefinitionUrl,
|
||||||
|
BatchUpdateDefinitionUrl,
|
||||||
|
CheckDefinitionScheduleUrl,
|
||||||
|
DebugDefinitionUrl,
|
||||||
DefinitionMockPageUrl,
|
DefinitionMockPageUrl,
|
||||||
DefinitionPageUrl,
|
DefinitionPageUrl,
|
||||||
|
DeleteDefinitionScheduleUrl,
|
||||||
DeleteDefinitionUrl,
|
DeleteDefinitionUrl,
|
||||||
DeleteMockUrl,
|
DeleteMockUrl,
|
||||||
DeleteModuleUrl,
|
DeleteModuleUrl,
|
||||||
GetDefinitionDetailUrl,
|
GetDefinitionDetailUrl,
|
||||||
|
GetDefinitionScheduleUrl,
|
||||||
GetEnvModuleUrl,
|
GetEnvModuleUrl,
|
||||||
GetModuleCountUrl,
|
GetModuleCountUrl,
|
||||||
GetModuleOnlyTreeUrl,
|
GetModuleOnlyTreeUrl,
|
||||||
GetModuleTreeUrl,
|
GetModuleTreeUrl,
|
||||||
|
ImportDefinitionUrl,
|
||||||
MoveModuleUrl,
|
MoveModuleUrl,
|
||||||
|
SortDefinitionUrl,
|
||||||
|
SwitchDefinitionScheduleUrl,
|
||||||
TransferFileModuleOptionUrl,
|
TransferFileModuleOptionUrl,
|
||||||
TransferFileUrl,
|
TransferFileUrl,
|
||||||
|
UpdateDefinitionScheduleUrl,
|
||||||
UpdateDefinitionUrl,
|
UpdateDefinitionUrl,
|
||||||
UpdateMockStatusUrl,
|
UpdateMockStatusUrl,
|
||||||
UpdateModuleUrl,
|
UpdateModuleUrl,
|
||||||
UploadTempFileUrl,
|
UploadTempFileUrl,
|
||||||
} from '@/api/requrls/api-test/management';
|
} from '@/api/requrls/api-test/management';
|
||||||
|
|
||||||
|
import { ExecuteRequestParams } from '@/models/apiTest/common';
|
||||||
import {
|
import {
|
||||||
|
ApiDefinitionBatchDeleteParams,
|
||||||
|
ApiDefinitionBatchMoveParams,
|
||||||
|
ApiDefinitionBatchUpdateParams,
|
||||||
ApiDefinitionCreateParams,
|
ApiDefinitionCreateParams,
|
||||||
ApiDefinitionDetail,
|
ApiDefinitionDetail,
|
||||||
ApiDefinitionGetEnvModuleParams,
|
ApiDefinitionGetEnvModuleParams,
|
||||||
|
@ -32,10 +47,21 @@ import {
|
||||||
ApiDefinitionPageParams,
|
ApiDefinitionPageParams,
|
||||||
ApiDefinitionUpdateModuleParams,
|
ApiDefinitionUpdateModuleParams,
|
||||||
ApiDefinitionUpdateParams,
|
ApiDefinitionUpdateParams,
|
||||||
|
CheckScheduleParams,
|
||||||
|
CreateImportApiDefinitionScheduleParams,
|
||||||
EnvModule,
|
EnvModule,
|
||||||
|
ImportApiDefinitionParams,
|
||||||
mockParams,
|
mockParams,
|
||||||
|
UpdateScheduleParams,
|
||||||
} from '@/models/apiTest/management';
|
} from '@/models/apiTest/management';
|
||||||
import { AddModuleParams, CommonList, ModuleTreeNode, MoveModules, TransferFileParams } from '@/models/common';
|
import {
|
||||||
|
AddModuleParams,
|
||||||
|
CommonList,
|
||||||
|
DragSortParams,
|
||||||
|
ModuleTreeNode,
|
||||||
|
MoveModules,
|
||||||
|
TransferFileParams,
|
||||||
|
} from '@/models/common';
|
||||||
|
|
||||||
// 更新模块
|
// 更新模块
|
||||||
export function updateModule(data: ApiDefinitionUpdateModuleParams) {
|
export function updateModule(data: ApiDefinitionUpdateModuleParams) {
|
||||||
|
@ -94,7 +120,7 @@ export function updateDefinition(data: ApiDefinitionUpdateParams) {
|
||||||
|
|
||||||
// 获取接口定义详情
|
// 获取接口定义详情
|
||||||
export function getDefinitionDetail(id: string) {
|
export function getDefinitionDetail(id: string) {
|
||||||
return MSR.get({ url: GetDefinitionDetailUrl, params: id });
|
return MSR.get<ApiDefinitionDetail>({ url: GetDefinitionDetailUrl, params: id });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件转存
|
// 文件转存
|
||||||
|
@ -118,8 +144,63 @@ export function deleteDefinition(id: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量删除定义
|
// 批量删除定义
|
||||||
export function batchDeleteDefinition(id: string) {
|
export function batchDeleteDefinition(data: ApiDefinitionBatchDeleteParams) {
|
||||||
return MSR.get({ url: BatchDeleteDefinitionUrl, params: id });
|
return MSR.post({ url: BatchDeleteDefinitionUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导入定义
|
||||||
|
export function importDefinition(params: ImportApiDefinitionParams) {
|
||||||
|
return MSR.uploadFile({ url: ImportDefinitionUrl }, { fileList: [params.file], request: params.request }, 'file');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拖拽定义节点
|
||||||
|
export function sortDefinition(data: DragSortParams) {
|
||||||
|
return MSR.post({ url: SortDefinitionUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量更新定义
|
||||||
|
export function batchUpdateDefinition(data: ApiDefinitionBatchUpdateParams) {
|
||||||
|
return MSR.post({ url: BatchUpdateDefinitionUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量移动定义
|
||||||
|
export function batchMoveDefinition(data: ApiDefinitionBatchMoveParams) {
|
||||||
|
return MSR.post({ url: BatchMoveDefinitionUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新定时同步
|
||||||
|
export function updateDefinitionSchedule(data: UpdateScheduleParams) {
|
||||||
|
return MSR.post({ url: UpdateDefinitionScheduleUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定时同步-检查 url 是否存在
|
||||||
|
export function checkDefinitionSchedule(data: CheckScheduleParams) {
|
||||||
|
return MSR.post({ url: CheckDefinitionScheduleUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加定时同步
|
||||||
|
export function createDefinitionSchedule(data: CreateImportApiDefinitionScheduleParams) {
|
||||||
|
return MSR.post({ url: AddDefinitionScheduleUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定时同步-开启关闭
|
||||||
|
export function switchDefinitionSchedule(id: string) {
|
||||||
|
return MSR.get({ url: SwitchDefinitionScheduleUrl, params: id });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询定时同步详情
|
||||||
|
export function getDefinitionSchedule(id: string) {
|
||||||
|
return MSR.get({ url: GetDefinitionScheduleUrl, params: id });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除定时同步
|
||||||
|
export function deleteDefinitionSchedule(id: string) {
|
||||||
|
return MSR.get({ url: DeleteDefinitionScheduleUrl, params: id });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接口定义调试
|
||||||
|
export function debugDefinition(data: ExecuteRequestParams) {
|
||||||
|
return MSR.post({ url: DebugDefinitionUrl, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
export const GetProtocolListUrl = '/api/test/protocol'; // 获取协议列表
|
export const GetProtocolListUrl = '/api/test/protocol'; // 获取协议列表
|
||||||
export const GetPluginOptionsUrl = '/api/test/plugin/form/option'; // 获取插件表单选项
|
export const GetPluginOptionsUrl = '/api/test/plugin/form/option'; // 获取插件表单选项
|
||||||
export const GetPluginScriptUrl = '/api/test/plugin/script'; // 获取插件配置脚本
|
export const GetPluginScriptUrl = '/api/test/plugin/script'; // 获取插件配置脚本
|
||||||
|
export const LocalExecuteApiDebugUrl = '/api/debug'; // 本地执行调试
|
||||||
|
export const GetEnvListUrl = '/api/test/env-list'; // 获取接口测试环境列表
|
||||||
|
export const GetEnvironmentUrl = '/api/test/environment'; // 获取接口测试环境详情
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
export const ExecuteApiDebugUrl = '/api/debug/debug'; // 执行调试
|
export const ExecuteApiDebugUrl = '/api/debug/debug'; // 执行调试
|
||||||
export const LocalExecuteApiDebugUrl = '/api/debug'; // 本地执行调试
|
|
||||||
export const AddApiDebugUrl = '/api/debug/add'; // 新增调试
|
export const AddApiDebugUrl = '/api/debug/add'; // 新增调试
|
||||||
export const UpdateApiDebugUrl = '/api/debug/update'; // 更新调试
|
export const UpdateApiDebugUrl = '/api/debug/update'; // 更新调试
|
||||||
export const GetApiDebugDetailUrl = '/api/debug/get'; // 获取接口调试详情
|
export const GetApiDebugDetailUrl = '/api/debug/get'; // 获取接口调试详情
|
||||||
|
|
|
@ -13,8 +13,20 @@ export const GetDefinitionDetailUrl = '/api/definition/get-detail'; // 获取接
|
||||||
export const TransferFileUrl = '/api/definition/transfer'; // 文件转存
|
export const TransferFileUrl = '/api/definition/transfer'; // 文件转存
|
||||||
export const TransferFileModuleOptionUrl = '/api/definition/transfer/options'; // 文件转存目录
|
export const TransferFileModuleOptionUrl = '/api/definition/transfer/options'; // 文件转存目录
|
||||||
export const UploadTempFileUrl = '/api/definition/upload/temp/file'; // 临时文件上传
|
export const UploadTempFileUrl = '/api/definition/upload/temp/file'; // 临时文件上传
|
||||||
export const DeleteDefinitionUrl = '/api/definition/delete'; // 删除接口定义
|
|
||||||
export const BatchDeleteDefinitionUrl = '/api/definition/batch-del'; // 批量删除接口定义
|
|
||||||
export const DefinitionMockPageUrl = '/api/definition/mock/page'; // mock列表
|
export const DefinitionMockPageUrl = '/api/definition/mock/page'; // mock列表
|
||||||
export const UpdateMockStatusUrl = '/api/definition/mock/enable/'; // 更新mock状态
|
export const UpdateMockStatusUrl = '/api/definition/mock/enable/'; // 更新mock状态
|
||||||
export const DeleteMockUrl = '/api/definition/mock/delete'; // 刪除mock
|
export const DeleteMockUrl = '/api/definition/mock/delete'; // 刪除mock
|
||||||
|
export const DeleteDefinitionUrl = '/api/definition/delete-to-gc'; // 删除接口定义
|
||||||
|
export const ImportDefinitionUrl = '/api/definition/import'; // 导入接口定义
|
||||||
|
export const SortDefinitionUrl = '/api/definition/edit/pos'; // 接口定义拖拽
|
||||||
|
export const CopyDefinitionUrl = '/api/definition/copy'; // 复制接口定义
|
||||||
|
export const BatchUpdateDefinitionUrl = '/api/definition/batch-update'; // 批量更新接口定义
|
||||||
|
export const BatchMoveDefinitionUrl = '/api/definition/batch-move'; // 批量移动接口定义
|
||||||
|
export const BatchDeleteDefinitionUrl = '/api/definition/batch/delete-to-gc'; // 批量删除接口定义
|
||||||
|
export const UpdateDefinitionScheduleUrl = '/api/definition/schedule/update'; // 接口定义-定时同步-更新
|
||||||
|
export const CheckDefinitionScheduleUrl = '/api/definition/schedule/check'; // 接口定义-定时同步-检查 url 是否存在
|
||||||
|
export const AddDefinitionScheduleUrl = '/api/definition/schedule/add'; // 接口定义-定时同步-添加
|
||||||
|
export const SwitchDefinitionScheduleUrl = '/api/definition/schedule/switch'; // 接口定义-定时同步-开启关闭
|
||||||
|
export const GetDefinitionScheduleUrl = '/api/definition/schedule/get'; // 接口定义-定时同步-查询
|
||||||
|
export const DeleteDefinitionScheduleUrl = '/api/definition/schedule/delete'; // 接口定义-定时同步-删除
|
||||||
|
export const DebugDefinitionUrl = '/api/definition/debug'; // 接口定义-调试
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
import _Comment from './comment';
|
|
||||||
import type { App } from 'vue';
|
|
||||||
|
|
||||||
const MsComment = Object.assign(_Comment, {
|
|
||||||
install: (app: App) => {
|
|
||||||
app.component(_Comment.name, _Comment);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export type CommentInstance = InstanceType<typeof _Comment>;
|
|
||||||
|
|
||||||
export { default as CommentInput } from './input.vue';
|
|
||||||
export default MsComment;
|
|
|
@ -1,12 +0,0 @@
|
||||||
import EditComp from './edit-comp';
|
|
||||||
import type { App } from 'vue';
|
|
||||||
|
|
||||||
const MsEditComp = Object.assign(EditComp, {
|
|
||||||
install: (app: App) => {
|
|
||||||
app.component(EditComp.name, EditComp);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export type CommentInstance = InstanceType<typeof EditComp>;
|
|
||||||
|
|
||||||
export default MsEditComp;
|
|
|
@ -160,7 +160,7 @@
|
||||||
|
|
||||||
import { APIKEY } from '@/models/user';
|
import { APIKEY } from '@/models/user';
|
||||||
|
|
||||||
const { copy } = useClipboard();
|
const { copy, isSupported } = useClipboard();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
|
|
||||||
|
@ -226,8 +226,12 @@
|
||||||
];
|
];
|
||||||
|
|
||||||
async function handleCopy(val: string) {
|
async function handleCopy(val: string) {
|
||||||
|
if (isSupported) {
|
||||||
await copy(val);
|
await copy(val);
|
||||||
Message.success(t('ms.personal.copySuccess'));
|
Message.success(t('ms.personal.copySuccess'));
|
||||||
|
} else {
|
||||||
|
Message.warning(t('common.copyNotSupport'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function desensitization(item: APIKEYItem) {
|
function desensitization(item: APIKEYItem) {
|
||||||
|
|
|
@ -24,6 +24,9 @@
|
||||||
<slot name="title" v-bind="_props"></slot>
|
<slot name="title" v-bind="_props"></slot>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="$slots['drag-icon']" #drag-icon="_props">
|
||||||
|
<slot name="title" v-bind="_props"></slot>
|
||||||
|
</template>
|
||||||
<template v-if="$slots['extra']" #extra="_props">
|
<template v-if="$slots['extra']" #extra="_props">
|
||||||
<div
|
<div
|
||||||
v-if="_props.hideMoreAction !== true"
|
v-if="_props.hideMoreAction !== true"
|
||||||
|
@ -298,6 +301,7 @@
|
||||||
dropNode: MsTreeNodeData; // 放入的节点
|
dropNode: MsTreeNodeData; // 放入的节点
|
||||||
dropPosition: number; // 放入的位置,-1 为放入节点前,1 为放入节点后,0 为放入节点内
|
dropPosition: number; // 放入的位置,-1 为放入节点前,1 为放入节点后,0 为放入节点内
|
||||||
}) {
|
}) {
|
||||||
|
console.log('dropNode', dropNode);
|
||||||
loop(originalTreeData.value, dragNode.key, (item, index, arr) => {
|
loop(originalTreeData.value, dragNode.key, (item, index, arr) => {
|
||||||
arr.splice(index, 1);
|
arr.splice(index, 1);
|
||||||
});
|
});
|
||||||
|
|
|
@ -120,6 +120,9 @@
|
||||||
const { arrivedState } = useScroll(tabNav);
|
const { arrivedState } = useScroll(tabNav);
|
||||||
const isNotOverflow = computed(() => arrivedState.left && arrivedState.right); // 内容是否溢出,用于判断左右滑动按钮是否展示
|
const isNotOverflow = computed(() => arrivedState.left && arrivedState.right); // 内容是否溢出,用于判断左右滑动按钮是否展示
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滚动tab
|
||||||
|
*/
|
||||||
const scrollTabs = (direction: 'left' | 'right') => {
|
const scrollTabs = (direction: 'left' | 'right') => {
|
||||||
if (tabNav.value) {
|
if (tabNav.value) {
|
||||||
const tabNavWidth = tabNav.value?.clientWidth || 0;
|
const tabNavWidth = tabNav.value?.clientWidth || 0;
|
||||||
|
@ -139,6 +142,9 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滚动到当前激活的tab
|
||||||
|
*/
|
||||||
const scrollToActiveTab = () => {
|
const scrollToActiveTab = () => {
|
||||||
const activeTabDom = tabNav.value?.querySelector('.ms-editable-tab.active');
|
const activeTabDom = tabNav.value?.querySelector('.ms-editable-tab.active');
|
||||||
if (activeTabDom) {
|
if (activeTabDom) {
|
||||||
|
@ -169,6 +175,9 @@
|
||||||
return props.moreActionList ? [...dl, ...props.moreActionList] : dl;
|
return props.moreActionList ? [...dl, ...props.moreActionList] : dl;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听激活的tab变化,滚动到激活的tab
|
||||||
|
*/
|
||||||
watch(
|
watch(
|
||||||
() => props.activeTab,
|
() => props.activeTab,
|
||||||
() => {
|
() => {
|
||||||
|
@ -192,14 +201,21 @@
|
||||||
emit('add');
|
emit('add');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭一个tab
|
||||||
|
*/
|
||||||
function closeOneTab(item: TabItem) {
|
function closeOneTab(item: TabItem) {
|
||||||
const index = innerTabs.value.findIndex((e) => e.id === item.id);
|
const index = innerTabs.value.findIndex((e) => e.id === item.id);
|
||||||
innerTabs.value.splice(index, 1);
|
innerTabs.value.splice(index, 1);
|
||||||
if (innerActiveTab.value?.id === item.id && innerTabs.value[0]) {
|
if (innerActiveTab.value?.id === item.id && innerTabs.value[0]) {
|
||||||
[innerActiveTab.value] = innerTabs.value;
|
[innerActiveTab.value] = innerTabs.value;
|
||||||
|
emit('change', innerTabs.value[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭tab前处理
|
||||||
|
*/
|
||||||
function close(item: TabItem) {
|
function close(item: TabItem) {
|
||||||
if (item.unSaved) {
|
if (item.unSaved) {
|
||||||
openModal({
|
openModal({
|
||||||
|
@ -219,18 +235,24 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTabClick(item: TabItem) {
|
function handleTabClick(item: TabItem) {
|
||||||
|
if (innerActiveTab.value?.id !== item.id) {
|
||||||
innerActiveTab.value = item;
|
innerActiveTab.value = item;
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
tabNav.value?.querySelector('.tab.active')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
tabNav.value?.querySelector('.tab.active')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
});
|
});
|
||||||
emit('change', item);
|
emit('change', item);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行更多操作
|
||||||
|
*/
|
||||||
function executeAction(event: ActionsItem) {
|
function executeAction(event: ActionsItem) {
|
||||||
switch (event.eventTag) {
|
switch (event.eventTag) {
|
||||||
case 'closeAll':
|
case 'closeAll':
|
||||||
innerTabs.value = innerTabs.value.filter((item) => item.closable === false);
|
innerTabs.value = innerTabs.value.filter((item) => item.closable === false);
|
||||||
[innerActiveTab.value] = innerTabs.value;
|
[innerActiveTab.value] = innerTabs.value;
|
||||||
|
emit('change', innerActiveTab.value);
|
||||||
break;
|
break;
|
||||||
case 'closeOther':
|
case 'closeOther':
|
||||||
innerTabs.value = innerTabs.value.filter(
|
innerTabs.value = innerTabs.value.filter(
|
||||||
|
@ -243,6 +265,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理更多操作选择
|
||||||
|
*/
|
||||||
function handleMoreActionSelect(event: ActionsItem) {
|
function handleMoreActionSelect(event: ActionsItem) {
|
||||||
if (
|
if (
|
||||||
(event.eventTag === 'closeAll' && innerTabs.value.some((item) => item.unSaved)) ||
|
(event.eventTag === 'closeAll' && innerTabs.value.some((item) => item.unSaved)) ||
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
<template>
|
||||||
|
<MsBaseTable
|
||||||
|
v-bind="propsRes"
|
||||||
|
:hoverable="false"
|
||||||
|
no-disable
|
||||||
|
is-simple-setting
|
||||||
|
:span-method="props.spanMethod"
|
||||||
|
v-on="propsEvent"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-for="item of props.columns.filter((e) => e.slotName !== undefined)"
|
||||||
|
#[item.slotName!]="{ record, rowIndex, column }"
|
||||||
|
>
|
||||||
|
<slot :name="item.slotName" v-bind="{ record, rowIndex, column, dataIndex: item.dataIndex, columnConfig: item }">
|
||||||
|
{{ record[item.dataIndex as string] || '-' }}
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
</MsBaseTable>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { TableColumnData, TableData } from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||||
|
import type { MsTableColumnData } from '@/components/pure/ms-table/type';
|
||||||
|
import useTable from '@/components/pure/ms-table/useTable';
|
||||||
|
|
||||||
|
import useTableStore from '@/hooks/useTableStore';
|
||||||
|
|
||||||
|
import { SelectAllEnum, TableKeyEnum } from '@/enums/tableEnum';
|
||||||
|
|
||||||
|
import { TableOperationColumn } from '@arco-design/web-vue/es/table/interface';
|
||||||
|
|
||||||
|
export interface FormTableColumn extends MsTableColumnData {
|
||||||
|
enable?: boolean; // 是否启用
|
||||||
|
[key: string]: any; // 扩展属性
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
data?: any[];
|
||||||
|
columns: FormTableColumn[];
|
||||||
|
scroll?: {
|
||||||
|
x?: number | string;
|
||||||
|
y?: number | string;
|
||||||
|
maxHeight?: number | string;
|
||||||
|
minWidth?: number | string;
|
||||||
|
};
|
||||||
|
heightUsed?: number;
|
||||||
|
draggable?: boolean;
|
||||||
|
selectable?: boolean;
|
||||||
|
showSetting?: boolean; // 是否显示列设置
|
||||||
|
tableKey?: TableKeyEnum; // 表格key showSetting为true时必传
|
||||||
|
disabled?: boolean; // 是否禁用
|
||||||
|
showSelectorAll?: boolean; // 是否显示全选
|
||||||
|
isTreeTable?: boolean; // 是否树形表格
|
||||||
|
spanMethod?: (data: {
|
||||||
|
record: TableData;
|
||||||
|
column: TableColumnData | TableOperationColumn;
|
||||||
|
rowIndex: number;
|
||||||
|
columnIndex: number;
|
||||||
|
}) => { rowspan?: number; colspan?: number } | void;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
data: () => [],
|
||||||
|
selectable: true,
|
||||||
|
showSetting: false,
|
||||||
|
tableKey: undefined,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'change', data: any[]): void; // 都触发这个事件以通知父组件表格数据被更改
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const tableStore = useTableStore();
|
||||||
|
|
||||||
|
async function initColumns() {
|
||||||
|
if (props.showSetting && props.tableKey) {
|
||||||
|
await tableStore.initColumn(props.tableKey, props.columns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { propsRes, propsEvent } = useTable(() => Promise.resolve([]), {
|
||||||
|
firstColumnWidth: 32,
|
||||||
|
tableKey: props.showSetting ? props.tableKey : undefined,
|
||||||
|
scroll: props.scroll,
|
||||||
|
heightUsed: props.heightUsed,
|
||||||
|
columns: props.columns,
|
||||||
|
selectable: props.selectable,
|
||||||
|
draggable: props.draggable ? { type: 'handle', width: 24 } : undefined,
|
||||||
|
showSetting: props.showSetting,
|
||||||
|
disabled: props.disabled,
|
||||||
|
showSelectorAll: props.showSelectorAll,
|
||||||
|
showPagination: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedKeys = computed(() => propsRes.value.data.filter((e) => e.enable).map((e) => e.id));
|
||||||
|
propsEvent.value.rowSelectChange = (key: string) => {
|
||||||
|
propsRes.value.data = propsRes.value.data.map((e) => {
|
||||||
|
if (e.id === key) {
|
||||||
|
e.enable = !e.enable;
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
emit('change', propsRes.value.data);
|
||||||
|
};
|
||||||
|
propsEvent.value.selectAllChange = (v: SelectAllEnum) => {
|
||||||
|
propsRes.value.data = propsRes.value.data.map((e) => {
|
||||||
|
e.enable = v !== SelectAllEnum.NONE;
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
emit('change', propsRes.value.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => selectedKeys.value,
|
||||||
|
(arr) => {
|
||||||
|
propsRes.value.selectedKeys = new Set(arr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.heightUsed,
|
||||||
|
(val) => {
|
||||||
|
propsRes.value.heightUsed = val;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
(val) => {
|
||||||
|
propsRes.value.data = val;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await initColumns();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(.arco-table-th) {
|
||||||
|
background-color: var(--color-text-n9);
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
:deep(.arco-table .arco-table-cell) {
|
||||||
|
padding: 8px 2px;
|
||||||
|
}
|
||||||
|
:deep(.arco-table-cell-align-left) {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
:deep(.arco-table-col-fixed-right) {
|
||||||
|
.arco-table-cell-align-left {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.param-input:not(.arco-input-focus, .arco-select-view-focus)) {
|
||||||
|
&:not(:hover) {
|
||||||
|
border-color: transparent !important;
|
||||||
|
.arco-input::placeholder {
|
||||||
|
@apply invisible;
|
||||||
|
}
|
||||||
|
.arco-select-view-icon {
|
||||||
|
@apply invisible;
|
||||||
|
}
|
||||||
|
.arco-select-view-value {
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
.arco-select {
|
||||||
|
border-color: transparent !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.param-input-number) {
|
||||||
|
@apply pr-0;
|
||||||
|
.arco-input {
|
||||||
|
@apply text-right;
|
||||||
|
}
|
||||||
|
.arco-input-suffix {
|
||||||
|
@apply hidden;
|
||||||
|
}
|
||||||
|
&:hover,
|
||||||
|
&.arco-input-focus {
|
||||||
|
.arco-input {
|
||||||
|
@apply text-left;
|
||||||
|
}
|
||||||
|
.arco-input-suffix {
|
||||||
|
@apply inline-flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.arco-table-expand-btn) {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<template>
|
||||||
|
<MsFormTable :data="data" :columns="props.columns"> </MsFormTable>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import MsFormTable, { FormTableColumn } from '@/components/pure/ms-form-table/index.vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
columns: FormTableColumn[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const data = defineModel<Record<string, any>[]>('data', {
|
||||||
|
required: true,
|
||||||
|
default: () => [],
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -69,12 +69,14 @@
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const innerModelValue = ref(props.modelValue);
|
const innerModelValue = ref(props.modelValue);
|
||||||
const innerInputValue = ref(props.inputValue);
|
const innerInputValue = defineModel<string>('inputValue', {
|
||||||
|
default: '',
|
||||||
|
});
|
||||||
const tagsLength = ref(0); // 记录每次回车或失去焦点前的tags长度,以判断是否有新的tag被添加,新标签添加时需要判断是否重复的标签
|
const tagsLength = ref(0); // 记录每次回车或失去焦点前的tags长度,以判断是否有新的tag被添加,新标签添加时需要判断是否重复的标签
|
||||||
|
|
||||||
const isError = computed(
|
const isError = computed(
|
||||||
() =>
|
() =>
|
||||||
(innerInputValue.value || '').length > props.maxLength ||
|
innerInputValue.value.length > props.maxLength ||
|
||||||
innerModelValue.value.some((item) => item.toString().length > props.maxLength)
|
innerModelValue.value.some((item) => item.toString().length > props.maxLength)
|
||||||
);
|
);
|
||||||
watch(
|
watch(
|
||||||
|
@ -97,20 +99,6 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.inputValue,
|
|
||||||
(val) => {
|
|
||||||
innerInputValue.value = val;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => innerInputValue.value,
|
|
||||||
(val) => {
|
|
||||||
emit('update:inputValue', val);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
function validateTagsCountEnter() {
|
function validateTagsCountEnter() {
|
||||||
if (innerModelValue.value.length > 10) {
|
if (innerModelValue.value.length > 10) {
|
||||||
innerModelValue.value.pop();
|
innerModelValue.value.pop();
|
||||||
|
@ -160,7 +148,8 @@
|
||||||
if (
|
if (
|
||||||
validateTagsCountEnter() &&
|
validateTagsCountEnter() &&
|
||||||
validateUniqueValue() &&
|
validateUniqueValue() &&
|
||||||
(innerInputValue.value || '').trim().length <= props.maxLength
|
innerInputValue.value &&
|
||||||
|
innerInputValue.value.trim().length <= props.maxLength
|
||||||
) {
|
) {
|
||||||
innerInputValue.value = '';
|
innerInputValue.value = '';
|
||||||
tagsLength.value += 1;
|
tagsLength.value += 1;
|
||||||
|
|
|
@ -220,9 +220,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const { copy, isSupported } = useClipboard();
|
const { copy, isSupported } = useClipboard();
|
||||||
function copyVersion() {
|
async function copyVersion() {
|
||||||
if (isSupported) {
|
if (isSupported) {
|
||||||
copy(appStore.version);
|
await copy(appStore.version);
|
||||||
Message.success(t('common.copySuccess'));
|
Message.success(t('common.copySuccess'));
|
||||||
} else {
|
} else {
|
||||||
Message.warning(t('common.copyNotSupport'));
|
Message.warning(t('common.copyNotSupport'));
|
||||||
|
|
|
@ -56,6 +56,7 @@ export enum ResponseComposition {
|
||||||
}
|
}
|
||||||
// 接口响应体格式
|
// 接口响应体格式
|
||||||
export enum ResponseBodyFormat {
|
export enum ResponseBodyFormat {
|
||||||
|
NONE = 'NONE',
|
||||||
JSON = 'JSON',
|
JSON = 'JSON',
|
||||||
XML = 'XML',
|
XML = 'XML',
|
||||||
RAW = 'RAW',
|
RAW = 'RAW',
|
||||||
|
@ -70,7 +71,17 @@ export enum RequestDefinitionStatus {
|
||||||
}
|
}
|
||||||
// 接口导入支持格式
|
// 接口导入支持格式
|
||||||
export enum RequestImportFormat {
|
export enum RequestImportFormat {
|
||||||
SWAGGER = 'SWAGGER',
|
SWAGGER = 'Swagger3',
|
||||||
|
// MeterSphere = 'MeterSphere',
|
||||||
|
// Postman= 'Postman',
|
||||||
|
// Plugin = 'Plugin',
|
||||||
|
// Jmeter = 'Jmeter',
|
||||||
|
// Har = 'Har',
|
||||||
|
}
|
||||||
|
// 接口导入方式
|
||||||
|
export enum RequestImportType {
|
||||||
|
API = 'API',
|
||||||
|
SCHEDULE = 'Schedule',
|
||||||
}
|
}
|
||||||
// 接口认证设置类型
|
// 接口认证设置类型
|
||||||
export enum RequestAuthType {
|
export enum RequestAuthType {
|
||||||
|
|
|
@ -254,14 +254,13 @@ export interface ExecuteConditionProcessorCommon {
|
||||||
export type ScriptProcessor = ScriptCommonConfig & ExecuteConditionProcessorCommon;
|
export type ScriptProcessor = ScriptCommonConfig & ExecuteConditionProcessorCommon;
|
||||||
// 执行请求-前后置条件-SQL脚本处理器
|
// 执行请求-前后置条件-SQL脚本处理器
|
||||||
export interface SQLProcessor extends ExecuteConditionProcessorCommon {
|
export interface SQLProcessor extends ExecuteConditionProcessorCommon {
|
||||||
description: string; // 描述
|
name: string; // 描述
|
||||||
dataSourceId: string; // 数据源ID
|
dataSourceId: string; // 数据源ID
|
||||||
environmentId: string; // 环境ID
|
dataSourceName: string; // 数据源名称
|
||||||
queryTimeout: number; // 超时时间
|
queryTimeout: number; // 超时时间
|
||||||
resultVariable: string; // 按结果存储时的结果变量
|
resultVariable: string; // 按结果存储时的结果变量
|
||||||
script: string; // 脚本内容
|
script: string; // 脚本内容
|
||||||
variableNames: string; // 按列存储时的变量名集合,多个列可以使用逗号分隔
|
variableNames: string; // 按列存储时的变量名集合,多个列可以使用逗号分隔
|
||||||
variables: EnableKeyValueParam[]; // 变量列表
|
|
||||||
extractParams: KeyValueParam[]; // 提取参数列表
|
extractParams: KeyValueParam[]; // 提取参数列表
|
||||||
}
|
}
|
||||||
// 执行请求-前后置条件-等待时间处理器
|
// 执行请求-前后置条件-等待时间处理器
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { ModuleTreeNode, TableQueryParams } from '../common';
|
import { RequestDefinitionStatus, RequestImportFormat, RequestImportType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
|
import { BatchApiParams, ModuleTreeNode, TableQueryParams } from '../common';
|
||||||
import { ExecuteRequestParams, ResponseDefinition } from './common';
|
import { ExecuteRequestParams, ResponseDefinition } from './common';
|
||||||
|
|
||||||
// 定义-自定义字段
|
// 定义-自定义字段
|
||||||
|
@ -13,7 +15,7 @@ export interface ApiDefinitionCreateParams extends ExecuteRequestParams {
|
||||||
tags: string[];
|
tags: string[];
|
||||||
response: ResponseDefinition;
|
response: ResponseDefinition;
|
||||||
description: string;
|
description: string;
|
||||||
status: string;
|
status: RequestDefinitionStatus;
|
||||||
customFields: ApiDefinitionCustomField[];
|
customFields: ApiDefinitionCustomField[];
|
||||||
moduleId: string;
|
moduleId: string;
|
||||||
versionId: string;
|
versionId: string;
|
||||||
|
@ -22,10 +24,10 @@ export interface ApiDefinitionCreateParams extends ExecuteRequestParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新定义参数
|
// 更新定义参数
|
||||||
export interface ApiDefinitionUpdateParams extends ApiDefinitionCreateParams {
|
export interface ApiDefinitionUpdateParams extends Partial<ApiDefinitionCreateParams> {
|
||||||
id: string;
|
id: string;
|
||||||
deleteFileIds: string[];
|
deleteFileIds?: string[];
|
||||||
unLinkFileIds: string[];
|
unLinkFileIds?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义-自定义字段详情
|
// 定义-自定义字段详情
|
||||||
|
@ -164,3 +166,67 @@ export interface mockParams {
|
||||||
id: string;
|
id: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}
|
}
|
||||||
|
// 批量操作参数
|
||||||
|
export interface ApiDefinitionBatchParams extends BatchApiParams {
|
||||||
|
protocol: string;
|
||||||
|
}
|
||||||
|
// 批量更新定义参数
|
||||||
|
export interface ApiDefinitionBatchUpdateParams extends ApiDefinitionBatchParams {
|
||||||
|
type?: string;
|
||||||
|
append?: boolean;
|
||||||
|
method?: string;
|
||||||
|
status?: RequestDefinitionStatus;
|
||||||
|
versionId?: string;
|
||||||
|
tags?: string[];
|
||||||
|
customField?: Record<string, any>;
|
||||||
|
}
|
||||||
|
// 批量移动定义参数
|
||||||
|
export interface ApiDefinitionBatchMoveParams extends ApiDefinitionBatchParams {
|
||||||
|
moduleId: string | number;
|
||||||
|
}
|
||||||
|
// 批量删除定义参数
|
||||||
|
export interface ApiDefinitionBatchDeleteParams extends ApiDefinitionBatchParams {
|
||||||
|
deleteAll: boolean;
|
||||||
|
}
|
||||||
|
// 定义-定时同步-更新参数
|
||||||
|
export interface UpdateScheduleParams {
|
||||||
|
id: string;
|
||||||
|
taskId: string;
|
||||||
|
}
|
||||||
|
// 定义-定时同步-检查 url 是否存在参数
|
||||||
|
export interface CheckScheduleParams {
|
||||||
|
projectId: string;
|
||||||
|
swaggerUrl: string;
|
||||||
|
}
|
||||||
|
// 导入定义-request参数
|
||||||
|
export interface ImportApiDefinitionRequest {
|
||||||
|
userId: string;
|
||||||
|
versionId?: string;
|
||||||
|
updateVersionId?: string;
|
||||||
|
defaultVersion?: boolean;
|
||||||
|
platform: RequestImportFormat;
|
||||||
|
type: RequestImportType;
|
||||||
|
coverModule: boolean; // 是否覆盖子目录
|
||||||
|
coverData: boolean; // 是否覆盖数据
|
||||||
|
syncCase: boolean; // 是否同步导入用例
|
||||||
|
protocol: string;
|
||||||
|
authSwitch?: boolean;
|
||||||
|
authUsername?: string;
|
||||||
|
authPassword?: string;
|
||||||
|
uniquelyIdentifies?: string;
|
||||||
|
resourceId?: string;
|
||||||
|
swaggerUrl?: string;
|
||||||
|
moduleId: string;
|
||||||
|
projectId: string;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
// 导入定义参数
|
||||||
|
export interface ImportApiDefinitionParams {
|
||||||
|
file: File | null;
|
||||||
|
request: ImportApiDefinitionRequest;
|
||||||
|
}
|
||||||
|
// 导入定义-创建定时同步参数
|
||||||
|
export interface CreateImportApiDefinitionScheduleParams extends ImportApiDefinitionRequest {
|
||||||
|
value: string; // cron 表达式
|
||||||
|
config?: string;
|
||||||
|
}
|
||||||
|
|
|
@ -135,7 +135,6 @@ export interface BatchReviewCaseParams extends BatchApiParams {
|
||||||
status: ReviewResult; // 评审结果
|
status: ReviewResult; // 评审结果
|
||||||
content: string; // 评论内容
|
content: string; // 评论内容
|
||||||
notifier: string; // 评论@的人的Id, 多个以';'隔开
|
notifier: string; // 评论@的人的Id, 多个以';'隔开
|
||||||
moduleIds: (string | number)[];
|
|
||||||
}
|
}
|
||||||
// 评审详情-批量修改评审人
|
// 评审详情-批量修改评审人
|
||||||
export interface BatchChangeReviewerParams extends BatchApiParams {
|
export interface BatchChangeReviewerParams extends BatchApiParams {
|
||||||
|
@ -143,13 +142,11 @@ export interface BatchChangeReviewerParams extends BatchApiParams {
|
||||||
userId: string; // 用户id, 用来判断是否只看我的
|
userId: string; // 用户id, 用来判断是否只看我的
|
||||||
reviewerId: string[]; // 评审人员id
|
reviewerId: string[]; // 评审人员id
|
||||||
append: boolean; // 是否追加
|
append: boolean; // 是否追加
|
||||||
moduleIds: (string | number)[];
|
|
||||||
}
|
}
|
||||||
// 评审详情-批量取消关联用例
|
// 评审详情-批量取消关联用例
|
||||||
export interface BatchCancelReviewCaseParams extends BatchApiParams {
|
export interface BatchCancelReviewCaseParams extends BatchApiParams {
|
||||||
reviewId: string; // 评审id
|
reviewId: string; // 评审id
|
||||||
userId: string; // 用户id, 用来判断是否只看我的
|
userId: string; // 用户id, 用来判断是否只看我的
|
||||||
moduleIds: (string | number)[];
|
|
||||||
}
|
}
|
||||||
export interface ReviewDetailReviewersItem {
|
export interface ReviewDetailReviewersItem {
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
|
|
@ -44,6 +44,8 @@ export interface BatchApiParams {
|
||||||
selectAll: boolean; // 是否跨页全选,即选择当前筛选条件下的全部表格数据
|
selectAll: boolean; // 是否跨页全选,即选择当前筛选条件下的全部表格数据
|
||||||
condition: Record<string, any>; // 当前表格查询的筛选条件
|
condition: Record<string, any>; // 当前表格查询的筛选条件
|
||||||
currentSelectCount?: number; // 当前已选择的数量
|
currentSelectCount?: number; // 当前已选择的数量
|
||||||
|
projectId?: string; // 项目 ID
|
||||||
|
moduleIds?: (string | number)[]; // 模块 ID 集合
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移动模块树
|
// 移动模块树
|
||||||
|
|
|
@ -155,3 +155,16 @@ export interface HttpForm {
|
||||||
condition: '';
|
condition: '';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// 环境列表项
|
||||||
|
export interface EnvironmentItem {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
projectId: string;
|
||||||
|
createUser: string;
|
||||||
|
updateUser: string;
|
||||||
|
createTime: number;
|
||||||
|
updateTime: number;
|
||||||
|
mock: boolean;
|
||||||
|
description: string;
|
||||||
|
pos: number;
|
||||||
|
}
|
||||||
|
|
|
@ -217,14 +217,15 @@
|
||||||
<div class="mb-[16px]">
|
<div class="mb-[16px]">
|
||||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('common.desc') }}</div>
|
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('common.desc') }}</div>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:model-value="condition.description"
|
v-model:model-value="condition.name"
|
||||||
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
|
@input="() => emit('change')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-[16px] flex w-full items-center bg-[var(--color-text-n9)] p-[12px]">
|
<div class="mb-[16px] flex w-full items-center bg-[var(--color-text-n9)] p-[12px]">
|
||||||
<div class="text-[var(--color-text-2)]">
|
<div class="text-[var(--color-text-2)]">
|
||||||
{{ condition.scriptName || '-' }}
|
{{ condition.dataSourceName || '-' }}
|
||||||
</div>
|
</div>
|
||||||
<a-divider margin="8px" direction="vertical" />
|
<a-divider margin="8px" direction="vertical" />
|
||||||
<MsButton type="text" class="font-medium" @click="quoteSqlSourceDrawerVisible = true">
|
<MsButton type="text" class="font-medium" @click="quoteSqlSourceDrawerVisible = true">
|
||||||
|
@ -240,48 +241,52 @@
|
||||||
:language="LanguageEnum.SQL"
|
:language="LanguageEnum.SQL"
|
||||||
:show-full-screen="false"
|
:show-full-screen="false"
|
||||||
:show-theme-change="false"
|
:show-theme-change="false"
|
||||||
read-only
|
@change="() => emit('change')"
|
||||||
>
|
>
|
||||||
</MsCodeEditor>
|
</MsCodeEditor>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-[16px]">
|
<div class="mb-[16px]">
|
||||||
<div class="mb-[8px] flex items-center text-[var(--color-text-1)]">
|
<div class="mb-[8px] flex items-center text-[var(--color-text-1)]">
|
||||||
{{ t('apiTestDebug.storageType') }}
|
{{ t('apiTestDebug.storageByCol') }}
|
||||||
<a-tooltip position="right">
|
<a-tooltip position="right" :content="t('apiTestDebug.storageColTip')">
|
||||||
<icon-question-circle
|
<icon-question-circle
|
||||||
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||||
size="16"
|
size="16"
|
||||||
/>
|
/>
|
||||||
<template #content>
|
|
||||||
<div>{{ t('apiTestDebug.storageTypeTip1') }}</div>
|
|
||||||
<div>{{ t('apiTestDebug.storageTypeTip2') }}</div>
|
|
||||||
</template>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="mb-[16px]">
|
|
||||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.storageByCol') }}</div>
|
|
||||||
<a-input
|
<a-input
|
||||||
v-model:model-value="condition.variableNames"
|
v-model:model-value="condition.variableNames"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
:placeholder="t('apiTestDebug.storageByColPlaceholder', { a: '{id_1}', b: '{username_1}' })"
|
:placeholder="t('apiTestDebug.storageByColPlaceholder', { a: '{id_1}', b: '{username_1}' })"
|
||||||
|
@input="() => emit('change')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="sql-table-container">
|
<div class="sql-table-container">
|
||||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.extractParameter') }}</div>
|
<div class="mb-[8px] flex items-center text-[var(--color-text-1)]">
|
||||||
|
{{ t('apiTestDebug.extractParameter') }}
|
||||||
|
<a-tooltip position="right" :content="t('apiTestDebug.storageResultTip')">
|
||||||
|
<icon-question-circle
|
||||||
|
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
<paramTable
|
<paramTable
|
||||||
v-model:params="condition.variables"
|
v-model:params="condition.extractParams"
|
||||||
:columns="sqlSourceColumns"
|
:columns="sqlSourceColumns"
|
||||||
:selectable="false"
|
:selectable="false"
|
||||||
|
:default-param-item="defaultKeyValueParamItem"
|
||||||
@change="handleSqlSourceParamTableChange"
|
@change="handleSqlSourceParamTableChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-[16px]">
|
<div class="mt-[16px]">
|
||||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.storageByResult') }}</div>
|
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.storageByResult') }}</div>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:model-value="condition.resultVariable"
|
v-model:model-value="condition.resultVariable"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
:placeholder="t('apiTestDebug.storageByResultPlaceholder', { a: '${result}' })"
|
:placeholder="t('apiTestDebug.storageByResultPlaceholder', { a: '${result}' })"
|
||||||
|
@input="() => emit('change')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -431,6 +436,7 @@
|
||||||
import type { ProtocolItem } from '@/models/apiTest/common';
|
import type { ProtocolItem } from '@/models/apiTest/common';
|
||||||
import { ExecuteConditionProcessor, JSONPathExtract, RegexExtract, XPathExtract } from '@/models/apiTest/common';
|
import { ExecuteConditionProcessor, JSONPathExtract, RegexExtract, XPathExtract } from '@/models/apiTest/common';
|
||||||
import { ParamsRequestType } from '@/models/projectManagement/commonScript';
|
import { ParamsRequestType } from '@/models/projectManagement/commonScript';
|
||||||
|
import { DataSourceItem, EnvConfig } from '@/models/projectManagement/environmental';
|
||||||
import {
|
import {
|
||||||
RequestConditionProcessor,
|
RequestConditionProcessor,
|
||||||
RequestExtractEnvType,
|
RequestExtractEnvType,
|
||||||
|
@ -441,6 +447,8 @@
|
||||||
ResponseBodyXPathAssertionFormat,
|
ResponseBodyXPathAssertionFormat,
|
||||||
} from '@/enums/apiEnum';
|
} from '@/enums/apiEnum';
|
||||||
|
|
||||||
|
import { defaultKeyValueParamItem } from '@/views/api-test/components/config';
|
||||||
|
|
||||||
export type ExpressionConfig = (RegexExtract | JSONPathExtract | XPathExtract) & Record<string, any>;
|
export type ExpressionConfig = (RegexExtract | JSONPathExtract | XPathExtract) & Record<string, any>;
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
|
@ -468,7 +476,31 @@
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
|
||||||
const condition = useVModel(props, 'data', emit);
|
const condition = useVModel(props, 'data', emit);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (condition.value.processorType === RequestConditionProcessor.SQL && condition.value.dataSourceId) {
|
||||||
|
// 如果是SQL类型的条件且已选数据源,需要根据环境切换数据源
|
||||||
|
const dataSourceItem = currentEnvConfig?.value.dataSources.find(
|
||||||
|
(item) => item.dataSource === condition.value.dataSourceName
|
||||||
|
);
|
||||||
|
if (dataSourceItem) {
|
||||||
|
// 每次初始化都去查找一下最新的数据源,因为切换环境的时候数据源也需要切换
|
||||||
|
condition.value.dataSourceName = dataSourceItem.dataSource;
|
||||||
|
condition.value.dataSourceId = dataSourceItem.id;
|
||||||
|
} else if (currentEnvConfig && currentEnvConfig.value.dataSources.length > 0) {
|
||||||
|
// 如果没有找到,就默认取第一个数据源
|
||||||
|
condition.value.dataSourceName = currentEnvConfig.value.dataSources[0].dataSource;
|
||||||
|
condition.value.dataSourceId = currentEnvConfig.value.dataSources[0].id;
|
||||||
|
} else {
|
||||||
|
// 如果没有数据源,就清除已选的数据源
|
||||||
|
condition.value.dataSourceName = '';
|
||||||
|
condition.value.dataSourceId = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 是否显示脚本名称编辑框
|
// 是否显示脚本名称编辑框
|
||||||
const isShowEditScriptNameInput = ref(false);
|
const isShowEditScriptNameInput = ref(false);
|
||||||
const scriptNameInputRef = ref<InputInstance>();
|
const scriptNameInputRef = ref<InputInstance>();
|
||||||
|
@ -490,9 +522,9 @@ if (!result){
|
||||||
}`);
|
}`);
|
||||||
const { copy, isSupported } = useClipboard();
|
const { copy, isSupported } = useClipboard();
|
||||||
|
|
||||||
function copyScriptEx() {
|
async function copyScriptEx() {
|
||||||
if (isSupported) {
|
if (isSupported) {
|
||||||
copy(scriptEx.value);
|
await copy(scriptEx.value);
|
||||||
Message.success(t('apiTestDebug.scriptExCopySuccess'));
|
Message.success(t('apiTestDebug.scriptExCopySuccess'));
|
||||||
} else {
|
} else {
|
||||||
Message.warning(t('apiTestDebug.copyNotSupport'));
|
Message.warning(t('apiTestDebug.copyNotSupport'));
|
||||||
|
@ -598,14 +630,14 @@ if (!result){
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const quoteSqlSourceDrawerVisible = ref(false);
|
const quoteSqlSourceDrawerVisible = ref(false);
|
||||||
function handleQuoteSqlSourceApply(sqlSource: Record<string, any>) {
|
function handleQuoteSqlSourceApply(sqlSource: DataSourceItem) {
|
||||||
condition.value.script = sqlSource.script;
|
condition.value.dataSourceName = sqlSource.dataSource;
|
||||||
condition.value.dataSourceId = sqlSource.id;
|
condition.value.dataSourceId = sqlSource.id;
|
||||||
emit('change');
|
emit('change');
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSqlSourceParamTableChange(resultArr: any[], isInit?: boolean) {
|
function handleSqlSourceParamTableChange(resultArr: any[], isInit?: boolean) {
|
||||||
condition.value.variables = [...resultArr];
|
condition.value.extractParams = [...resultArr];
|
||||||
if (!isInit) {
|
if (!isInit) {
|
||||||
emit('change');
|
emit('change');
|
||||||
}
|
}
|
||||||
|
|
|
@ -169,15 +169,13 @@
|
||||||
associateScenarioResult: false,
|
associateScenarioResult: false,
|
||||||
ignoreProtocols: [],
|
ignoreProtocols: [],
|
||||||
beforeStepScript: true,
|
beforeStepScript: true,
|
||||||
description: '',
|
name: '',
|
||||||
enable: true,
|
enable: true,
|
||||||
dataSourceId: '',
|
dataSourceId: '',
|
||||||
environmentId: '',
|
|
||||||
queryTimeout: 0,
|
queryTimeout: 0,
|
||||||
resultVariable: '',
|
resultVariable: '',
|
||||||
script: '',
|
script: '',
|
||||||
variableNames: '',
|
variableNames: '',
|
||||||
variables: [],
|
|
||||||
extractParams: [],
|
extractParams: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {
|
||||||
EnableKeyValueParam,
|
EnableKeyValueParam,
|
||||||
ExecuteRequestCommonParam,
|
ExecuteRequestCommonParam,
|
||||||
ExecuteRequestFormBodyFormValue,
|
ExecuteRequestFormBodyFormValue,
|
||||||
|
KeyValueParam,
|
||||||
ResponseDefinition,
|
ResponseDefinition,
|
||||||
} from '@/models/apiTest/common';
|
} from '@/models/apiTest/common';
|
||||||
import { RequestContentTypeEnum, RequestParamsType, ResponseBodyFormat, ResponseComposition } from '@/enums/apiEnum';
|
import { RequestContentTypeEnum, RequestParamsType, ResponseBodyFormat, ResponseComposition } from '@/enums/apiEnum';
|
||||||
|
@ -74,5 +75,11 @@ export const defaultResponseItem: ResponseDefinition = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 默认提取参数的 key-value 表格行的值
|
||||||
|
export const defaultKeyValueParamItem: KeyValueParam = {
|
||||||
|
key: '',
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
|
||||||
// 请求的响应 response 的响应状态码集合
|
// 请求的响应 response 的响应状态码集合
|
||||||
export const statusCodes = [200, 201, 202, 203, 204, 205, 400, 401, 402, 403, 404, 405, 500, 501, 502, 503, 504, 505];
|
export const statusCodes = [200, 201, 202, 203, 204, 205, 400, 401, 402, 403, 404, 405, 500, 501, 502, 503, 504, 505];
|
||||||
|
|
|
@ -1,12 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<MsBaseTable
|
<MsFormTable v-bind="props" :data="paramsData">
|
||||||
v-bind="propsRes"
|
|
||||||
:hoverable="false"
|
|
||||||
no-disable
|
|
||||||
is-simple-setting
|
|
||||||
:span-method="props.spanMethod"
|
|
||||||
v-on="propsEvent"
|
|
||||||
>
|
|
||||||
<!-- 展开行-->
|
<!-- 展开行-->
|
||||||
<template #expand-icon="{ record }">
|
<template #expand-icon="{ record }">
|
||||||
<div class="flex flex-row items-center gap-[2px] text-[var(--color-text-4)]">
|
<div class="flex flex-row items-center gap-[2px] text-[var(--color-text-4)]">
|
||||||
|
@ -404,7 +397,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</MsBaseTable>
|
</MsFormTable>
|
||||||
<a-modal
|
<a-modal
|
||||||
v-model:visible="showQuickInputParam"
|
v-model:visible="showQuickInputParam"
|
||||||
:title="t('ms.paramsInput.value')"
|
:title="t('ms.paramsInput.value')"
|
||||||
|
@ -463,9 +456,7 @@
|
||||||
|
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
import MsFormTable, { FormTableColumn } from '@/components/pure/ms-form-table/index.vue';
|
||||||
import type { MsTableColumnData } from '@/components/pure/ms-table/type';
|
|
||||||
import useTable from '@/components/pure/ms-table/useTable';
|
|
||||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||||
import MsTagsGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
|
import MsTagsGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
|
||||||
|
@ -476,13 +467,12 @@
|
||||||
|
|
||||||
import { groupProjectEnv, listEnv } from '@/api/modules/project-management/envManagement';
|
import { groupProjectEnv, listEnv } from '@/api/modules/project-management/envManagement';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useTableStore from '@/hooks/useTableStore';
|
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
import { ModuleTreeNode, TransferFileParams } from '@/models/common';
|
import { ModuleTreeNode, TransferFileParams } from '@/models/common';
|
||||||
import { ProjectOptionItem } from '@/models/projectManagement/environmental';
|
import { ProjectOptionItem } from '@/models/projectManagement/environmental';
|
||||||
import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum';
|
import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum';
|
||||||
import { SelectAllEnum, TableKeyEnum } from '@/enums/tableEnum';
|
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||||
|
|
||||||
import { filterKeyValParams } from './utils';
|
import { filterKeyValParams } from './utils';
|
||||||
import { TableOperationColumn } from '@arco-design/web-vue/es/table/interface';
|
import { TableOperationColumn } from '@arco-design/web-vue/es/table/interface';
|
||||||
|
@ -490,7 +480,7 @@
|
||||||
const MsAddAttachment = defineAsyncComponent(() => import('@/components/business/ms-add-attachment/index.vue'));
|
const MsAddAttachment = defineAsyncComponent(() => import('@/components/business/ms-add-attachment/index.vue'));
|
||||||
const MsParamsInput = defineAsyncComponent(() => import('@/components/business/ms-params-input/index.vue'));
|
const MsParamsInput = defineAsyncComponent(() => import('@/components/business/ms-params-input/index.vue'));
|
||||||
|
|
||||||
export type ParamTableColumn = MsTableColumnData & {
|
export interface ParamTableColumn extends FormTableColumn {
|
||||||
isNormal?: boolean; // 用于 value 列区分是普通输入框还是 MsParamsInput
|
isNormal?: boolean; // 用于 value 列区分是普通输入框还是 MsParamsInput
|
||||||
hasRequired?: boolean; // 用于 type 列区分是否有 required 星号
|
hasRequired?: boolean; // 用于 type 列区分是否有 required 星号
|
||||||
typeOptions?: { label: string; value: string }[]; // 用于 type 列选择器选项
|
typeOptions?: { label: string; value: string }[]; // 用于 type 列选择器选项
|
||||||
|
@ -499,7 +489,7 @@
|
||||||
moreAction?: ActionsItem[]; // 用于 operation 列更多操作按钮配置
|
moreAction?: ActionsItem[]; // 用于 operation 列更多操作按钮配置
|
||||||
format?: RequestBodyFormat; // 用于 operation 列区分是否有请求体格式选择器
|
format?: RequestBodyFormat; // 用于 operation 列区分是否有请求体格式选择器
|
||||||
addLineDisabled?: boolean; // 用于 是否禁用添加新行
|
addLineDisabled?: boolean; // 用于 是否禁用添加新行
|
||||||
};
|
}
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -565,75 +555,22 @@
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const tableStore = useTableStore();
|
const paramsData = ref<any[]>(props.params);
|
||||||
|
|
||||||
async function initColumns() {
|
|
||||||
if (props.showSetting && props.tableKey) {
|
|
||||||
await tableStore.initColumn(props.tableKey, props.columns);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { propsRes, propsEvent } = useTable(() => Promise.resolve([]), {
|
|
||||||
firstColumnWidth: 32,
|
|
||||||
tableKey: props.showSetting ? props.tableKey : undefined,
|
|
||||||
scroll: props.scroll,
|
|
||||||
heightUsed: props.heightUsed,
|
|
||||||
columns: props.columns,
|
|
||||||
selectable: props.selectable,
|
|
||||||
draggable: props.draggable ? { type: 'handle', width: 24 } : undefined,
|
|
||||||
showSetting: props.showSetting,
|
|
||||||
disabled: props.disabled,
|
|
||||||
showSelectorAll: props.showSelectorAll,
|
|
||||||
isSimpleSetting: props.isSimpleSetting,
|
|
||||||
showPagination: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
function emitChange(from: string, isInit?: boolean) {
|
function emitChange(from: string, isInit?: boolean) {
|
||||||
if (!isInit) {
|
if (!isInit) {
|
||||||
emit('change', propsRes.value.data);
|
emit('change', paramsData.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedKeys = computed(() => propsRes.value.data.filter((e) => e.enable).map((e) => e.id));
|
const paramsLength = computed(() => paramsData.value.length);
|
||||||
propsEvent.value.rowSelectChange = (key: string) => {
|
|
||||||
propsRes.value.data = propsRes.value.data.map((e) => {
|
|
||||||
if (e.id === key) {
|
|
||||||
e.enable = !e.enable;
|
|
||||||
}
|
|
||||||
return e;
|
|
||||||
});
|
|
||||||
emitChange('rowSelectChange');
|
|
||||||
};
|
|
||||||
propsEvent.value.selectAllChange = (v: SelectAllEnum) => {
|
|
||||||
propsRes.value.data = propsRes.value.data.map((e) => {
|
|
||||||
e.enable = v !== SelectAllEnum.NONE;
|
|
||||||
return e;
|
|
||||||
});
|
|
||||||
emitChange('selectAllChange');
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => selectedKeys.value,
|
|
||||||
(arr) => {
|
|
||||||
propsRes.value.selectedKeys = new Set(arr);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.heightUsed,
|
|
||||||
(val) => {
|
|
||||||
propsRes.value.heightUsed = val;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const paramsLength = computed(() => propsRes.value.data.length);
|
|
||||||
|
|
||||||
function deleteParam(record: Record<string, any>, rowIndex: number) {
|
function deleteParam(record: Record<string, any>, rowIndex: number) {
|
||||||
if (props.isTreeTable) {
|
if (props.isTreeTable) {
|
||||||
emit('treeDelete', record);
|
emit('treeDelete', record);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
propsRes.value.data.splice(rowIndex, 1);
|
paramsData.value.splice(rowIndex, 1);
|
||||||
emitChange('deleteParam');
|
emitChange('deleteParam');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -644,17 +581,14 @@
|
||||||
const handleMustIncludeChange = (val: boolean) => {
|
const handleMustIncludeChange = (val: boolean) => {
|
||||||
mustIncludeAllChecked.value = val;
|
mustIncludeAllChecked.value = val;
|
||||||
mustIncludeIndeterminate.value = false;
|
mustIncludeIndeterminate.value = false;
|
||||||
const { data } = propsRes.value;
|
paramsData.value.forEach((e: any) => {
|
||||||
data.forEach((e: any) => {
|
|
||||||
e.mustInclude = val;
|
e.mustInclude = val;
|
||||||
});
|
});
|
||||||
propsRes.value.data = data;
|
|
||||||
emitChange('handleMustIncludeChange');
|
emitChange('handleMustIncludeChange');
|
||||||
};
|
};
|
||||||
const handleMustContainColChange = (notEmit?: boolean) => {
|
const handleMustContainColChange = (notEmit?: boolean) => {
|
||||||
const { data } = propsRes.value;
|
const checkedList = paramsData.value.filter((e: any) => e.mustInclude).map((e: any) => e.id);
|
||||||
const checkedList = data.filter((e: any) => e.mustInclude).map((e: any) => e.id);
|
if (checkedList.length === paramsData.value.length) {
|
||||||
if (checkedList.length === data.length) {
|
|
||||||
mustIncludeAllChecked.value = true;
|
mustIncludeAllChecked.value = true;
|
||||||
mustIncludeIndeterminate.value = false;
|
mustIncludeIndeterminate.value = false;
|
||||||
} else if (checkedList.length === 0) {
|
} else if (checkedList.length === 0) {
|
||||||
|
@ -674,17 +608,14 @@
|
||||||
const handleTypeCheckingChange = (val: boolean) => {
|
const handleTypeCheckingChange = (val: boolean) => {
|
||||||
typeCheckingAllChecked.value = val;
|
typeCheckingAllChecked.value = val;
|
||||||
typeCheckingIndeterminate.value = false;
|
typeCheckingIndeterminate.value = false;
|
||||||
const { data } = propsRes.value;
|
paramsData.value.forEach((e: any) => {
|
||||||
data.forEach((e: any) => {
|
|
||||||
e.typeChecking = val;
|
e.typeChecking = val;
|
||||||
});
|
});
|
||||||
propsRes.value.data = data;
|
|
||||||
emitChange('handleTypeCheckingChange');
|
emitChange('handleTypeCheckingChange');
|
||||||
};
|
};
|
||||||
const handleTypeCheckingColChange = (notEmit?: boolean) => {
|
const handleTypeCheckingColChange = (notEmit?: boolean) => {
|
||||||
const { data } = propsRes.value;
|
const checkedList = paramsData.value.filter((e: any) => e.typeChecking).map((e: any) => e.id);
|
||||||
const checkedList = data.filter((e: any) => e.typeChecking).map((e: any) => e.id);
|
if (checkedList.length === paramsData.value.length) {
|
||||||
if (checkedList.length === data.length) {
|
|
||||||
typeCheckingAllChecked.value = true;
|
typeCheckingAllChecked.value = true;
|
||||||
typeCheckingIndeterminate.value = false;
|
typeCheckingIndeterminate.value = false;
|
||||||
} else if (checkedList.length === 0) {
|
} else if (checkedList.length === 0) {
|
||||||
|
@ -766,10 +697,10 @@
|
||||||
if (addLineDisabled) {
|
if (addLineDisabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (rowIndex === propsRes.value.data.length - 1) {
|
if (rowIndex === paramsData.value.length - 1) {
|
||||||
// 最后一行的更改才会触发添加新一行
|
// 最后一行的更改才会触发添加新一行
|
||||||
const id = new Date().getTime().toString();
|
const id = new Date().getTime().toString();
|
||||||
propsRes.value.data.push({
|
paramsData.value.push({
|
||||||
id,
|
id,
|
||||||
...cloneDeep(props.defaultParamItem), // 深拷贝,避免有嵌套引用类型,数据隔离
|
...cloneDeep(props.defaultParamItem), // 深拷贝,避免有嵌套引用类型,数据隔离
|
||||||
enable: true, // 是否勾选
|
enable: true, // 是否勾选
|
||||||
|
@ -785,7 +716,14 @@
|
||||||
(arr) => {
|
(arr) => {
|
||||||
if (arr.length > 0) {
|
if (arr.length > 0) {
|
||||||
let hasNoIdItem = false; // 是否有没有id的项,用以判断是否是后台数据初始化表格
|
let hasNoIdItem = false; // 是否有没有id的项,用以判断是否是后台数据初始化表格
|
||||||
propsRes.value.data = arr.map((item, i) => {
|
paramsData.value = arr.map((item, i) => {
|
||||||
|
if (!item) {
|
||||||
|
// 批量添加过来的数据最后一行会是 undefined
|
||||||
|
return {
|
||||||
|
...props.defaultParamItem,
|
||||||
|
id: new Date().getTime() + i,
|
||||||
|
};
|
||||||
|
}
|
||||||
if (!item.id) {
|
if (!item.id) {
|
||||||
// 后台存储无id,渲染时需要手动添加一次
|
// 后台存储无id,渲染时需要手动添加一次
|
||||||
hasNoIdItem = true;
|
hasNoIdItem = true;
|
||||||
|
@ -801,7 +739,7 @@
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const id = new Date().getTime().toString();
|
const id = new Date().getTime().toString();
|
||||||
propsRes.value.data = [
|
paramsData.value = [
|
||||||
{
|
{
|
||||||
id, // 默认给时间戳 id,若 props.defaultParamItem 有 id,则覆盖
|
id, // 默认给时间戳 id,若 props.defaultParamItem 有 id,则覆盖
|
||||||
...props.defaultParamItem,
|
...props.defaultParamItem,
|
||||||
|
@ -880,7 +818,7 @@
|
||||||
function applyQuickInputParam() {
|
function applyQuickInputParam() {
|
||||||
activeQuickInputRecord.value.value = quickInputParamValue.value;
|
activeQuickInputRecord.value.value = quickInputParamValue.value;
|
||||||
showQuickInputParam.value = false;
|
showQuickInputParam.value = false;
|
||||||
addTableLine(propsRes.value.data.findIndex((e) => e.id === activeQuickInputRecord.value.id));
|
addTableLine(paramsData.value.findIndex((e) => e.id === activeQuickInputRecord.value.id));
|
||||||
clearQuickInputParam();
|
clearQuickInputParam();
|
||||||
emitChange('applyQuickInputParam');
|
emitChange('applyQuickInputParam');
|
||||||
}
|
}
|
||||||
|
@ -902,7 +840,7 @@
|
||||||
function applyQuickInputDesc() {
|
function applyQuickInputDesc() {
|
||||||
activeQuickInputRecord.value.description = quickInputDescValue.value;
|
activeQuickInputRecord.value.description = quickInputDescValue.value;
|
||||||
showQuickInputDesc.value = false;
|
showQuickInputDesc.value = false;
|
||||||
addTableLine(propsRes.value.data.findIndex((e) => e.id === activeQuickInputRecord.value.id));
|
addTableLine(paramsData.value.findIndex((e) => e.id === activeQuickInputRecord.value.id));
|
||||||
clearQuickInputDesc();
|
clearQuickInputDesc();
|
||||||
emitChange('applyQuickInputDesc');
|
emitChange('applyQuickInputDesc');
|
||||||
}
|
}
|
||||||
|
@ -959,61 +897,9 @@
|
||||||
defineExpose({
|
defineExpose({
|
||||||
addTableLine,
|
addTableLine,
|
||||||
});
|
});
|
||||||
|
|
||||||
await initColumns();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
:deep(.arco-table-th) {
|
|
||||||
background-color: var(--color-text-n9);
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
:deep(.arco-table .arco-table-cell) {
|
|
||||||
padding: 8px 2px;
|
|
||||||
}
|
|
||||||
:deep(.arco-table-cell-align-left) {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
:deep(.arco-table-col-fixed-right) {
|
|
||||||
.arco-table-cell-align-left {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:deep(.param-input:not(.arco-input-focus, .arco-select-view-focus)) {
|
|
||||||
&:not(:hover) {
|
|
||||||
border-color: transparent !important;
|
|
||||||
.arco-input::placeholder {
|
|
||||||
@apply invisible;
|
|
||||||
}
|
|
||||||
.arco-select-view-icon {
|
|
||||||
@apply invisible;
|
|
||||||
}
|
|
||||||
.arco-select-view-value {
|
|
||||||
color: var(--color-text-1);
|
|
||||||
}
|
|
||||||
.arco-select {
|
|
||||||
border-color: transparent !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:deep(.param-input-number) {
|
|
||||||
@apply pr-0;
|
|
||||||
.arco-input {
|
|
||||||
@apply text-right;
|
|
||||||
}
|
|
||||||
.arco-input-suffix {
|
|
||||||
@apply hidden;
|
|
||||||
}
|
|
||||||
&:hover,
|
|
||||||
&.arco-input-focus {
|
|
||||||
.arco-input {
|
|
||||||
@apply text-left;
|
|
||||||
}
|
|
||||||
.arco-input-suffix {
|
|
||||||
@apply inline-flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.content-type-trigger-content {
|
.content-type-trigger-content {
|
||||||
@apply bg-white;
|
@apply bg-white;
|
||||||
|
|
||||||
|
@ -1043,7 +929,4 @@
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
color: var(--color-text-1);
|
color: var(--color-text-1);
|
||||||
}
|
}
|
||||||
:deep(.arco-table-expand-btn) {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -13,8 +13,9 @@
|
||||||
:placeholder="t('project.projectVersion.searchPlaceholder')"
|
:placeholder="t('project.projectVersion.searchPlaceholder')"
|
||||||
class="w-[230px]"
|
class="w-[230px]"
|
||||||
allow-clear
|
allow-clear
|
||||||
@search="searchSource"
|
@search="searchDataSource"
|
||||||
@press-enter="searchSource"
|
@press-enter="searchDataSource"
|
||||||
|
@clear="searchDataSource"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<MsBaseTable v-bind="propsRes" v-model:selected-key="selectedKey" v-on="propsEvent">
|
<MsBaseTable v-bind="propsRes" v-model:selected-key="selectedKey" v-on="propsEvent">
|
||||||
|
@ -29,6 +30,7 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||||
|
@ -37,6 +39,8 @@
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
import { EnvConfig } from '@/models/projectManagement/environmental';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
selectedKey?: string;
|
selectedKey?: string;
|
||||||
|
@ -48,6 +52,8 @@
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
/** 接收祖先组件提供的属性 */
|
||||||
|
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
|
||||||
const innerVisible = useVModel(props, 'visible', emit);
|
const innerVisible = useVModel(props, 'visible', emit);
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
const selectedKey = ref(props.selectedKey || '');
|
const selectedKey = ref(props.selectedKey || '');
|
||||||
|
@ -55,7 +61,7 @@
|
||||||
const columns: MsTableColumn = [
|
const columns: MsTableColumn = [
|
||||||
{
|
{
|
||||||
title: 'apiTestDebug.sqlSourceName',
|
title: 'apiTestDebug.sqlSourceName',
|
||||||
dataIndex: 'name',
|
dataIndex: 'dataSource',
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -71,7 +77,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'apiTestDebug.maxConnection',
|
title: 'apiTestDebug.maxConnection',
|
||||||
dataIndex: 'maxConnection',
|
dataIndex: 'poolMax',
|
||||||
width: 140,
|
width: 140,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -82,47 +88,8 @@
|
||||||
width: 120,
|
width: 120,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
async function loadSource() {
|
|
||||||
return Promise.resolve({
|
const { propsRes, propsEvent } = useTable(undefined, {
|
||||||
list: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
name: 'test',
|
|
||||||
driver: 'com.mysql.cj.jdbc.Driver',
|
|
||||||
username: 'root',
|
|
||||||
maxConnection: 10,
|
|
||||||
timeout: 1000,
|
|
||||||
storageType: 'column',
|
|
||||||
params: [],
|
|
||||||
script: 'select * from test1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
name: 'test2',
|
|
||||||
driver: 'com.mysql.cj.jdbc.Driver',
|
|
||||||
username: 'root',
|
|
||||||
maxConnection: 10,
|
|
||||||
timeout: 1000,
|
|
||||||
storageType: 'column',
|
|
||||||
params: [],
|
|
||||||
script: 'select * from test2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
name: 'test3',
|
|
||||||
driver: 'com.mysql.cj.jdbc.Driver',
|
|
||||||
username: 'root',
|
|
||||||
maxConnection: 10,
|
|
||||||
timeout: 10000000000,
|
|
||||||
storageType: 'result',
|
|
||||||
params: [],
|
|
||||||
script: 'select * from test3',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
total: 99,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const { propsRes, propsEvent, setLoadListParams, loadList } = useTable(loadSource, {
|
|
||||||
columns,
|
columns,
|
||||||
scroll: { x: '100%' },
|
scroll: { x: '100%' },
|
||||||
heightUsed: 300,
|
heightUsed: 300,
|
||||||
|
@ -130,14 +97,28 @@
|
||||||
showSelectorAll: false,
|
showSelectorAll: false,
|
||||||
selectorType: 'radio',
|
selectorType: 'radio',
|
||||||
firstColumnWidth: 44,
|
firstColumnWidth: 44,
|
||||||
|
showPagination: false,
|
||||||
});
|
});
|
||||||
function searchSource() {
|
|
||||||
setLoadListParams({
|
watch(
|
||||||
keyword: keyword.value,
|
() => currentEnvConfig?.value,
|
||||||
});
|
(config) => {
|
||||||
loadList();
|
if (config) {
|
||||||
|
propsRes.value.data = cloneDeep(config.dataSources) as any[];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function searchDataSource() {
|
||||||
|
if (keyword.value.trim() !== '') {
|
||||||
|
propsRes.value.data = propsRes.value.data.filter((e) => e.dataSource.includes(keyword.value));
|
||||||
|
} else {
|
||||||
|
propsRes.value.data = cloneDeep(currentEnvConfig?.value.dataSources) as any[];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
searchSource();
|
|
||||||
|
|
||||||
function handleConfirm() {
|
function handleConfirm() {
|
||||||
innerVisible.value = false;
|
innerVisible.value = false;
|
||||||
|
|
|
@ -339,6 +339,7 @@
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ExecuteApiRequestFullParams,
|
ExecuteApiRequestFullParams,
|
||||||
|
ExecuteConditionConfig,
|
||||||
ExecuteRequestParams,
|
ExecuteRequestParams,
|
||||||
PluginConfig,
|
PluginConfig,
|
||||||
RequestTaskResult,
|
RequestTaskResult,
|
||||||
|
@ -348,6 +349,7 @@
|
||||||
RequestAuthType,
|
RequestAuthType,
|
||||||
RequestBodyFormat,
|
RequestBodyFormat,
|
||||||
RequestComposition,
|
RequestComposition,
|
||||||
|
RequestConditionProcessor,
|
||||||
RequestMethods,
|
RequestMethods,
|
||||||
RequestParamsType,
|
RequestParamsType,
|
||||||
} from '@/enums/apiEnum';
|
} from '@/enums/apiEnum';
|
||||||
|
@ -356,6 +358,7 @@
|
||||||
import {
|
import {
|
||||||
defaultBodyParamsItem,
|
defaultBodyParamsItem,
|
||||||
defaultHeaderParamsItem,
|
defaultHeaderParamsItem,
|
||||||
|
defaultKeyValueParamItem,
|
||||||
defaultRequestParamsItem,
|
defaultRequestParamsItem,
|
||||||
} from '@/views/api-test/components/config';
|
} from '@/views/api-test/components/config';
|
||||||
import { filterKeyValParams, parseRequestBodyFiles } from '@/views/api-test/components/utils';
|
import { filterKeyValParams, parseRequestBodyFiles } from '@/views/api-test/components/utils';
|
||||||
|
@ -849,6 +852,20 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function filterConditionsSqlValidParams(condition: ExecuteConditionConfig) {
|
||||||
|
const conditionCopy = cloneDeep(condition);
|
||||||
|
conditionCopy.processors = conditionCopy.processors.map((processor) => {
|
||||||
|
if (processor.processorType === RequestConditionProcessor.SQL) {
|
||||||
|
processor.extractParams = filterKeyValParams(
|
||||||
|
processor.extractParams || [],
|
||||||
|
defaultKeyValueParamItem
|
||||||
|
).validParams;
|
||||||
|
}
|
||||||
|
return processor;
|
||||||
|
});
|
||||||
|
return conditionCopy;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成请求参数
|
* 生成请求参数
|
||||||
* @param executeType 执行类型,执行时传入
|
* @param executeType 执行类型,执行时传入
|
||||||
|
@ -930,12 +947,11 @@
|
||||||
{
|
{
|
||||||
polymorphicName: 'MsCommonElement', // 协议多态名称,写死MsCommonElement
|
polymorphicName: 'MsCommonElement', // 协议多态名称,写死MsCommonElement
|
||||||
assertionConfig: {
|
assertionConfig: {
|
||||||
// TODO:暂时不做断言
|
|
||||||
enableGlobal: false,
|
enableGlobal: false,
|
||||||
assertions: [],
|
assertions: [],
|
||||||
},
|
},
|
||||||
postProcessorConfig: requestVModel.value.children[0].postProcessorConfig,
|
postProcessorConfig: filterConditionsSqlValidParams(requestVModel.value.children[0].postProcessorConfig),
|
||||||
preProcessorConfig: requestVModel.value.children[0].preProcessorConfig,
|
preProcessorConfig: filterConditionsSqlValidParams(requestVModel.value.children[0].preProcessorConfig),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -193,6 +193,15 @@
|
||||||
});
|
});
|
||||||
const activeResponse = ref<ResponseItem>(responseTabs.value[0]);
|
const activeResponse = ref<ResponseItem>(responseTabs.value[0]);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => responseTabs.value,
|
||||||
|
(arr) => {
|
||||||
|
if (arr[0]) {
|
||||||
|
[activeResponse.value] = arr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
function addResponseTab(defaultProps?: Partial<ResponseItem>) {
|
function addResponseTab(defaultProps?: Partial<ResponseItem>) {
|
||||||
responseTabs.value.push({
|
responseTabs.value.push({
|
||||||
...cloneDeep(defaultResponseItem),
|
...cloneDeep(defaultResponseItem),
|
||||||
|
|
|
@ -117,8 +117,8 @@
|
||||||
</div>
|
</div>
|
||||||
<a-spin :loading="props.loading" class="h-[calc(100%-35px)] w-full px-[18px] pb-[18px]">
|
<a-spin :loading="props.loading" class="h-[calc(100%-35px)] w-full px-[18px] pb-[18px]">
|
||||||
<edit
|
<edit
|
||||||
v-if="props.isEdit && activeResponseType === 'content' && props.responseDefinition"
|
v-if="props.isEdit && activeResponseType === 'content' && validResponseDefinition"
|
||||||
:response-definition="props.responseDefinition"
|
:response-definition="validResponseDefinition"
|
||||||
:upload-temp-file-api="props.uploadTempFileApi"
|
:upload-temp-file-api="props.uploadTempFileApi"
|
||||||
@change="handleResponseChange"
|
@change="handleResponseChange"
|
||||||
/>
|
/>
|
||||||
|
@ -144,7 +144,7 @@
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
import { RequestTaskResult } from '@/models/apiTest/common';
|
import { RequestTaskResult } from '@/models/apiTest/common';
|
||||||
import { ResponseComposition } from '@/enums/apiEnum';
|
import { ResponseBodyFormat, ResponseComposition } from '@/enums/apiEnum';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -215,6 +215,45 @@
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
});
|
});
|
||||||
|
// 过滤无效数据后的有效响应数据
|
||||||
|
const validResponseDefinition = computed(() => {
|
||||||
|
return props.responseDefinition?.map((item, i) => {
|
||||||
|
// 某些字段在导入时接口返回 null,需要设置默认值
|
||||||
|
if (!item.headers) {
|
||||||
|
item.headers = [];
|
||||||
|
}
|
||||||
|
if (!item.id) {
|
||||||
|
item.id = new Date().getTime() + i;
|
||||||
|
}
|
||||||
|
if (item.body.bodyType === ResponseBodyFormat.NONE) {
|
||||||
|
item.body.bodyType = ResponseBodyFormat.RAW;
|
||||||
|
}
|
||||||
|
if (!item.body.binaryBody) {
|
||||||
|
item.body.binaryBody = {
|
||||||
|
description: '',
|
||||||
|
file: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!item.body.jsonBody) {
|
||||||
|
item.body.jsonBody = {
|
||||||
|
jsonValue: '',
|
||||||
|
enableJsonSchema: false,
|
||||||
|
enableTransition: false,
|
||||||
|
};
|
||||||
|
if (!item.body.xmlBody) {
|
||||||
|
item.body.xmlBody = {
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!item.body.rawBody) {
|
||||||
|
item.body.rawBody = {
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function handleResponseChange() {
|
function handleResponseChange() {
|
||||||
emit('change');
|
emit('change');
|
||||||
|
@ -229,7 +268,7 @@
|
||||||
watch(
|
watch(
|
||||||
() => props.requestTaskResult,
|
() => props.requestTaskResult,
|
||||||
(task) => {
|
(task) => {
|
||||||
if (task) {
|
if (task?.requestResults[0]?.responseResult?.responseCode) {
|
||||||
setActiveResponse('result');
|
setActiveResponse('result');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,9 +131,9 @@
|
||||||
|
|
||||||
const { copy, isSupported } = useClipboard();
|
const { copy, isSupported } = useClipboard();
|
||||||
|
|
||||||
function copyScript() {
|
async function copyScript() {
|
||||||
if (isSupported) {
|
if (isSupported) {
|
||||||
copy(props.requestResult?.responseResult.body || '');
|
await copy(props.requestResult?.responseResult.body || '');
|
||||||
Message.success(t('common.copySuccess'));
|
Message.success(t('common.copySuccess'));
|
||||||
} else {
|
} else {
|
||||||
Message.warning(t('apiTestDebug.copyNotSupport'));
|
Message.warning(t('apiTestDebug.copyNotSupport'));
|
||||||
|
|
|
@ -3,8 +3,6 @@ import { cloneDeep, isEqual } from 'lodash-es';
|
||||||
import { ExecuteBody } from '@/models/apiTest/common';
|
import { ExecuteBody } from '@/models/apiTest/common';
|
||||||
import { RequestParamsType } from '@/enums/apiEnum';
|
import { RequestParamsType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
export default {};
|
|
||||||
|
|
||||||
export interface ParseResult {
|
export interface ParseResult {
|
||||||
uploadFileIds: string[];
|
uploadFileIds: string[];
|
||||||
linkFileIds: string[];
|
linkFileIds: string[];
|
||||||
|
@ -108,7 +106,7 @@ export function parseRequestBodyFiles(
|
||||||
* @param params 原始参数数组
|
* @param params 原始参数数组
|
||||||
* @param defaultParamItem 默认参数项
|
* @param defaultParamItem 默认参数项
|
||||||
*/
|
*/
|
||||||
export function filterKeyValParams(params: Record<string, any>[], defaultParamItem: Record<string, any>) {
|
export function filterKeyValParams<T>(params: (T & Record<string, any>)[], defaultParamItem: Record<string, any>) {
|
||||||
const lastData = cloneDeep(params[params.length - 1]);
|
const lastData = cloneDeep(params[params.length - 1]);
|
||||||
const defaultParam = cloneDeep(defaultParamItem);
|
const defaultParam = cloneDeep(defaultParamItem);
|
||||||
if (!lastData || !defaultParam) {
|
if (!lastData || !defaultParam) {
|
||||||
|
@ -123,7 +121,7 @@ export function filterKeyValParams(params: Record<string, any>[], defaultParamIt
|
||||||
delete defaultParam.id;
|
delete defaultParam.id;
|
||||||
delete defaultParam.enable;
|
delete defaultParam.enable;
|
||||||
const lastDataIsDefault = isEqual(lastData, defaultParam);
|
const lastDataIsDefault = isEqual(lastData, defaultParam);
|
||||||
let validParams: Record<string, any>[] = [];
|
let validParams: (T & Record<string, any>)[];
|
||||||
if (lastDataIsDefault) {
|
if (lastDataIsDefault) {
|
||||||
// 如果最后一条数据是默认数据,非用户添加更改的,说明是无效参数,删除最后一个
|
// 如果最后一条数据是默认数据,非用户添加更改的,说明是无效参数,删除最后一个
|
||||||
validParams = params.slice(0, params.length - 1);
|
validParams = params.slice(0, params.length - 1);
|
||||||
|
|
|
@ -249,7 +249,6 @@
|
||||||
return {
|
return {
|
||||||
...e,
|
...e,
|
||||||
hideMoreAction: e.id === 'root',
|
hideMoreAction: e.id === 'root',
|
||||||
draggable: e.id !== 'root',
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
rootModulesName.value = folderTree.value.map((e) => e.name || '');
|
rootModulesName.value = folderTree.value.map((e) => e.name || '');
|
||||||
|
@ -381,6 +380,7 @@
|
||||||
}
|
}
|
||||||
if (dropNode.type === 'MODULE' && dragNode?.type === 'API' && dropPosition !== 0) {
|
if (dropNode.type === 'MODULE' && dragNode?.type === 'API' && dropPosition !== 0) {
|
||||||
// API节点不移动到模块的前后位置
|
// API节点不移动到模块的前后位置
|
||||||
|
document.querySelector('.arco-tree-node-title-draggable::before')?.setAttribute('style', 'display: none');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -400,6 +400,10 @@
|
||||||
dropPosition: number
|
dropPosition: number
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
if (dragNode.id === 'root' || (dragNode.type === 'MODULE' && dropNode.id === 'root')) {
|
||||||
|
// 根节点不可拖拽;模块不可拖拽到根节点
|
||||||
|
return;
|
||||||
|
}
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
if (dragNode.type === 'MODULE') {
|
if (dragNode.type === 'MODULE') {
|
||||||
await moveDebugModule({
|
await moveDebugModule({
|
||||||
|
@ -411,8 +415,8 @@
|
||||||
await dragDebug({
|
await dragDebug({
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
moveMode: dropPositionMap[dropPosition],
|
moveMode: dropPositionMap[dropPosition],
|
||||||
moveId: dropNode.id,
|
moveId: dragNode.id,
|
||||||
targetId: dragNode.id,
|
targetId: dropNode.id,
|
||||||
moduleId: dropNode.type === 'API' ? dropNode.parentId : dropNode.id, // 释放节点是 API,则传入它所属模块id;模块的话直接是模块id
|
moduleId: dropNode.type === 'API' ? dropNode.parentId : dropNode.id, // 释放节点是 API,则传入它所属模块id;模块的话直接是模块id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,12 +103,12 @@
|
||||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||||
import debug, { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
import debug, { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
|
||||||
|
import { localExecuteApiDebug } from '@/api/modules/api-test/common';
|
||||||
import {
|
import {
|
||||||
addDebug,
|
addDebug,
|
||||||
executeDebug,
|
executeDebug,
|
||||||
getDebugDetail,
|
getDebugDetail,
|
||||||
getTransferOptions,
|
getTransferOptions,
|
||||||
localExecuteApiDebug,
|
|
||||||
transferFile,
|
transferFile,
|
||||||
updateDebug,
|
updateDebug,
|
||||||
uploadTempFile,
|
uploadTempFile,
|
||||||
|
|
|
@ -86,11 +86,10 @@ export default {
|
||||||
'apiTestDebug.quoteSource': 'Reference data source',
|
'apiTestDebug.quoteSource': 'Reference data source',
|
||||||
'apiTestDebug.sourceList': 'Data source list',
|
'apiTestDebug.sourceList': 'Data source list',
|
||||||
'apiTestDebug.quoteSourcePlaceholder': 'Please select a data source',
|
'apiTestDebug.quoteSourcePlaceholder': 'Please select a data source',
|
||||||
'apiTestDebug.storageType': 'Storage method',
|
'apiTestDebug.storageColTip':
|
||||||
'apiTestDebug.storageTypeTip1':
|
'Specify the names of columns extracted from the database result set; multiple columns can be separated by ","',
|
||||||
'Store by column: Specify the names of columns extracted from the database result set; multiple columns can be separated by ","',
|
'apiTestDebug.storageResultTip':
|
||||||
'apiTestDebug.storageTypeTip2':
|
'Save the entire result set as a variable instead of saving each column value as a separate variable',
|
||||||
'Store by result: Save the entire result set as a variable instead of saving each column value as a separate variable',
|
|
||||||
'apiTestDebug.storageByCol': 'Store by columns',
|
'apiTestDebug.storageByCol': 'Store by columns',
|
||||||
'apiTestDebug.storageByColPlaceholder': 'For example, {a} is changed to {b}',
|
'apiTestDebug.storageByColPlaceholder': 'For example, {a} is changed to {b}',
|
||||||
'apiTestDebug.storageByResult': 'Store by result',
|
'apiTestDebug.storageByResult': 'Store by result',
|
||||||
|
|
|
@ -82,9 +82,8 @@ export default {
|
||||||
'apiTestDebug.quoteSource': '引用数据源',
|
'apiTestDebug.quoteSource': '引用数据源',
|
||||||
'apiTestDebug.sourceList': '数据源列表',
|
'apiTestDebug.sourceList': '数据源列表',
|
||||||
'apiTestDebug.quoteSourcePlaceholder': '请选择数据源',
|
'apiTestDebug.quoteSourcePlaceholder': '请选择数据源',
|
||||||
'apiTestDebug.storageType': '存储方式',
|
'apiTestDebug.storageColTip': '指定从数据库结果集中提取的列的名称;多个列可以使用“,”分隔',
|
||||||
'apiTestDebug.storageTypeTip1': '按列存储:指定从数据库结果集中提取的列的名称;多个列可以使用“,”分隔',
|
'apiTestDebug.storageResultTip': '把整个结果集保存为一个变量,而不是将每个列的值保存为单独的变量',
|
||||||
'apiTestDebug.storageTypeTip2': '按结果存储:把整个结果集保存为一个变量,而不是将每个列的值保存为单独的变量',
|
|
||||||
'apiTestDebug.storageByCol': '按列存储',
|
'apiTestDebug.storageByCol': '按列存储',
|
||||||
'apiTestDebug.storageByColPlaceholder': '如 {a} 改成 {b}',
|
'apiTestDebug.storageByColPlaceholder': '如 {a} 改成 {b}',
|
||||||
'apiTestDebug.storageByResult': '按结果存储',
|
'apiTestDebug.storageByResult': '按结果存储',
|
||||||
|
|
|
@ -1,21 +1,24 @@
|
||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
<MsDrawer
|
<MsDrawer
|
||||||
v-model:visible="visible"
|
v-model:visible="visible"
|
||||||
width="100%"
|
width="100%"
|
||||||
:popup-container="props.popupContainer"
|
:popup-container="props.popupContainer"
|
||||||
:closable="false"
|
:closable="false"
|
||||||
:ok-disabled="disabledConfirm"
|
:ok-disabled="disabledConfirm"
|
||||||
|
:ok-text="t('common.import')"
|
||||||
|
:ok-loading="importLoading"
|
||||||
disabled-width-drag
|
disabled-width-drag
|
||||||
no-title
|
desc
|
||||||
@confirm="confirmImport"
|
@confirm="confirmImport"
|
||||||
@cancel="cancelImport"
|
@cancel="cancelImport"
|
||||||
>
|
>
|
||||||
<template #title> </template>
|
<template #title> </template>
|
||||||
<div class="flex items-center justify-between p-[12px_8px]">
|
<div class="flex items-center justify-between p-[12px_8px]">
|
||||||
<div class="font-medium text-[var(--color-text-1)]">{{ t('apiTestManagement.importApi') }}</div>
|
<div class="font-medium text-[var(--color-text-1)]">{{ t('apiTestManagement.importApi') }}</div>
|
||||||
<a-radio-group v-model:model-value="importType" type="button">
|
<a-radio-group v-model:model-value="importForm.type" type="button">
|
||||||
<a-radio value="file">{{ t('apiTestManagement.fileImport') }}</a-radio>
|
<a-radio :value="RequestImportType.API">{{ t('apiTestManagement.fileImport') }}</a-radio>
|
||||||
<a-radio value="time">{{ t('apiTestManagement.timeImport') }}</a-radio>
|
<a-radio :value="RequestImportType.SCHEDULE">{{ t('apiTestManagement.timeImport') }}</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -23,9 +26,9 @@
|
||||||
class="my-[16px] flex items-center gap-[16px] rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[16px]"
|
class="my-[16px] flex items-center gap-[16px] rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[16px]"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="item of importFormatList"
|
v-for="item of platformList"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
:class="`import-item ${importFormat === item.value ? 'import-item--active' : ''}`"
|
:class="`import-item ${importForm.platform === item.value ? 'import-item--active' : ''}`"
|
||||||
@click="() => setActiveImportFormat(item.value)"
|
@click="() => setActiveImportFormat(item.value)"
|
||||||
>
|
>
|
||||||
<div class="flex h-[24px] w-[24px] items-center justify-center rounded-[var(--border-radius-small)] bg-white">
|
<div class="flex h-[24px] w-[24px] items-center justify-center rounded-[var(--border-radius-small)] bg-white">
|
||||||
|
@ -35,10 +38,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a-form ref="importFormRef" :model="importForm" layout="vertical">
|
<a-form ref="importFormRef" :model="importForm" layout="vertical">
|
||||||
<template v-if="importType === 'file'">
|
<template v-if="importForm.type === RequestImportType.API">
|
||||||
<a-form-item :label="t('apiTestManagement.belongModule')">
|
<a-form-item :label="t('apiTestManagement.belongModule')">
|
||||||
<a-tree-select
|
<a-tree-select
|
||||||
v-model:modelValue="importForm.module"
|
v-model:modelValue="importForm.moduleId"
|
||||||
:data="moduleTree"
|
:data="moduleTree"
|
||||||
class="w-[436px]"
|
class="w-[436px]"
|
||||||
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
||||||
|
@ -67,9 +70,9 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<a-select v-model:model-value="importForm.mode" class="w-[240px]">
|
<a-select v-model:model-value="importForm.coverData" class="w-[240px]">
|
||||||
<a-option value="cover">{{ t('apiTestManagement.cover') }}</a-option>
|
<a-option :value="true">{{ t('apiTestManagement.cover') }}</a-option>
|
||||||
<a-option value="uncover">{{ t('apiTestManagement.uncover') }}</a-option>
|
<a-option :value="false">{{ t('apiTestManagement.uncover') }}</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-collapse v-model:active-key="moreSettingActive" :bordered="false" :show-expand-icon="false">
|
<a-collapse v-model:active-key="moreSettingActive" :bordered="false" :show-expand-icon="false">
|
||||||
|
@ -85,24 +88,24 @@
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</template>
|
</template>
|
||||||
<div class="mt-[16px]">
|
<div class="mt-[16px]">
|
||||||
<a-checkbox v-model:model-value="importForm.syncImportCase" class="mr-[24px]">
|
<a-checkbox v-model:model-value="importForm.syncCase" class="mr-[24px]">
|
||||||
{{ t('apiTestManagement.syncImportCase') }}
|
{{ t('apiTestManagement.syncImportCase') }}
|
||||||
</a-checkbox>
|
</a-checkbox>
|
||||||
<a-checkbox v-model:model-value="importForm.syncUpdateDirectory">
|
<a-checkbox v-model:model-value="importForm.coverModule">
|
||||||
{{ t('apiTestManagement.syncUpdateDirectory') }}
|
{{ t('apiTestManagement.syncUpdateDirectory') }}
|
||||||
</a-checkbox>
|
</a-checkbox>
|
||||||
</div>
|
</div>
|
||||||
</a-collapse-item>
|
</a-collapse-item>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-form-item :label="t('apiTestManagement.importType')" class="mt-[8px]">
|
<a-form-item :label="t('apiTestManagement.importType')" class="mt-[8px]">
|
||||||
<a-radio-group v-model:model-value="importForm.importType" type="button">
|
<a-radio-group v-model:model-value="importType" type="button">
|
||||||
<a-radio value="file">{{ t('apiTestManagement.fileImport') }}</a-radio>
|
<a-radio value="file">{{ t('apiTestManagement.fileImport') }}</a-radio>
|
||||||
<a-radio value="url">{{ t('apiTestManagement.urlImport') }}</a-radio>
|
<a-radio value="swaggerUrl">{{ t('apiTestManagement.urlImport') }}</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<MsUpload
|
<MsUpload
|
||||||
v-if="importForm.importType === 'file'"
|
v-if="importType === 'file'"
|
||||||
v-model:file-list="importForm.file"
|
v-model:file-list="fileList"
|
||||||
accept="json"
|
accept="json"
|
||||||
:auto-upload="false"
|
:auto-upload="false"
|
||||||
draggable
|
draggable
|
||||||
|
@ -119,45 +122,45 @@
|
||||||
</MsUpload>
|
</MsUpload>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="url"
|
field="swaggerUrl"
|
||||||
label="SwaggerURL"
|
label="SwaggerURL"
|
||||||
asterisk-position="end"
|
asterisk-position="end"
|
||||||
:rules="[{ required: true, message: t('apiTestManagement.swaggerURLRequired') }]"
|
:rules="[{ required: true, message: t('apiTestManagement.swaggerURLRequired') }]"
|
||||||
>
|
>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:model-value="importForm.url"
|
v-model:model-value="importForm.swaggerUrl"
|
||||||
:placeholder="t('apiTestManagement.urlImportPlaceholder')"
|
:placeholder="t('apiTestManagement.urlImportPlaceholder')"
|
||||||
class="w-[700px]"
|
class="w-[700px]"
|
||||||
allow-clear
|
allow-clear
|
||||||
></a-input>
|
></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<div class="mb-[16px] flex items-center gap-[8px]">
|
<div class="mb-[16px] flex items-center gap-[8px]">
|
||||||
<a-switch v-model:model-value="importForm.basicAuth" type="line" size="small"></a-switch>
|
<a-switch v-model:model-value="importForm.authSwitch" type="line" size="small"></a-switch>
|
||||||
{{ t('apiTestManagement.basicAuth') }}
|
{{ t('apiTestManagement.basicAuth') }}
|
||||||
</div>
|
</div>
|
||||||
<template v-if="importForm.basicAuth">
|
<template v-if="importForm.authSwitch">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="account"
|
field="authUsername"
|
||||||
:label="t('apiTestManagement.account')"
|
:label="t('apiTestManagement.account')"
|
||||||
asterisk-position="end"
|
asterisk-position="end"
|
||||||
:rules="[{ required: true, message: t('apiTestManagement.accountRequired') }]"
|
:rules="[{ required: true, message: t('apiTestManagement.accountRequired') }]"
|
||||||
>
|
>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:model-value="importForm.account"
|
v-model:model-value="importForm.authUsername"
|
||||||
:placeholder="t('common.pleaseInput')"
|
:placeholder="t('common.pleaseInput')"
|
||||||
class="w-[500px]"
|
class="w-[500px]"
|
||||||
allow-clear
|
allow-clear
|
||||||
></a-input>
|
></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="password"
|
field="authPassword"
|
||||||
:label="t('apiTestManagement.password')"
|
:label="t('apiTestManagement.password')"
|
||||||
asterisk-position="end"
|
asterisk-position="end"
|
||||||
:rules="[{ required: true, message: t('apiTestManagement.passwordRequired') }]"
|
:rules="[{ required: true, message: t('apiTestManagement.passwordRequired') }]"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
>
|
>
|
||||||
<a-input-password
|
<a-input-password
|
||||||
v-model:model-value="importForm.password"
|
v-model:model-value="importForm.authPassword"
|
||||||
:placeholder="t('common.pleaseInput')"
|
:placeholder="t('common.pleaseInput')"
|
||||||
class="w-[500px]"
|
class="w-[500px]"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
|
@ -169,60 +172,62 @@
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="taskName"
|
field="name"
|
||||||
:label="t('apiTestManagement.taskName')"
|
:label="t('apiTestManagement.taskName')"
|
||||||
:rules="[{ required: true, message: t('apiTestManagement.taskNameRequired') }]"
|
:rules="[{ required: true, message: t('apiTestManagement.taskNameRequired') }]"
|
||||||
>
|
>
|
||||||
<div class="flex w-full items-center gap-[8px]">
|
<div class="flex w-full items-center gap-[8px]">
|
||||||
<a-input
|
<a-input
|
||||||
v-model:model-value="importForm.taskName"
|
v-model:model-value="importForm.name"
|
||||||
:placeholder="t('apiTestManagement.taskNamePlaceholder')"
|
:placeholder="t('apiTestManagement.taskNamePlaceholder')"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
class="flex-1"
|
class="flex-1"
|
||||||
></a-input>
|
></a-input>
|
||||||
<MsButton type="text">{{ t('apiTestManagement.timeTaskList') }}</MsButton>
|
<MsButton type="text" @click="taskDrawerVisible = true">{{
|
||||||
|
t('apiTestManagement.timeTaskList')
|
||||||
|
}}</MsButton>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="url"
|
field="swaggerUrl"
|
||||||
label="SwaggerURL"
|
label="SwaggerURL"
|
||||||
asterisk-position="end"
|
asterisk-position="end"
|
||||||
:rules="[{ required: true, message: t('apiTestManagement.swaggerURLRequired') }]"
|
:rules="[{ required: true, message: t('apiTestManagement.swaggerURLRequired') }]"
|
||||||
>
|
>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:model-value="importForm.url"
|
v-model:model-value="importForm.swaggerUrl"
|
||||||
:placeholder="t('apiTestManagement.urlImportPlaceholder')"
|
:placeholder="t('apiTestManagement.urlImportPlaceholder')"
|
||||||
class="w-[700px]"
|
class="w-[700px]"
|
||||||
allow-clear
|
allow-clear
|
||||||
></a-input>
|
></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<div class="mb-[16px] flex items-center gap-[8px]">
|
<div class="mb-[16px] flex items-center gap-[8px]">
|
||||||
<a-switch v-model:model-value="importForm.basicAuth" type="line" size="small"></a-switch>
|
<a-switch v-model:model-value="importForm.authSwitch" type="line" size="small"></a-switch>
|
||||||
{{ t('apiTestManagement.basicAuth') }}
|
{{ t('apiTestManagement.basicAuth') }}
|
||||||
</div>
|
</div>
|
||||||
<template v-if="importForm.basicAuth">
|
<template v-if="importForm.authSwitch">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="account"
|
field="authUsername"
|
||||||
:label="t('apiTestManagement.account')"
|
:label="t('apiTestManagement.account')"
|
||||||
:rules="[{ required: true, message: t('apiTestManagement.accountRequired') }]"
|
:rules="[{ required: true, message: t('apiTestManagement.accountRequired') }]"
|
||||||
asterisk-position="end"
|
asterisk-position="end"
|
||||||
>
|
>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:model-value="importForm.account"
|
v-model:model-value="importForm.authUsername"
|
||||||
:placeholder="t('common.pleaseInput')"
|
:placeholder="t('common.pleaseInput')"
|
||||||
class="w-[500px]"
|
class="w-[500px]"
|
||||||
allow-clear
|
allow-clear
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="password"
|
field="authPassword"
|
||||||
:label="t('apiTestManagement.password')"
|
:label="t('apiTestManagement.password')"
|
||||||
:rules="[{ required: true, message: t('apiTestManagement.passwordRequired') }]"
|
:rules="[{ required: true, message: t('apiTestManagement.passwordRequired') }]"
|
||||||
asterisk-position="end"
|
asterisk-position="end"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
>
|
>
|
||||||
<a-input-password
|
<a-input-password
|
||||||
v-model:model-value="importForm.password"
|
v-model:model-value="importForm.authPassword"
|
||||||
:placeholder="t('common.pleaseInput')"
|
:placeholder="t('common.pleaseInput')"
|
||||||
class="w-[500px]"
|
class="w-[500px]"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
|
@ -232,7 +237,7 @@
|
||||||
</template>
|
</template>
|
||||||
<a-form-item :label="t('apiTestManagement.belongModule')">
|
<a-form-item :label="t('apiTestManagement.belongModule')">
|
||||||
<a-tree-select
|
<a-tree-select
|
||||||
v-model:modelValue="importForm.module"
|
v-model:modelValue="importForm.moduleId"
|
||||||
:data="moduleTree"
|
:data="moduleTree"
|
||||||
class="w-[436px]"
|
class="w-[436px]"
|
||||||
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
||||||
|
@ -261,13 +266,13 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<a-select v-model:model-value="importForm.mode" class="w-[240px]">
|
<a-select v-model:model-value="importForm.coverData" class="w-[240px]">
|
||||||
<a-option value="cover">{{ t('apiTestManagement.cover') }}</a-option>
|
<a-option :value="true">{{ t('apiTestManagement.cover') }}</a-option>
|
||||||
<a-option value="uncover">{{ t('apiTestManagement.uncover') }}</a-option>
|
<a-option :value="false">{{ t('apiTestManagement.uncover') }}</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('apiTestManagement.syncFrequency')">
|
<a-form-item :label="t('apiTestManagement.syncFrequency')">
|
||||||
<a-select v-model:model-value="importForm.syncFrequency" class="w-[240px]">
|
<a-select v-model:model-value="cronValue" class="w-[240px]">
|
||||||
<template #label="{ data }">
|
<template #label="{ data }">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
{{ data.value }}
|
{{ data.value }}
|
||||||
|
@ -280,46 +285,88 @@
|
||||||
<div class="ml-[4px] text-[var(--color-text-4)]">{{ item.label }}</div>
|
<div class="ml-[4px] text-[var(--color-text-4)]">{{ item.label }}</div>
|
||||||
</div>
|
</div>
|
||||||
</a-option>
|
</a-option>
|
||||||
<template #footer>
|
<!-- TODO:第一版不做自定义 -->
|
||||||
|
<!-- <template #footer>
|
||||||
<div class="flex items-center p-[4px_8px]">
|
<div class="flex items-center p-[4px_8px]">
|
||||||
<MsButton type="text">{{ t('apiTestManagement.customFrequency') }}</MsButton>
|
<MsButton type="text">{{ t('apiTestManagement.customFrequency') }}</MsButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template> -->
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</a-form>
|
</a-form>
|
||||||
</MsDrawer>
|
</MsDrawer>
|
||||||
|
<MsDrawer v-model:visible="taskDrawerVisible" :width="960" :title="t('apiTestManagement.timeTask')" :footer="false">
|
||||||
|
<div class="mb-[16px] flex items-center justify-between">
|
||||||
|
{{ t('apiTestManagement.timeTaskList') }}
|
||||||
|
<a-input-search
|
||||||
|
v-model:model-value="keyword"
|
||||||
|
:placeholder="t('apiTestManagement.searchPlaceholder')"
|
||||||
|
allow-clear
|
||||||
|
class="mr-[8px] w-[240px]"
|
||||||
|
@search="loadTaskList"
|
||||||
|
@press-enter="loadTaskList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
|
||||||
|
<template #action="{ record }">
|
||||||
|
<a-switch
|
||||||
|
v-model:modelValue="record.enable"
|
||||||
|
type="line"
|
||||||
|
size="small"
|
||||||
|
:before-change="() => handleBeforeEnableChange(record)"
|
||||||
|
></a-switch>
|
||||||
|
</template>
|
||||||
|
</ms-base-table>
|
||||||
|
</MsDrawer>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
import { FormInstance, Message } from '@arco-design/web-vue';
|
import { FormInstance, Message } from '@arco-design/web-vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
|
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||||
|
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||||
|
import useTable from '@/components/pure/ms-table/useTable';
|
||||||
import MsUpload from '@/components/pure/ms-upload/index.vue';
|
import MsUpload from '@/components/pure/ms-upload/index.vue';
|
||||||
|
import type { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createDefinitionSchedule,
|
||||||
|
importDefinition,
|
||||||
|
switchDefinitionSchedule,
|
||||||
|
} from '@/api/modules/api-test/management';
|
||||||
|
import { getScheduleProApiCaseList } from '@/api/modules/project-management/taskCenter';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import useAppStore from '@/store/modules/app';
|
||||||
|
import useUserStore from '@/store/modules/user';
|
||||||
import { mapTree } from '@/utils';
|
import { mapTree } from '@/utils';
|
||||||
|
|
||||||
import { ModuleTreeNode } from '@/models/common';
|
import type { ImportApiDefinitionParams, ImportApiDefinitionRequest } from '@/models/apiTest/management';
|
||||||
import { RequestImportFormat } from '@/enums/apiEnum';
|
import type { ModuleTreeNode } from '@/models/common';
|
||||||
|
import { TimingTaskCenterApiCaseItem } from '@/models/projectManagement/taskCenter';
|
||||||
|
import { RequestImportFormat, RequestImportType } from '@/enums/apiEnum';
|
||||||
|
import { TaskCenterEnum } from '@/enums/taskCenter';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
moduleTree: ModuleTreeNode[];
|
moduleTree: ModuleTreeNode[];
|
||||||
popupContainer?: string;
|
popupContainer?: string;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits(['update:visible']);
|
const emit = defineEmits(['update:visible', 'done']);
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const visible = useVModel(props, 'visible', emit);
|
const visible = useVModel(props, 'visible', emit);
|
||||||
const importType = ref<'file' | 'time'>('file');
|
const importType = ref<'file' | 'time'>('file');
|
||||||
const importFormat = ref<keyof typeof RequestImportFormat>('SWAGGER');
|
const platformList = [
|
||||||
const importFormatList = [
|
|
||||||
{
|
{
|
||||||
name: 'Swagger',
|
name: 'Swagger',
|
||||||
value: RequestImportFormat.SWAGGER,
|
value: RequestImportFormat.SWAGGER,
|
||||||
|
@ -327,64 +374,232 @@
|
||||||
iconColor: 'rgb(var(--success-7))',
|
iconColor: 'rgb(var(--success-7))',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
const fileList = ref<MsFileItem[]>([]);
|
||||||
function setActiveImportFormat(format: RequestImportFormat) {
|
const defaultForm: ImportApiDefinitionRequest = {
|
||||||
importFormat.value = format;
|
platform: RequestImportFormat.SWAGGER,
|
||||||
}
|
name: '',
|
||||||
|
moduleId: 'root',
|
||||||
const defaultForm = {
|
coverData: true,
|
||||||
taskName: '',
|
syncCase: true,
|
||||||
module: 'root',
|
coverModule: false,
|
||||||
mode: 'cover',
|
swaggerUrl: '',
|
||||||
syncImportCase: true,
|
authSwitch: false,
|
||||||
syncUpdateDirectory: false,
|
authUsername: '',
|
||||||
importType: 'file',
|
authPassword: '',
|
||||||
file: [],
|
type: RequestImportType.API,
|
||||||
url: '',
|
userId: userStore.id || '',
|
||||||
basicAuth: false,
|
protocol: 'HTTP',
|
||||||
account: '',
|
projectId: appStore.currentProjectId,
|
||||||
password: '',
|
|
||||||
syncFrequency: '0 0 0/1 * ?',
|
|
||||||
};
|
};
|
||||||
const importForm = ref({ ...defaultForm });
|
const importForm = ref({ ...defaultForm });
|
||||||
const importFormRef = ref<FormInstance>();
|
const importFormRef = ref<FormInstance>();
|
||||||
const moreSettingActive = ref<number[]>([]);
|
const moreSettingActive = ref<number[]>([]);
|
||||||
const disabledConfirm = computed(() => {
|
const disabledConfirm = computed(() => {
|
||||||
|
if (importForm.value.type === RequestImportType.API) {
|
||||||
if (importType.value === 'file') {
|
if (importType.value === 'file') {
|
||||||
if (importForm.value.importType === 'file') {
|
return !fileList.value.length;
|
||||||
return !importForm.value.file.length;
|
|
||||||
}
|
}
|
||||||
return !importForm.value.url;
|
return !importForm.value.swaggerUrl;
|
||||||
}
|
}
|
||||||
return !importForm.value.taskName || !importForm.value.url;
|
return !importForm.value.name || !importForm.value.swaggerUrl;
|
||||||
});
|
});
|
||||||
const moduleTree = computed(() => mapTree(props.moduleTree, (node) => ({ ...node, draggable: false })));
|
const moduleTree = computed(() => mapTree(props.moduleTree, (node) => ({ ...node, draggable: false })));
|
||||||
const syncFrequencyOptions = [
|
const syncFrequencyOptions = [
|
||||||
{ label: t('apiTestManagement.timeTaskHour'), value: '0 0 0/1 * ?' },
|
{ label: t('apiTestManagement.timeTaskHour'), value: '0 0 0/1 * * ? ' },
|
||||||
{ label: t('apiTestManagement.timeTaskSixHour'), value: '0 0 0/6 * ?' },
|
{ label: t('apiTestManagement.timeTaskSixHour'), value: '0 0 0/6 * * ?' },
|
||||||
{ label: t('apiTestManagement.timeTaskTwelveHour'), value: '0 0 0/12 * ?' },
|
{ label: t('apiTestManagement.timeTaskTwelveHour'), value: '0 0 0/12 * * ?' },
|
||||||
{ label: t('apiTestManagement.timeTaskDay'), value: '0 0 0 * ?' },
|
{ label: t('apiTestManagement.timeTaskDay'), value: '0 0 0 * * ?' },
|
||||||
];
|
];
|
||||||
|
const cronValue = ref('0 0 0/1 * * ? ');
|
||||||
|
const importLoading = ref(false);
|
||||||
|
const taskDrawerVisible = ref(false);
|
||||||
|
|
||||||
|
function setActiveImportFormat(format: RequestImportFormat) {
|
||||||
|
importForm.value.platform = format;
|
||||||
|
}
|
||||||
|
|
||||||
function cancelImport() {
|
function cancelImport() {
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
importForm.value = { ...defaultForm };
|
importForm.value = { ...defaultForm };
|
||||||
importFormRef.value?.resetFields();
|
importFormRef.value?.resetFields();
|
||||||
|
importType.value = 'file';
|
||||||
|
fileList.value = [];
|
||||||
|
moreSettingActive.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmImport() {
|
async function importDefinitionByFile() {
|
||||||
importFormRef.value?.validate(async (errors) => {
|
|
||||||
if (!errors) {
|
|
||||||
try {
|
try {
|
||||||
|
importLoading.value = true;
|
||||||
|
let params: ImportApiDefinitionParams;
|
||||||
|
if (importType.value === 'file') {
|
||||||
|
params = {
|
||||||
|
file: fileList.value[0].file || null,
|
||||||
|
request: {
|
||||||
|
type: importForm.value.type,
|
||||||
|
platform: importForm.value.platform,
|
||||||
|
userId: userStore.id || '',
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
coverModule: importForm.value.coverModule,
|
||||||
|
coverData: importForm.value.coverData,
|
||||||
|
syncCase: importForm.value.syncCase,
|
||||||
|
protocol: importForm.value.protocol,
|
||||||
|
moduleId: importForm.value.moduleId,
|
||||||
|
authSwitch: importForm.value.authSwitch,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
params = {
|
||||||
|
file: null,
|
||||||
|
request: {
|
||||||
|
type: importForm.value.type,
|
||||||
|
platform: importForm.value.platform,
|
||||||
|
userId: userStore.id || '',
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
coverModule: importForm.value.coverModule,
|
||||||
|
coverData: importForm.value.coverData,
|
||||||
|
syncCase: importForm.value.syncCase,
|
||||||
|
protocol: importForm.value.protocol,
|
||||||
|
moduleId: importForm.value.moduleId,
|
||||||
|
swaggerUrl: importForm.value.swaggerUrl,
|
||||||
|
authSwitch: importForm.value.authSwitch,
|
||||||
|
authUsername: importForm.value.authUsername,
|
||||||
|
authPassword: importForm.value.authPassword,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
await importDefinition(params);
|
||||||
Message.success(t('common.importSuccess'));
|
Message.success(t('common.importSuccess'));
|
||||||
|
emit('done');
|
||||||
cancelImport();
|
cancelImport();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
importLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function importDefinitionBySchedule() {
|
||||||
|
try {
|
||||||
|
importLoading.value = true;
|
||||||
|
await createDefinitionSchedule({
|
||||||
|
type: importForm.value.type,
|
||||||
|
platform: importForm.value.platform,
|
||||||
|
userId: userStore.id || '',
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
coverModule: importForm.value.coverModule,
|
||||||
|
coverData: importForm.value.coverData,
|
||||||
|
syncCase: importForm.value.syncCase,
|
||||||
|
protocol: importForm.value.protocol,
|
||||||
|
moduleId: importForm.value.moduleId,
|
||||||
|
swaggerUrl: importForm.value.swaggerUrl,
|
||||||
|
authSwitch: importForm.value.authSwitch,
|
||||||
|
authUsername: importForm.value.authUsername,
|
||||||
|
authPassword: importForm.value.authPassword,
|
||||||
|
value: cronValue.value,
|
||||||
|
name: importForm.value.name,
|
||||||
|
});
|
||||||
|
Message.success(t('apiTestManagement.createTaskSuccess'));
|
||||||
|
taskDrawerVisible.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
importLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmImport() {
|
||||||
|
importFormRef.value?.validate((errors) => {
|
||||||
|
if (!errors) {
|
||||||
|
if (importForm.value.type === RequestImportType.API) {
|
||||||
|
importDefinitionByFile();
|
||||||
|
} else {
|
||||||
|
importDefinitionBySchedule();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const keyword = ref('');
|
||||||
|
const columns: MsTableColumn = [
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.name',
|
||||||
|
dataIndex: 'taskName',
|
||||||
|
showTooltip: true,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.taskRunRule',
|
||||||
|
dataIndex: 'value',
|
||||||
|
width: 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.taskNextRunTime',
|
||||||
|
dataIndex: 'nextTime',
|
||||||
|
showTooltip: true,
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.taskOperator',
|
||||||
|
dataIndex: 'createUserName',
|
||||||
|
showTooltip: true,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.taskOperationTime',
|
||||||
|
dataIndex: 'createTime',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'common.operation',
|
||||||
|
slotName: 'action',
|
||||||
|
dataIndex: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(
|
||||||
|
getScheduleProApiCaseList,
|
||||||
|
{
|
||||||
|
columns,
|
||||||
|
scroll: { x: '100%' },
|
||||||
|
},
|
||||||
|
(item) => ({
|
||||||
|
...item,
|
||||||
|
operationTime: dayjs(item.operationTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
function loadTaskList() {
|
||||||
|
setLoadListParams({
|
||||||
|
keyword: keyword.value,
|
||||||
|
moduleType: TaskCenterEnum.API_IMPORT,
|
||||||
|
});
|
||||||
|
loadList();
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => taskDrawerVisible.value,
|
||||||
|
(value) => {
|
||||||
|
if (value) {
|
||||||
|
loadTaskList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
async function handleBeforeEnableChange(record: TimingTaskCenterApiCaseItem) {
|
||||||
|
try {
|
||||||
|
await switchDefinitionSchedule(record.id);
|
||||||
|
Message.success(
|
||||||
|
t(record.enable ? 'apiTestManagement.disableTaskSuccess' : 'apiTestManagement.enableTaskSuccess')
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="['p-[16px_22px]', props.class]">
|
<div :class="['p-[16px_22px]', props.class]">
|
||||||
<div class="mb-[16px] flex items-center justify-between">
|
<div class="mb-[16px] flex items-center justify-between">
|
||||||
<div v-if="!props.readOnly" class="flex items-center gap-[8px]">
|
|
||||||
<a-switch v-model:model-value="showSubdirectory" size="small" type="line"></a-switch>
|
|
||||||
{{ t('apiTestManagement.showSubdirectory') }}
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-[8px]">
|
<div class="flex items-center gap-[8px]">
|
||||||
<a-input-search
|
<a-input-search
|
||||||
v-model:model-value="keyword"
|
v-model:model-value="keyword"
|
||||||
|
@ -37,7 +33,7 @@
|
||||||
trigger="click"
|
trigger="click"
|
||||||
@popup-visible-change="handleFilterHidden"
|
@popup-visible-change="handleFilterHidden"
|
||||||
>
|
>
|
||||||
<MsButton type="text" class="arco-btn-text--secondary" @click="methodFilterVisible = true">
|
<MsButton type="text" class="arco-btn-text--secondary ml-[10px]" @click="methodFilterVisible = true">
|
||||||
{{ t(columnConfig.title as string) }}
|
{{ t(columnConfig.title as string) }}
|
||||||
<icon-down :class="methodFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
|
<icon-down :class="methodFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
|
||||||
</MsButton>
|
</MsButton>
|
||||||
|
@ -81,12 +77,22 @@
|
||||||
<MsButton type="text" @click="openApiTab(record)">{{ record.num }}</MsButton>
|
<MsButton type="text" @click="openApiTab(record)">{{ record.num }}</MsButton>
|
||||||
</template>
|
</template>
|
||||||
<template #method="{ record }">
|
<template #method="{ record }">
|
||||||
|
<a-select
|
||||||
|
v-model:model-value="record.method"
|
||||||
|
class="param-input w-full"
|
||||||
|
@change="() => handleMethodChange(record)"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
<apiMethodName :method="record.method" is-tag />
|
<apiMethodName :method="record.method" is-tag />
|
||||||
</template>
|
</template>
|
||||||
|
<a-option v-for="item of Object.values(RequestMethods)" :key="item" :value="item">
|
||||||
|
<apiMethodName :method="item" is-tag />
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
<template #status="{ record }">
|
<template #status="{ record }">
|
||||||
<a-select
|
<a-select
|
||||||
v-model:model-value="record.status"
|
v-model:model-value="record.status"
|
||||||
:placeholder="t('common.pleaseSelect')"
|
|
||||||
class="param-input w-full"
|
class="param-input w-full"
|
||||||
@change="() => handleStatusChange(record)"
|
@change="() => handleStatusChange(record)"
|
||||||
>
|
>
|
||||||
|
@ -103,7 +109,7 @@
|
||||||
{{ t('apiTestManagement.execute') }}
|
{{ t('apiTestManagement.execute') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||||
<MsButton type="text" class="!mr-0">
|
<MsButton type="text" class="!mr-0" @click="copyDefinition(record)">
|
||||||
{{ t('common.copy') }}
|
{{ t('common.copy') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||||
|
@ -136,12 +142,14 @@
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
v-if="batchForm.attr === 'tag'"
|
v-if="batchForm.attr === 'tags'"
|
||||||
field="values"
|
field="values"
|
||||||
:label="t('apiTestManagement.batchUpdate')"
|
:label="t('apiTestManagement.batchUpdate')"
|
||||||
|
:validate-trigger="['blur', 'input']"
|
||||||
:rules="[{ required: true, message: t('apiTestManagement.valueRequired') }]"
|
:rules="[{ required: true, message: t('apiTestManagement.valueRequired') }]"
|
||||||
asterisk-position="end"
|
asterisk-position="end"
|
||||||
class="mb-0"
|
class="mb-0"
|
||||||
|
required
|
||||||
>
|
>
|
||||||
<MsTagsInput
|
<MsTagsInput
|
||||||
v-model:model-value="batchForm.values"
|
v-model:model-value="batchForm.values"
|
||||||
|
@ -159,11 +167,7 @@
|
||||||
asterisk-position="end"
|
asterisk-position="end"
|
||||||
class="mb-0"
|
class="mb-0"
|
||||||
>
|
>
|
||||||
<apiMethodSelect
|
<apiMethodSelect v-if="batchForm.attr === 'method'" v-model:model-value="batchForm.value" />
|
||||||
v-if="batchForm.attr === 'type'"
|
|
||||||
v-model:model-value="batchForm.value"
|
|
||||||
@change="handleActiveDebugChange"
|
|
||||||
/>
|
|
||||||
<a-select
|
<a-select
|
||||||
v-else
|
v-else
|
||||||
v-model="batchForm.value"
|
v-model="batchForm.value"
|
||||||
|
@ -218,7 +222,7 @@
|
||||||
<moduleTree
|
<moduleTree
|
||||||
v-if="moveModalVisible"
|
v-if="moveModalVisible"
|
||||||
:is-expand-all="true"
|
:is-expand-all="true"
|
||||||
is-modal
|
:is-modal="true"
|
||||||
:active-module="props.activeModule"
|
:active-module="props.activeModule"
|
||||||
@folder-node-select="folderNodeSelect"
|
@folder-node-select="folderNodeSelect"
|
||||||
/>
|
/>
|
||||||
|
@ -241,7 +245,14 @@
|
||||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||||
import moduleTree from '@/views/api-test/management/components/moduleTree.vue';
|
import moduleTree from '@/views/api-test/management/components/moduleTree.vue';
|
||||||
|
|
||||||
import { deleteDefinition, getDefinitionPage } from '@/api/modules/api-test/management';
|
import {
|
||||||
|
batchDeleteDefinition,
|
||||||
|
batchMoveDefinition,
|
||||||
|
batchUpdateDefinition,
|
||||||
|
deleteDefinition,
|
||||||
|
getDefinitionPage,
|
||||||
|
updateDefinition,
|
||||||
|
} from '@/api/modules/api-test/management';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useModal from '@/hooks/useModal';
|
import useModal from '@/hooks/useModal';
|
||||||
import useTableStore from '@/hooks/useTableStore';
|
import useTableStore from '@/hooks/useTableStore';
|
||||||
|
@ -259,39 +270,16 @@
|
||||||
readOnly?: boolean; // 是否是只读模式
|
readOnly?: boolean; // 是否是只读模式
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'init', params: any): void;
|
|
||||||
(e: 'change'): void;
|
|
||||||
(e: 'openApiTab', record: ApiDefinitionDetail): void;
|
(e: 'openApiTab', record: ApiDefinitionDetail): void;
|
||||||
|
(e: 'openCopyApiTab', record: ApiDefinitionDetail): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
|
|
||||||
function handleActiveDebugChange() {
|
const folderTreePathMap = inject('folderTreePathMap');
|
||||||
emit('change');
|
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
|
||||||
}
|
|
||||||
|
|
||||||
const showSubdirectory = ref(false);
|
|
||||||
const checkedEnv = ref('DEV');
|
|
||||||
const envOptions = ref([
|
|
||||||
{
|
|
||||||
label: 'DEV',
|
|
||||||
value: 'DEV',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'TEST',
|
|
||||||
value: 'TEST',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'PRE',
|
|
||||||
value: 'PRE',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'PROD',
|
|
||||||
value: 'PROD',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
|
|
||||||
let columns: MsTableColumn = [
|
let columns: MsTableColumn = [
|
||||||
|
@ -322,7 +310,7 @@
|
||||||
dataIndex: 'method',
|
dataIndex: 'method',
|
||||||
slotName: 'method',
|
slotName: 'method',
|
||||||
titleSlotName: 'methodFilter',
|
titleSlotName: 'methodFilter',
|
||||||
width: 120,
|
width: 140,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'apiTestManagement.apiStatus',
|
title: 'apiTestManagement.apiStatus',
|
||||||
|
@ -333,7 +321,6 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'apiTestManagement.path',
|
title: 'apiTestManagement.path',
|
||||||
slotName: 'path',
|
|
||||||
dataIndex: 'path',
|
dataIndex: 'path',
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
width: 200,
|
width: 200,
|
||||||
|
@ -386,19 +373,21 @@
|
||||||
selectable: true,
|
selectable: true,
|
||||||
showSelectAll: !props.readOnly,
|
showSelectAll: !props.readOnly,
|
||||||
draggable: props.readOnly ? undefined : { type: 'handle', width: 32 },
|
draggable: props.readOnly ? undefined : { type: 'handle', width: 32 },
|
||||||
|
heightUsed: 374,
|
||||||
},
|
},
|
||||||
(item) => ({
|
(item) => ({
|
||||||
...item,
|
...item,
|
||||||
|
fullPath: folderTreePathMap?.[item.moduleId],
|
||||||
createTime: dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss'),
|
createTime: dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
updateTime: dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss'),
|
updateTime: dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const batchActions = {
|
const batchActions = {
|
||||||
baseAction: [
|
baseAction: [
|
||||||
{
|
// {
|
||||||
label: 'common.export',
|
// label: 'common.export',
|
||||||
eventTag: 'export',
|
// eventTag: 'export',
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
label: 'common.edit',
|
label: 'common.edit',
|
||||||
eventTag: 'edit',
|
eventTag: 'edit',
|
||||||
|
@ -432,10 +421,6 @@
|
||||||
if (props.activeModule === 'all') {
|
if (props.activeModule === 'all') {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
if (showSubdirectory.value) {
|
|
||||||
// 显示子目录接口
|
|
||||||
return [props.activeModule, ...props.offspringIds];
|
|
||||||
}
|
|
||||||
return [props.activeModule];
|
return [props.activeModule];
|
||||||
});
|
});
|
||||||
const tableQueryParams = ref<any>();
|
const tableQueryParams = ref<any>();
|
||||||
|
@ -444,9 +429,8 @@
|
||||||
keyword: keyword.value,
|
keyword: keyword.value,
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
moduleIds: moduleIds.value,
|
moduleIds: moduleIds.value,
|
||||||
env: checkedEnv.value,
|
|
||||||
protocol: props.protocol,
|
protocol: props.protocol,
|
||||||
filter: { status: statusFilters.value, type: methodFilters.value },
|
filter: { status: statusFilters.value, method: methodFilters.value },
|
||||||
};
|
};
|
||||||
setLoadListParams(params);
|
setLoadListParams(params);
|
||||||
loadList();
|
loadList();
|
||||||
|
@ -455,14 +439,12 @@
|
||||||
current: propsRes.value.msPagination?.current,
|
current: propsRes.value.msPagination?.current,
|
||||||
pageSize: propsRes.value.msPagination?.pageSize,
|
pageSize: propsRes.value.msPagination?.pageSize,
|
||||||
};
|
};
|
||||||
emit('init', {
|
|
||||||
...tableQueryParams.value,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.activeModule,
|
() => props.activeModule,
|
||||||
() => {
|
() => {
|
||||||
|
resetSelector();
|
||||||
loadApiList();
|
loadApiList();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -470,6 +452,7 @@
|
||||||
watch(
|
watch(
|
||||||
() => props.protocol,
|
() => props.protocol,
|
||||||
() => {
|
() => {
|
||||||
|
resetSelector();
|
||||||
loadApiList();
|
loadApiList();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -480,8 +463,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleMethodChange(record: ApiDefinitionDetail) {
|
||||||
|
try {
|
||||||
|
await updateDefinition({
|
||||||
|
id: record.id,
|
||||||
|
method: record.method,
|
||||||
|
});
|
||||||
|
Message.success(t('common.updateSuccess'));
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleStatusChange(record: ApiDefinitionDetail) {
|
async function handleStatusChange(record: ApiDefinitionDetail) {
|
||||||
try {
|
try {
|
||||||
|
await updateDefinition({
|
||||||
|
id: record.id,
|
||||||
|
status: record.status,
|
||||||
|
});
|
||||||
Message.success(t('common.updateSuccess'));
|
Message.success(t('common.updateSuccess'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
@ -493,16 +493,6 @@
|
||||||
loadApiList();
|
loadApiList();
|
||||||
});
|
});
|
||||||
|
|
||||||
function emitTableParams() {
|
|
||||||
emit('init', {
|
|
||||||
keyword: keyword.value,
|
|
||||||
moduleIds: [],
|
|
||||||
projectId: appStore.currentProjectId,
|
|
||||||
current: propsRes.value.msPagination?.current,
|
|
||||||
pageSize: propsRes.value.msPagination?.pageSize,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableSelected = ref<(string | number)[]>([]);
|
const tableSelected = ref<(string | number)[]>([]);
|
||||||
const batchParams = ref<BatchActionQueryParams>({
|
const batchParams = ref<BatchActionQueryParams>({
|
||||||
selectedIds: [],
|
selectedIds: [],
|
||||||
|
@ -536,20 +526,25 @@
|
||||||
onBeforeOk: async () => {
|
onBeforeOk: async () => {
|
||||||
try {
|
try {
|
||||||
if (isBatch) {
|
if (isBatch) {
|
||||||
// await batchDeleteDefinition({
|
await batchDeleteDefinition({
|
||||||
// selectIds,
|
selectIds,
|
||||||
// selectAll: !!params?.selectAll,
|
selectAll: !!params?.selectAll,
|
||||||
// excludeIds: params?.excludeIds || [],
|
excludeIds: params?.excludeIds || [],
|
||||||
// condition: { keyword: keyword.value },
|
condition: { keyword: keyword.value },
|
||||||
// projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
// moduleIds: props.activeModule === 'all' ? [] : [props.activeModule],
|
moduleIds: props.activeModule === 'all' ? [] : [props.activeModule],
|
||||||
// });
|
deleteAll: true,
|
||||||
|
protocol: props.protocol,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
await deleteDefinition(record?.id as string);
|
await deleteDefinition(record?.id as string);
|
||||||
}
|
}
|
||||||
Message.success(t('common.deleteSuccess'));
|
Message.success(t('common.deleteSuccess'));
|
||||||
resetSelector();
|
resetSelector();
|
||||||
loadList();
|
loadList();
|
||||||
|
if (typeof refreshModuleTree === 'function') {
|
||||||
|
refreshModuleTree();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
@ -588,20 +583,26 @@
|
||||||
value: '',
|
value: '',
|
||||||
values: [],
|
values: [],
|
||||||
});
|
});
|
||||||
const attrOptions = [
|
const fullAttrs = [
|
||||||
{
|
{
|
||||||
name: 'apiTestManagement.apiStatus',
|
name: 'apiTestManagement.apiStatus',
|
||||||
value: 'status',
|
value: 'status',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'apiTestManagement.apiType',
|
name: 'apiTestManagement.apiType',
|
||||||
value: 'type',
|
value: 'method',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'common.tag',
|
name: 'common.tag',
|
||||||
value: 'tag',
|
value: 'tags',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
const attrOptions = computed(() => {
|
||||||
|
if (props.protocol === 'HTTP') {
|
||||||
|
return fullAttrs;
|
||||||
|
}
|
||||||
|
return fullAttrs.filter((e) => e.value !== 'method');
|
||||||
|
});
|
||||||
const valueOptions = computed(() => {
|
const valueOptions = computed(() => {
|
||||||
switch (batchForm.value.attr) {
|
switch (batchForm.value.attr) {
|
||||||
case 'status':
|
case 'status':
|
||||||
|
@ -643,6 +644,17 @@
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
try {
|
try {
|
||||||
batchUpdateLoading.value = true;
|
batchUpdateLoading.value = true;
|
||||||
|
await batchUpdateDefinition({
|
||||||
|
selectIds: batchParams.value?.selectedIds || [],
|
||||||
|
selectAll: !!batchParams.value?.selectAll,
|
||||||
|
excludeIds: batchParams.value?.excludeIds || [],
|
||||||
|
condition: { keyword: keyword.value },
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
moduleIds: props.activeModule === 'all' ? [] : [props.activeModule],
|
||||||
|
protocol: props.protocol,
|
||||||
|
type: batchForm.value.attr,
|
||||||
|
[batchForm.value.attr]: batchForm.value.attr === 'tags' ? batchForm.value.values : batchForm.value.value,
|
||||||
|
});
|
||||||
Message.success(t('common.updateSuccess'));
|
Message.success(t('common.updateSuccess'));
|
||||||
cancelBatch();
|
cancelBatch();
|
||||||
resetSelector();
|
resetSelector();
|
||||||
|
@ -669,16 +681,17 @@
|
||||||
async function handleApiMove() {
|
async function handleApiMove() {
|
||||||
try {
|
try {
|
||||||
batchMoveApiLoading.value = true;
|
batchMoveApiLoading.value = true;
|
||||||
// await batchMoveFile({
|
await batchMoveDefinition({
|
||||||
// selectIds: isBatchMove.value ? batchParams.value?.selectedIds || [] : [activeApi.value?.id || ''],
|
selectIds: isBatchMove.value ? batchParams.value?.selectedIds || [] : [activeApi.value?.id || ''],
|
||||||
// selectAll: !!batchParams.value?.selectAll,
|
selectAll: !!batchParams.value?.selectAll,
|
||||||
// excludeIds: batchParams.value?.excludeIds || [],
|
excludeIds: batchParams.value?.excludeIds || [],
|
||||||
// condition: { keyword: keyword.value },
|
condition: { keyword: keyword.value },
|
||||||
// projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
// moduleIds: props.activeModule === 'all' ? [] : [props.activeModule],
|
moduleIds: props.activeModule === 'all' ? [] : [props.activeModule],
|
||||||
// moveModuleId: selectedModuleKeys.value[0],
|
moduleId: selectedModuleKeys.value[0],
|
||||||
// });
|
protocol: props.protocol,
|
||||||
Message.success(t('apiTestManagement.batchMoveSuccess'));
|
});
|
||||||
|
Message.success(t('common.batchMoveSuccess'));
|
||||||
if (isBatchMove.value) {
|
if (isBatchMove.value) {
|
||||||
tableSelected.value = [];
|
tableSelected.value = [];
|
||||||
isBatchMove.value = false;
|
isBatchMove.value = false;
|
||||||
|
@ -687,7 +700,9 @@
|
||||||
}
|
}
|
||||||
loadList();
|
loadList();
|
||||||
resetSelector();
|
resetSelector();
|
||||||
emitTableParams();
|
if (typeof refreshModuleTree === 'function') {
|
||||||
|
refreshModuleTree();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
@ -735,6 +750,10 @@
|
||||||
emit('openApiTab', record);
|
emit('openApiTab', record);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyDefinition(record: ApiDefinitionDetail) {
|
||||||
|
emit('openCopyApiTab', record);
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
loadApiList,
|
loadApiList,
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
:offspring-ids="props.offspringIds"
|
:offspring-ids="props.offspringIds"
|
||||||
:protocol="props.protocol"
|
:protocol="props.protocol"
|
||||||
@open-api-tab="openApiTab"
|
@open-api-tab="openApiTab"
|
||||||
|
@open-copy-api-tab="openApiTab($event, true)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="activeApiTab.id !== 'all'" class="flex-1 overflow-hidden">
|
<div v-if="activeApiTab.id !== 'all'" class="flex-1 overflow-hidden">
|
||||||
|
@ -49,7 +50,7 @@
|
||||||
hide-response-layout-switch
|
hide-response-layout-switch
|
||||||
:create-api="addDefinition"
|
:create-api="addDefinition"
|
||||||
:update-api="updateDefinition"
|
:update-api="updateDefinition"
|
||||||
:execute-api="executeDebug"
|
:execute-api="debugDefinition"
|
||||||
:local-execute-api="localExecuteApiDebug"
|
:local-execute-api="localExecuteApiDebug"
|
||||||
:permission-map="{
|
:permission-map="{
|
||||||
execute: 'PROJECT_API_DEFINITION:READ+EXECUTE',
|
execute: 'PROJECT_API_DEFINITION:READ+EXECUTE',
|
||||||
|
@ -196,9 +197,10 @@
|
||||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||||
|
|
||||||
import { executeDebug, localExecuteApiDebug } from '@/api/modules/api-test/debug';
|
import { localExecuteApiDebug } from '@/api/modules/api-test/common';
|
||||||
import {
|
import {
|
||||||
addDefinition,
|
addDefinition,
|
||||||
|
debugDefinition,
|
||||||
getDefinitionDetail,
|
getDefinitionDetail,
|
||||||
getTransferOptions,
|
getTransferOptions,
|
||||||
transferFile,
|
transferFile,
|
||||||
|
@ -411,9 +413,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
async function openApiTab(apiInfo: ModuleTreeNode | ApiDefinitionDetail) {
|
async function openApiTab(apiInfo: ModuleTreeNode | ApiDefinitionDetail, isCopy = false) {
|
||||||
const isLoadedTabIndex = apiTabs.value.findIndex((e) => e.id === apiInfo.id);
|
const isLoadedTabIndex = apiTabs.value.findIndex((e) => e.id === apiInfo.id);
|
||||||
if (isLoadedTabIndex > -1) {
|
if (isLoadedTabIndex > -1 && !isCopy) {
|
||||||
// 如果点击的请求在tab中已经存在,则直接切换到该tab
|
// 如果点击的请求在tab中已经存在,则直接切换到该tab
|
||||||
activeApiTab.value = apiTabs.value[isLoadedTabIndex] as RequestParam;
|
activeApiTab.value = apiTabs.value[isLoadedTabIndex] as RequestParam;
|
||||||
return;
|
return;
|
||||||
|
@ -421,14 +423,17 @@
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const res = await getDefinitionDetail(apiInfo.id);
|
const res = await getDefinitionDetail(apiInfo.id);
|
||||||
|
const name = isCopy ? `${res.name}-copy` : res.name;
|
||||||
addApiTab({
|
addApiTab({
|
||||||
label: apiInfo.name,
|
label: name,
|
||||||
...res.request,
|
...res.request,
|
||||||
...res,
|
...res,
|
||||||
response: cloneDeep(defaultResponse),
|
response: cloneDeep(defaultResponse),
|
||||||
|
responseDefinition: res.response.map((e) => ({ ...e, responseActiveTab: ResponseComposition.BODY })),
|
||||||
url: res.path,
|
url: res.path,
|
||||||
name: res.name, // request里面还有个name但是是null
|
name, // request里面还有个name但是是null
|
||||||
isNew: false,
|
isNew: isCopy,
|
||||||
|
unSaved: isCopy,
|
||||||
});
|
});
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 等待内容渲染出来再隐藏loading
|
// 等待内容渲染出来再隐藏loading
|
||||||
|
@ -483,7 +488,7 @@
|
||||||
const splitBoxRef = ref<InstanceType<typeof MsSplitBox>>();
|
const splitBoxRef = ref<InstanceType<typeof MsSplitBox>>();
|
||||||
const activeApiTabFormRef = ref<FormInstance>();
|
const activeApiTabFormRef = ref<FormInstance>();
|
||||||
|
|
||||||
function handleSave(params: ApiDefinitionCreateParams | ApiDefinitionUpdateParams) {
|
function handleSave(params: ApiDefinitionCreateParams) {
|
||||||
activeApiTabFormRef.value?.validate(async (errors) => {
|
activeApiTabFormRef.value?.validate(async (errors) => {
|
||||||
if (errors) {
|
if (errors) {
|
||||||
splitBoxRef.value?.expand();
|
splitBoxRef.value?.expand();
|
||||||
|
@ -521,9 +526,14 @@
|
||||||
console.log(params);
|
console.log(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function refreshTable() {
|
||||||
|
apiTableRef.value?.loadApiList();
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
openApiTab,
|
openApiTab,
|
||||||
addApiTab,
|
addApiTab,
|
||||||
|
refreshTable,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -28,12 +28,14 @@
|
||||||
</template>
|
</template>
|
||||||
</a-button>
|
</a-button>
|
||||||
<MsSelect
|
<MsSelect
|
||||||
v-model:model-value="checkedEnv"
|
v-model:model-value="currentEnv"
|
||||||
mode="static"
|
mode="static"
|
||||||
:options="envOptions"
|
:options="envOptions"
|
||||||
class="!w-[150px]"
|
class="!w-[150px]"
|
||||||
:search-keys="['label']"
|
:search-keys="['label']"
|
||||||
|
:loading="envLoading"
|
||||||
allow-search
|
allow-search
|
||||||
|
@change="initEnvironment"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -41,11 +43,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { SelectOptionData } from '@arco-design/web-vue';
|
||||||
|
|
||||||
import MsSelect from '@/components/business/ms-select';
|
import MsSelect from '@/components/business/ms-select';
|
||||||
import api from './api/index.vue';
|
import api from './api/index.vue';
|
||||||
import MockTable from '@/views/api-test/management/components/management/mock/mockTable.vue';
|
import MockTable from '@/views/api-test/management/components/management/mock/mockTable.vue';
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { getEnvironment, getEnvList } from '@/api/modules/api-test/common';
|
||||||
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
import { ModuleTreeNode } from '@/models/common';
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
|
|
||||||
|
@ -56,7 +61,7 @@
|
||||||
moduleTree: ModuleTreeNode[]; // 模块树
|
moduleTree: ModuleTreeNode[]; // 模块树
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const appStore = useAppStore();
|
||||||
|
|
||||||
const activeTab = ref('api');
|
const activeTab = ref('api');
|
||||||
const apiRef = ref<InstanceType<typeof api>>();
|
const apiRef = ref<InstanceType<typeof api>>();
|
||||||
|
@ -69,28 +74,52 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkedEnv = ref('DEV');
|
const currentEnv = ref('');
|
||||||
const envOptions = ref([
|
const currentEnvConfig = ref({});
|
||||||
{
|
const envLoading = ref(false);
|
||||||
label: 'DEV',
|
const envOptions = ref<SelectOptionData[]>([]);
|
||||||
value: 'DEV',
|
|
||||||
},
|
async function initEnvironment() {
|
||||||
{
|
try {
|
||||||
label: 'TEST',
|
currentEnvConfig.value = await getEnvironment(currentEnv.value);
|
||||||
value: 'TEST',
|
} catch (error) {
|
||||||
},
|
// eslint-disable-next-line no-console
|
||||||
{
|
console.log(error);
|
||||||
label: 'PRE',
|
}
|
||||||
value: 'PRE',
|
}
|
||||||
},
|
|
||||||
{
|
async function initEnvList() {
|
||||||
label: 'PROD',
|
try {
|
||||||
value: 'PROD',
|
envLoading.value = true;
|
||||||
},
|
const res = await getEnvList(appStore.currentProjectId);
|
||||||
]);
|
envOptions.value = res.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
}));
|
||||||
|
currentEnv.value = res[0]?.id || '';
|
||||||
|
initEnvironment();
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
envLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshApiTable() {
|
||||||
|
apiRef.value?.refreshTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
initEnvList();
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 向孙组件提供属性 */
|
||||||
|
provide('currentEnvConfig', readonly(currentEnvConfig));
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
newTab,
|
newTab,
|
||||||
|
refreshApiTable,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<template v-if="!props.isModal">
|
||||||
<a-select
|
<a-select
|
||||||
v-if="!props.readOnly"
|
v-if="!props.readOnly"
|
||||||
v-model:model-value="moduleProtocol"
|
v-model:model-value="moduleProtocol"
|
||||||
|
@ -13,7 +14,9 @@
|
||||||
<a-button type="primary">{{ t('apiTestManagement.newApi') }}</a-button>
|
<a-button type="primary">{{ t('apiTestManagement.newApi') }}</a-button>
|
||||||
<template #content>
|
<template #content>
|
||||||
<a-doption value="newApi">{{ t('apiTestManagement.newApi') }}</a-doption>
|
<a-doption value="newApi">{{ t('apiTestManagement.newApi') }}</a-doption>
|
||||||
<a-doption value="import">{{ t('apiTestManagement.importApi') }}</a-doption>
|
<a-doption v-if="moduleProtocol === 'HTTP'" value="import">
|
||||||
|
{{ t('apiTestManagement.importApi') }}
|
||||||
|
</a-doption>
|
||||||
</template>
|
</template>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
@ -64,6 +67,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a-divider class="my-[8px]" />
|
<a-divider class="my-[8px]" />
|
||||||
|
</template>
|
||||||
|
<a-input
|
||||||
|
v-else
|
||||||
|
v-model:model-value="moduleKeyword"
|
||||||
|
:placeholder="t('apiTestManagement.searchTip')"
|
||||||
|
class="mb-[16px]"
|
||||||
|
allow-clear
|
||||||
|
/>
|
||||||
<a-spin class="min-h-[400px] w-full" :loading="loading">
|
<a-spin class="min-h-[400px] w-full" :loading="loading">
|
||||||
<MsTree
|
<MsTree
|
||||||
v-model:focus-node-key="focusNodeKey"
|
v-model:focus-node-key="focusNodeKey"
|
||||||
|
@ -81,8 +92,9 @@
|
||||||
children: 'children',
|
children: 'children',
|
||||||
count: 'count',
|
count: 'count',
|
||||||
}"
|
}"
|
||||||
:draggable="!props.readOnly"
|
:draggable="!props.readOnly && !props.isModal"
|
||||||
:filter-more-action-func="filterMoreActionFunc"
|
:filter-more-action-func="filterMoreActionFunc"
|
||||||
|
:allow-drop="allowDrop"
|
||||||
block-node
|
block-node
|
||||||
title-tooltip-position="left"
|
title-tooltip-position="left"
|
||||||
@select="folderNodeSelect"
|
@select="folderNodeSelect"
|
||||||
|
@ -99,12 +111,12 @@
|
||||||
<apiMethodName :method="nodeData.attachInfo?.method || nodeData.attachInfo?.protocol" />
|
<apiMethodName :method="nodeData.attachInfo?.method || nodeData.attachInfo?.protocol" />
|
||||||
<div class="one-line-text w-[calc(100%-32px)] text-[var(--color-text-1)]">{{ nodeData.name }}</div>
|
<div class="one-line-text w-[calc(100%-32px)] text-[var(--color-text-1)]">{{ nodeData.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="inline-flex w-full">
|
<div v-else :id="nodeData.id" class="inline-flex w-full">
|
||||||
<div class="one-line-text w-[calc(100%-32px)] text-[var(--color-text-1)]">{{ nodeData.name }}</div>
|
<div class="one-line-text w-[calc(100%-32px)] text-[var(--color-text-1)]">{{ nodeData.name }}</div>
|
||||||
<div class="ml-[4px] text-[var(--color-text-4)]">({{ nodeData.count || 0 }})</div>
|
<div v-if="!props.isModal" class="ml-[4px] text-[var(--color-text-4)]">({{ nodeData.count || 0 }})</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="!props.readOnly" #extra="nodeData">
|
<template v-if="!props.readOnly && !props.isModal" #extra="nodeData">
|
||||||
<!-- 默认模块的 id 是root,默认模块不可编辑、不可添加子模块 -->
|
<!-- 默认模块的 id 是root,默认模块不可编辑、不可添加子模块 -->
|
||||||
<popConfirm
|
<popConfirm
|
||||||
v-if="nodeData.id !== 'root' && nodeData.type === 'MODULE'"
|
v-if="nodeData.id !== 'root' && nodeData.type === 'MODULE'"
|
||||||
|
@ -122,12 +134,13 @@
|
||||||
<popConfirm
|
<popConfirm
|
||||||
v-if="nodeData.id !== 'root'"
|
v-if="nodeData.id !== 'root'"
|
||||||
mode="rename"
|
mode="rename"
|
||||||
|
:node-type="nodeData.type"
|
||||||
:parent-id="nodeData.id"
|
:parent-id="nodeData.id"
|
||||||
:node-id="nodeData.id"
|
:node-id="nodeData.id"
|
||||||
:field-config="{ field: renameFolderTitle }"
|
:field-config="{ field: renameFolderTitle }"
|
||||||
:all-names="(nodeData.children || []).map((e: ModuleTreeNode) => e.name || '')"
|
:all-names="(nodeData.children || []).map((e: ModuleTreeNode) => e.name || '')"
|
||||||
:update-module-api="updateModule"
|
:update-module-api="updateModule"
|
||||||
:update-api-node-api="updateModule"
|
:update-api-node-api="updateDefinition"
|
||||||
@close="resetFocusNodeKey"
|
@close="resetFocusNodeKey"
|
||||||
@rename-finish="initModules"
|
@rename-finish="initModules"
|
||||||
>
|
>
|
||||||
|
@ -159,8 +172,11 @@
|
||||||
getModuleTree,
|
getModuleTree,
|
||||||
getModuleTreeOnlyModules,
|
getModuleTreeOnlyModules,
|
||||||
moveModule,
|
moveModule,
|
||||||
|
sortDefinition,
|
||||||
|
updateDefinition,
|
||||||
updateModule,
|
updateModule,
|
||||||
} from '@/api/modules/api-test/management';
|
} from '@/api/modules/api-test/management';
|
||||||
|
import { dropPositionMap } from '@/config/common';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useModal from '@/hooks/useModal';
|
import useModal from '@/hooks/useModal';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
@ -174,9 +190,12 @@
|
||||||
activeModule?: string | number; // 选中的节点 key
|
activeModule?: string | number; // 选中的节点 key
|
||||||
readOnly?: boolean; // 是否是只读模式
|
readOnly?: boolean; // 是否是只读模式
|
||||||
activeNodeId?: string | number; // 当前选中节点 id
|
activeNodeId?: string | number; // 当前选中节点 id
|
||||||
|
isModal?: boolean; // 是否弹窗模式,只读且只可见模块树
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
activeModule: 'all',
|
activeModule: 'all',
|
||||||
|
readOnly: false,
|
||||||
|
isModal: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const emit = defineEmits(['init', 'newApi', 'import', 'folderNodeSelect', 'clickApiNode', 'changeProtocol']);
|
const emit = defineEmits(['init', 'newApi', 'import', 'folderNodeSelect', 'clickApiNode', 'changeProtocol']);
|
||||||
|
@ -224,7 +243,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const virtualListProps = computed(() => {
|
const virtualListProps = computed(() => {
|
||||||
if (props.readOnly) {
|
if (props.readOnly || props.isModal) {
|
||||||
return {
|
return {
|
||||||
height: 'calc(60vh - 190px)',
|
height: 'calc(60vh - 190px)',
|
||||||
threshold: 200,
|
threshold: 200,
|
||||||
|
@ -335,28 +354,38 @@
|
||||||
moduleIds: [],
|
moduleIds: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (props.readOnly) {
|
const nodePathObj: Record<string, any> = {};
|
||||||
folderTree.value = mapTree<ModuleTreeNode>(res, (e) => {
|
if (props.readOnly || props.isModal) {
|
||||||
|
folderTree.value = mapTree<ModuleTreeNode>(res, (e, fullPath) => {
|
||||||
|
// 拼接当前节点的完整路径
|
||||||
|
nodePathObj[e.id] = {
|
||||||
|
path: e.path,
|
||||||
|
fullPath,
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
...e,
|
...e,
|
||||||
hideMoreAction: true,
|
hideMoreAction: true,
|
||||||
draggable: false,
|
draggable: false,
|
||||||
|
disabled: e.id === selectedKeys.value[0],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
folderTree.value = mapTree<ModuleTreeNode>(res, (e) => {
|
folderTree.value = mapTree<ModuleTreeNode>(res, (e, fullPath) => {
|
||||||
|
// 拼接当前节点的完整路径
|
||||||
|
nodePathObj[e.id] = {
|
||||||
|
path: e.path,
|
||||||
|
fullPath,
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
...e,
|
...e,
|
||||||
hideMoreAction: e.id === 'root',
|
hideMoreAction: e.id === 'root',
|
||||||
draggable: e.id !== 'root',
|
|
||||||
disabled: e.id === selectedKeys.value[0],
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (isSetDefaultKey) {
|
if (isSetDefaultKey) {
|
||||||
selectedKeys.value = [folderTree.value[0].id];
|
selectedKeys.value = [folderTree.value[0].id];
|
||||||
}
|
}
|
||||||
emit('init', folderTree.value, moduleProtocol.value);
|
emit('init', folderTree.value, moduleProtocol.value, nodePathObj);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
@ -378,7 +407,8 @@
|
||||||
return {
|
return {
|
||||||
...node,
|
...node,
|
||||||
count: res[node.id] || 0,
|
count: res[node.id] || 0,
|
||||||
draggable: props.readOnly ? false : node.id !== 'root',
|
draggable: !(props.readOnly || props.isModal),
|
||||||
|
disabled: props.readOnly || props.isModal ? node.id === selectedKeys.value[0] : false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -482,6 +512,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function allowDrop(dropNode: MsTreeNodeData, dropPosition: number, dragNode?: MsTreeNodeData | null) {
|
||||||
|
if (dropNode.type === 'API' && dropPosition === 0) {
|
||||||
|
// API节点不可添加子节点
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dropNode.type === 'MODULE' && dragNode?.type === 'API' && dropPosition !== 0) {
|
||||||
|
// API节点不移动到模块的前后位置
|
||||||
|
document.querySelector('.arco-tree-node-title-draggable::before')?.setAttribute('style', 'display: none');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理文件夹树节点拖拽事件
|
* 处理文件夹树节点拖拽事件
|
||||||
* @param tree 树数据
|
* @param tree 树数据
|
||||||
|
@ -495,13 +538,27 @@
|
||||||
dropNode: MsTreeNodeData,
|
dropNode: MsTreeNodeData,
|
||||||
dropPosition: number
|
dropPosition: number
|
||||||
) {
|
) {
|
||||||
|
if (dragNode.id === 'root' || (dragNode.type === 'MODULE' && dropNode.id === 'root')) {
|
||||||
|
// 根节点不可拖拽;模块不可拖拽到根节点
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
if (dragNode.type === 'MODULE') {
|
||||||
await moveModule({
|
await moveModule({
|
||||||
dragNodeId: dragNode.id as string,
|
dragNodeId: dragNode.id as string,
|
||||||
dropNodeId: dropNode.id || '',
|
dropNodeId: dropNode.id || '',
|
||||||
dropPosition,
|
dropPosition,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
await sortDefinition({
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
moveMode: dropPositionMap[dropPosition],
|
||||||
|
moveId: dragNode.id,
|
||||||
|
targetId: dropNode.id,
|
||||||
|
moduleId: dropNode.type === 'API' ? dropNode.parentId : dropNode.id, // 释放节点是 API,则传入它所属模块id;模块的话直接是模块id
|
||||||
|
});
|
||||||
|
}
|
||||||
Message.success(t('apiTestDebug.moduleMoveSuccess'));
|
Message.success(t('apiTestDebug.moduleMoveSuccess'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
@ -567,4 +624,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
:deep(#root ~ .arco-tree-node-drag-icon) {
|
||||||
|
@apply hidden;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<MsCard simple no-content-padding>
|
<MsCard :min-width="1180" simple no-content-padding>
|
||||||
<MsSplitBox :size="0.25" :max="0.5">
|
<MsSplitBox :size="0.25" :max="0.5">
|
||||||
<template #first>
|
<template #first>
|
||||||
<div class="p-[24px]">
|
<div class="p-[24px]">
|
||||||
|
@ -36,6 +36,7 @@
|
||||||
v-model:visible="importDrawerVisible"
|
v-model:visible="importDrawerVisible"
|
||||||
:module-tree="folderTree"
|
:module-tree="folderTree"
|
||||||
popup-container="#managementContainer"
|
popup-container="#managementContainer"
|
||||||
|
@done="handleImportDone"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<management
|
<management
|
||||||
|
@ -65,6 +66,7 @@
|
||||||
|
|
||||||
const activeModule = ref<string>('all');
|
const activeModule = ref<string>('all');
|
||||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||||
|
const folderTreePathMap = ref<Record<string, any>>({});
|
||||||
const importDrawerVisible = ref(false);
|
const importDrawerVisible = ref(false);
|
||||||
const offspringIds = ref<string[]>([]);
|
const offspringIds = ref<string[]>([]);
|
||||||
const protocol = ref('HTTP');
|
const protocol = ref('HTTP');
|
||||||
|
@ -72,9 +74,10 @@
|
||||||
const moduleTreeRef = ref<InstanceType<typeof moduleTree>>();
|
const moduleTreeRef = ref<InstanceType<typeof moduleTree>>();
|
||||||
const managementRef = ref<InstanceType<typeof management>>();
|
const managementRef = ref<InstanceType<typeof management>>();
|
||||||
|
|
||||||
function handleModuleInit(tree, _protocol: string) {
|
function handleModuleInit(tree, _protocol: string, pathMap: Record<string, any>) {
|
||||||
folderTree.value = tree;
|
folderTree.value = tree;
|
||||||
protocol.value = _protocol;
|
protocol.value = _protocol;
|
||||||
|
folderTreePathMap.value = pathMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
function newApi() {
|
function newApi() {
|
||||||
|
@ -102,9 +105,15 @@
|
||||||
moduleTreeRef.value?.refresh();
|
moduleTreeRef.value?.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 向子孙组件提供方法 */
|
function handleImportDone() {
|
||||||
|
refreshModuleTree();
|
||||||
|
managementRef.value?.refreshApiTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 向子孙组件提供方法和值 */
|
||||||
provide('setActiveApi', setActiveApi);
|
provide('setActiveApi', setActiveApi);
|
||||||
provide('refreshModuleTree', refreshModuleTree);
|
provide('refreshModuleTree', refreshModuleTree);
|
||||||
|
provide('folderTreePathMap', folderTreePathMap.value);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped></style>
|
||||||
|
|
|
@ -3,6 +3,15 @@ export default {
|
||||||
'apiTestManagement.importApi': 'Import api',
|
'apiTestManagement.importApi': 'Import api',
|
||||||
'apiTestManagement.fileImport': 'Import file',
|
'apiTestManagement.fileImport': 'Import file',
|
||||||
'apiTestManagement.timeImport': 'Scheduled import',
|
'apiTestManagement.timeImport': 'Scheduled import',
|
||||||
|
'apiTestManagement.timeTask': 'Timed tasks',
|
||||||
|
'apiTestManagement.name': 'Task name',
|
||||||
|
'apiTestManagement.taskRunRule': 'Run rules',
|
||||||
|
'apiTestManagement.taskNextRunTime': 'Next execution time',
|
||||||
|
'apiTestManagement.taskOperator': 'Operator',
|
||||||
|
'apiTestManagement.taskOperationTime': 'Operating time',
|
||||||
|
'apiTestManagement.createTaskSuccess': 'Create scheduled import task successfully',
|
||||||
|
'apiTestManagement.enableTaskSuccess': 'Start scheduled import task successfully',
|
||||||
|
'apiTestManagement.disableTaskSuccess': 'Closing the scheduled import task successfully',
|
||||||
'apiTestManagement.addSubModule': 'Add submodule',
|
'apiTestManagement.addSubModule': 'Add submodule',
|
||||||
'apiTestManagement.allApi': 'All api',
|
'apiTestManagement.allApi': 'All api',
|
||||||
'apiTestManagement.searchTip': 'Please enter module/api name',
|
'apiTestManagement.searchTip': 'Please enter module/api name',
|
||||||
|
|
|
@ -3,6 +3,15 @@ export default {
|
||||||
'apiTestManagement.importApi': '导入接口',
|
'apiTestManagement.importApi': '导入接口',
|
||||||
'apiTestManagement.fileImport': '文件导入',
|
'apiTestManagement.fileImport': '文件导入',
|
||||||
'apiTestManagement.timeImport': '定时导入',
|
'apiTestManagement.timeImport': '定时导入',
|
||||||
|
'apiTestManagement.timeTask': '定时任务',
|
||||||
|
'apiTestManagement.name': '名称',
|
||||||
|
'apiTestManagement.taskRunRule': '运行规则',
|
||||||
|
'apiTestManagement.taskNextRunTime': '下次执行时间',
|
||||||
|
'apiTestManagement.taskOperator': '操作人',
|
||||||
|
'apiTestManagement.taskOperationTime': '操作时间',
|
||||||
|
'apiTestManagement.createTaskSuccess': '创建定时导入任务成功',
|
||||||
|
'apiTestManagement.enableTaskSuccess': '开启定时导入任务成功',
|
||||||
|
'apiTestManagement.disableTaskSuccess': '关闭定时导入任务成功',
|
||||||
'apiTestManagement.addSubModule': '添加子模块',
|
'apiTestManagement.addSubModule': '添加子模块',
|
||||||
'apiTestManagement.allApi': '全部接口',
|
'apiTestManagement.allApi': '全部接口',
|
||||||
'apiTestManagement.searchTip': '请输入模块/接口名称',
|
'apiTestManagement.searchTip': '请输入模块/接口名称',
|
||||||
|
|
|
@ -106,7 +106,7 @@
|
||||||
<BugCaseTab
|
<BugCaseTab
|
||||||
v-else-if="activeTab === 'case'"
|
v-else-if="activeTab === 'case'"
|
||||||
:bug-id="detailInfo.id"
|
:bug-id="detailInfo.id"
|
||||||
@updateCaseSuccess="updateSuccess"
|
@update-case-success="updateSuccess"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CommentTab v-else-if="activeTab === 'comment'" ref="commentRef" :bug-id="detailInfo.id" />
|
<CommentTab v-else-if="activeTab === 'comment'" ref="commentRef" :bug-id="detailInfo.id" />
|
||||||
|
@ -170,7 +170,7 @@
|
||||||
import MsTab from '@/components/pure/ms-tab/index.vue';
|
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||||
import type { MsPaginationI } from '@/components/pure/ms-table/type';
|
import type { MsPaginationI } from '@/components/pure/ms-table/type';
|
||||||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||||
import { CommentInput } from '@/components/business/ms-comment';
|
import CommentInput from '@/components/business/ms-comment/input.vue';
|
||||||
import { CommentParams } from '@/components/business/ms-comment/types';
|
import { CommentParams } from '@/components/business/ms-comment/types';
|
||||||
import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue';
|
import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue';
|
||||||
import BugCaseTab from './bugCaseTab.vue';
|
import BugCaseTab from './bugCaseTab.vue';
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import MsComment from '@/components/business/ms-comment';
|
import MsComment from '@/components/business/ms-comment/comment';
|
||||||
import { CommentItem, CommentParams } from '@/components/business/ms-comment/types';
|
import { CommentItem, CommentParams } from '@/components/business/ms-comment/types';
|
||||||
|
|
||||||
import { createOrUpdateComment, deleteComment, getCommentList } from '@/api/modules/bug-management/index';
|
import { createOrUpdateComment, deleteComment, getCommentList } from '@/api/modules/bug-management/index';
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
|
|
||||||
import MSAvatar from '@/components/pure/ms-avatar/index.vue';
|
import MSAvatar from '@/components/pure/ms-avatar/index.vue';
|
||||||
import MsEmpty from '@/components/pure/ms-empty/index.vue';
|
import MsEmpty from '@/components/pure/ms-empty/index.vue';
|
||||||
import MsComment from '@/components/business/ms-comment';
|
import MsComment from '@/components/business/ms-comment/comment';
|
||||||
import { CommentItem, CommentParams } from '@/components/business/ms-comment/types';
|
import { CommentItem, CommentParams } from '@/components/business/ms-comment/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
Loading…
Reference in New Issue