feat(系统管理&项目管理): 模板调试

This commit is contained in:
xinxin.wu 2023-11-10 15:25:59 +08:00 committed by rubylliu
parent 167801743c
commit 112a21e7e1
47 changed files with 2570 additions and 686 deletions

View File

@ -2,15 +2,23 @@ import MSR from '@/api/http/index';
import { import {
CreateFieldUrl, CreateFieldUrl,
CreateOrganizeTemplateUrl, CreateOrganizeTemplateUrl,
CreateProjectFieldUrl,
CreateProjectTemplateUrl,
DeleteFieldDetailUrl, DeleteFieldDetailUrl,
DeleteOrganizeTemplateUrl, DeleteOrganizeTemplateUrl,
DeleteProjectFieldDetailUrl,
DeleteProjectTemplateUrl,
EnableOrOffTemplateUrl, EnableOrOffTemplateUrl,
GetDefinedFieldListUrl, GetDefinedFieldListUrl,
GetDefinedProjectFieldListUrl,
GetFieldDetailUrl, GetFieldDetailUrl,
GetFieldProjectDetailUrl,
getOrdTemplateStateUrl,
GetOrganizeTemplateDetailUrl, GetOrganizeTemplateDetailUrl,
GetOrganizeTemplateUrl, GetOrganizeTemplateUrl,
GetProjectTemplateDetailUrl, GetProjectTemplateDetailUrl,
isEnableTemplateUrl, getProjectTemplateStateUrl,
GetProjectTemplateUrl,
OrdCreateFlowStatusUrl, OrdCreateFlowStatusUrl,
OrdDeleteFlowStatusUrl, OrdDeleteFlowStatusUrl,
OrdSetStateUrl, OrdSetStateUrl,
@ -18,9 +26,17 @@ import {
OrdUpdateFlowStatusUrl, OrdUpdateFlowStatusUrl,
OrdUpdateStateFlowUrl, OrdUpdateStateFlowUrl,
OrdWorkFlowUrl, OrdWorkFlowUrl,
SetOrganizeTemplateUrl, ProjectCreateFlowStatusUrl,
ProjectDeleteFlowStatusUrl,
ProjectSetStateUrl,
ProjectStateSortUrl,
ProjectUpdateFlowStatusUrl,
ProjectUpdateStateFlowUrl,
ProjectWorkFlowUrl,
SetProjectTemplateUrl,
UpdateFieldUrl, UpdateFieldUrl,
UpdateOrganizeTemplateUrl, UpdateOrganizeTemplateUrl,
UpdateProjectFieldUrl,
UpdateProjectTemplateUrl, UpdateProjectTemplateUrl,
} from '@/api/requrls/setting/template'; } from '@/api/requrls/setting/template';
@ -28,9 +44,7 @@ import { TableQueryParams } from '@/models/common';
import type { import type {
ActionTemplateManage, ActionTemplateManage,
AddOrUpdateField, AddOrUpdateField,
DefinedFieldItem,
OrdWorkStatus, OrdWorkStatus,
OrganizeTemplateItem,
SeneType, SeneType,
SetStateType, SetStateType,
UpdateWorkFlowSetting, UpdateWorkFlowSetting,
@ -38,13 +52,13 @@ import type {
} from '@/models/setting/template'; } from '@/models/setting/template';
/** * /** *
* * ()
*/ */
// 获取模版列表(组织) // 获取模版列表(组织)
export function getOrganizeTemplateList(params: TableQueryParams) { export function getOrganizeTemplateList(params: TableQueryParams) {
return MSR.get({ url: `${GetOrganizeTemplateUrl}/${params.organizationId}/${params.scene}` }); return MSR.get({ url: `${GetOrganizeTemplateUrl}/${params.organizationId}/${params.scene}` });
} }
// 获取模版详情 // 获取模版详情(组织)
export function getOrganizeTemplateInfo(id: string) { export function getOrganizeTemplateInfo(id: string) {
return MSR.get({ url: `${GetOrganizeTemplateDetailUrl}/${id}` }); return MSR.get({ url: `${GetOrganizeTemplateDetailUrl}/${id}` });
} }
@ -57,21 +71,32 @@ export function createOrganizeTemplateInfo(data: ActionTemplateManage) {
export function updateOrganizeTemplateInfo(data: ActionTemplateManage) { export function updateOrganizeTemplateInfo(data: ActionTemplateManage) {
return MSR.post({ url: `${UpdateOrganizeTemplateUrl}`, data }); return MSR.post({ url: `${UpdateOrganizeTemplateUrl}`, data });
} }
// 是否启用组织XX模板
export function isEnableTemplate(organizationId: string) { // 获取模板列表的状态(组织)
return MSR.get<Record<string, boolean>>({ url: `${isEnableTemplateUrl}/${organizationId}` }); export function getOrdTemplate(scopedId: string) {
return MSR.get<Record<string, boolean>>({ url: `${getOrdTemplateStateUrl}/${scopedId}` });
} }
// 删除模板
// 获取模板列表的状态(项目)
export function getProTemplate(scopedId: string) {
return MSR.get<Record<string, boolean>>({ url: `${getProjectTemplateStateUrl}/${scopedId}` });
}
// 删除模板(组织)
export function deleteOrdTemplate(id: string) { export function deleteOrdTemplate(id: string) {
return MSR.get({ url: `${DeleteOrganizeTemplateUrl}/${id}` }); return MSR.get({ url: `${DeleteOrganizeTemplateUrl}/${id}` });
} }
// 关闭组织模板||开启项目模板
export function enableOrOffTemplate(organizationId: string, scene: SeneType) {
return MSR.get({ url: `${EnableOrOffTemplateUrl}/${organizationId}/${scene}` });
}
/** * /** *
* () * ()
*/ */
// 获取自定义字段列表 // 获取自定义字段列表(组织)
export function getFieldList(params: TableQueryParams) { export function getFieldList(params: TableQueryParams) {
return MSR.get({ url: `${GetDefinedFieldListUrl}${params.organizationId}/${params.scene}` }); return MSR.get({ url: `${GetDefinedFieldListUrl}${params.scopedId}/${params.scene}` });
} }
// 创建自定义字段(组织) // 创建自定义字段(组织)
@ -95,8 +120,8 @@ export function getOrdFieldDetail(id: string) {
// 组织模板-工作流 // 组织模板-工作流
// 获取组织下边模板工作流 // 获取组织下边模板工作流
export function getWorkFlowList(organizationId: string, scene: SeneType) { export function getWorkFlowList(scopedId: string, scene: SeneType) {
return MSR.get<WorkFlowType[]>({ url: `${OrdWorkFlowUrl}/${organizationId}/${scene}` }); return MSR.get<WorkFlowType[]>({ url: `${OrdWorkFlowUrl}/${scopedId}/${scene}` });
} }
// 创建工作流状态 // 创建工作流状态
export function createWorkFlowStatus(data: OrdWorkStatus) { export function createWorkFlowStatus(data: OrdWorkStatus) {
@ -116,11 +141,99 @@ export function setOrdWorkState(data: SetStateType) {
return MSR.post({ url: OrdSetStateUrl, data }); return MSR.post({ url: OrdSetStateUrl, data });
} }
// 设置工作流状态排序 // 设置工作流状态排序
export function setOrdWorkStateSort(organizationId: string, scene: SeneType, data: string[]) { export function setOrdWorkStateSort(scopedId: string, scene: SeneType, data: string[]) {
return MSR.post({ url: `${OrdStateSortUrl}/${organizationId}/${scene}`, data }); return MSR.post({ url: `${OrdStateSortUrl}/${scopedId}/${scene}`, data });
} }
// 更新工作流流转状态 // 更新工作流流转状态
export function updateOrdWorkStateFlow(data: UpdateWorkFlowSetting) { export function updateOrdWorkStateFlow(data: UpdateWorkFlowSetting) {
return MSR.post({ url: `${OrdUpdateStateFlowUrl}`, data }); return MSR.post({ url: `${OrdUpdateStateFlowUrl}`, data });
} }
export default {};
/** *
* ()
*/
// 获取自定义字段列表(组织)
export function getProjectFieldList(params: TableQueryParams) {
return MSR.get({ url: `${GetDefinedProjectFieldListUrl}${params.scopedId}/${params.scene}` });
}
// 创建自定义字段(组织)
export function addOrUpdateProjectField(data: AddOrUpdateField) {
if (data.id) {
return MSR.post({ url: UpdateProjectFieldUrl, data });
}
return MSR.post({ url: CreateProjectFieldUrl, data });
}
// 删除自定义字段(组织)
export function deleteProjectField(id: string) {
return MSR.get({ url: DeleteProjectFieldDetailUrl, params: id });
}
// 获取自定义字段详情选项(组织)
export function getProjectFieldDetail(id: string) {
return MSR.get({ url: GetFieldProjectDetailUrl, params: id });
}
/** *
* ()
*/
// 获取模版列表(项目)
export function getProjectTemplateList(params: TableQueryParams) {
return MSR.get({ url: `${GetProjectTemplateUrl}/${params.projectId}/${params.scene}` });
}
// 获取模版详情(项目)
export function getProjectTemplateInfo(id: string) {
return MSR.get({ url: `${GetProjectTemplateDetailUrl}/${id}` });
}
// 创建模板列表(项目)
export function createProjectTemplateInfo(data: ActionTemplateManage) {
return MSR.post({ url: `${CreateProjectTemplateUrl}`, data });
}
// 编辑模板列表(项目)
export function updateProjectTemplateInfo(data: ActionTemplateManage) {
return MSR.post({ url: `${UpdateProjectTemplateUrl}`, data });
}
// 删除模板(项目)
export function deleteProjectTemplate(id: string) {
return MSR.get({ url: `${DeleteProjectTemplateUrl}/${id}` });
}
// 设置默认项目模板
export function setDefaultTemplate(projectId: string, id: string) {
return MSR.get({ url: `${SetProjectTemplateUrl}/${projectId}/${id}` });
}
// 项目模板-工作流
// 获取项目模板工作流
export function getProjectWorkFlowList(scopedId: string, scene: SeneType) {
return MSR.get<WorkFlowType[]>({ url: `${ProjectWorkFlowUrl}/${scopedId}/${scene}` });
}
// 创建工作流状态
export function createProjectWorkFlowStatus(data: OrdWorkStatus) {
return MSR.post<WorkFlowType[]>({ url: ProjectCreateFlowStatusUrl, data });
}
// 更新工作流状态
export function updateProjectWorkFlowStatus(data: OrdWorkStatus) {
return MSR.post<WorkFlowType[]>({ url: ProjectUpdateFlowStatusUrl, data });
}
// 删除工作流状态
export function deleteProjectWorkState(id: string) {
return MSR.get({ url: ProjectDeleteFlowStatusUrl, params: id });
}
// 设置工作流状态初始态 || 结束态
export function setProjectWorkState(data: SetStateType) {
return MSR.post({ url: ProjectSetStateUrl, data });
}
// 设置工作流状态排序
export function setProjectWorkStateSort(scopedId: string, scene: SeneType, data: string[]) {
return MSR.post({ url: `${ProjectStateSortUrl}/${scopedId}/${scene}`, data });
}
// 更新工作流流转状态
export function updateProjectWorkStateFlow(data: UpdateWorkFlowSetting) {
return MSR.post({ url: `${ProjectUpdateStateFlowUrl}`, data });
}

View File

@ -12,6 +12,8 @@ export const UpdateProjectTemplateUrl = '/project/template/update';
export const GetProjectTemplateDetailUrl = '/project/template/get'; export const GetProjectTemplateDetailUrl = '/project/template/get';
// 删除模版 // 删除模版
export const DeleteProjectTemplateUrl = '/project/template/delete'; export const DeleteProjectTemplateUrl = '/project/template/delete';
// 是否启用项目模板
export const getProjectTemplateStateUrl = '/project/template/enable/config';
// 组织--- 模板 // 组织--- 模板
@ -29,8 +31,8 @@ export const GetOrganizeTemplateDetailUrl = '/organization/template/get';
export const DeleteOrganizeTemplateUrl = '/organization/template/delete'; export const DeleteOrganizeTemplateUrl = '/organization/template/delete';
// 关闭组织模板,开启项目模版 // 关闭组织模板,开启项目模版
export const EnableOrOffTemplateUrl = '/organization/template/disable'; export const EnableOrOffTemplateUrl = '/organization/template/disable';
// 是否启用组织模板 // 获取所有模板状态
export const isEnableTemplateUrl = '/organization/template/enable/config'; export const getOrdTemplateStateUrl = '/organization/template/enable/config';
// 系统设置-组织-自定义字段 // 系统设置-组织-自定义字段
@ -61,3 +63,33 @@ export const OrdSetStateUrl = '/organization/status/flow/setting/status/definiti
export const OrdStateSortUrl = '/organization/status/flow/setting/status/sort'; export const OrdStateSortUrl = '/organization/status/flow/setting/status/sort';
// 更新状态流转 // 更新状态流转
export const OrdUpdateStateFlowUrl = '/organization/status/flow/setting/status/flow/update'; export const OrdUpdateStateFlowUrl = '/organization/status/flow/setting/status/flow/update';
// 项目管理-模板-自定义字段
// 获取自定义字段列表
export const GetDefinedProjectFieldListUrl = '/project/custom/field/list/';
// 创建自定义字段
export const CreateProjectFieldUrl = '/project/custom/field/add';
// 更新自定义字段
export const UpdateProjectFieldUrl = '/project/custom/field/update';
// 获取自定义字段详情
export const GetFieldProjectDetailUrl = '/project/custom/field/get';
// 删除自定义字段
export const DeleteProjectFieldDetailUrl = '/project/custom/field/delete';
// 项目管理-模板-获取工作流列表
export const ProjectWorkFlowUrl = '/project/status/flow/setting/get';
// 状态流更新
export const ProjectWorkFlowSetting = '/project/status/flow/setting/status/update';
// 创建工作流状态
export const ProjectCreateFlowStatusUrl = '/project/status/flow/setting/status/add';
// 更新工作流状态
export const ProjectUpdateFlowStatusUrl = '/project/status/flow/setting/status/update';
// 删除工作流状态
export const ProjectDeleteFlowStatusUrl = '/project/status/flow/setting/status/delete';
// 设置工作流状态为初始态或者标记结束状态
export const ProjectSetStateUrl = '/project/status/flow/setting/status/definition/update';
// 设置选项排序
export const ProjectStateSortUrl = '/project/status/flow/setting/status/sort';
// 更新状态流转
export const ProjectUpdateStateFlowUrl = '/project/status/flow/setting/status/flow/update';

View File

@ -0,0 +1,38 @@
<svg width="33" height="30" viewBox="0 0 33 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.9333 7.27246C15.5224 7.27246 16 7.63063 16 8.07246L16 22.4725C16 22.9143 15.5224 23.2725 14.9333 23.2725L1.06667 23.2725C0.477563 23.2725 2.69948e-07 22.9143 2.89261e-07 22.4725L9.18705e-07 8.07246C9.38018e-07 7.63063 0.477564 7.27246 1.06667 7.27246L14.9333 7.27246Z" fill="#3370FF"/>
<g filter="url(#filter0_i_11927_97606)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.9993 0H11.6357C10.029 0 8.72656 1.30245 8.72656 2.90909V26.1818C8.72656 27.7885 10.029 29.0909 11.6357 29.0909H29.0902C30.6968 29.0909 31.9993 27.7885 31.9993 26.1818V2.90909C31.9993 1.30244 30.6968 0 29.0902 0H24.7266V1.90909C24.7266 2.46137 24.2788 2.90909 23.7266 2.90909H16.9993C16.447 2.90909 15.9993 2.46138 15.9993 1.90909V0Z" fill="#F5F8FF"/>
</g>
<g filter="url(#filter1_f_11927_97606)">
<path d="M25.2731 5.81836C26.5785 5.81836 27.6367 6.26393 27.6367 6.81357L27.6367 23.7322C27.6367 24.2819 26.5785 24.7275 25.2731 24.7275L11.0913 24.7275C9.78586 24.7275 8.72763 24.2819 8.72763 23.7322L8.72763 6.81357C8.72763 6.26393 9.78586 5.81836 11.0913 5.81836L25.2731 5.81836Z" fill="#3370FF"/>
</g>
<g filter="url(#filter2_d_11927_97606)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.7297 7.02636C16.7297 6.3592 17.2705 5.81836 17.9377 5.81836C18.6048 5.81836 19.1457 6.3592 19.1457 7.02636V10.49C19.1457 10.9533 19.0726 11.3916 18.9265 11.8049C18.784 12.2147 18.5577 12.5746 18.2477 12.8846C17.9412 13.1911 17.6187 13.4067 17.2802 13.5314C16.8098 13.706 16.245 13.7933 15.5858 13.7933C15.2045 13.7933 14.7876 13.7666 14.335 13.7131C13.886 13.6597 13.5101 13.5546 13.2072 13.3978C12.9043 13.2374 12.6264 13.0111 12.3734 12.7189C12.1239 12.4267 11.9529 12.1256 11.8602 11.8156C11.7106 11.3167 11.6357 10.8749 11.6357 10.49V7.02636C11.6357 6.3592 12.1766 5.81836 12.8437 5.81836C13.5109 5.81836 14.0517 6.3592 14.0517 7.02636V10.6023C14.0517 11.0299 14.1693 11.3648 14.4045 11.6071C14.6433 11.8459 14.9729 11.9653 15.3934 11.9653C15.8103 11.9653 16.1363 11.8477 16.3715 11.6125C16.6103 11.3737 16.7297 11.037 16.7297 10.6023V7.02636ZM20.8445 7.03171C20.8445 6.36159 21.3878 5.81836 22.0579 5.81836C22.728 5.81836 23.2712 6.36159 23.2712 7.03171V12.441C23.2712 13.1111 22.728 13.6543 22.0579 13.6543C21.3878 13.6543 20.8445 13.1111 20.8445 12.441V7.03171ZM11.6361 17.4547C11.6361 16.6514 12.2873 16.0002 13.0906 16.0002H17.4543C18.2576 16.0002 18.9088 16.6514 18.9088 17.4547C18.9088 18.258 18.2576 18.9093 17.4543 18.9093H13.0906C12.2873 18.9093 11.6361 18.258 11.6361 17.4547ZM11.6361 21.8183C11.6361 21.015 12.2873 20.3638 13.0906 20.3638H27.6361C28.4394 20.3638 29.0906 21.015 29.0906 21.8183C29.0906 22.6217 28.4394 23.2729 27.6361 23.2729H13.0906C12.2873 23.2729 11.6361 22.6217 11.6361 21.8183Z" fill="white"/>
</g>
<defs>
<filter id="filter0_i_11927_97606" x="8.72656" y="0" width="23.2725" height="30.0908" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.2 0 0 0 0 0.439216 0 0 0 0 1 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_11927_97606"/>
</filter>
<filter id="filter1_f_11927_97606" x="3.72754" y="0.818359" width="28.9092" height="28.9092" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2.5" result="effect1_foregroundBlur_11927_97606"/>
</filter>
<filter id="filter2_d_11927_97606" x="11.6357" y="5.81836" width="17.4551" height="17.5541" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="0.1"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.00986111 0 0 0 0 0.0953724 0 0 0 0 0.295833 0 0 0 0.22 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_11927_97606"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_11927_97606" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -58,6 +58,7 @@ export interface FormItem {
}; };
// 表单布局 // 表单布局
wrap?: Record<string, any>; wrap?: Record<string, any>;
props?: Record<string, any>;
} }
interface FomItemSelect extends FormItemComplexCommonConfig { interface FomItemSelect extends FormItemComplexCommonConfig {

View File

@ -52,6 +52,11 @@
useEditor, useEditor,
} from '@halo-dev/richtext-editor'; } from '@halo-dev/richtext-editor';
const props = defineProps<{
modelValue: string;
}>();
const emit = defineEmits(['update:model-value']);
const content = useLocalStorage('content', ''); const content = useLocalStorage('content', '');
const editor = useEditor({ const editor = useEditor({
@ -132,6 +137,22 @@
// const locale = useLocalStorage('locale', 'zh-CN'); // const locale = useLocalStorage('locale', 'zh-CN');
const locale = computed(() => currentLocale.value as 'zh-CN' | 'en-US'); const locale = computed(() => currentLocale.value as 'zh-CN' | 'en-US');
watch(
() => props.modelValue,
(val) => {
if (val) {
content.value = val;
}
}
);
watch(
() => content.value,
() => {
emit('update:model-value', `${editor.value?.getHTML()}`);
}
);
</script> </script>
<template> <template>

View File

@ -236,6 +236,7 @@
router.replace({ router.replace({
path: route.path, path: route.path,
query: { query: {
...route.query,
organizationId: appStore.currentOrgId, organizationId: appStore.currentOrgId,
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,
}, },

View File

@ -165,11 +165,39 @@ export const pathMap: PathMapItem[] = [
}, },
{ {
key: 'SETTING_ORGANIZATION_TEMPLATE', // 系统设置-组织-模版 key: 'SETTING_ORGANIZATION_TEMPLATE', // 系统设置-组织-模版
locale: 'menu.settings.organization.serviceIntegration', locale: 'menu.settings.organization.template',
route: RouteEnum.SETTING_ORGANIZATION_TEMPLATE, route: RouteEnum.SETTING_ORGANIZATION_TEMPLATE,
permission: [], permission: [],
level: MENU_LEVEL[1], level: MENU_LEVEL[1],
}, },
{
key: 'SETTING_ORGANIZATION_TEMPLATE_FILED_SETTING', // 系统设置-模板管理-字段设置
locale: 'menu.settings.organization.templateFieldSetting',
route: RouteEnum.SETTING_ORGANIZATION_TEMPLATE_FILED_SETTING,
permission: [],
level: MENU_LEVEL[1],
},
{
key: 'SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT', // 系统设置-模板管理列表
locale: 'menu.settings.organization.templateManagementList',
route: RouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT,
permission: [],
level: MENU_LEVEL[1],
},
{
key: 'SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL', // 系统设置-模板管理-详情
locale: 'menu.settings.organization.templateManagementDetail',
route: RouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL,
permission: [],
level: MENU_LEVEL[1],
},
{
key: 'SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_WORKFLOW', // 系统设置-模板管理-工作流
locale: 'menu.settings.organization.templateManagementWorkFlow',
route: RouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_WORKFLOW,
permission: [],
level: MENU_LEVEL[1],
},
], ],
}, },
], ],
@ -202,13 +230,6 @@ export const pathMap: PathMapItem[] = [
permission: [], permission: [],
level: MENU_LEVEL[2], level: MENU_LEVEL[2],
}, },
{
key: 'PROJECT_MANAGEMENT_PERMISSION_TEMPLATE', // 项目管理-项目与权限-菜单管理
locale: 'project.permission.templateManager',
route: RouteEnum.PROJECT_MANAGEMENT_PERMISSION_TEMPLATE,
permission: [],
level: MENU_LEVEL[2],
},
{ {
key: 'PROJECT_MANAGEMENT_PERMISSION_VERSION', // 项目管理-项目与权限-项目版本 key: 'PROJECT_MANAGEMENT_PERMISSION_VERSION', // 项目管理-项目与权限-项目版本
locale: 'project.permission.projectVersion', locale: 'project.permission.projectVersion',
@ -232,6 +253,41 @@ export const pathMap: PathMapItem[] = [
}, },
], ],
}, },
{
key: 'PROJECT_MANAGEMENT_TEMPLATE', // 项目管理-模板管理
locale: 'menu.settings.organization.template',
route: RouteEnum.PROJECT_MANAGEMENT_TEMPLATE,
permission: [],
level: MENU_LEVEL[2],
},
{
key: 'PROJECT_MANAGEMENT_TEMPLATE_FIELD_SETTING', // 项目管理-模板管理-字段设置
locale: 'menu.settings.organization.templateFieldSetting',
route: RouteEnum.PROJECT_MANAGEMENT_TEMPLATE_FIELD_SETTING,
permission: [],
level: MENU_LEVEL[2],
},
{
key: 'PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT', // 项目管理-模板管理列表
locale: 'menu.settings.organization.templateManagementList',
route: RouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT,
permission: [],
level: MENU_LEVEL[2],
},
{
key: 'PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL', // 项目管理-模板管理-详情
locale: 'menu.settings.organization.templateManagementDetail',
route: RouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL,
permission: [],
level: MENU_LEVEL[2],
},
{
key: 'PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_WORKFLOW', // 项目管理-模板管理-工作流
locale: 'menu.settings.organization.templateManagementWorkFlow',
route: RouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_WORKFLOW,
permission: [],
level: MENU_LEVEL[2],
},
{ {
key: 'PROJECT_MANAGEMENT_FILE_MANAGEMENT', // 项目管理-文件管理 key: 'PROJECT_MANAGEMENT_FILE_MANAGEMENT', // 项目管理-文件管理
locale: 'menu.projectManagement.fileManagement', locale: 'menu.projectManagement.fileManagement',

View File

@ -24,7 +24,11 @@ export enum ProjectManagementRouteEnum {
PROJECT_MANAGEMENT_PERMISSION = 'projectManagementPermission', PROJECT_MANAGEMENT_PERMISSION = 'projectManagementPermission',
PROJECT_MANAGEMENT_PERMISSION_BASIC_INFO = 'projectManagementPermissionBasicInfo', PROJECT_MANAGEMENT_PERMISSION_BASIC_INFO = 'projectManagementPermissionBasicInfo',
PROJECT_MANAGEMENT_PERMISSION_MENU_MANAGEMENT = 'projectManagementPermissionMenuManagement', PROJECT_MANAGEMENT_PERMISSION_MENU_MANAGEMENT = 'projectManagementPermissionMenuManagement',
PROJECT_MANAGEMENT_PERMISSION_TEMPLATE = 'projectManagementPermissionTemplate', PROJECT_MANAGEMENT_TEMPLATE = 'projectManagementTemplate',
PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT = 'projectManagementTemplateManagement',
PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL = 'projectManagementTemplateManagementDetail',
PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_WORKFLOW = 'projectManagementTemplateManagementWorkFlow',
PROJECT_MANAGEMENT_TEMPLATE_FIELD_SETTING = 'projectManagementTemplateFiledSetting',
PROJECT_MANAGEMENT_PERMISSION_VERSION = 'projectManagementPermissionVersion', PROJECT_MANAGEMENT_PERMISSION_VERSION = 'projectManagementPermissionVersion',
PROJECT_MANAGEMENT_PERMISSION_USER_GROUP = 'projectManagementPermissionUserGroup', PROJECT_MANAGEMENT_PERMISSION_USER_GROUP = 'projectManagementPermissionUserGroup',
PROJECT_MANAGEMENT_PERMISSION_MEMBER = 'projectManagementPermissionMember', PROJECT_MANAGEMENT_PERMISSION_MEMBER = 'projectManagementPermissionMember',

View File

@ -27,6 +27,7 @@ export enum TableKeyEnum {
FILE_MANAGEMENT_CASE = 'fileManagementCase', FILE_MANAGEMENT_CASE = 'fileManagementCase',
FILE_MANAGEMENT_VERSION = 'fileManagementVersion', FILE_MANAGEMENT_VERSION = 'fileManagementVersion',
PROJECT_MANAGEMENT_MENU_FALSE_ALERT = 'projectManagementMenuFalseAlert', PROJECT_MANAGEMENT_MENU_FALSE_ALERT = 'projectManagementMenuFalseAlert',
ORGANIZATION_TEMPLATE_DEFECT_TABLE = 'organizationTemplateManagementDefect',
} }
// 具有特殊功能的列 // 具有特殊功能的列

View File

@ -21,7 +21,7 @@ export enum TemplateIconEnum {
export enum TemplateCardEnum { export enum TemplateCardEnum {
FUNCTIONAL = 'caseTemplate', // 用例模版 FUNCTIONAL = 'caseTemplate', // 用例模版
API = 'api_ui_Template', // ui模板 API = 'api_ui_Template', // ui模板
UI = 'api_ui_Template', // API模板 UI = 'uiTemplate', // API模板
TEST_PLAN = 'testPlanTemplate', // 测试计划模板 TEST_PLAN = 'testPlanTemplate', // 测试计划模板
BUG = 'defectTemplate', // 缺陷模板 BUG = 'defectTemplate', // 缺陷模板
} }

View File

@ -3,28 +3,41 @@ import { onBeforeRouteLeave } from 'vue-router';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
const isSave = ref(false);
// 离开页面确认提示 // 离开页面确认提示
export default function useLeaveUnSaveTip() { export default function useLeaveUnSaveTip() {
const { openModal } = useModal(); const { openModal } = useModal();
const { t } = useI18n(); const { t } = useI18n();
const setState = (flag: boolean) => {
isSave.value = flag;
};
onBeforeRouteLeave((to, from, next) => { onBeforeRouteLeave((to, from, next) => {
if (to.path === from.path) { if (to.path === from.path) {
return; return;
} }
openModal({
type: 'error', if (!isSave.value) {
title: t('common.unSaveLeaveTitle'), openModal({
content: t('common.unSaveLeaveContent'), type: 'error',
okText: t('common.leave'), title: t('common.unSaveLeaveTitle'),
cancelText: t('common.cancel'), content: t('common.unSaveLeaveContent'),
okButtonProps: { okText: t('common.leave'),
status: 'normal', cancelText: t('common.cancel'),
}, okButtonProps: {
onBeforeOk: async () => { status: 'normal',
next(); },
}, onBeforeOk: async () => {
hideCancel: false, next();
}); },
hideCancel: false,
});
} else {
next();
}
}); });
return {
setState,
};
} }

View File

@ -53,11 +53,12 @@ export default {
'menu.settings.organization.userGroup': 'User Group', 'menu.settings.organization.userGroup': 'User Group',
'menu.settings.organization.project': 'Project', 'menu.settings.organization.project': 'Project',
'menu.settings.organization.template': 'Template', 'menu.settings.organization.template': 'Template',
'menu.settings.organization.bugTemplate': 'BUG Template',
'menu.settings.organization.templateFieldSetting': 'fieldSetting', 'menu.settings.organization.templateFieldSetting': 'fieldSetting',
'menu.settings.organization.templateManagementList': 'Template list', 'menu.settings.organization.templateManagementList': 'Template list',
'menu.settings.organization.templateManagementEdit': 'Update Template', 'menu.settings.organization.templateManagementEdit': 'Update Template',
'menu.settings.organization.templateManagementDetail': 'Create Template', 'menu.settings.organization.templateManagementDetail': 'Create Template',
'menu.settings.organization.templateManagementWorkFlow': 'WorkFlow', 'menu.settings.organization.templateManagementWorkFlow': 'WorkFlow Setting',
'menu.settings.organization.serviceIntegration': 'Service Integration', 'menu.settings.organization.serviceIntegration': 'Service Integration',
'menu.settings.organization.log': 'Log', 'menu.settings.organization.log': 'Log',
'navbar.action.locale': 'Switch to English', 'navbar.action.locale': 'Switch to English',

View File

@ -29,6 +29,7 @@ export default {
'menu.caseManagement': '功能测试', 'menu.caseManagement': '功能测试',
'menu.performanceTest': '性能测试', 'menu.performanceTest': '性能测试',
'menu.projectManagement': '项目管理', 'menu.projectManagement': '项目管理',
'menu.projectManagement.templateManager': '模板管理',
'menu.projectManagement.log': '日志', 'menu.projectManagement.log': '日志',
'menu.projectManagement.fileManagement': '文件管理', 'menu.projectManagement.fileManagement': '文件管理',
'menu.projectManagement.messageManagement': '消息管理', 'menu.projectManagement.messageManagement': '消息管理',
@ -52,13 +53,14 @@ export default {
'menu.settings.organization.userGroup': '用户组', 'menu.settings.organization.userGroup': '用户组',
'menu.settings.organization.project': '项目', 'menu.settings.organization.project': '项目',
'menu.settings.organization.serviceIntegration': '服务集成', 'menu.settings.organization.serviceIntegration': '服务集成',
'menu.settings.organization.template': '模版', 'menu.settings.organization.template': '模板',
'menu.settings.organization.bugTemplate': '缺陷模板',
'menu.settings.organization.templateFieldSetting': '字段设置', 'menu.settings.organization.templateFieldSetting': '字段设置',
'menu.settings.organization.templateManagementList': '模版列表', 'menu.settings.organization.templateManagementList': '模版列表',
'menu.settings.organization.templateManagementDetail': '创建模版', 'menu.settings.organization.templateManagementDetail': '创建模版',
'menu.settings.organization.templateManagementCopy': '复制模版', 'menu.settings.organization.templateManagementCopy': '复制模版',
'menu.settings.organization.templateManagementEdit': '更新模板', 'menu.settings.organization.templateManagementEdit': '更新模板',
'menu.settings.organization.templateManagementWorkFlow': '工作流', 'menu.settings.organization.templateManagementWorkFlow': '工作流设置',
'menu.settings.organization.log': '日志', 'menu.settings.organization.log': '日志',
'navbar.action.locale': '切换为中文', 'navbar.action.locale': '切换为中文',
...sys, ...sys,

View File

@ -27,7 +27,7 @@ export interface FieldOptions {
fieldId?: string; fieldId?: string;
value: string | string[] | number | number[]; value: string | string[] | number | number[];
text: string; text: string;
internal?: boolean; internal?: boolean; // 是否是内置模版
} }
// 自定义字段 // 自定义字段
@ -69,6 +69,7 @@ export interface AddOrUpdateField {
scopeId: string; // 组织或项目ID scopeId: string; // 组织或项目ID
options?: FieldOption[]; options?: FieldOption[];
enableOptionKey: boolean; enableOptionKey: boolean;
[key: string]: any;
} }
export interface fieldIconAndNameModal { export interface fieldIconAndNameModal {
@ -98,8 +99,8 @@ export interface OrdTemplateManagement {
export interface CustomField { export interface CustomField {
fieldId: string; fieldId: string;
required: boolean; // 是否必填 required?: boolean; // 是否必填
apiFieldId: string; // api字段名 apiFieldId?: string; // api字段名
defaultValue: string | string[] | null | number; // 默认值 defaultValue: string | string[] | null | number; // 默认值
} }
@ -111,6 +112,8 @@ export interface ActionTemplateManage {
enableThirdPart?: boolean; // 是否开启api字段名配置 enableThirdPart?: boolean; // 是否开启api字段名配置
scene?: SeneType; scene?: SeneType;
customFields?: CustomField[]; customFields?: CustomField[];
fieldType?: string;
systemFields?: Record<string, any>[];
} }
// 工作流列表字段 // 工作流列表字段
@ -153,7 +156,7 @@ export interface SetStateType {
// 更新流转状态 // 更新流转状态
export interface UpdateWorkFlowSetting { export interface UpdateWorkFlowSetting {
fromId: string; fromId: string; // 开始id
toId: string; toId: string; // 结束id
enable: boolean; enable: boolean;
} }

View File

@ -47,16 +47,6 @@ const ProjectManagement: AppRouteRecordRaw = {
roles: ['*'], roles: ['*'],
}, },
}, },
// 模版管理
{
path: 'templateManager',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_TEMPLATE,
component: () => import('@/views/project-management/projectAndPermission/templateManagement/index.vue'),
meta: {
locale: 'project.permission.templateManager',
roles: ['*'],
},
},
// 项目版本 // 项目版本
{ {
path: 'projectVersion', path: 'projectVersion',
@ -89,6 +79,104 @@ const ProjectManagement: AppRouteRecordRaw = {
}, },
], ],
}, },
// 項目管理模板
{
path: 'templateManager',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE,
component: () => import('@/views/project-management/template/index.vue'),
meta: {
locale: 'menu.projectManagement.templateManager',
roles: ['*'],
isTopMenu: true,
},
},
// 模板列表-模板字段设置
{
path: 'filedSetting',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_FIELD_SETTING,
component: () => import('@/views/project-management/template/components/projectFieldSetting.vue'),
meta: {
locale: 'menu.settings.organization.templateFieldSetting',
roles: ['*'],
breadcrumbs: [
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE,
locale: 'menu.projectManagement.templateManager',
},
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_FIELD_SETTING,
locale: 'menu.settings.organization.templateFieldSetting',
editLocale: 'menu.settings.organization.templateFieldSetting',
},
],
},
},
// 模板列表-模板管理列表
{
path: 'templateManagement',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT,
component: () => import('@/views/project-management/template/components/templateManagement.vue'),
meta: {
locale: 'menu.settings.organization.templateManagement',
roles: ['*'],
breadcrumbs: [
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE,
locale: 'menu.settings.organization.template',
},
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT,
locale: 'menu.settings.organization.templateManagementList',
editLocale: 'menu.settings.organization.templateManagementList',
},
],
},
},
// 项目-模板-创建模板和模板详情
{
path: 'templateDetail/:mode?',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL,
component: () => import('@/views/project-management/template/components/proTemplateDetail.vue'),
meta: {
locale: 'menu.settings.organization.templateManagementDetail',
roles: ['*'],
breadcrumbs: [
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE,
locale: 'menu.settings.organization.template',
},
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT,
locale: 'menu.settings.organization.templateManagementList',
},
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL,
locale: 'menu.settings.organization.templateManagementDetail',
editLocale: 'menu.settings.organization.templateManagementEdit',
},
],
},
},
// 模板列表-模板管理-工作流
{
path: 'templateWorkFlow',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_WORKFLOW,
component: () => import('@/views/project-management/template/components/workFlowTableIndex.vue'),
meta: {
locale: 'menu.settings.organization.templateManagementWorkFlow',
roles: ['*'],
breadcrumbs: [
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE,
locale: 'menu.settings.organization.bugTemplate',
},
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_WORKFLOW,
locale: 'menu.settings.organization.templateManagementWorkFlow',
},
],
},
},
// 文件管理 // 文件管理
{ {
path: 'fileManagement', path: 'fileManagement',

View File

@ -192,7 +192,7 @@ const Setting: AppRouteRecordRaw = {
{ {
path: 'templateFiledSetting', path: 'templateFiledSetting',
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_FILED_SETTING, name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_FILED_SETTING,
component: () => import('@/views/setting/organization/template/components/fieldSetting.vue'), component: () => import('@/views/setting/organization/template/components/ordFieldSetting.vue'),
meta: { meta: {
locale: 'menu.settings.organization.templateFieldSetting', locale: 'menu.settings.organization.templateFieldSetting',
roles: ['*'], roles: ['*'],
@ -259,14 +259,14 @@ const Setting: AppRouteRecordRaw = {
{ {
path: 'templateWorkFlow', path: 'templateWorkFlow',
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_WORKFLOW, name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_WORKFLOW,
component: () => import('@/views/setting/organization/template/components/workflowTable.vue'), component: () => import('@/views/setting/organization/template/components/workFlowTableIndex.vue'),
meta: { meta: {
locale: 'menu.settings.organization.templateManagementWorkFlow', locale: 'menu.settings.organization.templateManagementWorkFlow',
roles: ['*'], roles: ['*'],
breadcrumbs: [ breadcrumbs: [
{ {
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE, name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE,
locale: 'menu.settings.organization.template', locale: 'menu.settings.organization.bugTemplate',
}, },
{ {
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_WORKFLOW, name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_WORKFLOW,

View File

@ -73,6 +73,7 @@ const useFormCreateStore = defineStore('form-create', {
'modelValue': item.value, 'modelValue': item.value,
'options': currentOptions, // 当前已经存在的options 'options': currentOptions, // 当前已经存在的options
'formKey': key, // 对应pinia-form-create里边初始化的KEY 'formKey': key, // 对应pinia-form-create里边初始化的KEY
'disabled': item?.props?.disabled,
}, },
}; };
// 如果不存在关联name删除link关联属性 // 如果不存在关联name删除link关联属性
@ -87,9 +88,7 @@ const useFormCreateStore = defineStore('form-create', {
} }
return {}; return {};
}); });
if (result && result.length) { this.setInitdRules(key, result as FormRuleItem[]);
this.setInitdRules(key, result as FormRuleItem[]);
}
}, },
// 初始化好了的格式给formCreate // 初始化好了的格式给formCreate
setInitdRules(key: FormCreateKeyEnum[keyof FormCreateKeyEnum], result: FormRuleItem[]) { setInitdRules(key: FormCreateKeyEnum[keyof FormCreateKeyEnum], result: FormRuleItem[]) {

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { isEnableTemplate } from '@/api/modules/setting/template'; import { getOrdTemplate, getProTemplate } from '@/api/modules/setting/template';
import useAppStore from '../app'; import useAppStore from '../app';
@ -9,40 +9,36 @@ const appStore = useAppStore();
const useTemplateStore = defineStore('template', { const useTemplateStore = defineStore('template', {
persist: true, persist: true,
state: (): { state: (): {
templateStatus: Record<string, boolean>; ordStatus: Record<string, boolean>;
projectStatus: Record<string, boolean>;
} => ({ } => ({
templateStatus: { ordStatus: {
FUNCTIONAL: true, FUNCTIONAL: false,
API: true, API: false,
UI: true, UI: false,
TEST_PLAN: true, TEST_PLAN: false,
BUG: true, BUG: false,
},
projectStatus: {
FUNCTIONAL: false,
API: false,
UI: false,
TEST_PLAN: false,
BUG: false,
}, },
}), }),
actions: { actions: {
// 模板列表的状态 // 模板列表的状态
async getStatus() { async getStatus() {
const currentOrgId = computed(() => appStore.currentOrgId);
const currentProjectId = computed(() => appStore.currentProjectId);
try { try {
this.templateStatus = await isEnableTemplate(appStore.currentOrgId); this.ordStatus = await getOrdTemplate(currentOrgId.value);
this.projectStatus = await getProTemplate(currentProjectId.value);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
}, },
// 获取项目模板状态
getProjectTemplateState() {
const { FUNCTIONAL, API, UI, TEST_PLAN, BUG } = this.templateStatus;
return {
FUNCTIONAL: !FUNCTIONAL,
API: !API,
UI: !UI,
TEST_PLAN: !TEST_PLAN,
BUG: !BUG,
} as Record<string, boolean>;
},
// 获取组织模板状态
getOrdTemplateState() {
return this.templateStatus;
},
}, },
}); });

View File

@ -64,12 +64,6 @@
level: 2, level: 2,
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_MENU_MANAGEMENT, name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_MENU_MANAGEMENT,
}, },
{
key: 'templateManager',
title: 'project.permission.templateManager',
level: 2,
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_TEMPLATE,
},
{ {
key: 'projectVersion', key: 'projectVersion',
title: 'project.permission.projectVersion', title: 'project.permission.projectVersion',

View File

@ -58,7 +58,7 @@
{{ t('organization.member.statusDisable') }} {{ t('organization.member.statusDisable') }}
</div> </div>
</template> </template>
<template #action="{ record }"> <template #operation="{ record }">
<MsRemoveButton <MsRemoveButton
position="br" position="br"
:title="t('project.member.deleteMemberTip', { name: characterLimit(record.name) })" :title="t('project.member.deleteMemberTip', { name: characterLimit(record.name) })"
@ -108,7 +108,7 @@
} from '@/api/modules/project-management/projectMember'; } from '@/api/modules/project-management/projectMember';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import { useAppStore } from '@/store'; import { useAppStore, useTableStore } from '@/store';
import { characterLimit } from '@/utils'; import { characterLimit } from '@/utils';
import type { import type {
@ -122,7 +122,7 @@
const { t } = useI18n(); const { t } = useI18n();
const { openModal } = useModal(); const { openModal } = useModal();
const appStore = useAppStore(); const appStore = useAppStore();
const tableStore = useTableStore();
const lastProjectId = computed(() => appStore.getCurrentProjectId); const lastProjectId = computed(() => appStore.getCurrentProjectId);
const columns: MsTableColumn = [ const columns: MsTableColumn = [
@ -131,18 +131,22 @@
dataIndex: 'name', dataIndex: 'name',
showInTable: true, showInTable: true,
showTooltip: true, showTooltip: true,
fixed: 'left', sortIndex: 0,
ellipsis: true,
showDrag: false,
}, },
{ {
title: 'project.member.tableColumnEmail', title: 'project.member.tableColumnEmail',
dataIndex: 'email', dataIndex: 'email',
showInTable: true, showInTable: true,
showTooltip: true, showTooltip: true,
showDrag: true,
}, },
{ {
title: 'project.member.tableColumnPhone', title: 'project.member.tableColumnPhone',
dataIndex: 'phone', dataIndex: 'phone',
showInTable: true, showInTable: true,
showDrag: true,
width: 150, width: 150,
}, },
{ {
@ -150,6 +154,7 @@
slotName: 'userRole', slotName: 'userRole',
dataIndex: 'userRoleIdNameMap', dataIndex: 'userRoleIdNameMap',
showInTable: true, showInTable: true,
showDrag: true,
width: 300, width: 300,
}, },
{ {
@ -161,10 +166,12 @@
}, },
{ {
title: 'project.member.tableColumnActions', title: 'project.member.tableColumnActions',
slotName: 'action', slotName: 'operation',
fixed: 'right', fixed: 'right',
dataIndex: 'operation',
width: 100, width: 100,
showInTable: true, showInTable: true,
showDrag: false,
}, },
]; ];
@ -368,6 +375,7 @@
initData(); initData();
initOptions(); initOptions();
}); });
tableStore.initColumn(TableKeyEnum.PROJECT_MEMBER, columns, 'drawer');
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -1,9 +0,0 @@
<template>
<div> 模版管理 </div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
</script>
<style scoped></style>

View File

@ -0,0 +1,403 @@
<template>
<MsCard
:loading="loading"
:title="title"
:is-edit="isEdit && route.params.mode !== 'copy'"
has-breadcrumb
@save="saveHandler"
@save-and-continue="saveHandler(true)"
>
<template #headerRight>
<div class="rightBtn">
<a-button v-show="isPreview" type="outline" class="text-[var(--color-text-1)]" @click="togglePreview">{{
t('system.orgTemplate.templatePreview')
}}</a-button>
<a-button v-show="!isPreview" type="outline" class="text-[var(--color-text-1)]" @click="togglePreview">{{
t('system.orgTemplate.exitPreview')
}}</a-button>
</div>
</template>
<!-- 非预览模式 -->
<div v-if="isPreview" class="nonPreview">
<a-form ref="formRef" :model="templateForm" layout="vertical">
<a-form-item
:label="t('system.orgTemplate.templateName')"
field="name"
asterisk-position="end"
:rules="[{ required: true, message: t('system.orgTemplate.templateNameRules') }]"
>
<a-input
v-model:model-value="templateForm.name"
:placeholder="t('system.orgTemplate.templateNamePlaceholder')"
:max-length="255"
show-word-limit
class="max-w-[732px]"
></a-input>
</a-form-item>
<a-form-item field="remark" :label="t('system.orgTemplate.description')" asterisk-position="end">
<a-textarea
v-model="templateForm.remark"
:max-length="255"
:placeholder="t('system.orgTemplate.resDescription')"
:auto-size="{
maxRows: 1,
}"
class="max-w-[732px]"
></a-textarea>
</a-form-item>
<a-form-item v-if="route.query.type === 'BUG'" field="remark" label="" asterisk-position="end"
><a-checkbox v-model="templateForm.enableThirdPart">{{ t('system.orgTemplate.thirdParty') }}</a-checkbox>
</a-form-item>
<a-form-item
v-if="route.query.type === 'BUG'"
field="fieldType"
:label="t('system.orgTemplate.columnFieldType')"
asterisk-position="end"
>
<a-radio-group v-model="fieldType" type="button">
<a-radio value="custom">{{ t('system.orgTemplate.custom') }}</a-radio>
<a-radio value="detail">{{ t('system.orgTemplate.details') }}</a-radio>
</a-radio-group>
</a-form-item>
</a-form>
<!-- 已有字段表 -->
<TemplateManagementTable
v-if="fieldType === 'custom'"
ref="templateFieldTableRef"
v-model:select-data="selectData"
:data="(totalTemplateField as DefinedFieldItem[])"
:enable-third-part="templateForm.enableThirdPart"
mode="project"
@update="updateHandler"
/>
<!-- 缺陷详情表 -->
<a-form
v-if="fieldType === 'detail'"
ref="defectFormRef"
class="rounded-[4px]"
:model="defectForm"
layout="vertical"
>
<a-form-item
class="max-w-[732px]"
field="name"
:label="t('system.orgTemplate.defectName')"
:rules="[{ required: true, message: t('system.orgTemplate.defectNamePlaceholder') }]"
required
asterisk-position="end"
>
<a-input
v-model="defectForm.name"
:max-length="255"
:placeholder="t('system.orgTemplate.defectNamePlaceholder')"
show-word-limit
allow-clear
></a-input>
<MsFormItemSub :text="t('system.orgTemplate.defectNameTip')" :show-fill-icon="false" />
</a-form-item>
<a-form-item
field="precondition"
:label="t('system.orgTemplate.defectContent')"
asterisk-position="end"
class="max-w-[732px]"
>
<MsRichText v-model:model-value="defectForm.description" />
<MsFormItemSub :text="t('system.orgTemplate.defectContentTip')" :show-fill-icon="false" />
</a-form-item>
</a-form>
</div>
<!-- 预览模式 -->
<PreviewTemplate
v-else
:select-field="(selectData as DefinedFieldItem[])"
:template-type="route.query.type"
:defect-form="defectForm"
/>
</MsCard>
</template>
<script setup lang="ts">
/**
* @description 系统管理-项目-模版-模版管理-创建&编辑
*/
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { FormInstance, Message } from '@arco-design/web-vue';
import MsCard from '@/components/pure/ms-card/index.vue';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
import MsFormItemSub from '@/components/business/ms-form-item-sub/index.vue';
import TemplateManagementTable from '@/views/setting/organization/template/components/templateManagementTable.vue';
import PreviewTemplate from '@/views/setting/organization/template/components/viewTemplate.vue';
import {
createProjectTemplateInfo,
getProjectFieldList,
getProjectTemplateInfo,
updateProjectTemplateInfo,
} from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n';
import useLeaveUnSaveTip from '@/hooks/useLeaveUnSaveTip';
import { useAppStore } from '@/store';
import { sleep } from '@/utils';
import { scrollIntoView } from '@/utils/dom';
import type { ActionTemplateManage, CustomField, DefinedFieldItem } from '@/models/setting/template';
import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
import {
getCardList,
getCustomDetailFields,
getTotalFieldOptionList,
} from '@/views/setting/organization/template/components/fieldSetting';
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const appStore = useAppStore();
const currentProjectId = computed(() => appStore.currentProjectId);
const { setState } = useLeaveUnSaveTip();
setState(false);
const title = ref('');
const loading = ref(false);
const initTemplateForm: ActionTemplateManage = {
id: '',
name: '',
remark: '',
scopeId: currentProjectId.value,
enableThirdPart: false,
};
const fieldType = ref<string>('custom'); //
const templateForm = ref<ActionTemplateManage>({ ...initTemplateForm });
const selectData = ref<DefinedFieldItem[]>([]); //
const selectFiled = ref<DefinedFieldItem[]>([]);
const formRef = ref<FormInstance>();
const totalTemplateField = ref<DefinedFieldItem[]>([]);
const isEdit = computed(() => !!route.query.id);
const isEditField = ref<boolean>(false);
const systemFieldData = ref<CustomField[]>([]);
//
const getTemplateInfo = async () => {
try {
loading.value = true;
const res = await getProjectTemplateInfo(route.query.id as string);
const { name, customFields, systemFields } = res;
templateForm.value = {
...res,
name: route.params.mode === 'copy' ? `${name}_copy` : name,
};
if (route.params.mode === 'copy') {
templateForm.value.id = undefined;
}
selectData.value = getCustomDetailFields(totalTemplateField.value as DefinedFieldItem[], customFields);
systemFieldData.value = systemFields;
} catch (error) {
console.log(error);
} finally {
loading.value = false;
}
};
//
const getFieldOptionList = () => {
totalTemplateField.value = getTotalFieldOptionList(totalTemplateField.value as DefinedFieldItem[]);
//
if (!isEdit.value && !isEditField.value) {
selectData.value = totalTemplateField.value.filter((item) => item.internal);
}
};
//
const getClassifyField = async () => {
try {
totalTemplateField.value = await getProjectFieldList({
scopedId: currentProjectId.value,
scene: route.query.type,
});
getFieldOptionList();
//
if (isEditField.value) {
selectData.value = totalTemplateField.value.filter(
(item) => selectFiled.value.map((it) => it.id).indexOf(item.id) > -1
);
}
if (isEdit.value) {
getTemplateInfo();
}
} catch (error) {
console.log(error);
}
};
watchEffect(async () => {
if (isEdit.value && route.params.mode === 'copy') {
title.value = t('system.orgTemplate.copyTemplate');
getClassifyField();
} else if (isEdit.value) {
title.value = t('menu.settings.organization.templateManagementEdit');
getClassifyField();
} else {
title.value = t('menu.settings.organization.templateManagementDetail');
}
});
const initDefectForm = {
name: '',
description: '',
};
//
const defectForm = ref<Record<string, any>>({ ...initDefectForm });
//
function getTemplateParams(): ActionTemplateManage {
const result = selectData.value.map((item) => {
if (item.formRules?.length) {
const { value } = item.formRules[0];
return {
fieldId: item.id,
required: item.required,
apiFieldId: item.apiFieldId,
defaultValue: value,
};
}
return [];
});
//
const sysDetailFields = Object.keys(defectForm.value).map((formKey: string) => {
return {
fieldId: formKey,
defaultValue: defectForm.value[formKey],
};
});
const { name, remark, enableThirdPart, id } = templateForm.value;
return {
id,
name,
remark,
enableThirdPart,
customFields: result as CustomField[],
scopeId: currentProjectId.value,
scene: route.query.type,
systemFields: sysDetailFields,
};
}
function resetForm() {
templateForm.value = { ...initTemplateForm };
}
const isContinueFlag = ref(false);
//
async function save() {
try {
loading.value = true;
const params = getTemplateParams();
if (isEdit.value && route.params.mode !== 'copy') {
await updateProjectTemplateInfo(params);
Message.success(t('system.orgTemplate.updateSuccess'));
} else {
await createProjectTemplateInfo(params);
Message.success(t('system.orgTemplate.addSuccess'));
}
if (isContinueFlag.value) {
resetForm();
} else {
await sleep(300);
router.push({ name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT, query: route.query });
setState(true);
}
} catch (error) {
console.log(error);
} finally {
loading.value = false;
}
}
//
function saveHandler(isContinue = false) {
isContinueFlag.value = isContinue;
formRef.value?.validate().then((res) => {
if (!res) {
return save();
}
return scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
});
}
//
const isPreview = ref<boolean>(true); //
function togglePreview() {
isPreview.value = !isPreview.value;
}
// title
const breadTitle = computed(() => {
const firstBreadTitle = getCardList('organization').find((item: any) => 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;
selectFiled.value = selectData.value;
getClassifyField();
};
watch(
() => systemFieldData.value,
(val) => {
if (val) {
defectForm.value = { ...initDefectForm };
systemFieldData.value.forEach((item) => {
defectForm.value[item.fieldId] = item.defaultValue;
});
}
},
{ deep: true }
);
onMounted(() => {
setBreadText();
getClassifyField();
if (!isEdit.value) {
selectData.value = totalTemplateField.value.filter((item) => item.internal);
}
});
</script>
<style scoped lang="less">
.rightBtn {
:deep(.arco-btn-outline) {
border-color: var(--color-text-input-border) !important;
color: var(--color-text-1) !important;
}
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<MsCard has-breadcrumb simple> <FieldSetting mode="project" /></MsCard>
</template>
<script setup lang="ts">
/**
* @description 项目管理-模板-字段设置-首页
*/
import { useRoute } from 'vue-router';
import MsCard from '@/components/pure/ms-card/index.vue';
import FieldSetting from '@/views/setting/organization/template/components/fieldSetting.vue';
import { useAppStore } from '@/store';
import { getCardList } from '@/views/setting/organization/template/components/fieldSetting';
const route = useRoute();
const appStore = useAppStore();
//
const updateBreadcrumbList = () => {
const { breadcrumbList } = appStore;
const breadTitle = getCardList('project').find((item: any) => item.key === route.query.type);
if (breadTitle) {
breadcrumbList[0].locale = breadTitle.name;
appStore.setBreadcrumbList(breadcrumbList);
}
};
onMounted(() => {
updateBreadcrumbList();
});
</script>
<style scoped></style>

View File

@ -0,0 +1,369 @@
<template>
<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>
<a-button v-else type="primary" :disabled="false" @click="createTemplate">
{{ t('system.orgTemplate.createTemplate') }}
</a-button>
<a-input-search
v-model:model-value="keyword"
:placeholder="t('system.orgTemplate.searchTip')"
class="w-[230px]"
allow-clear
@search="searchFiled"
@press-enter="searchFiled"
></a-input-search>
</div>
<MsBaseTable v-bind="propsRes" ref="tableRef" v-on="propsEvent">
<template #defaultTemplate="{ record }">
<a-switch
v-model="record.enableDefault"
:disabled="record.enableDefault || isEnableOrdTemplate"
size="small"
@change="(value) => changeDefault(value, record)"
/>
</template>
<template #enableThirdPart="{ record }">
{{ record.enableThirdPart ? t('system.orgTemplate.yes') : t('system.orgTemplate.no') }}
</template>
<template #name="{ record }">
<span class="ml-2 cursor-pointer text-[rgb(var(--primary-5))]" @click="previewDetail(record)">{{
record.name
}}</span>
<MsTag v-if="record.internal" size="small" class="ml-2">{{ t('system.orgTemplate.isSystem') }}</MsTag>
</template>
<template #operation="{ record }">
<div class="flex flex-row flex-nowrap">
<MsButton @click="editTemplate(record.id)">{{ t('system.orgTemplate.edit') }}</MsButton>
<MsButton class="!mr-0" @click="copyTemplate(record.id)">{{ t('system.orgTemplate.copy') }}</MsButton>
<a-divider v-if="!record.internal" direction="vertical" />
<MsTableMoreAction
v-if="!record.internal"
:list="moreActions"
@select="(item) => handleMoreActionSelect(item, record)"
/>
</div>
</template>
</MsBaseTable>
<MsDrawer
v-model:visible="showDetailVisible"
:title="titleDetail"
:width="1200"
:footer="false"
unmount-on-close
@cancel="handleCancel"
>
<PreviewTemplate
:select-field="(selectData as DefinedFieldItem[])"
:template-type="route.query.type"
:defect-form="defectForm"
/>
</MsDrawer>
</MsCard>
</template>
<script setup lang="ts">
/**
* @description 系统管理-项目-模版-模版管理列表
*/
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsCard from '@/components/pure/ms-card/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import PreviewTemplate from '@/views/setting/organization/template/components/viewTemplate.vue';
import {
deleteOrdTemplate,
getProjectFieldList,
getProjectTemplateInfo,
getProjectTemplateList,
setDefaultTemplate,
} from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import router from '@/router';
import { useAppStore, useTableStore } from '@/store';
import useTemplateStore from '@/store/modules/setting/template';
import { characterLimit } from '@/utils';
import type { DefinedFieldItem, OrdTemplateManagement } from '@/models/setting/template';
import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import {
getCardList,
getCustomDetailFields,
getTotalFieldOptionList,
} from '@/views/setting/organization/template/components/fieldSetting';
const route = useRoute();
const { t } = useI18n();
const tableStore = useTableStore();
const appStore = useAppStore();
const templateStore = useTemplateStore();
const { openModal } = useModal();
const keyword = ref('');
const currentProjectId = computed(() => appStore.currentProjectId);
const sceneType = computed(() => route.query.type);
const fieldColumns: MsTableColumn = [
{
title: 'system.orgTemplate.columnTemplateName',
slotName: 'name',
dataIndex: 'name',
width: 300,
fixed: 'left',
showDrag: true,
showInTable: true,
},
{
title: 'system.orgTemplate.defaultTemplate',
dataIndex: 'remark',
slotName: 'defaultTemplate',
showDrag: true,
showInTable: true,
},
{
title: 'system.orgTemplate.description',
dataIndex: 'remark',
showDrag: true,
showInTable: true,
width: 300,
showTooltip: true,
},
{
title: 'system.orgTemplate.columnFieldUpdatedTime',
dataIndex: 'updateTime',
showDrag: true,
showInTable: true,
},
{
title: 'system.orgTemplate.operation',
slotName: 'operation',
dataIndex: 'operation',
fixed: 'right',
width: 200,
showInTable: true,
showDrag: false,
},
];
const isThirdParty = {
title: 'system.orgTemplate.isThirdParty',
dataIndex: 'enableThirdPart',
slotName: 'enableThirdPart',
showDrag: true,
showInTable: true,
};
await tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT, fieldColumns, 'drawer');
const { propsRes, propsEvent, loadList, setLoadListParams, setProps } = useTable(getProjectTemplateList, {
tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT,
scroll: { x: '1400px' },
selectable: false,
noDisable: true,
size: 'default',
showSetting: true,
showPagination: false,
heightUsed: 380,
});
const scene = route.query.type;
const isEnableOrdTemplate = computed(() => {
return !templateStore.projectStatus[scene as string];
});
const totalList = ref<OrdTemplateManagement[]>([]);
//
const searchFiled = async () => {
try {
totalList.value = await getProjectTemplateList({ projectId: currentProjectId.value, scene });
const filterData = totalList.value.filter((item: OrdTemplateManagement) => item.name.includes(keyword.value));
setProps({ data: filterData });
} catch (error) {
console.log(error);
}
};
const moreActions: ActionsItem[] = [
{
label: 'system.userGroup.delete',
danger: true,
eventTag: 'delete',
},
];
//
const handlerDelete = (record: any) => {
openModal({
type: 'error',
title: t('system.orgTemplate.deleteTemplateTitle', { name: characterLimit(record.name) }),
content: t('system.userGroup.beforeDeleteUserGroup'),
okText: t('system.userGroup.confirmDelete'),
cancelText: t('system.userGroup.cancel'),
okButtonProps: {
status: 'danger',
},
onBeforeOk: async () => {
try {
if (record.id) await deleteOrdTemplate(record.id);
Message.success(t('common.deleteSuccess'));
loadList();
} catch (error) {
console.log(error);
}
},
hideCancel: false,
});
};
//
const handleMoreActionSelect = (item: ActionsItem, record: any) => {
if (item.eventTag === 'delete') {
handlerDelete(record);
}
};
const fetchData = async () => {
setLoadListParams({ projectId: currentProjectId.value, scene: route.query.type });
await loadList();
};
//
const changeDefault = async (value: any, record: OrdTemplateManagement) => {
if (value) {
try {
await setDefaultTemplate(currentProjectId.value, record.id);
Message.success(t('system.orgTemplate.setSuccessfully'));
fetchData();
} catch (error) {
console.log(error);
}
}
};
//
const createTemplate = () => {
router.push({
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL,
query: {
type: route.query.type,
},
params: {
mode: 'create',
},
});
};
//
const editTemplate = (id: string) => {
router.push({
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL,
query: {
id,
type: route.query.type,
},
params: {
mode: 'edit',
},
});
};
//
const copyTemplate = (id: string) => {
router.push({
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL,
query: {
id,
type: route.query.type,
},
params: {
mode: 'copy',
},
});
};
const showDetailVisible = ref<boolean>(false);
const selectData = ref<DefinedFieldItem[]>([]);
const totalData = ref<DefinedFieldItem[]>([]);
//
const getFieldOptionList = () => {
totalData.value = getTotalFieldOptionList(totalData.value as DefinedFieldItem[]);
};
const initDetailForm = {
name: '',
description: '',
};
const titleDetail = ref<string>();
const defectForm = ref<Record<string, any>>({ ...initDetailForm });
//
const previewDetail = async (record: OrdTemplateManagement) => {
showDetailVisible.value = true;
titleDetail.value = record.name;
try {
totalData.value = await getProjectFieldList({ scopedId: currentProjectId.value, scene: route.query.type });
getFieldOptionList();
const res = await getProjectTemplateInfo(record.id);
selectData.value = getCustomDetailFields(totalData.value as DefinedFieldItem[], res.customFields);
res.systemFields.forEach((item: any) => {
defectForm.value[item.fieldId] = item.defaultValue;
});
} catch (error) {
console.log(error);
}
};
const handleCancel = () => {
showDetailVisible.value = false;
defectForm.value = { ...initDetailForm };
};
//
const updateBreadcrumbList = () => {
const { breadcrumbList } = appStore;
const breadTitle = getCardList('project').find((item: any) => item.key === route.query.type);
if (breadTitle) {
breadcrumbList[0].locale = breadTitle.name;
appStore.setBreadcrumbList(breadcrumbList);
}
};
const tableRef = ref();
function updateColumns() {
const columns =
sceneType.value === 'BUG' ? fieldColumns.slice(0, 2).concat(isThirdParty, fieldColumns.slice(2)) : fieldColumns;
if (isEnableOrdTemplate.value) {
const result = columns.slice(0, fieldColumns.length - 1);
tableRef.value.initColumn(result);
} else {
tableRef.value.initColumn(columns);
}
}
onMounted(() => {
updateBreadcrumbList();
fetchData();
updateColumns();
});
</script>
<style scoped lang="less">
.system-flag {
background: var(--color-text-n8);
@apply ml-2 rounded p-1 text-xs;
}
</style>

View File

@ -0,0 +1,15 @@
<template>
<MsCard has-breadcrumb simple>
<WorkflowTable mode="project" />
</MsCard>
</template>
<script setup lang="ts">
/**
* @description 项目管理-模版-模版管理-工作流首页
*/
import MsCard from '@/components/pure/ms-card/index.vue';
import WorkflowTable from '@/views/setting/organization/template/components/workflowTable.vue';
</script>
<style scoped></style>

View File

@ -0,0 +1,89 @@
<template>
<MsCard simple>
<MsCardList
mode="static"
:card-min-width="360"
class="flex-1"
:shadow-limit="50"
:list="cardList"
:is-proportional="false"
:gap="16"
padding-bottom-space="16px"
>
<template #item="{ item, index }">
<TemplateItem
:card-item="item"
:index="index"
mode="organization"
@field-setting="fieldSetting"
@template-management="templateManagement"
@workflow-setup="workflowSetup"
@update-state="updateState"
/>
</template>
</MsCardList>
</MsCard>
</template>
<script setup lang="ts">
/**
* @description 项目设置--模版
*/
import { useRouter } from 'vue-router';
import MsCard from '@/components/pure/ms-card/index.vue';
import MsCardList from '@/components/business/ms-card-list/index.vue';
import TemplateItem from '@/views/setting/organization/template/components/templateItem.vue';
import useTemplateStore from '@/store/modules/setting/template';
import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
import { getCardList } from '@/views/setting/organization/template/components/fieldSetting';
const router = useRouter();
const templateStore = useTemplateStore();
//
const fieldSetting = (key: string) => {
router.push({
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_FIELD_SETTING,
query: {
type: key,
},
});
};
//
const templateManagement = (key: string) => {
router.push({
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT,
query: {
type: key,
},
});
};
//
const workflowSetup = (key: string) => {
router.push({
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_WORKFLOW,
query: {
type: key,
},
});
};
const cardList = ref<Record<string, any>[]>([]);
//
const updateState = () => {
cardList.value = [...getCardList('project')];
};
onBeforeMount(() => {
templateStore.getStatus();
updateState();
});
</script>
<style scoped lang="less"></style>

View File

@ -43,6 +43,7 @@
> >
<a-option v-for="item of projectOptions" :key="item.id" :value="item.id">{{ item.name }}</a-option> <a-option v-for="item of projectOptions" :key="item.id" :value="item.id">{{ item.name }}</a-option>
</a-select> </a-select>
<span v-if="(record.selectProjectList || []).length === 0">-</span>
</template> </template>
<template #userRole="{ record }"> <template #userRole="{ record }">
<MsTagGroup <MsTagGroup
@ -64,6 +65,7 @@
> >
<a-option v-for="item of userGroupOptions" :key="item.id" :value="item.id">{{ item.name }}</a-option> <a-option v-for="item of userGroupOptions" :key="item.id" :value="item.id">{{ item.name }}</a-option>
</a-select> </a-select>
<span v-if="(record.selectUserList || []).length === 0">-</span>
</template> </template>
<template #enable="{ record }"> <template #enable="{ record }">
<div v-if="record.enable" class="flex items-center"> <div v-if="record.enable" class="flex items-center">

View File

@ -21,7 +21,7 @@
height: `calc(100vh - ${collapseHeight} - 230px)`, height: `calc(100vh - ${collapseHeight} - 230px)`,
}" }"
> >
<div class="list"> <div v-if="filterList.length" class="list">
<div v-for="item of filterList" :key="item.id" class="item"> <div v-for="item of filterList" :key="item.id" class="item">
<div class="flex"> <div class="flex">
<span class="icon float-left mr-2 h-[40px] w-[40px] rounded"> <span class="icon float-left mr-2 h-[40px] w-[40px] rounded">
@ -94,6 +94,10 @@
</div> </div>
</div> </div>
</div> </div>
<a-empty class="mt-20">
暂无数据
<span class="cursor-pointer text-[rgb(var(--primary-5))]" @click="goPluginManagement">跳转至插件管理</span>
</a-empty>
</a-scrollbar> </a-scrollbar>
</div> </div>
</div> </div>
@ -103,6 +107,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeMount, ref } from 'vue'; import { onBeforeMount, ref } from 'vue';
import { useRouter } from 'vue-router';
import MsCard from '@/components/pure/ms-card/index.vue'; import MsCard from '@/components/pure/ms-card/index.vue';
import ConfigModal from './conifgModal.vue'; import ConfigModal from './conifgModal.vue';
@ -114,11 +119,13 @@
import { characterLimit } from '@/utils'; import { characterLimit } from '@/utils';
import type { ServiceItem, ServiceList } from '@/models/setting/serviceIntegration'; import type { ServiceItem, ServiceList } from '@/models/setting/serviceIntegration';
import { SettingRouteEnum } from '@/enums/routeEnum';
import Message from '@arco-design/web-vue/es/message'; import Message from '@arco-design/web-vue/es/message';
const { t } = useI18n(); const { t } = useI18n();
const { openModal } = useModal(); const { openModal } = useModal();
const router = useRouter();
const appStore = useAppStore(); const appStore = useAppStore();
const lastOrganizationId = appStore.currentOrgId; const lastOrganizationId = appStore.currentOrgId;
@ -211,7 +218,12 @@
loading.value = false; loading.value = false;
} }
}; };
//
function goPluginManagement() {
router.push({
name: SettingRouteEnum.SETTING_SYSTEM_PLUGIN_MANAGEMENT,
});
}
onBeforeMount(() => { onBeforeMount(() => {
loadList(); loadList();
}); });

View File

@ -49,7 +49,12 @@
</div> </div>
</a-checkbox-group> </a-checkbox-group>
</div> </div>
<EditFieldDrawer ref="fieldDrawerRef" v-model:visible="showFieldDrawer" @success="okHandler" /> <EditFieldDrawer
ref="fieldDrawerRef"
v-model:visible="showFieldDrawer"
:mode="props.mode"
@success="okHandler"
/>
<div> <div>
<a-button class="mt-1 px-0" type="text" :disabled="totalData.length > 20" @click="createField"> <a-button class="mt-1 px-0" type="text" :disabled="totalData.length > 20" @click="createField">
<template #icon> <template #icon>
@ -92,8 +97,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
/**
* @description 系统管理-模版-模版管理-创建模板-添加字段到模板抽屉
*/
import { ref } from 'vue'; import { ref } from 'vue';
import { useRoute } from 'vue-router';
import MsButton from '@/components/pure/ms-button/index.vue'; import MsButton from '@/components/pure/ms-button/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue';
@ -111,6 +118,7 @@
const drawerLoading = ref<boolean>(false); const drawerLoading = ref<boolean>(false);
const props = defineProps<{ const props = defineProps<{
mode: 'organization' | 'project';
visible: boolean; visible: boolean;
totalData: DefinedFieldItem[]; // totalData: DefinedFieldItem[]; //
tableSelectData: DefinedFieldItem[]; // tableSelectData: DefinedFieldItem[]; //
@ -136,7 +144,7 @@
// //
watch( watch(
() => props.tableSelectData, () => props.tableSelectData,
(val) => { () => {
const sysField = props.tableSelectData.filter((item) => item.internal); const sysField = props.tableSelectData.filter((item) => item.internal);
const cusField = props.tableSelectData.filter((item) => !item.internal); const cusField = props.tableSelectData.filter((item) => !item.internal);
selectSystemIds.value = sysField.map((item) => item.id); selectSystemIds.value = sysField.map((item) => item.id);
@ -183,10 +191,9 @@
() => totalIds.value, () => totalIds.value,
(val) => { (val) => {
const res = totalList.value.filter((item) => val.indexOf(item.id) > -1); const res = totalList.value.filter((item) => val.indexOf(item.id) > -1);
const result = res.sort((a, b) => { selectedList.value = res.sort((a, b) => {
return val.indexOf(a.id) - val.indexOf(b.id); return val.indexOf(a.id) - val.indexOf(b.id);
}); });
selectedList.value = result;
} }
); );

View File

@ -42,31 +42,42 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
/**
* @description 系统管理-组织-模板管理-工作流-创建工作流状态
*/
import { ref } from 'vue'; import { ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { FormInstance, Message } from '@arco-design/web-vue'; import { FormInstance, Message } from '@arco-design/web-vue';
import MsDialog from '@/components/pure/ms-dialog/index.vue'; import MsDialog from '@/components/pure/ms-dialog/index.vue';
import { createWorkFlowStatus, updateWorkFlowStatus } from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import type { OrdWorkStatus } from '@/models/setting/template'; import type { OrdWorkStatus } from '@/models/setting/template';
import { getWorkFlowRequestApi } from '@/views/setting/organization/template/components/fieldSetting';
const appStore = useAppStore(); const appStore = useAppStore();
const currentOrgId = computed(() => appStore.currentOrgId); const currentOrgId = computed(() => appStore.currentOrgId);
const currentProjectId = computed(() => appStore.currentProjectId);
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{
mode: 'organization' | 'project';
}>();
const emits = defineEmits<{ const emits = defineEmits<{
(e: 'update:visible', visible: boolean): void; (e: 'update:visible', visible: boolean): void;
(e: 'success'): void; (e: 'success'): void;
}>(); }>();
const visible = ref<boolean>(false); const visible = ref<boolean>(false);
const initFormValue: OrdWorkStatus = { const initFormValue: OrdWorkStatus = {
scopeId: currentOrgId.value, scopeId: '',
id: '', id: '',
name: '', name: '',
scene: route.query.type, scene: route.query.type,
@ -87,11 +98,14 @@
const isEdit = computed(() => !!form.value.id); const isEdit = computed(() => !!form.value.id);
// const createWorkFlowStatus = getWorkFlowRequestApi(props.mode).create;
const updateWorkFlowStatus = getWorkFlowRequestApi(props.mode).update;
const confirmHandler = async (enable: boolean | undefined) => { const confirmHandler = async (enable: boolean | undefined) => {
await formRef.value?.validate().then(async (error) => { await formRef.value?.validate().then(async (error) => {
if (!error) { if (!error) {
try { try {
const scopeId = props.mode === 'organization' ? currentOrgId.value : currentProjectId.value;
form.value.scopeId = scopeId;
if (!form.value.id) { if (!form.value.id) {
form.value.allTransferTo = enable as boolean; form.value.allTransferTo = enable as boolean;
await createWorkFlowStatus(form.value); await createWorkFlowStatus(form.value);

View File

@ -0,0 +1,191 @@
<template>
<a-form ref="viewFormRef" class="rounded-[4px]" :model="viewForm" layout="vertical">
<a-form-item
field="caseName"
:label="t('system.orgTemplate.caseName')"
:rules="[{ required: true, message: t('system.orgTemplate.caseNamePlaceholder') }]"
required
asterisk-position="end"
>
<a-input
v-model="viewForm.name"
:max-length="255"
:placeholder="t('system.orgTemplate.caseNamePlaceholder')"
show-word-limit
allow-clear
:disabled="isDisabled"
></a-input>
</a-form-item>
<a-form-item field="precondition" :label="t('system.orgTemplate.precondition')" asterisk-position="end">
<MsRichText v-model="viewForm.precondition" />
</a-form-item>
<a-form-item field="step" :label="t('system.orgTemplate.stepDescription')" class="relative">
<div class="absolute left-16 top-0">
<a-divider direction="vertical" />
<a-dropdown :popup-max-height="false" @select="handleSelectType">
<span class="text-[14px] text-[var(--color-text-4)]"
>{{ t('system.orgTemplate.changeType') }} <icon-down
/></span>
<template #content>
<a-doption> {{ t('system.orgTemplate.stepDescription') }}</a-doption>
<a-doption>{{ t('system.orgTemplate.textDescription') }}</a-doption>
</template>
</a-dropdown>
</div>
<!-- 步骤描述 -->
<div class="w-full">
<MsBaseTable v-bind="propsRes" ref="stepTableRef" v-on="propsEvent">
<template #index="{ rowIndex }">
{{ rowIndex + 1 }}
</template>
<template #caseStep="{ record }">
<a-input v-if="record.showStep" v-model="record.caseStep" class="w-max-[267px]" />
<span v-else-if="record.caseStep && !record.showStep">{{ record.caseStep }}</span>
<span v-else-if="!record.caseStep && !record.showStep" class="placeholder text-[var(--color-text-brand)]">{{
t('system.orgTemplate.stepTip')
}}</span>
</template>
<template #expectedResult="{ record }">
<a-input v-if="record.showExpected" v-model="record.expectedResult" class="w-max-[267px]" />
<span v-else-if="record.expectedResult && !record.showExpected">{{ record.caseStep }}</span>
<span
v-else-if="!record.expectedResult && !record.showExpected"
class="placeholder text-[var(--color-text-brand)]"
>{{ t('system.orgTemplate.expectationTip') }}</span
>
</template>
<template #operation="{ record }">
<MsTableMoreAction
v-if="!record.internal"
:list="moreActions"
@select="(item:ActionsItem) => handleMoreActionSelect(item)"
/>
</template>
</MsBaseTable>
</div>
<a-button class="mt-2 px-0" type="text" :disabled="isDisabled" @click="addStep">
<template #icon>
<icon-plus class="text-[14px]" />
</template>
{{ t('system.orgTemplate.addStep') }}
</a-button>
</a-form-item>
<a-form-item field="remark" label="备注"> <MsRichText v-model="viewForm.remark" /> </a-form-item>
<a-form-item field="attachment" label="添加附件">
<div class="flex flex-col">
<div class="mb-1"
><a-button type="outline" :disabled="isDisabled">
<template #icon> <icon-plus class="text-[14px]" /> </template
>{{ t('system.orgTemplate.addAttachment') }}</a-button
>
</div>
<div class="text-[var(--color-text-4)]">{{ t('system.orgTemplate.addAttachmentTip') }}</div>
</div>
</a-form-item>
</a-form>
</template>
<script setup lang="ts">
/**
* @description 系统管理-组织-模板管理-用例模板左侧内容
*/
import { ref } from 'vue';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import { useI18n } from '@/hooks/useI18n';
import { TableKeyEnum } from '@/enums/tableEnum';
const isDisabled = ref<boolean>(true);
const { t } = useI18n();
const viewForm = ref({
name: '',
precondition: '',
value: '',
remark: '',
});
const handleSelectType = () => {};
const moreActions: ActionsItem[] = [
{
label: 'system.orgTemplate.copy',
eventTag: 'copy',
disabled: isDisabled.value,
},
{
label: 'system.orgTemplate.delete',
danger: true,
eventTag: 'delete',
disabled: isDisabled.value,
},
];
const templateFieldColumns: MsTableColumn = [
{
title: 'system.orgTemplate.numberIndex',
dataIndex: 'index',
slotName: 'index',
width: 100,
showDrag: false,
showInTable: true,
},
{
title: 'system.orgTemplate.useCaseStep',
slotName: 'caseStep',
dataIndex: 'caseStep',
showDrag: true,
showInTable: true,
},
{
title: 'system.orgTemplate.expectedResult',
dataIndex: 'expectedResult',
slotName: 'expectedResult',
showDrag: true,
showInTable: true,
},
{
title: 'system.orgTemplate.operation',
slotName: 'operation',
fixed: 'right',
width: 200,
showInTable: true,
showDrag: false,
},
];
const { propsRes, propsEvent, setProps } = useTable(undefined, {
tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_STEP,
columns: templateFieldColumns,
scroll: { x: '800px' },
selectable: false,
noDisable: true,
size: 'default',
showSetting: true,
showPagination: false,
enableDrag: false,
});
const addStep = () => {};
const handlerDelete = () => {};
//
const handleMoreActionSelect = (item: ActionsItem) => {
if (item.eventTag === 'delete') {
handlerDelete();
}
};
onMounted(() => {
setProps({ data: [{ id: 1, showStep: false, showExpected: false }] });
});
</script>
<style scoped></style>

View File

@ -0,0 +1,60 @@
<template>
<a-form ref="viewFormRef" class="rounded-[4px]" :model="viewForm" layout="vertical">
<a-form-item
field="defectName"
:label="t('system.orgTemplate.defectName')"
:rules="[{ required: true, message: t('system.orgTemplate.defectNamePlaceholder') }]"
required
asterisk-position="end"
>
<a-input
v-model="viewForm.name"
:disabled="true"
:max-length="255"
:placeholder="t('system.orgTemplate.defectNamePlaceholder')"
show-word-limit
allow-clear
></a-input>
</a-form-item>
<a-form-item field="precondition" :label="t('system.orgTemplate.defectContent')" asterisk-position="end">
<MsRichText v-model="viewForm.description" />
</a-form-item>
<a-form-item field="attachment" label="添加附件">
<div class="flex flex-col">
<div class="mb-1"
><a-button type="outline" :disabled="true">
<template #icon> <icon-plus class="text-[14px]" /> </template
>{{ t('system.orgTemplate.addAttachment') }}</a-button
>
</div>
<div class="text-[var(--color-text-4)]">{{ t('system.orgTemplate.addAttachmentTip') }}</div>
</div>
</a-form-item>
</a-form>
</template>
<script setup lang="ts">
/**
* @description 系统管理-组织-模板管理-缺陷模板左侧内容
*/
import { ref } from 'vue';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const viewForm = ref<Record<string, any>>({
name: '',
description: '',
});
const props = defineProps<{
defectForm: Record<string, any>;
}>();
watchEffect(() => {
viewForm.value = { ...props.defectForm };
});
</script>
<style scoped></style>

View File

@ -58,7 +58,7 @@
:label="t('system.orgTemplate.allowMultiMember')" :label="t('system.orgTemplate.allowMultiMember')"
asterisk-position="end" asterisk-position="end"
> >
<a-switch v-model="isMultipleSelectMember" size="small" /> <a-switch v-model="isMultipleSelectMember" size="small" :disabled="isEdit" />
</a-form-item> </a-form-item>
<!-- 选项选择器 --> <!-- 选项选择器 -->
<a-form-item <a-form-item
@ -115,6 +115,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
/**
* @description 模板管理-自定义字段-添加自定义字段&编辑自定义字段
*/
import { ref } from 'vue'; import { ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue'; import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
@ -125,22 +128,22 @@
import MsBatchForm from '@/components/business/ms-batch-form/index.vue'; import MsBatchForm from '@/components/business/ms-batch-form/index.vue';
import type { FormItemModel, MsBatchFormInstance } from '@/components/business/ms-batch-form/types'; import type { FormItemModel, MsBatchFormInstance } from '@/components/business/ms-batch-form/types';
import { addOrUpdateOrdField, getOrdFieldDetail } from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import { getGenerateId } from '@/utils'; import { getGenerateId } from '@/utils';
import type { AddOrUpdateField, fieldIconAndNameModal } from '@/models/setting/template'; import type { AddOrUpdateField, fieldIconAndNameModal } from '@/models/setting/template';
import { fieldIconAndName, getFieldType } from './fieldSetting'; import { fieldIconAndName, getFieldRequestApi, getFieldType } from './fieldSetting';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const appStore = useAppStore(); const appStore = useAppStore();
const currentOrgId = computed(() => appStore.currentOrgId);
const sceneType = route.query.type; const sceneType = route.query.type;
const props = defineProps<{ const props = defineProps<{
visible: boolean; visible: boolean;
mode: 'organization' | 'project';
}>(); }>();
const emit = defineEmits(['success', 'update:visible']); const emit = defineEmits(['success', 'update:visible']);
@ -148,11 +151,17 @@
const drawerLoading = ref<boolean>(false); const drawerLoading = ref<boolean>(false);
const fieldFormRef = ref<FormInstance>(); const fieldFormRef = ref<FormInstance>();
const currentOrgId = computed(() => appStore.currentOrgId);
const currentProjectId = computed(() => appStore.currentProjectId);
const scopeId = computed(() => {
return props.mode === 'organization' ? currentOrgId.value : currentProjectId.value;
});
const initFieldForm: AddOrUpdateField = { const initFieldForm: AddOrUpdateField = {
name: '', name: '',
type: undefined, type: undefined,
remark: '', remark: '',
scopeId: '', scopeId: scopeId.value,
scene: 'FUNCTIONAL', scene: 'FUNCTIONAL',
options: [], options: [],
enableOptionKey: false, enableOptionKey: false,
@ -232,6 +241,7 @@
resetForm(); resetForm();
}; };
const { addOrUpdate, detail } = getFieldRequestApi(props.mode);
// //
const confirmHandler = async (isContinue: boolean) => { const confirmHandler = async (isContinue: boolean) => {
try { try {
@ -240,7 +250,7 @@
const formCopy = cloneDeep(fieldForm.value); const formCopy = cloneDeep(fieldForm.value);
formCopy.scene = route.query.type; formCopy.scene = route.query.type;
formCopy.scopeId = currentOrgId.value; formCopy.scopeId = scopeId.value;
// //
if (selectFormat.value) { if (selectFormat.value) {
@ -258,12 +268,12 @@
} }
// //
const { id, name, options, scopeId, scene, type, remark, enableOptionKey } = formCopy; const { id, name, options, scene, type, remark, enableOptionKey } = formCopy;
const params: AddOrUpdateField = { const params: AddOrUpdateField = {
name, name,
options, options,
scopeId, scopeId: scopeId.value,
scene, scene,
type, type,
remark, remark,
@ -272,7 +282,7 @@
if (id) { if (id) {
params.id = id; params.id = id;
} }
await addOrUpdateOrdField(params); await addOrUpdate(params);
Message.success(isEdit.value ? t('common.updateSuccess') : t('common.addSuccess')); Message.success(isEdit.value ? t('common.updateSuccess') : t('common.addSuccess'));
if (!isContinue) { if (!isContinue) {
handleDrawerCancel(); handleDrawerCancel();
@ -310,7 +320,7 @@
// //
const getFieldDetail = async (id: string) => { const getFieldDetail = async (id: string) => {
try { try {
const fieldDetail = await getOrdFieldDetail(id); const fieldDetail = await detail(id);
fieldDefaultValues.value = fieldDetail.options.map((item: any) => { fieldDefaultValues.value = fieldDetail.options.map((item: any) => {
return { return {
...item, ...item,
@ -336,6 +346,7 @@
selectFormat.value = itemType; selectFormat.value = itemType;
return 'DATE'; return 'DATE';
default: default:
selectFormat.value = itemType;
return itemType; return itemType;
} }
}; };

View File

@ -1,11 +1,37 @@
import { cloneDeep } from 'lodash-es';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { FieldTypeFormRules } from '@/components/pure/ms-form-create/form-create';
import type { FormItemType } from '@/components/pure/ms-form-create/types'; import type { FormItemType } from '@/components/pure/ms-form-create/types';
import {
addOrUpdateOrdField,
addOrUpdateProjectField,
createProjectWorkFlowStatus,
createWorkFlowStatus,
deleteOrdField,
deleteOrdWorkState,
deleteProjectField,
deleteProjectWorkState,
getFieldList,
getOrdFieldDetail,
getProjectFieldDetail,
getProjectFieldList,
getProjectWorkFlowList,
getWorkFlowList,
setOrdWorkState,
setOrdWorkStateSort,
setProjectWorkState,
setProjectWorkStateSort,
updateOrdWorkStateFlow,
updateProjectWorkFlowStatus,
updateProjectWorkStateFlow,
updateWorkFlowStatus,
} from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useTemplateStore from '@/store/modules/setting/template'; import useTemplateStore from '@/store/modules/setting/template';
import type { fieldIconAndNameModal } from '@/models/setting/template'; import type { CustomField, DefinedFieldItem, fieldIconAndNameModal } from '@/models/setting/template';
import { TemplateCardEnum, TemplateIconEnum } from '@/enums/templateEnum'; import { TemplateCardEnum, TemplateIconEnum } from '@/enums/templateEnum';
const { t } = useI18n(); const { t } = useI18n();
@ -47,49 +73,12 @@ export const getFieldType = (selectFieldType: FormItemType) => {
} }
}; };
const organizationState = computed(() => templateStore.getOrdTemplateState()); const organizationState = computed(() => templateStore.ordStatus);
const projectState = computed(() => templateStore.getProjectTemplateState()); const projectState = computed(() => templateStore.projectStatus);
// 模板列表Icon
export const cardList = [
{
id: 1001,
key: 'FUNCTIONAL',
value: TemplateCardEnum.FUNCTIONAL,
name: t('system.orgTemplate.caseTemplates'),
enable: templateStore.templateStatus.FUNCTIONAL,
},
{
id: 1002,
key: 'API',
value: TemplateCardEnum.API,
name: t('system.orgTemplate.APITemplates'),
enable: templateStore.templateStatus.API,
},
{
id: 1003,
key: 'UI',
value: TemplateCardEnum.UI,
name: t('system.orgTemplate.UITemplates'),
enable: templateStore.templateStatus.UI,
},
{
id: 1004,
key: 'TEST_PLAN',
value: TemplateCardEnum.TEST_PLAN,
name: t('system.orgTemplate.testPlanTemplates'),
enable: templateStore.templateStatus.TEST_PLAN,
},
{
id: 1005,
key: 'BUG',
value: TemplateCardEnum.BUG,
name: t('system.orgTemplate.defectTemplates'),
enable: templateStore.templateStatus.BUG,
},
];
// 模板列表Icon
export function getCardList(type: string): Record<string, any>[] { export function getCardList(type: string): Record<string, any>[] {
const dataList = [ const dataList = ref([
{ {
id: 1001, id: 1001,
key: 'FUNCTIONAL', key: 'FUNCTIONAL',
@ -120,16 +109,17 @@ export function getCardList(type: string): Record<string, any>[] {
value: TemplateCardEnum.BUG, value: TemplateCardEnum.BUG,
name: t('system.orgTemplate.defectTemplates'), name: t('system.orgTemplate.defectTemplates'),
}, },
]; ]);
if (type === 'organization') { if (type === 'organization') {
return dataList.map((item) => { return dataList.value.map((item) => {
return { return {
...item, ...item,
enable: organizationState.value[item.key], enable: organizationState.value[item.key],
}; };
}); });
} }
return dataList.map((item) => {
return dataList.value.map((item) => {
return { return {
...item, ...item,
enable: projectState.value[item.key], enable: projectState.value[item.key],
@ -221,4 +211,102 @@ export const getIconType = (iconType: FormItemType) => {
return fieldIconAndName.find((item) => item.key === iconType); return fieldIconAndName.find((item) => item.key === iconType);
}; };
// 获取接口类型
export const getFieldRequestApi = (mode: 'organization' | 'project') => {
if (mode === 'organization') {
return {
list: getFieldList,
delete: deleteOrdField,
addOrUpdate: addOrUpdateOrdField,
detail: getOrdFieldDetail,
};
}
return {
list: getProjectFieldList,
delete: deleteProjectField,
addOrUpdate: addOrUpdateProjectField,
detail: getProjectFieldDetail,
};
};
// 获取工作流类型接口
export const getWorkFlowRequestApi = (mode: 'organization' | 'project') => {
if (mode === 'organization') {
return {
list: getWorkFlowList,
create: createWorkFlowStatus,
update: updateWorkFlowStatus,
delete: deleteOrdWorkState,
changeState: setOrdWorkState,
dragChange: setOrdWorkStateSort,
updateFlow: updateOrdWorkStateFlow,
};
}
return {
list: getProjectWorkFlowList,
create: createProjectWorkFlowStatus,
update: updateProjectWorkFlowStatus,
delete: deleteProjectWorkState,
changeState: setProjectWorkState,
dragChange: setProjectWorkStateSort,
updateFlow: updateProjectWorkStateFlow,
};
};
/** **
* @description totalData自定义字段列表格式
* @param totalData: 自定义字段总列表
*/
export const getTotalFieldOptionList = (totalData: DefinedFieldItem[]) => {
return totalData.map((item: any) => {
const currentFormRules = FieldTypeFormRules[item.type];
let selectOptions: any = [];
if (item.options && item.options.length) {
selectOptions = item.options.map((optionItem: any) => {
return {
label: optionItem.text,
value: optionItem.value,
};
});
currentFormRules.options = selectOptions;
}
return {
...item,
formRules: [
{ ...currentFormRules, value: item.value, props: { ...currentFormRules.props, options: selectOptions } },
],
fApi: null,
required: item.internal,
};
});
};
/** **
* @description
* @param totalData: 自定义字段总列表
* @param customFields: 自定义字段总列表
*/
export const getCustomDetailFields = (totalData: DefinedFieldItem[], customFields: CustomField[]) => {
const customFieldsIds = customFields.map((index: any) => index.fieldId);
return totalData.filter((item) => {
const currentCustomFieldIndex = customFieldsIds.findIndex((it: any) => it === item.id);
if (customFieldsIds.indexOf(item.id) > -1) {
const currentForm = item.formRules?.map((it: any) => {
it.props.modelValue = customFields[currentCustomFieldIndex].defaultValue;
return {
...it,
value: customFields[currentCustomFieldIndex].defaultValue,
};
});
const formItem = item;
formItem.formRules = cloneDeep(currentForm);
formItem.apiFieldId = customFields[currentCustomFieldIndex].apiFieldId;
formItem.required = customFields[currentCustomFieldIndex].required;
return true;
}
return false;
});
};
export default {}; export default {};

View File

@ -1,10 +1,12 @@
<template> <template>
<MsCard :has-breadcrumb="true" simple> <div>
<a-alert class="mb-6" :type="isEnable ? 'warning' : 'info'">{{ <a-alert class="mb-6" :type="isEnabledTemplate && props.mode === 'organization' ? 'warning' : 'info'">{{
isEnable ? t('system.orgTemplate.enableDescription') : t('system.orgTemplate.fieldLimit') isEnabledTemplate && props.mode === 'organization'
? t('system.orgTemplate.enableDescription')
: t('system.orgTemplate.fieldLimit')
}}</a-alert> }}</a-alert>
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<span v-if="isEnable" class="font-medium">{{ t('system.orgTemplate.fieldList') }}</span> <span v-if="isEnabledTemplate" class="font-medium">{{ t('system.orgTemplate.fieldList') }}</span>
<a-button v-else type="primary" :disabled="isDisabled" @click="fieldHandler"> <a-button v-else type="primary" :disabled="isDisabled" @click="fieldHandler">
{{ t('system.orgTemplate.addField') }} {{ t('system.orgTemplate.addField') }}
</a-button> </a-button>
@ -19,12 +21,28 @@
</div> </div>
<MsBaseTable v-bind="propsRes" ref="tableRef" v-on="propsEvent"> <MsBaseTable v-bind="propsRes" ref="tableRef" v-on="propsEvent">
<template #name="{ record }"> <template #name="{ record }">
<MsIcon v-if="!record.internal" :type="getIconType(record.type)?.iconName || ''" size="16" /> <MsIcon
<span class="ml-2">{{ record.name }}</span> v-if="!record.internal"
<span v-if="record.internal" class="system-flag">{{ t('system.orgTemplate.isSystem') }}</span> :type="getIconType(record.type)?.iconName || ''"
size="16"
:class="{
'text-[rgb(var(--primary-5))]': props.mode === 'project',
'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
>
<MsTag v-if="record.internal" size="small" class="ml-2">{{ t('system.orgTemplate.isSystem') }}</MsTag>
</template> </template>
<template #operation="{ record }"> <template #operation="{ record }">
<div class="flex flex-row flex-nowrap"> <div class="flex flex-row flex-nowrap items-center">
<MsPopConfirm <MsPopConfirm
type="error" type="error"
:title="t('system.orgTemplate.updateTip', { name: characterLimit(record.name) })" :title="t('system.orgTemplate.updateTip', { name: characterLimit(record.name) })"
@ -35,7 +53,7 @@
<MsButton class="!mr-0">{{ t('system.orgTemplate.edit') }}</MsButton></MsPopConfirm <MsButton class="!mr-0">{{ t('system.orgTemplate.edit') }}</MsButton></MsPopConfirm
> >
<a-divider v-if="!record.internal" direction="vertical" /> <a-divider v-if="!record.internal" class="h-[12px]" direction="vertical" />
<MsTableMoreAction <MsTableMoreAction
v-if="!record.internal" v-if="!record.internal"
:list="moreActions" :list="moreActions"
@ -47,29 +65,75 @@
<span>{{ getIconType(record.type)?.label }}</span> <span>{{ getIconType(record.type)?.label }}</span>
</template> </template>
</MsBaseTable> </MsBaseTable>
<EditFieldDrawer ref="fieldDrawerRef" v-model:visible="showDrawer" @success="successHandler" /> <EditFieldDrawer ref="fieldDrawerRef" v-model:visible="showDrawer" :mode="props.mode" @success="successHandler" />
</MsCard> <MsDrawer
ref="detailDrawerRef"
v-model:visible="showDetailVisible"
:width="480"
:footer="false"
:title="t('system.orgTemplate.filedDetail', { name: detailInfo?.name })"
>
<div class="p-4">
<div class="flex">
<span class="label">{{ t('system.orgTemplate.fieldName') }}</span>
<span class="content">{{ detailInfo?.name }}</span>
</div>
<div class="flex">
<span class="label">{{ t('system.orgTemplate.description') }}</span>
<span class="content">{{ detailInfo?.remark || '-' }}</span>
</div>
<div class="flex">
<span class="label">{{ t('system.orgTemplate.fieldType') }}</span>
<span class="content">{{ detailInfo?.fieldType || '-' }}</span>
</div>
<div v-if="detailInfo?.options?.length" class="flex">
<span class="label">{{ t('system.orgTemplate.optionContent') }}</span>
<span class="content flex flex-col">
<span v-for="item of detailInfo?.options" :key="item.value" class="flex">
<MsTag class="!mr-2 mb-2">{{ item.text }}</MsTag>
<MsTag v-if="detailInfo?.enableOptionKey" class="mb-2">{{ item.value }}</MsTag></span
>
</span>
</div>
<div v-if="detailInfo?.type === 'DATE' || detailInfo?.type === 'DATETIME'" class="flex">
<span class="label">{{ t('system.orgTemplate.dateFormat') }}</span>
<span class="content">
{{
detailInfo?.type === 'DATE' ? dayjs().format('YYYY/MM/DD') : dayjs().format('YYYY/MM/DD HH:mm:ss')
}}</span
>
</div>
<div v-if="detailInfo?.type === 'INT' || detailInfo?.type === 'FLOAT'" class="flex">
<span class="label">{{ t('system.orgTemplate.numberFormat') }}</span>
<span class="content">{{
detailInfo?.type === 'INT' ? t('system.orgTemplate.int') : t('system.orgTemplate.float')
}}</span>
</div>
</div>
</MsDrawer>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
/** /**
* @description 系统管理-组织-模版-字段列表 * @description 模版-字段列表
*/ */
import { ref } from 'vue'; import { ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import dayjs from 'dayjs';
import MsButton from '@/components/pure/ms-button/index.vue'; import MsButton from '@/components/pure/ms-button/index.vue';
import MsCard from '@/components/pure/ms-card/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsPopConfirm from '@/components/pure/ms-popconfirm/index.vue'; import MsPopConfirm from '@/components/pure/ms-popconfirm/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue'; import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type'; import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable'; import useTable from '@/components/pure/ms-table/useTable';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue'; import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types'; import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import EditFieldDrawer from './editFieldDrawer.vue'; import EditFieldDrawer from './editFieldDrawer.vue';
import { deleteOrdField, getFieldList } from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import { useAppStore, useTableStore } from '@/store'; import { useAppStore, useTableStore } from '@/store';
@ -79,7 +143,7 @@
import type { AddOrUpdateField, SeneType } from '@/models/setting/template'; import type { AddOrUpdateField, SeneType } from '@/models/setting/template';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
import { getCardList, getIconType } from './fieldSetting'; import { getFieldRequestApi, getIconType } from './fieldSetting';
const templateStore = useTemplateStore(); const templateStore = useTemplateStore();
@ -89,7 +153,12 @@
const route = useRoute(); const route = useRoute();
const { openModal } = useModal(); const { openModal } = useModal();
const props = defineProps<{
mode: 'organization' | 'project';
}>();
const currentOrd = computed(() => appStore.currentOrgId); const currentOrd = computed(() => appStore.currentOrgId);
const currentProjectId = computed(() => appStore.currentProjectId);
const fieldColumns: MsTableColumn = [ const fieldColumns: MsTableColumn = [
{ {
@ -97,7 +166,6 @@
slotName: 'name', slotName: 'name',
dataIndex: 'name', dataIndex: 'name',
width: 300, width: 300,
showDrag: true,
showInTable: true, showInTable: true,
showTooltip: true, showTooltip: true,
}, },
@ -105,64 +173,67 @@
title: 'system.orgTemplate.columnFieldType', title: 'system.orgTemplate.columnFieldType',
dataIndex: 'type', dataIndex: 'type',
slotName: 'fieldType', slotName: 'fieldType',
showDrag: true,
showInTable: true, showInTable: true,
}, },
{ {
title: 'system.orgTemplate.columnFieldDescription', title: 'system.orgTemplate.columnFieldDescription',
dataIndex: 'remark', dataIndex: 'remark',
showDrag: true,
showInTable: true, showInTable: true,
}, },
{ {
title: 'system.orgTemplate.columnFieldUpdatedTime', title: 'system.orgTemplate.columnFieldUpdatedTime',
dataIndex: 'updateTime', dataIndex: 'updateTime',
showDrag: true,
showInTable: true, showInTable: true,
}, },
{ {
title: 'system.orgTemplate.operation', title: 'system.orgTemplate.operation',
slotName: 'operation', slotName: 'operation',
dataIndex: 'operation',
fixed: 'right', fixed: 'right',
width: 200, width: 200,
showInTable: true, showInTable: true,
showDrag: false,
}, },
]; ];
await tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_FIELD_SETTING, fieldColumns, 'drawer');
const { propsRes, propsEvent, loadList, setLoadListParams, setProps } = useTable(getFieldList, { const getList = getFieldRequestApi(props.mode).list;
const { propsRes, propsEvent, loadList, setLoadListParams, setProps } = useTable(getList, {
tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_FIELD_SETTING, tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_FIELD_SETTING,
scroll: { x: '1000px' }, scroll: { x: '1000px' },
selectable: false, selectable: false,
noDisable: true, noDisable: true,
size: 'default', size: 'default',
showSetting: true, showSetting: false,
showPagination: false, showPagination: false,
heightUsed: 380, heightUsed: 380,
}); });
const keyword = ref(''); const keyword = ref('');
const totalData = ref([]); const totalData = ref([]);
const scene = ref<SeneType>(route.query.type);
const getParams = () => {
scene.value = route.query.type;
return {
scene: scene.value,
scopedId: props.mode === 'organization' ? currentOrd.value : currentProjectId.value,
};
};
// //
const searchFiled = async () => { const searchFiled = async () => {
try { try {
totalData.value = await getFieldList({ organizationId: currentOrd.value, scene: route.query.type }); totalData.value = await getList(getParams());
const filterData = totalData.value.filter((item: AddOrUpdateField) => item.name.includes(keyword.value)); const filterData = totalData.value.filter((item: AddOrUpdateField) => item.name.includes(keyword.value));
setProps({ data: filterData }); setProps({ data: filterData });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
}; };
const scene = ref<SeneType>(route.query.type);
// //
const fetchData = async () => { const fetchData = async () => {
scene.value = route.query.type; setLoadListParams(getParams());
setLoadListParams({ organizationId: currentOrd.value, scene });
await loadList(); await loadList();
totalData.value = await getFieldList({ organizationId: currentOrd.value, scene: route.query.type }); totalData.value = await getList(getParams());
}; };
const isDisabled = computed(() => { const isDisabled = computed(() => {
@ -170,13 +241,16 @@
}); });
const tableRef = ref(); const tableRef = ref();
const isEnable = computed(() => {
return templateStore.templateStatus[scene.value as string]; const isEnabledTemplate = computed(() => {
return props.mode === 'organization'
? templateStore.projectStatus[scene.value as string]
: !templateStore.projectStatus[scene.value as string];
}); });
// //
const isEnableOperation = () => { const isEnableOperation = () => {
if (isEnable.value) { if (isEnabledTemplate.value) {
const noOperationColumn = fieldColumns.slice(0, -1); const noOperationColumn = fieldColumns.slice(0, -1);
tableRef.value.initColumn(noOperationColumn); tableRef.value.initColumn(noOperationColumn);
} else { } else {
@ -192,6 +266,7 @@
}, },
]; ];
const deleteApi = getFieldRequestApi(props.mode).delete;
// //
const handlerDelete = (record: AddOrUpdateField) => { const handlerDelete = (record: AddOrUpdateField) => {
openModal({ openModal({
@ -205,7 +280,7 @@
}, },
onBeforeOk: async () => { onBeforeOk: async () => {
try { try {
if (record.id) await deleteOrdField(record.id); if (record.id) await deleteApi(record.id);
Message.success(t('system.orgTemplate.deleteSuccess')); Message.success(t('system.orgTemplate.deleteSuccess'));
fetchData(); fetchData();
} catch (error) { } catch (error) {
@ -223,6 +298,25 @@
} }
}; };
const showDetailVisible = ref<boolean>(false);
const detailInfo = ref<AddOrUpdateField>();
//
const showDetail = (record: AddOrUpdateField) => {
if (props.mode === 'organization') return;
showDetailVisible.value = true;
let fieldType;
if (record.type === 'MEMBER') {
fieldType = getIconType(record.type)?.label;
} else if (record.type === 'MULTIPLE_MEMBER') {
fieldType = `${getIconType(record.type)?.label}(允许添加多个)`;
} else {
fieldType = getIconType(record.type)?.label;
}
detailInfo.value = { ...record, fieldType };
};
const showDrawer = ref<boolean>(false); const showDrawer = ref<boolean>(false);
const fieldDrawerRef = ref(); const fieldDrawerRef = ref();
@ -238,26 +332,26 @@
fetchData(); fetchData();
}; };
//
const updateBreadcrumbList = () => {
const { breadcrumbList } = appStore;
const breadTitle = getCardList('organization').find((item) => item.key === route.query.type);
if (breadTitle) {
breadcrumbList[0].locale = breadTitle.name;
appStore.setBreadcrumbList(breadcrumbList);
}
};
onMounted(() => { onMounted(() => {
updateBreadcrumbList();
isEnableOperation(); isEnableOperation();
fetchData(); fetchData();
}); });
await tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_FIELD_SETTING, fieldColumns, 'drawer');
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.system-flag { .system-flag {
background: var(--color-text-n8); background: var(--color-text-n8);
@apply ml-2 rounded p-1 text-xs; @apply ml-2 inline-block p-1 align-middle text-xs;
}
.label {
margin-top: 16px;
width: 30%;
color: var(--color-text-3);
}
.content {
margin-top: 16px;
width: 70%;
color: var(--color-text-1);
} }
</style> </style>

View File

@ -0,0 +1,36 @@
<template>
<MsCard has-breadcrumb simple> <FieldSetting mode="organization" /></MsCard>
</template>
<script setup lang="ts">
/**
* @description 系统管理-组织-模板管理-字段列表首页
*/
import { useRoute } from 'vue-router';
import MsCard from '@/components/pure/ms-card/index.vue';
import FieldSetting from '@/views/setting/organization/template/components/fieldSetting.vue';
import { useAppStore } from '@/store';
import { getCardList } from '@/views/setting/organization/template/components/fieldSetting';
const route = useRoute();
const appStore = useAppStore();
//
const updateBreadcrumbList = () => {
const { breadcrumbList } = appStore;
const breadTitle = getCardList('organization').find((item: any) => item.key === route.query.type);
if (breadTitle) {
breadcrumbList[0].locale = breadTitle.name;
appStore.setBreadcrumbList(breadcrumbList);
}
};
onMounted(() => {
updateBreadcrumbList();
});
</script>
<style scoped></style>

View File

@ -20,7 +20,12 @@
<!-- 非预览模式 --> <!-- 非预览模式 -->
<div v-if="isPreview" class="nonPreview"> <div v-if="isPreview" class="nonPreview">
<a-form ref="formRef" :model="templateForm" layout="vertical"> <a-form ref="formRef" :model="templateForm" layout="vertical">
<a-form-item :label="t('system.orgTemplate.templateName')" field="name" asterisk-position="end" required> <a-form-item
:label="t('system.orgTemplate.templateName')"
field="name"
asterisk-position="end"
:rules="[{ required: true, message: t('system.orgTemplate.templateNameRules') }]"
>
<a-input <a-input
v-model:model-value="templateForm.name" v-model:model-value="templateForm.name"
:placeholder="t('system.orgTemplate.templateNamePlaceholder')" :placeholder="t('system.orgTemplate.templateNamePlaceholder')"
@ -40,21 +45,68 @@
class="max-w-[732px]" class="max-w-[732px]"
></a-textarea> ></a-textarea>
</a-form-item> </a-form-item>
<a-form-item field="remark" label="" asterisk-position="end" <a-form-item v-if="route.query.type === 'BUG'" field="remark" label="" asterisk-position="end"
><a-checkbox v-model="templateForm.enableThirdPart">{{ t('system.orgTemplate.thirdParty') }}</a-checkbox> ><a-checkbox v-model="templateForm.enableThirdPart">{{ t('system.orgTemplate.thirdParty') }}</a-checkbox>
</a-form-item> </a-form-item>
<a-form-item
v-if="route.query.type === 'BUG'"
field="fieldType"
:label="t('system.orgTemplate.columnFieldType')"
asterisk-position="end"
>
<a-radio-group v-model="fieldType" type="button">
<a-radio value="custom">{{ t('system.orgTemplate.custom') }}</a-radio>
<a-radio value="detail">{{ t('system.orgTemplate.details') }}</a-radio>
</a-radio-group>
</a-form-item>
</a-form> </a-form>
<!-- 已有字段表 --> <!-- 已有字段表 -->
<TemplateManagementTable <TemplateManagementTable
v-if="fieldType === 'custom'"
ref="templateFieldTableRef" ref="templateFieldTableRef"
v-model:select-data="selectData" v-model:select-data="selectData"
:data="(totalTemplateField as DefinedFieldItem[])" :data="(totalTemplateField as DefinedFieldItem[])"
:enable-third-part="templateForm.enableThirdPart" :enable-third-part="templateForm.enableThirdPart"
mode="organization"
@update="updateHandler" @update="updateHandler"
/> />
<!-- 缺陷详情表 -->
<a-form
v-if="fieldType === 'detail'"
ref="defectFormRef"
class="rounded-[4px]"
:model="defectForm"
layout="vertical"
>
<a-form-item
field="name"
:label="t('system.orgTemplate.defectName')"
:rules="[{ required: true, message: t('system.orgTemplate.defectNamePlaceholder') }]"
required
asterisk-position="end"
>
<a-input
v-model="defectForm.name"
:max-length="255"
:placeholder="t('system.orgTemplate.defectNamePlaceholder')"
show-word-limit
allow-clear
></a-input>
<MsFormItemSub :text="t('system.orgTemplate.defectNameTip')" :show-fill-icon="false" />
</a-form-item>
<a-form-item field="precondition" :label="t('system.orgTemplate.defectContent')" asterisk-position="end">
<MsRichText v-model:model-value="defectForm.description" />
<MsFormItemSub :text="t('system.orgTemplate.defectContentTip')" :show-fill-icon="false" />
</a-form-item>
</a-form>
</div> </div>
<!-- 预览模式 --> <!-- 预览模式 -->
<PreviewTemplate v-else :select-field="(selectData as DefinedFieldItem[])" /> <PreviewTemplate
v-else
:select-field="(selectData as DefinedFieldItem[])"
:template-type="route.query.type"
:defect-form="defectForm"
/>
</MsCard> </MsCard>
</template> </template>
@ -64,11 +116,11 @@
*/ */
import { ref } from 'vue'; import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue'; import { FormInstance, Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import MsCard from '@/components/pure/ms-card/index.vue'; import MsCard from '@/components/pure/ms-card/index.vue';
import { FieldTypeFormRules } from '@/components/pure/ms-form-create/form-create'; import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
import MsFormItemSub from '@/components/business/ms-form-item-sub/index.vue';
import TemplateManagementTable from './templateManagementTable.vue'; import TemplateManagementTable from './templateManagementTable.vue';
import PreviewTemplate from './viewTemplate.vue'; import PreviewTemplate from './viewTemplate.vue';
@ -87,14 +139,16 @@
import type { ActionTemplateManage, CustomField, DefinedFieldItem } from '@/models/setting/template'; import type { ActionTemplateManage, CustomField, DefinedFieldItem } from '@/models/setting/template';
import { SettingRouteEnum } from '@/enums/routeEnum'; import { SettingRouteEnum } from '@/enums/routeEnum';
import { getCardList } from './fieldSetting'; import { getCardList, getCustomDetailFields, getTotalFieldOptionList } from './fieldSetting';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const appStore = useAppStore(); const appStore = useAppStore();
const currentOrgId = computed(() => appStore.currentOrgId); const currentOrgId = computed(() => appStore.currentOrgId);
// useLeaveUnSaveTip(); const { setState } = useLeaveUnSaveTip();
setState(false);
const title = ref(''); const title = ref('');
const loading = ref(false); const loading = ref(false);
@ -107,6 +161,8 @@
enableThirdPart: false, enableThirdPart: false,
}; };
const fieldType = ref<string>('custom'); //
const templateForm = ref<ActionTemplateManage>({ ...initTemplateForm }); const templateForm = ref<ActionTemplateManage>({ ...initTemplateForm });
const selectData = ref<DefinedFieldItem[]>([]); // const selectData = ref<DefinedFieldItem[]>([]); //
@ -115,15 +171,15 @@
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const totalTemplateField = ref<DefinedFieldItem[]>([]); const totalTemplateField = ref<DefinedFieldItem[]>([]);
const isEdit = computed(() => !!route.query.id); const isEdit = computed(() => !!route.query.id);
const currentOrd = currentOrgId.value;
const isEditField = ref<boolean>(false); const isEditField = ref<boolean>(false);
const systemFieldData = ref<CustomField[]>([]);
// //
const getTemplateInfo = async () => { const getTemplateInfo = async () => {
try { try {
loading.value = true; loading.value = true;
const res = await getOrganizeTemplateInfo(route.query.id as string); const res = await getOrganizeTemplateInfo(route.query.id as string);
const { name, customFields } = res; const { name, customFields, systemFields } = res;
templateForm.value = { templateForm.value = {
...res, ...res,
name: route.params.mode === 'copy' ? `${name}_copy` : name, name: route.params.mode === 'copy' ? `${name}_copy` : name,
@ -131,27 +187,8 @@
if (route.params.mode === 'copy') { if (route.params.mode === 'copy') {
templateForm.value.id = undefined; templateForm.value.id = undefined;
} }
// selectData.value = getCustomDetailFields(totalTemplateField.value as DefinedFieldItem[], customFields);
const customFieldsIds = customFields.map((index: any) => index.fieldId); systemFieldData.value = systemFields;
const result = totalTemplateField.value.filter((item) => {
const currentCustomFieldIndex = customFieldsIds.findIndex((it: any) => it === item.id);
if (customFieldsIds.indexOf(item.id) > -1) {
const currentForm = item.formRules?.map((it: any) => {
it.props.modelValue = customFields[currentCustomFieldIndex].defaultValue;
return {
...it,
value: customFields[currentCustomFieldIndex].defaultValue,
};
});
const formItem = item;
formItem.formRules = cloneDeep(currentForm);
formItem.apiFieldId = customFields[currentCustomFieldIndex].apiFieldId;
formItem.required = customFields[currentCustomFieldIndex].required;
return true;
}
return false;
});
selectData.value = result;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {
@ -161,27 +198,7 @@
// //
const getFieldOptionList = () => { const getFieldOptionList = () => {
totalTemplateField.value = totalTemplateField.value.map((item: any) => { totalTemplateField.value = getTotalFieldOptionList(totalTemplateField.value as DefinedFieldItem[]);
const currentFormRules = FieldTypeFormRules[item.type];
let selectOptions: any = [];
if (item.options && item.options.length) {
selectOptions = item.options.map((optionItem: any) => {
return {
label: optionItem.text,
value: optionItem.value,
};
});
currentFormRules.options = selectOptions;
}
return {
...item,
formRules: [
{ ...currentFormRules, value: item.value, props: { ...currentFormRules.props, options: selectOptions } },
],
fApi: null,
required: item.internal,
};
});
// //
if (!isEdit.value && !isEditField.value) { if (!isEdit.value && !isEditField.value) {
selectData.value = totalTemplateField.value.filter((item) => item.internal); selectData.value = totalTemplateField.value.filter((item) => item.internal);
@ -191,9 +208,9 @@
// //
const getClassifyField = async () => { const getClassifyField = async () => {
try { try {
totalTemplateField.value = await getFieldList({ organizationId: currentOrd, scene: route.query.type }); totalTemplateField.value = await getFieldList({ scopedId: currentOrgId.value, scene: route.query.type });
getFieldOptionList(); getFieldOptionList();
// //
if (isEditField.value) { if (isEditField.value) {
selectData.value = totalTemplateField.value.filter( selectData.value = totalTemplateField.value.filter(
(item) => selectFiled.value.map((it) => it.id).indexOf(item.id) > -1 (item) => selectFiled.value.map((it) => it.id).indexOf(item.id) > -1
@ -219,6 +236,8 @@
} }
}); });
const defectForm = ref<Record<string, any>>({}); //
// //
function getTemplateParams(): ActionTemplateManage { function getTemplateParams(): ActionTemplateManage {
const result = selectData.value.map((item) => { const result = selectData.value.map((item) => {
@ -227,13 +246,20 @@
return { return {
fieldId: item.id, fieldId: item.id,
required: item.required, required: item.required,
apiFieldId: item.apiFieldId, apiFieldId: item.apiFieldId || '',
defaultValue: value, defaultValue: value || '',
}; };
} }
return []; return [];
}); });
//
const sysDetailFields = Object.keys(defectForm.value).map((formKey: string) => {
return {
fieldId: formKey,
defaultValue: defectForm.value[formKey],
};
});
const { name, remark, enableThirdPart, id } = templateForm.value; const { name, remark, enableThirdPart, id } = templateForm.value;
return { return {
id, id,
@ -243,6 +269,7 @@
customFields: result as CustomField[], customFields: result as CustomField[],
scopeId: currentOrgId.value, scopeId: currentOrgId.value,
scene: route.query.type, scene: route.query.type,
systemFields: sysDetailFields,
}; };
} }
@ -269,6 +296,7 @@
} else { } else {
await sleep(300); await sleep(300);
router.push({ name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT, query: route.query }); router.push({ name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT, query: route.query });
setState(true);
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@ -323,6 +351,19 @@
getClassifyField(); getClassifyField();
}; };
//
watch(
() => systemFieldData.value,
(val) => {
if (val) {
systemFieldData.value.forEach((item) => {
defectForm.value[item.fieldId] = item.defaultValue;
});
}
},
{ deep: true }
);
onMounted(() => { onMounted(() => {
setBreadText(); setBreadText();
getClassifyField(); getClassifyField();

View File

@ -8,7 +8,7 @@
<div class="template-operation"> <div class="template-operation">
<div class="flex items-center"> <div class="flex items-center">
<span class="font-medium">{{ props.cardItem.name }}</span> <span class="font-medium">{{ props.cardItem.name }}</span>
<span class="enable">{{ t('system.orgTemplate.enabledTemplates') }}</span> <span v-if="!isEnableProject" class="enable">{{ t('system.orgTemplate.enabledTemplates') }}</span>
</div> </div>
<div class="flex min-w-[300px] flex-nowrap items-center"> <div class="flex min-w-[300px] flex-nowrap items-center">
<span class="operation hover:text-[rgb(var(--primary-5))]"> <span class="operation hover:text-[rgb(var(--primary-5))]">
@ -17,14 +17,14 @@
</span> </span>
<span class="operation hover:text-[rgb(var(--primary-5))]"> <span class="operation hover:text-[rgb(var(--primary-5))]">
<span @click="templateManagement">{{ t('system.orgTemplate.TemplateManagement') }}</span> <span @click="templateManagement">{{ t('system.orgTemplate.TemplateManagement') }}</span>
<a-divider v-if="!props.cardItem.enable || props.cardItem.key === 'BUG'" direction="vertical" /> <a-divider v-if="isEnableProject || props.cardItem.key === 'BUG'" direction="vertical" />
</span> </span>
<span v-if="props.cardItem.key === 'BUG'" class="operation hover:text-[rgb(var(--primary-5))]"> <span v-if="props.cardItem.key === 'BUG'" class="operation hover:text-[rgb(var(--primary-5))]">
<span @click="workflowSetup">{{ t('system.orgTemplate.workflowSetup') }}</span> <span @click="workflowSetup">{{ t('system.orgTemplate.workflowSetup') }}</span>
<a-divider v-if="!props.cardItem.enable && props.cardItem.key === 'BUG'" direction="vertical" /> <a-divider v-if="isEnableProject && props.cardItem.key === 'BUG'" direction="vertical" />
</span> </span>
<span v-if="!props.cardItem.enable" class="rounded p-[2px] hover:bg-[rgb(var(--primary-9))]"> <span v-if="isEnableProject" class="rounded p-[2px] hover:bg-[rgb(var(--primary-9))]">
<MsTableMoreAction :list="moreActions" @select="(item) => handleMoreActionSelect" <MsTableMoreAction :list="moreActions" @select="handleMoreActionSelect"
/></span> /></span>
</div> </div>
</div> </div>
@ -34,20 +34,20 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
/**
* @description 模版-模版管理小卡片
*/
import { ref } from 'vue'; import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue'; import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types'; import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import { isEnableTemplate } from '@/api/modules/setting/template'; import { enableOrOffTemplate } from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import useTemplateStore from '@/store/modules/setting/template'; import useTemplateStore from '@/store/modules/setting/template';
import { SettingRouteEnum } from '@/enums/routeEnum';
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStore(); const appStore = useAppStore();
const templateStore = useTemplateStore(); const templateStore = useTemplateStore();
@ -56,9 +56,25 @@
const props = defineProps<{ const props = defineProps<{
cardItem: Record<string, any>; cardItem: Record<string, any>;
mode: 'organization' | 'project';
}>(); }>();
const router = useRouter(); const emit = defineEmits<{
(e: 'fieldSetting', key: string): void;
(e: 'templateManagement', key: string): void;
(e: 'workflowSetup', key: string): void;
(e: 'updateState'): void;
}>();
//
const isEnableProject = computed(() => {
if (props.mode === 'organization') {
return !templateStore.projectStatus[props.cardItem.key];
}
if (props.mode === 'project') {
return templateStore.ordStatus[props.cardItem.key];
}
});
const moreActions = ref<ActionsItem[]>([ const moreActions = ref<ActionsItem[]>([
{ {
@ -71,47 +87,46 @@
// //
const enableHandler = async () => { const enableHandler = async () => {
try { try {
await isEnableTemplate(currentOrgId.value); if (props.mode) {
Message.success(t('system.orgTemplate.enabledSuccessfully')); await enableOrOffTemplate(currentOrgId.value, props.cardItem.key);
templateStore.getStatus(); Message.success(t('system.orgTemplate.enabledSuccessfully'));
await templateStore.getStatus();
emit('updateState');
}
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
}; };
const handleMoreActionSelect = (item: ActionsItem) => { const handleMoreActionSelect = () => {
if (item.eventTag === 'enable') { enableHandler();
enableHandler();
}
}; };
// //
const fieldSetting = () => { const fieldSetting = () => {
router.push({ emit('fieldSetting', props.cardItem.key);
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_FILED_SETTING,
query: {
type: props.cardItem.key,
},
});
}; };
const templateManagement = () => { const templateManagement = () => {
router.push({ emit('templateManagement', props.cardItem.key);
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT,
query: {
type: props.cardItem.key,
},
});
}; };
const workflowSetup = () => { const workflowSetup = () => {
router.push({ emit('workflowSetup', props.cardItem.key);
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_WORKFLOW,
query: {
type: props.cardItem.key,
},
});
}; };
const templateCardInfo = ref<Record<string, any>>({});
watch(
() => props.cardItem,
(val) => {
if (val) {
debugger;
templateCardInfo.value = { ...props.cardItem };
}
},
{ deep: true }
);
</script> </script>
<style scoped lang="less"> <style scoped lang="less">

View File

@ -1,8 +1,10 @@
<template> <template>
<MsCard has-breadcrumb simple> <MsCard has-breadcrumb simple>
<a-alert v-if="isEnable" class="mb-6" type="warning">{{ t('system.orgTemplate.enableTemplateTip') }}</a-alert> <a-alert v-if="isEnableOrdTemplate" class="mb-6" type="warning">{{
t('system.orgTemplate.enableTemplateTip')
}}</a-alert>
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<span v-if="isEnable" class="font-medium">{{ t('system.orgTemplate.templateList') }}</span> <span v-if="isEnableOrdTemplate" class="font-medium">{{ t('system.orgTemplate.templateList') }}</span>
<a-button v-else type="primary" :disabled="false" @click="createTemplate"> <a-button v-else type="primary" :disabled="false" @click="createTemplate">
{{ t('system.orgTemplate.createTemplate') }} {{ t('system.orgTemplate.createTemplate') }}
</a-button> </a-button>
@ -18,13 +20,16 @@
<MsBaseTable v-bind="propsRes" ref="tableRef" v-on="propsEvent"> <MsBaseTable v-bind="propsRes" ref="tableRef" v-on="propsEvent">
<template #name="{ record }"> <template #name="{ record }">
<span class="ml-2">{{ record.name }}</span> <span class="ml-2">{{ record.name }}</span>
<span v-if="record.internal" class="system-flag">{{ t('system.orgTemplate.isSystem') }}</span> <MsTag v-if="record.internal" size="small" class="ml-2">{{ t('system.orgTemplate.isSystem') }}</MsTag>
</template>
<template #enableThirdPart="{ record }">
{{ record.enableThirdPart ? t('system.orgTemplate.yes') : t('system.orgTemplate.no') }}
</template> </template>
<template #operation="{ record }"> <template #operation="{ record }">
<div class="flex flex-row flex-nowrap"> <div class="flex flex-row flex-nowrap">
<MsButton @click="editTemplate(record.id)">{{ t('system.orgTemplate.edit') }}</MsButton> <MsButton @click="editTemplate(record.id)">{{ t('system.orgTemplate.edit') }}</MsButton>
<MsButton class="!mr-0" @click="copyTemplate(record.id)">{{ t('system.orgTemplate.copy') }}</MsButton> <MsButton class="!mr-0" @click="copyTemplate(record.id)">{{ t('system.orgTemplate.copy') }}</MsButton>
<a-divider v-if="!record.internal" direction="vertical" /> <a-divider v-if="!record.internal" class="h-[12px]" direction="vertical" />
<MsTableMoreAction <MsTableMoreAction
v-if="!record.internal" v-if="!record.internal"
:list="moreActions" :list="moreActions"
@ -51,6 +56,7 @@
import useTable from '@/components/pure/ms-table/useTable'; import useTable from '@/components/pure/ms-table/useTable';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue'; import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types'; import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import { deleteOrdTemplate, getOrganizeTemplateList } from '@/api/modules/setting/template'; import { deleteOrdTemplate, getOrganizeTemplateList } from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
@ -84,12 +90,15 @@
width: 300, width: 300,
showDrag: true, showDrag: true,
showInTable: true, showInTable: true,
showTooltip: true,
}, },
{ {
title: 'system.orgTemplate.description', title: 'system.orgTemplate.description',
dataIndex: 'remark', dataIndex: 'remark',
showDrag: true, showDrag: true,
showInTable: true, showInTable: true,
width: 300,
showTooltip: true,
}, },
{ {
title: 'system.orgTemplate.columnFieldUpdatedTime', title: 'system.orgTemplate.columnFieldUpdatedTime',
@ -100,6 +109,7 @@
{ {
title: 'system.orgTemplate.operation', title: 'system.orgTemplate.operation',
slotName: 'operation', slotName: 'operation',
dataIndex: 'operation',
fixed: 'right', fixed: 'right',
width: 200, width: 200,
showInTable: true, showInTable: true,
@ -107,10 +117,9 @@
}, },
]; ];
await tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT, fieldColumns, 'drawer');
const { propsRes, propsEvent, loadList, setLoadListParams, setProps } = useTable(getOrganizeTemplateList, { const { propsRes, propsEvent, loadList, setLoadListParams, setProps } = useTable(getOrganizeTemplateList, {
tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT, tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT,
scroll: { x: '1000px' }, scroll: { x: '100%' },
selectable: false, selectable: false,
noDisable: true, noDisable: true,
size: 'default', size: 'default',
@ -119,9 +128,13 @@
heightUsed: 380, heightUsed: 380,
}); });
const scene = route.query.type; const scene = route.query.type;
const isEnable = templateStore.templateStatus[scene as string];
const isEnableOrdTemplate = computed(() => {
return templateStore.projectStatus[scene as string];
});
const totalList = ref<OrdTemplateManagement[]>([]); const totalList = ref<OrdTemplateManagement[]>([]);
// //
const searchFiled = async () => { const searchFiled = async () => {
try { try {
@ -140,6 +153,7 @@
eventTag: 'delete', eventTag: 'delete',
}, },
]; ];
// //
const handlerDelete = (record: any) => { const handlerDelete = (record: any) => {
openModal({ openModal({
@ -202,6 +216,7 @@
}, },
}); });
}; };
// //
const copyTemplate = (id: string) => { const copyTemplate = (id: string) => {
router.push({ router.push({
@ -228,12 +243,24 @@
const tableRef = ref(); const tableRef = ref();
const sceneType = computed(() => route.query.type);
const isThirdParty = {
title: 'system.orgTemplate.isThirdParty',
dataIndex: 'enableThirdPart',
slotName: 'enableThirdPart',
showDrag: true,
showInTable: true,
};
function updateColumns() { function updateColumns() {
if (isEnable) { const columns =
const result = fieldColumns.slice(0, fieldColumns.length - 1); sceneType.value === 'BUG' ? fieldColumns.slice(0, 1).concat(isThirdParty, fieldColumns.slice(1)) : fieldColumns;
if (isEnableOrdTemplate.value) {
const result = columns.slice(0, columns.length - 1);
tableRef.value.initColumn(result); tableRef.value.initColumn(result);
} else { } else {
tableRef.value.initColumn(fieldColumns); tableRef.value.initColumn(columns);
} }
} }
@ -242,6 +269,7 @@
fetchData(); fetchData();
updateColumns(); updateColumns();
}); });
tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT, fieldColumns, 'drawer');
</script> </script>
<style scoped lang="less"> <style scoped lang="less">

View File

@ -3,7 +3,7 @@
<template #name="{ record }"> <template #name="{ record }">
<MsIcon v-if="!record.internal" :type="getIconType(record.type)?.iconName || ''" size="16" /> <MsIcon v-if="!record.internal" :type="getIconType(record.type)?.iconName || ''" size="16" />
<span class="ml-2">{{ record.name }}</span> <span class="ml-2">{{ record.name }}</span>
<span v-if="record.internal" class="system-flag">{{ t('system.orgTemplate.isSystem') }}</span> <MsTag v-if="record.internal" size="small" class="ml-2">{{ t('system.orgTemplate.isSystem') }}</MsTag>
</template> </template>
<template #apiFieldId="{ record }"> <template #apiFieldId="{ record }">
<a-input <a-input
@ -37,6 +37,7 @@
v-model:visible="showDrawer" v-model:visible="showDrawer"
:total-data="(totalData as DefinedFieldItem[])" :total-data="(totalData as DefinedFieldItem[])"
:table-select-data="(selectList as DefinedFieldItem[])" :table-select-data="(selectList as DefinedFieldItem[])"
:mode="props.mode"
@confirm="confirmHandler" @confirm="confirmHandler"
@update-data="updateFieldHandler" @update-data="updateFieldHandler"
/> />
@ -46,33 +47,42 @@
</template> </template>
{{ t('system.orgTemplate.createField') }} {{ t('system.orgTemplate.createField') }}
</a-button> </a-button>
<EditFieldDrawer ref="fieldDrawerRef" v-model:visible="showFieldDrawer" @success="updateFieldHandler" /> <EditFieldDrawer
ref="fieldDrawerRef"
v-model:visible="showFieldDrawer"
:mode="props.mode"
@success="updateFieldHandler"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
/**
* @description 系统管理-组织-模板管理-创建模板-非缺陷模板自定义字段表格
*/
import { ref } from 'vue'; import { ref } from 'vue';
import { useRoute } from 'vue-router';
import MsButton from '@/components/pure/ms-button/index.vue'; import MsButton from '@/components/pure/ms-button/index.vue';
import MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue'; import MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue'; import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type'; import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable'; import useTable from '@/components/pure/ms-table/useTable';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import AddFieldToTemplateDrawer from './addFieldToTemplateDrawer.vue'; import AddFieldToTemplateDrawer from './addFieldToTemplateDrawer.vue';
import EditFieldDrawer from './editFieldDrawer.vue'; import EditFieldDrawer from './editFieldDrawer.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useTableStore } from '@/store';
import type { DefinedFieldItem } from '@/models/setting/template'; import type { DefinedFieldItem } from '@/models/setting/template';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
import { getIconType } from './fieldSetting'; import { getIconType } from './fieldSetting';
const tableStore = useTableStore();
const { t } = useI18n(); const { t } = useI18n();
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
mode: 'organization' | 'project';
enableThirdPart: boolean; // enableThirdPart: boolean; //
data: DefinedFieldItem[]; // data: DefinedFieldItem[]; //
selectData: Record<string, any>[]; // selectData: Record<string, any>[]; //
@ -84,6 +94,8 @@
const emit = defineEmits(['update:select-data', 'update']); const emit = defineEmits(['update:select-data', 'update']);
const route = useRoute();
const columns: MsTableColumn = [ const columns: MsTableColumn = [
{ {
title: 'system.orgTemplate.name', title: 'system.orgTemplate.name',
@ -121,6 +133,7 @@
{ {
title: 'system.orgTemplate.operation', title: 'system.orgTemplate.operation',
slotName: 'operation', slotName: 'operation',
dataIndex: 'operation',
fixed: 'right', fixed: 'right',
width: 200, width: 200,
showInTable: true, showInTable: true,
@ -141,14 +154,14 @@
const tableRef = ref(); const tableRef = ref();
tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_FIELD, columns, 'drawer');
const { propsRes, propsEvent, setProps } = useTable(undefined, { const { propsRes, propsEvent, setProps } = useTable(undefined, {
tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_FIELD, tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_FIELD,
columns,
scroll: { x: '1800px' }, scroll: { x: '1800px' },
selectable: false, selectable: false,
noDisable: true, noDisable: true,
size: 'default', size: 'default',
showSetting: true, showSetting: false,
showPagination: false, showPagination: false,
enableDrag: true, enableDrag: true,
}); });
@ -241,7 +254,7 @@
watch( watch(
() => props.enableThirdPart, () => props.enableThirdPart,
(val) => { (val) => {
if (val) { if (val && route.query.type === 'BUG') {
const result = [...columns.slice(0, 1), getApiColumns(), ...columns.slice(1)]; const result = [...columns.slice(0, 1), getApiColumns(), ...columns.slice(1)];
tableRef.value.initColumn(result); tableRef.value.initColumn(result);
} else { } else {

View File

@ -1,220 +1,61 @@
<template> <template>
<div class="wrapper-preview"> <div class="wrapper-preview">
<div class="preview-left pr-4"> <div class="preview-left pr-4">
<a-form ref="viewFormRef" class="rounded-[4px]" :model="viewForm" layout="vertical"> <DefectTemplateLeftContent v-if="props.templateType === 'BUG'" :defect-form="props.defectForm" />
<a-form-item <CaseTemplateLeftContent v-else />
field="caseName"
:label="t('system.orgTemplate.caseName')"
:rules="[{ required: true, message: t('system.orgTemplate.caseNamePlaceholder') }]"
required
asterisk-position="end"
>
<a-input
v-model="viewForm.name"
:max-length="255"
:placeholder="t('system.orgTemplate.caseNamePlaceholder')"
show-word-limit
allow-clear
></a-input>
</a-form-item>
<a-form-item field="precondition" :label="t('system.orgTemplate.precondition')" asterisk-position="end">
<MsRichText v-model="viewForm.precondition" />
</a-form-item>
<a-form-item field="step" :label="t('system.orgTemplate.stepDescription')" class="relative">
<div class="absolute left-16 top-0">
<a-divider direction="vertical" />
<a-dropdown :popup-max-height="false" @select="handleSelectType">
<span class="text-[14px] text-[var(--color-text-4)]"
>{{ t('system.orgTemplate.changeType') }} <icon-down
/></span>
<template #content>
<a-doption> {{ t('system.orgTemplate.stepDescription') }}</a-doption>
<a-doption>{{ t('system.orgTemplate.textDescription') }}</a-doption>
</template>
</a-dropdown>
</div>
<!-- 步骤描述 -->
<div class="w-full">
<MsBaseTable v-bind="propsRes" ref="stepTableRef" v-on="propsEvent">
<template #index="{ rowIndex }">
{{ rowIndex + 1 }}
</template>
<template #caseStep="{ record }">
<a-input v-if="record.showStep" v-model="record.caseStep" class="w-max-[267px]" />
<span v-else-if="record.caseStep && !record.showStep">{{ record.caseStep }}</span>
<span
v-else-if="!record.caseStep && !record.showStep"
class="placeholder text-[var(--color-text-brand)]"
>{{ t('system.orgTemplate.stepTip') }}</span
>
</template>
<template #expectedResult="{ record }">
<a-input v-if="record.showExpected" v-model="record.expectedResult" class="w-max-[267px]" />
<span v-else-if="record.expectedResult && !record.showExpected">{{ record.caseStep }}</span>
<span
v-else-if="!record.expectedResult && !record.showExpected"
class="placeholder text-[var(--color-text-brand)]"
>{{ t('system.orgTemplate.expectationTip') }}</span
>
</template>
<template #operation="{ record }">
<MsTableMoreAction
v-if="!record.internal"
:list="moreActions"
@select="(item) => handleMoreActionSelect(item)"
/>
</template>
</MsBaseTable>
</div>
<a-button class="mt-2 px-0" type="text" @click="addStep">
<template #icon>
<icon-plus class="text-[14px]" />
</template>
{{ t('system.orgTemplate.addStep') }}
</a-button>
</a-form-item>
<a-form-item field="remark" label="备注"> <MsRichText v-model="viewForm.remark" /> </a-form-item>
<a-form-item field="attachment" label="添加附件">
<div class="flex flex-col">
<div class="mb-1"
><a-button type="outline">
<template #icon> <icon-plus class="text-[14px]" /> </template
>{{ t('system.orgTemplate.addAttachment') }}</a-button
>
</div>
<div class="text-[var(--color-text-4)]">{{ t('system.orgTemplate.addAttachmentTip') }}</div>
</div>
</a-form-item>
</a-form>
</div> </div>
<div class="preview-right px-4"> <div class="preview-right px-4">
<MsFormCreate <MsFormCreate
v-if="formRules.length"
ref="formCreateRef" ref="formCreateRef"
:form-rule="formRules" :form-rule="formRules"
:form-create-key="FormCreateKeyEnum.ORGANIZE_TEMPLATE_PREVIEW_TEMPLATE" :form-create-key="FormCreateKeyEnum.ORGANIZE_TEMPLATE_PREVIEW_TEMPLATE"
/> />
<a-empty v-else />
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
/**
* @description 模板-创建模板&编辑模板-预览模板
*/
import { ref } from 'vue'; import { ref } from 'vue';
import MsFormCreate from '@/components/pure/ms-form-create/form-create.vue'; import MsFormCreate from '@/components/pure/ms-form-create/form-create.vue';
import type { FormItem } from '@/components/pure/ms-form-create/types'; import type { FormItem } from '@/components/pure/ms-form-create/types';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue'; import CaseTemplateLeftContent from './caseTemplateLeftContent.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue'; import DefectTemplateLeftContent from './defectTemplateLeftContent.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import { useI18n } from '@/hooks/useI18n'; import type { DefinedFieldItem, SeneType } from '@/models/setting/template';
import { useTableStore } from '@/store';
import type { DefinedFieldItem } from '@/models/setting/template';
import { FormCreateKeyEnum } from '@/enums/formCreateEnum'; import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
const { t } = useI18n();
const tableStore = useTableStore();
const props = defineProps<{ const props = defineProps<{
templateType: SeneType; //
selectField: DefinedFieldItem[]; // selectField: DefinedFieldItem[]; //
defectForm: Record<string, any>; //
}>(); }>();
const templateFieldColumns: MsTableColumn = [
{
title: 'system.orgTemplate.numberIndex',
dataIndex: 'index',
slotName: 'index',
width: 100,
showDrag: false,
showInTable: true,
},
{
title: 'system.orgTemplate.useCaseStep',
slotName: 'caseStep',
dataIndex: 'caseStep',
showDrag: true,
showInTable: true,
},
{
title: 'system.orgTemplate.expectedResult',
dataIndex: 'expectedResult',
slotName: 'expectedResult',
showDrag: true,
showInTable: true,
},
{
title: 'system.orgTemplate.operation',
slotName: 'operation',
fixed: 'right',
width: 200,
showInTable: true,
showDrag: false,
},
];
const { propsRes, propsEvent, setProps } = useTable(undefined, {
tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_STEP,
scroll: { x: '800px' },
selectable: false,
noDisable: true,
size: 'default',
showSetting: true,
showPagination: false,
enableDrag: false,
});
const viewForm = ref({
name: '',
precondition: '',
value: '',
remark: '',
});
const handleSelectType = () => {};
const stepTableRef = ref();
const moreActions: ActionsItem[] = [
{
label: 'system.orgTemplate.copy',
danger: true,
eventTag: 'copy',
},
{
label: 'system.orgTemplate.delete',
danger: true,
eventTag: 'delete',
},
];
const addStep = () => {};
const handlerDelete = () => {};
//
const handleMoreActionSelect = (item: ActionsItem) => {
if (item.eventTag === 'delete') {
handlerDelete();
}
};
const formRuleField = ref<FormItem[][]>([]); const formRuleField = ref<FormItem[][]>([]);
const formRules = ref<FormItem[]>([]); const formRules = ref<FormItem[]>([]);
const formCreateRef = ref(); const formCreateRef = ref();
// //
const getFormRules = () => { const getFormRules = () => {
formRuleField.value = [];
formRules.value = [];
if (props.selectField && props.selectField.length) { if (props.selectField && props.selectField.length) {
props.selectField.forEach((item: DefinedFieldItem) => { props.selectField.forEach((item: DefinedFieldItem) => {
const currentFormItem = item.formRules?.map((rule: any) => { const currentFormItem = item.formRules?.map((rule: any) => {
const optionsItem = rule.options.map((opt: any) => { let optionsItem = [];
return { if (rule.options && rule.options.length) {
text: opt.label, optionsItem = rule.options.map((opt: any) => {
value: opt.value, return {
}; text: opt.label,
}); value: opt.value,
};
});
}
return { return {
type: item.type, type: item.type,
name: rule.field, name: rule.field,
@ -225,24 +66,28 @@
props: { props: {
modelValue: rule.value, modelValue: rule.value,
options: optionsItem, options: optionsItem,
disabled: true,
}, },
}; };
}); });
formRuleField.value.push(currentFormItem as FormItem[]); formRuleField.value.push(currentFormItem as FormItem[]);
}); });
const result = formRuleField.value.flatMap((item) => item); formRules.value = formRuleField.value.flatMap((item) => item);
formRules.value = result;
} }
}; };
watchEffect(() => {
getFormRules();
});
onBeforeUnmount(() => {
formRules.value = [];
formRuleField.value = [];
});
defineExpose({ defineExpose({
getFormRules, getFormRules,
}); });
onMounted(() => {
setProps({ data: [{ id: 1, showStep: false, showExpected: false }] });
getFormRules();
});
await tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_STEP, templateFieldColumns, 'drawer');
</script> </script>
<style scoped lang="less"> <style scoped lang="less">

View File

@ -0,0 +1,15 @@
<template>
<MsCard has-breadcrumb simple>
<WorkflowTable mode="organization" />
</MsCard>
</template>
<script setup lang="ts">
/**
* @description 系统管理-组织=模板-工作流首页
*/
import MsCard from '@/components/pure/ms-card/index.vue';
import WorkflowTable from './workflowTable.vue';
</script>
<style scoped></style>

View File

@ -3,10 +3,10 @@
class="wrapper" class="wrapper"
:class="{ :class="{
...styleClass.wrapper, ...styleClass.wrapper,
_hover_Wrapper: isEnableProjectState || isNotAllowCreate ? false : true, '_hover_Wrapper': isEnableProjectState || isNotAllowCreate ? false : true,
_pointer: !isNotAllowCreate, '_pointer': !isNotAllowCreate,
_not_allowed: isNotAllowCreate, 'cursor-not-allowed': isNotAllowCreate,
_disabled_gray_bg: isEnableProjectState, '_disabled_gray_bg': isEnableProjectState,
}" }"
> >
<!-- 不允许状态流转 --> <!-- 不允许状态流转 -->
@ -64,27 +64,27 @@
<script setup lang="ts"> <script setup lang="ts">
/** /**
* @description 系统设置-组织-工作流table小卡片 * @description 工作流table小卡片
*/ */
import { ref } from 'vue'; import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { Message, TableColumnData } from '@arco-design/web-vue'; import { Message, TableColumnData } from '@arco-design/web-vue';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue'; import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import { updateOrdWorkStateFlow } from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import useTemplateStore from '@/store/modules/setting/template'; import useTemplateStore from '@/store/modules/setting/template';
import type { UpdateWorkFlowSetting, WorkFlowType } from '@/models/setting/template'; import type { UpdateWorkFlowSetting, WorkFlowType } from '@/models/setting/template';
import { getWorkFlowRequestApi } from '@/views/setting/organization/template/components/fieldSetting';
const templateStore = useTemplateStore(); const templateStore = useTemplateStore();
const { t } = useI18n(); const { t } = useI18n();
const { openModal } = useModal(); const { openModal } = useModal();
const props = defineProps<{ const props = defineProps<{
mode: 'organization' | 'project';
stateItem: WorkFlowType; stateItem: WorkFlowType;
columnItem: TableColumnData; columnItem: TableColumnData;
cellCoordinates: { rowId: string; columnId: string }; cellCoordinates: { rowId: string; columnId: string };
@ -134,8 +134,9 @@
// //
const isEnableProjectState = computed(() => { const isEnableProjectState = computed(() => {
const projectState = templateStore.getProjectTemplateState(); return props.mode === 'project'
return projectState[props.stateItem.scene]; ? !templateStore.projectStatus[props.stateItem.scene]
: !templateStore.ordStatus[props.stateItem.scene];
}); });
const title = computed(() => { const title = computed(() => {
@ -227,6 +228,7 @@
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const updateOrdWorkStateFlow = getWorkFlowRequestApi(props.mode).updateFlow;
// //
async function changeWorkFlow(type: string) { async function changeWorkFlow(type: string) {
try { try {
@ -307,9 +309,9 @@
} }
// hover // hover
._hover_Wrapper { ._hover_Wrapper {
@apply hover:shadow-xl;
&:hover { &:hover {
border-radius: 4px; border-radius: 4px;
box-shadow: 0 4px 15px -1px rgba(100 100 102 / 15%);
} }
} }
// hover---- // hover----

View File

@ -1,172 +1,204 @@
<template> <template>
<MsCard has-breadcrumb simple> <a-alert v-if="isShowTip" class="mb-6" type="warning">
<a-alert class="mb-6" type="warning">{{ t('system.orgTemplate.workFlowTip') }}</a-alert> <div class="flex items-start justify-between">
<div class="mb-4"> <span class="w-[80%]">{{ t('system.orgTemplate.workFlowTip') }}</span>
<div class="mb-4 flex items-center" <span class="cursor-pointer text-[var(--color-text-2)]" @click="noRemindHandler">{{
><a-button class="mr-2" type="outline" @click="addStatus">{{ t('system.orgTemplate.addState') }}</a-button> t('system.orgTemplate.noReminders')
<a-popover title="" position="right"> }}</span>
<MsButton class="!mr-1">{{ t('system.orgTemplate.example') }}</MsButton>
<template #content>
<div class="w-[410px] bg-[var(--color-bg-3)] p-[16px]">
<img src="@/assets/images/schematicDrawing.png" alt="" />
</div>
</template>
</a-popover>
<a-tooltip :content="t('system.orgTemplate.workFlowTip')">
<icon-exclamation-circle class="text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]" />
<template #content>
<div class="whitespace-nowrap">{{ t('system.orgTemplate.workFlowToolTip') }}</div>
<div>{{ t('system.orgTemplate.workFlowToolTipHover') }}</div>
</template>
</a-tooltip>
</div>
<a-table
:columns="workFlowColumns"
:data="dataList"
row-key="id"
:bordered="{ cell: true }"
:hoverable="false"
:pagination="false"
:draggable="{ type: 'handle', width: 39 }"
:loading="tableLoading"
@change="handleChange"
>
<template #columns>
<a-table-column
v-for="column in workFlowColumns"
:key="column.dataIndex"
:data-index="column.dataIndex"
:title="(column?.title as string)"
:header-cell-class="column.headerCellClass"
:fixed="column.fixed"
>
<template #title>
<div v-if="column.dataIndex !== 'statusName'" class="w-full">
<MsTag class="relative" size="large" theme="light">{{ column.title }} </MsTag></div
>
<div v-else class="splitBox">
<div class="startStatus"> {{ t('system.orgTemplate.startState') }} </div>
<div class="line"></div>
<div class="endStatus"> {{ t('system.orgTemplate.endState') }} </div>
</div>
</template>
<template #cell="{ record }">
<div v-if="column.dataIndex === 'statusName'">
<div class="flex items-center justify-between">
<div class="relative">
<MsTag class="relative" size="large" theme="light">{{ record.name }}</MsTag>
<span v-if="record.statusDefinitions.join() === 'START'" class="absolute -top-6 left-7">
<svg-icon width="36px" height="36px" class="inline-block text-[white]" name="start"></svg-icon
></span>
</div>
<div class="action mr-2 flex h-8 w-8 items-center justify-center rounded opacity-0">
<MsTableMoreAction
:list="getMoreActions(record)"
@select="(item) => handleMoreActionSelect(item, record)"
></MsTableMoreAction
></div>
</div>
</div>
<div v-else class="!h-[82px] min-w-[116px] p-[2px]">
<WorkflowCard
:column-item="column"
:state-item="record"
:cell-coordinates="cellCoordinates"
:total-data="dataList"
@click="selectCard(record, column.dataIndex)"
@ok="getWorkFetchList()"
/>
</div>
</template>
</a-table-column>
<a-table-column :title="t('system.orgTemplate.operation')" :width="360" header-cell-class="splitOperation">
<template #cell="{ record }">
<div class="flex">
<MsButton class="!mr-0 ml-4" @click="editWorkStatus(record)">{{ t('common.edit') }}</MsButton>
<a-divider direction="vertical" />
<a-checkbox v-model="record.currentState" @change="(value) => changeState(value, record)">
<MsButton>{{ t('system.orgTemplate.endState') }}</MsButton></a-checkbox
>
<MsButton class="!mr-0 ml-4" @click="detailWorkStatus(record)">{{
t('system.orgTemplate.details')
}}</MsButton>
</div>
</template>
</a-table-column>
</template>
</a-table>
<div class="mt-4 flex items-center text-[var(--color-text-4)]">
<span>tips: </span>
<MsIcon type="icon-icon_drag" class="mx-4 text-[16px] text-[var(--color-text-4)]" />
<span>{{ t('system.orgTemplate.anyStateToAll') }}</span>
<a-popover title="" position="right">
<MsButton class="!mr-0 ml-2">{{ t('system.orgTemplate.example') }}</MsButton>
<template #content>
<div class="bg-[var(--color-bg-3)]">
<img src="@/assets/images/colorSelect.png" alt="" />
</div>
</template>
</a-popover>
</div>
<AddWorkStatusModal ref="addWorkStateRef" v-model:visible="showModel" @success="getWorkFetchList()" />
<MsDrawer
ref="detailDrawerRef"
v-model:visible="showDetailVisible"
:width="480"
:footer="false"
:title="t('system.orgTemplate.stateDetail', { name: detailInfo?.name })"
>
<div class="flex p-4">
<div class="flex w-[40%] flex-col">
<span class="label">{{ t('system.orgTemplate.stateName') }}</span>
<span class="label">{{ t('system.orgTemplate.description') }}</span>
</div>
<div class="flex w-[60%] flex-col">
<span class="content">{{ detailInfo?.name }}</span>
<span class="content">{{ detailInfo?.remark || '-' }}</span>
</div>
</div>
</MsDrawer>
</div> </div>
</MsCard> </a-alert>
<div class="mb-4">
<div class="mb-4 flex items-center"
><a-button v-if="!isEnableProjectState" class="mr-2" type="outline" @click="addStatus">{{
t('system.orgTemplate.addState')
}}</a-button>
<span v-else class="mr-2 font-medium text-[var(--color-text-1)]">工作流</span>
<a-popover title="" position="right">
<MsButton class="!mr-1">{{ t('system.orgTemplate.example') }}</MsButton>
<template #content>
<div class="w-[410px] bg-[var(--color-bg-3)] p-1">
<img src="@/assets/images/schematicDrawing.png" alt="" />
</div>
</template>
</a-popover>
<a-tooltip :content="t('system.orgTemplate.workFlowTip')">
<icon-exclamation-circle class="text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]" />
<template #content>
<div class="whitespace-nowrap">{{ t('system.orgTemplate.workFlowToolTip') }}</div>
<div>{{ t('system.orgTemplate.workFlowToolTipHover') }}</div>
</template>
</a-tooltip>
</div>
<a-table
:columns="workFlowColumns"
:data="dataList"
row-key="id"
:bordered="{ cell: true }"
:hoverable="false"
:pagination="false"
:scroll="{ x: '1400px' }"
:draggable="{ type: 'handle', width: 39 }"
:loading="tableLoading"
@change="handleChange"
>
<template #columns>
<a-table-column
v-for="column in workFlowColumns"
:key="column.dataIndex"
:data-index="column.dataIndex"
:title="(column?.title as string)"
:header-cell-class="column.headerCellClass"
:fixed="column.fixed"
>
<template #title>
<div v-if="column.dataIndex !== 'statusName'" class="w-full">
<MsTag class="relative" size="large" theme="light">{{ column.title }} </MsTag></div
>
<div v-else class="splitBox">
<div class="startStatus"> {{ t('system.orgTemplate.startState') }} </div>
<div class="line"></div>
<div class="endStatus"> {{ t('system.orgTemplate.endState') }} </div>
</div>
</template>
<template #cell="{ record }">
<div v-if="column.dataIndex === 'statusName'">
<div class="flex items-center justify-between">
<div class="relative">
<MsTag class="relative" size="large" theme="light">{{ record.name }}</MsTag>
<span v-if="record.statusDefinitions.join() === 'START'" class="absolute -top-6 left-7">
<svg-icon width="36px" height="36px" class="inline-block text-[white]" name="start"></svg-icon
></span>
</div>
<div
v-if="!isEnableProjectState"
class="action mr-2 flex h-8 w-8 items-center justify-center rounded opacity-0"
>
<MsTableMoreAction
:list="getMoreActions(record)"
@select="(item) => handleMoreActionSelect(item, record)"
></MsTableMoreAction
></div>
</div>
</div>
<div v-else class="!h-[82px] min-w-[116px] p-[2px]">
<WorkflowCard
:mode="props.mode"
:column-item="column"
:state-item="record"
:cell-coordinates="cellCoordinates"
:total-data="dataList"
@click="selectCard(record, column.dataIndex)"
@ok="getWorkFetchList()"
/>
</div>
</template>
</a-table-column>
<a-table-column
:title="t('system.orgTemplate.operation')"
:width="320"
header-cell-class="splitOperation"
fixed="right"
>
<template #cell="{ record }">
<div class="ml-4 flex items-center">
<MsButton v-if="!isEnableProjectState" class="!mr-0 ml-4" @click="editWorkStatus(record)">{{
t('common.edit')
}}</MsButton>
<a-divider v-if="!isEnableProjectState" class="h-[12px]" direction="vertical" />
<a-checkbox
v-if="!isEnableProjectState"
v-model="record.currentState"
@change="(value) => changeState(value, record)"
>
<MsButton class="!mr-0">{{ t('system.orgTemplate.endState') }}</MsButton></a-checkbox
>
<a-divider v-if="!isEnableProjectState" class="h-[12px]" direction="vertical" />
<MsButton class="!mr-0" @click="detailWorkStatus(record)">{{ t('system.orgTemplate.details') }}</MsButton>
</div>
</template>
</a-table-column>
</template>
</a-table>
<div class="mt-4 flex items-center text-[var(--color-text-4)]">
<span>tips: </span>
<MsIcon type="icon-icon_drag" class="mx-4 text-[16px] text-[var(--color-text-4)]" />
<span>{{ t('system.orgTemplate.anyStateToAll') }}</span>
<a-popover title="" position="right">
<MsButton class="!mr-0 ml-2">{{ t('system.orgTemplate.example') }}</MsButton>
<template #content>
<div class="bg-[var(--color-bg-3)]">
<img src="@/assets/images/colorSelect.png" alt="" />
</div>
</template>
</a-popover>
</div>
<AddWorkStatusModal
ref="addWorkStateRef"
v-model:visible="showModel"
:mode="props.mode"
@success="getWorkFetchList()"
/>
<MsDrawer
ref="detailDrawerRef"
v-model:visible="showDetailVisible"
:width="480"
:footer="false"
:title="t('system.orgTemplate.stateDetail', { name: detailInfo?.name })"
>
<div class="flex p-4">
<div class="flex w-[40%] flex-col">
<span class="label">{{ t('system.orgTemplate.stateName') }}</span>
<span class="label">{{ t('system.orgTemplate.description') }}</span>
</div>
<div class="flex w-[60%] flex-col">
<span class="content">{{ detailInfo?.name }}</span>
<span class="content">{{ detailInfo?.remark || '-' }}</span>
</div>
</div>
</MsDrawer>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
/**
* @description 模板-工作流table
*/
import { ref } from 'vue'; import { ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { Message, TableColumnData, TableData } from '@arco-design/web-vue'; import { Message, TableColumnData, TableData } from '@arco-design/web-vue';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import MsButton from '@/components/pure/ms-button/index.vue'; import MsButton from '@/components/pure/ms-button/index.vue';
import MsCard from '@/components/pure/ms-card/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue'; import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types'; import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue'; import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import AddWorkStatusModal from './addWorkStatusModal.vue'; import AddWorkStatusModal from '@/views/setting/organization/template/components/addWorkStatusModal.vue';
import WorkflowCard from './workflowCard.vue'; import WorkflowCard from '@/views/setting/organization/template/components/workflowCard.vue';
import {
deleteOrdWorkState,
getWorkFlowList,
setOrdWorkState,
setOrdWorkStateSort,
} from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import useVisit from '@/hooks/useVisit';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import useTemplateStore from '@/store/modules/setting/template'; import useTemplateStore from '@/store/modules/setting/template';
import { characterLimit } from '@/utils'; import { characterLimit } from '@/utils';
import type { SetStateType, WorkFlowType } from '@/models/setting/template'; import type { SetStateType, WorkFlowType } from '@/models/setting/template';
import { getWorkFlowRequestApi } from '@/views/setting/organization/template/components/fieldSetting';
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStore(); const appStore = useAppStore();
const templateStore = useTemplateStore(); const templateStore = useTemplateStore();
const props = defineProps<{
mode: 'organization' | 'project'; // ||
}>();
const currentOrgId = computed(() => appStore.currentOrgId); const currentOrgId = computed(() => appStore.currentOrgId);
const currentProjectId = computed(() => appStore.currentProjectId);
const { openModal } = useModal(); const { openModal } = useModal();
const route = useRoute(); const route = useRoute();
@ -180,10 +212,11 @@
// //
const isEnableProjectState = computed(() => { const isEnableProjectState = computed(() => {
const projectState = templateStore.getProjectTemplateState(); return props.mode === 'organization'
return projectState[route.query.type as string]; ? templateStore.projectStatus[route.query.type as string]
: !templateStore.projectStatus[route.query.type as string];
}); });
const dataList = ref<any>([]); const dataList = ref<WorkFlowType[]>([]);
// //
const workData = ref<WorkFlowType[]>([]); const workData = ref<WorkFlowType[]>([]);
@ -212,12 +245,16 @@
const tableLoading = ref<boolean>(false); const tableLoading = ref<boolean>(false);
const scopedId = computed(() => (props.mode === 'organization' ? currentOrgId.value : currentProjectId.value));
const getWorkList = getWorkFlowRequestApi(props.mode).list;
// table // table
async function getWorkFetchList() { async function getWorkFetchList() {
try { try {
tableLoading.value = true; tableLoading.value = true;
workData.value = await getWorkFlowList(currentOrgId.value, route.query.type); workData.value = await getWorkList(scopedId.value, route.query.type);
workFlowColumns.value = workData.value.map((item, index) => { workFlowColumns.value = workData.value.map((item) => {
const columns = { const columns = {
title: item.name, title: item.name,
dataIndex: item.id, dataIndex: item.id,
@ -261,7 +298,7 @@
function addStatus() { function addStatus() {
showModel.value = true; showModel.value = true;
} }
const deleteState = getWorkFlowRequestApi(props.mode).delete;
// //
function deleteHandler(record: WorkFlowType) { function deleteHandler(record: WorkFlowType) {
if (record.statusDefinitions.join().includes('START')) { if (record.statusDefinitions.join().includes('START')) {
@ -279,7 +316,7 @@
}, },
onBeforeOk: async () => { onBeforeOk: async () => {
try { try {
if (record.id) await deleteOrdWorkState(record.id); if (record.id) await deleteState(record.id);
Message.success(t('system.orgTemplate.deleteSuccess')); Message.success(t('system.orgTemplate.deleteSuccess'));
getWorkFetchList(); getWorkFetchList();
} catch (error) { } catch (error) {
@ -290,6 +327,7 @@
}); });
} }
const setInitAndEndState = getWorkFlowRequestApi(props.mode).changeState;
// || // ||
async function setState(record: WorkFlowType, type: string) { async function setState(record: WorkFlowType, type: string) {
const params: SetStateType = { const params: SetStateType = {
@ -298,7 +336,7 @@
enable: type === 'START' ? true : record.currentState, enable: type === 'START' ? true : record.currentState,
}; };
try { try {
await setOrdWorkState(params); await setInitAndEndState(params);
Message.success( Message.success(
type === 'END' ? t('system.orgTemplate.setEndStateSuccess') : t('system.orgTemplate.setInitStateSuccess') type === 'END' ? t('system.orgTemplate.setEndStateSuccess') : t('system.orgTemplate.setInitStateSuccess')
); );
@ -314,15 +352,17 @@
setState(record, 'START'); setState(record, 'START');
} }
} }
const dragChangeRequest = getWorkFlowRequestApi(props.mode).dragChange;
// //
async function handleChange(_data: TableData[]) { async function handleChange(_data: TableData[]) {
const originIds = dataList.value.map((item: any) => item.id); const originIds = dataList.value.map((item: any) => item.id);
dataList.value = _data; dataList.value = _data as WorkFlowType[];
const dataIds = _data.map((item: any) => item.id); const dataIds = _data.map((item: any) => item.id);
const isChange = isEqual(originIds, dataIds); const isChange = isEqual(originIds, dataIds);
if (isChange) return {}; if (isChange) return false;
try { try {
await setOrdWorkStateSort(currentOrgId.value, route.query.type, dataIds); await dragChangeRequest(scopedId.value, route.query.type, dataIds);
getWorkFetchList(); getWorkFetchList();
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@ -364,8 +404,24 @@
detailInfo.value = { ...record }; detailInfo.value = { ...record };
} }
const visitedKey = 'notRemindWorkFlowTip';
const { addVisited } = useVisit(visitedKey);
const { getIsVisited } = useVisit(visitedKey);
const isShowTip = ref<boolean>(true);
//
const noRemindHandler = () => {
isShowTip.value = false;
addVisited();
};
const doCheckIsTip = () => {
isShowTip.value = !getIsVisited();
};
onBeforeMount(() => { onBeforeMount(() => {
getWorkFetchList(); getWorkFetchList();
doCheckIsTip();
}); });
</script> </script>

View File

@ -14,13 +14,21 @@
:card-min-width="360" :card-min-width="360"
class="flex-1" class="flex-1"
:shadow-limit="50" :shadow-limit="50"
:list="getCardList('organization')" :list="cardList"
:is-proportional="false" :is-proportional="false"
:gap="16" :gap="16"
padding-bottom-space="16px" padding-bottom-space="16px"
> >
<template #item="{ item, index }"> <template #item="{ item, index }">
<TemplateItem :card-item="item" :index="index" /> <TemplateItem
:card-item="item"
:index="index"
mode="organization"
@field-setting="fieldSetting"
@template-management="templateManagement"
@workflow-setup="workflowSetup"
@update-state="updateState"
/>
</template> </template>
</MsCardList> </MsCardList>
</div> </div>
@ -31,7 +39,7 @@
/** /**
* @description 系统设置--组织--模版 * @description 系统设置--组织--模版
*/ */
import { onMounted, ref } from 'vue'; import { useRouter } from 'vue-router';
import MsCard from '@/components/pure/ms-card/index.vue'; import MsCard from '@/components/pure/ms-card/index.vue';
import MsCardList from '@/components/business/ms-card-list/index.vue'; import MsCardList from '@/components/business/ms-card-list/index.vue';
@ -41,16 +49,16 @@
import useVisit from '@/hooks/useVisit'; import useVisit from '@/hooks/useVisit';
import useTemplateStore from '@/store/modules/setting/template'; import useTemplateStore from '@/store/modules/setting/template';
import { SettingRouteEnum } from '@/enums/routeEnum';
import { getCardList } from './components/fieldSetting'; import { getCardList } from './components/fieldSetting';
const templateStore = useTemplateStore(); const templateStore = useTemplateStore();
const { t } = useI18n(); const { t } = useI18n();
const router = useRouter();
const visitedKey = 'notRemind'; const visitedKey = 'notRemind';
const { addVisited } = useVisit(visitedKey); const { addVisited } = useVisit(visitedKey);
const { getIsVisited } = useVisit(visitedKey); const { getIsVisited } = useVisit(visitedKey);
const isShowTip = ref<boolean>(true); const isShowTip = ref<boolean>(true);
const noRemindHandler = () => { const noRemindHandler = () => {
isShowTip.value = false; isShowTip.value = false;
@ -65,8 +73,46 @@
templateStore.getStatus(); templateStore.getStatus();
}); });
onMounted(() => { //
const fieldSetting = (key: string) => {
router.push({
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_FILED_SETTING,
query: {
type: key,
},
});
};
//
const templateManagement = (key: string) => {
router.push({
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT,
query: {
type: key,
},
});
};
//
const workflowSetup = (key: string) => {
router.push({
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_WORKFLOW,
query: {
type: key,
},
});
};
const cardList = ref<Record<string, any>[]>([]);
//
const updateState = () => {
cardList.value = [...getCardList('organization')];
};
onBeforeMount(() => {
doCheckIsTip(); doCheckIsTip();
updateState();
}); });
</script> </script>

View File

@ -18,6 +18,7 @@ export default {
'system.orgTemplate.testPlanTemplates': 'TestPlan template', 'system.orgTemplate.testPlanTemplates': 'TestPlan template',
'system.orgTemplate.defectTemplates': 'Defect template', 'system.orgTemplate.defectTemplates': 'Defect template',
'system.orgTemplate.enabledTemplates': 'enabled', 'system.orgTemplate.enabledTemplates': 'enabled',
'system.orgTemplate.disabledTemplates': 'disabled',
'system.orgTemplate.fieldSetting': 'Setting', 'system.orgTemplate.fieldSetting': 'Setting',
'system.orgTemplate.TemplateManagement': 'Management', 'system.orgTemplate.TemplateManagement': 'Management',
'system.orgTemplate.workflowSetup': 'Workflow', 'system.orgTemplate.workflowSetup': 'Workflow',
@ -86,6 +87,7 @@ export default {
'system.orgTemplate.addStep': 'Add Step', 'system.orgTemplate.addStep': 'Add Step',
'system.orgTemplate.caseName': 'Use case name', 'system.orgTemplate.caseName': 'Use case name',
'system.orgTemplate.caseNamePlaceholder': 'Please enter a use case name', 'system.orgTemplate.caseNamePlaceholder': 'Please enter a use case name',
'system.orgTemplate.defectNamePlaceholder': 'Please enter a use defect name',
'system.orgTemplate.precondition': 'precondition', 'system.orgTemplate.precondition': 'precondition',
'system.orgTemplate.stepDescription': 'Step description', 'system.orgTemplate.stepDescription': 'Step description',
'system.orgTemplate.changeType': 'Change type', 'system.orgTemplate.changeType': 'Change type',
@ -146,4 +148,20 @@ export default {
'system.orgTemplate.stateTip': 'system.orgTemplate.stateTip':
'Open, the existing state will transfer to the state, only in the state of new Settings', 'Open, the existing state will transfer to the state, only in the state of new Settings',
'system.orgTemplate.createSuccess': 'Created successfully', 'system.orgTemplate.createSuccess': 'Created successfully',
'system.orgTemplate.filedDetail': '{name}',
'system.orgTemplate.int': 'Int',
'system.orgTemplate.float': 'Float',
'system.orgTemplate.defaultTemplate': 'Default Template',
'system.orgTemplate.setSuccessfully': 'Set successfully',
'system.orgTemplate.isThirdParty': 'Third Party',
'system.orgTemplate.yes': 'Yes',
'system.orgTemplate.no': 'No',
'system.orgTemplate.custom': 'custom',
'system.orgTemplate.defectName': 'Defect Name',
'system.orgTemplate.defectContent': 'Defect Content',
'system.orgTemplate.defectNameTip':
'You can set a default value for the defect name, which is used uniformly when creating it',
'system.orgTemplate.defectContentTip':
'You can set the default value for the defect content and use it when creating',
'system.orgTemplate.templateNameRules': 'Please enter a template name',
}; };

View File

@ -18,6 +18,7 @@ export default {
'system.orgTemplate.testPlanTemplates': '测试计划模版', 'system.orgTemplate.testPlanTemplates': '测试计划模版',
'system.orgTemplate.defectTemplates': '缺陷模版', 'system.orgTemplate.defectTemplates': '缺陷模版',
'system.orgTemplate.enabledTemplates': '已启用项目模版', 'system.orgTemplate.enabledTemplates': '已启用项目模版',
'system.orgTemplate.disabledTemplates': '未启用项目模版',
'system.orgTemplate.fieldSetting': '字段设置', 'system.orgTemplate.fieldSetting': '字段设置',
'system.orgTemplate.TemplateManagement': '模版管理', 'system.orgTemplate.TemplateManagement': '模版管理',
'system.orgTemplate.workflowSetup': '工作流设置', 'system.orgTemplate.workflowSetup': '工作流设置',
@ -88,6 +89,7 @@ export default {
'system.orgTemplate.addStep': '添加步骤', 'system.orgTemplate.addStep': '添加步骤',
'system.orgTemplate.caseName': '用例名称', 'system.orgTemplate.caseName': '用例名称',
'system.orgTemplate.caseNamePlaceholder': '请输入用例名称', 'system.orgTemplate.caseNamePlaceholder': '请输入用例名称',
'system.orgTemplate.defectNamePlaceholder': '请输入缺陷名称',
'system.orgTemplate.precondition': '前置条件', 'system.orgTemplate.precondition': '前置条件',
'system.orgTemplate.stepDescription': '步骤描述', 'system.orgTemplate.stepDescription': '步骤描述',
'system.orgTemplate.changeType': '更改类型', 'system.orgTemplate.changeType': '更改类型',
@ -140,4 +142,18 @@ export default {
'system.orgTemplate.details': '详情', 'system.orgTemplate.details': '详情',
'system.orgTemplate.stateTip': '开启: 已有的状态会流转到该状态,仅在新建状态时设置', 'system.orgTemplate.stateTip': '开启: 已有的状态会流转到该状态,仅在新建状态时设置',
'system.orgTemplate.createSuccess': '创建成功', 'system.orgTemplate.createSuccess': '创建成功',
'system.orgTemplate.filedDetail': '{name}',
'system.orgTemplate.int': '整数',
'system.orgTemplate.float': '保留小数',
'system.orgTemplate.defaultTemplate': '默认模板',
'system.orgTemplate.setSuccessfully': '设置成功',
'system.orgTemplate.isThirdParty': '对接第三方',
'system.orgTemplate.yes': '是',
'system.orgTemplate.no': '否',
'system.orgTemplate.custom': '自定义',
'system.orgTemplate.defectName': '缺陷名称',
'system.orgTemplate.defectContent': '缺陷内容',
'system.orgTemplate.defectNameTip': '可为缺陷名称设置默认值,创建时统一使用',
'system.orgTemplate.defectContentTip': '可为缺陷内容设置默认值,创建时统一使用',
'system.orgTemplate.templateNameRules': '请输入模板名称',
}; };