feat(测试计划): 测试计划联调执行历史&补充权限&用例详情缺陷新建和关联&调整bug细节问题
This commit is contained in:
parent
d96d4fc8ac
commit
a66ba037f5
|
@ -8,6 +8,7 @@ import {
|
||||||
batchCopyPlanUrl,
|
batchCopyPlanUrl,
|
||||||
batchDeletePlanUrl,
|
batchDeletePlanUrl,
|
||||||
BatchDisassociateCaseUrl,
|
BatchDisassociateCaseUrl,
|
||||||
|
BatchEditTestPlanUrl,
|
||||||
batchMovePlanUrl,
|
batchMovePlanUrl,
|
||||||
BatchRunCaseUrl,
|
BatchRunCaseUrl,
|
||||||
BatchUpdateCaseExecutorUrl,
|
BatchUpdateCaseExecutorUrl,
|
||||||
|
@ -16,6 +17,7 @@ import {
|
||||||
DeleteTestPlanModuleUrl,
|
DeleteTestPlanModuleUrl,
|
||||||
DisassociateCaseUrl,
|
DisassociateCaseUrl,
|
||||||
EditCaseLastExecResultUrl,
|
EditCaseLastExecResultUrl,
|
||||||
|
ExecuteHistoryUrl,
|
||||||
followPlanUrl,
|
followPlanUrl,
|
||||||
GenerateReportUrl,
|
GenerateReportUrl,
|
||||||
GetAssociatedBugUrl,
|
GetAssociatedBugUrl,
|
||||||
|
@ -52,6 +54,8 @@ import type {
|
||||||
BatchUpdateCaseExecutorParams,
|
BatchUpdateCaseExecutorParams,
|
||||||
DisassociateCaseParams,
|
DisassociateCaseParams,
|
||||||
EditLastExecResultParams,
|
EditLastExecResultParams,
|
||||||
|
ExecuteHistoryItem,
|
||||||
|
ExecuteHistoryType,
|
||||||
FollowPlanParams,
|
FollowPlanParams,
|
||||||
PassRateCountDetail,
|
PassRateCountDetail,
|
||||||
PlanDetailBugItem,
|
PlanDetailBugItem,
|
||||||
|
@ -85,6 +89,11 @@ export function moveTestPlanModuleTree(data: MoveModules) {
|
||||||
return MSR.post({ url: MoveTestPlanModuleUrl, data });
|
return MSR.post({ url: MoveTestPlanModuleUrl, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量编辑测试计划
|
||||||
|
export function batchEditTestPlan(data: TableQueryParams) {
|
||||||
|
return MSR.post({ url: BatchEditTestPlanUrl, data });
|
||||||
|
}
|
||||||
|
|
||||||
// 删除模块
|
// 删除模块
|
||||||
export function deletePlanModuleTree(id: string) {
|
export function deletePlanModuleTree(id: string) {
|
||||||
return MSR.get({ url: `${DeleteTestPlanModuleUrl}/${id}` });
|
return MSR.get({ url: `${DeleteTestPlanModuleUrl}/${id}` });
|
||||||
|
@ -226,3 +235,7 @@ export function associateBugToPlan(data: TableQueryParams) {
|
||||||
export function testPlanCancelBug(id: string) {
|
export function testPlanCancelBug(id: string) {
|
||||||
return MSR.get({ url: `${TestPlanCancelBugUrl}/${id}` });
|
return MSR.get({ url: `${TestPlanCancelBugUrl}/${id}` });
|
||||||
}
|
}
|
||||||
|
// 测试计划-用例详情-执行历史
|
||||||
|
export function executeHistory(data: ExecuteHistoryType) {
|
||||||
|
return MSR.post<ExecuteHistoryItem[]>({ url: ExecuteHistoryUrl, data });
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ export const UpdateTestPlanUrl = '/test-plan/update';
|
||||||
export const batchDeletePlanUrl = '/test-plan/batch-delete';
|
export const batchDeletePlanUrl = '/test-plan/batch-delete';
|
||||||
// 删除测试计划
|
// 删除测试计划
|
||||||
export const deletePlanUrl = '/test-plan/delete';
|
export const deletePlanUrl = '/test-plan/delete';
|
||||||
|
// 测试计划批量编辑
|
||||||
|
export const BatchEditTestPlanUrl = '/test-plan/batch-edit';
|
||||||
// 获取统计数量
|
// 获取统计数量
|
||||||
export const getStatisticalCountUrl = '/test-plan/getCount';
|
export const getStatisticalCountUrl = '/test-plan/getCount';
|
||||||
// 归档
|
// 归档
|
||||||
|
@ -29,7 +31,7 @@ export const archivedPlanUrl = '/test-plan/archived';
|
||||||
// 批量复制
|
// 批量复制
|
||||||
export const batchCopyPlanUrl = '/test-plan/batch-copy';
|
export const batchCopyPlanUrl = '/test-plan/batch-copy';
|
||||||
// 批量移动
|
// 批量移动
|
||||||
export const batchMovePlanUrl = '/test-plan/batch/move';
|
export const batchMovePlanUrl = '/test-plan/batch-move';
|
||||||
// 批量归档
|
// 批量归档
|
||||||
export const batchArchivedPlanUrl = '/test-plan/batch-archived';
|
export const batchArchivedPlanUrl = '/test-plan/batch-archived';
|
||||||
// 计划详情缺陷管理列表
|
// 计划详情缺陷管理列表
|
||||||
|
@ -74,3 +76,5 @@ export const BatchRunCaseUrl = '/test-plan/functional/case/batch/run';
|
||||||
export const GetTestPlanUsersUrl = '/test-plan/functional/case/user-option';
|
export const GetTestPlanUsersUrl = '/test-plan/functional/case/user-option';
|
||||||
// 计划详情-功能用例-批量更新执行人
|
// 计划详情-功能用例-批量更新执行人
|
||||||
export const BatchUpdateCaseExecutorUrl = '/test-plan/functional/case/batch/update/executor';
|
export const BatchUpdateCaseExecutorUrl = '/test-plan/functional/case/batch/update/executor';
|
||||||
|
// 计划详情-功能用例-执行历史
|
||||||
|
export const ExecuteHistoryUrl = '/test-plan/functional/case/exec/history';
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
}"
|
}"
|
||||||
:expand-all="isExpandAll"
|
:expand-all="isExpandAll"
|
||||||
block-node
|
block-node
|
||||||
title-tooltip-position="left"
|
title-tooltip-position="top"
|
||||||
@select="folderNodeSelect"
|
@select="folderNodeSelect"
|
||||||
>
|
>
|
||||||
<template #title="nodeData">
|
<template #title="nodeData">
|
||||||
|
@ -212,9 +212,11 @@
|
||||||
moduleCountParams?: TableQueryParams; // 获取模块树数量额外的参数
|
moduleCountParams?: TableQueryParams; // 获取模块树数量额外的参数
|
||||||
hideProjectSelect?: boolean; // 是否隐藏项目选择
|
hideProjectSelect?: boolean; // 是否隐藏项目选择
|
||||||
isHiddenCaseLevel?: boolean;
|
isHiddenCaseLevel?: boolean;
|
||||||
|
selectorAll: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
isHiddenCaseLevel: false,
|
isHiddenCaseLevel: false,
|
||||||
|
selectorAll: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -421,6 +423,7 @@
|
||||||
selectable: true,
|
selectable: true,
|
||||||
showSelectAll: true,
|
showSelectAll: true,
|
||||||
heightUsed: 310,
|
heightUsed: 310,
|
||||||
|
showSelectorAll: !props.selectorAll,
|
||||||
},
|
},
|
||||||
(record) => {
|
(record) => {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -185,6 +185,8 @@ export interface RunFeatureCaseParams extends ExecuteFeatureCaseFormParams {
|
||||||
notifier?: string;
|
notifier?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ExecuteHistoryType = Pick<RunFeatureCaseParams, 'id' | 'testPlanId' | 'caseId'>;
|
||||||
|
|
||||||
export interface BatchExecuteFeatureCaseParams extends BatchFeatureCaseParams, ExecuteFeatureCaseFormParams {
|
export interface BatchExecuteFeatureCaseParams extends BatchFeatureCaseParams, ExecuteFeatureCaseFormParams {
|
||||||
notifier?: string;
|
notifier?: string;
|
||||||
}
|
}
|
||||||
|
@ -213,4 +215,19 @@ export interface PassRateCountDetail {
|
||||||
apiScenarioCount: number;
|
apiScenarioCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 执行历史
|
||||||
|
export interface ExecuteHistoryItem {
|
||||||
|
status: string;
|
||||||
|
content: string;
|
||||||
|
contentText: string;
|
||||||
|
stepsExecResult: string;
|
||||||
|
createUser: string;
|
||||||
|
userName: string;
|
||||||
|
userLogo: string;
|
||||||
|
email: string;
|
||||||
|
steps: string;
|
||||||
|
createTime: string;
|
||||||
|
deleted: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export default {};
|
export default {};
|
||||||
|
|
|
@ -65,7 +65,7 @@
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
import type { BatchEditCaseType, CustomAttributes } from '@/models/caseManagement/featureCase';
|
import type { CustomAttributes } from '@/models/caseManagement/featureCase';
|
||||||
import { TableQueryParams } from '@/models/common';
|
import { TableQueryParams } from '@/models/common';
|
||||||
|
|
||||||
import Message from '@arco-design/web-vue/es/message';
|
import Message from '@arco-design/web-vue/es/message';
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
children: 'children',
|
children: 'children',
|
||||||
count: 'count',
|
count: 'count',
|
||||||
}"
|
}"
|
||||||
title-tooltip-position="left"
|
title-tooltip-position="top"
|
||||||
@select="caseNodeSelect"
|
@select="caseNodeSelect"
|
||||||
@more-action-select="handleCaseMoreSelect"
|
@more-action-select="handleCaseMoreSelect"
|
||||||
@more-actions-close="moreActionsClose"
|
@more-actions-close="moreActionsClose"
|
||||||
|
|
|
@ -97,10 +97,10 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
const innerKeyword = useVModel(props, 'keyword', emit);
|
const innerKeyword = useVModel(props, 'keyword', emit);
|
||||||
function searchData() {
|
function searchData(keyword?: string) {
|
||||||
setLinkListParams({
|
setLinkListParams({
|
||||||
...props.loadParams,
|
...props.loadParams,
|
||||||
keyword: innerKeyword.value,
|
keyword,
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
condition: {
|
condition: {
|
||||||
keyword: innerKeyword.value,
|
keyword: innerKeyword.value,
|
||||||
|
@ -123,7 +123,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
searchData();
|
searchData(innerKeyword.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
@ -139,7 +139,7 @@
|
||||||
() => props.caseId,
|
() => props.caseId,
|
||||||
(val) => {
|
(val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
searchData();
|
searchData(innerKeyword.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -57,7 +57,6 @@
|
||||||
|
|
||||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||||
|
|
||||||
import { getCaseLevels } from '@/views/case-management/caseManagementFeature/components/utils';
|
|
||||||
import debounce from 'lodash-es/debounce';
|
import debounce from 'lodash-es/debounce';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
|
@ -73,19 +73,19 @@
|
||||||
/>
|
/>
|
||||||
<ms-base-table v-else v-bind="testPlanPropsRes" ref="planTableRef" v-on="testPlanTableEvent">
|
<ms-base-table v-else v-bind="testPlanPropsRes" ref="planTableRef" v-on="testPlanTableEvent">
|
||||||
<template #name="{ record }">
|
<template #name="{ record }">
|
||||||
<div class="one-line-text max-w-[300px]"> {{ record.name }}</div>
|
<div class="flex flex-nowrap items-center">
|
||||||
<a-popover title="" position="right">
|
<div class="one-line-text">{{ characterLimit(record.name) }}</div>
|
||||||
<span class="ml-1 text-[rgb(var(--primary-5))]">{{ t('caseManagement.featureCase.preview') }}</span>
|
<a-popover title="" position="right" style="width: 480px">
|
||||||
|
<div class="ml-1 text-[rgb(var(--primary-5))]">{{ t('caseManagement.featureCase.preview') }}</div>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="max-w-[600px] text-[14px] text-[var(--color-text-1)]">
|
<div v-dompurify-html="record.content" class="markdown-body" style="margin-left: 48px"> </div>
|
||||||
{{ record.content }}
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #handleUserName="{ record }">
|
<template #handleUserName="{ record }">
|
||||||
<a-tooltip :content="record.handleUserName">
|
<a-tooltip :content="record.handleUserName">
|
||||||
<div class="one-line-text max-w-[200px]">{{ characterLimit(record.handleUserName) }}</div>
|
<div class="one-line-text max-w-[200px]">{{ characterLimit(record.handleUserName) || '-' }}</div>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<template #testPlanName="{ record }">
|
<template #testPlanName="{ record }">
|
||||||
|
@ -259,7 +259,7 @@
|
||||||
{
|
{
|
||||||
title: 'caseManagement.featureCase.tableColumnID',
|
title: 'caseManagement.featureCase.tableColumnID',
|
||||||
dataIndex: 'num',
|
dataIndex: 'num',
|
||||||
width: 200,
|
width: 100,
|
||||||
showInTable: true,
|
showInTable: true,
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
@ -271,7 +271,7 @@
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
showInTable: true,
|
showInTable: true,
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
width: 300,
|
width: 250,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
showDrag: false,
|
showDrag: false,
|
||||||
},
|
},
|
||||||
|
@ -281,7 +281,7 @@
|
||||||
dataIndex: 'testPlanName',
|
dataIndex: 'testPlanName',
|
||||||
showInTable: true,
|
showInTable: true,
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
width: 300,
|
width: 200,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
showDrag: false,
|
showDrag: false,
|
||||||
},
|
},
|
||||||
|
@ -291,7 +291,7 @@
|
||||||
dataIndex: 'defectState',
|
dataIndex: 'defectState',
|
||||||
showInTable: true,
|
showInTable: true,
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
width: 300,
|
width: 150,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
showDrag: false,
|
showDrag: false,
|
||||||
},
|
},
|
||||||
|
@ -305,7 +305,7 @@
|
||||||
},
|
},
|
||||||
showInTable: true,
|
showInTable: true,
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
width: 300,
|
width: 200,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -364,7 +364,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (showType.value === 'link') {
|
if (showType.value === 'link') {
|
||||||
bugTableListRef.value?.searchData();
|
bugTableListRef.value?.searchData(keyword.value);
|
||||||
} else {
|
} else {
|
||||||
setTestPlanListParams(initTableParams());
|
setTestPlanListParams(initTableParams());
|
||||||
await testPlanLinkList();
|
await testPlanLinkList();
|
||||||
|
@ -373,7 +373,7 @@
|
||||||
}
|
}
|
||||||
async function resetFetch() {
|
async function resetFetch() {
|
||||||
if (showType.value === 'link') {
|
if (showType.value === 'link') {
|
||||||
bugTableListRef.value?.searchData();
|
bugTableListRef.value?.searchData(keyword.value);
|
||||||
} else {
|
} else {
|
||||||
setTestPlanListParams({ keyword: '', projectId: appStore.currentProjectId, testPlanCaseId: props.caseId });
|
setTestPlanListParams({ keyword: '', projectId: appStore.currentProjectId, testPlanCaseId: props.caseId });
|
||||||
await testPlanLinkList();
|
await testPlanLinkList();
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
{{ t('caseManagement.caseReview.fail') }}
|
{{ t('caseManagement.caseReview.fail') }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.status === 'UNDER_REVIEWED'" class="flex items-center">
|
<div v-else-if="item.status === 'UNDER_REVIEWED'" class="flex items-center">
|
||||||
<MsIcon type="icon-icon_warning_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
<MsIcon type="icon-icon_warning_filled" class="mr-[4px] text-[rgb(var(--link-6))]" />
|
||||||
{{ t('caseManagement.caseReview.suggestion') }}
|
{{ t('caseManagement.caseReview.suggestion') }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.status === 'RE_REVIEWED'" class="flex items-center">
|
<div v-else-if="item.status === 'RE_REVIEWED'" class="flex items-center">
|
||||||
|
@ -57,11 +57,11 @@
|
||||||
{{ t('caseManagement.featureCase.execute.success') }}
|
{{ t('caseManagement.featureCase.execute.success') }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.status === 'BLOCKED'" class="flex items-center">
|
<div v-if="item.status === 'BLOCKED'" class="flex items-center">
|
||||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
||||||
{{ t('caseManagement.featureCase.execute.blocked') }}
|
{{ t('caseManagement.featureCase.execute.blocked') }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.status === 'FAILED'" class="flex items-center">
|
<div v-if="item.status === 'FAILED'" class="flex items-center">
|
||||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
|
||||||
{{ t('caseManagement.featureCase.execute.failed') }}
|
{{ t('caseManagement.featureCase.execute.failed') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,23 +10,30 @@
|
||||||
:type="RequestModuleEnum.CASE_MANAGEMENT"
|
:type="RequestModuleEnum.CASE_MANAGEMENT"
|
||||||
hide-project-select
|
hide-project-select
|
||||||
is-hidden-case-level
|
is-hidden-case-level
|
||||||
|
:selector-all="true"
|
||||||
@save="saveHandler"
|
@save="saveHandler"
|
||||||
>
|
>
|
||||||
</MsCaseAssociate>
|
</MsCaseAssociate>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
|
||||||
import MsCaseAssociate from '@/components/business/ms-case-associate/index.vue';
|
import MsCaseAssociate from '@/components/business/ms-case-associate/index.vue';
|
||||||
import { RequestModuleEnum } from '@/components/business/ms-case-associate/utils';
|
import { RequestModuleEnum } from '@/components/business/ms-case-associate/utils';
|
||||||
|
|
||||||
import { getCaseList, getCaseModuleTree } from '@/api/modules/case-management/featureCase';
|
import { getCaseList, getCaseModuleTree } from '@/api/modules/case-management/featureCase';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
import type { AssociateCaseRequest } from '@/models/testPlan/testPlan';
|
import type { AssociateCaseRequest, AssociateCaseRequestType } from '@/models/testPlan/testPlan';
|
||||||
import { CaseLinkEnum } from '@/enums/caseEnum';
|
import { CaseLinkEnum } from '@/enums/caseEnum';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
hasNotAssociatedIds?: string[];
|
hasNotAssociatedIds?: string[];
|
||||||
|
saveApi?: (params: AssociateCaseRequestType) => Promise<any>;
|
||||||
}>();
|
}>();
|
||||||
const innerVisible = defineModel<boolean>('visible', {
|
const innerVisible = defineModel<boolean>('visible', {
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -36,16 +43,31 @@
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
const route = useRoute();
|
||||||
const currentSelectCase = ref<keyof typeof CaseLinkEnum>('FUNCTIONAL');
|
const currentSelectCase = ref<keyof typeof CaseLinkEnum>('FUNCTIONAL');
|
||||||
const currentProjectId = ref(appStore.currentProjectId);
|
const currentProjectId = ref(appStore.currentProjectId);
|
||||||
|
|
||||||
const confirmLoading = ref<boolean>(false);
|
const confirmLoading = ref<boolean>(false);
|
||||||
|
const planId = ref(route.query.id as string);
|
||||||
|
|
||||||
function saveHandler(params: AssociateCaseRequest) {
|
async function saveHandler(params: AssociateCaseRequest) {
|
||||||
try {
|
try {
|
||||||
confirmLoading.value = true;
|
confirmLoading.value = true;
|
||||||
|
if (typeof props.saveApi !== 'function') {
|
||||||
emit('success', { ...params, functionalSelectIds: params.selectIds });
|
emit('success', { ...params, functionalSelectIds: params.selectIds });
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await props.saveApi({
|
||||||
|
functionalSelectIds: params.selectIds,
|
||||||
|
testPlanId: planId.value,
|
||||||
|
});
|
||||||
|
emit('success', { ...params, functionalSelectIds: params.selectIds });
|
||||||
|
Message.success(t('ms.case.associate.associateSuccess'));
|
||||||
|
confirmLoading.value = false;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
innerVisible.value = false;
|
innerVisible.value = false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
|
|
@ -1,20 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<MsDialog
|
<a-modal v-model:visible="isVisible" title-align="start" class="ms-modal-upload ms-modal-medium" :width="480">
|
||||||
v-model:visible="isVisible"
|
<template #title>
|
||||||
dialog-size="small"
|
{{ t('common.edit') }}
|
||||||
:title="t('testPlan.testPlanIndex.batchEdit', { number: props.batchParams.currentSelectCount })"
|
<div class="text-[var(--color-text-4)]">
|
||||||
ok-text="common.update"
|
{{
|
||||||
:confirm="confirmHandler"
|
t('case.batchModalSubTitle', {
|
||||||
:close="closeHandler"
|
count: props.batchParams.currentSelectCount,
|
||||||
unmount-on-close
|
})
|
||||||
:switch-props="{
|
}}
|
||||||
switchName: t('caseManagement.featureCase.appendTag'),
|
</div>
|
||||||
switchTooltip: t('caseManagement.featureCase.enableTags'),
|
</template>
|
||||||
showSwitch: form.selectedAttrsId === 'tags' ? true : false,
|
|
||||||
enable: form.append,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="form">
|
|
||||||
<a-form ref="formRef" class="rounded-[4px]" :model="form" layout="vertical">
|
<a-form ref="formRef" class="rounded-[4px]" :model="form" layout="vertical">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="selectedAttrsId"
|
field="selectedAttrsId"
|
||||||
|
@ -30,21 +25,14 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
v-if="form.selectedAttrsId === 'tags'"
|
v-if="form.selectedAttrsId === 'tags'"
|
||||||
field="values"
|
field="tags"
|
||||||
:label="t('apiTestManagement.batchUpdate')"
|
:label="t('apiTestManagement.batchUpdate')"
|
||||||
:validate-trigger="['blur', 'input']"
|
|
||||||
:rules="[{ required: true, message: t('apiTestManagement.valueRequired') }]"
|
:rules="[{ required: true, message: t('apiTestManagement.valueRequired') }]"
|
||||||
asterisk-position="end"
|
asterisk-position="end"
|
||||||
class="mb-0"
|
class="mb-0"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
<MsTagsInput
|
<MsTagsInput v-model:modelValue="form.tags" allow-clear></MsTagsInput>
|
||||||
v-model:model-value="form.tags"
|
|
||||||
placeholder="common.tagsInputPlaceholder"
|
|
||||||
allow-clear
|
|
||||||
unique-value
|
|
||||||
retain-input-value
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
v-else
|
v-else
|
||||||
|
@ -54,47 +42,75 @@
|
||||||
asterisk-position="end"
|
asterisk-position="end"
|
||||||
class="mb-0"
|
class="mb-0"
|
||||||
>
|
>
|
||||||
<a-select
|
<a-select v-model="form.value" :placeholder="t('common.pleaseSelect')" :disabled="form.selectedAttrsId === ''">
|
||||||
v-model="form.value"
|
|
||||||
:placeholder="t('common.pleaseSelect')"
|
|
||||||
:disabled="form.selectedAttrsId === ''"
|
|
||||||
>
|
|
||||||
<a-option v-for="item of valueOptions" :key="item.value" :value="item.value">
|
<a-option v-for="item of valueOptions" :key="item.value" :value="item.value">
|
||||||
{{ t(item.label) }}
|
{{ t(item.label) }}
|
||||||
</a-option>
|
</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex" :class="[form.selectedAttrsId === 'tags' ? 'justify-between' : 'justify-end']">
|
||||||
|
<div
|
||||||
|
v-if="form.selectedAttrsId === 'tags'"
|
||||||
|
class="flex flex-row items-center justify-center"
|
||||||
|
style="padding-top: 10px"
|
||||||
|
>
|
||||||
|
<a-switch v-model="form.append" class="mr-1" size="small" type="line" />
|
||||||
|
<span class="flex items-center">
|
||||||
|
<span class="mr-1">{{ t('caseManagement.featureCase.appendTag') }}</span>
|
||||||
|
<span class="mt-[2px]">
|
||||||
|
<a-tooltip>
|
||||||
|
<IconQuestionCircle class="h-[16px] w-[16px] text-[rgb(var(--primary-5))]" />
|
||||||
|
<template #content>
|
||||||
|
<div>{{ t('caseManagement.featureCase.enableTags') }}</div>
|
||||||
|
<div>{{ t('caseManagement.featureCase.closeTags') }}</div>
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</MsDialog>
|
<div class="flex justify-end">
|
||||||
|
<a-button type="secondary" :disabled="batchEditLoading" @click="closeHandler">
|
||||||
|
{{ t('common.cancel') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button class="ml-3" type="primary" :loading="batchEditLoading" @click="confirmHandler">
|
||||||
|
{{ t('common.update') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { FormInstance, ValidatedError } from '@arco-design/web-vue';
|
import { FormInstance } from '@arco-design/web-vue';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import MsDialog from '@/components/pure/ms-dialog/index.vue';
|
|
||||||
import type { BatchActionQueryParams } from '@/components/pure/ms-table/type';
|
import type { BatchActionQueryParams } from '@/components/pure/ms-table/type';
|
||||||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||||
|
|
||||||
|
import { batchEditTestPlan } from '@/api/modules/test-plan/testPlan';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
import { TableQueryParams } from '@/models/common';
|
import { TableQueryParams } from '@/models/common';
|
||||||
|
import { testPlanTypeEnum } from '@/enums/testPlanEnum';
|
||||||
|
|
||||||
import Message from '@arco-design/web-vue/es/message';
|
import Message from '@arco-design/web-vue/es/message';
|
||||||
|
|
||||||
const isVisible = ref<boolean>(false);
|
const isVisible = ref<boolean>(false);
|
||||||
const appStore = useAppStore();
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const appStore = useAppStore();
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
batchParams: BatchActionQueryParams;
|
batchParams: BatchActionQueryParams;
|
||||||
activeFolder: string;
|
activeFolder: string;
|
||||||
offspringIds: string[];
|
offspringIds: string[];
|
||||||
condition?: TableQueryParams;
|
condition?: TableQueryParams;
|
||||||
|
showType: keyof typeof testPlanTypeEnum;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
|
@ -102,7 +118,6 @@
|
||||||
(e: 'success'): void;
|
(e: 'success'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const currentProjectId = computed(() => appStore.currentProjectId);
|
|
||||||
const initForm = {
|
const initForm = {
|
||||||
selectedAttrsId: '',
|
selectedAttrsId: '',
|
||||||
append: false,
|
append: false,
|
||||||
|
@ -126,33 +141,33 @@
|
||||||
form.value = { ...initForm };
|
form.value = { ...initForm };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirmHandler(enable: boolean | undefined) {
|
const batchEditLoading = ref(false);
|
||||||
formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
|
function confirmHandler() {
|
||||||
|
formRef.value?.validate(async (errors) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
try {
|
try {
|
||||||
const customField = {
|
batchEditLoading.value = true;
|
||||||
fieldId: '',
|
|
||||||
value: '',
|
|
||||||
};
|
|
||||||
const { selectedIds, selectAll, excludeIds } = props.batchParams;
|
const { selectedIds, selectAll, excludeIds } = props.batchParams;
|
||||||
const params: TableQueryParams = {
|
const params: TableQueryParams = {
|
||||||
selectIds: selectedIds || [],
|
selectIds: selectedIds || [],
|
||||||
selectAll: !!selectAll,
|
selectAll: !!selectAll,
|
||||||
excludeIds: excludeIds || [],
|
excludeIds: excludeIds || [],
|
||||||
projectId: currentProjectId.value,
|
projectId: appStore.currentProjectId,
|
||||||
append: enable as boolean,
|
|
||||||
tags: form.value.tags,
|
|
||||||
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
|
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
|
||||||
customField: form.value.selectedAttrsId === 'systemTags' ? {} : customField,
|
|
||||||
condition: {
|
condition: {
|
||||||
...props.condition,
|
...props.condition,
|
||||||
},
|
},
|
||||||
|
...form.value,
|
||||||
|
type: props.showType,
|
||||||
};
|
};
|
||||||
|
await batchEditTestPlan(params);
|
||||||
Message.success(t('caseManagement.featureCase.editSuccess'));
|
Message.success(t('caseManagement.featureCase.editSuccess'));
|
||||||
closeHandler();
|
closeHandler();
|
||||||
emits('success');
|
emits('success');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
} finally {
|
||||||
|
batchEditLoading.value = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
class="mt-4"
|
class="mt-4"
|
||||||
:action-config="testPlanBatchActions"
|
:action-config="testPlanBatchActions"
|
||||||
filter-icon-align-left
|
filter-icon-align-left
|
||||||
|
:selectable="hasOperationPermission"
|
||||||
v-on="propsEvent"
|
v-on="propsEvent"
|
||||||
@batch-action="handleTableBatch"
|
@batch-action="handleTableBatch"
|
||||||
>
|
>
|
||||||
|
@ -115,7 +116,7 @@
|
||||||
<StatusProgress :status-detail="defaultCountDetailMap[record.id]" height="5px" />
|
<StatusProgress :status-detail="defaultCountDetailMap[record.id]" height="5px" />
|
||||||
</div>
|
</div>
|
||||||
<div class="text-[var(--color-text-1)]">
|
<div class="text-[var(--color-text-1)]">
|
||||||
{{ `${record.passRate || 0}%` }}
|
{{ `${defaultCountDetailMap[record.id] ? defaultCountDetailMap[record.id].passRate : '-'}%` }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #passRateTitleSlot="{ columnConfig }">
|
<template #passRateTitleSlot="{ columnConfig }">
|
||||||
|
@ -173,10 +174,17 @@
|
||||||
|
|
||||||
<template #operation="{ record }">
|
<template #operation="{ record }">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<MsButton v-if="record.functionalCaseCount > 0" class="!mx-0">{{
|
<MsButton
|
||||||
t('testPlan.testPlanIndex.execution')
|
v-if="record.functionalCaseCount > 0 && hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE'])"
|
||||||
}}</MsButton>
|
class="!mx-0"
|
||||||
<a-divider v-if="record.functionalCaseCount > 0" direction="vertical" :margin="8"></a-divider>
|
@click="openDetail(record.id)"
|
||||||
|
>{{ t('testPlan.testPlanIndex.execution') }}</MsButton
|
||||||
|
>
|
||||||
|
<a-divider
|
||||||
|
v-if="record.functionalCaseCount > 0 && hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE'])"
|
||||||
|
direction="vertical"
|
||||||
|
:margin="8"
|
||||||
|
></a-divider>
|
||||||
|
|
||||||
<MsButton
|
<MsButton
|
||||||
v-permission="['PROJECT_TEST_PLAN:READ+UPDATE']"
|
v-permission="['PROJECT_TEST_PLAN:READ+UPDATE']"
|
||||||
|
@ -245,6 +253,7 @@
|
||||||
:active-folder="props.activeFolder"
|
:active-folder="props.activeFolder"
|
||||||
:offspring-ids="props.offspringIds"
|
:offspring-ids="props.offspringIds"
|
||||||
:condition="conditionParams"
|
:condition="conditionParams"
|
||||||
|
:show-type="showType"
|
||||||
@success="successHandler"
|
@success="successHandler"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -289,6 +298,7 @@
|
||||||
import { characterLimit } from '@/utils';
|
import { characterLimit } from '@/utils';
|
||||||
import { hasAnyPermission } from '@/utils/permission';
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
|
||||||
|
import type { TableQueryParams } from '@/models/common';
|
||||||
import { ModuleTreeNode } from '@/models/common';
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
import type { PassRateCountDetail, planStatusType, TestPlanItem } from '@/models/testPlan/testPlan';
|
import type { PassRateCountDetail, planStatusType, TestPlanItem } from '@/models/testPlan/testPlan';
|
||||||
import { TestPlanRouteEnum } from '@/enums/routeEnum';
|
import { TestPlanRouteEnum } from '@/enums/routeEnum';
|
||||||
|
@ -319,6 +329,10 @@
|
||||||
(e: 'editOrCopy', id: string, isCopy: boolean): void;
|
(e: 'editOrCopy', id: string, isCopy: boolean): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const hasOperationPermission = computed(() =>
|
||||||
|
hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE', 'PROJECT_TEST_PLAN:READ+EXECUTE', 'PROJECT_TEST_PLAN:READ+ADD'])
|
||||||
|
);
|
||||||
|
|
||||||
const columns: MsTableColumn = [
|
const columns: MsTableColumn = [
|
||||||
{
|
{
|
||||||
title: 'testPlan.testPlanIndex.ID',
|
title: 'testPlan.testPlanIndex.ID',
|
||||||
|
@ -443,11 +457,11 @@
|
||||||
showDrag: true,
|
showDrag: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'testPlan.testPlanIndex.operation',
|
title: hasOperationPermission.value ? 'testPlan.testPlanIndex.operation' : '',
|
||||||
slotName: 'operation',
|
slotName: 'operation',
|
||||||
dataIndex: 'operation',
|
dataIndex: 'operation',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 200,
|
width: hasOperationPermission.value ? 200 : 50,
|
||||||
showInTable: true,
|
showInTable: true,
|
||||||
showDrag: false,
|
showDrag: false,
|
||||||
},
|
},
|
||||||
|
@ -537,37 +551,43 @@
|
||||||
{
|
{
|
||||||
label: 'common.archive',
|
label: 'common.archive',
|
||||||
eventTag: 'archive',
|
eventTag: 'archive',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const copyActions: ActionsItem[] = [
|
const copyActions: ActionsItem[] = [
|
||||||
{
|
{
|
||||||
label: 'common.copy',
|
label: 'common.copy',
|
||||||
eventTag: 'copy',
|
eventTag: 'copy',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+ADD'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function getMoreActions(status: planStatusType, useCount: number) {
|
function getMoreActions(status: planStatusType, useCount: number) {
|
||||||
|
// 有用例数量才可以执行 否则不展示执行
|
||||||
const copyAction = useCount > 0 ? copyActions : [];
|
const copyAction = useCount > 0 ? copyActions : [];
|
||||||
if (status === 'COMPLETED' || status === 'ARCHIVED') {
|
// 单独操作已归档和已完成 不展示归档
|
||||||
|
if (status === 'ARCHIVED' || status === 'PREPARED' || status === 'UNDERWAY') {
|
||||||
return [
|
return [
|
||||||
...copyAction,
|
...copyAction,
|
||||||
{
|
|
||||||
isDivider: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'common.delete',
|
label: 'common.delete',
|
||||||
danger: true,
|
danger: true,
|
||||||
eventTag: 'delete',
|
eventTag: 'delete',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+DELETE'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
...copyAction,
|
...copyAction,
|
||||||
...archiveActions,
|
...archiveActions,
|
||||||
|
{
|
||||||
|
isDivider: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'common.delete',
|
label: 'common.delete',
|
||||||
danger: true,
|
danger: true,
|
||||||
eventTag: 'delete',
|
eventTag: 'delete',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+DELETE'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -610,7 +630,6 @@
|
||||||
filter: propsRes.value.filter,
|
filter: propsRes.value.filter,
|
||||||
combine: batchParams.value.condition,
|
combine: batchParams.value.condition,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: showType.value,
|
type: showType.value,
|
||||||
moduleIds: props.activeFolder && props.activeFolder !== 'all' ? [props.activeFolder, ...props.offspringIds] : [],
|
moduleIds: props.activeFolder && props.activeFolder !== 'all' ? [props.activeFolder, ...props.offspringIds] : [],
|
||||||
|
@ -620,6 +639,7 @@
|
||||||
selectIds: batchParams.value.selectedIds || [],
|
selectIds: batchParams.value.selectedIds || [],
|
||||||
keyword: keyword.value,
|
keyword: keyword.value,
|
||||||
condition: {
|
condition: {
|
||||||
|
filter: propsRes.value.filter,
|
||||||
keyword: keyword.value,
|
keyword: keyword.value,
|
||||||
},
|
},
|
||||||
combine: {
|
combine: {
|
||||||
|
@ -832,6 +852,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function successHandler() {
|
function successHandler() {
|
||||||
|
resetSelector();
|
||||||
fetchData();
|
fetchData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
:node-more-actions="caseMoreActions"
|
:node-more-actions="caseMoreActions"
|
||||||
:expand-all="props.isExpandAll"
|
:expand-all="props.isExpandAll"
|
||||||
:empty-text="t('testPlan.testPlanIndex.planEmptyContent')"
|
:empty-text="t('testPlan.testPlanIndex.planEmptyContent')"
|
||||||
draggable
|
:draggable="hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE'])"
|
||||||
:virtual-list-props="virtualListProps"
|
:virtual-list-props="virtualListProps"
|
||||||
block-node
|
block-node
|
||||||
:field-names="{
|
:field-names="{
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
children: 'children',
|
children: 'children',
|
||||||
count: 'count',
|
count: 'count',
|
||||||
}"
|
}"
|
||||||
title-tooltip-position="left"
|
title-tooltip-position="top"
|
||||||
@select="planNodeSelect"
|
@select="planNodeSelect"
|
||||||
@more-action-select="handlePlanMoreSelect"
|
@more-action-select="handlePlanMoreSelect"
|
||||||
@more-actions-close="moreActionsClose"
|
@more-actions-close="moreActionsClose"
|
||||||
|
@ -33,6 +33,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template #extra="nodeData">
|
<template #extra="nodeData">
|
||||||
<MsPopConfirm
|
<MsPopConfirm
|
||||||
|
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+ADD'])"
|
||||||
:visible="addSubVisible"
|
:visible="addSubVisible"
|
||||||
:is-delete="false"
|
:is-delete="false"
|
||||||
:all-names="[]"
|
:all-names="[]"
|
||||||
|
@ -50,6 +51,7 @@
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</MsPopConfirm>
|
</MsPopConfirm>
|
||||||
<MsPopConfirm
|
<MsPopConfirm
|
||||||
|
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE'])"
|
||||||
:title="t('testPlan.testPlanIndex.rename')"
|
:title="t('testPlan.testPlanIndex.rename')"
|
||||||
:all-names="[]"
|
:all-names="[]"
|
||||||
:is-delete="false"
|
:is-delete="false"
|
||||||
|
@ -87,6 +89,7 @@
|
||||||
import useModal from '@/hooks/useModal';
|
import useModal from '@/hooks/useModal';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
import { mapTree } from '@/utils';
|
import { mapTree } from '@/utils';
|
||||||
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
|
||||||
import type { CreateOrUpdateModule, UpdateModule } from '@/models/caseManagement/featureCase';
|
import type { CreateOrUpdateModule, UpdateModule } from '@/models/caseManagement/featureCase';
|
||||||
import { ModuleTreeNode } from '@/models/common';
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
|
|
|
@ -287,9 +287,12 @@
|
||||||
if (props.planId?.length) {
|
if (props.planId?.length) {
|
||||||
const result = await getTestPlanDetail(props.planId);
|
const result = await getTestPlanDetail(props.planId);
|
||||||
form.value = cloneDeep(result);
|
form.value = cloneDeep(result);
|
||||||
|
if (props.isCopy) {
|
||||||
let copyName = `copy_${result.name}`;
|
let copyName = `copy_${result.name}`;
|
||||||
copyName = copyName.length > 255 ? copyName.slice(0, 255) : copyName;
|
copyName = copyName.length > 255 ? copyName.slice(0, 255) : copyName;
|
||||||
form.value.name = copyName;
|
form.value.name = copyName;
|
||||||
|
}
|
||||||
|
|
||||||
form.value.cycle = [result.plannedStartTime as number, result.plannedEndTime as number];
|
form.value.cycle = [result.plannedStartTime as number, result.plannedEndTime as number];
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -534,6 +534,7 @@
|
||||||
...tableParams.value,
|
...tableParams.value,
|
||||||
...batchExecuteForm.value,
|
...batchExecuteForm.value,
|
||||||
notifier: batchExecuteForm.value?.commentIds?.join(';'),
|
notifier: batchExecuteForm.value?.commentIds?.join(';'),
|
||||||
|
selectIds: batchParams.value.selectedIds,
|
||||||
});
|
});
|
||||||
Message.success(t('common.updateSuccess'));
|
Message.success(t('common.updateSuccess'));
|
||||||
resetSelector();
|
resetSelector();
|
||||||
|
@ -636,6 +637,7 @@
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
resetSelector,
|
resetSelector,
|
||||||
|
loadCaseList,
|
||||||
});
|
});
|
||||||
|
|
||||||
await tableStore.initColumn(TableKeyEnum.TEST_PLAN_DETAIL_FEATURE_CASE_TABLE, columns, 'drawer', true);
|
await tableStore.initColumn(TableKeyEnum.TEST_PLAN_DETAIL_FEATURE_CASE_TABLE, columns, 'drawer', true);
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
class="mx-[8px] w-[240px]"
|
class="mx-[8px] w-[240px]"
|
||||||
@search="initData"
|
@search="initData"
|
||||||
@press-enter="initData"
|
@press-enter="initData"
|
||||||
@clear="initData"
|
@clear="resetHandler"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<BugList
|
<BugList
|
||||||
|
@ -62,26 +62,10 @@
|
||||||
testPlanCaseId: route.query.testPlanCaseId,
|
testPlanCaseId: route.query.testPlanCaseId,
|
||||||
caseId: props.caseId,
|
caseId: props.caseId,
|
||||||
}"
|
}"
|
||||||
@link="linkDefect"
|
@link="emit('link')"
|
||||||
@new="createDefect"
|
@new="emit('new')"
|
||||||
@cancel-link="cancelLink"
|
@cancel-link="cancelLink"
|
||||||
/>
|
/>
|
||||||
<LinkDefectDrawer
|
|
||||||
v-model:visible="showLinkDrawer"
|
|
||||||
:case-id="props.caseId"
|
|
||||||
:drawer-loading="drawerLoading"
|
|
||||||
@save="saveHandler"
|
|
||||||
/>
|
|
||||||
<AddDefectDrawer
|
|
||||||
v-model:visible="showDrawer"
|
|
||||||
:case-id="props.caseId"
|
|
||||||
::extra-params="{
|
|
||||||
testPlanCaseId: route.query.testPlanCaseId,
|
|
||||||
caseId: props.caseId,
|
|
||||||
testPlanId:props.testPlanId,
|
|
||||||
}"
|
|
||||||
@success="initData()"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -92,12 +76,10 @@
|
||||||
|
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||||
import AddDefectDrawer from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/addDefectDrawer.vue';
|
|
||||||
import BugList from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/bugList.vue';
|
import BugList from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/bugList.vue';
|
||||||
import LinkDefectDrawer from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/linkDefectDrawer.vue';
|
|
||||||
|
|
||||||
import { getBugList, getCustomOptionHeader } from '@/api/modules/bug-management';
|
import { getBugList, getCustomOptionHeader } from '@/api/modules/bug-management';
|
||||||
import { associateBugToPlan, associatedBugPage, testPlanCancelBug } from '@/api/modules/test-plan/testPlan';
|
import { associatedBugPage, testPlanCancelBug } from '@/api/modules/test-plan/testPlan';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { useAppStore } from '@/store';
|
import { useAppStore } from '@/store';
|
||||||
import { hasAnyPermission } from '@/utils/permission';
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
@ -116,6 +98,12 @@
|
||||||
|
|
||||||
const keyword = ref<string>('');
|
const keyword = ref<string>('');
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'link'): void;
|
||||||
|
(e: 'new'): void;
|
||||||
|
(e: 'save', params: TableQueryParams): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
const columns = ref<MsTableColumn>([
|
const columns = ref<MsTableColumn>([
|
||||||
{
|
{
|
||||||
title: 'caseManagement.featureCase.tableColumnID',
|
title: 'caseManagement.featureCase.tableColumnID',
|
||||||
|
@ -188,26 +176,16 @@
|
||||||
if (!hasAnyPermission(['FUNCTIONAL_CASE:READ', 'FUNCTIONAL_CASE:READ+UPDATE', 'FUNCTIONAL_CASE:READ+DELETE'])) {
|
if (!hasAnyPermission(['FUNCTIONAL_CASE:READ', 'FUNCTIONAL_CASE:READ+UPDATE', 'FUNCTIONAL_CASE:READ+DELETE'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bugTableListRef.value?.searchData();
|
bugTableListRef.value?.searchData(keyword.value);
|
||||||
}
|
|
||||||
|
|
||||||
const showLinkDrawer = ref<boolean>(false);
|
|
||||||
function linkDefect() {
|
|
||||||
showLinkDrawer.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const showDrawer = ref<boolean>(false);
|
|
||||||
function createDefect() {
|
|
||||||
showDrawer.value = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSelect(value: string | number | Record<string, any> | undefined) {
|
function handleSelect(value: string | number | Record<string, any> | undefined) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 'associated':
|
case 'associated':
|
||||||
linkDefect();
|
emit('link');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
createDefect();
|
emit('new');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,6 +210,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancelLoading = ref<boolean>(false);
|
const cancelLoading = ref<boolean>(false);
|
||||||
|
|
||||||
// 取消关联缺陷
|
// 取消关联缺陷
|
||||||
async function cancelLink(id: string) {
|
async function cancelLink(id: string) {
|
||||||
cancelLoading.value = true;
|
cancelLoading.value = true;
|
||||||
|
@ -261,25 +240,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const drawerLoading = ref<boolean>(false);
|
|
||||||
// 关联缺陷
|
function resetHandler() {
|
||||||
async function saveHandler(params: TableQueryParams) {
|
keyword.value = '';
|
||||||
try {
|
|
||||||
drawerLoading.value = true;
|
|
||||||
await associateBugToPlan({
|
|
||||||
...params,
|
|
||||||
caseId: props.caseId,
|
|
||||||
testPlanId: props.testPlanId,
|
|
||||||
testPlanCaseId: route.query.testPlanCaseId as string,
|
|
||||||
});
|
|
||||||
Message.success(t('caseManagement.featureCase.associatedSuccess'));
|
|
||||||
initData();
|
initData();
|
||||||
showLinkDrawer.value = false;
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
} finally {
|
|
||||||
drawerLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
@ -296,6 +260,10 @@
|
||||||
initData();
|
initData();
|
||||||
initBugList();
|
initBugList();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
initData,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<!-- TODO: 待联调 -->
|
<a-spin :loading="loading" class="w-full">
|
||||||
<div class="review-history-list">
|
<div class="execute-history-list">
|
||||||
<div v-for="item of executeHistoryList" :key="item.id" class="review-history-list-item">
|
<div v-for="item of executeHistoryList" :key="item.status" class="execute-history-list-item">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<MsAvatar :avatar="item.userLogo" />
|
<MsAvatar :avatar="item.userLogo" />
|
||||||
<div class="ml-[8px] flex items-center">
|
<div class="ml-[8px] flex items-center">
|
||||||
|
@ -9,33 +9,17 @@
|
||||||
<div class="one-line-text max-w-[300px] font-medium text-[var(--color-text-1)]">{{ item.userName }}</div>
|
<div class="one-line-text max-w-[300px] font-medium text-[var(--color-text-1)]">{{ item.userName }}</div>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-divider direction="vertical" margin="8px"></a-divider>
|
<a-divider direction="vertical" margin="8px"></a-divider>
|
||||||
<div v-if="item.status === 'PASS'" class="flex items-center">
|
<div v-if="item.status === 'SUCCESS'" class="flex items-center">
|
||||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
||||||
{{ t('caseManagement.caseReview.pass') }}
|
{{ t('common.success') }}
|
||||||
</div>
|
|
||||||
<div v-else-if="item.status === 'UN_PASS'" class="flex items-center">
|
|
||||||
<MsIcon type="icon-icon_close_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
|
|
||||||
{{ t('caseManagement.caseReview.fail') }}
|
|
||||||
</div>
|
|
||||||
<div v-else-if="item.status === 'UNDER_REVIEWED'" class="flex items-center">
|
|
||||||
<MsIcon type="icon-icon_warning_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
|
||||||
{{ t('caseManagement.caseReview.suggestion') }}
|
|
||||||
</div>
|
|
||||||
<div v-else-if="item.status === 'RE_REVIEWED'" class="flex items-center">
|
|
||||||
<MsIcon type="icon-icon_resubmit_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
|
||||||
{{ t('caseManagement.caseReview.reReview') }}
|
|
||||||
</div>
|
|
||||||
<div v-if="item.status === 'PASSED'" class="flex items-center">
|
|
||||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
|
||||||
{{ t('caseManagement.featureCase.execute.success') }}
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.status === 'BLOCKED'" class="flex items-center">
|
<div v-if="item.status === 'BLOCKED'" class="flex items-center">
|
||||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
<MsIcon type="icon-icon_block_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
||||||
{{ t('caseManagement.featureCase.execute.blocked') }}
|
{{ t('common.block') }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.status === 'FAILED'" class="flex items-center">
|
<div v-if="item.status === 'ERROR'" class="flex items-center">
|
||||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
<MsIcon type="icon-icon_close_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
|
||||||
{{ t('caseManagement.featureCase.execute.failed') }}
|
{{ t('common.fail') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,16 +27,9 @@
|
||||||
<div class="ml-[48px] mt-[8px] flex text-[var(--color-text-4)]">
|
<div class="ml-[48px] mt-[8px] flex text-[var(--color-text-4)]">
|
||||||
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
|
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||||||
<div>
|
<div>
|
||||||
<a-tooltip :content="item.reviewName" :mouse-enter-delay="300">
|
<a-tooltip :content="item.userName" :mouse-enter-delay="300" :disabled="!item.userName">
|
||||||
<span v-if="item.deleted" class="one-line-text ml-[16px] max-w-[300px] break-words break-all">
|
<span v-if="item.deleted" class="one-line-text ml-[16px] max-w-[300px] break-words break-all">
|
||||||
{{ characterLimit(item.reviewName) }}
|
{{ characterLimit(item.userName) }}
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
class="one-line-text ml-[16px] max-w-[300px] cursor-pointer break-words break-all text-[rgb(var(--primary-5))]"
|
|
||||||
>
|
|
||||||
{{ characterLimit(item.reviewName) }}
|
|
||||||
</span>
|
</span>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -60,25 +37,71 @@
|
||||||
</div>
|
</div>
|
||||||
<MsEmpty v-if="executeHistoryList.length === 0" />
|
<MsEmpty v-if="executeHistoryList.length === 0" />
|
||||||
</div>
|
</div>
|
||||||
|
</a-spin>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import MsAvatar from '@/components/pure/ms-avatar/index.vue';
|
import MsAvatar from '@/components/pure/ms-avatar/index.vue';
|
||||||
import MsEmpty from '@/components/pure/ms-empty/index.vue';
|
import MsEmpty from '@/components/pure/ms-empty/index.vue';
|
||||||
import { CommentItem, CommentParams } from '@/components/business/ms-comment/types';
|
|
||||||
|
|
||||||
|
import { executeHistory } from '@/api/modules/test-plan/testPlan';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { characterLimit } from '@/utils';
|
import { characterLimit } from '@/utils';
|
||||||
|
|
||||||
|
import type { ExecuteHistoryItem } from '@/models/testPlan/testPlan';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
caseId: string;
|
caseId: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const executeHistoryList = ref<CommentItem[]>([]);
|
const executeHistoryList = ref<ExecuteHistoryItem[]>([]);
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
|
||||||
|
async function initList() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
executeHistoryList.value = await executeHistory({
|
||||||
|
caseId: props.caseId,
|
||||||
|
id: route.query.testPlanCaseId as string,
|
||||||
|
testPlanId: route.query.id as string,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
initList();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.caseId,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
initList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped lang="less">
|
||||||
|
.execute-history-list {
|
||||||
|
height: calc(100vh - 240px);
|
||||||
|
@apply overflow-auto;
|
||||||
|
.ms-scroll-bar();
|
||||||
|
.execute-history-list-item {
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -120,7 +120,33 @@
|
||||||
size="16"
|
size="16"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<!-- TODO: 缺陷 -->
|
<MsTag type="danger" theme="light" size="medium" class="ml-4">
|
||||||
|
<MsIcon type="icon-icon_defect" class="!text-[14px] text-[rgb(var(--danger-6))]" size="16" />
|
||||||
|
<span class="ml-1 text-[rgb(var(--danger-6))]"> {{ t('testPlan.featureCase.bug') }}</span>
|
||||||
|
<span class="ml-1 text-[rgb(var(--danger-6))]">{{ bugCount }}</span>
|
||||||
|
</MsTag>
|
||||||
|
<a-dropdown @select="handleSelect">
|
||||||
|
<a-button type="outline" size="mini" class="ml-1">
|
||||||
|
<template #icon> <icon-plus class="text-[12px]" /> </template>
|
||||||
|
</a-button>
|
||||||
|
<template #content>
|
||||||
|
<a-doption value="new">{{ t('common.newCreate') }}</a-doption>
|
||||||
|
<a-doption v-if="createdBugCount > 0" value="link">{{ t('common.associated') }}</a-doption>
|
||||||
|
<a-popover v-else title="" position="left">
|
||||||
|
<a-doption :disabled="true" value="link">{{ t('common.associated') }}</a-doption>
|
||||||
|
<template #content>
|
||||||
|
<div class="flex items-center text-[14px]">
|
||||||
|
<span class="text-[var(--color-text-4)]">{{
|
||||||
|
t('testPlan.featureCase.noBugDataTooltip')
|
||||||
|
}}</span>
|
||||||
|
<MsButton type="text" @click="handleSelect('new')">
|
||||||
|
{{ t('testPlan.featureCase.noBugDataNewBug') }}
|
||||||
|
</MsButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ExecuteSubmit
|
<ExecuteSubmit
|
||||||
|
@ -134,19 +160,39 @@
|
||||||
</div>
|
</div>
|
||||||
<BugList
|
<BugList
|
||||||
v-if="activeTab === 'defectList'"
|
v-if="activeTab === 'defectList'"
|
||||||
:case-id="caseDetail.id"
|
ref="bugRef"
|
||||||
|
:case-id="activeCaseId"
|
||||||
:test-plan-id="route.query.id as string"
|
:test-plan-id="route.query.id as string"
|
||||||
|
@link="linkDefect"
|
||||||
|
@new="addBug"
|
||||||
/>
|
/>
|
||||||
<ExecutionHistory v-if="activeTab === 'executionHistory'" :case-id="caseDetail.id" />
|
<ExecutionHistory v-if="activeTab === 'executionHistory'" :case-id="activeCaseId" />
|
||||||
</div>
|
</div>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</div>
|
</div>
|
||||||
</MsCard>
|
</MsCard>
|
||||||
<EditCaseDetailDrawer v-model:visible="editCaseVisible" :case-id="activeCaseId" @load-case="loadCase" />
|
<EditCaseDetailDrawer v-model:visible="editCaseVisible" :case-id="activeCaseId" @load-case="loadCase" />
|
||||||
|
<LinkDefectDrawer
|
||||||
|
v-model:visible="showLinkDrawer"
|
||||||
|
:case-id="activeCaseId"
|
||||||
|
:drawer-loading="drawerLoading"
|
||||||
|
@save="associateSuccessHandler"
|
||||||
|
/>
|
||||||
|
<AddDefectDrawer
|
||||||
|
v-model:visible="showDrawer"
|
||||||
|
:case-id="activeCaseId"
|
||||||
|
::extra-params="{
|
||||||
|
testPlanCaseId: route.query.testPlanCaseId,
|
||||||
|
caseId: activeCaseId,
|
||||||
|
testPlanId:route.query.id as string,
|
||||||
|
}"
|
||||||
|
@success="addSuccess"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||||
|
@ -154,19 +200,31 @@
|
||||||
import MsEmpty from '@/components/pure/ms-empty/index.vue';
|
import MsEmpty from '@/components/pure/ms-empty/index.vue';
|
||||||
import MsPagination from '@/components/pure/ms-pagination/index';
|
import MsPagination from '@/components/pure/ms-pagination/index';
|
||||||
import MsTab from '@/components/pure/ms-tab/index.vue';
|
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||||
|
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||||
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
|
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
|
||||||
import MsStatusTag from '@/components/business/ms-status-tag/index.vue';
|
import MsStatusTag from '@/components/business/ms-status-tag/index.vue';
|
||||||
import BugList from './bug/index.vue';
|
import BugList from './bug/index.vue';
|
||||||
import ExecuteSubmit from './executeSubmit.vue';
|
import ExecuteSubmit from './executeSubmit.vue';
|
||||||
|
import AddDefectDrawer from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/addDefectDrawer.vue';
|
||||||
|
import LinkDefectDrawer from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/linkDefectDrawer.vue';
|
||||||
import CaseTabDetail from '@/views/case-management/caseManagementFeature/components/tabContent/tabDetail.vue';
|
import CaseTabDetail from '@/views/case-management/caseManagementFeature/components/tabContent/tabDetail.vue';
|
||||||
import EditCaseDetailDrawer from '@/views/case-management/caseReview/components/editCaseDetailDrawer.vue';
|
import EditCaseDetailDrawer from '@/views/case-management/caseReview/components/editCaseDetailDrawer.vue';
|
||||||
import ExecutionHistory from '@/views/test-plan/testPlan/detail/featureCase/detail/executionHistory/index.vue';
|
import ExecutionHistory from '@/views/test-plan/testPlan/detail/featureCase/detail/executionHistory/index.vue';
|
||||||
|
|
||||||
import { getCaseDetail, getPlanDetailFeatureCaseList, getTestPlanDetail } from '@/api/modules/test-plan/testPlan';
|
import { getBugList } from '@/api/modules/bug-management';
|
||||||
|
import {
|
||||||
|
associateBugToPlan,
|
||||||
|
associatedBugPage,
|
||||||
|
getCaseDetail,
|
||||||
|
getPlanDetailFeatureCaseList,
|
||||||
|
getTestPlanDetail,
|
||||||
|
} from '@/api/modules/test-plan/testPlan';
|
||||||
import { testPlanDefaultDetail } from '@/config/testPlan';
|
import { testPlanDefaultDetail } from '@/config/testPlan';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
|
||||||
|
import type { TableQueryParams } from '@/models/common';
|
||||||
import type { PlanDetailFeatureCaseItem, TestPlanDetail } from '@/models/testPlan/testPlan';
|
import type { PlanDetailFeatureCaseItem, TestPlanDetail } from '@/models/testPlan/testPlan';
|
||||||
import { LastExecuteResults } from '@/enums/caseEnum';
|
import { LastExecuteResults } from '@/enums/caseEnum';
|
||||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||||
|
@ -338,7 +396,6 @@
|
||||||
() => activeId.value,
|
() => activeId.value,
|
||||||
() => {
|
() => {
|
||||||
loadCaseDetail();
|
loadCaseDetail();
|
||||||
// TODO 更新历史列表
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -389,6 +446,95 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bugCount = ref<number>(0);
|
||||||
|
|
||||||
|
const showLinkDrawer = ref<boolean>(false);
|
||||||
|
const drawerLoading = ref<boolean>(false);
|
||||||
|
|
||||||
|
const showDrawer = ref<boolean>(false);
|
||||||
|
|
||||||
|
function addBug() {
|
||||||
|
showDrawer.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function linkDefect() {
|
||||||
|
showLinkDrawer.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelect(value: string | number | Record<string, any> | undefined) {
|
||||||
|
switch (value) {
|
||||||
|
case 'new':
|
||||||
|
addBug();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
linkDefect();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const bugRef = ref();
|
||||||
|
|
||||||
|
async function getBugTotal() {
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
testPlanCaseId: route.query.testPlanCaseId,
|
||||||
|
caseId: activeCaseId.value,
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
};
|
||||||
|
const res = await associatedBugPage(params);
|
||||||
|
bugCount.value = res.total;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSuccess() {
|
||||||
|
if (activeTab.value === 'defectList') {
|
||||||
|
bugRef.value?.initData();
|
||||||
|
} else {
|
||||||
|
getBugTotal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function associateSuccessHandler(params: TableQueryParams) {
|
||||||
|
try {
|
||||||
|
drawerLoading.value = true;
|
||||||
|
await associateBugToPlan({
|
||||||
|
...params,
|
||||||
|
caseId: activeCaseId.value,
|
||||||
|
testPlanId: route.query.id as string,
|
||||||
|
testPlanCaseId: route.query.testPlanCaseId as string,
|
||||||
|
});
|
||||||
|
Message.success(t('caseManagement.featureCase.associatedSuccess'));
|
||||||
|
showLinkDrawer.value = false;
|
||||||
|
addSuccess();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
drawerLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdBugCount = ref<number>(0);
|
||||||
|
|
||||||
|
async function initBugList() {
|
||||||
|
if (!hasAnyPermission(['PROJECT_BUG:READ'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = await getBugList({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
sort: {},
|
||||||
|
filter: {},
|
||||||
|
keyword: '',
|
||||||
|
combine: {},
|
||||||
|
searchMode: 'AND',
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
});
|
||||||
|
createdBugCount.value = res.total;
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
const lastPageParams = window.history.state.params ? JSON.parse(window.history.state.params) : null; // 获取上个页面带过来的表格查询参数
|
const lastPageParams = window.history.state.params ? JSON.parse(window.history.state.params) : null; // 获取上个页面带过来的表格查询参数
|
||||||
if (lastPageParams) {
|
if (lastPageParams) {
|
||||||
|
@ -404,7 +550,11 @@
|
||||||
moduleIds,
|
moduleIds,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (activeTab.value === 'detail') {
|
||||||
|
getBugTotal();
|
||||||
|
}
|
||||||
getPlanDetail();
|
getPlanDetail();
|
||||||
|
initBugList();
|
||||||
await loadCase();
|
await loadCase();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -76,4 +76,12 @@
|
||||||
function initModuleTree(tree: ModuleTreeNode[]) {
|
function initModuleTree(tree: ModuleTreeNode[]) {
|
||||||
moduleTree.value = unref(tree);
|
moduleTree.value = unref(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCaseTableList() {
|
||||||
|
caseTableRef.value?.loadCaseList();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getCaseTableList,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -101,11 +101,21 @@
|
||||||
</MsCard>
|
</MsCard>
|
||||||
<!-- special-height的174: 上面卡片高度158 + mt的16 -->
|
<!-- special-height的174: 上面卡片高度158 + mt的16 -->
|
||||||
<MsCard class="mt-[16px]" :special-height="174" simple has-breadcrumb no-content-padding>
|
<MsCard class="mt-[16px]" :special-height="174" simple has-breadcrumb no-content-padding>
|
||||||
<FeatureCase v-if="activeTab === 'featureCase'" :repeat-case="detail.repeatCase" @refresh="getStatistics" />
|
<FeatureCase
|
||||||
|
v-if="activeTab === 'featureCase'"
|
||||||
|
ref="featureCaseRef"
|
||||||
|
:repeat-case="detail.repeatCase"
|
||||||
|
@refresh="getStatistics"
|
||||||
|
/>
|
||||||
<!-- TODO 先不上 -->
|
<!-- TODO 先不上 -->
|
||||||
<!-- <BugManagement v-if="activeTab === 'defectList'" :plan-id="detail.id" /> -->
|
<!-- <BugManagement v-if="activeTab === 'defectList'" :plan-id="detail.id" /> -->
|
||||||
</MsCard>
|
</MsCard>
|
||||||
<AssociateDrawer v-model:visible="caseAssociateVisible" :associated-ids="hasSelectedIds" @success="success" />
|
<AssociateDrawer
|
||||||
|
v-model:visible="caseAssociateVisible"
|
||||||
|
:associated-ids="detail.repeatCase ? hasSelectedIds : []"
|
||||||
|
:save-api="associationCaseToPlan"
|
||||||
|
@success="handleSuccess"
|
||||||
|
/>
|
||||||
<CreateAndEditPlanDrawer
|
<CreateAndEditPlanDrawer
|
||||||
v-model:visible="showPlanDrawer"
|
v-model:visible="showPlanDrawer"
|
||||||
:plan-id="planId"
|
:plan-id="planId"
|
||||||
|
@ -117,7 +127,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
@ -153,12 +163,7 @@
|
||||||
import { characterLimit } from '@/utils';
|
import { characterLimit } from '@/utils';
|
||||||
|
|
||||||
import { ModuleTreeNode } from '@/models/common';
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
import type {
|
import type { PassRateCountDetail, TestPlanDetail, TestPlanItem } from '@/models/testPlan/testPlan';
|
||||||
AssociateCaseRequest,
|
|
||||||
PassRateCountDetail,
|
|
||||||
TestPlanDetail,
|
|
||||||
TestPlanItem,
|
|
||||||
} from '@/models/testPlan/testPlan';
|
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
@ -353,6 +358,7 @@
|
||||||
function successHandler() {
|
function successHandler() {
|
||||||
initDetail();
|
initDetail();
|
||||||
}
|
}
|
||||||
|
|
||||||
const testPlanTree = ref<ModuleTreeNode[]>([]);
|
const testPlanTree = ref<ModuleTreeNode[]>([]);
|
||||||
async function initPlanTree() {
|
async function initPlanTree() {
|
||||||
try {
|
try {
|
||||||
|
@ -362,19 +368,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关联用例到测试计划
|
const featureCaseRef = ref<InstanceType<typeof FeatureCase>>();
|
||||||
async function success(params: AssociateCaseRequest) {
|
function handleSuccess() {
|
||||||
try {
|
|
||||||
await associationCaseToPlan({
|
|
||||||
functionalSelectIds: params.selectIds,
|
|
||||||
testPlanId: planId.value,
|
|
||||||
});
|
|
||||||
Message.success(t('ms.case.associate.associateSuccess'));
|
|
||||||
caseAssociateVisible.value = false;
|
|
||||||
initDetail();
|
initDetail();
|
||||||
} catch (error) {
|
featureCaseRef.value?.getCaseTableList();
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
isExpandAll ? t('testPlan.testPlanIndex.collapseAll') : t('testPlan.testPlanIndex.expandAll')
|
isExpandAll ? t('testPlan.testPlanIndex.collapseAll') : t('testPlan.testPlanIndex.expandAll')
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" @click="expandHandler">
|
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" position="top" @click="expandHandler">
|
||||||
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
|
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|
|
@ -19,6 +19,7 @@ export default {
|
||||||
'testPlan.testPlanIndex.passRate': 'Pass Rate',
|
'testPlan.testPlanIndex.passRate': 'Pass Rate',
|
||||||
'testPlan.testPlanIndex.useCount': 'Use cases',
|
'testPlan.testPlanIndex.useCount': 'Use cases',
|
||||||
'testPlan.testPlanIndex.bugCount': 'bug count',
|
'testPlan.testPlanIndex.bugCount': 'bug count',
|
||||||
|
'testPlan.featureCase.bug': 'bug',
|
||||||
'testPlan.testPlanIndex.belongModule': 'belong module',
|
'testPlan.testPlanIndex.belongModule': 'belong module',
|
||||||
'testPlan.testPlanIndex.createTime': 'create time',
|
'testPlan.testPlanIndex.createTime': 'create time',
|
||||||
'testPlan.testPlanIndex.operation': 'operation',
|
'testPlan.testPlanIndex.operation': 'operation',
|
||||||
|
|
|
@ -88,6 +88,7 @@ export default {
|
||||||
'testPlan.bugManagement.defectState': '缺陷状态',
|
'testPlan.bugManagement.defectState': '缺陷状态',
|
||||||
'testPlan.bugManagement.caseClassification': '用例分类',
|
'testPlan.bugManagement.caseClassification': '用例分类',
|
||||||
'testPlan.featureCase.bugCount': '缺陷数',
|
'testPlan.featureCase.bugCount': '缺陷数',
|
||||||
|
'testPlan.featureCase.bug': '缺陷',
|
||||||
'testPlan.featureCase.executor': '执行人',
|
'testPlan.featureCase.executor': '执行人',
|
||||||
'testPlan.featureCase.changeExecutor': '修改执行人',
|
'testPlan.featureCase.changeExecutor': '修改执行人',
|
||||||
'testPlan.featureCase.batchChangeExecutor': '批量修改执行人',
|
'testPlan.featureCase.batchChangeExecutor': '批量修改执行人',
|
||||||
|
|
Loading…
Reference in New Issue