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