feat(任务中心): 任务中心静态页面

This commit is contained in:
baiqi 2024-10-11 17:41:27 +08:00 committed by Craftsman
parent 5a35423d4f
commit 97ae904911
40 changed files with 2163 additions and 2985 deletions

View File

@ -0,0 +1,33 @@
<template>
<MsDrawer
v-model:visible="visible"
:title="t('settings.navbar.task')"
width="100%"
class="ms-task-center-drawer"
:footer="false"
>
<TaskCenter type="project" />
</MsDrawer>
</template>
<script setup lang="ts">
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import TaskCenter from '@/views/taskCenter/index.vue';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const visible = defineModel<boolean>('visible', { required: true });
</script>
<style lang="less">
.ms-task-center-drawer {
.arco-drawer-body {
padding-top: 0;
:deep(.arco-tabs-tab) {
margin-left: 0;
}
}
}
</style>

View File

@ -82,7 +82,6 @@
import useLicenseStore from '@/store/modules/setting/license';
import {
type AuthScopeType,
AuthTableItem,
CurrentUserGroupItem,
SavePermissions,
@ -131,18 +130,6 @@
const loading = ref(false);
const systemSpan = ref(1);
const projectSpan = ref(1);
const organizationSpan = ref(1);
const workstationSpan = ref(1);
const testPlanSpan = ref(1);
const bugManagementSpan = ref(1);
const caseManagementSpan = ref(1);
const uiTestSpan = ref(1);
const apiTestSpan = ref(1);
const loadTestSpan = ref(1);
const personalSpan = ref(1);
//
const allChecked = ref(false);
const allIndeterminate = ref(false);
@ -171,62 +158,10 @@
}) => {
const { record, column } = data;
if ((column as TableColumnData).dataIndex === 'ability') {
if (record.isSystem) {
return {
rowspan: systemSpan.value,
rowspan: record.rowSpan,
};
}
if (record.isOrganization) {
return {
rowspan: organizationSpan.value,
};
}
if (record.isProject) {
return {
rowspan: projectSpan.value,
};
}
if (record.isWorkstation) {
return {
rowspan: workstationSpan.value,
};
}
if (record.isTestPlan) {
return {
rowspan: testPlanSpan.value,
};
}
if (record.isBugManagement) {
return {
rowspan: bugManagementSpan.value,
};
}
if (record.isCaseManagement) {
return {
rowspan: caseManagementSpan.value,
};
}
if (record.isUiTest) {
return {
rowspan: uiTestSpan.value,
};
}
if (record.isApiTest) {
return {
rowspan: apiTestSpan.value,
};
}
if (record.isLoadTest) {
return {
rowspan: loadTestSpan.value,
};
}
if (record.isPersonal) {
return {
rowspan: personalSpan.value,
};
}
}
};
const { t } = useI18n();
@ -236,7 +171,7 @@
* @param item
* @param type
*/
const makeData = (item: UserGroupAuthSetting, type: AuthScopeType) => {
const makeData = (item: UserGroupAuthSetting) => {
const result: AuthTableItem[] = [];
item.children?.forEach((child, index) => {
if (!child.license || (child.license && licenseStore.hasLicense())) {
@ -261,17 +196,7 @@
perChecked,
ability: index === 0 ? item.name : undefined,
operationObject: t(child.name),
isSystem: index === 0 && type === 'SYSTEM',
isOrganization: index === 0 && type === 'ORGANIZATION',
isProject: index === 0 && type === 'PROJECT',
isWorkstation: index === 0 && type === 'WORKSTATION',
isTestPlan: index === 0 && type === 'TEST_PLAN',
isBugManagement: index === 0 && type === 'BUG_MANAGEMENT',
isCaseManagement: index === 0 && type === 'CASE_MANAGEMENT',
isUiTest: index === 0 && type === 'UI_TEST',
isLoadTest: index === 0 && type === 'LOAD_TEST',
isApiTest: index === 0 && type === 'API_TEST',
isPersonal: index === 0 && type === 'PERSONAL',
rowSpan: index === 0 ? item.children?.length || 1 : undefined,
});
}
});
@ -281,30 +206,7 @@
const transformData = (data: UserGroupAuthSetting[]) => {
const result: AuthTableItem[] = [];
data.forEach((item) => {
if (item.id === 'SYSTEM') {
systemSpan.value = item.children?.length || 0;
} else if (item.id === 'PROJECT') {
projectSpan.value = item.children?.length || 0;
} else if (item.id === 'ORGANIZATION') {
organizationSpan.value = item.children?.length || 0;
} else if (item.id === 'WORKSTATION') {
workstationSpan.value = item.children?.length || 0;
} else if (item.id === 'TEST_PLAN') {
testPlanSpan.value = item.children?.length || 0;
} else if (item.id === 'BUG_MANAGEMENT') {
bugManagementSpan.value = item.children?.length || 0;
} else if (item.id === 'CASE_MANAGEMENT') {
caseManagementSpan.value = item.children?.length || 0;
} else if (item.id === 'UI_TEST') {
uiTestSpan.value = item.children?.length || 0;
} else if (item.id === 'API_TEST') {
apiTestSpan.value = item.children?.length || 0;
} else if (item.id === 'LOAD_TEST') {
loadTestSpan.value = item.children?.length || 0;
} else if (item.id === 'PERSONAL') {
personalSpan.value = item.children?.length || 0;
}
result.push(...makeData(item, item.id));
result.push(...makeData(item));
});
return result;
};

View File

@ -13,7 +13,8 @@
v-for="(item, index) of props.descriptions"
:key="item.label"
class="ms-description-item"
:style="{ marginBottom: props.descriptions.length - index <= props.column ? '' : '16px' }"
:class="item.class"
:style="{ marginBottom: props.descriptions.length - index <= props.column ? '' : `${props.lineGap}px` }"
>
<a-tooltip :content="item.label">
<div :class="`ms-description-item-label one-line-text max-w-[${props.labelWidth || '120px'}]`">
@ -153,6 +154,7 @@
label: string;
value: (string | number) | (string | number)[];
key?: string;
class?: string;
isTag?: boolean; //
tagClass?: string; //
tagType?: TagType; //
@ -188,10 +190,12 @@
descriptions: Description[];
labelWidth?: string;
oneLineValue?: boolean;
lineGap?: number;
addTagFunc?: (val: string, item: Description) => Promise<void>;
}>(),
{
column: 1,
lineGap: 16,
}
);
const emit = defineEmits<{

View File

@ -167,7 +167,7 @@
</li>
</ul>
</div>
<TaskCenterModal v-model:visible="taskCenterVisible" />
<TaskCenterDrawer v-model:visible="taskCenterVisible" />
<MessageCenterDrawer v-model:visible="messageCenterVisible" />
<AddProjectModal :visible="projectVisible" @cancel="projectVisible = false" />
</template>
@ -180,13 +180,13 @@
import MessageBox from '@/components/pure/message-box/index.vue';
import MessageCenterDrawer from '@/components/business/ms-message/MessageCenterDrawer.vue';
import TaskCenterDrawer from '@/components/business/ms-task-center-drawer/index.vue';
import TopMenu from '@/components/business/ms-top-menu/index.vue';
import TaskCenterModal from './taskCenterModal.vue';
import AddProjectModal from '@/views/setting/organization/project/components/addProjectModal.vue';
import { getMessageUnReadCount } from '@/api/modules/message';
import { switchProject } from '@/api/modules/project-management/project';
import { updateBaseInfo, updateLanguage } from '@/api/modules/user';
import { updateLanguage } from '@/api/modules/user';
import { MENU_LEVEL, type PathMapRoute } from '@/config/pathMap';
import { useI18n } from '@/hooks/useI18n';
import usePathMap from '@/hooks/usePathMap';

View File

@ -1,60 +0,0 @@
<template>
<a-modal
v-model:visible="innerVisible"
class="ms-modal-no-padding"
width="100%"
title-align="start"
unmount-on-close
:footer="false"
modal-class="modalSelf"
:body-style="{
'height': '100%',
'max-height': 'none',
}"
:modal-style="{
padding: '16px 16px 0',
}"
esc-to-close
fullscreen
@ok="handleOk"
@cancel="handleCancel"
>
<template #title> {{ t('settings.navbar.task') }}</template>
<div class="divider h-full">
<Suspense>
<TaskCenter v-if="visible" group="project" mode="modal"></TaskCenter>
</Suspense>
</div>
</a-modal>
</template>
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import TaskCenter from '@/views/project-management/taskCenter/component/taskCom.vue';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
}>();
const emit = defineEmits<{
(e: 'update:visible', visible: boolean): void;
}>();
const innerVisible = useVModel(props, 'visible', emit);
function handleOk() {}
function handleCancel() {
innerVisible.value = false;
}
</script>
<style scoped lang="less">
.divider {
border-top: 1px solid var(--color-text-n8);
}
</style>

View File

@ -331,95 +331,6 @@ export const pathMap: PathMapItem[] = [
route: RouteEnum.SETTING_SYSTEM_TASK_CENTER,
permission: [],
level: MENU_LEVEL[0],
children: [
{
key: 'SETTING_SYSTEM_TASK_CENTER_REAL_TIME', // 系统设置-系统-任务中心-实时任务
locale: 'project.taskCenter.realTimeTask',
route: RouteEnum.SETTING_SYSTEM_TASK_CENTER,
permission: [],
level: MENU_LEVEL[0],
children: [
{
key: 'SETTING_SYSTEM_TASK_CENTER_REAL_TIME_API_CASE', // 系统设置-系统-任务中心-实时任务-接口用例
locale: 'project.taskCenter.interfaceCase',
route: RouteEnum.SETTING_SYSTEM_TASK_CENTER,
permission: [],
level: MENU_LEVEL[0],
routeQuery: {
tab: 'real',
type: TaskCenterEnum.API_CASE,
},
},
{
key: 'SETTING_SYSTEM_TASK_CENTER_REAL_TIME_API_SCENARIO', // 系统设置-系统-任务中心-实时任务-接口场景
locale: 'project.taskCenter.apiScenario',
route: RouteEnum.SETTING_SYSTEM_TASK_CENTER,
permission: [],
level: MENU_LEVEL[0],
routeQuery: {
tab: 'real',
type: TaskCenterEnum.API_SCENARIO,
},
},
{
key: 'SETTING_SYSTEM_TASK_CENTER_REAL_TIME_TEST_PLAN', // 系统设置-系统-任务中心-实时任务-测试计划
locale: 'project.taskCenter.testPlan',
route: RouteEnum.SETTING_SYSTEM_TASK_CENTER,
permission: [],
level: MENU_LEVEL[0],
routeQuery: {
tab: 'real',
type: TaskCenterEnum.TEST_PLAN,
},
},
],
},
{
key: 'SETTING_SYSTEM_TASK_CENTER_TIME', // 系统设置-系统-任务中心-定时任务
locale: 'apiTestManagement.timeTask',
route: RouteEnum.SETTING_SYSTEM_TASK_CENTER,
permission: [],
level: MENU_LEVEL[0],
routeQuery: {
tab: 'timing',
},
children: [
{
key: 'SETTING_SYSTEM_TASK_CENTER_TIME_API_SCENARIO', // 系统设置-系统-任务中心-定时任务-接口场景
locale: 'project.taskCenter.apiScenario',
route: RouteEnum.SETTING_SYSTEM_TASK_CENTER,
permission: [],
level: MENU_LEVEL[0],
routeQuery: {
tab: 'timing',
type: TaskCenterEnum.API_SCENARIO,
},
},
{
key: 'SETTING_SYSTEM_TASK_CENTER_TIME_API_IMPORT', // 系统设置-系统-任务中心-定时任务-接口导入
locale: 'project.taskCenter.apiImport',
route: RouteEnum.SETTING_SYSTEM_TASK_CENTER,
permission: [],
level: MENU_LEVEL[0],
routeQuery: {
tab: 'timing',
type: TaskCenterEnum.API_IMPORT,
},
},
{
key: 'SETTING_SYSTEM_TASK_CENTER_TIME_TEST_PLAN', // 系统设置-系统-任务中心-定时任务-测试计划
locale: 'project.taskCenter.testPlan',
route: RouteEnum.SETTING_SYSTEM_TASK_CENTER,
permission: [],
level: MENU_LEVEL[0],
routeQuery: {
tab: 'timing',
type: TaskCenterEnum.TEST_PLAN,
},
},
],
},
],
},
{
key: 'SETTING_SYSTEM_PLUGIN_MANAGEMENT', // 系统设置-系统-插件管理
@ -471,95 +382,6 @@ export const pathMap: PathMapItem[] = [
route: RouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
permission: [],
level: MENU_LEVEL[1],
children: [
{
key: 'SETTING_ORGANIZATION_TASK_CENTER_REAL_TIME', // 系统设置-组织-任务中心-实时任务
locale: 'project.taskCenter.realTimeTask',
route: RouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
permission: [],
level: MENU_LEVEL[1],
children: [
{
key: 'SETTING_ORGANIZATION_TASK_CENTER_REAL_TIME_API_CASE', // 系统设置-组织-任务中心-实时任务-接口用例
locale: 'project.taskCenter.interfaceCase',
route: RouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
permission: [],
level: MENU_LEVEL[1],
routeQuery: {
tab: 'real',
type: TaskCenterEnum.API_CASE,
},
},
{
key: 'SETTING_ORGANIZATION_TASK_CENTER_REAL_TIME_API_SCENARIO', // 系统设置-组织-任务中心-实时任务-接口场景
locale: 'project.taskCenter.apiScenario',
route: RouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
permission: [],
level: MENU_LEVEL[1],
routeQuery: {
tab: 'real',
type: TaskCenterEnum.API_SCENARIO,
},
},
{
key: 'SETTING_ORGANIZATION_TASK_CENTER_REAL_TIME_TEST_PLAN', // 系统设置-组织-任务中心-实时任务-测试计划
locale: 'project.taskCenter.testPlan',
route: RouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
permission: [],
level: MENU_LEVEL[1],
routeQuery: {
tab: 'real',
type: TaskCenterEnum.TEST_PLAN,
},
},
],
},
{
key: 'SETTING_ORGANIZATION_TASK_CENTER_TIME', // 系统设置-组织-任务中心-定时任务
locale: 'apiTestManagement.timeTask',
route: RouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
permission: [],
level: MENU_LEVEL[1],
routeQuery: {
tab: 'timeTask',
},
children: [
{
key: 'SETTING_ORGANIZATION_TASK_CENTER_TIME_API_SCENARIO', // 系统设置-组织-任务中心-定时任务-接口场景
locale: 'project.taskCenter.apiScenario',
route: RouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
permission: [],
level: MENU_LEVEL[1],
routeQuery: {
tab: 'timeTask',
type: TaskCenterEnum.API_SCENARIO,
},
},
{
key: 'SETTING_ORGANIZATION_TASK_CENTER_TIME_API_IMPORT', // 系统设置-组织-任务中心-定时任务-接口导入
locale: 'project.taskCenter.apiImport',
route: RouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
permission: [],
level: MENU_LEVEL[1],
routeQuery: {
tab: 'timeTask',
type: TaskCenterEnum.API_IMPORT,
},
},
{
key: 'SETTING_ORGANIZATION_TASK_CENTER_TIME_TEST_PLAN', // 系统设置-组织-任务中心-定时任务-测试计划
locale: 'project.taskCenter.testPlan',
route: RouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
permission: [],
level: MENU_LEVEL[1],
routeQuery: {
tab: 'timeTask',
type: TaskCenterEnum.TEST_PLAN,
},
},
],
},
],
},
{
key: 'SETTING_ORGANIZATION_TEMPLATE', // 系统设置-组织-模板管理
@ -925,98 +747,6 @@ export const pathMap: PathMapItem[] = [
route: '',
permission: [],
level: MENU_LEVEL[2],
children: [
{
key: 'PROJECT_MANAGEMENT_TASK_CENTER_REAL_TIME', // 项目管理-任务中心-实时任务
locale: 'project.taskCenter.realTimeTask',
route: '',
permission: [],
level: MENU_LEVEL[2],
children: [
{
key: 'PROJECT_MANAGEMENT_TASK_CENTER_REAL_TIME_API_CASE', // 项目管理-任务中心-实时任务-接口用例
locale: 'project.taskCenter.interfaceCase',
route: '',
permission: [],
level: MENU_LEVEL[2],
routeQuery: {
task: true,
tab: 'real',
type: TaskCenterEnum.API_CASE,
},
},
{
key: 'PROJECT_MANAGEMENT_TASK_CENTER_REAL_TIME_API_SCENARIO', // 项目管理-任务中心-实时任务-接口场景
locale: 'project.taskCenter.apiScenario',
route: '',
permission: [],
level: MENU_LEVEL[2],
routeQuery: {
task: true,
tab: 'real',
type: TaskCenterEnum.API_SCENARIO,
},
},
{
key: 'PROJECT_MANAGEMENT_TASK_CENTER_REAL_TIME_TEST_PLAN', // 项目管理-任务中心-实时任务-接口场景
locale: 'project.taskCenter.testPlan',
route: '',
permission: [],
level: MENU_LEVEL[2],
routeQuery: {
task: true,
tab: 'real',
type: TaskCenterEnum.TEST_PLAN,
},
},
],
},
{
key: 'PROJECT_MANAGEMENT_TASK_CENTER_TIME', // 项目管理-任务中心-定时任务
locale: 'project.taskCenter.scheduledTask',
route: '',
permission: [],
level: MENU_LEVEL[2],
children: [
{
key: 'PROJECT_MANAGEMENT_TASK_CENTER_TIME_API_SCENARIO', // 项目管理-任务中心-定时任务-接口场景
locale: 'project.taskCenter.apiScenario',
route: '',
permission: [],
level: MENU_LEVEL[2],
routeQuery: {
task: true,
tab: 'timing',
type: TaskCenterEnum.API_SCENARIO,
},
},
{
key: 'PROJECT_MANAGEMENT_TASK_CENTER_TIME_API_IMPORT', // 项目管理-任务中心-定时任务-接口导入
locale: 'project.taskCenter.apiImport',
route: '',
permission: [],
level: MENU_LEVEL[2],
routeQuery: {
task: true,
tab: 'timing',
type: TaskCenterEnum.API_IMPORT,
},
},
{
key: 'PROJECT_MANAGEMENT_TASK_CENTER_TIME_TEST_PLAN', // 项目管理-任务中心-定时任务-测试计划
locale: 'project.taskCenter.testPlan',
route: '',
permission: [],
level: MENU_LEVEL[2],
routeQuery: {
task: true,
tab: 'timing',
type: TaskCenterEnum.TEST_PLAN,
},
},
],
},
],
},
],
},

View File

@ -1,7 +1,5 @@
// 模板展示字段icon
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
export enum ReportEnum {
API_SCENARIO_REPORT = 'API_SCENARIO_REPORT',
API_REPORT = 'API_REPORT',

View File

@ -73,9 +73,9 @@ export enum TableKeyEnum {
TEST_PLAN_DETAIL_API_CASE = 'testPlanDetailApiCase',
TEST_PLAN_DETAIL_API_SCENARIO = 'testPlanDetailApiScenario',
TEST_PLAN_REPORT_TABLE = 'testPlanReportTable',
TASK_API_CASE_SYSTEM = 'taskCenterApiCaseSystem',
TASK_API_CASE_ORGANIZATION = 'taskCenterApiCaseOrganization',
TASK_API_CASE_PROJECT = 'taskCenterApiCaseProject',
TASK_CENTER_CASE_TASK = 'taskCenterCaseTask',
TASK_CENTER_CASE_TASK_DETAIL = 'taskCenterCaseTaskDetail',
TASK_CENTER_SYSTEM_TASK = 'taskCenterSystemTask',
TASK_SCHEDULE_TASK_API_IMPORT_SYSTEM = 'taskCenterScheduleApiImportSystem',
TASK_SCHEDULE_TASK_API_SCENARIO_SYSTEM = 'taskCenterScheduleApiScenarioSystem',
TASK_SCHEDULE_TASK_API_IMPORT_ORGANIZATION = 'taskCenterScheduleApiImportOrganization',

View File

@ -20,6 +20,8 @@ export enum FilterSlotNameEnum {
API_TEST_REPORT_TYPE = 'API_TEST_REPORT_TYPE', // 接口测试-报告-报告类型
API_TEST_CASE_API_REPORT_STATUS = 'API_TEST_CASE_API_REPORT_STATUS', // 接口测试-定义-用例列表-报告状态
GLOBAL_TASK_CENTER_EXEC_STATUS = 'GLOBAL_TASK_CENTER_EXEC_STATUS', // 任务中心-执行状态
GLOBAL_TASK_CENTER_EXEC_RESULT = 'GLOBAL_TASK_CENTER_EXEC_RESULT', // 任务中心-执行结果
GLOBAL_TASK_CENTER_EXEC_METHOD = 'GLOBAL_TASK_CENTER_EXEC_METHOD', // 任务中心-执行方式
}
export enum FilterRemoteMethodsEnum {

View File

@ -1,20 +1,10 @@
// 模板展示字段icon
// 任务中心 tab 枚举
export enum TaskCenterEnum {
API_CASE = 'API_CASE', // 接口用例
API_SCENARIO = 'API_SCENARIO', // 接口场景 API场景
UI_TEST = 'UI_TEST', // ui测试
LOAD_TEST = 'LOAD_TEST', // 性能测试
TEST_PLAN = 'TEST_PLAN', // 测试计划
TEST_RESOURCE = 'TEST_RESOURCE', // 测试资源
API_IMPORT = 'API_IMPORT', // API导入
CASE = 'CASE',
DETAIL = 'DETAIL',
BACKEND = 'BACKEND',
API_IMPORT = 'API_IMPORT',
}
export type ResourceTypeMapKey =
| TaskCenterEnum.API_CASE
| TaskCenterEnum.API_SCENARIO
| TaskCenterEnum.UI_TEST
| TaskCenterEnum.LOAD_TEST
| TaskCenterEnum.TEST_PLAN;
// 执行方式
export enum ExecutionMethods {
SCHEDULE = 'SCHEDULE', // 定时任务

View File

@ -14,6 +14,7 @@ export default {
'common.end': 'End',
'common.enable': 'Enable',
'common.disable': 'Disable',
'common.open': 'Open',
'common.close': 'Close',
'common.create': 'Create',
'common.newCreate': 'Create',
@ -142,6 +143,7 @@ export default {
'common.stay': 'Stay',
'common.apply': 'Apply',
'common.stop': 'Stop',
'common.stopConfirm': 'Confirm stop',
'common.module': 'Module',
'common.yes': 'Yes',
'common.no': 'No',
@ -191,9 +193,11 @@ export default {
'common.updateUserName': 'Update user name',
'common.updateTime': 'Update time',
'common.belongProject': 'Belong to Project',
'common.belongOrg': 'Belong to Organization',
'common.noMatchData': 'No matching data',
'common.name': 'name',
'common.stopped': 'Stopped',
'common.completed': 'Completed',
'common.config': 'Config',
'common.executionResult': 'Execution result',
'common.expandAllSubModule': 'Expand all submodules',

View File

@ -14,6 +14,7 @@ export default {
'common.end': '结束',
'common.enable': '启用',
'common.disable': '禁用',
'common.open': '开启',
'common.close': '关闭',
'common.create': '创建',
'common.newCreate': '新建',
@ -142,6 +143,7 @@ export default {
'common.stay': '留下',
'common.apply': '应用',
'common.stop': '停止',
'common.stopConfirm': '确认停止',
'common.module': '模块',
'common.yes': '是',
'common.no': '否',
@ -192,9 +194,11 @@ export default {
'common.updateUserName': '更新人',
'common.updateTime': '更新时间',
'common.belongProject': '所属项目',
'common.belongOrg': '所属项目',
'common.noMatchData': '暂无匹配数据',
'common.name': '名称',
'common.stopped': '已停止',
'common.completed': '已完成',
'common.config': '配置',
'common.expandAllSubModule': '展开全部子模块',
'common.collapseAllSubModule': '收起全部子模块',

View File

@ -112,17 +112,7 @@ export interface AuthTableItem {
// 对应表格权限的复选框组的绑定值
perChecked?: string[];
operationObject?: string;
isSystem?: boolean;
isOrganization?: boolean;
isProject?: boolean;
isWorkstation?: boolean;
isTestPlan?: boolean;
isBugManagement?: boolean;
isCaseManagement?: boolean;
isApiTest?: boolean;
isUiTest?: boolean;
isLoadTest?: boolean;
isPersonal?: boolean;
rowSpan?: number;
indeterminate?: boolean;
}
export interface SavePermissions {

View File

@ -22,7 +22,10 @@
@change="loadControlLoop"
/>
</div>
<div class="flex w-full items-center justify-between rounded bg-[var(--color-text-n9)] px-[16px] py-[8px]">
<div
v-if="!props.hideResponseTime"
class="flex w-full items-center justify-between rounded bg-[var(--color-text-n9)] px-[16px] py-[8px]"
>
<div class="font-medium">
<span
:class="{ 'text-[rgb(var(--primary-5))]': activeType === 'ResContent' }"
@ -128,7 +131,6 @@
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { cloneDeep } from 'lodash-es';
@ -148,6 +150,7 @@
const props = defineProps<{
mode: 'tiled' | 'tab'; // | tab
hideResponseTime?: boolean; //
stepItem?: ScenarioItemType; //
console?: string;
isPriorityLocalExec?: boolean;

View File

@ -155,7 +155,7 @@
<div v-if="!step.fold" class="line"></div>
</div>
<!-- 折叠展开内容 -->
<div v-if="showResContent(step)" class="foldContent mt-4 pl-2">
<div v-if="showResContent(step)" class="foldContent mt-[18px] pl-2">
<Suspense>
<StepDetailContent
:mode="props.activeType"
@ -167,6 +167,7 @@
:report-id="props?.reportId"
:steps="steps"
:get-report-step-detail="props.getReportStepDetail"
hide-response-time
/>
</Suspense>
</div>

View File

@ -177,7 +177,7 @@
import dayjs from 'dayjs';
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
import { FilterFormItem, FilterResult } from '@/components/pure/ms-advance-filter/type';
import { FilterFormItem } from '@/components/pure/ms-advance-filter/type';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';

View File

@ -1,572 +0,0 @@
<template>
<div class="px-[16px]">
<div class="mb-[16px] flex items-center justify-between">
<div class="flex items-center"></div>
<div class="items-right flex gap-[8px]">
<a-input-search
v-model:model-value="keyword"
:placeholder="t('system.organization.searchIndexPlaceholder')"
allow-clear
class="mx-[8px] w-[240px]"
@search="searchList"
@press-enter="searchList"
@clear="searchList"
></a-input-search>
</div>
</div>
<ms-base-table
v-bind="propsRes"
ref="tableRef"
:action-config="tableBatchActions"
:selectable="hasOperationPermission"
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<template #resourceNum="{ record }">
<div
v-if="!record.integrated"
type="text"
class="one-line-text w-full"
:class="[hasJumpPermission ? 'text-[rgb(var(--primary-5))]' : '']"
@click="showDetail(record)"
>{{ record.resourceNum }}
</div>
</template>
<template #resourceName="{ record }">
<div
v-if="!record.integrated"
class="one-line-text max-w-[300px]"
:class="[hasJumpPermission ? 'text-[rgb(var(--primary-5))]' : '']"
@click="showDetail(record)"
>{{ record.resourceName }}
</div>
</template>
<template #[FilterSlotNameEnum.GLOBAL_TASK_CENTER_API_CASE_STATUS]="{ filterContent }">
<ExecutionStatus :module-type="props.moduleType" :status="filterContent.value" />
</template>
<template #status="{ record }">
<ExecutionStatus
:module-type="props.moduleType"
:status="record.status"
:script-identifier="record.scriptIdentifier"
/>
</template>
<template #execStatus="{ record }">
<ExecStatus :status="record.execStatus" />
</template>
<template #[FilterSlotNameEnum.GLOBAL_TASK_CENTER_EXEC_STATUS]="{ filterContent }">
<ExecStatus :status="filterContent.value" />
</template>
<template #projectName="{ record }">
<a-tooltip :content="`${record.projectName}`" position="tl">
<div class="one-line-text">{{ characterLimit(record.projectName) }}</div>
</a-tooltip>
</template>
<template #organizationName="{ record }">
<a-tooltip :content="`${record.organizationName}`" position="tl">
<div class="one-line-text">{{ characterLimit(record.organizationName) }}</div>
</a-tooltip>
</template>
<template #triggerMode="{ record }">
<span>{{ t(ExecutionMethodsLabel[record.triggerMode as keyof typeof ExecutionMethodsLabel]) }}</span>
</template>
<template #operationTime="{ record }">
<span>{{ dayjs(record.operationTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #operation="{ record, rowIndex }">
<div v-if="record.historyDeleted">
<a-tooltip :content="t('project.executionHistory.cleared')">
<MsButton
class="!mr-0"
:disabled="
record.historyDeleted || !hasAnyPermission(permissionsMap[props.group][props.moduleType].report)
"
@click="viewReport(record.id, rowIndex)"
>{{ t('project.taskCenter.viewReport') }}
</MsButton>
</a-tooltip>
</div>
<div v-else>
<MsButton
class="!mr-0"
:disabled="record.historyDeleted || !hasAnyPermission(permissionsMap[props.group][props.moduleType].report)"
@click="viewReport(record.id, rowIndex)"
>{{ t('project.taskCenter.viewReport') }}
</MsButton>
</div>
<a-divider v-if="['RUNNING', 'RERUNNING'].includes(record.execStatus)" direction="vertical" />
<MsButton
v-if="
['RUNNING', 'RERUNNING'].includes(record.execStatus) &&
hasAnyPermission(permissionsMap[props.group][props.moduleType].stop)
"
class="!mr-0"
@click="stop(record)"
>{{ t('project.taskCenter.stop') }}
</MsButton>
</template>
</ms-base-table>
</div>
<ReportDetailDrawer
v-model:visible="showDetailDrawer"
:report-id="activeDetailId"
:active-report-index="activeReportIndex"
:table-data="propsRes.data"
:page-change="propsEvent.pageChange"
:pagination="propsRes.msPagination!"
/>
<caseAndScenarioReportDrawer v-model:visible="showCaseDetailDrawer" :report-id="activeDetailId" />
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import dayjs from 'dayjs';
import MsButton from '@/components/pure/ms-button/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 ExecutionStatus from './executionStatus.vue';
import caseAndScenarioReportDrawer from '@/views/api-test/components/caseAndScenarioReportDrawer.vue';
import ReportDetailDrawer from '@/views/api-test/report/component/reportDetailDrawer.vue';
import ExecStatus from '@/views/test-plan/report/component/execStatus.vue';
import {
batchStopRealOrdApi,
batchStopRealProjectApi,
batchStopRealSystemApi,
getRealOrdApiCaseList,
getRealProApiCaseList,
getRealSysApiCaseList,
stopRealOrdApi,
stopRealProjectApi,
stopRealSysApi,
} from '@/api/modules/project-management/taskCenter';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useOpenNewPage from '@/hooks/useOpenNewPage';
import { useTableStore } from '@/store';
import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import { BatchApiParams } from '@/models/common';
import { ReportExecStatus } from '@/enums/apiEnum';
import { RouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { ExecutionMethodsLabel } from '@/enums/taskCenter';
import type { ExtractedKeys } from './utils';
import { getOrgColumns, getProjectColumns, Group, TaskStatus } from './utils';
const { openNewPage } = useOpenNewPage();
const tableStore = useTableStore();
const { openModal } = useModal();
const { t } = useI18n();
const props = defineProps<{
group: Group;
moduleType: ExtractedKeys;
name: string;
}>();
const keyword = ref<string>('');
const permissionsMap: Record<Group, any> = {
organization: {
API_CASE: {
stop: ['ORGANIZATION_TASK_CENTER:READ+STOP', 'PROJECT_API_DEFINITION_CASE:READ+EXECUTE'],
jump: ['PROJECT_API_DEFINITION_CASE:READ'],
report: ['PROJECT_API_DEFINITION_CASE:READ+EXECUTE', 'PROJECT_API_REPORT:READ'],
},
API_SCENARIO: {
stop: ['ORGANIZATION_TASK_CENTER:READ+STOP', 'PROJECT_API_SCENARIO:READ+EXECUTE'],
jump: ['PROJECT_API_SCENARIO:READ'],
report: ['PROJECT_API_SCENARIO:READ+EXECUTE', 'PROJECT_API_REPORT:READ'],
},
},
system: {
API_CASE: {
stop: ['SYSTEM_TASK_CENTER:READ+STOP', 'PROJECT_API_DEFINITION_CASE:READ+EXECUTE'],
jump: ['PROJECT_API_DEFINITION_CASE:READ'],
report: ['PROJECT_API_DEFINITION_CASE:READ+EXECUTE', 'PROJECT_API_REPORT:READ'],
},
API_SCENARIO: {
stop: ['SYSTEM_TASK_CENTER:READ+STOP', 'PROJECT_API_SCENARIO:READ+EXECUTE'],
jump: ['PROJECT_API_SCENARIO:READ'],
report: ['PROJECT_API_SCENARIO:READ+EXECUTE', 'PROJECT_API_REPORT:READ'],
},
},
project: {
API_CASE: {
stop: ['PROJECT_API_DEFINITION_CASE:READ+EXECUTE'],
jump: ['PROJECT_API_DEFINITION_CASE:READ'],
report: ['PROJECT_API_DEFINITION_CASE:READ+EXECUTE', 'PROJECT_API_REPORT:READ'],
},
API_SCENARIO: {
stop: ['PROJECT_API_SCENARIO:READ+EXECUTE'],
jump: ['PROJECT_API_SCENARIO:READ'],
report: ['PROJECT_API_SCENARIO:READ+EXECUTE', 'PROJECT_API_REPORT:READ'],
},
},
};
const loadRealMap = ref({
system: {
list: getRealSysApiCaseList,
stop: stopRealSysApi,
batchStop: batchStopRealSystemApi,
},
organization: {
list: getRealOrdApiCaseList,
stop: stopRealOrdApi,
batchStop: batchStopRealOrdApi,
},
project: {
list: getRealProApiCaseList,
stop: stopRealProjectApi,
batchStop: batchStopRealProjectApi,
},
});
const hasJumpPermission = computed(() => hasAnyPermission(permissionsMap[props.group][props.moduleType].jump));
const hasOperationPermission = computed(() => hasAnyPermission(permissionsMap[props.group][props.moduleType].stop));
const statusFilters = computed(() => {
return Object.keys(TaskStatus[props.moduleType]).map((key: any) => {
return {
value: key,
...TaskStatus[props.moduleType][key],
};
});
});
const ExecStatusList = computed(() => {
return Object.values(ReportExecStatus).map((e) => {
return {
value: e,
key: e,
};
});
});
const triggerModeList = [
{
value: 'SCHEDULE',
label: t('project.taskCenter.scheduledTask'),
},
{
value: 'MANUAL',
label: t('project.taskCenter.manualExecution'),
},
{
value: 'API',
label: t('project.taskCenter.interfaceCall'),
},
{
value: 'BATCH',
label: t('project.taskCenter.batchExecution'),
},
];
const staticColumns: MsTableColumn = [
{
title: 'project.taskCenter.resourceID',
dataIndex: 'resourceNum',
slotName: 'resourceNum',
width: 200,
sortIndex: 1,
fixed: 'left',
showTooltip: true,
showInTable: true,
showDrag: false,
columnSelectorDisabled: true,
},
{
title: 'project.taskCenter.resourceName',
slotName: 'resourceName',
dataIndex: 'resourceName',
width: 300,
showDrag: false,
showTooltip: true,
showInTable: true,
columnSelectorDisabled: true,
},
{
title: 'project.taskCenter.executionResult',
dataIndex: 'status',
slotName: 'status',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
filterConfig: {
options: statusFilters.value,
filterSlotName: FilterSlotNameEnum.GLOBAL_TASK_CENTER_API_CASE_STATUS,
},
showInTable: true,
width: 200,
showDrag: true,
},
{
title: 'project.taskCenter.status',
dataIndex: 'execStatus',
slotName: 'execStatus',
filterConfig: {
options: ExecStatusList.value,
filterSlotName: FilterSlotNameEnum.GLOBAL_TASK_CENTER_EXEC_STATUS,
},
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showInTable: true,
width: 200,
showDrag: true,
},
{
title: 'project.taskCenter.executionMode',
dataIndex: 'triggerMode',
slotName: 'triggerMode',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
filterConfig: {
options: triggerModeList,
},
showInTable: true,
width: 150,
showDrag: true,
},
{
title: 'project.taskCenter.resourcePool',
slotName: 'poolName',
dataIndex: 'poolName',
showInTable: true,
showDrag: true,
showTooltip: true,
width: 200,
},
{
title: 'project.taskCenter.operator',
slotName: 'operationName',
dataIndex: 'operationName',
showInTable: true,
showDrag: true,
showTooltip: true,
width: 200,
},
{
title: 'project.taskCenter.operating',
dataIndex: 'operationTime',
slotName: 'operationTime',
width: 180,
showDrag: true,
},
{
title: 'common.operation',
slotName: 'operation',
dataIndex: 'operation',
fixed: 'right',
width: hasOperationPermission.value ? 180 : 100,
},
];
const tableKeysMap: Record<string, any> = {
system: TableKeyEnum.TASK_API_CASE_SYSTEM,
organization: TableKeyEnum.TASK_API_CASE_ORGANIZATION,
project: TableKeyEnum.TASK_API_CASE_PROJECT,
};
const groupColumnsMap: Record<string, any> = {
system: [getOrgColumns(), getProjectColumns(tableKeysMap[props.group]), ...staticColumns],
organization: [getProjectColumns(tableKeysMap[props.group]), ...staticColumns],
project: staticColumns,
};
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, resetFilterParams } = useTable(
loadRealMap.value[props.group].list,
{
tableKey: tableKeysMap[props.group],
scroll: {
x: 1400,
},
showSetting: true,
selectable: hasOperationPermission.value,
heightUsed: 330,
showSelectAll: true,
}
);
function initData() {
setLoadListParams({
keyword: keyword.value,
moduleType: props.moduleType,
});
loadList();
}
const tableBatchActions = {
baseAction: [
{
label: 'project.taskCenter.batchStop',
eventTag: 'batchStop',
anyPermission: permissionsMap[props.group][props.moduleType].stop,
},
// {
// label: 'project.taskCenter.batchExecution',
// eventTag: 'batchExecution',
// },
],
};
const batchParams = ref<BatchApiParams>({
selectIds: [],
selectAll: false,
excludeIds: [] as string[],
condition: {},
});
function batchStopRealTask() {
openModal({
type: 'warning',
title: t('project.taskCenter.batchStopTask', { num: batchParams.value.currentSelectCount }),
content: t('project.taskCenter.stopTaskContent'),
okText: t('project.taskCenter.confirmStop'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
const { selectIds, selectAll, excludeIds } = batchParams.value;
await loadRealMap.value[props.group].batchStop({
moduleType: props.moduleType,
selectIds: selectIds || [],
selectAll,
excludeIds: excludeIds || [],
condition: {
keyword: keyword.value,
filter: {
...propsRes.value.filter,
},
},
});
resetSelector();
Message.success(t('project.taskCenter.stopSuccess'));
initData();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
batchParams.value = { ...params, selectIds: params?.selectedIds || [], condition: params?.condition || {} };
if (event.eventTag === 'batchStop') {
batchStopRealTask();
}
}
function searchList() {
resetSelector();
initData();
}
function stop(record: any) {
openModal({
type: 'warning',
title: t('project.taskCenter.stopTask', { name: characterLimit(record.resourceName) }),
content: t('project.taskCenter.stopTaskContent'),
okText: t('project.taskCenter.confirmStop'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
await loadRealMap.value[props.group].stop(props.moduleType, record.id);
resetSelector();
Message.success(t('project.taskCenter.stopSuccess'));
initData();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
function execution(record: any) {}
onBeforeMount(async () => {
initData();
});
/**
* 报告详情 showReportDetail
*/
const activeDetailId = ref<string>('');
const activeReportIndex = ref<number>(0);
const showDetailDrawer = ref<boolean>(false);
const showCaseDetailDrawer = ref<boolean>(false);
function viewReport(id: string, rowIndex: number) {
activeDetailId.value = id;
activeReportIndex.value = rowIndex;
if (props.moduleType === 'API_CASE') {
showCaseDetailDrawer.value = true;
} else {
showDetailDrawer.value = true;
}
}
watch(
() => props.moduleType,
(val) => {
if (val) {
resetSelector();
resetFilterParams();
initData();
}
}
);
/**
* 跳转接口用例详情
*/
function showDetail(record: any) {
if (!hasJumpPermission.value) {
return;
}
if (props.moduleType === 'API_CASE') {
openNewPage(RouteEnum.API_TEST_MANAGEMENT, {
orgId: record.organizationId,
pId: record.projectId,
cId: record.resourceId,
});
}
if (props.moduleType === 'API_SCENARIO') {
openNewPage(RouteEnum.API_TEST_SCENARIO, {
orgId: record.organizationId,
pId: record.projectId,
id: record.resourceId,
});
}
}
onBeforeUnmount(() => {
if (props.moduleType === 'API_CASE') {
showCaseDetailDrawer.value = false;
} else {
showDetailDrawer.value = false;
}
});
await tableStore.initColumn(tableKeysMap[props.group], groupColumnsMap[props.group], 'drawer', true);
</script>
<style scoped></style>

View File

@ -1,42 +0,0 @@
<template>
<a-tag :color="statusMap[props.status]?.color" :class="statusMap[props.status]?.class" :size="props.size">
{{ t(statusMap[props.status]?.label) }}
</a-tag>
</template>
<script setup lang="ts">
import { useI18n } from '@/hooks/useI18n';
import { ReportExecStatus } from '@/enums/apiEnum';
const props = defineProps<{
status: ReportExecStatus;
size?: 'small' | 'medium' | 'large';
}>();
const { t } = useI18n();
const statusMap = {
PENDING: {
label: 'common.unExecute',
color: 'var(--color-text-n8)',
class: '!text-[var(--color-text-1)]',
},
RUNNING: {
label: 'common.running',
color: 'rgb(var(--link-2))',
class: '!text-[rgb(var(--link-6))]',
},
STOPPED: {
label: 'common.stopped',
color: 'rgb(var(--warning-2))',
class: '!text-[rgb(var(--warning-6))]',
},
COMPLETED: {
label: 'report.completed',
color: 'rgb(var(--success-2))',
class: '!text-[rgb(var(--success-6))]',
},
} as const;
</script>
<style lang="less" scoped></style>

View File

@ -1,75 +0,0 @@
<template>
<div class="flex items-center justify-start">
<MsIcon :type="getExecutionResult().icon" :class="getExecutionResult()?.color" size="14" />
<span class="ml-1">{{ t(getExecutionResult().label) }}</span>
<a-tooltip v-if="props.scriptIdentifier" :content="getMsg()">
<MsTag
class="ml-2"
:self-style="{
border: `1px solid ${methodColor}`,
color: methodColor,
backgroundColor: 'white',
}"
>
{{ t('report.detail.script.error') }}
</MsTag>
</a-tooltip>
</div>
</template>
<script setup lang="ts">
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import { useI18n } from '@/hooks/useI18n';
import { ReportEnum } from '@/enums/reportEnum';
import type { ResourceTypeMapKey } from '@/enums/taskCenter';
import { TaskCenterEnum } from '@/enums/taskCenter';
const { t } = useI18n();
const props = defineProps<{
status: string;
moduleType: ResourceTypeMapKey;
scriptIdentifier?: string;
}>();
export interface IconType {
icon: string;
label: string;
color?: string;
}
const iconTypeStatus: Record<string, any> = {
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: 'common.success',
},
ERROR: {
icon: 'icon-icon_close_colorful',
label: 'common.fail',
},
FAKE_ERROR: {
icon: 'icon-icon_warning_colorful',
label: 'common.fakeError',
},
DEFAULT: {
icon: '',
label: '-',
color: '!text-[var(--color-text-input-border)]',
},
};
function getExecutionResult(): IconType {
return iconTypeStatus[props.status] ? iconTypeStatus[props.status] : iconTypeStatus.DEFAULT;
}
const methodColor = 'rgb(var(--warning-7))';
function getMsg() {
if (props.moduleType === TaskCenterEnum.API_SCENARIO && props.scriptIdentifier) {
return t('report.detail.scenario.errorTip');
}
return t('report.detail.api.errorTip');
}
</script>
<style scoped></style>

View File

@ -1,702 +0,0 @@
<template>
<div class="p-4 pt-0">
<a-radio-group
v-if="props.moduleType === 'TEST_PLAN'"
v-model:model-value="showType"
type="button"
class="file-show-type"
@change="changeShowType"
>
<a-radio value="All">{{ t('report.all') }}</a-radio>
<a-radio value="TEST_PLAN">{{ t('project.taskCenter.plan') }}</a-radio>
<a-radio value="GROUP">{{ t('project.taskCenter.planGroup') }}</a-radio>
</a-radio-group>
<div class="mb-4 flex items-center justify-end">
<!-- TODO这个版本不上 -->
<!-- <a-button type="primary">
{{ t('project.taskCenter.createTask') }}
</a-button>-->
<div class="flex items-center"></div>
<div class="items-right flex gap-[8px]">
<a-input-search
v-model:model-value="keyword"
:placeholder="
props.moduleType === 'API_IMPORT'
? t('apiTestManagement.searchTaskPlaceholder')
: t('system.organization.searchIndexPlaceholder')
"
allow-clear
class="mx-[8px] w-[240px]"
@search="searchList"
@press-enter="searchList"
@clear="searchList"
></a-input-search>
</div>
</div>
<ms-base-table
v-bind="propsRes"
ref="tableRef"
:action-config="tableBatchActions"
:selectable="hasOperationPermission"
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<template #resourceNum="{ record }">
<div
v-if="
props.moduleType === TaskCenterEnum.API_SCENARIO ||
(props.moduleType === TaskCenterEnum.TEST_PLAN && record.type === TaskCenterEnum.TEST_PLAN)
"
type="text"
class="one-line-text w-full"
:class="[hasJumpPermission ? 'text-[rgb(var(--primary-5))]' : '']"
@click="showDetail(record)"
>{{ record.resourceNum }}
</div>
</template>
<template #resourceName="{ record }">
<div
v-if="
props.moduleType === TaskCenterEnum.API_SCENARIO ||
(props.moduleType === TaskCenterEnum.TEST_PLAN && record.type === TaskCenterEnum.TEST_PLAN)
"
class="one-line-text max-w-[300px]"
:class="[hasJumpPermission ? 'text-[rgb(var(--primary-5))]' : '']"
@click="showDetail(record)"
>{{ record.resourceName }}
</div>
</template>
<template #resourceType="{ record }">
<div type="text" class="flex w-full">
{{ t(resourceTypeMap[record.resourceType as ResourceTypeMapKey].label) }}
</div>
</template>
<template #projectName="{ record }">
<a-tooltip :content="`${record.projectName}`" position="tl">
<div class="one-line-text">{{ characterLimit(record.projectName) }}</div>
</a-tooltip>
</template>
<template #organizationName="{ record }">
<a-tooltip :content="`${record.organizationName}`" position="tl">
<div class="one-line-text">{{ characterLimit(record.organizationName) }}</div>
</a-tooltip>
</template>
<template #value="{ record }">
<MsCronSelect
v-model:model-value="record.value"
class="param-input w-full min-w-[250px]"
:disabled="!record.enable || !hasAnyPermission(permissionsMap[props.group][props.moduleType]?.edit)"
@change="() => changeRunRules(record)"
/>
</template>
<template #operation="{ record }">
<a-switch
v-model="record.enable"
size="small"
type="line"
:before-change="() => handleBeforeEnableChange(record)"
:disabled="!hasAnyPermission(permissionsMap[props.group][props.moduleType]?.edit)"
/>
<a-divider direction="vertical" />
<MsButton
class="!mr-0"
:disabled="!hasAnyPermission(permissionsMap[props.group][props.moduleType]?.edit)"
@click="delSchedule(record)"
>{{ t('common.delete') }}
</MsButton>
<!-- TODO这一版不上 -->
<!-- <a-divider direction="vertical" />
<MsButton class="!mr-0" @click="edit(record)">{{ t('common.edit') }}</MsButton>
<a-divider direction="vertical" />
<MsTableMoreAction :list="moreActions" @select="handleMoreActionSelect" /> -->
</template>
</ms-base-table>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import dayjs from 'dayjs';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsCronSelect from '@/components/pure/ms-cron-select/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 {
batchDisableScheduleOrgTask,
batchDisableScheduleProTask,
batchDisableScheduleSysTask,
batchEnableScheduleOrgTask,
batchEnableScheduleProTask,
batchEnableScheduleSysTask,
deleteScheduleOrgTask,
deleteScheduleProTask,
deleteScheduleSysTask,
enableScheduleOrgTask,
enableScheduleProTask,
enableScheduleSysTask,
getScheduleOrgApiCaseList,
getScheduleProApiCaseList,
getScheduleSysApiCaseList,
updateRunRules,
updateRunRulesOrg,
updateRunRulesPro,
} from '@/api/modules/project-management/taskCenter';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useOpenNewPage from '@/hooks/useOpenNewPage';
import { useTableStore } from '@/store';
import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import { BatchApiParams } from '@/models/common';
import { TimingTaskCenterApiCaseItem } from '@/models/projectManagement/taskCenter';
import { RouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import type { ResourceTypeMapKey } from '@/enums/taskCenter';
import { TaskCenterEnum } from '@/enums/taskCenter';
import { getOrgColumns, getProjectColumns, Group, resourceTypeMap } from './utils';
const { openNewPage } = useOpenNewPage();
const tableStore = useTableStore();
const { openModal } = useModal();
const { t } = useI18n();
const props = defineProps<{
group: Group;
moduleType: keyof typeof TaskCenterEnum;
name: string;
}>();
const keyword = ref<string>('');
type ReportShowType = 'All' | 'TEST_PLAN' | 'GROUP';
const showType = ref<ReportShowType>('All');
const loadRealMap = ref({
system: {
list: getScheduleSysApiCaseList,
enable: enableScheduleSysTask,
delete: deleteScheduleSysTask,
edit: updateRunRules,
batchEnable: batchEnableScheduleSysTask,
batchDisable: batchDisableScheduleSysTask,
},
organization: {
list: getScheduleOrgApiCaseList,
enable: enableScheduleOrgTask,
delete: deleteScheduleOrgTask,
edit: updateRunRulesOrg,
batchEnable: batchEnableScheduleOrgTask,
batchDisable: batchDisableScheduleOrgTask,
},
project: {
list: getScheduleProApiCaseList,
enable: enableScheduleProTask,
delete: deleteScheduleProTask,
edit: updateRunRulesPro,
batchEnable: batchEnableScheduleProTask,
batchDisable: batchDisableScheduleProTask,
},
});
const permissionsMap: Record<Group, any> = {
organization: {
API_IMPORT: {
edit: ['ORGANIZATION_TASK_CENTER:READ+STOP', 'PROJECT_API_DEFINITION:READ+IMPORT'],
},
API_SCENARIO: {
edit: ['ORGANIZATION_TASK_CENTER:READ+STOP', 'PROJECT_API_SCENARIO:READ+EXECUTE'],
jump: ['PROJECT_API_SCENARIO:READ'],
},
TEST_PLAN: {
edit: ['ORGANIZATION_TASK_CENTER:READ+STOP', 'PROJECT_TEST_PLAN:READ+EXECUTE'],
jump: ['PROJECT_TEST_PLAN:READ'],
},
},
system: {
API_IMPORT: {
edit: ['SYSTEM_TASK_CENTER:READ+STOP', 'PROJECT_API_DEFINITION:READ+IMPORT'],
},
API_SCENARIO: {
edit: ['SYSTEM_TASK_CENTER:READ+STOP', 'PROJECT_API_SCENARIO:READ+EXECUTE'],
jump: ['PROJECT_API_SCENARIO:READ'],
},
TEST_PLAN: {
edit: ['SYSTEM_TASK_CENTER:READ+STOP', 'PROJECT_TEST_PLAN:READ+EXECUTE'],
jump: ['PROJECT_TEST_PLAN:READ'],
},
},
project: {
API_IMPORT: {
edit: ['PROJECT_API_DEFINITION:READ+IMPORT'],
},
API_SCENARIO: {
edit: ['PROJECT_API_SCENARIO:READ+EXECUTE'],
jump: ['PROJECT_API_SCENARIO:READ'],
},
TEST_PLAN: {
edit: ['PROJECT_TEST_PLAN:READ+EXECUTE'],
jump: ['PROJECT_TEST_PLAN:READ'],
},
},
};
const hasOperationPermission = computed(() =>
hasAnyPermission([...(permissionsMap[props.group][props.moduleType]?.edit || '')])
);
const resourceColumns: MsTableColumn = [
{
title: 'project.taskCenter.resourceID',
dataIndex: 'resourceNum',
slotName: 'resourceNum',
width: 140,
showInTable: true,
showTooltip: true,
showDrag: false,
sortIndex: 1,
columnSelectorDisabled: true,
},
{
title: 'project.taskCenter.resourceName',
slotName: 'resourceName',
dataIndex: 'resourceName',
width: 300,
showDrag: false,
showTooltip: true,
sortIndex: 2,
columnSelectorDisabled: true,
showInTable: true,
},
];
const staticColumns: MsTableColumn = [
{
title: 'project.taskCenter.operationRule',
dataIndex: 'value',
slotName: 'value',
showInTable: true,
width: 300,
showDrag: true,
showTooltip: true,
},
{
title: 'project.taskCenter.operator',
slotName: 'createUserName',
dataIndex: 'createUserName',
showInTable: true,
width: 200,
showDrag: true,
showTooltip: true,
},
{
title: 'project.taskCenter.operating',
slotName: 'createTime',
dataIndex: 'createTime',
showInTable: true,
width: 200,
showDrag: true,
},
{
title: 'project.taskCenter.nextExecutionTime',
slotName: 'nextTime',
dataIndex: 'nextTime',
showInTable: true,
width: 200,
showDrag: true,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
},
{
title: 'common.operation',
slotName: 'operation',
dataIndex: 'operation',
fixed: 'right',
showDrag: false,
width: 180,
},
];
const swaggerUrlColumn: MsTableColumn = [
{
title: 'project.taskCenter.swaggerUrl',
slotName: 'swaggerUrl',
dataIndex: 'swaggerUrl',
width: 300,
showDrag: false,
showTooltip: true,
columnSelectorDisabled: true,
showInTable: true,
},
];
const tableKeyMap: Record<string, any> = {
system: {
API_IMPORT: TableKeyEnum.TASK_SCHEDULE_TASK_API_IMPORT_SYSTEM,
API_SCENARIO: TableKeyEnum.TASK_SCHEDULE_TASK_API_SCENARIO_SYSTEM,
TEST_PLAN: TableKeyEnum.TASK_SCHEDULE_TASK_TEST_PLAN_SYSTEM,
},
organization: {
API_IMPORT: TableKeyEnum.TASK_SCHEDULE_TASK_API_IMPORT_ORGANIZATION,
API_SCENARIO: TableKeyEnum.TASK_SCHEDULE_TASK_API_SCENARIO_ORGANIZATION,
TEST_PLAN: TableKeyEnum.TASK_SCHEDULE_TASK_TEST_PLAN_ORGANIZATION,
},
project: {
API_IMPORT: TableKeyEnum.TASK_SCHEDULE_TASK_API_IMPORT_PROJECT,
API_SCENARIO: TableKeyEnum.TASK_SCHEDULE_TASK_API_SCENARIO_PROJECT,
TEST_PLAN: TableKeyEnum.TASK_SCHEDULE_TASK_TEST_PLAN_PROJECT,
},
};
const groupColumnsMap: Record<string, any> = {
system: {
API_IMPORT: [
getOrgColumns(),
getProjectColumns(tableKeyMap[props.group][props.moduleType]),
...resourceColumns,
...swaggerUrlColumn,
...staticColumns,
],
API_SCENARIO: [
getOrgColumns(),
getProjectColumns(tableKeyMap[props.group][props.moduleType]),
...resourceColumns,
...staticColumns,
],
TEST_PLAN: [
getOrgColumns(),
getProjectColumns(tableKeyMap[props.group][props.moduleType]),
...resourceColumns,
...staticColumns,
],
},
organization: {
API_IMPORT: [
getProjectColumns(tableKeyMap[props.group][props.moduleType]),
...resourceColumns,
...swaggerUrlColumn,
...staticColumns,
],
API_SCENARIO: [
getProjectColumns(tableKeyMap[props.group][props.moduleType]),
...resourceColumns,
...staticColumns,
],
TEST_PLAN: [getProjectColumns(tableKeyMap[props.group][props.moduleType]), ...resourceColumns, ...staticColumns],
},
project: {
API_IMPORT: [...resourceColumns, ...swaggerUrlColumn, ...staticColumns],
API_SCENARIO: [...resourceColumns, ...staticColumns],
TEST_PLAN: [...resourceColumns, ...staticColumns],
},
};
const typeFilter = computed(() => {
if (showType.value === 'All') {
return [];
}
return [showType.value];
});
const hasJumpPermission = computed(() => hasAnyPermission(permissionsMap[props.group][props.moduleType].jump));
const { propsRes, propsEvent, loadList, setLoadListParams, setPagination, resetSelector, resetFilterParams } =
useTable(
loadRealMap.value[props.group].list,
{
tableKey: tableKeyMap[props.group][props.moduleType],
scroll: {
x: 1200,
},
showSetting: true,
selectable: hasOperationPermission.value,
heightUsed: 300,
showSelectorAll: true,
},
// eslint-disable-next-line no-return-assign
(item) => ({
...item,
nextTime: item.nextTime ? dayjs(item.nextTime).format('YYYY-MM-DD HH:mm:ss') : null,
})
);
function initData() {
const filterParams = {
...propsRes.value.filter,
};
setLoadListParams({
keyword: keyword.value,
scheduleTagType: props.moduleType,
filter: {
...(props.moduleType === 'TEST_PLAN'
? {
type: typeFilter.value,
...filterParams,
}
: { ...filterParams }),
},
});
loadList();
}
function changeShowType(val: string | number | boolean) {
showType.value = val as ReportShowType;
resetFilterParams();
resetSelector();
//
setPagination({
current: 1,
});
initData();
}
function searchList() {
resetSelector();
initData();
}
const tableBatchActions = {
baseAction: [
{
label: 'project.taskCenter.batchEnable',
eventTag: 'batchEnable',
anyPermission: permissionsMap[props.group][props.moduleType]?.edit,
},
{
label: 'project.taskCenter.batchDisable',
eventTag: 'batchDisable',
anyPermission: permissionsMap[props.group][props.moduleType]?.edit,
},
],
};
function delSchedule(record: any) {
openModal({
type: 'error',
title: t('project.taskCenter.delSchedule'),
content: t('project.taskCenter.delSchedule.tip'),
okText: t('common.confirmDelete'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'danger',
},
maskClosable: false,
onBeforeOk: async () => {
try {
await loadRealMap.value[props.group].delete(props.moduleType, record?.id as string);
Message.success(t('project.basicInfo.deleted'));
initData();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
async function handleBeforeEnableChange(record: TimingTaskCenterApiCaseItem) {
try {
await loadRealMap.value[props.group].enable(props.moduleType, record?.id as string);
Message.success(
t(record.enable ? 'project.taskCenter.disableScheduleSuccess' : 'project.taskCenter.enableScheduleSuccess')
);
return true;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
return false;
}
}
/**
* 更新运行规则
*/
async function changeRunRules(record: TimingTaskCenterApiCaseItem) {
try {
await loadRealMap.value[props.group].edit(props.moduleType, record.id, record.value);
Message.success(t('common.updateSuccess'));
} catch (error) {
console.log(error);
}
}
/**
* 跳转接口用例详情
*/
function showDetail(record: any) {
if (!hasJumpPermission.value) {
return;
}
if (props.moduleType === TaskCenterEnum.API_SCENARIO) {
openNewPage(RouteEnum.API_TEST_SCENARIO, {
orgId: record.organizationId,
pId: record.projectId,
id: record.resourceId,
});
}
if (props.moduleType === TaskCenterEnum.TEST_PLAN) {
openNewPage(RouteEnum.TEST_PLAN_INDEX_DETAIL, {
orgId: record.organizationId,
pId: record.projectId,
id: record.resourceId,
});
}
}
const batchParams = ref<BatchApiParams>({
selectIds: [],
selectAll: false,
excludeIds: [] as string[],
condition: {},
});
function batchEnableTask() {
openModal({
type: 'warning',
title: t('project.taskCenter.batchEnableTask', { num: batchParams.value.currentSelectCount }),
content: t('project.taskCenter.batchEnableTaskContent'),
okText: t('project.taskCenter.confirmEnable'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
const { selectIds, selectAll, excludeIds } = batchParams.value;
await loadRealMap.value[props.group].batchEnable({
selectIds: selectIds || [],
selectAll: !!selectAll,
scheduleTagType: props.moduleType,
excludeIds,
condition: {
keyword: keyword.value,
filter: {
...(props.moduleType === 'TEST_PLAN'
? {
type: typeFilter.value,
...propsRes.value.filter,
}
: { ...propsRes.value.filter }),
},
},
});
resetSelector();
Message.success(t('project.taskCenter.enableSuccess'));
initData();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
function batchDisableTask() {
openModal({
type: 'warning',
title: t('project.taskCenter.batchDisableTask', { num: batchParams.value.currentSelectCount }),
content: t('project.taskCenter.batchDisableTaskContent'),
okText: t('project.taskCenter.confirmDisable'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
const { selectIds, selectAll, excludeIds } = batchParams.value;
await loadRealMap.value[props.group].batchDisable({
selectIds: selectIds || [],
selectAll: !!selectAll,
excludeIds: excludeIds || [],
scheduleTagType: props.moduleType,
condition: {
keyword: keyword.value,
filter: {
...(props.moduleType === 'TEST_PLAN'
? { type: typeFilter.value, ...propsRes.value.filter }
: { ...propsRes.value.filter }),
},
},
});
resetSelector();
Message.success(t('project.taskCenter.disableSuccess'));
initData();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
batchParams.value = { ...params, selectIds: params?.selectedIds || [], condition: {} };
if (event.eventTag === 'batchEnable') {
batchEnableTask();
} else if (event.eventTag === 'batchDisable') {
batchDisableTask();
}
}
onBeforeMount(() => {
initData();
});
watch(
() => props.moduleType,
(val) => {
if (val) {
resetSelector();
initData();
}
}
);
await tableStore.initColumn(
tableKeyMap[props.group][props.moduleType],
groupColumnsMap[props.group][props.moduleType],
'drawer',
true
);
const tableRef = ref();
watch(
() => props.moduleType,
(val) => {
if (val) {
resetFilterParams();
tableRef.value.initColumn(groupColumnsMap[props.group][props.moduleType]);
}
}
);
</script>
<style scoped lang="less">
:deep(.param-input:not(.arco-input-focus, .arco-select-view-focus)) {
&:not(:hover) {
border-color: transparent !important;
.arco-input::placeholder {
@apply invisible;
}
.arco-select-view-icon {
@apply invisible;
}
.arco-select-view-value {
color: var(--color-text-1);
}
}
}
</style>

View File

@ -1,226 +0,0 @@
<template>
<div class="box">
<div class="left" :class="getStyleClass()">
<div
v-for="item of menuTab"
:key="item.value"
:class="`${activeTask === item.value ? 'active' : ''} item`"
@click="toggleTask(item.value)"
>
<div class="mr-2">
{{ item.label }}
</div>
<a-badge
v-if="getTextFunc(item.value) !== ''"
:class="`${item.value === activeTask ? 'active-badge' : ''} mt-[2px]`"
:max-count="99"
:text="getTextFunc(item.value)"
/>
</div>
</div>
<div class="right">
<a-tabs v-model:active-key="activeTab" class="no-content">
<a-tab-pane v-for="item of rightTabList" :key="item.value" :title="item.label" />
</a-tabs>
<a-divider margin="0" class="!mb-[16px]"></a-divider>
<!-- 接口用例列表-->
<ApiCase
v-if="
activeTask === 'real' && (activeTab === TaskCenterEnum.API_CASE || activeTab === TaskCenterEnum.API_SCENARIO)
"
:name="listName"
:module-type="activeTab"
:group="props.group"
/>
<!-- 测试计划列表-->
<TestPlan
v-if="activeTask === 'real' && activeTab === TaskCenterEnum.TEST_PLAN"
:name="listName"
:group="props.group"
/>
<ScheduledTask v-if="activeTask === 'timing'" :name="listName" :group="props.group" :module-type="activeTab" />
</div>
</div>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';
import ApiCase from './apiCase.vue';
import ScheduledTask from './scheduledTask.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 { TaskCenterEnum } from '@/enums/taskCenter';
import type { ExtractedKeys } from './utils';
const { t } = useI18n();
const props = defineProps<{
group: 'system' | 'organization' | 'project';
mode?: 'modal' | 'normal';
}>();
const route = useRoute();
const realTabList = ref([
{
value: TaskCenterEnum.API_CASE,
label: t('project.taskCenter.interfaceCase'),
},
{
value: TaskCenterEnum.API_SCENARIO,
label: t('project.taskCenter.apiScenario'),
},
{
value: TaskCenterEnum.TEST_PLAN,
label: t('project.taskCenter.testPlan'),
},
// TODO
// {
// value: TaskCenterEnum.UI_TEST,
// label: t('project.taskCenter.uiDefaultFile'),
// },
// {
// value: TaskCenterEnum.LOAD_TEST,
// label: t('project.taskCenter.performanceTest'),
// },
]);
const timingTabList = ref([
{
value: TaskCenterEnum.API_SCENARIO,
label: t('project.taskCenter.apiScenario'),
},
{
value: TaskCenterEnum.API_IMPORT,
label: t('project.taskCenter.apiImport'),
},
{
value: TaskCenterEnum.TEST_PLAN,
label: t('project.taskCenter.testPlan'),
},
]);
const activeTask = ref<string>((route.query.tab as string) || 'real');
const activeTab = ref<ExtractedKeys>((route.query.type as ExtractedKeys) || TaskCenterEnum.API_CASE);
const rightTabList = computed(() => {
return activeTask.value === 'real' ? realTabList.value : timingTabList.value;
});
function toggleTask(activeType: string) {
activeTask.value = activeType;
if (activeTask.value === 'real') {
activeTab.value = TaskCenterEnum.API_CASE;
} else {
activeTab.value = TaskCenterEnum.API_SCENARIO;
}
}
function getStyleClass() {
return props.mode === 'modal' ? ['p-0', 'pt-[16px]', 'pr-[16px]'] : ['p-[16px]'];
}
const listName = computed(() => {
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>
<style scoped lang="less">
.box {
display: flex;
height: 100%;
.left {
width: 252px;
height: 100%;
border-right: 1px solid var(--color-text-n8);
.item {
display: flex;
align-items: center;
padding: 0 20px;
height: 38px;
font-size: 14px;
border-radius: 4px;
cursor: pointer;
line-height: 38px;
&.active {
color: rgb(var(--primary-5));
background: rgb(var(--primary-1));
}
}
}
.right {
width: calc(100% - 300px);
flex-grow: 1; /* 自适应 */
height: 100%;
}
}
.no-content {
:deep(.arco-tabs-content) {
padding-top: 0;
}
}
</style>

View File

@ -1,516 +0,0 @@
<template>
<div class="px-[16px]">
<div class="mb-[16px] flex items-center justify-between">
<div class="flex items-center"></div>
<div class="items-right flex gap-[8px]">
<a-input-search
v-model:model-value="keyword"
:placeholder="t('system.organization.searchIndexPlaceholder')"
allow-clear
class="mx-[8px] w-[240px]"
@search="searchList"
@press-enter="searchList"
@clear="searchList"
></a-input-search>
</div>
</div>
<ms-base-table
v-bind="propsRes"
ref="tableRef"
:action-config="tableBatchActions"
:selectable="hasOperationPermission"
:expanded-keys="expandedKeys"
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<template #resourceNum="{ record }">
<div class="flex items-center">
<PlanExpandRow
v-model:expanded-keys="expandedKeys"
num-key="resourceNum"
:record="record"
:permission="permissionsMap[props.group].jump"
@action="showDetail(record)"
@expand="expandHandler(record)"
/>
</div>
</template>
<template #resourceName="{ record }">
<div
v-if="!record.integrated"
class="one-line-text max-w-[300px]"
:class="[hasJumpPermission ? 'text-[rgb(var(--primary-5))]' : '']"
@click="showDetail(record)"
>{{ record.resourceName }}
</div>
</template>
<template #status="{ record }">
<ExecutionStatus :status="record.status" />
</template>
<template #execStatus="{ record }">
<ExecStatus :status="record.execStatus" />
</template>
<template #[FilterSlotNameEnum.TEST_PLAN_REPORT_EXEC_STATUS]="{ filterContent }">
<ExecStatus :status="filterContent.value" />
</template>
<template #[FilterSlotNameEnum.TEST_PLAN_STATUS_FILTER]="{ filterContent }">
<ExecutionStatus :status="filterContent.value" />
</template>
<template #projectName="{ record }">
<a-tooltip :content="`${record.projectName}`" position="tl">
<div class="one-line-text">{{ characterLimit(record.projectName) }}</div>
</a-tooltip>
</template>
<template #organizationName="{ record }">
<a-tooltip :content="`${record.organizationName}`" position="tl">
<div class="one-line-text">{{ characterLimit(record.organizationName) }}</div>
</a-tooltip>
</template>
<template #triggerMode="{ record }">
<span>{{ t(ExecutionMethodsLabel[record.triggerMode as keyof typeof ExecutionMethodsLabel]) }}</span>
</template>
<template #operationTime="{ record }">
<span>{{ dayjs(record.operationTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #operation="{ record }">
<div v-if="record.historyDeleted">
<a-tooltip :content="t('project.executionHistory.cleared')">
<MsButton
class="!mr-0"
:disabled="record.historyDeleted || !hasAnyPermission(permissionsMap[props.group].report)"
@click="viewReport(record)"
>{{ t('project.taskCenter.viewReport') }}
</MsButton>
</a-tooltip>
</div>
<div v-else>
<MsButton
class="!mr-0"
:disabled="record.historyDeleted || !hasAnyPermission(permissionsMap[props.group].report)"
@click="viewReport(record)"
>{{ t('project.taskCenter.viewReport') }}
</MsButton>
</div>
<a-divider v-if="['RUNNING', 'RERUNNING'].includes(record.execStatus)" direction="vertical" />
<MsButton
v-if="
['RUNNING', 'RERUNNING'].includes(record.execStatus) && hasAnyPermission(permissionsMap[props.group].stop)
"
class="!mr-0"
@click="stop(record)"
>{{ t('project.taskCenter.stop') }}
</MsButton>
</template>
</ms-base-table>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import dayjs from 'dayjs';
import MsButton from '@/components/pure/ms-button/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 ExecStatus from '@/views/test-plan/report/component/execStatus.vue';
import ExecutionStatus from '@/views/test-plan/report/component/reportStatus.vue';
import PlanExpandRow from '@/views/test-plan/testPlan/components/planExpandRow.vue';
import {
batchStopRealOrgPlan,
batchStopRealProPlan,
batchStopRealSysPlan,
getRealOrgPlanList,
getRealProPlanList,
getRealSysPlanList,
stopRealOrgPlan,
stopRealProPlan,
stopRealSysPlan,
} from '@/api/modules/project-management/taskCenter';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useOpenNewPage from '@/hooks/useOpenNewPage';
import { useTableStore } from '@/store';
import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import { BatchApiParams } from '@/models/common';
import type { TestPlanTaskCenterItem } from '@/models/projectManagement/taskCenter';
import { ReportExecStatus } from '@/enums/apiEnum';
import { PlanReportStatus } from '@/enums/reportEnum';
import { RouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { ExecutionMethodsLabel, TaskCenterEnum } from '@/enums/taskCenter';
import { getOrgColumns, getProjectColumns, Group } from './utils';
const { openNewPage } = useOpenNewPage();
const tableStore = useTableStore();
const { openModal } = useModal();
const { t } = useI18n();
const props = defineProps<{
group: Group;
name: string;
}>();
const keyword = ref<string>('');
const permissionsMap: Record<Group, any> = {
organization: {
stop: ['ORGANIZATION_TASK_CENTER:READ+STOP', 'PROJECT_TEST_PLAN:READ+EXECUTE'],
jump: ['PROJECT_TEST_PLAN:READ'],
report: ['PROJECT_TEST_PLAN:READ+EXECUTE', 'PROJECT_TEST_PLAN_REPORT:READ'],
},
system: {
stop: ['SYSTEM_TASK_CENTER:READ+STOP', 'PROJECT_TEST_PLAN:READ+EXECUTE'],
jump: ['PROJECT_TEST_PLAN:READ'],
report: ['PROJECT_TEST_PLAN:READ+EXECUTE', 'PROJECT_TEST_PLAN_REPORT:READ'],
},
project: {
stop: ['PROJECT_TEST_PLAN:READ+EXECUTE'],
jump: ['PROJECT_TEST_PLAN:READ'],
report: ['PROJECT_TEST_PLAN:READ+EXECUTE', 'PROJECT_TEST_PLAN_REPORT:READ'],
},
};
const loadRealMap = ref({
system: {
list: getRealSysPlanList,
stop: stopRealSysPlan,
batchStop: batchStopRealSysPlan,
},
organization: {
list: getRealOrgPlanList,
stop: stopRealOrgPlan,
batchStop: batchStopRealOrgPlan,
},
project: {
list: getRealProPlanList,
stop: stopRealProPlan,
batchStop: batchStopRealProPlan,
},
});
const hasJumpPermission = computed(() => hasAnyPermission(permissionsMap[props.group].jump));
const hasOperationPermission = computed(() => hasAnyPermission(permissionsMap[props.group].stop));
const statusResultOptions = computed(() => {
return Object.keys(PlanReportStatus).map((key) => {
return {
value: key,
label: PlanReportStatus[key].statusText,
};
});
});
const ExecStatusList = computed(() => {
return Object.values(ReportExecStatus).map((e) => {
return {
value: e,
key: e,
};
});
});
const triggerModeList = [
{
value: 'SCHEDULE',
label: t('project.taskCenter.scheduledTask'),
},
{
value: 'MANUAL',
label: t('project.taskCenter.manualExecution'),
},
{
value: 'API',
label: t('project.taskCenter.interfaceCall'),
},
{
value: 'BATCH',
label: t('project.taskCenter.batchExecution'),
},
];
const staticColumns: MsTableColumn = [
{
title: 'project.taskCenter.resourceID',
dataIndex: 'resourceNum',
slotName: 'resourceNum',
width: 200,
sortIndex: 1,
fixed: 'left',
showTooltip: true,
showInTable: true,
showDrag: false,
columnSelectorDisabled: true,
},
{
title: 'project.taskCenter.resourceName',
slotName: 'resourceName',
dataIndex: 'resourceName',
width: 300,
showDrag: false,
showTooltip: true,
showInTable: true,
columnSelectorDisabled: true,
},
{
title: 'project.taskCenter.executionResult',
dataIndex: 'status',
slotName: 'status',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
filterConfig: {
options: statusResultOptions.value,
filterSlotName: FilterSlotNameEnum.TEST_PLAN_STATUS_FILTER,
},
showInTable: true,
width: 200,
showDrag: true,
},
{
title: 'project.taskCenter.status',
dataIndex: 'execStatus',
slotName: 'execStatus',
filterConfig: {
options: ExecStatusList.value,
filterSlotName: FilterSlotNameEnum.TEST_PLAN_REPORT_EXEC_STATUS,
},
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showInTable: true,
width: 200,
showDrag: true,
},
{
title: 'project.taskCenter.executionMode',
dataIndex: 'triggerMode',
slotName: 'triggerMode',
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
filterConfig: {
options: triggerModeList,
},
showInTable: true,
width: 150,
showDrag: true,
},
{
title: 'project.taskCenter.operator',
slotName: 'operationName',
dataIndex: 'operationName',
showInTable: true,
showDrag: true,
showTooltip: true,
width: 200,
},
{
title: 'project.taskCenter.operating',
dataIndex: 'operationTime',
slotName: 'operationTime',
width: 180,
showDrag: true,
},
{
title: 'common.operation',
slotName: 'operation',
dataIndex: 'operation',
fixed: 'right',
width: hasOperationPermission.value ? 180 : 100,
},
];
const tableKeysMap: Record<string, any> = {
system: TableKeyEnum.TASK_PLAN_SYSTEM,
organization: TableKeyEnum.TASK_PLAN_ORGANIZATION,
project: TableKeyEnum.TASK_PLAN_PROJECT,
};
const groupColumnsMap: Record<string, any> = {
system: [getOrgColumns(), getProjectColumns(tableKeysMap[props.group]), ...staticColumns],
organization: [getProjectColumns(tableKeysMap[props.group]), ...staticColumns],
project: staticColumns,
};
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, resetFilterParams } = useTable(
loadRealMap.value[props.group].list,
{
tableKey: tableKeysMap[props.group],
scroll: {
x: 1400,
},
showSetting: true,
selectable: hasOperationPermission.value,
heightUsed: 330,
showSelectAll: true,
rowSelectionDisabledConfig: {
parentKey: 'parent',
checkStrictly: true,
},
}
);
function initData() {
setLoadListParams({
moduleType: TaskCenterEnum.TEST_PLAN,
keyword: keyword.value,
filter: {
...propsRes.value.filter,
},
});
loadList();
}
const tableBatchActions = {
baseAction: [
{
label: 'project.taskCenter.batchStop',
eventTag: 'batchStop',
anyPermission: permissionsMap[props.group].stop,
},
],
};
const batchParams = ref<BatchApiParams>({
selectIds: [],
selectAll: false,
excludeIds: [] as string[],
condition: {},
});
function batchStopRealTask() {
openModal({
type: 'warning',
title: t('project.taskCenter.batchStopTask', { num: batchParams.value.currentSelectCount }),
content: t('project.taskCenter.stopTaskContent'),
okText: t('project.taskCenter.confirmStop'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
const { selectIds, selectAll, excludeIds } = batchParams.value;
await loadRealMap.value[props.group].batchStop({
selectIds: selectIds || [],
selectAll,
excludeIds: excludeIds || [],
condition: {
keyword: keyword.value,
filter: {
...propsRes.value.filter,
},
},
});
resetSelector();
Message.success(t('project.taskCenter.stopSuccess'));
initData();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
batchParams.value = { ...params, selectIds: params?.selectedIds || [], condition: params?.condition || {} };
if (event.eventTag === 'batchStop') {
batchStopRealTask();
}
}
function viewReport(record: any) {
openNewPage(RouteEnum.TEST_PLAN_REPORT_DETAIL, {
orgId: record.organizationId,
pId: record.projectId,
id: record.id,
type: record.integrated ? 'GROUP' : 'TEST_PLAN',
});
}
function showDetail(record: any) {
if (!hasJumpPermission.value) {
return;
}
openNewPage(RouteEnum.TEST_PLAN_INDEX_DETAIL, {
orgId: record.organizationId,
pId: record.projectId,
id: record.resourceId,
});
}
function stop(record: any) {
openModal({
type: 'warning',
title: t('project.taskCenter.stopTask', { name: characterLimit(record.resourceName) }),
content: t('project.taskCenter.stopTaskContent'),
okText: t('project.taskCenter.confirmStop'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
await loadRealMap.value[props.group].stop(record.id);
resetSelector();
Message.success(t('project.taskCenter.stopSuccess'));
initData();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
const expandedKeys = ref<string[]>([]);
function expandHandler(record: TestPlanTaskCenterItem) {
if (expandedKeys.value.includes(record.id)) {
expandedKeys.value = expandedKeys.value.filter((key) => key !== record.id);
} else {
expandedKeys.value = [...expandedKeys.value, record.id];
}
}
function searchList() {
resetSelector();
initData();
}
onBeforeMount(() => {
initData();
});
watch(
() => props.group,
(val) => {
if (val) {
resetSelector();
resetFilterParams();
initData();
}
}
);
await tableStore.initColumn(tableKeysMap[props.group], groupColumnsMap[props.group], 'drawer', true);
</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;
}
</style>

View File

@ -1,205 +0,0 @@
import type { MsTableColumnData } from '@/components/pure/ms-table/type';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import useLicenseStore from '@/store/modules/setting/license';
import { hasAnyPermission } from '@/utils/permission';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum } from '@/enums/tableFilterEnum';
import type { ResourceTypeMapKey } from '@/enums/taskCenter';
import { TaskCenterEnum } from '@/enums/taskCenter';
const appStore = useAppStore();
const licenseStore = useLicenseStore();
const { t } = useI18n();
export const TaskStatus: Record<ResourceTypeMapKey, Record<string, { icon: string; label: string; color?: string }>> = {
[TaskCenterEnum.API_CASE]: {
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: t('common.success'),
},
ERROR: {
icon: 'icon-icon_close_colorful',
label: t('common.fail'),
},
FAKE_ERROR: {
icon: 'icon-icon_warning_colorful',
label: t('common.fakeError'),
},
},
[TaskCenterEnum.API_SCENARIO]: {
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: t('common.success'),
},
ERROR: {
icon: 'icon-icon_close_colorful',
label: t('common.fail'),
},
FAKE_ERROR: {
icon: 'icon-icon_warning_colorful',
label: t('common.fakeError'),
},
},
[TaskCenterEnum.LOAD_TEST]: {
STARTING: {
icon: 'icon-icon_restarting',
label: t('project.taskCenter.starting'),
color: '!text-[rgb(var(--link-6))]',
},
RUNNING: {
icon: 'icon-icon_testing',
label: t('common.running'),
color: '!text-[rgb(var(--link-6))]',
},
ERROR: {
icon: 'icon-icon_close_colorful',
label: t('common.fail'),
},
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: t('common.success'),
},
COMPLETED: {
icon: 'icon-icon_succeed_colorful',
label: t('project.taskCenter.complete'),
},
STOPPED: {
icon: 'icon-icon_block_filled',
label: t('project.taskCenter.stop'),
color: 'var(--color-text-input-border)',
},
},
[TaskCenterEnum.UI_TEST]: {
PENDING: {
icon: 'icon-icon_wait',
label: t('common.unExecute'),
color: '!text-[rgb(var(--link-6))]',
},
RUNNING: {
icon: 'icon-icon_testing',
label: t('common.running'),
color: '!text-[rgb(var(--link-6))]',
},
// RERUNNING: {
// icon: 'icon-icon_testing',
// label: t('project.taskCenter.rerun',
// color: '!text-[rgb(var(--link-6))]',
// },
ERROR: {
icon: 'icon-icon_close_colorful',
label: t('common.fail'),
},
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: t('common.success'),
},
STOPPED: {
icon: 'icon-icon_block_filled',
label: t('project.taskCenter.stop'),
color: 'var(--color-text-input-border)',
},
},
[TaskCenterEnum.TEST_PLAN]: {
RUNNING: {
icon: 'icon-icon_testing',
label: t('common.unExecute'),
color: '!text-[rgb(var(--link-6))]',
},
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: t('common.success'),
},
STARTING: {
icon: 'icon-icon_restarting',
label: t('project.taskCenter.starting'),
color: '!text-[rgb(var(--link-6))]',
},
},
};
export type Group = 'system' | 'organization' | 'project';
export type ExtractedKeys = Extract<
ResourceTypeMapKey,
TaskCenterEnum.API_CASE | TaskCenterEnum.API_SCENARIO | TaskCenterEnum.TEST_PLAN
>;
export const resourceTypeMap: Record<ResourceTypeMapKey, Record<string, any>> = {
[TaskCenterEnum.API_CASE]: {
value: TaskCenterEnum.API_CASE,
label: t('project.taskCenter.interfaceCase'),
},
[TaskCenterEnum.API_SCENARIO]: {
value: TaskCenterEnum.API_SCENARIO,
label: t('project.taskCenter.apiScenario'),
},
[TaskCenterEnum.UI_TEST]: {
value: TaskCenterEnum.UI_TEST,
label: t('project.taskCenter.uiDefaultFile'),
},
[TaskCenterEnum.LOAD_TEST]: {
value: TaskCenterEnum.LOAD_TEST,
label: t('project.taskCenter.performanceTest'),
},
[TaskCenterEnum.TEST_PLAN]: {
value: TaskCenterEnum.TEST_PLAN,
label: t('project.taskCenter.testPlan'),
},
};
export function getOrgColumns(): MsTableColumnData {
const config: MsTableColumnData = {
title: 'project.belongOrganization',
dataIndex: 'organizationIds',
slotName: 'organizationName',
showDrag: true,
width: 200,
showInTable: true,
};
if (licenseStore.hasLicense()) {
config.filterConfig = {
mode: 'remote',
remoteMethod: FilterRemoteMethodsEnum.SYSTEM_ORGANIZATION_LIST,
placeholderText: t('project.taskCenter.filterOrgPlaceholderText'),
};
}
return config;
}
export function getProjectColumns(key: TableKeyEnum): MsTableColumnData {
const systemKey = [
TableKeyEnum.TASK_API_CASE_SYSTEM,
TableKeyEnum.TASK_SCHEDULE_TASK_API_IMPORT_SYSTEM,
TableKeyEnum.TASK_SCHEDULE_TASK_API_SCENARIO_SYSTEM,
];
const filterKeyPermission = systemKey.includes(key)
? hasAnyPermission(['SYSTEM_ORGANIZATION_PROJECT:READ'])
: hasAnyPermission(['ORGANIZATION_PROJECT:READ']);
const remoteMethod = systemKey.includes(key)
? FilterRemoteMethodsEnum.SYSTEM_PROJECT_LIST
: FilterRemoteMethodsEnum.SYSTEM_ORGANIZATION_PROJECT;
const config: MsTableColumnData = {
title: 'project.belongProject',
dataIndex: 'projectIds',
slotName: 'projectName',
showDrag: true,
width: 200,
showInTable: true,
};
if (filterKeyPermission && remoteMethod) {
config.filterConfig = {
mode: 'remote',
loadOptionParams: {
organizationId: appStore.currentOrgId,
},
remoteMethod,
placeholderText: t('project.taskCenter.filterProPlaceholderText'),
};
}
return config;
}
export default {};

View File

@ -1,44 +0,0 @@
<template>
<MsCard simple no-content-padding>
<TaskCenter group="project" />
</MsCard>
</template>
<script setup lang="ts">
import MsCard from '@/components/pure/ms-card/index.vue';
import TaskCenter from './component/taskCom.vue';
</script>
<style scoped lang="less">
.box {
display: flex;
height: 100%;
.left {
padding: 24px;
width: 252px;
height: 100%;
border-right: 1px solid var(--color-text-n8);
.item {
padding: 0 20px;
height: 38px;
font-size: 14px;
line-height: 38px;
border-radius: 4px;
cursor: pointer;
&.active {
background: rgb(var(--primary-1));
}
}
}
.right {
width: calc(100% - 300px);
flex-grow: 1; /* 自适应 */
height: 100%;
}
}
.no-content {
:deep(.arco-tabs-content) {
padding-top: 0;
}
}
</style>

View File

@ -1,64 +0,0 @@
export default {
'project.taskCenter.interfaceCase': 'Interface use cases',
'project.taskCenter.apiScenario': 'Interface scenario',
'project.taskCenter.uiDefaultFile': 'UI testing',
'project.taskCenter.performanceTest': 'Performance test',
'project.taskCenter.testPlan': 'Test plan',
'project.taskCenter.resourceID': 'Resource ID',
'project.taskCenter.resourceName': 'resourceName',
'project.taskCenter.executionResult': 'Execution result',
'project.taskCenter.status': 'Execution status',
'project.taskCenter.executionMode': 'Execution mode',
'project.taskCenter.resourcePool': 'Resource pool',
'project.taskCenter.operator': 'operator',
'project.taskCenter.operating': 'Operating time',
'project.taskCenter.batchStop': 'Batch stop',
'project.taskCenter.batchExecution': 'Batch execution',
'project.taskCenter.stop': 'stop',
'project.taskCenter.execution': 'execution',
'project.taskCenter.viewReport': 'Execution Result',
'project.taskCenter.batchStopTask': 'Are you sure to stop {num} tasks?',
'project.taskCenter.stopTask': 'Are you sure to stop {name} task?',
'project.taskCenter.stopTaskContent':
'Stopping will affect report generation, and the completed report cannot be stopped.',
'project.taskCenter.confirmStop': 'Confirm stop',
'project.taskCenter.stopSuccess': 'Stop successfully',
'project.taskCenter.testResource': 'Test resource',
'project.taskCenter.apiImport': 'API import',
'project.taskCenter.resourceClassification': 'Resource Classification',
'project.taskCenter.operationRule': 'Operation rule',
'project.taskCenter.nextExecutionTime': 'Next execution time',
'project.taskCenter.rerun': 'Rerun',
'project.taskCenter.starting': 'Be starting',
'project.taskCenter.complete': 'complete',
'project.taskCenter.scheduledTask': 'Scheduled task',
'project.taskCenter.manualExecution': 'Manual execution',
'project.taskCenter.interfaceCall': 'Interface call',
'project.taskCenter.realTimeTask': 'Real Time Task',
'project.taskCenter.createTask': 'create',
'project.taskCenter.apiCaseList': '{type} list',
'project.taskCenter.delSchedule': 'Are you sure to delete the scheduled task?',
'project.taskCenter.delScheduleSuccess': 'Delete scheduled task successfully',
'project.taskCenter.delSchedule.tip':
'Deleting the scheduled task will cause the task to stop. Do you want to continue?',
'project.taskCenter.enableScheduleSuccess': 'Enable successfully',
'project.taskCenter.disableScheduleSuccess': 'Disable successfully',
'project.belongProject': 'Project',
'project.belongOrganization': 'Organization',
'project.taskCenter.batchEnable': 'Batch enable',
'project.taskCenter.batchDisable': 'Batch disable',
'project.taskCenter.batchEnableTask': 'Are you sure to enable {num} tasks?',
'project.taskCenter.batchDisableTask': 'Are you sure to disable {num} tasks?',
'project.taskCenter.batchEnableTaskContent': 'Enabling will execute the task',
'project.taskCenter.batchDisableTaskContent': 'Closing will affect the execution of the task',
'project.taskCenter.swaggerUrl': 'Swagger URL',
'project.taskCenter.confirmEnable': 'Confirm enable',
'project.taskCenter.confirmDisable': 'Confirm disable',
'project.taskCenter.enableSuccess': 'Enable successfully',
'project.taskCenter.disableSuccess': 'Disable successfully',
'project.taskCenter.filterPlaceholderText': 'Please select a project',
'project.taskCenter.filterOrgPlaceholderText': 'Please select an organization',
'project.executionHistory.cleared': 'Execution result has been cleared',
'project.taskCenter.plan': 'Plan',
'project.taskCenter.planGroup': 'Plan group',
};

View File

@ -1,62 +0,0 @@
export default {
'project.taskCenter.interfaceCase': '接口用例',
'project.taskCenter.apiScenario': '接口场景',
'project.taskCenter.uiDefaultFile': 'UI 测试',
'project.taskCenter.performanceTest': '性能测试',
'project.taskCenter.testPlan': '测试计划',
'project.taskCenter.resourceID': '资源 ID',
'project.taskCenter.resourceName': '资源名称',
'project.taskCenter.executionResult': '执行结果',
'project.taskCenter.status': '执行状态',
'project.taskCenter.executionMode': '执行方式',
'project.taskCenter.resourcePool': '资源池',
'project.taskCenter.operator': '操作人',
'project.taskCenter.operating': '操作时间',
'project.taskCenter.batchStop': '批量停止',
'project.taskCenter.batchExecution': '批量执行',
'project.taskCenter.stop': '停止',
'project.taskCenter.execution': '执行',
'project.taskCenter.viewReport': '执行结果',
'project.taskCenter.batchStopTask': '确定停止 {num} 个任务吗?',
'project.taskCenter.stopTask': '确定停止 {name} 吗?',
'project.taskCenter.stopTaskContent': '停止后会影响报告的生成,执行完成的报告不可以停止',
'project.taskCenter.confirmStop': '确认停止',
'project.taskCenter.stopSuccess': '停止成功',
'project.taskCenter.testResource': '测试资源',
'project.taskCenter.apiImport': 'API 导入',
'project.taskCenter.resourceClassification': '资源分类',
'project.taskCenter.operationRule': '运行规则',
'project.taskCenter.nextExecutionTime': '下次执行时间',
'project.taskCenter.rerun': '重跑中',
'project.taskCenter.starting': '启动中',
'project.taskCenter.complete': '完成',
'project.taskCenter.scheduledTask': '定时任务',
'project.taskCenter.manualExecution': '手动执行',
'project.taskCenter.interfaceCall': '接口调用',
'project.taskCenter.realTimeTask': '实时任务',
'project.taskCenter.createTask': '创建定时任务',
'project.taskCenter.apiCaseList': '{type}列表',
'project.taskCenter.delSchedule': '确定删除定时任务吗?',
'project.taskCenter.delScheduleSuccess': '删除定时任务成功',
'project.taskCenter.delSchedule.tip': '删除定时任务会导致任务停止,是否继续?',
'project.taskCenter.enableScheduleSuccess': '已开启',
'project.taskCenter.disableScheduleSuccess': '已关闭',
'project.belongProject': '所属项目',
'project.belongOrganization': '所属组织',
'project.taskCenter.batchEnable': '批量开启',
'project.taskCenter.batchDisable': '批量关闭',
'project.taskCenter.batchEnableTask': '确定开启 {num} 个任务吗?',
'project.taskCenter.batchDisableTask': '确定关闭 {num} 个任务吗?',
'project.taskCenter.batchEnableTaskContent': '开启会执行任务',
'project.taskCenter.batchDisableTaskContent': '关闭后会影响任务的执行',
'project.taskCenter.swaggerUrl': 'Swagger URL',
'project.taskCenter.confirmEnable': '确认开启',
'project.taskCenter.confirmDisable': '确认关闭',
'project.taskCenter.enableSuccess': '开启成功',
'project.taskCenter.disableSuccess': '关闭成功',
'project.taskCenter.filterProPlaceholderText': '请选择所属项目',
'project.taskCenter.filterOrgPlaceholderText': '请选择所属组织',
'project.executionHistory.cleared': '执行结果被清理',
'project.taskCenter.plan': '计划',
'project.taskCenter.planGroup': '计划组',
};

View File

@ -1,12 +1,12 @@
<template>
<MsCard simple no-content-padding>
<TaskCenter group="organization" />
<TaskCenter type="org" />
</MsCard>
</template>
<script setup lang="ts">
import MsCard from '@/components/pure/ms-card/index.vue';
import TaskCenter from '@/views/project-management/taskCenter/component/taskCom.vue';
import TaskCenter from '@/views/taskCenter/index.vue';
</script>
<style scoped></style>

View File

@ -1,12 +1,12 @@
<template>
<MsCard simple no-content-padding>
<TaskCenter group="system" />
<TaskCenter type="system" />
</MsCard>
</template>
<script setup lang="ts">
import MsCard from '@/components/pure/ms-card/index.vue';
import TaskCenter from '@/views/project-management/taskCenter/component/taskCom.vue';
import TaskCenter from '@/views/taskCenter/index.vue';
</script>
<style scoped lang="less">

View File

@ -0,0 +1,232 @@
<template>
<MsDrawer v-model:visible="visible" :title="title" :width="800" :footer="false">
<ms-base-table v-bind="propsRes" ref="tableRef" v-on="propsEvent" @filter-change="filterChange">
<template #name="{ record, rowIndex }">
<a-button type="text" class="max-w-full justify-start px-0" @click="showReportDetail(record.id, rowIndex)">
<div class="one-line-text">
{{ record.num }}
</div>
</a-button>
</template>
<!-- 报告类型 -->
<template #integrated="{ record }">
<MsTag theme="light" :type="record.integrated ? 'primary' : undefined">
{{ record.integrated ? t('report.collection') : t('report.independent') }}
</MsTag>
</template>
<template #status="{ record }">
<ExecutionStatus
:module-type="props.moduleType"
:status="record.status"
:script-identifier="props.moduleType === ReportEnum.API_SCENARIO_REPORT ? record.scriptIdentifier : null"
/>
</template>
<template #[FilterSlotNameEnum.API_TEST_CASE_API_REPORT_STATUS]="{ filterContent }">
<ExecutionStatus :module-type="props.moduleType" :status="filterContent.value" />
</template>
<template #execStatus="{ record }">
<ExecStatus :status="record.execStatus" />
</template>
<template #[FilterSlotNameEnum.API_TEST_CASE_API_REPORT_EXECUTE_RESULT]="{ filterContent }">
<ExecStatus :status="filterContent.value" />
</template>
<template #[FilterSlotNameEnum.API_TEST_REPORT_TYPE]="{ filterContent }">
<MsTag theme="light" :type="filterContent.value ? 'primary' : undefined">
{{ filterContent.value ? t('report.collection') : t('report.independent') }}
</MsTag>
</template>
<template #triggerMode="{ record }">
<span>{{ t(TriggerModeLabel[record.triggerMode as keyof typeof TriggerModeLabel]) }}</span>
</template>
<template #operationTime="{ record }">
<span>{{ dayjs(record.operationTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</ms-base-table>
</MsDrawer>
</template>
<script setup lang="ts">
import dayjs from 'dayjs';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
import ExecStatus from '@/views/test-plan/report/component/execStatus.vue';
import { useI18n } from '@/hooks/useI18n';
import useTableStore from '@/hooks/useTableStore';
import useAppStore from '@/store/modules/app';
import { ReportExecStatus } from '@/enums/apiEnum';
import { ReportEnum, ReportStatus, TriggerModeLabel } from '@/enums/reportEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
const props = defineProps<{
type: 'case' | 'scenario';
moduleType: keyof typeof ReportEnum;
}>();
const { t } = useI18n();
const tableStore = useTableStore();
const appStore = useAppStore();
const visible = defineModel<boolean>('visible', { required: true });
const title = computed(() =>
props.type === 'case' ? t('ms.taskCenter.batchCaseTask') : t('ms.taskCenter.batchScenarioTask')
);
const keyword = ref<string>('');
type ReportShowType = 'All' | 'INDEPENDENT' | 'INTEGRATED';
const showType = ref<ReportShowType>('All');
const typeFilter = computed(() => {
if (showType.value === 'All') {
return [];
}
return showType.value === 'INDEPENDENT' ? [false] : [true];
});
const statusList = computed(() => {
return Object.keys(ReportStatus).map((key) => {
return {
value: key,
label: t(ReportStatus[key].label),
};
});
});
const ExecStatusList = computed(() => {
return Object.values(ReportExecStatus).map((e) => {
return {
value: e,
key: e,
};
});
});
const columns: MsTableColumn = [
{
title: 'report.name',
dataIndex: 'name',
slotName: 'name',
width: 300,
showTooltip: true,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
ellipsis: true,
},
{
title: 'report.type',
slotName: 'integrated',
dataIndex: 'integrated',
width: 150,
},
{
title: 'report.result',
dataIndex: 'status',
slotName: 'status',
filterConfig: {
options: statusList.value,
filterSlotName: FilterSlotNameEnum.API_TEST_CASE_API_REPORT_STATUS,
},
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showInTable: true,
width: 200,
},
{
title: 'report.status',
dataIndex: 'execStatus',
slotName: 'execStatus',
filterConfig: {
options: ExecStatusList.value,
filterSlotName: FilterSlotNameEnum.API_TEST_CASE_API_REPORT_EXECUTE_RESULT,
},
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
showInTable: true,
width: 200,
},
{
title: 'report.trigger.mode',
dataIndex: 'triggerMode',
slotName: 'triggerMode',
showInTable: true,
width: 150,
},
{
title: 'report.operator',
slotName: 'createUserName',
dataIndex: 'createUserName',
showInTable: true,
width: 300,
showTooltip: true,
},
{
title: 'report.operating',
dataIndex: 'startTime',
slotName: 'startTime',
width: 180,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
},
];
await tableStore.initColumn(TableKeyEnum.API_TEST_REPORT, columns, 'drawer');
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(
() => Promise.resolve({ list: [], total: 0 }),
{
tableKey: TableKeyEnum.API_TEST_REPORT,
scroll: {
x: '100%',
},
heightUsed: 256,
paginationSize: 'mini',
},
(item) => ({
...item,
startTime: dayjs(item.startTime).format('YYYY-MM-DD HH:mm:ss'),
})
);
function initData(dataIndex?: string, value?: string[] | (string | number | boolean)[] | undefined) {
const filterParams = {
...propsRes.value.filter,
};
if (dataIndex && value) {
filterParams[dataIndex] = value;
}
setLoadListParams({
keyword: keyword.value,
projectId: appStore.currentProjectId,
moduleType: props.moduleType,
filter: {
integrated: typeFilter.value,
...filterParams,
},
});
loadList();
}
function filterChange(dataIndex: string, value: string[] | (string | number | boolean)[] | undefined) {
initData(dataIndex, value);
}
function showReportDetail(id: string, rowIndex: number) {
console.log(id, rowIndex);
}
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,135 @@
<template>
<MsDrawer v-model:visible="visible" :width="800" :footer="false">
<template #title>
<div class="flex items-center gap-[8px]">
<a-tag :color="executeResultMap[detail.executeResult]?.color">
{{ t(executeResultMap[detail.executeResult]?.label) }}
</a-tag>
<div>{{ detail.name }}</div>
</div>
<div class="flex flex-1 justify-end">
<MsButton type="icon" status="secondary" class="!rounded-[var(--border-radius-small)]" @click="refresh">
<MsIcon type="icon-icon_reset_outlined" class="mr-[8px]" size="14" />
{{ t('common.refresh') }}
</MsButton>
</div>
</template>
<MsDescription :descriptions="detail.description" :column="3" :line-gap="8" one-line-value>
<template #value="{ item }">
<execStatus v-if="item.key === 'status'" :status="item.value as ReportExecStatus" size="small" />
<a-tooltip
v-else
:content="`${item.value}`"
:disabled="item.value === undefined || item.value === null || item.value?.toString() === ''"
:position="item.tooltipPosition ?? 'tl'"
>
<div class="w-[fit-content]">
{{ item.value === undefined || item.value === null || item.value?.toString() === '' ? '-' : item.value }}
</div>
</a-tooltip>
</template>
</MsDescription>
<div class="mt-[8px]">
<StepDetailContent
mode="tiled"
show-type="CASE"
:step-item="detail.scenarioDetail"
:console="detail.console"
:is-definition="true"
:get-report-step-detail="props.getReportStepDetail"
:report-id="detail.scenarioDetail?.reportId"
/>
</div>
</MsDrawer>
</template>
<script setup lang="ts">
import dayjs from 'dayjs';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsDescription, { Description } from '@/components/pure/ms-description/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import execStatus from './execStatus.vue';
import StepDetailContent from '@/views/api-test/components/requestComposition/response/result/index.vue';
import { useI18n } from '@/hooks/useI18n';
import { ReportExecStatus } from '@/enums/apiEnum';
import { executeResultMap } from './utils';
const props = defineProps<{
id: string;
getReportStepDetail?: (...args: any) => Promise<any>; //
}>();
const { t } = useI18n();
const visible = defineModel<boolean>('visible', { required: true });
const detail = ref<any>({ description: [] });
watch(
() => props.id,
async () => {
if (props.id) {
detail.value = {
id: props.id,
name: '测试用例名称',
executeResult: 'SUCCESS',
description: [
{
label: t('ms.taskCenter.executeStatus'),
key: 'status',
value: 'COMPLETED',
},
{
label: t('ms.taskCenter.operationUser'),
value: 'admin',
},
{
label: t('ms.taskCenter.taskCreateTime'),
value: dayjs(1626844800000).format('YYYY-MM-DD HH:mm:ss'),
},
{
label: t('ms.taskCenter.taskResource'),
value: '测试计划',
},
{
label: t('ms.taskCenter.threadID'),
value: '1231231231',
},
{
label: t('ms.taskCenter.taskStartTime'),
value: dayjs(1626844800000).format('YYYY-MM-DD HH:mm:ss'),
},
{
label: t('ms.taskCenter.executeEnvInfo'),
value: 'DEV 资源池1 10.11.1.1',
class: '!w-[calc(100%/3*2)]',
},
{
label: t('ms.taskCenter.taskEndTime'),
value: dayjs(1626844800000).format('YYYY-MM-DD HH:mm:ss'),
},
] as Description[],
};
}
},
{ immediate: true }
);
function refresh() {
console.log('refresh');
}
</script>
<style lang="less" scoped>
:deep(.ms-description-item) {
@apply items-center;
margin-bottom: 8px;
font-size: 12px;
line-height: 16px;
}
</style>

View File

@ -0,0 +1,419 @@
<template>
<div class="my-[16px] flex items-center justify-end gap-[12px]">
<a-input-search
v-model:model-value="keyword"
:placeholder="t('ms.taskCenter.search')"
class="w-[240px]"
allow-clear
@search="searchTask"
@press-enter="searchTask"
@clear="searchTask"
/>
<MsCascader
v-model:model-value="resourcePool"
mode="native"
:options="resourcePoolOptions"
:placeholder="t('common.pleaseSelect')"
option-size="small"
label-key="value"
value-key="key"
class="w-[240px]"
:prefix="t('ms.taskCenter.resourcePool')"
@change="searchTask"
>
</MsCascader>
<MsTag no-margin size="large" :tooltip-disabled="true" class="cursor-pointer" theme="outline" @click="searchTask">
<MsIcon class="text-[16px] text-[var(color-text-4)]" :size="32" type="icon-icon_reset_outlined" />
</MsTag>
</div>
<ms-base-table
v-bind="propsRes"
:action-config="tableBatchActions"
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<template #executeStatus="{ record }">
<execStatus :status="record.executeStatus" />
</template>
<template #[FilterSlotNameEnum.GLOBAL_TASK_CENTER_EXEC_STATUS]="{ filterContent }">
<execStatus :status="filterContent.value" />
</template>
<template #executeResult="{ record }">
<executionStatus :status="record.executeResult" />
</template>
<template #[FilterSlotNameEnum.GLOBAL_TASK_CENTER_EXEC_RESULT]="{ filterContent }">
<executionStatus :status="filterContent.value" />
</template>
<template #node="{ record }">
<div>{{ record.node }}</div>
<a-tooltip :content="t('ms.taskCenter.nodeErrorTip')">
<icon-exclamation-circle-fill class="!text-[rgb(var(--warning-6))]" :size="18" />
</a-tooltip>
</template>
<template #action="{ record }">
<MsButton v-permission="['SYSTEM_USER:READ+DELETE']" @click="stopTask(record)">
{{ t('common.stop') }}
</MsButton>
<MsButton v-permission="['SYSTEM_USER:READ+DELETE']" @click="deleteTask(record)">
{{ t('common.delete') }}
</MsButton>
<MsButton v-permission="['SYSTEM_USER:READ+DELETE']" @click="rerunTask(record)">
{{ t('ms.taskCenter.rerun') }}
</MsButton>
<MsButton v-permission="['SYSTEM_USER:READ+DELETE']" @click="checkExecuteResult(record)">
{{ t('ms.taskCenter.executeResult') }}
</MsButton>
</template>
</ms-base-table>
<caseExecuteResultDrawer :id="executeResultId" v-model:visible="caseExecuteResultDrawerVisible" />
<scenarioExecuteResultDrawer :id="executeResultId" v-model:visible="scenarioExecuteResultDrawerVisible" />
</template>
<script setup lang="ts">
import { Message } from '@arco-design/web-vue';
import dayjs from 'dayjs';
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 MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import MsCascader from '@/components/business/ms-cascader/index.vue';
import caseExecuteResultDrawer from './caseExecuteResultDrawer.vue';
import execStatus from './execStatus.vue';
import executionStatus from './executionStatus.vue';
import scenarioExecuteResultDrawer from './scenarioExecuteResultDrawer.vue';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useTableStore from '@/hooks/useTableStore';
import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { executeMethodMap, executeResultMap, executeStatusMap } from './utils';
const props = defineProps<{
type: 'system' | 'project' | 'org';
}>();
const { t } = useI18n();
const { openModal } = useModal();
const tableStore = useTableStore();
const keyword = ref('');
const resourcePool = ref([]);
const resourcePoolOptions = ref([]);
const tableSelected = ref<string[]>([]);
const batchModalParams = ref();
const columns: MsTableColumn = [
{
title: t('ms.taskCenter.taskID'),
dataIndex: 'num',
width: 100,
columnSelectorDisabled: true,
},
{
title: 'ms.taskCenter.taskName',
dataIndex: 'name',
showTooltip: true,
width: 200,
},
{
title: 'ms.taskCenter.executeStatus',
dataIndex: 'executeStatus',
slotName: 'executeStatus',
width: 100,
filterConfig: {
options: Object.keys(executeStatusMap).map((key) => ({
label: t(executeStatusMap[key].label),
value: key,
})),
filterSlotName: FilterSlotNameEnum.GLOBAL_TASK_CENTER_EXEC_STATUS,
},
},
{
title: 'ms.taskCenter.executeMethod',
dataIndex: 'executeMethod',
width: 100,
filterConfig: {
options: Object.keys(executeMethodMap).map((key) => ({
label: t(executeMethodMap[key]),
value: key,
})),
filterSlotName: FilterSlotNameEnum.GLOBAL_TASK_CENTER_EXEC_METHOD,
},
},
{
title: 'ms.taskCenter.executeResult',
dataIndex: 'executeResult',
slotName: 'executeResult',
width: 100,
filterConfig: {
options: Object.keys(executeResultMap).map((key) => ({
label: t(executeResultMap[key].label),
value: key,
icon: executeResultMap[key].icon,
})),
filterSlotName: FilterSlotNameEnum.GLOBAL_TASK_CENTER_EXEC_RESULT,
},
},
{
title: 'ms.taskCenter.resourcePool',
dataIndex: 'resourcePools',
isStringTag: true,
isTag: true,
},
{
title: 'ms.taskCenter.node',
dataIndex: 'node',
slotName: 'node',
width: 100,
},
{
title: 'ms.taskCenter.queue',
dataIndex: 'queue',
width: 100,
},
{
title: 'ms.taskCenter.threadID',
dataIndex: 'threadID',
width: 100,
},
{
title: 'ms.taskCenter.startExecuteTime',
dataIndex: 'startExecuteTime',
width: 170,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
},
{
title: 'ms.taskCenter.endExecuteTime',
dataIndex: 'endExecuteTime',
width: 170,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
},
{
title: 'common.operation',
slotName: 'action',
dataIndex: 'operation',
fixed: 'right',
width: 220,
},
];
if (props.type === 'system') {
columns.splice(1, 0, [
{
title: 'common.belongProject',
dataIndex: 'belongProject',
showTooltip: true,
width: 100,
},
{
title: 'common.belongOrg',
dataIndex: 'belongOrg',
showTooltip: true,
width: 100,
},
]);
} else if (props.type === 'org') {
columns.splice(1, 0, [
{
title: 'common.belongProject',
dataIndex: 'belongProject',
showTooltip: true,
width: 100,
},
]);
}
await tableStore.initColumn(TableKeyEnum.TASK_CENTER_CASE_TASK_DETAIL, columns, 'drawer');
const tableBatchActions = {
baseAction: [
{
label: 'common.stop',
eventTag: 'stop',
},
{
label: 'ms.taskCenter.rerun',
eventTag: 'rerun',
},
{
label: 'common.delete',
eventTag: 'delete',
},
],
};
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
() =>
Promise.resolve({
list: [
{
id: '1',
num: 10086,
name: 'test',
belongProject: 'test',
belongOrg: 'test',
executeStatus: 'PENDING',
executeMethod: '手动执行',
executeResult: 'SUCCESS',
resourcePools: ['test'],
node: '11.11.1',
queue: '10',
threadID: '1736',
startExecuteTime: 1629782400000,
endExecuteTime: 1629782400000,
},
],
total: 1,
}),
{
tableKey: TableKeyEnum.TASK_CENTER_CASE_TASK_DETAIL,
scroll: { x: '1000px' },
selectable: true,
heightUsed: 288,
showSetting: true,
size: 'default',
},
(item) => {
return {
...item,
startExecuteTime: dayjs(item.startExecuteTime).format('YYYY-MM-DD HH:mm:ss'),
endExecuteTime: dayjs(item.endExecuteTime).format('YYYY-MM-DD HH:mm:ss'),
};
}
);
function searchTask() {
setLoadListParams({ keyword: keyword.value, resourcePools: resourcePool.value });
loadList();
}
/**
* 删除任务
*/
function deleteTask(record?: any, isBatch?: boolean, params?: BatchActionQueryParams) {
let title = t('ms.taskCenter.deleteTaskTitle', { name: characterLimit(record?.name) });
let selectIds = [record?.id || ''];
if (isBatch) {
title = t('ms.taskCenter.deleteCaseTaskTitle', {
count: params?.currentSelectCount || tableSelected.value.length,
});
selectIds = tableSelected.value as string[];
}
openModal({
type: 'error',
title,
content: t('ms.taskCenter.deleteCaseTaskTip'),
okText: t('common.confirmDelete'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'danger',
},
maskClosable: false,
onBeforeOk: async () => {
try {
// await deleteUserInfo({
// selectIds,
// selectAll: !!params?.selectAll,
// excludeIds: params?.excludeIds || [],
// condition: { keyword: keyword.value },
// });
Message.success(t('common.deleteSuccess'));
resetSelector();
loadList();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
function stopTask(record?: any, isBatch?: boolean, params?: BatchActionQueryParams) {
let title = t('ms.taskCenter.stopTaskTitle', { name: characterLimit(record?.name) });
let selectIds = [record?.id || ''];
if (isBatch) {
title = t('ms.taskCenter.batchStopTaskTitle', {
count: params?.currentSelectCount || tableSelected.value.length,
});
selectIds = tableSelected.value as string[];
}
openModal({
type: 'warning',
title,
content: t('ms.taskCenter.stopTimeTaskTip'),
okText: t('common.stopConfirm'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'danger',
},
maskClosable: false,
onBeforeOk: async () => {
try {
// await deleteUserInfo({
// selectIds,
// selectAll: !!params?.selectAll,
// excludeIds: params?.excludeIds || [],
// condition: { keyword: keyword.value },
// });
Message.success(t('common.stopped'));
resetSelector();
loadList();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
/**
* 处理表格选中后批量操作
* @param event 批量操作事件对象
*/
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
batchModalParams.value = params;
switch (event.eventTag) {
case 'delete':
deleteTask(undefined, true, params);
break;
case 'stop':
stopTask(undefined, true, params);
break;
default:
break;
}
}
function rerunTask(record: any) {
console.log('rerunTask', record);
}
const executeResultId = ref('');
const caseExecuteResultDrawerVisible = ref(false);
const scenarioExecuteResultDrawerVisible = ref(false);
function checkExecuteResult(record: any) {
executeResultId.value = record.id;
scenarioExecuteResultDrawerVisible.value = true;
}
onMounted(() => {
loadList();
});
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,487 @@
<template>
<div class="my-[16px] flex items-center justify-end">
<a-input-search
v-model:model-value="keyword"
:placeholder="t('ms.taskCenter.search')"
class="mr-[12px] w-[240px]"
allow-clear
@search="searchTask"
@press-enter="searchTask"
@clear="searchTask"
/>
<MsTag no-margin size="large" :tooltip-disabled="true" class="cursor-pointer" theme="outline" @click="searchTask">
<MsIcon class="text-[16px] text-[var(color-text-4)]" :size="32" type="icon-icon_reset_outlined" />
</MsTag>
</div>
<ms-base-table
v-bind="propsRes"
:action-config="tableBatchActions"
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<template #num="{ record }">
<a-button type="text" class="max-w-full justify-start px-0" @click="showTaskDetail(record.id)">
<div class="one-line-text">
{{ record.num }}
</div>
</a-button>
</template>
<template #executeStatus="{ record }">
<execStatus :status="record.executeStatus" />
</template>
<template #[FilterSlotNameEnum.GLOBAL_TASK_CENTER_EXEC_STATUS]="{ filterContent }">
<execStatus :status="filterContent.value" />
</template>
<template #executeResult="{ record }">
<executionStatus :status="record.executeResult" />
</template>
<template #[FilterSlotNameEnum.GLOBAL_TASK_CENTER_EXEC_RESULT]="{ filterContent }">
<executionStatus :status="filterContent.value" />
</template>
<template #executeFinishedRate="{ record }">
<a-popover trigger="hover" position="bottom">
<div>{{ record.executeFinishedRate }}</div>
<template #content>
<div class="flex w-[130px] flex-col gap-[8px]">
<div class="ms-taskCenter-execute-rate-item">
<div class="ms-taskCenter-execute-rate-item-label">{{ t('ms.taskCenter.passThreshold') }}</div>
<div class="ms-taskCenter-execute-rate-item-value">{{ record.passThreshold }}</div>
</div>
<div class="ms-taskCenter-execute-rate-item">
<div class="ms-taskCenter-execute-rate-item-label">
{{ record.testPlanId ? t('ms.taskCenter.executeFinishedRate') : t('ms.taskCenter.executeProgress') }}
</div>
<div class="ms-taskCenter-execute-rate-item-value">
{{ `${((record.unExecuteCount / record.caseCount) * 100).toFixed(2)}%` }}
</div>
</div>
<div class="ms-taskCenter-execute-rate-item">
<div class="ms-taskCenter-execute-rate-item-label">
<div
:class="`ms-taskCenter-execute-rate-item-label-point bg-[${executeFinishedRateMap.UN_EXECUTE.color}]`"
></div>
{{ t(executeFinishedRateMap.UN_EXECUTE.label) }}
</div>
<div class="ms-taskCenter-execute-rate-item-value">{{ record.unExecuteCount }}</div>
</div>
<div class="ms-taskCenter-execute-rate-item">
<div class="ms-taskCenter-execute-rate-item-label">
<div
:class="`ms-taskCenter-execute-rate-item-label-point bg-[${executeFinishedRateMap.SUCCESS.color}]`"
></div>
{{ t(executeFinishedRateMap.SUCCESS.label) }}
</div>
<div class="ms-taskCenter-execute-rate-item-value">{{ record.successCount }}</div>
</div>
<div class="ms-taskCenter-execute-rate-item">
<div class="ms-taskCenter-execute-rate-item-label">
<div
:class="`ms-taskCenter-execute-rate-item-label-point bg-[${executeFinishedRateMap.FAKE_ERROR.color}]`"
></div>
{{ t(executeFinishedRateMap.FAKE_ERROR.label) }}
</div>
<div class="ms-taskCenter-execute-rate-item-value">{{ record.fakeErrorCount }}</div>
</div>
<div v-if="record.testPlanId" class="ms-taskCenter-execute-rate-item">
<div class="ms-taskCenter-execute-rate-item-label">
<div
:class="`ms-taskCenter-execute-rate-item-label-point`"
:style="{ backgroundColor: executeFinishedRateMap.BLOCK.color }"
></div>
{{ t(executeFinishedRateMap.BLOCK.label) }}
</div>
<div class="ms-taskCenter-execute-rate-item-value">{{ record.blockCount }}</div>
</div>
<div class="ms-taskCenter-execute-rate-item">
<div class="ms-taskCenter-execute-rate-item-label">
<div
:class="`ms-taskCenter-execute-rate-item-label-point bg-[${executeFinishedRateMap.ERROR.color}]`"
></div>
{{ t(executeFinishedRateMap.ERROR.label) }}
</div>
<div class="ms-taskCenter-execute-rate-item-value">{{ record.errorCount }}</div>
</div>
</div>
</template>
</a-popover>
</template>
<template #action="{ record }">
<MsButton v-permission="['SYSTEM_USER:READ+DELETE']" @click="stopTask(record)">
{{ t('common.stop') }}
</MsButton>
<MsButton v-permission="['SYSTEM_USER:READ+DELETE']" @click="deleteTask(record)">
{{ t('common.delete') }}
</MsButton>
<MsButton v-permission="['SYSTEM_USER:READ+DELETE']" @click="rerunTask(record)">
{{ t('ms.taskCenter.rerun') }}
</MsButton>
<MsButton v-permission="['SYSTEM_USER:READ+DELETE']" @click="checkReport(record)">
{{ t('ms.taskCenter.checkReport') }}
</MsButton>
</template>
</ms-base-table>
</template>
<script setup lang="ts">
import { Message } from '@arco-design/web-vue';
import dayjs from 'dayjs';
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 MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import execStatus from './execStatus.vue';
import executionStatus from './executionStatus.vue';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useTableStore from '@/hooks/useTableStore';
import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { executeFinishedRateMap, executeMethodMap, executeResultMap, executeStatusMap } from './utils';
const props = defineProps<{
type: 'system' | 'project' | 'org';
}>();
const { t } = useI18n();
const { openModal } = useModal();
const tableStore = useTableStore();
const keyword = ref('');
const tableSelected = ref<string[]>([]);
const batchModalParams = ref();
const columns: MsTableColumn = [
{
title: 'ID',
dataIndex: 'num',
slotName: 'num',
width: 100,
columnSelectorDisabled: true,
},
{
title: 'ms.taskCenter.taskName',
dataIndex: 'name',
showTooltip: true,
width: 200,
},
{
title: 'ms.taskCenter.executeStatus',
dataIndex: 'executeStatus',
slotName: 'executeStatus',
width: 90,
filterConfig: {
options: Object.keys(executeStatusMap).map((key) => ({
label: t(executeStatusMap[key].label),
value: key,
})),
filterSlotName: FilterSlotNameEnum.GLOBAL_TASK_CENTER_EXEC_STATUS,
},
},
{
title: 'ms.taskCenter.executeMethod',
dataIndex: 'executeMethod',
width: 90,
filterConfig: {
options: Object.keys(executeMethodMap).map((key) => ({
label: t(executeMethodMap[key]),
value: key,
})),
filterSlotName: FilterSlotNameEnum.GLOBAL_TASK_CENTER_EXEC_METHOD,
},
},
{
title: 'ms.taskCenter.executeResult',
dataIndex: 'executeResult',
slotName: 'executeResult',
width: 90,
filterConfig: {
options: Object.keys(executeResultMap).map((key) => ({
label: t(executeResultMap[key].label),
value: key,
icon: executeResultMap[key].icon,
})),
filterSlotName: FilterSlotNameEnum.GLOBAL_TASK_CENTER_EXEC_RESULT,
},
},
{
title: 'ms.taskCenter.caseCount',
dataIndex: 'caseCount',
width: 90,
},
{
title: 'ms.taskCenter.executeFinishedRate',
dataIndex: 'executeFinishedRate',
slotName: 'executeFinishedRate',
width: 100,
},
{
title: 'ms.taskCenter.createTime',
dataIndex: 'createTime',
width: 170,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
},
{
title: 'ms.taskCenter.startTime',
dataIndex: 'startTime',
width: 170,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
},
{
title: 'ms.taskCenter.endTime',
dataIndex: 'endTime',
width: 170,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
},
{
title: 'common.operation',
slotName: 'action',
dataIndex: 'operation',
fixed: 'right',
width: 220,
},
];
if (props.type === 'system') {
columns.splice(
1,
0,
{
title: 'common.belongProject',
dataIndex: 'belongProject',
showTooltip: true,
width: 100,
},
{
title: 'common.belongOrg',
dataIndex: 'belongOrg',
showTooltip: true,
width: 100,
}
);
} else if (props.type === 'org') {
columns.splice(1, 0, {
title: 'common.belongProject',
dataIndex: 'belongProject',
showTooltip: true,
width: 100,
});
}
const tableBatchActions = {
baseAction: [
{
label: 'common.stop',
eventTag: 'stop',
},
{
label: 'ms.taskCenter.rerun',
eventTag: 'rerun',
},
{
label: 'common.delete',
eventTag: 'delete',
},
],
};
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
() =>
Promise.resolve({
list: [
{
id: '1',
num: 10086,
name: '测试任务',
belongProject: '测试项目',
belongOrg: '测试组织',
executeStatus: 'PENDING',
executeMethod: '手动执行',
executeResult: 'SUCCESS',
caseCount: 100,
executeFinishedRate: '100%',
startTime: 1630000000000,
createTime: 1630000000000,
endTime: 1630000000000,
passThreshold: '100%',
unExecuteCount: 0,
successCount: 100,
fakeErrorCount: 0,
blockCount: 0,
errorCount: 0,
},
],
total: 1,
}),
{
tableKey: TableKeyEnum.TASK_CENTER_CASE_TASK,
scroll: { x: '1000px' },
selectable: true,
showSetting: true,
showPagination: true,
},
(item) => {
return {
...item,
startTime: dayjs(item.startTime).format('YYYY-MM-DD HH:mm:ss'),
createTime: dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss'),
endTime: dayjs(item.endTime).format('YYYY-MM-DD HH:mm:ss'),
};
}
);
function searchTask() {
setLoadListParams({ keyword: keyword.value });
loadList();
}
function showTaskDetail(id: string) {
console.log('showTaskDetail', id);
}
/**
* 删除任务
*/
function deleteTask(record?: any, isBatch?: boolean, params?: BatchActionQueryParams) {
let title = t('ms.taskCenter.deleteTaskTitle', { name: characterLimit(record?.name) });
let selectIds = [record?.id || ''];
if (isBatch) {
title = t('ms.taskCenter.deleteCaseTaskTitle', {
count: params?.currentSelectCount || tableSelected.value.length,
});
selectIds = tableSelected.value as string[];
}
openModal({
type: 'error',
title,
content: t('ms.taskCenter.deleteCaseTaskTip'),
okText: t('common.confirmDelete'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'danger',
},
maskClosable: false,
onBeforeOk: async () => {
try {
// await deleteUserInfo({
// selectIds,
// selectAll: !!params?.selectAll,
// excludeIds: params?.excludeIds || [],
// condition: { keyword: keyword.value },
// });
Message.success(t('common.deleteSuccess'));
resetSelector();
loadList();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
function stopTask(record?: any, isBatch?: boolean, params?: BatchActionQueryParams) {
let title = t('ms.taskCenter.stopTaskTitle', { name: characterLimit(record?.name) });
let selectIds = [record?.id || ''];
if (isBatch) {
title = t('ms.taskCenter.batchStopTaskTitle', {
count: params?.currentSelectCount || tableSelected.value.length,
});
selectIds = tableSelected.value as string[];
}
openModal({
type: 'warning',
title,
content: t('ms.taskCenter.stopTimeTaskTip'),
okText: t('common.stopConfirm'),
cancelText: t('common.cancel'),
maskClosable: false,
onBeforeOk: async () => {
try {
// await deleteUserInfo({
// selectIds,
// selectAll: !!params?.selectAll,
// excludeIds: params?.excludeIds || [],
// condition: { keyword: keyword.value },
// });
Message.success(t('common.stopped'));
resetSelector();
loadList();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
/**
* 处理表格选中后批量操作
* @param event 批量操作事件对象
*/
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
tableSelected.value = params.selectedIds || [];
batchModalParams.value = params;
switch (event.eventTag) {
case 'delete':
deleteTask(undefined, true, params);
break;
case 'stop':
stopTask(undefined, true, params);
break;
default:
break;
}
}
function rerunTask(record: any) {
console.log('rerunTask', record);
}
function checkReport(record: any) {
console.log('checkReport', record);
}
onMounted(() => {
loadList();
});
await tableStore.initColumn(TableKeyEnum.TASK_CENTER_CASE_TASK, columns, 'drawer');
</script>
<style lang="less">
.ms-taskCenter-execute-rate-item {
@apply flex items-center justify-between;
.ms-taskCenter-execute-rate-item-label {
@apply flex items-center;
gap: 4px;
color: var(--color-text-4);
.ms-taskCenter-execute-rate-item-label-point {
width: 6px;
height: 6px;
border-radius: 100%;
}
}
.ms-taskCenter-execute-rate-item-value {
font-weight: 500;
color: var(--color-text-1);
}
}
</style>

View File

@ -0,0 +1,25 @@
<template>
<a-tag
:color="executeStatusMap[props.status]?.color"
:class="executeStatusMap[props.status]?.class"
:size="props.size"
>
{{ t(executeStatusMap[props.status]?.label) }}
</a-tag>
</template>
<script setup lang="ts">
import { useI18n } from '@/hooks/useI18n';
import { ReportExecStatus } from '@/enums/apiEnum';
import { executeStatusMap } from './utils';
const props = defineProps<{
status: ReportExecStatus;
size?: 'small' | 'medium' | 'large';
}>();
const { t } = useI18n();
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,54 @@
<template>
<div class="flex items-center justify-start">
<MsIcon :type="getExecutionResult().icon" :class="`text-[${getExecutionResult()?.color}]`" size="14" />
<span class="ml-1">{{ t(getExecutionResult().label) }}</span>
<!-- <a-tooltip v-if="props.scriptIdentifier" :content="getMsg()">
<MsTag
class="ml-2"
:self-style="{
border: `1px solid ${methodColor}`,
color: methodColor,
backgroundColor: 'white',
}"
>
{{ t('report.detail.script.error') }}
</MsTag>
</a-tooltip> -->
</div>
</template>
<script setup lang="ts">
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import { useI18n } from '@/hooks/useI18n';
import { TaskCenterEnum } from '@/enums/taskCenter';
import { executeResultMap } from './utils';
const { t } = useI18n();
const props = defineProps<{
status: string;
scriptIdentifier?: string;
}>();
export interface IconType {
icon: string;
label: string;
color?: string;
}
function getExecutionResult(): IconType {
return executeResultMap[props.status] ? executeResultMap[props.status] : executeResultMap.DEFAULT;
}
const methodColor = 'rgb(var(--warning-7))';
// function getMsg() {
// if (props.moduleType === TaskCenterEnum.CASE && props.scriptIdentifier) {
// return t('report.detail.scenario.errorTip');
// }
// return t('report.detail.api.errorTip');
// }
</script>
<style scoped></style>

View File

@ -0,0 +1,163 @@
<template>
<MsDrawer v-model:visible="visible" :width="800" :footer="false">
<template #title>
<div class="flex items-center gap-[8px]">
<a-tag :color="executeResultMap[detail.executeResult]?.color">
{{ t(executeResultMap[detail.executeResult]?.label) }}
</a-tag>
<div>{{ detail.name }}</div>
</div>
<div class="flex flex-1 justify-end">
<MsButton type="icon" status="secondary" class="!rounded-[var(--border-radius-small)]" @click="refresh">
<MsIcon type="icon-icon_reset_outlined" class="mr-[8px]" size="14" />
{{ t('common.refresh') }}
</MsButton>
</div>
</template>
<MsDescription :descriptions="detail.description" :column="3" :line-gap="8" one-line-value>
<template #value="{ item }">
<execStatus v-if="item.key === 'status'" :status="item.value as ReportExecStatus" size="small" />
<a-tooltip
v-else
:content="`${item.value}`"
:disabled="item.value === undefined || item.value === null || item.value?.toString() === ''"
:position="item.tooltipPosition ?? 'tl'"
>
<div class="w-[fit-content]">
{{ item.value === undefined || item.value === null || item.value?.toString() === '' ? '-' : item.value }}
</div>
</a-tooltip>
</template>
</MsDescription>
<div class="mt-[8px]">
<reportInfoHeader
v-model:keywordName="keywordName"
v-model:keyword="cascaderKeywords"
v-model:active-tab="activeTab"
show-type="API"
@search="searchHandler"
@reset="resetHandler"
/>
<TiledList
ref="tiledListRef"
v-model:keyword-name="keywordName"
:key-words="cascaderKeywords"
show-type="API"
:get-report-step-detail="props.getReportStepDetail"
:active-type="activeTab"
:report-detail="detail || []"
class="p-[16px]"
/>
</div>
</MsDrawer>
</template>
<script setup lang="ts">
import dayjs from 'dayjs';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsDescription, { Description } from '@/components/pure/ms-description/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import execStatus from './execStatus.vue';
import reportInfoHeader from '@/views/api-test/report/component/step/reportInfoHeaders.vue';
import TiledList from '@/views/api-test/report/component/tiledList.vue';
import { useI18n } from '@/hooks/useI18n';
import { ReportExecStatus } from '@/enums/apiEnum';
import { executeResultMap } from './utils';
const props = defineProps<{
id: string;
getReportStepDetail?: (...args: any) => Promise<any>; //
}>();
const { t } = useI18n();
const visible = defineModel<boolean>('visible', { required: true });
const detail = ref<any>({ description: [], children: [] });
watch(
() => props.id,
async () => {
if (props.id) {
detail.value = {
id: props.id,
name: '测试用例名称',
executeResult: 'SUCCESS',
description: [
{
label: t('ms.taskCenter.executeStatus'),
key: 'status',
value: 'COMPLETED',
},
{
label: t('ms.taskCenter.operationUser'),
value: 'admin',
},
{
label: t('ms.taskCenter.taskCreateTime'),
value: dayjs(1626844800000).format('YYYY-MM-DD HH:mm:ss'),
},
{
label: t('ms.taskCenter.taskResource'),
value: '测试计划',
},
{
label: t('ms.taskCenter.threadID'),
value: '1231231231',
},
{
label: t('ms.taskCenter.taskStartTime'),
value: dayjs(1626844800000).format('YYYY-MM-DD HH:mm:ss'),
},
{
label: t('ms.taskCenter.executeEnvInfo'),
value: 'DEV 资源池1 10.11.1.1',
class: '!w-[calc(100%/3*2)]',
},
{
label: t('ms.taskCenter.taskEndTime'),
value: dayjs(1626844800000).format('YYYY-MM-DD HH:mm:ss'),
},
] as Description[],
children: [],
};
}
},
{ immediate: true }
);
const cascaderKeywords = ref<string>('');
const keywordName = ref<string>('');
const activeTab = ref<'tiled' | 'tab'>('tiled');
const tiledListRef = ref<InstanceType<typeof TiledList>>();
function searchHandler() {
if (keywordName.value) {
tiledListRef.value?.updateDebouncedSearch();
} else {
tiledListRef.value?.initStepTree();
}
}
function resetHandler() {
tiledListRef.value?.initStepTree();
}
function refresh() {
console.log('refresh');
}
</script>
<style lang="less" scoped>
:deep(.ms-description-item) {
@apply items-center;
margin-bottom: 8px;
font-size: 12px;
line-height: 16px;
}
</style>

View File

@ -0,0 +1,258 @@
<template>
<div class="my-[16px] flex items-center justify-end">
<a-input-search
v-model:model-value="keyword"
:placeholder="t('ms.taskCenter.search')"
class="mr-[12px] w-[240px]"
allow-clear
@search="searchTask"
@press-enter="searchTask"
@clear="searchTask"
/>
<MsTag no-margin size="large" :tooltip-disabled="true" class="cursor-pointer" theme="outline" @click="searchTask">
<MsIcon class="text-[16px] text-[var(color-text-4)]" :size="32" type="icon-icon_reset_outlined" />
</MsTag>
</div>
<ms-base-table
v-bind="propsRes"
:action-config="tableBatchActions"
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<template #status="{ record }">
<a-switch v-model:model-value="record.enable" size="small"></a-switch>
</template>
<template #action="{ record }">
<MsButton v-permission="['SYSTEM_USER:READ+DELETE']" @click="deleteTask(record)">
{{ t('common.delete') }}
</MsButton>
</template>
</ms-base-table>
</template>
<script setup lang="ts">
import { Message } from '@arco-design/web-vue';
import dayjs from 'dayjs';
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 MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useTableStore from '@/hooks/useTableStore';
import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import { TableKeyEnum } from '@/enums/tableEnum';
const { t } = useI18n();
const { openModal } = useModal();
const tableStore = useTableStore();
const keyword = ref('');
const tableSelected = ref<string[]>([]);
const batchModalParams = ref();
const columns: MsTableColumn = [
{
title: 'ID',
dataIndex: 'num',
width: 100,
},
{
title: 'ms.taskCenter.taskName',
dataIndex: 'name',
showTooltip: true,
width: 200,
},
{
title: 'common.status',
dataIndex: 'status',
slotName: 'status',
width: 50,
},
{
title: 'ms.taskCenter.type',
dataIndex: 'type',
slotName: 'type',
width: 120,
},
{
title: 'ms.taskCenter.runRule',
dataIndex: 'rule',
width: 100,
},
{
title: 'ms.taskCenter.operationUser',
dataIndex: 'operationUser',
width: 100,
},
{
title: 'ms.taskCenter.operationTime',
dataIndex: 'operationTime',
width: 170,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
},
{
title: 'ms.taskCenter.lastFinishTime',
dataIndex: 'lastFinishTime',
width: 170,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
},
{
title: 'ms.taskCenter.nextExecuteTime',
dataIndex: 'nextExecuteTime',
width: 170,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
},
{
title: 'common.operation',
slotName: 'action',
dataIndex: 'operation',
fixed: 'right',
width: 60,
},
];
await tableStore.initColumn(TableKeyEnum.TASK_CENTER_SYSTEM_TASK, columns, 'drawer');
const tableBatchActions = {
baseAction: [
{
label: 'common.open',
eventTag: 'open',
},
{
label: 'common.close',
eventTag: 'close',
},
],
};
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
() =>
Promise.resolve({
list: [
{
id: '1',
num: 10086,
name: '测试任务',
status: 'success',
type: 'API 导入',
rule: '每 1 小时',
enable: true,
operationUser: 'admin',
operationTime: '2021-09-01 12:00:00',
lastFinishTime: '2021-09-01 12:00:00',
nextExecuteTime: '2021-09-01 12:00:00',
},
],
total: 1,
}),
{
tableKey: TableKeyEnum.TASK_CENTER_SYSTEM_TASK,
scroll: { x: '100%' },
selectable: true,
heightUsed: 288,
showSetting: true,
size: 'default',
},
(item) => {
return {
...item,
operationTime: dayjs(item.operationTime).format('YYYY-MM-DD HH:mm:ss'),
lastFinishTime: dayjs(item.lastFinishTime).format('YYYY-MM-DD HH:mm:ss'),
nextExecuteTime: dayjs(item.nextExecuteTime).format('YYYY-MM-DD HH:mm:ss'),
};
}
);
function searchTask() {
setLoadListParams({ keyword: keyword.value });
loadList();
}
/**
* 删除任务
*/
function deleteTask(record?: any, isBatch?: boolean, params?: BatchActionQueryParams) {
let title = t('ms.taskCenter.deleteTaskTitle', { name: characterLimit(record?.name) });
let selectIds = [record?.id || ''];
if (isBatch) {
title = t('ms.taskCenter.deleteTimeTaskTitle', {
count: params?.currentSelectCount || tableSelected.value.length,
});
selectIds = tableSelected.value as string[];
}
openModal({
type: 'error',
title,
content: t('ms.taskCenter.deleteTimeTaskTip'),
okText: t('common.confirmDelete'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'danger',
},
maskClosable: false,
onBeforeOk: async () => {
try {
// await deleteUserInfo({
// selectIds,
// selectAll: !!params?.selectAll,
// excludeIds: params?.excludeIds || [],
// condition: { keyword: keyword.value },
// });
Message.success(t('common.deleteSuccess'));
resetSelector();
loadList();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
function openTask(record?: any, isBatch?: boolean, params?: BatchActionQueryParams) {
console.log(record);
}
function closeTask(record?: any, isBatch?: boolean, params?: BatchActionQueryParams) {
console.log(record);
}
/**
* 处理表格选中后批量操作
* @param event 批量操作事件对象
*/
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
batchModalParams.value = params;
switch (event.eventTag) {
case 'open':
openTask(undefined, true, params);
break;
case 'close':
closeTask(undefined, true, params);
break;
default:
break;
}
}
onMounted(() => {
loadList();
});
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,148 @@
import type { MsTableColumnData } from '@/components/pure/ms-table/type';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import useLicenseStore from '@/store/modules/setting/license';
import { hasAnyPermission } from '@/utils/permission';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum } from '@/enums/tableFilterEnum';
const { t } = useI18n();
// 执行结果
export const executeResultMap: Record<string, any> = {
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: 'common.success',
color: 'rgb(var(--success-6))',
},
ERROR: {
icon: 'icon-icon_close_colorful',
label: 'common.fail',
color: 'rgb(var(--danger-6))',
},
FAKE_ERROR: {
icon: 'icon-icon_warning_colorful',
label: 'common.fakeError',
color: 'rgb(var(--warning-6))',
},
};
// 执行完成率
export const executeFinishedRateMap: Record<string, any> = {
SUCCESS: {
label: 'common.success',
color: 'rgb(var(--success-6))',
},
ERROR: {
label: 'common.fail',
color: 'rgb(var(--danger-6))',
},
FAKE_ERROR: {
label: 'common.fakeError',
color: 'rgb(var(--warning-6))',
},
BLOCK: {
label: 'common.block',
color: 'rgb(179,121,200)',
},
UN_EXECUTE: {
label: 'common.unExecute',
color: 'var(--color-text-4)',
},
};
// 执行状态
export const executeStatusMap: Record<string, any> = {
PENDING: {
label: 'common.unExecute',
color: 'var(--color-text-n8)',
class: '!text-[var(--color-text-1)]',
},
RUNNING: {
label: 'common.running',
color: 'rgb(var(--link-2))',
class: '!text-[rgb(var(--link-6))]',
},
STOPPED: {
label: 'common.stopped',
color: 'rgb(var(--warning-2))',
class: '!text-[rgb(var(--warning-6))]',
},
COMPLETED: {
label: 'common.completed',
color: 'rgb(var(--success-2))',
class: '!text-[rgb(var(--success-6))]',
},
RERUNNING: {
label: 'ms.taskCenter.failRerun',
color: 'rgb(var(--link-2))',
class: '!text-[rgb(var(--link-6))]',
},
};
// 执行方式
export const executeMethodMap: Record<string, any> = {
MANUAL: 'ms.taskCenter.execute',
BATCH: 'ms.taskCenter.batchExecute',
API: 'ms.taskCenter.interfaceCall',
SCHEDULE: 'ms.taskCenter.scheduledTask',
};
export type Group = 'system' | 'organization' | 'project';
export function getOrgColumns(): MsTableColumnData {
const licenseStore = useLicenseStore();
const config: MsTableColumnData = {
title: 'project.belongOrganization',
dataIndex: 'organizationIds',
slotName: 'organizationName',
showDrag: true,
width: 200,
showInTable: true,
};
if (licenseStore.hasLicense()) {
config.filterConfig = {
mode: 'remote',
remoteMethod: FilterRemoteMethodsEnum.SYSTEM_ORGANIZATION_LIST,
placeholderText: t('project.taskCenter.filterOrgPlaceholderText'),
};
}
return config;
}
export function getProjectColumns(key: TableKeyEnum): MsTableColumnData {
const appStore = useAppStore();
const systemKey = [
TableKeyEnum.TASK_CENTER_CASE_TASK,
TableKeyEnum.TASK_SCHEDULE_TASK_API_IMPORT_SYSTEM,
TableKeyEnum.TASK_SCHEDULE_TASK_API_SCENARIO_SYSTEM,
];
const filterKeyPermission = systemKey.includes(key)
? hasAnyPermission(['SYSTEM_ORGANIZATION_PROJECT:READ'])
: hasAnyPermission(['ORGANIZATION_PROJECT:READ']);
const remoteMethod = systemKey.includes(key)
? FilterRemoteMethodsEnum.SYSTEM_PROJECT_LIST
: FilterRemoteMethodsEnum.SYSTEM_ORGANIZATION_PROJECT;
const config: MsTableColumnData = {
title: 'project.belongProject',
dataIndex: 'projectIds',
slotName: 'projectName',
showDrag: true,
width: 200,
showInTable: true,
};
if (filterKeyPermission && remoteMethod) {
config.filterConfig = {
mode: 'remote',
loadOptionParams: {
organizationId: appStore.currentOrgId,
},
remoteMethod,
placeholderText: t('project.taskCenter.filterProPlaceholderText'),
};
}
return config;
}

View File

@ -0,0 +1,58 @@
<template>
<div>
<a-tabs v-model:active-key="activeTab" class="no-content">
<a-tab-pane v-for="item of tabList" :key="item.value" :title="item.label" />
</a-tabs>
<a-divider margin="0"></a-divider>
<Suspense>
<caseTaskTable v-if="activeTab === TaskCenterEnum.CASE" :type="props.type" />
<caseTaskDetailTable v-else-if="activeTab === TaskCenterEnum.DETAIL" :type="props.type" />
<systemTaskTable v-else-if="activeTab === TaskCenterEnum.BACKEND" />
</Suspense>
</div>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';
import caseTaskDetailTable from './component/caseTaskDetailTable.vue';
import caseTaskTable from './component/caseTaskTable.vue';
import systemTaskTable from './component/systemTaskTable.vue';
import { useI18n } from '@/hooks/useI18n';
import { TaskCenterEnum } from '@/enums/taskCenter';
const props = defineProps<{
type: 'system' | 'org' | 'project';
mode?: 'modal' | 'normal';
}>();
const { t } = useI18n();
const route = useRoute();
const tabList = ref([
{
value: TaskCenterEnum.CASE,
label: t('ms.taskCenter.caseTaskList'),
},
{
value: TaskCenterEnum.DETAIL,
label: t('ms.taskCenter.caseTaskDetailList'),
},
{
value: TaskCenterEnum.BACKEND,
label: t('ms.taskCenter.backendTaskList'),
},
]);
const activeTab = ref<TaskCenterEnum>((route.query.type as TaskCenterEnum) || TaskCenterEnum.CASE);
</script>
<style scoped lang="less">
.no-content {
:deep(.arco-tabs-content) {
padding-top: 0;
}
}
</style>

View File

@ -0,0 +1,53 @@
export default {
'ms.taskCenter.caseTaskList': 'Case Execution Task',
'ms.taskCenter.caseTaskDetailList': 'Case Execution Task Details',
'ms.taskCenter.backendTaskList': 'System Backend Task',
'ms.taskCenter.search': 'Search by ID/Name',
'ms.taskCenter.rerun': 'Rerun',
'ms.taskCenter.checkReport': 'View Report',
'ms.taskCenter.taskName': 'Task Name',
'ms.taskCenter.executeStatus': 'Execution Status',
'ms.taskCenter.executeMethod': 'Execution Method',
'ms.taskCenter.executeResult': 'Execution Result',
'ms.taskCenter.caseCount': 'Case Count',
'ms.taskCenter.executeFinishedRate': 'Execution Completion Rate',
'ms.taskCenter.createTime': 'Initiation Time',
'ms.taskCenter.startTime': 'Start Time',
'ms.taskCenter.endTime': 'End Time',
'ms.taskCenter.startExecuteTime': 'Start Execution Time',
'ms.taskCenter.endExecuteTime': 'End Execution Time',
'ms.taskCenter.taskCreateTime': 'Task Initiation Time',
'ms.taskCenter.taskStartTime': 'Task Start Time',
'ms.taskCenter.taskEndTime': 'Task End Time',
'ms.taskCenter.taskResource': 'Task Source',
'ms.taskCenter.executeEnvInfo': 'Execution Environment Information',
'ms.taskCenter.passThreshold': 'Pass Threshold',
'ms.taskCenter.executeProgress': 'Execution Progress',
'ms.taskCenter.taskID': 'Task ID',
'ms.taskCenter.resourcePool': 'Resource Pool',
'ms.taskCenter.node': 'Node',
'ms.taskCenter.nodeErrorTip': 'Node has an exception, please check',
'ms.taskCenter.queue': 'Queue',
'ms.taskCenter.threadID': 'Thread ID',
'ms.taskCenter.type': 'Type',
'ms.taskCenter.runRule': 'Run Rule',
'ms.taskCenter.operationUser': 'Operator',
'ms.taskCenter.operationTime': 'Operation Time',
'ms.taskCenter.lastFinishTime': 'Last Finish Time',
'ms.taskCenter.nextExecuteTime': 'Next Execution Time',
'ms.taskCenter.deleteTaskTitle': 'Confirm delete {name}?',
'ms.taskCenter.deleteCaseTaskTitle': 'Confirm delete {count} case tasks?',
'ms.taskCenter.deleteTimeTaskTitle': 'Confirm delete {count} scheduled tasks?',
'ms.taskCenter.deleteCaseTaskTip': 'After deletion, the case task will stop, please operate with caution!',
'ms.taskCenter.deleteTimeTaskTip': 'After deletion, the scheduled task will stop, please operate with caution!',
'ms.taskCenter.stopTaskTitle': 'Confirm stop {name}?',
'ms.taskCenter.batchStopTaskTitle': 'Confirm stop {count} tasks?',
'ms.taskCenter.stopTimeTaskTip': 'After stopping, it will affect task execution, please operate with caution!',
'ms.taskCenter.failRerun': 'Fail Rerun',
'ms.taskCenter.execute': 'Manual Execution',
'ms.taskCenter.batchExecute': 'Batch Execution',
'ms.taskCenter.interfaceCall': 'Interface Call',
'ms.taskCenter.scheduledTask': 'Scheduled Execution',
'ms.taskCenter.batchCaseTask': 'Batch Case Execution Task',
'ms.taskCenter.batchScenarioTask': 'Batch Scenario Execution Task',
};

View File

@ -0,0 +1,53 @@
export default {
'ms.taskCenter.caseTaskList': '用例执行任务',
'ms.taskCenter.caseTaskDetailList': '用例执行任务详情',
'ms.taskCenter.backendTaskList': '系统后台任务',
'ms.taskCenter.search': '通过 ID/名称搜索',
'ms.taskCenter.rerun': '重跑',
'ms.taskCenter.checkReport': '查看报告',
'ms.taskCenter.taskName': '任务名称',
'ms.taskCenter.executeStatus': '执行状态',
'ms.taskCenter.executeMethod': '执行方式',
'ms.taskCenter.executeResult': '执行结果',
'ms.taskCenter.caseCount': '用例数',
'ms.taskCenter.executeFinishedRate': '执行完成率',
'ms.taskCenter.createTime': '发起时间',
'ms.taskCenter.startTime': '开始时间',
'ms.taskCenter.endTime': '结束时间',
'ms.taskCenter.startExecuteTime': '开始执行时间',
'ms.taskCenter.endExecuteTime': '结束执行时间',
'ms.taskCenter.taskCreateTime': '任务发起时间',
'ms.taskCenter.taskStartTime': '任务开始时间',
'ms.taskCenter.taskEndTime': '任务结束时间',
'ms.taskCenter.taskResource': '任务来源',
'ms.taskCenter.executeEnvInfo': '执行环境信息',
'ms.taskCenter.passThreshold': '通过阈值',
'ms.taskCenter.executeProgress': '执行进度',
'ms.taskCenter.taskID': '任务 ID',
'ms.taskCenter.resourcePool': '资源池',
'ms.taskCenter.node': '节点',
'ms.taskCenter.nodeErrorTip': '节点存在异常,请检查',
'ms.taskCenter.queue': '排队',
'ms.taskCenter.threadID': '线程 ID',
'ms.taskCenter.type': '类型',
'ms.taskCenter.runRule': '运行规则',
'ms.taskCenter.operationUser': '操作人',
'ms.taskCenter.operationTime': '操作时间',
'ms.taskCenter.lastFinishTime': '上次完成时间',
'ms.taskCenter.nextExecuteTime': '下次执行时间',
'ms.taskCenter.deleteTaskTitle': '确认删除 {name} 吗?',
'ms.taskCenter.deleteCaseTaskTitle': '确认删除 {count} 项用例任务吗?',
'ms.taskCenter.deleteTimeTaskTitle': '确认删除 {count} 项定时任务吗?',
'ms.taskCenter.deleteCaseTaskTip': '删除后,用例任务停止,请谨慎操作!',
'ms.taskCenter.deleteTimeTaskTip': '删除后,定时任务停止,请谨慎操作!',
'ms.taskCenter.stopTaskTitle': '确认停止 {name} 吗?',
'ms.taskCenter.batchStopTaskTitle': '确认停止 {count} 项任务吗?',
'ms.taskCenter.stopTimeTaskTip': '停止后,影响任务执行,请谨慎操作!',
'ms.taskCenter.failRerun': '失败重跑',
'ms.taskCenter.execute': '手动执行',
'ms.taskCenter.batchExecute': '批量执行',
'ms.taskCenter.interfaceCall': '接口调用',
'ms.taskCenter.scheduledTask': '定时执行',
'ms.taskCenter.batchCaseTask': '用例批量执行任务',
'ms.taskCenter.batchScenarioTask': '场景批量执行任务',
};