refactor(系统管理): 组织模板页面重构&级联对接接口

This commit is contained in:
xinxin.wu 2023-10-29 20:44:14 +08:00 committed by 刘瑞斌
parent 3e29ff80d2
commit 347d548fa9
28 changed files with 880 additions and 428 deletions

View File

@ -2,12 +2,20 @@ import MSR from '@/api/http/index';
import {
DeletePluginUrl,
GetPluginListUrl,
GetPluginOptionsUrl,
GetScriptUrl,
UpdatePluginUrl,
UploadPluginUrl,
} from '@/api/requrls/setting/plugin';
import type { AddReqData, PluginItem, PluginList, UpdatePluginModel, UploadFile } from '@/models/setting/plugin';
import type {
AddReqData,
OptionsParams,
PluginItem,
PluginList,
UpdatePluginModel,
UploadFile,
} from '@/models/setting/plugin';
export function getPluginList() {
return MSR.get<PluginList>({ url: GetPluginListUrl });
@ -24,3 +32,8 @@ export function deletePluginReq(id: string) {
export function getScriptDetail(pluginId: string, scriptId: string) {
return MSR.get({ url: GetScriptUrl, params: `${pluginId}/${scriptId}` });
}
// 获取插件下拉选项级联统一接口
export function getPluginOptions(data: OptionsParams) {
return MSR.post<{ text: string; value: string }[]>({ url: GetPluginOptionsUrl, data });
}

View File

@ -46,8 +46,12 @@ 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}` });
export function isEnableTemplate(organizationId: string) {
return MSR.get<Record<string, boolean>>({ url: `${isEnableTemplateUrl}/${organizationId}` });
}
// 删除模板
export function deleteOrdTemplate(id: string) {
return MSR.get({ url: `${DeleteOrganizeTemplateUrl}/${id}` });
}
/** *

View File

@ -3,3 +3,4 @@ export const UploadPluginUrl = '/plugin/add';
export const UpdatePluginUrl = '/plugin/update';
export const DeletePluginUrl = '/plugin/delete';
export const GetScriptUrl = '/plugin/script/get';
export const GetPluginOptionsUrl = '/plugin/options';

View File

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

View File

@ -359,6 +359,9 @@
border: 1px solid var(--color-text-input-border);
}
}
.arco-checkbox-disabled.arco-checkbox-checked .arco-checkbox-icon {
background: rgb(var(--primary-2)) !important ;
}
/** radio **/
.arco-radio-group-button {

View File

@ -20,6 +20,7 @@ export const SELECT = {
multiple: false,
placeholder: '请选择',
options: [],
modelValue: '',
},
};
@ -33,6 +34,7 @@ export const MULTIPLE_SELECT = {
multiple: true,
placeholder: '请选择',
options: [],
modelValue: [],
},
};
@ -61,6 +63,7 @@ export const MEMBER = {
props: {
multiple: false,
placeholder: '请选择',
modelValue: '',
},
};
@ -74,6 +77,7 @@ export const MULTIPLE_MEMBER = {
multiple: true,
placeholder: '请选择',
options: [],
modelValue: [],
},
};

View File

@ -1,5 +1,5 @@
<template>
<FormCreate v-model:api="formApi" :rule="formRules" :option="props.options || option"></FormCreate>
<FormCreate v-model:api="formApi" :rule="formRuleList" :option="props.options || option"></FormCreate>
</template>
<script setup lang="ts">
@ -16,7 +16,7 @@
import type { FormItem } from './types';
import { FormRuleItem } from './types';
import formCreate, { FormRule } from '@form-create/arco-design';
import formCreate from '@form-create/arco-design';
const formCreateStore = useFormCreateStore();
@ -49,9 +49,9 @@
formCreateKey: FormCreateKeyEnum[keyof FormCreateKeyEnum]; // Key
}>();
const formApi = ref<any>({});
const emit = defineEmits(['update:form-rule']);
const formRules = ref<FormRule | undefined>([]);
const formApi = ref<any>({});
//
const cascadeItem = computed(() => {
@ -97,11 +97,62 @@
);
watchEffect(() => {
formCreateStore.setInitFormCreate(props.formCreateKey, props.formRule);
formCreateStore.initFormCreateFormRules(props.formCreateKey);
formRules.value = formCreateStore.formCreateRuleMap.get(props.formCreateKey);
// formRules.value = cloneDeep(props.formRule);
// formCreateStore.setInitFormCreate(props.formCreateKey, props.formRule);
// formCreateStore.initFormCreateFormRules(props.formCreateKey);
});
const formRuleList = ref<FormRuleItem[]>([]); //
const formRules = ref<FormItem[]>([]);
watch(
() => props.formRule,
(val) => {
formRules.value = props.formRule;
formCreateStore.setInitFormCreate(props.formCreateKey, props.formRule);
formCreateStore.initFormCreateFormRules(props.formCreateKey);
formRuleList.value = formCreateStore.formCreateRuleMap.get(props.formCreateKey) as FormRuleItem[];
},
{ deep: true, immediate: true }
);
const formData = computed(() => {
return formCreateStore.formRuleMap.get(props.formCreateKey);
});
watch(
() => formData.value,
(val) => {
formRuleList.value = formCreateStore.formCreateRuleMap.get(props.formCreateKey) as FormRuleItem[];
}
);
watch(
() => formRuleList.value,
() => {
//
const result = formRuleList.value.map((item: any) => {
const type = props.formRule.find((it) => it.name === item.field)?.type;
const formItemRule = {
name: item.field,
type,
label: item.title,
value: item.value,
required: item?.effect?.required,
inputSearch: item.props.inputSearch || false,
instructionsIcon: item.props.instructionsIcon || '',
optionMethod: item.props.optionMethod || '',
couplingConfig: {
type: item.props.couplingConfig.type,
cascade: item.link,
},
};
return formItemRule;
});
formCreateStore.setInitFormCreate(props.formCreateKey, result as FormItem[]);
},
{ deep: true }
);
defineExpose({
formApi, // form-createAPI
});

View File

@ -1,6 +1,6 @@
<template>
<a-select
v-model="selectValue"
v-model:model-value="selectValue"
:placeholder="t(props.placeholder || 'common.pleaseSelect')"
allow-search
:multiple="props.multiple"
@ -14,7 +14,18 @@
import { ref } from 'vue';
import { debounce } from 'lodash-es';
import { getPluginOptions } from '@/api/modules/setting/pluginManger';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import useFormCreateStore from '@/store/modules/form-create/form-create';
import type { OptionsParams } from '@/models/setting/plugin';
import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
const appStore = useAppStore();
const attrs = useAttrs();
const formCreateStore = useFormCreateStore();
const { t } = useI18n();
const props = withDefaults(
@ -35,54 +46,70 @@
);
const emit = defineEmits(['update:model-value']);
const selectValue = ref([]);
const selectValue = ref<string[] | string>();
const optionsList = ref<{ label: string; value: string }[]>([]);
//
const innerKeyword = ref<string | undefined>('');
async function getOptionsList() {
if (props.inputSearch && props.optionMethod) {
const params = ref<OptionsParams>();
async function getLinksItem() {
const { formKey } = attrs;
const formRulesList = formCreateStore.formRuleMap.get(formKey as FormCreateKeyEnum[keyof FormCreateKeyEnum]);
if (formRulesList && props.optionMethod) {
params.value = {
pluginId: props.keyword as string,
organizationId: appStore.currentOrgId,
projectConfig: formRulesList,
optionMethod: props.optionMethod,
};
//
// console.log(selectValue.value, props.keyword, props.modelValue);
try {
setTimeout(() => {
// console.log('');
optionsList.value = [
{
value: '111',
label: '测试测试测试111111',
},
];
}, 1000);
const res = await getPluginOptions(params.value);
optionsList.value = res.map((item) => {
return {
label: item.text,
value: item.value,
};
});
} catch (error) {
console.log(error);
}
}
}
const searchHandler = debounce(async (inputVal: string) => {
//
const innerKeyword = ref<string | undefined>('');
const searchHandler = debounce((inputVal: string) => {
innerKeyword.value = inputVal;
getOptionsList();
}, 300);
watch(
() => props.modelValue,
(val) => {
selectValue.value = val as any;
}
if (val) {
selectValue.value = val as any;
}
},
{ immediate: true }
);
watch(
() => props.keyword,
(val) => {
if (val) {
innerKeyword.value = val;
getOptionsList();
if (props.inputSearch && props.optionMethod) {
getLinksItem();
}
}
}
);
watch(
() => selectValue.value,
(val) => {
emit('update:model-value', val);
async (val) => {
selectValue.value = val;
await emit('update:model-value', val);
}
);

View File

@ -18,7 +18,8 @@ export type FormItemType =
| 'MULTIPLE_INPUT'
| 'INT'
| 'FLOAT'
| 'NUMBER';
| 'NUMBER'
| undefined;
// 表单选项
export interface FormItemComplexCommonConfig {
@ -51,10 +52,10 @@ export interface FormItem {
// 表单联动配置
couplingConfig?: {
// 联动类型visible显示隐藏disabled禁用启用filterOptions过滤选项disabledOptions禁用选项initOptions初始化选项。都由联动的表单项触发
type: 'visible' | 'disabled' | 'filterOptions' | 'disabledOptions' | 'initOptions'; // 目前初始化选项
cascade: string; // 联动表单项名称
matchRule: 'same' | 'includes' | 'excludes' | RegExp; // 联动匹配规则same值相同includes值包含excludes值不包含 RegExp自定义匹配正则表达式 // 场景 目前只考虑等于情况
}[];
type?: 'initOptions'; // 目前初始化选项
cascade?: string; // 联动表单项名称
matchRule?: 'same' | 'includes' | 'excludes' | RegExp; // 联动匹配规则same值相同includes值包含excludes值不包含 RegExp自定义匹配正则表达式 // 场景 目前只考虑等于情况
};
// 表单布局
wrap?: Record<string, any>;
}

View File

@ -56,6 +56,7 @@ export default {
'menu.settings.organization.templateFieldSetting': '字段设置',
'menu.settings.organization.templateManagementList': '模版列表',
'menu.settings.organization.templateManagementDetail': '创建模版',
'menu.settings.organization.templateManagementCopy': '复制模版',
'menu.settings.organization.templateManagementEdit': '更新模板',
'menu.settings.organization.log': '日志',
'navbar.action.locale': '切换为中文',

View File

@ -1,3 +1,5 @@
import { FormItem } from '@/components/pure/ms-form-create/types';
export interface organizationItem {
id?: string;
num: number;
@ -92,3 +94,11 @@ export interface DrawerReqParams {
export interface PluginState {
doNotShowAgain: boolean;
}
// 定义统一插件下拉选项请求参数
export interface OptionsParams {
pluginId: string;
organizationId: string;
optionMethod: string;
projectConfig: Record<string, any>[] | FormItem[];
}

View File

@ -49,6 +49,7 @@ export interface DefinedFieldItem {
required?: boolean | undefined;
fApi?: any; // 表单值
formRules?: FormRuleItem[] | FormItem[] | FormRule[]; // 表单列表
[key: string]: any;
}
// 创建自定义字段
@ -67,6 +68,7 @@ export interface AddOrUpdateField {
remark: string; // 备注
scopeId: string; // 组织或项目ID
options?: FieldOption[];
enableOptionKey: boolean;
}
export interface fieldIconAndNameModal {
@ -98,7 +100,7 @@ export interface CustomField {
fieldId: string;
required: boolean; // 是否必填
apiFieldId: string; // api字段名
defaultValue: string; // 默认值
defaultValue: string | string[] | null | number; // 默认值
}
export interface ActionTemplateManage {
@ -107,6 +109,6 @@ export interface ActionTemplateManage {
remark: string;
scopeId: string;
enableThirdPart?: boolean; // 是否开启api字段名配置
scene: SeneType;
customFields: CustomField[];
scene?: SeneType;
customFields?: CustomField[];
}

View File

@ -232,7 +232,7 @@ const Setting: AppRouteRecordRaw = {
},
// 模板列表-模板管理-创建&编辑模版
{
path: 'templateManagementDetail',
path: 'templateManagementDetail/:mode?',
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL,
component: () => import('@/views/setting/organization/template/components/templateDetail.vue'),
meta: {

View File

@ -28,7 +28,7 @@ const useFormCreateStore = defineStore('form-create', {
// 当前类型
let fieldType;
const currentTypeForm = Object.keys(FieldTypeFormRules).find(
(formItemType: any) => item.type.toUpperCase() === formItemType
(formItemType: any) => item.type?.toUpperCase() === formItemType
);
if (currentTypeForm) {
fieldType = FieldTypeFormRules[currentTypeForm].type;
@ -39,17 +39,18 @@ const useFormCreateStore = defineStore('form-create', {
value: optionsItem.value,
};
});
return {
const ruleItem = {
type: fieldType, // 表单类型
field: item.name, // 字段
title: item.label, // label 表单标签
value: FieldTypeFormRules[currentTypeForm].value, // 目前的值
value: item.value || FieldTypeFormRules[currentTypeForm].value, // 目前的值
effect: {
required: item.required, // 是否必填
},
// 级联关联到某一个form上 可能存在多个级联
options: !item.optionMethod ? currentOptions : [],
link: item.couplingConfig?.map((cascadeItem: any) => cascadeItem.cascade),
// link: item.couplingConfig?.map((cascadeItem: any) => cascadeItem.cascade),
link: item.couplingConfig?.cascade,
rule: item.validate || [],
// 梳理表单所需要属性
props: {
@ -67,8 +68,18 @@ const useFormCreateStore = defineStore('form-create', {
'keyword': '',
'modelValue': item.value,
'options': currentOptions,
'formKey': key,
},
};
// 如果不存在关联name删除link关联属性
if (ruleItem.link === '') {
delete ruleItem.link;
}
// 如果不是等于下拉多选或者单选等
if (ruleItem.type !== 'SearchSelect') {
delete ruleItem.props.inputSearch;
}
return ruleItem;
}
return {};
});

View File

@ -2,35 +2,31 @@ import { defineStore } from 'pinia';
import { isEnableTemplate } from '@/api/modules/setting/template';
import type { DefinedFieldItem } from '@/models/setting/template';
import useAppStore from '../app';
const appStore = useAppStore();
const useTemplateStore = defineStore('template', {
persist: true,
state: (): { templateStatus: Record<string, boolean>; previewList: DefinedFieldItem[] } => ({
state: (): {
templateStatus: Record<string, boolean>;
} => ({
templateStatus: {
FUNCTIONAL: false,
API: false,
UI: false,
TEST_PLAN: false,
BUG: false,
FUNCTIONAL: true,
API: true,
UI: true,
TEST_PLAN: true,
BUG: true,
},
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;
async getStatus() {
try {
this.templateStatus = await isEnableTemplate(appStore.currentOrgId);
} catch (error) {
console.log(error);
}
},
},
});

View File

@ -108,7 +108,7 @@
} from '@/api/modules/project-management/projectMember';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import { useTableStore, useUserStore } from '@/store';
import { useAppStore, useTableStore } from '@/store';
import { characterLimit } from '@/utils';
import type {
@ -121,10 +121,10 @@
const { t } = useI18n();
const { openModal } = useModal();
const appStore = useAppStore();
const tableStore = useTableStore();
const userStore = useUserStore();
const lastProjectId = userStore?.$state?.lastProjectId;
const lastProjectId = appStore.getCurrentProjectId;
const columns: MsTableColumn = [
{
@ -187,7 +187,6 @@
const handleTableSelect = (selectArr: (string | number)[]) => {
tableSelected.value = selectArr;
console.log(selectArr, 'selectArrselectArrselectArr');
};
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(getProjectMemberList, {

View File

@ -1,6 +1,6 @@
<template>
<div>项目版本 waiting for development </div>
<MsFormCreate :form-rule="formRules" :form-create-key="FormCreateKeyEnum.ORGANIZE_TEMPLATE" />
<MsFormCreate v-model:form-rule="formRules" :form-create-key="FormCreateKeyEnum.ORGANIZE_TEMPLATE" />
<br />
<br />
</template>
@ -15,14 +15,23 @@
const formRules = ref<FormItem[]>([
{
type: 'INPUT',
type: 'SELECT',
name: 'name',
label: '姓名',
value: '',
subDesc: '请输入姓名',
required: true,
options: [
{
value: '1001',
text: '单选',
},
{
value: '1002',
text: '多选',
},
],
},
{
type: 'MULTIPLE_SELECT',
name: 'gender',
@ -32,13 +41,12 @@
optionMethod: 'getGenderOptions',
inputSearch: true,
required: true,
couplingConfig: [
{
type: 'initOptions',
cascade: 'name',
matchRule: 'includes',
},
],
couplingConfig: {
type: 'initOptions',
cascade: 'name',
matchRule: 'includes',
},
options: [
{
value: '1',
@ -51,7 +59,7 @@
],
},
{
type: 'INPUT',
type: 'TEXTAREA',
name: 'member',
label: '成员',
value: '',
@ -76,7 +84,7 @@
},
{
type: 'INT',
name: 'birthday',
name: 'birthday1',
label: '出生日期',
value: 0,
subDesc: '请选择出生日期',
@ -108,13 +116,11 @@
text: '多选',
},
],
couplingConfig: [
{
type: 'initOptions',
cascade: 'member',
matchRule: 'includes',
},
],
couplingConfig: {
type: 'initOptions',
cascade: 'member',
matchRule: 'includes',
},
},
{
type: 'SELECT',
@ -134,13 +140,11 @@
text: '多选',
},
],
couplingConfig: [
{
type: 'initOptions',
cascade: 'member',
matchRule: 'includes',
},
],
couplingConfig: {
type: 'initOptions',
cascade: 'member',
matchRule: 'includes',
},
},
]);

View File

@ -4,7 +4,8 @@
:title="t('system.orgTemplate.createField')"
:ok-text="t('system.orgTemplate.save')"
:ok-loading="drawerLoading"
:width="800"
width="65%"
min-width="800px"
unmount-on-close
:show-continue="false"
@confirm="handleDrawerConfirm"
@ -15,78 +16,74 @@
<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">
<a-checkbox :model-value="isCheckedAll" :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">
<div>
<a-checkbox-group v-model="selectSystemIds" class="checkboxContainer">
<div v-for="field in systemField" :key="field.id" class="item checkbox">
<a-checkbox :value="field.id" :disabled="field.internal"
><a-tooltip :content="field.name">
<div class="checkbox">{{ field.name }}</div></a-tooltip
<div>{{ field.name }}</div></a-tooltip
></a-checkbox
>
</a-grid-item>
</a-grid>
</a-checkbox-group>
</div>
</a-checkbox-group>
</div>
</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">
<div>
<a-checkbox-group v-model="selectCustomIds" class="checkboxContainer">
<div v-for="field in customField" :key="field.id" class="item">
<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>
</div>
</a-checkbox-group>
</div>
<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>
<a-button class="mt-1 px-0" type="text" :disabled="totalData.length > 20" @click="createField">
<template #icon>
<icon-plus class="text-[14px]" />
</template>
{{ t('system.orgTemplate.addField') }}
</a-button>
</div>
</div>
</div>
<div class="selected-field">
<div class="selected-field w-[272px]">
<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 }">
<Draggable tag="div" :list="selectedList" ghost-class="ghost" item-key="dateIndex">
<template #item="{ element }">
<div class="selected-item">
<a-tooltip :content="item.name">
<span class="one-line-text w-[270px]">{{ item.name }}</span>
<a-tooltip :content="element.name">
<span>
<MsIcon type="icon-icon_drag" class="mt-[3px] text-[16px] text-[var(--color-text-4)]"
/></span>
<span class="one-line-text ml-2 w-[270px]">{{ element.name }}</span>
</a-tooltip>
<icon-close
v-if="!item.internal"
v-if="!element.internal"
:style="{ 'font-size': '14px' }"
class="cursor-pointer text-[var(--color-text-3)]"
@click="removeSelectedField(item.id)"
@click="removeSelectedField(element.id)"
/>
</div>
</template>
</MsList>
</Draggable>
</div>
</div>
</div>
@ -96,16 +93,18 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
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';
import Draggable from 'vuedraggable';
const { t } = useI18n();
const showAddDrawer = ref<boolean>(false);
@ -113,78 +112,97 @@
const props = defineProps<{
visible: boolean;
selectedData: DefinedFieldItem[]; //
systemData: DefinedFieldItem[]; //
customData: DefinedFieldItem[]; //
totalData: DefinedFieldItem[]; //
tableSelectData: DefinedFieldItem[]; //
}>();
const emit = defineEmits(['confirm', 'update:visible', 'update']);
const emit = defineEmits(['confirm', 'update:visible', 'update-data']);
//
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 totalList = ref<DefinedFieldItem[]>([]);
const checkedAll = ref<boolean>(false);
//
const systemField = computed(() => {
return totalList.value.filter((item: any) => item.internal);
});
//
const customField = computed(() => {
return totalList.value.filter((item: any) => !item.internal);
});
const selectSystemIds = ref<string[]>([]);
const selectCustomIds = ref<string[]>([]);
//
watch(
() => props.tableSelectData,
(val) => {
const sysField = props.tableSelectData.filter((item) => item.internal);
const cusField = props.tableSelectData.filter((item) => !item.internal);
selectSystemIds.value = sysField.map((item) => item.id);
selectCustomIds.value = cusField.map((item) => item.id);
}
);
// id
const totalIds = computed(() => {
return [...new Set([...selectSystemIds.value, ...selectCustomIds.value])];
});
const selectedList = ref<DefinedFieldItem[]>([]);
//
const isCheckSystemIdsAll = computed(() => {
return systemField.value.length === selectSystemIds.value.length;
});
//
const isCheckCustomIdsAll = computed(() => {
return customField.value.length === selectCustomIds.value.length;
});
//
const isCheckedAll = computed(() => {
return isCheckSystemIdsAll.value && isCheckCustomIdsAll.value;
});
//
const indeterminate = computed(() => {
return selectSystemIds.value.length + selectCustomIds.value.length === selectCustomIds.value.length;
});
//
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);
selectSystemIds.value = systemField.value.map((item) => item.id);
selectCustomIds.value = customField.value.map((item) => item.id);
} else {
checkedAll.value = false;
selectSystemData.value = [];
selectCustomField.value = [];
selectCustomIds.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;
//
watch(
() => totalIds.value,
(val) => {
const res = totalList.value.filter((item) => val.indexOf(item.id) > -1);
const result = res.sort((a, b) => {
return val.indexOf(a.id) - val.indexOf(b.id);
});
selectedList.value = result;
}
};
);
watchEffect(() => {
isCheckedAll();
//
getSelectedField();
selectedList.value = props.tableSelectData;
const sysField = props.tableSelectData.filter((item) => item.internal);
const cusField = props.tableSelectData.filter((item) => !item.internal);
selectSystemIds.value = sysField.map((item) => item.id);
selectCustomIds.value = cusField.map((item) => item.id);
selectedList.value = customField.value;
});
//
const removeSelectedField = (id: string) => {
selectSystemData.value = selectSystemData.value.filter((item) => item !== id);
selectCustomField.value = selectCustomField.value.filter((item) => item !== id);
selectCustomIds.value = selectCustomIds.value.filter((item) => item !== id);
selectedList.value = selectedList.value.filter((item) => item.id !== id);
};
//
@ -195,32 +213,23 @@
//
const clearHandler = () => {
selectSystemData.value = [];
selectCustomField.value = [];
selectCustomIds.value = [];
};
const handleDrawerCancel = () => {
showAddDrawer.value = false;
selectCustomIds.value = props.tableSelectData.filter((item) => !item.internal).map((item) => item.id);
selectSystemIds.value = props.tableSelectData.filter((item) => item.internal).map((item) => item.id);
};
const handleDrawerConfirm = () => {
emit('confirm', selectedFields.value);
emit('confirm', selectedList.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);
// eslint-disable-next-line vue/custom-event-name-casing
emit('update-data');
};
watch(
@ -235,14 +244,15 @@
showAddDrawer.value = val;
}
);
defineExpose({
removeSelectedField,
showSelectField,
watchEffect(() => {
totalList.value = props.totalData;
});
</script>
<style scoped lang="less">
:deep(.arco-drawer) {
width: 65% !important;
}
.panel-wrapper {
width: 100%;
height: 100%;
@ -264,13 +274,6 @@
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 {
@ -286,10 +289,25 @@
.selected-item {
height: 36px;
background: var(--color-bg-3);
cursor: move;
@apply mb-2 flex items-center justify-between rounded px-2;
}
}
}
}
}
.checkboxContainer {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(116px, 1fr));
grid-gap: 16px;
.checkbox {
width: 90px;
white-space: nowrap;
@apply overflow-hidden text-ellipsis;
}
}
.ghost {
border: 1px dashed rgba(var(--primary-5));
background-color: rgba(var(--primary-1));
}
</style>

View File

@ -4,7 +4,7 @@
:title="isEdit ? t('system.orgTemplate.update') : t('system.orgTemplate.addField')"
:ok-text="t(isEdit ? 'system.orgTemplate.update' : 'system.orgTemplate.addField')"
:ok-loading="drawerLoading"
:width="680"
:width="800"
:show-continue="!isEdit"
@confirm="handleDrawerConfirm"
@continue="handleDrawerConfirm(true)"
@ -36,9 +36,9 @@
}"
></a-textarea>
</a-form-item>
<a-form-item field="type" :label="t('system.orgTemplate.fieldType')" asterisk-position="end">
<a-form-item field="type" :label="t('system.orgTemplate.fieldType')" asterisk-position="end" :required="true">
<a-select
v-model="fieldType"
v-model="fieldForm.type"
class="w-[260px]"
:placeholder="t('system.orgTemplate.fieldTypePlaceholder')"
allow-clear
@ -53,7 +53,7 @@
</a-select>
</a-form-item>
<a-form-item
v-if="fieldType === 'MEMBER'"
v-if="fieldForm.type === 'MEMBER'"
field="type"
:label="t('system.orgTemplate.allowMultiMember')"
asterisk-position="end"
@ -63,18 +63,28 @@
<!-- 选项选择器 -->
<a-form-item
v-if="showOptionsSelect"
field="options"
field="optionsModels"
:label="t('system.orgTemplate.optionContent')"
asterisk-position="end"
:rules="[{ message: t('system.orgTemplate.optionContentRules') }]"
class="relative"
:class="[!fieldForm?.enableOptionKey ? 'max-w-[340px]' : 'w-full']"
>
<div v-if="sceneType === 'BUG'" class="optionsKey">
<a-checkbox v-model="fieldForm.enableOptionKey"
>选项KEY值
<a-tooltip :content="t('system.orgTemplate.thirdPartyPlatforms')"
><icon-question-circle
:style="{ 'font-size': '16px' }"
class="text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]" /></a-tooltip></a-checkbox
></div>
<MsBatchForm
ref="batchFormRef"
:models="optionsModels"
form-mode="create"
add-text="system.orgTemplate.addOptions"
:is-show-drag="true"
form-width="340px"
:form-width="!fieldForm?.enableOptionKey ? '340px' : ''"
:default-vals="fieldDefaultValues"
/>
</a-form-item>
@ -82,7 +92,9 @@
<a-form-item
v-if="showDateOrNumber"
field="selectFormat"
:label="fieldType === 'NUMBER' ? t('system.orgTemplate.numberFormat') : t('system.orgTemplate.dateFormat')"
:label="
fieldForm.type === 'NUMBER' ? t('system.orgTemplate.numberFormat') : t('system.orgTemplate.dateFormat')
"
asterisk-position="end"
>
<a-select
@ -106,6 +118,7 @@
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import type { FormItemType } from '@/components/pure/ms-form-create/types';
@ -124,7 +137,7 @@
const { t } = useI18n();
const route = useRoute();
const appStore = useAppStore();
const sceneType = route.query.type;
const props = defineProps<{
visible: boolean;
}>();
@ -136,14 +149,15 @@
const fieldFormRef = ref<FormInstance>();
const initFieldForm: AddOrUpdateField = {
name: '',
type: 'INPUT',
type: undefined,
remark: '',
scopeId: '',
scene: 'FUNCTIONAL',
options: [],
enableOptionKey: false,
};
const fieldForm = ref<AddOrUpdateField>({ ...initFieldForm });
const isEdit = computed(() => !!fieldForm.value.id);
const isEdit = ref<boolean>(false);
const selectFormat = ref<FormItemType>(); //
const isMultipleSelectMember = ref<boolean | undefined>(false); //
const fieldType = ref<FormItemType>(); //
@ -151,12 +165,12 @@
//
const showOptionsSelect = computed(() => {
const showOptionsType: FormItemType[] = ['RADIO', 'CHECKBOX', 'SELECT', 'MULTIPLE_SELECT'];
return showOptionsType.includes(fieldType.value as FormItemType);
return showOptionsType.includes(fieldForm.value.type as FormItemType);
});
//
const showDateOrNumber = computed(() => {
if (fieldType.value) return getFieldType(fieldType.value);
if (fieldForm.value.type) return getFieldType(fieldForm.value.type);
});
// -1.
@ -164,12 +178,43 @@
filed: 'text',
type: 'input',
label: '',
rules: [{ required: true, message: t('system.orgTemplate.optionContentRules') }],
rules: [
{ required: true, message: t('system.orgTemplate.optionContentRules') },
{ notRepeat: true, message: t('system.orgTemplate.optionsContentNoRepeat') },
],
placeholder: t('system.orgTemplate.optionsPlaceholder'),
hideAsterisk: true,
hideLabel: true,
});
const optionsModels: Ref<FormItemModel[]> = ref([{ ...onlyOptions.value }]);
// -2
const bugBatchFormRules = ref<FormItemModel[]>([
{
filed: 'text',
type: 'input',
label: '',
rules: [
{ required: true, message: t('system.orgTemplate.optionContentRules') },
{ notRepeat: true, message: t('system.orgTemplate.optionsContentNoRepeat') },
],
placeholder: 'system.orgTemplate.optionsPlaceholder',
hideAsterisk: true,
hideLabel: true,
},
{
filed: 'value',
type: 'input',
label: '',
rules: [
{ required: true, message: t('system.orgTemplate.optionsIdTip') },
{ notRepeat: true, message: t('system.orgTemplate.optionsIdNoRepeat') },
],
placeholder: 'system.orgTemplate.optionsIdPlaceholder',
hideAsterisk: true,
hideLabel: true,
},
]);
const optionsModels: Ref<FormItemModel[]> = ref([]);
const batchFormRef = ref<MsBatchFormInstance | null>(null);
const resetForm = () => {
@ -190,42 +235,49 @@
const confirmHandler = async (isContinue: boolean) => {
try {
drawerLoading.value = true;
if (fieldType.value) {
fieldForm.value.type = fieldType.value;
}
fieldForm.value.scene = route.query.type;
fieldForm.value.scopeId = appStore.currentOrgId;
const formCopy = cloneDeep(fieldForm.value);
formCopy.scene = route.query.type;
formCopy.scopeId = appStore.currentOrgId;
//
if (selectFormat.value) {
fieldForm.value.type = selectFormat.value;
formCopy.type = selectFormat.value;
}
// ||
if (isMultipleSelectMember.value) {
fieldForm.value.type = isMultipleSelectMember.value ? 'MULTIPLE_MEMBER' : 'MEMBER';
formCopy.type = isMultipleSelectMember.value ? 'MULTIPLE_MEMBER' : 'MEMBER';
}
//
if (selectFormat.value) {
fieldForm.value.type = selectFormat.value;
formCopy.type = selectFormat.value;
}
//
const { id, name, options, scopeId, scene, type, remark } = fieldForm.value;
const params: AddOrUpdateField = { name, options, scopeId, scene, type, remark };
const { id, name, options, scopeId, scene, type, remark, enableOptionKey } = formCopy;
if (isEdit) {
const params: AddOrUpdateField = {
name,
options,
scopeId,
scene,
type,
remark,
enableOptionKey,
};
if (id) {
params.id = id;
}
await addOrUpdateOrdField(params);
Message.success(isEdit ? t('common.addSuccess') : t('common.updateSuccess'));
Message.success(isEdit.value ? t('common.updateSuccess') : t('common.addSuccess'));
if (!isContinue) {
handleDrawerCancel();
}
resetForm();
emit('success');
emit('success', isEdit.value);
} catch (error) {
console.log(error);
} finally {
@ -241,7 +293,7 @@
fieldForm.value.options = (batchFormRef.value?.getFormResult() || []).map((item: any) => {
return {
...item,
value: getGenerateId(),
value: fieldForm.value.enableOptionKey ? item.value : getGenerateId(),
};
});
}
@ -291,13 +343,12 @@
const editHandler = (item: AddOrUpdateField) => {
showDrawer.value = true;
isMultipleSelectMember.value = item.type === 'MULTIPLE_MEMBER';
if (isEdit && item.id) {
if (item.id) {
getFieldDetail(item.id);
fieldForm.value = {
...item,
type: getSpecialHandler(item.type),
};
fieldType.value = fieldForm.value.type;
}
};
@ -320,6 +371,27 @@
showDrawer.value = val;
}
);
watchEffect(() => {
if (fieldForm.value.id) {
isEdit.value = true;
} else {
isEdit.value = false;
}
});
// KEY
watch(
() => fieldForm.value.enableOptionKey,
(val) => {
if (val && sceneType === 'BUG') {
optionsModels.value = cloneDeep(bugBatchFormRules.value);
} else {
optionsModels.value = [{ ...onlyOptions.value }];
}
},
{ immediate: true }
);
onMounted(() => {
const excludeOptions = ['MULTIPLE_MEMBER', 'DATETIME', 'SYSTEM', 'INT', 'FLOAT'];
fieldOptions.value = fieldIconAndName.filter((item: any) => excludeOptions.indexOf(item.key) < 0);
@ -330,4 +402,10 @@
});
</script>
<style scoped></style>
<style scoped lang="less">
.optionsKey {
position: absolute;
top: 0;
right: 0;
}
</style>

View File

@ -5,7 +5,7 @@
}}</a-alert>
<div class="mb-4 flex items-center justify-between">
<span v-if="isEnable" class="font-medium">{{ t('system.orgTemplate.fieldList') }}</span>
<a-button v-else type="primary" :disabled="totalData.length > 20" @click="fieldHandler('add')">
<a-button v-else type="primary" :disabled="isDisabled" @click="fieldHandler">
{{ t('system.orgTemplate.addField') }}
</a-button>
<a-input-search
@ -99,6 +99,7 @@
width: 300,
showDrag: true,
showInTable: true,
showTooltip: true,
},
{
title: 'system.orgTemplate.columnFieldType',
@ -154,15 +155,24 @@
console.log(error);
}
};
const scene = ref<SeneType>('');
const scene = ref<SeneType>(route.query.type);
//
const fetchData = async () => {
scene.value = route.query.type;
setLoadListParams({ organizationId: currentOrd, scene });
await loadList();
totalData.value = await getFieldList({ organizationId: currentOrd, scene: route.query.type });
};
const isDisabled = computed(() => {
return totalData.value.length > 20;
});
const tableRef = ref();
const isEnable = ref<boolean>(templateStore.templateStatus[scene.value as string]); //
const isEnable = computed(() => {
return templateStore.templateStatus[scene.value as string];
}); //
//
const isEnableOperation = () => {
@ -218,13 +228,12 @@
const showDrawer = ref<boolean>(false);
const fieldDrawerRef = ref();
const fieldHandler = (type: string, record?: AddOrUpdateField) => {
const fieldHandler = () => {
showDrawer.value = true;
if (type === 'edit' && record) fieldDrawerRef.value.editHandler(record);
};
const handleOk = (record: AddOrUpdateField) => {
fieldHandler('edit', record);
fieldDrawerRef.value.editHandler(record);
};
const successHandler = () => {
@ -242,9 +251,9 @@
};
onMounted(() => {
fetchData();
isEnableOperation();
updateBreadcrumbList();
isEnableOperation();
fetchData();
});
</script>

View File

@ -2,7 +2,7 @@
<MsCard
:loading="loading"
:title="title"
:is-edit="isEdit"
:is-edit="isEdit && route.params.mode !== 'copy'"
has-breadcrumb
@save="saveHandler"
@save-and-continue="saveHandler(true)"
@ -40,12 +40,21 @@
class="max-w-[732px]"
></a-textarea>
</a-form-item>
<a-form-item field="remark" label="" asterisk-position="end"
><a-checkbox v-model="templateForm.enableThirdPart">{{ t('system.orgTemplate.thirdParty') }}</a-checkbox>
</a-form-item>
</a-form>
<!-- 已有字段表 -->
<TemplateManagementTable ref="templateFieldTableRef" :custom-list="tableFiledDetailList" :is-edit="isEdit" />
<TemplateManagementTable
ref="templateFieldTableRef"
v-model:select-data="selectData"
:data="(totalTemplateField as DefinedFieldItem[])"
:enable-third-part="templateForm.enableThirdPart"
@update="updateHandler"
/>
</div>
<!-- 预览模式 -->
<PreviewTemplate v-else :select-field="selectFiledToTem" />
<PreviewTemplate v-else :select-field="(selectData as DefinedFieldItem[])" />
</MsCard>
</template>
@ -56,54 +65,92 @@
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import MsCard from '@/components/pure/ms-card/index.vue';
import { FieldTypeFormRules } from '@/components/pure/ms-form-create/form-create';
import TemplateManagementTable from './templateManagementTable.vue';
import PreviewTemplate from './viewTemplate.vue';
import {
createOrganizeTemplateInfo,
getFieldList,
getOrganizeTemplateInfo,
updateOrganizeTemplateInfo,
} from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n';
import useLeaveUnSaveTip from '@/hooks/useLeaveUnSaveTip';
import { useAppStore } from '@/store';
import useTemplateStore from '@/store/modules/setting/template';
import { sleep } from '@/utils';
import { scrollIntoView } from '@/utils/dom';
import type { ActionTemplateManage, CustomField } from '@/models/setting/template';
import type { ActionTemplateManage, CustomField, DefinedFieldItem } from '@/models/setting/template';
import { SettingRouteEnum } from '@/enums/routeEnum';
import { cardList } from './fieldSetting';
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const appStore = useAppStore();
const templateStore = useTemplateStore();
useLeaveUnSaveTip();
// useLeaveUnSaveTip();
const title = ref('');
const loading = ref(false);
const initTemplateForm = {
const initTemplateForm: ActionTemplateManage = {
id: '',
name: '',
remark: '',
scopeId: appStore.currentOrgId,
enableThirdPart: false,
};
const templateForm = ref({ ...initTemplateForm });
const templateForm = ref<ActionTemplateManage>({ ...initTemplateForm });
const selectData = ref<DefinedFieldItem[]>([]); //
const selectFiled = ref<DefinedFieldItem[]>([]);
const formRef = ref<FormInstance>();
const totalTemplateField = ref<DefinedFieldItem[]>([]);
const isEdit = computed(() => !!route.query.id);
const currentOrd = appStore.currentOrgId;
const isEditField = ref<boolean>(false);
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;
const { name, customFields } = res;
templateForm.value = {
...res,
name: route.params.mode === 'copy' ? `${name}_copy` : name,
};
if (route.params.mode === 'copy') {
templateForm.value.id = undefined;
}
//
const customFieldsIds = customFields.map((index: any) => index.fieldId);
const result = totalTemplateField.value.filter((item) => {
const currentCustomFieldIndex = customFieldsIds.findIndex((it: any) => it === item.id);
if (customFieldsIds.indexOf(item.id) > -1) {
const currentForm = item.formRules?.map((it: any) => {
it.props.modelValue = customFields[currentCustomFieldIndex].defaultValue;
return {
...it,
value: customFields[currentCustomFieldIndex].defaultValue,
};
});
const formItem = item;
formItem.formRules = cloneDeep(currentForm);
formItem.apiFieldId = customFields[currentCustomFieldIndex].apiFieldId;
formItem.required = customFields[currentCustomFieldIndex].required;
return true;
}
return false;
});
selectData.value = result;
} catch (error) {
console.log(error);
} finally {
@ -111,29 +158,88 @@
}
};
const isEdit = ref(false);
const templateFieldTableRef = ref();
watchEffect(() => {
if (route.query.id) {
//
const getFieldOptionList = () => {
totalTemplateField.value = totalTemplateField.value.map((item: any) => {
const currentFormRules = FieldTypeFormRules[item.type];
let selectOptions: any = [];
if (item.options && item.options.length) {
selectOptions = item.options.map((optionItem: any) => {
return {
label: optionItem.text,
value: optionItem.value,
};
});
currentFormRules.options = selectOptions;
}
return {
...item,
formRules: [
{ ...currentFormRules, value: item.value, props: { ...currentFormRules.props, options: selectOptions } },
],
fApi: null,
required: item.internal,
};
});
//
if (!isEdit.value && !isEditField.value) {
selectData.value = totalTemplateField.value.filter((item) => item.internal);
}
};
//
const getClassifyField = async () => {
try {
totalTemplateField.value = await getFieldList({ organizationId: currentOrd, scene: route.query.type });
getFieldOptionList();
//
if (isEditField.value) {
selectData.value = totalTemplateField.value.filter(
(item) => selectFiled.value.map((it) => it.id).indexOf(item.id) > -1
);
}
if (isEdit.value) {
getTemplateInfo();
}
} catch (error) {
console.log(error);
}
};
watchEffect(async () => {
if (isEdit.value && route.params.mode === 'copy') {
title.value = t('system.orgTemplate.copyTemplate');
getClassifyField();
} else if (isEdit.value) {
title.value = t('menu.settings.organization.templateManagementEdit');
isEdit.value = true;
getTemplateInfo();
getClassifyField();
} 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;
const result = selectData.value.map((item) => {
if (item.formRules?.length) {
const { value } = item.formRules[0];
return {
fieldId: item.id,
required: item.required,
apiFieldId: item.apiFieldId,
defaultValue: value,
};
}
return [];
});
const { name, remark, enableThirdPart, id } = templateForm.value;
return {
id,
name,
remark,
customFields: result,
enableThirdPart,
customFields: result as CustomField[],
scopeId: appStore.currentOrgId,
scene: route.query.type,
};
@ -145,13 +251,14 @@
const isContinueFlag = ref(false);
//
async function save() {
try {
loading.value = true;
const params = getTemplateParams();
if (isEdit.value) {
if (isEdit.value && route.params.mode !== 'copy') {
await updateOrganizeTemplateInfo(params);
Message.success(t('system.resourcePool.updateSuccess'));
Message.success(t('system.orgTemplate.updateSuccess'));
} else {
await createOrganizeTemplateInfo(params);
Message.success(t('system.orgTemplate.addSuccess'));
@ -160,7 +267,7 @@
resetForm();
} else {
await sleep(300);
router.push({ name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL });
router.push({ name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT, query: route.query });
}
} catch (error) {
console.log(error);
@ -170,30 +277,57 @@
}
//
async function saveHandler(isContinue = false) {
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' });
formRef.value?.validate().then((res) => {
if (!res) {
return save();
}
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);
}
}
// title
const breadTitle = computed(() => {
const firstBreadTitle = cardList.find((item) => item.key === route.query.type)?.name;
const ThirdBreadTitle = title.value;
return {
firstBreadTitle,
ThirdBreadTitle,
};
});
//
const setBreadText = () => {
const { breadcrumbList } = appStore;
const { firstBreadTitle, ThirdBreadTitle } = breadTitle.value;
if (firstBreadTitle) {
breadcrumbList[0].locale = firstBreadTitle;
if (appStore.breadcrumbList.length > 2) {
breadcrumbList[2].locale = ThirdBreadTitle;
}
appStore.setBreadcrumbList(breadcrumbList);
}
};
//
const updateHandler = (flag: boolean) => {
isEditField.value = flag;
selectFiled.value = selectData.value;
getClassifyField();
};
onMounted(() => {
templateFieldTableRef.value.setDefaultField();
setBreadText();
getClassifyField();
if (!isEdit.value) {
selectData.value = totalTemplateField.value.filter((item) => item.internal);
}
});
</script>

View File

@ -17,14 +17,14 @@
</span>
<span class="operation hover:text-[rgb(var(--primary-5))]">
<span @click="templateManagement">{{ t('system.orgTemplate.TemplateManagement') }}</span>
<a-divider v-if="props.cardItem.key == 'BUG'" direction="vertical" />
<a-divider v-if="!props.cardItem.enable || props.cardItem.key === 'BUG'" direction="vertical" />
</span>
<span v-if="props.cardItem.key === 'BUG'" class="operation hover:text-[rgb(var(--primary-5))]">
<span>{{ t('system.orgTemplate.workflowSetup') }}</span>
<a-divider v-if="!props.cardItem.enable" direction="vertical" />
<a-divider v-if="!props.cardItem.enable && props.cardItem.key === 'BUG'" direction="vertical" />
</span>
<span v-if="!props.cardItem.enable" class="rounded p-[2px] hover:bg-[rgb(var(--primary-9))]">
<MsTableMoreAction :list="moreActions" @select="handleMoreActionSelect"
<MsTableMoreAction :list="moreActions" @select="(item) => handleMoreActionSelect"
/></span>
</div>
</div>
@ -36,15 +36,19 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import { isEnableTemplate } from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import { SettingRouteEnum } from '@/enums/routeEnum';
const { t } = useI18n();
const appStore = useAppStore();
const props = defineProps<{
cardItem: Record<string, any>;
@ -60,7 +64,21 @@
},
]);
const handleMoreActionSelect = (item: ActionsItem) => {};
//
const enableHandler = async () => {
try {
await isEnableTemplate(appStore.currentOrgId);
Message.success(t('system.orgTemplate.enabledSuccessfully'));
} catch (error) {
console.log(error);
}
};
const handleMoreActionSelect = (item: ActionsItem) => {
if (item.eventTag === 'enable') {
enableHandler();
}
};
//
const fieldSetting = () => {

View File

@ -23,7 +23,7 @@
<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>
<MsButton class="!mr-0" @click="copyTemplate(record.id)">{{ t('system.orgTemplate.copy') }}</MsButton>
<a-divider v-if="!record.internal" direction="vertical" />
<MsTableMoreAction
v-if="!record.internal"
@ -52,7 +52,7 @@
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 { deleteOrdTemplate, getOrganizeTemplateList } from '@/api/modules/setting/template';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import router from '@/router';
@ -64,6 +64,8 @@
import { SettingRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { cardList } from './fieldSetting';
const route = useRoute();
const { t } = useI18n();
const tableStore = useTableStore();
@ -71,8 +73,6 @@
const templateStore = useTemplateStore();
const { openModal } = useModal();
const isEnable = ref<boolean>(false);
const keyword = ref('');
const currentOrd = appStore.currentOrgId;
@ -118,12 +118,14 @@
showPagination: false,
heightUsed: 380,
});
const scene = route.query.type;
const isEnable = templateStore.templateStatus[scene as string];
const totalList = ref<OrdTemplateManagement[]>([]);
//
const searchFiled = async () => {
try {
totalList.value = await getOrganizeTemplateList({ organizationId: currentOrd, scene: route.query.type });
totalList.value = await getOrganizeTemplateList({ organizationId: currentOrd, scene });
const filterData = totalList.value.filter((item: OrdTemplateManagement) => item.name.includes(keyword.value));
setProps({ data: filterData });
} catch (error) {
@ -142,7 +144,7 @@
const handlerDelete = (record: any) => {
openModal({
type: 'error',
title: t('system.orgTemplate.deleteTitle', { name: characterLimit(record.name) }),
title: t('system.orgTemplate.deleteTemplateTitle', { name: characterLimit(record.name) }),
content: t('system.userGroup.beforeDeleteUserGroup'),
okText: t('system.userGroup.confirmDelete'),
cancelText: t('system.userGroup.cancel'),
@ -151,8 +153,8 @@
},
onBeforeOk: async () => {
try {
if (record.id) await deleteOrdField(record.id);
Message.success(t('system.user.deleteUserSuccess'));
if (record.id) await deleteOrdTemplate(record.id);
Message.success(t('common.deleteSuccess'));
loadList();
} catch (error) {
console.log(error);
@ -170,19 +172,20 @@
};
const fetchData = async () => {
const scene = route.query.type;
setLoadListParams({ organizationId: currentOrd, scene });
setLoadListParams({ organizationId: currentOrd, scene: route.query.type });
await loadList();
};
//
const createTemplate = () => {
templateStore.setPreviewHandler([]);
router.push({
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL,
query: {
type: route.query.type,
},
params: {
mode: 'create',
},
});
};
@ -191,14 +194,55 @@
router.push({
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL,
query: {
type: route.query.type,
id,
type: route.query.type,
},
params: {
mode: 'edit',
},
});
};
//
const copyTemplate = (id: string) => {
router.push({
name: SettingRouteEnum.SETTING_ORGANIZATION_TEMPLATE_MANAGEMENT_DETAIL,
query: {
id,
type: route.query.type,
},
params: {
mode: 'copy',
},
});
};
//
const updateBreadcrumbList = () => {
const { breadcrumbList } = appStore;
const breadTitle = cardList.find((item) => item.key === route.query.type);
if (breadTitle) {
breadcrumbList[0].locale = breadTitle.name;
appStore.setBreadcrumbList(breadcrumbList);
}
};
const tableRef = ref();
function updateColumns() {
if (isEnable) {
const result = fieldColumns.slice(0, fieldColumns.length - 1);
tableStore.setColumns(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT, result, 'drawer');
tableRef.value.initColumn();
} else {
tableStore.setColumns(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT, fieldColumns, 'drawer');
tableRef.value.initColumn();
}
}
onMounted(() => {
updateBreadcrumbList();
fetchData();
updateColumns();
});
</script>

View File

@ -5,6 +5,14 @@
<span class="ml-2">{{ record.name }}</span>
<span v-if="record.internal" class="system-flag">{{ t('system.orgTemplate.isSystem') }}</span>
</template>
<template #apiFieldId="{ record }">
<a-input
v-model="record.apiFieldId"
class="min-w-[200px] max-w-[300px]"
:placeholder="t('system.orgTemplate.apiInputPlaceholder')"
allow-clear
/>
</template>
<template #defaultValue="{ record }">
<div class="form-create-wrapper w-full">
<MsFormCreate v-model:api="record.fApi" :rule="record.formRules" :option="configOptions" />
@ -27,11 +35,10 @@
<AddFieldToTemplateDrawer
ref="fieldSelectRef"
v-model:visible="showDrawer"
:system-data="(systemData as DefinedFieldItem[])"
:custom-data="(customData as DefinedFieldItem[])"
:selected-data="(templateStore.previewList as DefinedFieldItem[])"
:total-data="(totalData as DefinedFieldItem[])"
:table-select-data="(selectList as DefinedFieldItem[])"
@confirm="confirmHandler"
@update="updateFieldHandler"
@update-data="updateFieldHandler"
/>
<a-button class="mt-3 px-0" type="text" @click="addField">
<template #icon>
@ -47,7 +54,6 @@
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';
@ -55,40 +61,46 @@
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 { useTableStore } from '@/store';
import type { CustomField, DefinedFieldItem } from '@/models/setting/template';
import type { 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 props = withDefaults(
defineProps<{
enableThirdPart: boolean; //
data: DefinedFieldItem[]; //
selectData: Record<string, any>[]; //
}>(),
{
enableThirdPart: false,
}
);
const templateFieldColumns: MsTableColumn = [
const emit = defineEmits(['update:select-data', 'update']);
const columns = ref<MsTableColumn>([
{
title: 'system.orgTemplate.name',
slotName: 'name',
dataIndex: 'name',
width: 300,
fixed: 'left',
showDrag: true,
showInTable: true,
showTooltip: true,
},
{
title: 'system.orgTemplate.defaultValue',
dataIndex: 'defaultValue',
slotName: 'defaultValue',
width: 300,
showDrag: true,
showInTable: true,
},
@ -105,6 +117,7 @@
dataIndex: 'remark',
showDrag: true,
showInTable: true,
showTooltip: true,
},
{
title: 'system.orgTemplate.operation',
@ -114,18 +127,28 @@
showInTable: true,
showDrag: false,
},
];
]);
tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_FIELD, templateFieldColumns, 'drawer');
function getApiColumns() {
return {
title: 'system.orgTemplate.apiFieldId',
dataIndex: 'apiFieldId',
slotName: 'apiFieldId',
showDrag: true,
showInTable: true,
width: 300,
};
}
tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_FIELD, columns.value, 'drawer');
const { propsRes, propsEvent, setProps } = useTable(undefined, {
tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_FIELD,
scroll: { x: '1000px' },
scroll: { x: '1800px' },
selectable: false,
noDisable: true,
size: 'default',
showSetting: true,
showPagination: false,
heightUsed: 560,
enableDrag: true,
});
@ -148,52 +171,17 @@
},
});
const showDrawer = ref<boolean>(false);
const totalData = ref<DefinedFieldItem[]>([]);
const data = ref<DefinedFieldItem[]>([]);
const systemData = ref<DefinedFieldItem[]>([]);
const customData = ref<DefinedFieldItem[]>([]);
const totalTemplateField = ref<DefinedFieldItem[]>([]);
const currentOrd = appStore.currentOrgId;
watchEffect(() => {
totalData.value = props.data;
});
//
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 selectList = ref<DefinedFieldItem[]>([]);
//
const confirmHandler = (dataList: DefinedFieldItem[]) => {
data.value = dataList;
setProps({ data: data.value });
selectList.value = dataList;
};
const showFieldDrawer = ref<boolean>(false);
@ -205,77 +193,77 @@
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 updateFieldHandler = async (isEdit: boolean) => {
emit('update', isEdit);
};
//
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;
selectList.value = selectList.value.filter((item) => item.id !== record.id);
};
watch(
() => data.value,
() => selectList.value,
(val) => {
templateStore.setPreviewHandler(val as DefinedFieldItem[]);
setProps({ data: templateStore.previewList });
if (val) {
emit('update:select-data', val);
}
},
{ deep: true }
);
const setDefaultField = async () => {
await getClassifyField();
data.value = systemData.value;
};
watch(
() => props.selectData,
(val) => {
if (val) {
selectList.value = val as DefinedFieldItem[];
setProps({ data: selectList.value });
}
},
{ immediate: true }
);
const showDetailFields = () => {};
onMounted(() => {
if (props.isEdit) {
showDetailFields();
} else {
data.value = templateStore.previewList;
setProps({ data: templateStore.previewList });
watch(
() => props.data,
(val) => {
totalData.value = props.data;
}
);
const tableRef = ref();
// API
watch(
() => props.enableThirdPart,
() => {
if (props.enableThirdPart) {
const result = [...columns.value.slice(0, 1), getApiColumns(), ...columns.value.slice(1)];
tableStore.setColumns(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_FIELD, result, 'drawer');
tableRef.value.initColumn();
} else {
tableStore.setColumns(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_FIELD, columns.value, 'drawer');
tableRef.value.initColumn();
}
}
);
//
const dragTableData = computed(() => {
return propsRes.value.data;
});
defineExpose({
getCustomFields,
getSelectFiled,
setDefaultField,
});
watch(
() => dragTableData.value,
(val) => {
selectList.value = dragTableData.value as DefinedFieldItem[];
emit('update:select-data', val);
}
);
</script>
<style scoped lang="less">
@ -287,4 +275,8 @@
width: 100% !important;
}
}
.system-flag {
background: var(--color-text-n8);
@apply ml-2 rounded p-1 text-xs;
}
</style>

View File

@ -88,7 +88,11 @@
</a-form>
</div>
<div class="preview-right px-4">
<MsFormCreate :form-rule="formRules" :form-create-key="FormCreateKeyEnum.ORGANIZE_TEMPLATE_PREVIEW_TEMPLATE" />
<MsFormCreate
ref="formCreateRef"
:form-rule="formRules"
:form-create-key="FormCreateKeyEnum.ORGANIZE_TEMPLATE_PREVIEW_TEMPLATE"
/>
</div>
</div>
</template>
@ -116,7 +120,7 @@
const tableStore = useTableStore();
const props = defineProps<{
selectField: DefinedFieldItem[];
selectField: DefinedFieldItem[]; //
}>();
const templateFieldColumns: MsTableColumn = [
@ -151,6 +155,7 @@
showDrag: false,
},
];
tableStore.initColumn(TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_STEP, templateFieldColumns, 'drawer');
const { propsRes, propsEvent, setProps } = useTable(undefined, {
tableKey: TableKeyEnum.ORGANIZATION_TEMPLATE_MANAGEMENT_STEP,
@ -186,8 +191,9 @@
eventTag: 'delete',
},
];
const addStep = () => {};
const handlerDelete = () => {};
//
const handleMoreActionSelect = (item: ActionsItem) => {
if (item.eventTag === 'delete') {
@ -195,10 +201,10 @@
}
};
const addStep = () => {};
const formRuleField = ref<FormItem[][]>([]);
const formRules = ref<FormItem[]>([]);
const formCreateRef = ref();
//
const getFormRules = () => {
if (props.selectField && props.selectField.length) {
@ -217,6 +223,10 @@
value: rule.value,
options: optionsItem,
required: item.required,
props: {
modelValue: rule.value,
options: optionsItem,
},
};
});
formRuleField.value.push(currentFormItem as FormItem[]);

View File

@ -62,7 +62,7 @@
isShowTip.value = !getIsVisited();
};
onBeforeMount(() => {
templateStore.setStatus();
templateStore.getStatus();
});
onMounted(() => {

View File

@ -59,6 +59,8 @@ export default {
'system.orgTemplate.dateFormat': 'Date format',
'system.orgTemplate.formatPlaceholder': 'Please select a format',
'system.orgTemplate.optionsPlaceholder': 'Please enter options',
'system.orgTemplate.optionsIdPlaceholder': 'Please enter options ID',
'system.orgTemplate.optionsIdTip': 'The option ID cannot be empty',
'system.orgTemplate.columnTemplateName': 'Template name',
'system.orgTemplate.copy': 'Copy',
'system.orgTemplate.defaultValue': 'Default value',
@ -92,4 +94,13 @@ export default {
'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',
'system.orgTemplate.enabledSuccessfully': 'Enabled successfully',
'system.orgTemplate.thirdPartyPlatforms': 'Used to map field values for third-party platforms such as JARA',
'system.orgTemplate.optionsIdNoRepeat': 'The option ID must be unique',
'system.orgTemplate.optionsContentNoRepeat': 'The options cannot be repeated',
'system.orgTemplate.thirdParty': 'Connect to third-party platforms',
'system.orgTemplate.deleteTemplateTitle': 'Are you sure to delete { name }?',
'system.orgTemplate.apiInputPlaceholder': 'Please enter a third-party API',
'system.orgTemplate.apiFieldId': 'API field',
'system.orgTemplate.copyTemplate': 'Copy Template',
};

View File

@ -58,6 +58,8 @@ export default {
'system.orgTemplate.dateFormat': '日期格式',
'system.orgTemplate.formatPlaceholder': '请选择格式',
'system.orgTemplate.optionsPlaceholder': '请输入选项',
'system.orgTemplate.optionsIdPlaceholder': '请输入选项ID',
'system.orgTemplate.optionsIdTip': '选项ID不能为空',
'system.orgTemplate.updateTip': '确认更新 `{name}` 吗?',
'system.orgTemplate.updateDescription': '更新后,所使用该字段模版将一起修改',
'system.orgTemplate.confirm': '确定',
@ -94,4 +96,13 @@ export default {
'system.orgTemplate.expectationTip': '请输入预期',
'system.orgTemplate.addAttachment': '添加附件',
'system.orgTemplate.addAttachmentTip': '支持任意类型文件,文件大小不超过 500MB',
'system.orgTemplate.enabledSuccessfully': '启用成功',
'system.orgTemplate.thirdPartyPlatforms': '用于映射 JARA 等第三方平台字段值',
'system.orgTemplate.optionsIdNoRepeat': '选项 ID 不可以重复',
'system.orgTemplate.optionsContentNoRepeat': '选项内容不可以重复',
'system.orgTemplate.thirdParty': '对接第三方平台',
'system.orgTemplate.deleteTemplateTitle': '确认删除 {name} 吗?',
'system.orgTemplate.apiInputPlaceholder': '请输入第三方 API',
'system.orgTemplate.apiFieldId': 'API 字段',
'system.orgTemplate.copyTemplate': '复制模板',
};