feat(测试计划): 脑图执行用例-查看
This commit is contained in:
parent
b2f6d322a5
commit
dba7e10a0d
|
@ -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}` });
|
||||||
|
|
|
@ -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'; // 获取测试计划用例脑图
|
||||||
|
|
|
@ -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 选中节点
|
||||||
|
|
|
@ -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,
|
|
||||||
query: {
|
|
||||||
id,
|
id,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
@ -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;
|
||||||
|
}) || []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
|
@ -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)"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,20 +1,40 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="p-[16px]">
|
<div class="h-full p-[16px]">
|
||||||
|
<div class="mb-[16px]">
|
||||||
<MsAdvanceFilter
|
<MsAdvanceFilter
|
||||||
v-model:keyword="keyword"
|
v-model:keyword="keyword"
|
||||||
:filter-config-list="[]"
|
:filter-config-list="[]"
|
||||||
:custom-fields-config-list="[]"
|
:custom-fields-config-list="[]"
|
||||||
:row-count="0"
|
:row-count="0"
|
||||||
:count="props.modulesCount[props.activeModule] || 0"
|
:count="modulesCount[props.activeModule] || 0"
|
||||||
:name="moduleNamePath"
|
:name="moduleNamePath"
|
||||||
|
:not-show-input-search="showType !== 'list'"
|
||||||
:search-placeholder="t('ms.case.associate.searchPlaceholder')"
|
:search-placeholder="t('ms.case.associate.searchPlaceholder')"
|
||||||
@keyword-search="loadCaseList()"
|
@keyword-search="loadCaseList()"
|
||||||
@adv-search="loadCaseList()"
|
@adv-search="loadCaseList()"
|
||||||
@refresh="loadCaseList()"
|
@refresh="handleRefreshAndInitModules()"
|
||||||
/>
|
>
|
||||||
|
<template v-if="props.treeType === 'MODULE'" #right>
|
||||||
|
<a-radio-group
|
||||||
|
v-model:model-value="showType"
|
||||||
|
type="button"
|
||||||
|
size="small"
|
||||||
|
class="list-show-type"
|
||||||
|
@change="handleShowTypeChange"
|
||||||
|
>
|
||||||
|
<a-radio value="list" class="show-type-icon !m-[2px]">
|
||||||
|
<MsIcon :size="14" type="icon-icon_view-list_outlined" />
|
||||||
|
</a-radio>
|
||||||
|
<a-radio value="minder" class="show-type-icon !m-[2px]">
|
||||||
|
<MsIcon :size="14" type="icon-icon_mindnote_outlined" />
|
||||||
|
</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</template>
|
||||||
|
</MsAdvanceFilter>
|
||||||
|
</div>
|
||||||
|
<template v-if="showType === 'list'">
|
||||||
<MsBaseTable
|
<MsBaseTable
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
class="mt-[16px]"
|
|
||||||
v-bind="propsRes"
|
v-bind="propsRes"
|
||||||
:action-config="batchActions"
|
:action-config="batchActions"
|
||||||
:selectable="hasOperationPermission"
|
:selectable="hasOperationPermission"
|
||||||
|
@ -52,7 +72,9 @@
|
||||||
<ExecuteResult :execute-result="item.key" />
|
<ExecuteResult :execute-result="item.key" />
|
||||||
</a-option>
|
</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<span v-else class="text-[var(--color-text-2)]"><ExecuteResult :execute-result="record.lastExecResult" /></span>
|
<span v-else class="text-[var(--color-text-2)]">
|
||||||
|
<ExecuteResult :execute-result="record.lastExecResult" />
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template #bugCount="{ record }">
|
<template #bugCount="{ record }">
|
||||||
<BugCountPopover :case-item="record" :can-edit="props.canEdit" @load-list="loadList" />
|
<BugCountPopover :case-item="record" :can-edit="props.canEdit" @load-list="loadList" />
|
||||||
|
@ -85,6 +107,16 @@
|
||||||
</MsPopconfirm>
|
</MsPopconfirm>
|
||||||
</template>
|
</template>
|
||||||
</MsBaseTable>
|
</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') {
|
||||||
|
params = {
|
||||||
...tableParams,
|
...tableParams,
|
||||||
current: propsRes.value.msPagination?.current,
|
current: propsRes.value.msPagination?.current,
|
||||||
pageSize: propsRes.value.msPagination?.pageSize,
|
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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue