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, GetFieldDetailUrl,
GetOrganizeTemplateDetailUrl, GetOrganizeTemplateDetailUrl,
GetOrganizeTemplateUrl, GetOrganizeTemplateUrl,
GetProjectTemplateDetailUrl,
isEnableTemplateUrl,
SetOrganizeTemplateUrl, SetOrganizeTemplateUrl,
UpdateFieldUrl, UpdateFieldUrl,
UpdateOrganizeTemplateUrl,
UpdateProjectTemplateUrl, UpdateProjectTemplateUrl,
} from '@/api/requrls/setting/template'; } from '@/api/requrls/setting/template';
import { CommonList, TableQueryParams } from '@/models/common'; 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) { export function getOrganizeTemplateList(params: TableQueryParams) {
return MSR.get<OrganizeTemplateItem[]>({ url: GetOrganizeTemplateUrl, params: `/${organizationId}/${scene}` }); 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 DeleteOrganizeTemplateUrl = '/organization/template/delete';
// 关闭组织模板,开启项目模版 // 关闭组织模板,开启项目模版
export const EnableOrOffTemplateUrl = '/organization/template/disable'; 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,
.arco-select-view-single, .arco-select-view-single,
.arco-select { .arco-select {
width: 100%;
border: 1px solid var(--color-text-input-border); border: 1px solid var(--color-text-input-border);
background-color: var(--color-text-fff); background-color: var(--color-text-fff);
&:not(:disabled):hover { &:not(:disabled):hover {

View File

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

View File

@ -47,7 +47,7 @@
}, true); }, true);
function jumpTo(name: RouteRecordName) { function jumpTo(name: RouteRecordName) {
router.push({ name }); router.push({ name, query: route.query });
} }
</script> </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 { ref, watch, watchEffect } from 'vue';
import PassWord from './formcreate-password.vue'; import PassWord from './formcreate-password.vue';
import SearchSelect from './searchSelect.vue';
import formCreate, { FormRule } from '@form-create/arco-design'; import formCreate, { FormRule } from '@form-create/arco-design';
formCreate.component('PassWord', PassWord); formCreate.component('PassWord', PassWord);
formCreate.component('SearchSelect', SearchSelect);
const FormCreate = formCreate.$form(); const FormCreate = formCreate.$form();
const props = defineProps<{ const props = defineProps<{
@ -21,7 +23,7 @@
const emits = defineEmits<{ const emits = defineEmits<{
(e: 'update:api', val: any): void; (e: 'update:api', val: any): void;
}>(); }>();
const formApi = ref({}); const formApi = ref<any>({});
watchEffect(() => { watchEffect(() => {
formApi.value = props.api; formApi.value = props.api;
@ -32,6 +34,18 @@
emits('update:api', val); 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> </script>
<style scoped></style> <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))]" class="mr-[2px] text-xl text-[rgb(var(--danger-6))]"
/> />
</slot> </slot>
<span :class="titleClass"> <span :class="[titleClass]">
{{ characterLimit(props.title) || '' }} {{ props.title || '' }}
</span> </span>
</div> </div>
<!-- 描述展示 --> <!-- 描述展示 -->
@ -64,7 +64,7 @@
{{ props.cancelText || t('common.cancel') }} {{ props.cancelText || t('common.cancel') }}
</a-button> </a-button>
<a-button type="primary" size="mini" :loading="props.loading" @click="handleConfirm"> <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> </a-button>
</div> </div>
</template> </template>
@ -79,7 +79,6 @@
import MsIcon from '@/components/pure/ms-icon-font/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { characterLimit } from '@/utils';
import type { FieldRule, FormInstance } from '@arco-design/web-vue'; import type { FieldRule, FormInstance } from '@arco-design/web-vue';
@ -113,6 +112,7 @@
{ {
type: 'warning', type: 'warning',
isDelete: true, // pop isDelete: true, // pop
okText: 'common.remove',
} }
); );
const emits = defineEmits<{ const emits = defineEmits<{
@ -167,7 +167,7 @@
// //
const titleClass = computed(() => { const titleClass = computed(() => {
return props.isDelete 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]'; : 'mb-[8px] font-medium text-[var(--color-text-1)] text-[14px]';
}); });

View File

@ -135,7 +135,13 @@
</script> </script>
<template> <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" /> <RichTextEditor v-if="editor" :editor="editor" :locale="locale" />
</div> </div>
</template> </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 appStore = useAppStore();
const tableStore = useTableStore(); const tableStore = useTableStore();
export default function useTableProps<T>( 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>>, props?: Partial<MsTableProps<T>>,
// 数据处理的回调函数 // 数据处理的回调函数
dataTransform?: (item: TableData) => (TableData & T) | any, dataTransform?: (item: TableData) => (TableData & T) | any,
@ -161,40 +161,42 @@ export default function useTableProps<T>(
const { current, pageSize } = propsRes.value.msPagination as Pagination; const { current, pageSize } = propsRes.value.msPagination as Pagination;
const { rowKey, selectorStatus, excludeKeys } = propsRes.value; const { rowKey, selectorStatus, excludeKeys } = propsRes.value;
try { try {
setLoading(true); if (loadListFunc) {
const data = await loadListFunc({ setLoading(true);
current, const data = await loadListFunc({
pageSize, current,
sort: sortItem.value, pageSize,
filter: filterItem.value, sort: sortItem.value,
keyword: keyword.value, filter: filterItem.value,
...loadListParams.value, keyword: keyword.value,
}); ...loadListParams.value,
const tmpArr = data.list; });
propsRes.value.data = tmpArr.map((item: MsTableDataItem<T>) => { const tmpArr = data.list;
if (item.updateTime) { propsRes.value.data = tmpArr.map((item: MsTableDataItem<T>) => {
item.updateTime = dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss'); 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 (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; setPagination({ current: data.current, total: data.total });
}); return data;
if (data.total === 0) {
setTableErrorStatus('empty');
} else {
setTableErrorStatus(false);
} }
setPagination({ current: data.current, total: data.total });
return data;
} catch (err) { } catch (err) {
setTableErrorStatus('error'); setTableErrorStatus('error');
} finally { } finally {
@ -208,26 +210,28 @@ export default function useTableProps<T>(
} else { } else {
// 没分页的情况下直接调用loadListFunc // 没分页的情况下直接调用loadListFunc
try { try {
setLoading(true); if (loadListFunc) {
const data = await loadListFunc({ keyword: keyword.value, ...loadListParams.value }); setLoading(true);
if (data.length === 0) { const data = await loadListFunc({ keyword: keyword.value, ...loadListParams.value });
setTableErrorStatus('empty'); if (data.length === 0) {
return; 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) { } catch (err) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(err); 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_PROJECT = 'settingOrganizationProject',
SETTING_ORGANIZATION_TEMPLATE = 'settingOrganizationTemplate', SETTING_ORGANIZATION_TEMPLATE = 'settingOrganizationTemplate',
SETTING_ORGANIZATION_TEMPLATE_FILED_SETTING = 'settingOrganizationTemplateFiledSetting', SETTING_ORGANIZATION_TEMPLATE_FILED_SETTING = 'settingOrganizationTemplateFiledSetting',
SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT = 'settingOrganizationTemplateManagement',
SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL = 'settingOrganizationTemplateManagementDetail',
SETTING_ORGANIZATION_SERVICE = 'settingOrganizationService', SETTING_ORGANIZATION_SERVICE = 'settingOrganizationService',
SETTING_ORGANIZATION_LOG = 'settingOrganizationLog', SETTING_ORGANIZATION_LOG = 'settingOrganizationLog',
} }

View File

@ -17,7 +17,10 @@ export enum TableKeyEnum {
PROJECT_MEMBER = 'projectMember', PROJECT_MEMBER = 'projectMember',
PROJECT_USER_GROUP = 'projectUserGroup', PROJECT_USER_GROUP = 'projectUserGroup',
ORGANIZATION_MEMBER = 'organizationMember', 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 = 'organizationProject',
ORGANIZATION_PROJECT_USER_DRAWER = 'organizationProjectUserDrawer', ORGANIZATION_PROJECT_USER_DRAWER = 'organizationProjectUserDrawer',
FILE_MANAGEMENT_FILE = 'fileManagementFile', FILE_MANAGEMENT_FILE = 'fileManagementFile',

View File

@ -1,3 +1,4 @@
// 模版展示字段icon
export enum TemplateIconEnum { export enum TemplateIconEnum {
INPUT = 'icon-icon_input', // 输入框 INPUT = 'icon-icon_input', // 输入框
TEXTAREA = 'icon-icon_style_one', // 文本 TEXTAREA = 'icon-icon_style_one', // 文本
@ -14,5 +15,16 @@ export enum TemplateIconEnum {
FLOAT = 'icon-icon_pound', // 数字-浮点型 FLOAT = 'icon-icon_pound', // 数字-浮点型
MULTIPLE_INPUT = 'icon-icon_tag', // 多值输入框、 MULTIPLE_INPUT = 'icon-icon_tag', // 多值输入框、
NUMBER = 'icon-icon_pound', // 数字 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 {}; export default {};

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,10 @@
import { LocationQueryValue } from 'vue-router'; 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 { export interface OrganizeTemplateItem {
id: string; id: string;
name: string; name: string;
@ -18,21 +22,33 @@ export interface OrganizeTemplateItem {
} }
export type SeneType = 'FUNCTIONAL' | 'BUG' | 'API' | 'UI' | 'TEST_PLAN' | LocationQueryValue[] | LocationQueryValue; 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 { export interface DefinedFieldItem {
id: string; id: string;
name: string; name: string;
scene: SeneType; // 使用场景 scene: SeneType; // 使用场景
type: string; type: FormItemType; // 表单类型
remark: string; remark: string;
internal: boolean; // 是否是内置字段 internal: boolean; // 是否是内置字段
scopeType: string; // 组织或项目级别字段PROJECT, ORGANIZATION scopeType: string; // 组织或项目级别字段PROJECT, ORGANIZATION
createTime: number; createTime: number;
updateTime: number; updateTime: number;
createUser: string; createUser: string;
refId: string; // 项目字段所关联的组织字段ID refId: string | null; // 项目字段所关联的组织字段ID
enableOptionKey: boolean; // 是否需要手动输入选项key enableOptionKey: boolean | null; // 是否需要手动输入选项key
scopeId: string; // 组织或项目ID scopeId: string; // 组织或项目ID
options: FieldOptions[] | null;
required?: boolean | undefined;
fApi?: any; // 表单值
formRules?: FormRuleItem[] | FormItem[] | FormRule[]; // 表单列表
} }
// 创建自定义字段 // 创建自定义字段
@ -42,12 +58,55 @@ export interface FieldOption {
text: string; text: string;
} }
// 新增 || 编辑参数
export interface AddOrUpdateField { export interface AddOrUpdateField {
id?: string; id?: string;
name: string; name: string;
scene: SeneType; // 使用场景 scene: SeneType; // 使用场景
type: string; type: FormItemType;
remark: string; // 备注 remark: string; // 备注
scopeId: string; // 组织或项目ID scopeId: string; // 组织或项目ID
options?: FieldOption[]; 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, isTopMenu: true,
}, },
}, },
// 模版字段设置 // 模板列表-模版字段设置
{ {
path: 'templateFiledSetting', path: 'templateFiledSetting',
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_FILED_SETTING, 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', path: 'log',
name: SettingRouteEnum.SETTING_ORGANIZATION_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> <template>
<div>项目版本 waiting for development </div> <div>项目版本 waiting for development </div>
<MsFormCreate :form-rule="formRules" :form-create-key="FormCreateKeyEnum.ORGANIZE_TEMPLATE" />
<br />
<br />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; 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> </script>
<style scoped></style> <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 <a-input
v-model:model-value="fieldForm.name" v-model:model-value="fieldForm.name"
:placeholder="t('system.orgTemplate.fieldNamePlaceholder')" :placeholder="t('system.orgTemplate.fieldNamePlaceholder')"
:max-length="250" :max-length="255"
show-word-limit show-word-limit
></a-input> ></a-input>
</a-form-item> </a-form-item>
<a-form-item field="remark" :label="t('system.orgTemplate.description')" asterisk-position="end"> <a-form-item field="remark" :label="t('system.orgTemplate.description')" asterisk-position="end">
<a-textarea <a-textarea
v-model="fieldForm.remark" v-model="fieldForm.remark"
:max-length="250" :max-length="255"
:placeholder="t('system.orgTemplate.resDescription')" :placeholder="t('system.orgTemplate.resDescription')"
:auto-size="{ :auto-size="{
maxRows: 1, maxRows: 1,
@ -42,11 +42,12 @@
class="w-[260px]" class="w-[260px]"
:placeholder="t('system.orgTemplate.fieldTypePlaceholder')" :placeholder="t('system.orgTemplate.fieldTypePlaceholder')"
allow-clear allow-clear
:disabled="isEdit"
@change="fieldChangeHandler" @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" <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-option>
</a-select> </a-select>
@ -81,9 +82,7 @@
<a-form-item <a-form-item
v-if="showDateOrNumber" v-if="showDateOrNumber"
field="selectFormat" field="selectFormat"
:label=" :label="fieldType === 'NUMBER' ? t('system.orgTemplate.numberFormat') : t('system.orgTemplate.dateFormat')"
fieldForm.type === 'NUMBER' ? t('system.orgTemplate.numberFormat') : t('system.orgTemplate.dateFormat')
"
asterisk-position="end" asterisk-position="end"
> >
<a-select <a-select
@ -91,6 +90,7 @@
class="w-[260px]" class="w-[260px]"
:placeholder="t('system.orgTemplate.formatPlaceholder')" :placeholder="t('system.orgTemplate.formatPlaceholder')"
allow-clear allow-clear
:disabled="isEdit"
> >
<a-option v-for="item of showDateOrNumber" :key="item.value" :value="item.value"> <a-option v-for="item of showDateOrNumber" :key="item.value" :value="item.value">
<div class="flex items-center">{{ item.label }}</div> <div class="flex items-center">{{ item.label }}</div>
@ -108,6 +108,7 @@
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue'; import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
import MsDrawer from '@/components/pure/ms-drawer/index.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 MsBatchForm from '@/components/business/ms-batch-form/index.vue';
import type { FormItemModel, MsBatchFormInstance } from '@/components/business/ms-batch-form/types'; import type { FormItemModel, MsBatchFormInstance } from '@/components/business/ms-batch-form/types';
@ -116,10 +117,9 @@
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import { getGenerateId } from '@/utils'; import { getGenerateId } from '@/utils';
import type { AddOrUpdateField } from '@/models/setting/template'; import type { AddOrUpdateField, fieldIconAndNameModal } from '@/models/setting/template';
import { TemplateIconEnum } from '@/enums/templateEnum';
import { getFieldType } from './fieldSetting'; import { fieldIconAndName, getFieldType } from './fieldSetting';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -136,7 +136,7 @@
const fieldFormRef = ref<FormInstance>(); const fieldFormRef = ref<FormInstance>();
const initFieldForm: AddOrUpdateField = { const initFieldForm: AddOrUpdateField = {
name: '', name: '',
type: '', type: 'INPUT',
remark: '', remark: '',
scopeId: '', scopeId: '',
scene: 'FUNCTIONAL', scene: 'FUNCTIONAL',
@ -144,19 +144,19 @@
}; };
const fieldForm = ref<AddOrUpdateField>({ ...initFieldForm }); const fieldForm = ref<AddOrUpdateField>({ ...initFieldForm });
const isEdit = computed(() => !!fieldForm.value.id); const isEdit = computed(() => !!fieldForm.value.id);
const selectFormat = ref(''); // const selectFormat = ref<FormItemType>(); //
const isMultipleSelectMember = ref<boolean | undefined>(false); // const isMultipleSelectMember = ref<boolean | undefined>(false); //
const fieldType = ref(''); // const fieldType = ref<FormItemType>(); //
// //
const showOptionsSelect = computed(() => { const showOptionsSelect = computed(() => {
const showOptionsType = ['RADIO', 'CHECKBOX', 'SELECT', 'MULTIPLE_SELECT']; const showOptionsType: FormItemType[] = ['RADIO', 'CHECKBOX', 'SELECT', 'MULTIPLE_SELECT'];
return showOptionsType.includes(fieldType.value); return showOptionsType.includes(fieldType.value as FormItemType);
}); });
// //
const showDateOrNumber = computed(() => { const showDateOrNumber = computed(() => {
return getFieldType(fieldType.value); if (fieldType.value) return getFieldType(fieldType.value);
}); });
// -1. // -1.
@ -174,9 +174,9 @@
const resetForm = () => { const resetForm = () => {
fieldForm.value = { ...initFieldForm }; fieldForm.value = { ...initFieldForm };
selectFormat.value = ''; selectFormat.value = undefined;
isMultipleSelectMember.value = false; isMultipleSelectMember.value = false;
fieldType.value = ''; fieldType.value = undefined;
batchFormRef.value?.resetForm(); batchFormRef.value?.resetForm();
}; };
@ -190,20 +190,23 @@
const confirmHandler = async (isContinue: boolean) => { const confirmHandler = async (isContinue: boolean) => {
try { try {
drawerLoading.value = true; drawerLoading.value = true;
if (fieldType.value) {
fieldForm.value.type = fieldType.value;
}
fieldForm.value.scene = route.query.type; fieldForm.value.scene = route.query.type;
fieldForm.value = { fieldForm.value.scopeId = appStore.currentOrgId;
...fieldForm.value,
scopeId: appStore.currentOrgId,
type: fieldType.value,
};
// //
if (selectFormat.value) { if (selectFormat.value) {
fieldForm.value.type = selectFormat.value; fieldForm.value.type = selectFormat.value;
} }
// || // ||
if (isMultipleSelectMember.value) { if (isMultipleSelectMember.value) {
fieldForm.value.type = isMultipleSelectMember.value ? 'MULTIPLE_MEMBER' : 'MEMBER'; fieldForm.value.type = isMultipleSelectMember.value ? 'MULTIPLE_MEMBER' : 'MEMBER';
} }
// //
if (selectFormat.value) { if (selectFormat.value) {
fieldForm.value.type = selectFormat.value; fieldForm.value.type = selectFormat.value;
@ -212,6 +215,7 @@
// //
const { id, name, options, scopeId, scene, type, remark } = fieldForm.value; const { id, name, options, scopeId, scene, type, remark } = fieldForm.value;
const params: AddOrUpdateField = { name, options, scopeId, scene, type, remark }; const params: AddOrUpdateField = { name, options, scopeId, scene, type, remark };
if (isEdit) { if (isEdit) {
params.id = id; params.id = id;
} }
@ -247,59 +251,7 @@
}; };
// //
const fieldOptions = ref([ const fieldOptions = ref<fieldIconAndNameModal[]>([]);
{
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 fieldDefaultValues = ref([]); const fieldDefaultValues = ref([]);
// //
@ -317,7 +269,7 @@
}; };
// //
const getSpecialHandler = (itemType: string): string => { const getSpecialHandler = (itemType: FormItemType): FormItemType => {
switch (itemType) { switch (itemType) {
case 'INT': case 'INT':
selectFormat.value = itemType; selectFormat.value = itemType;
@ -336,7 +288,7 @@
}; };
// //
const isEditHandler = (item: AddOrUpdateField) => { const editHandler = (item: AddOrUpdateField) => {
showDrawer.value = true; showDrawer.value = true;
isMultipleSelectMember.value = item.type === 'MULTIPLE_MEMBER'; isMultipleSelectMember.value = item.type === 'MULTIPLE_MEMBER';
if (isEdit && item.id) { if (isEdit && item.id) {
@ -345,7 +297,7 @@
...item, ...item,
type: getSpecialHandler(item.type), type: getSpecialHandler(item.type),
}; };
fieldType.value = getSpecialHandler(item.type); fieldType.value = fieldForm.value.type;
} }
}; };
@ -368,9 +320,13 @@
showDrawer.value = val; showDrawer.value = val;
} }
); );
onMounted(() => {
const excludeOptions = ['MULTIPLE_MEMBER', 'DATETIME', 'SYSTEM', 'INT', 'FLOAT'];
fieldOptions.value = fieldIconAndName.filter((item: any) => excludeOptions.indexOf(item.key) < 0);
});
defineExpose({ defineExpose({
isEditHandler, editHandler,
}); });
</script> </script>

View File

@ -1,8 +1,18 @@
import { ref } from 'vue';
import dayjs from 'dayjs'; 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'), label: dayjs().format('YYYY/MM/DD'),
value: 'DATE', value: 'DATE',
@ -11,10 +21,10 @@ const dateOptions = ref([
label: dayjs().format('YYYY/MM/DD HH:mm:ss'), label: dayjs().format('YYYY/MM/DD HH:mm:ss'),
value: 'DATETIME', value: 'DATETIME',
}, },
]); ];
// 字段类型- 数字 // 字段类型- 数字
const numberTypeOptions = ref([ const numberTypeOptions = [
{ {
label: '整数', label: '整数',
value: 'INT', value: 'INT',
@ -23,17 +33,141 @@ const numberTypeOptions = ref([
label: '保留小数', label: '保留小数',
value: 'FLOAT', value: 'FLOAT',
}, },
]); ];
export const getFieldType = (selectFieldType: string) => { // 获取字段类型是数值 || 日期
export const getFieldType = (selectFieldType: FormItemType) => {
switch (selectFieldType) { switch (selectFieldType) {
case 'DATE': case 'DATE':
return dateOptions.value; return dateOptions;
case 'NUMBER': case 'NUMBER':
return numberTypeOptions.value; return numberTypeOptions;
default: default:
break; 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 {}; export default {};

View File

@ -1,9 +1,11 @@
<template> <template>
<MsCard :has-breadcrumb="true" simple> <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"> <div class="mb-4 flex items-center justify-between">
<span v-if="isEnable" class="font-medium">{{ t('system.orgTemplate.fieldList') }}</span> <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') }} {{ t('system.orgTemplate.addField') }}
</a-button> </a-button>
<a-input-search <a-input-search
@ -17,13 +19,22 @@
</div> </div>
<MsBaseTable v-bind="propsRes" ref="tableRef" v-on="propsEvent"> <MsBaseTable v-bind="propsRes" ref="tableRef" v-on="propsEvent">
<template #name="{ record }"> <template #name="{ record }">
<MsIcon v-if="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 class="ml-2">{{ record.name }}</span>
<span v-if="record.internal" class="system-flag">{{ t('system.orgTemplate.isSystem') }}</span> <span v-if="record.internal" class="system-flag">{{ t('system.orgTemplate.isSystem') }}</span>
</template> </template>
<template #operation="{ record }"> <template #operation="{ record }">
<div class="flex flex-row flex-nowrap"> <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" /> <a-divider v-if="!record.internal" direction="vertical" />
<MsTableMoreAction <MsTableMoreAction
v-if="!record.internal" v-if="!record.internal"
@ -33,7 +44,7 @@
</div> </div>
</template> </template>
<template #fieldType="{ record }"> <template #fieldType="{ record }">
<span>{{ getIconType(record.type)['type'] }}</span> <span>{{ getIconType(record.type)?.label }}</span>
</template> </template>
</MsBaseTable> </MsBaseTable>
<EditFieldDrawer ref="fieldDrawerRef" v-model:visible="showDrawer" @success="successHandler" /> <EditFieldDrawer ref="fieldDrawerRef" v-model:visible="showDrawer" @success="successHandler" />
@ -41,12 +52,16 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
/**
* @description 系统管理-组织-模版-字段列表
*/
import { ref } from 'vue'; import { ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue'; import MsButton from '@/components/pure/ms-button/index.vue';
import MsCard from '@/components/pure/ms-card/index.vue'; import 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 MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type'; import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable'; import useTable from '@/components/pure/ms-table/useTable';
@ -58,11 +73,15 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import { useAppStore, useTableStore } from '@/store'; import { useAppStore, useTableStore } from '@/store';
import useTemplateStore from '@/store/modules/setting/template';
import { characterLimit } from '@/utils'; 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 { TableKeyEnum } from '@/enums/tableEnum';
import { TemplateIconEnum } from '@/enums/templateEnum';
import { cardList, getIconType } from './fieldSetting';
const templateStore = useTemplateStore();
const { t } = useI18n(); const { t } = useI18n();
const tableStore = useTableStore(); const tableStore = useTableStore();
@ -109,11 +128,10 @@
showDrag: false, showDrag: false,
}, },
]; ];
tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_FIELD_SETTING, fieldColumns, 'drawer');
tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE, fieldColumns, 'drawer');
const { propsRes, propsEvent, loadList, setLoadListParams, setProps } = useTable(getFieldList, { const { propsRes, propsEvent, loadList, setLoadListParams, setProps } = useTable(getFieldList, {
tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE, tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_FIELD_SETTING,
scroll: { x: '1000px' }, scroll: { x: '1000px' },
selectable: false, selectable: false,
noDisable: true, noDisable: true,
@ -124,73 +142,40 @@
}); });
const keyword = ref(''); const keyword = ref('');
const totalData = ref([]);
// //
const searchFiled = async () => { const searchFiled = async () => {
try { try {
const totalData = await getFieldList({ organizationId: currentOrd, scene: route.query.type }); totalData.value = await getFieldList({ organizationId: currentOrd, scene: route.query.type });
const filterData = totalData.filter((item: AddOrUpdateField) => item.name.includes(keyword.value)); const filterData = totalData.value.filter((item: AddOrUpdateField) => item.name.includes(keyword.value));
setProps({ data: filterData }); setProps({ data: filterData });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
}; };
const scene = ref<SeneType>('');
const fetchData = async () => { const fetchData = async () => {
const scene = route.query.type; scene.value = route.query.type;
setLoadListParams({ organizationId: currentOrd, scene }); setLoadListParams({ organizationId: currentOrd, scene });
await loadList(); await loadList();
}; };
const tableRef = ref(); const tableRef = ref();
const isEnable = ref<boolean>(false); // const isEnable = ref<boolean>(templateStore.templateStatus[scene.value as string]); //
// //
const isEnableOperation = () => { const isEnableOperation = () => {
if (isEnable.value) { if (isEnable.value) {
const noOperationColumn = fieldColumns.slice(0, -1); 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(); tableRef.value.initColumn();
} else { } else {
tableStore.setColumns(TableKeyEnum.ORGANIZATION_TEMPLATE, fieldColumns, 'drawer'); tableStore.setColumns(TableKeyEnum.ORGANIZATION_TEMPLATE_FIELD_SETTING, fieldColumns, 'drawer');
tableRef.value.initColumn(); 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[] = [ const moreActions: ActionsItem[] = [
{ {
label: 'system.userGroup.delete', label: 'system.userGroup.delete',
@ -214,7 +199,7 @@
try { try {
if (record.id) await deleteOrdField(record.id); if (record.id) await deleteOrdField(record.id);
Message.success(t('system.user.deleteUserSuccess')); Message.success(t('system.user.deleteUserSuccess'));
loadList(); fetchData();
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
@ -235,28 +220,21 @@
const fieldDrawerRef = ref(); const fieldDrawerRef = ref();
const fieldHandler = (type: string, record?: AddOrUpdateField) => { const fieldHandler = (type: string, record?: AddOrUpdateField) => {
showDrawer.value = true; 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 = () => { 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 updateBreadcrumbList = () => {
const { breadcrumbList } = appStore; 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) { if (breadTitle) {
breadcrumbList[0].locale = breadTitle.name; breadcrumbList[0].locale = breadTitle.name;
appStore.setBreadcrumbList(breadcrumbList); 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="innerWrapper">
<div class="content"> <div class="content">
<div class="logo-img h-[48px] w-[48px]"> <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>
<div class="template-operation"> <div class="template-operation">
<div class="flex items-center"> <div class="flex items-center">
@ -16,12 +16,14 @@
<a-divider direction="vertical" /> <a-divider direction="vertical" />
</span> </span>
<span class="operation hover:text-[rgb(var(--primary-5))]"> <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>
<span v-if="props.cardItem.value === 'BUG'" class="operation hover:text-[rgb(var(--primary-5))]"> <span v-if="props.cardItem.key === 'BUG'" class="operation hover:text-[rgb(var(--primary-5))]">
<span>{{ t('system.orgTemplate.workflowSetup') }}</span> <a-divider direction="vertical" /> <span>{{ t('system.orgTemplate.workflowSetup') }}</span>
<a-divider v-if="!props.cardItem.enable" direction="vertical" />
</span> </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" <MsTableMoreAction :list="moreActions" @select="handleMoreActionSelect"
/></span> /></span>
</div> </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) => {}; const handleMoreActionSelect = (item: ActionsItem) => {};
// //
@ -73,7 +67,16 @@
router.push({ router.push({
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_FILED_SETTING, name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_FILED_SETTING,
query: { 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 MsCard from '@/components/pure/ms-card/index.vue';
import MsCardList from '@/components/business/ms-card-list/index.vue'; import MsCardList from '@/components/business/ms-card-list/index.vue';
import TemplateItem from '@/components/business/ms-template-card/index.vue'; import TemplateItem from './components/templateItem.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useVisit from '@/hooks/useVisit'; import useVisit from '@/hooks/useVisit';
import useTemplateStore from '@/store/modules/setting/template';
import { cardList } from './components/fieldSetting';
const templateStore = useTemplateStore();
const { t } = useI18n(); 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 visitedKey = 'notRemind';
const { addVisited } = useVisit(visitedKey); const { addVisited } = useVisit(visitedKey);
const { getIsVisited } = useVisit(visitedKey); const { getIsVisited } = useVisit(visitedKey);
@ -68,6 +61,9 @@
const doCheckIsTip = () => { const doCheckIsTip = () => {
isShowTip.value = !getIsVisited(); isShowTip.value = !getIsVisited();
}; };
onBeforeMount(() => {
templateStore.setStatus();
});
onMounted(() => { onMounted(() => {
doCheckIsTip(); doCheckIsTip();

View File

@ -22,9 +22,11 @@ export default {
'system.orgTemplate.TemplateManagement': 'Management', 'system.orgTemplate.TemplateManagement': 'Management',
'system.orgTemplate.workflowSetup': 'Workflow', 'system.orgTemplate.workflowSetup': 'Workflow',
'system.orgTemplate.enable': 'Enable', 'system.orgTemplate.enable': 'Enable',
'system.orgTemplate.update': 'Update', 'system.orgTemplate.update': 'Update Field',
'system.orgTemplate.addField': 'Add 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.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.isSystem': 'System',
'system.orgTemplate.fieldList': 'Field list', 'system.orgTemplate.fieldList': 'Field list',
'system.orgTemplate.addOptions': 'Add an option', 'system.orgTemplate.addOptions': 'Add an option',
@ -57,4 +59,37 @@ export default {
'system.orgTemplate.dateFormat': 'Date format', 'system.orgTemplate.dateFormat': 'Date format',
'system.orgTemplate.formatPlaceholder': 'Please select a format', 'system.orgTemplate.formatPlaceholder': 'Please select a format',
'system.orgTemplate.optionsPlaceholder': 'Please enter options', '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.TemplateManagement': '模版管理',
'system.orgTemplate.workflowSetup': '工作流设置', 'system.orgTemplate.workflowSetup': '工作流设置',
'system.orgTemplate.enable': '启用项目模版', 'system.orgTemplate.enable': '启用项目模版',
'system.orgTemplate.update': '更新', 'system.orgTemplate.update': '更新字段',
'system.orgTemplate.addField': '新增字段', 'system.orgTemplate.addField': '新增字段',
'system.orgTemplate.createField': '添加字段',
'system.orgTemplate.enableDescription': '已启用项目模版,字段设置不可操作', 'system.orgTemplate.enableDescription': '已启用项目模版,字段设置不可操作',
'system.orgTemplate.fieldLimit': '最多可新增 20 个字段',
'system.orgTemplate.isSystem': '系统', 'system.orgTemplate.isSystem': '系统',
'system.orgTemplate.fieldList': '字段列表', 'system.orgTemplate.fieldList': '字段列表',
'system.orgTemplate.addOptions': '添加一个选项', 'system.orgTemplate.addOptions': '添加一个选项',
@ -56,4 +58,40 @@ export default {
'system.orgTemplate.dateFormat': '日期格式', 'system.orgTemplate.dateFormat': '日期格式',
'system.orgTemplate.formatPlaceholder': '请选择格式', 'system.orgTemplate.formatPlaceholder': '请选择格式',
'system.orgTemplate.optionsPlaceholder': '请输入选项', '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 class="ms-scroll">
<div v-for="(item, index) in recordItem.pluginForms" :key="item.id" class="ms-self"> <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="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 item.name
}}</span> }}</span>
</div> </div>
@ -24,8 +24,16 @@
recordItem: PluginItem; recordItem: PluginItem;
}>(); }>();
const emit = defineEmits<{ 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> </script>
<style scoped lang="less"> <style scoped lang="less">

View File

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

View File

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