feat(全局): 任务中心&部分bug修复&需求调整

This commit is contained in:
xinxin.wu 2024-02-07 17:21:07 +08:00 committed by Craftsman
parent fa5aa54071
commit 50139a84ac
59 changed files with 1742 additions and 199 deletions

View File

@ -12,6 +12,7 @@
import { getProjectInfo } from '@/api/modules/project-management/basicInfo';
import { saveBaseUrl } from '@/api/modules/setting/config';
import { getUserHasProjectPermission } from '@/api/modules/system';
import { GetPlatformIconUrl } from '@/api/requrls/setting/config';
// import GlobalSetting from '@/components/pure/global-setting/index.vue';
import useLocale from '@/locale/useLocale';
@ -70,21 +71,32 @@
console.log(error);
}
});
const checkIsLogin = async () => {
const isLogin = await userStore.isLogin();
const isLoginPage = route.name === 'login';
if (isLogin && appStore.currentProjectId && appStore.currentProjectId !== 'no_such_project') {
//
try {
const res = await getProjectInfo(appStore.currentProjectId);
if (res && (res.deleted || !res.enable)) {
//
const HasProjectPermission = await getUserHasProjectPermission(appStore.currentProjectId);
if (!HasProjectPermission) {
// &
router.push({
name: NO_PROJECT_ROUTE_NAME,
});
return;
}
appStore.setCurrentMenuConfig(res?.moduleIds || []);
const res = await getProjectInfo(appStore.currentProjectId);
if (res.deleted) {
//
router.push({
name: NO_PROJECT_ROUTE_NAME,
});
}
if (res) {
appStore.setCurrentMenuConfig(res?.moduleIds || []);
}
} catch (err) {
appStore.setCurrentMenuConfig([]);
// eslint-disable-next-line no-console

View File

@ -0,0 +1,45 @@
import MSR from '@/api/http';
import {
deleteScheduleSysTaskUrl,
scheduleOrgCenterListUrl,
scheduleProCenterListUrl,
scheduleSysCenterListUrl,
taskOrgRealCenterListUrl,
taskProRealCenterListUrl,
taskSysRealCenterListUrl,
} from '@/api/requrls/project-management/taskCenter';
import type { CommonList, TableQueryParams } from '@/models/common';
import type { RealTaskCenterApiCaseItem, TimingTaskCenterApiCaseItem } from '@/models/projectManagement/taskCenter';
// 实时任务
export function getRealSysApiCaseList(data: TableQueryParams) {
return MSR.post<CommonList<RealTaskCenterApiCaseItem>>({ url: taskSysRealCenterListUrl, data });
}
export function getRealOrdApiCaseList(data: TableQueryParams) {
return MSR.post<CommonList<RealTaskCenterApiCaseItem>>({ url: taskOrgRealCenterListUrl, data });
}
export function getRealProApiCaseList(data: TableQueryParams) {
return MSR.post<CommonList<RealTaskCenterApiCaseItem>>({ url: taskProRealCenterListUrl, data });
}
// 定时任务
export function getScheduleSysApiCaseList(data: TableQueryParams) {
return MSR.post<CommonList<TimingTaskCenterApiCaseItem>>({ url: scheduleSysCenterListUrl, data });
}
export function getScheduleOrgApiCaseList(data: TableQueryParams) {
return MSR.post<CommonList<TimingTaskCenterApiCaseItem>>({ url: scheduleOrgCenterListUrl, data });
}
export function getScheduleProApiCaseList(data: TableQueryParams) {
return MSR.post<CommonList<TimingTaskCenterApiCaseItem>>({ url: scheduleProCenterListUrl, data });
}
export function deleteScheduleSysTask(id: string) {
return MSR.get({ url: `${deleteScheduleSysTaskUrl}/${id}` });
}
export default {};

View File

@ -1,6 +1,12 @@
// 系统全局类的接口
import MSR from '@/api/http/index';
import { GetVersionUrl, OrgOptionsUrl, PackageTypeUrl, SwitchOrgUrl } from '@/api/requrls/system';
import {
GetVersionUrl,
OrgOptionsUrl,
PackageTypeUrl,
SwitchOrgUrl,
userHasProjectPermissionUrl,
} from '@/api/requrls/system';
// 获取系统版本
export function getSystemVersion() {
@ -21,3 +27,8 @@ export function switchUserOrg(organizationId: string, userId: string) {
export function getPackageType() {
return MSR.get<string>({ url: PackageTypeUrl });
}
// 获取当前用户是否具备项目权限
export function getUserHasProjectPermission(userId: string) {
return MSR.get({ url: `${userHasProjectPermissionUrl}/${userId}` });
}

View File

@ -0,0 +1,31 @@
// 系统管理
// 任务中心-实时任务-接口用例/场景
export const taskSysRealCenterListUrl = '/task/center/api/system/real-time/page';
// 系统-任务中心-定时任务列表
export const scheduleSysCenterListUrl = '/task/center/system/schedule/page';
// 系统-任务中心-删除定时任务
export const deleteScheduleSysTaskUrl = '/task/center/schedule/delete';
// 组织管理
// 任务中心-实时任务-接口用例/场景
export const taskOrgRealCenterListUrl = '/task/center/api/org/real-time/page';
// 系统-任务中心-定时任务列表
export const scheduleOrgCenterListUrl = '/task/center/org/schedule/page';
// 项目管理
// 任务中心-实时任务-接口用例/场景
export const taskProRealCenterListUrl = '/task/center/api/project/real-time/page';
// 系统-任务中心-定时任务列表
export const scheduleProCenterListUrl = '/task/center/project/schedule/page';
export default {
taskSysRealCenterListUrl,
scheduleSysCenterListUrl,
deleteScheduleSysTaskUrl,
taskOrgRealCenterListUrl,
scheduleOrgCenterListUrl,
taskProRealCenterListUrl,
scheduleProCenterListUrl,
};

View File

@ -4,3 +4,4 @@ export const GetVersionUrl = '/system/version/current';
export const OrgOptionsUrl = '/system/organization/switch-option';
export const SwitchOrgUrl = '/system/organization/switch';
export const PackageTypeUrl = '/system/version/package-type';
export const userHasProjectPermissionUrl = '/project/has-permission'; // 判断当前用户是否还在项目里或者项目禁止有权限操作

View File

@ -626,9 +626,6 @@
}
/** 开关 **/
.arco-switch[disabled] {
background: var(--color-text-n8);
}
.arco-switch-type-line.arco-switch-small {
height: 14px;
line-height: 14px;

View File

@ -202,6 +202,7 @@
orgListLoading.value = true;
const res = await getOrgOptions();
originOrgList.value = res || [];
appStore.setOrdList(originOrgList.value);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);

View File

@ -19,7 +19,10 @@
<slot name="title">
<div class="flex w-full items-center justify-between">
<div class="flex items-center">
{{ props.title }}
<a-tooltip :content="props.title">
<span> {{ props.title }}</span>
</a-tooltip>
<slot name="headerLeft"></slot>
<a-tag v-if="titleTag" :color="props.titleTagColor" class="ml-[8px] mr-auto">
{{ props.titleTag }}

View File

@ -10,7 +10,8 @@ export const INPUT = {
field: 'fieldName',
value: '',
props: {
placeholder: t('formCreate.PleaseEnter'),
'placeholder': t('formCreate.PleaseEnter'),
'max-length': 255,
},
};
export const SELECT = {
@ -150,6 +151,7 @@ export const TEXTAREA = {
minRows: 1,
maxRows: 3,
},
'max-length': 1000,
},
};
export const JIRAKEY = {

View File

@ -1,5 +1,6 @@
<template>
<FormCreate v-model:api="fApi" :rule="formRuleList" :option="props.options || option"> </FormCreate>
<FormCreate v-model:api="fApi" :rule="formRuleList" :option="props.options || option" @change="changeHandler">
</FormCreate>
</template>
<script setup lang="ts">
@ -268,12 +269,16 @@
},
};
function changeHandler(value: any) {
fApi.value.validateField(value);
}
watch(
() => formRuleList.value,
(val) => {
if (val) emit('update:form-item', formRuleList.value);
fApi.value.refreshValidate();
fApi.value.clearValidateState();
if (val) {
emit('update:form-item', formRuleList.value);
}
},
{
deep: true,
@ -291,13 +296,4 @@
});
</script>
<style scoped>
:deep(.arco-form-item-status-success .arco-select-view:not(.arco-select-view-disabled).arco-select-view-focus) {
border-color: var(--color-text-input-border);
background: none;
}
:deep(.arco-form-item-status-success .arco-select-view:not(.arco-select-view-disabled)) {
border-color: var(--color-text-input-border);
background: transparent;
}
</style>
<style scoped></style>

View File

@ -13,6 +13,7 @@
hide-button
:formatter="handleFormatter"
@change="handleChange"
@enter="handleChange"
/>
<span v-if="$slots['jumper-append']" :class="`${prefixCls}-append`"><slot name="jumper-append" /></span>
<span :class="`${prefixCls}-total-page`" :style="{ 'min-width': totalPageWidth }">{{

View File

@ -75,7 +75,7 @@
</li>
<li>
<a-tooltip :content="t('settings.navbar.task')">
<a-button type="secondary">
<a-button type="secondary" @click="goTaskCenter">
<template #icon>
<icon-calendar-clock />
</template>
@ -135,6 +135,7 @@
</li>
</ul>
</div>
<TaskCenterModal v-model:visible="taskCenterVisible" />
</template>
<script lang="ts" setup>
@ -145,6 +146,7 @@
import TopMenu from '@/components/business/ms-top-menu/index.vue';
import MessageBox from '../message-box/index.vue';
import TaskCenterModal from './taskCenterModal.vue';
import { switchProject } from '@/api/modules/project-management/project';
import { MENU_LEVEL, type PathMapRoute } from '@/config/pathMap';
@ -234,6 +236,11 @@
});
refBtn.value.dispatchEvent(event);
};
const taskCenterVisible = ref<boolean>(false);
function goTaskCenter() {
taskCenterVisible.value = true;
}
</script>
<style scoped lang="less">

View File

@ -0,0 +1,58 @@
<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%',
}"
: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">
<TaskCenter group="system" mode="modal"></TaskCenter>
</div>
</a-modal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
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

@ -330,12 +330,19 @@ export const pathMap: PathMapItem[] = [
level: MENU_LEVEL[1],
},
{
key: 'SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL', // 系统设置-模板管理-详情
key: 'SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_CREATE', // 系统设置-模板管理-创建
locale: 'menu.settings.organization.templateManagementDetail',
route: RouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL,
permission: [],
level: MENU_LEVEL[1],
},
{
key: 'SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_UPDATE', // 系统设置-模板管理-更新模版
locale: 'menu.settings.organization.templateManagementEdit',
route: RouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL,
permission: [],
level: MENU_LEVEL[1],
},
{
key: 'SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_WORKFLOW', // 系统设置-模板管理-工作流
locale: 'menu.settings.organization.templateManagementWorkFlow',
@ -375,6 +382,13 @@ export const pathMap: PathMapItem[] = [
permission: [],
level: MENU_LEVEL[2],
},
// {
// key: 'PROJECT_MANAGEMENT_PERMISSION_VERSION', // 项目管理-项目与权限-项目版本
// locale: 'project.permission.projectVersion',
// route: RouteEnum.PROJECT_MANAGEMENT_PERMISSION_VERSION,
// permission: [],
// level: MENU_LEVEL[2],
// },
{
key: 'PROJECT_MANAGEMENT_PERMISSION_MEMBER', // 项目管理-项目与权限-成员
locale: 'project.permission.member',
@ -413,12 +427,19 @@ export const pathMap: PathMapItem[] = [
level: MENU_LEVEL[2],
},
{
key: 'PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL', // 项目管理-模板管理-详情
key: 'PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_CREATE', // 项目管理-模板管理-创建模版
locale: 'menu.settings.organization.templateManagementDetail',
route: RouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL,
permission: [],
level: MENU_LEVEL[2],
},
{
key: 'PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_UPDATE', // 项目管理-模板管理-更新模版
locale: 'menu.settings.organization.templateManagementEdit',
route: RouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL,
permission: [],
level: MENU_LEVEL[2],
},
{
key: 'PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_WORKFLOW', // 项目管理-模板管理-工作流
locale: 'menu.settings.organization.templateManagementWorkFlow',
@ -490,7 +511,7 @@ export const pathMap: PathMapItem[] = [
],
},
// 测试计划
/* {
{
key: 'TEST_PLAN', // 测试计划
locale: 'menu.testPlan',
route: RouteEnum.TEST_PLAN,
@ -505,7 +526,7 @@ export const pathMap: PathMapItem[] = [
level: MENU_LEVEL[2],
},
],
}, */
},
{
key: 'PERSONAL_INFORMATION', // 个人信息
locale: 'ms.personal',
@ -550,4 +571,12 @@ export const pathMap: PathMapItem[] = [
},
],
},
// 系统
{
key: 'SYSTEM',
locale: 'menu.settings.system', // 系统
route: '',
permission: [],
level: MENU_LEVEL[0],
},
];

View File

@ -34,6 +34,7 @@ export enum ProjectManagementRouteEnum {
PROJECT_MANAGEMENT_MESSAGE_MANAGEMENT = 'projectManagementMessageManagement',
PROJECT_MANAGEMENT_COMMON_SCRIPT = 'projectManagementCommonScript',
PROJECT_MANAGEMENT_MESSAGE_MANAGEMENT_EDIT = 'projectManagementMessageManagementEdit',
PROJECT_MANAGEMENT_TASK_CENTER = 'projectManagementTaskCenter',
PROJECT_MANAGEMENT_LOG = 'projectManagementLog',
PROJECT_MANAGEMENT_PERMISSION = 'projectManagementPermission',
PROJECT_MANAGEMENT_PERMISSION_BASIC_INFO = 'projectManagementPermissionBasicInfo',
@ -86,6 +87,7 @@ export enum SettingRouteEnum {
SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_WORKFLOW = 'settingOrganizationTemplateWorkFlow',
SETTING_ORGANIZATION_SERVICE = 'settingOrganizationService',
SETTING_ORGANIZATION_LOG = 'settingOrganizationLog',
SETTING_ORGANIZATION_TASK_CENTER = 'settingOrganizationTaskCenter',
}
export const RouteEnum = {

View File

@ -0,0 +1,26 @@
// 模板展示字段icon
export enum TaskCenterEnum {
API_CASE = 'API_CASE', // 接口用例
API_SCENARIO = 'API_SCENARIO', // 接口场景
UI_TEST = 'UI_TEST', // ui测试
LOAD_TEST = 'LOAD_TEST', // 性能测试
TEST_PLAN = 'TEST_PLAN', // 测试计划
TEST_RESOURCE = 'TEST_RESOURCE', // 测试资源
API_IMPORT = 'API_IMPORT', // API导入
}
// 执行方式
export enum ExecutionMethods {
SCHEDULE = 'SCHEDULE', // 定时任务
MANUAL = 'MANUAL', // 手动执行
API = 'API', // 接口调用
BATCH = 'API', // 批量执行
}
export enum ExecutionMethodsLabel {
SCHEDULE = 'project.taskCenter.scheduledTask', // 定时任务
MANUAL = 'project.taskCenter.manualExecution', // 手动执行
API = 'project.taskCenter.interfaceCall', // 接口调用
BATCH = 'project.taskCenter.batchExecution', // 批量执行
}
export default {};

View File

@ -49,6 +49,7 @@ export default {
'menu.loadTest': 'Performance Test',
'menu.projectManagement.projectPermission': 'Project Permission',
'menu.projectManagement.log': 'Log',
'menu.projectManagement.taskCenter': 'The task center',
'menu.projectManagement.environmentManagement': 'EnvironmentManagement',
'menu.settings': 'Settings',
'menu.settings.system': 'System',

View File

@ -33,6 +33,7 @@ export default {
'menu.projectManagement': '项目管理',
'menu.projectManagement.templateManager': '模板管理',
'menu.projectManagement.log': '日志',
'menu.projectManagement.taskCenter': '任务中心',
'menu.projectManagement.fileManagement': '文件管理',
'menu.projectManagement.messageManagement': '消息管理',
'menu.projectManagement.commonScript': '公共脚本',

View File

@ -0,0 +1,32 @@
// 实时
export interface RealTaskCenterApiCaseItem {
organizationName: string; // 所属组织
projectName: string;
projectId: string;
id: string;
resourceId: string;
resourceName: string; // 资源名称 单独报告显示模块名称 集合报告显示报告名称
triggerMode: string; // 触发模式(手动,定时,批量,测试计划)
poolName: string; // 资源池名称
status: string; // 执行状态/SUCCESS/ERROR
operationName: string; // 操作人
operationTime: string;
integrated: boolean; // 是否为集合报告
}
// 定时任务
export interface TimingTaskCenterApiCaseItem {
organizationName: string;
projectName: string;
projectId: string;
id: string;
taskName: string; // 任务名称
resourceId: string; // 资源Id
resourceNum: number; // 资源业务id
resourceName: string; // 资源名称
resourceType: string; // 资源分类
value: string;
nextTime: string; // 下次执行时间
enable: true; // 任务状态
createUserName: string;
createTime: string;
}

View File

@ -58,7 +58,7 @@ export interface UpdatePluginModel {
global?: boolean | string; // 是否选择全部组织
description?: string;
enable?: boolean;
organizationIds?: Array<string | number>; // 指定组织
organizationIds?: string[]; // 指定组织
[key: string]: any;
}

View File

@ -14,10 +14,8 @@ export default function setupUserLoginInfoGuard(router: Router) {
const appStore = useAppStore();
if (!appStore.packageType) {
await appStore.initSystemPackage();
next();
} else {
next();
}
next();
} else {
// 未登录的都直接跳转至登录页,访问的页面地址缓存到 query 上
if (to.name === 'login') {

View File

@ -289,6 +289,17 @@ const ProjectManagement: AppRouteRecordRaw = {
isTopMenu: true,
},
},
// 任务中心
{
path: 'taskCenter',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TASK_CENTER,
component: () => import('@/views/project-management/taskCenter/index.vue'),
meta: {
locale: 'menu.projectManagement.taskCenter',
roles: ['*'],
isTopMenu: true,
},
},
// 菜单管理-误报规则
{
path: 'errorReportRule',

View File

@ -322,6 +322,17 @@ const Setting: AppRouteRecordRaw = {
isTopMenu: true,
},
},
// 任务中心
{
path: 'taskCenter',
name: SettingRouteEnum.SETTING_ORGANIZATION_TASK_CENTER,
component: () => import('@/views/setting/organization/taskCenter/index.vue'),
meta: {
locale: 'menu.projectManagement.taskCenter',
roles: ['*'],
isTopMenu: true,
},
},
],
},
],

View File

@ -5,19 +5,19 @@ import { cloneDeep } from 'lodash-es';
import type { BreadcrumbItem } from '@/components/business/ms-breadcrumb/types';
import { getProjectInfo } from '@/api/modules/project-management/basicInfo';
import { getProjectList } from '@/api/modules/project-management/project';
import { getPageConfig } from '@/api/modules/setting/config';
import { getPackageType, getSystemVersion } from '@/api/modules/system';
import { getPackageType, getSystemVersion, getUserHasProjectPermission } from '@/api/modules/system';
import { getMenuList } from '@/api/modules/user';
import defaultSettings from '@/config/settings.json';
import { useI18n } from '@/hooks/useI18n';
import { NO_PROJECT_ROUTE_NAME } from '@/router/constants';
import { NO_PROJECT_ROUTE_NAME, NO_RESOURCE_ROUTE_NAME, WHITE_LIST } from '@/router/constants';
import { watchStyle, watchTheme } from '@/utils/theme';
import type { PageConfig, PageConfigKeys, Style, Theme } from '@/models/setting/config';
import { ProjectListItem } from '@/models/setting/project';
import useUserStore from '../user';
import type { AppState } from './types';
import type { NotificationReturn } from '@arco-design/web-vue/es/notification/interface';
import type { RouteRecordNormalized, RouteRecordRaw } from 'vue-router';
@ -64,6 +64,7 @@ const useAppStore = defineStore('app', {
},
packageType: '',
projectList: [] as ProjectListItem[],
ordList: [],
}),
getters: {
@ -206,6 +207,12 @@ const useAppStore = defineStore('app', {
setCurrentProjectId(id: string) {
this.currentProjectId = id;
},
/**
*
*/
setOrdList(ordList: { id: string; name: string }[]) {
this.ordList = ordList;
},
/**
*
*/
@ -240,6 +247,32 @@ const useAppStore = defineStore('app', {
console.log(error);
}
},
async validateUserProjectPermission() {
try {
const router = useRouter();
const HasProjectPermission = await getUserHasProjectPermission(this.currentProjectId);
if (!HasProjectPermission) {
// 没有项目权限(用户所在的当前项目被禁用&用户被移除出去该项目)
router.push({
name: NO_PROJECT_ROUTE_NAME,
});
return false;
}
const res = await getProjectInfo(this.currentProjectId);
if (res.deleted) {
// 如果项目被删除或者被禁用,跳转到无项目页面
router.push({
name: NO_PROJECT_ROUTE_NAME,
});
return false;
}
return true;
} catch (error) {
console.log(error);
return false;
}
},
/**
*
*/

View File

@ -39,6 +39,7 @@ export interface AppState {
currentMenuConfig: string[];
packageType: string;
projectList: ProjectListItem[];
ordList: { id: string; name: string }[];
}
export interface UploadFileTaskState {

View File

@ -55,10 +55,6 @@ const useFeatureCaseStore = defineStore('featureCase', {
console.log(error);
}
},
// 设置是否是编辑或者新增成功状态
setIsAlreadySuccess(state: boolean) {
this.operatingState = state;
},
// 设置菜单
setTab(list: TabItemType[]) {
this.tabSettingList = list;
@ -85,6 +81,15 @@ const useFeatureCaseStore = defineStore('featureCase', {
};
});
},
// 初始化count
initCountMap(countMap: Record<string, any>) {
this.tabSettingList = this.tabSettingList.map((item) => {
return {
...item,
total: countMap[item.key] || 0,
};
});
},
},
});

View File

@ -51,6 +51,7 @@
try {
if (appStore.getCurrentOrgId && hasAnyPermission(['PROJECT_BASE_INFO:READ'])) {
const res = await getProjectList(appStore.getCurrentOrgId); // TODO:
projectList.value = res;
} else {
projectList.value = [];
}

View File

@ -455,7 +455,7 @@
color: rgb(var(--danger-6));
}
}
:deep(.active .arco-badge-number) {
:deep(.active .arco-badge-text) {
background: rgb(var(--primary-5));
}
</style>

View File

@ -51,7 +51,6 @@
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const appStore = useAppStore();
const featureCaseStore = useFeatureCaseStore();
@ -63,27 +62,6 @@
fileList: [],
});
const initDetail = {
id: '',
templateId: '',
name: '',
prerequisite: '', // prerequisite
caseEditType: 'STEP', // /
steps: '',
textDescription: '',
expectedResult: '', //
description: '',
publicCase: false, //
moduleId: '',
versionId: '',
tags: [],
projectId: appStore.currentProjectId,
customFields: {}, //
relateFileMetaIds: [], // ID
deleteFileMetaIds: [], // id
unLinkFilesIds: [], // id
};
const title = ref('');
const loading = ref(false);
const isEdit = computed(() => !!route.query.id);
@ -99,6 +77,7 @@
//
if (route.params.mode === 'edit') {
await updateCaseRequest(caseDetailInfo.value);
featureCaseStore.setModuleId([caseDetailInfo.value.request.moduleId]);
Message.success(t('caseManagement.featureCase.editSuccess'));
router.push({
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE,
@ -118,7 +97,6 @@
}
createSuccessId.value = res.data.id;
Message.success(route.params.mode === 'copy' ? t('ms.description.copySuccess') : t('common.addSuccess'));
featureCaseStore.setIsAlreadySuccess(true);
isShowTip.value = !getIsVisited();
if (isReview) {
router.back();

View File

@ -91,7 +91,14 @@
</template>
<template #default>
<div ref="wrapperRef" class="wrapperRef bg-white">
<MsSplitBox ref="wrapperRef" expand-direction="right" :max="0.7" :min="0.7" :size="900">
<MsSplitBox
ref="wrapperRef"
:class="isFullScreen ? 'h-[100%]' : 'h-[calc(100% - 78px)]'"
expand-direction="right"
:max="0.7"
:min="0.7"
:size="900"
>
<template #first>
<div class="leftWrapper">
<div class="header h-[50px]">
@ -103,7 +110,6 @@
<a-badge
class="ml-1"
:class="activeTab === tab.key ? 'active' : ''"
:count="1000"
:text="getTotal(tab.total)"
/> </div
></a-menu-item>
@ -182,17 +188,17 @@
</div>
</template>
</MsSplitBox>
<inputComment
v-model:content="content"
v-model:notice-user-ids="noticeUserIds"
v-permission="['FUNCTIONAL_CASE:READ+COMMENT']"
:is-active="isActive"
is-show-avatar
is-use-bottom
@publish="publishHandler"
@cancel="cancelPublish"
/>
</div>
<inputComment
v-model:content="content"
v-model:notice-user-ids="noticeUserIds"
v-permission="['FUNCTIONAL_CASE:READ+COMMENT']"
:is-active="isActive"
is-show-avatar
is-use-bottom
@publish="publishHandler"
@cancel="cancelPublish"
/>
</template>
</MsDetailDrawer>
<SettingDrawer ref="settingDrawerRef" v-model:visible="showSettingDrawer" />
@ -212,7 +218,7 @@
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
import inputComment from '@/components/business/ms-comment/input.vue';
import { CommentItem, CommentParams } from '@/components/business/ms-comment/types';
import { CommentParams } from '@/components/business/ms-comment/types';
import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue';
import SettingDrawer from './tabContent/settingDrawer.vue';
import TabDefect from './tabContent/tabBug/tabDefect.vue';
@ -334,9 +340,24 @@
const detailInfo = ref<DetailCase>({ ...initDetail });
const customFields = ref<CustomAttributes[]>([]);
const caseLevels = ref<CaseLevel>('P0');
// count
function setCount(detail: DetailCase) {
const { bugCount, caseCount, caseReviewCount, demandCount, relateEdgeCount, testPlanCount } = detail;
const countMap: Record<string, any> = {
case: caseCount,
dependency: relateEdgeCount,
caseReview: caseReviewCount,
testPlan: testPlanCount,
bug: bugCount,
requirement: demandCount,
};
featureCaseStore.initCountMap(countMap);
}
function loadedCase(detail: DetailCase) {
getCaseTree();
detailInfo.value = { ...detail };
setCount(detail);
customFields.value = detailInfo.value.customFields;
caseLevels.value = getCaseLevels(customFields.value) as CaseLevel;
}
@ -476,6 +497,23 @@
tabDetailRef.value.handleOK();
}
function getTotal(total: number): string {
if (total === 0) {
return '0';
}
if (total && total !== 0) {
if (total <= 99) {
return String(total);
}
if (total > 99) {
return `${total}+`;
}
}
return `${total}+`;
}
watch(
() => customFields.value,
() => {
@ -544,13 +582,6 @@
tabDetailRef.value.handleOK();
}, 300);
function getTotal(total: number) {
if (total <= 99) {
return String(total);
}
return `${total}+`;
}
watch(
() => props.detailId,
(val) => {
@ -562,9 +593,6 @@
</script>
<style scoped lang="less">
.wrapperRef {
height: calc(100% - 78px);
}
:deep(.arco-menu-light) {
height: 50px;
background: none !important;

View File

@ -9,7 +9,7 @@
:row-count="filterRowCount"
@keyword-search="fetchData"
@adv-search="handleAdvSearch"
@reset="fetchData"
@refresh="fetchData()"
>
<template #left>
<div class="text-[var(--color-text-1)]"
@ -524,14 +524,14 @@
label: 'caseManagement.featureCase.associatedDemand',
eventTag: 'associatedDemand',
},
{
label: 'caseManagement.featureCase.generatingDependencies',
eventTag: 'generatingDependencies',
},
{
label: 'caseManagement.featureCase.addToPublic',
eventTag: 'addToPublic',
},
// {
// label: 'caseManagement.featureCase.generatingDependencies',
// eventTag: 'generatingDependencies',
// },
// {
// label: 'caseManagement.featureCase.addToPublic',
// eventTag: 'addToPublic',
// },
{
isDivider: true,
},
@ -703,9 +703,10 @@
//
function emitTableParams() {
const moduleIds = props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds];
emit('init', {
keyword: keyword.value,
moduleIds: [],
moduleIds,
projectId: currentProjectId.value,
current: propsRes.value.msPagination?.current,
pageSize: propsRes.value.msPagination?.pageSize,
@ -727,7 +728,7 @@
if (props.activeFolder === 'all') {
searchParams.value.moduleIds = [];
} else {
searchParams.value.moduleIds = [moduleId.value, ...props.offspringIds];
searchParams.value.moduleIds = [...featureCaseStore.moduleId, ...props.offspringIds];
}
setLoadListParams({
...searchParams.value,
@ -838,7 +839,7 @@
excludeIds: batchParams.value?.excludeIds || [],
condition: { keyword: keyword.value },
projectId: currentProjectId.value,
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder],
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
moduleId: selectedModuleKeys.value[0],
};
if (isMove.value) {
@ -1029,7 +1030,7 @@
...customFieldsColumns,
...columns.slice(columns.length - 1, columns.length),
];
tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_TABLE, fullColumns, 'drawer');
await tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_TABLE, fullColumns, 'drawer');
tableRef.value?.initColumn(fullColumns);
}

View File

@ -181,6 +181,7 @@
asterisk-position="end"
:label="t('system.orgTemplate.modules')"
:rules="[{ required: true, message: t('system.orgTemplate.moduleRuleTip') }]"
@change="changeSelectModule"
>
<a-tree-select
v-model="form.moduleId"
@ -592,6 +593,10 @@
},
});
function changeSelectModule(value: string) {
featureCaseStore.setModuleId([value]);
}
//
watch(
() => fileList.value,
@ -616,7 +621,6 @@
if (val) {
params.value.request = { ...form.value };
emit('update:formModeValue', params.value);
featureCaseStore.setModuleId([form.value.moduleId]);
}
}
},

View File

@ -22,20 +22,26 @@
}}<span class="mx-1 font-medium text-[rgb(var(--danger-6))]">{{ validateResultInfo.failCount }}</span
>{{ t('caseManagement.featureCase.caseCount') }}</span
>
<a-popover position="bottom">
<a-popover
position="bottom"
:content-style="{
padding: '0px',
}"
>
<span v-if="validateResultInfo.failCount" class="font-medium text-[rgb(var(--primary-5))]">{{
t('caseManagement.featureCase.viewErrorDetail')
}}</span>
<template #title>
<div class="w-[440px]"
<div class="w-[440px] px-4 pt-4"
>{{ t('caseManagement.featureCase.someCaseImportFailed') }}
<span class="text-[var(--color-text-4)]">({{ validateResultInfo.failCount }})</span></div
<span class="text-[14px] font-medium text-[var(--color-text-4)]"
>({{ validateResultInfo.failCount }})</span
></div
>
</template>
<template #content>
<div class="w-[440px]">
<a-divider class="mx-0 my-0" />
<div class="max-h-[400px] overflow-hidden">
<div class="max-h-[400px] overflow-hidden px-4">
<div
v-for="(item, index) of validateResultInfo.errorMessages"
:key="`${item.rowNum}-${index}`"
@ -44,9 +50,9 @@
{{ item.errMsg }}
</div>
</div>
<a-button class="mt-[8px]" type="text" long @click="showMore">{{
<div class="moreBtn h-[40px] text-[14px]" type="text" long @click="showMore">{{
t('caseManagement.featureCase.ViewMore')
}}</a-button>
}}</div>
</div>
</template>
</a-popover>
@ -77,13 +83,11 @@
</div>
</template>
</a-modal>
<MsDrawer
v-model:visible="showMoreFailureCase"
:title="t('caseManagement.featureCase.cancelValidateSuccess', { number: validateResultInfo.failCount })"
:width="960"
:footer="false"
no-content-padding
>
<MsDrawer v-model:visible="showMoreFailureCase" :width="960" :footer="false" no-content-padding>
<template #title>
{{ t('caseManagement.featureCase.importFailedCases')
}}<span class="text-[var(--color-text-4)]">({{ validateResultInfo.failCount }})</span>
</template>
<MsList
mode="static"
:virtual-list-props="{
@ -203,4 +207,12 @@
background: var(--color-text-input-border);
@apply mr-2 mt-2;
}
.moreBtn {
color: rgb(var(--primary-5));
box-shadow: 0 -1px 4px rgba(31 35 41/10%);
@apply mt-2 flex items-center justify-center;
}
:deep(.arco-popover-popup-content) {
padding: 0;
}
</style>

View File

@ -77,7 +77,7 @@
</template>
</MsAdvanceFilter>
<ms-base-table
class="mb-4"
class="my-4"
v-bind="propsRes"
:action-config="tableBatchActions"
@selected-change="handleTableSelect"

View File

@ -136,7 +136,11 @@
import ValidateModal from './components/export/validateModal.vue';
import ValidateResult from './components/export/validateResult.vue';
import { createCaseModuleTree, importExcelCase, importExcelChecked } from '@/api/modules/case-management/featureCase';
import featureCase, {
createCaseModuleTree,
importExcelCase,
importExcelChecked,
} from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import useFeatureCaseStore from '@/store/modules/case/featureCase';
@ -266,16 +270,6 @@
});
}
//
router.beforeEach((to: any, from: any, next) => {
const routeEnumValues = Object.values(CaseManagementRouteEnum);
if (!routeEnumValues.includes(to.name)) {
//
featureCaseStore.setIsAlreadySuccess(false);
}
next();
});
const showExcelModal = ref<boolean>(false);
const validateType = ref<'Excel' | 'Xmind'>('Excel');
@ -389,12 +383,6 @@
importLoading.value = false;
}
}
onMounted(() => {
if (featureCaseStore.operatingState) {
[activeFolder.value] = featureCaseStore.moduleId;
}
});
</script>
<style scoped lang="less">

View File

@ -217,7 +217,7 @@ export default {
'Check, the original use case will be overwritten when the ID is the same',
'caseManagement.featureCase.notSelectedRecoverCase': 'If the ID already exists, the use case is skipped',
'caseManagement.featureCase.cancelValidate': 'Cancel the check',
'caseManagement.featureCase.cancelValidateSuccess': 'Cancel check successfully',
'caseManagement.featureCase.importFailedCases': 'Import failed use cases {number}',
'caseManagement.featureCase.importFailedCountTitle': 'Import failure use case ({number})',
'caseManagement.featureCase.beforeUploadTip':
'Before uploading, please click edit content {type} in the template format',

View File

@ -213,7 +213,7 @@ export default {
'caseManagement.featureCase.selectedRecoverCase': '勾选ID相同时覆盖原用例',
'caseManagement.featureCase.notSelectedRecoverCase': '不勾选ID已存在时跳过该用例',
'caseManagement.featureCase.cancelValidate': '取消校验',
'caseManagement.featureCase.cancelValidateSuccess': '取消校验成功',
'caseManagement.featureCase.importFailedCases': '导入失败用例 {number}',
'caseManagement.featureCase.importFailedCountTitle': '导入失败用例({number})',
'caseManagement.featureCase.beforeUploadTip': '上传前请先按 { type } 模板中的格式编辑内容',
'caseManagement.featureCase.downloadTemplate': '下载 {type} 模板',

View File

@ -0,0 +1,273 @@
<template>
<div class="px-[16px]">
<div class="mb-4 flex items-center justify-between">
<span>{{ t('project.taskCenter.apiCaseList', { type: props.name }) }}</span>
<a-input-search
v-model:model-value="keyword"
:placeholder="t('caseManagement.featureCase.searchByNameAndId')"
allow-clear
class="mx-[8px] w-[240px]"
@search="searchList"
@press-enter="searchList"
></a-input-search>
</div>
<ms-base-table
v-bind="propsRes"
ref="tableRef"
:action-config="tableBatchActions"
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<template #statusFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="statusFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<a-button type="text" class="arco-btn-text--secondary p-[8px_4px]" @click="statusFilterVisible = true">
<div class="font-medium">
{{ t(columnConfig.title as string) }}
</div>
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</a-button>
<template #content>
<div class="arco-table-filters-content">
<div class="flex items-center justify-center px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="statusListFilters" direction="vertical" size="small">
<a-checkbox v-for="key of statusFilters" :key="key" :value="key">
<ExecutionStatus :module-type="props.moduleType" :status="key" />
</a-checkbox>
</a-checkbox-group>
</div>
</div>
</template>
</a-trigger>
</template>
<template #status="{ record }">
<ExecutionStatus :module-type="props.moduleType" :status="record.status" />
</template>
<template #triggerMode="{ record }">
<span>{{ t(ExecutionMethodsLabel[record.triggerMode]) }}</span>
</template>
<template #operation="{ record }">
<MsButton class="!mr-0" @click="stop(record)">{{ t('project.taskCenter.stop') }}</MsButton>
<a-divider direction="vertical" />
<MsButton class="!mr-0" @click="execution(record)">{{ t('project.taskCenter.execution') }}</MsButton>
<MsButton class="!mr-0">{{ t('project.taskCenter.viewReport') }}</MsButton>
</template>
</ms-base-table>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import 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 {
getRealOrdApiCaseList,
getRealProApiCaseList,
getRealSysApiCaseList,
} from '@/api/modules/project-management/taskCenter';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import { ExecutionMethodsLabel, TaskCenterEnum } from '@/enums/taskCenter';
import { TaskStatus } from './utils';
const { openModal } = useModal();
const { t } = useI18n();
const props = defineProps<{
group: 'system' | 'organization' | 'project';
moduleType: keyof typeof TaskCenterEnum;
name: string;
}>();
const keyword = ref<string>('');
const statusFilterVisible = ref(false);
const statusListFilters = ref<string[]>(Object.keys(TaskStatus[props.moduleType]));
const filterOptions = computed(() => {
return statusListFilters.value.map((item) => {
return {
label: item,
value: item,
};
});
});
const loadRealMap = ref({
system: getRealSysApiCaseList,
organization: getRealOrdApiCaseList,
project: getRealProApiCaseList,
});
const columns: MsTableColumn = [
{
title: 'project.taskCenter.resourceID',
dataIndex: 'resourceId',
slotName: 'resourceId',
width: 300,
showInTable: true,
},
{
title: 'project.taskCenter.resourceName',
slotName: 'resourceName',
dataIndex: 'resourceName',
width: 200,
showDrag: true,
},
{
title: 'project.taskCenter.executionResult',
dataIndex: 'status',
slotName: 'status',
titleSlotName: 'statusFilter',
// filterConfig: {
// filterSlotName: 'status', // slotName
// multiple: true, //
// options: filterOptions.value,
// },
showInTable: true,
width: 150,
showDrag: true,
},
{
title: 'project.taskCenter.executionMode',
dataIndex: 'triggerMode',
slotName: 'triggerMode',
showInTable: true,
isTag: true,
width: 150,
showDrag: true,
},
{
title: 'project.taskCenter.resourcePool',
slotName: 'poolName',
dataIndex: 'poolName',
showInTable: true,
width: 200,
showDrag: true,
},
{
title: 'project.taskCenter.operator',
slotName: 'operationName',
dataIndex: 'operationName',
showInTable: true,
width: 300,
showDrag: true,
},
{
title: 'project.taskCenter.operating',
dataIndex: 'operationTime',
slotName: 'operationTime',
width: 180,
showDrag: true,
},
{
title: 'common.operation',
slotName: 'operation',
dataIndex: 'operation',
width: 120,
fixed: 'right',
},
];
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, setProps } = useTable(
loadRealMap.value[props.group],
{
columns,
scroll: {
x: '100%',
},
showSetting: false,
selectable: true,
heightUsed: 300,
enableDrag: true,
showSelectAll: true,
}
);
function initData() {
setLoadListParams({
keyword: keyword.value,
moduleType: props.moduleType,
});
loadList();
}
const tableBatchActions = {
baseAction: [
{
label: 'project.taskCenter.batchStop',
eventTag: 'batchStop',
},
{
label: 'project.taskCenter.batchExecution',
eventTag: 'batchExecution',
},
],
};
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {}
function searchList() {
resetSelector();
initData();
}
function stop(record: any) {
openModal({
type: 'warning',
title: t('project.taskCenter.batchStopTask', { num: 3 }),
content: t('project.taskCenter.stopTaskContent'),
okText: t('project.taskCenter.confirmStop'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
resetSelector();
Message.success(t('project.taskCenter.stopSuccess'));
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
function execution(record: any) {}
onBeforeMount(() => {
initData();
});
const statusFilters = computed(() => {
return Object.keys(TaskStatus[props.moduleType]);
});
function handleFilterHidden(val: boolean) {
if (!val) {
initData();
}
}
watch(
() => props.moduleType,
(val) => {
if (val) {
resetSelector();
initData();
}
}
);
</script>
<style scoped></style>

View File

@ -0,0 +1,178 @@
<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>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import { TaskCenterEnum } from '@/enums/taskCenter';
const { t } = useI18n();
const props = defineProps<{
status: string;
moduleType: keyof typeof TaskCenterEnum;
}>();
export interface IconType {
icon: string;
label: string;
color?: string;
}
const iconTypeStatus = ref({
[TaskCenterEnum.API_CASE]: {
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: 'project.taskCenter.successful',
},
ERROR: {
icon: 'icon-icon_close_colorful',
label: 'project.taskCenter.failure',
},
FAKE_ERROR: {
icon: 'icon-icon_warning_colorful',
label: 'project.taskCenter.falseAlarm',
},
STOPPED: {
icon: 'icon-icon_block_filled',
label: 'project.taskCenter.stop',
color: '!text-[var(--color-text-input-border)]',
},
RUNNING: {
icon: 'icon-icon_testing',
label: 'project.taskCenter.inExecution',
color: '!text-[rgb(var(--link-6))]',
},
RERUNNING: {
icon: 'icon-icon_testing',
label: 'project.taskCenter.rerun',
color: '!text-[rgb(var(--link-6))]',
},
PENDING: {
icon: 'icon-icon_wait',
label: 'project.taskCenter.queuing',
color: '!text-[rgb(var(--link-6))]',
},
},
[TaskCenterEnum.API_SCENARIO]: {
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: 'project.taskCenter.successful',
},
ERROR: {
icon: 'icon-icon_close_colorful',
label: 'project.taskCenter.failure',
},
FAKE_ERROR: {
icon: 'icon-icon_warning_colorful',
label: 'project.taskCenter.falseAlarm',
},
STOPPED: {
icon: 'icon-icon_block_filled',
label: 'project.taskCenter.stop',
color: '!text-[var(--color-text-input-border)]',
},
RUNNING: {
icon: 'icon-icon_testing',
label: 'project.taskCenter.inExecution',
color: '!text-[rgb(var(--link-6))]',
},
RERUNNING: {
icon: 'icon-icon_testing',
label: 'project.taskCenter.rerun',
color: '!text-[rgb(var(--link-6))]',
},
PENDING: {
icon: 'icon-icon_wait',
label: 'project.taskCenter.queuing',
color: '!text-[rgb(var(--link-6))]',
},
},
[TaskCenterEnum.LOAD_TEST]: {
STARTING: {
icon: 'icon-icon_restarting',
label: 'project.taskCenter.starting',
color: '!text-[rgb(var(--link-6))]',
},
RUNNING: {
icon: 'icon-icon_testing',
label: 'project.taskCenter.inExecution',
color: '!text-[rgb(var(--link-6))]',
},
ERROR: {
icon: 'icon-icon_close_colorful',
label: 'project.taskCenter.failure',
},
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: 'project.taskCenter.successful',
},
COMPLETED: {
icon: 'icon-icon_succeed_colorful',
label: 'project.taskCenter.complete',
},
STOPPED: {
icon: 'icon-icon_block_filled',
label: 'project.taskCenter.stop',
color: '!text-[var(--color-text-input-border)]',
},
},
[TaskCenterEnum.UI_TEST]: {
PENDING: {
icon: 'icon-icon_wait',
label: 'project.taskCenter.queuing',
color: '!text-[rgb(var(--link-6))]',
},
RUNNING: {
icon: 'icon-icon_testing',
label: 'project.taskCenter.inExecution',
color: '!text-[rgb(var(--link-6))]',
},
RERUNNING: {
icon: 'icon-icon_testing',
label: 'project.taskCenter.rerun',
color: '!text-[rgb(var(--link-6))]',
},
ERROR: {
icon: 'icon-icon_close_colorful',
label: 'project.taskCenter.failure',
},
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: 'project.taskCenter.successful',
},
STOPPED: {
icon: 'icon-icon_block_filled',
label: 'project.taskCenter.stop',
color: '!text-[var(--color-text-input-border)]',
},
},
[TaskCenterEnum.TEST_PLAN]: {
RUNNING: {
icon: 'icon-icon_testing',
label: 'project.taskCenter.queuing',
color: '!text-[rgb(var(--link-6))]',
},
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: 'project.taskCenter.successful',
},
STARTING: {
icon: 'icon-icon_restarting',
label: 'project.taskCenter.starting',
color: '!text-[rgb(var(--link-6))]',
},
},
});
function getExecutionResult(): IconType {
return iconTypeStatus.value[props.moduleType][props.status];
}
</script>
<style scoped></style>

View File

@ -0,0 +1,211 @@
<template>
<div class="p-4 pt-0">
<div class="mb-4 flex items-center justify-between">
<!-- TODO这个版本不上 -->
<a-button type="primary">
{{ t('project.taskCenter.createTask') }}
</a-button>
<a-input-search
v-model:model-value="keyword"
:placeholder="t('caseManagement.featureCase.searchByNameAndId')"
allow-clear
class="mx-[8px] w-[240px]"
@search="searchList"
@press-enter="searchList"
></a-input-search>
</div>
<ms-base-table
v-bind="propsRes"
ref="tableRef"
:action-config="tableBatchActions"
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<template #resourceNum="{ record }">
<a-button type="text" class="flex w-full">{{ record.resourceNum }}</a-button>
</template>
<template #resourceName="{ record }">
<a-button type="text" class="flex w-full">{{ record.resourceName }}</a-button>
</template>
<template #operation="{ record }">
<a-switch v-model="record.enable" size="small" type="line" />
<!-- 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 { ref } from 'vue';
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 MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import {
getScheduleOrgApiCaseList,
getScheduleProApiCaseList,
getScheduleSysApiCaseList,
} from '@/api/modules/project-management/taskCenter';
import { useI18n } from '@/hooks/useI18n';
import { TaskCenterEnum } from '@/enums/taskCenter';
const { t } = useI18n();
const props = defineProps<{
group: 'system' | 'organization' | 'project';
moduleType: keyof typeof TaskCenterEnum;
name: string;
}>();
const keyword = ref<string>('');
const columns: MsTableColumn = [
{
title: 'project.taskCenter.resourceID',
dataIndex: 'resourceNum',
slotName: 'resourceNum',
width: 300,
showInTable: true,
},
{
title: 'project.taskCenter.resourceName',
slotName: 'resourceName',
dataIndex: 'resourceName',
width: 200,
showDrag: true,
},
{
title: 'project.taskCenter.resourceClassification',
dataIndex: 'resourceType',
slotName: 'resourceType',
showInTable: true,
width: 150,
showDrag: true,
},
{
title: 'project.taskCenter.operationRule',
dataIndex: 'value',
slotName: 'value',
showInTable: true,
isTag: true,
width: 150,
showDrag: true,
},
{
title: 'project.taskCenter.operator',
slotName: 'createUserName',
dataIndex: 'createUserName',
showInTable: true,
width: 200,
showDrag: 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: 300,
showDrag: true,
},
{
title: 'common.operation',
slotName: 'operation',
dataIndex: 'operation',
width: 120,
fixed: 'right',
},
];
const loadRealMap = ref({
system: getScheduleSysApiCaseList,
organization: getScheduleOrgApiCaseList,
project: getScheduleProApiCaseList,
});
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, setProps } = useTable(
loadRealMap.value[props.group],
{
columns,
scroll: {
x: '100%',
},
showSetting: false,
selectable: true,
heightUsed: 300,
enableDrag: true,
showSelectAll: true,
}
);
function initData() {
setLoadListParams({
keyword: keyword.value,
moduleType: props.moduleType,
});
loadList();
}
function searchList() {
resetSelector();
initData();
}
const tableBatchActions = {
baseAction: [
{
label: 'project.taskCenter.batchStop',
eventTag: 'batchStop',
},
{
label: 'project.taskCenter.batchExecution',
eventTag: 'batchExecution',
},
],
};
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {}
function edit(record: any) {}
const moreActions: ActionsItem[] = [
{
label: 'common.delete',
danger: true,
eventTag: 'delete',
},
];
function handleMoreActionSelect(item: ActionsItem) {}
onBeforeMount(() => {
initData();
});
watch(
() => props.moduleType,
(val) => {
if (val) {
resetSelector();
initData();
}
}
);
</script>
<style scoped></style>

View File

@ -0,0 +1,131 @@
<template>
<div class="box h-full">
<div class="left" :class="getStyleClass()">
<div class="item" :class="[activeTask === 'real' ? 'active' : '']" @click="toggleTask('real')">
{{ t('project.taskCenter.realTimeTask') }}
</div>
<div class="item" :class="[activeTask === 'timing' ? 'active' : '']" @click="toggleTask('timing')">
{{ t('project.taskCenter.scheduledTask') }}
</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'" :name="listName" :module-type="activeTab" :group="props.group" />
<ScheduledTask v-if="activeTask === 'timing'" :name="listName" :group="props.group" :module-type="activeTab" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ApiCase from './apiCase.vue';
import ScheduledTask from './scheduledTask.vue';
import { useI18n } from '@/hooks/useI18n';
import { TaskCenterEnum } from '@/enums/taskCenter';
const { t } = useI18n();
const activeTab = ref<keyof typeof TaskCenterEnum>(TaskCenterEnum.API_CASE);
const props = defineProps<{
group: 'system' | 'organization' | 'project';
mode?: 'modal' | 'normal';
}>();
const realTabList = ref([
{
value: TaskCenterEnum.API_CASE,
label: t('project.taskCenter.interfaceCase'),
},
{
value: TaskCenterEnum.API_SCENARIO,
label: t('project.taskCenter.apiScenario'),
},
{
value: TaskCenterEnum.UI_TEST,
label: t('project.taskCenter.uiDefaultFile'),
},
{
value: TaskCenterEnum.LOAD_TEST,
label: t('project.taskCenter.performanceTest'),
},
{
value: TaskCenterEnum.TEST_PLAN,
label: t('project.taskCenter.testPlan'),
},
]);
const timingTabList = ref([
{
value: TaskCenterEnum.TEST_RESOURCE,
label: t('project.taskCenter.testResource'),
},
{
value: TaskCenterEnum.API_IMPORT,
label: t('project.taskCenter.apiImport'),
},
]);
const activeTask = ref('real');
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.TEST_RESOURCE;
}
}
function getStyleClass() {
return props.mode === 'modal' ? ['p-0', 'pt-[24px]', 'pr-[24px]'] : ['p-[24px]'];
}
const listName = computed(() => {
return rightTabList.value.find((item) => item.value === activeTab.value)?.label || '';
});
</script>
<style scoped lang="less">
.box {
display: flex;
height: 100%;
.left {
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 {
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

@ -0,0 +1,149 @@
import { TaskCenterEnum } from '@/enums/taskCenter';
export const TaskStatus = {
[TaskCenterEnum.API_CASE]: {
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: 'project.taskCenter.successful',
},
ERROR: {
icon: 'icon-icon_close_colorful',
label: 'project.taskCenter.failure',
},
FAKE_ERROR: {
icon: 'icon-icon_warning_colorful',
label: 'project.taskCenter.falseAlarm',
},
STOPPED: {
icon: 'icon-icon_block_filled',
label: 'project.taskCenter.stop',
color: '!var(--color-text-input-border)',
},
RUNNING: {
icon: 'icon-icon_testing',
label: 'project.taskCenter.inExecution',
color: '!text-[rgb(var(--link-6))]',
},
RERUNNING: {
icon: 'icon-icon_testing',
label: 'project.taskCenter.rerun',
color: '!text-[rgb(var(--link-6))]',
},
PENDING: {
icon: 'icon-icon_wait',
label: 'project.taskCenter.queuing',
color: '!text-[rgb(var(--link-6))]',
},
},
[TaskCenterEnum.API_SCENARIO]: {
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: 'project.taskCenter.successful',
},
ERROR: {
icon: 'icon-icon_close_colorful',
label: 'project.taskCenter.failure',
},
FAKE_ERROR: {
icon: 'icon-icon_warning_colorful',
label: 'project.taskCenter.falseAlarm',
},
STOPPED: {
icon: 'icon-icon_block_filled',
label: 'project.taskCenter.stop',
color: 'var(--color-text-input-border)',
},
RUNNING: {
icon: 'icon-icon_testing',
label: 'project.taskCenter.inExecution',
color: '!text-[rgb(var(--link-6))]',
},
RERUNNING: {
icon: 'icon-icon_testing',
label: 'project.taskCenter.rerun',
color: '!text-[rgb(var(--link-6))]',
},
PENDING: {
icon: 'icon-icon_wait',
label: 'project.taskCenter.queuing',
color: '!text-[rgb(var(--link-6))]',
},
},
[TaskCenterEnum.LOAD_TEST]: {
STARTING: {
icon: 'icon-icon_restarting',
label: 'project.taskCenter.starting',
color: '!text-[rgb(var(--link-6))]',
},
RUNNING: {
icon: 'icon-icon_testing',
label: 'project.taskCenter.inExecution',
color: '!text-[rgb(var(--link-6))]',
},
ERROR: {
icon: 'icon-icon_close_colorful',
label: 'project.taskCenter.failure',
},
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: 'project.taskCenter.successful',
},
COMPLETED: {
icon: 'icon-icon_succeed_colorful',
label: 'project.taskCenter.complete',
},
STOPPED: {
icon: 'icon-icon_block_filled',
label: 'project.taskCenter.stop',
color: 'var(--color-text-input-border)',
},
},
[TaskCenterEnum.UI_TEST]: {
PENDING: {
icon: 'icon-icon_wait',
label: 'project.taskCenter.queuing',
color: '!text-[rgb(var(--link-6))]',
},
RUNNING: {
icon: 'icon-icon_testing',
label: 'project.taskCenter.inExecution',
color: '!text-[rgb(var(--link-6))]',
},
RERUNNING: {
icon: 'icon-icon_testing',
label: 'project.taskCenter.rerun',
color: '!text-[rgb(var(--link-6))]',
},
ERROR: {
icon: 'icon-icon_close_colorful',
label: 'project.taskCenter.failure',
},
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: 'project.taskCenter.successful',
},
STOPPED: {
icon: 'icon-icon_block_filled',
label: 'project.taskCenter.stop',
color: 'var(--color-text-input-border)',
},
},
[TaskCenterEnum.TEST_PLAN]: {
RUNNING: {
icon: 'icon-icon_testing',
label: 'project.taskCenter.queuing',
color: '!text-[rgb(var(--link-6))]',
},
SUCCESS: {
icon: 'icon-icon_succeed_colorful',
label: 'project.taskCenter.successful',
},
STARTING: {
icon: 'icon-icon_restarting',
label: 'project.taskCenter.starting',
color: '!text-[rgb(var(--link-6))]',
},
},
};
export default {};

View File

@ -0,0 +1,46 @@
<template>
<MsCard simple no-content-padding>
<TaskCenter group="project" />
</MsCard>
</template>
<script setup lang="ts">
import { ref } from 'vue';
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

@ -0,0 +1,43 @@
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.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': 'View report',
'project.taskCenter.batchStopTask': 'Are you sure to stop {num} tasks?',
'project.taskCenter.stopTask': 'Are you sure to stop {name} task?',
'project.taskCenter.stopTaskContent': 'After stopping,{name} please operate with caution?',
'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.successful': 'Successful',
'project.taskCenter.failure': 'failure',
'project.taskCenter.falseAlarm': 'False alarm',
'project.taskCenter.inExecution': 'In execution',
'project.taskCenter.rerun': 'Rerun',
'project.taskCenter.queuing': 'Be queuing',
'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',
};

View File

@ -0,0 +1,43 @@
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.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': '停止后,{ name }请谨慎操作?',
'project.taskCenter.confirmStop': '确认停止',
'project.taskCenter.stopSuccess': '停止成功',
'project.taskCenter.testResource': '测试资源',
'project.taskCenter.apiImport': 'API 导入',
'project.taskCenter.resourceClassification': '资源分类',
'project.taskCenter.operationRule': '运行规则',
'project.taskCenter.nextExecutionTime': '下次执行时间',
'project.taskCenter.successful': '成功',
'project.taskCenter.failure': '失败',
'project.taskCenter.falseAlarm': '误报',
'project.taskCenter.inExecution': '执行中',
'project.taskCenter.rerun': '重跑中',
'project.taskCenter.queuing': '排队中',
'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}列表',
};

View File

@ -2,8 +2,9 @@
<MsCard has-breadcrumb simple>
<div class="mb-4 flex items-center justify-between">
<span v-if="isEnableOrdTemplate" class="font-medium">{{ t('system.orgTemplate.templateList') }}</span>
<!--TODO 这个版本不允许修改默认模版也不允许创建用例模版 -->
<a-button
v-else
v-if="!isEnableOrdTemplate && route.query.type === 'BUG'"
v-permission="['PROJECT_TEMPLATE:READ+ADD']"
type="primary"
:disabled="false"
@ -22,12 +23,20 @@
</div>
<MsBaseTable v-bind="propsRes" ref="tableRef" v-on="propsEvent">
<template #defaultTemplate="{ record }">
<a-switch
<!-- <a-switch
v-model="record.enableDefault"
:disabled="record.enableDefault || isEnableOrdTemplate"
size="small"
type="line"
@change="(value) => changeDefault(value, record)"
/> -->
<!-- TODO 这个版本不允许修改默认模版也不允许创建用例模版 -->
<a-switch
v-model="record.enableDefault"
:disabled="true"
size="small"
type="line"
@change="(value) => changeDefault(value, record)"
/>
</template>
<template #enableThirdPart="{ record }">

View File

@ -166,18 +166,25 @@
//
const testLink = async () => {
testLoading.value = true;
try {
const formValue = {
...fApi.value.formData(),
};
await postValidate(formValue, pluginId.value);
if (!isConfigOrigin.value) isDisabled.value = false;
Message.success(t('organization.service.successMessage'));
} catch (error) {
console.log(error);
} finally {
testLoading.value = false;
}
fApi.value?.validate(async (valid: any, fail: any) => {
if (valid === true) {
try {
const formValue = {
...fApi.value.formData(),
};
await postValidate(formValue, pluginId.value);
if (!isConfigOrigin.value) isDisabled.value = false;
Message.success(t('organization.service.successMessage'));
} catch (error) {
console.log(error);
} finally {
testLoading.value = false;
}
} else {
console.log(fail);
testLoading.value = false;
}
});
};
// &

View File

@ -8,9 +8,11 @@
v-model="keyword"
:placeholder="t('organization.service.searchService')"
:max-length="255"
class="w-[240px]"
allow-clear
@search="searchHandler"
@press-enter="searchHandler"
@clear="searchHandler"
/>
</div>
</div>
@ -179,7 +181,8 @@
};
const searchHandler = () => {
filterList.value = data.value.filter((item) => item.title?.includes(keyword.value));
const keywordLower = keyword.value.toLowerCase();
filterList.value = data.value.filter((item: any) => item.title.toLowerCase().includes(keywordLower));
};
const serviceVisible = ref<boolean>(false);

View File

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

View File

@ -36,7 +36,12 @@
}"
></a-textarea>
</a-form-item>
<a-form-item field="type" :label="t('system.orgTemplate.fieldType')" asterisk-position="end" :required="true">
<a-form-item
field="type"
:label="t('system.orgTemplate.fieldType')"
asterisk-position="end"
:rules="[{ required: true, message: t('system.orgTemplate.typeEmptyTip') }]"
>
<a-select
v-model="fieldForm.type"
class="w-[260px]"

View File

@ -37,15 +37,17 @@
'cursor-pointer': props.mode === 'project',
}"
/>
<span
class="ml-2"
:class="{
'text-[rgb(var(--primary-5))]': props.mode === 'project',
'cursor-pointer': props.mode === 'project',
}"
@click="showDetail(record)"
>{{ record.name }}</span
>
<a-tooltip :content="record.name">
<div
class="ellipsis ml-2 max-w-[200px]"
:class="{
'text-[rgb(var(--primary-5))]': props.mode === 'project',
'cursor-pointer': props.mode === 'project',
}"
@click="showDetail(record)"
>{{ record.name }}</div
>
</a-tooltip>
<MsTag v-if="record.internal" size="small" class="ml-2">{{ t('system.orgTemplate.isSystem') }}</MsTag></div
>
</template>
@ -58,7 +60,7 @@
:ok-text="t('system.orgTemplate.confirm')"
@confirm="handleOk(record)"
>
<MsButton v-permission="props.updatePermission" class="!mr-0">{{
<MsButton v-permission="props.updatePermission" :disabled="record.internal" class="!mr-0">{{
t('system.orgTemplate.edit')
}}</MsButton></MsPopConfirm
>
@ -82,7 +84,7 @@
v-model:visible="showDetailVisible"
:width="480"
:footer="false"
:title="t('system.orgTemplate.filedDetail', { name: detailInfo?.name })"
:title="t('system.orgTemplate.filedDetail', { name: characterLimit(detailInfo?.name) })"
>
<div class="p-4">
<div class="flex">
@ -197,7 +199,6 @@
dataIndex: 'name',
width: 300,
showInTable: true,
showTooltip: true,
},
{
title: 'system.orgTemplate.columnFieldType',
@ -208,7 +209,9 @@
{
title: 'system.orgTemplate.columnFieldDescription',
dataIndex: 'remark',
width: 300,
showInTable: true,
showTooltip: true,
},
{
title: 'system.orgTemplate.columnFieldUpdatedTime',
@ -384,4 +387,9 @@
width: 70%;
color: var(--color-text-1);
}
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@ -330,28 +330,6 @@
isPreview.value = !isPreview.value;
}
// title
const breadTitle = computed(() => {
const firstBreadTitle = getCardList('organization').find((item) => item.key === route.query.type)?.name;
const ThirdBreadTitle = title.value;
return {
firstBreadTitle,
ThirdBreadTitle,
};
});
//
const setBreadText = () => {
const { breadcrumbList } = appStore;
const { firstBreadTitle, ThirdBreadTitle } = breadTitle.value;
if (firstBreadTitle) {
breadcrumbList[0].locale = firstBreadTitle;
if (appStore.breadcrumbList.length > 2) {
breadcrumbList[2].locale = ThirdBreadTitle;
}
appStore.setBreadcrumbList(breadcrumbList);
}
};
//
const updateHandler = (flag: boolean) => {
isEditField.value = flag;
@ -373,7 +351,6 @@
);
onMounted(() => {
// setBreadText();
getClassifyField();
if (!isEdit.value) {
selectData.value = totalTemplateField.value.filter((item) => item.internal);

View File

@ -34,6 +34,50 @@
</div>
</div>
</div>
<a-modal
v-model:visible="showEnableVisible"
class="ms-modal-form ms-modal-small"
unmount-on-close
title-align="start"
:mask="true"
:mask-closable="false"
@close="cancelHandler"
>
<template #title>
<div class="flex items-center">
<icon-close-circle-fill class="mr-2 text-[20px] text-[rgb(var(--danger-6))]" />
<span>{{ t('system.orgTemplate.enableTip') }}</span></div
>
</template>
<span class="text-[rgb(var(--warning-6))]">{{ t('system.orgTemplate.enableWarningTip') }}</span>
<a-input
v-model="validateKeyWord"
:placeholder="t('personal.searchOrgPlaceholder')"
allow-clear
class="mb-4 mt-[8px]"
:max-length="255"
/>
<template #footer>
<div class="flex justify-end">
<a-button type="secondary" @click="cancelHandler">
{{ t('common.cancel') }}
</a-button>
<slot name="self-button"></slot>
<a-button
class="ml-3"
type="primary"
:loading="confirmLoading"
:disabled="!validateKeyWord.trim().length"
status="danger"
@click="okHandler"
>
{{ t('common.confirmEnable') }}
</a-button>
</div>
</template>
</a-modal>
</div>
</template>
@ -84,18 +128,40 @@
danger: true,
},
]);
const showEnableVisible = ref<boolean>(false);
const validateKeyWord = ref<string>('');
const confirmLoading = ref<boolean>(false);
//
const enableHandler = async () => {
const orgName = computed(() => {
return appStore.ordList.find((item: any) => item.id === appStore.currentOrgId)?.name;
});
async function okHandler() {
if (validateKeyWord.value.trim() !== '' && validateKeyWord.value !== orgName.value) {
return false;
}
try {
confirmLoading.value = true;
if (props.mode) {
await enableOrOffTemplate(currentOrgId.value, props.cardItem.key);
Message.success(t('system.orgTemplate.enabledSuccessfully'));
await templateStore.getStatus();
emit('updateState');
confirmLoading.value = false;
showEnableVisible.value = false;
}
} catch (error) {
console.log(error);
} finally {
confirmLoading.value = false;
}
}
//
const enableHandler = async () => {
try {
showEnableVisible.value = true;
} catch (error) {
console.log(error);
}
};
@ -127,6 +193,11 @@
},
{ deep: true }
);
function cancelHandler() {
showEnableVisible.value = false;
validateKeyWord.value = '';
}
</script>
<style scoped lang="less">

View File

@ -6,7 +6,7 @@
<div class="mb-4 flex items-center justify-between">
<span v-if="isEnableOrdTemplate" class="font-medium">{{ t('system.orgTemplate.templateList') }}</span>
<a-button
v-else
v-if="!isEnableOrdTemplate && route.query.type === 'BUG'"
v-permission="['ORGANIZATION_TEMPLATE:READ+ADD']"
type="primary"
:disabled="false"

View File

@ -50,7 +50,7 @@ export default {
'system.orgTemplate.fieldNameRules': 'The field name cannot be empty',
'system.orgTemplate.fieldNamePlaceholder': 'Please enter a field name',
'system.orgTemplate.description': 'Description',
'system.orgTemplate.resDescription': 'Describe the resource pool',
'system.orgTemplate.resDescription': 'Describe the fields pool',
'system.orgTemplate.fieldType': 'Field type',
'system.orgTemplate.fieldTypePlaceholder': 'Please select a field type',
'system.orgTemplate.allowMultiMember': 'Allows multiple members to be added',
@ -177,4 +177,7 @@ export default {
'system.orgTemplate.templateCase': 'Case',
'system.orgTemplate.templateApi': 'Api',
'system.orgTemplate.templateBug': 'Bug',
'system.orgTemplate.enableTip': 'Are you sure to enable the project template',
'system.orgTemplate.enableWarningTip': 'Enabled, irreversible for organization template, please careful operation.',
'system.orgTemplate.typeEmptyTip': 'The type cannot be empty',
};

View File

@ -49,7 +49,7 @@ export default {
'system.orgTemplate.fieldNameRules': '字段名称不能为空',
'system.orgTemplate.fieldNamePlaceholder': '请输入字段名称',
'system.orgTemplate.description': '描述',
'system.orgTemplate.resDescription': '请对该资源池进行描述',
'system.orgTemplate.resDescription': '请对该字段进行描述',
'system.orgTemplate.fieldType': '字段类型',
'system.orgTemplate.fieldTypePlaceholder': '请选择字段类型',
'system.orgTemplate.allowMultiMember': '允许添加多个成员',
@ -166,4 +166,7 @@ export default {
'system.orgTemplate.templateCase': '用例',
'system.orgTemplate.templateApi': '接口',
'system.orgTemplate.templateBug': '缺陷',
'system.orgTemplate.enableTip': '确认启用项目模版吗',
'system.orgTemplate.enableWarningTip': '启用后,不可恢复为组织模版,请谨慎操作!',
'system.orgTemplate.typeEmptyTip': '类型不能为空',
};

View File

@ -23,14 +23,16 @@
allow-clear
/>
</a-form-item>
<a-form-item field="global" :label="t('system.plugin.appOrganize')" asterisk-position="end">
<a-radio-group v-model="form.global">
<a-form-item
:tooltip="t('system.plugin.allOrganizeTip')"
field="global"
:label="t('system.plugin.appOrganize')"
asterisk-position="end"
:row-class="form.global ? 'allOrganizeTip' : ''"
>
<a-radio-group v-model="form.global" type="button">
<a-radio :value="true"
>{{ t('system.plugin.allOrganize')
}}<span class="float-right mx-1 mt-[1px]">
<a-tooltip :content="t('system.plugin.allOrganizeTip')" position="top">
<IconQuestionCircle class="h-[16px] w-[16px] text-[--color-text-4]"
/></a-tooltip> </span
>{{ t('system.plugin.allOrganize') }}<span class="float-right mx-1 mt-[1px]"> </span
></a-radio>
<a-radio :value="false">{{ t('system.plugin.theOrganize') }}</a-radio>
</a-radio-group>
@ -40,6 +42,7 @@
field="organizationIds"
:label="t('system.plugin.selectOrganization')"
asterisk-position="end"
:row-class="showIncludeOrg || showRemoveOrg ? 'allOrganizeTip' : ''"
:rules="[{ required: true, message: t('system.plugin.selectOrganizeTip') }]"
>
<a-select
@ -51,6 +54,15 @@
<a-option v-for="item of organizeList" :key="item.id" :value="item.id">{{ item.name }}</a-option>
</a-select>
</a-form-item>
<div v-if="form.global" class="mb-[16px] ml-1 text-[12px] text-[rgb(var(--danger-6))]">
{{ title }} {{ t('system.plugin.switchAllOrganizeTip') }}
</div>
<div v-if="showIncludeOrg" class="mb-[16px] ml-1 text-[12px] text-[rgb(var(--danger-6))]">
{{ title }} {{ t('system.plugin.switchSectionOrganizeTip') }}
</div>
<div v-if="showRemoveOrg" class="mb-[16px] ml-1 text-[12px] text-[rgb(var(--danger-6))]">
{{ t('system.plugin.changeOrganizeTip') }}
</div>
<a-form-item field="description" :label="t('system.plugin.description')" asterisk-position="end">
<a-textarea
v-model="form.description"
@ -72,6 +84,7 @@
<script setup lang="ts">
import { ref, watch, watchEffect } from 'vue';
import { FormInstance, Message, SelectOptionData, ValidatedError } from '@arco-design/web-vue';
import { isEqual } from 'lodash-es';
import { updatePlugin } from '@/api/modules/setting/pluginManger';
import { useI18n } from '@/hooks/useI18n';
@ -112,8 +125,10 @@
UpdateFormRef.value?.resetFields();
updateVisible.value = false;
};
const originOrgIds = ref<string[]>([]);
const open = (record: PluginItem) => {
title.value = record.name as string;
originOrgIds.value = (record.organizations || []).map((item) => item.id);
form.value = {
...record,
organizationIds: (record.organizations || []).map((item) => item.id),
@ -147,10 +162,31 @@
}
});
};
const showIncludeOrg = computed(() => {
const { organizationIds } = form.value;
if (!form.value.global && !isEqual(organizationIds, originOrgIds.value)) {
return originOrgIds.value?.every((item) => organizationIds?.includes(item));
}
return false;
});
const showRemoveOrg = computed(() => {
const { organizationIds } = form.value;
if (!form.value.global && !isEqual(originOrgIds.value, organizationIds)) {
return originOrgIds.value?.some((item) => !organizationIds?.includes(item));
}
return false;
});
defineExpose({
open,
UpdateFormRef,
});
</script>
<style scoped></style>
<style scoped lang="less">
.allOrganizeTip {
margin-bottom: 8px;
}
</style>

View File

@ -108,4 +108,7 @@ export default {
'system.plugin.deleteContentTip':
'After deletion, the defects/requirements of the platform cannot be synchronized, and the historical data is automatically switched to other templates. Please exercise caution!',
'system.plugin.allOrganizeTip': 'This rule is common to new organizations',
'system.plugin.switchAllOrganizeTip': 'The plugin will be visible to all organizations',
'system.plugin.switchSectionOrganizeTip': 'Plug-in to specify organization is visible',
'system.plugin.changeOrganizeTip': 'Changing the organization will make historical data unavailable, so be careful!',
};

View File

@ -87,4 +87,7 @@ export default {
'system.plugin.databaseDriver': '数据库驱动',
'system.plugin.deleteContentTip': '删除后,将无法同步该平台的缺陷/需求,历史数据自动切换为其它模板展示,请谨慎操作!',
'system.plugin.allOrganizeTip': '新建组织通用此规则',
'system.plugin.switchAllOrganizeTip': '插件将对所有组织可见',
'system.plugin.switchSectionOrganizeTip': '插件将对指定组织可见',
'system.plugin.changeOrganizeTip': ' 变更组织会导致历史数据不可用,请谨慎操作!',
};