feat(测试计划): 测试计划组联调拖拽&执行&定时任务&任务中心数量
This commit is contained in:
parent
f1bc941411
commit
705757d8be
|
@ -12,12 +12,15 @@ import {
|
||||||
batchMovePlanUrl,
|
batchMovePlanUrl,
|
||||||
BatchRunCaseUrl,
|
BatchRunCaseUrl,
|
||||||
BatchUpdateCaseExecutorUrl,
|
BatchUpdateCaseExecutorUrl,
|
||||||
|
ConfigScheduleUrl,
|
||||||
copyTestPlanUrl,
|
copyTestPlanUrl,
|
||||||
deletePlanUrl,
|
deletePlanUrl,
|
||||||
|
DeleteScheduleTaskUrl,
|
||||||
DeleteTestPlanModuleUrl,
|
DeleteTestPlanModuleUrl,
|
||||||
DisassociateCaseUrl,
|
DisassociateCaseUrl,
|
||||||
dragPlanOnGroupUrl,
|
dragPlanOnGroupUrl,
|
||||||
ExecuteHistoryUrl,
|
ExecuteHistoryUrl,
|
||||||
|
ExecutePlanUrl,
|
||||||
followPlanUrl,
|
followPlanUrl,
|
||||||
GenerateReportUrl,
|
GenerateReportUrl,
|
||||||
GetAssociatedBugUrl,
|
GetAssociatedBugUrl,
|
||||||
|
@ -59,9 +62,11 @@ import type {
|
||||||
BatchExecuteFeatureCaseParams,
|
BatchExecuteFeatureCaseParams,
|
||||||
BatchFeatureCaseParams,
|
BatchFeatureCaseParams,
|
||||||
BatchUpdateCaseExecutorParams,
|
BatchUpdateCaseExecutorParams,
|
||||||
|
CreateTask,
|
||||||
DisassociateCaseParams,
|
DisassociateCaseParams,
|
||||||
ExecuteHistoryItem,
|
ExecuteHistoryItem,
|
||||||
ExecuteHistoryType,
|
ExecuteHistoryType,
|
||||||
|
ExecutePlan,
|
||||||
FollowPlanParams,
|
FollowPlanParams,
|
||||||
PassRateCountDetail,
|
PassRateCountDetail,
|
||||||
PlanDetailApiCaseItem,
|
PlanDetailApiCaseItem,
|
||||||
|
@ -281,3 +286,15 @@ export function getPlanGroupOptions(projectId: string) {
|
||||||
export function dragPlanOnGroup(data: DragSortParams) {
|
export function dragPlanOnGroup(data: DragSortParams) {
|
||||||
return MSR.post({ url: dragPlanOnGroupUrl, data });
|
return MSR.post({ url: dragPlanOnGroupUrl, data });
|
||||||
}
|
}
|
||||||
|
// 测试计划-配置定时任务
|
||||||
|
export function configSchedule(data: CreateTask) {
|
||||||
|
return MSR.post({ url: ConfigScheduleUrl, data });
|
||||||
|
}
|
||||||
|
// 测试计划-计划&计划组-执行&批量执行
|
||||||
|
export function executePlanOrGroup(data: ExecutePlan) {
|
||||||
|
return MSR.post({ url: ExecutePlanUrl, data });
|
||||||
|
}
|
||||||
|
// 测试计划-计划&计划组-执行&批量执行
|
||||||
|
export function deleteScheduleTask(testPlanId: string) {
|
||||||
|
return MSR.get({ url: `${DeleteScheduleTaskUrl}/${testPlanId}` });
|
||||||
|
}
|
||||||
|
|
|
@ -90,3 +90,9 @@ export const TestPlanAndGroupCopyUrl = '/test-plan/copy';
|
||||||
export const TestPlanGroupOptionsUrl = 'test-plan/group-list';
|
export const TestPlanGroupOptionsUrl = 'test-plan/group-list';
|
||||||
// 测试计划-拖拽测试计划
|
// 测试计划-拖拽测试计划
|
||||||
export const dragPlanOnGroupUrl = '/test-plan/sort';
|
export const dragPlanOnGroupUrl = '/test-plan/sort';
|
||||||
|
// 测试计划-创建定时任务
|
||||||
|
export const ConfigScheduleUrl = '/test-plan/schedule-config';
|
||||||
|
// 测试计划-计划&计划组-执行&批量执行
|
||||||
|
export const ExecutePlanUrl = '/test-plan-execute/start';
|
||||||
|
// 测试计划-删除定时任务
|
||||||
|
export const DeleteScheduleTaskUrl = 'test-plan/schedule-config-delete';
|
||||||
|
|
|
@ -863,6 +863,12 @@
|
||||||
background: var(--color-text-brand);
|
background: var(--color-text-brand);
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
}
|
}
|
||||||
|
.active-badge {
|
||||||
|
.arco-badge-text,
|
||||||
|
.arco-badge-number {
|
||||||
|
background-color: rgb(var(--primary-5));
|
||||||
|
}
|
||||||
|
}
|
||||||
.filter-button {
|
.filter-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
|
@ -69,12 +69,6 @@
|
||||||
@apply relative right-0 top-0 transform-none shadow-none;
|
@apply relative right-0 top-0 transform-none shadow-none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.active-badge) {
|
|
||||||
.arco-badge-text,
|
|
||||||
.arco-badge-number {
|
|
||||||
background-color: rgb(var(--primary-5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.no-content {
|
.no-content {
|
||||||
:deep(.arco-tabs-content) {
|
:deep(.arco-tabs-content) {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
@ -157,11 +157,11 @@
|
||||||
<template v-else-if="item.showTooltip">
|
<template v-else-if="item.showTooltip">
|
||||||
<a-input
|
<a-input
|
||||||
v-if="
|
v-if="
|
||||||
editActiveKey === `${item.dataIndex}${rowIndex}` &&
|
editActiveKey === `${record[rowKey || 'id']}` &&
|
||||||
item.editType &&
|
item.editType &&
|
||||||
item.editType === ColumnEditTypeEnum.INPUT
|
item.editType === ColumnEditTypeEnum.INPUT
|
||||||
"
|
"
|
||||||
ref="currentInputRef"
|
:ref="(el: any) => setRefMap(el, `${record[rowKey|| 'id']}`)"
|
||||||
v-model="record[item.dataIndex as string]"
|
v-model="record[item.dataIndex as string]"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
@click.stop
|
@click.stop
|
||||||
|
@ -375,8 +375,16 @@
|
||||||
|
|
||||||
// 编辑按钮的Active状态
|
// 编辑按钮的Active状态
|
||||||
const editActiveKey = ref<string>('');
|
const editActiveKey = ref<string>('');
|
||||||
|
|
||||||
// 编辑项的Ref
|
// 编辑项的Ref
|
||||||
const currentInputRef = ref();
|
|
||||||
|
const refMap = ref<Record<string, any>>({});
|
||||||
|
const setRefMap = (el: any, id: string) => {
|
||||||
|
if (el) {
|
||||||
|
refMap.value[id] = el;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 编辑项的初始值,用于blur时恢复旧值
|
// 编辑项的初始值,用于blur时恢复旧值
|
||||||
const currentEditValue = ref<string>('');
|
const currentEditValue = ref<string>('');
|
||||||
// 是否是enter触发
|
// 是否是enter触发
|
||||||
|
@ -543,7 +551,7 @@
|
||||||
record[dataIndex] = currentEditValue.value;
|
record[dataIndex] = currentEditValue.value;
|
||||||
}
|
}
|
||||||
isEnter.value = false;
|
isEnter.value = false;
|
||||||
currentInputRef.value = null;
|
refMap.value[record[rowKey || 'id']] = null;
|
||||||
editActiveKey.value = '';
|
editActiveKey.value = '';
|
||||||
currentEditValue.value = '';
|
currentEditValue.value = '';
|
||||||
} else {
|
} else {
|
||||||
|
@ -585,6 +593,16 @@
|
||||||
emit('sorterChange', sortOrder ? { [dataIndex]: sortOrder } : {});
|
emit('sorterChange', sortOrder ? { [dataIndex]: sortOrder } : {});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getCurrentList(data: TableData[], key: string, id: string) {
|
||||||
|
return data.find((item) => {
|
||||||
|
const currentChildrenIds = (item.children || []).map((e) => e[key]);
|
||||||
|
if (currentChildrenIds?.includes(id)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 拖拽排序
|
// 拖拽排序
|
||||||
const handleDragChange = (data: TableData[], extra: TableChangeExtra, currentData: TableData[]) => {
|
const handleDragChange = (data: TableData[], extra: TableChangeExtra, currentData: TableData[]) => {
|
||||||
if (!currentData || currentData.length === 1) {
|
if (!currentData || currentData.length === 1) {
|
||||||
|
@ -592,36 +610,60 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extra && extra.dragTarget?.id) {
|
if (extra && extra.dragTarget?.id) {
|
||||||
|
let newDragData: TableData[] = data;
|
||||||
|
let oldDragData: TableData[] = currentData;
|
||||||
|
|
||||||
|
const newDragItem = getCurrentList(data, 'id', extra.dragTarget.id);
|
||||||
|
const oldDragItem = getCurrentList(currentData, 'key', extra.dragTarget.id);
|
||||||
|
|
||||||
|
if (newDragItem && newDragItem.children && oldDragItem && oldDragItem.children) {
|
||||||
|
newDragData = newDragItem.children;
|
||||||
|
oldDragData = oldDragItem.children;
|
||||||
|
}
|
||||||
|
|
||||||
|
let oldIndex = 0;
|
||||||
|
let newIndex = 0;
|
||||||
|
|
||||||
|
newIndex = newDragData.findIndex((item: any) => item.id === extra.dragTarget?.id);
|
||||||
|
oldIndex = oldDragData.findIndex((item: any) => item.key === extra.dragTarget?.id);
|
||||||
|
let position: 'AFTER' | 'BEFORE' = 'BEFORE';
|
||||||
|
|
||||||
|
position = newIndex > oldIndex ? 'AFTER' : 'BEFORE';
|
||||||
const params: DragSortParams = {
|
const params: DragSortParams = {
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
targetId: '', // 放置目标id
|
targetId: '', // 放置目标id
|
||||||
moveMode: 'BEFORE',
|
moveMode: position,
|
||||||
moveId: extra.dragTarget.id as string, // 拖拽id
|
moveId: extra.dragTarget.id as string, // 拖拽id
|
||||||
};
|
};
|
||||||
const index = currentData.findIndex((item: any) => item.key === extra.dragTarget?.id);
|
|
||||||
|
|
||||||
if (index > -1 && currentData[index + 1]) {
|
let targetIndex;
|
||||||
|
if (position === 'AFTER' && newIndex > 0) {
|
||||||
|
targetIndex = newIndex - 1;
|
||||||
|
} else if (position === 'AFTER') {
|
||||||
params.moveMode = 'BEFORE';
|
params.moveMode = 'BEFORE';
|
||||||
params.targetId = currentData[index + 1].raw.id;
|
targetIndex = newIndex + 1;
|
||||||
} else if (index > -1 && !currentData[index + 1]) {
|
} else if (position === 'BEFORE' && newIndex < newDragData.length - 1) {
|
||||||
if (index > -1 && currentData[index - 1]) {
|
targetIndex = newIndex + 1;
|
||||||
|
} else {
|
||||||
params.moveMode = 'AFTER';
|
params.moveMode = 'AFTER';
|
||||||
params.targetId = currentData[index - 1].raw.id;
|
targetIndex = newIndex - 1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
params.targetId = newDragData[targetIndex]?.id ?? newDragData[newIndex]?.id;
|
||||||
|
|
||||||
emit('dragChange', params);
|
emit('dragChange', params);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 编辑单元格的input
|
// 编辑单元格的input
|
||||||
const handleEdit = (dataIndex: string, rowIndex: number, record: TableData) => {
|
const handleEdit = (dataIndex: string, rowIndex: number, record: TableData) => {
|
||||||
editActiveKey.value = dataIndex + rowIndex;
|
editActiveKey.value = record.id;
|
||||||
currentEditValue.value = record[dataIndex];
|
currentEditValue.value = record[dataIndex];
|
||||||
if (currentInputRef.value) {
|
const refKey = `${record[rowKey as string]}`;
|
||||||
currentInputRef.value[0].focus();
|
if (refMap.value[refKey]) {
|
||||||
|
refMap.value[refKey]?.focus();
|
||||||
} else {
|
} else {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
currentInputRef.value[0].focus();
|
refMap.value[refKey]?.focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -38,6 +38,15 @@ export const defaultDetailCount: PassRateCountDetail = {
|
||||||
functionalCaseCount: 0,
|
functionalCaseCount: 0,
|
||||||
apiCaseCount: 0,
|
apiCaseCount: 0,
|
||||||
apiScenarioCount: 0,
|
apiScenarioCount: 0,
|
||||||
|
scheduleConfig: {
|
||||||
|
resourceId: '',
|
||||||
|
enable: false,
|
||||||
|
cron: '',
|
||||||
|
runConfig: {
|
||||||
|
runMode: 'SERIAL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nextTriggerTime: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultExecuteForm = {
|
export const defaultExecuteForm = {
|
||||||
|
|
|
@ -206,7 +206,7 @@ export interface BatchUpdateCaseExecutorParams extends BatchFeatureCaseParams {
|
||||||
export interface SortFeatureCaseParams extends DragSortParams {
|
export interface SortFeatureCaseParams extends DragSortParams {
|
||||||
testPlanId: string;
|
testPlanId: string;
|
||||||
}
|
}
|
||||||
|
export type RunModeType = 'SERIAL' | 'PARALLEL';
|
||||||
export interface PassRateCountDetail {
|
export interface PassRateCountDetail {
|
||||||
id: string;
|
id: string;
|
||||||
passThreshold: number;
|
passThreshold: number;
|
||||||
|
@ -221,6 +221,15 @@ export interface PassRateCountDetail {
|
||||||
functionalCaseCount: number;
|
functionalCaseCount: number;
|
||||||
apiCaseCount: number;
|
apiCaseCount: number;
|
||||||
apiScenarioCount: number;
|
apiScenarioCount: number;
|
||||||
|
scheduleConfig: {
|
||||||
|
resourceId: string;
|
||||||
|
enable: boolean;
|
||||||
|
cron: string;
|
||||||
|
runConfig: {
|
||||||
|
runMode: RunModeType;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
nextTriggerTime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行历史
|
// 执行历史
|
||||||
|
@ -300,4 +309,16 @@ export interface PlanDetailExecuteHistoryItem {
|
||||||
lastExecResult: LastExecuteResults;
|
lastExecResult: LastExecuteResults;
|
||||||
triggerMode: string;
|
triggerMode: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CreateTask {
|
||||||
|
resourceId: string;
|
||||||
|
enable: boolean;
|
||||||
|
cron: string;
|
||||||
|
runConfig: { runMode: 'SERIAL' | 'PARALLEL' };
|
||||||
|
}
|
||||||
|
export interface ExecutePlan {
|
||||||
|
projectId: string;
|
||||||
|
executeIds: string[];
|
||||||
|
executeMode: RunModeType;
|
||||||
|
}
|
||||||
export default {};
|
export default {};
|
||||||
|
|
|
@ -293,10 +293,4 @@
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.active-badge) {
|
|
||||||
.arco-badge-text,
|
|
||||||
.arco-badge-number {
|
|
||||||
background-color: rgb(var(--primary-5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -374,10 +374,4 @@
|
||||||
.ms-scroll-bar();
|
.ms-scroll-bar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.active-badge) {
|
|
||||||
.arco-badge-text,
|
|
||||||
.arco-badge-number {
|
|
||||||
background-color: rgb(var(--primary-5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -788,9 +788,6 @@
|
||||||
color: rgb(var(--danger-6));
|
color: rgb(var(--danger-6));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.active .arco-badge-text) {
|
|
||||||
background: rgb(var(--primary-5));
|
|
||||||
}
|
|
||||||
:deep(.tags-class .arco-form-item-label-col) {
|
:deep(.tags-class .arco-form-item-label-col) {
|
||||||
justify-content: flex-start !important;
|
justify-content: flex-start !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -862,7 +862,4 @@
|
||||||
color: rgb(var(--danger-6));
|
color: rgb(var(--danger-6));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.active .arco-badge-text) {
|
|
||||||
background: rgb(var(--primary-5));
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,11 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="left" :class="getStyleClass()">
|
<div class="left" :class="getStyleClass()">
|
||||||
<div class="item" :class="[activeTask === 'real' ? 'active' : '']" @click="toggleTask('real')">
|
<div
|
||||||
{{ t('project.taskCenter.realTimeTask') }}
|
v-for="item of menuTab"
|
||||||
|
:key="item.value"
|
||||||
|
:class="`${activeTask === item.value ? 'active' : ''} item flex items-center`"
|
||||||
|
@click="toggleTask(item.value)"
|
||||||
|
>
|
||||||
|
<div class="mr-2">
|
||||||
|
{{ item.label }}
|
||||||
</div>
|
</div>
|
||||||
<div class="item" :class="[activeTask === 'timing' ? 'active' : '']" @click="toggleTask('timing')">
|
<a-badge
|
||||||
{{ t('project.taskCenter.scheduledTask') }}
|
v-if="getTextFunc(item.value) !== ''"
|
||||||
|
:class="`${item.value === activeTask ? 'active-badge' : ''} mt-[2px]`"
|
||||||
|
:max-count="99"
|
||||||
|
:text="getTextFunc(item.value)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
|
@ -40,11 +50,20 @@
|
||||||
import ScheduledTask from './scheduledTask.vue';
|
import ScheduledTask from './scheduledTask.vue';
|
||||||
import TestPlan from './testPlan.vue';
|
import TestPlan from './testPlan.vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getOrgRealTotal,
|
||||||
|
getOrgScheduleTotal,
|
||||||
|
getProjectRealTotal,
|
||||||
|
getProjectScheduleTotal,
|
||||||
|
getSystemRealTotal,
|
||||||
|
getSystemScheduleTotal,
|
||||||
|
} from '@/api/modules/project-management/taskCenter';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
import { TaskCenterEnum } from '@/enums/taskCenter';
|
import { TaskCenterEnum } from '@/enums/taskCenter';
|
||||||
|
|
||||||
import type { ExtractedKeys } from './utils';
|
import type { ExtractedKeys } from './utils';
|
||||||
|
import { on } from 'events';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
@ -94,7 +113,7 @@
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const activeTask = ref(route.query.tab || 'real');
|
const activeTask = ref<string>((route.query.tab as string) || 'real');
|
||||||
const activeTab = ref<ExtractedKeys>((route.query.type as ExtractedKeys) || TaskCenterEnum.API_CASE);
|
const activeTab = ref<ExtractedKeys>((route.query.type as ExtractedKeys) || TaskCenterEnum.API_CASE);
|
||||||
|
|
||||||
const rightTabList = computed(() => {
|
const rightTabList = computed(() => {
|
||||||
|
@ -117,40 +136,86 @@
|
||||||
const listName = computed(() => {
|
const listName = computed(() => {
|
||||||
return rightTabList.value.find((item) => item.value === activeTab.value)?.label || '';
|
return rightTabList.value.find((item) => item.value === activeTab.value)?.label || '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type menuType = 'real' | 'timing';
|
||||||
|
|
||||||
|
const menuTab: { value: menuType; label: string }[] = [
|
||||||
|
{
|
||||||
|
value: 'real',
|
||||||
|
label: t('project.taskCenter.realTimeTask'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'timing',
|
||||||
|
label: t('project.taskCenter.scheduledTask'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getTotalMap: Record<menuType, any> = {
|
||||||
|
real: {
|
||||||
|
system: getSystemRealTotal,
|
||||||
|
organization: getOrgRealTotal,
|
||||||
|
project: getProjectRealTotal,
|
||||||
|
},
|
||||||
|
timing: {
|
||||||
|
system: getSystemScheduleTotal,
|
||||||
|
organization: getOrgScheduleTotal,
|
||||||
|
project: getProjectScheduleTotal,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const totalMap = ref<Record<menuType, number>>({
|
||||||
|
real: 0,
|
||||||
|
timing: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getTotal() {
|
||||||
|
try {
|
||||||
|
const [timingTotal, realTotal] = await Promise.all([
|
||||||
|
getTotalMap.timing[props.group](),
|
||||||
|
getTotalMap.real[props.group](),
|
||||||
|
]);
|
||||||
|
totalMap.value.timing = timingTotal;
|
||||||
|
totalMap.value.real = realTotal;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTextFunc(activeKey: menuType) {
|
||||||
|
return totalMap.value[activeKey] > 99 ? '99+' : `${totalMap.value[activeKey]}` || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getTotal();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.box {
|
.box {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
width: 252px;
|
width: 252px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-right: 1px solid var(--color-text-n8);
|
border-right: 1px solid var(--color-text-n8);
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
height: 38px;
|
height: 38px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 38px;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
color: rgb(var(--primary-5));
|
color: rgb(var(--primary-5));
|
||||||
background: rgb(var(--primary-1));
|
background: rgb(var(--primary-1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
width: calc(100% - 300px);
|
width: calc(100% - 300px);
|
||||||
flex-grow: 1; /* 自适应 */
|
flex-grow: 1; /* 自适应 */
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-content {
|
.no-content {
|
||||||
:deep(.arco-tabs-content) {
|
:deep(.arco-tabs-content) {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
{{ t('common.confirmDelete') }}
|
{{ t('common.confirmDelete') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button
|
<a-button
|
||||||
v-if="props.record?.status === 'COMPLETED'"
|
v-if="showArchive"
|
||||||
:loading="confirmLoading"
|
:loading="confirmLoading"
|
||||||
class="ml-3"
|
class="ml-3"
|
||||||
type="primary"
|
type="primary"
|
||||||
|
@ -105,6 +105,10 @@
|
||||||
return t('testPlan.testPlanIndex.deletePendingPlan');
|
return t('testPlan.testPlanIndex.deletePendingPlan');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const showArchive = computed(() => {
|
||||||
|
return props.record?.status === 'COMPLETED' && props.record.groupId && props.record.groupId === 'NONE';
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|
|
@ -108,24 +108,24 @@
|
||||||
:class="`${
|
:class="`${
|
||||||
record.type === testPlanTypeEnum.TEST_PLAN ? 'text-[rgb(var(--primary-5))]' : ''
|
record.type === testPlanTypeEnum.TEST_PLAN ? 'text-[rgb(var(--primary-5))]' : ''
|
||||||
} one-line-text ${hasIndent(record)}`"
|
} one-line-text ${hasIndent(record)}`"
|
||||||
@click="openDetail(record.id, record.type)"
|
@click="openDetail(record.id)"
|
||||||
>{{ record.num }}</div
|
>{{ record.num }}</div
|
||||||
>
|
>
|
||||||
<!-- TODO 待联调定时任务 -->
|
<a-tooltip position="right" :disabled="!getSchedule(record.id)" :mouse-enter-delay="300">
|
||||||
<a-tooltip position="right" :disabled="record.schedule" :mouse-enter-delay="300">
|
|
||||||
<MsTag
|
<MsTag
|
||||||
v-if="record.schedule"
|
v-if="getSchedule(record.id)"
|
||||||
size="small"
|
size="small"
|
||||||
:type="record.schedule ? 'link' : 'default'"
|
:type="getScheduleEnable(record.id) ? 'link' : 'default'"
|
||||||
theme="outline"
|
theme="outline"
|
||||||
class="ml-2"
|
class="ml-2"
|
||||||
|
:tooltip-disabled="true"
|
||||||
>{{ t('testPlan.testPlanIndex.timing') }}</MsTag
|
>{{ t('testPlan.testPlanIndex.timing') }}</MsTag
|
||||||
>
|
>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div v-if="record.schedule">
|
<div v-if="getScheduleEnable(record.id)">
|
||||||
<div>{{ t('testPlan.testPlanIndex.scheduledTaskOpened') }}</div>
|
<div>{{ t('testPlan.testPlanIndex.scheduledTaskOpened') }}</div>
|
||||||
<div>{{ t('testPlan.testPlanIndex.nextExecutionTime') }}</div>
|
<div>{{ t('testPlan.testPlanIndex.nextExecutionTime') }}</div>
|
||||||
<div> {{ dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss') }}</div>
|
<div> {{ dayjs(defaultCountDetailMap[record.id]?.nextTriggerTime).format('YYYY-MM-DD HH:mm:ss') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else> {{ t('testPlan.testPlanIndex.scheduledTaskUnEnable') }} </div>
|
<div v-else> {{ t('testPlan.testPlanIndex.scheduledTaskUnEnable') }} </div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -176,8 +176,8 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #functionalCaseCount="{ record }">
|
<template #functionalCaseCount="{ record }">
|
||||||
<a-popover position="bottom" content-class="p-[16px]" :disabled="record.functionalCaseCount < 1">
|
<a-popover position="bottom" content-class="p-[16px]" :disabled="getFunctionalCount(record.id) < 1">
|
||||||
<div>{{ record.functionalCaseCount }}</div>
|
<div>{{ getFunctionalCount(record.id) }}</div>
|
||||||
<template #content>
|
<template #content>
|
||||||
<table class="min-w-[140px] max-w-[176px]">
|
<table class="min-w-[140px] max-w-[176px]">
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -185,7 +185,7 @@
|
||||||
<div>{{ t('testPlan.testPlanIndex.TotalCases') }}</div>
|
<div>{{ t('testPlan.testPlanIndex.TotalCases') }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="popover-value-td">
|
<td class="popover-value-td">
|
||||||
{{ record.caseTotal }}
|
{{ defaultCountDetailMap[record.id]?.caseTotal ?? '0' }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -193,7 +193,7 @@
|
||||||
<div class="text-[var(--color-text-1)]">{{ t('testPlan.testPlanIndex.functionalUseCase') }}</div>
|
<div class="text-[var(--color-text-1)]">{{ t('testPlan.testPlanIndex.functionalUseCase') }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="popover-value-td">
|
<td class="popover-value-td">
|
||||||
{{ record.functionalCaseCount }}
|
{{ getFunctionalCount(record.id) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -201,7 +201,7 @@
|
||||||
<div class="text-[var(--color-text-1)]">{{ t('testPlan.testPlanIndex.apiCase') }}</div>
|
<div class="text-[var(--color-text-1)]">{{ t('testPlan.testPlanIndex.apiCase') }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="popover-value-td">
|
<td class="popover-value-td">
|
||||||
{{ record.apiCaseCount }}
|
{{ defaultCountDetailMap[record.id]?.apiCaseCount ?? '0' }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -209,7 +209,7 @@
|
||||||
<div class="text-[var(--color-text-1)]">{{ t('testPlan.testPlanIndex.apiScenarioCase') }}</div>
|
<div class="text-[var(--color-text-1)]">{{ t('testPlan.testPlanIndex.apiScenarioCase') }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="popover-value-td">
|
<td class="popover-value-td">
|
||||||
{{ record.apiScenarioCount }}
|
{{ defaultCountDetailMap[record.id]?.apiScenarioCount ?? '0' }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -221,17 +221,17 @@
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<MsButton
|
<MsButton
|
||||||
v-if="
|
v-if="
|
||||||
record.functionalCaseCount > 0 &&
|
getFunctionalCount(record.id) > 0 &&
|
||||||
hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE']) &&
|
hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE']) &&
|
||||||
record.status !== 'ARCHIVED'
|
record.status !== 'ARCHIVED'
|
||||||
"
|
"
|
||||||
class="!mx-0"
|
class="!mx-0"
|
||||||
@click="openDetail(record.id)"
|
@click="executePlan(record)"
|
||||||
>{{ t('testPlan.testPlanIndex.execution') }}</MsButton
|
>{{ t('testPlan.testPlanIndex.execution') }}</MsButton
|
||||||
>
|
>
|
||||||
<a-divider
|
<a-divider
|
||||||
v-if="
|
v-if="
|
||||||
record.functionalCaseCount > 0 &&
|
getFunctionalCount(record.id) > 0 &&
|
||||||
hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE']) &&
|
hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE']) &&
|
||||||
record.status !== 'ARCHIVED'
|
record.status !== 'ARCHIVED'
|
||||||
"
|
"
|
||||||
|
@ -254,7 +254,7 @@
|
||||||
<MsButton
|
<MsButton
|
||||||
v-if="
|
v-if="
|
||||||
hasAnyPermission(['PROJECT_TEST_PLAN:READ+ADD']) &&
|
hasAnyPermission(['PROJECT_TEST_PLAN:READ+ADD']) &&
|
||||||
record.functionalCaseCount < 1 &&
|
getFunctionalCount(record.id) < 1 &&
|
||||||
record.status !== 'ARCHIVED'
|
record.status !== 'ARCHIVED'
|
||||||
"
|
"
|
||||||
class="!mx-0"
|
class="!mx-0"
|
||||||
|
@ -264,7 +264,7 @@
|
||||||
<a-divider
|
<a-divider
|
||||||
v-if="
|
v-if="
|
||||||
hasAnyPermission(['PROJECT_TEST_PLAN:READ+ADD']) &&
|
hasAnyPermission(['PROJECT_TEST_PLAN:READ+ADD']) &&
|
||||||
record.functionalCaseCount < 1 &&
|
getFunctionalCount(record.id) < 1 &&
|
||||||
record.status !== 'ARCHIVED'
|
record.status !== 'ARCHIVED'
|
||||||
"
|
"
|
||||||
direction="vertical"
|
direction="vertical"
|
||||||
|
@ -286,9 +286,9 @@
|
||||||
<template #title>
|
<template #title>
|
||||||
{{ t('testPlan.testPlanIndex.batchExecution') }}
|
{{ t('testPlan.testPlanIndex.batchExecution') }}
|
||||||
</template>
|
</template>
|
||||||
<a-radio-group v-model="executeType">
|
<a-radio-group v-model="executeForm.executeMode">
|
||||||
<a-radio value="serial">{{ t('testPlan.testPlanIndex.serial') }}</a-radio>
|
<a-radio value="SERIAL">{{ t('testPlan.testPlanIndex.serial') }}</a-radio>
|
||||||
<a-radio value="parallel">{{ t('testPlan.testPlanIndex.parallel') }}</a-radio>
|
<a-radio value="PARALLEL">{{ t('testPlan.testPlanIndex.parallel') }}</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
|
@ -311,8 +311,14 @@
|
||||||
:type="showType"
|
:type="showType"
|
||||||
@save="handleMoveOrCopy"
|
@save="handleMoveOrCopy"
|
||||||
/>
|
/>
|
||||||
<!-- TODO 待联调定时任务 -->
|
<!-- TODO 待联调[编辑] 字段加到统计里边 -->
|
||||||
<ScheduledModal v-model:visible="showScheduledTaskModal" :type="currentPlanType" @close="resetPlanType" />
|
<ScheduledModal
|
||||||
|
v-model:visible="showScheduledTaskModal"
|
||||||
|
:type="planType"
|
||||||
|
:source-id="planSourceId"
|
||||||
|
:task-config="taskForm"
|
||||||
|
@handle-success="fetchData()"
|
||||||
|
/>
|
||||||
<ActionModal v-model:visible="showStatusDeleteModal" :record="activeRecord" @success="fetchData()" />
|
<ActionModal v-model:visible="showStatusDeleteModal" :record="activeRecord" @success="fetchData()" />
|
||||||
<BatchEditModal
|
<BatchEditModal
|
||||||
v-model:visible="showEditModel"
|
v-model:visible="showEditModel"
|
||||||
|
@ -361,7 +367,9 @@
|
||||||
batchDeletePlan,
|
batchDeletePlan,
|
||||||
batchMovePlan,
|
batchMovePlan,
|
||||||
deletePlan,
|
deletePlan,
|
||||||
|
deleteScheduleTask,
|
||||||
dragPlanOnGroup,
|
dragPlanOnGroup,
|
||||||
|
executePlanOrGroup,
|
||||||
getPlanPassRate,
|
getPlanPassRate,
|
||||||
getTestPlanDetail,
|
getTestPlanDetail,
|
||||||
getTestPlanList,
|
getTestPlanList,
|
||||||
|
@ -379,6 +387,8 @@
|
||||||
import type {
|
import type {
|
||||||
AddTestPlanParams,
|
AddTestPlanParams,
|
||||||
BatchMoveParams,
|
BatchMoveParams,
|
||||||
|
CreateTask,
|
||||||
|
ExecutePlan,
|
||||||
moduleForm,
|
moduleForm,
|
||||||
PassRateCountDetail,
|
PassRateCountDetail,
|
||||||
TestPlanItem,
|
TestPlanItem,
|
||||||
|
@ -414,7 +424,6 @@
|
||||||
|
|
||||||
const isArchived = ref<boolean>(false);
|
const isArchived = ref<boolean>(false);
|
||||||
const keyword = ref<string>('');
|
const keyword = ref<string>('');
|
||||||
const currentPlanType = ref<keyof typeof testPlanTypeEnum>(testPlanTypeEnum.TEST_PLAN);
|
|
||||||
|
|
||||||
const hasOperationPermission = computed(() =>
|
const hasOperationPermission = computed(() =>
|
||||||
hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE', 'PROJECT_TEST_PLAN:READ+EXECUTE', 'PROJECT_TEST_PLAN:READ+ADD'])
|
hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE', 'PROJECT_TEST_PLAN:READ+EXECUTE', 'PROJECT_TEST_PLAN:READ+ADD'])
|
||||||
|
@ -685,15 +694,28 @@
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const defaultCountDetailMap = ref<Record<string, PassRateCountDetail>>({});
|
||||||
|
function getFunctionalCount(id: string) {
|
||||||
|
return defaultCountDetailMap.value[id]?.functionalCaseCount ?? 0;
|
||||||
|
}
|
||||||
|
function getSchedule(id: string) {
|
||||||
|
return !!defaultCountDetailMap.value[id]?.scheduleConfig;
|
||||||
|
}
|
||||||
|
function getScheduleEnable(id: string) {
|
||||||
|
return defaultCountDetailMap.value[id].scheduleConfig.enable;
|
||||||
|
}
|
||||||
|
|
||||||
function getMoreActions(record: TestPlanItem) {
|
function getMoreActions(record: TestPlanItem) {
|
||||||
const { status: planStatus, functionalCaseCount: useCount, schedule } = record;
|
const { status: planStatus } = record;
|
||||||
|
const useCount = defaultCountDetailMap.value[record.id]?.functionalCaseCount ?? 0;
|
||||||
|
|
||||||
// 有用例数量才可以执行 否则不展示执行
|
// 有用例数量才可以执行 否则不展示执行
|
||||||
const copyAction =
|
const copyAction =
|
||||||
useCount > 0 && hasAnyPermission(['PROJECT_TEST_PLAN:READ+ADD']) && planStatus !== 'ARCHIVED' ? copyActions : [];
|
useCount > 0 && hasAnyPermission(['PROJECT_TEST_PLAN:READ+ADD']) && planStatus !== 'ARCHIVED' ? copyActions : [];
|
||||||
// TODO 定时任务待联调
|
|
||||||
let scheduledTaskAction: ActionsItem[] = [];
|
let scheduledTaskAction: ActionsItem[] = [];
|
||||||
if (planStatus !== 'ARCHIVED') {
|
if (planStatus !== 'ARCHIVED' && record.groupId && record.groupId === 'NONE') {
|
||||||
scheduledTaskAction = schedule ? updateAndDeleteScheduledActions : createScheduledActions;
|
scheduledTaskAction = getSchedule(record.id) ? updateAndDeleteScheduledActions : createScheduledActions;
|
||||||
}
|
}
|
||||||
// 计划组下没有计划&计划组内计划不允许单独归档
|
// 计划组下没有计划&计划组内计划不允许单独归档
|
||||||
const archiveAction =
|
const archiveAction =
|
||||||
|
@ -701,6 +723,7 @@
|
||||||
(record.type === testPlanTypeEnum.TEST_PLAN && record.groupId && record.groupId !== 'NONE')
|
(record.type === testPlanTypeEnum.TEST_PLAN && record.groupId && record.groupId !== 'NONE')
|
||||||
? []
|
? []
|
||||||
: archiveActions;
|
: archiveActions;
|
||||||
|
|
||||||
// 已归档和已完成不展示归档
|
// 已归档和已完成不展示归档
|
||||||
if (planStatus === 'ARCHIVED' || planStatus === 'PREPARED' || planStatus === 'UNDERWAY') {
|
if (planStatus === 'ARCHIVED' || planStatus === 'PREPARED' || planStatus === 'UNDERWAY') {
|
||||||
return [
|
return [
|
||||||
|
@ -741,7 +764,8 @@
|
||||||
draggableCondition: true,
|
draggableCondition: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, resetFilterParams } = useTable(
|
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, resetFilterParams, setPagination } =
|
||||||
|
useTable(
|
||||||
getTestPlanList,
|
getTestPlanList,
|
||||||
tableProps.value,
|
tableProps.value,
|
||||||
(item) => {
|
(item) => {
|
||||||
|
@ -816,8 +840,6 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultCountDetailMap = ref<Record<string, PassRateCountDetail>>({});
|
|
||||||
|
|
||||||
async function getStatistics(selectedPlanIds: (string | undefined)[]) {
|
async function getStatistics(selectedPlanIds: (string | undefined)[]) {
|
||||||
try {
|
try {
|
||||||
const result = await getPlanPassRate(selectedPlanIds);
|
const result = await getPlanPassRate(selectedPlanIds);
|
||||||
|
@ -829,18 +851,8 @@
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchData() {
|
|
||||||
resetSelector();
|
|
||||||
await loadPlanList();
|
|
||||||
emitTableParams();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试计划详情
|
// 测试计划详情
|
||||||
function openDetail(id: string, type?: keyof typeof testPlanTypeEnum) {
|
function openDetail(id: string) {
|
||||||
if (type && type === testPlanTypeEnum.GROUP) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
router.push({
|
router.push({
|
||||||
name: TestPlanRouteEnum.TEST_PLAN_INDEX_DETAIL,
|
name: TestPlanRouteEnum.TEST_PLAN_INDEX_DETAIL,
|
||||||
query: {
|
query: {
|
||||||
|
@ -849,29 +861,82 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
|
resetSelector();
|
||||||
|
await loadPlanList();
|
||||||
|
emitTableParams();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量执行
|
* 批量执行
|
||||||
*/
|
*/
|
||||||
const executeType = ref('serial');
|
const initExecuteForm: ExecutePlan = {
|
||||||
|
projectId: appStore.currentProjectId,
|
||||||
|
executeIds: [],
|
||||||
|
executeMode: 'SERIAL',
|
||||||
|
};
|
||||||
|
|
||||||
|
const executeForm = ref<ExecutePlan>(cloneDeep(initExecuteForm));
|
||||||
const executeVisible = ref<boolean>(false);
|
const executeVisible = ref<boolean>(false);
|
||||||
|
|
||||||
function handleExecute() {
|
function handleExecute(isBatch: boolean) {
|
||||||
|
if (isBatch) {
|
||||||
|
executeForm.value.executeIds = batchParams.value.selectedIds || [];
|
||||||
|
}
|
||||||
executeVisible.value = true;
|
executeVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelHandler() {
|
function cancelHandler() {
|
||||||
executeVisible.value = false;
|
executeVisible.value = false;
|
||||||
|
executeForm.value = cloneDeep(initExecuteForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmLoading = ref<boolean>(false);
|
|
||||||
/**
|
/**
|
||||||
* 执行 TODO 待联调
|
* 批量执行
|
||||||
*/
|
*/
|
||||||
function executeHandler() {
|
|
||||||
|
const confirmLoading = ref<boolean>(false);
|
||||||
|
|
||||||
|
async function executeHandler() {
|
||||||
|
confirmLoading.value = true;
|
||||||
try {
|
try {
|
||||||
|
await executePlanOrGroup(executeForm.value);
|
||||||
|
cancelHandler();
|
||||||
Message.success(t('case.detail.execute.success'));
|
Message.success(t('case.detail.execute.success'));
|
||||||
|
fetchData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
confirmLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试计划详情
|
||||||
|
function executePlan(record: TestPlanItem) {
|
||||||
|
const { type, id } = record;
|
||||||
|
|
||||||
|
if (type === testPlanTypeEnum.GROUP) {
|
||||||
|
handleExecute(false);
|
||||||
|
executeForm.value.executeIds = [id];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type === testPlanTypeEnum.TEST_PLAN) {
|
||||||
|
// 如果都为功能用例直接执行
|
||||||
|
if (defaultCountDetailMap.value[id]) {
|
||||||
|
const { apiScenarioCount, apiCaseCount } = defaultCountDetailMap.value[id];
|
||||||
|
if (!apiScenarioCount && !apiCaseCount) {
|
||||||
|
router.push({
|
||||||
|
name: TestPlanRouteEnum.TEST_PLAN_INDEX_DETAIL,
|
||||||
|
query: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
executeForm.value.executeIds = [id];
|
||||||
|
executeHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -920,6 +985,7 @@
|
||||||
showBatchModal.value = false;
|
showBatchModal.value = false;
|
||||||
fetchData();
|
fetchData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
} finally {
|
} finally {
|
||||||
okLoading.value = false;
|
okLoading.value = false;
|
||||||
|
@ -1042,7 +1108,7 @@
|
||||||
batchParams.value = params;
|
batchParams.value = params;
|
||||||
switch (event.eventTag) {
|
switch (event.eventTag) {
|
||||||
case 'execute':
|
case 'execute':
|
||||||
handleExecute();
|
handleExecute(true);
|
||||||
break;
|
break;
|
||||||
case 'copy':
|
case 'copy':
|
||||||
handleCopyOrMove('copy');
|
handleCopyOrMove('copy');
|
||||||
|
@ -1072,17 +1138,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const showScheduledTaskModal = ref<boolean>(false);
|
const showScheduledTaskModal = ref<boolean>(false);
|
||||||
|
const activeRecord = ref<TestPlanItem>();
|
||||||
|
|
||||||
|
const taskForm = ref<CreateTask>();
|
||||||
|
const planSourceId = ref<string>();
|
||||||
|
const planType = ref<keyof typeof testPlanTypeEnum>(testPlanTypeEnum.TEST_PLAN);
|
||||||
function handleScheduledTask(record: TestPlanItem) {
|
function handleScheduledTask(record: TestPlanItem) {
|
||||||
currentPlanType.value = record.type;
|
planType.value = record.type;
|
||||||
|
planSourceId.value = record.id;
|
||||||
|
taskForm.value = defaultCountDetailMap.value[record.id]?.scheduleConfig;
|
||||||
showScheduledTaskModal.value = true;
|
showScheduledTaskModal.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetPlanType() {
|
|
||||||
currentPlanType.value = testPlanTypeEnum.TEST_PLAN;
|
|
||||||
}
|
|
||||||
|
|
||||||
const showStatusDeleteModal = ref<boolean>(false);
|
const showStatusDeleteModal = ref<boolean>(false);
|
||||||
const activeRecord = ref<TestPlanItem>();
|
|
||||||
|
|
||||||
// 计划组删除: 没有计划直接删除
|
// 计划组删除: 没有计划直接删除
|
||||||
async function handleDeleteGroup(record: TestPlanItem) {
|
async function handleDeleteGroup(record: TestPlanItem) {
|
||||||
|
@ -1158,6 +1226,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleDeleteScheduled(record: TestPlanItem) {
|
||||||
|
try {
|
||||||
|
await deleteScheduleTask(record.id);
|
||||||
|
Message.success(t('testPlan.testPlanGroup.deleteScheduleTaskSuccess'));
|
||||||
|
fetchData();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleMoreActionSelect(item: ActionsItem, record: TestPlanItem) {
|
function handleMoreActionSelect(item: ActionsItem, record: TestPlanItem) {
|
||||||
switch (item.eventTag) {
|
switch (item.eventTag) {
|
||||||
case 'copy':
|
case 'copy':
|
||||||
|
@ -1166,6 +1244,12 @@
|
||||||
case 'createScheduledTask':
|
case 'createScheduledTask':
|
||||||
handleScheduledTask(record);
|
handleScheduledTask(record);
|
||||||
break;
|
break;
|
||||||
|
case 'updateScheduledTask':
|
||||||
|
handleScheduledTask(record);
|
||||||
|
break;
|
||||||
|
case 'deleteScheduledTask':
|
||||||
|
handleDeleteScheduled(record);
|
||||||
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
deleteStatusHandler(record);
|
deleteStatusHandler(record);
|
||||||
break;
|
break;
|
||||||
|
@ -1206,6 +1290,9 @@
|
||||||
(val) => {
|
(val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
tableProps.value.draggableCondition = hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && val === 'ALL';
|
tableProps.value.draggableCondition = hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && val === 'ALL';
|
||||||
|
setPagination({
|
||||||
|
current: 1,
|
||||||
|
});
|
||||||
expandedKeys.value = [];
|
expandedKeys.value = [];
|
||||||
resetFilterParams();
|
resetFilterParams();
|
||||||
fetchData();
|
fetchData();
|
||||||
|
|
|
@ -6,11 +6,15 @@
|
||||||
:mask-closable="false"
|
:mask-closable="false"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
{{ form.id ? t('testPlan.testPlanIndex.updateScheduledTask') : t('testPlan.testPlanIndex.createScheduledTask') }}
|
{{
|
||||||
|
props.taskConfig
|
||||||
|
? t('testPlan.testPlanIndex.updateScheduledTask')
|
||||||
|
: t('testPlan.testPlanIndex.createScheduledTask')
|
||||||
|
}}
|
||||||
</template>
|
</template>
|
||||||
<a-form ref="formRef" :model="form" layout="vertical">
|
<a-form ref="formRef" :model="form" layout="vertical">
|
||||||
<a-form-item :label="t('testPlan.testPlanIndex.triggerTime')" asterisk-position="end">
|
<a-form-item :label="t('testPlan.testPlanIndex.triggerTime')" asterisk-position="end">
|
||||||
<a-select v-model:model-value="form.time" :placeholder="t('common.pleaseSelect')">
|
<a-select v-model:model-value="form.cron" :placeholder="t('common.pleaseSelect')">
|
||||||
<a-option v-for="item of syncFrequencyOptions" :key="item.value" :value="item.value">
|
<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="text-[var(--color-text-2)]"> {{ item.value }}</span
|
||||||
><span class="ml-1 text-[var(--color-text-n4)] hover:text-[rgb(var(--primary-5))]">
|
><span class="ml-1 text-[var(--color-text-n4)] hover:text-[rgb(var(--primary-5))]">
|
||||||
|
@ -39,9 +43,9 @@
|
||||||
</a-radio>
|
</a-radio>
|
||||||
<a-radio value="new"> {{ t('testPlan.testPlanIndex.newEnv') }}</a-radio>
|
<a-radio value="new"> {{ t('testPlan.testPlanIndex.newEnv') }}</a-radio>
|
||||||
</a-radio-group> -->
|
</a-radio-group> -->
|
||||||
<a-radio-group v-if="props.type === testPlanTypeEnum.GROUP" v-model="form.methods">
|
<a-radio-group v-if="props.type === testPlanTypeEnum.GROUP" v-model="form.runConfig.runMode">
|
||||||
<a-radio value="serial">{{ t('testPlan.testPlanIndex.serial') }}</a-radio>
|
<a-radio value="SERIAL">{{ t('testPlan.testPlanIndex.serial') }}</a-radio>
|
||||||
<a-radio value="parallel">{{ t('testPlan.testPlanIndex.parallel') }}</a-radio>
|
<a-radio value="PARALLEL">{{ t('testPlan.testPlanIndex.parallel') }}</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
<!-- TODO 资源池暂时不做 -->
|
<!-- TODO 资源池暂时不做 -->
|
||||||
<!-- <a-form-item :label="t('testPlan.testPlanIndex.resourcePool')" asterisk-position="end" class="mb-0">
|
<!-- <a-form-item :label="t('testPlan.testPlanIndex.resourcePool')" asterisk-position="end" class="mb-0">
|
||||||
|
@ -89,7 +93,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a-button type="secondary" class="mr-3" @click="handleCancel">{{ t('system.plugin.pluginCancel') }}</a-button>
|
<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>
|
<a-button type="primary" :loading="confirmLoading" @click="handleCreate">{{
|
||||||
|
props.taskConfig ? t('common.update') : t('common.create')
|
||||||
|
}}</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -99,54 +105,72 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
|
import { type FormInstance, Message, type ValidatedError } from '@arco-design/web-vue';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import { configSchedule } from '@/api/modules/test-plan/testPlan';
|
||||||
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { useAppStore } from '@/store';
|
|
||||||
|
|
||||||
import type { ResourcesItem } from '@/models/testPlan/testPlan';
|
import type { CreateTask } from '@/models/testPlan/testPlan';
|
||||||
import { testPlanTypeEnum } from '@/enums/testPlanEnum';
|
import { testPlanTypeEnum } from '@/enums/testPlanEnum';
|
||||||
|
|
||||||
const appStore = useAppStore();
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
|
taskConfig?: CreateTask;
|
||||||
type: keyof typeof testPlanTypeEnum;
|
type: keyof typeof testPlanTypeEnum;
|
||||||
|
sourceId?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:visible', val: boolean): void;
|
(e: 'update:visible', val: boolean): void;
|
||||||
(e: 'close'): void;
|
(e: 'close'): void;
|
||||||
|
(e: 'handleSuccess'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const showModalVisible = useVModel(props, 'visible', emit);
|
const showModalVisible = useVModel(props, 'visible', emit);
|
||||||
|
|
||||||
const initForm = {
|
const initForm: CreateTask = {
|
||||||
id: '',
|
resourceId: '',
|
||||||
time: '',
|
cron: '',
|
||||||
env: '',
|
|
||||||
resourcePoolIds: '',
|
|
||||||
enable: false,
|
enable: false,
|
||||||
methods: 'parallel',
|
runConfig: { runMode: 'SERIAL' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const form = ref({ ...initForm });
|
const form = ref<CreateTask>(cloneDeep(initForm));
|
||||||
|
|
||||||
const confirmLoading = ref<boolean>(false);
|
const confirmLoading = ref<boolean>(false);
|
||||||
const formRef = ref();
|
const formRef = ref<FormInstance | null>(null);
|
||||||
function handleCreate() {}
|
|
||||||
|
|
||||||
function resetForm() {
|
|
||||||
form.value = { ...initForm };
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCancel() {
|
function handleCancel() {
|
||||||
showModalVisible.value = false;
|
showModalVisible.value = false;
|
||||||
formRef.value?.resetFields();
|
formRef.value?.resetFields();
|
||||||
resetForm();
|
form.value = cloneDeep(initForm);
|
||||||
emit('close');
|
}
|
||||||
|
|
||||||
|
function handleCreate() {
|
||||||
|
formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
|
||||||
|
if (!errors) {
|
||||||
|
confirmLoading.value = true;
|
||||||
|
try {
|
||||||
|
if (props.sourceId) {
|
||||||
|
const params = {
|
||||||
|
...form.value,
|
||||||
|
resourceId: props.sourceId,
|
||||||
|
};
|
||||||
|
await configSchedule(params);
|
||||||
|
handleCancel();
|
||||||
|
emit('handleSuccess');
|
||||||
|
Message.success(t('common.createSuccess'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
confirmLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncFrequencyOptions = [
|
const syncFrequencyOptions = [
|
||||||
|
@ -156,22 +180,14 @@
|
||||||
{ label: t('apiTestManagement.timeTaskDay'), value: '0 0 0 * * ?' },
|
{ label: t('apiTestManagement.timeTaskDay'), value: '0 0 0 * * ?' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const resourcesList = ref<ResourcesItem[]>([
|
watch(
|
||||||
{
|
() => props.taskConfig,
|
||||||
id: '1',
|
(val) => {
|
||||||
name: '200.4',
|
if (val) {
|
||||||
cpuRate: '80%',
|
form.value = cloneDeep(val);
|
||||||
status: true,
|
}
|
||||||
},
|
}
|
||||||
{
|
);
|
||||||
id: '2',
|
|
||||||
name: 'LOCAL',
|
|
||||||
cpuRate: '80%',
|
|
||||||
status: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
function createCustomFrequency() {}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|
|
@ -133,4 +133,5 @@ export default {
|
||||||
'testPlan.testPlanGroup.selectTestPlanGroupPlaceHolder': 'Please select the Plan group',
|
'testPlan.testPlanGroup.selectTestPlanGroupPlaceHolder': 'Please select the Plan group',
|
||||||
'testPlan.testPlanGroup.batchArchivedGroup': 'Confirm archive: {count} test plan groups',
|
'testPlan.testPlanGroup.batchArchivedGroup': 'Confirm archive: {count} test plan groups',
|
||||||
'testPlan.testPlanGroup.confirmBatchDeletePlanGroup': 'Are you sure to delete {count} test plan groups?',
|
'testPlan.testPlanGroup.confirmBatchDeletePlanGroup': 'Are you sure to delete {count} test plan groups?',
|
||||||
|
'testPlan.testPlanGroup.deleteScheduleTaskSuccess': 'Delete the scheduled task successfully',
|
||||||
};
|
};
|
||||||
|
|
|
@ -122,4 +122,5 @@ export default {
|
||||||
'testPlan.testPlanGroup.selectTestPlanGroupPlaceHolder': '请选择计划组',
|
'testPlan.testPlanGroup.selectTestPlanGroupPlaceHolder': '请选择计划组',
|
||||||
'testPlan.testPlanGroup.batchArchivedGroup': '确认归档:{count} 个测试计划组吗',
|
'testPlan.testPlanGroup.batchArchivedGroup': '确认归档:{count} 个测试计划组吗',
|
||||||
'testPlan.testPlanGroup.confirmBatchDeletePlanGroup': '确认删除 {count} 个测试计划组吗?',
|
'testPlan.testPlanGroup.confirmBatchDeletePlanGroup': '确认删除 {count} 个测试计划组吗?',
|
||||||
|
'testPlan.testPlanGroup.deleteScheduleTaskSuccess': '删除定时任务成功',
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue