feat(测试计划): 测试计划模块和测试计划列表静态页面
This commit is contained in:
parent
b642981f2f
commit
1ceb194def
|
@ -2,17 +2,21 @@ import MSR from '@/api/http/index';
|
|||
import {
|
||||
addTestPlanModuleUrl,
|
||||
DeleteTestPlanModuleUrl,
|
||||
GetTestPlanListUrl,
|
||||
GetTestPlanModuleCountUrl,
|
||||
GetTestPlanModuleUrl,
|
||||
MoveTestPlanModuleUrl,
|
||||
updateTestPlanModuleUrl,
|
||||
} 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 { ModuleTreeNode } from '@/models/common';
|
||||
import type { TestPlanItem } from '@/models/testPlan/testPlan';
|
||||
|
||||
// 获取模块树
|
||||
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) {
|
||||
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 GetTestPlanModuleCountUrl = '/test-plan/module/count';
|
||||
// 测试计划列表
|
||||
export const GetTestPlanListUrl = '/test-plan/page';
|
||||
|
|
|
@ -80,8 +80,21 @@
|
|||
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 = () => {
|
||||
if (props.currentData.some((item) => !props.selectedKeys.has(item[props.rowKey]))) {
|
||||
if (hasUnselectedChildren(props.currentData, props.selectedKeys, props.rowKey)) {
|
||||
// 当前页有数据没有勾选上,此时点击全选按钮代表全部选中
|
||||
handleSelect(SelectAllEnum.CURRENT);
|
||||
} else {
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
const getTagWidth = (tag: { [x: string]: any }) => {
|
||||
const tagStr = props.isStringTag ? tag : tag[props.nameKey];
|
||||
|
||||
const tagWidth = tagStr.length;
|
||||
const tagWidth = tagStr ? tagStr.length : 0;
|
||||
// 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.batchMove': 'Batch move',
|
||||
'common.batchCopy': 'Batch copy',
|
||||
'common.batchCopySuccess': 'Batch copy successful',
|
||||
'common.batchMoveSuccess': 'Batch move successful',
|
||||
'common.importSuccess': 'Import successful',
|
||||
'common.nameIsTooLang': 'The name exceeds 255 characters',
|
||||
|
@ -157,4 +158,10 @@ export default {
|
|||
'common.text': 'Text',
|
||||
'common.resourceDeleted': 'Resource has been deleted',
|
||||
'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.batchMove': '批量移动',
|
||||
'common.batchCopy': '批量复制',
|
||||
'common.batchCopySuccess': '批量复制成功',
|
||||
'common.batchMoveSuccess': '批量移动成功',
|
||||
'common.importSuccess': '导入成功',
|
||||
'common.nameIsTooLang': '名称超过255个字符',
|
||||
|
@ -157,4 +158,10 @@ export default {
|
|||
'common.text': '文本',
|
||||
'common.resourceDeleted': '资源已被删除',
|
||||
'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 {};
|
||||
|
|
|
@ -3,31 +3,31 @@ import { TestPlanRouteEnum } from '@/enums/routeEnum';
|
|||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
|
||||
// const TestPlan: AppRouteRecordRaw = {
|
||||
// path: '/test-plan',
|
||||
// name: TestPlanRouteEnum.TEST_PLAN,
|
||||
// redirect: '/test-plan/testPlanIndex',
|
||||
// component: DEFAULT_LAYOUT,
|
||||
// meta: {
|
||||
// locale: 'menu.testPlan',
|
||||
// icon: 'icon-icon_test-tracking_filled',
|
||||
// order: 1,
|
||||
// hideChildrenInMenu: true,
|
||||
// roles: ['TEST_PLAN:READ'],
|
||||
// },
|
||||
// children: [
|
||||
// // 测试计划
|
||||
// {
|
||||
// path: 'testPlanIndex',
|
||||
// name: TestPlanRouteEnum.TEST_PLAN_INDEX,
|
||||
// component: () => import('@/views/test-plan/testPlan/index.vue'),
|
||||
// meta: {
|
||||
// locale: 'menu.testPlan',
|
||||
// roles: ['TEST_PLAN:READ'],
|
||||
// isTopMenu: true,
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
const TestPlan: AppRouteRecordRaw = {
|
||||
path: '/test-plan',
|
||||
name: TestPlanRouteEnum.TEST_PLAN,
|
||||
redirect: '/test-plan/testPlanIndex',
|
||||
component: DEFAULT_LAYOUT,
|
||||
meta: {
|
||||
locale: 'menu.testPlan',
|
||||
icon: 'icon-icon_test-tracking_filled',
|
||||
order: 1,
|
||||
hideChildrenInMenu: true,
|
||||
roles: ['*'],
|
||||
},
|
||||
children: [
|
||||
// 测试计划
|
||||
{
|
||||
path: 'testPlanIndex',
|
||||
name: TestPlanRouteEnum.TEST_PLAN_INDEX,
|
||||
component: () => import('@/views/test-plan/testPlan/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.testPlan',
|
||||
roles: ['*'],
|
||||
isTopMenu: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// export default TestPlan;
|
||||
export default TestPlan;
|
||||
|
|
|
@ -7,7 +7,12 @@
|
|||
allow-clear
|
||||
: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') }}
|
||||
</a-button>
|
||||
</div>
|
||||
|
@ -225,7 +230,7 @@
|
|||
};
|
||||
return {
|
||||
...e,
|
||||
hideMoreAction: e.id === 'root',
|
||||
hideMoreAction: e.id === 'root' || props.isModal,
|
||||
draggable: e.id !== 'root' && !props.isModal,
|
||||
disabled: e.id === activeFolder.value && props.isModal,
|
||||
count: props.modulesCount?.[e.id] || 0, // 避免模块数量先初始化完成了,数量没更新
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
v-bind="propsRes"
|
||||
ref="tableRef"
|
||||
:action-config="tableBatchActions"
|
||||
:selectable="hasOperationPermission"
|
||||
v-on="propsEvent"
|
||||
@batch-action="handleTableBatch"
|
||||
>
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
v-bind="propsRes"
|
||||
ref="tableRef"
|
||||
:action-config="tableBatchActions"
|
||||
:selectable="hasOperationPermission"
|
||||
v-on="propsEvent"
|
||||
@batch-action="handleTableBatch"
|
||||
>
|
||||
|
|
|
@ -78,7 +78,6 @@
|
|||
<a-button type="secondary" @click="cancelHandler">
|
||||
{{ t('common.cancel') }}
|
||||
</a-button>
|
||||
<slot name="self-button"></slot>
|
||||
<a-button
|
||||
class="ml-3"
|
||||
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>
|
||||
<MsAdvanceFilter
|
||||
v-model:keyword="keyword"
|
||||
:filter-config-list="filterConfigList"
|
||||
:custom-fields-config-list="searchCustomFields"
|
||||
:row-count="filterRowCount"
|
||||
:search-placeholder="t('common.searchByNameAndId')"
|
||||
@keyword-search="fetchData"
|
||||
@adv-search="handleAdvSearch"
|
||||
@adv-search="fetchData"
|
||||
@refresh="fetchData"
|
||||
>
|
||||
<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 value="all" class="show-type-icon p-[2px]">{{ t('testPlan.testPlanIndex.all') }}</a-radio>
|
||||
<a-radio value="testPlan" 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 :value="testPlanTypeEnum.ALL" class="show-type-icon p-[2px]">{{
|
||||
t('testPlan.testPlanIndex.all')
|
||||
}}</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>
|
||||
</div>
|
||||
</template>
|
||||
</MsAdvanceFilter>
|
||||
<AllTable v-if="showType === 'all'" />
|
||||
<TestPlanTable v-if="showType === 'testPlan'" />
|
||||
<TestPlanGroupTable v-if="showType === 'testPlanGroup'" />
|
||||
<MsBaseTable
|
||||
v-bind="propsRes"
|
||||
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>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -33,13 +226,34 @@
|
|||
|
||||
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
|
||||
import { FilterFormItem } from '@/components/pure/ms-advance-filter/type';
|
||||
import AllTable from './allTable.vue';
|
||||
import TestPlanGroupTable from './testplanGroup.vue';
|
||||
import TestPlanTable from './testplanTable.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 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 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 { openModal } = useModal();
|
||||
|
||||
const props = defineProps<{
|
||||
activeFolder: string;
|
||||
activeFolderType: 'folder' | 'module';
|
||||
|
@ -51,19 +265,669 @@
|
|||
(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 searchCustomFields = ref<FilterFormItem[]>([]);
|
||||
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>
|
||||
|
||||
<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>
|
||||
<a-input-search
|
||||
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">
|
||||
<a-spin class="min-h-[400px] w-full" :loading="loading">
|
||||
<MsTree
|
||||
v-model:focus-node-key="focusNodeKey"
|
||||
:selected-keys="props.selectedKeys"
|
||||
|
@ -32,12 +26,12 @@
|
|||
<template #title="nodeData">
|
||||
<div class="inline-flex w-full gap-[8px]">
|
||||
<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 }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!props.isModal" #extra="nodeData">
|
||||
<template #extra="nodeData">
|
||||
<MsPopConfirm
|
||||
:visible="addSubVisible"
|
||||
:is-delete="false"
|
||||
|
@ -84,6 +78,7 @@
|
|||
|
||||
import {
|
||||
createPlanModuleTree,
|
||||
deletePlanModuleTree,
|
||||
getTestPlanModule,
|
||||
moveTestPlanModuleTree,
|
||||
updatePlanModuleTree,
|
||||
|
@ -103,7 +98,6 @@
|
|||
const loading = ref(false);
|
||||
|
||||
const props = defineProps<{
|
||||
isModal?: boolean; // 是否是弹窗模式
|
||||
activeFolder?: string; // 当前选中的文件夹,弹窗模式下需要使用
|
||||
selectedKeys?: Array<string | number>; // 选中的节点 key
|
||||
isExpandAll: boolean; // 是否展开用例节点
|
||||
|
@ -111,7 +105,7 @@
|
|||
modulesCount?: Record<string, number>; // 模块数量统计对象
|
||||
}>();
|
||||
|
||||
const emits = defineEmits(['update:selectedKeys', 'planTreeNodeSelect', 'init']);
|
||||
const emits = defineEmits(['update:selectedKeys', 'planTreeNodeSelect', 'init', 'dragUpdate']);
|
||||
|
||||
const currentProjectId = computed(() => appStore.currentProjectId);
|
||||
|
||||
|
@ -162,8 +156,8 @@
|
|||
return {
|
||||
...e,
|
||||
hideMoreAction: e.id === 'root',
|
||||
draggable: e.id !== 'root' && !props.isModal,
|
||||
disabled: e.id === props.activeFolder && props.isModal,
|
||||
draggable: e.id !== 'root',
|
||||
disabled: e.id === props.activeFolder,
|
||||
count: props.modulesCount?.[e.id] || 0,
|
||||
};
|
||||
});
|
||||
|
@ -194,7 +188,13 @@
|
|||
},
|
||||
maskClosable: false,
|
||||
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,
|
||||
});
|
||||
|
@ -262,7 +262,15 @@
|
|||
console.log(error);
|
||||
} finally {
|
||||
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(() => {
|
||||
return {
|
||||
height: 'calc(100vh - 366px)',
|
||||
height: 'calc(100vh - 240px)',
|
||||
threshold: 200,
|
||||
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>
|
||||
<div class="rounded-2xl bg-white">
|
||||
<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">
|
||||
<MsCard simple no-content-padding>
|
||||
<MsSplitBox>
|
||||
<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="case h-[38px]">
|
||||
<div class="flex items-center" :class="getActiveClass('all')" @click="setActiveFolder('all')">
|
||||
|
@ -50,7 +62,6 @@
|
|||
</MsPopConfirm>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider class="my-[8px]" />
|
||||
<TestPlanTree
|
||||
ref="planTreeRef"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
|
@ -65,7 +76,7 @@
|
|||
</div>
|
||||
</template>
|
||||
<template #second>
|
||||
<div class="p-[24px]">
|
||||
<div class="p-[16px]">
|
||||
<PlanTable
|
||||
:active-folder="activeFolder"
|
||||
:offspring-ids="offspringIds"
|
||||
|
@ -76,8 +87,7 @@
|
|||
</div>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
</div>
|
||||
</div>
|
||||
</MsCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -85,6 +95,7 @@
|
|||
import { useRouter } from 'vue-router';
|
||||
|
||||
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 MsPopConfirm from '@/components/pure/ms-popconfirm/index.vue';
|
||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
|
@ -107,6 +118,8 @@
|
|||
|
||||
const activeFolder = ref<string>('all');
|
||||
|
||||
const groupKeyword = ref<string>('');
|
||||
|
||||
// 获取激活用例类型样式
|
||||
const getActiveClass = (type: string) => {
|
||||
return activeFolder.value === type ? 'folder-text case-active' : 'folder-text';
|
||||
|
@ -181,14 +194,11 @@
|
|||
* 右侧表格数据刷新后,若当前展示的是模块,则刷新模块树的统计数量
|
||||
*/
|
||||
function initModulesCount(params: any) {}
|
||||
|
||||
function handleSelect() {}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pageWrap {
|
||||
min-width: 1000px;
|
||||
height: calc(100vh - 166px);
|
||||
border-radius: var(--border-radius-large);
|
||||
@apply bg-white;
|
||||
<style scoped lang="less">
|
||||
.case {
|
||||
padding: 8px 4px;
|
||||
border-radius: var(--border-radius-small);
|
||||
|
@ -227,5 +237,4 @@
|
|||
@apply flex cursor-pointer items-center rounded-full;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -13,12 +13,51 @@ export default {
|
|||
'testPlan.testPlanIndex.testPlanName': 'name',
|
||||
'testPlan.testPlanIndex.ID': 'ID',
|
||||
'testPlan.testPlanIndex.desc': 'Description',
|
||||
'testPlan.testPlanIndex.status': 'Execution state',
|
||||
'testPlan.testPlanIndex.executionResult': 'Execution Result',
|
||||
'testPlan.testPlanIndex.passRate': 'Pass Rate',
|
||||
'testPlan.testPlanIndex.useCount': 'Use cases',
|
||||
'testPlan.testPlanIndex.bugCount': 'bug count',
|
||||
'testPlan.testPlanIndex.belongModule': 'belong module',
|
||||
'testPlan.testPlanIndex.creator': 'creator',
|
||||
'testPlan.testPlanIndex.createTime': 'create time',
|
||||
'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.ID': 'ID',
|
||||
'testPlan.testPlanIndex.desc': '描述',
|
||||
'testPlan.testPlanIndex.status': '执行状态',
|
||||
'testPlan.testPlanIndex.executionResult': '执行结果',
|
||||
'testPlan.testPlanIndex.passRate': '通过率',
|
||||
'testPlan.testPlanIndex.useCount': '用例数',
|
||||
'testPlan.testPlanIndex.bugCount': 'bug数',
|
||||
'testPlan.testPlanIndex.belongModule': '所属模块',
|
||||
'testPlan.testPlanIndex.creator': '创建人',
|
||||
'testPlan.testPlanIndex.createTime': '创建时间',
|
||||
'testPlan.testPlanIndex.operation': '操作',
|
||||
'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