mirror of
https://gitee.com/fit2cloud-feizhiyun/MeterSphere.git
synced 2024-11-30 11:08:38 +08:00
feat(脑图): 测试规划脑图
This commit is contained in:
parent
251f923b43
commit
bfe7789e78
@ -40,7 +40,6 @@ import {
|
||||
GetDefinitionDetailUrl,
|
||||
GetDefinitionScheduleUrl,
|
||||
GetDependencyUrl,
|
||||
GetEnvListUrl,
|
||||
GetEnvModuleUrl,
|
||||
GetExecuteHistoryUrl,
|
||||
GetMockUrlUrl,
|
||||
@ -48,13 +47,13 @@ import {
|
||||
GetModuleOnlyTreeUrl,
|
||||
GetModuleTreeUrl,
|
||||
GetPoolId,
|
||||
GetPoolOptionUrl,
|
||||
GetTrashModuleCountUrl,
|
||||
GetTrashModuleTreeUrl,
|
||||
ImportDefinitionUrl,
|
||||
MockDetailUrl,
|
||||
MoveModuleUrl,
|
||||
OperationHistoryUrl,
|
||||
PoolOption,
|
||||
RecoverCaseUrl,
|
||||
RecoverDefinitionUrl,
|
||||
RecoverOperationHistoryUrl,
|
||||
@ -519,11 +518,6 @@ export function batchExecuteCase(data: ApiCaseBatchExecuteParams) {
|
||||
return MSR.post({ url: BatchExecuteCaseUrl, data });
|
||||
}
|
||||
|
||||
// 获取接口测试-环境列表
|
||||
export function getEnvList(projectId: string) {
|
||||
return MSR.get<Environment[]>({ url: GetEnvListUrl, params: projectId });
|
||||
}
|
||||
|
||||
// 获取接口用例-执行历史
|
||||
export function getApiCaseExecuteHistory(data: ApiCaseExecuteHistoryParams) {
|
||||
return MSR.post<CommonList<ApiCaseExecuteHistoryItem>>({ url: GetExecuteHistoryUrl, data });
|
||||
@ -541,7 +535,7 @@ export function getApiCaseDependency(data: ApiCaseDependencyParams) {
|
||||
|
||||
// 获取接口的资源池列表
|
||||
export function getPoolOption(projectId: string) {
|
||||
return MSR.get<ResourcePoolItem[]>({ url: PoolOption + projectId });
|
||||
return MSR.get<ResourcePoolItem[]>({ url: GetPoolOptionUrl, params: projectId });
|
||||
}
|
||||
|
||||
export function getPoolId(projectId: string) {
|
||||
|
@ -29,6 +29,7 @@ import {
|
||||
DisassociateApiScenarioUrl,
|
||||
DisassociateCaseUrl,
|
||||
dragPlanOnGroupUrl,
|
||||
EditPlanMinderUrl,
|
||||
ExecuteHistoryUrl,
|
||||
ExecutePlanUrl,
|
||||
followPlanUrl,
|
||||
@ -103,6 +104,7 @@ import type {
|
||||
PlanDetailExecuteHistoryItem,
|
||||
PlanDetailFeatureCaseItem,
|
||||
PlanDetailFeatureCaseListQueryParams,
|
||||
PlanMinderEditParams,
|
||||
PlanMinderNode,
|
||||
RunFeatureCaseParams,
|
||||
SortApiCaseParams,
|
||||
@ -408,3 +410,7 @@ export function deleteScheduleTask(testPlanId: string) {
|
||||
export function getPlanMinder(testPlanId: string) {
|
||||
return MSR.get<PlanMinderNode[]>({ url: GetPlanMinderUrl, params: testPlanId });
|
||||
}
|
||||
// 更新测试规划脑图
|
||||
export function editPlanMinder(data: PlanMinderEditParams) {
|
||||
return MSR.post({ url: EditPlanMinderUrl, data });
|
||||
}
|
||||
|
@ -77,7 +77,6 @@ export const TransferFileCaseUrl = '/api/case/transfer'; // 文件转存
|
||||
export const TransferFileModuleOptionCaseUrl = '/api/case/transfer/options'; // 文件转存目录
|
||||
export const UploadTempFileCaseUrl = '/api/case/upload/temp/file'; // 临时文件上传
|
||||
export const GetCaseDetailUrl = '/api/case/get-detail'; // 获取接口用例详情
|
||||
export const GetEnvListUrl = '/api/test/env-list'; // 接口测试-环境列表
|
||||
export const BatchExecuteCaseUrl = '/api/case/batch/run'; // 批量执行接口用例
|
||||
export const ExecuteCaseUrl = '/api/case/run'; // 单独执行接口用例
|
||||
export const GetExecuteHistoryUrl = '/api/case/execute/page'; // 获取用的执行历史
|
||||
@ -98,5 +97,5 @@ export const DeleteRecycleCaseUrl = '/api/case/delete'; // 接口用例彻底删
|
||||
export const BatchDeleteRecycleCaseUrl = '/api/case/batch/delete'; // 接口用例批量彻底删除
|
||||
export const AddCaseUrl = '/api/case/add'; // 添加用例
|
||||
|
||||
export const PoolOption = '/api/test/pool-option/'; // 获取接口资源池
|
||||
export const GetPoolOptionUrl = '/api/test/pool-option'; // 获取接口资源池
|
||||
export const GetPoolId = '/api/test/get-pool/'; // 获取项目应用设置的资源池id
|
||||
|
@ -143,3 +143,5 @@ export const BatchMoveApiScenarioUrl = '/test-plan/api/scenario/batch/move';
|
||||
|
||||
// 测试规划脑图
|
||||
export const GetPlanMinderUrl = '/test-plan/mind/data';
|
||||
// 修改测试规划脑图
|
||||
export const EditPlanMinderUrl = '/test-plan/mind/data/edit';
|
||||
|
@ -25,6 +25,7 @@
|
||||
@action="handleAction"
|
||||
@before-exec-command="handleBeforeExecCommand"
|
||||
@save="handleMinderSave"
|
||||
@float-menu-close="handleBaseInfoCancel"
|
||||
>
|
||||
<template #extractMenu>
|
||||
<a-tooltip v-if="showDetailMenu" :content="t('common.detail')">
|
||||
@ -258,69 +259,6 @@
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成脑图保存的入参
|
||||
*/
|
||||
function makeMinderParams(fullJson: MinderJson): FeatureCaseMinderUpdateParams {
|
||||
filterTree(fullJson.root.children, (node, nodeIndex, parent) => {
|
||||
if (node.data.isNew !== false || node.data.changed === true) {
|
||||
if (node.data.resource?.includes(moduleTag)) {
|
||||
// 处理模块节点
|
||||
tempMinderParams.value.updateModuleList.push({
|
||||
id: node.data.id,
|
||||
name: node.data.text,
|
||||
parentId: parent?.data.id || 'NONE',
|
||||
type: node.data.isNew !== false ? 'ADD' : 'UPDATE',
|
||||
...getNodeMoveInfo(nodeIndex, parent as MinderJsonNode),
|
||||
});
|
||||
} else if (node.data.resource?.includes(caseTag)) {
|
||||
// 处理用例节点
|
||||
const caseNodeInfo = getCaseNodeInfo(node as MinderJsonNode);
|
||||
const caseBaseInfo = baseInfoRef.value?.makeParams();
|
||||
tempMinderParams.value.updateCaseList.push({
|
||||
id: node.data.id,
|
||||
moduleId: parent?.data.id || '',
|
||||
type: node.data.isNew !== false ? 'ADD' : 'UPDATE',
|
||||
templateId: templateId.value,
|
||||
tags: caseBaseInfo?.tags || [],
|
||||
customFields: caseBaseInfo?.customFields || [],
|
||||
name: caseBaseInfo?.name || node.data.text,
|
||||
...getNodeMoveInfo(nodeIndex, parent as MinderJsonNode),
|
||||
...caseNodeInfo,
|
||||
});
|
||||
return false; // 用例的子孙节点已经处理过,跳过
|
||||
} else if (!node.data.resource || node.data.resource.length === 0) {
|
||||
// 处理文本节点
|
||||
tempMinderParams.value.additionalNodeList.push({
|
||||
id: node.data.id,
|
||||
parentId: parent?.data.id || 'NONE',
|
||||
type: node.data.isNew !== false ? 'ADD' : 'UPDATE',
|
||||
name: node.data.text,
|
||||
...getNodeMoveInfo(nodeIndex, parent as MinderJsonNode),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return tempMinderParams.value;
|
||||
}
|
||||
|
||||
async function handleMinderSave(fullJson: MinderJson, callback: () => void) {
|
||||
try {
|
||||
loading.value = true;
|
||||
await saveCaseMinder(makeMinderParams(fullJson));
|
||||
Message.success(t('common.saveSuccess'));
|
||||
initCaseTree();
|
||||
callback();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 已选中节点的可替换标签判断
|
||||
* @param node 选中节点
|
||||
@ -906,6 +844,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换用例详情显示
|
||||
*/
|
||||
async function toggleDetail(val?: boolean) {
|
||||
extraVisible.value = val !== undefined ? val : !extraVisible.value;
|
||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||
const { data } = node;
|
||||
if (extraVisible.value) {
|
||||
if (data?.resource && data.resource.includes(caseTag)) {
|
||||
activeExtraKey.value = 'baseInfo';
|
||||
resetExtractInfo();
|
||||
if (data.isNew === false) {
|
||||
// 非新用例节点才能加载详情
|
||||
initCaseDetail(data);
|
||||
} else {
|
||||
activeCase.value = {
|
||||
id: data.id,
|
||||
name: data.text,
|
||||
isNew: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const showDetailMenu = ref(false);
|
||||
const canShowEnterNode = ref(false);
|
||||
/**
|
||||
@ -929,6 +892,9 @@
|
||||
if (data?.resource && data.resource.includes(caseTag)) {
|
||||
// 用例节点才展示详情按钮
|
||||
showDetailMenu.value = true;
|
||||
if (extraVisible.value) {
|
||||
toggleDetail(true);
|
||||
}
|
||||
} else if (data?.resource?.includes(moduleTag) && data.count > 0 && data.isLoaded !== true) {
|
||||
// 模块节点且有用例且未加载过用例数据
|
||||
try {
|
||||
@ -1014,31 +980,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换用例详情显示
|
||||
*/
|
||||
async function toggleDetail() {
|
||||
extraVisible.value = !extraVisible.value;
|
||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||
const { data } = node;
|
||||
if (extraVisible.value) {
|
||||
if (data?.resource && data.resource.includes(caseTag)) {
|
||||
activeExtraKey.value = 'baseInfo';
|
||||
resetExtractInfo();
|
||||
if (data.isNew === false) {
|
||||
// 非新用例节点才能加载详情
|
||||
initCaseDetail(data);
|
||||
} else {
|
||||
activeCase.value = {
|
||||
id: data.id,
|
||||
name: data.text,
|
||||
isNew: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标签编辑后,如果将标签修改为模块,则删除已添加的优先级
|
||||
* @param node 选中节点
|
||||
@ -1334,6 +1275,70 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成脑图保存的入参
|
||||
*/
|
||||
function makeMinderParams(fullJson: MinderJson): FeatureCaseMinderUpdateParams {
|
||||
filterTree(fullJson.root.children, (node, nodeIndex, parent) => {
|
||||
if (node.data.isNew !== false || node.data.changed === true) {
|
||||
if (node.data.resource?.includes(moduleTag)) {
|
||||
// 处理模块节点
|
||||
tempMinderParams.value.updateModuleList.push({
|
||||
id: node.data.id,
|
||||
name: node.data.text,
|
||||
parentId: parent?.data.id || 'NONE',
|
||||
type: node.data.isNew !== false ? 'ADD' : 'UPDATE',
|
||||
...getNodeMoveInfo(nodeIndex, parent as MinderJsonNode),
|
||||
});
|
||||
} else if (node.data.resource?.includes(caseTag)) {
|
||||
// 处理用例节点
|
||||
const caseNodeInfo = getCaseNodeInfo(node as MinderJsonNode);
|
||||
const caseBaseInfo = baseInfoRef.value?.makeParams();
|
||||
tempMinderParams.value.updateCaseList.push({
|
||||
id: node.data.id,
|
||||
moduleId: parent?.data.id || '',
|
||||
type: node.data.isNew !== false ? 'ADD' : 'UPDATE',
|
||||
templateId: templateId.value,
|
||||
tags: caseBaseInfo?.tags || [],
|
||||
customFields: caseBaseInfo?.customFields || [],
|
||||
name: caseBaseInfo?.name || node.data.text,
|
||||
...getNodeMoveInfo(nodeIndex, parent as MinderJsonNode),
|
||||
...caseNodeInfo,
|
||||
});
|
||||
return false; // 用例的子孙节点已经处理过,跳过
|
||||
} else if (!node.data.resource || node.data.resource.length === 0) {
|
||||
// 处理文本节点
|
||||
tempMinderParams.value.additionalNodeList.push({
|
||||
id: node.data.id,
|
||||
parentId: parent?.data.id || 'NONE',
|
||||
type: node.data.isNew !== false ? 'ADD' : 'UPDATE',
|
||||
name: node.data.text,
|
||||
...getNodeMoveInfo(nodeIndex, parent as MinderJsonNode),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return tempMinderParams.value;
|
||||
}
|
||||
|
||||
async function handleMinderSave(fullJson: MinderJson, callback: () => void) {
|
||||
try {
|
||||
loading.value = true;
|
||||
await saveCaseMinder(makeMinderParams(fullJson));
|
||||
extraVisible.value = false;
|
||||
Message.success(t('common.saveSuccess'));
|
||||
initCaseTree();
|
||||
callback();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
@ -4,7 +4,7 @@
|
||||
v-model:loading="loading"
|
||||
v-model:import-json="importJson"
|
||||
:tags="[]"
|
||||
:insert-node="insertNode"
|
||||
:insert-node="(node, type) => insertNode(node as PlanMinderNode,type)"
|
||||
:can-show-enter-node="false"
|
||||
:insert-sibling-menus="insertSiblingMenus"
|
||||
:insert-son-menus="insertSonMenus"
|
||||
@ -12,16 +12,22 @@
|
||||
:can-show-more-menu="false"
|
||||
:can-show-priority-menu="false"
|
||||
:can-show-float-menu="canShowFloatMenu"
|
||||
:can-show-delete-menu="canShowDeleteMenu"
|
||||
custom-priority
|
||||
single-tag
|
||||
tag-enable
|
||||
sequence-enable
|
||||
@content-change="handleContentChange"
|
||||
@node-select="checkNodeCanShowMenu"
|
||||
@node-select="(node) => handleNodeSelect(node as PlanMinderNode)"
|
||||
@before-exec-command="handleBeforeExecCommand"
|
||||
@save="handleMinderSave"
|
||||
@float-menu-close="handleFloatMenuClose"
|
||||
>
|
||||
<template #extractMenu>
|
||||
<a-tooltip v-if="showAssociateCaseMenu" :content="t('ms.case.associate.title')">
|
||||
<MsButton type="icon" class="ms-minder-node-float-menu-icon-button" @click="associateCase">
|
||||
<MsIcon type="icon-icon_add_outlined" class="text-[var(--color-text-4)]" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<a-dropdown
|
||||
v-if="canShowExecuteMethodMenu"
|
||||
v-model:popup-visible="executeMethodMenuVisible"
|
||||
@ -65,14 +71,20 @@
|
||||
type="icon"
|
||||
class="ms-minder-node-float-menu-icon-button"
|
||||
:class="[extraVisible ? 'ms-minder-node-float-menu-icon-button--focus' : '']"
|
||||
@click="toggleDetail"
|
||||
@click="toggleConfig"
|
||||
>
|
||||
<MsIcon type="icon-icon_setting_filled" class="text-[var(--color-text-4)]" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #extractTabContent>
|
||||
<div class="px-[16px]">
|
||||
<div v-if="configForm" class="px-[16px]">
|
||||
<div class="mb-[16px] flex items-center gap-[8px]">
|
||||
<div class="h-[14px] w-[3px] rounded-[var(--border-radius-small)] bg-[rgb(var(--primary-4))]"></div>
|
||||
<a-tooltip :content="configForm.text" position="tl">
|
||||
<div class="one-line-text font-medium">{{ configForm.text }}</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-form ref="configFormRef" :model="configForm" layout="vertical">
|
||||
<a-form-item>
|
||||
<template #label>
|
||||
@ -113,76 +125,100 @@
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('system.project.resourcePool')">
|
||||
<a-select v-model:model-value="configForm.resourcePool" :options="resourcePoolOptions"></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item class="hidden-item">
|
||||
<a-radio-group v-model:model-value="configForm.executeType">
|
||||
<a-radio value="serial">{{ t('testPlan.testPlanIndex.serial') }}</a-radio>
|
||||
<a-radio value="parallel">{{ t('testPlan.testPlanIndex.parallel') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="configForm.executeType === 'serial'" class="hidden-item">
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<a-switch v-model:model-value="configForm.failStop" size="small"></a-switch>
|
||||
<div>{{ t('ms.minders.failStop') }}</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item class="hidden-item">
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<a-switch v-model:model-value="configForm.failRetry" size="small"></a-switch>
|
||||
<div>{{ t('ms.minders.failRetry') }}</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<template v-if="configForm.failRetry">
|
||||
<template
|
||||
v-if="
|
||||
configForm.type !== PlanMinderCollectionType.FUNCTIONAL &&
|
||||
(configForm.level === 1 || !configForm.extended)
|
||||
"
|
||||
>
|
||||
<a-form-item :label="t('system.project.resourcePool')">
|
||||
<a-select v-model:model-value="configForm.testResourcePoolId" :options="resourcePoolOptions"></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('project.environmental.env')">
|
||||
<a-select v-model:model-value="configForm.environmentId" :options="environmentOptions"></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item class="hidden-item">
|
||||
<a-radio-group v-model:model-value="configForm.failRetryType">
|
||||
<a-radio value="step">{{ t('ms.minders.stepRetry') }}</a-radio>
|
||||
<a-radio value="scenario">{{ t('ms.minders.scenarioRetry') }}</a-radio>
|
||||
<a-radio-group v-model:model-value="configForm.executeMethod">
|
||||
<a-radio :value="RunMode.SERIAL">{{ t('testPlan.testPlanIndex.serial') }}</a-radio>
|
||||
<a-radio :value="RunMode.PARALLEL">{{ t('testPlan.testPlanIndex.parallel') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('ms.minders.retry') }}</div>
|
||||
<div class="text-[var(--color-text-4)]">{{ t('ms.minders.retryTimes') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-input-number
|
||||
v-model:model-value="configForm.retryTimes"
|
||||
mode="button"
|
||||
:step="1"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
size="small"
|
||||
class="w-[120px]"
|
||||
></a-input-number>
|
||||
<a-form-item v-if="configForm.executeMethod === RunMode.SERIAL" class="hidden-item">
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<a-switch v-model:model-value="configForm.stopOnFail" size="small"></a-switch>
|
||||
<div>{{ t('ms.minders.failStop') }}</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('ms.minders.retrySpace') }}</div>
|
||||
<div class="text-[var(--color-text-4)]">{{ t('ms.minders.retrySpaces') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-input-number
|
||||
v-model:model-value="configForm.retrySpace"
|
||||
mode="button"
|
||||
:step="100"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
size="small"
|
||||
class="w-[120px]"
|
||||
></a-input-number>
|
||||
<a-form-item class="hidden-item">
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<a-switch v-model:model-value="configForm.retryOnFail" size="small"></a-switch>
|
||||
<div>{{ t('ms.minders.failRetry') }}</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<template v-if="configForm.retryOnFail">
|
||||
<a-form-item v-if="configForm.type === PlanMinderCollectionType.SCENARIO" class="hidden-item">
|
||||
<a-radio-group v-model:model-value="configForm.retryType">
|
||||
<a-radio :value="FailRetry.STEP">{{ t('ms.minders.stepRetry') }}</a-radio>
|
||||
<a-radio :value="FailRetry.SCENARIO">{{ t('ms.minders.scenarioRetry') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('ms.minders.retry') }}</div>
|
||||
<div class="text-[var(--color-text-4)]">{{ t('ms.minders.retryTimes') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-input-number
|
||||
v-model:model-value="configForm.retryTimes"
|
||||
mode="button"
|
||||
:step="1"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
size="small"
|
||||
class="w-[120px]"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('ms.minders.retrySpace') }}</div>
|
||||
<div class="text-[var(--color-text-4)]">{{ t('ms.minders.retrySpaces') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-input-number
|
||||
v-model:model-value="configForm.retryInterval"
|
||||
mode="button"
|
||||
:step="100"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
size="small"
|
||||
class="w-[120px]"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
<a-form-item class="hidden-item">
|
||||
<a-form-item
|
||||
v-if="configForm.type !== PlanMinderCollectionType.FUNCTIONAL && configForm.level === 2"
|
||||
class="hidden-item"
|
||||
>
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<a-switch v-model:model-value="configForm.extend" size="small"></a-switch>
|
||||
<a-switch v-model:model-value="configForm.extended" size="small"></a-switch>
|
||||
<div>{{ t('ms.minders.extend') }}</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="flex items-center gap-[12px] bg-white pb-[16px]">
|
||||
<a-button
|
||||
v-permission="['FUNCTIONAL_CASE:READ+UPDATE']"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="handleConfigSave"
|
||||
>
|
||||
{{ t('common.save') }}
|
||||
</a-button>
|
||||
<a-button type="secondary" :disabled="loading" @click="handleConfigCancel">{{ t('common.cancel') }}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsMinderEditor>
|
||||
@ -190,29 +226,43 @@
|
||||
v-model:visible="caseAssociateVisible"
|
||||
v-model:currentSelectCase="currentSelectCase"
|
||||
:has-not-associated-ids="selectedAssociateCasesParams.selectIds"
|
||||
test-plan-id=""
|
||||
:test-plan-id="props.planId"
|
||||
@success="writeAssociateCases"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FormInstance, SelectOptionData } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsMinderEditor from '@/components/pure/ms-minder-editor/minderEditor.vue';
|
||||
import { InsertMenuItem, MinderEvent, MinderJson, MinderJsonNode } from '@/components/pure/ms-minder-editor/props';
|
||||
import {
|
||||
InsertMenuItem,
|
||||
MinderEvent,
|
||||
MinderJson,
|
||||
MinderJsonNode,
|
||||
MinderJsonNodeData,
|
||||
} from '@/components/pure/ms-minder-editor/props';
|
||||
import { setCustomPriorityView } from '@/components/pure/ms-minder-editor/script/tool/utils';
|
||||
import caseAssociate from './associateDrawer.vue';
|
||||
|
||||
import { getPlanMinder } from '@/api/modules/test-plan/testPlan';
|
||||
import { getPoolOption } from '@/api/modules/api-test/management';
|
||||
import { editPlanMinder, getPlanMinder } from '@/api/modules/test-plan/testPlan';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useMinderStore from '@/store/modules/components/minder-editor';
|
||||
import { filterTree, mapTree } from '@/utils';
|
||||
import { filterTree, getGenerateId, mapTree } from '@/utils';
|
||||
|
||||
import { AssociateCaseRequest } from '@/models/testPlan/testPlan';
|
||||
import {
|
||||
AssociateCaseRequest,
|
||||
PlanMinderEditListItem,
|
||||
PlanMinderNode,
|
||||
PlanMinderNodeData,
|
||||
} from '@/models/testPlan/testPlan';
|
||||
import { CaseLinkEnum } from '@/enums/caseEnum';
|
||||
import { RunMode } from '@/enums/testPlanEnum';
|
||||
import { MinderEventName } from '@/enums/minderEnum';
|
||||
import { FailRetry, PlanMinderAssociateType, PlanMinderCollectionType, RunMode } from '@/enums/testPlanEnum';
|
||||
|
||||
import Message from '@arco-design/web-vue/es/message';
|
||||
|
||||
@ -224,50 +274,42 @@
|
||||
const { t } = useI18n();
|
||||
const minderStore = useMinderStore();
|
||||
const loading = ref(false);
|
||||
const extraVisible = ref<boolean>(false);
|
||||
const importJson = ref<MinderJson>({
|
||||
root: {} as MinderJsonNode,
|
||||
template: 'default',
|
||||
treePath: [],
|
||||
});
|
||||
const caseCountTag = t('ms.minders.caseCount');
|
||||
const resourcePoolTag = t('ms.minders.resourcePool');
|
||||
const envTag = t('ms.minders.env');
|
||||
|
||||
/**
|
||||
* 插入节点
|
||||
* @param node 目标节点
|
||||
* @param type 插入类型
|
||||
* @param value 插入值
|
||||
*/
|
||||
function insertNode(node: MinderJsonNode, type: string, value?: string) {
|
||||
switch (type) {
|
||||
case 'AppendChildNode':
|
||||
break;
|
||||
case 'AppendSiblingNode':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function handleContentChange(node: MinderJsonNode) {}
|
||||
|
||||
const canShowFloatMenu = ref(false);
|
||||
const insertSiblingMenus = ref<InsertMenuItem[]>([]);
|
||||
const insertSonMenus = ref<InsertMenuItem[]>([]);
|
||||
const canShowFloatMenu = ref(false);
|
||||
const showAssociateCaseMenu = ref(false);
|
||||
const canShowExecuteMethodMenu = ref(true);
|
||||
const executeMethodMenuVisible = ref(false);
|
||||
const showConfigMenu = ref(false);
|
||||
const canShowDeleteMenu = ref(false);
|
||||
const extraVisible = ref<boolean>(false);
|
||||
|
||||
/**
|
||||
* 检测节点可展示的菜单项
|
||||
* @param node 选中节点
|
||||
*/
|
||||
function checkNodeCanShowMenu(node: MinderJsonNode) {
|
||||
function checkNodeCanShowMenu(node: PlanMinderNode) {
|
||||
const { data } = node;
|
||||
|
||||
if (data?.level === 1 || data?.level === 2) {
|
||||
canShowFloatMenu.value = true;
|
||||
canShowExecuteMethodMenu.value = true;
|
||||
if (data?.type === PlanMinderCollectionType.FUNCTIONAL) {
|
||||
// 功能用例分类没有执行方式
|
||||
canShowExecuteMethodMenu.value = false;
|
||||
} else {
|
||||
canShowExecuteMethodMenu.value = true;
|
||||
}
|
||||
if (data?.level === 1) {
|
||||
// 测试分类只能添加下级测试集
|
||||
insertSiblingMenus.value = [];
|
||||
insertSonMenus.value = [
|
||||
{
|
||||
@ -275,46 +317,300 @@
|
||||
label: t('ms.minders.testSet'),
|
||||
},
|
||||
];
|
||||
showConfigMenu.value = true;
|
||||
if (data?.type === PlanMinderCollectionType.FUNCTIONAL) {
|
||||
// 功能用例分类不能配置任何东西
|
||||
showConfigMenu.value = false;
|
||||
} else {
|
||||
showConfigMenu.value = true;
|
||||
}
|
||||
showAssociateCaseMenu.value = false;
|
||||
canShowDeleteMenu.value = false;
|
||||
} else {
|
||||
insertSiblingMenus.value = [];
|
||||
// 测试集只能添加同级测试集
|
||||
insertSiblingMenus.value = [
|
||||
{
|
||||
value: 'testSet',
|
||||
label: t('ms.minders.testSet'),
|
||||
},
|
||||
];
|
||||
insertSonMenus.value = [];
|
||||
showAssociateCaseMenu.value = true;
|
||||
showConfigMenu.value = true;
|
||||
canShowDeleteMenu.value = true;
|
||||
}
|
||||
} else {
|
||||
canShowFloatMenu.value = false;
|
||||
canShowExecuteMethodMenu.value = false;
|
||||
showAssociateCaseMenu.value = false;
|
||||
showConfigMenu.value = false;
|
||||
extraVisible.value = false;
|
||||
canShowDeleteMenu.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行插入节点
|
||||
* @param command 插入命令
|
||||
* @param node 目标节点
|
||||
*/
|
||||
function execInert(command: string, data?: PlanMinderNodeData | MinderJsonNodeData) {
|
||||
if (window.minder.queryCommandState(command) !== -1) {
|
||||
window.minder.execCommand(command, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入节点
|
||||
* @param node 目标节点
|
||||
* @param type 插入类型
|
||||
* @param value 插入值
|
||||
*/
|
||||
function insertNode(node: PlanMinderNode, type: string) {
|
||||
let child: PlanMinderNodeData | undefined;
|
||||
// 用例数子节点
|
||||
const caseCountNodeData = {
|
||||
id: getGenerateId(),
|
||||
text: t('ms.minders.item', { count: 0 }),
|
||||
resource: [caseCountTag],
|
||||
level: 3,
|
||||
isNew: true,
|
||||
};
|
||||
// 环境子节点
|
||||
const envNodeData = {
|
||||
id: getGenerateId(),
|
||||
text: t('case.execute.defaultEnv'),
|
||||
resource: [envTag],
|
||||
level: 3,
|
||||
isNew: true,
|
||||
};
|
||||
// 资源池子节点
|
||||
const resourcePoolNodeData = {
|
||||
id: getGenerateId(),
|
||||
resource: [resourcePoolTag],
|
||||
text: t('ms.minders.defaultResourcePool'),
|
||||
level: 3,
|
||||
isNew: true,
|
||||
};
|
||||
if (node.data?.level === 1) {
|
||||
// 测试分类下插入测试集
|
||||
child = {
|
||||
...node.data,
|
||||
id: getGenerateId(),
|
||||
text: t('ms.minders.defaultTestSet'),
|
||||
level: 2,
|
||||
isNew: true,
|
||||
};
|
||||
} else if (node.parent?.data) {
|
||||
// 测试集同级插入测试集
|
||||
child = {
|
||||
...(node.parent.data as PlanMinderNodeData),
|
||||
id: getGenerateId(),
|
||||
text: t('ms.minders.defaultTestSet'),
|
||||
level: 2,
|
||||
isNew: true,
|
||||
};
|
||||
}
|
||||
if (child) {
|
||||
execInert(type, child);
|
||||
nextTick(() => {
|
||||
execInert('AppendChildNode', caseCountNodeData);
|
||||
if (node.data?.type !== PlanMinderCollectionType.FUNCTIONAL) {
|
||||
// 功能用例测试集没有环境和资源池
|
||||
execInert('AppendSiblingNode', envNodeData);
|
||||
execInert('AppendSiblingNode', resourcePoolNodeData);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 当前激活的测试集节点
|
||||
const activePlanSet = ref<PlanMinderNode>();
|
||||
|
||||
const currentPriority = ref<RunMode>(RunMode.SERIAL);
|
||||
// 优先级与串行/并行文本映射
|
||||
const priorityTextMap = {
|
||||
2: t('ms.minders.serial'),
|
||||
3: t('ms.minders.parallel'),
|
||||
};
|
||||
// 串行/并行枚举值与优先级映射
|
||||
const priorityMap = {
|
||||
[RunMode.SERIAL]: 2,
|
||||
[RunMode.PARALLEL]: 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理执行方式切换
|
||||
* @param val 执行方式
|
||||
*/
|
||||
function handleExecuteMethodMenuSelect(val: RunMode) {
|
||||
currentPriority.value = val;
|
||||
// 对节点执行优先级设置命令
|
||||
window.minder.execCommand('priority', priorityMap[val]);
|
||||
// 手动设置一次脑图的优先级 DOM 内容替换
|
||||
setCustomPriorityView(priorityTextMap);
|
||||
}
|
||||
|
||||
const configFormRef = ref<FormInstance>();
|
||||
const configForm = ref<PlanMinderNodeData>();
|
||||
const resourcePoolOptions = ref<SelectOptionData[]>();
|
||||
const environmentOptions = computed(() => [
|
||||
{
|
||||
label: t('testPlan.testPlanIndex.defaultEnv'),
|
||||
value: 'NONE',
|
||||
},
|
||||
...appStore.getEnvList.map((item) => ({ label: item.name, value: item.id })),
|
||||
]);
|
||||
// 正在给 configForm 赋值,不触发表单变更
|
||||
const switchingConfigFormData = ref(false);
|
||||
const configFormUnsaved = ref(false);
|
||||
|
||||
function handleConfigCancel() {
|
||||
extraVisible.value = false;
|
||||
activePlanSet.value = undefined;
|
||||
configForm.value = undefined;
|
||||
configFormUnsaved.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换用例详情显示
|
||||
* 检查配置表单是否未保存
|
||||
*/
|
||||
async function toggleDetail() {
|
||||
extraVisible.value = !extraVisible.value;
|
||||
function checkConfigFormUnsaved() {
|
||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||
const { data } = node;
|
||||
if (extraVisible.value) {
|
||||
if (data?.resource && data.resource.includes('')) {
|
||||
console.log();
|
||||
if (configFormUnsaved.value) {
|
||||
if (node?.data?.id !== configForm.value?.id) {
|
||||
// 避免重复提示
|
||||
Message.warning(t('ms.minders.unsavedTip'));
|
||||
// 前面激活的配置表单未保存,先取消选中当前节点,返回到上一个节点
|
||||
if (node) {
|
||||
window.minder.toggleSelect(node);
|
||||
}
|
||||
window.minder.selectById(configForm.value?.id);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleFloatMenuClose() {
|
||||
if (!checkConfigFormUnsaved()) {
|
||||
handleConfigCancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理节点选中
|
||||
* @param node 节点
|
||||
*/
|
||||
function handleNodeSelect(node: PlanMinderNode) {
|
||||
if (checkConfigFormUnsaved()) {
|
||||
return;
|
||||
}
|
||||
checkNodeCanShowMenu(node);
|
||||
if (extraVisible.value) {
|
||||
if (node.data?.type === PlanMinderCollectionType.FUNCTIONAL && node.data?.level === 1) {
|
||||
// 功能用例分类没有配置
|
||||
extraVisible.value = false;
|
||||
return;
|
||||
}
|
||||
activePlanSet.value = node;
|
||||
switchingConfigFormData.value = true;
|
||||
configForm.value = cloneDeep(activePlanSet.value.data);
|
||||
nextTick(() => {
|
||||
switchingConfigFormData.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换测试集配置显示
|
||||
*/
|
||||
function toggleConfig() {
|
||||
if (checkConfigFormUnsaved()) {
|
||||
// 未保存配置不能通过配置按钮关闭配置抽屉
|
||||
Message.warning(t('ms.minders.unsavedTip'));
|
||||
return;
|
||||
}
|
||||
extraVisible.value = !extraVisible.value;
|
||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||
switchingConfigFormData.value = true;
|
||||
if (extraVisible.value) {
|
||||
activePlanSet.value = node as PlanMinderNode;
|
||||
configForm.value = cloneDeep(activePlanSet.value.data);
|
||||
} else {
|
||||
activePlanSet.value = undefined;
|
||||
configForm.value = undefined;
|
||||
}
|
||||
nextTick(() => {
|
||||
switchingConfigFormData.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
const currentSelectCase = ref<keyof typeof CaseLinkEnum>('FUNCTIONAL');
|
||||
const caseAssociateVisible = ref<boolean>(false);
|
||||
|
||||
// 批量关联用例表格参数
|
||||
const selectedAssociateCasesParams = ref<AssociateCaseRequest>({
|
||||
excludeIds: [],
|
||||
selectIds: [],
|
||||
selectAll: false,
|
||||
condition: {},
|
||||
moduleIds: [],
|
||||
versionId: '',
|
||||
refId: '',
|
||||
projectId: '',
|
||||
});
|
||||
|
||||
function writeAssociateCases(param: AssociateCaseRequest) {
|
||||
selectedAssociateCasesParams.value = { ...param };
|
||||
const node: PlanMinderNode = window.minder.getSelectedNode();
|
||||
node.data.associateDTOS = [
|
||||
{
|
||||
ids: param.selectIds,
|
||||
associateType:
|
||||
node.data.type === PlanMinderCollectionType.SCENARIO ? PlanMinderAssociateType.SCENARIO_CASE : node.data.type,
|
||||
},
|
||||
];
|
||||
caseAssociateVisible.value = false;
|
||||
}
|
||||
|
||||
function clearSelectedCases() {
|
||||
selectedAssociateCasesParams.value = {
|
||||
excludeIds: [],
|
||||
selectIds: [],
|
||||
selectAll: false,
|
||||
condition: {},
|
||||
moduleIds: [],
|
||||
versionId: '',
|
||||
refId: '',
|
||||
projectId: '',
|
||||
};
|
||||
}
|
||||
|
||||
function associateCase() {
|
||||
const node: PlanMinderNode = window.minder.getSelectedNode();
|
||||
activePlanSet.value = node;
|
||||
switchingConfigFormData.value = true;
|
||||
configForm.value = cloneDeep(activePlanSet.value.data);
|
||||
extraVisible.value = true;
|
||||
currentSelectCase.value = node.data?.type || 'FUNCTIONAL';
|
||||
caseAssociateVisible.value = true;
|
||||
nextTick(() => {
|
||||
switchingConfigFormData.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [configForm.value, selectedAssociateCasesParams.value.selectIds],
|
||||
() => {
|
||||
if (!switchingConfigFormData.value && configForm.value) {
|
||||
configFormUnsaved.value = true;
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 是否停止拖拽排序动作
|
||||
* @param dragNode 拖动节点
|
||||
@ -326,8 +622,16 @@
|
||||
}
|
||||
for (let i = 0; i < dragNodes.length; i++) {
|
||||
const dragNode = (dragNodes as MinderJsonNode[])[i];
|
||||
if (dragNode.parent?.data?.id !== dropNode.parent?.data?.id) {
|
||||
// 不允许跨节点拖拽
|
||||
return true;
|
||||
}
|
||||
if (dragNode.data?.level === 2) {
|
||||
// 只允许拖拽同一节点内的测试集排序
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -358,62 +662,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
const tempMinderParams = ref({
|
||||
projectId: appStore.currentProjectId,
|
||||
versionId: '',
|
||||
updateCaseList: [],
|
||||
updateModuleList: [],
|
||||
deleteResourceList: [],
|
||||
additionalNodeList: [],
|
||||
});
|
||||
|
||||
const configFormRef = ref<FormInstance>();
|
||||
const configForm = ref({
|
||||
resourcePool: '',
|
||||
executeType: 'serial',
|
||||
failStop: true,
|
||||
failRetry: true,
|
||||
failRetryType: 'step',
|
||||
retryTimes: 1,
|
||||
retrySpace: 1000,
|
||||
extend: true,
|
||||
});
|
||||
const resourcePoolOptions = ref<SelectOptionData[]>();
|
||||
|
||||
const currentSelectCase = ref<keyof typeof CaseLinkEnum>('FUNCTIONAL');
|
||||
const caseAssociateVisible = ref<boolean>(false);
|
||||
const caseAssociateProject = ref(appStore.currentProjectId);
|
||||
|
||||
// 批量关联用例表格参数
|
||||
const selectedAssociateCasesParams = ref<AssociateCaseRequest>({
|
||||
excludeIds: [],
|
||||
selectIds: [],
|
||||
selectAll: false,
|
||||
condition: {},
|
||||
moduleIds: [],
|
||||
versionId: '',
|
||||
refId: '',
|
||||
projectId: '',
|
||||
});
|
||||
|
||||
function writeAssociateCases(param: AssociateCaseRequest) {
|
||||
selectedAssociateCasesParams.value = { ...param };
|
||||
caseAssociateVisible.value = false;
|
||||
}
|
||||
|
||||
function clearSelectedCases() {
|
||||
selectedAssociateCasesParams.value = {
|
||||
excludeIds: [],
|
||||
selectIds: [],
|
||||
selectAll: false,
|
||||
condition: {},
|
||||
moduleIds: [],
|
||||
versionId: '',
|
||||
refId: '',
|
||||
projectId: '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化测试规划脑图
|
||||
*/
|
||||
@ -427,6 +675,7 @@
|
||||
level,
|
||||
isNew: false,
|
||||
changed: false,
|
||||
disabled: level < 2,
|
||||
};
|
||||
return node;
|
||||
});
|
||||
@ -440,15 +689,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
const tempMinderParams = ref({
|
||||
planId: props.planId,
|
||||
editList: [] as PlanMinderEditListItem[],
|
||||
deletedIds: [],
|
||||
});
|
||||
|
||||
/**
|
||||
* 生成脑图保存的入参
|
||||
*/
|
||||
function makeMinderParams(fullJson: MinderJson) {
|
||||
filterTree(fullJson.root.children, (node, nodeIndex, parent) => {
|
||||
if (node.data.isNew !== false || node.data.changed === true) {
|
||||
return true;
|
||||
filterTree(fullJson.root.children, (node, nodeIndex) => {
|
||||
if (node.data.isNew) {
|
||||
tempMinderParams.value.editList.push({
|
||||
...node.data,
|
||||
id: undefined,
|
||||
num: nodeIndex,
|
||||
});
|
||||
} else {
|
||||
tempMinderParams.value.editList.push({
|
||||
...node.data,
|
||||
num: nodeIndex,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
return node.data.level < 2;
|
||||
});
|
||||
return tempMinderParams.value;
|
||||
}
|
||||
@ -456,8 +720,9 @@
|
||||
async function handleMinderSave(fullJson: MinderJson, callback: () => void) {
|
||||
try {
|
||||
loading.value = true;
|
||||
// await saveCaseMinder(makeMinderParams(fullJson));
|
||||
await editPlanMinder(makeMinderParams(fullJson));
|
||||
Message.success(t('common.saveSuccess'));
|
||||
handleConfigCancel();
|
||||
initMinder();
|
||||
callback();
|
||||
} catch (error) {
|
||||
@ -468,6 +733,40 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存测试集配置
|
||||
*/
|
||||
function handleConfigSave() {
|
||||
configFormRef.value?.validate((errors) => {
|
||||
if (!errors) {
|
||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||
if (node.data && configForm.value) {
|
||||
node.data = {
|
||||
...node.data,
|
||||
...cloneDeep(configForm.value),
|
||||
};
|
||||
}
|
||||
// 派发SAVE_MINDER事件触发脑图的保存处理
|
||||
minderStore.dispatchEvent(MinderEventName.SAVE_MINDER);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function initResourcePoolList() {
|
||||
try {
|
||||
const res = await getPoolOption(appStore.currentProjectId);
|
||||
resourcePoolOptions.value = res.map((e) => ({ label: e.name, value: e.id }));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
initResourcePoolList();
|
||||
appStore.initEnvList();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
initMinder();
|
||||
nextTick(() => {
|
||||
@ -478,6 +777,7 @@
|
||||
}, 0);
|
||||
});
|
||||
window.minder.on('selectionchange', () => {
|
||||
// 异步执行,否则执行完,还会被重置
|
||||
setTimeout(() => {
|
||||
setCustomPriorityView(priorityTextMap);
|
||||
}, 0);
|
||||
|
@ -12,4 +12,11 @@ export default {
|
||||
'ms.minders.executeMethod': '运行方式',
|
||||
'ms.minders.serial': '串',
|
||||
'ms.minders.parallel': '并',
|
||||
'ms.minders.defaultTestSet': '默认测试集',
|
||||
'ms.minders.caseCount': '用例数',
|
||||
'ms.minders.resourcePool': '资源池',
|
||||
'ms.minders.defaultResourcePool': '默认资源池',
|
||||
'ms.minders.env': '环境',
|
||||
'ms.minders.item': '{count}条',
|
||||
'ms.minders.unsavedTip': '请先保存您更改的配置!',
|
||||
};
|
||||
|
@ -11,6 +11,7 @@ export interface UseEventListenerProps {
|
||||
handleMinderEvent?: (event: MinderCustomEvent) => void;
|
||||
handleBeforeExecCommand?: (event: MinderEvent) => void;
|
||||
handleViewChange?: (event: MinderEvent) => void;
|
||||
handleDragFinish?: (event: MinderEvent) => void;
|
||||
}
|
||||
|
||||
export default function useEventListener(listener: UseEventListenerProps) {
|
||||
@ -43,6 +44,14 @@ export default function useEventListener(listener: UseEventListenerProps) {
|
||||
}
|
||||
});
|
||||
|
||||
// 监听脑图节点拖拽结束事件
|
||||
minder.on('dragFinish', (e: MinderEvent) => {
|
||||
if (listener.handleDragFinish) {
|
||||
listener.handleDragFinish(e);
|
||||
}
|
||||
});
|
||||
|
||||
// 监听脑图画布位移等视图变化
|
||||
minder.on(
|
||||
'viewchange',
|
||||
debounce((e: MinderEvent) => {
|
||||
|
@ -42,6 +42,7 @@
|
||||
const emit = defineEmits<{
|
||||
(e: 'click', eventTag: string): void;
|
||||
(e: 'save'): void;
|
||||
(e: 'toggleFullScreen', isFullScreen: boolean): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
@ -49,6 +50,13 @@
|
||||
const containerRef = ref<Element | null>(null);
|
||||
const { toggleFullScreen, isFullScreen } = useFullScreen(containerRef);
|
||||
|
||||
watch(
|
||||
() => isFullScreen.value,
|
||||
(value) => {
|
||||
emit('toggleFullScreen', value);
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
containerRef.value = document.querySelector('.ms-minder-editor-container');
|
||||
});
|
||||
|
@ -12,7 +12,7 @@
|
||||
</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
</div>
|
||||
<nodeFloatMenu v-if="props.canShowFloatMenu" v-bind="props">
|
||||
<nodeFloatMenu v-if="props.canShowFloatMenu" v-bind="props" @close="emit('floatMenuClose')">
|
||||
<template #extractMenu>
|
||||
<slot name="extractMenu"></slot>
|
||||
</template>
|
||||
@ -33,6 +33,7 @@
|
||||
|
||||
import { MinderEventName } from '@/enums/minderEnum';
|
||||
|
||||
import useEventListener from '../hooks/useMinderEventListener';
|
||||
import {
|
||||
editMenuProps,
|
||||
floatMenuProps,
|
||||
@ -61,6 +62,7 @@
|
||||
const emit = defineEmits<{
|
||||
(e: 'save', data: MinderJson, callback: () => void): void;
|
||||
(e: 'afterMount'): void;
|
||||
(e: 'floatMenuClose'): void;
|
||||
}>();
|
||||
|
||||
const minderStore = useMinderStore();
|
||||
@ -79,43 +81,6 @@
|
||||
treePath: [],
|
||||
});
|
||||
|
||||
function handlePriorityButton() {
|
||||
const { priorityPrefix } = props;
|
||||
const { priorityStartWithZero } = props;
|
||||
let start = priorityStartWithZero ? 0 : 1;
|
||||
let res = '';
|
||||
for (let i = 0; i < props.priorityCount; i++) {
|
||||
res += start++;
|
||||
}
|
||||
const priority = window.minder.hotbox.state('priority');
|
||||
res.replace(/./g, (p) => {
|
||||
priority.button({
|
||||
position: 'ring',
|
||||
label: priorityPrefix + p,
|
||||
key: p,
|
||||
action() {
|
||||
const pVal = parseInt(p, 10);
|
||||
window.minder.execCommand('Priority', priorityStartWithZero ? pVal + 1 : pVal);
|
||||
},
|
||||
});
|
||||
// 需要返回字符串
|
||||
return '';
|
||||
});
|
||||
}
|
||||
function handleTagButton() {
|
||||
const tag = window.minder.hotbox.state('tag');
|
||||
props.tags?.forEach((item) => {
|
||||
tag.button({
|
||||
position: 'ring',
|
||||
label: item,
|
||||
key: item,
|
||||
action() {
|
||||
window.minder.execCommand('resource', item);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function init() {
|
||||
window.editor = new Editor(mec.value, {
|
||||
sequenceEnable: props.sequenceEnable,
|
||||
@ -137,43 +102,42 @@
|
||||
window.minder.execCommand('RemoveNode');
|
||||
};
|
||||
|
||||
window.minder.on('preExecCommand', (env: any) => {
|
||||
const selectNodes = env.minder.getSelectedNodes();
|
||||
const notChangeCommands = new Set([
|
||||
'camera',
|
||||
'copy',
|
||||
'expand',
|
||||
'expandToLevel',
|
||||
'hand',
|
||||
'layout',
|
||||
'template',
|
||||
'theme',
|
||||
'zoom',
|
||||
'zoomIn',
|
||||
'zoomOut',
|
||||
]);
|
||||
if (selectNodes && !notChangeCommands.has(env.commandName.toLocaleLowerCase())) {
|
||||
minderStore.setMinderUnsaved(true);
|
||||
minderStore.dispatchEvent(MinderEventName.MINDER_CHANGED);
|
||||
selectNodes.forEach((node: MinderJsonNode) => {
|
||||
markChangeNode(node);
|
||||
});
|
||||
}
|
||||
if (env.commandName === 'movetoparent') {
|
||||
setTimeout(() => {
|
||||
const targetNode = window.minder.getSelectedNode();
|
||||
targetNode.parent.renderTree();
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
|
||||
handlePriorityButton();
|
||||
handleTagButton();
|
||||
emit('afterMount');
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(() => {
|
||||
init();
|
||||
useEventListener({
|
||||
handleBeforeExecCommand(event) {
|
||||
const selectNodes: MinderJsonNode[] = event.minder.getSelectedNodes();
|
||||
const notChangeCommands = new Set([
|
||||
'camera',
|
||||
'copy',
|
||||
'expand',
|
||||
'expandToLevel',
|
||||
'hand',
|
||||
'layout',
|
||||
'template',
|
||||
'theme',
|
||||
'zoom',
|
||||
'zoomIn',
|
||||
'zoomOut',
|
||||
]);
|
||||
if (selectNodes.length > 0 && !notChangeCommands.has(event.commandName.toLocaleLowerCase())) {
|
||||
minderStore.setMinderUnsaved(true);
|
||||
minderStore.dispatchEvent(MinderEventName.MINDER_CHANGED);
|
||||
selectNodes.forEach((node: MinderJsonNode) => {
|
||||
markChangeNode(node);
|
||||
});
|
||||
}
|
||||
if (event.commandName === 'movetoparent') {
|
||||
setTimeout(() => {
|
||||
const targetNode = window.minder.getSelectedNode();
|
||||
targetNode.parent.renderTree();
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const menuVisible = ref(false);
|
||||
@ -206,23 +170,6 @@
|
||||
}, 100); // TODO:暂未知渲染时机,临时延迟解决
|
||||
}
|
||||
|
||||
watch(
|
||||
() => minderStore.event.timestamp,
|
||||
() => {
|
||||
if (minderStore.event.name === MinderEventName.HOTBOX && minderStore.event.nodePosition) {
|
||||
const nodeDomWidth = minderStore.event.nodeDom?.getBoundingClientRect().width || 0;
|
||||
menuPopupOffset.value = [
|
||||
minderStore.event.nodePosition.x + nodeDomWidth / 2,
|
||||
minderStore.event.nodePosition.y - nodeDomWidth / 4,
|
||||
];
|
||||
menuVisible.value = true;
|
||||
}
|
||||
if (minderStore.event.name === MinderEventName.ENTER_NODE && minderStore.event.nodes) {
|
||||
switchNode(minderStore.event.nodes[0]);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function save() {
|
||||
let data = importJson.value;
|
||||
if (innerImportJson.value.treePath?.length > 1) {
|
||||
@ -242,6 +189,26 @@
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => minderStore.event.timestamp,
|
||||
() => {
|
||||
if (minderStore.event.name === MinderEventName.HOTBOX && minderStore.event.nodePosition) {
|
||||
const nodeDomWidth = minderStore.event.nodeDom?.getBoundingClientRect().width || 0;
|
||||
menuPopupOffset.value = [
|
||||
minderStore.event.nodePosition.x + nodeDomWidth / 2,
|
||||
minderStore.event.nodePosition.y - nodeDomWidth / 4,
|
||||
];
|
||||
menuVisible.value = true;
|
||||
}
|
||||
if (minderStore.event.name === MinderEventName.ENTER_NODE && minderStore.event.nodes) {
|
||||
switchNode(minderStore.event.nodes[0]);
|
||||
}
|
||||
if (minderStore.event.name === MinderEventName.SAVE_MINDER) {
|
||||
save();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => minderStore.getMinderUnsaved,
|
||||
(val) => {
|
||||
@ -256,10 +223,9 @@
|
||||
@apply !absolute;
|
||||
}
|
||||
.ms-minder-container {
|
||||
@apply relative overflow-hidden !bg-white;
|
||||
@apply relative h-full overflow-hidden !bg-white;
|
||||
|
||||
padding: 16px 0;
|
||||
height: calc(100% - 60px);
|
||||
}
|
||||
.ms-minder-dropdown {
|
||||
.arco-dropdown-list-wrapper {
|
||||
|
@ -166,6 +166,11 @@
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<a-tooltip v-else-if="props.canShowDeleteMenu" :content="t('common.delete')">
|
||||
<MsButton type="icon" class="ms-minder-node-float-menu-icon-button" @click="handleMinderMenuSelect('delete')">
|
||||
<MsIcon type="icon-icon_delete-trash_outlined" class="text-[var(--color-text-4)]" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-trigger>
|
||||
</template>
|
||||
@ -179,7 +184,7 @@
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useMinderStore from '@/store/modules/components/minder-editor/index';
|
||||
import { MinderNodePosition } from '@/store/modules/components/minder-editor/types';
|
||||
import { getGenerateId } from '@/utils';
|
||||
import { getGenerateId, sleep } from '@/utils';
|
||||
|
||||
import { MinderEventName } from '@/enums/minderEnum';
|
||||
|
||||
@ -192,6 +197,9 @@
|
||||
...tagProps,
|
||||
...priorityProps,
|
||||
});
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const minderStore = useMinderStore();
|
||||
@ -204,34 +212,37 @@
|
||||
|
||||
watch(
|
||||
() => minderStore.event.timestamp,
|
||||
() => {
|
||||
let nodePosition: MinderNodePosition | undefined;
|
||||
const selectedNodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
if (minderStore.event.name === MinderEventName.NODE_SELECT) {
|
||||
nodePosition = minderStore.event.nodePosition;
|
||||
currentNodeTags.value = minderStore.event.nodes?.[0].data?.resource || [];
|
||||
if (props.replaceableTags) {
|
||||
tags.value = props.replaceableTags(selectedNodes);
|
||||
} else {
|
||||
tags.value = [];
|
||||
async () => {
|
||||
if (window.minder) {
|
||||
let nodePosition: MinderNodePosition | undefined;
|
||||
const selectedNodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
if (minderStore.event.name === MinderEventName.NODE_SELECT) {
|
||||
nodePosition = minderStore.event.nodePosition;
|
||||
currentNodeTags.value = minderStore.event.nodes?.[0].data?.resource || [];
|
||||
if (props.replaceableTags) {
|
||||
tags.value = props.replaceableTags(selectedNodes);
|
||||
} else {
|
||||
tags.value = [];
|
||||
}
|
||||
}
|
||||
if (selectedNodes.length > 1) {
|
||||
// 多选时隐藏悬浮菜单 TODO:支持批量操作
|
||||
menuVisible.value = false;
|
||||
return;
|
||||
}
|
||||
if ([MinderEventName.VIEW_CHANGE, MinderEventName.DRAG_FINISH].includes(minderStore.event.name)) {
|
||||
// 脑图画布移动时,重新计算节点位置
|
||||
await sleep(300); // 拖拽完毕后会有 300ms 的动画,等待动画结束后再计算
|
||||
nodePosition = window.minder.getSelectedNode()?.getRenderBox();
|
||||
}
|
||||
if (nodePosition && isNodeInMinderView(undefined, nodePosition, nodePosition.width / 2)) {
|
||||
// 判断节点在脑图可视区域内且遮挡的节点不超过节点宽度的一半,则显示菜单
|
||||
const nodeDomHeight = nodePosition.height || 0;
|
||||
menuPopupOffset.value = [nodePosition.x, nodePosition.y + nodeDomHeight + 4]; // 菜单显示在节点下方4px处
|
||||
menuVisible.value = true;
|
||||
} else {
|
||||
menuVisible.value = false;
|
||||
}
|
||||
}
|
||||
if (selectedNodes.length > 1) {
|
||||
// 多选时隐藏悬浮菜单 TODO:支持批量操作
|
||||
menuVisible.value = false;
|
||||
return;
|
||||
}
|
||||
if (minderStore.event.name === MinderEventName.VIEW_CHANGE) {
|
||||
// 脑图画布移动时,重新计算节点位置
|
||||
nodePosition = window.minder.getSelectedNode()?.getRenderBox();
|
||||
}
|
||||
if (nodePosition && isNodeInMinderView(undefined, nodePosition, nodePosition.width / 2)) {
|
||||
// 判断节点在脑图可视区域内且遮挡的节点不超过节点宽度的一半,则显示菜单
|
||||
const nodeDomHeight = nodePosition.height || 0;
|
||||
menuPopupOffset.value = [nodePosition.x, nodePosition.y + nodeDomHeight + 4]; // 菜单显示在节点下方4px处
|
||||
menuVisible.value = true;
|
||||
} else {
|
||||
menuVisible.value = false;
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -376,6 +387,15 @@
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => menuVisible.value,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
emit('close');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
const freshFuc = setPriorityView;
|
||||
|
@ -1,7 +1,13 @@
|
||||
<template>
|
||||
<a-spin :loading="loading" class="ms-minder-editor-container">
|
||||
<div class="flex-1">
|
||||
<mainEditor v-model:import-json="importJson" v-bind="props" @after-mount="() => emit('afterMount')" @save="save">
|
||||
<div class="relative flex-1 overflow-hidden">
|
||||
<mainEditor
|
||||
v-model:import-json="importJson"
|
||||
v-bind="props"
|
||||
@after-mount="() => emit('afterMount')"
|
||||
@save="save"
|
||||
@float-menu-close="emit('floatMenuClose')"
|
||||
>
|
||||
<template #extractMenu>
|
||||
<slot name="extractMenu"></slot>
|
||||
</template>
|
||||
@ -52,6 +58,7 @@
|
||||
(e: 'action', event: MinderCustomEvent): void;
|
||||
(e: 'beforeExecCommand', event: MinderEvent): void;
|
||||
(e: 'nodeUnselect'): void;
|
||||
(e: 'floatMenuClose'): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps({
|
||||
@ -126,6 +133,9 @@
|
||||
handleViewChange() {
|
||||
minderStore.dispatchEvent(MinderEventName.VIEW_CHANGE);
|
||||
},
|
||||
handleDragFinish() {
|
||||
minderStore.dispatchEvent(MinderEventName.DRAG_FINISH);
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@ -134,7 +144,7 @@
|
||||
.ms-minder-editor-container {
|
||||
@apply relative flex h-full w-full;
|
||||
.ms-minder-editor-extra {
|
||||
@apply flex flex-col overflow-hidden;
|
||||
@apply flex flex-col overflow-hidden bg-white;
|
||||
|
||||
width: 0;
|
||||
transition: all 300ms ease-in-out;
|
||||
|
@ -149,6 +149,11 @@ export const floatMenuProps = {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 不显示更多菜单时,是否显示删除菜单
|
||||
canShowDeleteMenu: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 是否显示进入节点
|
||||
canShowEnterNode: {
|
||||
type: Boolean,
|
||||
|
@ -13,6 +13,8 @@ export enum MinderEventName {
|
||||
'NODE_UNSELECT' = 'NODE_UNSELECT', // 取消选中节点
|
||||
'VIEW_CHANGE' = 'VIEW_CHANGE', // 脑图视图移动
|
||||
'MINDER_CHANGED' = 'MINDER_CHANGED', // 脑图更改事件
|
||||
'SAVE_MINDER' = 'SAVE_MINDER', // 脑图保存事件
|
||||
'DRAG_FINISH' = 'DRAG_FINISH', // 脑图节点拖拽结束事件
|
||||
}
|
||||
|
||||
export default {};
|
||||
|
@ -9,13 +9,21 @@ export enum RunMode {
|
||||
PARALLEL = 'PARALLEL', // 并行
|
||||
}
|
||||
|
||||
export enum TestSetType {
|
||||
FUNCTIONAL_CASE = 'FUNCTIONAL_CASE',
|
||||
API_CASE = 'API_CASE',
|
||||
SCENARIO_CASE = 'SCENARIO_CASE',
|
||||
}
|
||||
|
||||
export enum FailRetry {
|
||||
STEP = 'STEP',
|
||||
SCENARIO = 'SCENARIO',
|
||||
}
|
||||
|
||||
// 功能:FUNCTIONAL_CASE/接口定义:API/接口用例:API_CASE/场景:SCENARIO_CASE
|
||||
export enum PlanMinderAssociateType {
|
||||
FUNCTIONAL_CASE = 'FUNCTIONAL',
|
||||
API = 'API',
|
||||
API_CASE = 'API_CASE',
|
||||
SCENARIO_CASE = 'SCENARIO_CASE',
|
||||
}
|
||||
// 测试集类型(功能:FUNCTIONAL/接口用例:API/场景:SCENARIO)
|
||||
export enum PlanMinderCollectionType {
|
||||
FUNCTIONAL = 'FUNCTIONAL',
|
||||
API = 'API',
|
||||
SCENARIO = 'SCENARIO',
|
||||
}
|
||||
|
@ -1,11 +1,17 @@
|
||||
import type { MinderJsonNodeData } from '@/components/pure/ms-minder-editor/props';
|
||||
import type { MinderJsonNode, MinderJsonNodeData } from '@/components/pure/ms-minder-editor/props';
|
||||
import type { BatchActionQueryParams } from '@/components/pure/ms-table/type';
|
||||
|
||||
import type { customFieldsItem } from '@/models/caseManagement/featureCase';
|
||||
import type { TableQueryParams } from '@/models/common';
|
||||
import { BatchApiParams, DragSortParams } from '@/models/common';
|
||||
import { LastExecuteResults } from '@/enums/caseEnum';
|
||||
import { type FailRetry, type RunMode, testPlanTypeEnum, type TestSetType } from '@/enums/testPlanEnum';
|
||||
import {
|
||||
type FailRetry,
|
||||
type PlanMinderAssociateType,
|
||||
type PlanMinderCollectionType,
|
||||
type RunMode,
|
||||
testPlanTypeEnum,
|
||||
} from '@/enums/testPlanEnum';
|
||||
|
||||
export type planStatusType = 'PREPARED' | 'UNDERWAY' | 'COMPLETED' | 'ARCHIVED';
|
||||
|
||||
@ -375,9 +381,9 @@ export interface PlanMinderNodeData extends MinderJsonNodeData {
|
||||
pos: number;
|
||||
text: string;
|
||||
num: number; // 关联用例数量
|
||||
priority: string; // 串行/并行
|
||||
executeMethod: RunMode; // 串行/并行值
|
||||
type: TestSetType; // 测试集类型(功能/接口/场景)
|
||||
priority?: number; // 串行/并行
|
||||
executeMethod?: RunMode; // 串行/并行值
|
||||
type: PlanMinderCollectionType; // 测试集类型(功能/接口/场景)
|
||||
extended: boolean;
|
||||
grouped: boolean; // 是否使用环境组
|
||||
environmentId: string;
|
||||
@ -388,7 +394,23 @@ export interface PlanMinderNodeData extends MinderJsonNodeData {
|
||||
retryInterval: number;
|
||||
stopOnFail: boolean;
|
||||
}
|
||||
export interface PlanMinderNode {
|
||||
export interface PlanMinderNode extends MinderJsonNode {
|
||||
data: PlanMinderNodeData;
|
||||
children: PlanMinderNode[];
|
||||
}
|
||||
|
||||
export interface PlanMinderAssociateDTO {
|
||||
ids: string[];
|
||||
associateType: PlanMinderAssociateType; // 关联关系的type(功能:FUNCTIONAL_CASE/接口定义:API/接口用例:API_CASE/场景:SCENARIO_CASE)
|
||||
}
|
||||
export interface PlanMinderEditListItem extends PlanMinderNodeData {
|
||||
name: string;
|
||||
collectionType: PlanMinderCollectionType; // 测试集类型(功能:FUNCTIONAL_CASE/接口用例:API_CASE/场景:SCENARIO_CASE)
|
||||
associateDTOS: PlanMinderAssociateDTO[];
|
||||
}
|
||||
|
||||
export interface PlanMinderEditParams {
|
||||
planId: string;
|
||||
editList: PlanMinderEditListItem[];
|
||||
deletedIds: string[];
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import { useI18n } from '@/hooks/useI18n';
|
||||
import { NO_PROJECT_ROUTE_NAME } from '@/router/constants';
|
||||
import { watchStyle, watchTheme } from '@/utils/theme';
|
||||
|
||||
import type { EnvironmentItem } from '@/models/projectManagement/environmental';
|
||||
import type { PageConfig, PageConfigKeys, Style, Theme } from '@/models/setting/config';
|
||||
import { ProjectListItem } from '@/models/setting/project';
|
||||
|
||||
@ -108,6 +109,9 @@ const useAppStore = defineStore('app', {
|
||||
getCurrentEnvId(state: AppState): string {
|
||||
return state.currentEnvConfig?.id || '';
|
||||
},
|
||||
getEnvList(state: AppState): EnvironmentItem[] {
|
||||
return state.envList;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
/**
|
||||
|
@ -105,7 +105,8 @@
|
||||
|
||||
import { BatchActionQueryParams } from '@/components/pure/ms-table/type';
|
||||
|
||||
import { getEnvList, getPoolId, getPoolOption } from '@/api/modules/api-test/management';
|
||||
import { getEnvList } from '@/api/modules/api-test/common';
|
||||
import { getPoolId, getPoolOption } from '@/api/modules/api-test/management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
|
@ -105,7 +105,8 @@
|
||||
|
||||
import { BatchActionQueryParams } from '@/components/pure/ms-table/type';
|
||||
|
||||
import { getEnvList, getPoolId, getPoolOption } from '@/api/modules/api-test/management';
|
||||
import { getEnvList } from '@/api/modules/api-test/common';
|
||||
import { getPoolId, getPoolOption } from '@/api/modules/api-test/management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
|
@ -506,7 +506,8 @@
|
||||
import BatchRunModal from '@/views/api-test/scenario/components/batchRunModal.vue';
|
||||
import operationScenarioModuleTree from '@/views/api-test/scenario/components/operationScenarioModuleTree.vue';
|
||||
|
||||
import { getEnvList, getPoolId, getPoolOption } from '@/api/modules/api-test/management';
|
||||
import { getEnvList } from '@/api/modules/api-test/common';
|
||||
import { getPoolId, getPoolOption } from '@/api/modules/api-test/management';
|
||||
import {
|
||||
batchEditScenario,
|
||||
batchOptionScenario,
|
||||
|
@ -95,6 +95,7 @@
|
||||
v-model:active-key="activeTab"
|
||||
:get-text-func="getTabBadge"
|
||||
:content-tab-list="tabList"
|
||||
:change-interceptor="changeTabInterceptor"
|
||||
no-content
|
||||
class="relative mx-[16px] border-b"
|
||||
/>
|
||||
@ -424,6 +425,29 @@
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function changeTabInterceptor(newVal: string, oldVal: string, done: () => void) {
|
||||
console.log('changeTabInterceptor', newVal, oldVal);
|
||||
if (oldVal === 'plan') {
|
||||
openModal({
|
||||
type: 'warning',
|
||||
title: t('common.tip'),
|
||||
content: t('ms.minders.leaveUnsavedTip'),
|
||||
okText: t('common.confirm'),
|
||||
cancelText: t('common.cancel'),
|
||||
okButtonProps: {
|
||||
status: 'normal',
|
||||
},
|
||||
onBeforeOk: async () => {
|
||||
done();
|
||||
},
|
||||
hideCancel: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
done();
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
initDetail();
|
||||
loadActiveTabList();
|
||||
|
Loading…
Reference in New Issue
Block a user