feat(测试计划): 测试计划详情卡片样式

This commit is contained in:
teukkk 2024-05-09 17:14:00 +08:00 committed by 刘瑞斌
parent 7478a1dff0
commit 8508848b48
9 changed files with 244 additions and 1 deletions

View File

@ -0,0 +1,21 @@
import { ReviewStatus } from '@/models/caseManagement/caseReview';
import type { TestPlanDetail } from '@/models/testPlan/testPlan';
// TODO: 对照后端
// 测试计划详情
export const testPlanDefaultDetail: TestPlanDetail = {
id: '',
name: '',
num: 0,
status: 'PREPARED' as ReviewStatus,
followFlag: false,
passRate: 0,
executedCount: 0,
caseCount: 0,
passCount: 0,
unPassCount: 0,
reReviewedCount: 0,
underReviewedCount: 0,
};
export default {};

View File

@ -61,6 +61,7 @@ export enum ProjectManagementRouteEnum {
export enum TestPlanRouteEnum {
TEST_PLAN = 'testPlan',
TEST_PLAN_INDEX = 'testPlanIndex',
TEST_PLAN_INDEX_DETAIL = 'testPlanIndexDetail',
}
export enum UITestRouteEnum {

View File

@ -20,6 +20,7 @@ export default {
message: {
'menu.workbench': '工作台',
'menu.testPlan': '测试计划',
'menu.testPlan.testPlanDetail': '测试计划详情',
'menu.bugManagement': '缺陷管理',
'menu.bugManagement.bugDetail': '缺陷',
'menu.bugManagement.bugRecycle': '回收站',

View File

@ -82,4 +82,21 @@ export interface UseCountType {
testProgress: string; // 测试进度
}
// TODO: 对后端
// 测试计划详情
export interface TestPlanDetail {
id: string;
name: string;
num: number;
status: planStatusType;
followFlag: boolean;
passRate: number;
executedCount: number;
caseCount: number;
passCount: number;
unPassCount: number;
reReviewedCount: number;
underReviewedCount: number;
}
export default {};

View File

@ -27,6 +27,26 @@ const TestPlan: AppRouteRecordRaw = {
isTopMenu: true,
},
},
// 测试计划详情
{
path: 'testPlanIndexDetail',
name: TestPlanRouteEnum.TEST_PLAN_INDEX_DETAIL,
component: () => import('@/views/test-plan/testPlan/detail/index.vue'),
meta: {
locale: 'menu.testPlan.testPlanDetail',
roles: ['PROJECT_TEST_PLAN:READ'],
breadcrumbs: [
{
name: TestPlanRouteEnum.TEST_PLAN_INDEX,
locale: 'menu.testPlan',
},
{
name: TestPlanRouteEnum.TEST_PLAN_INDEX_DETAIL,
locale: 'menu.testPlan.testPlanDetail',
},
],
},
},
],
};

View File

@ -79,7 +79,9 @@
</a-tooltip>
</div> -->
<div class="flex items-center">
<div class="one-line-text cursor-pointer text-[rgb(var(--primary-5))]">{{ record.num }}</div>
<div class="one-line-text cursor-pointer text-[rgb(var(--primary-5))]" @click="openDetail(record.id)">{{
record.num
}}</div>
<a-tooltip position="right" :disabled="!record.schedule" :mouse-enter-delay="300">
<MsTag v-if="record.schedule" size="small" type="link" theme="outline" class="ml-2">{{
t('testPlan.testPlanIndex.timing')
@ -257,6 +259,7 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
@ -283,6 +286,7 @@
import { characterLimit } from '@/utils';
import type { planStatusType, TestPlanItem } from '@/models/testPlan/testPlan';
import { TestPlanRouteEnum } from '@/enums/routeEnum';
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
import { testPlanTypeEnum } from '@/enums/testPlanEnum';
@ -290,6 +294,7 @@
const tableStore = useTableStore();
const appStore = useAppStore();
const router = useRouter();
const { t } = useI18n();
const { openModal } = useModal();
@ -611,6 +616,16 @@
});
}
//
function openDetail(id: string) {
router.push({
name: TestPlanRouteEnum.TEST_PLAN_INDEX_DETAIL,
query: {
id,
},
});
}
/**
* 批量执行
*/

View File

@ -0,0 +1,166 @@
<template>
<MsCard
:loading="loading"
:header-min-width="1100"
:min-width="150"
auto-height
hide-footer
no-content-padding
hide-divider
>
<template #headerLeft>
<statusTag :status="(detail.status as ReviewStatus)" />
<a-tooltip :content="`[${detail.num}]${detail.name}`">
<div class="one-line-text ml-[4px] max-w-[360px] gap-[4px] font-medium text-[var(--color-text-1)]">
<span>[{{ detail.num }}]</span>
{{ detail.name }}
</div>
</a-tooltip>
</template>
<template #headerRight>
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']" type="button" status="default">
<MsIcon type="icon-icon_link-record_outlined1" class="mr-[8px]" />
{{ t('ms.case.associate.title') }}
</MsButton>
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+UPDATE']" type="button" status="default">
<MsIcon type="icon-icon_edit_outlined" class="mr-[8px]" />
{{ t('common.edit') }}
</MsButton>
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+ADD']" type="button" status="default">
<MsIcon type="icon-icon_copy_outlined" class="mr-[8px]" />
{{ t('common.copy') }}
</MsButton>
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+UPDATE']" type="button" status="default">
<MsIcon
:type="detail.followFlag ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
:class="`mr-[8px] ${detail.followFlag ? 'text-[rgb(var(--warning-6))]' : ''}`"
/>
{{ t(detail.followFlag ? 'common.forked' : 'common.fork') }}
</MsButton>
<MsTableMoreAction :list="moreAction" @select="handleMoreSelect">
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+DELETE']" type="button" status="default">
<MsIcon type="icon-icon_more_outlined" class="mr-[8px]" />
{{ t('common.more') }}
</MsButton>
</MsTableMoreAction>
</template>
<template #subHeader>
<div class="mt-[16px] w-[476px]">
<div class="mb-[8px] flex items-center gap-[24px] text-[12px]">
<div class="text-[var(--color-text-4)]">
<span class="mr-[8px]">{{ t('testPlan.testPlanDetail.executed') }}</span>
<span v-if="detail.status === 'PREPARED'" class="text-[var(--color-text-1)]">-</span>
<span v-else>
<span class="font-medium text-[var(--color-text-1)]"> {{ detail.executedCount }} </span>/{{
detail.caseCount
}}
</span>
</div>
<div class="text-[var(--color-text-4)]">
<span class="mr-[8px]">{{ t('caseManagement.caseReview.passRate') }}</span>
<span v-if="detail.status === 'PREPARED'" class="text-[var(--color-text-1)]">-</span>
<span v-else>
<span class="font-medium text-[var(--color-text-1)]"> {{ detail.passRate }}% </span>
</span>
</div>
</div>
<passRateLine :review-detail="detail" height="8px" radius="var(--border-radius-mini)" />
</div>
</template>
<a-tabs v-model:active-key="activeTab" class="no-content">
<a-tab-pane v-for="item of tabList" :key="item.key" :title="item.title" />
</a-tabs>
</MsCard>
<!-- special-height的174: 上面卡片高度158 + mt的16 -->
<MsCard class="mt-[16px]" :special-height="174" simple has-breadcrumb no-content-padding></MsCard>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsCard from '@/components/pure/ms-card/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import passRateLine from '@/views/case-management/caseReview/components/passRateLine.vue';
import statusTag from '@/views/case-management/caseReview/components/statusTag.vue';
import { testPlanDefaultDetail } from '@/config/testPlan';
import { useI18n } from '@/hooks/useI18n';
import type { ReviewStatus } from '@/models/caseManagement/caseReview';
import type { TestPlanDetail } from '@/models/testPlan/testPlan';
const { t } = useI18n();
const loading = ref(false);
const detail = ref<TestPlanDetail>({
...testPlanDefaultDetail,
});
async function initDetail() {
try {
loading.value = true;
// TODO:
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
loading.value = false;
}
}
onMounted(() => {
initDetail();
});
const fullActions = [
{
label: t('common.archive'),
eventTag: 'archive',
permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
},
{
isDivider: true,
},
{
label: t('common.delete'),
eventTag: 'delete',
danger: true,
permission: ['PROJECT_TEST_PLAN:READ+DELETE'],
},
];
const moreAction = computed(() => {
if (detail.value.status === 'COMPLETED') {
return [...fullActions];
}
return fullActions.filter((e) => e.eventTag !== 'archive');
});
function handleMoreSelect(item: ActionsItem) {
switch (item.eventTag) {
case 'archive':
break;
case 'delete':
break;
default:
break;
}
}
const activeTab = ref('featureCase');
const tabList = ref([
{
key: 'featureCase',
title: t('menu.caseManagement.featureCase'),
},
{
key: 'defectList',
title: t('caseManagement.featureCase.defectList'),
},
]);
</script>
<style lang="less" scoped>
:deep(.arco-tabs-content) {
@apply hidden;
}
</style>

View File

@ -80,4 +80,5 @@ export default {
'testPlan.planForm.repeatCaseTip1': 'Enable: Repeatedly associate the same case',
'testPlan.planForm.repeatCaseTip2': 'Close: Cannot be associated with the same case repeatedly',
'testPlan.planForm.pickCases': 'Select cases',
'testPlan.testPlanDetail.executed': 'Executed',
};

View File

@ -78,4 +78,5 @@ export default {
'testPlan.planForm.repeatCaseTip1': '开启:可重复关联同一个用例',
'testPlan.planForm.repeatCaseTip2': '关闭:不可重复关联同一用例',
'testPlan.planForm.pickCases': '选择用例',
'testPlan.testPlanDetail.executed': '已执行',
};