feat(测试计划): 测试计划模块和测试计划列表静态页面
This commit is contained in:
parent
b642981f2f
commit
1ceb194def
|
@ -2,17 +2,21 @@ import MSR from '@/api/http/index';
|
||||||
import {
|
import {
|
||||||
addTestPlanModuleUrl,
|
addTestPlanModuleUrl,
|
||||||
DeleteTestPlanModuleUrl,
|
DeleteTestPlanModuleUrl,
|
||||||
|
GetTestPlanListUrl,
|
||||||
GetTestPlanModuleCountUrl,
|
GetTestPlanModuleCountUrl,
|
||||||
GetTestPlanModuleUrl,
|
GetTestPlanModuleUrl,
|
||||||
MoveTestPlanModuleUrl,
|
MoveTestPlanModuleUrl,
|
||||||
updateTestPlanModuleUrl,
|
updateTestPlanModuleUrl,
|
||||||
} from '@/api/requrls/test-plan/testPlan';
|
} from '@/api/requrls/test-plan/testPlan';
|
||||||
|
|
||||||
import type { CreateOrUpdateModule, ModulesTreeType, UpdateModule } from '@/models/caseManagement/featureCase';
|
import type { CreateOrUpdateModule, UpdateModule } from '@/models/caseManagement/featureCase';
|
||||||
import type { CommonList, MoveModules, TableQueryParams } from '@/models/common';
|
import type { CommonList, MoveModules, TableQueryParams } from '@/models/common';
|
||||||
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
|
import type { TestPlanItem } from '@/models/testPlan/testPlan';
|
||||||
|
|
||||||
// 获取模块树
|
// 获取模块树
|
||||||
export function getTestPlanModule(params: TableQueryParams) {
|
export function getTestPlanModule(params: TableQueryParams) {
|
||||||
return MSR.get<ModulesTreeType[]>({ url: `${GetTestPlanModuleUrl}/${params.projectId}` });
|
return MSR.get<ModuleTreeNode[]>({ url: `${GetTestPlanModuleUrl}/${params.projectId}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建模块树
|
// 创建模块树
|
||||||
|
@ -39,3 +43,8 @@ export function deletePlanModuleTree(id: string) {
|
||||||
export function getPlanModulesCounts(data: TableQueryParams) {
|
export function getPlanModulesCounts(data: TableQueryParams) {
|
||||||
return MSR.post({ url: GetTestPlanModuleCountUrl, data });
|
return MSR.post({ url: GetTestPlanModuleCountUrl, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取计划列表
|
||||||
|
export function getTestPlanList(data: TableQueryParams) {
|
||||||
|
return MSR.post<CommonList<TestPlanItem>>({ url: GetTestPlanListUrl, data });
|
||||||
|
}
|
||||||
|
|
|
@ -10,3 +10,5 @@ export const MoveTestPlanModuleUrl = '/test-plan/module/move';
|
||||||
export const DeleteTestPlanModuleUrl = '/test-plan/module/delete';
|
export const DeleteTestPlanModuleUrl = '/test-plan/module/delete';
|
||||||
// 测试计划模块树数量
|
// 测试计划模块树数量
|
||||||
export const GetTestPlanModuleCountUrl = '/test-plan/module/count';
|
export const GetTestPlanModuleCountUrl = '/test-plan/module/count';
|
||||||
|
// 测试计划列表
|
||||||
|
export const GetTestPlanListUrl = '/test-plan/page';
|
||||||
|
|
|
@ -80,8 +80,21 @@
|
||||||
emit('change', v as SelectAllEnum);
|
emit('change', v as SelectAllEnum);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function hasUnselectedChildren(
|
||||||
|
data: MsTableDataItem<Record<string, any>>[],
|
||||||
|
selectedKeys: Set<string>,
|
||||||
|
rowKey: string
|
||||||
|
): boolean {
|
||||||
|
return data.some((item: any) => {
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
return hasUnselectedChildren(item.children, selectedKeys, rowKey);
|
||||||
|
}
|
||||||
|
return !selectedKeys.has(item[rowKey]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const handleCheckChange = () => {
|
const handleCheckChange = () => {
|
||||||
if (props.currentData.some((item) => !props.selectedKeys.has(item[props.rowKey]))) {
|
if (hasUnselectedChildren(props.currentData, props.selectedKeys, props.rowKey)) {
|
||||||
// 当前页有数据没有勾选上,此时点击全选按钮代表全部选中
|
// 当前页有数据没有勾选上,此时点击全选按钮代表全部选中
|
||||||
handleSelect(SelectAllEnum.CURRENT);
|
handleSelect(SelectAllEnum.CURRENT);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
const getTagWidth = (tag: { [x: string]: any }) => {
|
const getTagWidth = (tag: { [x: string]: any }) => {
|
||||||
const tagStr = props.isStringTag ? tag : tag[props.nameKey];
|
const tagStr = props.isStringTag ? tag : tag[props.nameKey];
|
||||||
|
|
||||||
const tagWidth = tagStr.length;
|
const tagWidth = tagStr ? tagStr.length : 0;
|
||||||
// 16个中文字符
|
// 16个中文字符
|
||||||
return tagWidth < 16 ? tagWidth : 16;
|
return tagWidth < 16 ? tagWidth : 16;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
export enum testPlanTypeEnum {
|
||||||
|
ALL = 'ALL',
|
||||||
|
TEST_PLAN = 'TEST_PLAN',
|
||||||
|
GROUP = 'GROUP',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {};
|
|
@ -122,6 +122,7 @@ export default {
|
||||||
'common.moveSuccess': 'Move successful',
|
'common.moveSuccess': 'Move successful',
|
||||||
'common.batchMove': 'Batch move',
|
'common.batchMove': 'Batch move',
|
||||||
'common.batchCopy': 'Batch copy',
|
'common.batchCopy': 'Batch copy',
|
||||||
|
'common.batchCopySuccess': 'Batch copy successful',
|
||||||
'common.batchMoveSuccess': 'Batch move successful',
|
'common.batchMoveSuccess': 'Batch move successful',
|
||||||
'common.importSuccess': 'Import successful',
|
'common.importSuccess': 'Import successful',
|
||||||
'common.nameIsTooLang': 'The name exceeds 255 characters',
|
'common.nameIsTooLang': 'The name exceeds 255 characters',
|
||||||
|
@ -157,4 +158,10 @@ export default {
|
||||||
'common.text': 'Text',
|
'common.text': 'Text',
|
||||||
'common.resourceDeleted': 'Resource has been deleted',
|
'common.resourceDeleted': 'Resource has been deleted',
|
||||||
'common.refresh': 'Refresh',
|
'common.refresh': 'Refresh',
|
||||||
|
'common.searchByNameAndId': 'Search by ID, name, or tag',
|
||||||
|
'common.archive': 'archive',
|
||||||
|
'common.running': 'Running',
|
||||||
|
'common.unExecute': 'Not executed',
|
||||||
|
'common.pass': 'Pass',
|
||||||
|
'common.unPass': 'Fail pass',
|
||||||
};
|
};
|
||||||
|
|
|
@ -123,6 +123,7 @@ export default {
|
||||||
'common.moveSuccess': '移动成功',
|
'common.moveSuccess': '移动成功',
|
||||||
'common.batchMove': '批量移动',
|
'common.batchMove': '批量移动',
|
||||||
'common.batchCopy': '批量复制',
|
'common.batchCopy': '批量复制',
|
||||||
|
'common.batchCopySuccess': '批量复制成功',
|
||||||
'common.batchMoveSuccess': '批量移动成功',
|
'common.batchMoveSuccess': '批量移动成功',
|
||||||
'common.importSuccess': '导入成功',
|
'common.importSuccess': '导入成功',
|
||||||
'common.nameIsTooLang': '名称超过255个字符',
|
'common.nameIsTooLang': '名称超过255个字符',
|
||||||
|
@ -157,4 +158,10 @@ export default {
|
||||||
'common.text': '文本',
|
'common.text': '文本',
|
||||||
'common.resourceDeleted': '资源已被删除',
|
'common.resourceDeleted': '资源已被删除',
|
||||||
'common.refresh': '刷新',
|
'common.refresh': '刷新',
|
||||||
|
'common.searchByNameAndId': '通过ID、名称或标签搜索',
|
||||||
|
'common.archive': '归档',
|
||||||
|
'common.running': '执行中',
|
||||||
|
'common.unExecute': '未执行',
|
||||||
|
'common.pass': '通过',
|
||||||
|
'common.unPass': '不通过',
|
||||||
};
|
};
|
||||||
|
|
|
@ -1 +1,27 @@
|
||||||
|
// 计划分页
|
||||||
|
export interface TestPlanItem {
|
||||||
|
id?: string;
|
||||||
|
projectId: string;
|
||||||
|
num: number;
|
||||||
|
name: string;
|
||||||
|
status: string;
|
||||||
|
type: string;
|
||||||
|
tags: string[];
|
||||||
|
schedule: string; // 是否定时
|
||||||
|
createUser: string;
|
||||||
|
createTime: string;
|
||||||
|
moduleName: string;
|
||||||
|
moduleId: string;
|
||||||
|
children: TestPlanItem[];
|
||||||
|
childrenCount: number;
|
||||||
|
groupId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResourcesItem {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
cpuRate: string;
|
||||||
|
status: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export default {};
|
export default {};
|
||||||
|
|
|
@ -3,31 +3,31 @@ import { TestPlanRouteEnum } from '@/enums/routeEnum';
|
||||||
import { DEFAULT_LAYOUT } from '../base';
|
import { DEFAULT_LAYOUT } from '../base';
|
||||||
import type { AppRouteRecordRaw } from '../types';
|
import type { AppRouteRecordRaw } from '../types';
|
||||||
|
|
||||||
// const TestPlan: AppRouteRecordRaw = {
|
const TestPlan: AppRouteRecordRaw = {
|
||||||
// path: '/test-plan',
|
path: '/test-plan',
|
||||||
// name: TestPlanRouteEnum.TEST_PLAN,
|
name: TestPlanRouteEnum.TEST_PLAN,
|
||||||
// redirect: '/test-plan/testPlanIndex',
|
redirect: '/test-plan/testPlanIndex',
|
||||||
// component: DEFAULT_LAYOUT,
|
component: DEFAULT_LAYOUT,
|
||||||
// meta: {
|
meta: {
|
||||||
// locale: 'menu.testPlan',
|
locale: 'menu.testPlan',
|
||||||
// icon: 'icon-icon_test-tracking_filled',
|
icon: 'icon-icon_test-tracking_filled',
|
||||||
// order: 1,
|
order: 1,
|
||||||
// hideChildrenInMenu: true,
|
hideChildrenInMenu: true,
|
||||||
// roles: ['TEST_PLAN:READ'],
|
roles: ['*'],
|
||||||
// },
|
},
|
||||||
// children: [
|
children: [
|
||||||
// // 测试计划
|
// 测试计划
|
||||||
// {
|
{
|
||||||
// path: 'testPlanIndex',
|
path: 'testPlanIndex',
|
||||||
// name: TestPlanRouteEnum.TEST_PLAN_INDEX,
|
name: TestPlanRouteEnum.TEST_PLAN_INDEX,
|
||||||
// component: () => import('@/views/test-plan/testPlan/index.vue'),
|
component: () => import('@/views/test-plan/testPlan/index.vue'),
|
||||||
// meta: {
|
meta: {
|
||||||
// locale: 'menu.testPlan',
|
locale: 'menu.testPlan',
|
||||||
// roles: ['TEST_PLAN:READ'],
|
roles: ['*'],
|
||||||
// isTopMenu: true,
|
isTopMenu: true,
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// ],
|
],
|
||||||
// };
|
};
|
||||||
|
|
||||||
// export default TestPlan;
|
export default TestPlan;
|
||||||
|
|
|
@ -7,7 +7,12 @@
|
||||||
allow-clear
|
allow-clear
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
/>
|
/>
|
||||||
<a-button v-permission="['CASE_REVIEW:READ+ADD']" class="ml-2" type="primary" @click="emit('create')">
|
<a-button
|
||||||
|
v-if="!props.isModal && hasAnyPermission(['CASE_REVIEW:READ+ADD'])"
|
||||||
|
class="ml-2"
|
||||||
|
type="primary"
|
||||||
|
@click="emit('create')"
|
||||||
|
>
|
||||||
{{ t('common.newCreate') }}
|
{{ t('common.newCreate') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -225,7 +230,7 @@
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
...e,
|
...e,
|
||||||
hideMoreAction: e.id === 'root',
|
hideMoreAction: e.id === 'root' || props.isModal,
|
||||||
draggable: e.id !== 'root' && !props.isModal,
|
draggable: e.id !== 'root' && !props.isModal,
|
||||||
disabled: e.id === activeFolder.value && props.isModal,
|
disabled: e.id === activeFolder.value && props.isModal,
|
||||||
count: props.modulesCount?.[e.id] || 0, // 避免模块数量先初始化完成了,数量没更新
|
count: props.modulesCount?.[e.id] || 0, // 避免模块数量先初始化完成了,数量没更新
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
v-bind="propsRes"
|
v-bind="propsRes"
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:action-config="tableBatchActions"
|
:action-config="tableBatchActions"
|
||||||
|
:selectable="hasOperationPermission"
|
||||||
v-on="propsEvent"
|
v-on="propsEvent"
|
||||||
@batch-action="handleTableBatch"
|
@batch-action="handleTableBatch"
|
||||||
>
|
>
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
v-bind="propsRes"
|
v-bind="propsRes"
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:action-config="tableBatchActions"
|
:action-config="tableBatchActions"
|
||||||
|
:selectable="hasOperationPermission"
|
||||||
v-on="propsEvent"
|
v-on="propsEvent"
|
||||||
@batch-action="handleTableBatch"
|
@batch-action="handleTableBatch"
|
||||||
>
|
>
|
||||||
|
|
|
@ -78,7 +78,6 @@
|
||||||
<a-button type="secondary" @click="cancelHandler">
|
<a-button type="secondary" @click="cancelHandler">
|
||||||
{{ t('common.cancel') }}
|
{{ t('common.cancel') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<slot name="self-button"></slot>
|
|
||||||
<a-button
|
<a-button
|
||||||
class="ml-3"
|
class="ml-3"
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|
|
@ -1,429 +0,0 @@
|
||||||
<template>
|
|
||||||
<ms-base-table
|
|
||||||
v-if="showType === 'list'"
|
|
||||||
v-bind="propsRes"
|
|
||||||
ref="tableRef"
|
|
||||||
class="mt-4"
|
|
||||||
:action-config="tableBatchActions"
|
|
||||||
:expanded-keys="expandedKeys"
|
|
||||||
@selected-change="handleTableSelect"
|
|
||||||
v-on="propsEvent"
|
|
||||||
@batch-action="handleTableBatch"
|
|
||||||
>
|
|
||||||
<template #num="{ record }">
|
|
||||||
<div v-if="(record.children || []).length > 0" class="mr-2 flex items-center" @click="expandHandler(record)">
|
|
||||||
<MsIcon
|
|
||||||
type="icon-icon_split-turn-down-left"
|
|
||||||
class="arrowIcon mr-1 text-[16px]"
|
|
||||||
:class="getIconClass(record)"
|
|
||||||
/>
|
|
||||||
<span :class="getIconClass(record)">{{ (record.children || []).length || 0 }}</span>
|
|
||||||
</div>
|
|
||||||
<span>{{ record.id }}</span>
|
|
||||||
</template>
|
|
||||||
<template #statusFilter="{ columnConfig }">
|
|
||||||
<a-trigger v-model:popup-visible="statusFilterVisible" trigger="click" @popup-visible-change="handleFilterHidden">
|
|
||||||
<a-button type="text" class="arco-btn-text--secondary" @click="statusFilterVisible = true">
|
|
||||||
{{ t(columnConfig.title as string) }}
|
|
||||||
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
|
|
||||||
</a-button>
|
|
||||||
<template #content>
|
|
||||||
<div class="arco-table-filters-content">
|
|
||||||
<div class="flex items-center justify-center px-[6px] py-[2px]">
|
|
||||||
<a-checkbox-group v-model:model-value="statusFilters" direction="vertical" size="small">
|
|
||||||
<a-checkbox v-for="key of Object.keys(reviewStatusMap)" :key="key" :value="key">
|
|
||||||
<a-tag
|
|
||||||
:color="reviewStatusMap[key as ReviewStatus].color"
|
|
||||||
:class="[reviewStatusMap[key as ReviewStatus].class, 'px-[4px]']"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{{ t(reviewStatusMap[key as ReviewStatus].label) }}
|
|
||||||
</a-tag>
|
|
||||||
</a-checkbox>
|
|
||||||
</a-checkbox-group>
|
|
||||||
</div>
|
|
||||||
<div class="filter-button">
|
|
||||||
<a-button size="mini" class="mr-[8px]" @click="resetStatusFilter">
|
|
||||||
{{ t('common.reset') }}
|
|
||||||
</a-button>
|
|
||||||
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
|
|
||||||
{{ t('system.orgTemplate.confirm') }}
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</a-trigger>
|
|
||||||
</template>
|
|
||||||
<template #name="{ record }">
|
|
||||||
<a-button type="text" class="px-0">{{ record.name }}</a-button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #status="{ record }">
|
|
||||||
<statusTag :status="record.status" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #passRate="{ record }">
|
|
||||||
<div class="mr-[8px] w-[100px]">
|
|
||||||
<passRateLine :review-detail="record" height="5px" />
|
|
||||||
</div>
|
|
||||||
<div class="text-[var(--color-text-1)]">
|
|
||||||
{{ `${record.passRate || 0}%` }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #operation="{ record }">
|
|
||||||
<MsButton>{{ t('testPlan.testPlanIndex.execution') }}</MsButton>
|
|
||||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
|
||||||
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+ADD']">{{ t('testPlan.testPlanIndex.copy') }}</MsButton>
|
|
||||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
|
||||||
<MsTableMoreAction
|
|
||||||
v-permission="['PROJECT_TEST_PLAN:READ+DELETE']"
|
|
||||||
:list="moreActions"
|
|
||||||
@select="handleMoreActionSelect($event, record)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</ms-base-table>
|
|
||||||
</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 MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
|
||||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
|
||||||
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
|
|
||||||
import useTable from '@/components/pure/ms-table/useTable';
|
|
||||||
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 { reviewStatusMap } from '@/config/caseManagement';
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
|
||||||
import { useTableStore } from '@/store';
|
|
||||||
|
|
||||||
import { ReviewStatus } from '@/models/caseManagement/caseReview';
|
|
||||||
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
|
|
||||||
|
|
||||||
const tableStore = useTableStore();
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const columns: MsTableColumn = [
|
|
||||||
{
|
|
||||||
title: 'testPlan.testPlanIndex.ID',
|
|
||||||
slotName: 'num',
|
|
||||||
dataIndex: 'num',
|
|
||||||
width: 200,
|
|
||||||
showInTable: true,
|
|
||||||
ellipsis: true,
|
|
||||||
showDrag: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'testPlan.testPlanIndex.testPlanName',
|
|
||||||
slotName: 'name',
|
|
||||||
dataIndex: 'name',
|
|
||||||
showInTable: true,
|
|
||||||
showTooltip: true,
|
|
||||||
width: 300,
|
|
||||||
editType: ColumnEditTypeEnum.INPUT,
|
|
||||||
sortable: {
|
|
||||||
sortDirections: ['ascend', 'descend'],
|
|
||||||
sorter: true,
|
|
||||||
},
|
|
||||||
ellipsis: true,
|
|
||||||
showDrag: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'testPlan.testPlanIndex.desc',
|
|
||||||
slotName: 'desc',
|
|
||||||
showInTable: true,
|
|
||||||
width: 200,
|
|
||||||
showDrag: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'testPlan.testPlanIndex.status',
|
|
||||||
dataIndex: 'status',
|
|
||||||
slotName: 'status',
|
|
||||||
titleSlotName: 'statusFilter',
|
|
||||||
width: 150,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'testPlan.testPlanIndex.passRate',
|
|
||||||
dataIndex: 'passRate',
|
|
||||||
slotName: 'passRate',
|
|
||||||
showInTable: true,
|
|
||||||
width: 200,
|
|
||||||
showDrag: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'testPlan.testPlanIndex.useCount',
|
|
||||||
slotName: 'versionId',
|
|
||||||
dataIndex: 'versionId',
|
|
||||||
width: 300,
|
|
||||||
showTooltip: true,
|
|
||||||
showInTable: true,
|
|
||||||
showDrag: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'testPlan.testPlanIndex.bugCount',
|
|
||||||
slotName: 'moduleId',
|
|
||||||
dataIndex: 'moduleId',
|
|
||||||
showInTable: true,
|
|
||||||
width: 300,
|
|
||||||
showDrag: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'testPlan.testPlanIndex.belongModule',
|
|
||||||
slotName: 'belongModule',
|
|
||||||
dataIndex: 'belongModule',
|
|
||||||
showInTable: true,
|
|
||||||
showDrag: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'testPlan.testPlanIndex.creator',
|
|
||||||
slotName: 'createUser',
|
|
||||||
dataIndex: 'createUser',
|
|
||||||
showInTable: true,
|
|
||||||
width: 200,
|
|
||||||
showDrag: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'testPlan.testPlanIndex.createTime',
|
|
||||||
slotName: 'createTime',
|
|
||||||
dataIndex: 'createTime',
|
|
||||||
showInTable: true,
|
|
||||||
sortable: {
|
|
||||||
sortDirections: ['ascend', 'descend'],
|
|
||||||
sorter: true,
|
|
||||||
},
|
|
||||||
width: 200,
|
|
||||||
showDrag: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'testPlan.testPlanIndex.operation',
|
|
||||||
slotName: 'operation',
|
|
||||||
dataIndex: 'operation',
|
|
||||||
fixed: 'right',
|
|
||||||
width: 260,
|
|
||||||
showInTable: true,
|
|
||||||
showDrag: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新测试计划名称
|
|
||||||
*/
|
|
||||||
async function updatePlanName() {
|
|
||||||
try {
|
|
||||||
Message.success(t('common.updateSuccess'));
|
|
||||||
return Promise.resolve(true);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
return Promise.resolve(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyword = ref<string>('');
|
|
||||||
const scrollWidth = ref<number>(3400);
|
|
||||||
const statusFilterVisible = ref(false);
|
|
||||||
const statusFilters = ref<string[]>([]);
|
|
||||||
|
|
||||||
const tableBatchActions = {
|
|
||||||
baseAction: [
|
|
||||||
{
|
|
||||||
label: 'caseManagement.featureCase.export',
|
|
||||||
eventTag: 'export',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
label: 'caseManagement.featureCase.exportExcel',
|
|
||||||
eventTag: 'exportExcel',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'caseManagement.featureCase.exportXMind',
|
|
||||||
eventTag: 'exportXMind',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'common.edit',
|
|
||||||
eventTag: 'batchEdit',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'caseManagement.featureCase.moveTo',
|
|
||||||
eventTag: 'batchMoveTo',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'caseManagement.featureCase.copyTo',
|
|
||||||
eventTag: 'batchCopyTo',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
moreAction: [
|
|
||||||
{
|
|
||||||
label: 'caseManagement.featureCase.addDemand',
|
|
||||||
eventTag: 'addDemand',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'caseManagement.featureCase.associatedDemand',
|
|
||||||
eventTag: 'associatedDemand',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'caseManagement.featureCase.generatingDependencies',
|
|
||||||
eventTag: 'generatingDependencies',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'caseManagement.featureCase.addToPublic',
|
|
||||||
eventTag: 'addToPublic',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isDivider: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'common.delete',
|
|
||||||
eventTag: 'delete',
|
|
||||||
danger: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const moreActions: ActionsItem[] = [
|
|
||||||
{
|
|
||||||
label: 'common.delete',
|
|
||||||
danger: true,
|
|
||||||
eventTag: 'delete',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const tableSelected = ref<(string | number)[]>([]);
|
|
||||||
function handleTableSelect(selectArr: (string | number)[]) {
|
|
||||||
tableSelected.value = selectArr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
id: '100944',
|
|
||||||
projectId: 'string',
|
|
||||||
num: '100944',
|
|
||||||
name: '系统示例',
|
|
||||||
status: 'PREPARED',
|
|
||||||
tags: ['string'],
|
|
||||||
schedule: 'string',
|
|
||||||
createUser: 'string',
|
|
||||||
createTime: 'string',
|
|
||||||
moduleName: 'string',
|
|
||||||
moduleId: 'string',
|
|
||||||
passCount: 0,
|
|
||||||
unPassCount: 0,
|
|
||||||
reviewedCount: 0,
|
|
||||||
underReviewedCount: 0,
|
|
||||||
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: '100945',
|
|
||||||
projectId: 'string',
|
|
||||||
num: '100945',
|
|
||||||
name: '系统示例',
|
|
||||||
status: 'COMPLETED',
|
|
||||||
tags: ['string'],
|
|
||||||
schedule: 'string',
|
|
||||||
createUser: 'string',
|
|
||||||
createTime: 'string',
|
|
||||||
moduleName: 'string',
|
|
||||||
moduleId: 'string',
|
|
||||||
testPlanItem: [],
|
|
||||||
testPlanGroupId: 'string',
|
|
||||||
passCount: 0,
|
|
||||||
unPassCount: 0,
|
|
||||||
reviewedCount: 0,
|
|
||||||
underReviewedCount: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
testPlanGroupId: 'string',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, setKeyword, setAdvanceFilter, setProps } =
|
|
||||||
useTable(
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
tableKey: TableKeyEnum.TEST_PLAN_ALL_TABLE,
|
|
||||||
scroll: { x: scrollWidth.value },
|
|
||||||
selectable: true,
|
|
||||||
showSetting: true,
|
|
||||||
heightUsed: 374,
|
|
||||||
enableDrag: true,
|
|
||||||
},
|
|
||||||
(item) => {
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
tags: (item.tags || []).map((e: string) => ({ id: e, name: e })),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const showType = ref<string>('list');
|
|
||||||
const batchParams = ref<BatchActionQueryParams>({
|
|
||||||
selectedIds: [],
|
|
||||||
selectAll: false,
|
|
||||||
excludeIds: [],
|
|
||||||
currentSelectCount: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
|
|
||||||
batchParams.value = params;
|
|
||||||
}
|
|
||||||
|
|
||||||
function deletePlan(record: any) {}
|
|
||||||
|
|
||||||
function handleMoreActionSelect(item: ActionsItem, record: any) {
|
|
||||||
if (item.eventTag === 'delete') {
|
|
||||||
deletePlan(record);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tableStore.initColumn(TableKeyEnum.TEST_PLAN_ALL_TABLE, columns, 'drawer');
|
|
||||||
|
|
||||||
const expandedKeys = ref<string[]>([]);
|
|
||||||
|
|
||||||
function expandHandler(record: any) {
|
|
||||||
if (expandedKeys.value.includes(record.id)) {
|
|
||||||
expandedKeys.value = expandedKeys.value.filter((key) => key !== record.id);
|
|
||||||
} else {
|
|
||||||
expandedKeys.value = [...expandedKeys.value, record.id];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getIconClass(record: any) {
|
|
||||||
return expandedKeys.value.includes(record.id) ? 'text-[rgb(var(--primary-5))]' : 'text-[var(--color-text-4)]';
|
|
||||||
}
|
|
||||||
|
|
||||||
function searchPlan() {}
|
|
||||||
|
|
||||||
function handleFilterHidden(val: boolean) {
|
|
||||||
if (!val) {
|
|
||||||
statusFilterVisible.value = false;
|
|
||||||
searchPlan();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetStatusFilter() {
|
|
||||||
statusFilterVisible.value = false;
|
|
||||||
statusFilters.value = [];
|
|
||||||
searchPlan();
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
setProps({ data });
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="less">
|
|
||||||
:deep(.arco-table-cell-expand-icon .arco-table-cell-inline-icon) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:deep(.arco-table-cell-align-left) > span:first-child {
|
|
||||||
padding-left: 0 !important;
|
|
||||||
}
|
|
||||||
.arrowIcon {
|
|
||||||
transform: scaleX(-1);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="showModalVisible"
|
||||||
|
title-align="start"
|
||||||
|
class="ms-modal-no-padding ms-modal-small"
|
||||||
|
:mask-closable="false"
|
||||||
|
:ok-text="props.mode === 'move' ? t('common.move') : t('common.copy')"
|
||||||
|
:ok-button-props="{ disabled: innerSelectedModuleKeys.length === 0 }"
|
||||||
|
:cancel-button-props="{ disabled: props.okLoading }"
|
||||||
|
:on-before-ok="handleCaseMoveOrCopy"
|
||||||
|
@close="handleMoveCaseModalCancel"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<div class="flex w-full items-center justify-between">
|
||||||
|
<div>
|
||||||
|
{{ props.mode === 'move' ? t('common.batchMove') : t('common.batchCopy') }}
|
||||||
|
<span class="ml-[4px] text-[var(--color-text-4)]">
|
||||||
|
{{ t('testPlan.testPlanIndex.selectedCount', { count: props.currentSelectCount }) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<a-input
|
||||||
|
v-model:model-value="moduleKeyword"
|
||||||
|
:placeholder="t('caseManagement.caseReview.folderSearchPlaceholder')"
|
||||||
|
allow-clear
|
||||||
|
:max-length="255"
|
||||||
|
class="mb-4"
|
||||||
|
/>
|
||||||
|
<a-spin class="min-h-[400px] w-full" :loading="loading">
|
||||||
|
<MsTree
|
||||||
|
v-model:focus-node-key="focusNodeKey"
|
||||||
|
v-model:selected-keys="innerSelectedModuleKeys"
|
||||||
|
:data="treeData"
|
||||||
|
:keyword="moduleKeyword"
|
||||||
|
:default-expand-all="props.isExpandAll"
|
||||||
|
:expand-all="isExpandAll"
|
||||||
|
:empty-text="t(props.emptyText)"
|
||||||
|
:draggable="false"
|
||||||
|
:virtual-list-props="virtualListProps"
|
||||||
|
:field-names="{
|
||||||
|
title: 'name',
|
||||||
|
key: 'id',
|
||||||
|
children: 'children',
|
||||||
|
count: 'count',
|
||||||
|
}"
|
||||||
|
block-node
|
||||||
|
title-tooltip-position="top"
|
||||||
|
@select="nodeSelect"
|
||||||
|
>
|
||||||
|
<template #title="nodeData">
|
||||||
|
<div class="inline-flex w-full">
|
||||||
|
<div class="one-line-text w-full text-[var(--color-text-1)]">{{ nodeData.name }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</MsTree>
|
||||||
|
</a-spin>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
|
import MsTree from '@/components/business/ms-tree/index.vue';
|
||||||
|
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import { useAppStore } from '@/store';
|
||||||
|
import { mapTree } from '@/utils';
|
||||||
|
|
||||||
|
import type { TableQueryParams } from '@/models/common';
|
||||||
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
mode: 'move' | 'copy';
|
||||||
|
currentSelectCount: number;
|
||||||
|
visible: boolean;
|
||||||
|
isExpandAll?: boolean;
|
||||||
|
getModuleTreeApi: (params: TableQueryParams) => Promise<ModuleTreeNode[]>; // 模块树接口
|
||||||
|
selectedNodeKeys: (string | number)[];
|
||||||
|
okLoading: boolean;
|
||||||
|
emptyText?: string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
isExpandAll: false,
|
||||||
|
emptyText: 'common.noData',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', val: boolean): void;
|
||||||
|
(e: 'update:selectedNodeKeys', val: string[]): void;
|
||||||
|
(e: 'save'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const showModalVisible = useVModel(props, 'visible', emit);
|
||||||
|
const innerSelectedModuleKeys = useVModel(props, 'selectedNodeKeys', emit);
|
||||||
|
|
||||||
|
const moduleKeyword = ref<string>('');
|
||||||
|
|
||||||
|
const focusNodeKey = ref<string>('');
|
||||||
|
|
||||||
|
// 批量移动和复制
|
||||||
|
async function handleCaseMoveOrCopy() {
|
||||||
|
emit('save');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMoveCaseModalCancel() {
|
||||||
|
showModalVisible.value = false;
|
||||||
|
innerSelectedModuleKeys.value = [];
|
||||||
|
moduleKeyword.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
|
||||||
|
const treeData = ref<ModuleTreeNode[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化模块树
|
||||||
|
* @param isSetDefaultKey 是否设置第一个节点为选中节点
|
||||||
|
*/
|
||||||
|
async function initModules(isSetDefaultKey = false) {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const res = await props.getModuleTreeApi({ projectId: appStore.currentProjectId });
|
||||||
|
treeData.value = mapTree<ModuleTreeNode>(res, (e) => {
|
||||||
|
return {
|
||||||
|
...e,
|
||||||
|
hideMoreAction: true,
|
||||||
|
draggable: false,
|
||||||
|
disabled: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (isSetDefaultKey) {
|
||||||
|
innerSelectedModuleKeys.value = [treeData.value[0].id];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const virtualListProps = computed(() => {
|
||||||
|
return {
|
||||||
|
height: 'calc(60vh - 190px)',
|
||||||
|
threshold: 200,
|
||||||
|
fixedSize: true,
|
||||||
|
buffer: 15,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 节点选中事件
|
||||||
|
const nodeSelect = (selectedKeys: (string | number)[], node: MsTreeNodeData) => {
|
||||||
|
const offspringIds: string[] = [];
|
||||||
|
mapTree(node.children || [], (e) => {
|
||||||
|
offspringIds.push(e.id);
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
innerSelectedModuleKeys.value = selectedKeys;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => showModalVisible.value,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
initModules();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less"></style>
|
|
@ -1,30 +1,223 @@
|
||||||
<template>
|
<template>
|
||||||
<MsAdvanceFilter
|
<MsAdvanceFilter
|
||||||
|
v-model:keyword="keyword"
|
||||||
:filter-config-list="filterConfigList"
|
:filter-config-list="filterConfigList"
|
||||||
:custom-fields-config-list="searchCustomFields"
|
:custom-fields-config-list="searchCustomFields"
|
||||||
:row-count="filterRowCount"
|
:row-count="filterRowCount"
|
||||||
|
:search-placeholder="t('common.searchByNameAndId')"
|
||||||
@keyword-search="fetchData"
|
@keyword-search="fetchData"
|
||||||
@adv-search="handleAdvSearch"
|
@adv-search="fetchData"
|
||||||
|
@refresh="fetchData"
|
||||||
>
|
>
|
||||||
<template #left>
|
<template #left>
|
||||||
<div class="flex w-full justify-between">
|
|
||||||
<div class="text-[var(--color-text-1)]"
|
|
||||||
>{{ moduleNamePath }}
|
|
||||||
<span class="text-[var(--color-text-4)]"> ({{ props.modulesCount[props.activeFolder] || 0 }})</span></div
|
|
||||||
>
|
|
||||||
<a-radio-group v-model="showType" type="button" class="file-show-type mr-2">
|
<a-radio-group v-model="showType" type="button" class="file-show-type mr-2">
|
||||||
<a-radio value="all" class="show-type-icon p-[2px]">{{ t('testPlan.testPlanIndex.all') }}</a-radio>
|
<a-radio :value="testPlanTypeEnum.ALL" class="show-type-icon p-[2px]">{{
|
||||||
<a-radio value="testPlan" class="show-type-icon p-[2px]">{{ t('testPlan.testPlanIndex.testPlan') }}</a-radio>
|
t('testPlan.testPlanIndex.all')
|
||||||
<a-radio value="testPlanGroup" class="show-type-icon p-[2px]">{{
|
|
||||||
t('testPlan.testPlanIndex.testPlanGroup')
|
|
||||||
}}</a-radio>
|
}}</a-radio>
|
||||||
|
<a-radio :value="testPlanTypeEnum.TEST_PLAN" class="show-type-icon p-[2px]">{{
|
||||||
|
t('testPlan.testPlanIndex.testPlan')
|
||||||
|
}}</a-radio>
|
||||||
|
<!-- <a-radio value="testPlanGroup" class="show-type-icon p-[2px]">{{
|
||||||
|
t('testPlan.testPlanIndex.testPlanGroup')
|
||||||
|
}}</a-radio> -->
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</MsAdvanceFilter>
|
</MsAdvanceFilter>
|
||||||
<AllTable v-if="showType === 'all'" />
|
<MsBaseTable
|
||||||
<TestPlanTable v-if="showType === 'testPlan'" />
|
v-bind="propsRes"
|
||||||
<TestPlanGroupTable v-if="showType === 'testPlanGroup'" />
|
ref="tableRef"
|
||||||
|
class="mt-4"
|
||||||
|
:action-config="tableBatchActions"
|
||||||
|
:expanded-keys="expandedKeys"
|
||||||
|
filter-icon-align-left
|
||||||
|
v-on="propsEvent"
|
||||||
|
@batch-action="handleTableBatch"
|
||||||
|
>
|
||||||
|
<template #num="{ record }">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div v-if="record.childrenCount" class="mr-2 flex items-center" @click="expandHandler(record)">
|
||||||
|
<MsIcon
|
||||||
|
type="icon-icon_split-turn-down-left"
|
||||||
|
class="arrowIcon mr-1 text-[16px]"
|
||||||
|
:class="getIconClass(record)"
|
||||||
|
/>
|
||||||
|
<span :class="getIconClass(record)">{{ record.childrenCount }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:class="[record.childrenCount ? 'pl-0' : 'pl-[36px]']"
|
||||||
|
class="one-line-text text-[rgb(var(--primary-5))]"
|
||||||
|
>{{ 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')
|
||||||
|
}}</MsTag>
|
||||||
|
<template #content>
|
||||||
|
<div>
|
||||||
|
<div>{{ t('testPlan.testPlanIndex.scheduledTaskOpened') }}</div>
|
||||||
|
<div>{{ t('testPlan.testPlanIndex.nextExecutionTime') }}</div>
|
||||||
|
<!-- TODO 缺少字段 -->
|
||||||
|
<div>---</div>
|
||||||
|
</div>
|
||||||
|
<!-- TODO 缺少字段 -->
|
||||||
|
<!-- <div> {{ t('testPlan.testPlanIndex.scheduledTaskUnEnable') }} </div> -->
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #statusFilter="{ columnConfig }">
|
||||||
|
<a-trigger v-model:popup-visible="statusFilterVisible" trigger="click" @popup-visible-change="handleFilterHidden">
|
||||||
|
<a-button type="text" class="arco-btn-text--secondary" @click="statusFilterVisible = true">
|
||||||
|
{{ t(columnConfig.title as string) }}
|
||||||
|
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
|
||||||
|
</a-button>
|
||||||
|
<template #content>
|
||||||
|
<div class="arco-table-filters-content">
|
||||||
|
<div class="flex items-center justify-center px-[6px] py-[2px]">
|
||||||
|
<a-checkbox-group v-model:model-value="statusFilters" direction="vertical" size="small">
|
||||||
|
<a-checkbox v-for="key of Object.keys(reviewStatusMap)" :key="key" :value="key">
|
||||||
|
<a-tag
|
||||||
|
:color="reviewStatusMap[key as ReviewStatus].color"
|
||||||
|
:class="[reviewStatusMap[key as ReviewStatus].class, 'px-[4px]']"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ t(reviewStatusMap[key as ReviewStatus].label) }}
|
||||||
|
</a-tag>
|
||||||
|
</a-checkbox>
|
||||||
|
</a-checkbox-group>
|
||||||
|
</div>
|
||||||
|
<div class="filter-button">
|
||||||
|
<a-button size="mini" class="mr-[8px]" @click="resetStatusFilter">
|
||||||
|
{{ t('common.reset') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
|
||||||
|
{{ t('system.orgTemplate.confirm') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-trigger>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #status="{ record }">
|
||||||
|
<statusTag :status="record.status" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #passRate="{ record }">
|
||||||
|
<div class="mr-[8px] w-[100px]">
|
||||||
|
<StatusProgress :status-detail="record.statusDetail" height="5px" />
|
||||||
|
</div>
|
||||||
|
<div class="text-[var(--color-text-1)]">
|
||||||
|
{{ `${record.passRate || 0}%` }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #passRateTitleSlot="{ columnConfig }">
|
||||||
|
<div class="flex items-center text-[var(--color-text-3)]">
|
||||||
|
{{ t(columnConfig.title as string) }}
|
||||||
|
<a-tooltip position="right">
|
||||||
|
<icon-question-circle
|
||||||
|
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
|
<template #content>
|
||||||
|
<!-- TODO 需要提供文案 -->
|
||||||
|
<!-- <div>{{ t('apiTestDebug.encodeTip1') }}</div>
|
||||||
|
<div>{{ t('apiTestDebug.encodeTip2') }}</div> -->
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #useCount="{ record }">
|
||||||
|
<a-popover position="bottom" content-class="p-[16px]" trigger="click">
|
||||||
|
<div>{{ record.useCaseCount.caseCount }}</div>
|
||||||
|
<template #content>
|
||||||
|
<table class="min-w-[144px]">
|
||||||
|
<tr>
|
||||||
|
<td class="popover-label-td">
|
||||||
|
<div>{{ t('project.testPlanIndex.TotalCases') }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="popover-value-td">
|
||||||
|
{{ record.useCaseCount.caseCount }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="popover-label-td">
|
||||||
|
<div class="text-[var(--color-text-1)]">{{ t('project.testPlanIndex.functionalUseCase') }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="popover-value-td">
|
||||||
|
{{ record.useCaseCount.caseCount }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="popover-label-td">
|
||||||
|
<div class="text-[var(--color-text-1)]">{{ t('project.testPlanIndex.apiCase') }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="popover-value-td">
|
||||||
|
{{ record.useCaseCount.caseCount }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="popover-label-td">
|
||||||
|
<div class="text-[var(--color-text-1)]">{{ t('project.testPlanIndex.apiScenarioCase') }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="popover-value-td">
|
||||||
|
{{ record.useCaseCount.caseCount }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #operation="{ record }">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<MsButton class="!mx-0">{{ t('testPlan.testPlanIndex.execution') }}</MsButton>
|
||||||
|
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||||
|
|
||||||
|
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+UPDATE']" class="!mx-0">{{ t('common.edit') }}</MsButton>
|
||||||
|
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||||
|
|
||||||
|
<MsTableMoreAction :list="moreActions" @select="handleMoreActionSelect($event, record)" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</MsBaseTable>
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="executeVisible"
|
||||||
|
class="ms-modal-form ms-modal-small ms-modal-response-body"
|
||||||
|
unmount-on-close
|
||||||
|
title-align="start"
|
||||||
|
:mask="true"
|
||||||
|
:mask-closable="false"
|
||||||
|
@close="cancelHandler"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
{{ t('testPlan.testPlanIndex.batchExecution') }}
|
||||||
|
</template>
|
||||||
|
<a-radio-group>
|
||||||
|
<a-radio value="serial">{{ t('testPlan.testPlanIndex.serial') }}</a-radio>
|
||||||
|
<a-radio value="parallel">{{ t('testPlan.testPlanIndex.parallel') }}</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<a-button type="secondary" @click="cancelHandler">
|
||||||
|
{{ t('common.cancel') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button class="ml-3" type="primary" :loading="confirmLoading" @click="executeHandler">
|
||||||
|
{{ t('common.execute') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
<BatchMoveOrCopy
|
||||||
|
v-model:visible="showBatchModal"
|
||||||
|
v-model:selected-node-keys="selectNodeKeys"
|
||||||
|
:mode="modeType"
|
||||||
|
:current-select-count="batchParams.currentSelectCount || 0"
|
||||||
|
:get-module-tree-api="getTestPlanModule"
|
||||||
|
:ok-loading="okLoading"
|
||||||
|
@save="handleMoveOrCopy"
|
||||||
|
/>
|
||||||
|
<ScheduledModal v-model:visible="showScheduledTaskModal" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -33,13 +226,34 @@
|
||||||
|
|
||||||
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
|
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
|
||||||
import { FilterFormItem } from '@/components/pure/ms-advance-filter/type';
|
import { FilterFormItem } from '@/components/pure/ms-advance-filter/type';
|
||||||
import AllTable from './allTable.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import TestPlanGroupTable from './testplanGroup.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
import TestPlanTable from './testplanTable.vue';
|
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||||
|
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
|
||||||
|
import useTable from '@/components/pure/ms-table/useTable';
|
||||||
|
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||||
|
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||||
|
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||||
|
import BatchMoveOrCopy from './batchMoveOrCopy.vue';
|
||||||
|
import ScheduledModal from './scheduledModal.vue';
|
||||||
|
import StatusProgress from './statusProgress.vue';
|
||||||
|
import statusTag from '@/views/case-management/caseReview/components/statusTag.vue';
|
||||||
|
|
||||||
|
import { getTestPlanList, getTestPlanModule } from '@/api/modules/test-plan/testPlan';
|
||||||
|
import { reviewStatusMap } from '@/config/caseManagement';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import useModal from '@/hooks/useModal';
|
||||||
|
import { useAppStore, useTableStore } from '@/store';
|
||||||
|
|
||||||
|
import { ReviewStatus } from '@/models/caseManagement/caseReview';
|
||||||
|
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
|
||||||
|
import { testPlanTypeEnum } from '@/enums/testPlanEnum';
|
||||||
|
|
||||||
|
const tableStore = useTableStore();
|
||||||
|
const appStore = useAppStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const { openModal } = useModal();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
activeFolder: string;
|
activeFolder: string;
|
||||||
activeFolderType: 'folder' | 'module';
|
activeFolderType: 'folder' | 'module';
|
||||||
|
@ -51,19 +265,669 @@
|
||||||
(e: 'init', params: any): void;
|
(e: 'init', params: any): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const columns: MsTableColumn = [
|
||||||
|
{
|
||||||
|
title: 'testPlan.testPlanIndex.ID',
|
||||||
|
slotName: 'num',
|
||||||
|
dataIndex: 'num',
|
||||||
|
width: 200,
|
||||||
|
showInTable: true,
|
||||||
|
showDrag: false,
|
||||||
|
showTooltip: true,
|
||||||
|
columnSelectorDisabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'testPlan.testPlanIndex.testPlanName',
|
||||||
|
slotName: 'name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
showInTable: true,
|
||||||
|
showTooltip: true,
|
||||||
|
width: 180,
|
||||||
|
editType: ColumnEditTypeEnum.INPUT,
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
showDrag: false,
|
||||||
|
columnSelectorDisabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'testPlan.testPlanIndex.desc',
|
||||||
|
slotName: 'desc',
|
||||||
|
showInTable: true,
|
||||||
|
width: 200,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'testPlan.testPlanIndex.executionResult',
|
||||||
|
dataIndex: 'status',
|
||||||
|
slotName: 'status',
|
||||||
|
titleSlotName: 'statusFilter',
|
||||||
|
showInTable: true,
|
||||||
|
showDrag: true,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'common.creator',
|
||||||
|
slotName: 'createUser',
|
||||||
|
dataIndex: 'createUser',
|
||||||
|
showInTable: true,
|
||||||
|
width: 200,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'testPlan.testPlanIndex.passRate',
|
||||||
|
dataIndex: 'passRate',
|
||||||
|
slotName: 'passRate',
|
||||||
|
titleSlotName: 'passRateTitleSlot',
|
||||||
|
showInTable: true,
|
||||||
|
width: 200,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'testPlan.testPlanIndex.useCount',
|
||||||
|
slotName: 'useCount',
|
||||||
|
dataIndex: 'useCount',
|
||||||
|
showTooltip: true,
|
||||||
|
showInTable: true,
|
||||||
|
width: 150,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'common.tag',
|
||||||
|
slotName: 'tags',
|
||||||
|
dataIndex: 'tags',
|
||||||
|
showInTable: true,
|
||||||
|
isTag: true,
|
||||||
|
width: 300,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'testPlan.testPlanIndex.belongModule',
|
||||||
|
slotName: 'moduleName',
|
||||||
|
dataIndex: 'moduleName',
|
||||||
|
showInTable: true,
|
||||||
|
showDrag: true,
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'testPlan.testPlanIndex.createTime',
|
||||||
|
slotName: 'createTime',
|
||||||
|
dataIndex: 'createTime',
|
||||||
|
showInTable: true,
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
width: 200,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'testPlan.testPlanIndex.planStartToEndTime',
|
||||||
|
slotName: 'planStartToEndTime',
|
||||||
|
dataIndex: 'planStartToEndTime',
|
||||||
|
showInTable: false,
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
width: 200,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'testPlan.testPlanIndex.actualStartToEndTime',
|
||||||
|
slotName: 'actualStartToEndTime',
|
||||||
|
dataIndex: 'actualStartToEndTime',
|
||||||
|
showInTable: false,
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
width: 200,
|
||||||
|
showDrag: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'testPlan.testPlanIndex.operation',
|
||||||
|
slotName: 'operation',
|
||||||
|
dataIndex: 'operation',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 200,
|
||||||
|
showInTable: true,
|
||||||
|
showDrag: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新测试计划名称
|
||||||
|
*/
|
||||||
|
async function updatePlanName() {
|
||||||
|
try {
|
||||||
|
Message.success(t('common.updateSuccess'));
|
||||||
|
return Promise.resolve(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyword = ref<string>('');
|
||||||
|
const statusFilterVisible = ref(false);
|
||||||
|
const statusFilters = ref<string[]>([]);
|
||||||
|
|
||||||
|
const tableBatchActions = {
|
||||||
|
baseAction: [
|
||||||
|
{
|
||||||
|
label: 'testPlan.testPlanIndex.execute',
|
||||||
|
eventTag: 'execute',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+EXECUTE'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.copy',
|
||||||
|
eventTag: 'copy',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+ADD'],
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// label: 'common.export',
|
||||||
|
// eventTag: 'export',
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
label: 'common.move',
|
||||||
|
eventTag: 'move',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
moreAction: [
|
||||||
|
{
|
||||||
|
label: 'testPlan.testPlanIndex.openTimingTask',
|
||||||
|
eventTag: 'openTimingTask',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'testPlan.testPlanIndex.closeTimingTask',
|
||||||
|
eventTag: 'closeTimingTask',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.archive',
|
||||||
|
eventTag: 'archive',
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isDivider: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.delete',
|
||||||
|
eventTag: 'delete',
|
||||||
|
danger: true,
|
||||||
|
permission: ['PROJECT_TEST_PLAN:READ+DELETE'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const moreActions: ActionsItem[] = [
|
||||||
|
{
|
||||||
|
label: 'common.copy',
|
||||||
|
eventTag: 'copy',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'testPlan.testPlanIndex.createScheduledTask',
|
||||||
|
eventTag: 'createScheduledTask',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'testPlan.testPlanIndex.configuration',
|
||||||
|
eventTag: 'config',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.archive',
|
||||||
|
eventTag: 'archive',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isDivider: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.delete',
|
||||||
|
danger: true,
|
||||||
|
eventTag: 'delete',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
// TODO 临时数据
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
id: '100944',
|
||||||
|
projectId: 'string',
|
||||||
|
num: '100944',
|
||||||
|
name: '系统示例',
|
||||||
|
status: 'PREPARED',
|
||||||
|
tags: ['string'],
|
||||||
|
schedule: 'string',
|
||||||
|
createUser: 'string',
|
||||||
|
createTime: 'string',
|
||||||
|
moduleName: 'string',
|
||||||
|
moduleId: 'string',
|
||||||
|
passCount: 0,
|
||||||
|
unPassCount: 0,
|
||||||
|
reviewedCount: 0,
|
||||||
|
underReviewedCount: 0,
|
||||||
|
childrenCount: 2,
|
||||||
|
statusDetail: {
|
||||||
|
tolerance: 100,
|
||||||
|
UNPENDING: 100,
|
||||||
|
RUNNING: 30,
|
||||||
|
SUCCESS: 30,
|
||||||
|
ERROR: 30,
|
||||||
|
executionProgress: '100%',
|
||||||
|
},
|
||||||
|
useCaseCount: {
|
||||||
|
caseCount: 3,
|
||||||
|
apiCount: 3,
|
||||||
|
scenarioCount: 3,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: '100945',
|
||||||
|
projectId: 'string',
|
||||||
|
num: '100945',
|
||||||
|
name: '系统示例',
|
||||||
|
status: 'COMPLETED',
|
||||||
|
tags: ['string'],
|
||||||
|
schedule: 'string',
|
||||||
|
createUser: 'string',
|
||||||
|
createTime: 'string',
|
||||||
|
moduleName: 'string',
|
||||||
|
moduleId: 'string',
|
||||||
|
testPlanItem: [],
|
||||||
|
testPlanGroupId: 'string',
|
||||||
|
passCount: 0,
|
||||||
|
unPassCount: 0,
|
||||||
|
reviewedCount: 0,
|
||||||
|
underReviewedCount: 0,
|
||||||
|
childrenCount: 0,
|
||||||
|
useCaseCount: {
|
||||||
|
caseCount: 3,
|
||||||
|
apiCount: 3,
|
||||||
|
scenarioCount: 3,
|
||||||
|
},
|
||||||
|
statusDetail: {
|
||||||
|
tolerance: 100,
|
||||||
|
UNPENDING: 100,
|
||||||
|
RUNNING: 30,
|
||||||
|
SUCCESS: 30,
|
||||||
|
ERROR: 30,
|
||||||
|
executionProgress: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '100955',
|
||||||
|
projectId: 'string',
|
||||||
|
num: '100955',
|
||||||
|
name: '系统示例',
|
||||||
|
status: 'COMPLETED',
|
||||||
|
tags: ['string'],
|
||||||
|
schedule: 'string',
|
||||||
|
createUser: 'string',
|
||||||
|
createTime: 'string',
|
||||||
|
moduleName: 'string',
|
||||||
|
moduleId: 'string',
|
||||||
|
testPlanItem: [],
|
||||||
|
testPlanGroupId: 'string',
|
||||||
|
passCount: 0,
|
||||||
|
unPassCount: 0,
|
||||||
|
reviewedCount: 0,
|
||||||
|
underReviewedCount: 0,
|
||||||
|
childrenCount: 0,
|
||||||
|
useCaseCount: {
|
||||||
|
caseCount: 3,
|
||||||
|
apiCount: 3,
|
||||||
|
scenarioCount: 3,
|
||||||
|
},
|
||||||
|
statusDetail: {
|
||||||
|
tolerance: 100,
|
||||||
|
UNPENDING: 100,
|
||||||
|
RUNNING: 30,
|
||||||
|
SUCCESS: 30,
|
||||||
|
ERROR: 30,
|
||||||
|
executionProgress: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, setProps } = useTable(
|
||||||
|
getTestPlanList,
|
||||||
|
{
|
||||||
|
tableKey: TableKeyEnum.TEST_PLAN_ALL_TABLE,
|
||||||
|
selectable: true,
|
||||||
|
showSetting: true,
|
||||||
|
heightUsed: 128,
|
||||||
|
paginationSize: 'mini',
|
||||||
|
},
|
||||||
|
(item) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
tags: (item.tags || []).map((e: string) => ({ id: e, name: e })),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const batchParams = ref<BatchActionQueryParams>({
|
||||||
|
selectedIds: [],
|
||||||
|
selectAll: false,
|
||||||
|
excludeIds: [],
|
||||||
|
currentSelectCount: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const conditionParams = ref({
|
||||||
|
keyword: '',
|
||||||
|
filter: {},
|
||||||
|
combine: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const showType = ref<keyof typeof testPlanTypeEnum>('ALL');
|
||||||
|
|
||||||
|
async function initTableParams() {
|
||||||
|
conditionParams.value = {
|
||||||
|
keyword: keyword.value,
|
||||||
|
filter: propsRes.value.filter,
|
||||||
|
combine: batchParams.value.condition,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: showType.value,
|
||||||
|
moduleIds: props.activeFolder && props.activeFolder !== 'all' ? [props.activeFolder, ...props.offspringIds] : [],
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
excludeIds: batchParams.value.excludeIds || [],
|
||||||
|
selectAll: !!batchParams.value?.selectAll,
|
||||||
|
selectIds: batchParams.value.selectedIds || [],
|
||||||
|
keyword: keyword.value,
|
||||||
|
filter: {},
|
||||||
|
condition: {
|
||||||
|
keyword: keyword.value,
|
||||||
|
},
|
||||||
|
combine: {
|
||||||
|
...batchParams.value.condition,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
|
resetSelector();
|
||||||
|
setLoadListParams(await initTableParams());
|
||||||
|
loadList();
|
||||||
|
const tableParams = await initTableParams();
|
||||||
|
emit('init', {
|
||||||
|
...tableParams,
|
||||||
|
current: propsRes.value.msPagination?.current,
|
||||||
|
pageSize: propsRes.value.msPagination?.pageSize,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量执行
|
||||||
|
*/
|
||||||
|
const executeVisible = ref<boolean>(false);
|
||||||
|
function handleExecute() {
|
||||||
|
executeVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelHandler() {
|
||||||
|
executeVisible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmLoading = ref<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行
|
||||||
|
*/
|
||||||
|
function executeHandler() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量复制或者移动
|
||||||
|
*/
|
||||||
|
const modeType = ref<'move' | 'copy'>('move');
|
||||||
|
const showBatchModal = ref<boolean>(false);
|
||||||
|
const selectNodeKeys = ref<(string | number)[]>([]);
|
||||||
|
const okLoading = ref<boolean>(false);
|
||||||
|
function handleCopyOrMove(type: 'move' | 'copy') {
|
||||||
|
modeType.value = type;
|
||||||
|
selectNodeKeys.value = [];
|
||||||
|
showBatchModal.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量移动或复制保存
|
||||||
|
*/
|
||||||
|
async function handleMoveOrCopy() {
|
||||||
|
okLoading.value = true;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
selectIds: batchParams.value.selectedIds || [],
|
||||||
|
selectAll: !!batchParams.value?.selectAll,
|
||||||
|
excludeIds: batchParams.value?.excludeIds || [],
|
||||||
|
condition: {
|
||||||
|
keyword: keyword.value,
|
||||||
|
filter: {
|
||||||
|
reviewStatus: statusFilters.value,
|
||||||
|
},
|
||||||
|
combine: batchParams.value.condition,
|
||||||
|
},
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
moduleIds: [...selectNodeKeys.value],
|
||||||
|
};
|
||||||
|
if (modeType.value === 'copy') {
|
||||||
|
Message.success(t('common.batchCopySuccess'));
|
||||||
|
} else {
|
||||||
|
Message.success(t('common.batchMoveSuccess'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
okLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开关闭定时任务
|
||||||
|
*/
|
||||||
|
function handleStatusTimingTask(status: boolean) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 归档
|
||||||
|
*/
|
||||||
|
function handleArchive() {
|
||||||
|
openModal({
|
||||||
|
type: 'warning',
|
||||||
|
title: t('testPlan.testPlanIndex.confirmBatchArchivePlan', {
|
||||||
|
count: batchParams.value.currentSelectCount,
|
||||||
|
}),
|
||||||
|
content: t('testPlan.testPlanIndex.confirmBatchDeletePlanContentTip'),
|
||||||
|
okText: t('common.archive'),
|
||||||
|
cancelText: t('common.cancel'),
|
||||||
|
okButtonProps: {
|
||||||
|
status: 'normal',
|
||||||
|
},
|
||||||
|
onBeforeOk: async () => {
|
||||||
|
try {
|
||||||
|
const { selectedIds, selectAll, excludeIds } = batchParams.value;
|
||||||
|
Message.success(t('common.deleteSuccess'));
|
||||||
|
fetchData();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideCancel: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 归档
|
||||||
|
*/
|
||||||
|
function handleDelete() {
|
||||||
|
openModal({
|
||||||
|
type: 'error',
|
||||||
|
title: t('testPlan.testPlanIndex.confirmBatchDeletePlan', {
|
||||||
|
count: batchParams.value.currentSelectCount,
|
||||||
|
}),
|
||||||
|
content: t('testPlan.testPlanIndex.confirmBatchDeletePlanContentTip'),
|
||||||
|
okText: t('common.confirmDelete'),
|
||||||
|
cancelText: t('common.cancel'),
|
||||||
|
okButtonProps: {
|
||||||
|
status: 'danger',
|
||||||
|
},
|
||||||
|
onBeforeOk: async () => {
|
||||||
|
try {
|
||||||
|
const { selectedIds, selectAll, excludeIds } = batchParams.value;
|
||||||
|
Message.success(t('common.deleteSuccess'));
|
||||||
|
fetchData();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideCancel: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量操作
|
||||||
|
*/
|
||||||
|
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
|
||||||
|
batchParams.value = params;
|
||||||
|
switch (event.eventTag) {
|
||||||
|
case 'execute':
|
||||||
|
handleExecute();
|
||||||
|
break;
|
||||||
|
case 'copy':
|
||||||
|
handleCopyOrMove('copy');
|
||||||
|
break;
|
||||||
|
case 'move':
|
||||||
|
handleCopyOrMove('move');
|
||||||
|
break;
|
||||||
|
case 'openTimingTask':
|
||||||
|
handleStatusTimingTask(true);
|
||||||
|
break;
|
||||||
|
case 'closeTimingTask':
|
||||||
|
handleStatusTimingTask(false);
|
||||||
|
break;
|
||||||
|
case 'archive':
|
||||||
|
handleArchive();
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
handleDelete();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deletePlan(record: any) {}
|
||||||
|
|
||||||
|
function copyHandler() {}
|
||||||
|
|
||||||
|
const showScheduledTaskModal = ref<boolean>(false);
|
||||||
|
function handleScheduledTask() {
|
||||||
|
showScheduledTaskModal.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMoreActionSelect(item: ActionsItem, record: any) {
|
||||||
|
switch (item.eventTag) {
|
||||||
|
case 'copy':
|
||||||
|
copyHandler();
|
||||||
|
break;
|
||||||
|
case 'createScheduledTask':
|
||||||
|
handleScheduledTask();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const expandedKeys = ref<string[]>([]);
|
||||||
|
|
||||||
|
function expandHandler(record: any) {
|
||||||
|
if (expandedKeys.value.includes(record.id)) {
|
||||||
|
expandedKeys.value = expandedKeys.value.filter((key) => key !== record.id);
|
||||||
|
} else {
|
||||||
|
expandedKeys.value = [...expandedKeys.value, record.id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIconClass(record: any) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
/** *
|
/** *
|
||||||
* 高级检索
|
* 高级检索
|
||||||
*/
|
*/
|
||||||
const filterConfigList = ref<FilterFormItem[]>([]);
|
const filterConfigList = ref<FilterFormItem[]>([]);
|
||||||
const searchCustomFields = ref<FilterFormItem[]>([]);
|
const searchCustomFields = ref<FilterFormItem[]>([]);
|
||||||
const filterRowCount = ref(0);
|
const filterRowCount = ref(0);
|
||||||
const moduleNamePath = ref<string>('全部测试计划');
|
|
||||||
|
|
||||||
const showType = ref<string>('all');
|
watch(
|
||||||
|
() => showType.value,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
function fetchData() {}
|
watch(
|
||||||
|
() => props.activeFolder,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
function handleAdvSearch() {}
|
// TODO 临时数据模拟
|
||||||
|
// onMounted(() => {
|
||||||
|
// setProps({ data });
|
||||||
|
// });
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
fetchData();
|
||||||
|
});
|
||||||
|
|
||||||
|
await tableStore.initColumn(TableKeyEnum.TEST_PLAN_ALL_TABLE, columns, 'drawer');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped lang="less">
|
||||||
|
:deep(.arco-table-cell-expand-icon .arco-table-cell-inline-icon) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
:deep(.arco-table-cell-align-left) > span:first-child {
|
||||||
|
padding-left: 0 !important;
|
||||||
|
}
|
||||||
|
.arrowIcon {
|
||||||
|
transform: scaleX(-1);
|
||||||
|
}
|
||||||
|
:deep(.ms-modal-form .arco-modal-body) {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
.popover-label-td {
|
||||||
|
@apply flex items-center;
|
||||||
|
|
||||||
|
padding: 8px 8px 0 0;
|
||||||
|
color: var(--color-text-4);
|
||||||
|
}
|
||||||
|
.popover-value-td {
|
||||||
|
@apply font-medium;
|
||||||
|
|
||||||
|
padding-top: 8px;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="showModalVisible"
|
||||||
|
class="ms-modal-form ms-modal-small"
|
||||||
|
title-align="start"
|
||||||
|
:mask-closable="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
{{ form.id ? t('testPlan.testPlanIndex.updateScheduledTask') : t('testPlan.testPlanIndex.createScheduledTask') }}
|
||||||
|
</template>
|
||||||
|
<a-form ref="formRef" :model="form" layout="vertical">
|
||||||
|
<a-form-item :label="t('testPlan.testPlanIndex.triggerTime')" asterisk-position="end">
|
||||||
|
<a-select v-model:model-value="form.time" :placeholder="t('common.pleaseSelect')">
|
||||||
|
<a-option v-for="item of syncFrequencyOptions" :key="item.value" :value="item.value">
|
||||||
|
<span class="text-[var(--color-text-2)]"> {{ item.value }}</span
|
||||||
|
><span class="ml-1 text-[var(--color-text-n4)] hover:text-[rgb(var(--primary-5))]">
|
||||||
|
{{ item.label }}
|
||||||
|
</span>
|
||||||
|
</a-option>
|
||||||
|
<template #footer>
|
||||||
|
<div class="mb-[6px] mt-[4px] p-[3px_8px]">
|
||||||
|
<MsButton type="text" class="text-[rgb(var(--primary-5))]" @click="createCustomFrequency">
|
||||||
|
{{ t('project.testPlanIndex.customFrequency') }}
|
||||||
|
</MsButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-radio-group v-model="form.env" class="mb-4">
|
||||||
|
<a-radio value="">
|
||||||
|
{{ t('project.testPlanIndex.defaultEnv') }}
|
||||||
|
<span class="float-right mx-1 mt-[1px]">
|
||||||
|
<a-tooltip :content="t('testPlan.testPlanIndex.envTip')" position="top">
|
||||||
|
<IconQuestionCircle class="h-[16px] w-[16px] text-[--color-text-4] hover:text-[rgb(var(--primary-5))]"
|
||||||
|
/></a-tooltip>
|
||||||
|
</span>
|
||||||
|
</a-radio>
|
||||||
|
<a-radio value="new"> {{ t('project.testPlanIndex.newEnv') }}</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
<a-radio-group v-model="form.methods">
|
||||||
|
<a-radio value="serial">{{ t('testPlan.testPlanIndex.serial') }}</a-radio>
|
||||||
|
<a-radio value="parallel">{{ t('testPlan.testPlanIndex.parallel') }}</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
<a-form-item :label="t('testPlan.testPlanIndex.resourcePool')" asterisk-position="end" class="mb-0">
|
||||||
|
<a-select
|
||||||
|
v-model="form.resourcePoolIds"
|
||||||
|
:placeholder="t('common.pleaseSelect')"
|
||||||
|
:field-names="{ value: 'id', label: 'name' }"
|
||||||
|
>
|
||||||
|
<template #label="{ data }">
|
||||||
|
{{ data }}
|
||||||
|
</template>
|
||||||
|
<a-option v-for="item of resourcesList" :key="item.id" :value="item.id">
|
||||||
|
<div class="flex w-full items-center justify-between">
|
||||||
|
<div>
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="text-[var(--color-text-4)]">CPU</span>
|
||||||
|
<span class="mx-2"> {{ item.cpuRate }}</span>
|
||||||
|
<MsTag theme="outline" :type="item.status ? 'link' : 'success'" size="small">
|
||||||
|
{{ item.status ? t('project.testPlanIndex.doing') : t('project.testPlanIndex.inFreeTime') }}
|
||||||
|
</MsTag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex flex-row items-center justify-center">
|
||||||
|
<a-switch v-model="form.enable" size="small" type="line" />
|
||||||
|
<span class="ml-2">{{ t('testPlan.testPlanIndex.timingState') }}</span>
|
||||||
|
<a-tooltip size="mini" position="top">
|
||||||
|
<template #content>
|
||||||
|
<div>{{ t('testPlan.testPlanIndex.timingStateEnable') }}</div>
|
||||||
|
<div>{{ t('testPlan.testPlanIndex.timingStateClose') }}</div>
|
||||||
|
</template>
|
||||||
|
<div class="mx-1 flex items-center">
|
||||||
|
<span class="mt-[2px]"
|
||||||
|
><IconQuestionCircle class="h-[16px] w-[16px] text-[--color-text-4] hover:text-[rgb(var(--primary-5))]"
|
||||||
|
/></span>
|
||||||
|
</div>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a-button type="secondary" class="mr-3" @click="handleCancel">{{ t('system.plugin.pluginCancel') }}</a-button>
|
||||||
|
<a-button type="primary" :loading="confirmLoading" @click="handleCreate">{{ t('common.create') }}</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
|
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import { useAppStore } from '@/store';
|
||||||
|
|
||||||
|
import type { ResourcesItem } from '@/models/testPlan/testPlan';
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', val: boolean): void;
|
||||||
|
}>();
|
||||||
|
const showModalVisible = useVModel(props, 'visible', emit);
|
||||||
|
|
||||||
|
const initForm = {
|
||||||
|
id: '',
|
||||||
|
time: '',
|
||||||
|
env: '',
|
||||||
|
resourcePoolIds: '',
|
||||||
|
enable: false,
|
||||||
|
methods: 'parallel',
|
||||||
|
};
|
||||||
|
|
||||||
|
const form = ref({ ...initForm });
|
||||||
|
|
||||||
|
const confirmLoading = ref<boolean>(false);
|
||||||
|
const formRef = ref();
|
||||||
|
function handleCreate() {}
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
form.value = { ...initForm };
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
showModalVisible.value = false;
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncFrequencyOptions = [
|
||||||
|
{ label: t('apiTestManagement.timeTaskHour'), value: '0 0 0/1 * * ?' },
|
||||||
|
{ label: t('apiTestManagement.timeTaskSixHour'), value: '0 0 0/6 * * ?' },
|
||||||
|
{ label: t('apiTestManagement.timeTaskTwelveHour'), value: '0 0 0/12 * * ?' },
|
||||||
|
{ label: t('apiTestManagement.timeTaskDay'), value: '0 0 0 * * ?' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const resourcesList = ref<ResourcesItem[]>([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: '200.4',
|
||||||
|
cpuRate: '80%',
|
||||||
|
status: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
name: 'LOCAL',
|
||||||
|
cpuRate: '80%',
|
||||||
|
status: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
function createCustomFrequency() {}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
:deep(.arco-select-option-content) {
|
||||||
|
@apply w-full;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,137 @@
|
||||||
|
<template>
|
||||||
|
<MsColorLine :color-data="colorData" :height="props.height" :radius="props.radius">
|
||||||
|
<template #popoverContent>
|
||||||
|
<table class="min-w-[144px]">
|
||||||
|
<tr>
|
||||||
|
<td class="popover-label-td">
|
||||||
|
<div>{{ t('project.testPlanIndex.tolerance') }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="popover-value-td">
|
||||||
|
{{ props.statusDetail.tolerance }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="popover-label-td">
|
||||||
|
<div>{{ t('project.testPlanIndex.executionProgress') }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="popover-value-td">
|
||||||
|
{{ props.statusDetail.executionProgress }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="popover-label-td">
|
||||||
|
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[var(--color-text-input-border)]"></div>
|
||||||
|
<div>{{ t('common.unExecute') }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="popover-value-td">
|
||||||
|
{{ props.statusDetail.UNPENDING }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="popover-label-td">
|
||||||
|
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--link-6))]"></div>
|
||||||
|
<div>{{ t('common.running') }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="popover-value-td">
|
||||||
|
{{ props.statusDetail.RUNNING }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="popover-label-td">
|
||||||
|
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--success-6))]"></div>
|
||||||
|
<div>{{ t('common.pass') }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="popover-value-td">
|
||||||
|
{{ props.statusDetail.SUCCESS }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="popover-label-td">
|
||||||
|
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--danger-6))]"></div>
|
||||||
|
<div>{{ t('common.unPass') }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="popover-value-td">
|
||||||
|
{{ props.statusDetail.ERROR }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
</MsColorLine>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import MsColorLine from '@/components/pure/ms-color-line/index.vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
statusDetail: {
|
||||||
|
tolerance: number;
|
||||||
|
UNPENDING: number;
|
||||||
|
RUNNING: number;
|
||||||
|
SUCCESS: number;
|
||||||
|
ERROR: number;
|
||||||
|
executionProgress: string;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
height: string;
|
||||||
|
radius?: string;
|
||||||
|
}>();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const getCountTotal = computed(() => {
|
||||||
|
const { UNPENDING, RUNNING, ERROR, SUCCESS } = props.statusDetail;
|
||||||
|
return UNPENDING + RUNNING + ERROR + SUCCESS;
|
||||||
|
});
|
||||||
|
|
||||||
|
const colorData = computed(() => {
|
||||||
|
if (
|
||||||
|
props.statusDetail.UNPENDING === 0 &&
|
||||||
|
props.statusDetail.RUNNING === 0 &&
|
||||||
|
props.statusDetail.ERROR === 0 &&
|
||||||
|
props.statusDetail.SUCCESS === 0
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
percentage: 100,
|
||||||
|
color: 'var(--color-text-n8)',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
percentage: (props.statusDetail.SUCCESS / getCountTotal.value) * 100,
|
||||||
|
color: 'rgb(var(--success-6))',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
percentage: (props.statusDetail.ERROR / getCountTotal.value) * 100,
|
||||||
|
color: 'rgb(var(--danger-6))',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
percentage: (props.statusDetail.RUNNING / getCountTotal.value) * 100,
|
||||||
|
color: 'rgb(var(--link-6))',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
percentage: (props.statusDetail.UNPENDING / getCountTotal.value) * 100,
|
||||||
|
color: 'var(--color-text-input-border)',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.popover-label-td {
|
||||||
|
@apply flex items-center;
|
||||||
|
|
||||||
|
padding: 8px 8px 0 0;
|
||||||
|
color: var(--color-text-4);
|
||||||
|
}
|
||||||
|
.popover-value-td {
|
||||||
|
@apply font-medium;
|
||||||
|
|
||||||
|
padding-top: 8px;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,11 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<a-input-search
|
<a-spin class="min-h-[400px] w-full" :loading="loading">
|
||||||
v-model:model-value="groupKeyword"
|
|
||||||
:placeholder="t('caseManagement.featureCase.searchTip')"
|
|
||||||
allow-clear
|
|
||||||
class="mb-[16px]"
|
|
||||||
></a-input-search>
|
|
||||||
<a-spin class="w-full" :style="{ height: `calc(100vh - 346px)` }" :loading="loading">
|
|
||||||
<MsTree
|
<MsTree
|
||||||
v-model:focus-node-key="focusNodeKey"
|
v-model:focus-node-key="focusNodeKey"
|
||||||
:selected-keys="props.selectedKeys"
|
:selected-keys="props.selectedKeys"
|
||||||
|
@ -32,12 +26,12 @@
|
||||||
<template #title="nodeData">
|
<template #title="nodeData">
|
||||||
<div class="inline-flex w-full gap-[8px]">
|
<div class="inline-flex w-full gap-[8px]">
|
||||||
<div class="one-line-text w-full text-[var(--color-text-1)]">{{ nodeData.name }}</div>
|
<div class="one-line-text w-full text-[var(--color-text-1)]">{{ nodeData.name }}</div>
|
||||||
<div v-if="!props.isModal" class="ms-tree-node-count ml-[4px] text-[var(--color-text-brand)]">
|
<div class="ms-tree-node-count ml-[4px] text-[var(--color-text-brand)]">
|
||||||
{{ nodeData.count || 0 }}
|
{{ nodeData.count || 0 }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="!props.isModal" #extra="nodeData">
|
<template #extra="nodeData">
|
||||||
<MsPopConfirm
|
<MsPopConfirm
|
||||||
:visible="addSubVisible"
|
:visible="addSubVisible"
|
||||||
:is-delete="false"
|
:is-delete="false"
|
||||||
|
@ -84,6 +78,7 @@
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createPlanModuleTree,
|
createPlanModuleTree,
|
||||||
|
deletePlanModuleTree,
|
||||||
getTestPlanModule,
|
getTestPlanModule,
|
||||||
moveTestPlanModuleTree,
|
moveTestPlanModuleTree,
|
||||||
updatePlanModuleTree,
|
updatePlanModuleTree,
|
||||||
|
@ -103,7 +98,6 @@
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
isModal?: boolean; // 是否是弹窗模式
|
|
||||||
activeFolder?: string; // 当前选中的文件夹,弹窗模式下需要使用
|
activeFolder?: string; // 当前选中的文件夹,弹窗模式下需要使用
|
||||||
selectedKeys?: Array<string | number>; // 选中的节点 key
|
selectedKeys?: Array<string | number>; // 选中的节点 key
|
||||||
isExpandAll: boolean; // 是否展开用例节点
|
isExpandAll: boolean; // 是否展开用例节点
|
||||||
|
@ -111,7 +105,7 @@
|
||||||
modulesCount?: Record<string, number>; // 模块数量统计对象
|
modulesCount?: Record<string, number>; // 模块数量统计对象
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emits = defineEmits(['update:selectedKeys', 'planTreeNodeSelect', 'init']);
|
const emits = defineEmits(['update:selectedKeys', 'planTreeNodeSelect', 'init', 'dragUpdate']);
|
||||||
|
|
||||||
const currentProjectId = computed(() => appStore.currentProjectId);
|
const currentProjectId = computed(() => appStore.currentProjectId);
|
||||||
|
|
||||||
|
@ -162,8 +156,8 @@
|
||||||
return {
|
return {
|
||||||
...e,
|
...e,
|
||||||
hideMoreAction: e.id === 'root',
|
hideMoreAction: e.id === 'root',
|
||||||
draggable: e.id !== 'root' && !props.isModal,
|
draggable: e.id !== 'root',
|
||||||
disabled: e.id === props.activeFolder && props.isModal,
|
disabled: e.id === props.activeFolder,
|
||||||
count: props.modulesCount?.[e.id] || 0,
|
count: props.modulesCount?.[e.id] || 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -194,7 +188,13 @@
|
||||||
},
|
},
|
||||||
maskClosable: false,
|
maskClosable: false,
|
||||||
onBeforeOk: async () => {
|
onBeforeOk: async () => {
|
||||||
Message.success(t('caseManagement.featureCase.deleteSuccess'));
|
try {
|
||||||
|
await deletePlanModuleTree(node.id);
|
||||||
|
Message.success(t('common.deleteSuccess'));
|
||||||
|
initModules(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
hideCancel: false,
|
hideCancel: false,
|
||||||
});
|
});
|
||||||
|
@ -262,7 +262,15 @@
|
||||||
console.log(error);
|
console.log(error);
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
initModules();
|
await initModules();
|
||||||
|
const treeNode = ref<MsTreeNodeData | null>(null);
|
||||||
|
treeNode.value = dropNode;
|
||||||
|
treeNode.value.children = [];
|
||||||
|
if (dropPosition === 0) {
|
||||||
|
treeNode.value.children.push(dragNode);
|
||||||
|
}
|
||||||
|
caseNodeSelect(dropNode.id, treeNode.value);
|
||||||
|
emits('dragUpdate');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,10 +328,10 @@
|
||||||
|
|
||||||
const virtualListProps = computed(() => {
|
const virtualListProps = computed(() => {
|
||||||
return {
|
return {
|
||||||
height: 'calc(100vh - 366px)',
|
height: 'calc(100vh - 240px)',
|
||||||
threshold: 200,
|
threshold: 200,
|
||||||
fixedSize: true,
|
fixedSize: true,
|
||||||
buffer: 15, // 缓冲区默认 10 的时候,虚拟滚动的底部 padding 计算有问题
|
buffer: 15,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>测试计划组 </div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
|
@ -1,9 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>测试计划 </div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
|
@ -1,15 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="rounded-2xl bg-white">
|
<MsCard simple no-content-padding>
|
||||||
<div class="p-[24px] pb-[16px]">
|
|
||||||
<a-button v-permission="['PROJECT_TEST_PLAN:READ+ADD']" type="primary">
|
|
||||||
{{ t('testPlan.testPlanIndex.createTestPlan') }}
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
<a-divider class="!my-0" />
|
|
||||||
<div class="pageWrap">
|
|
||||||
<MsSplitBox>
|
<MsSplitBox>
|
||||||
<template #first>
|
<template #first>
|
||||||
<div class="p-[24px] pb-0">
|
<div class="p-[16px] pb-0">
|
||||||
|
<div class="mb-[16px] flex justify-between">
|
||||||
|
<a-input-search
|
||||||
|
v-model:model-value="groupKeyword"
|
||||||
|
:placeholder="t('caseManagement.featureCase.searchTip')"
|
||||||
|
allow-clear
|
||||||
|
/>
|
||||||
|
<a-dropdown-button class="ml-2" type="primary" @click="handleSelect">
|
||||||
|
{{ t('common.newCreate') }}
|
||||||
|
<template #icon>
|
||||||
|
<icon-down />
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<a-doption v-permission="['FUNCTIONAL_CASE:READ+IMPORT']" value="Excel">
|
||||||
|
{{ t('testPlan.testPlanIndex.newCreatePlanGroup') }}
|
||||||
|
</a-doption>
|
||||||
|
</template>
|
||||||
|
</a-dropdown-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="test-plan h-[100%]">
|
<div class="test-plan h-[100%]">
|
||||||
<div class="case h-[38px]">
|
<div class="case h-[38px]">
|
||||||
<div class="flex items-center" :class="getActiveClass('all')" @click="setActiveFolder('all')">
|
<div class="flex items-center" :class="getActiveClass('all')" @click="setActiveFolder('all')">
|
||||||
|
@ -50,7 +62,6 @@
|
||||||
</MsPopConfirm>
|
</MsPopConfirm>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a-divider class="my-[8px]" />
|
|
||||||
<TestPlanTree
|
<TestPlanTree
|
||||||
ref="planTreeRef"
|
ref="planTreeRef"
|
||||||
v-model:selected-keys="selectedKeys"
|
v-model:selected-keys="selectedKeys"
|
||||||
|
@ -65,7 +76,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #second>
|
<template #second>
|
||||||
<div class="p-[24px]">
|
<div class="p-[16px]">
|
||||||
<PlanTable
|
<PlanTable
|
||||||
:active-folder="activeFolder"
|
:active-folder="activeFolder"
|
||||||
:offspring-ids="offspringIds"
|
:offspring-ids="offspringIds"
|
||||||
|
@ -76,8 +87,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</MsSplitBox>
|
</MsSplitBox>
|
||||||
</div>
|
</MsCard>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -85,6 +95,7 @@
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
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 MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
import MsPopConfirm from '@/components/pure/ms-popconfirm/index.vue';
|
import MsPopConfirm from '@/components/pure/ms-popconfirm/index.vue';
|
||||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||||
|
@ -107,6 +118,8 @@
|
||||||
|
|
||||||
const activeFolder = ref<string>('all');
|
const activeFolder = ref<string>('all');
|
||||||
|
|
||||||
|
const groupKeyword = ref<string>('');
|
||||||
|
|
||||||
// 获取激活用例类型样式
|
// 获取激活用例类型样式
|
||||||
const getActiveClass = (type: string) => {
|
const getActiveClass = (type: string) => {
|
||||||
return activeFolder.value === type ? 'folder-text case-active' : 'folder-text';
|
return activeFolder.value === type ? 'folder-text case-active' : 'folder-text';
|
||||||
|
@ -181,14 +194,11 @@
|
||||||
* 右侧表格数据刷新后,若当前展示的是模块,则刷新模块树的统计数量
|
* 右侧表格数据刷新后,若当前展示的是模块,则刷新模块树的统计数量
|
||||||
*/
|
*/
|
||||||
function initModulesCount(params: any) {}
|
function initModulesCount(params: any) {}
|
||||||
|
|
||||||
|
function handleSelect() {}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="less">
|
||||||
.pageWrap {
|
|
||||||
min-width: 1000px;
|
|
||||||
height: calc(100vh - 166px);
|
|
||||||
border-radius: var(--border-radius-large);
|
|
||||||
@apply bg-white;
|
|
||||||
.case {
|
.case {
|
||||||
padding: 8px 4px;
|
padding: 8px 4px;
|
||||||
border-radius: var(--border-radius-small);
|
border-radius: var(--border-radius-small);
|
||||||
|
@ -227,5 +237,4 @@
|
||||||
@apply flex cursor-pointer items-center rounded-full;
|
@apply flex cursor-pointer items-center rounded-full;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -13,12 +13,51 @@ export default {
|
||||||
'testPlan.testPlanIndex.testPlanName': 'name',
|
'testPlan.testPlanIndex.testPlanName': 'name',
|
||||||
'testPlan.testPlanIndex.ID': 'ID',
|
'testPlan.testPlanIndex.ID': 'ID',
|
||||||
'testPlan.testPlanIndex.desc': 'Description',
|
'testPlan.testPlanIndex.desc': 'Description',
|
||||||
'testPlan.testPlanIndex.status': 'Execution state',
|
'testPlan.testPlanIndex.executionResult': 'Execution Result',
|
||||||
'testPlan.testPlanIndex.passRate': 'Pass Rate',
|
'testPlan.testPlanIndex.passRate': 'Pass Rate',
|
||||||
'testPlan.testPlanIndex.useCount': 'Use cases',
|
'testPlan.testPlanIndex.useCount': 'Use cases',
|
||||||
'testPlan.testPlanIndex.bugCount': 'bug count',
|
'testPlan.testPlanIndex.bugCount': 'bug count',
|
||||||
'testPlan.testPlanIndex.belongModule': 'belong module',
|
'testPlan.testPlanIndex.belongModule': 'belong module',
|
||||||
'testPlan.testPlanIndex.creator': 'creator',
|
|
||||||
'testPlan.testPlanIndex.createTime': 'create time',
|
'testPlan.testPlanIndex.createTime': 'create time',
|
||||||
'testPlan.testPlanIndex.operation': 'operation',
|
'testPlan.testPlanIndex.operation': 'operation',
|
||||||
|
'testPlan.testPlanIndex.newCreatePlanGroup': 'New Plan group',
|
||||||
|
'testPlan.testPlanIndex.planStartToEndTime': 'plan times',
|
||||||
|
'testPlan.testPlanIndex.actualStartToEndTime': 'Actual times',
|
||||||
|
'testPlan.testPlanIndex.timing': 'Timing',
|
||||||
|
'testPlan.testPlanIndex.execute': 'execute',
|
||||||
|
'testPlan.testPlanIndex.openTimingTask': 'Open timing task',
|
||||||
|
'testPlan.testPlanIndex.closeTimingTask': 'Turn off a timed task',
|
||||||
|
'testPlan.testPlanIndex.scheduledTaskOpened': 'The scheduled task has been enabled',
|
||||||
|
'testPlan.testPlanIndex.nextExecutionTime': 'Next execution time:',
|
||||||
|
'testPlan.testPlanIndex.scheduledTaskUnEnable': 'The scheduled task is not enabled',
|
||||||
|
'testPlan.testPlanIndex.batchExecution': 'Batch execution',
|
||||||
|
'testPlan.testPlanIndex.serial': 'Serial',
|
||||||
|
'testPlan.testPlanIndex.parallel': 'Parallel',
|
||||||
|
'testPlan.testPlanIndex.confirmBatchDeletePlan': 'Are you sure to delete {count} test plans?',
|
||||||
|
'testPlan.testPlanIndex.confirmBatchDeletePlanContentTip':
|
||||||
|
'Only sub-plans within the test plan and plan group are deleted',
|
||||||
|
'testPlan.testPlanIndex.confirmBatchArchivePlan': 'Are you sure to archive {count} test plans?',
|
||||||
|
'testPlan.testPlanIndex.confirmBatchArchivePlanContent':
|
||||||
|
'Only completed test plans can be archived! \n after filing, implement information no longer update and editing, data unrecoverable, please careful operation.',
|
||||||
|
'testPlan.testPlanIndex.selectedCount': '{count} data selected',
|
||||||
|
'testPlan.testPlanIndex.createScheduledTask': 'Create Scheduled Task',
|
||||||
|
'testPlan.testPlanIndex.updateScheduledTask': 'Update Scheduled Task',
|
||||||
|
'testPlan.testPlanIndex.configuration': 'config',
|
||||||
|
'testPlan.testPlanIndex.triggerTime': 'Trigger time',
|
||||||
|
'testPlan.testPlanIndex.envTip': 'Use case save environment',
|
||||||
|
'testPlan.testPlanIndex.resourcePool': 'Resource pool',
|
||||||
|
'testPlan.testPlanIndex.timingState': 'Timing state',
|
||||||
|
'testPlan.testPlanIndex.timingStateEnable': 'Enable: Executes scheduled tasks',
|
||||||
|
'testPlan.testPlanIndex.timingStateClose': 'Close: Stops a scheduled task',
|
||||||
|
'project.testPlanIndex.customFrequency': 'Custom frequency',
|
||||||
|
'project.testPlanIndex.doing': 'Doing',
|
||||||
|
'project.testPlanIndex.inFreeTime': 'In free time',
|
||||||
|
'project.testPlanIndex.defaultEnv': 'Default Environment',
|
||||||
|
'project.testPlanIndex.newEnv': 'New Environment',
|
||||||
|
'project.testPlanIndex.executionProgress': 'Execution progress',
|
||||||
|
'project.testPlanIndex.tolerance': 'tolerance',
|
||||||
|
'project.testPlanIndex.TotalCases': 'Total use cases',
|
||||||
|
'project.testPlanIndex.functionalUseCase': 'case',
|
||||||
|
'project.testPlanIndex.apiCase': 'Api use case',
|
||||||
|
'project.testPlanIndex.apiScenarioCase': 'Api scenario use cases',
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,14 +13,51 @@ export default {
|
||||||
'testPlan.testPlanIndex.testPlanName': '测试计划名称',
|
'testPlan.testPlanIndex.testPlanName': '测试计划名称',
|
||||||
'testPlan.testPlanIndex.ID': 'ID',
|
'testPlan.testPlanIndex.ID': 'ID',
|
||||||
'testPlan.testPlanIndex.desc': '描述',
|
'testPlan.testPlanIndex.desc': '描述',
|
||||||
'testPlan.testPlanIndex.status': '执行状态',
|
'testPlan.testPlanIndex.executionResult': '执行结果',
|
||||||
'testPlan.testPlanIndex.passRate': '通过率',
|
'testPlan.testPlanIndex.passRate': '通过率',
|
||||||
'testPlan.testPlanIndex.useCount': '用例数',
|
'testPlan.testPlanIndex.useCount': '用例数',
|
||||||
'testPlan.testPlanIndex.bugCount': 'bug数',
|
'testPlan.testPlanIndex.bugCount': 'bug数',
|
||||||
'testPlan.testPlanIndex.belongModule': '所属模块',
|
'testPlan.testPlanIndex.belongModule': '所属模块',
|
||||||
'testPlan.testPlanIndex.creator': '创建人',
|
|
||||||
'testPlan.testPlanIndex.createTime': '创建时间',
|
'testPlan.testPlanIndex.createTime': '创建时间',
|
||||||
'testPlan.testPlanIndex.operation': '操作',
|
'testPlan.testPlanIndex.operation': '操作',
|
||||||
'testPlan.testPlanIndex.execution': '执行',
|
'testPlan.testPlanIndex.execution': '执行',
|
||||||
'testPlan.testPlanIndex.copy': '复制',
|
'testPlan.testPlanIndex.newCreatePlanGroup': '新建计划组',
|
||||||
|
'testPlan.testPlanIndex.planStartToEndTime': '计划起止时间',
|
||||||
|
'testPlan.testPlanIndex.actualStartToEndTime': '实际起止时间',
|
||||||
|
'testPlan.testPlanIndex.timing': '定时',
|
||||||
|
'testPlan.testPlanIndex.execute': '执行',
|
||||||
|
'testPlan.testPlanIndex.openTimingTask': '开启定时任务',
|
||||||
|
'testPlan.testPlanIndex.closeTimingTask': '关闭定时任务',
|
||||||
|
'testPlan.testPlanIndex.scheduledTaskOpened': '定时任务已开启',
|
||||||
|
'testPlan.testPlanIndex.nextExecutionTime': '下次执行时间:',
|
||||||
|
'testPlan.testPlanIndex.scheduledTaskUnEnable': '定时任务未开启',
|
||||||
|
'testPlan.testPlanIndex.batchExecution': '批量执行',
|
||||||
|
'testPlan.testPlanIndex.serial': '串行',
|
||||||
|
'testPlan.testPlanIndex.parallel': '并行',
|
||||||
|
'testPlan.testPlanIndex.confirmBatchDeletePlan': '确认删除 {count} 个测试计划吗?',
|
||||||
|
'testPlan.testPlanIndex.confirmBatchDeletePlanContentTip': '仅删除测试计划和计划组内的子计划',
|
||||||
|
'testPlan.testPlanIndex.confirmBatchArchivePlan': '确认归档 {count} 个测试计划吗?',
|
||||||
|
'testPlan.testPlanIndex.confirmBatchArchivePlanContent':
|
||||||
|
'仅 已完成 测试计划可归档!\n 归档后,执行信息不再更新且不可编辑,数据不可恢复,请谨慎操作!',
|
||||||
|
'testPlan.testPlanIndex.selectedCount': '(已选 {count} 项数据)',
|
||||||
|
'testPlan.testPlanIndex.createScheduledTask': '创建定时任务',
|
||||||
|
'testPlan.testPlanIndex.updateScheduledTask': '更新定时任务',
|
||||||
|
'testPlan.testPlanIndex.configuration': '配置',
|
||||||
|
'testPlan.testPlanIndex.triggerTime': '触发时间',
|
||||||
|
'testPlan.testPlanIndex.envTip': '用例保存的环境',
|
||||||
|
'testPlan.testPlanIndex.resourcePool': '资源池',
|
||||||
|
'testPlan.testPlanIndex.timingState': '定时状态',
|
||||||
|
'testPlan.testPlanIndex.timingStateEnable': '开启:执行定时任务',
|
||||||
|
'testPlan.testPlanIndex.timingStateClose': '关闭:停止定时任务',
|
||||||
|
'project.testPlanIndex.customFrequency': '自定义频率',
|
||||||
|
'project.testPlanIndex.doing': '进行中',
|
||||||
|
'project.testPlanIndex.inFreeTime': '空闲中',
|
||||||
|
'project.testPlanIndex.defaultEnv': '默认环境',
|
||||||
|
'project.testPlanIndex.newEnv': '新环境',
|
||||||
|
'project.testPlanIndex.executionProgress': '执行进度',
|
||||||
|
'project.testPlanIndex.tolerance': '容错率',
|
||||||
|
'project.testPlanIndex.TotalCases': '用例总数',
|
||||||
|
'project.testPlanIndex.functionalUseCase': '功能用例',
|
||||||
|
'project.testPlanIndex.apiCase': '接口用例',
|
||||||
|
'project.testPlanIndex.apiScenarioCase': '接口场景用例',
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue