fix(接口场景): 引用场景响应&部分 bug 修复
This commit is contained in:
parent
b628f853cb
commit
ffd19fae21
|
@ -21,6 +21,7 @@ import {
|
||||||
GetModuleTreeUrl,
|
GetModuleTreeUrl,
|
||||||
GetScenarioStepUrl,
|
GetScenarioStepUrl,
|
||||||
GetScenarioUrl,
|
GetScenarioUrl,
|
||||||
|
GetStepProjectInfoUrl,
|
||||||
GetSystemRequestUrl,
|
GetSystemRequestUrl,
|
||||||
GetTrashModuleCountUrl,
|
GetTrashModuleCountUrl,
|
||||||
GetTrashModuleTreeUrl,
|
GetTrashModuleTreeUrl,
|
||||||
|
@ -223,7 +224,7 @@ export function addScenario(params: Scenario) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取场景详情
|
// 获取场景详情
|
||||||
export function getScenarioDetail(id: string) {
|
export function getScenarioDetail(id: string | number) {
|
||||||
return MSR.get<ScenarioDetail>({ url: GetScenarioUrl, params: id });
|
return MSR.get<ScenarioDetail>({ url: GetScenarioUrl, params: id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,3 +279,8 @@ export function updateScenarioStatus(id: string | number, status: ApiScenarioSta
|
||||||
export function updateScenarioPro(id: string | number, priority: CaseLevel | undefined) {
|
export function updateScenarioPro(id: string | number, priority: CaseLevel | undefined) {
|
||||||
return MSR.get({ url: `${UpdateScenarioPriorityUrl}/${id}/${priority}` });
|
return MSR.get({ url: `${UpdateScenarioPriorityUrl}/${id}/${priority}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取跨项目信息
|
||||||
|
export function getStepProjectInfo(id: string | number) {
|
||||||
|
return MSR.get({ url: GetStepProjectInfoUrl, params: id });
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ export const GetSystemRequestUrl = '/api/scenario/get/system-request'; // 获取
|
||||||
export const FollowScenarioUrl = '/api/scenario/follow'; // 关注/取消关注接口场景
|
export const FollowScenarioUrl = '/api/scenario/follow'; // 关注/取消关注接口场景
|
||||||
export const ScenarioScheduleConfigUrl = '/api/scenario/schedule-config'; // 场景定时任务
|
export const ScenarioScheduleConfigUrl = '/api/scenario/schedule-config'; // 场景定时任务
|
||||||
export const ScenarioScheduleConfigDeleteUrl = '/api/scenario/schedule-config-delete/'; // 场景定时任务
|
export const ScenarioScheduleConfigDeleteUrl = '/api/scenario/schedule-config-delete/'; // 场景定时任务
|
||||||
|
export const GetStepProjectInfoUrl = '/api/scenario/step/project-ifo'; // 获取跨项目信息
|
||||||
export const BatchRecycleScenarioUrl = '/api/scenario/batch-operation/delete-gc'; // 批量删除接口场景
|
export const BatchRecycleScenarioUrl = '/api/scenario/batch-operation/delete-gc'; // 批量删除接口场景
|
||||||
export const BatchMoveScenarioUrl = '/api/scenario/batch-operation/move'; // 批量移动接口场景
|
export const BatchMoveScenarioUrl = '/api/scenario/batch-operation/move'; // 批量移动接口场景
|
||||||
export const BatchCopyScenarioUrl = '/api/scenario/batch-operation/copy'; // 批量复制接口场景
|
export const BatchCopyScenarioUrl = '/api/scenario/batch-operation/copy'; // 批量复制接口场景
|
||||||
|
|
|
@ -739,9 +739,12 @@
|
||||||
.arco-switch-type-circle {
|
.arco-switch-type-circle {
|
||||||
background-color: var(--color-text-brand) !important;
|
background-color: var(--color-text-brand) !important;
|
||||||
}
|
}
|
||||||
.arco-switch-type-circle.arco-switch-checked {
|
.arco-switch-type-circle.arco-switch-checked:not(:disabled) {
|
||||||
background-color: rgb(var(--primary-5)) !important;
|
background-color: rgb(var(--primary-5)) !important;
|
||||||
}
|
}
|
||||||
|
.arco-switch-disabled {
|
||||||
|
background-color: rgb(var(--primary-3)) !important;
|
||||||
|
}
|
||||||
.arco-switch-type-line.arco-switch-small {
|
.arco-switch-type-line.arco-switch-small {
|
||||||
.arco-switch-handle {
|
.arco-switch-handle {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import localforage from 'localforage';
|
||||||
import { MsTableColumn, MsTableColumnData } from '@/components/pure/ms-table/type';
|
import { MsTableColumn, MsTableColumnData } from '@/components/pure/ms-table/type';
|
||||||
|
|
||||||
import { useAppStore } from '@/store';
|
import { useAppStore } from '@/store';
|
||||||
import { PageSizeMap, SelectorColumnMap, TableOpenDetailMode } from '@/store/modules/components/ms-table/types';
|
import { MsTableSelectorItem, PageSizeMap, TableOpenDetailMode } from '@/store/modules/components/ms-table/types';
|
||||||
import { isArraysEqualWithOrder } from '@/utils/equal';
|
import { isArraysEqualWithOrder } from '@/utils/equal';
|
||||||
|
|
||||||
import { SpecialColumnEnum } from '@/enums/tableEnum';
|
import { SpecialColumnEnum } from '@/enums/tableEnum';
|
||||||
|
@ -15,19 +15,6 @@ export default function useTableStore() {
|
||||||
operationBaseIndex: 100,
|
operationBaseIndex: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getSelectorColumnMap = async () => {
|
|
||||||
try {
|
|
||||||
const selectorColumnMap = await localforage.getItem<SelectorColumnMap>('selectorColumnMap');
|
|
||||||
if (!selectorColumnMap) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return selectorColumnMap;
|
|
||||||
} catch (e) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(e);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const getPageSizeMap = async () => {
|
const getPageSizeMap = async () => {
|
||||||
try {
|
try {
|
||||||
const pageSizeMap = await localforage.getItem<PageSizeMap>('pageSizeMap');
|
const pageSizeMap = await localforage.getItem<PageSizeMap>('pageSizeMap');
|
||||||
|
@ -77,32 +64,30 @@ export default function useTableStore() {
|
||||||
showSubdirectory?: boolean
|
showSubdirectory?: boolean
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const selectorColumnMap = await getSelectorColumnMap();
|
const tableColumnsMap = await localforage.getItem<MsTableSelectorItem>(tableKey);
|
||||||
if (!selectorColumnMap[tableKey]) {
|
if (!tableColumnsMap) {
|
||||||
// 如果没有在indexDB里初始化
|
// 如果没有在indexDB里初始化
|
||||||
column = columnsTransform(column);
|
column = columnsTransform(column);
|
||||||
selectorColumnMap[tableKey] = {
|
localforage.setItem(tableKey, {
|
||||||
mode,
|
mode,
|
||||||
showSubdirectory,
|
showSubdirectory,
|
||||||
column,
|
column,
|
||||||
columnBackup: JSON.parse(JSON.stringify(column)),
|
columnBackup: JSON.parse(JSON.stringify(column)),
|
||||||
};
|
});
|
||||||
await localforage.setItem('selectorColumnMap', selectorColumnMap);
|
|
||||||
} else {
|
} else {
|
||||||
// 初始化过了,但是可能有新变动,如列的顺序,列的显示隐藏,列的拖拽
|
// 初始化过了,但是可能有新变动,如列的顺序,列的显示隐藏,列的拖拽
|
||||||
column = columnsTransform(column);
|
column = columnsTransform(column);
|
||||||
const { columnBackup: oldColumn } = selectorColumnMap[tableKey];
|
const { columnBackup: oldColumn } = tableColumnsMap;
|
||||||
// 比较页面上定义的 column 和 浏览器备份的column 是否相同
|
// 比较页面上定义的 column 和 浏览器备份的column 是否相同
|
||||||
const isEqual = isArraysEqualWithOrder<MsTableColumnData>(oldColumn, column);
|
const isEqual = isArraysEqualWithOrder(oldColumn, column);
|
||||||
if (!isEqual) {
|
if (!isEqual) {
|
||||||
// 如果不相等,说明有变动将新的column存入indexDB
|
// 如果不相等,说明有变动将新的column存入indexDB
|
||||||
selectorColumnMap[tableKey] = {
|
localforage.setItem(tableKey, {
|
||||||
mode,
|
mode,
|
||||||
showSubdirectory,
|
showSubdirectory,
|
||||||
column,
|
column,
|
||||||
columnBackup: JSON.parse(JSON.stringify(column)),
|
columnBackup: JSON.parse(JSON.stringify(column)),
|
||||||
};
|
});
|
||||||
await localforage.setItem('selectorColumnMap', selectorColumnMap);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -112,13 +97,10 @@ export default function useTableStore() {
|
||||||
}
|
}
|
||||||
async function setMode(key: string, mode: TableOpenDetailMode) {
|
async function setMode(key: string, mode: TableOpenDetailMode) {
|
||||||
try {
|
try {
|
||||||
const selectorColumnMap = await getSelectorColumnMap();
|
const tableColumnsMap = await localforage.getItem<MsTableSelectorItem>(key);
|
||||||
if (selectorColumnMap[key]) {
|
if (tableColumnsMap) {
|
||||||
const item = selectorColumnMap[key];
|
tableColumnsMap.mode = mode;
|
||||||
if (item) {
|
await localforage.setItem(key, tableColumnsMap);
|
||||||
item.mode = mode;
|
|
||||||
}
|
|
||||||
await localforage.setItem('selectorColumnMap', selectorColumnMap);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
@ -128,13 +110,10 @@ export default function useTableStore() {
|
||||||
|
|
||||||
async function setSubdirectory(key: string, val: boolean) {
|
async function setSubdirectory(key: string, val: boolean) {
|
||||||
try {
|
try {
|
||||||
const selectorColumnMap = await getSelectorColumnMap();
|
const tableColumnsMap = await localforage.getItem<MsTableSelectorItem>(key);
|
||||||
if (selectorColumnMap[key]) {
|
if (tableColumnsMap) {
|
||||||
const item = selectorColumnMap[key];
|
tableColumnsMap.showSubdirectory = val;
|
||||||
if (item) {
|
await localforage.setItem(key, tableColumnsMap);
|
||||||
item.showSubdirectory = val;
|
|
||||||
}
|
|
||||||
await localforage.setItem('selectorColumnMap', selectorColumnMap);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
@ -155,23 +134,22 @@ export default function useTableStore() {
|
||||||
item.sortIndex = state.baseSortIndex + idx;
|
item.sortIndex = state.baseSortIndex + idx;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const selectorColumnMap = await getSelectorColumnMap();
|
const tableColumnsMap = await localforage.getItem<MsTableSelectorItem>(key);
|
||||||
if (!selectorColumnMap) {
|
if (!tableColumnsMap) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isSimple) {
|
if (isSimple) {
|
||||||
const oldColumns = selectorColumnMap[key].column;
|
const oldColumns = tableColumnsMap.column;
|
||||||
const operationColumn = oldColumns.find((i) => i.dataIndex === SpecialColumnEnum.OPERATION);
|
const operationColumn = oldColumns.find((i) => i.dataIndex === SpecialColumnEnum.OPERATION);
|
||||||
if (operationColumn) columns.push(operationColumn);
|
if (operationColumn) columns.push(operationColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
selectorColumnMap[key] = {
|
await localforage.setItem(key, {
|
||||||
mode,
|
mode,
|
||||||
showSubdirectory,
|
showSubdirectory,
|
||||||
column: JSON.parse(JSON.stringify(columns)),
|
column: JSON.parse(JSON.stringify(columns)),
|
||||||
columnBackup: selectorColumnMap[key].columnBackup,
|
columnBackup: tableColumnsMap.columnBackup,
|
||||||
};
|
});
|
||||||
await localforage.setItem('selectorColumnMap', selectorColumnMap);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error('tableStore.setColumns', e);
|
console.error('tableStore.setColumns', e);
|
||||||
|
@ -184,25 +162,25 @@ export default function useTableStore() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMode(key: string) {
|
async function getMode(key: string) {
|
||||||
const selectorColumnMap = await getSelectorColumnMap();
|
const tableColumnsMap = await localforage.getItem<MsTableSelectorItem>(key);
|
||||||
if (selectorColumnMap[key]) {
|
if (tableColumnsMap) {
|
||||||
return selectorColumnMap[key].mode;
|
return tableColumnsMap.mode;
|
||||||
}
|
}
|
||||||
return 'drawer';
|
return 'drawer';
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSubShow(key: string) {
|
async function getSubShow(key: string) {
|
||||||
const selectorColumnMap = await getSelectorColumnMap();
|
const tableColumnsMap = await localforage.getItem<MsTableSelectorItem>(key);
|
||||||
if (selectorColumnMap[key]) {
|
if (tableColumnsMap) {
|
||||||
return selectorColumnMap[key].showSubdirectory;
|
return tableColumnsMap.showSubdirectory;
|
||||||
}
|
}
|
||||||
return true as boolean;
|
return true as boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getColumns(key: string, isSimple?: boolean) {
|
async function getColumns(key: string, isSimple?: boolean) {
|
||||||
const selectorColumnMap = await getSelectorColumnMap();
|
const tableColumnsMap = await localforage.getItem<MsTableSelectorItem>(key);
|
||||||
if (selectorColumnMap[key]) {
|
if (tableColumnsMap) {
|
||||||
const tmpArr = selectorColumnMap[key].column;
|
const tmpArr = tableColumnsMap.column;
|
||||||
const { nonSortableColumns, couldSortableColumns } = tmpArr.reduce(
|
const { nonSortableColumns, couldSortableColumns } = tmpArr.reduce(
|
||||||
(result: { nonSortableColumns: MsTableColumnData[]; couldSortableColumns: MsTableColumnData[] }, item) => {
|
(result: { nonSortableColumns: MsTableColumnData[]; couldSortableColumns: MsTableColumnData[] }, item) => {
|
||||||
if (isSimple && item.dataIndex === SpecialColumnEnum.OPERATION) {
|
if (isSimple && item.dataIndex === SpecialColumnEnum.OPERATION) {
|
||||||
|
@ -222,9 +200,9 @@ export default function useTableStore() {
|
||||||
return { nonSort: [], couldSort: [] };
|
return { nonSort: [], couldSort: [] };
|
||||||
}
|
}
|
||||||
async function getShowInTableColumns(key: string) {
|
async function getShowInTableColumns(key: string) {
|
||||||
const selectorColumnMap = await getSelectorColumnMap();
|
const tableColumnsMap = await localforage.getItem<MsTableSelectorItem>(key);
|
||||||
if (selectorColumnMap[key]) {
|
if (tableColumnsMap) {
|
||||||
const tmpArr: MsTableColumn = selectorColumnMap[key].column;
|
const tmpArr: MsTableColumn = tableColumnsMap.column;
|
||||||
return orderBy(
|
return orderBy(
|
||||||
filter(tmpArr, (i) => i.showInTable),
|
filter(tmpArr, (i) => i.showInTable),
|
||||||
['sortIndex'],
|
['sortIndex'],
|
||||||
|
|
|
@ -360,6 +360,7 @@ export interface ScenarioStepItem {
|
||||||
executeStatus?: ScenarioExecuteStatus;
|
executeStatus?: ScenarioExecuteStatus;
|
||||||
isExecuting?: boolean; // 是否正在执行
|
isExecuting?: boolean; // 是否正在执行
|
||||||
reportId?: string | number; // 步骤单个调试时的报告id
|
reportId?: string | number; // 步骤单个调试时的报告id
|
||||||
|
uniqueId: string | number; // 获取报告时的步骤唯一标识(用来区分重复引用的步骤)
|
||||||
isQuoteScenarioStep?: boolean; // 是否是引用场景下的步骤(不分是不是完全引用,只要是引用类型就是),不可修改引用 api 的参数值
|
isQuoteScenarioStep?: boolean; // 是否是引用场景下的步骤(不分是不是完全引用,只要是引用类型就是),不可修改引用 api 的参数值
|
||||||
isRefScenarioStep?: boolean; // 是否是完全引用的场景下的步骤,是的话不允许启用禁用
|
isRefScenarioStep?: boolean; // 是否是完全引用的场景下的步骤,是的话不允许启用禁用
|
||||||
}
|
}
|
||||||
|
@ -401,7 +402,7 @@ export interface Scenario {
|
||||||
executeTime?: string | number; // 执行时间
|
executeTime?: string | number; // 执行时间
|
||||||
executeSuccessCount: number; // 执行成功数量
|
executeSuccessCount: number; // 执行成功数量
|
||||||
executeFailCount: number; // 执行失败数量
|
executeFailCount: number; // 执行失败数量
|
||||||
reportId?: string | number; // 场景报告 id
|
reportId: string | number; // 场景报告 id
|
||||||
stepResponses: Record<string | number, Array<RequestResult>>; // 步骤响应集合,key 为步骤 id,value 为步骤响应内容
|
stepResponses: Record<string | number, Array<RequestResult>>; // 步骤响应集合,key 为步骤 id,value 为步骤响应内容
|
||||||
isExecute?: boolean; // 是否从列表执行进去场景详情
|
isExecute?: boolean; // 是否从列表执行进去场景详情
|
||||||
isDebug?: boolean; // 是否调试,区分执行场景和批量调试步骤
|
isDebug?: boolean; // 是否调试,区分执行场景和批量调试步骤
|
||||||
|
|
|
@ -198,8 +198,8 @@ export interface TreeNode<T> {
|
||||||
*/
|
*/
|
||||||
export function traverseTree<T>(
|
export function traverseTree<T>(
|
||||||
tree: TreeNode<T> | TreeNode<T>[] | T | T[],
|
tree: TreeNode<T> | TreeNode<T>[] | T | T[],
|
||||||
|
customNodeFn: (node: TreeNode<T>) => void,
|
||||||
continueCondition?: (node: TreeNode<T>) => boolean,
|
continueCondition?: (node: TreeNode<T>) => boolean,
|
||||||
customNodeFn: (node: TreeNode<T>) => TreeNode<T> | null = (node) => node,
|
|
||||||
customChildrenKey = 'children'
|
customChildrenKey = 'children'
|
||||||
) {
|
) {
|
||||||
if (!Array.isArray(tree)) {
|
if (!Array.isArray(tree)) {
|
||||||
|
@ -215,7 +215,7 @@ export function traverseTree<T>(
|
||||||
// 如果有继续递归的条件,则判断是否继续递归
|
// 如果有继续递归的条件,则判断是否继续递归
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
traverseTree(node[customChildrenKey], continueCondition, customNodeFn, customChildrenKey);
|
traverseTree(node[customChildrenKey], customNodeFn, continueCondition, customChildrenKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -488,6 +488,7 @@ export function handleTreeDragDrop<T>(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const index = parentChildren.findIndex((node: TreeNode<T>) => node[customKey] === dragNode[customKey]);
|
const index = parentChildren.findIndex((node: TreeNode<T>) => node[customKey] === dragNode[customKey]);
|
||||||
|
console.log('index', parentChildren, dragNode, index);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
parentChildren.splice(index, 1);
|
parentChildren.splice(index, 1);
|
||||||
|
|
||||||
|
|
|
@ -346,6 +346,7 @@
|
||||||
offspringIds: string[];
|
offspringIds: string[];
|
||||||
protocol: string; // 查看的协议类型
|
protocol: string; // 查看的协议类型
|
||||||
readOnly?: boolean; // 是否是只读模式
|
readOnly?: boolean; // 是否是只读模式
|
||||||
|
refreshTimeStamp?: number;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'openApiTab', record: ApiDefinitionDetail, isExecute?: boolean): void;
|
(e: 'openApiTab', record: ApiDefinitionDetail, isExecute?: boolean): void;
|
||||||
|
@ -358,6 +359,7 @@
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
|
const tableStore = useTableStore();
|
||||||
|
|
||||||
const folderTreePathMap = inject('folderTreePathMap');
|
const folderTreePathMap = inject('folderTreePathMap');
|
||||||
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
|
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
|
||||||
|
@ -459,6 +461,14 @@
|
||||||
width: hasOperationPermission.value ? 220 : 50,
|
width: hasOperationPermission.value ? 220 : 50,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
await tableStore.initColumn(TableKeyEnum.API_TEST, columns, 'drawer', true);
|
||||||
|
if (props.readOnly) {
|
||||||
|
columns = columns.filter(
|
||||||
|
(item) => !['version', 'createTime', 'updateTime', 'operation'].includes(item.dataIndex as string)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
||||||
getDefinitionPage,
|
getDefinitionPage,
|
||||||
{
|
{
|
||||||
|
@ -523,7 +533,6 @@
|
||||||
const statusFilterVisible = ref(false);
|
const statusFilterVisible = ref(false);
|
||||||
const statusFilters = ref<string[]>([]);
|
const statusFilters = ref<string[]>([]);
|
||||||
|
|
||||||
const tableStore = useTableStore();
|
|
||||||
async function getModuleIds() {
|
async function getModuleIds() {
|
||||||
let moduleIds: string[] = [];
|
let moduleIds: string[] = [];
|
||||||
if (props.activeModule !== 'all') {
|
if (props.activeModule !== 'all') {
|
||||||
|
@ -552,6 +561,15 @@
|
||||||
loadList();
|
loadList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.refreshTimeStamp,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
loadApiList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.activeModule,
|
() => props.activeModule,
|
||||||
() => {
|
() => {
|
||||||
|
@ -915,18 +933,6 @@
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
loadApiList,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!props.readOnly) {
|
|
||||||
await tableStore.initColumn(TableKeyEnum.API_TEST, columns, 'drawer', true);
|
|
||||||
} else {
|
|
||||||
columns = columns.filter(
|
|
||||||
(item) => !['version', 'createTime', 'updateTime', 'operation'].includes(item.dataIndex as string)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-1 flex-col overflow-hidden">
|
<div class="flex flex-1 flex-col overflow-hidden">
|
||||||
<div v-show="activeApiTab.id === 'all'" class="flex-1 pt-[16px]">
|
<div v-if="activeApiTab.id === 'all'" class="flex-1 pt-[16px]">
|
||||||
<apiTable
|
<apiTable
|
||||||
ref="apiTableRef"
|
|
||||||
:active-module="props.activeModule"
|
:active-module="props.activeModule"
|
||||||
:offspring-ids="props.offspringIds"
|
:offspring-ids="props.offspringIds"
|
||||||
:protocol="props.protocol"
|
:protocol="props.protocol"
|
||||||
|
:refresh-time-stamp="refreshTableTimeStamp"
|
||||||
@open-api-tab="(record, isExecute) => openApiTab(record, false, isExecute)"
|
@open-api-tab="(record, isExecute) => openApiTab(record, false, isExecute)"
|
||||||
@open-copy-api-tab="openApiTab($event, true)"
|
@open-copy-api-tab="openApiTab($event, true)"
|
||||||
@add-api-tab="addApiTab"
|
@add-api-tab="addApiTab"
|
||||||
|
@ -288,14 +288,14 @@
|
||||||
activeApiTab.value = apiTabs.value[apiTabs.value.length - 1];
|
activeApiTab.value = apiTabs.value[apiTabs.value.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiTableRef = ref<InstanceType<typeof apiTable>>();
|
|
||||||
const caseTableRef = ref<InstanceType<typeof caseTable>>();
|
const caseTableRef = ref<InstanceType<typeof caseTable>>();
|
||||||
|
const refreshTableTimeStamp = ref(0);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => activeApiTab.value.id,
|
() => activeApiTab.value.id,
|
||||||
(id) => {
|
(id) => {
|
||||||
if (id === 'all') {
|
if (id === 'all') {
|
||||||
apiTableRef.value?.loadApiList();
|
refreshTableTimeStamp.value = Date.now();
|
||||||
}
|
}
|
||||||
if (activeApiTab.value.definitionActiveKey === 'case') {
|
if (activeApiTab.value.definitionActiveKey === 'case') {
|
||||||
caseTableRef.value?.loadCaseList();
|
caseTableRef.value?.loadCaseList();
|
||||||
|
@ -366,7 +366,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshTable() {
|
function refreshTable() {
|
||||||
apiTableRef.value?.loadApiList();
|
refreshTableTimeStamp.value = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeDefinitionActiveKey(val: string | number) {
|
function changeDefinitionActiveKey(val: string | number) {
|
||||||
|
|
|
@ -558,7 +558,6 @@
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(getCasePage, {
|
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(getCasePage, {
|
||||||
columns,
|
|
||||||
scroll: { x: '100%' },
|
scroll: { x: '100%' },
|
||||||
tableKey: TableKeyEnum.API_TEST_MANAGEMENT_CASE,
|
tableKey: TableKeyEnum.API_TEST_MANAGEMENT_CASE,
|
||||||
showSetting: true,
|
showSetting: true,
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
<a-select
|
<a-select
|
||||||
v-model:model-value="requestVModel.customizeRequestEnvEnable"
|
v-model:model-value="requestVModel.customizeRequestEnvEnable"
|
||||||
class="w-[150px]"
|
class="w-[150px]"
|
||||||
|
:disabled="props.step?.isQuoteScenarioStep"
|
||||||
popup-container=".customApiDrawer-title-right"
|
popup-container=".customApiDrawer-title-right"
|
||||||
@change="handleUseEnvChange"
|
@change="handleUseEnvChange"
|
||||||
>
|
>
|
||||||
|
@ -61,7 +62,7 @@
|
||||||
v-model:model-value="requestVModel.protocol"
|
v-model:model-value="requestVModel.protocol"
|
||||||
:options="protocolOptions"
|
:options="protocolOptions"
|
||||||
:loading="protocolLoading"
|
:loading="protocolLoading"
|
||||||
:disabled="_stepType.isQuoteApi"
|
:disabled="_stepType.isQuoteApi || props.step?.isQuoteScenarioStep"
|
||||||
class="w-[90px]"
|
class="w-[90px]"
|
||||||
@change="(val) => handleActiveDebugProtocolChange(val as string)"
|
@change="(val) => handleActiveDebugProtocolChange(val as string)"
|
||||||
/>
|
/>
|
||||||
|
@ -81,7 +82,7 @@
|
||||||
<apiMethodSelect
|
<apiMethodSelect
|
||||||
v-model:model-value="requestVModel.method"
|
v-model:model-value="requestVModel.method"
|
||||||
class="w-[140px]"
|
class="w-[140px]"
|
||||||
:disabled="_stepType.isQuoteApi"
|
:disabled="_stepType.isQuoteApi || props.step?.isQuoteScenarioStep"
|
||||||
@change="handleActiveDebugChange"
|
@change="handleActiveDebugChange"
|
||||||
/>
|
/>
|
||||||
<a-input
|
<a-input
|
||||||
|
@ -91,7 +92,7 @@
|
||||||
allow-clear
|
allow-clear
|
||||||
class="hover:z-10"
|
class="hover:z-10"
|
||||||
:style="isUrlError ? 'border: 1px solid rgb(var(--danger-6);z-index: 10' : ''"
|
:style="isUrlError ? 'border: 1px solid rgb(var(--danger-6);z-index: 10' : ''"
|
||||||
:disabled="_stepType.isQuoteApi"
|
:disabled="_stepType.isQuoteApi || props.step?.isQuoteScenarioStep"
|
||||||
@input="() => (isUrlError = false)"
|
@input="() => (isUrlError = false)"
|
||||||
@change="handleUrlChange"
|
@change="handleUrlChange"
|
||||||
>
|
>
|
||||||
|
@ -130,7 +131,7 @@
|
||||||
v-model:model-value="requestVModel.name"
|
v-model:model-value="requestVModel.name"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
:placeholder="t('apiTestManagement.apiNamePlaceholder')"
|
:placeholder="t('apiTestManagement.apiNamePlaceholder')"
|
||||||
:disabled="!isEditableApi || isQuoteScenarioStep"
|
:disabled="!isEditableApi"
|
||||||
allow-clear
|
allow-clear
|
||||||
class="mt-[8px]"
|
class="mt-[8px]"
|
||||||
/>
|
/>
|
||||||
|
@ -184,7 +185,7 @@
|
||||||
<httpHeader
|
<httpHeader
|
||||||
v-if="requestVModel.activeTab === RequestComposition.HEADER"
|
v-if="requestVModel.activeTab === RequestComposition.HEADER"
|
||||||
v-model:params="requestVModel.headers"
|
v-model:params="requestVModel.headers"
|
||||||
:disabled-param-value="isQuoteScenarioStep"
|
:disabled-param-value="!isEditableApi"
|
||||||
:disabled-except-param="!isEditableApi"
|
:disabled-except-param="!isEditableApi"
|
||||||
:layout="activeLayout"
|
:layout="activeLayout"
|
||||||
:second-box-height="secondBoxHeight"
|
:second-box-height="secondBoxHeight"
|
||||||
|
@ -194,7 +195,7 @@
|
||||||
v-else-if="requestVModel.activeTab === RequestComposition.BODY"
|
v-else-if="requestVModel.activeTab === RequestComposition.BODY"
|
||||||
v-model:params="requestVModel.body"
|
v-model:params="requestVModel.body"
|
||||||
:layout="activeLayout"
|
:layout="activeLayout"
|
||||||
:disabled-param-value="isQuoteScenarioStep"
|
:disabled-param-value="!isEditableApi"
|
||||||
:disabled-except-param="!isEditableApi"
|
:disabled-except-param="!isEditableApi"
|
||||||
:second-box-height="secondBoxHeight"
|
:second-box-height="secondBoxHeight"
|
||||||
:upload-temp-file-api="uploadTempFile"
|
:upload-temp-file-api="uploadTempFile"
|
||||||
|
@ -207,7 +208,7 @@
|
||||||
v-else-if="requestVModel.activeTab === RequestComposition.QUERY"
|
v-else-if="requestVModel.activeTab === RequestComposition.QUERY"
|
||||||
v-model:params="requestVModel.query"
|
v-model:params="requestVModel.query"
|
||||||
:layout="activeLayout"
|
:layout="activeLayout"
|
||||||
:disabled-param-value="isQuoteScenarioStep"
|
:disabled-param-value="!isEditableApi"
|
||||||
:disabled-except-param="!isEditableApi"
|
:disabled-except-param="!isEditableApi"
|
||||||
:second-box-height="secondBoxHeight"
|
:second-box-height="secondBoxHeight"
|
||||||
@change="handleActiveDebugChange"
|
@change="handleActiveDebugChange"
|
||||||
|
@ -216,7 +217,7 @@
|
||||||
v-else-if="requestVModel.activeTab === RequestComposition.REST"
|
v-else-if="requestVModel.activeTab === RequestComposition.REST"
|
||||||
v-model:params="requestVModel.rest"
|
v-model:params="requestVModel.rest"
|
||||||
:layout="activeLayout"
|
:layout="activeLayout"
|
||||||
:disabled-param-value="isQuoteScenarioStep"
|
:disabled-param-value="!isEditableApi"
|
||||||
:disabled-except-param="!isEditableApi"
|
:disabled-except-param="!isEditableApi"
|
||||||
:second-box-height="secondBoxHeight"
|
:second-box-height="secondBoxHeight"
|
||||||
@change="handleActiveDebugChange"
|
@change="handleActiveDebugChange"
|
||||||
|
@ -225,7 +226,7 @@
|
||||||
v-else-if="requestVModel.activeTab === RequestComposition.PRECONDITION"
|
v-else-if="requestVModel.activeTab === RequestComposition.PRECONDITION"
|
||||||
v-model:config="requestVModel.children[0].preProcessorConfig"
|
v-model:config="requestVModel.children[0].preProcessorConfig"
|
||||||
is-definition
|
is-definition
|
||||||
:disabled="!isEditableApi || isQuoteScenarioStep"
|
:disabled="!isEditableApi"
|
||||||
@change="handleActiveDebugChange"
|
@change="handleActiveDebugChange"
|
||||||
/>
|
/>
|
||||||
<postcondition
|
<postcondition
|
||||||
|
@ -233,7 +234,7 @@
|
||||||
v-model:config="requestVModel.children[0].postProcessorConfig"
|
v-model:config="requestVModel.children[0].postProcessorConfig"
|
||||||
:response="responseResultBody"
|
:response="responseResultBody"
|
||||||
:layout="activeLayout"
|
:layout="activeLayout"
|
||||||
:disabled="!isEditableApi || isQuoteScenarioStep"
|
:disabled="!isEditableApi"
|
||||||
:second-box-height="secondBoxHeight"
|
:second-box-height="secondBoxHeight"
|
||||||
is-definition
|
is-definition
|
||||||
@change="handleActiveDebugChange"
|
@change="handleActiveDebugChange"
|
||||||
|
@ -243,19 +244,19 @@
|
||||||
v-model:params="requestVModel.children[0].assertionConfig.assertions"
|
v-model:params="requestVModel.children[0].assertionConfig.assertions"
|
||||||
:response="responseResultBody"
|
:response="responseResultBody"
|
||||||
is-definition
|
is-definition
|
||||||
:disabled="!isEditableApi || isQuoteScenarioStep"
|
:disabled="!isEditableApi"
|
||||||
:assertion-config="requestVModel.children[0].assertionConfig"
|
:assertion-config="requestVModel.children[0].assertionConfig"
|
||||||
/>
|
/>
|
||||||
<auth
|
<auth
|
||||||
v-else-if="requestVModel.activeTab === RequestComposition.AUTH"
|
v-else-if="requestVModel.activeTab === RequestComposition.AUTH"
|
||||||
v-model:params="requestVModel.authConfig"
|
v-model:params="requestVModel.authConfig"
|
||||||
:disabled="!isEditableApi || isQuoteScenarioStep"
|
:disabled="!isEditableApi"
|
||||||
@change="handleActiveDebugChange"
|
@change="handleActiveDebugChange"
|
||||||
/>
|
/>
|
||||||
<setting
|
<setting
|
||||||
v-else-if="requestVModel.activeTab === RequestComposition.SETTING"
|
v-else-if="requestVModel.activeTab === RequestComposition.SETTING"
|
||||||
v-model:params="requestVModel.otherConfig"
|
v-model:params="requestVModel.otherConfig"
|
||||||
:disabled="!isEditableApi || isQuoteScenarioStep"
|
:disabled="!isEditableApi"
|
||||||
@change="handleActiveDebugChange"
|
@change="handleActiveDebugChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -506,11 +507,11 @@
|
||||||
);
|
);
|
||||||
const currentLoop = ref(1);
|
const currentLoop = ref(1);
|
||||||
const currentResponse = computed(() => {
|
const currentResponse = computed(() => {
|
||||||
if (props.step?.id) {
|
if (props.step?.uniqueId) {
|
||||||
return props.stepResponses?.[props.step?.id]?.[currentLoop.value - 1];
|
return props.stepResponses?.[props.step?.uniqueId]?.[currentLoop.value - 1];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const loopTotal = computed(() => (props.step?.id && props.stepResponses?.[props.step?.id]?.length) || 0);
|
const loopTotal = computed(() => (props.step?.uniqueId && props.stepResponses?.[props.step?.uniqueId]?.length) || 0);
|
||||||
// 执行响应结果 body 部分
|
// 执行响应结果 body 部分
|
||||||
const responseResultBody = computed(() => {
|
const responseResultBody = computed(() => {
|
||||||
return currentResponse.value?.responseResult.body;
|
return currentResponse.value?.responseResult.body;
|
||||||
|
@ -531,10 +532,11 @@
|
||||||
// 复制 api 只要加载过一次后就会保存,所以 props.request 是不为空的
|
// 复制 api 只要加载过一次后就会保存,所以 props.request 是不为空的
|
||||||
const isCopyApiNeedInit = computed(() => _stepType.value.isCopyApi && props.request === undefined);
|
const isCopyApiNeedInit = computed(() => _stepType.value.isCopyApi && props.request === undefined);
|
||||||
const isEditableApi = computed(
|
const isEditableApi = computed(
|
||||||
() => _stepType.value.isCopyApi || props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST || !props.step
|
() =>
|
||||||
|
!props.step?.isQuoteScenarioStep &&
|
||||||
|
(_stepType.value.isCopyApi || props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST || !props.step)
|
||||||
);
|
);
|
||||||
const isHttpProtocol = computed(() => requestVModel.value.protocol === 'HTTP');
|
const isHttpProtocol = computed(() => requestVModel.value.protocol === 'HTTP');
|
||||||
const isQuoteScenarioStep = computed(() => props.step?.isQuoteScenarioStep);
|
|
||||||
|
|
||||||
const isInitPluginForm = ref(false);
|
const isInitPluginForm = ref(false);
|
||||||
|
|
||||||
|
@ -1087,7 +1089,6 @@
|
||||||
}
|
}
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 等待内容渲染出来再隐藏loading
|
// 等待内容渲染出来再隐藏loading
|
||||||
requestVModel.value.activeTab = contentTabList.value[0].value;
|
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -1130,6 +1131,7 @@
|
||||||
stepId: getGenerateId(),
|
stepId: getGenerateId(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
requestVModel.value.activeTab = contentTabList.value[0].value;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -404,11 +404,13 @@
|
||||||
);
|
);
|
||||||
const currentLoop = ref(1);
|
const currentLoop = ref(1);
|
||||||
const currentResponse = computed(() => {
|
const currentResponse = computed(() => {
|
||||||
if (activeStep.value?.id) {
|
if (activeStep.value?.uniqueId) {
|
||||||
return props.stepResponses?.[activeStep.value?.id]?.[currentLoop.value - 1];
|
return props.stepResponses?.[activeStep.value?.uniqueId]?.[currentLoop.value - 1];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const loopTotal = computed(() => (activeStep.value?.id && props.stepResponses?.[activeStep.value?.id]?.length) || 0);
|
const loopTotal = computed(
|
||||||
|
() => (activeStep.value?.uniqueId && props.stepResponses?.[activeStep.value?.uniqueId]?.length) || 0
|
||||||
|
);
|
||||||
// 执行响应结果 body 部分
|
// 执行响应结果 body 部分
|
||||||
const responseResultBody = computed(() => {
|
const responseResultBody = computed(() => {
|
||||||
return currentResponse.value?.responseResult.body;
|
return currentResponse.value?.responseResult.body;
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
:selected-apis="selectedApis"
|
:selected-apis="selectedApis"
|
||||||
:selected-cases="selectedCases"
|
:selected-cases="selectedCases"
|
||||||
:selected-scenarios="selectedScenarios"
|
:selected-scenarios="selectedScenarios"
|
||||||
|
:scenario-id="props.scenarioId"
|
||||||
@select="handleTableSelect"
|
@select="handleTableSelect"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -112,6 +113,9 @@
|
||||||
scenario: MsTableDataItem<ApiScenarioTableItem>[];
|
scenario: MsTableDataItem<ApiScenarioTableItem>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
scenarioId?: string | number;
|
||||||
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'copy', data: ImportData): void;
|
(e: 'copy', data: ImportData): void;
|
||||||
(e: 'quote', data: ImportData): void;
|
(e: 'quote', data: ImportData): void;
|
||||||
|
@ -235,19 +239,12 @@
|
||||||
fullScenarioArr.push(...res);
|
fullScenarioArr.push(...res);
|
||||||
});
|
});
|
||||||
if (refType === ScenarioStepRefType.COPY) {
|
if (refType === ScenarioStepRefType.COPY) {
|
||||||
fullScenarioArr = fullScenarioArr.map((e) => {
|
fullScenarioArr = mapTree<MsTableDataItem<ApiScenarioTableItem>>(fullScenarioArr, (node) => {
|
||||||
return {
|
return {
|
||||||
...e,
|
...node,
|
||||||
children: mapTree<MsTableDataItem<ApiScenarioTableItem>>(e.children || [], (node) => {
|
copyFromStepId: node.id,
|
||||||
return {
|
originProjectId: node.projectId,
|
||||||
...node,
|
id: getGenerateId(),
|
||||||
copyFromStepId: node.id,
|
|
||||||
originProjectId: node.projectId,
|
|
||||||
id: getGenerateId(),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
copyFromStepId: e.resourceId,
|
|
||||||
originProjectId: e.projectId,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
emit(
|
emit(
|
||||||
|
@ -266,13 +263,12 @@
|
||||||
children: mapTree<MsTableDataItem<ApiScenarioTableItem>>(e.children || [], (node) => {
|
children: mapTree<MsTableDataItem<ApiScenarioTableItem>>(e.children || [], (node) => {
|
||||||
return {
|
return {
|
||||||
...node,
|
...node,
|
||||||
copyFromStepId: node.id,
|
|
||||||
originProjectId: node.projectId,
|
originProjectId: node.projectId,
|
||||||
id: getGenerateId(),
|
|
||||||
isQuoteScenarioStep: true,
|
isQuoteScenarioStep: true,
|
||||||
isRefScenarioStep: true, // 默认是完全引用的
|
isRefScenarioStep: true, // 默认是完全引用的
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
id: getGenerateId(),
|
||||||
originProjectId: e.projectId,
|
originProjectId: e.projectId,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -113,6 +113,7 @@
|
||||||
selectedApis: MsTableDataItem<ApiDefinitionDetail>[]; // 已选中的接口
|
selectedApis: MsTableDataItem<ApiDefinitionDetail>[]; // 已选中的接口
|
||||||
selectedCases: MsTableDataItem<ApiCaseDetail>[]; // 已选中的用例
|
selectedCases: MsTableDataItem<ApiCaseDetail>[]; // 已选中的用例
|
||||||
selectedScenarios: MsTableDataItem<ApiScenarioTableItem>[]; // 已选中的场景
|
selectedScenarios: MsTableDataItem<ApiScenarioTableItem>[]; // 已选中的场景
|
||||||
|
scenarioId?: string | number;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'select', data: MsTableDataItem<ApiCaseDetail | ApiDefinitionDetail | ApiScenarioTableItem>[]): void;
|
(e: 'select', data: MsTableDataItem<ApiCaseDetail | ApiDefinitionDetail | ApiScenarioTableItem>[]): void;
|
||||||
|
@ -335,6 +336,7 @@
|
||||||
status: statusFilters.value,
|
status: statusFilters.value,
|
||||||
method: methodFilters.value,
|
method: methodFilters.value,
|
||||||
},
|
},
|
||||||
|
excludeIds: [props.scenarioId || ''],
|
||||||
});
|
});
|
||||||
currentTable.value.loadList();
|
currentTable.value.loadList();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<a-popover
|
<a-popover
|
||||||
position="br"
|
position="lt"
|
||||||
content-class="scenario-step-response-popover"
|
content-class="scenario-step-response-popover"
|
||||||
@popup-visible-change="emit('visibleChange', $event, props.step)"
|
@popup-visible-change="emit('visibleChange', $event, props.step)"
|
||||||
>
|
>
|
||||||
|
@ -8,8 +8,18 @@
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="flex h-full flex-col">
|
<div class="flex h-full flex-col">
|
||||||
<loopPagination v-model:current-loop="currentLoop" :loop-total="loopTotal" />
|
<loopPagination v-model:current-loop="currentLoop" :loop-total="loopTotal" />
|
||||||
<div class="flex-1">
|
<div class="flex-1 overflow-y-hidden">
|
||||||
|
<div v-if="step.stepType === ScenarioStepType.SCRIPT" class="flex h-full flex-col p-[8px]">
|
||||||
|
<div class="mb-[8px] flex gap-[8px] text-[14px] font-medium text-[var(--color-text-1)]">
|
||||||
|
{{ t('apiScenario.executionResult') }}
|
||||||
|
<div class="one-line-text text-[var(--color-text-4)]">({{ step.name }})</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 bg-[var(--color-text-n9)] p-[12px]">
|
||||||
|
<pre class="response-header-pre">{{ currentResponse?.console }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<responseResult
|
<responseResult
|
||||||
|
v-else
|
||||||
:active-tab="ResponseComposition.BODY"
|
:active-tab="ResponseComposition.BODY"
|
||||||
:request-result="currentResponse"
|
:request-result="currentResponse"
|
||||||
:console="currentResponse?.console"
|
:console="currentResponse?.console"
|
||||||
|
@ -40,7 +50,7 @@
|
||||||
|
|
||||||
import { RequestResult } from '@/models/apiTest/common';
|
import { RequestResult } from '@/models/apiTest/common';
|
||||||
import { ScenarioStepItem } from '@/models/apiTest/scenario';
|
import { ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||||
import { ResponseComposition, ScenarioExecuteStatus } from '@/enums/apiEnum';
|
import { ResponseComposition, ScenarioExecuteStatus, ScenarioStepType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
const responseResult = defineAsyncComponent(
|
const responseResult = defineAsyncComponent(
|
||||||
() => import('@/views/api-test/components/requestComposition/response/index.vue')
|
() => import('@/views/api-test/components/requestComposition/response/index.vue')
|
||||||
|
@ -55,12 +65,12 @@
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const currentLoop = ref(1);
|
const currentLoop = ref(1);
|
||||||
const currentResponse = computed(() => props.stepResponses?.[props.step.id]?.[currentLoop.value - 1]);
|
const currentResponse = computed(() => props.stepResponses?.[props.step.uniqueId]?.[currentLoop.value - 1]);
|
||||||
const loopTotal = computed(() => props.stepResponses?.[props.step.id]?.length || 0);
|
const loopTotal = computed(() => props.stepResponses?.[props.step.uniqueId]?.length || 0);
|
||||||
const finalExecuteStatus = computed(() => {
|
const finalExecuteStatus = computed(() => {
|
||||||
if (props.stepResponses[props.step.id] && props.stepResponses[props.step.id].length > 0) {
|
if (props.stepResponses[props.step.uniqueId] && props.stepResponses[props.step.uniqueId].length > 0) {
|
||||||
// 有一次失败就是失败
|
// 有一次失败就是失败
|
||||||
return props.stepResponses[props.step.id].some((report) => !report.isSuccessful)
|
return props.stepResponses[props.step.uniqueId].some((report) => !report.isSuccessful)
|
||||||
? ScenarioExecuteStatus.FAILED
|
? ScenarioExecuteStatus.FAILED
|
||||||
: ScenarioExecuteStatus.SUCCESS;
|
: ScenarioExecuteStatus.SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -74,6 +84,13 @@
|
||||||
height: 500px;
|
height: 500px;
|
||||||
.arco-popover-content {
|
.arco-popover-content {
|
||||||
@apply h-full;
|
@apply h-full;
|
||||||
|
.response-header-pre {
|
||||||
|
@apply h-full overflow-auto bg-white;
|
||||||
|
.ms-scroll-bar();
|
||||||
|
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: var(--border-radius-small);
|
||||||
|
}
|
||||||
.response {
|
.response {
|
||||||
.response-head {
|
.response-head {
|
||||||
background-color: var(--color-text-n9);
|
background-color: var(--color-text-n9);
|
||||||
|
|
|
@ -88,11 +88,11 @@
|
||||||
const visible = defineModel<boolean>('visible', { required: true });
|
const visible = defineModel<boolean>('visible', { required: true });
|
||||||
const currentLoop = ref(1);
|
const currentLoop = ref(1);
|
||||||
const currentResponse = computed(() => {
|
const currentResponse = computed(() => {
|
||||||
if (props.step?.id) {
|
if (props.step?.uniqueId) {
|
||||||
return props.stepResponses?.[props.step?.id]?.[currentLoop.value - 1];
|
return props.stepResponses?.[props.step?.uniqueId]?.[currentLoop.value - 1];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const loopTotal = computed(() => (props.step?.id && props.stepResponses?.[props.step?.id]?.length) || 0);
|
const loopTotal = computed(() => (props.step?.uniqueId && props.stepResponses?.[props.step?.uniqueId]?.length) || 0);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => visible.value,
|
() => visible.value,
|
||||||
|
|
|
@ -120,6 +120,7 @@ export const defaultScenario: Scenario = {
|
||||||
executeFailCount: 0,
|
executeFailCount: 0,
|
||||||
uploadFileIds: [],
|
uploadFileIds: [],
|
||||||
linkFileIds: [],
|
linkFileIds: [],
|
||||||
|
reportId: '',
|
||||||
// 前端渲染字段
|
// 前端渲染字段
|
||||||
label: '',
|
label: '',
|
||||||
closable: true,
|
closable: true,
|
||||||
|
|
|
@ -132,10 +132,12 @@ export default function useCreateActions() {
|
||||||
return {
|
return {
|
||||||
...cloneDeep(defaultStepItemCommon),
|
...cloneDeep(defaultStepItemCommon),
|
||||||
id,
|
id,
|
||||||
|
uniqueId: getGenerateId(), // 生成唯一 ID,避免重复引用的步骤无法读取正确的执行结果
|
||||||
config: {
|
config: {
|
||||||
...defaultStepItemCommon.config,
|
...defaultStepItemCommon.config,
|
||||||
...config,
|
...config,
|
||||||
},
|
},
|
||||||
|
draggable: stepType !== ScenarioStepType.API_SCENARIO ? !item.config?.isQuoteScenarioStep : true, // 引用场景下的任何子步骤不可拖拽,除了场景本身
|
||||||
isQuoteScenarioStep: item.config?.isQuoteScenarioStep || false,
|
isQuoteScenarioStep: item.config?.isQuoteScenarioStep || false,
|
||||||
isRefScenarioStep: item.config?.isRefScenarioStep || false,
|
isRefScenarioStep: item.config?.isRefScenarioStep || false,
|
||||||
children: item.children || [],
|
children: item.children || [],
|
||||||
|
|
|
@ -1,101 +1,102 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex h-full flex-col gap-[8px]">
|
<div class="flex h-full flex-col gap-[8px]">
|
||||||
<div class="action-line">
|
<a-spin class="h-full w-full" :loading="loading">
|
||||||
<div class="action-group">
|
<div class="action-line">
|
||||||
<a-checkbox
|
<div class="action-group">
|
||||||
v-show="scenario.steps.length > 0"
|
<a-checkbox
|
||||||
v-model:model-value="checkedAll"
|
|
||||||
:indeterminate="indeterminate"
|
|
||||||
:disabled="scenarioExecuteLoading"
|
|
||||||
@change="handleChangeAll"
|
|
||||||
/>
|
|
||||||
<div class="flex items-center gap-[4px]">
|
|
||||||
{{ t('apiScenario.sum') }}
|
|
||||||
<div class="text-[rgb(var(--primary-5))]">{{ totalStepCount }}</div>
|
|
||||||
{{ t('apiScenario.steps') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="action-group">
|
|
||||||
<a-tooltip :content="isExpandAll ? t('apiScenario.collapseAllStep') : t('apiScenario.expandAllStep')">
|
|
||||||
<a-button
|
|
||||||
v-show="scenario.steps.length > 0"
|
v-show="scenario.steps.length > 0"
|
||||||
type="outline"
|
v-model:model-value="checkedAll"
|
||||||
class="expand-step-btn arco-btn-outline--secondary"
|
:indeterminate="indeterminate"
|
||||||
size="mini"
|
:disabled="scenarioExecuteLoading"
|
||||||
@click="expandAllStep"
|
@change="handleChangeAll"
|
||||||
>
|
|
||||||
<MsIcon v-if="isExpandAll" type="icon-icon_comment_collapse_text_input" />
|
|
||||||
<MsIcon v-else type="icon-icon_comment_expand_text_input" />
|
|
||||||
</a-button>
|
|
||||||
</a-tooltip>
|
|
||||||
<template v-if="checkedAll || indeterminate">
|
|
||||||
<a-button type="outline" size="mini" :disabled="scenarioExecuteLoading" @click="batchEnable">
|
|
||||||
{{ t('common.batchEnable') }}
|
|
||||||
</a-button>
|
|
||||||
<a-button type="outline" size="mini" :disabled="scenarioExecuteLoading" @click="batchDisable">
|
|
||||||
{{ t('common.batchDisable') }}
|
|
||||||
</a-button>
|
|
||||||
<a-button type="outline" size="mini" :disabled="scenarioExecuteLoading" @click="batchDebug">
|
|
||||||
{{ t('common.batchDebug') }}
|
|
||||||
</a-button>
|
|
||||||
<a-button type="outline" size="mini" :disabled="scenarioExecuteLoading" @click="batchDelete">
|
|
||||||
{{ t('common.batchDelete') }}
|
|
||||||
</a-button>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="action-group ml-auto">
|
|
||||||
<template v-if="scenario.executeTime">
|
|
||||||
<div class="action-group">
|
|
||||||
<div class="text-[var(--color-text-4)]">{{ t('apiScenario.executeTime') }}</div>
|
|
||||||
<div class="text-[var(--color-text-4)]">{{ scenario.executeTime }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="action-group">
|
|
||||||
<div class="text-[var(--color-text-4)]">{{ t('apiScenario.executeResult') }}</div>
|
|
||||||
<div class="flex items-center gap-[4px]">
|
|
||||||
<div class="text-[var(--color-text-1)]">{{ t('common.success') }}</div>
|
|
||||||
<div class="text-[rgb(var(--success-6))]">{{ scenario.executeSuccessCount }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-[4px]">
|
|
||||||
<div class="text-[var(--color-text-1)]">{{ t('common.fail') }}</div>
|
|
||||||
<div class="text-[rgb(var(--success-6))]">{{ scenario.executeFailCount }}</div>
|
|
||||||
</div>
|
|
||||||
<MsButton v-if="scenario.isDebug === false" type="text" @click="checkReport">
|
|
||||||
{{ t('apiScenario.checkReport') }}
|
|
||||||
</MsButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div v-if="!checkedAll && !indeterminate" class="action-group ml-auto">
|
|
||||||
<a-input
|
|
||||||
v-model:model-value="keyword"
|
|
||||||
:placeholder="t('apiScenario.searchByName')"
|
|
||||||
allow-clear
|
|
||||||
class="w-[200px]"
|
|
||||||
/>
|
/>
|
||||||
<a-button
|
<div class="flex items-center gap-[4px]">
|
||||||
v-if="!props.isNew"
|
{{ t('apiScenario.sum') }}
|
||||||
type="outline"
|
<div class="text-[rgb(var(--primary-5))]">{{ totalStepCount }}</div>
|
||||||
class="arco-btn-outline--secondary !mr-0 !p-[8px]"
|
{{ t('apiScenario.steps') }}
|
||||||
@click="refreshStepInfo"
|
</div>
|
||||||
>
|
</div>
|
||||||
<template #icon>
|
<div class="action-group">
|
||||||
<icon-refresh class="text-[var(--color-text-4)]" />
|
<a-tooltip :content="isExpandAll ? t('apiScenario.collapseAllStep') : t('apiScenario.expandAllStep')">
|
||||||
</template>
|
<a-button
|
||||||
</a-button>
|
v-show="scenario.steps.length > 0"
|
||||||
|
type="outline"
|
||||||
|
class="expand-step-btn arco-btn-outline--secondary"
|
||||||
|
size="mini"
|
||||||
|
@click="expandAllStep"
|
||||||
|
>
|
||||||
|
<MsIcon v-if="isExpandAll" type="icon-icon_comment_collapse_text_input" />
|
||||||
|
<MsIcon v-else type="icon-icon_comment_expand_text_input" />
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<template v-if="checkedAll || indeterminate">
|
||||||
|
<a-button type="outline" size="mini" :disabled="scenarioExecuteLoading" @click="batchEnable">
|
||||||
|
{{ t('common.batchEnable') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="outline" size="mini" :disabled="scenarioExecuteLoading" @click="batchDisable">
|
||||||
|
{{ t('common.batchDisable') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="outline" size="mini" :disabled="scenarioExecuteLoading" @click="batchDebug">
|
||||||
|
{{ t('common.batchDebug') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="outline" size="mini" :disabled="scenarioExecuteLoading" @click="batchDelete">
|
||||||
|
{{ t('common.batchDelete') }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="action-group ml-auto">
|
||||||
|
<template v-if="scenario.executeTime">
|
||||||
|
<div class="action-group">
|
||||||
|
<div class="text-[var(--color-text-4)]">{{ t('apiScenario.executeTime') }}</div>
|
||||||
|
<div class="text-[var(--color-text-4)]">{{ scenario.executeTime }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="action-group">
|
||||||
|
<div class="text-[var(--color-text-4)]">{{ t('apiScenario.executeResult') }}</div>
|
||||||
|
<div class="flex items-center gap-[4px]">
|
||||||
|
<div class="text-[var(--color-text-1)]">{{ t('common.success') }}</div>
|
||||||
|
<div class="text-[rgb(var(--success-6))]">{{ scenario.executeSuccessCount }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-[4px]">
|
||||||
|
<div class="text-[var(--color-text-1)]">{{ t('common.fail') }}</div>
|
||||||
|
<div class="text-[rgb(var(--success-6))]">{{ scenario.executeFailCount }}</div>
|
||||||
|
</div>
|
||||||
|
<MsButton v-if="scenario.isDebug === false" type="text" @click="checkReport">
|
||||||
|
{{ t('apiScenario.checkReport') }}
|
||||||
|
</MsButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-if="!checkedAll && !indeterminate" class="action-group ml-auto">
|
||||||
|
<a-input
|
||||||
|
v-model:model-value="keyword"
|
||||||
|
:placeholder="t('apiScenario.searchByName')"
|
||||||
|
allow-clear
|
||||||
|
class="w-[200px]"
|
||||||
|
/>
|
||||||
|
<a-tooltip v-if="!props.isNew" position="left" :content="t('apiScenario.refreshRefScenario')">
|
||||||
|
<a-button type="outline" class="arco-btn-outline--secondary !mr-0 !p-[8px]" @click="refreshStepInfo">
|
||||||
|
<template #icon>
|
||||||
|
<icon-refresh class="text-[var(--color-text-4)]" />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="h-[calc(100%-30px)]">
|
||||||
<div class="h-[calc(100%-30px)]">
|
<stepTree
|
||||||
<stepTree
|
ref="stepTreeRef"
|
||||||
ref="stepTreeRef"
|
v-model:selected-keys="selectedKeys"
|
||||||
v-model:steps="scenario.steps"
|
v-model:steps="scenario.steps"
|
||||||
v-model:checked-keys="checkedKeys"
|
v-model:checked-keys="checkedKeys"
|
||||||
v-model:stepKeyword="keyword"
|
v-model:stepKeyword="keyword"
|
||||||
v-model:scenario="scenario"
|
v-model:scenario="scenario"
|
||||||
:expand-all="isExpandAll"
|
:expand-all="isExpandAll"
|
||||||
:step-details="scenario.stepDetails"
|
:step-details="scenario.stepDetails"
|
||||||
@update-resource="handleUpdateResource"
|
@step-add="handleAddStepDone"
|
||||||
/>
|
@update-resource="handleUpdateResource"
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
</div>
|
</div>
|
||||||
<a-modal
|
<a-modal
|
||||||
v-model:visible="batchToggleVisible"
|
v-model:visible="batchToggleVisible"
|
||||||
|
@ -120,18 +121,20 @@
|
||||||
// import dayjs from 'dayjs';
|
// import dayjs from 'dayjs';
|
||||||
|
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
import stepTree from './stepTree.vue';
|
import stepTree from './stepTree.vue';
|
||||||
|
|
||||||
|
import { getScenarioDetail } from '@/api/modules/api-test/scenario';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
||||||
import { deleteNodes, filterTree, getGenerateId, mapTree } from '@/utils';
|
import { deleteNodes, filterTree, getGenerateId, mapTree, traverseTree } from '@/utils';
|
||||||
import { countNodes } from '@/utils/tree';
|
import { countNodes } from '@/utils/tree';
|
||||||
|
|
||||||
import { ApiScenarioDebugRequest, Scenario } from '@/models/apiTest/scenario';
|
import { ApiScenarioDebugRequest, Scenario, ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||||
import { ScenarioExecuteStatus, ScenarioStepType } from '@/enums/apiEnum';
|
import { ScenarioExecuteStatus, ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum';
|
||||||
import { ApiTestRouteEnum } from '@/enums/routeEnum';
|
import { ApiTestRouteEnum } from '@/enums/routeEnum';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -148,11 +151,13 @@
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
const scenarioExecuteLoading = inject<Ref<boolean>>('scenarioExecuteLoading');
|
const scenarioExecuteLoading = inject<Ref<boolean>>('scenarioExecuteLoading');
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
const checkedAll = ref(false); // 是否全选
|
const checkedAll = ref(false); // 是否全选
|
||||||
const indeterminate = ref(false); // 是否半选
|
const indeterminate = ref(false); // 是否半选
|
||||||
const isExpandAll = ref(false); // 是否展开全部
|
const isExpandAll = ref(false); // 是否展开全部
|
||||||
const checkedKeys = ref<(string | number)[]>([]); // 选中的key
|
const checkedKeys = ref<(string | number)[]>([]); // 选中的key
|
||||||
|
const selectedKeys = ref<(string | number)[]>([]); // 没啥用,现在用来展示选中样式
|
||||||
const stepTreeRef = ref<InstanceType<typeof stepTree>>();
|
const stepTreeRef = ref<InstanceType<typeof stepTree>>();
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
|
|
||||||
|
@ -184,10 +189,17 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function handleAddStepDone() {
|
||||||
|
checkedKeys.value = [];
|
||||||
|
checkedAll.value = false;
|
||||||
|
indeterminate.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => scenario.value.steps.length,
|
() => scenario.value.id,
|
||||||
() => {
|
() => {
|
||||||
checkedKeys.value = [];
|
checkedKeys.value = [];
|
||||||
|
selectedKeys.value = [];
|
||||||
checkedAll.value = false;
|
checkedAll.value = false;
|
||||||
indeterminate.value = false;
|
indeterminate.value = false;
|
||||||
}
|
}
|
||||||
|
@ -259,8 +271,73 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshStepInfo() {
|
/**
|
||||||
console.log('刷新步骤信息');
|
* 刷新引用场景的步骤数据
|
||||||
|
*/
|
||||||
|
async function refreshStepInfo() {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
if (scenario.value.id) {
|
||||||
|
const res = await getScenarioDetail(scenario.value.id);
|
||||||
|
const refScenarioMap = new Map<string, ScenarioStepItem>();
|
||||||
|
traverseTree(
|
||||||
|
res.steps,
|
||||||
|
(node) => {
|
||||||
|
if (
|
||||||
|
node.stepType === ScenarioStepType.API_SCENARIO &&
|
||||||
|
[ScenarioStepRefType.REF, ScenarioStepRefType.PARTIAL_REF].includes(node.refType)
|
||||||
|
) {
|
||||||
|
// 是引用的场景就存储起来
|
||||||
|
refScenarioMap.set(node.id, node as ScenarioStepItem);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(node) => {
|
||||||
|
// 是引用的场景就没必要再递归子孙节点了
|
||||||
|
return (
|
||||||
|
node.stepType !== ScenarioStepType.API_SCENARIO &&
|
||||||
|
[ScenarioStepRefType.REF, ScenarioStepRefType.PARTIAL_REF].includes(node.refType)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
scenario.value.steps = mapTree(scenario.value.steps, (node) => {
|
||||||
|
const newStep = refScenarioMap.get(node.id);
|
||||||
|
if (newStep) {
|
||||||
|
node = {
|
||||||
|
...cloneDeep(node), // 避免前端初始化的东西被丢弃
|
||||||
|
...newStep,
|
||||||
|
};
|
||||||
|
node.children = mapTree(newStep.children || [], (child) => {
|
||||||
|
if (
|
||||||
|
child.parent &&
|
||||||
|
child.parent.stepType === ScenarioStepType.API_SCENARIO &&
|
||||||
|
[ScenarioStepRefType.REF, ScenarioStepRefType.PARTIAL_REF].includes(child.parent.refType)
|
||||||
|
) {
|
||||||
|
// 如果根节点是引用场景
|
||||||
|
child.isQuoteScenarioStep = true; // 标记为引用场景下的子步骤
|
||||||
|
child.isRefScenarioStep = child.parent.refType === ScenarioStepRefType.REF; // 标记为完全引用场景
|
||||||
|
child.draggable = false; // 引用场景下的任何步骤不可拖拽
|
||||||
|
} else if (child.parent) {
|
||||||
|
// 如果有父节点
|
||||||
|
child.isQuoteScenarioStep = child.parent.isQuoteScenarioStep; // 复用父节点的引用场景标记
|
||||||
|
child.isRefScenarioStep = child.parent.isRefScenarioStep; // 复用父节点的是否完全引用场景标记
|
||||||
|
}
|
||||||
|
if (selectedKeys.value.includes(node.id) && !selectedKeys.value.includes(child.id)) {
|
||||||
|
// 如果有新增的子步骤,且当前步骤被选中,则这个新增的子步骤也要选中
|
||||||
|
selectedKeys.value.push(child.id);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
}) as ScenarioStepItem[];
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
Message.success(t('apiScenario.updateRefScenarioSuccess'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function batchDebug() {
|
function batchDebug() {
|
||||||
|
@ -272,14 +349,8 @@
|
||||||
// 如果步骤未开启,则删除已选 id,方便下面waitingDebugStepDetails详情判断是否携带
|
// 如果步骤未开启,则删除已选 id,方便下面waitingDebugStepDetails详情判断是否携带
|
||||||
checkedKeysSet.delete(node.id);
|
checkedKeysSet.delete(node.id);
|
||||||
node.executeStatus = undefined;
|
node.executeStatus = undefined;
|
||||||
} else if (
|
|
||||||
[ScenarioStepType.API, ScenarioStepType.API_CASE, ScenarioStepType.CUSTOM_REQUEST].includes(node.stepType)
|
|
||||||
) {
|
|
||||||
// 请求和场景类型才直接显示执行中,其他控制器需要等待执行完毕才结算执行结果
|
|
||||||
node.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
|
||||||
} else {
|
} else {
|
||||||
// 其他类型步骤不显示执行状态
|
node.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
||||||
node.executeStatus = undefined;
|
|
||||||
}
|
}
|
||||||
return !!node.enable;
|
return !!node.enable;
|
||||||
}
|
}
|
||||||
|
@ -329,8 +400,9 @@
|
||||||
.action-line {
|
.action-line {
|
||||||
@apply flex items-center;
|
@apply flex items-center;
|
||||||
|
|
||||||
gap: 16px;
|
margin-bottom: 8px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
gap: 16px;
|
||||||
.action-group {
|
.action-group {
|
||||||
@apply flex items-center;
|
@apply flex items-center;
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
class="w-[100px] px-[8px]"
|
class="w-[100px] px-[8px]"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
:placeholder="t('apiScenario.variable', { suffix: '${var}' })"
|
:placeholder="t('apiScenario.variable', { suffix: '${var}' })"
|
||||||
|
:disabled="props.disabled"
|
||||||
@change="handleInputChange"
|
@change="handleInputChange"
|
||||||
>
|
>
|
||||||
</a-input>
|
</a-input>
|
||||||
|
@ -15,6 +16,7 @@
|
||||||
v-model:model-value="innerData.condition"
|
v-model:model-value="innerData.condition"
|
||||||
size="mini"
|
size="mini"
|
||||||
class="w-[90px] px-[8px]"
|
class="w-[90px] px-[8px]"
|
||||||
|
:disabled="props.disabled"
|
||||||
@change="handleInputChange"
|
@change="handleInputChange"
|
||||||
>
|
>
|
||||||
<a-option v-for="opt of conditionOptions" :key="opt.value" :value="opt.value">
|
<a-option v-for="opt of conditionOptions" :key="opt.value" :value="opt.value">
|
||||||
|
@ -28,6 +30,7 @@
|
||||||
size="mini"
|
size="mini"
|
||||||
class="w-[110px] px-[8px]"
|
class="w-[110px] px-[8px]"
|
||||||
:placeholder="t('apiScenario.value')"
|
:placeholder="t('apiScenario.value')"
|
||||||
|
:disabled="props.disabled"
|
||||||
@change="handleInputChange"
|
@change="handleInputChange"
|
||||||
>
|
>
|
||||||
</a-input>
|
</a-input>
|
||||||
|
@ -45,6 +48,7 @@
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: ConditionStepDetail;
|
data: ConditionStepDetail;
|
||||||
stepId: string | number;
|
stepId: string | number;
|
||||||
|
disabled: boolean;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'change', innerData: ConditionStepDetail): void;
|
(e: 'change', innerData: ConditionStepDetail): void;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
:options="loopOptions"
|
:options="loopOptions"
|
||||||
size="mini"
|
size="mini"
|
||||||
class="w-[85px] px-[8px]"
|
class="w-[85px] px-[8px]"
|
||||||
|
:disabled="props.disabled"
|
||||||
@change="handleInputChange"
|
@change="handleInputChange"
|
||||||
/>
|
/>
|
||||||
<a-tooltip
|
<a-tooltip
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
hide-button
|
hide-button
|
||||||
:precision="0"
|
:precision="0"
|
||||||
model-event="input"
|
model-event="input"
|
||||||
|
:disabled="props.disabled"
|
||||||
@blur="handleInputChange"
|
@blur="handleInputChange"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
|
@ -38,6 +40,7 @@
|
||||||
class="w-[110px] px-[8px]"
|
class="w-[110px] px-[8px]"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
:placeholder="t('apiScenario.variable')"
|
:placeholder="t('apiScenario.variable')"
|
||||||
|
:disabled="props.disabled"
|
||||||
@change="handleInputChange"
|
@change="handleInputChange"
|
||||||
>
|
>
|
||||||
</a-input>
|
</a-input>
|
||||||
|
@ -50,6 +53,7 @@
|
||||||
class="w-[110px] px-[8px]"
|
class="w-[110px] px-[8px]"
|
||||||
:placeholder="t('apiScenario.valuePrefix')"
|
:placeholder="t('apiScenario.valuePrefix')"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
|
:disabled="props.disabled"
|
||||||
@change="handleInputChange"
|
@change="handleInputChange"
|
||||||
>
|
>
|
||||||
</a-input>
|
</a-input>
|
||||||
|
@ -61,6 +65,7 @@
|
||||||
:options="whileOptions"
|
:options="whileOptions"
|
||||||
size="mini"
|
size="mini"
|
||||||
class="w-[75px] px-[8px]"
|
class="w-[75px] px-[8px]"
|
||||||
|
:disabled="props.disabled"
|
||||||
@change="handleInputChange"
|
@change="handleInputChange"
|
||||||
/>
|
/>
|
||||||
<template v-if="innerData.whileController.conditionType === WhileConditionType.CONDITION">
|
<template v-if="innerData.whileController.conditionType === WhileConditionType.CONDITION">
|
||||||
|
@ -74,6 +79,7 @@
|
||||||
class="w-[100px] px-[8px]"
|
class="w-[100px] px-[8px]"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
:placeholder="t('apiScenario.variable', { suffix: '${var}' })"
|
:placeholder="t('apiScenario.variable', { suffix: '${var}' })"
|
||||||
|
:disabled="props.disabled"
|
||||||
@change="handleInputChange"
|
@change="handleInputChange"
|
||||||
>
|
>
|
||||||
</a-input>
|
</a-input>
|
||||||
|
@ -82,6 +88,7 @@
|
||||||
v-model:model-value="innerData.whileController.msWhileVariable.condition"
|
v-model:model-value="innerData.whileController.msWhileVariable.condition"
|
||||||
size="mini"
|
size="mini"
|
||||||
class="w-[90px] px-[8px]"
|
class="w-[90px] px-[8px]"
|
||||||
|
:disabled="props.disabled"
|
||||||
@change="handleInputChange"
|
@change="handleInputChange"
|
||||||
>
|
>
|
||||||
<a-option v-for="opt of conditionOptions" :key="opt.value" :value="opt.value">
|
<a-option v-for="opt of conditionOptions" :key="opt.value" :value="opt.value">
|
||||||
|
@ -98,6 +105,7 @@
|
||||||
size="mini"
|
size="mini"
|
||||||
class="w-[110px] px-[8px]"
|
class="w-[110px] px-[8px]"
|
||||||
:placeholder="t('apiScenario.value')"
|
:placeholder="t('apiScenario.value')"
|
||||||
|
:disabled="props.disabled"
|
||||||
@change="handleInputChange"
|
@change="handleInputChange"
|
||||||
>
|
>
|
||||||
</a-input>
|
</a-input>
|
||||||
|
@ -114,6 +122,7 @@
|
||||||
size="mini"
|
size="mini"
|
||||||
class="w-[200px] px-[8px]"
|
class="w-[200px] px-[8px]"
|
||||||
:placeholder="t('apiScenario.expression')"
|
:placeholder="t('apiScenario.expression')"
|
||||||
|
:disabled="props.disabled"
|
||||||
@change="handleInputChange"
|
@change="handleInputChange"
|
||||||
>
|
>
|
||||||
</a-input>
|
</a-input>
|
||||||
|
@ -131,6 +140,7 @@
|
||||||
hide-button
|
hide-button
|
||||||
:precision="0"
|
:precision="0"
|
||||||
model-event="input"
|
model-event="input"
|
||||||
|
:disabled="props.disabled"
|
||||||
@blur="handleInputChange"
|
@blur="handleInputChange"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
|
@ -154,6 +164,7 @@
|
||||||
hide-button
|
hide-button
|
||||||
class="w-[110px] px-[8px]"
|
class="w-[110px] px-[8px]"
|
||||||
model-event="input"
|
model-event="input"
|
||||||
|
:disabled="props.disabled"
|
||||||
@blur="handleInputChange"
|
@blur="handleInputChange"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
|
@ -170,6 +181,7 @@
|
||||||
hide-button
|
hide-button
|
||||||
class="w-[110px] px-[8px]"
|
class="w-[110px] px-[8px]"
|
||||||
model-event="input"
|
model-event="input"
|
||||||
|
:disabled="props.disabled"
|
||||||
@blur="handleInputChange"
|
@blur="handleInputChange"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
|
@ -191,6 +203,7 @@
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: LoopStepDetail;
|
data: LoopStepDetail;
|
||||||
stepId: string | number;
|
stepId: string | number;
|
||||||
|
disabled: boolean;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'change', innerData: LoopStepDetail): void;
|
(e: 'change', innerData: LoopStepDetail): void;
|
||||||
|
@ -227,7 +240,6 @@
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
innerData.value = props.data;
|
innerData.value = props.data;
|
||||||
console.log('watchEffect', props.data);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 接收全局双击时间戳
|
// 接收全局双击时间戳
|
||||||
|
|
|
@ -5,14 +5,19 @@
|
||||||
"
|
"
|
||||||
class="flex items-center gap-[4px]"
|
class="flex items-center gap-[4px]"
|
||||||
>
|
>
|
||||||
<a-popover position="bl" content-class="detail-popover" arrow-class="hidden">
|
<a-popover
|
||||||
|
position="bl"
|
||||||
|
content-class="quote-content-detail-popover"
|
||||||
|
arrow-class="hidden"
|
||||||
|
@popup-visible-change="handleVisibleChange"
|
||||||
|
>
|
||||||
<MsIcon type="icon-icon-draft" class="text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]" />
|
<MsIcon type="icon-icon-draft" class="text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]" />
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="flex flex-col gap-[16px]">
|
<div class="flex flex-col gap-[16px]">
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-[2px] text-[var(--color-text-4)]">{{ t('apiScenario.belongProject') }}</div>
|
<div class="mb-[2px] text-[var(--color-text-4)]">{{ t('apiScenario.belongProject') }}</div>
|
||||||
<div class="text-[14px] text-[var(--color-text-1)]">
|
<div class="text-[14px] text-[var(--color-text-1)]">
|
||||||
<!-- {{ props.data.belongProjectName }} -->
|
{{ originProjectName }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -43,6 +48,7 @@
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||||
|
|
||||||
|
import { getStepProjectInfo } from '@/api/modules/api-test/scenario';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
@ -61,6 +67,15 @@
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openNewPage } = useOpenNewPage();
|
const { openNewPage } = useOpenNewPage();
|
||||||
|
|
||||||
|
const originProjectName = ref('');
|
||||||
|
|
||||||
|
async function handleVisibleChange(val: boolean) {
|
||||||
|
if (val && props.data.originProjectId) {
|
||||||
|
const res = await getStepProjectInfo(props.data.originProjectId);
|
||||||
|
originProjectName.value = res.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function goDetail() {
|
function goDetail() {
|
||||||
const _stepType = getStepType(props.data);
|
const _stepType = getStepType(props.data);
|
||||||
switch (true) {
|
switch (true) {
|
||||||
|
@ -91,8 +106,8 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less">
|
||||||
.detail-popover {
|
.quote-content-detail-popover {
|
||||||
width: 350px;
|
width: 300px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
hide-button
|
hide-button
|
||||||
:precision="0"
|
:precision="0"
|
||||||
model-event="input"
|
model-event="input"
|
||||||
|
:disabled="props.disabled"
|
||||||
@blur="handleInputChange"
|
@blur="handleInputChange"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: WaitTimeContentProps;
|
data: WaitTimeContentProps;
|
||||||
|
disabled: boolean;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'change', innerData: WaitTimeContentProps): void;
|
(e: 'change', innerData: WaitTimeContentProps): void;
|
||||||
|
|
|
@ -60,8 +60,8 @@
|
||||||
<div class="mr-[8px] flex items-center gap-[8px]">
|
<div class="mr-[8px] flex items-center gap-[8px]">
|
||||||
<!-- 步骤启用/禁用,完全引用的场景下的子孙步骤不可禁用 -->
|
<!-- 步骤启用/禁用,完全引用的场景下的子孙步骤不可禁用 -->
|
||||||
<a-switch
|
<a-switch
|
||||||
v-show="step.isRefScenarioStep !== true"
|
|
||||||
v-model:model-value="step.enable"
|
v-model:model-value="step.enable"
|
||||||
|
:disabled="step.isRefScenarioStep"
|
||||||
size="small"
|
size="small"
|
||||||
@click.stop="handleStepToggleEnable(step)"
|
@click.stop="handleStepToggleEnable(step)"
|
||||||
></a-switch>
|
></a-switch>
|
||||||
|
@ -90,6 +90,7 @@
|
||||||
:is="getStepContent(step)"
|
:is="getStepContent(step)"
|
||||||
:data="checkStepIsApi(step) || step.stepType === ScenarioStepType.API_SCENARIO ? step : step.config"
|
:data="checkStepIsApi(step) || step.stepType === ScenarioStepType.API_SCENARIO ? step : step.config"
|
||||||
:step-id="step.id"
|
:step-id="step.id"
|
||||||
|
:disabled="!!step.isQuoteScenarioStep"
|
||||||
@quick-input="setQuickInput(step, $event)"
|
@quick-input="setQuickInput(step, $event)"
|
||||||
@change="handleStepContentChange($event, step)"
|
@change="handleStepContentChange($event, step)"
|
||||||
@click.stop
|
@click.stop
|
||||||
|
@ -117,6 +118,7 @@
|
||||||
{{ step.name }}
|
{{ step.name }}
|
||||||
</div>
|
</div>
|
||||||
<MsIcon
|
<MsIcon
|
||||||
|
v-if="!step.isQuoteScenarioStep"
|
||||||
type="icon-icon_edit_outlined"
|
type="icon-icon_edit_outlined"
|
||||||
class="edit-script-name-icon"
|
class="edit-script-name-icon"
|
||||||
@click.stop="handleStepNameClick(step)"
|
@click.stop="handleStepNameClick(step)"
|
||||||
|
@ -155,6 +157,7 @@
|
||||||
{{ step.name || t('apiScenario.pleaseInputStepDesc') }}
|
{{ step.name || t('apiScenario.pleaseInputStepDesc') }}
|
||||||
</div>
|
</div>
|
||||||
<MsIcon
|
<MsIcon
|
||||||
|
v-if="!step.isQuoteScenarioStep"
|
||||||
type="icon-icon_edit_outlined"
|
type="icon-icon_edit_outlined"
|
||||||
class="edit-script-name-icon"
|
class="edit-script-name-icon"
|
||||||
@click.stop="handleStepDescClick(step)"
|
@click.stop="handleStepDescClick(step)"
|
||||||
|
@ -174,17 +177,17 @@
|
||||||
@click="setFocusNodeKey(step.id)"
|
@click="setFocusNodeKey(step.id)"
|
||||||
@other-create="handleOtherCreate"
|
@other-create="handleOtherCreate"
|
||||||
@close="setFocusNodeKey('')"
|
@close="setFocusNodeKey('')"
|
||||||
@add-done="scenario.unSaved = true"
|
@add-done="handleAddStepDone"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #extraEnd="step">
|
<template #extraEnd="step">
|
||||||
<responsePopover
|
<responsePopover
|
||||||
v-if="
|
v-if="
|
||||||
![
|
[
|
||||||
ScenarioStepType.LOOP_CONTROLLER,
|
ScenarioStepType.API,
|
||||||
ScenarioStepType.IF_CONTROLLER,
|
ScenarioStepType.API_CASE,
|
||||||
ScenarioStepType.ONCE_ONLY_CONTROLLER,
|
ScenarioStepType.SCRIPT,
|
||||||
ScenarioStepType.CONSTANT_TIMER,
|
ScenarioStepType.CUSTOM_REQUEST,
|
||||||
].includes(step.stepType) &&
|
].includes(step.stepType) &&
|
||||||
(getExecuteStatus(step) === ScenarioExecuteStatus.SUCCESS ||
|
(getExecuteStatus(step) === ScenarioExecuteStatus.SUCCESS ||
|
||||||
getExecuteStatus(step) === ScenarioExecuteStatus.FAILED)
|
getExecuteStatus(step) === ScenarioExecuteStatus.FAILED)
|
||||||
|
@ -213,7 +216,7 @@
|
||||||
<createStepActions
|
<createStepActions
|
||||||
v-model:selected-keys="selectedKeys"
|
v-model:selected-keys="selectedKeys"
|
||||||
v-model:steps="steps"
|
v-model:steps="steps"
|
||||||
@add-done="scenario.unSaved = true"
|
@add-done="handleAddStepDone"
|
||||||
@other-create="handleOtherCreate"
|
@other-create="handleOtherCreate"
|
||||||
>
|
>
|
||||||
<a-button type="dashed" class="add-step-btn" long>
|
<a-button type="dashed" class="add-step-btn" long>
|
||||||
|
@ -248,6 +251,7 @@
|
||||||
<importApiDrawer
|
<importApiDrawer
|
||||||
v-if="importApiDrawerVisible"
|
v-if="importApiDrawerVisible"
|
||||||
v-model:visible="importApiDrawerVisible"
|
v-model:visible="importApiDrawerVisible"
|
||||||
|
:scenario-id="scenario.id"
|
||||||
@copy="handleImportApiApply('copy', $event)"
|
@copy="handleImportApiApply('copy', $event)"
|
||||||
@quote="handleImportApiApply('quote', $event)"
|
@quote="handleImportApiApply('quote', $event)"
|
||||||
/>
|
/>
|
||||||
|
@ -452,6 +456,7 @@
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'updateResource', uploadFileIds: string[], linkFileIds: string[]): void;
|
(e: 'updateResource', uploadFileIds: string[], linkFileIds: string[]): void;
|
||||||
|
(e: 'stepAdd'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
@ -470,11 +475,13 @@
|
||||||
const scenario = defineModel<Scenario>('scenario', {
|
const scenario = defineModel<Scenario>('scenario', {
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
|
const selectedKeys = defineModel<(string | number)[]>('selectedKeys', {
|
||||||
|
required: true,
|
||||||
|
}); // 没啥用,目前用来展示选中样式
|
||||||
const isPriorityLocalExec = inject<Ref<boolean>>('isPriorityLocalExec');
|
const isPriorityLocalExec = inject<Ref<boolean>>('isPriorityLocalExec');
|
||||||
const localExecuteUrl = inject<Ref<string>>('localExecuteUrl');
|
const localExecuteUrl = inject<Ref<string>>('localExecuteUrl');
|
||||||
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
|
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
|
||||||
|
|
||||||
const selectedKeys = ref<(string | number)[]>([]); // 没啥用,目前用来展示选中样式
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const treeRef = ref<InstanceType<typeof MsTree>>();
|
const treeRef = ref<InstanceType<typeof MsTree>>();
|
||||||
const focusStepKey = ref<string | number>(''); // 聚焦的key
|
const focusStepKey = ref<string | number>(''); // 聚焦的key
|
||||||
|
@ -489,9 +496,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExecuteStatus(step: ScenarioStepItem) {
|
function getExecuteStatus(step: ScenarioStepItem) {
|
||||||
if (scenario.value.stepResponses && scenario.value.stepResponses[step.id]) {
|
if (scenario.value.stepResponses && scenario.value.stepResponses[step.uniqueId]) {
|
||||||
// 有一次失败就是失败
|
// 有一次失败就是失败
|
||||||
return scenario.value.stepResponses[step.id].some((report) => !report.isSuccessful)
|
return scenario.value.stepResponses[step.uniqueId].some((report) => !report.isSuccessful)
|
||||||
? ScenarioExecuteStatus.FAILED
|
? ScenarioExecuteStatus.FAILED
|
||||||
: ScenarioExecuteStatus.SUCCESS;
|
: ScenarioExecuteStatus.SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -509,8 +516,8 @@
|
||||||
const firstHasResultChild = step.children?.find((child) => {
|
const firstHasResultChild = step.children?.find((child) => {
|
||||||
return checkStepIsApi(child) || child.stepType === ScenarioStepType.SCRIPT;
|
return checkStepIsApi(child) || child.stepType === ScenarioStepType.SCRIPT;
|
||||||
});
|
});
|
||||||
return firstHasResultChild && scenario.value.stepResponses[firstHasResultChild.id]
|
return firstHasResultChild && scenario.value.stepResponses[firstHasResultChild.uniqueId]
|
||||||
? `${scenario.value.stepResponses[firstHasResultChild.id].length}/${step.config.msCountController.loops}`
|
? `${scenario.value.stepResponses[firstHasResultChild.uniqueId].length}/${step.config.msCountController.loops}`
|
||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -798,6 +805,7 @@
|
||||||
executeStatus: undefined,
|
executeStatus: undefined,
|
||||||
copyFromStepId: childCopyFromStepId,
|
copyFromStepId: childCopyFromStepId,
|
||||||
id: childId,
|
id: childId,
|
||||||
|
uniqueId: childId,
|
||||||
};
|
};
|
||||||
})[0]
|
})[0]
|
||||||
),
|
),
|
||||||
|
@ -806,6 +814,7 @@
|
||||||
sort: node.sort + 1,
|
sort: node.sort + 1,
|
||||||
isNew: true,
|
isNew: true,
|
||||||
id,
|
id,
|
||||||
|
uniqueId: id,
|
||||||
},
|
},
|
||||||
'after',
|
'after',
|
||||||
selectedIfNeed,
|
selectedIfNeed,
|
||||||
|
@ -950,6 +959,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleAddStepDone() {
|
||||||
|
emit('stepAdd');
|
||||||
|
scenario.value.unSaved = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理步骤选中事件
|
* 处理步骤选中事件
|
||||||
* @param _selectedKeys 选中的 key集合
|
* @param _selectedKeys 选中的 key集合
|
||||||
|
@ -1000,27 +1014,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const websocketMap: Record<string | number, WebSocket> = {};
|
const websocketMap: Record<string | number, WebSocket> = {};
|
||||||
let temporaryStepReportMap = {}; // 缓存websocket返回的报告内容,避免执行接口后切换场景tab导致报告丢失
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => scenario.value.id,
|
|
||||||
() => {
|
|
||||||
const stepKeys = Object.keys(temporaryStepReportMap);
|
|
||||||
if (stepKeys.length > 0) {
|
|
||||||
stepKeys.forEach((key) => {
|
|
||||||
const report = temporaryStepReportMap[key];
|
|
||||||
scenario.value.stepResponses[report.stepId] = temporaryStepReportMap[key];
|
|
||||||
});
|
|
||||||
temporaryStepReportMap = {};
|
|
||||||
updateStepStatus(steps.value, scenario.value.stepResponses);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开启websocket监听,接收执行结果
|
* 开启websocket监听,接收执行结果
|
||||||
*/
|
*/
|
||||||
function debugSocket(step: ScenarioStepItem, reportId: string | number, executeType?: 'localExec' | 'serverExec') {
|
function debugSocket(
|
||||||
|
step: ScenarioStepItem,
|
||||||
|
_scenario: Scenario,
|
||||||
|
reportId: string | number,
|
||||||
|
executeType?: 'localExec' | 'serverExec'
|
||||||
|
) {
|
||||||
websocketMap[reportId] = getSocket(
|
websocketMap[reportId] = getSocket(
|
||||||
reportId || '',
|
reportId || '',
|
||||||
executeType === 'localExec' ? '/ws/debug' : '',
|
executeType === 'localExec' ? '/ws/debug' : '',
|
||||||
|
@ -1032,34 +1035,21 @@
|
||||||
if (step.reportId === data.reportId) {
|
if (step.reportId === data.reportId) {
|
||||||
// 判断当前查看的tab是否是当前返回的报告的tab,是的话直接赋值
|
// 判断当前查看的tab是否是当前返回的报告的tab,是的话直接赋值
|
||||||
data.taskResult.requestResults.forEach((result) => {
|
data.taskResult.requestResults.forEach((result) => {
|
||||||
if (scenario.value.stepResponses[result.stepId] === undefined) {
|
if (_scenario.stepResponses[result.stepId] === undefined) {
|
||||||
scenario.value.stepResponses[result.stepId] = [];
|
_scenario.stepResponses[result.stepId] = [];
|
||||||
}
|
}
|
||||||
scenario.value.stepResponses[result.stepId].push({
|
_scenario.stepResponses[result.stepId].push({
|
||||||
...result,
|
...result,
|
||||||
console: data.taskResult.console,
|
console: data.taskResult.console,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
// 不是则需要把报告缓存起来,等切换到对应的tab再赋值
|
|
||||||
data.taskResult.requestResults.forEach((result) => {
|
|
||||||
if (step.reportId) {
|
|
||||||
if (temporaryStepReportMap[step.reportId] === undefined) {
|
|
||||||
temporaryStepReportMap[step.reportId] = [];
|
|
||||||
}
|
|
||||||
temporaryStepReportMap[step.reportId].push({
|
|
||||||
...result,
|
|
||||||
console: data.taskResult.console,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else if (data.msgType === 'EXEC_END') {
|
} else if (data.msgType === 'EXEC_END') {
|
||||||
// 执行结束,关闭websocket
|
// 执行结束,关闭websocket
|
||||||
websocketMap[reportId].close();
|
websocketMap[reportId]?.close();
|
||||||
if (step.reportId === data.reportId) {
|
if (step.reportId === data.reportId) {
|
||||||
step.isExecuting = false;
|
step.isExecuting = false;
|
||||||
updateStepStatus([step], scenario.value.stepResponses);
|
updateStepStatus([step], _scenario.stepResponses);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1076,7 +1066,7 @@
|
||||||
try {
|
try {
|
||||||
currentStep.isExecuting = true;
|
currentStep.isExecuting = true;
|
||||||
currentStep.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
currentStep.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
||||||
debugSocket(currentStep, executeParams.reportId, executeType); // 开启websocket
|
debugSocket(currentStep, scenario.value, executeParams.reportId, executeType); // 开启websocket
|
||||||
const res = await debugScenario({
|
const res = await debugScenario({
|
||||||
id: scenario.value.id || '',
|
id: scenario.value.id || '',
|
||||||
grouped: false,
|
grouped: false,
|
||||||
|
@ -1100,6 +1090,7 @@
|
||||||
console.log(error);
|
console.log(error);
|
||||||
websocketMap[executeParams.reportId].close();
|
websocketMap[executeParams.reportId].close();
|
||||||
currentStep.isExecuting = false;
|
currentStep.isExecuting = false;
|
||||||
|
updateStepStatus([currentStep], scenario.value.stepResponses);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1110,25 +1101,24 @@
|
||||||
if (node.isExecuting) {
|
if (node.isExecuting) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, node.id, 'id');
|
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, node.uniqueId, 'uniqueId');
|
||||||
if (realStep) {
|
if (realStep) {
|
||||||
realStep.reportId = getGenerateId();
|
realStep.reportId = getGenerateId();
|
||||||
const _stepDetails = {};
|
const _stepDetails = {};
|
||||||
const stepFileParam = scenario.value.stepFileParam[realStep.id];
|
const stepFileParam = scenario.value.stepFileParam[realStep.id];
|
||||||
traverseTree(
|
traverseTree(
|
||||||
realStep,
|
realStep,
|
||||||
(step) => {
|
|
||||||
// 当前步骤是启用的情况,才需要继续递归子孙步骤;否则无需向下递归
|
|
||||||
return step.enable;
|
|
||||||
},
|
|
||||||
(step) => {
|
(step) => {
|
||||||
if (step.enable) {
|
if (step.enable) {
|
||||||
// 启用的步骤才执行
|
// 启用的步骤才执行
|
||||||
_stepDetails[step.id] = stepDetails.value[step.id];
|
_stepDetails[step.id] = stepDetails.value[step.id];
|
||||||
step.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
step.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
||||||
}
|
}
|
||||||
delete scenario.value.stepResponses[step.id]; // 先移除上一次的执行结果
|
delete scenario.value.stepResponses[step.uniqueId]; // 先移除上一次的执行结果
|
||||||
return step;
|
},
|
||||||
|
(step) => {
|
||||||
|
// 当前步骤是启用的情况,才需要继续递归子孙步骤;否则无需向下递归
|
||||||
|
return step.enable;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
realExecute(
|
realExecute(
|
||||||
|
@ -1152,7 +1142,7 @@
|
||||||
function handleApiExecute(request: RequestParam, executeType?: 'localExec' | 'serverExec') {
|
function handleApiExecute(request: RequestParam, executeType?: 'localExec' | 'serverExec') {
|
||||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, request.stepId, 'id');
|
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, request.stepId, 'id');
|
||||||
if (realStep) {
|
if (realStep) {
|
||||||
delete scenario.value.stepResponses[realStep.id]; // 先移除上一次的执行结果
|
delete scenario.value.stepResponses[realStep.uniqueId]; // 先移除上一次的执行结果
|
||||||
realStep.reportId = getGenerateId();
|
realStep.reportId = getGenerateId();
|
||||||
realStep.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
realStep.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
||||||
request.executeLoading = true;
|
request.executeLoading = true;
|
||||||
|
@ -1184,6 +1174,7 @@
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
reportId,
|
reportId,
|
||||||
|
uniqueId: request.stepId,
|
||||||
};
|
};
|
||||||
realExecute(
|
realExecute(
|
||||||
{
|
{
|
||||||
|
@ -1288,6 +1279,7 @@
|
||||||
} else {
|
} else {
|
||||||
steps.value = steps.value.concat(insertSteps);
|
steps.value = steps.value.concat(insertSteps);
|
||||||
}
|
}
|
||||||
|
emit('stepAdd');
|
||||||
scenario.value.unSaved = true;
|
scenario.value.unSaved = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1318,6 +1310,7 @@
|
||||||
method: request.method,
|
method: request.method,
|
||||||
},
|
},
|
||||||
id: request.stepId,
|
id: request.stepId,
|
||||||
|
uniqueId: request.stepId,
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
},
|
},
|
||||||
activeStep.value,
|
activeStep.value,
|
||||||
|
@ -1333,6 +1326,7 @@
|
||||||
method: request.method,
|
method: request.method,
|
||||||
},
|
},
|
||||||
id: request.stepId,
|
id: request.stepId,
|
||||||
|
uniqueId: request.stepId,
|
||||||
sort: steps.value.length + 1,
|
sort: steps.value.length + 1,
|
||||||
stepType: ScenarioStepType.CUSTOM_REQUEST,
|
stepType: ScenarioStepType.CUSTOM_REQUEST,
|
||||||
refType: ScenarioStepRefType.DIRECT,
|
refType: ScenarioStepRefType.DIRECT,
|
||||||
|
@ -1340,6 +1334,7 @@
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
emit('stepAdd');
|
||||||
scenario.value.unSaved = true;
|
scenario.value.unSaved = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1347,6 +1342,14 @@
|
||||||
* API 详情抽屉关闭时应用更改
|
* API 详情抽屉关闭时应用更改
|
||||||
*/
|
*/
|
||||||
function applyApiStep(request: RequestParam | CaseRequestParam) {
|
function applyApiStep(request: RequestParam | CaseRequestParam) {
|
||||||
|
if (activeStep.value) {
|
||||||
|
const _stepType = getStepType(activeStep.value);
|
||||||
|
if (_stepType.isQuoteCase || activeStep.value.isQuoteScenarioStep) {
|
||||||
|
// 引用的 case 和引用的场景步骤都不可更改
|
||||||
|
stepDetails.value[activeStep.value.id] = request; // 为了设置一次正确的polymorphicName
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (request.unSaved) {
|
if (request.unSaved) {
|
||||||
scenario.value.unSaved = true;
|
scenario.value.unSaved = true;
|
||||||
}
|
}
|
||||||
|
@ -1400,6 +1403,7 @@
|
||||||
steps.value.push({
|
steps.value.push({
|
||||||
...cloneDeep(defaultStepItemCommon),
|
...cloneDeep(defaultStepItemCommon),
|
||||||
id,
|
id,
|
||||||
|
uniqueId: id,
|
||||||
sort: steps.value.length + 1,
|
sort: steps.value.length + 1,
|
||||||
stepType: ScenarioStepType.SCRIPT,
|
stepType: ScenarioStepType.SCRIPT,
|
||||||
refType: ScenarioStepRefType.DIRECT,
|
refType: ScenarioStepRefType.DIRECT,
|
||||||
|
@ -1407,6 +1411,7 @@
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
emit('stepAdd');
|
||||||
scenario.value.unSaved = true;
|
scenario.value.unSaved = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1482,6 +1487,7 @@
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
console.log(dragNode, dropNode);
|
||||||
const dragResult = handleTreeDragDrop(steps.value, dragNode, dropNode, dropPosition, 'id');
|
const dragResult = handleTreeDragDrop(steps.value, dragNode, dropNode, dropPosition, 'id');
|
||||||
if (dragResult) {
|
if (dragResult) {
|
||||||
Message.success(t('common.moveSuccess'));
|
Message.success(t('common.moveSuccess'));
|
||||||
|
|
|
@ -17,9 +17,14 @@ export default function updateStepStatus(
|
||||||
ScenarioStepType.LOOP_CONTROLLER,
|
ScenarioStepType.LOOP_CONTROLLER,
|
||||||
ScenarioStepType.IF_CONTROLLER,
|
ScenarioStepType.IF_CONTROLLER,
|
||||||
ScenarioStepType.ONCE_ONLY_CONTROLLER,
|
ScenarioStepType.ONCE_ONLY_CONTROLLER,
|
||||||
|
ScenarioStepType.API_SCENARIO,
|
||||||
].includes(node.stepType)
|
].includes(node.stepType)
|
||||||
) {
|
) {
|
||||||
// 逻辑控制器内部可以放入任意步骤,所以它的最终执行结果是根据内部步骤的执行结果来判断的
|
if (!node.executeStatus) {
|
||||||
|
// 没有执行状态,说明未参与执行,直接跳过
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 逻辑控制器和场景内部可以放入任意步骤,所以它的最终执行结果是根据内部步骤的执行结果来判断的
|
||||||
let hasNotExecuted = false;
|
let hasNotExecuted = false;
|
||||||
let hasFailure = false;
|
let hasFailure = false;
|
||||||
if (!node.children || node.children.length === 0) {
|
if (!node.children || node.children.length === 0) {
|
||||||
|
@ -54,8 +59,8 @@ export default function updateStepStatus(
|
||||||
node.executeStatus = ScenarioExecuteStatus.SUCCESS;
|
node.executeStatus = ScenarioExecuteStatus.SUCCESS;
|
||||||
} else if (node.executeStatus === ScenarioExecuteStatus.EXECUTING) {
|
} else if (node.executeStatus === ScenarioExecuteStatus.EXECUTING) {
|
||||||
// 非逻辑控制器直接更改本身状态
|
// 非逻辑控制器直接更改本身状态
|
||||||
if (stepResponses[node.id] && stepResponses[node.id].length > 0) {
|
if (stepResponses[node.uniqueId] && stepResponses[node.uniqueId].length > 0) {
|
||||||
node.executeStatus = stepResponses[node.id].some((report) => !report.isSuccessful)
|
node.executeStatus = stepResponses[node.uniqueId].some((report) => !report.isSuccessful)
|
||||||
? ScenarioExecuteStatus.FAILED
|
? ScenarioExecuteStatus.FAILED
|
||||||
: ScenarioExecuteStatus.SUCCESS;
|
: ScenarioExecuteStatus.SUCCESS;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -158,65 +158,48 @@
|
||||||
const currentEnvConfig = ref<EnvConfig>();
|
const currentEnvConfig = ref<EnvConfig>();
|
||||||
const executeButtonRef = ref<InstanceType<typeof executeButton>>();
|
const executeButtonRef = ref<InstanceType<typeof executeButton>>();
|
||||||
|
|
||||||
const websocket = ref<WebSocket>();
|
const websocketMap: Record<string | number, WebSocket> = {};
|
||||||
const temporaryScenarioReportMap = {}; // 缓存websocket返回的报告内容,避免执行接口后切换tab导致报告丢失
|
|
||||||
|
|
||||||
function setStepExecuteStatus() {
|
function setStepExecuteStatus(scenario: Scenario) {
|
||||||
updateStepStatus(activeScenarioTab.value.steps, activeScenarioTab.value.stepResponses);
|
updateStepStatus(scenario.steps, scenario.stepResponses);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开启websocket监听,接收执行结果
|
* 开启websocket监听,接收执行结果
|
||||||
*/
|
*/
|
||||||
function debugSocket(reportId?: string | number, executeType?: 'localExec' | 'serverExec', localExecuteUrl?: string) {
|
function debugSocket(scenario: Scenario, executeType?: 'localExec' | 'serverExec', localExecuteUrl?: string) {
|
||||||
websocket.value = getSocket(
|
websocketMap[scenario.reportId] = getSocket(
|
||||||
reportId || '',
|
scenario.reportId || '',
|
||||||
executeType === 'localExec' ? '/ws/debug' : '',
|
executeType === 'localExec' ? '/ws/debug' : '',
|
||||||
executeType === 'localExec' ? localExecuteUrl : ''
|
executeType === 'localExec' ? localExecuteUrl : ''
|
||||||
);
|
);
|
||||||
websocket.value.addEventListener('message', (event) => {
|
websocketMap[scenario.reportId].addEventListener('message', (event) => {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
if (data.msgType === 'EXEC_RESULT') {
|
if (data.msgType === 'EXEC_RESULT') {
|
||||||
if (activeScenarioTab.value.reportId === data.reportId) {
|
if (scenario.reportId === data.reportId) {
|
||||||
// 判断当前查看的tab是否是当前返回的报告的tab,是的话直接赋值
|
// 判断当前查看的tab是否是当前返回的报告的tab,是的话直接赋值
|
||||||
data.taskResult.requestResults.forEach((result) => {
|
data.taskResult.requestResults.forEach((result) => {
|
||||||
if (activeScenarioTab.value.stepResponses[result.stepId] === undefined) {
|
if (scenario.stepResponses[result.stepId] === undefined) {
|
||||||
activeScenarioTab.value.stepResponses[result.stepId] = [];
|
scenario.stepResponses[result.stepId] = [];
|
||||||
}
|
}
|
||||||
activeScenarioTab.value.stepResponses[result.stepId].push({
|
scenario.stepResponses[result.stepId].push({
|
||||||
...result,
|
...result,
|
||||||
console: data.taskResult.console,
|
console: data.taskResult.console,
|
||||||
});
|
});
|
||||||
if (result.isSuccessful) {
|
if (result.isSuccessful) {
|
||||||
activeScenarioTab.value.executeSuccessCount += 1;
|
scenario.executeSuccessCount += 1;
|
||||||
} else {
|
} else {
|
||||||
activeScenarioTab.value.executeFailCount += 1;
|
scenario.executeFailCount += 1;
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 不是则需要把报告缓存起来,等切换到对应的tab再赋值
|
|
||||||
data.taskResult.requestResults.forEach((result) => {
|
|
||||||
if (activeScenarioTab.value.reportId) {
|
|
||||||
if (temporaryScenarioReportMap[activeScenarioTab.value.reportId] === undefined) {
|
|
||||||
temporaryScenarioReportMap[activeScenarioTab.value.reportId] = {};
|
|
||||||
}
|
|
||||||
if (temporaryScenarioReportMap[activeScenarioTab.value.reportId][result.stepId]) {
|
|
||||||
temporaryScenarioReportMap[activeScenarioTab.value.reportId][result.stepId] = [];
|
|
||||||
}
|
|
||||||
temporaryScenarioReportMap[activeScenarioTab.value.reportId][result.stepId].push({
|
|
||||||
...result,
|
|
||||||
console: data.taskResult.console,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (data.msgType === 'EXEC_END') {
|
} else if (data.msgType === 'EXEC_END') {
|
||||||
// 执行结束,关闭websocket
|
// 执行结束,关闭websocket
|
||||||
websocket.value?.close();
|
websocketMap[scenario.reportId]?.close();
|
||||||
if (activeScenarioTab.value.reportId === data.reportId) {
|
if (scenario.reportId === data.reportId) {
|
||||||
activeScenarioTab.value.executeLoading = false;
|
scenario.executeLoading = false;
|
||||||
activeScenarioTab.value.isExecute = false;
|
scenario.isExecute = false;
|
||||||
setStepExecuteStatus();
|
setStepExecuteStatus(scenario);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -237,7 +220,6 @@
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
activeScenarioTab.value.executeLoading = true;
|
activeScenarioTab.value.executeLoading = true;
|
||||||
debugSocket(executeParams.reportId, executeType, localExecuteUrl); // 开启websocket
|
|
||||||
// 重置执行结果
|
// 重置执行结果
|
||||||
activeScenarioTab.value.executeTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
activeScenarioTab.value.executeTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||||
activeScenarioTab.value.executeSuccessCount = 0;
|
activeScenarioTab.value.executeSuccessCount = 0;
|
||||||
|
@ -245,6 +227,7 @@
|
||||||
activeScenarioTab.value.stepResponses = {};
|
activeScenarioTab.value.stepResponses = {};
|
||||||
activeScenarioTab.value.reportId = executeParams.reportId; // 存储报告ID
|
activeScenarioTab.value.reportId = executeParams.reportId; // 存储报告ID
|
||||||
activeScenarioTab.value.isDebug = !isExecute;
|
activeScenarioTab.value.isDebug = !isExecute;
|
||||||
|
debugSocket(activeScenarioTab.value, executeType, localExecuteUrl); // 开启websocket
|
||||||
let res;
|
let res;
|
||||||
if (isExecute && executeType !== 'localExec' && !activeScenarioTab.value.isNew) {
|
if (isExecute && executeType !== 'localExec' && !activeScenarioTab.value.isNew) {
|
||||||
// 执行场景且非本地执行且非未保存场景
|
// 执行场景且非本地执行且非未保存场景
|
||||||
|
@ -290,9 +273,9 @@
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
websocket.value?.close();
|
websocketMap[activeScenarioTab.value.reportId]?.close();
|
||||||
activeScenarioTab.value.executeLoading = false;
|
activeScenarioTab.value.executeLoading = false;
|
||||||
setStepExecuteStatus();
|
setStepExecuteStatus(activeScenarioTab.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,12 +290,6 @@
|
||||||
if (node.enable) {
|
if (node.enable) {
|
||||||
node.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
node.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
||||||
waitingDebugStepDetails[node.id] = activeScenarioTab.value.stepDetails[node.id];
|
waitingDebugStepDetails[node.id] = activeScenarioTab.value.stepDetails[node.id];
|
||||||
if (
|
|
||||||
[ScenarioStepType.API, ScenarioStepType.API_CASE, ScenarioStepType.CUSTOM_REQUEST].includes(node.stepType)
|
|
||||||
) {
|
|
||||||
// 请求和场景类型才直接显示执行中,其他控制器需要等待执行完毕才结算执行结果
|
|
||||||
node.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return !!node.enable;
|
return !!node.enable;
|
||||||
});
|
});
|
||||||
|
@ -329,63 +306,59 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStopExecute() {
|
function handleStopExecute() {
|
||||||
websocket.value?.close();
|
websocketMap[activeScenarioTab.value.reportId]?.close();
|
||||||
activeScenarioTab.value.executeLoading = false;
|
activeScenarioTab.value.executeLoading = false;
|
||||||
setStepExecuteStatus();
|
setStepExecuteStatus(activeScenarioTab.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
|
||||||
() => activeScenarioTab.value.id,
|
|
||||||
(val) => {
|
|
||||||
if (val !== 'all' && activeScenarioTab.value.reportId && !activeScenarioTab.value.executeLoading) {
|
|
||||||
// 当前查看的 tab 非全部场景 tab 页,且当前场景有报告ID,且不是正在执行中,则读取缓存报告
|
|
||||||
const cacheReport = temporaryScenarioReportMap[activeScenarioTab.value.reportId];
|
|
||||||
if (cacheReport) {
|
|
||||||
// 如果有缓存的报告未读取,则直接赋值
|
|
||||||
Object.keys(cacheReport).forEach((stepId) => {
|
|
||||||
const result = cacheReport[stepId];
|
|
||||||
activeScenarioTab.value.stepResponses[stepId] = result;
|
|
||||||
if (result.isSuccessful) {
|
|
||||||
activeScenarioTab.value.executeSuccessCount += 1;
|
|
||||||
} else {
|
|
||||||
activeScenarioTab.value.executeFailCount += 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
activeScenarioTab.value.executeLoading = false;
|
|
||||||
delete temporaryScenarioReportMap[activeScenarioTab.value.reportId]; // 取完释放缓存
|
|
||||||
setStepExecuteStatus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
function newTab(defaultScenarioInfo?: Scenario, action?: 'copy' | 'execute') {
|
function newTab(defaultScenarioInfo?: Scenario, action?: 'copy' | 'execute') {
|
||||||
if (defaultScenarioInfo) {
|
if (defaultScenarioInfo) {
|
||||||
const isCopy = action === 'copy';
|
const isCopy = action === 'copy';
|
||||||
let copySteps: ScenarioStepItem[] = [];
|
let copySteps: ScenarioStepItem[] = [];
|
||||||
if (isCopy) {
|
if (isCopy) {
|
||||||
copySteps = mapTree(defaultScenarioInfo.steps, (node) => {
|
// 场景被复制,递归处理节点,增加copyFromStepId
|
||||||
return {
|
|
||||||
...node,
|
|
||||||
copyFromStepId: node.id,
|
|
||||||
id: getGenerateId(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
copySteps = mapTree(defaultScenarioInfo.steps, (node) => {
|
copySteps = mapTree(defaultScenarioInfo.steps, (node) => {
|
||||||
if (
|
if (
|
||||||
node.parent &&
|
node.parent &&
|
||||||
node.parent.stepType === ScenarioStepType.API_SCENARIO &&
|
node.parent.stepType === ScenarioStepType.API_SCENARIO &&
|
||||||
[ScenarioStepRefType.REF, ScenarioStepRefType.PARTIAL_REF].includes(node.parent.refType)
|
[ScenarioStepRefType.REF, ScenarioStepRefType.PARTIAL_REF].includes(node.parent.refType)
|
||||||
) {
|
) {
|
||||||
// 如果父节点是引用场景
|
// 如果根节点是引用场景
|
||||||
node.isQuoteScenarioStep = true; // 标记为引用场景下的子步骤
|
node.isQuoteScenarioStep = true; // 标记为引用场景下的子步骤
|
||||||
node.isRefScenarioStep = node.parent.refType === ScenarioStepRefType.REF; // 标记为引用场景下的子步骤
|
node.isRefScenarioStep = node.parent.refType === ScenarioStepRefType.REF; // 标记为完全引用场景
|
||||||
|
node.draggable = false; // 引用场景下的任何步骤不可拖拽
|
||||||
|
node.id = getGenerateId(); // 重新生成 ID
|
||||||
} else if (node.parent) {
|
} else if (node.parent) {
|
||||||
// 如果有父节点
|
// 如果有父节点
|
||||||
node.isQuoteScenarioStep = node.parent.isQuoteScenarioStep; // 复用父节点的引用场景标记
|
node.isQuoteScenarioStep = node.parent.isQuoteScenarioStep; // 复用父节点的引用场景标记
|
||||||
node.isRefScenarioStep = node.parent.isRefScenarioStep; // 复用父节点的是否完全引用场景标记
|
node.isRefScenarioStep = node.parent.isRefScenarioStep; // 复用父节点的是否完全引用场景标记
|
||||||
}
|
}
|
||||||
|
if (!node.isQuoteScenarioStep && !node.isRefScenarioStep) {
|
||||||
|
// 非引用场景步骤
|
||||||
|
node.id = getGenerateId(); // 重新生成 ID
|
||||||
|
}
|
||||||
|
node.copyFromStepId = node.id;
|
||||||
|
node.uniqueId = getGenerateId();
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 正常打开场景详情,递归处理节点,标记引用场景下的子步骤
|
||||||
|
copySteps = mapTree(defaultScenarioInfo.steps, (node) => {
|
||||||
|
if (
|
||||||
|
node.parent &&
|
||||||
|
node.parent.stepType === ScenarioStepType.API_SCENARIO &&
|
||||||
|
[ScenarioStepRefType.REF, ScenarioStepRefType.PARTIAL_REF].includes(node.parent.refType)
|
||||||
|
) {
|
||||||
|
// 如果根节点是引用场景
|
||||||
|
node.isQuoteScenarioStep = true; // 标记为引用场景下的子步骤
|
||||||
|
node.isRefScenarioStep = node.parent.refType === ScenarioStepRefType.REF; // 标记为完全引用场景
|
||||||
|
node.draggable = false; // 引用场景下的任何步骤不可拖拽
|
||||||
|
} else if (node.parent) {
|
||||||
|
// 如果有父节点
|
||||||
|
node.isQuoteScenarioStep = node.parent.isQuoteScenarioStep; // 复用父节点的引用场景标记
|
||||||
|
node.isRefScenarioStep = node.parent.isRefScenarioStep; // 复用父节点的是否完全引用场景标记
|
||||||
|
}
|
||||||
|
node.uniqueId = getGenerateId();
|
||||||
return node;
|
return node;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -393,6 +366,7 @@
|
||||||
...defaultScenarioInfo,
|
...defaultScenarioInfo,
|
||||||
steps: copySteps,
|
steps: copySteps,
|
||||||
id: isCopy ? getGenerateId() : defaultScenarioInfo.id || '',
|
id: isCopy ? getGenerateId() : defaultScenarioInfo.id || '',
|
||||||
|
uniqueId: getGenerateId(),
|
||||||
label: isCopy ? `copy-${defaultScenarioInfo.name}` : defaultScenarioInfo.name,
|
label: isCopy ? `copy-${defaultScenarioInfo.name}` : defaultScenarioInfo.name,
|
||||||
name: isCopy ? `copy-${defaultScenarioInfo.name}` : defaultScenarioInfo.name,
|
name: isCopy ? `copy-${defaultScenarioInfo.name}` : defaultScenarioInfo.name,
|
||||||
isNew: isCopy,
|
isNew: isCopy,
|
||||||
|
|
|
@ -65,6 +65,8 @@ export default {
|
||||||
'apiScenario.sumLoop': '共{count}次循环',
|
'apiScenario.sumLoop': '共{count}次循环',
|
||||||
'apiScenario.times': '次',
|
'apiScenario.times': '次',
|
||||||
'apiScenario.executionResult': '执行结果',
|
'apiScenario.executionResult': '执行结果',
|
||||||
|
'apiScenario.refreshRefScenario': '刷新引用场景数据',
|
||||||
|
'apiScenario.updateRefScenarioSuccess': '引用场景数据已更新',
|
||||||
// 批量操作文案
|
// 批量操作文案
|
||||||
'api_scenario.batch_operation.success': '成功{opt}至 {name}',
|
'api_scenario.batch_operation.success': '成功{opt}至 {name}',
|
||||||
'api_scenario.table.batchMoveConfirm': '{opt}{count}个场景至已选模块',
|
'api_scenario.table.batchMoveConfirm': '{opt}{count}个场景至已选模块',
|
||||||
|
|
Loading…
Reference in New Issue