feat(测试计划): 脑图执行用例-查看

This commit is contained in:
teukkk 2024-07-29 18:53:40 +08:00 committed by Craftsman
parent b2f6d322a5
commit dba7e10a0d
13 changed files with 881 additions and 216 deletions

View File

@ -15,6 +15,7 @@ import {
EditReviewUrl, EditReviewUrl,
FollowReviewUrl, FollowReviewUrl,
GetAssociatedIdsUrl, GetAssociatedIdsUrl,
GetCasePlanMinderUrl,
getCaseReviewerListUrl, getCaseReviewerListUrl,
GetCaseReviewHistoryListUrl, GetCaseReviewHistoryListUrl,
GetCaseReviewMinderUrl, GetCaseReviewMinderUrl,
@ -41,6 +42,7 @@ import {
BatchChangeReviewerParams, BatchChangeReviewerParams,
BatchMoveReviewParams, BatchMoveReviewParams,
BatchReviewCaseParams, BatchReviewCaseParams,
CasePlanMinderParams,
CaseReviewFunctionalCaseUserItem, CaseReviewFunctionalCaseUserItem,
CaseReviewMinderParams, CaseReviewMinderParams,
CommitReviewResultParams, CommitReviewResultParams,
@ -209,11 +211,16 @@ export const getCaseReviewerList = (reviewId: string, caseId: string) => {
return MSR.get<CaseReviewFunctionalCaseUserItem[]>({ url: `${getCaseReviewerListUrl}/${reviewId}/${caseId}` }); return MSR.get<CaseReviewFunctionalCaseUserItem[]>({ url: `${getCaseReviewerListUrl}/${reviewId}/${caseId}` });
}; };
// 获取脑图 // 获取评审脑图
export function getCaseReviewMinder(data: CaseReviewMinderParams) { export function getCaseReviewMinder(data: CaseReviewMinderParams) {
return MSR.post<CommonList<MinderJsonNode>>({ url: `${GetCaseReviewMinderUrl}`, data }); return MSR.post<CommonList<MinderJsonNode>>({ url: `${GetCaseReviewMinderUrl}`, data });
} }
// 获取测试计划用例脑图
export function getCasePlanMinder(data: CasePlanMinderParams) {
return MSR.post<CommonList<MinderJsonNode>>({ url: `${GetCasePlanMinderUrl}`, data });
}
// 脑图-获取用例评审最终结果和每个评审人最终的评审结果 // 脑图-获取用例评审最终结果和每个评审人最终的评审结果
export const getReviewerAndStatus = (reviewId: string, caseId: string) => { export const getReviewerAndStatus = (reviewId: string, caseId: string) => {
return MSR.get<ReviewerAndStatus>({ url: `${GetReviewerAndStatusUrl}/${reviewId}/${caseId}` }); return MSR.get<ReviewerAndStatus>({ url: `${GetReviewerAndStatusUrl}/${reviewId}/${caseId}` });

View File

@ -27,5 +27,6 @@ export const GetReviewDetailModuleTreeUrl = '/case/review/detail/tree'; // 评
export const GetCaseReviewHistoryListUrl = '/review/functional/case/get/list'; // 评审详情-获取用例评审历史 export const GetCaseReviewHistoryListUrl = '/review/functional/case/get/list'; // 评审详情-获取用例评审历史
export const SaveCaseReviewResultUrl = '/review/functional/case/save'; // 评审详情-提交评审 export const SaveCaseReviewResultUrl = '/review/functional/case/save'; // 评审详情-提交评审
export const getCaseReviewerListUrl = '/case/review/detail/reviewer/list'; // 评审详情-获取用例的评审人 export const getCaseReviewerListUrl = '/case/review/detail/reviewer/list'; // 评审详情-获取用例的评审人
export const GetCaseReviewMinderUrl = '/functional/mind/case/review/list'; // 获取脑图数据 export const GetCaseReviewMinderUrl = '/functional/mind/case/review/list'; // 获取评审脑图数据
export const GetReviewerAndStatusUrl = '/case/review/detail/reviewer/status/total'; // 脑图-获取用例评审最终结果和每个评审人最终的评审结果 export const GetReviewerAndStatusUrl = '/case/review/detail/reviewer/status/total'; // 脑图-获取用例评审最终结果和每个评审人最终的评审结果
export const GetCasePlanMinderUrl = '/functional/mind/case/plan/list'; // 获取测试计划用例脑图

View File

@ -105,8 +105,11 @@
import MsMinderEditor from '@/components/pure/ms-minder-editor/minderEditor.vue'; import MsMinderEditor from '@/components/pure/ms-minder-editor/minderEditor.vue';
import type { MinderJson, MinderJsonNode, MinderJsonNodeData } from '@/components/pure/ms-minder-editor/props'; import type { MinderJson, MinderJsonNode, MinderJsonNodeData } from '@/components/pure/ms-minder-editor/props';
import { import {
createNode,
expendNodeAndChildren, expendNodeAndChildren,
handleRenderNode, handleRenderNode,
removeFakeNode,
renderSubNodes,
setPriorityView, setPriorityView,
} from '@/components/pure/ms-minder-editor/script/tool/utils'; } from '@/components/pure/ms-minder-editor/script/tool/utils';
import { MsFileItem } from '@/components/pure/ms-upload/types'; import { MsFileItem } from '@/components/pure/ms-upload/types';
@ -247,50 +250,6 @@
initCaseTree(); initCaseTree();
}); });
/**
* 移除占位的虚拟节点
* @param node 对应节点
* @param fakeNodeName 虚拟节点名称
*/
function removeFakeNode(node: MinderJsonNode, fakeNodeName: string) {
const fakeNode = node.children?.find((e: MinderJsonNode) => e.data?.id === fakeNodeName);
if (fakeNode) {
window.minder.removeNode(fakeNode);
}
}
/**
* 创建节点
* @param data 节点数据
* @param parentNode 父节点
*/
function createNode(data?: MinderJsonNodeData, parentNode?: MinderJsonNode) {
return window.minder.createNode(
{
...data,
expandState: 'collapse',
disabled: true,
},
parentNode
);
}
/**
* 递归渲染子节点及其子节点
* @param parentNode - 父节点
* @param children - 子节点数组
*/
function renderSubNodes(parentNode: MinderJsonNode, children?: MinderJsonNode[]) {
return (
children?.map((item: MinderJsonNode) => {
const grandChild = createNode(item.data, parentNode);
const greatGrandChildren = renderSubNodes(grandChild, item.children);
window.minder.renderNodeBatch(greatGrandChildren);
return grandChild;
}) || []
);
}
/** /**
* 加载模块节点下的用例节点 * 加载模块节点下的用例节点
* @param node 选中节点 * @param node 选中节点

View File

@ -1,6 +1,6 @@
<template> <template>
<a-spin :loading="bugListLoading" class="block h-full pl-[16px]"> <a-spin :loading="bugListLoading" class="block h-full pl-[16px]">
<div class="flex items-center justify-between"> <div v-if="!props.isTestPlanCase" class="flex items-center justify-between">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<a-button v-if="hasEditPermission" class="mr-3" type="primary" @click="linkBug"> <a-button v-if="hasEditPermission" class="mr-3" type="primary" @click="linkBug">
{{ t('caseManagement.featureCase.linkDefect') }} {{ t('caseManagement.featureCase.linkDefect') }}
@ -39,7 +39,11 @@
<div class="bug-item"> <div class="bug-item">
<div class="mb-[4px] flex items-center justify-between"> <div class="mb-[4px] flex items-center justify-between">
<MsButton type="text" @click="goBug(item.bugId)">{{ item.num }}</MsButton> <MsButton type="text" @click="goBug(item.bugId)">{{ item.num }}</MsButton>
<MsButton v-if="hasEditPermission && showType === 'link'" type="text" @click="disassociateBug(item.id)"> <MsButton
v-if="hasEditPermission && (!props.isTestPlanCase ? showType === 'link' : props.showDisassociateButton)"
type="text"
@click="disassociateBug(item.id)"
>
{{ t('ms.add.attachment.cancelAssociate') }} {{ t('ms.add.attachment.cancelAssociate') }}
</MsButton> </MsButton>
</div> </div>
@ -67,7 +71,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue'; import MsButton from '@/components/pure/ms-button/index.vue';
@ -79,6 +82,7 @@
getLinkedCaseBugList, getLinkedCaseBugList,
} from '@/api/modules/case-management/featureCase'; } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useOpenNewPage from '@/hooks/useOpenNewPage';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
@ -94,9 +98,11 @@
const props = defineProps<{ const props = defineProps<{
activeCase: Record<string, any>; activeCase: Record<string, any>;
isTestPlanCase?: boolean;
showDisassociateButton?: boolean;
}>(); }>();
const router = useRouter(); const { openNewPage } = useOpenNewPage();
const appStore = useAppStore(); const appStore = useAppStore();
const { t } = useI18n(); const { t } = useI18n();
@ -108,7 +114,7 @@
pageSize: 10, pageSize: 10,
current: 1, current: 1,
}); });
const showType = ref<'link' | 'testPlan'>('link'); const showType = ref<'link' | 'testPlan'>(!props.isTestPlanCase ? 'link' : 'testPlan');
const bugListLoading = ref(false); const bugListLoading = ref(false);
async function loadBugList() { async function loadBugList() {
@ -167,11 +173,8 @@
} }
function goBug(id: string) { function goBug(id: string) {
router.push({ openNewPage(BugManagementRouteEnum.BUG_MANAGEMENT_INDEX, {
name: BugManagementRouteEnum.BUG_MANAGEMENT_INDEX, id,
query: {
id,
},
}); });
} }

View File

@ -0,0 +1,539 @@
<template>
<div class="h-full">
<MsMinderEditor
v-model:activeExtraKey="activeExtraKey"
v-model:extra-visible="extraVisible"
v-model:loading="loading"
v-model:import-json="importJson"
:minder-key="MinderKeyEnum.TEST_PLAN_FEATURE_CASE_MINDER"
:extract-content-tab-list="extractContentTabList"
:can-show-float-menu="canShowFloatMenu"
:can-show-priority-menu="false"
:can-show-more-menu="canShowMoreMenu"
:can-show-enter-node="canShowEnterNode"
:can-show-more-menu-node-operation="false"
:more-menu-other-operation-list="canShowFloatMenu ? moreMenuOtherOperationList : []"
disabled
@node-select="handleNodeSelect"
@node-unselect="handleNodeUnselect"
>
<template #extractMenu>
<!-- 缺陷 -->
<a-dropdown position="bl">
<a-tooltip
v-if="showAssociateBugMenu && hasAnyPermission(['PROJECT_BUG:READ', 'PROJECT_BUG:READ+ADD'])"
:content="t('common.add')"
>
<MsButton type="icon" class="ms-minder-node-float-menu-icon-button">
<MsIcon type="icon-icon_add_outlined" class="text-[var(--color-text-4)]" />
</MsButton>
</a-tooltip>
<template #content>
<a-doption v-permission="['PROJECT_BUG:READ+ADD']" value="new">
{{ t('testPlan.featureCase.noBugDataNewBug') }}
</a-doption>
<a-doption v-permission="['PROJECT_BUG:READ']" value="link">
{{ t('caseManagement.featureCase.linkDefect') }}
</a-doption>
</template>
</a-dropdown>
<!-- 执行 -->
<a-tooltip :content="t('common.execute')">
<MsButton type="icon" class="ms-minder-node-float-menu-icon-button">
<MsIcon type="icon-icon_play-round_filled" class="text-[var(--color-text-4)]" />
</MsButton>
</a-tooltip>
<!-- 查看详情 -->
<a-tooltip v-if="canShowDetail" :content="t('common.detail')">
<MsButton
type="icon"
:class="[
'ms-minder-node-float-menu-icon-button',
`${extraVisible ? 'ms-minder-node-float-menu-icon-button--focus' : ''}`,
]"
@click="toggleDetail"
>
<MsIcon type="icon-icon_describe_outlined" class="text-[var(--color-text-4)]" />
</MsButton>
</a-tooltip>
</template>
<template #extractTabContent>
<MsDescription
v-if="activeExtraKey === 'baseInfo'"
:loading="baseInfoLoading"
:descriptions="descriptions"
label-width="90px"
class="pl-[16px]"
/>
<Attachment
v-else-if="activeExtraKey === 'attachment'"
v-model:model-value="fileList"
not-show-add-button
disabled
:active-case="activeCaseInfo"
/>
<BugList
v-else-if="activeExtraKey === 'bug'"
:active-case="activeCaseInfo"
is-test-plan-case
show-disassociate-button
/>
<ReviewCommentList
v-else
class="pl-[16px]"
:review-comment-list="executeHistoryList"
active-comment="executiveComment"
not-show-review-name
/>
</template>
</MsMinderEditor>
</div>
</template>
<script setup lang="ts">
import MsButton from '@/components/pure/ms-button/index.vue';
import MsDescription, { Description } from '@/components/pure/ms-description/index.vue';
import MsMinderEditor from '@/components/pure/ms-minder-editor/minderEditor.vue';
import type { MinderJson, MinderJsonNode, MinderJsonNodeData } from '@/components/pure/ms-minder-editor/props';
import {
createNode,
expendNodeAndChildren,
handleRenderNode,
removeFakeNode,
renderSubNodes,
setPriorityView,
} from '@/components/pure/ms-minder-editor/script/tool/utils';
import { MsFileItem } from '@/components/pure/ms-upload/types';
import Attachment from '@/components/business/ms-minders/featureCaseMinder/attachment.vue';
import BugList from '@/components/business/ms-minders/featureCaseMinder/bugList.vue';
import ReviewCommentList from '@/views/case-management/caseManagementFeature/components/tabContent/tabComment/reviewCommentList.vue';
import { getCasePlanMinder } from '@/api/modules/case-management/caseReview';
import { executeHistory, getCaseDetail } from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n';
import useMinderStore from '@/store/modules/components/minder-editor/index';
import useTestPlanFeatureCaseStore from '@/store/modules/testPlan/testPlanFeatureCase';
import { findNodeByKey, mapTree, replaceNodeInTree } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import { ModuleTreeNode } from '@/models/common';
import type { ExecuteHistoryItem } from '@/models/testPlan/testPlan';
import { MinderEventName, MinderKeyEnum } from '@/enums/minderEnum';
import {
convertToFile,
executionResultMap,
getCustomField,
} from '@/views/case-management/caseManagementFeature/components/utils';
const props = defineProps<{
activeModule: string;
moduleTree: ModuleTreeNode[];
planId: string;
}>();
const emit = defineEmits<{
(e: 'operation', type: string, node: MinderJsonNode): void;
}>();
const { t } = useI18n();
const minderStore = useMinderStore();
const testPlanFeatureCaseStore = useTestPlanFeatureCaseStore();
const caseTag = t('common.case');
const moduleTag = t('common.module');
const importJson = ref<MinderJson>({
root: {} as MinderJsonNode,
treePath: [],
});
const loading = ref(false);
const modulesCount = computed(() => testPlanFeatureCaseStore.modulesCount);
/**
* 找到最顶层的父节点id
* @param node 选中节点
*/
function getMinderNodeParentId(node: MinderJsonNode): string {
while (node?.parent && node.parent.data?.id !== 'NONE') {
node = node.parent;
}
return node.id ?? '';
}
/**
* 初始化用例模块树
*/
async function initCaseTree() {
const tree = mapTree<MinderJsonNode>(props.moduleTree, (e) => ({
...e,
data: {
...e.data,
id: e.id || e.data?.id || '',
text: e.name || e.data?.text || '',
resource: modulesCount.value[e.id] !== undefined ? [moduleTag] : e.data?.resource,
expandState: e.level === 0 ? 'expand' : 'collapse',
count: modulesCount.value[e.id],
disabled: true,
projectId: getMinderNodeParentId(e),
},
children:
modulesCount.value[e.id] > 0 && !e.children?.length
? [
{
data: {
id: 'fakeNode',
text: 'fakeNode',
resource: ['fakeNode'],
},
},
]
: e.children,
}));
importJson.value.root = {
children: tree,
data: {
id: 'NONE',
text: t('testPlan.testPlanIndex.functionalUseCase'),
resource: [moduleTag],
disabled: true,
count: modulesCount.value.all,
},
};
importJson.value.treePath = [];
window.minder.importJson(importJson.value);
if (props.activeModule !== 'all') {
// ID
nextTick(() => {
minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, undefined, [
findNodeByKey(importJson.value.root.children || [], props.activeModule, 'id', 'data') as MinderJsonNode,
]);
});
} else {
//
nextTick(() => {
minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, undefined, [importJson.value.root]);
});
}
}
onMounted(() => {
initCaseTree();
//
window.minder._resourceColorMapping = {
[executionResultMap.SUCCESS.statusText]: 4,
[executionResultMap.ERROR.statusText]: 5,
[executionResultMap.BLOCKED.statusText]: 6,
};
});
watch(
() => props.activeModule,
() => {
initCaseTree();
}
);
/**
* 加载模块节点下的用例节点
* @param node 选中节点
* @param loadMoreCurrent 加载模块下更多用例时的当前页码
*/
async function initNodeCases(node: MinderJsonNode, loadMoreCurrent?: number) {
try {
loading.value = true;
if (!node?.data) return;
const { list, total } = await getCasePlanMinder({
current: (loadMoreCurrent ?? 0) + 1,
moduleId: node.data?.id,
projectId: node.data?.projectId,
planId: props.planId,
});
//
removeFakeNode(node, loadMoreCurrent ? `tmp-${node.data?.id}` : 'fakeNode');
//
if ((!list || list.length === 0) && node.children?.length && !loadMoreCurrent) {
node.expand();
handleRenderNode(node, node.children);
return;
}
//
let waitingRenderNodes: MinderJsonNode[] = [];
list.forEach((e: MinderJsonNode) => {
//
const child = createNode(
{
...(e.data as MinderJsonNodeData),
resource: [
...(executionResultMap[e.data?.status]?.statusText
? [executionResultMap[e.data?.status].statusText]
: []),
...(e.data?.resource ?? []),
],
},
node
);
waitingRenderNodes.push(child);
// ///
const grandChildren = renderSubNodes(child, e.children);
window.minder.renderNodeBatch(grandChildren);
});
node.expand();
if (node.children && node.children.length > 0) {
waitingRenderNodes = waitingRenderNodes.concat(node.children);
}
//
if (total > 100 * ((loadMoreCurrent ?? 0) + 1)) {
const moreNode = window.minder.createNode(
{
id: `tmp-${node.data?.id}`,
text: '...',
type: 'tmp',
expandState: 'collapse',
current: (loadMoreCurrent ?? 0) + 1,
disabled: true,
},
node
);
waitingRenderNodes.push(moreNode);
}
handleRenderNode(node, waitingRenderNodes);
// importJson
replaceNodeInTree([importJson.value.root], node.data?.id || '', window.minder.exportNode(node), 'data', 'id');
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
loading.value = false;
}
}
const extraVisible = ref<boolean>(false);
const activeExtraKey = ref<'baseInfo' | 'attachment' | 'history' | 'bug'>('history');
const baseInfoLoading = ref(false);
const activeCaseInfo = ref<Record<string, any>>({});
const descriptions = ref<Description[]>([]);
const fileList = ref<MsFileItem[]>([]);
const extractContentTabList = [
{
value: 'baseInfo',
label: t('common.baseInfo'),
},
{
value: 'attachment',
label: t('caseManagement.featureCase.attachment'),
},
{
value: 'bug',
label: t('testPlan.featureCase.bug'),
},
{
value: 'history',
label: t('testPlan.featureCase.executionHistory'),
},
];
function resetExtractInfo() {
activeCaseInfo.value = {};
fileList.value = [];
}
/**
* 初始化用例详情
* @param data 节点数据
*/
async function initCaseDetail(data: MinderJsonNodeData) {
try {
baseInfoLoading.value = true;
const res = await getCaseDetail(data?.id || activeCaseInfo.value.id);
activeCaseInfo.value = res;
//
descriptions.value = [
{
label: t('caseManagement.caseReview.caseName'),
value: res.name,
},
{
label: t('common.tag'),
value: res.tags,
isTag: true,
},
//
...res.customFields.map((e: Record<string, any>) => {
try {
return {
label: e.fieldName,
value: getCustomField(e),
};
} catch (error) {
return {
label: e.fieldName,
value: e.defaultValue,
};
}
}),
].map((item) => ({ ...item, tooltipPosition: 'tr' }));
//
if (activeCaseInfo.value.attachments) {
fileList.value = activeCaseInfo.value.attachments
.map((fileInfo: any) => {
return {
...fileInfo,
name: fileInfo.fileName,
};
})
.map((fileInfo: any) => {
return convertToFile(fileInfo);
});
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
baseInfoLoading.value = false;
}
}
//
const executeHistoryList = ref<ExecuteHistoryItem[]>([]);
async function initExecuteHistory(data: MinderJsonNodeData) {
try {
executeHistoryList.value = await executeHistory({
caseId: data?.caseId,
id: data.id,
testPlanId: props.planId,
});
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
/**
* 切换用例详情显示
*/
async function toggleDetail(val?: boolean) {
extraVisible.value = val !== undefined ? val : !extraVisible.value;
const node: MinderJsonNode = window.minder.getSelectedNode();
const { data } = node;
if (extraVisible.value && data?.resource?.includes(caseTag)) {
activeExtraKey.value = 'history';
initExecuteHistory(data);
initCaseDetail(data);
}
}
const hasOperationPermission = hasAnyPermission([
'PROJECT_TEST_PLAN:READ+UPDATE',
'PROJECT_TEST_PLAN:READ+ASSOCIATION',
]);
const canShowFloatMenu = ref(false); //
const canShowMoreMenu = ref(false); //
const canShowEnterNode = ref(false);
const showAssociateBugMenu = ref(false);
const canShowDetail = ref(false);
const moreMenuOtherOperationList = ref();
function setMoreMenuOtherOperationList(node: MinderJsonNode) {
moreMenuOtherOperationList.value = [
{
value: 'changeExecutor',
label: t('testPlan.featureCase.changeExecutor'),
permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
onClick: () => {
emit('operation', 'changeExecutor', node);
},
},
{
value: 'disassociate',
label: t('caseManagement.caseReview.disassociateCase'),
permission: ['PROJECT_TEST_PLAN:READ+ASSOCIATION'],
onClick: () => {
emit('operation', 'disassociate', node);
},
},
];
}
const selectNode = ref();
async function handleNodeSelect(node: MinderJsonNode) {
const { data } = node;
//
if (data?.type === 'tmp' && node.parent?.data?.resource?.includes(moduleTag)) {
canShowFloatMenu.value = false;
await initNodeCases(node.parent, data.current);
setPriorityView(true, 'P');
return;
}
selectNode.value = node;
// :
if (
node.data?.resource?.includes(caseTag) ||
(node.data?.resource?.includes(moduleTag) &&
(node.children || []).length > 0 &&
!(!hasOperationPermission && node.type === 'root'))
) {
canShowFloatMenu.value = true;
setMoreMenuOtherOperationList(node);
} else {
canShowFloatMenu.value = false;
}
//
if (node.data?.resource?.includes(caseTag) && !hasOperationPermission) {
canShowMoreMenu.value = false;
} else {
canShowMoreMenu.value = true;
}
// :
if (data?.resource?.includes(moduleTag) && (node.children || []).length > 0 && node.type !== 'root') {
canShowEnterNode.value = true;
} else {
canShowEnterNode.value = false;
}
if (data?.resource?.includes(caseTag)) {
canShowDetail.value = true;
showAssociateBugMenu.value = true;
if (extraVisible.value) {
toggleDetail(true);
}
//
expendNodeAndChildren(node);
} else if (data?.resource?.includes(moduleTag) && data.count > 0 && data.isLoaded !== true) {
//
if (data.id !== 'NONE') {
await initNodeCases(node);
}
extraVisible.value = false;
canShowDetail.value = false;
showAssociateBugMenu.value = false;
} else {
extraVisible.value = false;
canShowDetail.value = false;
showAssociateBugMenu.value = false;
resetExtractInfo();
removeFakeNode(node, 'fakeNode');
}
setPriorityView(true, 'P');
}
function handleNodeUnselect() {
extraVisible.value = false;
}
defineExpose({
initCaseTree,
});
</script>
<style lang="less" scoped>
:deep(.comment-list-item-name) {
max-width: 200px;
}
:deep(.ms-list) {
margin: 0;
}
</style>

View File

@ -1,6 +1,6 @@
import type { MinderNodePosition } from '@/store/modules/components/minder-editor/types'; import type { MinderJsonNode, MinderJsonNodeData } from '@/components/pure/ms-minder-editor/props';
import type { MinderJsonNode } from '../../props'; import type { MinderNodePosition } from '@/store/modules/components/minder-editor/types';
export function isDisableNode(minder: any) { export function isDisableNode(minder: any) {
let node: MinderJsonNode; let node: MinderJsonNode;
@ -182,3 +182,47 @@ export function expendNodeAndChildren(node: MinderJsonNode) {
node.children?.forEach((child) => expendNodeAndChildren(child)); node.children?.forEach((child) => expendNodeAndChildren(child));
} }
} }
/**
*
* @param node
* @param fakeNodeName
*/
export function removeFakeNode(node: MinderJsonNode, fakeNodeName: string) {
const fakeNode = node.children?.find((e: MinderJsonNode) => e.data?.id === fakeNodeName);
if (fakeNode) {
window.minder.removeNode(fakeNode);
}
}
/**
*
* @param data
* @param parentNode
*/
export function createNode(data?: MinderJsonNodeData, parentNode?: MinderJsonNode) {
return window.minder.createNode(
{
...data,
expandState: 'collapse',
disabled: true,
},
parentNode
);
}
/**
*
* @param parentNode -
* @param children -
*/
export function renderSubNodes(parentNode: MinderJsonNode, children?: MinderJsonNode[]) {
return (
children?.map((item: MinderJsonNode) => {
const grandChild = createNode(item.data, parentNode);
const greatGrandChildren = renderSubNodes(grandChild, item.children);
window.minder.renderNodeBatch(greatGrandChildren);
return grandChild;
}) || []
);
}

View File

@ -21,6 +21,7 @@ export enum MinderKeyEnum {
FEATURE_CASE_MINDER = 'featureCaseMinder', FEATURE_CASE_MINDER = 'featureCaseMinder',
CASE_REVIEW_MINDER = 'caseReviewMinder', CASE_REVIEW_MINDER = 'caseReviewMinder',
TEST_PLAN_MINDER = 'testPlanMinder', TEST_PLAN_MINDER = 'testPlanMinder',
TEST_PLAN_FEATURE_CASE_MINDER = 'testPlanFeatureCaseMinder',
} }
export enum ModeIcon { export enum ModeIcon {

View File

@ -273,3 +273,11 @@ export interface CaseReviewMinderParams {
viewFlag: boolean; // 是否只看我的 viewFlag: boolean; // 是否只看我的
viewStatusFlag: boolean; // 我的评审结果 viewStatusFlag: boolean; // 我的评审结果
} }
// 测试计划用例脑图
export interface CasePlanMinderParams {
projectId: string;
moduleId: string;
current?: number;
planId: string;
}

View File

@ -0,0 +1,53 @@
import { defineStore } from 'pinia';
import { getFeatureCaseModule, getFeatureCaseModuleCount } from '@/api/modules/test-plan/testPlan';
import { mapTree } from '@/utils';
import { ModuleTreeNode } from '@/models/common';
import type { PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
const useTestPlanFeatureCaseStore = defineStore('testPlanFeatureCase', {
state: (): {
modulesCount: Record<string, any>; // 用例树模块数量
moduleTree: ModuleTreeNode[]; // 用例树
loading: boolean;
} => ({
modulesCount: {},
moduleTree: [],
loading: false,
}),
actions: {
// 初始化模块树
async initModules(id: string, treeType: 'MODULE' | 'COLLECTION') {
try {
this.loading = true;
const res = await getFeatureCaseModule({ testPlanId: id, treeType });
this.moduleTree = mapTree<ModuleTreeNode>(res, (node) => {
return {
...node,
count: this.modulesCount?.[node.id] || 0,
};
});
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
this.loading = false;
}
},
// 设置模块树
setModulesTree(tree: ModuleTreeNode[]) {
this.moduleTree = tree;
},
async getModuleCount(params: PlanDetailFeatureCaseListQueryParams) {
try {
this.modulesCount = await getFeatureCaseModuleCount(params);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
},
});
export default useTestPlanFeatureCaseStore;

View File

@ -64,7 +64,7 @@
</span> </span>
<span <span
v-else v-if="!item.deleted && !props.notShowReviewName"
class="one-line-text ml-[16px] max-w-[300px] cursor-pointer break-words break-all text-[rgb(var(--primary-5))]" class="one-line-text ml-[16px] max-w-[300px] cursor-pointer break-words break-all text-[rgb(var(--primary-5))]"
@click="toPlan(item)" @click="toPlan(item)"
> >

View File

@ -1,90 +1,122 @@
<template> <template>
<div class="p-[16px]"> <div class="h-full p-[16px]">
<MsAdvanceFilter <div class="mb-[16px]">
v-model:keyword="keyword" <MsAdvanceFilter
:filter-config-list="[]" v-model:keyword="keyword"
:custom-fields-config-list="[]" :filter-config-list="[]"
:row-count="0" :custom-fields-config-list="[]"
:count="props.modulesCount[props.activeModule] || 0" :row-count="0"
:name="moduleNamePath" :count="modulesCount[props.activeModule] || 0"
:search-placeholder="t('ms.case.associate.searchPlaceholder')" :name="moduleNamePath"
@keyword-search="loadCaseList()" :not-show-input-search="showType !== 'list'"
@adv-search="loadCaseList()" :search-placeholder="t('ms.case.associate.searchPlaceholder')"
@refresh="loadCaseList()" @keyword-search="loadCaseList()"
/> @adv-search="loadCaseList()"
<MsBaseTable @refresh="handleRefreshAndInitModules()"
ref="tableRef" >
class="mt-[16px]" <template v-if="props.treeType === 'MODULE'" #right>
v-bind="propsRes" <a-radio-group
:action-config="batchActions" v-model:model-value="showType"
:selectable="hasOperationPermission" type="button"
v-on="propsEvent" size="small"
@batch-action="handleTableBatch" class="list-show-type"
@drag-change="handleDragChange" @change="handleShowTypeChange"
@selected-change="handleTableSelect" >
@filter-change="getModuleCount" <a-radio value="list" class="show-type-icon !m-[2px]">
@module-change="loadCaseList(false)" <MsIcon :size="14" type="icon-icon_view-list_outlined" />
> </a-radio>
<template #num="{ record }"> <a-radio value="minder" class="show-type-icon !m-[2px]">
<MsButton type="text" @click="toCaseDetail(record)">{{ record.num }}</MsButton> <MsIcon :size="14" type="icon-icon_mindnote_outlined" />
</template> </a-radio>
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL]="{ filterContent }"> </a-radio-group>
<CaseLevel :case-level="filterContent.value" /> </template>
</template> </MsAdvanceFilter>
<template #caseLevel="{ record }"> </div>
<CaseLevel :case-level="record.caseLevel" /> <template v-if="showType === 'list'">
</template> <MsBaseTable
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_EXECUTE_RESULT]="{ filterContent }"> ref="tableRef"
<ExecuteResult :execute-result="filterContent.key" /> v-bind="propsRes"
</template> :action-config="batchActions"
<template #lastExecResult="{ record }"> :selectable="hasOperationPermission"
<a-select v-on="propsEvent"
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE']) && props.canEdit" @batch-action="handleTableBatch"
v-model:model-value="record.lastExecResult" @drag-change="handleDragChange"
:placeholder="t('common.pleaseSelect')" @selected-change="handleTableSelect"
class="param-input w-full" @filter-change="getModuleCount"
@change="() => handleEditLastExecResult(record)" @module-change="loadCaseList(false)"
> >
<template #label> <template #num="{ record }">
<span class="text-[var(--color-text-2)]"><ExecuteResult :execute-result="record.lastExecResult" /></span> <MsButton type="text" @click="toCaseDetail(record)">{{ record.num }}</MsButton>
</template> </template>
<a-option v-for="item in Object.values(executionResultMap)" :key="item.key" :value="item.key"> <template #[FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL]="{ filterContent }">
<ExecuteResult :execute-result="item.key" /> <CaseLevel :case-level="filterContent.value" />
</a-option> </template>
</a-select> <template #caseLevel="{ record }">
<span v-else class="text-[var(--color-text-2)]"><ExecuteResult :execute-result="record.lastExecResult" /></span> <CaseLevel :case-level="record.caseLevel" />
</template> </template>
<template #bugCount="{ record }"> <template #[FilterSlotNameEnum.CASE_MANAGEMENT_EXECUTE_RESULT]="{ filterContent }">
<BugCountPopover :case-item="record" :can-edit="props.canEdit" @load-list="loadList" /> <ExecuteResult :execute-result="filterContent.key" />
</template> </template>
<template v-if="props.canEdit" #operation="{ record }"> <template #lastExecResult="{ record }">
<MsButton <a-select
v-permission="['PROJECT_TEST_PLAN:READ+EXECUTE']" v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE']) && props.canEdit"
type="text" v-model:model-value="record.lastExecResult"
class="!mr-0" :placeholder="t('common.pleaseSelect')"
@click="toCaseDetail(record)" class="param-input w-full"
> @change="() => handleEditLastExecResult(record)"
{{ t('common.execute') }} >
</MsButton> <template #label>
<a-divider <span class="text-[var(--color-text-2)]"><ExecuteResult :execute-result="record.lastExecResult" /></span>
v-if="hasAllPermission(['PROJECT_TEST_PLAN:READ+EXECUTE', 'PROJECT_TEST_PLAN:READ+ASSOCIATION'])" </template>
direction="vertical" <a-option v-for="item in Object.values(executionResultMap)" :key="item.key" :value="item.key">
:margin="8" <ExecuteResult :execute-result="item.key" />
></a-divider> </a-option>
<MsPopconfirm </a-select>
:title="t('testPlan.featureCase.disassociateTip', { name: characterLimit(record.name) })" <span v-else class="text-[var(--color-text-2)]">
:sub-title-tip="t('testPlan.featureCase.disassociateTipContent')" <ExecuteResult :execute-result="record.lastExecResult" />
:ok-text="t('common.confirm')" </span>
:loading="disassociateLoading" </template>
type="error" <template #bugCount="{ record }">
@confirm="(val, done) => handleDisassociateCase(record, done)" <BugCountPopover :case-item="record" :can-edit="props.canEdit" @load-list="loadList" />
> </template>
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']" type="text" class="!mr-0"> <template v-if="props.canEdit" #operation="{ record }">
{{ t('common.cancelLink') }} <MsButton
v-permission="['PROJECT_TEST_PLAN:READ+EXECUTE']"
type="text"
class="!mr-0"
@click="toCaseDetail(record)"
>
{{ t('common.execute') }}
</MsButton> </MsButton>
</MsPopconfirm> <a-divider
</template> v-if="hasAllPermission(['PROJECT_TEST_PLAN:READ+EXECUTE', 'PROJECT_TEST_PLAN:READ+ASSOCIATION'])"
</MsBaseTable> direction="vertical"
:margin="8"
></a-divider>
<MsPopconfirm
:title="t('testPlan.featureCase.disassociateTip', { name: characterLimit(record.name) })"
:sub-title-tip="t('testPlan.featureCase.disassociateTipContent')"
:ok-text="t('common.confirm')"
:loading="disassociateLoading"
type="error"
@confirm="(val, done) => handleDisassociateCase(record, done)"
>
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']" type="text" class="!mr-0">
{{ t('common.cancelLink') }}
</MsButton>
</MsPopconfirm>
</template>
</MsBaseTable>
</template>
<!-- 脑图 -->
<div v-else class="h-[calc(100%-48px)] border-t border-[var(--color-text-n8)]">
<MsTestPlanFeatureCaseMinder
ref="msTestPlanFeatureCaseMinderRef"
:active-module="props.activeModule"
:module-tree="moduleTree"
:plan-id="props.planId"
/>
</div>
<!-- 批量执行 --> <!-- 批量执行 -->
<a-modal <a-modal
v-model:visible="batchExecuteModalVisible" v-model:visible="batchExecuteModalVisible"
@ -120,7 +152,7 @@
<!-- 批量移动 --> <!-- 批量移动 -->
<BatchApiMoveModal <BatchApiMoveModal
v-model:visible="batchMoveModalVisible" v-model:visible="batchMoveModalVisible"
:module-tree="props.moduleTree" :module-tree="moduleTree"
:count="batchParams.currentSelectCount || tableSelected.length" :count="batchParams.currentSelectCount || tableSelected.length"
:params="batchUpdateParams" :params="batchUpdateParams"
:batch-move="batchMoveFeatureCase" :batch-move="batchMoveFeatureCase"
@ -147,6 +179,7 @@
import useTable from '@/components/pure/ms-table/useTable'; import useTable from '@/components/pure/ms-table/useTable';
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue'; import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue'; import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
import MsTestPlanFeatureCaseMinder from '@/components/business/ms-minders/testPlanFeatureCaseMinder/index.vue';
import BugCountPopover from './bugCountPopover.vue'; import BugCountPopover from './bugCountPopover.vue';
import BatchApiMoveModal from '@/views/test-plan/testPlan/components/batchApiMoveModal.vue'; import BatchApiMoveModal from '@/views/test-plan/testPlan/components/batchApiMoveModal.vue';
import BatchUpdateExecutorModal from '@/views/test-plan/testPlan/components/batchUpdateExecutorModal.vue'; import BatchUpdateExecutorModal from '@/views/test-plan/testPlan/components/batchUpdateExecutorModal.vue';
@ -167,15 +200,12 @@
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import useTableStore from '@/hooks/useTableStore'; import useTableStore from '@/hooks/useTableStore';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import useTestPlanFeatureCaseStore from '@/store/modules/testPlan/testPlanFeatureCase';
import { characterLimit } from '@/utils'; import { characterLimit } from '@/utils';
import { hasAllPermission, hasAnyPermission } from '@/utils/permission'; import { hasAllPermission, hasAnyPermission } from '@/utils/permission';
import { DragSortParams, ModuleTreeNode } from '@/models/common'; import { DragSortParams } from '@/models/common';
import type { import type { ExecuteFeatureCaseFormParams, PlanDetailFeatureCaseItem } from '@/models/testPlan/testPlan';
ExecuteFeatureCaseFormParams,
PlanDetailFeatureCaseItem,
PlanDetailFeatureCaseListQueryParams,
} from '@/models/testPlan/testPlan';
import { LastExecuteResults } from '@/enums/caseEnum'; import { LastExecuteResults } from '@/enums/caseEnum';
import { TestPlanRouteEnum } from '@/enums/routeEnum'; import { TestPlanRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
@ -185,21 +215,17 @@
import { executionResultMap, getCaseLevels } from '@/views/case-management/caseManagementFeature/components/utils'; import { executionResultMap, getCaseLevels } from '@/views/case-management/caseManagementFeature/components/utils';
const props = defineProps<{ const props = defineProps<{
modulesCount: Record<string, number>; //
moduleName: string; moduleName: string;
moduleParentId: string; moduleParentId: string;
activeModule: string; activeModule: string;
offspringIds: string[]; offspringIds: string[];
planId: string; planId: string;
moduleTree: ModuleTreeNode[];
canEdit: boolean; canEdit: boolean;
treeType: 'MODULE' | 'COLLECTION'; treeType: 'MODULE' | 'COLLECTION';
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'getModuleCount', params: PlanDetailFeatureCaseListQueryParams): void;
(e: 'refresh'): void; (e: 'refresh'): void;
(e: 'initModules'): void;
}>(); }>();
const { t } = useI18n(); const { t } = useI18n();
@ -208,7 +234,14 @@
const appStore = useAppStore(); const appStore = useAppStore();
const tableStore = useTableStore(); const tableStore = useTableStore();
const { openModal } = useModal(); const { openModal } = useModal();
const testPlanFeatureCaseStore = useTestPlanFeatureCaseStore();
const moduleTree = computed(() => unref(testPlanFeatureCaseStore.moduleTree));
async function initModules() {
await testPlanFeatureCaseStore.initModules(route.query.id as string, props.treeType);
}
const showType = ref<'list' | 'minder'>('list');
const keyword = ref(''); const keyword = ref('');
const moduleNamePath = computed(() => { const moduleNamePath = computed(() => {
return props.activeModule === 'all' ? t('caseManagement.featureCase.allCase') : props.moduleName; return props.activeModule === 'all' ? t('caseManagement.featureCase.allCase') : props.moduleName;
@ -430,9 +463,10 @@
...tableParams, ...tableParams,
projectId: props.activeModule !== 'all' && props.treeType === 'MODULE' ? props.moduleParentId : '', projectId: props.activeModule !== 'all' && props.treeType === 'MODULE' ? props.moduleParentId : '',
}); });
resetSelector();
loadList(); loadList();
if (refreshTreeCount) { if (refreshTreeCount) {
emit('getModuleCount', { testPlanFeatureCaseStore.getModuleCount({
...tableParams, ...tableParams,
current: propsRes.value.msPagination?.current, current: propsRes.value.msPagination?.current,
pageSize: propsRes.value.msPagination?.pageSize, pageSize: propsRes.value.msPagination?.pageSize,
@ -446,13 +480,62 @@
} }
); );
onBeforeMount(() => {
loadCaseList();
});
const modulesCount = computed(() => testPlanFeatureCaseStore.modulesCount);
async function getModuleCount() { async function getModuleCount() {
let params;
const tableParams = await getTableParams(false); const tableParams = await getTableParams(false);
emit('getModuleCount', { if (showType.value === 'list') {
...tableParams, params = {
current: propsRes.value.msPagination?.current, ...tableParams,
pageSize: propsRes.value.msPagination?.pageSize, current: propsRes.value.msPagination?.current,
}); pageSize: propsRes.value.msPagination?.pageSize,
};
} else {
params = { treeType: props.treeType, moduleIds: [], testPlanId: props.planId, pageSize: 10, current: 1 };
}
await testPlanFeatureCaseStore.getModuleCount(params);
}
/**
* 更新数据
* @param getCount 获取模块树数量
*/
const msTestPlanFeatureCaseMinderRef = ref<InstanceType<typeof MsTestPlanFeatureCaseMinder>>();
async function refresh(getCount = true) {
if (showType.value === 'list') {
loadCaseList(getCount);
} else {
if (getCount) {
await getModuleCount();
}
msTestPlanFeatureCaseMinderRef.value?.initCaseTree();
}
}
async function handleRefreshAndInitModules() {
await initModules();
refresh();
}
function handleTreeTypeChange() {
if (showType.value !== 'list') {
showType.value = 'list';
}
loadCaseList(true);
}
function handleShowTypeChange(val: string | number | boolean) {
if (val === 'minder') {
keyword.value = '';
//
getModuleCount();
} else {
loadCaseList();
}
} }
const tableSelected = ref<(string | number)[]>([]); // const tableSelected = ref<(string | number)[]>([]); //
@ -519,7 +602,7 @@
} }
Message.success(t('common.unLinkSuccess')); Message.success(t('common.unLinkSuccess'));
resetCaseList(); resetCaseList();
emit('initModules'); initModules();
emit('refresh'); emit('refresh');
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -550,7 +633,7 @@
}); });
Message.success(t('common.updateSuccess')); Message.success(t('common.updateSuccess'));
resetCaseList(); resetCaseList();
emit('initModules'); initModules();
emit('refresh'); emit('refresh');
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -643,13 +726,9 @@
}); });
} }
onBeforeMount(() => {
loadCaseList();
});
defineExpose({ defineExpose({
resetSelector, resetSelector,
loadCaseList, handleTreeTypeChange,
}); });
await tableStore.initColumn(TableKeyEnum.TEST_PLAN_DETAIL_FEATURE_CASE_TABLE, columns.value, 'drawer', true); await tableStore.initColumn(TableKeyEnum.TEST_PLAN_DETAIL_FEATURE_CASE_TABLE, columns.value, 'drawer', true);
@ -670,4 +749,10 @@
} }
} }
} }
.list-show-type {
padding: 0;
:deep(.arco-radio-button-content) {
padding: 4px 6px;
}
}
</style> </style>

View File

@ -57,8 +57,8 @@
import MsTree from '@/components/business/ms-tree/index.vue'; import MsTree from '@/components/business/ms-tree/index.vue';
import type { MsTreeNodeData } from '@/components/business/ms-tree/types'; import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import { getFeatureCaseModule } from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useTestPlanFeatureCaseStore from '@/store/modules/testPlan/testPlanFeatureCase';
import { mapTree } from '@/utils'; import { mapTree } from '@/utils';
import { getNodeParentId } from '@/utils/tree'; import { getNodeParentId } from '@/utils/tree';
@ -76,6 +76,7 @@
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const testPlanFeatureCaseStore = useTestPlanFeatureCaseStore();
const virtualListProps = computed(() => { const virtualListProps = computed(() => {
return { return {
@ -109,8 +110,8 @@
} }
const moduleKeyword = ref(''); const moduleKeyword = ref('');
const folderTree = ref<ModuleTreeNode[]>([]); const folderTree = computed(() => testPlanFeatureCaseStore.moduleTree);
const loading = ref(false); const loading = computed(() => testPlanFeatureCaseStore.loading);
const selectedKeys = useVModel(props, 'selectedKeys', emit); const selectedKeys = useVModel(props, 'selectedKeys', emit);
@ -118,22 +119,7 @@
* 初始化模块树 * 初始化模块树
*/ */
async function initModules() { async function initModules() {
try { await testPlanFeatureCaseStore.initModules(route.query.id as string, props.treeType);
loading.value = true;
const res = await getFeatureCaseModule({ testPlanId: route.query.id as string, treeType: props.treeType });
folderTree.value = mapTree<ModuleTreeNode>(res, (node) => {
return {
...node,
count: props.modulesCount?.[node.id] || 0,
};
});
emit('init', folderTree.value);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
loading.value = false;
}
} }
/** /**
@ -160,12 +146,13 @@
watch( watch(
() => props.modulesCount, () => props.modulesCount,
(obj) => { (obj) => {
folderTree.value = mapTree<ModuleTreeNode>(folderTree.value, (node) => { const tree = mapTree<ModuleTreeNode>(folderTree.value, (node) => {
return { return {
...node, ...node,
count: obj?.[node.id] || 0, count: obj?.[node.id] || 0,
}; };
}); });
testPlanFeatureCaseStore.setModulesTree(tree);
allCount.value = obj?.all || 0; allCount.value = obj?.all || 0;
} }
); );

View File

@ -8,7 +8,6 @@
:modules-count="modulesCount" :modules-count="modulesCount"
:selected-keys="selectedKeys" :selected-keys="selectedKeys"
@folder-node-select="handleFolderNodeSelect" @folder-node-select="handleFolderNodeSelect"
@init="initModuleTree"
/> />
</div> </div>
</template> </template>
@ -22,11 +21,8 @@
:module-parent-id="moduleParentId" :module-parent-id="moduleParentId"
:active-module="activeFolderId" :active-module="activeFolderId"
:offspring-ids="offspringIds" :offspring-ids="offspringIds"
:module-tree="moduleTree"
:can-edit="props.canEdit" :can-edit="props.canEdit"
@get-module-count="getModuleCount"
@refresh="emit('refresh')" @refresh="emit('refresh')"
@init-modules="initModules"
></CaseTable> ></CaseTable>
</template> </template>
</MsSplitBox> </MsSplitBox>
@ -40,10 +36,7 @@
import CaseTable from './components/caseTable.vue'; import CaseTable from './components/caseTable.vue';
import CaseTree from './components/caseTree.vue'; import CaseTree from './components/caseTree.vue';
import { getFeatureCaseModuleCount } from '@/api/modules/test-plan/testPlan'; import useTestPlanFeatureCaseStore from '@/store/modules/testPlan/testPlanFeatureCase';
import { ModuleTreeNode } from '@/models/common';
import type { PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
const props = defineProps<{ const props = defineProps<{
canEdit: boolean; canEdit: boolean;
@ -55,17 +48,10 @@
}>(); }>();
const route = useRoute(); const route = useRoute();
const testPlanFeatureCaseStore = useTestPlanFeatureCaseStore();
const planId = ref(route.query.id as string); const planId = ref(route.query.id as string);
const modulesCount = ref<Record<string, any>>({}); const modulesCount = computed(() => testPlanFeatureCaseStore.modulesCount);
async function getModuleCount(params: PlanDetailFeatureCaseListQueryParams) {
try {
modulesCount.value = await getFeatureCaseModuleCount(params);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
const caseTableRef = ref<InstanceType<typeof CaseTable>>(); const caseTableRef = ref<InstanceType<typeof CaseTable>>();
const activeFolderId = ref<string>('all'); const activeFolderId = ref<string>('all');
@ -84,23 +70,15 @@
caseTableRef.value?.resetSelector(); caseTableRef.value?.resetSelector();
} }
const moduleTree = ref<ModuleTreeNode[]>([]);
function initModuleTree(tree: ModuleTreeNode[]) {
moduleTree.value = unref(tree);
}
const caseTreeRef = ref<InstanceType<typeof CaseTree>>(); const caseTreeRef = ref<InstanceType<typeof CaseTree>>();
function initModules() {
caseTreeRef.value?.initModules();
}
function getCaseTableList() { function getCaseTableList() {
nextTick(() => { nextTick(async () => {
initModules(); await caseTreeRef.value?.initModules();
if (activeFolderId.value !== 'all') { if (activeFolderId.value !== 'all') {
caseTreeRef.value?.setActiveFolder('all'); caseTreeRef.value?.setActiveFolder('all');
} else { } else {
caseTableRef.value?.loadCaseList(); caseTableRef.value?.handleTreeTypeChange();
} }
}); });
} }