feat(测试计划): 测试计划详情-功能用例详情样式

This commit is contained in:
teukkk 2024-05-14 11:44:26 +08:00 committed by 刘瑞斌
parent b254e3a0a3
commit be16db3689
7 changed files with 278 additions and 5 deletions

View File

@ -965,6 +965,13 @@ export const pathMap: PathMapItem[] = [
permission: [],
level: MENU_LEVEL[2],
},
{
key: 'TEST_PLAN_INDEX_DETAIL_FEATURE_CASE_DETAIL', // 测试计划-测试计划-测试计划详情-功能用例详情
locale: 'menu.caseManagement.caseManagementCaseDetail',
route: RouteEnum.TEST_PLAN_INDEX_DETAIL_FEATURE_CASE_DETAIL,
permission: [],
level: MENU_LEVEL[2],
},
],
},
{

View File

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

View File

@ -171,4 +171,6 @@ export default {
'common.belongModule': '所属模块',
'common.moreSetting': '更多设置',
'common.executionResult': '执行结果',
'common.detail': '详情',
'common.baseInfo': '基本信息',
};

View File

@ -1,5 +1,6 @@
import type { customFieldsItem } from '@/models/caseManagement/featureCase';
import type { TableQueryParams } from '@/models/common';
import { LastExecuteResults } from '@/enums/caseEnum';
import { BatchApiParams } from '../common';
@ -133,7 +134,7 @@ export interface PlanDetailFeatureCaseItem {
versionName: string;
createUser: string;
createUserName: string;
lastExecResult: string;
lastExecResult: LastExecuteResults;
lastExecTime: number;
executeUser: string;
executeUserName: string;

View File

@ -47,6 +47,32 @@ const TestPlan: AppRouteRecordRaw = {
],
},
},
// 测试计划-测试计划详情-功能用例详情
{
path: 'testPlanIndexDetailFeatureCaseDetail',
name: TestPlanRouteEnum.TEST_PLAN_INDEX_DETAIL_FEATURE_CASE_DETAIL,
component: () => import('@/views/test-plan/testPlan/detail/featureCase/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',
isBack: true,
query: ['id'],
},
{
name: TestPlanRouteEnum.TEST_PLAN_INDEX_DETAIL_FEATURE_CASE_DETAIL,
locale: 'menu.caseManagement.caseManagementCaseDetail',
},
],
},
},
],
};

View File

@ -18,7 +18,7 @@
</div>
<MsBaseTable v-bind="propsRes" :action-config="batchActions" v-on="propsEvent" @batch-action="handleTableBatch">
<template #num="{ record }">
<MsButton type="text">{{ record.num }}</MsButton>
<MsButton type="text" @click="toCaseDetail(record)">{{ record.num }}</MsButton>
</template>
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL]="{ filterContent }">
<CaseLevel :case-level="filterContent.value" />
@ -70,6 +70,7 @@
<script setup lang="ts">
import { computed, onBeforeMount, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
@ -85,7 +86,8 @@
import { hasAnyPermission } from '@/utils/permission';
import { ModuleTreeNode } from '@/models/common';
import type { PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
import type { PlanDetailFeatureCaseItem, PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
import { TestPlanRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
@ -107,8 +109,10 @@
(e: 'init', params: PlanDetailFeatureCaseListQueryParams): void;
}>();
const appStore = useAppStore();
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const appStore = useAppStore();
const tableStore = useTableStore();
const keyword = ref('');
@ -203,7 +207,7 @@
width: hasOperationPermission.value ? 200 : 50,
},
];
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, getTableQueryParams } = useTable(
getPlanDetailFeatureCaseList,
{
scroll: { x: '100%' },
@ -307,6 +311,20 @@
loadCaseList();
});
//
function toCaseDetail(record: PlanDetailFeatureCaseItem) {
router.push({
name: TestPlanRouteEnum.TEST_PLAN_INDEX_DETAIL_FEATURE_CASE_DETAIL,
query: {
...route.query,
caseId: record.id,
},
state: {
params: JSON.stringify(getTableQueryParams()),
},
});
}
defineExpose({
resetSelector,
});

View File

@ -0,0 +1,218 @@
<template>
<MsCard :min-width="1100" has-breadcrumb simple no-content-padding>
<div class="flex h-full w-full">
<!-- 左侧 -->
<div class="flex h-full w-[318px] flex-col border-r border-[var(--color-text-n8)] p-[16px]">
<a-tooltip :content="`[${planDetail.num}]${planDetail.name}`">
<div class="one-line-text w-full gap-[4px] font-medium">
<span>[{{ planDetail.num }}]</span>
{{ planDetail.name }}
</div>
</a-tooltip>
<div class="my-[8px] flex">
<a-input-search
v-model:model-value="keyword"
:placeholder="t('caseManagement.caseReview.searchPlaceholder')"
allow-clear
class="mr-[8px] w-[176px]"
@search="loadCaseList"
@press-enter="loadCaseList"
@clear="loadCaseList"
/>
<a-select
v-model:model-value="lastExecResult"
:options="executeResultOptions"
class="flex-1"
@change="loadCaseList"
>
</a-select>
</div>
<a-spin :loading="caseListLoading" class="w-full flex-1 overflow-hidden">
<div class="case-list">
<div
v-for="item of caseList"
:key="item.id"
:class="['case-item', caseDetail.id === item.id ? 'case-item--active' : '']"
>
<div class="mb-[8px] flex items-center justify-between">
<div class="text-[var(--color-text-4)]">{{ item.num }}</div>
<ExecuteResult :execute-result="item.lastExecResult" />
</div>
<a-tooltip :content="item.name">
<div class="one-line-text">{{ item.name }}</div>
</a-tooltip>
</div>
<MsEmpty v-if="caseList.length === 0" />
</div>
<!-- TODO 样式 -->
<MsPagination
v-model:page-size="pageNation.pageSize"
v-model:current="pageNation.current"
:total="pageNation.total"
size="mini"
simple
@change="loadCaseList"
@page-size-change="loadCaseList"
/>
</a-spin>
</div>
<!-- 右侧 -->
<a-spin :loading="caseDetailLoading" class="relative flex flex-1 flex-col p-[16px]">
<div class="flex">
<div class="mr-[24px] flex flex-1 items-center">
<MsStatusTag :status="caseDetail.status" />
<div class="ml-[8px] mr-[2px] font-medium text-[rgb(var(--primary-5))]">[{{ caseDetail.num }}]</div>
<div class="flex-1 overflow-hidden">
<a-tooltip :content="caseDetail.name">
<div class="one-line-text max-w-[100%] font-medium">
{{ caseDetail.name }}
</div>
</a-tooltip>
</div>
</div>
<a-button type="outline">{{ t('common.edit') }}</a-button>
</div>
<MsTab
v-model:active-key="activeTab"
:show-badge="false"
:content-tab-list="contentTabList"
no-content
class="relative border-b"
/>
</a-spin>
</div>
</MsCard>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';
import MsCard from '@/components/pure/ms-card/index.vue';
import MsEmpty from '@/components/pure/ms-empty/index.vue';
import MsPagination from '@/components/pure/ms-pagination/index';
import MsTab from '@/components/pure/ms-tab/index.vue';
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
import MsStatusTag from '@/components/business/ms-status-tag/index.vue';
import { getPlanDetailFeatureCaseList } from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import type { PlanDetailFeatureCaseItem } from '@/models/testPlan/testPlan';
import { executionResultMap } from '@/views/case-management/caseManagementFeature/components/utils';
const { t } = useI18n();
const route = useRoute();
const appStore = useAppStore();
// TODO
const planDetail = ref({ num: '111', name: '222lalallalallalalalal222lalallalallalalalal222lalallalallalalalal' });
const keyword = ref('');
const lastExecResult = ref('');
const executeResultOptions = computed(() => {
return [
{ label: t('common.all'), value: '' },
...Object.keys(executionResultMap).map((key) => {
return {
value: key,
label: executionResultMap[key].statusText,
};
}),
];
});
const caseList = ref<PlanDetailFeatureCaseItem[]>([]);
const pageNation = ref({
total: 0,
pageSize: 10,
current: 1,
});
const otherListQueryParams = ref<Record<string, any>>({});
const caseListLoading = ref(false);
//
async function loadCaseList() {
try {
caseListLoading.value = true;
const res = await getPlanDetailFeatureCaseList({
projectId: appStore.currentProjectId,
testPlanId: route.query.id as string,
keyword: keyword.value,
current: pageNation.value.current || 1,
pageSize: pageNation.value.pageSize,
filter: lastExecResult.value
? {
lastExecResult: [lastExecResult.value],
}
: undefined,
...otherListQueryParams.value,
});
caseList.value = res.list;
pageNation.value.total = res.total;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
caseListLoading.value = false;
}
}
const caseDetail = ref<any>({});
const caseDetailLoading = ref(false);
const activeTab = ref('detail');
const contentTabList = ref([
{
value: 'baseInfo',
label: t('common.baseInfo'),
},
{
value: 'detail',
label: t('common.detail'),
},
]);
onBeforeMount(async () => {
const lastPageParams = window.history.state.params ? JSON.parse(window.history.state.params) : null; //
if (lastPageParams) {
const { total, pageSize, current, keyword: _keyword, sort, moduleIds } = lastPageParams;
pageNation.value = {
total: total || 0,
pageSize,
current,
};
keyword.value = _keyword;
otherListQueryParams.value = {
sort,
moduleIds,
};
}
await loadCaseList();
// TODO
caseDetail.value = caseList.value[0] ?? {};
});
</script>
<style lang="less" scoped>
.case-list {
@apply flex flex-col overflow-y-auto;
margin-bottom: 16px;
height: calc(100% - 40px);
gap: 8px;
.ms-scroll-bar();
.case-item {
@apply cursor-pointer;
padding: 8px;
border: 1px solid var(--color-text-n8);
border-radius: var(--border-radius-small);
&:hover {
border: 1px solid rgb(var(--primary-4));
}
}
.case-item--active {
border: 1px solid rgb(var(--primary-5));
background-color: var(--color-text-n9);
}
}
</style>