feat(系统管理): 组织模板管理页面和封装表单级联formCreate

This commit is contained in:
xinxin.wu 2023-10-25 12:42:55 +08:00 committed by Craftsman
parent 8a711405eb
commit a7538c1fad
41 changed files with 2593 additions and 256 deletions

View File

@ -9,20 +9,45 @@ import {
GetFieldDetailUrl,
GetOrganizeTemplateDetailUrl,
GetOrganizeTemplateUrl,
GetProjectTemplateDetailUrl,
isEnableTemplateUrl,
SetOrganizeTemplateUrl,
UpdateFieldUrl,
UpdateOrganizeTemplateUrl,
UpdateProjectTemplateUrl,
} from '@/api/requrls/setting/template';
import { CommonList, TableQueryParams } from '@/models/common';
import type { AddOrUpdateField, DefinedFieldItem, OrganizeTemplateItem } from '@/models/setting/template';
import type {
ActionTemplateManage,
AddOrUpdateField,
DefinedFieldItem,
OrganizeTemplateItem,
} from '@/models/setting/template';
/** *
*
*/
// 获取模版列表(组织)
export function getOrganizeTemplateList(organizationId: string, scene: string) {
return MSR.get<OrganizeTemplateItem[]>({ url: GetOrganizeTemplateUrl, params: `/${organizationId}/${scene}` });
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}` });
}
// 创建模板列表(组织)
export function createOrganizeTemplateInfo(data: ActionTemplateManage) {
return MSR.post({ url: `${CreateOrganizeTemplateUrl}`, data });
}
// 编辑模板列表(组织)
export function updateOrganizeTemplateInfo(data: ActionTemplateManage) {
return MSR.post({ url: `${UpdateOrganizeTemplateUrl}`, data });
}
// 是否启用组织XX模板
export function isEnableTemplate(organizationId: string, scene: string) {
return MSR.get<boolean>({ url: `${isEnableTemplateUrl}/${organizationId}/${scene}` });
}
/** *

View File

@ -29,6 +29,8 @@ export const GetOrganizeTemplateDetailUrl = '/organization/template/get';
export const DeleteOrganizeTemplateUrl = '/organization/template/delete';
// 关闭组织模板,开启项目模版
export const EnableOrOffTemplateUrl = '/organization/template/disable';
// 是否启用组织模板
export const isEnableTemplateUrl = '/organization/template/is-enable';
// 系统设置-组织-自定义字段

View File

@ -209,6 +209,7 @@
.arco-select-view,
.arco-select-view-single,
.arco-select {
width: 100%;
border: 1px solid var(--color-text-input-border);
background-color: var(--color-text-fff);
&:not(:disabled):hover {

View File

@ -13,6 +13,7 @@
<MsTransfer
v-model="target"
:data="treeList"
:title="[t('system.user.batchOptional'), t('system.user.batchChosen')]"
:tree-filed="{
key: 'id',
title: 'name',

View File

@ -47,7 +47,7 @@
}, true);
function jumpTo(name: RouteRecordName) {
router.push({ name });
router.push({ name, query: route.query });
}
</script>

View File

@ -0,0 +1,163 @@
import { FormRule } from '@form-create/arco-design';
// 表单字段使用
export const INPUT = {
type: 'input',
title: '',
field: 'fieldName',
value: '',
props: {
placeholder: '请输入',
},
};
export const SELECT = {
type: 'SearchSelect',
field: 'fieldName',
title: '',
value: '',
options: [],
props: {
multiple: false,
placeholder: '请选择',
options: [],
},
};
export const MULTIPLE_SELECT = {
type: 'SearchSelect',
field: 'fieldName',
title: '',
value: [],
options: [],
props: {
multiple: true,
placeholder: '请选择',
options: [],
},
};
export const RADIO = {
type: 'radio',
field: 'fieldName',
title: '',
value: '',
options: [],
};
export const CHECKBOX = {
type: 'checkbox',
field: 'fieldName',
title: '',
value: [],
options: [],
};
export const MEMBER = {
type: 'SearchSelect',
field: 'fieldName',
title: '',
value: '',
options: [],
props: {
multiple: false,
placeholder: '请选择',
},
};
export const MULTIPLE_MEMBER = {
type: 'SearchSelect',
field: 'fieldName',
title: '',
value: '',
options: [],
props: {
multiple: true,
placeholder: '请选择',
options: [],
},
};
export const DATE = {
type: 'DatePicker',
field: 'fieldName',
title: '',
value: '',
props: {
'placeholder': '请选择',
'format': 'YYYY/MM/DD',
'show-time': false,
},
};
export const DATETIME = {
type: 'DatePicker',
field: 'fieldName',
title: '',
value: '',
props: {
'placeholder': '请选择',
'format': 'YYYY/MM/DD HH:mm:ss',
'show-time': true,
},
};
export const FLOAT = {
type: 'InputNumber',
field: 'fieldName',
title: '',
value: 0,
props: {
placeholder: '请输入',
},
};
export const INT = {
type: 'InputNumber',
field: 'fieldName',
title: '',
value: 0,
props: {
precision: 0,
placeholder: '请输入',
},
};
export const MULTIPLE_INPUT = {
type: 'a-input-tag',
field: 'fieldName',
title: '',
value: [],
props: {
placeholder: '请选择',
},
};
export const TEXTAREA = {
type: 'a-textarea',
field: 'fieldName',
title: '',
value: '',
props: {
'placeholder': '请输入',
'auto-size': {
minRows: 1,
maxRows: 3,
},
},
};
export const FieldTypeFormRules: Record<string, FormRule> = {
INPUT,
SELECT,
MULTIPLE_SELECT,
RADIO,
CHECKBOX,
MEMBER,
MULTIPLE_MEMBER,
DATE,
DATETIME,
INT,
FLOAT,
MULTIPLE_INPUT,
TEXTAREA,
};

View File

@ -0,0 +1,109 @@
<template>
<FormCreate v-model:api="formApi" :rule="formRules" :option="props.options || option"></FormCreate>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { debounce } from 'lodash-es';
import PassWord from './formcreate-password.vue';
import SearchSelect from './searchSelect.vue';
import useFormCreateStore from '@/store/modules/form-create/form-create';
import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
import type { FormItem } from './types';
import { FormRuleItem } from './types';
import formCreate, { FormRule } from '@form-create/arco-design';
const formCreateStore = useFormCreateStore();
formCreate.component('PassWord', PassWord);
formCreate.component('SearchSelect', SearchSelect);
const FormCreate = formCreate.$form();
const option = {
resetBtn: false, //
submitBtn: false,
on: false, // on
form: {
layout: 'vertical',
labelAlign: 'left',
},
//
row: {
gutter: 0,
},
wrap: {
'asterisk-position': 'end',
'validate-trigger': ['change'],
},
};
//
const props = defineProps<{
options?: any; //
formRule: FormItem[]; //
formCreateKey: FormCreateKeyEnum[keyof FormCreateKeyEnum]; // Key
}>();
const formApi = ref<any>({});
const formRules = ref<FormRule | undefined>([]);
//
const cascadeItem = computed(() => {
const currentFormCreateRules = formCreateStore.formCreateRuleMap.get(props.formCreateKey);
// cascadeitem
if (currentFormCreateRules) {
const cascade = currentFormCreateRules
.map((item: FormRuleItem) => item.link)
.filter((item) => item)
.flatMap((flatItem: any) => flatItem);
// linkitem
return currentFormCreateRules.filter((item: FormRuleItem) => {
return cascade.indexOf(item.field) > -1;
});
}
});
//
const getOptionsRequest = debounce((val: FormRuleItem) => {
//
// link
const totalFormList = formCreateStore.formCreateRuleMap.get(props.formCreateKey);
if (totalFormList) {
const resultItem = totalFormList.find(
(item) => item.link && (item.link as string[]).indexOf(val.field as string) > -1
);
if (resultItem) formCreateStore.getOptions(val, props.formCreateKey, resultItem, formApi.value);
}
}, 300);
watch(
cascadeItem,
(val) => {
// options
if (val) {
val.forEach((item) => {
if (item.value) {
getOptionsRequest(item);
}
});
}
},
{ deep: true, immediate: false }
);
watchEffect(() => {
formCreateStore.setInitFormCreate(props.formCreateKey, props.formRule);
formCreateStore.initFormCreateFormRules(props.formCreateKey);
formRules.value = formCreateStore.formCreateRuleMap.get(props.formCreateKey);
});
defineExpose({
formApi, // form-createAPI
});
</script>
<style scoped></style>
@/store/modules/form-create/form-create

View File

@ -6,10 +6,12 @@
import { ref, watch, watchEffect } from 'vue';
import PassWord from './formcreate-password.vue';
import SearchSelect from './searchSelect.vue';
import formCreate, { FormRule } from '@form-create/arco-design';
formCreate.component('PassWord', PassWord);
formCreate.component('SearchSelect', SearchSelect);
const FormCreate = formCreate.$form();
const props = defineProps<{
@ -21,7 +23,7 @@
const emits = defineEmits<{
(e: 'update:api', val: any): void;
}>();
const formApi = ref({});
const formApi = ref<any>({});
watchEffect(() => {
formApi.value = props.api;
@ -32,6 +34,18 @@
emits('update:api', val);
}
);
const formRules = ref<FormRule | undefined>([]);
watchEffect(() => {
formRules.value = props.rule;
});
watch(
() => props.rule,
(val) => {
formRules.value = val;
formApi.value?.refresh();
}
);
</script>
<style scoped></style>

View File

@ -0,0 +1,94 @@
<template>
<a-select
v-model="selectValue"
:placeholder="t(props.placeholder || 'common.pleaseSelect')"
allow-search
:multiple="props.multiple"
@search="searchHandler"
>
<a-option v-for="opt of optionsList" :key="opt.value" :value="opt.value">{{ opt.label }}</a-option>
</a-select>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { debounce } from 'lodash-es';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const props = withDefaults(
defineProps<{
optionMethod?: string; //
inputSearch?: boolean; //
modelValue: string[] | string | undefined; //
keyword?: string; //
formValue?: Record<string, any>; //
options?: { label: string; value: string }[];
multiple?: boolean; //
placeholder?: string;
}>(),
{
inputSearch: false,
multiple: false,
}
);
const emit = defineEmits(['update:model-value']);
const selectValue = ref([]);
const optionsList = ref<{ label: string; value: string }[]>([]);
//
const innerKeyword = ref<string | undefined>('');
async function getOptionsList() {
if (props.inputSearch && props.optionMethod) {
try {
setTimeout(() => {
// console.log('');
optionsList.value = [
{
value: '111',
label: '测试测试测试111111',
},
];
}, 1000);
} catch (error) {
console.log(error);
}
}
}
const searchHandler = debounce(async (inputVal: string) => {
innerKeyword.value = inputVal;
getOptionsList();
}, 300);
watch(
() => props.modelValue,
(val) => {
selectValue.value = val as any;
}
);
watch(
() => props.keyword,
(val) => {
if (val) {
innerKeyword.value = val;
getOptionsList();
}
}
);
watch(
() => selectValue.value,
(val) => {
emit('update:model-value', val);
}
);
watchEffect(() => {
if (props.options) optionsList.value = props.options;
});
</script>
<style scoped></style>

View File

@ -0,0 +1,77 @@
import { FieldRule } from '@arco-design/web-vue';
import { FormRule } from '@form-create/arco-design';
export type FormItemType =
| 'INPUT'
| 'TEXTAREA'
| 'SELECT'
| 'MULTIPLE_SELECT'
| 'RADIO'
| 'CHECKBOX'
| 'MEMBER'
| 'MULTIPLE_MEMBER'
| 'DATE'
| 'DATETIME'
| 'INT'
| 'FLOAT'
| 'MULTIPLE_INPUT'
| 'INT'
| 'FLOAT'
| 'NUMBER';
// 表单选项
export interface FormItemComplexCommonConfig {
options: { label: string; value: string | number; disabled: boolean }[]; // 选择器、复选框组、单选框组选项列表
optionsRemoteMethodKey?: string; // 选择器、复选框组、单选框组选项列表远程搜索或初始化方法名
}
export interface FormItemDefaultOptions {
text: string;
value: string;
}
export type FormRuleItem = FormRule & {
props: Record<string, any>;
};
// 表单配置项
export interface FormItem {
type: FormItemType;
name: string; // 表单项名称,作为唯一标志 --field
label: string; // 表单项文本 --- title
// 选择器的值绑定为Record<string, any>,避免携带远程搜索时默认选中的选项不在 options 列表中,所以还需要设置 fallbackOptions
value: string | number | boolean | string[] | number[] | Record<string, any> | Record<string, any>[];
subDesc?: string; // 表单项描述,在表单项下方显示
inputSearch?: boolean; // 是否支持远程搜索
tooltip?: ''; // 表单后边的提示info
instructionsIcon?: ''; // 是否有图片在表单后边展示
optionMethod?: string; // 请求检索的方法 两个参数 表单项的所有值
options?: FormItemDefaultOptions[];
required: boolean;
validate?: FieldRule[];
// 表单联动配置
couplingConfig?: {
// 联动类型visible显示隐藏disabled禁用启用filterOptions过滤选项disabledOptions禁用选项initOptions初始化选项。都由联动的表单项触发
type: 'visible' | 'disabled' | 'filterOptions' | 'disabledOptions' | 'initOptions'; // 目前初始化选项
cascade: string; // 联动表单项名称
matchRule: 'same' | 'includes' | 'excludes' | RegExp; // 联动匹配规则same值相同includes值包含excludes值不包含 RegExp自定义匹配正则表达式 // 场景 目前只考虑等于情况
}[];
// 表单布局
wrap?: Record<string, any>;
}
interface FomItemSelect extends FormItemComplexCommonConfig {
selectMultiple?: boolean; // 选择器是否多选
selectMultipleLimit?: [number, number]; // 选择器多选时最少和最多可选项数,如:[1, 3]表示最少选1项最多选3项[0, 3]表示最多选3项可不选[1, 0]表示最少选1项不限制最大可选数
}
interface FomItemCheckbox extends FormItemComplexCommonConfig {
checkedAll?: boolean; // 复选框组是否支持全选
checkedAllLabel?: string; // 复选框组全选选项文本
checkedMax?: number; // 复选框组最多可选项数
direction?: 'horizontal' | 'vertical'; // 单选框组选项排列方向,默认为 'horizontal'
}
interface FormItemRadio extends FormItemComplexCommonConfig {
type?: 'radio' | 'button'; // 单选框组选项排列方式,默认为 'radio'
direction?: 'horizontal' | 'vertical'; // 单选框组选项排列方向,默认为 'horizontal'
}

View File

@ -18,8 +18,8 @@
class="mr-[2px] text-xl text-[rgb(var(--danger-6))]"
/>
</slot>
<span :class="titleClass">
{{ characterLimit(props.title) || '' }}
<span :class="[titleClass]">
{{ props.title || '' }}
</span>
</div>
<!-- 描述展示 -->
@ -64,7 +64,7 @@
{{ props.cancelText || t('common.cancel') }}
</a-button>
<a-button type="primary" size="mini" :loading="props.loading" @click="handleConfirm">
{{ props.isDelete ? t('common.remove') : props.okText || t('common.confirm') }}
{{ t(props.okText) || t('common.confirm') }}
</a-button>
</div>
</template>
@ -79,7 +79,6 @@
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import { useI18n } from '@/hooks/useI18n';
import { characterLimit } from '@/utils';
import type { FieldRule, FormInstance } from '@arco-design/web-vue';
@ -113,6 +112,7 @@
{
type: 'warning',
isDelete: true, // pop
okText: 'common.remove',
}
);
const emits = defineEmits<{
@ -167,7 +167,7 @@
//
const titleClass = computed(() => {
return props.isDelete
? 'ml-2 font-[14px] text-[var(--color-text-1)]'
? 'ml-2 font-medium text-[var(--color-text-1)] text-[14px]'
: 'mb-[8px] font-medium text-[var(--color-text-1)] text-[14px]';
});

View File

@ -135,7 +135,13 @@
</script>
<template>
<div style="height: 100vh" class="flex">
<div style="height: 140px" class="rich-wrapper flex w-full">
<RichTextEditor v-if="editor" :editor="editor" :locale="locale" />
</div>
</template>
<style scoped lang="less">
.rich-wrapper {
border: 1px solid var(--color-text-n8);
}
</style>

View File

@ -21,7 +21,7 @@ export interface Pagination {
const appStore = useAppStore();
const tableStore = useTableStore();
export default function useTableProps<T>(
loadListFunc: (v?: TableQueryParams | any) => Promise<CommonList<MsTableDataItem<T>> | MsTableDataItem<T>>,
loadListFunc?: (v?: TableQueryParams | any) => Promise<CommonList<MsTableDataItem<T>> | MsTableDataItem<T>>,
props?: Partial<MsTableProps<T>>,
// 数据处理的回调函数
dataTransform?: (item: TableData) => (TableData & T) | any,
@ -161,40 +161,42 @@ export default function useTableProps<T>(
const { current, pageSize } = propsRes.value.msPagination as Pagination;
const { rowKey, selectorStatus, excludeKeys } = propsRes.value;
try {
setLoading(true);
const data = await loadListFunc({
current,
pageSize,
sort: sortItem.value,
filter: filterItem.value,
keyword: keyword.value,
...loadListParams.value,
});
const tmpArr = data.list;
propsRes.value.data = tmpArr.map((item: MsTableDataItem<T>) => {
if (item.updateTime) {
item.updateTime = dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss');
}
if (item.createTime) {
item.createTime = dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss');
}
if (dataTransform) {
item = dataTransform(item);
}
if (selectorStatus === SelectAllEnum.ALL) {
if (!excludeKeys.has(item[rowKey])) {
setTableSelected(item[rowKey]);
if (loadListFunc) {
setLoading(true);
const data = await loadListFunc({
current,
pageSize,
sort: sortItem.value,
filter: filterItem.value,
keyword: keyword.value,
...loadListParams.value,
});
const tmpArr = data.list;
propsRes.value.data = tmpArr.map((item: MsTableDataItem<T>) => {
if (item.updateTime) {
item.updateTime = dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss');
}
if (item.createTime) {
item.createTime = dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss');
}
if (dataTransform) {
item = dataTransform(item);
}
if (selectorStatus === SelectAllEnum.ALL) {
if (!excludeKeys.has(item[rowKey])) {
setTableSelected(item[rowKey]);
}
}
return item;
});
if (data.total === 0) {
setTableErrorStatus('empty');
} else {
setTableErrorStatus(false);
}
return item;
});
if (data.total === 0) {
setTableErrorStatus('empty');
} else {
setTableErrorStatus(false);
setPagination({ current: data.current, total: data.total });
return data;
}
setPagination({ current: data.current, total: data.total });
return data;
} catch (err) {
setTableErrorStatus('error');
} finally {
@ -208,26 +210,28 @@ export default function useTableProps<T>(
} else {
// 没分页的情况下直接调用loadListFunc
try {
setLoading(true);
const data = await loadListFunc({ keyword: keyword.value, ...loadListParams.value });
if (data.length === 0) {
setTableErrorStatus('empty');
return;
if (loadListFunc) {
setLoading(true);
const data = await loadListFunc({ keyword: keyword.value, ...loadListParams.value });
if (data.length === 0) {
setTableErrorStatus('empty');
return;
}
setTableErrorStatus(false);
propsRes.value.data = data.map((item: MsTableDataItem<T>) => {
if (item.updateTime) {
item.updateTime = dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss');
}
if (item.createTime) {
item.createTime = dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss');
}
if (dataTransform) {
item = dataTransform(item);
}
return item;
});
return data;
}
setTableErrorStatus(false);
propsRes.value.data = data.map((item: MsTableDataItem<T>) => {
if (item.updateTime) {
item.updateTime = dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss');
}
if (item.createTime) {
item.createTime = dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss');
}
if (dataTransform) {
item = dataTransform(item);
}
return item;
});
return data;
} catch (err) {
// eslint-disable-next-line no-console
console.log(err);

View File

@ -0,0 +1,6 @@
export enum FormCreateKeyEnum {
ORGANIZE_TEMPLATE = 'OrganizeTemplate',
ORGANIZE_TEMPLATE_PREVIEW_TEMPLATE = 'OrganizeTemplatePreview',
}
export default {};

View File

@ -61,6 +61,8 @@ export enum SettingRouteEnum {
SETTING_ORGANIZATION_PROJECT = 'settingOrganizationProject',
SETTING_ORGANIZATION_TEMPLATE = 'settingOrganizationTemplate',
SETTING_ORGANIZATION_TEMPLATE_FILED_SETTING = 'settingOrganizationTemplateFiledSetting',
SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT = 'settingOrganizationTemplateManagement',
SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL = 'settingOrganizationTemplateManagementDetail',
SETTING_ORGANIZATION_SERVICE = 'settingOrganizationService',
SETTING_ORGANIZATION_LOG = 'settingOrganizationLog',
}

View File

@ -17,7 +17,10 @@ export enum TableKeyEnum {
PROJECT_MEMBER = 'projectMember',
PROJECT_USER_GROUP = 'projectUserGroup',
ORGANIZATION_MEMBER = 'organizationMember',
ORGANIZATION_TEMPLATE = 'organizationTemplate',
ORGANIZATION_TEMPLATE_FIELD_SETTING = 'organizationTemplateFieldSetting',
ORGANIZATION_TEMPLATE_MANAGEMENT = 'organizationTemplateManagement',
ORGANIZATION_TEMPLATE_MANAGEMENT_FIELD = 'organizationTemplateManagementField',
ORGANIZATION_TEMPLATE_MANAGEMENT_STEP = 'organizationTemplateManagementStep',
ORGANIZATION_PROJECT = 'organizationProject',
ORGANIZATION_PROJECT_USER_DRAWER = 'organizationProjectUserDrawer',
FILE_MANAGEMENT_FILE = 'fileManagementFile',

View File

@ -1,3 +1,4 @@
// 模版展示字段icon
export enum TemplateIconEnum {
INPUT = 'icon-icon_input', // 输入框
TEXTAREA = 'icon-icon_style_one', // 文本
@ -14,5 +15,16 @@ export enum TemplateIconEnum {
FLOAT = 'icon-icon_pound', // 数字-浮点型
MULTIPLE_INPUT = 'icon-icon_tag', // 多值输入框、
NUMBER = 'icon-icon_pound', // 数字
SYSTEM = 'icon-icon_pound',
}
// 模版列表图标卡片icon
export enum TemplateCardEnum {
FUNCTIONAL = 'caseTemplate', // 用例模版
API = 'api_ui_Template', // ui模板
UI = 'api_ui_Template', // API模板
TEST_PLAN = 'testPlanTemplate', // 测试计划模板
BUG = 'defectTemplate', // 缺陷模板
}
export default {};

View File

@ -60,4 +60,5 @@ export default {
'common.allSelect': 'Select All',
'common.setting': 'Setting',
'common.resetDefault': 'Restore default',
'common.pleaseSelect': 'Please Select',
};

View File

@ -54,6 +54,9 @@ export default {
'menu.settings.organization.project': 'Project',
'menu.settings.organization.template': '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.serviceIntegration': 'Service Integration',
'menu.settings.organization.log': 'Log',
'navbar.action.locale': 'Switch to English',

View File

@ -64,4 +64,5 @@ export default {
'common.resetDefault': '恢复默认',
'common.tagPlaceholder': '添加标签回车结束',
'common.batchModify': '批量修改',
'common.pleaseSelect': '请选择',
};

View File

@ -54,6 +54,9 @@ export default {
'menu.settings.organization.serviceIntegration': '服务集成',
'menu.settings.organization.template': '模版',
'menu.settings.organization.templateFieldSetting': '字段设置',
'menu.settings.organization.templateManagementList': '模版列表',
'menu.settings.organization.templateManagementDetail': '创建模版',
'menu.settings.organization.templateManagementEdit': '更新模板',
'menu.settings.organization.log': '日志',
'navbar.action.locale': '切换为中文',
...sys,

View File

@ -1,6 +1,10 @@
import { LocationQueryValue } from 'vue-router';
// 模版管理
import type { FormItem, FormItemType, FormRuleItem } from '@/components/pure/ms-form-create/types';
import { FormRule } from '@form-create/arco-design';
// 模版管理(组织)
export interface OrganizeTemplateItem {
id: string;
name: string;
@ -18,21 +22,33 @@ export interface OrganizeTemplateItem {
}
export type SeneType = 'FUNCTIONAL' | 'BUG' | 'API' | 'UI' | 'TEST_PLAN' | LocationQueryValue[] | LocationQueryValue;
export interface FieldOptions {
fieldId?: string;
value: string | string[] | number | number[];
text: string;
internal?: boolean;
}
// 自定义字段
export interface DefinedFieldItem {
id: string;
name: string;
scene: SeneType; // 使用场景
type: string;
type: FormItemType; // 表单类型
remark: string;
internal: boolean; // 是否是内置字段
scopeType: string; // 组织或项目级别字段PROJECT, ORGANIZATION
createTime: number;
updateTime: number;
createUser: string;
refId: string; // 项目字段所关联的组织字段ID
enableOptionKey: boolean; // 是否需要手动输入选项key
refId: string | null; // 项目字段所关联的组织字段ID
enableOptionKey: boolean | null; // 是否需要手动输入选项key
scopeId: string; // 组织或项目ID
options: FieldOptions[] | null;
required?: boolean | undefined;
fApi?: any; // 表单值
formRules?: FormRuleItem[] | FormItem[] | FormRule[]; // 表单列表
}
// 创建自定义字段
@ -42,12 +58,55 @@ export interface FieldOption {
text: string;
}
// 新增 || 编辑参数
export interface AddOrUpdateField {
id?: string;
name: string;
scene: SeneType; // 使用场景
type: string;
type: FormItemType;
remark: string; // 备注
scopeId: string; // 组织或项目ID
options?: FieldOption[];
}
export interface fieldIconAndNameModal {
key: string;
iconName: string; // 图标名称
label: string; // 对应标签
}
// 模板管理列表(组织)
export interface OrdTemplateManagement {
id: string;
name: string;
remark: string; // 描述
internal: boolean; // 是否是系统模板
updateTime: number;
createTime: number;
createUser: string; // 创建人
scopeType: string; // 组织或项目级别字段
scopeId: string; // 组织或项目ID
enableThirdPart: boolean; // 是否开启api字段名配置
enableDefault: boolean; // 是否是默认模板
refId: string; // 项目模板所关联的组织模板ID
scene: string; // 使用场景
}
// 创建模板& 更新模板管理项
export interface CustomField {
fieldId: string;
required: boolean; // 是否必填
apiFieldId: string; // api字段名
defaultValue: string; // 默认值
}
export interface ActionTemplateManage {
id?: string;
name: string;
remark: string;
scopeId: string;
enableThirdPart?: boolean; // 是否开启api字段名配置
scene: SeneType;
customFields: CustomField[];
}

View File

@ -188,7 +188,7 @@ const Setting: AppRouteRecordRaw = {
isTopMenu: true,
},
},
// 模版字段设置
// 模板列表-模版字段设置
{
path: 'templateFiledSetting',
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_FILED_SETTING,
@ -209,6 +209,52 @@ const Setting: AppRouteRecordRaw = {
],
},
},
// 模版管理-模版列表
{
path: 'templateManagement',
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT,
component: () => import('@/views/setting/organization/template/components/templateManagement.vue'),
meta: {
locale: 'menu.settings.organization.templateManagementList',
roles: ['*'],
breadcrumbs: [
{
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE,
locale: 'menu.settings.organization.template',
},
{
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT,
locale: 'menu.settings.organization.templateManagementList',
editLocale: 'menu.settings.organization.templateManagementList',
},
],
},
},
// 模板列表-模板管理-创建&编辑模版
{
path: 'templateManagementDetail',
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL,
component: () => import('@/views/setting/organization/template/components/templateDetail.vue'),
meta: {
locale: 'menu.settings.organization.templateManagementDetail',
roles: ['*'],
breadcrumbs: [
{
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE,
locale: 'menu.settings.organization.template',
},
{
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT,
locale: 'menu.settings.organization.templateManagementList',
},
{
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT,
locale: 'menu.settings.organization.templateManagementDetail',
editLocale: 'menu.settings.organization.templateManagementEdit',
},
],
},
},
{
path: 'log',
name: SettingRouteEnum.SETTING_ORGANIZATION_LOG,

View File

@ -0,0 +1,109 @@
import { defineStore } from 'pinia';
import { FieldTypeFormRules } from '@/components/pure/ms-form-create/form-create';
import type { FormItem, FormRuleItem } from '@/components/pure/ms-form-create/types';
import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
const useFormCreateStore = defineStore('form-create', {
persist: false,
state: (): {
formRuleMap: Map<FormCreateKeyEnum[keyof FormCreateKeyEnum], FormItem[]>;
formCreateRuleMap: Map<FormCreateKeyEnum[keyof FormCreateKeyEnum], FormRuleItem[]>;
} => ({
formRuleMap: new Map(),
formCreateRuleMap: new Map(),
}),
actions: {
// 存储外边传递初始化数据格式存储form-item
setInitFormCreate(key: FormCreateKeyEnum[keyof FormCreateKeyEnum], formRule: FormItem[]) {
this.formRuleMap = new Map();
this.formRuleMap.set(key, formRule);
},
// 根据不同的类型初始化数据
initFormCreateFormRules(key: FormCreateKeyEnum[keyof FormCreateKeyEnum]) {
const currentFormRule = this.formRuleMap.get(key);
// 处理数据结构
const result = currentFormRule?.map((item: FormItem) => {
// 当前类型
let fieldType;
const currentTypeForm = Object.keys(FieldTypeFormRules).find(
(formItemType: any) => item.type.toUpperCase() === formItemType
);
if (currentTypeForm) {
fieldType = FieldTypeFormRules[currentTypeForm].type;
const options = item?.options;
const currentOptions = options?.map((optionsItem) => {
return {
label: optionsItem.text,
value: optionsItem.value,
};
});
return {
type: fieldType, // 表单类型
field: item.name, // 字段
title: item.label, // label 表单标签
value: FieldTypeFormRules[currentTypeForm].value, // 目前的值
effect: {
required: item.required, // 是否必填
},
// 级联关联到某一个form上 可能存在多个级联
options: !item.optionMethod ? currentOptions : [],
link: item.couplingConfig?.map((cascadeItem: any) => cascadeItem.cascade),
rule: item.validate || [],
// 梳理表单所需要属性
props: {
...FieldTypeFormRules[currentTypeForm].props,
'tooltip': item.tooltip,
// 表单后边展示图片
'instructionsIcon': item.instructionsIcon,
// 下拉选项请求 必须是开启远程搜索才有该方法
'subDesc': item.subDesc,
// 级联匹配规则
'couplingConfig': { ...item.couplingConfig },
'optionMethod': item.inputSearch && item.optionMethod ? item.optionMethod : '',
'inputSearch': item.inputSearch,
'allow-search': item.inputSearch,
'keyword': '',
'modelValue': item.value,
'options': currentOptions,
},
};
}
return {};
});
if (result && result.length) {
this.setInitdRules(key, result as FormRuleItem[]);
}
},
// 初始化好了的格式给formCreate
setInitdRules(key: FormCreateKeyEnum[keyof FormCreateKeyEnum], result: FormRuleItem[]) {
this.formCreateRuleMap.set(key, result);
},
/** **
* @description
* @param key: 对应Map的Key
* @param item: 当前对应关联项-options
* @param formValueApi: 当前表单值实例可以获取表单的当前已经设置的值
*/
async getOptions(
val: FormRuleItem,
key: FormCreateKeyEnum[keyof FormCreateKeyEnum],
cascadeItem: FormRuleItem,
formValueApi: any
) {
const formValue = formValueApi.formData();
// 设置自定义属性给到searchSelect
const formCreateRuleArr = this.formCreateRuleMap.get(key);
const formCreateItem = formCreateRuleArr?.find((items: FormRuleItem) => cascadeItem.field === items.field);
if (formCreateItem) {
formCreateItem.props.keyword = val.value;
formCreateItem.props.formValue = formValue;
}
},
},
getters: {},
});
export default useFormCreateStore;

View File

@ -0,0 +1,38 @@
import { defineStore } from 'pinia';
import { isEnableTemplate } from '@/api/modules/setting/template';
import type { DefinedFieldItem } from '@/models/setting/template';
import useAppStore from '../app';
const useTemplateStore = defineStore('template', {
persist: true,
state: (): { templateStatus: Record<string, boolean>; previewList: DefinedFieldItem[] } => ({
templateStatus: {
FUNCTIONAL: false,
API: false,
UI: false,
TEST_PLAN: false,
BUG: false,
},
previewList: [],
}),
actions: {
// 模板列表的状态
setStatus() {
// 需要调整接口
// const appStore = useAppStore();
// Object.keys(this.templateStatus).forEach(async (item) => {
// const sceneStatus = await isEnableTemplate(appStore.currentOrgId, item);
// this.templateStatus[item] = sceneStatus;
// });
},
// 预览存储表数据
setPreviewHandler(filedData: DefinedFieldItem[]) {
this.previewList = filedData;
},
},
});
export default useTemplateStore;

View File

@ -1,9 +1,165 @@
<template>
<div>项目版本 waiting for development </div>
<MsFormCreate :form-rule="formRules" :form-create-key="FormCreateKeyEnum.ORGANIZE_TEMPLATE" />
<br />
<br />
</template>
<script setup lang="ts">
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 { FormCreateKeyEnum } from '@/enums/formCreateEnum';
const formRules = ref<FormItem[]>([
{
type: 'INPUT',
name: 'name',
label: '姓名',
value: '',
subDesc: '请输入姓名',
required: true,
},
{
type: 'MULTIPLE_SELECT',
name: 'gender',
label: '性别',
value: [],
subDesc: '请选择性别',
optionMethod: 'getGenderOptions',
inputSearch: true,
required: true,
couplingConfig: [
{
type: 'initOptions',
cascade: 'name',
matchRule: 'includes',
},
],
options: [
{
value: '1',
text: '单选',
},
{
value: '2',
text: '多选',
},
],
},
{
type: 'INPUT',
name: 'member',
label: '成员',
value: '',
required: true,
},
{
type: 'MULTIPLE_MEMBER',
name: 'multiple_member',
label: '多选成员',
value: [],
required: true,
options: [
{
value: '1',
text: '单选',
},
{
value: '2',
text: '多选',
},
],
},
{
type: 'INT',
name: 'birthday',
label: '出生日期',
value: 0,
subDesc: '请选择出生日期',
required: true,
},
{
type: 'DATE',
name: 'birthday',
label: '出生日期',
value: '',
subDesc: '请选择出生日期',
required: true,
},
{
type: 'MULTIPLE_MEMBER',
name: 'radio',
label: '单选',
value: '',
subDesc: '请选择出生日期',
inputSearch: true,
required: true,
options: [
{
value: '1',
text: '单选',
},
{
value: '2',
text: '多选',
},
],
couplingConfig: [
{
type: 'initOptions',
cascade: 'member',
matchRule: 'includes',
},
],
},
{
type: 'SELECT',
name: 'selectName',
label: '单选',
value: '',
subDesc: '请选择出生日期',
inputSearch: true,
required: true,
options: [
{
value: '1',
text: '单选',
},
{
value: '2',
text: '多选',
},
],
couplingConfig: [
{
type: 'initOptions',
cascade: 'member',
matchRule: 'includes',
},
],
},
]);
const options = ref({
resetBtn: false,
submitBtn: false,
on: false,
form: {
layout: 'vertical',
labelAlign: 'left',
},
row: {
gutter: 0,
},
wrap: {
'asterisk-position': 'end',
'validate-trigger': ['change'],
},
});
</script>
<style scoped></style>

View File

@ -0,0 +1,295 @@
<template>
<MsDrawer
v-model:visible="showAddDrawer"
:title="t('system.orgTemplate.createField')"
:ok-text="t('system.orgTemplate.save')"
:ok-loading="drawerLoading"
:width="800"
unmount-on-close
:show-continue="false"
@confirm="handleDrawerConfirm"
@cancel="handleDrawerCancel"
>
<div class="panel-wrapper">
<div class="inner-wrapper">
<div class="optional-field">
<div class="optional-header">
<div class="font-medium">{{ t('system.orgTemplate.optionalField') }}</div>
<a-checkbox :model-value="checkedAll" :indeterminate="indeterminate" @change="handleChangeAll">
<span class="font-medium text-[var(--color-text-3)]">{{ t('system.orgTemplate.selectAll') }}</span>
</a-checkbox>
</div>
<div class="optional-panel p-4">
<div class="mb-2 font-medium text-[var(--color-text-3)]">{{ t('system.orgTemplate.systemField') }}</div>
<div>
<a-checkbox-group v-model="selectSystemData">
<a-grid :cols="4" :col-gap="16" :row-gap="4">
<a-grid-item v-for="field in systemField" :key="field.id">
<a-checkbox :value="field.id" :disabled="field.internal"
><a-tooltip :content="field.name">
<div class="checkbox">{{ field.name }}</div></a-tooltip
></a-checkbox
>
</a-grid-item>
</a-grid>
</a-checkbox-group>
</div>
<div class="my-2 mt-8 font-medium text-[var(--color-text-3)]">{{
t('system.orgTemplate.customField')
}}</div>
<a-checkbox-group v-model="selectCustomField">
<a-grid :cols="4" :col-gap="16" :row-gap="4">
<a-grid-item v-for="field in customField" :key="field.id">
<a-checkbox :value="field.id"
><a-tooltip :content="field.name">
<div class="checkbox">{{ field.name }}</div></a-tooltip
></a-checkbox
>
</a-grid-item>
</a-grid>
</a-checkbox-group>
<EditFieldDrawer ref="fieldDrawerRef" v-model:visible="showFieldDrawer" @success="okHandler" />
<a-button class="mt-1 px-0" type="text" @click="createField">
<template #icon>
<icon-plus class="text-[14px]" />
</template>
{{ t('system.orgTemplate.addField') }}
</a-button>
</div>
</div>
<div class="selected-field">
<div class="optional-header">
<div class="font-medium">{{ t('system.orgTemplate.selectedField') }}</div>
<MsButton @click="clearHandler">{{ t('system.orgTemplate.clear') }}</MsButton>
</div>
<div class="selected-list p-4">
<MsList
:data="selectedFields"
:bordered="false"
:split="false"
item-border
no-hover
:virtual-list-props="{
height: 'calc(100vh - 226px)',
}"
>
<template #item="{ item }">
<div class="selected-item">
<a-tooltip :content="item.name">
<span class="one-line-text w-[270px]">{{ item.name }}</span>
</a-tooltip>
<icon-close
v-if="!item.internal"
:style="{ 'font-size': '14px' }"
class="cursor-pointer text-[var(--color-text-3)]"
@click="removeSelectedField(item.id)"
/>
</div>
</template>
</MsList>
</div>
</div>
</div>
</div>
</MsDrawer>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsList from '@/components/pure/ms-list/index.vue';
import EditFieldDrawer from './editFieldDrawer.vue';
import { useI18n } from '@/hooks/useI18n';
import type { DefinedFieldItem } from '@/models/setting/template';
const { t } = useI18n();
const showAddDrawer = ref<boolean>(false);
const drawerLoading = ref<boolean>(false);
const props = defineProps<{
visible: boolean;
selectedData: DefinedFieldItem[]; //
systemData: DefinedFieldItem[]; //
customData: DefinedFieldItem[]; //
}>();
const emit = defineEmits(['confirm', 'update:visible', 'update']);
//
const systemField = ref<DefinedFieldItem[]>([]);
//
const customField = ref<DefinedFieldItem[]>([]);
//
const totalTemplateField = ref<DefinedFieldItem[]>([]);
//
const selectSystemData = ref<string[]>([]);
//
const selectCustomField = ref<string[]>([]);
//
const indeterminate = ref<boolean>(false);
const checkedAll = ref<boolean>(false);
//
const handleChangeAll = (value: any) => {
indeterminate.value = false;
if (value) {
checkedAll.value = true;
selectSystemData.value = systemField.value.map((item) => item.id);
selectCustomField.value = customField.value.map((item) => item.id);
} else {
checkedAll.value = false;
selectSystemData.value = [];
selectCustomField.value = [];
}
};
//
const selectedFields = ref<DefinedFieldItem[]>([]);
const getSelectedField = () => {
const totalSelectIds = [...selectSystemData.value, ...selectCustomField.value];
selectedFields.value = totalTemplateField.value.filter((item) => totalSelectIds.indexOf(item.id) > -1);
};
// &&
const isCheckedAll = () => {
totalTemplateField.value = [...props.systemData, ...props.customData];
systemField.value = props.systemData;
customField.value = props.customData;
const systemAll = selectSystemData.value.length === systemField.value.length;
const customAll = selectCustomField.value.length === customField.value.length;
if (systemAll && customAll) {
checkedAll.value = true;
indeterminate.value = false;
} else if (selectCustomField.value.length === 0 || selectCustomField.value.length === 0) {
checkedAll.value = false;
indeterminate.value = false;
} else {
checkedAll.value = false;
indeterminate.value = true;
}
};
watchEffect(() => {
isCheckedAll();
//
getSelectedField();
});
//
const removeSelectedField = (id: string) => {
selectSystemData.value = selectSystemData.value.filter((item) => item !== id);
selectCustomField.value = selectCustomField.value.filter((item) => item !== id);
};
//
const showFieldDrawer = ref<boolean>(false);
const createField = () => {
showFieldDrawer.value = true;
};
//
const clearHandler = () => {
selectSystemData.value = [];
selectCustomField.value = [];
};
const handleDrawerCancel = () => {
showAddDrawer.value = false;
};
const handleDrawerConfirm = () => {
emit('confirm', selectedFields.value);
showAddDrawer.value = false;
};
const okHandler = () => {
emit('update');
};
//
const showSelectField = () => {
const selectIds = props.selectedData.map((item) => item.id);
//
selectSystemData.value = systemField.value.filter((item) => selectIds.indexOf(item.id) > -1).map((item) => item.id);
//
selectCustomField.value = customField.value
.filter((item) => selectIds.indexOf(item.id) > -1)
.map((item) => item.id);
};
watch(
() => showAddDrawer.value,
(val) => {
emit('update:visible', val);
}
);
watch(
() => props.visible,
(val) => {
showAddDrawer.value = val;
}
);
defineExpose({
removeSelectedField,
showSelectField,
});
</script>
<style scoped lang="less">
.panel-wrapper {
width: 100%;
height: 100%;
.inner-wrapper {
display: flex;
overflow: hidden;
width: 100%;
height: 100%;
border: 1px solid var(--color-text-n8);
//
.optional-field {
flex-grow: 1;
height: 100%;
border-right: 1px solid var(--color-text-n8);
.optional-header {
padding: 0 16px;
height: 54px;
color: var(--color-text-3);
background: var(--color-text-n9);
@apply flex items-center justify-between;
}
.optional-panel {
.checkbox {
width: 74px;
white-space: nowrap;
@apply overflow-hidden text-ellipsis;
}
}
}
//
.selected-field {
width: 272px;
.optional-header {
padding: 0 16px;
height: 54px;
color: var(--color-text-3);
background: var(--color-text-n9);
@apply flex items-center justify-between;
}
.selected-list {
.selected-item {
height: 36px;
background: var(--color-bg-3);
@apply mb-2 flex items-center justify-between rounded px-2;
}
}
}
}
}
</style>

View File

@ -22,14 +22,14 @@
<a-input
v-model:model-value="fieldForm.name"
:placeholder="t('system.orgTemplate.fieldNamePlaceholder')"
:max-length="250"
:max-length="255"
show-word-limit
></a-input>
</a-form-item>
<a-form-item field="remark" :label="t('system.orgTemplate.description')" asterisk-position="end">
<a-textarea
v-model="fieldForm.remark"
:max-length="250"
:max-length="255"
:placeholder="t('system.orgTemplate.resDescription')"
:auto-size="{
maxRows: 1,
@ -42,11 +42,12 @@
class="w-[260px]"
:placeholder="t('system.orgTemplate.fieldTypePlaceholder')"
allow-clear
:disabled="isEdit"
@change="fieldChangeHandler"
>
<a-option v-for="item of fieldOptions" :key="item.value" :value="item.id">
<a-option v-for="item of fieldOptions" :key="item.key" :value="item.key">
<div class="flex items-center"
><MsIcon :type="item.value" class="mx-2" /> <span>{{ item.label }}</span></div
><MsIcon :type="item.iconName" class="mx-2" /> <span>{{ item.label }}</span></div
>
</a-option>
</a-select>
@ -81,9 +82,7 @@
<a-form-item
v-if="showDateOrNumber"
field="selectFormat"
:label="
fieldForm.type === 'NUMBER' ? t('system.orgTemplate.numberFormat') : t('system.orgTemplate.dateFormat')
"
:label="fieldType === 'NUMBER' ? t('system.orgTemplate.numberFormat') : t('system.orgTemplate.dateFormat')"
asterisk-position="end"
>
<a-select
@ -91,6 +90,7 @@
class="w-[260px]"
:placeholder="t('system.orgTemplate.formatPlaceholder')"
allow-clear
:disabled="isEdit"
>
<a-option v-for="item of showDateOrNumber" :key="item.value" :value="item.value">
<div class="flex items-center">{{ item.label }}</div>
@ -108,6 +108,7 @@
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import type { FormItemType } from '@/components/pure/ms-form-create/types';
import MsBatchForm from '@/components/business/ms-batch-form/index.vue';
import type { FormItemModel, MsBatchFormInstance } from '@/components/business/ms-batch-form/types';
@ -116,10 +117,9 @@
import { useAppStore } from '@/store';
import { getGenerateId } from '@/utils';
import type { AddOrUpdateField } from '@/models/setting/template';
import { TemplateIconEnum } from '@/enums/templateEnum';
import type { AddOrUpdateField, fieldIconAndNameModal } from '@/models/setting/template';
import { getFieldType } from './fieldSetting';
import { fieldIconAndName, getFieldType } from './fieldSetting';
const { t } = useI18n();
const route = useRoute();
@ -136,7 +136,7 @@
const fieldFormRef = ref<FormInstance>();
const initFieldForm: AddOrUpdateField = {
name: '',
type: '',
type: 'INPUT',
remark: '',
scopeId: '',
scene: 'FUNCTIONAL',
@ -144,19 +144,19 @@
};
const fieldForm = ref<AddOrUpdateField>({ ...initFieldForm });
const isEdit = computed(() => !!fieldForm.value.id);
const selectFormat = ref(''); //
const selectFormat = ref<FormItemType>(); //
const isMultipleSelectMember = ref<boolean | undefined>(false); //
const fieldType = ref(''); //
const fieldType = ref<FormItemType>(); //
//
const showOptionsSelect = computed(() => {
const showOptionsType = ['RADIO', 'CHECKBOX', 'SELECT', 'MULTIPLE_SELECT'];
return showOptionsType.includes(fieldType.value);
const showOptionsType: FormItemType[] = ['RADIO', 'CHECKBOX', 'SELECT', 'MULTIPLE_SELECT'];
return showOptionsType.includes(fieldType.value as FormItemType);
});
//
const showDateOrNumber = computed(() => {
return getFieldType(fieldType.value);
if (fieldType.value) return getFieldType(fieldType.value);
});
// -1.
@ -174,9 +174,9 @@
const resetForm = () => {
fieldForm.value = { ...initFieldForm };
selectFormat.value = '';
selectFormat.value = undefined;
isMultipleSelectMember.value = false;
fieldType.value = '';
fieldType.value = undefined;
batchFormRef.value?.resetForm();
};
@ -190,20 +190,23 @@
const confirmHandler = async (isContinue: boolean) => {
try {
drawerLoading.value = true;
if (fieldType.value) {
fieldForm.value.type = fieldType.value;
}
fieldForm.value.scene = route.query.type;
fieldForm.value = {
...fieldForm.value,
scopeId: appStore.currentOrgId,
type: fieldType.value,
};
fieldForm.value.scopeId = appStore.currentOrgId;
//
if (selectFormat.value) {
fieldForm.value.type = selectFormat.value;
}
// ||
if (isMultipleSelectMember.value) {
fieldForm.value.type = isMultipleSelectMember.value ? 'MULTIPLE_MEMBER' : 'MEMBER';
}
//
if (selectFormat.value) {
fieldForm.value.type = selectFormat.value;
@ -212,6 +215,7 @@
//
const { id, name, options, scopeId, scene, type, remark } = fieldForm.value;
const params: AddOrUpdateField = { name, options, scopeId, scene, type, remark };
if (isEdit) {
params.id = id;
}
@ -247,59 +251,7 @@
};
//
const fieldOptions = ref([
{
id: 'TEXTAREA',
label: t('system.orgTemplate.textarea'),
value: TemplateIconEnum.TEXTAREA,
},
{
id: 'INPUT',
label: t('system.orgTemplate.input'),
value: TemplateIconEnum.INPUT,
},
{
id: 'RADIO',
label: t('system.orgTemplate.radio'),
value: TemplateIconEnum.RADIO,
},
{
id: 'CHECKBOX',
label: t('system.orgTemplate.checkbox'),
value: TemplateIconEnum.CHECKBOX,
},
{
id: 'SELECT',
label: t('system.orgTemplate.select'),
value: TemplateIconEnum.SELECT,
},
{
id: 'MULTIPLE_SELECT',
label: t('system.orgTemplate.multipleSelect'),
value: TemplateIconEnum.MULTIPLE_SELECT,
},
{
id: 'MEMBER',
label: t('system.orgTemplate.member'),
value: TemplateIconEnum.MEMBER,
},
{
id: 'DATE',
label: t('system.orgTemplate.date'),
value: TemplateIconEnum.DATE,
},
{
id: 'NUMBER',
label: t('system.orgTemplate.number'),
value: TemplateIconEnum.NUMBER,
},
{
id: 'MULTIPLE_INPUT',
label: t('system.orgTemplate.multipleInput'),
value: TemplateIconEnum.MULTIPLE_INPUT,
},
]);
const fieldOptions = ref<fieldIconAndNameModal[]>([]);
const fieldDefaultValues = ref([]);
//
@ -317,7 +269,7 @@
};
//
const getSpecialHandler = (itemType: string): string => {
const getSpecialHandler = (itemType: FormItemType): FormItemType => {
switch (itemType) {
case 'INT':
selectFormat.value = itemType;
@ -336,7 +288,7 @@
};
//
const isEditHandler = (item: AddOrUpdateField) => {
const editHandler = (item: AddOrUpdateField) => {
showDrawer.value = true;
isMultipleSelectMember.value = item.type === 'MULTIPLE_MEMBER';
if (isEdit && item.id) {
@ -345,7 +297,7 @@
...item,
type: getSpecialHandler(item.type),
};
fieldType.value = getSpecialHandler(item.type);
fieldType.value = fieldForm.value.type;
}
};
@ -368,9 +320,13 @@
showDrawer.value = val;
}
);
onMounted(() => {
const excludeOptions = ['MULTIPLE_MEMBER', 'DATETIME', 'SYSTEM', 'INT', 'FLOAT'];
fieldOptions.value = fieldIconAndName.filter((item: any) => excludeOptions.indexOf(item.key) < 0);
});
defineExpose({
isEditHandler,
editHandler,
});
</script>

View File

@ -1,8 +1,18 @@
import { ref } from 'vue';
import dayjs from 'dayjs';
import type { FormItemType } from '@/components/pure/ms-form-create/types';
import { useI18n } from '@/hooks/useI18n';
import useTemplateStore from '@/store/modules/setting/template';
import type { fieldIconAndNameModal } from '@/models/setting/template';
import { TemplateCardEnum, TemplateIconEnum } from '@/enums/templateEnum';
const { t } = useI18n();
const templateStore = useTemplateStore();
// 字段类型-日期
const dateOptions = ref([
const dateOptions = [
{
label: dayjs().format('YYYY/MM/DD'),
value: 'DATE',
@ -11,10 +21,10 @@ const dateOptions = ref([
label: dayjs().format('YYYY/MM/DD HH:mm:ss'),
value: 'DATETIME',
},
]);
];
// 字段类型- 数字
const numberTypeOptions = ref([
const numberTypeOptions = [
{
label: '整数',
value: 'INT',
@ -23,17 +33,141 @@ const numberTypeOptions = ref([
label: '保留小数',
value: 'FLOAT',
},
]);
];
export const getFieldType = (selectFieldType: string) => {
// 获取字段类型是数值 || 日期
export const getFieldType = (selectFieldType: FormItemType) => {
switch (selectFieldType) {
case 'DATE':
return dateOptions.value;
return dateOptions;
case 'NUMBER':
return numberTypeOptions.value;
return numberTypeOptions;
default:
break;
}
};
// 模板列表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,
},
];
// table名称展示图标类型表格展示类型
export const fieldIconAndName: fieldIconAndNameModal[] = [
{
key: 'INPUT',
iconName: TemplateIconEnum.INPUT,
label: t('system.orgTemplate.input'),
},
{
key: 'TEXTAREA',
iconName: TemplateIconEnum.TEXTAREA,
label: t('system.orgTemplate.textarea'),
},
{
key: 'SELECT',
iconName: TemplateIconEnum.SELECT,
label: t('system.orgTemplate.select'),
},
{
key: 'MULTIPLE_SELECT',
iconName: TemplateIconEnum.MULTIPLE_SELECT,
label: t('system.orgTemplate.multipleSelect'),
},
{
key: 'RADIO',
iconName: TemplateIconEnum.RADIO,
label: t('system.orgTemplate.radio'),
},
{
key: 'CHECKBOX',
iconName: TemplateIconEnum.CHECKBOX,
label: t('system.orgTemplate.checkbox'),
},
{
key: 'MEMBER',
iconName: TemplateIconEnum.MEMBER,
label: t('system.orgTemplate.member'),
},
{
key: 'MULTIPLE_MEMBER',
iconName: TemplateIconEnum.MULTIPLE_MEMBER,
label: t('system.orgTemplate.multipleMember'),
},
{
key: 'DATE',
iconName: TemplateIconEnum.DATE,
label: t('system.orgTemplate.date'),
},
{
key: 'DATETIME',
iconName: TemplateIconEnum.DATETIME,
label: t('system.orgTemplate.dateTime'),
},
{
key: 'NUMBER',
iconName: TemplateIconEnum.NUMBER,
label: t('system.orgTemplate.number'),
},
{
key: 'INT',
iconName: TemplateIconEnum.INT,
label: t('system.orgTemplate.number'),
},
{
key: 'FLOAT',
iconName: TemplateIconEnum.FLOAT,
label: t('system.orgTemplate.number'),
},
{
key: 'MULTIPLE_INPUT',
iconName: TemplateIconEnum.MULTIPLE_INPUT,
label: t('system.orgTemplate.multipleInput'),
},
{
key: 'SYSTEM',
iconName: TemplateIconEnum.SYSTEM,
label: '',
},
];
// 获取图标类型
export const getIconType = (iconType: FormItemType) => {
return fieldIconAndName.find((item) => item.key === iconType);
};
export default {};

View File

@ -1,9 +1,11 @@
<template>
<MsCard :has-breadcrumb="true" simple>
<a-alert class="mb-6" type="warning">{{ t('system.orgTemplate.enableDescription') }}</a-alert>
<a-alert class="mb-6" :type="isEnable ? 'warning' : 'info'">{{
isEnable ? 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>
<a-button v-else type="primary" :disabled="false" @click="fieldHandler('add')">
<a-button v-else type="primary" :disabled="totalData.length > 20" @click="fieldHandler('add')">
{{ t('system.orgTemplate.addField') }}
</a-button>
<a-input-search
@ -17,13 +19,22 @@
</div>
<MsBaseTable v-bind="propsRes" ref="tableRef" v-on="propsEvent">
<template #name="{ record }">
<MsIcon v-if="getIconType(record.type).type !== 'system'" :type="getIconType(record.type).iconName" size="16" />
<MsIcon v-if="!record.internal" :type="getIconType(record.type)?.iconName || ''" size="16" />
<span class="ml-2">{{ record.name }}</span>
<span v-if="record.internal" class="system-flag">{{ t('system.orgTemplate.isSystem') }}</span>
</template>
<template #operation="{ record }">
<div class="flex flex-row flex-nowrap">
<MsButton class="!mr-0" @click="fieldHandler('edit', record)">{{ t('system.orgTemplate.edit') }}</MsButton>
<MsPopConfirm
type="error"
:title="t('system.orgTemplate.updateTip', { name: characterLimit(record.name) })"
:sub-title-tip="t('system.orgTemplate.updateDescription')"
:ok-text="t('system.orgTemplate.confirm')"
@confirm="handleOk(record)"
>
<MsButton class="!mr-0">{{ t('system.orgTemplate.edit') }}</MsButton></MsPopConfirm
>
<a-divider v-if="!record.internal" direction="vertical" />
<MsTableMoreAction
v-if="!record.internal"
@ -33,7 +44,7 @@
</div>
</template>
<template #fieldType="{ record }">
<span>{{ getIconType(record.type)['type'] }}</span>
<span>{{ getIconType(record.type)?.label }}</span>
</template>
</MsBaseTable>
<EditFieldDrawer ref="fieldDrawerRef" v-model:visible="showDrawer" @success="successHandler" />
@ -41,12 +52,16 @@
</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 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';
@ -58,11 +73,15 @@
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import { useAppStore, useTableStore } from '@/store';
import useTemplateStore from '@/store/modules/setting/template';
import { characterLimit } from '@/utils';
import type { AddOrUpdateField } from '@/models/setting/template';
import type { AddOrUpdateField, SeneType } from '@/models/setting/template';
import { TableKeyEnum } from '@/enums/tableEnum';
import { TemplateIconEnum } from '@/enums/templateEnum';
import { cardList, getIconType } from './fieldSetting';
const templateStore = useTemplateStore();
const { t } = useI18n();
const tableStore = useTableStore();
@ -109,11 +128,10 @@
showDrag: false,
},
];
tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE, fieldColumns, 'drawer');
tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_FIELD_SETTING, fieldColumns, 'drawer');
const { propsRes, propsEvent, loadList, setLoadListParams, setProps } = useTable(getFieldList, {
tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE,
tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_FIELD_SETTING,
scroll: { x: '1000px' },
selectable: false,
noDisable: true,
@ -124,73 +142,40 @@
});
const keyword = ref('');
const totalData = ref([]);
//
//
const searchFiled = async () => {
try {
const totalData = await getFieldList({ organizationId: currentOrd, scene: route.query.type });
const filterData = totalData.filter((item: AddOrUpdateField) => item.name.includes(keyword.value));
totalData.value = await getFieldList({ organizationId: currentOrd, scene: route.query.type });
const filterData = totalData.value.filter((item: AddOrUpdateField) => item.name.includes(keyword.value));
setProps({ data: filterData });
} catch (error) {
console.log(error);
}
};
const scene = ref<SeneType>('');
const fetchData = async () => {
const scene = route.query.type;
scene.value = route.query.type;
setLoadListParams({ organizationId: currentOrd, scene });
await loadList();
};
const tableRef = ref();
const isEnable = ref<boolean>(false); //
const isEnable = ref<boolean>(templateStore.templateStatus[scene.value as string]); //
//
const isEnableOperation = () => {
if (isEnable.value) {
const noOperationColumn = fieldColumns.slice(0, -1);
tableStore.setColumns(TableKeyEnum.ORGANIZATION_TEMPLATE, noOperationColumn, 'drawer');
tableStore.setColumns(TableKeyEnum.ORGANIZATION_TEMPLATE_FIELD_SETTING, noOperationColumn, 'drawer');
tableRef.value.initColumn();
} else {
tableStore.setColumns(TableKeyEnum.ORGANIZATION_TEMPLATE, fieldColumns, 'drawer');
tableStore.setColumns(TableKeyEnum.ORGANIZATION_TEMPLATE_FIELD_SETTING, fieldColumns, 'drawer');
tableRef.value.initColumn();
}
};
//
const getIconType = (iconType: string) => {
switch (iconType) {
case 'INPUT':
return { iconName: TemplateIconEnum.INPUT, type: t('system.orgTemplate.input') };
case 'TEXTAREA':
return { iconName: TemplateIconEnum.TEXTAREA, type: t('system.orgTemplate.textarea') };
case 'SELECT':
return { iconName: TemplateIconEnum.SELECT, type: t('system.orgTemplate.select') };
case 'MULTIPLE_SELECT':
return { iconName: TemplateIconEnum.MULTIPLE_SELECT, type: t('system.orgTemplate.multipleSelect') };
case 'RADIO':
return { iconName: TemplateIconEnum.RADIO, type: t('system.orgTemplate.radio') };
case 'CHECKBOX':
return { iconName: TemplateIconEnum.CHECKBOX, type: t('system.orgTemplate.checkbox') };
case 'MEMBER':
return { iconName: TemplateIconEnum.MEMBER, type: t('system.orgTemplate.member') };
case 'MULTIPLE_MEMBER':
return { iconName: TemplateIconEnum.MULTIPLE_MEMBER, type: t('system.orgTemplate.multipleMember') };
case 'DATE':
return { iconName: TemplateIconEnum.DATE, type: t('system.orgTemplate.date') };
case 'DATETIME':
return { iconName: TemplateIconEnum.DATE, type: t('system.orgTemplate.dateTime') };
case 'INT':
return { iconName: TemplateIconEnum.NUMBER, type: t('system.orgTemplate.number') };
case 'FLOAT':
return { iconName: TemplateIconEnum.NUMBER, type: t('system.orgTemplate.number') };
case 'MULTIPLE_INPUT':
return { iconName: TemplateIconEnum.MULTIPLE_INPUT, type: t('system.orgTemplate.multipleInput') };
default:
return { type: 'system', iconName: '' };
}
};
const moreActions: ActionsItem[] = [
{
label: 'system.userGroup.delete',
@ -214,7 +199,7 @@
try {
if (record.id) await deleteOrdField(record.id);
Message.success(t('system.user.deleteUserSuccess'));
loadList();
fetchData();
} catch (error) {
console.log(error);
}
@ -235,28 +220,21 @@
const fieldDrawerRef = ref();
const fieldHandler = (type: string, record?: AddOrUpdateField) => {
showDrawer.value = true;
if (type === 'edit' && record) fieldDrawerRef.value.isEditHandler(record);
if (type === 'edit' && record) fieldDrawerRef.value.editHandler(record);
};
const handleOk = (record: AddOrUpdateField) => {
fieldHandler('edit', record);
};
const successHandler = () => {
loadList();
fetchData();
};
const templateList = ref([
{
value: 'FUNCTIONAL',
name: 'system.orgTemplate.caseTemplates',
},
{ value: 'API', name: 'system.orgTemplate.APITemplates' },
{ value: 'UI', name: 'system.orgTemplate.UITemplates' },
{ value: 'TEST_PLAN', name: 'system.orgTemplate.testPlanTemplates' },
{ value: 'BUG', name: 'system.orgTemplate.defectTemplates' },
]);
//
const updateBreadcrumbList = () => {
const { breadcrumbList } = appStore;
const breadTitle = templateList.value.find((item) => item.value === route.query.type);
const breadTitle = cardList.find((item) => item.key === route.query.type);
if (breadTitle) {
breadcrumbList[0].locale = breadTitle.name;
appStore.setBreadcrumbList(breadcrumbList);

View File

@ -0,0 +1,207 @@
<template>
<MsCard
:loading="loading"
:title="title"
:is-edit="isEdit"
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" required>
<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>
<!-- 已有字段表 -->
<TemplateManagementTable ref="templateFieldTableRef" :custom-list="tableFiledDetailList" :is-edit="isEdit" />
</div>
<!-- 预览模式 -->
<PreviewTemplate v-else :select-field="selectFiledToTem" />
</MsCard>
</template>
<script setup lang="ts">
/**
* @description 系统管理-组织-模版-模版管理-创建&编辑
*/
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
import MsCard from '@/components/pure/ms-card/index.vue';
import TemplateManagementTable from './templateManagementTable.vue';
import PreviewTemplate from './viewTemplate.vue';
import {
createOrganizeTemplateInfo,
getOrganizeTemplateInfo,
updateOrganizeTemplateInfo,
} from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n';
import useLeaveUnSaveTip from '@/hooks/useLeaveUnSaveTip';
import { useAppStore } from '@/store';
import useTemplateStore from '@/store/modules/setting/template';
import { sleep } from '@/utils';
import { scrollIntoView } from '@/utils/dom';
import type { ActionTemplateManage, CustomField } from '@/models/setting/template';
import { SettingRouteEnum } from '@/enums/routeEnum';
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const appStore = useAppStore();
const templateStore = useTemplateStore();
useLeaveUnSaveTip();
const title = ref('');
const loading = ref(false);
const initTemplateForm = {
name: '',
remark: '',
};
const templateForm = ref({ ...initTemplateForm });
const tableFiledDetailList = ref([]);
//
const getTemplateInfo = async () => {
try {
loading.value = true;
const res = await getOrganizeTemplateInfo(route.query.id as string);
const { name, remark, customFields, scoped, enableThirdPart, scene } = res;
templateForm.value.name = name;
templateForm.value.remark = remark;
//
tableFiledDetailList.value = customFields;
} catch (error) {
console.log(error);
} finally {
loading.value = false;
}
};
const isEdit = ref(false);
const templateFieldTableRef = ref();
watchEffect(() => {
if (route.query.id) {
title.value = t('menu.settings.organization.templateManagementEdit');
isEdit.value = true;
getTemplateInfo();
} else {
title.value = t('menu.settings.organization.templateManagementDetail');
isEdit.value = false;
}
});
const formRef = ref<FormInstance>();
//
function getTemplateParams(): ActionTemplateManage {
const result: CustomField[] = templateFieldTableRef.value.getCustomFields();
const { name, remark } = templateForm.value;
return {
name,
remark,
customFields: result,
scopeId: appStore.currentOrgId,
scene: route.query.type,
};
}
function resetForm() {
templateForm.value = { ...initTemplateForm };
}
const isContinueFlag = ref(false);
async function save() {
try {
loading.value = true;
const params = getTemplateParams();
if (isEdit.value) {
await updateOrganizeTemplateInfo(params);
Message.success(t('system.resourcePool.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_DETAIL });
}
} catch (error) {
console.log(error);
} finally {
loading.value = false;
}
}
//
async function saveHandler(isContinue = false) {
isContinueFlag.value = isContinue;
formRef.value?.validate(async (errors: Record<string, ValidatedError> | undefined) => {
if (!errors) {
save();
} else {
return scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
}
});
}
const selectFiledToTem = ref([]); //
//
const isPreview = ref<boolean>(true); //
function togglePreview() {
isPreview.value = !isPreview.value;
if (!isPreview.value) {
selectFiledToTem.value = templateFieldTableRef.value.getSelectFiled();
templateStore.setPreviewHandler(selectFiledToTem.value);
}
}
onMounted(() => {
templateFieldTableRef.value.setDefaultField();
});
</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

@ -3,7 +3,7 @@
<div class="innerWrapper">
<div class="content">
<div class="logo-img h-[48px] w-[48px]">
<svg-icon width="36px" height="36px" :name="svgList[props.cardItem.value]"></svg-icon>
<svg-icon width="36px" height="36px" :name="props.cardItem.value"></svg-icon>
</div>
<div class="template-operation">
<div class="flex items-center">
@ -16,12 +16,14 @@
<a-divider direction="vertical" />
</span>
<span class="operation hover:text-[rgb(var(--primary-5))]">
<span>{{ t('system.orgTemplate.TemplateManagement') }}</span> <a-divider direction="vertical" />
<span @click="templateManagement">{{ t('system.orgTemplate.TemplateManagement') }}</span>
<a-divider v-if="props.cardItem.key == 'BUG'" direction="vertical" />
</span>
<span v-if="props.cardItem.value === 'BUG'" class="operation hover:text-[rgb(var(--primary-5))]">
<span>{{ t('system.orgTemplate.workflowSetup') }}</span> <a-divider direction="vertical" />
<span v-if="props.cardItem.key === 'BUG'" class="operation hover:text-[rgb(var(--primary-5))]">
<span>{{ t('system.orgTemplate.workflowSetup') }}</span>
<a-divider v-if="!props.cardItem.enable" direction="vertical" />
</span>
<span class="rounded p-[2px] hover:bg-[rgb(var(--primary-9))]">
<span v-if="!props.cardItem.enable" class="rounded p-[2px] hover:bg-[rgb(var(--primary-9))]">
<MsTableMoreAction :list="moreActions" @select="handleMoreActionSelect"
/></span>
</div>
@ -58,14 +60,6 @@
},
]);
const svgList = ref<Record<string, any>>({
FUNCTIONAL: 'caseTemplate',
API: 'api_ui_Template',
UI: 'api_ui_Template',
TEST_PLAN: 'testPlanTemplate',
BUG: 'defectTemplate',
});
const handleMoreActionSelect = (item: ActionsItem) => {};
//
@ -73,7 +67,16 @@
router.push({
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_FILED_SETTING,
query: {
type: props.cardItem.value,
type: props.cardItem.key,
},
});
};
const templateManagement = () => {
router.push({
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT,
query: {
type: props.cardItem.key,
},
});
};

View File

@ -0,0 +1,210 @@
<template>
<MsCard has-breadcrumb simple>
<a-alert v-if="isEnable" 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>
<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 #name="{ record }">
<span class="ml-2">{{ record.name }}</span>
<span v-if="record.internal" class="system-flag">{{ t('system.orgTemplate.isSystem') }}</span>
</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">{{ 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>
</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 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 { deleteOrdField, getOrganizeTemplateList } 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 { OrdTemplateManagement } from '@/models/setting/template';
import { SettingRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
const route = useRoute();
const { t } = useI18n();
const tableStore = useTableStore();
const appStore = useAppStore();
const templateStore = useTemplateStore();
const { openModal } = useModal();
const isEnable = ref<boolean>(false);
const keyword = ref('');
const currentOrd = appStore.currentOrgId;
const fieldColumns: MsTableColumn = [
{
title: 'system.orgTemplate.columnTemplateName',
slotName: 'name',
dataIndex: 'name',
width: 300,
showDrag: true,
showInTable: true,
},
{
title: 'system.orgTemplate.description',
dataIndex: 'remark',
showDrag: true,
showInTable: true,
},
{
title: 'system.orgTemplate.columnFieldUpdatedTime',
dataIndex: 'updateTime',
showDrag: true,
showInTable: true,
},
{
title: 'system.orgTemplate.operation',
slotName: 'operation',
fixed: 'right',
width: 200,
showInTable: true,
showDrag: false,
},
];
tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT, fieldColumns, 'drawer');
const { propsRes, propsEvent, loadList, setLoadListParams, setProps } = useTable(getOrganizeTemplateList, {
tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT,
scroll: { x: '1000px' },
selectable: false,
noDisable: true,
size: 'default',
showSetting: true,
showPagination: false,
heightUsed: 380,
});
const totalList = ref<OrdTemplateManagement[]>([]);
//
const searchFiled = async () => {
try {
totalList.value = await getOrganizeTemplateList({ organizationId: currentOrd, scene: route.query.type });
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.deleteTitle', { 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 deleteOrdField(record.id);
Message.success(t('system.user.deleteUserSuccess'));
loadList();
} catch (error) {
console.log(error);
}
},
hideCancel: false,
});
};
//
const handleMoreActionSelect = (item: ActionsItem, record: any) => {
if (item.eventTag === 'delete') {
handlerDelete(record);
}
};
const fetchData = async () => {
const scene = route.query.type;
setLoadListParams({ organizationId: currentOrd, scene });
await loadList();
};
//
const createTemplate = () => {
templateStore.setPreviewHandler([]);
router.push({
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL,
query: {
type: route.query.type,
},
});
};
//
const editTemplate = (id: string) => {
router.push({
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL,
query: {
type: route.query.type,
id,
},
});
};
onMounted(() => {
fetchData();
});
</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,290 @@
<template>
<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>
</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"
:system-data="(systemData as DefinedFieldItem[])"
:custom-data="(customData as DefinedFieldItem[])"
:selected-data="(templateStore.previewList as DefinedFieldItem[])"
@confirm="confirmHandler"
@update="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" @success="updateFieldHandler" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import MsButton from '@/components/pure/ms-button/index.vue';
import { FieldTypeFormRules } from '@/components/pure/ms-form-create/form-create';
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 AddFieldToTemplateDrawer from './addFieldToTemplateDrawer.vue';
import EditFieldDrawer from './editFieldDrawer.vue';
import { getFieldList } from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore, useTableStore } from '@/store';
import useTemplateStore from '@/store/modules/setting/template';
import type { CustomField, DefinedFieldItem } from '@/models/setting/template';
import { TableKeyEnum } from '@/enums/tableEnum';
import { getIconType } from './fieldSetting';
const tableStore = useTableStore();
const { t } = useI18n();
const appStore = useAppStore();
const templateStore = useTemplateStore();
const route = useRoute();
const props = defineProps<{
customList: CustomField[]; //
isEdit: boolean; //
}>();
const templateFieldColumns: MsTableColumn = [
{
title: 'system.orgTemplate.name',
slotName: 'name',
dataIndex: 'name',
width: 300,
showDrag: true,
showInTable: true,
},
{
title: 'system.orgTemplate.defaultValue',
dataIndex: 'defaultValue',
slotName: 'defaultValue',
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,
},
{
title: 'system.orgTemplate.operation',
slotName: 'operation',
fixed: 'right',
width: 200,
showInTable: true,
showDrag: false,
},
];
tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_FIELD, templateFieldColumns, 'drawer');
const { propsRes, propsEvent, setProps } = useTable(undefined, {
tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_FIELD,
scroll: { x: '1000px' },
selectable: false,
noDisable: true,
size: 'default',
showSetting: true,
showPagination: false,
heightUsed: 560,
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 data = ref<DefinedFieldItem[]>([]);
const systemData = ref<DefinedFieldItem[]>([]);
const customData = ref<DefinedFieldItem[]>([]);
const totalTemplateField = ref<DefinedFieldItem[]>([]);
const currentOrd = appStore.currentOrgId;
//
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: { options: selectOptions } }],
fApi: null,
required: item.internal,
};
});
};
//
const getClassifyField = async () => {
try {
totalTemplateField.value = await getFieldList({ organizationId: currentOrd, scene: route.query.type });
getFieldOptionList();
systemData.value = totalTemplateField.value.filter((item: any) => item.internal);
customData.value = totalTemplateField.value.filter((item: any) => !item.internal);
} catch (error) {
console.log(error);
}
};
//
const confirmHandler = (dataList: DefinedFieldItem[]) => {
data.value = dataList;
setProps({ data: data.value });
};
const showFieldDrawer = ref<boolean>(false);
const fieldDrawerRef = ref();
//
const editField = (record: DefinedFieldItem) => {
showFieldDrawer.value = true;
fieldDrawerRef.value.editHandler(record);
};
const fieldSelectRef = ref();
//
const addField = async () => {
showDrawer.value = true;
await getClassifyField();
fieldSelectRef.value.showSelectField();
};
//
const updateFieldHandler = async () => {
await getClassifyField();
data.value =
totalTemplateField.value.filter((item) => data.value.some((dataItem) => item.id === dataItem.id)) || [];
if (data.value.length > 0) {
setProps({ data: data.value });
}
};
//
const deleteSelectedField = (record: DefinedFieldItem) => {
data.value = data.value.filter((item) => item.id !== record.id);
setProps({ data: data.value });
};
// tablecustomFields
const getCustomFields = () => {
return data.value.map((item) => {
return {
fieldId: item.id,
fieldName: item.name,
required: item.required,
apiFieldId: '',
defaultValue: item.fApi.formData().fieldName,
};
});
};
//
const getSelectFiled = () => {
return data.value;
};
watch(
() => data.value,
(val) => {
templateStore.setPreviewHandler(val as DefinedFieldItem[]);
setProps({ data: templateStore.previewList });
},
{ deep: true }
);
const setDefaultField = async () => {
await getClassifyField();
data.value = systemData.value;
};
const showDetailFields = () => {};
onMounted(() => {
if (props.isEdit) {
showDetailFields();
} else {
data.value = templateStore.previewList;
setProps({ data: templateStore.previewList });
}
});
defineExpose({
getCustomFields,
getSelectFiled,
setDefaultField,
});
</script>
<style scoped lang="less">
.form-create-wrapper {
:deep(.arco-form-item) {
margin-bottom: 0 !important;
}
:deep(.arco-picker) {
width: 100% !important;
}
}
</style>

View File

@ -0,0 +1,250 @@
<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>
</div>
<div class="preview-right px-4">
<MsFormCreate :form-rule="formRules" :form-create-key="FormCreateKeyEnum.ORGANIZE_TEMPLATE_PREVIEW_TEMPLATE" />
</div>
</div>
</template>
<script setup lang="ts">
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 { useI18n } from '@/hooks/useI18n';
import { useTableStore } from '@/store';
import type { DefinedFieldItem } from '@/models/setting/template';
import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
const { t } = useI18n();
const tableStore = useTableStore();
const props = defineProps<{
selectField: DefinedFieldItem[];
}>();
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,
},
];
tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_STEP, templateFieldColumns, 'drawer');
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 handlerDelete = () => {};
//
const handleMoreActionSelect = (item: ActionsItem) => {
if (item.eventTag === 'delete') {
handlerDelete();
}
};
const addStep = () => {};
const formRuleField = ref<FormItem[][]>([]);
const formRules = ref<FormItem[]>([]);
//
const getFormRules = () => {
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,
};
});
return {
type: item.type,
name: rule.field,
label: item.name,
value: rule.value,
options: optionsItem,
required: item.required,
};
});
formRuleField.value.push(currentFormItem as FormItem[]);
});
const result = formRuleField.value.flatMap((item) => item);
formRules.value = result;
}
};
onMounted(() => {
setProps({ data: [{ id: 1, showStep: false, showExpected: false }] });
getFormRules();
});
defineExpose({
getFormRules,
});
</script>
<style scoped lang="less">
.wrapper-preview {
display: flex;
.preview-left {
width: 100%;
border-right: 1px solid var(--color-text-n8);
}
.preview-right {
width: 428px;
}
}
</style>

View File

@ -35,25 +35,18 @@
import MsCard from '@/components/pure/ms-card/index.vue';
import MsCardList from '@/components/business/ms-card-list/index.vue';
import TemplateItem from '@/components/business/ms-template-card/index.vue';
import TemplateItem from './components/templateItem.vue';
import { useI18n } from '@/hooks/useI18n';
import useVisit from '@/hooks/useVisit';
import useTemplateStore from '@/store/modules/setting/template';
import { cardList } from './components/fieldSetting';
const templateStore = useTemplateStore();
const { t } = useI18n();
const cardList = ref([
{
id: 1001,
value: 'FUNCTIONAL',
name: t('system.orgTemplate.caseTemplates'),
},
{ id: 1002, value: 'API', name: t('system.orgTemplate.APITemplates') },
{ id: 1003, value: 'UI', name: t('system.orgTemplate.UITemplates') },
{ id: 1004, value: 'TEST_PLAN', name: t('system.orgTemplate.testPlanTemplates') },
{ id: 1005, value: 'BUG', name: t('system.orgTemplate.defectTemplates') },
]);
const visitedKey = 'notRemind';
const { addVisited } = useVisit(visitedKey);
const { getIsVisited } = useVisit(visitedKey);
@ -68,6 +61,9 @@
const doCheckIsTip = () => {
isShowTip.value = !getIsVisited();
};
onBeforeMount(() => {
templateStore.setStatus();
});
onMounted(() => {
doCheckIsTip();

View File

@ -22,9 +22,11 @@ export default {
'system.orgTemplate.TemplateManagement': 'Management',
'system.orgTemplate.workflowSetup': 'Workflow',
'system.orgTemplate.enable': 'Enable',
'system.orgTemplate.update': 'Update',
'system.orgTemplate.addField': 'Add Field',
'system.orgTemplate.update': 'Update Field',
'system.orgTemplate.addField': 'Create Field',
'system.orgTemplate.createField': 'Add Field',
'system.orgTemplate.enableDescription': 'The project template is enabled. Field Settings are inoperable',
'system.orgTemplate.fieldLimit': 'You can add a maximum of 20 fields',
'system.orgTemplate.isSystem': 'System',
'system.orgTemplate.fieldList': 'Field list',
'system.orgTemplate.addOptions': 'Add an option',
@ -57,4 +59,37 @@ export default {
'system.orgTemplate.dateFormat': 'Date format',
'system.orgTemplate.formatPlaceholder': 'Please select a format',
'system.orgTemplate.optionsPlaceholder': 'Please enter options',
'system.orgTemplate.columnTemplateName': 'Template name',
'system.orgTemplate.copy': 'Copy',
'system.orgTemplate.defaultValue': 'Default value',
'system.orgTemplate.required': 'Required',
'system.orgTemplate.enableTemplateTip': 'The project template is enabled. The organization template is unavailable',
'system.orgTemplate.templateList': 'Template list',
'system.orgTemplate.createTemplate': 'Create',
'system.orgTemplate.templatePreview': 'Template preview',
'system.orgTemplate.templateName': 'Template name',
'system.orgTemplate.templateNamePlaceholder': 'Please enter a template name',
'system.orgTemplate.optionalField': 'Optional Field',
'system.orgTemplate.selectAll': 'select All',
'system.orgTemplate.systemField': 'System Field',
'system.orgTemplate.customField': 'Custom Field',
'system.orgTemplate.selectedField': 'Selected Field',
'system.orgTemplate.clear': 'Clear',
'system.orgTemplate.addSuccess': 'create successfully',
'system.orgTemplate.updateSuccess': 'update successfully',
'system.orgTemplate.exitPreview': 'Exit Preview',
'system.orgTemplate.useCaseStep': 'useCase Step',
'system.orgTemplate.expectedResult': 'Expected Result',
'system.orgTemplate.numberIndex': 'Index',
'system.orgTemplate.addStep': 'Add Step',
'system.orgTemplate.caseName': 'Use case name',
'system.orgTemplate.caseNamePlaceholder': 'Please enter a use case name',
'system.orgTemplate.precondition': 'precondition',
'system.orgTemplate.stepDescription': 'Step description',
'system.orgTemplate.changeType': 'Change type',
'system.orgTemplate.textDescription': 'Text description',
'system.orgTemplate.stepTip': 'Please enter steps',
'system.orgTemplate.expectationTip': 'Please enter expectations',
'system.orgTemplate.addAttachment': 'Add attachment',
'system.orgTemplate.addAttachmentTip': 'Support any type of file, the file size does not exceed 500MB',
};

View File

@ -22,9 +22,11 @@ export default {
'system.orgTemplate.TemplateManagement': '模版管理',
'system.orgTemplate.workflowSetup': '工作流设置',
'system.orgTemplate.enable': '启用项目模版',
'system.orgTemplate.update': '更新',
'system.orgTemplate.update': '更新字段',
'system.orgTemplate.addField': '新增字段',
'system.orgTemplate.createField': '添加字段',
'system.orgTemplate.enableDescription': '已启用项目模版,字段设置不可操作',
'system.orgTemplate.fieldLimit': '最多可新增 20 个字段',
'system.orgTemplate.isSystem': '系统',
'system.orgTemplate.fieldList': '字段列表',
'system.orgTemplate.addOptions': '添加一个选项',
@ -56,4 +58,40 @@ export default {
'system.orgTemplate.dateFormat': '日期格式',
'system.orgTemplate.formatPlaceholder': '请选择格式',
'system.orgTemplate.optionsPlaceholder': '请输入选项',
'system.orgTemplate.updateTip': '确认更新 `{name}` 吗?',
'system.orgTemplate.updateDescription': '更新后,所使用该字段模版将一起修改',
'system.orgTemplate.confirm': '确定',
'system.orgTemplate.columnTemplateName': '模版名称',
'system.orgTemplate.copy': '复制',
'system.orgTemplate.defaultValue': '默认值',
'system.orgTemplate.required': '是否必填',
'system.orgTemplate.enableTemplateTip': '已启用项目模版,组织模版不可操作',
'system.orgTemplate.templateList': '模板列表',
'system.orgTemplate.createTemplate': '创建模版',
'system.orgTemplate.templatePreview': '模板预览',
'system.orgTemplate.templateName': '模板名称',
'system.orgTemplate.templateNamePlaceholder': '请输入模版名称',
'system.orgTemplate.optionalField': '可选字段',
'system.orgTemplate.selectAll': '全选',
'system.orgTemplate.systemField': '系统字段',
'system.orgTemplate.customField': '自定义字段',
'system.orgTemplate.selectedField': '已选字段',
'system.orgTemplate.clear': '清空',
'system.orgTemplate.addSuccess': '新增成功',
'system.orgTemplate.updateSuccess': '更新成功',
'system.orgTemplate.exitPreview': '退出预览',
'system.orgTemplate.useCaseStep': '用例步骤',
'system.orgTemplate.expectedResult': '预期结果',
'system.orgTemplate.numberIndex': '序号',
'system.orgTemplate.addStep': '添加步骤',
'system.orgTemplate.caseName': '用例名称',
'system.orgTemplate.caseNamePlaceholder': '请输入用例名称',
'system.orgTemplate.precondition': '前置条件',
'system.orgTemplate.stepDescription': '步骤描述',
'system.orgTemplate.changeType': '更改类型',
'system.orgTemplate.textDescription': '文本描述',
'system.orgTemplate.stepTip': '请输入步骤',
'system.orgTemplate.expectationTip': '请输入预期',
'system.orgTemplate.addAttachment': '添加附件',
'system.orgTemplate.addAttachmentTip': '支持任意类型文件,文件大小不超过 500MB',
};

View File

@ -9,7 +9,7 @@
<div class="ms-scroll">
<div v-for="(item, index) in recordItem.pluginForms" :key="item.id" class="ms-self">
<span class="circle text-xs leading-[16px]"> {{ index + 1 }} </span>
<span class="cursor-pointer text-[rgb(var(--primary-5))]" @click="emit('MessageEvent', recordItem, item)">{{
<span class="cursor-pointer text-[rgb(var(--primary-5))]" @click="getScriptEmit(recordItem, item)">{{
item.name
}}</span>
</div>
@ -24,8 +24,16 @@
recordItem: PluginItem;
}>();
const emit = defineEmits<{
(e: 'MessageEvent', record: PluginItem, item: PluginForms): void;
(e: 'messageEvent', record: PluginItem, item: PluginForms): void;
}>();
const originPluginId = ref('');
const getScriptEmit = (record: PluginItem, item: PluginForms) => {
if (originPluginId.value !== item.id) {
emit('messageEvent', record, item);
originPluginId.value = item.id;
}
};
</script>
<style scoped lang="less">

View File

@ -35,6 +35,7 @@ export default {
'system.resourcePool.disablePoolCancel': 'Cancel',
'system.plugin.deletePluginSuccess': 'Delete successfully',
'system.plugin.disablePluginSuccess': 'Disabled successfully',
'system.plugin.enablePluginSuccess': 'Enabled successfully',
'system.plugin.disablePluginContent':
'The project can not integrate with the platform and the default template of the platform is not available, be careful!',
'system.plugin.alertDescribe':

View File

@ -58,6 +58,7 @@ export default {
'system.plugin.currentScene': '当前场景',
'system.plugin.deletePluginSuccess': '删除成功',
'system.plugin.disablePluginSuccess': '禁用成功',
'system.plugin.enablePluginSuccess': '启用成功',
'system.plugin.disablePluginContent': '项目无法与该平台集成且该平台默认模版不可用,谨慎操作!',
'system.plugin.alertDescribe':
'MeterSphereV2.10LTS 版本支持 DevOps、API 导入、请求、项目管理、协议类型的插件,具体支持插件请',