feat(测试计划): 测试计划table联调和细节完善以及测试计划用例详情缺陷管理列表tab
This commit is contained in:
parent
d4b86ac339
commit
22e9354a51
|
@ -3,13 +3,17 @@ import {
|
|||
addTestPlanModuleUrl,
|
||||
AddTestPlanUrl,
|
||||
archivedPlanUrl,
|
||||
associationCaseToPlanUrl,
|
||||
batchArchivedPlanUrl,
|
||||
batchCopyPlanUrl,
|
||||
batchDeletePlanUrl,
|
||||
BatchDisassociateCaseUrl,
|
||||
batchMovePlanUrl,
|
||||
copyTestPlanUrl,
|
||||
deletePlanUrl,
|
||||
DeleteTestPlanModuleUrl,
|
||||
DisassociateCaseUrl,
|
||||
followPlanUrl,
|
||||
GetFeatureCaseModuleCountUrl,
|
||||
GetFeatureCaseModuleUrl,
|
||||
GetPlanDetailFeatureCaseListUrl,
|
||||
|
@ -20,6 +24,7 @@ import {
|
|||
GetTestPlanModuleUrl,
|
||||
MoveTestPlanModuleUrl,
|
||||
planDetailBugPageUrl,
|
||||
planPassRateUrl,
|
||||
updateTestPlanModuleUrl,
|
||||
UpdateTestPlanUrl,
|
||||
} from '@/api/requrls/test-plan/testPlan';
|
||||
|
@ -29,8 +34,11 @@ import type { CommonList, MoveModules, TableQueryParams } from '@/models/common'
|
|||
import { ModuleTreeNode } from '@/models/common';
|
||||
import type {
|
||||
AddTestPlanParams,
|
||||
AssociateCaseRequestType,
|
||||
BatchFeatureCaseParams,
|
||||
DisassociateCaseParams,
|
||||
FollowPlanParams,
|
||||
PassRateCountDetail,
|
||||
PlanDetailBugItem,
|
||||
PlanDetailFeatureCaseItem,
|
||||
PlanDetailFeatureCaseListQueryParams,
|
||||
|
@ -78,6 +86,10 @@ export function getTestPlanList(data: TableQueryParams) {
|
|||
export function addTestPlan(data: AddTestPlanParams) {
|
||||
return MSR.post({ url: AddTestPlanUrl, data });
|
||||
}
|
||||
// 创建测试计划
|
||||
export function copyTestPlan(data: AddTestPlanParams) {
|
||||
return MSR.post({ url: copyTestPlanUrl, data });
|
||||
}
|
||||
|
||||
// 获取测试计划详情
|
||||
export function getTestPlanDetail(id: string) {
|
||||
|
@ -112,10 +124,26 @@ export function batchCopyPlan(data: TableQueryParams) {
|
|||
export function batchMovePlan(data: TableQueryParams) {
|
||||
return MSR.post({ url: batchMovePlanUrl, data });
|
||||
}
|
||||
// 批量移动测试计划
|
||||
export function batchArchivedPlan(data: TableQueryParams) {
|
||||
return MSR.post({ url: batchArchivedPlanUrl, data });
|
||||
}
|
||||
// 计划详情缺陷管理列表
|
||||
export function planDetailBugPage(data: TableQueryParams) {
|
||||
return MSR.post<CommonList<PlanDetailBugItem>>({ url: planDetailBugPageUrl, data });
|
||||
}
|
||||
// 关注
|
||||
export function followPlanRequest(data: FollowPlanParams) {
|
||||
return MSR.post({ url: followPlanUrl, data });
|
||||
}
|
||||
// 关联用例到测试计划
|
||||
export function associationCaseToPlan(data: AssociateCaseRequestType) {
|
||||
return MSR.post({ url: associationCaseToPlanUrl, data });
|
||||
}
|
||||
// 测试计划通过率执行进度
|
||||
export function getPlanPassRate(data: (string | undefined)[]) {
|
||||
return MSR.post<PassRateCountDetail[]>({ url: planPassRateUrl, data });
|
||||
}
|
||||
// 计划详情-功能用例列表
|
||||
export function getPlanDetailFeatureCaseList(data: PlanDetailFeatureCaseListQueryParams) {
|
||||
return MSR.post<CommonList<PlanDetailFeatureCaseItem>>({ url: GetPlanDetailFeatureCaseListUrl, data });
|
||||
|
|
|
@ -27,11 +27,21 @@ export const getStatisticalCountUrl = '/test-plan/getCount';
|
|||
// 归档
|
||||
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 batchArchivedPlanUrl = '/test-plan/batch-archived';
|
||||
// 计划详情缺陷管理列表
|
||||
export const planDetailBugPageUrl = '/test-plan/bug/page';
|
||||
// 关注测试计划
|
||||
export const followPlanUrl = '/test-plan/edit/follower';
|
||||
// 复制测试计划
|
||||
export const copyTestPlanUrl = '/test-plan/copy';
|
||||
// 关联测试计划
|
||||
export const associationCaseToPlanUrl = '/test-plan/association';
|
||||
// 测试计划通过率执行进度
|
||||
export const planPassRateUrl = '/test-plan/statistics';
|
||||
// 计划详情-功能用例列表
|
||||
export const GetPlanDetailFeatureCaseListUrl = '/test-plan/functional/case/page';
|
||||
// 计划详情-功能用例-获取模块数量
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { planStatusType, TestPlanDetail } from '@/models/testPlan/testPlan';
|
||||
import type { PassRateCountDetail, planStatusType, TestPlanDetail } from '@/models/testPlan/testPlan';
|
||||
|
||||
// TODO: 对照后端字段
|
||||
// 测试计划详情
|
||||
|
@ -23,4 +23,20 @@ export const testPlanDefaultDetail: TestPlanDetail = {
|
|||
underReviewedCount: 0,
|
||||
};
|
||||
|
||||
export const initDetailCount: PassRateCountDetail = {
|
||||
id: '',
|
||||
passThreshold: 0,
|
||||
passRate: 0,
|
||||
executeRate: 0,
|
||||
successCount: 0,
|
||||
errorCount: 0,
|
||||
fakeErrorCount: 0,
|
||||
blockCount: 0,
|
||||
pendingCount: 0,
|
||||
caseTotal: 0,
|
||||
functionalCaseCount: 0,
|
||||
apiCaseCount: 0,
|
||||
apiScenarioCount: 0,
|
||||
};
|
||||
|
||||
export default {};
|
||||
|
|
|
@ -38,6 +38,7 @@ export default {
|
|||
'common.editFailed': 'Edit failed',
|
||||
'common.saveSuccess': 'Save success',
|
||||
'common.saveFailed': 'Save failed',
|
||||
'common.associated': 'Associated',
|
||||
'common.linkSuccess': 'Link success',
|
||||
'common.unLinkSuccess': 'Unlink success',
|
||||
'common.confirmEnable': 'Confirm enable',
|
||||
|
@ -168,6 +169,8 @@ export default {
|
|||
'common.unExecute': 'Not executed',
|
||||
'common.pass': 'Pass',
|
||||
'common.unPass': 'Fail pass',
|
||||
'common.block': 'block',
|
||||
'common.fakeError': 'Fake error',
|
||||
'common.belongModule': 'Belong module',
|
||||
'common.moreSetting': 'More settings',
|
||||
};
|
||||
|
|
|
@ -39,6 +39,7 @@ export default {
|
|||
'common.editFailed': '编辑失败',
|
||||
'common.saveSuccess': '保存成功',
|
||||
'common.saveFailed': '保存失败',
|
||||
'common.associated': '关联',
|
||||
'common.linkSuccess': '关联成功',
|
||||
'common.cancelLink': '取消关联',
|
||||
'common.unLinkSuccess': '取消关联成功',
|
||||
|
@ -168,6 +169,8 @@ export default {
|
|||
'common.unExecute': '未执行',
|
||||
'common.pass': '通过',
|
||||
'common.unPass': '不通过',
|
||||
'common.block': '阻塞',
|
||||
'common.fakeError': '误报',
|
||||
'common.belongModule': '所属模块',
|
||||
'common.moreSetting': '更多设置',
|
||||
'common.executionResult': '执行结果',
|
||||
|
|
|
@ -29,8 +29,11 @@ export interface AssociateCaseRequest extends BatchApiParams {
|
|||
apiCaseSelectIds?: string[];
|
||||
apiScenarioSelectIds?: string[];
|
||||
totalCount?: number;
|
||||
testPlanId?: string;
|
||||
}
|
||||
|
||||
export type AssociateCaseRequestType = Pick<AssociateCaseRequest, 'functionalSelectIds' | 'testPlanId'>;
|
||||
|
||||
export interface AddTestPlanParams {
|
||||
id?: string;
|
||||
name: string;
|
||||
|
@ -45,11 +48,12 @@ export interface AddTestPlanParams {
|
|||
repeatCase: boolean; // 是否允许重复添加用例
|
||||
passThreshold: number;
|
||||
type?: string;
|
||||
baseAssociateCaseRequest?: AssociateCaseRequest;
|
||||
baseAssociateCaseRequest?: AssociateCaseRequest | null;
|
||||
groupOption?: boolean;
|
||||
cycle?: number[];
|
||||
projectId?: string;
|
||||
testPlanId?: string;
|
||||
functionalCaseCount?: number;
|
||||
}
|
||||
|
||||
// TODO: 对照后端字段
|
||||
|
@ -66,6 +70,7 @@ export interface TestPlanDetail extends AddTestPlanParams {
|
|||
unPassCount: number;
|
||||
reReviewedCount: number;
|
||||
underReviewedCount: number;
|
||||
functionalCaseCount?: number;
|
||||
}
|
||||
|
||||
// 计划分页
|
||||
|
@ -160,4 +165,20 @@ export interface BatchFeatureCaseParams extends BatchActionQueryParams {
|
|||
moduleIds?: string[];
|
||||
}
|
||||
|
||||
export interface PassRateCountDetail {
|
||||
id: string;
|
||||
passThreshold: number;
|
||||
passRate: number;
|
||||
executeRate: number;
|
||||
successCount: number;
|
||||
errorCount: number;
|
||||
fakeErrorCount: number;
|
||||
blockCount: number;
|
||||
pendingCount: number;
|
||||
caseTotal: number;
|
||||
functionalCaseCount: number;
|
||||
apiCaseCount: number;
|
||||
apiScenarioCount: number;
|
||||
}
|
||||
|
||||
export default {};
|
||||
|
|
|
@ -273,7 +273,6 @@
|
|||
getCaseDetail,
|
||||
getCaseModuleTree,
|
||||
} from '@/api/modules/case-management/featureCase';
|
||||
import { postTabletList } from '@/api/modules/project-management/menuManagement';
|
||||
import { PreviewEditorImageUrl } from '@/api/requrls/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
|
@ -282,7 +281,6 @@
|
|||
import useUserStore from '@/store/modules/user';
|
||||
import { characterLimit } from '@/utils';
|
||||
import { translateTextToPX } from '@/utils/css';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import type { CustomAttributes, DetailCase, TabItemType } from '@/models/caseManagement/featureCase';
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
<template>
|
||||
<ms-base-table ref="bugTableRef" v-bind="linkPropsRes" v-on="linkTableEvent">
|
||||
<template #num="{ record }">
|
||||
<span type="text" class="one-line-text cursor-pointer px-0 text-[rgb(var(--primary-5))]">{{ record.num }}</span>
|
||||
</template>
|
||||
<template #name="{ record }">
|
||||
<span class="one-line-text max-w-[150px]"> {{ characterLimit(record.name) }}</span>
|
||||
<a-popover title="" position="right" style="width: 480px">
|
||||
<span class="ml-1 text-[rgb(var(--primary-5))]">{{ t('caseManagement.featureCase.preview') }}</span>
|
||||
<template #content>
|
||||
<div v-dompurify-html="record.content" class="markdown-body" style="margin-left: 48px"> </div>
|
||||
</template>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template #severityFilter="{ columnConfig }">
|
||||
<TableFilter
|
||||
v-model:visible="severityFilterVisible"
|
||||
v-model:status-filters="severityFilterValue"
|
||||
:title="(columnConfig.title as string)"
|
||||
:list="severityFilterOptions"
|
||||
value-key="value"
|
||||
@search="searchData()"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
{{ item.text }}
|
||||
</template>
|
||||
</TableFilter>
|
||||
</template>
|
||||
<template #statusName="{ record }">
|
||||
<div class="one-line-text">{{ record.statusName || '-' }}</div>
|
||||
</template>
|
||||
<template #handleUserName="{ record }">
|
||||
<a-tooltip :content="record.handleUserName">
|
||||
<div class="one-line-text max-w-[200px]">{{ characterLimit(record.handleUserName) || '-' }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<template #operation="{ record }">
|
||||
<MsButton v-permission="['FUNCTIONAL_CASE:READ+UPDATE']" @click="cancelLink(record.id)">{{
|
||||
t('caseManagement.featureCase.cancelLink')
|
||||
}}</MsButton>
|
||||
</template>
|
||||
<template v-if="(keyword || '').trim() === ''" #empty>
|
||||
<div class="flex w-full items-center justify-center text-[var(--color-text-4)]">
|
||||
{{ t('caseManagement.featureCase.tableNoDataWidthComma') }}
|
||||
<span v-if="hasAnyPermission(['FUNCTIONAL_CASE:READ+UPDATE', 'PROJECT_BUG:READ+ADD'])">{{
|
||||
t('caseManagement.featureCase.please')
|
||||
}}</span>
|
||||
<MsButton
|
||||
v-if="hasAnyPermission(['FUNCTIONAL_CASE:READ+UPDATE'])"
|
||||
:disabled="!props.bugTotal"
|
||||
class="ml-[8px]"
|
||||
@click="linkDefect"
|
||||
>
|
||||
{{ t('caseManagement.featureCase.linkDefect') }}
|
||||
</MsButton>
|
||||
<span v-if="hasAnyPermission(['PROJECT_BUG:READ+ADD'])">{{ t('caseManagement.featureCase.or') }}</span>
|
||||
<MsButton v-permission="['PROJECT_BUG:READ+ADD']" class="ml-[8px]" @click="createDefect">
|
||||
{{ t('caseManagement.featureCase.createDefect') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import TableFilter from '@/views/case-management/caseManagementFeature/components/tableFilter.vue';
|
||||
|
||||
import { getLinkedCaseBugList } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
import { characterLimit } from '@/utils';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import { BugOptionItem } from '@/models/bug-management';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
caseId: string;
|
||||
keyword: string;
|
||||
bugColumns: MsTableColumn;
|
||||
bugTotal: number; // 平台缺陷总数决定是否新建还是关联
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'link'): void;
|
||||
(e: 'new'): void;
|
||||
(e: 'cancelLink', bugId: string): void;
|
||||
}>();
|
||||
|
||||
const severityFilterVisible = ref(false);
|
||||
const severityFilterOptions = ref<BugOptionItem[]>([]);
|
||||
const bugTableRef = ref();
|
||||
const {
|
||||
propsRes: linkPropsRes,
|
||||
propsEvent: linkTableEvent,
|
||||
loadList: loadLinkList,
|
||||
setLoadListParams: setLinkListParams,
|
||||
} = useTable(getLinkedCaseBugList, {
|
||||
columns: props.bugColumns,
|
||||
scroll: { x: 'auto' },
|
||||
heightUsed: 340,
|
||||
enableDrag: false,
|
||||
});
|
||||
const severityColumnId = ref('');
|
||||
const severityFilterValue = ref<string[]>([]);
|
||||
function initTableParams() {
|
||||
const filterParams: Record<string, any> = {
|
||||
// status: statusFilterValue.value,
|
||||
// handleUser: handleUserFilterValue.value,
|
||||
};
|
||||
// TODO 不知道干啥的 要和后台同学确认一下
|
||||
filterParams[severityColumnId.value] = severityFilterValue.value;
|
||||
return {
|
||||
keyword: props.keyword,
|
||||
caseId: props.caseId,
|
||||
projectId: appStore.currentProjectId,
|
||||
condition: {
|
||||
keyword: props.keyword,
|
||||
filter: linkPropsRes.value.filter,
|
||||
},
|
||||
};
|
||||
}
|
||||
function searchData() {
|
||||
setLinkListParams(initTableParams());
|
||||
loadLinkList();
|
||||
}
|
||||
|
||||
function linkDefect() {
|
||||
emit('link');
|
||||
}
|
||||
|
||||
function createDefect() {
|
||||
emit('new');
|
||||
}
|
||||
|
||||
function cancelLink(id: string) {
|
||||
emit('cancelLink', id);
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
searchData();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
searchData,
|
||||
bugTableRef,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -76,7 +76,7 @@
|
|||
{
|
||||
title: 'caseManagement.featureCase.tableColumnID',
|
||||
dataIndex: 'num',
|
||||
width: 200,
|
||||
width: 150,
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
ellipsis: true,
|
||||
|
@ -88,7 +88,7 @@
|
|||
dataIndex: 'name',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 300,
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
showDrag: false,
|
||||
},
|
||||
|
@ -99,7 +99,7 @@
|
|||
dataIndex: 'statusName',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 300,
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
showDrag: false,
|
||||
},
|
||||
|
@ -109,6 +109,7 @@
|
|||
dataIndex: 'tags',
|
||||
showInTable: true,
|
||||
isTag: true,
|
||||
width: 300,
|
||||
showDrag: true,
|
||||
},
|
||||
{
|
||||
|
@ -117,7 +118,7 @@
|
|||
dataIndex: 'handleUserName',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 300,
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
showDrag: false,
|
||||
},
|
||||
|
@ -127,7 +128,7 @@
|
|||
dataIndex: 'createUser',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 300,
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
|
@ -146,7 +147,6 @@
|
|||
columns,
|
||||
tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_DEFECT,
|
||||
selectable: true,
|
||||
scroll: { x: 'auto' },
|
||||
heightUsed: 340,
|
||||
enableDrag: false,
|
||||
},
|
||||
|
|
|
@ -56,65 +56,17 @@
|
|||
></a-input-search>
|
||||
</div>
|
||||
</div>
|
||||
<ms-base-table v-if="showType === 'link'" ref="bugTableRef" v-bind="linkPropsRes" v-on="linkTableEvent">
|
||||
<template #name="{ record }">
|
||||
<span class="one-line-text max-w-[150px]"> {{ characterLimit(record.name) }}</span>
|
||||
<a-popover title="" position="right" style="width: 480px">
|
||||
<span class="ml-1 text-[rgb(var(--primary-5))]">{{ t('caseManagement.featureCase.preview') }}</span>
|
||||
<template #content>
|
||||
<div v-dompurify-html="record.content" class="markdown-body" style="margin-left: 48px"> </div>
|
||||
</template>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template #severityFilter="{ columnConfig }">
|
||||
<TableFilter
|
||||
v-model:visible="severityFilterVisible"
|
||||
v-model:status-filters="severityFilterValue"
|
||||
:title="(columnConfig.title as string)"
|
||||
:list="severityFilterOptions"
|
||||
value-key="value"
|
||||
@search="searchData()"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
{{ item.text }}
|
||||
</template>
|
||||
</TableFilter>
|
||||
</template>
|
||||
<template #statusName="{ record }">
|
||||
<div class="one-line-text">{{ record.statusName }}</div>
|
||||
</template>
|
||||
<template #handleUserName="{ record }">
|
||||
<a-tooltip :content="record.handleUserName">
|
||||
<div class="one-line-text max-w-[200px]">{{ characterLimit(record.handleUserName) }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<template #operation="{ record }">
|
||||
<MsButton v-permission="['FUNCTIONAL_CASE:READ+UPDATE']" @click="cancelLink(record.id)">{{
|
||||
t('caseManagement.featureCase.cancelLink')
|
||||
}}</MsButton>
|
||||
</template>
|
||||
<template v-if="(keyword || '').trim() === ''" #empty>
|
||||
<div class="flex w-full items-center justify-center text-[var(--color-text-4)]">
|
||||
{{ t('caseManagement.featureCase.tableNoDataWidthComma') }}
|
||||
<span v-if="hasAnyPermission(['FUNCTIONAL_CASE:READ+UPDATE', 'PROJECT_BUG:READ+ADD'])">{{
|
||||
t('caseManagement.featureCase.please')
|
||||
}}</span>
|
||||
<MsButton
|
||||
v-if="hasAnyPermission(['FUNCTIONAL_CASE:READ+UPDATE'])"
|
||||
:disabled="!total"
|
||||
class="ml-[8px]"
|
||||
@click="linkDefect"
|
||||
>
|
||||
{{ t('caseManagement.featureCase.linkDefect') }}
|
||||
</MsButton>
|
||||
<span v-if="hasAnyPermission(['PROJECT_BUG:READ+ADD'])">{{ t('caseManagement.featureCase.or') }}</span>
|
||||
<MsButton v-permission="['PROJECT_BUG:READ+ADD']" class="ml-[8px]" @click="createDefect">
|
||||
{{ t('caseManagement.featureCase.createDefect') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
<BugList
|
||||
v-if="showType === 'link'"
|
||||
ref="bugTableListRef"
|
||||
:case-id="props.caseId"
|
||||
:keyword="keyword"
|
||||
:bug-total="total"
|
||||
:bug-columns="columns"
|
||||
@link="linkDefect"
|
||||
@new="createDefect"
|
||||
@cancel-link="cancelLink"
|
||||
/>
|
||||
<ms-base-table v-else v-bind="testPlanPropsRes" ref="planTableRef" v-on="testPlanTableEvent">
|
||||
<template #name="{ record }">
|
||||
<div class="one-line-text max-w-[300px]"> {{ record.name }}</div>
|
||||
|
@ -143,7 +95,7 @@
|
|||
:title="(columnConfig.title as string)"
|
||||
:list="severityFilterOptions"
|
||||
value-key="value"
|
||||
@search="searchData()"
|
||||
@search="getFetch()"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
{{ item.text }}
|
||||
|
@ -182,6 +134,7 @@
|
|||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import AddDefectDrawer from './addDefectDrawer.vue';
|
||||
import BugList from './bugList.vue';
|
||||
import LinkDefectDrawer from './linkDefectDrawer.vue';
|
||||
import TableFilter from '@/views/case-management/caseManagementFeature/components/tableFilter.vue';
|
||||
|
||||
|
@ -200,7 +153,8 @@
|
|||
import { BugListItem, BugOptionItem } from '@/models/bug-management';
|
||||
import type { TableQueryParams } from '@/models/common';
|
||||
import { TestPlanRouteEnum } from '@/enums/routeEnum';
|
||||
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||
|
||||
import { makeColumns } from '@/views/case-management/caseManagementFeature/components/utils';
|
||||
|
||||
const featureCaseStore = useFeatureCaseStore();
|
||||
|
||||
|
@ -210,7 +164,7 @@
|
|||
const props = defineProps<{
|
||||
caseId: string;
|
||||
}>();
|
||||
// const activeTab = computed(() => featureCaseStore.activeTab);
|
||||
|
||||
const showType = ref('link');
|
||||
|
||||
const keyword = ref<string>('');
|
||||
|
@ -291,17 +245,6 @@
|
|||
showDrag: false,
|
||||
},
|
||||
];
|
||||
const {
|
||||
propsRes: linkPropsRes,
|
||||
propsEvent: linkTableEvent,
|
||||
loadList: loadLinkList,
|
||||
setLoadListParams: setLinkListParams,
|
||||
} = useTable(getLinkedCaseBugList, {
|
||||
columns,
|
||||
scroll: { x: 'auto' },
|
||||
heightUsed: 340,
|
||||
enableDrag: false,
|
||||
});
|
||||
|
||||
const testPlanColumns: MsTableColumn = [
|
||||
{
|
||||
|
@ -378,58 +321,31 @@
|
|||
filterParams[severityColumnId.value] = severityFilterValue.value;
|
||||
return {
|
||||
keyword: keyword.value,
|
||||
caseId: showType.value === 'link' ? props.caseId : null,
|
||||
testPlanCaseId: showType.value === 'link' ? null : props.caseId,
|
||||
testPlanCaseId: props.caseId,
|
||||
projectId: appStore.currentProjectId,
|
||||
condition: {
|
||||
keyword: keyword.value,
|
||||
filter: showType.value === 'link' ? linkPropsRes : 'testPlanPropsRes',
|
||||
filter: testPlanPropsRes.value.filter,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function searchData() {
|
||||
if (showType.value === 'link') {
|
||||
setLinkListParams(initTableParams());
|
||||
loadLinkList();
|
||||
} else {
|
||||
setTestPlanListParams(initTableParams());
|
||||
testPlanLinkList();
|
||||
}
|
||||
}
|
||||
|
||||
const bugTableRef = ref();
|
||||
const bugTableListRef = ref();
|
||||
const planTableRef = ref();
|
||||
|
||||
function makeColumns(columnData: MsTableColumn) {
|
||||
const optionsMap: Record<string, any> = {
|
||||
status: statusFilterOptions.value,
|
||||
handleUser: handleUserFilterOptions.value,
|
||||
};
|
||||
return columnData.map((e) => {
|
||||
if (Object.prototype.hasOwnProperty.call(optionsMap, e.dataIndex as string)) {
|
||||
return {
|
||||
...e,
|
||||
filterConfig: {
|
||||
...e.filterConfig,
|
||||
options: optionsMap[e.dataIndex as string],
|
||||
},
|
||||
};
|
||||
}
|
||||
return { ...e };
|
||||
});
|
||||
}
|
||||
async function initFilterOptions() {
|
||||
if (hasAnyPermission(['PROJECT_BUG:READ'])) {
|
||||
const res = await getCustomOptionHeader(appStore.currentProjectId);
|
||||
handleUserFilterOptions.value = res.handleUserOption;
|
||||
statusFilterOptions.value = res.statusOption;
|
||||
|
||||
const optionsMap: Record<string, any> = {
|
||||
status: statusFilterOptions.value,
|
||||
handleUser: handleUserFilterOptions.value,
|
||||
};
|
||||
if (showType.value === 'link') {
|
||||
const columnList = makeColumns(columns);
|
||||
bugTableRef.value.initColumn(columnList);
|
||||
const columnList = makeColumns(optionsMap, columns);
|
||||
bugTableListRef.value.bugTableRef.initColumn(columnList);
|
||||
} else {
|
||||
const planColumnList = makeColumns(testPlanColumns);
|
||||
const planColumnList = makeColumns(optionsMap, testPlanColumns);
|
||||
planTableRef.value.initColumn(planColumnList);
|
||||
}
|
||||
}
|
||||
|
@ -440,31 +356,21 @@
|
|||
return;
|
||||
}
|
||||
if (showType.value === 'link') {
|
||||
setLinkListParams({ keyword: keyword.value, projectId: appStore.currentProjectId, caseId: props.caseId });
|
||||
await loadLinkList();
|
||||
const { msPagination } = linkPropsRes.value;
|
||||
featureCaseStore.setListCount(featureCaseStore.activeTab, msPagination?.total || 0);
|
||||
bugTableListRef.value?.searchData();
|
||||
} else {
|
||||
setTestPlanListParams({
|
||||
keyword: keyword.value,
|
||||
projectId: appStore.currentProjectId,
|
||||
testPlanCaseId: props.caseId,
|
||||
});
|
||||
setTestPlanListParams(initTableParams());
|
||||
await testPlanLinkList();
|
||||
featureCaseStore.getCaseCounts(props.caseId);
|
||||
}
|
||||
featureCaseStore.getCaseCounts(props.caseId);
|
||||
}
|
||||
async function resetFetch() {
|
||||
if (showType.value === 'link') {
|
||||
setLinkListParams({ keyword: '', projectId: appStore.currentProjectId, caseId: props.caseId });
|
||||
await loadLinkList();
|
||||
const { msPagination } = linkPropsRes.value;
|
||||
featureCaseStore.setListCount(featureCaseStore.activeTab, msPagination?.total || 0);
|
||||
bugTableListRef.value?.searchData();
|
||||
} else {
|
||||
setTestPlanListParams({ keyword: '', projectId: appStore.currentProjectId, testPlanCaseId: props.caseId });
|
||||
await testPlanLinkList();
|
||||
featureCaseStore.getCaseCounts(props.caseId);
|
||||
}
|
||||
featureCaseStore.getCaseCounts(props.caseId);
|
||||
}
|
||||
const cancelLoading = ref<boolean>(false);
|
||||
// 取消关联
|
||||
|
@ -473,8 +379,8 @@
|
|||
try {
|
||||
if (showType.value === 'link') {
|
||||
await cancelAssociatedDebug(id);
|
||||
getFetch();
|
||||
Message.success(t('caseManagement.featureCase.cancelLinkSuccess'));
|
||||
getFetch();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
@ -566,13 +472,10 @@
|
|||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
getFetch();
|
||||
initBugList();
|
||||
});
|
||||
|
||||
onBeforeMount(() => {
|
||||
initFilterOptions();
|
||||
getFetch();
|
||||
initBugList();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -59,7 +59,6 @@
|
|||
import { ReviewCaseItem, ReviewStatus } from '@/models/caseManagement/caseReview';
|
||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||
|
||||
import { statusIconMap } from '../utils';
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { FormItem } from '@/components/pure/ms-form-create/types';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import { MsTableColumnData } from '@/components/pure/ms-table/type';
|
||||
import { getFileEnum } from '@/components/pure/ms-upload/iconMap';
|
||||
import type { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||
|
@ -253,3 +254,22 @@ export function initFormCreate(customFields: CustomAttributes[], permission: str
|
|||
};
|
||||
}) as FormItem[];
|
||||
}
|
||||
|
||||
export function makeColumns(optionsMap: Record<string, any>, columnData: MsTableColumn) {
|
||||
// const optionsMap: Record<string, any> = {
|
||||
// status: statusFilterOptions.value,
|
||||
// handleUser: handleUserFilterOptions.value,
|
||||
// };
|
||||
return columnData.map((e) => {
|
||||
if (Object.prototype.hasOwnProperty.call(optionsMap, e.dataIndex as string)) {
|
||||
return {
|
||||
...e,
|
||||
filterConfig: {
|
||||
...e.filterConfig,
|
||||
options: optionsMap[e.dataIndex as string],
|
||||
},
|
||||
};
|
||||
}
|
||||
return { ...e };
|
||||
});
|
||||
}
|
||||
|
|
|
@ -250,7 +250,7 @@
|
|||
return Object.keys(reviewStatusMap).map((key) => {
|
||||
return {
|
||||
value: key,
|
||||
label: reviewStatusMap[key as ReviewStatus].label,
|
||||
label: t(reviewStatusMap[key as ReviewStatus].label),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -48,19 +48,19 @@
|
|||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { characterLimit } from '@/utils';
|
||||
|
||||
import type { TestPlanItem } from '@/models/testPlan/testPlan';
|
||||
import type { TestPlanDetail, TestPlanItem } from '@/models/testPlan/testPlan';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
// isScheduled: boolean; // TODO 这个版本不做有无定时任务区分
|
||||
record: TestPlanItem | undefined; // 表record
|
||||
record: TestPlanItem | TestPlanDetail | undefined; // 表record
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void;
|
||||
(e: 'success'): void;
|
||||
(e: 'success', isDelete: boolean): void;
|
||||
}>();
|
||||
|
||||
const showModalVisible = useVModel(props, 'visible', emit);
|
||||
|
@ -76,11 +76,13 @@
|
|||
confirmLoading.value = true;
|
||||
if (isDelete) {
|
||||
await deletePlan(props.record?.id);
|
||||
emit('success', true);
|
||||
} else {
|
||||
await archivedPlan(props.record?.id);
|
||||
emit('success', false);
|
||||
}
|
||||
Message.success(isDelete ? t('common.deleteSuccess') : t('common.batchArchiveSuccess'));
|
||||
emit('success');
|
||||
showModalVisible.value = false;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
|
|
|
@ -5,12 +5,11 @@
|
|||
:get-modules-func="getCaseModuleTree"
|
||||
:get-table-func="getCaseList"
|
||||
:confirm-loading="confirmLoading"
|
||||
:associated-ids="[]"
|
||||
:associated-ids="props.hasNotAssociatedIds || []"
|
||||
:project-id="currentProjectId"
|
||||
:type="RequestModuleEnum.CASE_MANAGEMENT"
|
||||
hide-project-select
|
||||
is-hidden-case-level
|
||||
:has-not-associated-ids="props.hasNotAssociatedIds"
|
||||
@save="saveHandler"
|
||||
>
|
||||
</MsCaseAssociate>
|
||||
|
|
|
@ -102,15 +102,22 @@
|
|||
<template #status="{ record }">
|
||||
<MsStatusTag :status="record.status" />
|
||||
</template>
|
||||
<template #moduleId="{ record }">
|
||||
<a-tooltip :content="getModules(record.moduleId, props.moduleTree)" position="top">
|
||||
<span class="one-line-text inline-block">
|
||||
{{ getModules(record.moduleId, props.moduleTree) }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<!-- <template #passRate="{ record }">
|
||||
<template #passRate="{ record }">
|
||||
<div class="mr-[8px] w-[100px]">
|
||||
<StatusProgress :status-detail="record.statusDetail" height="5px" />
|
||||
<StatusProgress :status-detail="initDefaultCountDetailMap[record.id]" height="5px" />
|
||||
</div>
|
||||
<div class="text-[var(--color-text-1)]">
|
||||
{{ `${record.passRate || 0}%` }}
|
||||
</div>
|
||||
</template> -->
|
||||
</template>
|
||||
<template #passRateTitleSlot="{ columnConfig }">
|
||||
<div class="flex items-center text-[var(--color-text-3)]">
|
||||
{{ t(columnConfig.title as string) }}
|
||||
|
@ -122,17 +129,17 @@
|
|||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<!-- <template #useCount="{ record }">
|
||||
<a-popover position="bottom" content-class="p-[16px]" trigger="click">
|
||||
<div>{{ record.useCaseCount.caseCount }}</div>
|
||||
<template #functionalCaseCount="{ record }">
|
||||
<a-popover position="bottom" content-class="p-[16px]" :disabled="record.functionalCaseCount < 1">
|
||||
<div>{{ record.functionalCaseCount }}</div>
|
||||
<template #content>
|
||||
<table class="min-w-[144px]">
|
||||
<table class="min-w-[140px] max-w-[176px]">
|
||||
<tr>
|
||||
<td class="popover-label-td">
|
||||
<div>{{ t('testPlan.testPlanIndex.TotalCases') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ record.useCaseCount.caseCount }}
|
||||
{{ record.caseTotal }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -140,7 +147,7 @@
|
|||
<div class="text-[var(--color-text-1)]">{{ t('testPlan.testPlanIndex.functionalUseCase') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ record.useCaseCount.caseCount }}
|
||||
{{ record.functionalCaseCount }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -148,7 +155,7 @@
|
|||
<div class="text-[var(--color-text-1)]">{{ t('testPlan.testPlanIndex.apiCase') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ record.useCaseCount.caseCount }}
|
||||
{{ record.apiCaseCount }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -156,25 +163,41 @@
|
|||
<div class="text-[var(--color-text-1)]">{{ t('testPlan.testPlanIndex.apiScenarioCase') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ record.useCaseCount.caseCount }}
|
||||
{{ record.apiScenarioCount }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
</a-popover>
|
||||
</template> -->
|
||||
</template>
|
||||
|
||||
<template #operation="{ record }">
|
||||
<div class="flex items-center">
|
||||
<MsButton class="!mx-0">{{ t('testPlan.testPlanIndex.execution') }}</MsButton>
|
||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||
|
||||
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+UPDATE']" class="!mx-0" @click="emit('edit', record.id)">{{
|
||||
t('common.edit')
|
||||
<MsButton v-if="record.functionalCaseCount > 0" class="!mx-0">{{
|
||||
t('testPlan.testPlanIndex.execution')
|
||||
}}</MsButton>
|
||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||
<a-divider v-if="record.functionalCaseCount > 0" direction="vertical" :margin="8"></a-divider>
|
||||
|
||||
<MsTableMoreAction :list="moreActions" @select="handleMoreActionSelect($event, record)" />
|
||||
<MsButton
|
||||
v-permission="['PROJECT_TEST_PLAN:READ+UPDATE']"
|
||||
class="!mx-0"
|
||||
@click="emit('editOrCopy', record.id, false)"
|
||||
>{{ t('common.edit') }}</MsButton
|
||||
>
|
||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||
<MsButton
|
||||
v-if="record.functionalCaseCount < 1"
|
||||
v-permission="['PROJECT_TEST_PLAN:READ+UPDATE']"
|
||||
class="!mx-0"
|
||||
@click="emit('editOrCopy', record.id, true)"
|
||||
>{{ t('common.copy') }}</MsButton
|
||||
>
|
||||
<a-divider v-if="record.functionalCaseCount < 1" direction="vertical" :margin="8"></a-divider>
|
||||
|
||||
<MsTableMoreAction
|
||||
:list="getMoreActions(record.status, record.functionalCaseCount)"
|
||||
@select="handleMoreActionSelect($event, record)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
|
@ -250,9 +273,11 @@
|
|||
|
||||
import {
|
||||
archivedPlan,
|
||||
batchArchivedPlan,
|
||||
batchCopyPlan,
|
||||
batchDeletePlan,
|
||||
batchMovePlan,
|
||||
getPlanPassRate,
|
||||
getTestPlanDetail,
|
||||
getTestPlanList,
|
||||
getTestPlanModule,
|
||||
|
@ -264,13 +289,15 @@
|
|||
import { characterLimit } from '@/utils';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import type { planStatusType, TestPlanItem } from '@/models/testPlan/testPlan';
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
import type { PassRateCountDetail, planStatusType, TestPlanItem } from '@/models/testPlan/testPlan';
|
||||
import { TestPlanRouteEnum } from '@/enums/routeEnum';
|
||||
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
|
||||
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||
import { testPlanTypeEnum } from '@/enums/testPlanEnum';
|
||||
|
||||
import { planStatusOptions } from '../config';
|
||||
import { getModules } from '@/views/case-management/caseManagementFeature/components/utils';
|
||||
|
||||
const tableStore = useTableStore();
|
||||
const appStore = useAppStore();
|
||||
|
@ -284,11 +311,12 @@
|
|||
offspringIds: string[]; // 当前选中文件夹的所有子孙节点id
|
||||
modulesCount: Record<string, number>; // 模块数量
|
||||
nodeName: string; // 选中模块名称
|
||||
moduleTree: ModuleTreeNode[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'init', params: any): void;
|
||||
(e: 'edit', id: string): void;
|
||||
(e: 'editOrCopy', id: string, isCopy: boolean): void;
|
||||
}>();
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
|
@ -355,9 +383,8 @@
|
|||
},
|
||||
{
|
||||
title: 'testPlan.testPlanIndex.useCount',
|
||||
slotName: 'useCount',
|
||||
dataIndex: 'useCount',
|
||||
showTooltip: true,
|
||||
slotName: 'functionalCaseCount',
|
||||
dataIndex: 'functionalCaseCount',
|
||||
showInTable: true,
|
||||
width: 150,
|
||||
showDrag: true,
|
||||
|
@ -373,8 +400,8 @@
|
|||
},
|
||||
{
|
||||
title: 'testPlan.testPlanIndex.belongModule',
|
||||
slotName: 'moduleName',
|
||||
dataIndex: 'moduleName',
|
||||
slotName: 'moduleId',
|
||||
dataIndex: 'moduleId',
|
||||
showInTable: true,
|
||||
showDrag: true,
|
||||
width: 200,
|
||||
|
@ -508,34 +535,45 @@
|
|||
],
|
||||
};
|
||||
|
||||
const moreActions: ActionsItem[] = [
|
||||
{
|
||||
label: 'common.copy',
|
||||
eventTag: 'copy',
|
||||
},
|
||||
// TODO 这个版本不上
|
||||
// {
|
||||
// label: 'testPlan.testPlanIndex.createScheduledTask',
|
||||
// eventTag: 'createScheduledTask',
|
||||
// },
|
||||
// {
|
||||
// label: 'testPlan.testPlanIndex.configuration',
|
||||
// eventTag: 'config',
|
||||
// },
|
||||
const archiveActions: ActionsItem[] = [
|
||||
{
|
||||
label: 'common.archive',
|
||||
eventTag: 'archive',
|
||||
},
|
||||
];
|
||||
const copyActions: ActionsItem[] = [
|
||||
{
|
||||
isDivider: true,
|
||||
},
|
||||
{
|
||||
label: 'common.delete',
|
||||
danger: true,
|
||||
eventTag: 'delete',
|
||||
label: 'common.copy',
|
||||
eventTag: 'copy',
|
||||
},
|
||||
];
|
||||
|
||||
function getMoreActions(status: planStatusType, useCount: number) {
|
||||
const copyAction = useCount > 0 ? copyActions : [];
|
||||
if (status === 'COMPLETED' || status === 'ARCHIVED') {
|
||||
return [
|
||||
...copyAction,
|
||||
{
|
||||
isDivider: true,
|
||||
},
|
||||
{
|
||||
label: 'common.delete',
|
||||
danger: true,
|
||||
eventTag: 'delete',
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
...copyAction,
|
||||
...archiveActions,
|
||||
{
|
||||
label: 'common.delete',
|
||||
danger: true,
|
||||
eventTag: 'delete',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
||||
getTestPlanList,
|
||||
{
|
||||
|
@ -607,6 +645,19 @@
|
|||
});
|
||||
}
|
||||
|
||||
const initDefaultCountDetailMap = ref<Record<string, PassRateCountDetail>>({});
|
||||
|
||||
async function getStatistics(selectedPlanIds: (string | undefined)[]) {
|
||||
try {
|
||||
const result = await getPlanPassRate(selectedPlanIds);
|
||||
result.forEach((item: PassRateCountDetail) => {
|
||||
initDefaultCountDetailMap.value[item.id] = item;
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchData() {
|
||||
resetSelector();
|
||||
await loadPlanList();
|
||||
|
@ -711,7 +762,18 @@
|
|||
},
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
const { selectedIds, selectAll, excludeIds } = batchParams.value;
|
||||
await batchArchivedPlan({
|
||||
selectIds: batchParams.value.selectedIds || [],
|
||||
condition: {
|
||||
keyword: keyword.value,
|
||||
filter: propsRes.value.filter,
|
||||
combine: batchParams.value.condition,
|
||||
},
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
|
||||
type: showType.value,
|
||||
moduleId: props.activeFolder,
|
||||
});
|
||||
Message.success(t('common.batchArchiveSuccess'));
|
||||
fetchData();
|
||||
} catch (error) {
|
||||
|
@ -811,9 +873,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
function deletePlan(record: TestPlanItem) {}
|
||||
|
||||
function copyHandler(record: TestPlanItem) {}
|
||||
function copyHandler(record: TestPlanItem) {
|
||||
emit('editOrCopy', record.id as string, true);
|
||||
}
|
||||
|
||||
const showScheduledTaskModal = ref<boolean>(false);
|
||||
function handleScheduledTask() {
|
||||
|
@ -821,7 +883,7 @@
|
|||
}
|
||||
|
||||
const showStatusDeleteModal = ref<boolean>(false);
|
||||
const activeRecord = ref<TestPlanItem>();
|
||||
const activeRecord = ref<TestPlanItem | undefined>();
|
||||
function deleteStatusHandler(record: TestPlanItem) {
|
||||
activeRecord.value = cloneDeep(record);
|
||||
showStatusDeleteModal.value = true;
|
||||
|
@ -884,19 +946,6 @@
|
|||
// return expandedKeys.value.includes(record.id) ? 'text-[rgb(var(--primary-5))]' : 'text-[var(--color-text-4)]';
|
||||
// }
|
||||
|
||||
function handleFilterHidden(val: boolean) {
|
||||
if (!val) {
|
||||
statusFilterVisible.value = false;
|
||||
fetchData();
|
||||
}
|
||||
}
|
||||
|
||||
function resetStatusFilter() {
|
||||
statusFilterVisible.value = false;
|
||||
statusFilters.value = [];
|
||||
fetchData();
|
||||
}
|
||||
|
||||
/** *
|
||||
* 高级检索
|
||||
*/
|
||||
|
@ -926,6 +975,25 @@
|
|||
fetchData();
|
||||
});
|
||||
|
||||
const planData = computed(() => {
|
||||
return propsRes.value.data;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => planData.value,
|
||||
(val) => {
|
||||
if (val) {
|
||||
const selectedPlanIds: (string | undefined)[] = propsRes.value.data.map((e) => e.id) || [];
|
||||
if (selectedPlanIds.length) {
|
||||
getStatistics(selectedPlanIds);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
fetchData,
|
||||
emitTableParams,
|
||||
|
|
|
@ -4,18 +4,50 @@
|
|||
<table class="min-w-[144px]">
|
||||
<tr>
|
||||
<td class="popover-label-td">
|
||||
<div>{{ t('testPlan.testPlanIndex.tolerance') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ props.statusDetail.tolerance }}
|
||||
<div>{{ t('testPlan.testPlanIndex.threshold') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td"> {{ detailCount.passThreshold }}% </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="popover-label-td">
|
||||
<div>{{ t('testPlan.testPlanIndex.executionProgress') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td"> {{ detailCount.executeRate }}% </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="popover-label-td">
|
||||
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--success-6))]"></div>
|
||||
<div>{{ t('common.success') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ props.statusDetail.executionProgress }}
|
||||
{{ detailCount.successCount }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="popover-label-td">
|
||||
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--danger-6))]"></div>
|
||||
<div>{{ t('common.fail') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ detailCount.errorCount }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="popover-label-td">
|
||||
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--warning-6))]"></div>
|
||||
<div>{{ t('common.fakeError') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ detailCount.fakeErrorCount }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="popover-label-td">
|
||||
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--link-6))]"></div>
|
||||
<div>{{ t('common.block') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ detailCount.blockCount }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -24,34 +56,7 @@
|
|||
<div>{{ t('common.unExecute') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ props.statusDetail.UNPENDING }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="popover-label-td">
|
||||
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--link-6))]"></div>
|
||||
<div>{{ t('common.running') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ props.statusDetail.RUNNING }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="popover-label-td">
|
||||
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--success-6))]"></div>
|
||||
<div>{{ t('common.pass') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ props.statusDetail.SUCCESS }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="popover-label-td">
|
||||
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--danger-6))]"></div>
|
||||
<div>{{ t('common.unPass') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ props.statusDetail.ERROR }}
|
||||
{{ detailCount.pendingCount }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -64,35 +69,29 @@
|
|||
|
||||
import MsColorLine from '@/components/pure/ms-color-line/index.vue';
|
||||
|
||||
import { initDetailCount } from '@/config/testPlan';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import type { PassRateCountDetail } from '@/models/testPlan/testPlan';
|
||||
|
||||
const props = defineProps<{
|
||||
statusDetail: {
|
||||
tolerance: number;
|
||||
UNPENDING: number;
|
||||
RUNNING: number;
|
||||
SUCCESS: number;
|
||||
ERROR: number;
|
||||
executionProgress: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
statusDetail: PassRateCountDetail | undefined;
|
||||
height: string;
|
||||
radius?: string;
|
||||
}>();
|
||||
const { t } = useI18n();
|
||||
|
||||
const getCountTotal = computed(() => {
|
||||
const { UNPENDING, RUNNING, ERROR, SUCCESS } = props.statusDetail;
|
||||
return UNPENDING + RUNNING + ERROR + SUCCESS;
|
||||
const detailCount = ref({ ...initDetailCount });
|
||||
watchEffect(() => {
|
||||
detailCount.value = {
|
||||
...initDetailCount,
|
||||
...props.statusDetail,
|
||||
};
|
||||
});
|
||||
|
||||
const colorData = computed(() => {
|
||||
if (
|
||||
props.statusDetail.UNPENDING === 0 &&
|
||||
props.statusDetail.RUNNING === 0 &&
|
||||
props.statusDetail.ERROR === 0 &&
|
||||
props.statusDetail.SUCCESS === 0
|
||||
) {
|
||||
const { caseTotal, successCount, errorCount, fakeErrorCount, blockCount, pendingCount } = detailCount.value;
|
||||
if (fakeErrorCount === 0 && blockCount === 0 && errorCount === 0 && successCount === 0 && pendingCount === 0) {
|
||||
return [
|
||||
{
|
||||
percentage: 100,
|
||||
|
@ -100,21 +99,33 @@
|
|||
},
|
||||
];
|
||||
}
|
||||
if (detailCount.value.passRate > detailCount.value.passThreshold) {
|
||||
return [
|
||||
{
|
||||
percentage: 100,
|
||||
color: 'rgb(var(--success-6))',
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
{
|
||||
percentage: (props.statusDetail.SUCCESS / getCountTotal.value) * 100,
|
||||
percentage: (successCount / caseTotal) * 100,
|
||||
color: 'rgb(var(--success-6))',
|
||||
},
|
||||
{
|
||||
percentage: (props.statusDetail.ERROR / getCountTotal.value) * 100,
|
||||
percentage: (errorCount / caseTotal) * 100,
|
||||
color: 'rgb(var(--danger-6))',
|
||||
},
|
||||
{
|
||||
percentage: (props.statusDetail.RUNNING / getCountTotal.value) * 100,
|
||||
percentage: (blockCount / caseTotal) * 100,
|
||||
color: 'rgb(var(--link-6))',
|
||||
},
|
||||
{
|
||||
percentage: (props.statusDetail.UNPENDING / getCountTotal.value) * 100,
|
||||
percentage: (fakeErrorCount / caseTotal) * 100,
|
||||
color: 'rgb(var(--warning-6))',
|
||||
},
|
||||
{
|
||||
percentage: (pendingCount / caseTotal) * 100,
|
||||
color: 'var(--color-text-input-border)',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
v-model:visible="innerVisible"
|
||||
:title="props.planId?.length ? t('case.updateCase') : t('testPlan.testPlanIndex.createTestPlan')"
|
||||
:title="modelTitle"
|
||||
:width="800"
|
||||
unmount-on-close
|
||||
:ok-text="props.planId?.length ? 'common.update' : 'common.create'"
|
||||
:ok-text="okText"
|
||||
:save-continue-text="t('case.saveContinueText')"
|
||||
:show-continue="!props.planId?.length"
|
||||
:ok-loading="drawerLoading"
|
||||
|
@ -86,9 +86,7 @@
|
|||
<div class="text-[var(--color-text-2)]">
|
||||
{{
|
||||
t('caseManagement.caseReview.selectedCases', {
|
||||
count: form.baseAssociateCaseRequest?.selectAll
|
||||
? form.baseAssociateCaseRequest?.totalCount
|
||||
: form.baseAssociateCaseRequest?.selectIds.length,
|
||||
count: getSelectedCount,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
|
@ -143,7 +141,7 @@
|
|||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||
import AssociateDrawer from './components/associateDrawer.vue';
|
||||
|
||||
import { addTestPlan, getTestPlanDetail, updateTestPlan } from '@/api/modules/test-plan/testPlan';
|
||||
import { addTestPlan, copyTestPlan, getTestPlanDetail, updateTestPlan } from '@/api/modules/test-plan/testPlan';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
|
@ -154,6 +152,7 @@
|
|||
const props = defineProps<{
|
||||
planId?: string;
|
||||
moduleTree?: ModuleTreeNode[];
|
||||
isCopy: boolean;
|
||||
}>();
|
||||
const innerVisible = defineModel<boolean>('visible', {
|
||||
required: true,
|
||||
|
@ -170,6 +169,7 @@
|
|||
const drawerLoading = ref(false);
|
||||
const formRef = ref<FormInstance>();
|
||||
const initForm: AddTestPlanParams = {
|
||||
groupId: 'NONE',
|
||||
name: '',
|
||||
projectId: '',
|
||||
moduleId: 'root',
|
||||
|
@ -218,8 +218,21 @@
|
|||
if (!errors) {
|
||||
drawerLoading.value = true;
|
||||
try {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
moduleId,
|
||||
tags,
|
||||
description,
|
||||
testPlanning,
|
||||
automaticStatusUpdate,
|
||||
repeatCase,
|
||||
passThreshold,
|
||||
groupOption,
|
||||
} = form.value;
|
||||
const params: AddTestPlanParams = {
|
||||
...cloneDeep(form.value),
|
||||
groupId: 'NONE',
|
||||
plannedStartTime: form.value.cycle ? form.value.cycle[0] : undefined,
|
||||
plannedEndTime: form.value.cycle ? form.value.cycle[1] : undefined,
|
||||
projectId: appStore.currentProjectId,
|
||||
|
@ -228,8 +241,31 @@
|
|||
await addTestPlan(params);
|
||||
Message.success(t('common.createSuccess'));
|
||||
} else {
|
||||
await updateTestPlan(params);
|
||||
Message.success(t('common.updateSuccess'));
|
||||
if (props.isCopy) {
|
||||
const copyParams: AddTestPlanParams = {
|
||||
id,
|
||||
groupId: 'NONE',
|
||||
name,
|
||||
moduleId,
|
||||
tags,
|
||||
description,
|
||||
testPlanning,
|
||||
automaticStatusUpdate,
|
||||
repeatCase,
|
||||
passThreshold,
|
||||
baseAssociateCaseRequest: null,
|
||||
groupOption,
|
||||
plannedStartTime: form.value.cycle ? form.value.cycle[0] : undefined,
|
||||
plannedEndTime: form.value.cycle ? form.value.cycle[1] : undefined,
|
||||
projectId: appStore.currentProjectId,
|
||||
type: testPlanTypeEnum.TEST_PLAN,
|
||||
};
|
||||
await copyTestPlan(copyParams);
|
||||
} else {
|
||||
await updateTestPlan(params);
|
||||
}
|
||||
|
||||
Message.success(props.isCopy ? t('common.copySuccess') : t('common.updateSuccess'));
|
||||
}
|
||||
emit('loadPlanList');
|
||||
} catch (error) {
|
||||
|
@ -251,6 +287,9 @@
|
|||
if (props.planId?.length) {
|
||||
const result = await getTestPlanDetail(props.planId);
|
||||
form.value = cloneDeep(result);
|
||||
let copyName = `copy_${result.name}`;
|
||||
copyName = copyName.length > 255 ? copyName.slice(0, 255) : copyName;
|
||||
form.value.name = copyName;
|
||||
form.value.cycle = [result.plannedStartTime as number, result.plannedEndTime as number];
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -268,4 +307,27 @@
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
const modelTitle = computed(() => {
|
||||
if (props.planId) {
|
||||
return props.isCopy ? t('testPlan.testPlanIndex.copyTestPlan') : t('testPlan.testPlanIndex.updateTestPlan');
|
||||
}
|
||||
return t('testPlan.testPlanIndex.createTestPlan');
|
||||
});
|
||||
|
||||
const okText = computed(() => {
|
||||
if (props.planId) {
|
||||
return props.isCopy ? t('common.copy') : t('common.update');
|
||||
}
|
||||
return t('common.create');
|
||||
});
|
||||
|
||||
const getSelectedCount = computed(() => {
|
||||
if (props.planId) {
|
||||
return form.value?.functionalCaseCount || 0;
|
||||
}
|
||||
return form.value.baseAssociateCaseRequest?.selectAll
|
||||
? form.value.baseAssociateCaseRequest?.totalCount
|
||||
: form.value.baseAssociateCaseRequest?.selectIds.length;
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -90,8 +90,8 @@
|
|||
},
|
||||
{
|
||||
title: 'testPlan.bugManagement.defectState',
|
||||
slotName: 'statusName',
|
||||
dataIndex: 'statusName',
|
||||
slotName: 'status',
|
||||
dataIndex: 'status',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 200,
|
||||
|
|
|
@ -0,0 +1,271 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<a-dropdown-button
|
||||
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && total"
|
||||
type="primary"
|
||||
@click="handleSelect('associated')"
|
||||
>
|
||||
{{ t('common.associated') }}
|
||||
<template #icon>
|
||||
<icon-down />
|
||||
</template>
|
||||
<template #content>
|
||||
<a-doption value="new" @click="handleSelect('new')">
|
||||
{{ t('common.newCreate') }}
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown-button>
|
||||
<a-dropdown-button
|
||||
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && !total"
|
||||
type="primary"
|
||||
@click="handleSelect('new')"
|
||||
>
|
||||
{{ t('common.newCreate') }}
|
||||
<template #icon>
|
||||
<icon-down />
|
||||
</template>
|
||||
<template #content>
|
||||
<a-popover title="" position="right">
|
||||
<a-doption value="associated" :disabled="!total" @click="handleSelect('associated')">
|
||||
{{ 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-button>
|
||||
<a-input-search
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('caseManagement.featureCase.searchByName')"
|
||||
allow-clear
|
||||
class="mx-[8px] w-[240px]"
|
||||
@search="initData"
|
||||
@press-enter="initData"
|
||||
@clear="initData"
|
||||
/>
|
||||
</div>
|
||||
<BugList
|
||||
ref="bugTableListRef"
|
||||
:case-id="props.caseId"
|
||||
:keyword="keyword"
|
||||
:bug-total="total"
|
||||
:bug-columns="columns"
|
||||
@link="linkDefect"
|
||||
@new="createDefect"
|
||||
@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" @success="initData()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
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 LinkDefectDrawer from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/linkDefectDrawer.vue';
|
||||
|
||||
import { getBugList, getCustomOptionHeader } from '@/api/modules/bug-management';
|
||||
import { associatedDrawerDebug, cancelAssociatedDebug } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import { BugOptionItem } from '@/models/bug-management';
|
||||
import type { TableQueryParams } from '@/models/common';
|
||||
|
||||
import { makeColumns } from '@/views/case-management/caseManagementFeature/components/utils';
|
||||
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
const props = defineProps<{
|
||||
caseId: string;
|
||||
}>();
|
||||
|
||||
const keyword = ref<string>('');
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnID',
|
||||
dataIndex: 'num',
|
||||
width: 200,
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
showDrag: false,
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.defectName',
|
||||
slotName: 'name',
|
||||
dataIndex: 'name',
|
||||
showInTable: true,
|
||||
showTooltip: false,
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
showDrag: false,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.defectState',
|
||||
slotName: 'statusName',
|
||||
dataIndex: 'status',
|
||||
filterConfig: {
|
||||
options: [],
|
||||
labelKey: 'text',
|
||||
},
|
||||
showInTable: true,
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
showDrag: false,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.updateUser',
|
||||
slotName: 'handleUserName',
|
||||
dataIndex: 'handleUser',
|
||||
filterConfig: {
|
||||
options: [],
|
||||
labelKey: 'text',
|
||||
},
|
||||
showInTable: true,
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.defectSource',
|
||||
slotName: 'source',
|
||||
dataIndex: 'source',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
showDrag: false,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnActions',
|
||||
slotName: 'operation',
|
||||
dataIndex: 'operation',
|
||||
fixed: 'right',
|
||||
width: 100,
|
||||
showInTable: true,
|
||||
showDrag: false,
|
||||
},
|
||||
];
|
||||
|
||||
const bugTableListRef = ref();
|
||||
|
||||
async function initData() {
|
||||
if (!hasAnyPermission(['FUNCTIONAL_CASE:READ', 'FUNCTIONAL_CASE:READ+UPDATE', 'FUNCTIONAL_CASE:READ+DELETE'])) {
|
||||
return;
|
||||
}
|
||||
bugTableListRef.value?.searchData();
|
||||
}
|
||||
|
||||
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) {
|
||||
switch (value) {
|
||||
case 'associated':
|
||||
linkDefect();
|
||||
break;
|
||||
default:
|
||||
createDefect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const total = 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,
|
||||
});
|
||||
total.value = res.total;
|
||||
}
|
||||
|
||||
const cancelLoading = ref<boolean>(false);
|
||||
// 取消关联
|
||||
async function cancelLink(id: string) {
|
||||
cancelLoading.value = true;
|
||||
try {
|
||||
await cancelAssociatedDebug(id);
|
||||
Message.success(t('caseManagement.featureCase.cancelLinkSuccess'));
|
||||
initData();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
cancelLoading.value = false;
|
||||
}
|
||||
}
|
||||
const handleUserFilterOptions = ref<BugOptionItem[]>([]);
|
||||
const statusFilterOptions = ref<BugOptionItem[]>([]);
|
||||
|
||||
async function initFilterOptions() {
|
||||
if (hasAnyPermission(['PROJECT_BUG:READ'])) {
|
||||
const res = await getCustomOptionHeader(appStore.currentProjectId);
|
||||
handleUserFilterOptions.value = res.handleUserOption;
|
||||
statusFilterOptions.value = res.statusOption;
|
||||
const optionsMap: Record<string, any> = {
|
||||
status: statusFilterOptions.value,
|
||||
handleUser: handleUserFilterOptions.value,
|
||||
};
|
||||
const columnList = makeColumns(optionsMap, columns);
|
||||
bugTableListRef.value.bugTableRef.initColumn(columnList);
|
||||
}
|
||||
}
|
||||
|
||||
const drawerLoading = ref<boolean>(false);
|
||||
async function saveHandler(params: TableQueryParams) {
|
||||
try {
|
||||
drawerLoading.value = true;
|
||||
await associatedDrawerDebug(params);
|
||||
Message.success(t('caseManagement.featureCase.associatedSuccess'));
|
||||
initData();
|
||||
showLinkDrawer.value = false;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
drawerLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
initFilterOptions();
|
||||
initData();
|
||||
initBugList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -60,7 +60,7 @@
|
|||
<a-spin :loading="caseDetailLoading" class="relative flex flex-1 flex-col p-[16px]">
|
||||
<div class="flex">
|
||||
<div class="mr-[24px] flex flex-1 items-center">
|
||||
<MsStatusTag :status="caseDetail.status" />
|
||||
<MsStatusTag :status="caseDetail.status || 'PREPARED'" />
|
||||
<div class="ml-[8px] mr-[2px] font-medium text-[rgb(var(--primary-5))]">[{{ caseDetail.num }}]</div>
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<a-tooltip :content="caseDetail.name">
|
||||
|
@ -79,6 +79,10 @@
|
|||
no-content
|
||||
class="relative border-b"
|
||||
/>
|
||||
<div class="tab-content">
|
||||
<BugList v-if="activeTab === 'defectList'" :case-id="caseDetail.id" />
|
||||
<ExecutionHistory v-if="activeTab === 'executionHistory'" :case-id="caseDetail.id" />
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</MsCard>
|
||||
|
@ -93,6 +97,8 @@
|
|||
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
|
||||
import MsStatusTag from '@/components/business/ms-status-tag/index.vue';
|
||||
import BugList from './bug/index.vue';
|
||||
import ExecutionHistory from '@/views/test-plan/testPlan/detail/featureCase/detail/executionHistory/index.vue';
|
||||
|
||||
import { getPlanDetailFeatureCaseList } from '@/api/modules/test-plan/testPlan';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
@ -169,6 +175,14 @@
|
|||
value: 'detail',
|
||||
label: t('common.detail'),
|
||||
},
|
||||
{
|
||||
value: 'defectList',
|
||||
label: t('caseManagement.featureCase.defectList'),
|
||||
},
|
||||
{
|
||||
value: 'executionHistory',
|
||||
label: t('testPlan.featureCase.executionHistory'),
|
||||
},
|
||||
]);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
|
@ -215,4 +229,8 @@
|
|||
background-color: var(--color-text-n9);
|
||||
}
|
||||
}
|
||||
.tab-content {
|
||||
.ms-scroll-bar();
|
||||
@apply py-4;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
hide-divider
|
||||
>
|
||||
<template #headerLeft>
|
||||
<MsStatusTag :status="detail.status" />
|
||||
<MsStatusTag :status="detail.status || 'PREPARED'" />
|
||||
<a-tooltip :content="`[${detail.num}]${detail.name}`">
|
||||
<div class="one-line-text ml-[4px] max-w-[360px] gap-[4px] font-medium text-[var(--color-text-1)]">
|
||||
<span>[{{ detail.num }}]</span>
|
||||
|
@ -18,19 +18,35 @@
|
|||
</a-tooltip>
|
||||
</template>
|
||||
<template #headerRight>
|
||||
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']" type="button" status="default">
|
||||
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']" type="button" status="default" @click="linkCase">
|
||||
<MsIcon type="icon-icon_link-record_outlined1" class="mr-[8px]" />
|
||||
{{ t('ms.case.associate.title') }}
|
||||
</MsButton>
|
||||
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+UPDATE']" type="button" status="default">
|
||||
<MsButton
|
||||
v-permission="['PROJECT_TEST_PLAN:READ+UPDATE']"
|
||||
type="button"
|
||||
status="default"
|
||||
@click="editorCopyHandler(false)"
|
||||
>
|
||||
<MsIcon type="icon-icon_edit_outlined" class="mr-[8px]" />
|
||||
{{ t('common.edit') }}
|
||||
</MsButton>
|
||||
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+ADD']" type="button" status="default">
|
||||
<MsButton
|
||||
v-permission="['PROJECT_TEST_PLAN:READ+ADD']"
|
||||
type="button"
|
||||
status="default"
|
||||
@click="editorCopyHandler(true)"
|
||||
>
|
||||
<MsIcon type="icon-icon_copy_outlined" class="mr-[8px]" />
|
||||
{{ t('common.copy') }}
|
||||
</MsButton>
|
||||
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+UPDATE']" type="button" status="default">
|
||||
<MsButton
|
||||
v-permission="['PROJECT_TEST_PLAN:READ+UPDATE']"
|
||||
type="button"
|
||||
status="default"
|
||||
:loading="followLoading"
|
||||
@click="followHandler"
|
||||
>
|
||||
<MsIcon
|
||||
:type="detail.followFlag ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
||||
:class="`mr-[8px] ${detail.followFlag ? 'text-[rgb(var(--warning-6))]' : ''}`"
|
||||
|
@ -51,20 +67,21 @@
|
|||
<span class="mr-[8px]">{{ t('testPlan.testPlanDetail.executed') }}</span>
|
||||
<span v-if="detail.status === 'PREPARED'" class="text-[var(--color-text-1)]">-</span>
|
||||
<span v-else>
|
||||
<span class="font-medium text-[var(--color-text-1)]"> {{ detail.executedCount }} </span>/{{
|
||||
detail.caseCount
|
||||
}}
|
||||
<span class="mr-1 font-medium text-[var(--color-text-1)]"> {{ hasExecutedCount }} </span>/<span
|
||||
class="ml-1"
|
||||
>{{ countDetail.caseTotal }}</span
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-[var(--color-text-4)]">
|
||||
<span class="mr-[8px]">{{ t('caseManagement.caseReview.passRate') }}</span>
|
||||
<span v-if="detail.status === 'PREPARED'" class="text-[var(--color-text-1)]">-</span>
|
||||
<span v-if="detail.status === 'PREPARED'" class="text-[var(--color-text-1)]"></span>
|
||||
<span v-else>
|
||||
<span class="font-medium text-[var(--color-text-1)]"> {{ detail.passRate }}% </span>
|
||||
<span class="font-medium text-[var(--color-text-1)]"> {{ countDetail.passRate }}% </span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<passRateLine :review-detail="detail" height="8px" radius="var(--border-radius-mini)" />
|
||||
<StatusProgress :status-detail="countDetail" height="8px" radius="var(--border-radius-mini)" />
|
||||
</div>
|
||||
</template>
|
||||
<a-tabs v-model:active-key="activeTab" class="no-content">
|
||||
|
@ -74,13 +91,25 @@
|
|||
<!-- special-height的174: 上面卡片高度158 + mt的16 -->
|
||||
<MsCard class="mt-[16px]" :special-height="174" simple has-breadcrumb no-content-padding>
|
||||
<FeatureCase v-if="activeTab === 'featureCase'" />
|
||||
<BugManagement v-if="activeTab === 'defectList'" :plan-id="detail.id" />
|
||||
<!-- TODO 先不上 -->
|
||||
<!-- <BugManagement v-if="activeTab === 'defectList'" :plan-id="detail.id" /> -->
|
||||
</MsCard>
|
||||
<AssociateDrawer v-model:visible="caseAssociateVisible" :associated-ids="hasSelectedIds" @success="success" />
|
||||
<CreateAndEditPlanDrawer
|
||||
v-model:visible="showPlanDrawer"
|
||||
:plan-id="planId"
|
||||
:is-copy="isCopy"
|
||||
:module-tree="testPlanTree"
|
||||
@load-plan-list="successHandler"
|
||||
/>
|
||||
<ActionModal v-model:visible="showStatusDeleteModal" :record="activeRecord" @success="okHandler" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
|
@ -88,28 +117,69 @@
|
|||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import MsStatusTag from '@/components/business/ms-status-tag/index.vue';
|
||||
import ActionModal from '../components/actionModal.vue';
|
||||
import AssociateDrawer from '../components/associateDrawer.vue';
|
||||
import StatusProgress from '../components/statusProgress.vue';
|
||||
import BugManagement from './bugManagement/index.vue';
|
||||
import FeatureCase from './featureCase/index.vue';
|
||||
import passRateLine from '@/views/case-management/caseReview/components/passRateLine.vue';
|
||||
import CreateAndEditPlanDrawer from '@/views/test-plan/testPlan/createAndEditPlanDrawer.vue';
|
||||
|
||||
import { getTestPlanDetail } from '@/api/modules/test-plan/testPlan';
|
||||
import { testPlanDefaultDetail } from '@/config/testPlan';
|
||||
import {
|
||||
archivedPlan,
|
||||
associationCaseToPlan,
|
||||
followPlanRequest,
|
||||
getPlanPassRate,
|
||||
getTestPlanDetail,
|
||||
getTestPlanModule,
|
||||
} from '@/api/modules/test-plan/testPlan';
|
||||
import { initDetailCount, testPlanDefaultDetail } from '@/config/testPlan';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import { characterLimit } from '@/utils';
|
||||
|
||||
import type { TestPlanDetail } from '@/models/testPlan/testPlan';
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
import type {
|
||||
AssociateCaseRequest,
|
||||
PassRateCountDetail,
|
||||
TestPlanDetail,
|
||||
TestPlanItem,
|
||||
} from '@/models/testPlan/testPlan';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const appStore = useAppStore();
|
||||
const { openModal } = useModal();
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
const router = useRouter();
|
||||
const loading = ref(false);
|
||||
const planId = ref(route.query.id as string);
|
||||
const detail = ref<TestPlanDetail>({
|
||||
...testPlanDefaultDetail,
|
||||
});
|
||||
|
||||
const countDetail = ref<PassRateCountDetail>({ ...initDetailCount });
|
||||
|
||||
const hasExecutedCount = computed(() => {
|
||||
const { successCount, fakeErrorCount, errorCount, blockCount } = countDetail.value;
|
||||
return successCount + fakeErrorCount + errorCount + blockCount;
|
||||
});
|
||||
// 初始化统计
|
||||
async function getStatistics() {
|
||||
try {
|
||||
const result = await getPlanPassRate([planId.value]);
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
countDetail.value = result[0];
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
async function initDetail() {
|
||||
try {
|
||||
loading.value = true;
|
||||
detail.value = await getTestPlanDetail(planId.value);
|
||||
getStatistics();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
|
@ -117,9 +187,6 @@
|
|||
loading.value = false;
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
initDetail();
|
||||
});
|
||||
|
||||
const fullActions = [
|
||||
{
|
||||
|
@ -143,11 +210,53 @@
|
|||
}
|
||||
return fullActions.filter((e) => e.eventTag !== 'archive');
|
||||
});
|
||||
|
||||
function archiveHandler() {
|
||||
openModal({
|
||||
type: 'warning',
|
||||
title: t('common.archiveConfirmTitle', { name: characterLimit(detail.value.name) }),
|
||||
content: t('testPlan.testPlanIndex.confirmArchivePlan'),
|
||||
okText: t('common.archive'),
|
||||
cancelText: t('common.cancel'),
|
||||
okButtonProps: {
|
||||
status: 'normal',
|
||||
},
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
await archivedPlan(planId.value);
|
||||
Message.success(t('common.batchArchiveSuccess'));
|
||||
initDetail();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
hideCancel: false,
|
||||
});
|
||||
}
|
||||
const showStatusDeleteModal = ref<boolean>(false);
|
||||
const activeRecord = ref<TestPlanItem | TestPlanDetail | undefined>();
|
||||
// 删除
|
||||
function deleteHandler() {
|
||||
activeRecord.value = cloneDeep(detail.value);
|
||||
showStatusDeleteModal.value = true;
|
||||
}
|
||||
|
||||
// 删除或者删除弹窗归档成功
|
||||
function okHandler(isDelete: boolean) {
|
||||
if (isDelete) {
|
||||
router.back();
|
||||
} else {
|
||||
initDetail();
|
||||
}
|
||||
}
|
||||
|
||||
function handleMoreSelect(item: ActionsItem) {
|
||||
switch (item.eventTag) {
|
||||
case 'archive':
|
||||
archiveHandler();
|
||||
break;
|
||||
case 'delete':
|
||||
deleteHandler();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -160,11 +269,81 @@
|
|||
key: 'featureCase',
|
||||
title: t('menu.caseManagement.featureCase'),
|
||||
},
|
||||
{
|
||||
key: 'defectList',
|
||||
title: t('caseManagement.featureCase.defectList'),
|
||||
},
|
||||
// TODO 先不上
|
||||
// {
|
||||
// key: 'defectList',
|
||||
// title: t('caseManagement.featureCase.defectList'),
|
||||
// },
|
||||
]);
|
||||
const hasSelectedIds = ref<string[]>([]);
|
||||
const caseAssociateVisible = ref(false);
|
||||
// 关联用例
|
||||
function linkCase() {
|
||||
caseAssociateVisible.value = true;
|
||||
}
|
||||
const showPlanDrawer = ref(false);
|
||||
|
||||
// 更新 | 复制
|
||||
const isCopy = ref<boolean>(false);
|
||||
function editorCopyHandler(copyFlog: boolean) {
|
||||
isCopy.value = copyFlog;
|
||||
showPlanDrawer.value = true;
|
||||
}
|
||||
|
||||
const followLoading = ref<boolean>(false);
|
||||
// 关注
|
||||
async function followHandler() {
|
||||
try {
|
||||
followLoading.value = true;
|
||||
await followPlanRequest({
|
||||
userId: userStore.id || '',
|
||||
testPlanId: detail.value.id as string,
|
||||
});
|
||||
Message.success(
|
||||
detail.value.followFlag
|
||||
? t('caseManagement.caseReview.unFollowSuccess')
|
||||
: t('caseManagement.caseReview.followSuccess')
|
||||
);
|
||||
detail.value.followFlag = !detail.value.followFlag;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
followLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function successHandler() {
|
||||
initDetail();
|
||||
}
|
||||
const testPlanTree = ref<ModuleTreeNode[]>([]);
|
||||
async function initPlanTree() {
|
||||
try {
|
||||
testPlanTree.value = await getTestPlanModule({ projectId: appStore.currentProjectId });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 关联用例到测试计划
|
||||
async function success(params: AssociateCaseRequest) {
|
||||
try {
|
||||
await associationCaseToPlan({
|
||||
functionalSelectIds: params.selectIds,
|
||||
testPlanId: planId.value,
|
||||
});
|
||||
Message.success(t('ms.case.associate.associateSuccess'));
|
||||
caseAssociateVisible.value = false;
|
||||
initDetail();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
initDetail();
|
||||
initPlanTree();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -89,9 +89,10 @@
|
|||
:offspring-ids="offspringIds"
|
||||
:active-folder-type="activeCaseType"
|
||||
:modules-count="modulesCount"
|
||||
:module-tree="folderTree"
|
||||
:node-name="nodeName"
|
||||
@init="initModulesCount"
|
||||
@edit="handleEdit"
|
||||
@edit-or-copy="handleEditOrCopy"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -100,6 +101,7 @@
|
|||
v-model:visible="showPlanDrawer"
|
||||
:plan-id="planId"
|
||||
:module-tree="folderTree"
|
||||
:is-copy="isCopy"
|
||||
@close="resetPlanId"
|
||||
@load-plan-list="loadPlanList"
|
||||
/>
|
||||
|
@ -237,8 +239,10 @@
|
|||
|
||||
const planTableRef = ref<InstanceType<typeof PlanTable>>();
|
||||
const planId = ref('');
|
||||
function handleEdit(id: string) {
|
||||
const isCopy = ref<boolean>(false);
|
||||
function handleEditOrCopy(id: string, isCopyFlag: boolean) {
|
||||
planId.value = id;
|
||||
isCopy.value = isCopyFlag;
|
||||
showPlanDrawer.value = true;
|
||||
}
|
||||
function resetPlanId() {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
export default {
|
||||
'testPlan.testPlanIndex.createTestPlan': 'create test plan',
|
||||
'testPlan.testPlanIndex.updateTestPlan': 'update test plan',
|
||||
'testPlan.testPlanIndex.copyTestPlan': 'copy test plan',
|
||||
'testPlan.testPlanIndex.allTestPlan': 'All test Plans',
|
||||
'testPlan.testPlanIndex.collapseAll': 'Collapse all submodules',
|
||||
'testPlan.testPlanIndex.expandAll': 'Expand all submodules',
|
||||
|
@ -56,7 +58,7 @@ export default {
|
|||
'testPlan.testPlanIndex.defaultEnv': 'Default Environment',
|
||||
'testPlan.testPlanIndex.newEnv': 'New Environment',
|
||||
'testPlan.testPlanIndex.executionProgress': 'Execution progress',
|
||||
'testPlan.testPlanIndex.tolerance': 'tolerance',
|
||||
'testPlan.testPlanIndex.threshold': 'threshold',
|
||||
'testPlan.testPlanIndex.TotalCases': 'Total use cases',
|
||||
'testPlan.testPlanIndex.functionalUseCase': 'case',
|
||||
'testPlan.testPlanIndex.apiCase': 'Api use case',
|
||||
|
@ -89,6 +91,9 @@ export default {
|
|||
'testPlan.featureCase.executor': 'Executor',
|
||||
'testPlan.featureCase.changeExecutor': 'Change executor',
|
||||
'testPlan.featureCase.sort': 'sort',
|
||||
'testPlan.featureCase.executionHistory': 'Execution History',
|
||||
'testPlan.featureCase.noBugDataTooltip': 'No related defects, please',
|
||||
'testPlan.featureCase.noBugDataNewBug': 'New defect',
|
||||
'testPlan.featureCase.disassociateTip': 'Are you sure to cancel the association {name}? ',
|
||||
'testPlan.featureCase.disassociateTipContent':
|
||||
'After cancellation, it will affect the statistics related to the test plan',
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
export default {
|
||||
'testPlan.testPlanIndex.createTestPlan': '创建测试计划',
|
||||
'testPlan.testPlanIndex.updateTestPlan': '更新测试计划',
|
||||
'testPlan.testPlanIndex.copyTestPlan': '复制测试计划',
|
||||
'testPlan.testPlanIndex.allTestPlan': '全部测试计划',
|
||||
'testPlan.testPlanIndex.collapseAll': '收起全部子模块',
|
||||
'testPlan.testPlanIndex.expandAll': '展开全部子模块',
|
||||
|
@ -56,7 +58,7 @@ export default {
|
|||
'testPlan.testPlanIndex.defaultEnv': '默认环境',
|
||||
'testPlan.testPlanIndex.newEnv': '新环境',
|
||||
'testPlan.testPlanIndex.executionProgress': '执行进度',
|
||||
'testPlan.testPlanIndex.tolerance': '容错率',
|
||||
'testPlan.testPlanIndex.threshold': '通过阈值',
|
||||
'testPlan.testPlanIndex.TotalCases': '用例总数',
|
||||
'testPlan.testPlanIndex.functionalUseCase': '功能用例',
|
||||
'testPlan.testPlanIndex.apiCase': '接口用例',
|
||||
|
@ -87,6 +89,9 @@ export default {
|
|||
'testPlan.featureCase.executor': '执行人',
|
||||
'testPlan.featureCase.changeExecutor': '修改执行人',
|
||||
'testPlan.featureCase.sort': '排序',
|
||||
'testPlan.featureCase.executionHistory': '执行历史',
|
||||
'testPlan.featureCase.noBugDataTooltip': '暂无可关联缺陷,请 ',
|
||||
'testPlan.featureCase.noBugDataNewBug': '新建缺陷',
|
||||
'testPlan.featureCase.disassociateTip': '确认取消关联 { name } 吗?',
|
||||
'testPlan.featureCase.disassociateTipContent': '取消后,影响测试计划相关统计',
|
||||
'testPlan.featureCase.batchDisassociateTipContent': '取消后,再次关联,执行结果为:未执行',
|
||||
|
|
Loading…
Reference in New Issue