refactor(系统设置): 重构系统设置模板&项目模板

This commit is contained in:
xinxin.wu 2024-02-27 13:33:21 +08:00 committed by 刘瑞斌
parent f21064cb3e
commit 53226e689e
31 changed files with 1097 additions and 1191 deletions

View File

@ -28,7 +28,10 @@ export function getCommonScriptPage(data: TableQueryParams) {
} }
// 添加公共脚本 // 添加公共脚本
export function addCommonScriptReq(data: AddOrUpdateCommonScript) { export function addOrUpdateCommonScriptReq(data: AddOrUpdateCommonScript) {
if (data.id) {
return MSR.post({ url: UpdateCommonScriptUrl, data });
}
return MSR.post({ url: AddCommonScriptUrl, data }); return MSR.post({ url: AddCommonScriptUrl, data });
} }
// 更新公共脚本 // 更新公共脚本

View File

@ -58,6 +58,15 @@
</template> </template>
</ms-base-table> </ms-base-table>
</MsDrawer> </MsDrawer>
<AddScriptDrawer
v-model:visible="showScriptDrawer"
v-model:params="paramsList"
:confirm-loading="confirmLoading"
:script-id="isEditId"
ok-text="project.commonScript.apply"
:enable-radio-selected="radioSelected"
@save="saveHandler"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -71,14 +80,20 @@
import useTable from '@/components/pure/ms-table/useTable'; import useTable from '@/components/pure/ms-table/useTable';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue'; import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import { getInsertCommonScriptPage } from '@/api/modules/project-management/commonScript'; import { addOrUpdateCommonScriptReq, getInsertCommonScriptPage } from '@/api/modules/project-management/commonScript';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import type { AddOrUpdateCommonScript, ParamsRequestType } from '@/models/projectManagement/commonScript';
import Message from '@arco-design/web-vue/es/message';
import debounce from 'lodash-es/debounce'; import debounce from 'lodash-es/debounce';
const appStore = useAppStore(); const appStore = useAppStore();
const currentProjectId = computed(() => appStore.currentProjectId); const currentProjectId = computed(() => appStore.currentProjectId);
const AddScriptDrawer = defineAsyncComponent(
() => import('@/components/business/ms-common-script/ms-addScriptDrawer.vue')
);
const { t } = useI18n(); const { t } = useI18n();
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -93,7 +108,7 @@
} }
); );
const emit = defineEmits(['update:visible', 'update:checkedId', 'save', 'addScript']); const emit = defineEmits(['update:visible', 'update:checkedId', 'save']);
const insertScriptDrawer = computed({ const insertScriptDrawer = computed({
get() { get() {
return props.visible; return props.visible;
@ -229,9 +244,41 @@
function handleDrawerCancel() { function handleDrawerCancel() {
insertScriptDrawer.value = false; insertScriptDrawer.value = false;
} }
const showScriptDrawer = ref<boolean>(false);
function addCommonScript() { function addCommonScript() {
emit('addScript'); showScriptDrawer.value = true;
}
const paramsList = ref<ParamsRequestType[]>([]);
const confirmLoading = ref<boolean>(false);
const isEditId = ref<string>('');
const radioSelected = ref<boolean>(false);
//
async function saveHandler(form: AddOrUpdateCommonScript) {
try {
confirmLoading.value = true;
const { status } = form;
const paramTableList = paramsList.value.slice(0, -1);
const paramsObj: AddOrUpdateCommonScript = {
...form,
status: status || 'DRAFT',
projectId: currentProjectId.value,
params: JSON.stringify(paramTableList),
};
await addOrUpdateCommonScriptReq(paramsObj);
showScriptDrawer.value = false;
initData();
Message.success(
form.status === 'DRAFT'
? t('project.commonScript.saveDraftSuccessfully')
: t('project.commonScript.appliedSuccessfully')
);
} catch (error) {
console.log(error);
} finally {
confirmLoading.value = false;
}
} }
watch( watch(

View File

@ -1,7 +1,7 @@
<template> <template>
<MsDrawer <MsDrawer
v-model:visible="showScriptDrawer" v-model:visible="showScriptDrawer"
:title="t('project.commonScript.addPublicScript')" :title="form.id ? t('project.commonScript.editPublicScript') : t('project.commonScript.addPublicScript')"
:width="768" :width="768"
:footer="true" :footer="true"
unmount-on-close unmount-on-close
@ -43,6 +43,7 @@
:scroll="{ x: '100%' }" :scroll="{ x: '100%' }"
:columns="columns" :columns="columns"
:height-used="heightUsed" :height-used="heightUsed"
:selectable="false"
@change="handleParamTableChange" @change="handleParamTableChange"
/> />
</a-form-item> </a-form-item>
@ -218,12 +219,23 @@
} }
); );
// watchEffect(() => {
// editScriptId.value = props.scriptId;
// if (editScriptId.value) {
// getDetail();
// }
// });
watch( watch(
() => showScriptDrawer.value, () => showScriptDrawer.value,
(val) => { (val) => {
if (val) { if (val) {
form.value = { ...initForm }; form.value = { ...initForm };
innerParams.value = []; innerParams.value = [];
editScriptId.value = props.scriptId;
if (editScriptId.value) {
getDetail();
}
} }
} }
); );
@ -311,6 +323,10 @@
} }
} }
); );
// onBeforeUnmount(() => {
// editScriptId.value = '';
// });
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -61,7 +61,6 @@
v-model:visible="showInsertDrawer" v-model:visible="showInsertDrawer"
:script-language="innerLanguagesType" :script-language="innerLanguagesType"
:enable-radio-selected="props.enableRadioSelected" :enable-radio-selected="props.enableRadioSelected"
@add-script="insertCommonScript"
@save="saveHandler" @save="saveHandler"
/> />
<FormApiImportDrawer <FormApiImportDrawer
@ -168,6 +167,7 @@ ${item.script}
`; `;
}); });
codeEditorRef.value?.insertContent(scriptStr); codeEditorRef.value?.insertContent(scriptStr);
showInsertDrawer.value = false;
} }
} }

View File

@ -150,7 +150,7 @@ export const TEXTAREA = {
props: { props: {
'placeholder': t('formCreate.PleaseEnter'), 'placeholder': t('formCreate.PleaseEnter'),
'max-length': 1000, 'max-length': 1000,
'auto-size': '{ minRows: 1 }', 'auto-size': { minRows: 1 },
}, },
}; };
export const JIRAKEY = { export const JIRAKEY = {

View File

@ -73,7 +73,8 @@
emits('reload'); emits('reload');
} }
function handleChange() { function handleChange(value: any) {
formApi.value?.validateField(value);
emits('change'); emits('change');
} }
</script> </script>

View File

@ -480,7 +480,7 @@ export const pathMap: PathMapItem[] = [
locale: 'menu.projectManagement.templateManager', locale: 'menu.projectManagement.templateManager',
route: RouteEnum.PROJECT_MANAGEMENT_TEMPLATE, route: RouteEnum.PROJECT_MANAGEMENT_TEMPLATE,
permission: [], permission: [],
level: MENU_LEVEL[1], level: MENU_LEVEL[2],
children: [ children: [
{ {
key: 'PROJECT_MANAGEMENT_TEMPLATE_FUNCTIONAL', // 模板管理-用例模板 key: 'PROJECT_MANAGEMENT_TEMPLATE_FUNCTIONAL', // 模板管理-用例模板
@ -490,7 +490,7 @@ export const pathMap: PathMapItem[] = [
routeQuery: { routeQuery: {
type: 'FUNCTIONAL', type: 'FUNCTIONAL',
}, },
level: MENU_LEVEL[1], level: MENU_LEVEL[2],
children: [ children: [
{ {
key: 'PROJECT_MANAGEMENT_TEMPLATE_FUNCTIONAL_FIELD', // 模板管理-用例模板-用例模板字段管理 key: 'PROJECT_MANAGEMENT_TEMPLATE_FUNCTIONAL_FIELD', // 模板管理-用例模板-用例模板字段管理
@ -500,7 +500,7 @@ export const pathMap: PathMapItem[] = [
routeQuery: { routeQuery: {
type: 'FUNCTIONAL', type: 'FUNCTIONAL',
}, },
level: MENU_LEVEL[1], level: MENU_LEVEL[2],
}, },
{ {
key: 'PROJECT_MANAGEMENT_TEMPLATE_FUNCTIONAL_TEMPLATE', // 模板管理-用例模板-用例模板管理 key: 'PROJECT_MANAGEMENT_TEMPLATE_FUNCTIONAL_TEMPLATE', // 模板管理-用例模板-用例模板管理
@ -510,7 +510,7 @@ export const pathMap: PathMapItem[] = [
routeQuery: { routeQuery: {
type: 'FUNCTIONAL', type: 'FUNCTIONAL',
}, },
level: MENU_LEVEL[1], level: MENU_LEVEL[2],
}, },
], ],
}, },
@ -522,7 +522,7 @@ export const pathMap: PathMapItem[] = [
routeQuery: { routeQuery: {
type: 'API', type: 'API',
}, },
level: MENU_LEVEL[1], level: MENU_LEVEL[2],
children: [ children: [
{ {
key: 'PROJECT_MANAGEMENT_TEMPLATE_API_FIELD', // 模板管理-接口模板-接口模板字段管理 key: 'PROJECT_MANAGEMENT_TEMPLATE_API_FIELD', // 模板管理-接口模板-接口模板字段管理
@ -532,7 +532,7 @@ export const pathMap: PathMapItem[] = [
routeQuery: { routeQuery: {
type: 'API', type: 'API',
}, },
level: MENU_LEVEL[1], level: MENU_LEVEL[2],
}, },
{ {
key: 'PROJECT_MANAGEMENT_TEMPLATE_API_TEMPLATE', // 模板管理-接口模板-接口模板管理 key: 'PROJECT_MANAGEMENT_TEMPLATE_API_TEMPLATE', // 模板管理-接口模板-接口模板管理
@ -542,7 +542,7 @@ export const pathMap: PathMapItem[] = [
routeQuery: { routeQuery: {
type: 'API', type: 'API',
}, },
level: MENU_LEVEL[1], level: MENU_LEVEL[2],
}, },
], ],
}, },
@ -554,7 +554,7 @@ export const pathMap: PathMapItem[] = [
routeQuery: { routeQuery: {
type: 'BUG', type: 'BUG',
}, },
level: MENU_LEVEL[1], level: MENU_LEVEL[2],
children: [ children: [
{ {
key: 'PROJECT_MANAGEMENT_TEMPLATE_BUG_FIELD', // 模板管理-缺陷模板管理-字段管理 key: 'PROJECT_MANAGEMENT_TEMPLATE_BUG_FIELD', // 模板管理-缺陷模板管理-字段管理
@ -564,7 +564,7 @@ export const pathMap: PathMapItem[] = [
routeQuery: { routeQuery: {
type: 'BUG', type: 'BUG',
}, },
level: MENU_LEVEL[1], level: MENU_LEVEL[2],
}, },
{ {
key: 'PROJECT_MANAGEMENT_TEMPLATE_BUG_TEMPLATE', // 模板管理-缺陷模板-缺陷模板管理 key: 'PROJECT_MANAGEMENT_TEMPLATE_BUG_TEMPLATE', // 模板管理-缺陷模板-缺陷模板管理
@ -574,14 +574,14 @@ export const pathMap: PathMapItem[] = [
routeQuery: { routeQuery: {
type: 'BUG', type: 'BUG',
}, },
level: MENU_LEVEL[1], level: MENU_LEVEL[2],
}, },
{ {
key: 'PROJECT_MANAGEMENT_TEMPLATE_BUG_WORKFLOW', // 模板管理-缺陷模板-缺陷工作流 key: 'PROJECT_MANAGEMENT_TEMPLATE_BUG_WORKFLOW', // 模板管理-缺陷模板-缺陷工作流
locale: 'menu.settings.organization.templateManagementWorkFlow', locale: 'menu.settings.organization.templateManagementWorkFlow',
route: RouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_WORKFLOW, route: RouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_WORKFLOW,
permission: [], permission: [],
level: MENU_LEVEL[1], level: MENU_LEVEL[2],
}, },
], ],
}, },

View File

@ -2,6 +2,7 @@ export enum ApiTestRouteEnum {
API_TEST = 'apiTest', API_TEST = 'apiTest',
API_TEST_DEBUG = 'apiTestDebug', API_TEST_DEBUG = 'apiTestDebug',
API_TEST_MANAGEMENT = 'apiTestManagement', API_TEST_MANAGEMENT = 'apiTestManagement',
API_TEST_REPORT = 'apiTestReport',
} }
export enum BugManagementRouteEnum { export enum BugManagementRouteEnum {
@ -41,7 +42,9 @@ export enum ProjectManagementRouteEnum {
PROJECT_MANAGEMENT_PERMISSION_MENU_MANAGEMENT = 'projectManagementPermissionMenuManagement', PROJECT_MANAGEMENT_PERMISSION_MENU_MANAGEMENT = 'projectManagementPermissionMenuManagement',
PROJECT_MANAGEMENT_TEMPLATE = 'projectManagementTemplate', PROJECT_MANAGEMENT_TEMPLATE = 'projectManagementTemplate',
PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT = 'projectManagementTemplateManagement', PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT = 'projectManagementTemplateManagement',
PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL = 'projectManagementTemplateManagementDetail', PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_CASE_DETAIL = 'projectManagementTemplateManagementCaseDetail',
PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_API_DETAIL = 'projectManagementTemplateManagementCaseDetail',
PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_BUG_DETAIL = 'projectManagementTemplateManagementBugDetail',
PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_WORKFLOW = 'projectManagementTemplateManagementWorkFlow', PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_WORKFLOW = 'projectManagementTemplateManagementWorkFlow',
PROJECT_MANAGEMENT_TEMPLATE_FIELD_SETTING = 'projectManagementTemplateFiledSetting', PROJECT_MANAGEMENT_TEMPLATE_FIELD_SETTING = 'projectManagementTemplateFiledSetting',
PROJECT_MANAGEMENT_PERMISSION_VERSION = 'projectManagementPermissionVersion', PROJECT_MANAGEMENT_PERMISSION_VERSION = 'projectManagementPermissionVersion',
@ -84,7 +87,9 @@ export enum SettingRouteEnum {
SETTING_ORGANIZATION_TEMPLATE = 'settingOrganizationTemplate', SETTING_ORGANIZATION_TEMPLATE = 'settingOrganizationTemplate',
SETTING_ORGANIZATION_TEMPLATE_FILED_SETTING = 'settingOrganizationTemplateFiledSetting', SETTING_ORGANIZATION_TEMPLATE_FILED_SETTING = 'settingOrganizationTemplateFiledSetting',
SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT = 'settingOrganizationTemplateManagement', SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT = 'settingOrganizationTemplateManagement',
SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL = 'settingOrganizationTemplateManagementDetail', SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_CASE_DETAIL = 'settingOrganizationTemplateManagementCaseDetail',
SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_API_DETAIL = 'settingOrganizationTemplateManagementApiDetail',
SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_BUG_DETAIL = 'settingOrganizationTemplateManagementBugDetail',
SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_WORKFLOW = 'settingOrganizationTemplateWorkFlow', SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_WORKFLOW = 'settingOrganizationTemplateWorkFlow',
SETTING_ORGANIZATION_SERVICE = 'settingOrganizationService', SETTING_ORGANIZATION_SERVICE = 'settingOrganizationService',
SETTING_ORGANIZATION_LOG = 'settingOrganizationLog', SETTING_ORGANIZATION_LOG = 'settingOrganizationLog',

View File

@ -116,6 +116,8 @@ export interface ActionTemplateManage {
fieldType?: string; fieldType?: string;
systemFields?: Record<string, any>[]; systemFields?: Record<string, any>[];
internal?: boolean; // 是否为系统模板 internal?: boolean; // 是否为系统模板
platForm?: string;
[key: string]: any;
} }
// 工作流列表字段 // 工作流列表字段

View File

@ -177,11 +177,11 @@ const ProjectManagement: AppRouteRecordRaw = {
}, },
// 项目-模板-创建模板和模板详情 // 项目-模板-创建模板和模板详情
{ {
path: 'templateDetail/:mode?', path: 'templateCaseDetail/:mode?',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL, name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_CASE_DETAIL,
component: () => import('@/views/project-management/template/components/proTemplateDetail.vue'), component: () => import('@/views/project-management/template/components/detail.vue'),
meta: { meta: {
locale: 'menu.settings.organization.templateManagementDetail', locale: 'system.orgTemplate.createCaseTemplate',
roles: ['PROJECT_TEMPLATE:READ+UPDATE', 'PROJECT_TEMPLATE:READ+ADD'], roles: ['PROJECT_TEMPLATE:READ+UPDATE', 'PROJECT_TEMPLATE:READ+ADD'],
breadcrumbs: [ breadcrumbs: [
{ {
@ -194,9 +194,65 @@ const ProjectManagement: AppRouteRecordRaw = {
query: ['type'], query: ['type'],
}, },
{ {
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL, name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_CASE_DETAIL,
locale: 'menu.settings.organization.templateManagementDetail', locale: 'system.orgTemplate.createCaseTemplate',
editLocale: 'menu.settings.organization.templateManagementEdit', editLocale: 'system.orgTemplate.updateCaseTemplate',
editTag: 'id',
query: ['type'],
},
],
},
},
// 项目-模板-接口模板
{
path: 'templateApiDetail/:mode?',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_API_DETAIL,
component: () => import('@/views/project-management/template/components/detail.vue'),
meta: {
locale: 'system.orgTemplate.createApiTemplate',
roles: ['PROJECT_TEMPLATE:READ+UPDATE', 'PROJECT_TEMPLATE:READ+ADD'],
breadcrumbs: [
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE,
locale: 'menu.settings.organization.template',
},
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT,
locale: 'menu.settings.organization.templateManagementList',
query: ['type'],
},
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_API_DETAIL,
locale: 'system.orgTemplate.createApiTemplate',
editLocale: 'system.orgTemplate.updateApiTemplate',
editTag: 'id',
query: ['type'],
},
],
},
},
// 项目-模板-缺陷模板
{
path: 'templateBugDetail/:mode?',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_BUG_DETAIL,
component: () => import('@/views/project-management/template/components/detail.vue'),
meta: {
locale: 'system.orgTemplate.createDefectTemplate',
roles: ['PROJECT_TEMPLATE:READ+UPDATE', 'PROJECT_TEMPLATE:READ+ADD'],
breadcrumbs: [
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE,
locale: 'menu.settings.organization.template',
},
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT,
locale: 'menu.settings.organization.templateManagementList',
query: ['type'],
},
{
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_BUG_DETAIL,
locale: 'system.orgTemplate.createDefectTemplate',
editLocale: 'system.orgTemplate.updateDefectTemplate',
editTag: 'id', editTag: 'id',
query: ['type'], query: ['type'],
}, },

View File

@ -285,13 +285,14 @@ const Setting: AppRouteRecordRaw = {
], ],
}, },
}, },
// 模板列表-模板管理-创建&编辑模板 // 模板列表-创建&编辑模板
// 用例模板
{ {
path: 'templateManagementDetail/:mode?', path: 'templateManagementCaseDetail/:mode?',
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL, name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_CASE_DETAIL,
component: () => import('@/views/setting/organization/template/components/templateDetail.vue'), component: () => import('@/views/setting/organization/template/components/detail.vue'),
meta: { meta: {
locale: 'menu.settings.organization.templateManagementDetail', locale: 'system.orgTemplate.createCaseTemplate',
roles: ['ORGANIZATION_TEMPLATE:READ+UPDATE', 'ORGANIZATION_TEMPLATE:READ+ADD'], roles: ['ORGANIZATION_TEMPLATE:READ+UPDATE', 'ORGANIZATION_TEMPLATE:READ+ADD'],
breadcrumbs: [ breadcrumbs: [
{ {
@ -305,9 +306,65 @@ const Setting: AppRouteRecordRaw = {
}, },
{ {
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT, name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT,
locale: 'menu.settings.organization.templateManagementDetail', locale: 'system.orgTemplate.createCaseTemplate',
editTag: 'id', editTag: 'id',
editLocale: 'menu.settings.organization.templateManagementEdit', editLocale: 'system.orgTemplate.updateCaseTemplate',
query: ['type'],
},
],
},
},
// 接口模板
{
path: 'templateManagementApiDetail/:mode?',
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_API_DETAIL,
component: () => import('@/views/setting/organization/template/components/detail.vue'),
meta: {
locale: 'system.orgTemplate.createApiTemplate',
roles: ['ORGANIZATION_TEMPLATE:READ+UPDATE', 'ORGANIZATION_TEMPLATE:READ+ADD'],
breadcrumbs: [
{
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE,
locale: 'menu.settings.organization.template',
},
{
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT,
locale: 'menu.settings.organization.templateManagementList',
query: ['type'],
},
{
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT,
locale: 'system.orgTemplate.createApiTemplate',
editTag: 'id',
editLocale: 'system.orgTemplate.updateApiTemplate',
query: ['type'],
},
],
},
},
// 缺陷模板
{
path: 'templateManagementBugDetail/:mode?',
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_BUG_DETAIL,
component: () => import('@/views/setting/organization/template/components/detail.vue'),
meta: {
locale: 'system.orgTemplate.createDefectTemplate',
roles: ['ORGANIZATION_TEMPLATE:READ+UPDATE', 'ORGANIZATION_TEMPLATE:READ+ADD'],
breadcrumbs: [
{
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE,
locale: 'menu.settings.organization.template',
},
{
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT,
locale: 'menu.settings.organization.templateManagementList',
query: ['type'],
},
{
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT,
locale: 'system.orgTemplate.createDefectTemplate',
editTag: 'id',
editLocale: 'system.orgTemplate.updateDefectTemplate',
query: ['type'], query: ['type'],
}, },
], ],

View File

@ -38,6 +38,7 @@
import { createCaseRequest, updateCaseRequest } from '@/api/modules/case-management/featureCase'; import { createCaseRequest, updateCaseRequest } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useLeaveUnSaveTip from '@/hooks/useLeaveUnSaveTip';
import useVisit from '@/hooks/useVisit'; import useVisit from '@/hooks/useVisit';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import useFeatureCaseStore from '@/store/modules/case/featureCase'; import useFeatureCaseStore from '@/store/modules/case/featureCase';
@ -48,10 +49,13 @@
import Message from '@arco-design/web-vue/es/message'; import Message from '@arco-design/web-vue/es/message';
const { setState } = useLeaveUnSaveTip();
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
setState(false);
const featureCaseStore = useFeatureCaseStore(); const featureCaseStore = useFeatureCaseStore();
const visitedKey = 'doNotNextTipCreateCase'; const visitedKey = 'doNotNextTipCreateCase';
@ -83,6 +87,7 @@
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE, name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE,
query: { organizationId: route.query.organizationId, projectId: route.query.projectId }, query: { organizationId: route.query.organizationId, projectId: route.query.projectId },
}); });
setState(true);
// //
} else { } else {
// //
@ -119,6 +124,7 @@
}, },
}); });
} }
setState(true);
} }
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console

View File

@ -229,15 +229,6 @@
import { CommentParams } from '@/components/business/ms-comment/types'; import { CommentParams } from '@/components/business/ms-comment/types';
import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue'; import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue';
import SettingDrawer from './tabContent/settingDrawer.vue'; import SettingDrawer from './tabContent/settingDrawer.vue';
import TabDefect from './tabContent/tabBug/tabDefect.vue';
import TabCaseTable from './tabContent/tabCase/tabCaseTable.vue';
import TabCaseReview from './tabContent/tabCaseReview.vue';
import TabChangeHistory from './tabContent/tabChangeHistory.vue';
import TabComment from './tabContent/tabComment/tabCommentIndex.vue';
import TabDemand from './tabContent/tabDemand/demand.vue';
import TabDependency from './tabContent/tabDependency/tabDependency.vue';
import TabDetail from './tabContent/tabDetail.vue';
import TabTestPlan from './tabContent/tabTestPlan.vue';
import { import {
createCommentList, createCommentList,
@ -261,6 +252,16 @@
import { getCaseLevels } from './utils'; import { getCaseLevels } from './utils';
import { LabelValue } from '@arco-design/web-vue/es/tree-select/interface'; import { LabelValue } from '@arco-design/web-vue/es/tree-select/interface';
import debounce from 'lodash-es/debounce'; import debounce from 'lodash-es/debounce';
//
const TabDefect = defineAsyncComponent(() => import('./tabContent/tabBug/tabDefect.vue'));
const TabCaseTable = defineAsyncComponent(() => import('./tabContent/tabCase/tabCaseTable.vue'));
const TabCaseReview = defineAsyncComponent(() => import('./tabContent/tabCaseReview.vue'));
const TabChangeHistory = defineAsyncComponent(() => import('./tabContent/tabChangeHistory.vue'));
const TabComment = defineAsyncComponent(() => import('./tabContent/tabComment/tabCommentIndex.vue'));
const TabDemand = defineAsyncComponent(() => import('./tabContent/tabDemand/demand.vue'));
const TabDependency = defineAsyncComponent(() => import('./tabContent/tabDependency/tabDependency.vue'));
const TabDetail = defineAsyncComponent(() => import('./tabContent/tabDetail.vue'));
const TabTestPlan = defineAsyncComponent(() => import('./tabContent/tabTestPlan.vue'));
const router = useRouter(); const router = useRouter();
const detailDrawerRef = ref<InstanceType<typeof MsDetailDrawer>>(); const detailDrawerRef = ref<InstanceType<typeof MsDetailDrawer>>();

View File

@ -465,21 +465,18 @@
'slotName': 'num', 'slotName': 'num',
'dataIndex': 'num', 'dataIndex': 'num',
'width': 200, 'width': 200,
'showInTable': true,
'sortable': { 'sortable': {
sortDirections: ['ascend', 'descend'], sortDirections: ['ascend', 'descend'],
sorter: true, sorter: true,
}, },
'filter-icon-align-left': true, 'filter-icon-align-left': true,
'showTooltip': true, 'showTooltip': true,
'ellipsis': true,
'showDrag': false, 'showDrag': false,
}, },
{ {
title: 'caseManagement.featureCase.tableColumnName', title: 'caseManagement.featureCase.tableColumnName',
slotName: 'name', slotName: 'name',
dataIndex: 'name', dataIndex: 'name',
showInTable: true,
showTooltip: true, showTooltip: true,
width: 300, width: 300,
editType: hasAnyPermission(['FUNCTIONAL_CASE:READ+UPDATE']) ? ColumnEditTypeEnum.INPUT : undefined, editType: hasAnyPermission(['FUNCTIONAL_CASE:READ+UPDATE']) ? ColumnEditTypeEnum.INPUT : undefined,
@ -487,7 +484,6 @@
sortDirections: ['ascend', 'descend'], sortDirections: ['ascend', 'descend'],
sorter: true, sorter: true,
}, },
ellipsis: true,
showDrag: false, showDrag: false,
}, },
{ {
@ -791,7 +787,7 @@
} }
} }
const initDefaultFields = ref<CustomAttributes[]>([]); const initDefaultFields = ref<CustomAttributes[]>([]);
let fullColumns: MsTableColumn = []; // // let fullColumns: MsTableColumn = []; //
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, setKeyword, setAdvanceFilter } = useTable( const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, setKeyword, setAdvanceFilter } = useTable(
getCaseList, getCaseList,
@ -1183,6 +1179,7 @@
// //
let customFieldsColumns: Record<string, any>[] = []; let customFieldsColumns: Record<string, any>[] = [];
const tableRef = ref<InstanceType<typeof MsBaseTable> | null>(null); const tableRef = ref<InstanceType<typeof MsBaseTable> | null>(null);
const fullColumns = ref<MsTableColumn>([]);
// //
async function getDefaultFields() { async function getDefaultFields() {
@ -1204,12 +1201,12 @@
caseLevelFields.value = result.customFields.find((item: any) => item.internal && item.fieldName === '用例等级'); caseLevelFields.value = result.customFields.find((item: any) => item.internal && item.fieldName === '用例等级');
caseFilters.value = caseLevelFields.value.options.map((item: any) => item.value); caseFilters.value = caseLevelFields.value.options.map((item: any) => item.value);
fullColumns = [ fullColumns.value = [
...columns.slice(0, columns.length - 1), ...columns.slice(0, columns.length - 1),
...customFieldsColumns, ...customFieldsColumns,
...columns.slice(columns.length - 1, columns.length), ...columns.slice(columns.length - 1, columns.length),
]; ];
await tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_TABLE, fullColumns, 'drawer'); await tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_TABLE, fullColumns.value, 'drawer');
} }
// //

View File

@ -30,7 +30,6 @@
/> --> /> -->
<MsButton @click="saveAsHandler(record)">{{ t('caseManagement.featureCase.saveAsVersion') }}</MsButton> <MsButton @click="saveAsHandler(record)">{{ t('caseManagement.featureCase.saveAsVersion') }}</MsButton>
</template> </template>
</ms-base-table> </ms-base-table>
<a-modal <a-modal
v-model:visible="showModal" v-model:visible="showModal"
@ -129,15 +128,15 @@
dataIndex: 'createTime', dataIndex: 'createTime',
width: 200, width: 200,
}, },
{ // {
title: 'caseManagement.featureCase.tableColumnActions', // title: 'caseManagement.featureCase.tableColumnActions',
slotName: 'operation', // slotName: 'operation',
dataIndex: 'operation', // dataIndex: 'operation',
fixed: 'right', // fixed: 'right',
width: 140, // width: 140,
showInTable: true, // showInTable: true,
showDrag: false, // showDrag: false,
}, // },
]; ];
const typeOptions = [ const typeOptions = [
@ -159,9 +158,9 @@
columns, columns,
tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_CHANGE_HISTORY, tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_CHANGE_HISTORY,
scroll: { x: '100%' }, scroll: { x: '100%' },
selectable: true, selectable: false,
heightUsed: 340, heightUsed: 340,
enableDrag: true, enableDrag: false,
}); });
const form = ref({ const form = ref({
@ -240,8 +239,8 @@
setLoadListParams({ setLoadListParams({
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,
sourceId: props.caseId, sourceId: props.caseId,
type:['IMPORT','ADD','UPDATE'], type: ['IMPORT', 'ADD', 'UPDATE'],
module: ['CASE_MANAGEMENT_CASE_CREATE','CASE_MANAGEMENT_CASE_UPDATE'], module: ['CASE_MANAGEMENT_CASE_CREATE', 'CASE_MANAGEMENT_CASE_UPDATE'],
}); });
await loadList(); await loadList();
featureCaseStore.getCaseCounts(props.caseId); featureCaseStore.getCaseCounts(props.caseId);

View File

@ -77,7 +77,7 @@
width: 200, width: 200,
}, },
{ {
title: 'caseManagement.featureCase.name', title: 'caseManagement.featureCase.demandName',
slotName: 'demandName', slotName: 'demandName',
dataIndex: 'demandName', dataIndex: 'demandName',
width: 300, width: 300,

View File

@ -91,7 +91,7 @@
import ScriptDetailDrawer from './components/scriptDetailDrawer.vue'; import ScriptDetailDrawer from './components/scriptDetailDrawer.vue';
import { import {
addCommonScriptReq, addOrUpdateCommonScriptReq,
deleteCommonScript, deleteCommonScript,
getCommonScriptPage, getCommonScriptPage,
} from '@/api/modules/project-management/commonScript'; } from '@/api/modules/project-management/commonScript';
@ -279,13 +279,14 @@
try { try {
confirmLoading.value = true; confirmLoading.value = true;
const { status } = form; const { status } = form;
const paramTableList = paramsList.value.slice(0, -1);
const paramsObj: AddOrUpdateCommonScript = { const paramsObj: AddOrUpdateCommonScript = {
...form, ...form,
status: status || 'DRAFT', status: status || 'DRAFT',
projectId: currentProjectId.value, projectId: currentProjectId.value,
params: JSON.stringify(paramsList.value), params: JSON.stringify(paramTableList),
}; };
await addCommonScriptReq(paramsObj); await addOrUpdateCommonScriptReq(paramsObj);
showScriptDrawer.value = false; showScriptDrawer.value = false;
initData(); initData();
Message.success( Message.success(

View File

@ -1,6 +1,7 @@
export default { export default {
'project.commonScript.searchByNameAndId': 'Search by name', 'project.commonScript.searchByNameAndId': 'Search by name',
'project.commonScript.addPublicScript': 'Add public Script', 'project.commonScript.addPublicScript': 'Add public Script',
'project.commonScript.editPublicScript': 'Edit public Script',
'project.commonScript.name': 'Name', 'project.commonScript.name': 'Name',
'project.commonScript.description': 'Description', 'project.commonScript.description': 'Description',
'project.commonScript.enable': 'Enable', 'project.commonScript.enable': 'Enable',

View File

@ -1,6 +1,7 @@
export default { export default {
'project.commonScript.searchByNameAndId': '通过名称搜索', 'project.commonScript.searchByNameAndId': '通过名称搜索',
'project.commonScript.addPublicScript': '添加公共脚本', 'project.commonScript.addPublicScript': '添加公共脚本',
'project.commonScript.editPublicScript': '编辑公共脚本',
'project.commonScript.name': '名称', 'project.commonScript.name': '名称',
'project.commonScript.description': '描述', 'project.commonScript.description': '描述',
'project.commonScript.enable': '状态', 'project.commonScript.enable': '状态',

View File

@ -0,0 +1,11 @@
<template>
<AddTemplate mode="project" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import AddTemplate from '@/views/setting/organization/template/components/addTemplate.vue';
</script>
<style scoped></style>

View File

@ -1,408 +0,0 @@
<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"
class="max-w-[732px]"
:disabled="templateForm?.internal"
></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="1000"
:placeholder="t('system.orgTemplate.resDescription')"
:auto-size="{ minRows: 1 }"
style="resize: vertical"
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')"
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:raw="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,
getTemplateName,
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' ? `copy_${name}` : 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', {
type: getTemplateName('organization', route.query.type as string),
});
getClassifyField();
} else if (isEdit.value) {
title.value = t('system.orgTemplate.editTemplateType', {
type: getTemplateName('organization', route.query.type as string),
});
getClassifyField();
} else {
title.value = t('system.orgTemplate.createTemplateType', {
type: getTemplateName('organization', route.query.type as string),
});
}
});
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

@ -301,11 +301,11 @@
} }
} }
}; };
const routeName = ref<string>('');
// //
const createTemplate = () => { const createTemplate = () => {
router.push({ router.push({
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL, name: routeName.value,
query: { query: {
type: route.query.type, type: route.query.type,
}, },
@ -318,7 +318,7 @@
// //
const editTemplate = (id: string) => { const editTemplate = (id: string) => {
router.push({ router.push({
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL, name: routeName.value,
query: { query: {
id, id,
type: route.query.type, type: route.query.type,
@ -332,7 +332,7 @@
// //
const copyTemplate = (id: string) => { const copyTemplate = (id: string) => {
router.push({ router.push({
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_DETAIL, name: routeName.value,
query: { query: {
id, id,
type: route.query.type, type: route.query.type,
@ -413,6 +413,13 @@
onMounted(() => { onMounted(() => {
fetchData(); fetchData();
updateColumns(); updateColumns();
if (route.query.type === 'FUNCTIONAL') {
routeName.value = ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_CASE_DETAIL;
} else if (route.query.type === 'API') {
routeName.value = ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_API_DETAIL;
} else {
routeName.value = ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT_API_DETAIL;
}
}); });
</script> </script>

View File

@ -0,0 +1,713 @@
<template>
<MsCard
:loading="loading"
:title="title"
:is-edit="isEdit && route.params.mode !== 'copy'"
has-breadcrumb
:hide-back="true"
@save="saveHandler"
@save-and-continue="saveHandler(true)"
>
<template #headerLeft>
<a-alert v-if="templateForm.enableThirdPart && route.query.type === 'BUG'" class="mb-[16px] w-full">
{{ t('system.orgTemplate.enableApiAlert') }}
</a-alert>
<a-form ref="formRef" class="mt-1 max-w-[710px]" :model="templateForm">
<a-form-item
v-if="!templateForm?.internal"
field="name"
asterisk-position="end"
:hide-label="true"
hide-asterisk
:rules="[{ required: true, message: t('system.orgTemplate.templateNameRules') }]"
content-class="contentClass"
class="mb-0 max-w-[710px]"
>
<a-input
v-model:model-value="templateForm.name"
:placeholder="t('system.orgTemplate.templateNamePlaceholder')"
:max-length="255"
class="max-w-[732px]"
:disabled="templateForm?.internal"
></a-input>
</a-form-item>
<span v-else class="font-medium text-[var(--color-text-1)] underline">{{ templateForm.name }}</span>
</a-form>
</template>
<template #headerRight>
<div class="flex items-center">
<!-- <a-select
v-if="templateForm.enableThirdPart && route.query.type === 'BUG'"
v-model="templateForm.platForm"
class="!my-0 w-[240px]"
:placeholder="t('system.orgTemplate.selectThirdPlatType')"
>
<a-option v-for="item of platFormList" :key="item.value" :value="item.value">{{ item.label }}</a-option>
</a-select> -->
<a-checkbox v-if="route.query.type === 'BUG'" v-model="templateForm.enableThirdPart" class="mx-2">{{
t('system.orgTemplate.thirdParty')
}}</a-checkbox>
<MsTag size="large" class="cursor-pointer" theme="outline" @click="brash">
<MsIcon class="text-[var(color-text-4)]" :size="16" type="icon-icon_reset_outlined" />
</MsTag>
</div>
</template>
<div class="wrapper-preview">
<div class="preview-left pr-4">
<DefectTemplateLeftContent v-if="route.query.type === 'BUG'" :defect-form="defectForm" />
<CaseTemplateLeftContent v-else />
</div>
<div class="preview-right px-4">
<!-- 自定义字段开始 -->
<VueDraggable v-model="selectData" handle=".form" ghost-class="ghost" @change="changeDrag">
<div v-for="(formItem, index) of selectData" :key="formItem.id" class="customWrapper">
<div class="action">
<span class="required">
<a-checkbox
v-model="formItem.required"
class="mr-1"
@change="(value) => changeState(value, formItem)"
>{{ t('ms.assertion.mustInclude') }}</a-checkbox
>
</span>
<div class="actionList">
<a-tooltip :content="t('system.orgTemplate.toTop')">
<MsIcon
type="icon-icon_up_outlined"
size="16"
:class="getColor(index, 'top')"
@click="moveField(formItem as DefinedFieldItem, 'top')"
/>
</a-tooltip>
<a-divider direction="vertical" class="!m-0 !mx-2" />
<a-tooltip :content="t('system.orgTemplate.toBottom')">
<MsIcon
:class="getColor(index, 'bottom')"
type="icon-icon_down_outlined"
size="16"
@click="moveField(formItem as DefinedFieldItem, 'bottom')"
/>
</a-tooltip>
<a-divider v-if="!formItem.internal" direction="vertical" class="!m-0 !mx-2" />
<a-tooltip :content="t('common.edit')">
<MsIcon
v-if="!formItem.internal"
type="icon-icon_edit_outlined"
size="16"
@click="editField(formItem as DefinedFieldItem)"
/>
</a-tooltip>
<a-divider v-if="!formItem.internal" direction="vertical" class="!m-0 !mx-2" />
<a-tooltip :content="t('common.delete')">
<MsIcon
v-if="!formItem.internal"
type="icon-icon_delete-trash_outlined"
size="16"
@click="deleteSelectedField(formItem as DefinedFieldItem)"
/>
</a-tooltip>
</div>
</div>
<div
class="form"
:class="{
'hover:border-[var(--color-text-n8)]': activeIndex !== index,
'activeStyle': activeIndex === index,
}"
@click="activeHandler(index)"
>
<!-- 表单 -->
<MsFormCreate
v-model:api="formItem.api"
v-model:rule="formItem.formRules"
:option="configOptions"
@click="activeHandler(index)"
/>
<a-form
v-if="templateForm.enableThirdPart && route.query.type === 'BUG'"
:ref="(el: refItem) => setStepRefMap(el, formItem as DefinedFieldItem)"
:model="formItem"
>
<a-form-item
row-class="apiFieldIdClass"
hide-asterisk
hide-label
field="apiFieldId"
:rules="[{ required: true, message: t('system.orgTemplate.apiFieldNotEmpty') }]"
>
<a-input
v-model:model-value="formItem.apiFieldId"
:placeholder="t('system.orgTemplate.pleaseEnterAPITip')"
class="mt-1"
:max-length="255"
/></a-form-item>
</a-form>
</div>
</div>
</VueDraggable>
<!-- 自定义字段结束 -->
<div class="flex items-center">
<a-button class="mr-1 mt-1 px-0" type="text" @click="associatedField">
<template #icon>
<icon-plus class="text-[14px]" />
</template>
{{ t('system.orgTemplate.associatedField') }}
</a-button>
<a-tooltip :content="t('system.orgTemplate.associatedHasField')" placement="top" effect="dark">
<IconQuestionCircle
class="mr-8 mt-1 h-[16px] w-[16px] text-[--color-text-4] hover:text-[rgb(var(--primary-5))]"
/>
</a-tooltip>
<a-button class="mr-1 mt-1 px-0" type="text" :disabled="totalTemplateField.length >= 20" @click="createField">
<template #icon>
<icon-plus class="text-[14px]" />
</template>
{{ t('system.orgTemplate.addField') }}
</a-button>
<a-tooltip :content="t('system.orgTemplate.addFieldDesc')" placement="top" effect="dark">
<IconQuestionCircle
class="mt-1 h-[16px] w-[16px] text-[--color-text-4] hover:text-[rgb(var(--primary-5))]"
/>
</a-tooltip>
</div>
</div>
<!-- 添加字段到模板抽屉 -->
<AddFieldToTemplateDrawer
ref="fieldSelectRef"
v-model:visible="showDrawer"
:total-data="(totalTemplateField as DefinedFieldItem[])"
:table-select-data="(selectData as DefinedFieldItem[])"
:mode="props.mode"
@confirm="confirmHandler"
/>
<EditFieldDrawer
ref="fieldDrawerRef"
v-model:visible="showFieldDrawer"
:mode="props.mode"
@success="updateFieldHandler"
/>
</div>
</MsCard>
</template>
<script setup lang="ts">
/**
* @description 模板-创建模板&编辑模板-预览模板
*/
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import { VueDraggable } from 'vue-draggable-plus';
import MsCard from '@/components/pure/ms-card/index.vue';
import { FieldTypeFormRules } from '@/components/pure/ms-form-create/form-create';
import MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import AddFieldToTemplateDrawer from './addFieldToTemplateDrawer.vue';
import CaseTemplateLeftContent from './caseTemplateLeftContent.vue';
import DefectTemplateLeftContent from './defectTemplateLeftContent.vue';
import EditFieldDrawer from './editFieldDrawer.vue';
import {
createOrganizeTemplateInfo,
createProjectTemplateInfo,
getFieldList,
getOrganizeTemplateInfo,
getProjectFieldList,
getProjectTemplateInfo,
updateOrganizeTemplateInfo,
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, SeneType } from '@/models/setting/template';
import { ProjectManagementRouteEnum, SettingRouteEnum } from '@/enums/routeEnum';
import { getTemplateName, getTotalFieldOptionList } from './fieldSetting';
import { FormInstance } from '@arco-design/web-vue/es/form';
const props = defineProps<{
mode: 'organization' | 'project';
}>();
type refItem = Element | ComponentPublicInstance | null;
const appStore = useAppStore();
const currentOrgId = computed(() => appStore.currentOrgId);
const currentProjectId = computed(() => appStore.currentProjectId);
const { setState } = useLeaveUnSaveTip();
setState(false);
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const title = ref('');
const initTemplateForm: ActionTemplateManage = {
id: '',
name: '',
remark: '',
scopeId: props.mode === 'organization' ? currentOrgId.value : currentProjectId.value,
enableThirdPart: false,
platForm: '',
};
const initBugForm = {
name: '',
description: '',
};
const defectForm = ref({ ...initBugForm });
const isEdit = computed(() => !!route.query.id);
const loading = ref(false);
const formRef = ref<FormInstance>();
const templateForm = ref<ActionTemplateManage>({ ...initTemplateForm });
const isContinueFlag = ref(false);
const platFormList = ref<{ value: string; label: string }[]>([
{
value: 'zental',
label: '禅道',
},
{
value: 'JIRA',
label: 'JIRA',
},
]);
const templateApiMaps: Record<string, any> = {
organization: {
fieldList: getFieldList,
create: createOrganizeTemplateInfo,
update: updateOrganizeTemplateInfo,
detail: getOrganizeTemplateInfo,
},
project: {
fieldList: getProjectFieldList,
create: createProjectTemplateInfo,
update: updateProjectTemplateInfo,
detail: getProjectTemplateInfo,
},
};
const totalTemplateField = ref<DefinedFieldItem[]>([]);
const selectData = ref<DefinedFieldItem[]>([]);
//
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 { name, remark, enableThirdPart, id } = templateForm.value;
return {
id,
name,
remark,
enableThirdPart,
customFields: result as CustomField[],
scopeId: props.mode === 'organization' ? currentOrgId.value : currentProjectId.value,
scene: route.query.type,
};
}
function resetForm() {
templateForm.value = { ...initTemplateForm };
}
//
async function save() {
try {
loading.value = true;
const params = getTemplateParams();
if (isEdit.value && route.params.mode !== 'copy') {
await templateApiMaps[props.mode].update(params);
Message.success(t('system.orgTemplate.updateSuccess'));
} else {
await templateApiMaps[props.mode].create(params);
Message.success(t('system.orgTemplate.addSuccess'));
}
if (isContinueFlag.value) {
resetForm();
} else {
await sleep(300);
if (props.mode === 'organization') {
router.push({ name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT, query: route.query });
} else {
router.push({ name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT, query: route.query });
}
setState(true);
}
} catch (error) {
console.log(error);
} finally {
loading.value = false;
}
}
const refStepMap: Record<string, any> = {};
function setStepRefMap(el: refItem, formItem: DefinedFieldItem) {
if (el) {
refStepMap[`${formItem.id}`] = el;
}
}
//
function saveHandler(isContinue = false) {
isContinueFlag.value = isContinue;
formRef.value?.validate().then((res) => {
if (!res) {
const allValidatePromises = Object.keys(refStepMap).map((key) => {
return refStepMap[key].validate();
});
Promise.all(allValidatePromises).then((results) => {
const allValid = results.every((result) => !result);
if (allValid) {
return save();
}
});
}
return scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
});
}
//
const getFieldOptionList = () => {
totalTemplateField.value = getTotalFieldOptionList(totalTemplateField.value as DefinedFieldItem[]);
selectData.value = totalTemplateField.value.filter((item) => item.internal);
};
//
const getClassifyField = async () => {
try {
totalTemplateField.value = await templateApiMaps[props.mode].fieldList({
scopedId: props.mode === 'organization' ? currentOrgId.value : currentProjectId.value,
scene: route.query.type,
});
} catch (error) {
console.log(error);
}
};
watchEffect(async () => {
if (isEdit.value && route.params.mode === 'copy') {
title.value = t('system.orgTemplate.copyTemplate');
} else if (isEdit.value) {
title.value = t('system.orgTemplate.editTemplateType', {
type: getTemplateName('organization', route.query.type as string),
});
} else {
title.value = t('system.orgTemplate.createTemplateType', {
type: getTemplateName('organization', route.query.type as string),
});
templateForm.value.name = title.value;
}
});
const showFieldDrawer = ref<boolean>(false);
function createField() {
showFieldDrawer.value = true;
}
const showDrawer = ref<boolean>(false);
function associatedField() {
showDrawer.value = true;
}
const selectFiled = ref<DefinedFieldItem[]>([]);
const selectedIds = ref<string[]>();
//
const isEditField = ref<boolean>(false);
//
const updateFieldHandler = async (editFlag: boolean, fieldId: string) => {
selectFiled.value = selectData.value;
isEditField.value = editFlag;
await getClassifyField();
totalTemplateField.value = getTotalFieldOptionList(totalTemplateField.value as DefinedFieldItem[]);
//
if (isEditField.value) {
const index = selectData.value.findIndex((e: any) => e.id === fieldId);
const newUpdateData = totalTemplateField.value.find((item: any) => item.id === fieldId);
if (index > -1 && newUpdateData) {
selectData.value.splice(index, 1);
selectData.value.splice(index, 0, newUpdateData);
}
}
//
if (!isEditField.value && fieldId) {
const newUpdateData = totalTemplateField.value.find((item: any) => item.id === fieldId);
if (newUpdateData) {
selectData.value.push(newUpdateData);
}
}
};
//
const deleteSelectedField = (record: DefinedFieldItem) => {
selectData.value = selectData.value.filter((item) => item.id !== record.id);
};
const fieldDrawerRef = ref();
//
const editField = (record: DefinedFieldItem) => {
showFieldDrawer.value = true;
fieldDrawerRef.value.editHandler(record);
};
//
const confirmHandler = (dataList: DefinedFieldItem[]) => {
const selectFieldIds = selectData.value.map((e) => e.id);
const newData = dataList.filter((item) => !selectFieldIds.includes(item.id));
selectData.value = [...selectData.value, ...newData];
};
function changeState(value: boolean | (string | number | boolean)[], formItem) {
formItem.required = value;
formItem.formRules[0].effect.required = value;
}
const systemFieldData = ref<CustomField[]>([]);
function getSelectData(customFields: DefinedFieldItem[]) {
return customFields.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,
id: item.fieldId,
formRules: [
{
...currentFormRules,
title: item.fieldName,
effect: {
required: item.required,
},
value: item.defaultValue,
props: { ...currentFormRules.props, options: selectOptions, modelValue: item.defaultValue },
},
],
fApi: null,
required: item.required,
};
});
}
//
const getTemplateInfo = async () => {
try {
loading.value = true;
const res = await templateApiMaps[props.mode].detail(route.query.id as string);
const { name, customFields, systemFields } = res;
templateForm.value = {
...res,
name: route.params.mode === 'copy' ? `copy_${name}` : name,
};
if (route.params.mode === 'copy') {
templateForm.value.id = undefined;
}
selectData.value = getSelectData(customFields);
systemFieldData.value = systemFields;
} catch (error) {
console.log(error);
} finally {
loading.value = false;
}
};
function moveField(formItem: DefinedFieldItem, type: string) {
const moveIndex = selectData.value.findIndex((item: any) => item.id === formItem.id);
if (type === 'top') {
if (moveIndex === 0) {
return;
}
selectData.value.splice(moveIndex, 1);
selectData.value.splice(moveIndex - 1, 0, formItem);
} else {
if (moveIndex === selectData.value.length - 1) {
return;
}
selectData.value.splice(moveIndex, 1);
selectData.value.splice(moveIndex + 1, 0, formItem);
}
}
function getColor(index: number, type: string) {
if (type === 'top' && index === 0) {
return ['text-[rgb(var(--primary-3))]'];
}
if (type === 'bottom' && index === selectData.value.length - 1) {
return ['text-[rgb(var(--primary-3))]'];
}
}
onMounted(async () => {
await getClassifyField();
getFieldOptionList();
if (isEdit.value) {
getTemplateInfo();
}
});
const activeIndex = ref(-1);
async function brash() {
activeIndex.value = -1;
await getClassifyField();
getFieldOptionList();
if (isEdit.value) {
getTemplateInfo();
}
}
function activeHandler(index: number) {
activeIndex.value = index;
}
function changeDrag() {
activeIndex.value = -1;
}
const configOptions = ref({
resetBtn: false,
submitBtn: false,
on: false,
form: {
layout: 'vertical',
labelAlign: 'left',
},
row: {
gutter: 0,
},
wrap: {
'asterisk-position': 'end',
'validate-trigger': ['change'],
'row-class': 'selfClass',
},
});
</script>
<style scoped lang="less">
.wrapper-preview {
display: flex;
height: 100%;
.preview-left {
width: 100%;
border-right: 1px solid var(--color-text-n8);
}
.preview-right {
padding-top: 8px;
width: 428px;
min-width: 428px;
.customWrapper {
position: relative;
margin-bottom: 4px;
border: 1px solid transparent;
border-radius: 6px;
@apply flex flex-col justify-between;
.form {
padding: 8px;
border: 1px solid transparent;
border-radius: 6px;
}
.form.activeStyle {
border-color: rgb(var(--primary-5));
background: var(--color-text-n9);
}
.action {
position: absolute;
top: -12px;
right: 16px;
z-index: 99999999 !important;
background: white;
opacity: 0;
@apply flex items-center justify-end;
.actionList {
padding: 4px;
border-radius: 4px;
@apply flex items-center justify-center;
}
.required > .arco-checkbox {
padding: 2px 4px;
border-radius: 4px;
box-shadow: 0 4px 10px -1px rgba(100 100 102/ 15%);
}
}
&:hover {
border: 1px solid var(--color-text-n8);
background: var(--color-text-n9);
}
&:hover > .action {
opacity: 1;
}
&:hover > .action > .actionList {
color: rgb(var(--primary-5));
box-shadow: 0 4px 10px -1px rgba(100 100 102/ 15%);
}
}
}
}
.hoverStyle {
.customWrapper:hover > .form {
border-color: var(--color-text-n8) !important;
}
}
.activeStyle {
.customWrapper:hover .form {
border-color: rgb(var(--primary-5));
}
}
:deep(.selfClass) {
margin-bottom: 0;
}
:deep(.contentClass > .arco-input-wrapper) {
border-color: transparent;
&:hover {
border-color: var(--color-text-input-border);
}
&:hover > .arco-input {
font-weight: normal;
text-decoration: none;
color: var(--color-text-1);
}
& > .arco-input {
font-weight: 500;
text-decoration: underline;
color: var(--color-text-1);
}
}
:deep(.contentClass > .arco-input-focus) {
border-color: rgb(var(--primary-5));
& > .arco-input {
font-weight: normal;
text-decoration: none;
}
}
.ghost {
border: 1px solid rgba(var(--primary-5));
background-color: var(--color-text-n9);
}
:deep(.apiFieldIdClass) {
margin-bottom: 0;
}
</style>

View File

@ -0,0 +1,11 @@
<template>
<AddTemplate mode="organization" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import AddTemplate from './addTemplate.vue';
</script>
<style scoped></style>

View File

@ -252,6 +252,7 @@
// //
const confirmHandler = async (isContinue = false) => { const confirmHandler = async (isContinue = false) => {
try { try {
drawerLoading.value = true;
const formCopy = cloneDeep(fieldForm.value); const formCopy = cloneDeep(fieldForm.value);
formCopy.scene = route.query.type; formCopy.scene = route.query.type;
@ -287,13 +288,13 @@
if (id) { if (id) {
params.id = id; params.id = id;
} }
await addOrUpdate(params); const res = await addOrUpdate(params);
Message.success(isEdit.value ? t('common.updateSuccess') : t('common.newSuccess')); Message.success(isEdit.value ? t('common.updateSuccess') : t('common.newSuccess'));
if (!isContinue) { if (!isContinue) {
handleDrawerCancel(); handleDrawerCancel();
} }
resetForm(); resetForm();
emit('success', isEdit.value); emit('success', isEdit.value, res.id);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {
@ -302,15 +303,16 @@
}; };
const fieldDefaultValues = ref<FormItemModel[]>([]); const fieldDefaultValues = ref<FormItemModel[]>([]);
function userFormFiledValidate(cb: () => Promise<any>) { function userFormFiledValidate(cb: () => Promise<any>) {
fieldFormRef.value?.validate((errors: undefined | Record<string, ValidatedError>) => { fieldFormRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
if (errors) { if (errors) {
return; return;
} }
if (showOptionsSelect.value) {
batchFormRef.value?.formValidate(async (list: any) => { batchFormRef.value?.formValidate(async (list: any) => {
try { try {
drawerLoading.value = true; drawerLoading.value = true;
fieldDefaultValues.value = [...list]; fieldDefaultValues.value = [...list];
if (showOptionsSelect) { if (showOptionsSelect.value) {
fieldForm.value.options = (batchFormRef.value?.getFormResult() || []).map((item: any) => { fieldForm.value.options = (batchFormRef.value?.getFormResult() || []).map((item: any) => {
return { return {
...item, ...item,
@ -326,6 +328,9 @@
drawerLoading.value = false; drawerLoading.value = false;
} }
}); });
} else {
await cb();
}
}); });
} }
@ -342,21 +347,6 @@
// //
const fieldOptions = ref<fieldIconAndNameModal[]>([]); const fieldOptions = ref<fieldIconAndNameModal[]>([]);
//
const getFieldDetail = async (id: string) => {
try {
const fieldDetail = await detail(id);
fieldDefaultValues.value = fieldDetail.options.map((item: any) => {
return {
...item,
};
});
} catch (error) {
console.log(error);
}
};
// //
const getSpecialHandler = (itemType: FormItemType): FormItemType => { const getSpecialHandler = (itemType: FormItemType): FormItemType => {
switch (itemType) { switch (itemType) {
@ -377,16 +367,30 @@
} }
}; };
//
const getFieldDetail = async (id: string) => {
try {
const fieldDetail = await detail(id);
fieldForm.value = {
...fieldDetail,
type: getSpecialHandler(fieldDetail.type),
};
fieldDefaultValues.value = fieldDetail.options.map((item: any) => {
return {
...item,
};
});
} catch (error) {
console.log(error);
}
};
// //
const editHandler = (item: AddOrUpdateField) => { const editHandler = (item: AddOrUpdateField) => {
showDrawer.value = true; showDrawer.value = true;
isMultipleSelectMember.value = item.type === 'MULTIPLE_MEMBER'; isMultipleSelectMember.value = item.type === 'MULTIPLE_MEMBER';
if (item.id) { if (item.id) {
getFieldDetail(item.id); getFieldDetail(item.id);
fieldForm.value = {
...item,
type: getSpecialHandler(item.type),
};
} }
}; };

View File

@ -281,11 +281,15 @@ export const getTotalFieldOptionList = (totalData: DefinedFieldItem[]) => {
formRules: [ formRules: [
{ {
...currentFormRules, ...currentFormRules,
title: item.name,
effect: {
required: false,
},
props: { ...currentFormRules.props, options: selectOptions }, props: { ...currentFormRules.props, options: selectOptions },
}, },
], ],
fApi: null, fApi: null,
required: item.internal, required: false,
}; };
}); });
}; };
@ -305,6 +309,9 @@ export const getCustomDetailFields = (totalData: DefinedFieldItem[], customField
return { return {
...it, ...it,
value: customFields[currentCustomFieldIndex].defaultValue, value: customFields[currentCustomFieldIndex].defaultValue,
effect: {
required: item.required,
},
}; };
}); });
const formItem = item; const formItem = item;

View File

@ -1,368 +0,0 @@
<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"
class="max-w-[732px]"
:disabled="templateForm?.internal"
></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="1000"
:placeholder="t('system.orgTemplate.resDescription')"
:auto-size="{ minRows: 1 }"
style="resize: vertical"
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="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
class="max-w-[732px]"
asterisk-position="end"
>
<a-input
v-model="defectForm.name"
:max-length="255"
:placeholder="t('system.orgTemplate.defectNamePlaceholder')"
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:raw="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 './templateManagementTable.vue';
import PreviewTemplate from './viewTemplate.vue';
import {
createOrganizeTemplateInfo,
getFieldList,
getOrganizeTemplateInfo,
updateOrganizeTemplateInfo,
} 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 { SettingRouteEnum } from '@/enums/routeEnum';
import { getCardList, getCustomDetailFields, getTemplateName, getTotalFieldOptionList } from './fieldSetting';
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const appStore = useAppStore();
const currentOrgId = computed(() => appStore.currentOrgId);
const { setState } = useLeaveUnSaveTip();
setState(false);
const title = ref('');
const loading = ref(false);
const initTemplateForm: ActionTemplateManage = {
id: '',
name: '',
remark: '',
scopeId: currentOrgId.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 getOrganizeTemplateInfo(route.query.id as string);
const { name, customFields, systemFields } = res;
templateForm.value = {
...res,
name: route.params.mode === 'copy' ? `copy_${name}` : 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 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
);
}
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('system.orgTemplate.editTemplateType', {
type: getTemplateName('organization', route.query.type as string),
});
getClassifyField();
} else {
title.value = t('system.orgTemplate.createTemplateType', {
type: getTemplateName('organization', route.query.type as string),
});
}
});
const defectForm = ref<Record<string, any>>({}); //
//
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: currentOrgId.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 updateOrganizeTemplateInfo(params);
Message.success(t('system.orgTemplate.updateSuccess'));
} else {
await createOrganizeTemplateInfo(params);
Message.success(t('system.orgTemplate.addSuccess'));
}
if (isContinueFlag.value) {
resetForm();
} else {
await sleep(300);
router.push({ name: SettingRouteEnum.SETTING_ORGANIZATION_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;
}
//
const updateHandler = (flag: boolean) => {
isEditField.value = flag;
selectFiled.value = selectData.value;
getClassifyField();
};
//
watch(
() => systemFieldData.value,
(val) => {
if (val) {
systemFieldData.value.forEach((item) => {
defectForm.value[item.fieldId] = item.defaultValue;
});
}
},
{ deep: true }
);
onMounted(() => {
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

@ -230,10 +230,12 @@
await loadList(); await loadList();
}; };
const routeName = ref<string>('');
// //
const createTemplate = () => { const createTemplate = () => {
router.push({ router.push({
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL, name: routeName.value,
query: { query: {
type: route.query.type, type: route.query.type,
}, },
@ -246,7 +248,7 @@
// //
const editTemplate = (id: string) => { const editTemplate = (id: string) => {
router.push({ router.push({
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL, name: routeName.value,
query: { query: {
id, id,
type: route.query.type, type: route.query.type,
@ -260,7 +262,7 @@
// //
const copyTemplate = (id: string) => { const copyTemplate = (id: string) => {
router.push({ router.push({
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL, name: routeName.value,
query: { query: {
id, id,
type: route.query.type, type: route.query.type,
@ -307,6 +309,13 @@
onMounted(() => { onMounted(() => {
fetchData(); fetchData();
updateColumns(); updateColumns();
if (route.query.type === 'FUNCTIONAL') {
routeName.value = SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_CASE_DETAIL;
} else if (route.query.type === 'API') {
routeName.value = SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_API_DETAIL;
} else {
routeName.value = SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_BUG_DETAIL;
}
}); });
</script> </script>

View File

@ -1,296 +0,0 @@
<template>
<MsBaseTable v-bind="propsRes" ref="tableRef" v-on="propsEvent">
<template #name="{ record }">
<div class="flex items-center">
<MsIcon v-if="!record.internal" :type="getIconType(record.type)?.iconName || ''" size="16" />
<span class="ml-2">{{ record.name }}</span>
<MsTag v-if="record.internal" size="small" class="ml-2">{{ t('system.orgTemplate.isSystem') }}</MsTag>
</div>
</template>
<template #apiFieldId="{ record }">
<a-input
v-model="record.apiFieldId"
:max-length="255"
class="min-w-[200px] max-w-[300px]"
:placeholder="t('system.orgTemplate.apiInputPlaceholder')"
allow-clear
/>
</template>
<template #defaultValue="{ record }">
<div class="form-create-wrapper w-full">
<MsFormCreate v-model:api="record.fApi" :rule="record.formRules" :option="configOptions" />
</div>
</template>
<template #required="{ record }">
<a-checkbox v-model="record.required"></a-checkbox>
</template>
<template #operation="{ record }">
<div class="flex flex-row flex-nowrap">
<MsButton class="!mr-0" @click="editField(record)">{{ t('system.orgTemplate.edit') }}</MsButton>
<a-divider v-if="!record.internal" direction="vertical" />
<MsButton v-if="!record.internal" class="!mr-0" @click="deleteSelectedField(record)">{{
t('system.orgTemplate.delete')
}}</MsButton>
</div>
</template>
</MsBaseTable>
<!-- 添加字段到模板抽屉 -->
<AddFieldToTemplateDrawer
ref="fieldSelectRef"
v-model:visible="showDrawer"
:total-data="(totalData as DefinedFieldItem[])"
:table-select-data="(selectList as DefinedFieldItem[])"
:mode="props.mode"
@confirm="confirmHandler"
@update-data="updateFieldHandler"
/>
<a-button class="mt-3 px-0" type="text" @click="addField">
<template #icon>
<icon-plus class="text-[14px]" />
</template>
{{ t('system.orgTemplate.createField') }}
</a-button>
<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 type { DefinedFieldItem } from '@/models/setting/template';
import { TableKeyEnum } from '@/enums/tableEnum';
import { getIconType } from './fieldSetting';
const { t } = useI18n();
const props = withDefaults(
defineProps<{
mode: 'organization' | 'project';
enableThirdPart: boolean; //
data: DefinedFieldItem[]; //
selectData: Record<string, any>[]; //
}>(),
{
enableThirdPart: false,
}
);
const emit = defineEmits(['update:select-data', 'update']);
const route = useRoute();
const columns: MsTableColumn = [
{
title: 'system.orgTemplate.name',
slotName: 'name',
dataIndex: 'name',
width: 300,
fixed: 'left',
showDrag: true,
showInTable: true,
showTooltip: true,
},
{
title: 'system.orgTemplate.defaultValue',
dataIndex: 'defaultValue',
slotName: 'defaultValue',
width: 300,
showDrag: true,
showInTable: true,
},
{
title: 'system.orgTemplate.required',
dataIndex: 'required',
slotName: 'required',
width: 180,
showDrag: true,
showInTable: true,
},
{
title: 'system.orgTemplate.description',
dataIndex: 'remark',
showDrag: true,
showInTable: true,
showTooltip: true,
},
{
title: 'system.orgTemplate.operation',
slotName: 'operation',
dataIndex: 'operation',
fixed: 'right',
width: 200,
showInTable: true,
showDrag: false,
},
];
function getApiColumns() {
return {
title: 'system.orgTemplate.apiFieldId',
dataIndex: 'apiFieldId',
slotName: 'apiFieldId',
showDrag: true,
showInTable: true,
width: 300,
};
}
const tableRef = ref();
const { propsRes, propsEvent, setProps } = useTable(undefined, {
tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_FIELD,
columns,
scroll: { x: '1800px' },
selectable: false,
noDisable: true,
size: 'default',
showSetting: false,
showPagination: false,
enableDrag: true,
});
const configOptions = ref({
resetBtn: false,
submitBtn: false,
on: false,
form: {
layout: 'vertical',
labelAlign: 'left',
},
row: {
gutter: 0,
},
wrap: {
'asterisk-position': 'end',
'validate-trigger': ['change'],
'hide-label': true,
'hide-asterisk': true,
},
});
const showDrawer = ref<boolean>(false);
const totalData = ref<DefinedFieldItem[]>([]);
watchEffect(() => {
totalData.value = props.data;
});
const selectList = ref<DefinedFieldItem[]>([]);
//
const confirmHandler = (dataList: DefinedFieldItem[]) => {
selectList.value = dataList;
};
const showFieldDrawer = ref<boolean>(false);
const fieldDrawerRef = ref();
//
const editField = (record: DefinedFieldItem) => {
showFieldDrawer.value = true;
fieldDrawerRef.value.editHandler(record);
};
//
const addField = async () => {
showDrawer.value = true;
};
//
const updateFieldHandler = async (isEdit: boolean) => {
emit('update', isEdit);
};
//
const deleteSelectedField = (record: DefinedFieldItem) => {
selectList.value = selectList.value.filter((item) => item.id !== record.id);
};
watch(
() => selectList.value,
(val) => {
if (val) {
emit('update:select-data', val);
}
},
{ deep: true }
);
watch(
() => props.selectData,
(val) => {
if (val) {
selectList.value = val as DefinedFieldItem[];
setProps({ data: val });
}
},
{ immediate: true }
);
watch(
() => props.data,
(val) => {
totalData.value = val;
}
);
// API
watch(
() => props.enableThirdPart,
(val) => {
if (val && route.query.type === 'BUG') {
const result = [...columns.slice(0, 1), getApiColumns(), ...columns.slice(1)];
tableRef.value.initColumn(result);
} else {
tableRef.value.initColumn(columns);
}
}
);
//
const dragTableData = computed(() => {
return propsRes.value.data;
});
watch(
() => dragTableData.value,
(val) => {
selectList.value = dragTableData.value as DefinedFieldItem[];
emit('update:select-data', val);
}
);
</script>
<style scoped lang="less">
.form-create-wrapper {
:deep(.arco-form-item) {
margin-bottom: 0 !important;
}
:deep(.arco-picker) {
width: 100% !important;
}
}
.system-flag {
background: var(--color-text-n8);
@apply ml-2 rounded p-1 text-xs;
}
</style>

View File

@ -189,4 +189,16 @@ export default {
'system.orgTemplate.caseTemplateManagement': 'Use case template management', 'system.orgTemplate.caseTemplateManagement': 'Use case template management',
'system.orgTemplate.apiTemplateManagement': 'Interface template management', 'system.orgTemplate.apiTemplateManagement': 'Interface template management',
'system.orgTemplate.bugTemplateManagement': 'Defect template management', 'system.orgTemplate.bugTemplateManagement': 'Defect template management',
'system.orgTemplate.templateTypeName': ' {type}name',
'system.orgTemplate.createCaseTemplate': 'Create a use case template',
'system.orgTemplate.updateCaseTemplate': 'Update the use case template',
'system.orgTemplate.createApiTemplate': 'Creating Api Template',
'system.orgTemplate.updateApiTemplate': 'Update Api Template',
'system.orgTemplate.createDefectTemplate': 'Create defect templates',
'system.orgTemplate.updateDefectTemplate': 'Update defect templates',
'system.orgTemplate.enableApiAlert':
'After connecting to the third-party platform, you need to fill in the field API for the custom field. When using the template, the API field is not displayed',
'system.orgTemplate.pleaseEnterAPITip': 'Please enter the field API',
'system.orgTemplate.apiFieldNotEmpty': 'The field API cannot be empty',
'system.orgTemplate.selectThirdPlatType': 'Please select the third party platform',
}; };

View File

@ -180,4 +180,15 @@ export default {
'system.orgTemplate.caseTemplateManagement': '用例模板管理', 'system.orgTemplate.caseTemplateManagement': '用例模板管理',
'system.orgTemplate.apiTemplateManagement': '接口模板管理', 'system.orgTemplate.apiTemplateManagement': '接口模板管理',
'system.orgTemplate.bugTemplateManagement': '缺陷模板管理', 'system.orgTemplate.bugTemplateManagement': '缺陷模板管理',
'system.orgTemplate.templateTypeName': ' {type}名称',
'system.orgTemplate.createCaseTemplate': '创建用例模板',
'system.orgTemplate.updateCaseTemplate': '更新用例模板',
'system.orgTemplate.createApiTemplate': '创建接口模板',
'system.orgTemplate.updateApiTemplate': '更新接口模板',
'system.orgTemplate.createDefectTemplate': '创建缺陷模板',
'system.orgTemplate.updateDefectTemplate': '更新缺陷模板',
'system.orgTemplate.enableApiAlert': '对接第三方平台后,自定义字段需要填写字段 API使用模版时不显示 API 字段',
'system.orgTemplate.pleaseEnterAPITip': '请输入字段API',
'system.orgTemplate.apiFieldNotEmpty': '字段 API 不能为空',
'system.orgTemplate.selectThirdPlatType': '请选择三方平台',
}; };