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 {
CreateFieldUrl,
CreateOrganizeTemplateUrl,
CreateProjectFieldUrl,
CreateProjectTemplateUrl,
DeleteFieldDetailUrl,
DeleteOrganizeTemplateUrl,
DeleteProjectFieldDetailUrl,
DeleteProjectTemplateUrl,
EnableOrOffTemplateUrl,
GetDefinedFieldListUrl,
GetDefinedProjectFieldListUrl,
GetFieldDetailUrl,
GetFieldProjectDetailUrl,
getOrdTemplateStateUrl,
GetOrganizeTemplateDetailUrl,
GetOrganizeTemplateUrl,
GetProjectTemplateDetailUrl,
isEnableTemplateUrl,
getProjectTemplateStateUrl,
GetProjectTemplateUrl,
OrdCreateFlowStatusUrl,
OrdDeleteFlowStatusUrl,
OrdSetStateUrl,
@ -18,9 +26,17 @@ import {
OrdUpdateFlowStatusUrl,
OrdUpdateStateFlowUrl,
OrdWorkFlowUrl,
SetOrganizeTemplateUrl,
ProjectCreateFlowStatusUrl,
ProjectDeleteFlowStatusUrl,
ProjectSetStateUrl,
ProjectStateSortUrl,
ProjectUpdateFlowStatusUrl,
ProjectUpdateStateFlowUrl,
ProjectWorkFlowUrl,
SetProjectTemplateUrl,
UpdateFieldUrl,
UpdateOrganizeTemplateUrl,
UpdateProjectFieldUrl,
UpdateProjectTemplateUrl,
} from '@/api/requrls/setting/template';
@ -28,9 +44,7 @@ import { TableQueryParams } from '@/models/common';
import type {
ActionTemplateManage,
AddOrUpdateField,
DefinedFieldItem,
OrdWorkStatus,
OrganizeTemplateItem,
SeneType,
SetStateType,
UpdateWorkFlowSetting,
@ -38,13 +52,13 @@ import type {
} from '@/models/setting/template';
/** *
*
* ()
*/
// 获取模版列表(组织)
export function getOrganizeTemplateList(params: TableQueryParams) {
return MSR.get({ url: `${GetOrganizeTemplateUrl}/${params.organizationId}/${params.scene}` });
}
// 获取模版详情
// 获取模版详情(组织)
export function getOrganizeTemplateInfo(id: string) {
return MSR.get({ url: `${GetOrganizeTemplateDetailUrl}/${id}` });
}
@ -57,21 +71,32 @@ export function createOrganizeTemplateInfo(data: ActionTemplateManage) {
export function updateOrganizeTemplateInfo(data: ActionTemplateManage) {
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) {
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) {
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) {
return MSR.get<WorkFlowType[]>({ url: `${OrdWorkFlowUrl}/${organizationId}/${scene}` });
export function getWorkFlowList(scopedId: string, scene: SeneType) {
return MSR.get<WorkFlowType[]>({ url: `${OrdWorkFlowUrl}/${scopedId}/${scene}` });
}
// 创建工作流状态
export function createWorkFlowStatus(data: OrdWorkStatus) {
@ -116,11 +141,99 @@ export function setOrdWorkState(data: SetStateType) {
return MSR.post({ url: OrdSetStateUrl, data });
}
// 设置工作流状态排序
export function setOrdWorkStateSort(organizationId: string, scene: SeneType, data: string[]) {
return MSR.post({ url: `${OrdStateSortUrl}/${organizationId}/${scene}`, data });
export function setOrdWorkStateSort(scopedId: string, scene: SeneType, data: string[]) {
return MSR.post({ url: `${OrdStateSortUrl}/${scopedId}/${scene}`, data });
}
// 更新工作流流转状态
export function updateOrdWorkStateFlow(data: UpdateWorkFlowSetting) {
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 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 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 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>;
props?: Record<string, any>;
}
interface FomItemSelect extends FormItemComplexCommonConfig {

View File

@ -52,6 +52,11 @@
useEditor,
} from '@halo-dev/richtext-editor';
const props = defineProps<{
modelValue: string;
}>();
const emit = defineEmits(['update:model-value']);
const content = useLocalStorage('content', '');
const editor = useEditor({
@ -132,6 +137,22 @@
// const locale = useLocalStorage('locale', 'zh-CN');
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>
<template>

View File

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

View File

@ -165,11 +165,39 @@ export const pathMap: PathMapItem[] = [
},
{
key: 'SETTING_ORGANIZATION_TEMPLATE', // 系统设置-组织-模版
locale: 'menu.settings.organization.serviceIntegration',
locale: 'menu.settings.organization.template',
route: RouteEnum.SETTING_ORGANIZATION_TEMPLATE,
permission: [],
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: [],
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', // 项目管理-项目与权限-项目版本
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', // 项目管理-文件管理
locale: 'menu.projectManagement.fileManagement',

View File

@ -24,7 +24,11 @@ export enum ProjectManagementRouteEnum {
PROJECT_MANAGEMENT_PERMISSION = 'projectManagementPermission',
PROJECT_MANAGEMENT_PERMISSION_BASIC_INFO = 'projectManagementPermissionBasicInfo',
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_USER_GROUP = 'projectManagementPermissionUserGroup',
PROJECT_MANAGEMENT_PERMISSION_MEMBER = 'projectManagementPermissionMember',

View File

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

View File

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

View File

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

View File

@ -53,11 +53,12 @@ export default {
'menu.settings.organization.userGroup': 'User Group',
'menu.settings.organization.project': 'Project',
'menu.settings.organization.template': 'Template',
'menu.settings.organization.bugTemplate': 'BUG Template',
'menu.settings.organization.templateFieldSetting': 'fieldSetting',
'menu.settings.organization.templateManagementList': 'Template list',
'menu.settings.organization.templateManagementEdit': 'Update 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.log': 'Log',
'navbar.action.locale': 'Switch to English',

View File

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

View File

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

View File

@ -47,16 +47,6 @@ const ProjectManagement: AppRouteRecordRaw = {
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',
@ -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',

View File

@ -192,7 +192,7 @@ const Setting: AppRouteRecordRaw = {
{
path: 'templateFiledSetting',
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: {
locale: 'menu.settings.organization.templateFieldSetting',
roles: ['*'],
@ -259,14 +259,14 @@ const Setting: AppRouteRecordRaw = {
{
path: 'templateWorkFlow',
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: {
locale: 'menu.settings.organization.templateManagementWorkFlow',
roles: ['*'],
breadcrumbs: [
{
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE,
locale: 'menu.settings.organization.template',
locale: 'menu.settings.organization.bugTemplate',
},
{
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_WORKFLOW,

View File

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

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia';
import { isEnableTemplate } from '@/api/modules/setting/template';
import { getOrdTemplate, getProTemplate } from '@/api/modules/setting/template';
import useAppStore from '../app';
@ -9,40 +9,36 @@ const appStore = useAppStore();
const useTemplateStore = defineStore('template', {
persist: true,
state: (): {
templateStatus: Record<string, boolean>;
ordStatus: Record<string, boolean>;
projectStatus: Record<string, boolean>;
} => ({
templateStatus: {
FUNCTIONAL: true,
API: true,
UI: true,
TEST_PLAN: true,
BUG: true,
ordStatus: {
FUNCTIONAL: false,
API: false,
UI: false,
TEST_PLAN: false,
BUG: false,
},
projectStatus: {
FUNCTIONAL: false,
API: false,
UI: false,
TEST_PLAN: false,
BUG: false,
},
}),
actions: {
// 模板列表的状态
async getStatus() {
const currentOrgId = computed(() => appStore.currentOrgId);
const currentProjectId = computed(() => appStore.currentProjectId);
try {
this.templateStatus = await isEnableTemplate(appStore.currentOrgId);
this.ordStatus = await getOrdTemplate(currentOrgId.value);
this.projectStatus = await getProTemplate(currentProjectId.value);
} catch (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,
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_MENU_MANAGEMENT,
},
{
key: 'templateManager',
title: 'project.permission.templateManager',
level: 2,
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_TEMPLATE,
},
{
key: 'projectVersion',
title: 'project.permission.projectVersion',

View File

@ -58,7 +58,7 @@
{{ t('organization.member.statusDisable') }}
</div>
</template>
<template #action="{ record }">
<template #operation="{ record }">
<MsRemoveButton
position="br"
:title="t('project.member.deleteMemberTip', { name: characterLimit(record.name) })"
@ -108,7 +108,7 @@
} from '@/api/modules/project-management/projectMember';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import { useAppStore } from '@/store';
import { useAppStore, useTableStore } from '@/store';
import { characterLimit } from '@/utils';
import type {
@ -122,7 +122,7 @@
const { t } = useI18n();
const { openModal } = useModal();
const appStore = useAppStore();
const tableStore = useTableStore();
const lastProjectId = computed(() => appStore.getCurrentProjectId);
const columns: MsTableColumn = [
@ -131,18 +131,22 @@
dataIndex: 'name',
showInTable: true,
showTooltip: true,
fixed: 'left',
sortIndex: 0,
ellipsis: true,
showDrag: false,
},
{
title: 'project.member.tableColumnEmail',
dataIndex: 'email',
showInTable: true,
showTooltip: true,
showDrag: true,
},
{
title: 'project.member.tableColumnPhone',
dataIndex: 'phone',
showInTable: true,
showDrag: true,
width: 150,
},
{
@ -150,6 +154,7 @@
slotName: 'userRole',
dataIndex: 'userRoleIdNameMap',
showInTable: true,
showDrag: true,
width: 300,
},
{
@ -161,10 +166,12 @@
},
{
title: 'project.member.tableColumnActions',
slotName: 'action',
slotName: 'operation',
fixed: 'right',
dataIndex: 'operation',
width: 100,
showInTable: true,
showDrag: false,
},
];
@ -368,6 +375,7 @@
initData();
initOptions();
});
tableStore.initColumn(TableKeyEnum.PROJECT_MEMBER, columns, 'drawer');
</script>
<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-select>
<span v-if="(record.selectProjectList || []).length === 0">-</span>
</template>
<template #userRole="{ record }">
<MsTagGroup
@ -64,6 +65,7 @@
>
<a-option v-for="item of userGroupOptions" :key="item.id" :value="item.id">{{ item.name }}</a-option>
</a-select>
<span v-if="(record.selectUserList || []).length === 0">-</span>
</template>
<template #enable="{ record }">
<div v-if="record.enable" class="flex items-center">

View File

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

View File

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

View File

@ -42,31 +42,42 @@
</template>
<script setup lang="ts">
/**
* @description 系统管理-组织-模板管理-工作流-创建工作流状态
*/
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { FormInstance, Message } from '@arco-design/web-vue';
import MsDialog from '@/components/pure/ms-dialog/index.vue';
import { createWorkFlowStatus, updateWorkFlowStatus } from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import type { OrdWorkStatus } from '@/models/setting/template';
import { getWorkFlowRequestApi } from '@/views/setting/organization/template/components/fieldSetting';
const appStore = useAppStore();
const currentOrgId = computed(() => appStore.currentOrgId);
const currentProjectId = computed(() => appStore.currentProjectId);
const route = useRoute();
const { t } = useI18n();
const props = defineProps<{
mode: 'organization' | 'project';
}>();
const emits = defineEmits<{
(e: 'update:visible', visible: boolean): void;
(e: 'success'): void;
}>();
const visible = ref<boolean>(false);
const initFormValue: OrdWorkStatus = {
scopeId: currentOrgId.value,
scopeId: '',
id: '',
name: '',
scene: route.query.type,
@ -87,11 +98,14 @@
const isEdit = computed(() => !!form.value.id);
//
const createWorkFlowStatus = getWorkFlowRequestApi(props.mode).create;
const updateWorkFlowStatus = getWorkFlowRequestApi(props.mode).update;
const confirmHandler = async (enable: boolean | undefined) => {
await formRef.value?.validate().then(async (error) => {
if (!error) {
try {
const scopeId = props.mode === 'organization' ? currentOrgId.value : currentProjectId.value;
form.value.scopeId = scopeId;
if (!form.value.id) {
form.value.allTransferTo = enable as boolean;
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')"
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
@ -115,6 +115,9 @@
</template>
<script setup lang="ts">
/**
* @description 模板管理-自定义字段-添加自定义字段&编辑自定义字段
*/
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
@ -125,22 +128,22 @@
import MsBatchForm from '@/components/business/ms-batch-form/index.vue';
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 { useAppStore } from '@/store';
import { getGenerateId } from '@/utils';
import type { AddOrUpdateField, fieldIconAndNameModal } from '@/models/setting/template';
import { fieldIconAndName, getFieldType } from './fieldSetting';
import { fieldIconAndName, getFieldRequestApi, getFieldType } from './fieldSetting';
const { t } = useI18n();
const route = useRoute();
const appStore = useAppStore();
const currentOrgId = computed(() => appStore.currentOrgId);
const sceneType = route.query.type;
const props = defineProps<{
visible: boolean;
mode: 'organization' | 'project';
}>();
const emit = defineEmits(['success', 'update:visible']);
@ -148,11 +151,17 @@
const drawerLoading = ref<boolean>(false);
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 = {
name: '',
type: undefined,
remark: '',
scopeId: '',
scopeId: scopeId.value,
scene: 'FUNCTIONAL',
options: [],
enableOptionKey: false,
@ -232,6 +241,7 @@
resetForm();
};
const { addOrUpdate, detail } = getFieldRequestApi(props.mode);
//
const confirmHandler = async (isContinue: boolean) => {
try {
@ -240,7 +250,7 @@
const formCopy = cloneDeep(fieldForm.value);
formCopy.scene = route.query.type;
formCopy.scopeId = currentOrgId.value;
formCopy.scopeId = scopeId.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 = {
name,
options,
scopeId,
scopeId: scopeId.value,
scene,
type,
remark,
@ -272,7 +282,7 @@
if (id) {
params.id = id;
}
await addOrUpdateOrdField(params);
await addOrUpdate(params);
Message.success(isEdit.value ? t('common.updateSuccess') : t('common.addSuccess'));
if (!isContinue) {
handleDrawerCancel();
@ -310,7 +320,7 @@
//
const getFieldDetail = async (id: string) => {
try {
const fieldDetail = await getOrdFieldDetail(id);
const fieldDetail = await detail(id);
fieldDefaultValues.value = fieldDetail.options.map((item: any) => {
return {
...item,
@ -336,6 +346,7 @@
selectFormat.value = itemType;
return 'DATE';
default:
selectFormat.value = itemType;
return itemType;
}
};

View File

@ -1,11 +1,37 @@
import { cloneDeep } from 'lodash-es';
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 {
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 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';
const { t } = useI18n();
@ -47,49 +73,12 @@ export const getFieldType = (selectFieldType: FormItemType) => {
}
};
const organizationState = computed(() => templateStore.getOrdTemplateState());
const projectState = computed(() => templateStore.getProjectTemplateState());
// 模板列表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,
},
];
const organizationState = computed(() => templateStore.ordStatus);
const projectState = computed(() => templateStore.projectStatus);
// 模板列表Icon
export function getCardList(type: string): Record<string, any>[] {
const dataList = [
const dataList = ref([
{
id: 1001,
key: 'FUNCTIONAL',
@ -120,16 +109,17 @@ export function getCardList(type: string): Record<string, any>[] {
value: TemplateCardEnum.BUG,
name: t('system.orgTemplate.defectTemplates'),
},
];
]);
if (type === 'organization') {
return dataList.map((item) => {
return dataList.value.map((item) => {
return {
...item,
enable: organizationState.value[item.key],
};
});
}
return dataList.map((item) => {
return dataList.value.map((item) => {
return {
...item,
enable: projectState.value[item.key],
@ -221,4 +211,102 @@ export const getIconType = (iconType: FormItemType) => {
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 {};

View File

@ -1,10 +1,12 @@
<template>
<MsCard :has-breadcrumb="true" simple>
<a-alert class="mb-6" :type="isEnable ? 'warning' : 'info'">{{
isEnable ? t('system.orgTemplate.enableDescription') : t('system.orgTemplate.fieldLimit')
<div>
<a-alert class="mb-6" :type="isEnabledTemplate && props.mode === 'organization' ? 'warning' : 'info'">{{
isEnabledTemplate && props.mode === 'organization'
? t('system.orgTemplate.enableDescription')
: t('system.orgTemplate.fieldLimit')
}}</a-alert>
<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">
{{ t('system.orgTemplate.addField') }}
</a-button>
@ -19,12 +21,28 @@
</div>
<MsBaseTable v-bind="propsRes" ref="tableRef" v-on="propsEvent">
<template #name="{ record }">
<MsIcon v-if="!record.internal" :type="getIconType(record.type)?.iconName || ''" size="16" />
<span class="ml-2">{{ record.name }}</span>
<span v-if="record.internal" class="system-flag">{{ t('system.orgTemplate.isSystem') }}</span>
<MsIcon
v-if="!record.internal"
: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 #operation="{ record }">
<div class="flex flex-row flex-nowrap">
<div class="flex flex-row flex-nowrap items-center">
<MsPopConfirm
type="error"
:title="t('system.orgTemplate.updateTip', { name: characterLimit(record.name) })"
@ -35,7 +53,7 @@
<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
v-if="!record.internal"
:list="moreActions"
@ -47,29 +65,75 @@
<span>{{ getIconType(record.type)?.label }}</span>
</template>
</MsBaseTable>
<EditFieldDrawer ref="fieldDrawerRef" v-model:visible="showDrawer" @success="successHandler" />
</MsCard>
<EditFieldDrawer ref="fieldDrawerRef" v-model:visible="showDrawer" :mode="props.mode" @success="successHandler" />
<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>
<script setup lang="ts">
/**
* @description 系统管理-组织-模版-字段列表
* @description 模版-字段列表
*/
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import dayjs from 'dayjs';
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 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 EditFieldDrawer from './editFieldDrawer.vue';
import { deleteOrdField, getFieldList } from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import { useAppStore, useTableStore } from '@/store';
@ -79,7 +143,7 @@
import type { AddOrUpdateField, SeneType } from '@/models/setting/template';
import { TableKeyEnum } from '@/enums/tableEnum';
import { getCardList, getIconType } from './fieldSetting';
import { getFieldRequestApi, getIconType } from './fieldSetting';
const templateStore = useTemplateStore();
@ -89,7 +153,12 @@
const route = useRoute();
const { openModal } = useModal();
const props = defineProps<{
mode: 'organization' | 'project';
}>();
const currentOrd = computed(() => appStore.currentOrgId);
const currentProjectId = computed(() => appStore.currentProjectId);
const fieldColumns: MsTableColumn = [
{
@ -97,7 +166,6 @@
slotName: 'name',
dataIndex: 'name',
width: 300,
showDrag: true,
showInTable: true,
showTooltip: true,
},
@ -105,64 +173,67 @@
title: 'system.orgTemplate.columnFieldType',
dataIndex: 'type',
slotName: 'fieldType',
showDrag: true,
showInTable: true,
},
{
title: 'system.orgTemplate.columnFieldDescription',
dataIndex: 'remark',
showDrag: true,
showInTable: 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,
},
];
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,
scroll: { x: '1000px' },
selectable: false,
noDisable: true,
size: 'default',
showSetting: true,
showSetting: false,
showPagination: false,
heightUsed: 380,
});
const keyword = 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 () => {
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));
setProps({ data: filterData });
} catch (error) {
console.log(error);
}
};
const scene = ref<SeneType>(route.query.type);
//
const fetchData = async () => {
scene.value = route.query.type;
setLoadListParams({ organizationId: currentOrd.value, scene });
setLoadListParams(getParams());
await loadList();
totalData.value = await getFieldList({ organizationId: currentOrd.value, scene: route.query.type });
totalData.value = await getList(getParams());
};
const isDisabled = computed(() => {
@ -170,13 +241,16 @@
});
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 = () => {
if (isEnable.value) {
if (isEnabledTemplate.value) {
const noOperationColumn = fieldColumns.slice(0, -1);
tableRef.value.initColumn(noOperationColumn);
} else {
@ -192,6 +266,7 @@
},
];
const deleteApi = getFieldRequestApi(props.mode).delete;
//
const handlerDelete = (record: AddOrUpdateField) => {
openModal({
@ -205,7 +280,7 @@
},
onBeforeOk: async () => {
try {
if (record.id) await deleteOrdField(record.id);
if (record.id) await deleteApi(record.id);
Message.success(t('system.orgTemplate.deleteSuccess'));
fetchData();
} 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 fieldDrawerRef = ref();
@ -238,26 +332,26 @@
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(() => {
updateBreadcrumbList();
isEnableOperation();
fetchData();
});
await tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_FIELD_SETTING, fieldColumns, 'drawer');
</script>
<style scoped lang="less">
.system-flag {
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>

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">
<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
v-model:model-value="templateForm.name"
:placeholder="t('system.orgTemplate.templateNamePlaceholder')"
@ -40,21 +45,68 @@
class="max-w-[732px]"
></a-textarea>
</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-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="organization"
@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>
<!-- 预览模式 -->
<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>
</template>
@ -64,11 +116,11 @@
*/
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import { FormInstance, Message } from '@arco-design/web-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 PreviewTemplate from './viewTemplate.vue';
@ -87,14 +139,16 @@
import type { ActionTemplateManage, CustomField, DefinedFieldItem } from '@/models/setting/template';
import { SettingRouteEnum } from '@/enums/routeEnum';
import { getCardList } from './fieldSetting';
import { getCardList, getCustomDetailFields, getTotalFieldOptionList } from './fieldSetting';
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const appStore = useAppStore();
const currentOrgId = computed(() => appStore.currentOrgId);
// useLeaveUnSaveTip();
const { setState } = useLeaveUnSaveTip();
setState(false);
const title = ref('');
const loading = ref(false);
@ -107,6 +161,8 @@
enableThirdPart: false,
};
const fieldType = ref<string>('custom'); //
const templateForm = ref<ActionTemplateManage>({ ...initTemplateForm });
const selectData = ref<DefinedFieldItem[]>([]); //
@ -115,15 +171,15 @@
const formRef = ref<FormInstance>();
const totalTemplateField = ref<DefinedFieldItem[]>([]);
const isEdit = computed(() => !!route.query.id);
const currentOrd = currentOrgId.value;
const isEditField = ref<boolean>(false);
const systemFieldData = ref<CustomField[]>([]);
//
const getTemplateInfo = async () => {
try {
loading.value = true;
const res = await getOrganizeTemplateInfo(route.query.id as string);
const { name, customFields } = res;
const { name, customFields, systemFields } = res;
templateForm.value = {
...res,
name: route.params.mode === 'copy' ? `${name}_copy` : name,
@ -131,27 +187,8 @@
if (route.params.mode === 'copy') {
templateForm.value.id = undefined;
}
//
const customFieldsIds = customFields.map((index: any) => index.fieldId);
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;
selectData.value = getCustomDetailFields(totalTemplateField.value as DefinedFieldItem[], customFields);
systemFieldData.value = systemFields;
} catch (error) {
console.log(error);
} finally {
@ -161,27 +198,7 @@
//
const getFieldOptionList = () => {
totalTemplateField.value = totalTemplateField.value.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,
};
});
totalTemplateField.value = getTotalFieldOptionList(totalTemplateField.value as DefinedFieldItem[]);
//
if (!isEdit.value && !isEditField.value) {
selectData.value = totalTemplateField.value.filter((item) => item.internal);
@ -191,9 +208,9 @@
//
const getClassifyField = async () => {
try {
totalTemplateField.value = await getFieldList({ organizationId: currentOrd, scene: route.query.type });
totalTemplateField.value = await getFieldList({ scopedId: currentOrgId.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
@ -219,6 +236,8 @@
}
});
const defectForm = ref<Record<string, any>>({}); //
//
function getTemplateParams(): ActionTemplateManage {
const result = selectData.value.map((item) => {
@ -227,13 +246,20 @@
return {
fieldId: item.id,
required: item.required,
apiFieldId: item.apiFieldId,
defaultValue: value,
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,
@ -243,6 +269,7 @@
customFields: result as CustomField[],
scopeId: currentOrgId.value,
scene: route.query.type,
systemFields: sysDetailFields,
};
}
@ -269,6 +296,7 @@
} else {
await sleep(300);
router.push({ name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT, query: route.query });
setState(true);
}
} catch (error) {
console.log(error);
@ -323,6 +351,19 @@
getClassifyField();
};
//
watch(
() => systemFieldData.value,
(val) => {
if (val) {
systemFieldData.value.forEach((item) => {
defectForm.value[item.fieldId] = item.defaultValue;
});
}
},
{ deep: true }
);
onMounted(() => {
setBreadText();
getClassifyField();

View File

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

View File

@ -1,8 +1,10 @@
<template>
<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">
<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">
{{ t('system.orgTemplate.createTemplate') }}
</a-button>
@ -18,13 +20,16 @@
<MsBaseTable v-bind="propsRes" ref="tableRef" v-on="propsEvent">
<template #name="{ record }">
<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 #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" />
<a-divider v-if="!record.internal" class="h-[12px]" direction="vertical" />
<MsTableMoreAction
v-if="!record.internal"
:list="moreActions"
@ -51,6 +56,7 @@
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 { deleteOrdTemplate, getOrganizeTemplateList } from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n';
@ -84,12 +90,15 @@
width: 300,
showDrag: true,
showInTable: true,
showTooltip: true,
},
{
title: 'system.orgTemplate.description',
dataIndex: 'remark',
showDrag: true,
showInTable: true,
width: 300,
showTooltip: true,
},
{
title: 'system.orgTemplate.columnFieldUpdatedTime',
@ -100,6 +109,7 @@
{
title: 'system.orgTemplate.operation',
slotName: 'operation',
dataIndex: 'operation',
fixed: 'right',
width: 200,
showInTable: true,
@ -107,10 +117,9 @@
},
];
await tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT, fieldColumns, 'drawer');
const { propsRes, propsEvent, loadList, setLoadListParams, setProps } = useTable(getOrganizeTemplateList, {
tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT,
scroll: { x: '1000px' },
scroll: { x: '100%' },
selectable: false,
noDisable: true,
size: 'default',
@ -119,9 +128,13 @@
heightUsed: 380,
});
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 searchFiled = async () => {
try {
@ -140,6 +153,7 @@
eventTag: 'delete',
},
];
//
const handlerDelete = (record: any) => {
openModal({
@ -202,6 +216,7 @@
},
});
};
//
const copyTemplate = (id: string) => {
router.push({
@ -228,12 +243,24 @@
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() {
if (isEnable) {
const result = fieldColumns.slice(0, fieldColumns.length - 1);
const columns =
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);
} else {
tableRef.value.initColumn(fieldColumns);
tableRef.value.initColumn(columns);
}
}
@ -242,6 +269,7 @@
fetchData();
updateColumns();
});
tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT, fieldColumns, 'drawer');
</script>
<style scoped lang="less">

View File

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

View File

@ -1,220 +1,61 @@
<template>
<div class="wrapper-preview">
<div class="preview-left pr-4">
<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
></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>
<DefectTemplateLeftContent v-if="props.templateType === 'BUG'" :defect-form="props.defectForm" />
<CaseTemplateLeftContent v-else />
</div>
<div class="preview-right px-4">
<MsFormCreate
v-if="formRules.length"
ref="formCreateRef"
:form-rule="formRules"
:form-create-key="FormCreateKeyEnum.ORGANIZE_TEMPLATE_PREVIEW_TEMPLATE"
/>
<a-empty v-else />
</div>
</div>
</template>
<script setup lang="ts">
/**
* @description 模板-创建模板&编辑模板-预览模板
*/
import { ref } from 'vue';
import MsFormCreate from '@/components/pure/ms-form-create/form-create.vue';
import type { FormItem } from '@/components/pure/ms-form-create/types';
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 CaseTemplateLeftContent from './caseTemplateLeftContent.vue';
import DefectTemplateLeftContent from './defectTemplateLeftContent.vue';
import { useI18n } from '@/hooks/useI18n';
import { useTableStore } from '@/store';
import type { DefinedFieldItem } from '@/models/setting/template';
import type { DefinedFieldItem, SeneType } from '@/models/setting/template';
import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
const { t } = useI18n();
const tableStore = useTableStore();
const props = defineProps<{
templateType: SeneType; //
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 formRules = ref<FormItem[]>([]);
const formCreateRef = ref();
//
const getFormRules = () => {
formRuleField.value = [];
formRules.value = [];
if (props.selectField && props.selectField.length) {
props.selectField.forEach((item: DefinedFieldItem) => {
const currentFormItem = item.formRules?.map((rule: any) => {
const optionsItem = rule.options.map((opt: any) => {
return {
text: opt.label,
value: opt.value,
};
});
let optionsItem = [];
if (rule.options && rule.options.length) {
optionsItem = rule.options.map((opt: any) => {
return {
text: opt.label,
value: opt.value,
};
});
}
return {
type: item.type,
name: rule.field,
@ -225,24 +66,28 @@
props: {
modelValue: rule.value,
options: optionsItem,
disabled: true,
},
};
});
formRuleField.value.push(currentFormItem as FormItem[]);
});
const result = formRuleField.value.flatMap((item) => item);
formRules.value = result;
formRules.value = formRuleField.value.flatMap((item) => item);
}
};
watchEffect(() => {
getFormRules();
});
onBeforeUnmount(() => {
formRules.value = [];
formRuleField.value = [];
});
defineExpose({
getFormRules,
});
onMounted(() => {
setProps({ data: [{ id: 1, showStep: false, showExpected: false }] });
getFormRules();
});
await tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_STEP, templateFieldColumns, 'drawer');
</script>
<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="{
...styleClass.wrapper,
_hover_Wrapper: isEnableProjectState || isNotAllowCreate ? false : true,
_pointer: !isNotAllowCreate,
_not_allowed: isNotAllowCreate,
_disabled_gray_bg: isEnableProjectState,
'_hover_Wrapper': isEnableProjectState || isNotAllowCreate ? false : true,
'_pointer': !isNotAllowCreate,
'cursor-not-allowed': isNotAllowCreate,
'_disabled_gray_bg': isEnableProjectState,
}"
>
<!-- 不允许状态流转 -->
@ -64,27 +64,27 @@
<script setup lang="ts">
/**
* @description 系统设置-组织-工作流table小卡片
* @description 工作流table小卡片
*/
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { Message, TableColumnData } from '@arco-design/web-vue';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import { updateOrdWorkStateFlow } from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useTemplateStore from '@/store/modules/setting/template';
import type { UpdateWorkFlowSetting, WorkFlowType } from '@/models/setting/template';
import { getWorkFlowRequestApi } from '@/views/setting/organization/template/components/fieldSetting';
const templateStore = useTemplateStore();
const { t } = useI18n();
const { openModal } = useModal();
const props = defineProps<{
mode: 'organization' | 'project';
stateItem: WorkFlowType;
columnItem: TableColumnData;
cellCoordinates: { rowId: string; columnId: string };
@ -134,8 +134,9 @@
//
const isEnableProjectState = computed(() => {
const projectState = templateStore.getProjectTemplateState();
return projectState[props.stateItem.scene];
return props.mode === 'project'
? !templateStore.projectStatus[props.stateItem.scene]
: !templateStore.ordStatus[props.stateItem.scene];
});
const title = computed(() => {
@ -227,6 +228,7 @@
const loading = ref<boolean>(false);
const updateOrdWorkStateFlow = getWorkFlowRequestApi(props.mode).updateFlow;
//
async function changeWorkFlow(type: string) {
try {
@ -307,9 +309,9 @@
}
// hover
._hover_Wrapper {
@apply hover:shadow-xl;
&:hover {
border-radius: 4px;
box-shadow: 0 4px 15px -1px rgba(100 100 102 / 15%);
}
}
// hover----

View File

@ -1,172 +1,204 @@
<template>
<MsCard has-breadcrumb simple>
<a-alert class="mb-6" type="warning">{{ t('system.orgTemplate.workFlowTip') }}</a-alert>
<div class="mb-4">
<div class="mb-4 flex items-center"
><a-button class="mr-2" type="outline" @click="addStatus">{{ t('system.orgTemplate.addState') }}</a-button>
<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-[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>
<a-alert v-if="isShowTip" class="mb-6" type="warning">
<div class="flex items-start justify-between">
<span class="w-[80%]">{{ t('system.orgTemplate.workFlowTip') }}</span>
<span class="cursor-pointer text-[var(--color-text-2)]" @click="noRemindHandler">{{
t('system.orgTemplate.noReminders')
}}</span>
</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>
<script setup lang="ts">
/**
* @description 模板-工作流table
*/
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { Message, TableColumnData, TableData } from '@arco-design/web-vue';
import { isEqual } from 'lodash-es';
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 MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import AddWorkStatusModal from './addWorkStatusModal.vue';
import WorkflowCard from './workflowCard.vue';
import AddWorkStatusModal from '@/views/setting/organization/template/components/addWorkStatusModal.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 useModal from '@/hooks/useModal';
import useVisit from '@/hooks/useVisit';
import { useAppStore } from '@/store';
import useTemplateStore from '@/store/modules/setting/template';
import { characterLimit } from '@/utils';
import type { SetStateType, WorkFlowType } from '@/models/setting/template';
import { getWorkFlowRequestApi } from '@/views/setting/organization/template/components/fieldSetting';
const { t } = useI18n();
const appStore = useAppStore();
const templateStore = useTemplateStore();
const props = defineProps<{
mode: 'organization' | 'project'; // ||
}>();
const currentOrgId = computed(() => appStore.currentOrgId);
const currentProjectId = computed(() => appStore.currentProjectId);
const { openModal } = useModal();
const route = useRoute();
@ -180,10 +212,11 @@
//
const isEnableProjectState = computed(() => {
const projectState = templateStore.getProjectTemplateState();
return projectState[route.query.type as string];
return props.mode === 'organization'
? 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[]>([]);
@ -212,12 +245,16 @@
const tableLoading = ref<boolean>(false);
const scopedId = computed(() => (props.mode === 'organization' ? currentOrgId.value : currentProjectId.value));
const getWorkList = getWorkFlowRequestApi(props.mode).list;
// table
async function getWorkFetchList() {
try {
tableLoading.value = true;
workData.value = await getWorkFlowList(currentOrgId.value, route.query.type);
workFlowColumns.value = workData.value.map((item, index) => {
workData.value = await getWorkList(scopedId.value, route.query.type);
workFlowColumns.value = workData.value.map((item) => {
const columns = {
title: item.name,
dataIndex: item.id,
@ -261,7 +298,7 @@
function addStatus() {
showModel.value = true;
}
const deleteState = getWorkFlowRequestApi(props.mode).delete;
//
function deleteHandler(record: WorkFlowType) {
if (record.statusDefinitions.join().includes('START')) {
@ -279,7 +316,7 @@
},
onBeforeOk: async () => {
try {
if (record.id) await deleteOrdWorkState(record.id);
if (record.id) await deleteState(record.id);
Message.success(t('system.orgTemplate.deleteSuccess'));
getWorkFetchList();
} catch (error) {
@ -290,6 +327,7 @@
});
}
const setInitAndEndState = getWorkFlowRequestApi(props.mode).changeState;
// ||
async function setState(record: WorkFlowType, type: string) {
const params: SetStateType = {
@ -298,7 +336,7 @@
enable: type === 'START' ? true : record.currentState,
};
try {
await setOrdWorkState(params);
await setInitAndEndState(params);
Message.success(
type === 'END' ? t('system.orgTemplate.setEndStateSuccess') : t('system.orgTemplate.setInitStateSuccess')
);
@ -314,15 +352,17 @@
setState(record, 'START');
}
}
const dragChangeRequest = getWorkFlowRequestApi(props.mode).dragChange;
//
async function handleChange(_data: TableData[]) {
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 isChange = isEqual(originIds, dataIds);
if (isChange) return {};
if (isChange) return false;
try {
await setOrdWorkStateSort(currentOrgId.value, route.query.type, dataIds);
await dragChangeRequest(scopedId.value, route.query.type, dataIds);
getWorkFetchList();
} catch (error) {
console.log(error);
@ -364,8 +404,24 @@
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(() => {
getWorkFetchList();
doCheckIsTip();
});
</script>

View File

@ -14,13 +14,21 @@
:card-min-width="360"
class="flex-1"
:shadow-limit="50"
:list="getCardList('organization')"
:list="cardList"
:is-proportional="false"
:gap="16"
padding-bottom-space="16px"
>
<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>
</MsCardList>
</div>
@ -31,7 +39,7 @@
/**
* @description 系统设置--组织--模版
*/
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import MsCard from '@/components/pure/ms-card/index.vue';
import MsCardList from '@/components/business/ms-card-list/index.vue';
@ -41,16 +49,16 @@
import useVisit from '@/hooks/useVisit';
import useTemplateStore from '@/store/modules/setting/template';
import { SettingRouteEnum } from '@/enums/routeEnum';
import { getCardList } from './components/fieldSetting';
const templateStore = useTemplateStore();
const { t } = useI18n();
const router = useRouter();
const visitedKey = 'notRemind';
const { addVisited } = useVisit(visitedKey);
const { getIsVisited } = useVisit(visitedKey);
const isShowTip = ref<boolean>(true);
const noRemindHandler = () => {
isShowTip.value = false;
@ -65,8 +73,46 @@
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();
updateState();
});
</script>

View File

@ -18,6 +18,7 @@ export default {
'system.orgTemplate.testPlanTemplates': 'TestPlan template',
'system.orgTemplate.defectTemplates': 'Defect template',
'system.orgTemplate.enabledTemplates': 'enabled',
'system.orgTemplate.disabledTemplates': 'disabled',
'system.orgTemplate.fieldSetting': 'Setting',
'system.orgTemplate.TemplateManagement': 'Management',
'system.orgTemplate.workflowSetup': 'Workflow',
@ -86,6 +87,7 @@ export default {
'system.orgTemplate.addStep': 'Add Step',
'system.orgTemplate.caseName': '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.stepDescription': 'Step description',
'system.orgTemplate.changeType': 'Change type',
@ -146,4 +148,20 @@ export default {
'system.orgTemplate.stateTip':
'Open, the existing state will transfer to the state, only in the state of new Settings',
'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.defectTemplates': '缺陷模版',
'system.orgTemplate.enabledTemplates': '已启用项目模版',
'system.orgTemplate.disabledTemplates': '未启用项目模版',
'system.orgTemplate.fieldSetting': '字段设置',
'system.orgTemplate.TemplateManagement': '模版管理',
'system.orgTemplate.workflowSetup': '工作流设置',
@ -88,6 +89,7 @@ export default {
'system.orgTemplate.addStep': '添加步骤',
'system.orgTemplate.caseName': '用例名称',
'system.orgTemplate.caseNamePlaceholder': '请输入用例名称',
'system.orgTemplate.defectNamePlaceholder': '请输入缺陷名称',
'system.orgTemplate.precondition': '前置条件',
'system.orgTemplate.stepDescription': '步骤描述',
'system.orgTemplate.changeType': '更改类型',
@ -140,4 +142,18 @@ export default {
'system.orgTemplate.details': '详情',
'system.orgTemplate.stateTip': '开启: 已有的状态会流转到该状态,仅在新建状态时设置',
'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': '请输入模板名称',
};