feat(测试计划): 测试计划详情卡片样式
This commit is contained in:
parent
7478a1dff0
commit
8508848b48
|
@ -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 {};
|
|
@ -61,6 +61,7 @@ export enum ProjectManagementRouteEnum {
|
||||||
export enum TestPlanRouteEnum {
|
export enum TestPlanRouteEnum {
|
||||||
TEST_PLAN = 'testPlan',
|
TEST_PLAN = 'testPlan',
|
||||||
TEST_PLAN_INDEX = 'testPlanIndex',
|
TEST_PLAN_INDEX = 'testPlanIndex',
|
||||||
|
TEST_PLAN_INDEX_DETAIL = 'testPlanIndexDetail',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum UITestRouteEnum {
|
export enum UITestRouteEnum {
|
||||||
|
|
|
@ -20,6 +20,7 @@ export default {
|
||||||
message: {
|
message: {
|
||||||
'menu.workbench': '工作台',
|
'menu.workbench': '工作台',
|
||||||
'menu.testPlan': '测试计划',
|
'menu.testPlan': '测试计划',
|
||||||
|
'menu.testPlan.testPlanDetail': '测试计划详情',
|
||||||
'menu.bugManagement': '缺陷管理',
|
'menu.bugManagement': '缺陷管理',
|
||||||
'menu.bugManagement.bugDetail': '缺陷',
|
'menu.bugManagement.bugDetail': '缺陷',
|
||||||
'menu.bugManagement.bugRecycle': '回收站',
|
'menu.bugManagement.bugRecycle': '回收站',
|
||||||
|
|
|
@ -82,4 +82,21 @@ export interface UseCountType {
|
||||||
testProgress: string; // 测试进度
|
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 {};
|
export default {};
|
||||||
|
|
|
@ -27,6 +27,26 @@ const TestPlan: AppRouteRecordRaw = {
|
||||||
isTopMenu: true,
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,9 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div> -->
|
</div> -->
|
||||||
<div class="flex items-center">
|
<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">
|
<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">{{
|
<MsTag v-if="record.schedule" size="small" type="link" theme="outline" class="ml-2">{{
|
||||||
t('testPlan.testPlanIndex.timing')
|
t('testPlan.testPlanIndex.timing')
|
||||||
|
@ -257,6 +259,7 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
|
@ -283,6 +286,7 @@
|
||||||
import { characterLimit } from '@/utils';
|
import { characterLimit } from '@/utils';
|
||||||
|
|
||||||
import type { planStatusType, TestPlanItem } from '@/models/testPlan/testPlan';
|
import type { planStatusType, TestPlanItem } from '@/models/testPlan/testPlan';
|
||||||
|
import { TestPlanRouteEnum } from '@/enums/routeEnum';
|
||||||
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
|
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
|
||||||
import { testPlanTypeEnum } from '@/enums/testPlanEnum';
|
import { testPlanTypeEnum } from '@/enums/testPlanEnum';
|
||||||
|
|
||||||
|
@ -290,6 +294,7 @@
|
||||||
|
|
||||||
const tableStore = useTableStore();
|
const tableStore = useTableStore();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
|
|
||||||
|
@ -611,6 +616,16 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 测试计划详情
|
||||||
|
function openDetail(id: string) {
|
||||||
|
router.push({
|
||||||
|
name: TestPlanRouteEnum.TEST_PLAN_INDEX_DETAIL,
|
||||||
|
query: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量执行
|
* 批量执行
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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>
|
|
@ -80,4 +80,5 @@ export default {
|
||||||
'testPlan.planForm.repeatCaseTip1': 'Enable: Repeatedly associate the same case',
|
'testPlan.planForm.repeatCaseTip1': 'Enable: Repeatedly associate the same case',
|
||||||
'testPlan.planForm.repeatCaseTip2': 'Close: Cannot be associated with the same case repeatedly',
|
'testPlan.planForm.repeatCaseTip2': 'Close: Cannot be associated with the same case repeatedly',
|
||||||
'testPlan.planForm.pickCases': 'Select cases',
|
'testPlan.planForm.pickCases': 'Select cases',
|
||||||
|
'testPlan.testPlanDetail.executed': 'Executed',
|
||||||
};
|
};
|
||||||
|
|
|
@ -78,4 +78,5 @@ export default {
|
||||||
'testPlan.planForm.repeatCaseTip1': '开启:可重复关联同一个用例',
|
'testPlan.planForm.repeatCaseTip1': '开启:可重复关联同一个用例',
|
||||||
'testPlan.planForm.repeatCaseTip2': '关闭:不可重复关联同一用例',
|
'testPlan.planForm.repeatCaseTip2': '关闭:不可重复关联同一用例',
|
||||||
'testPlan.planForm.pickCases': '选择用例',
|
'testPlan.planForm.pickCases': '选择用例',
|
||||||
|
'testPlan.testPlanDetail.executed': '已执行',
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue