feat: 新增 ele infra 表单设计器模块
parent
c743932c5a
commit
ffc7e21d4a
|
@ -26,6 +26,8 @@
|
||||||
"#/*": "./src/*"
|
"#/*": "./src/*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@form-create/designer": "^3.2.6",
|
||||||
|
"@form-create/element-ui": "^3.2.11",
|
||||||
"@tinymce/tinymce-vue": "catalog:",
|
"@tinymce/tinymce-vue": "catalog:",
|
||||||
"@vben/access": "workspace:*",
|
"@vben/access": "workspace:*",
|
||||||
"@vben/common-ui": "workspace:*",
|
"@vben/common-ui": "workspace:*",
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { useTitle } from '@vueuse/core';
|
||||||
import { ElLoading } from 'element-plus';
|
import { ElLoading } from 'element-plus';
|
||||||
|
|
||||||
import { $t, setupI18n } from '#/locales';
|
import { $t, setupI18n } from '#/locales';
|
||||||
|
import { setupFormCreate } from '#/plugins/form-create';
|
||||||
|
|
||||||
import { initComponentAdapter } from './adapter/component';
|
import { initComponentAdapter } from './adapter/component';
|
||||||
import App from './app.vue';
|
import App from './app.vue';
|
||||||
|
@ -58,6 +59,9 @@ async function bootstrap(namespace: string) {
|
||||||
const { MotionPlugin } = await import('@vben/plugins/motion');
|
const { MotionPlugin } = await import('@vben/plugins/motion');
|
||||||
app.use(MotionPlugin);
|
app.use(MotionPlugin);
|
||||||
|
|
||||||
|
// formCreate
|
||||||
|
setupFormCreate(app);
|
||||||
|
|
||||||
// 动态更新标题
|
// 动态更新标题
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (preferences.app.dynamicTitle) {
|
if (preferences.app.dynamicTitle) {
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<!-- 数据字典 Select 选择器 -->
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { DictSelectProps } from '../typing';
|
||||||
|
|
||||||
|
import { computed, useAttrs } from 'vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ElCheckbox,
|
||||||
|
ElCheckboxGroup,
|
||||||
|
ElOption,
|
||||||
|
ElRadio,
|
||||||
|
ElRadioGroup,
|
||||||
|
ElSelect,
|
||||||
|
} from 'element-plus';
|
||||||
|
|
||||||
|
import { getDictObj, getIntDictOptions, getStrDictOptions } from '#/utils';
|
||||||
|
|
||||||
|
defineOptions({ name: 'DictSelect' });
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<DictSelectProps>(), {
|
||||||
|
valueType: 'str',
|
||||||
|
selectType: 'select',
|
||||||
|
});
|
||||||
|
|
||||||
|
const attrs = useAttrs();
|
||||||
|
|
||||||
|
// 获得字典配置
|
||||||
|
// TODO @dhb:可以使用 getDictOptions 替代么?
|
||||||
|
const getDictOptions = computed(() => {
|
||||||
|
switch (props.valueType) {
|
||||||
|
case 'bool': {
|
||||||
|
return getDictObj(props.dictType, 'bool');
|
||||||
|
}
|
||||||
|
case 'int': {
|
||||||
|
return getIntDictOptions(props.dictType);
|
||||||
|
}
|
||||||
|
case 'str': {
|
||||||
|
return getStrDictOptions(props.dictType);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ElSelect v-if="selectType === 'select'" class="w-1/1" v-bind="attrs">
|
||||||
|
<ElOption
|
||||||
|
v-for="(dict, index) in getDictOptions"
|
||||||
|
:key="index"
|
||||||
|
:value="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
<ElRadioGroup v-if="selectType === 'radio'" class="w-1/1" v-bind="attrs">
|
||||||
|
<ElRadio
|
||||||
|
v-for="(dict, index) in getDictOptions"
|
||||||
|
:key="index"
|
||||||
|
:label="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</ElRadio>
|
||||||
|
</ElRadioGroup>
|
||||||
|
<ElCheckboxGroup
|
||||||
|
v-if="selectType === 'checkbox'"
|
||||||
|
class="w-1/1"
|
||||||
|
v-bind="attrs"
|
||||||
|
>
|
||||||
|
<ElCheckbox
|
||||||
|
v-for="(dict, index) in getDictOptions"
|
||||||
|
:key="index"
|
||||||
|
:label="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</ElCheckbox>
|
||||||
|
</ElCheckboxGroup>
|
||||||
|
</template>
|
|
@ -0,0 +1,288 @@
|
||||||
|
import type { ApiSelectProps } from '#/components/form-create/typing';
|
||||||
|
|
||||||
|
import { defineComponent, onMounted, ref, useAttrs } from 'vue';
|
||||||
|
|
||||||
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ElCheckbox,
|
||||||
|
ElCheckboxGroup,
|
||||||
|
ElOption,
|
||||||
|
ElRadio,
|
||||||
|
ElRadioGroup,
|
||||||
|
ElSelect,
|
||||||
|
} from 'element-plus';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export const useApiSelect = (option: ApiSelectProps) => {
|
||||||
|
return defineComponent({
|
||||||
|
name: option.name,
|
||||||
|
props: {
|
||||||
|
// 选项标签
|
||||||
|
labelField: {
|
||||||
|
type: String,
|
||||||
|
default: () => option.labelField ?? 'label',
|
||||||
|
},
|
||||||
|
// 选项的值
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: () => option.valueField ?? 'value',
|
||||||
|
},
|
||||||
|
// api 接口
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: () => option.url ?? '',
|
||||||
|
},
|
||||||
|
// 请求类型
|
||||||
|
method: {
|
||||||
|
type: String,
|
||||||
|
default: 'GET',
|
||||||
|
},
|
||||||
|
// 选项解析函数
|
||||||
|
parseFunc: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
// 请求参数
|
||||||
|
data: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
// 选择器类型,下拉框 select、多选框 checkbox、单选框 radio
|
||||||
|
selectType: {
|
||||||
|
type: String,
|
||||||
|
default: 'select',
|
||||||
|
},
|
||||||
|
// 是否多选
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
// 是否远程搜索
|
||||||
|
remote: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
// 远程搜索时携带的参数
|
||||||
|
remoteField: {
|
||||||
|
type: String,
|
||||||
|
default: 'label',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const attrs = useAttrs();
|
||||||
|
const options = ref<any[]>([]); // 下拉数据
|
||||||
|
const loading = ref(false); // 是否正在从远程获取数据
|
||||||
|
const queryParam = ref<any>(); // 当前输入的值
|
||||||
|
const getOptions = async () => {
|
||||||
|
options.value = [];
|
||||||
|
// 接口选择器
|
||||||
|
if (isEmpty(props.url)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (props.method) {
|
||||||
|
case 'GET': {
|
||||||
|
let url: string = props.url;
|
||||||
|
if (props.remote && queryParam.value !== undefined) {
|
||||||
|
url = url.includes('?')
|
||||||
|
? `${url}&${props.remoteField}=${queryParam.value}`
|
||||||
|
: `${url}?${props.remoteField}=${queryParam.value}`;
|
||||||
|
}
|
||||||
|
parseOptions(await requestClient.get(url));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'POST': {
|
||||||
|
const data: any = JSON.parse(props.data);
|
||||||
|
if (props.remote) {
|
||||||
|
data[props.remoteField] = queryParam.value;
|
||||||
|
}
|
||||||
|
parseOptions(await requestClient.post(props.url, data));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseOptions(data: any) {
|
||||||
|
// 情况一:如果有自定义解析函数优先使用自定义解析
|
||||||
|
if (!isEmpty(props.parseFunc)) {
|
||||||
|
options.value = parseFunc()?.(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 情况二:返回的直接是一个列表
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
parseOptions0(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 情况二:返回的是分页数据,尝试读取 list
|
||||||
|
data = data.list;
|
||||||
|
if (!!data && Array.isArray(data)) {
|
||||||
|
parseOptions0(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 情况三:不是 yudao-vue-pro 标准返回
|
||||||
|
console.warn(
|
||||||
|
`接口[${props.url}] 返回结果不是 yudao-vue-pro 标准返回建议采用自定义解析函数处理`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseOptions0(data: any[]) {
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
options.value = data.map((item: any) => ({
|
||||||
|
label: parseExpression(item, props.labelField),
|
||||||
|
value: parseExpression(item, props.valueField),
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.warn(`接口[${props.url}] 返回结果不是一个数组`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFunc() {
|
||||||
|
let parse: any = null;
|
||||||
|
if (props.parseFunc) {
|
||||||
|
// 解析字符串函数
|
||||||
|
// eslint-disable-next-line no-new-func
|
||||||
|
parse = new Function(`return ${props.parseFunc}`)();
|
||||||
|
}
|
||||||
|
return parse;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseExpression(data: any, template: string) {
|
||||||
|
// 检测是否使用了表达式
|
||||||
|
if (!template.includes('${')) {
|
||||||
|
return data[template];
|
||||||
|
}
|
||||||
|
// 正则表达式匹配模板字符串中的 ${...}
|
||||||
|
const pattern = /\$\{([^}]*)\}/g;
|
||||||
|
// 使用replace函数配合正则表达式和回调函数来进行替换
|
||||||
|
return template.replaceAll(pattern, (_, expr) => {
|
||||||
|
// expr 是匹配到的 ${} 内的表达式(这里是属性名),从 data 中获取对应的值
|
||||||
|
const result = data[expr.trim()]; // 去除前后空白,以防用户输入带空格的属性名
|
||||||
|
if (!result) {
|
||||||
|
console.warn(
|
||||||
|
`接口选择器选项模版[${template}][${expr.trim()}] 解析值失败结果为[${result}], 请检查属性名称是否存在于接口返回值中,存在则忽略此条!!!`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteMethod = async (query: any) => {
|
||||||
|
if (!query) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
queryParam.value = query;
|
||||||
|
await getOptions();
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getOptions();
|
||||||
|
});
|
||||||
|
|
||||||
|
const buildSelect = () => {
|
||||||
|
if (props.multiple) {
|
||||||
|
// fix:多写此步是为了解决 multiple 属性问题
|
||||||
|
return (
|
||||||
|
<ElSelect
|
||||||
|
class="w-1/1"
|
||||||
|
loading={loading.value}
|
||||||
|
multiple
|
||||||
|
{...attrs}
|
||||||
|
// 远程搜索
|
||||||
|
filterable={props.remote}
|
||||||
|
remote={props.remote}
|
||||||
|
{...(props.remote && { remoteMethod })}
|
||||||
|
>
|
||||||
|
{options.value.map(
|
||||||
|
(item: { label: any; value: any }, index: any) => (
|
||||||
|
<ElOption key={index} label={item.label} value={item.value} />
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</ElSelect>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ElSelect
|
||||||
|
class="w-1/1"
|
||||||
|
loading={loading.value}
|
||||||
|
{...attrs}
|
||||||
|
// 远程搜索
|
||||||
|
filterable={props.remote}
|
||||||
|
remote={props.remote}
|
||||||
|
{...(props.remote && { remoteMethod })}
|
||||||
|
>
|
||||||
|
{options.value.map(
|
||||||
|
(item: { label: any; value: any }, index: any) => (
|
||||||
|
<ElOption key={index} label={item.label} value={item.value} />
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</ElSelect>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const buildCheckbox = () => {
|
||||||
|
if (isEmpty(options.value)) {
|
||||||
|
options.value = [
|
||||||
|
{ label: '选项1', value: '选项1' },
|
||||||
|
{ label: '选项2', value: '选项2' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ElCheckboxGroup class="w-1/1" {...attrs}>
|
||||||
|
{options.value.map(
|
||||||
|
(item: { label: any; value: any }, index: any) => (
|
||||||
|
<ElCheckbox key={index} label={item.value}>
|
||||||
|
{item.label}
|
||||||
|
</ElCheckbox>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</ElCheckboxGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const buildRadio = () => {
|
||||||
|
if (isEmpty(options.value)) {
|
||||||
|
options.value = [
|
||||||
|
{ label: '选项1', value: '选项1' },
|
||||||
|
{ label: '选项2', value: '选项2' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ElRadioGroup class="w-1/1" {...attrs}>
|
||||||
|
{options.value.map(
|
||||||
|
(item: { label: any; value: any }, index: any) => (
|
||||||
|
<ElRadio key={index} label={item.value}>
|
||||||
|
{item.label}
|
||||||
|
</ElRadio>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</ElRadioGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return () => (
|
||||||
|
<>
|
||||||
|
{(() => {
|
||||||
|
switch (props.selectType) {
|
||||||
|
case 'checkbox': {
|
||||||
|
return buildCheckbox();
|
||||||
|
}
|
||||||
|
case 'radio': {
|
||||||
|
return buildRadio();
|
||||||
|
}
|
||||||
|
case 'select': {
|
||||||
|
return buildSelect();
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return buildSelect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
import ImageUpload from '#/components/upload/image-upload.vue';
|
||||||
|
|
||||||
|
export const useImagesUpload = () => {
|
||||||
|
return defineComponent({
|
||||||
|
name: 'ImagesUpload',
|
||||||
|
props: {
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
maxNumber: {
|
||||||
|
type: Number,
|
||||||
|
default: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
// TODO: @dhb52 其实还是靠 props 默认参数起作用,没能从 formCreate 传递
|
||||||
|
return (props: { maxNumber?: number; multiple?: boolean }) => (
|
||||||
|
<ImageUpload maxNumber={props.maxNumber} multiple={props.multiple} />
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,182 @@
|
||||||
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
|
import type { Menu } from '#/components/form-create/typing';
|
||||||
|
|
||||||
|
import { nextTick, onMounted } from 'vue';
|
||||||
|
|
||||||
|
import { apiSelectRule } from '#/components/form-create/rules/data';
|
||||||
|
|
||||||
|
import {
|
||||||
|
useDictSelectRule,
|
||||||
|
useEditorRule,
|
||||||
|
useSelectRule,
|
||||||
|
useUploadFileRule,
|
||||||
|
useUploadImageRule,
|
||||||
|
useUploadImagesRule,
|
||||||
|
} from './rules';
|
||||||
|
|
||||||
|
export function makeRequiredRule() {
|
||||||
|
return {
|
||||||
|
type: 'Required',
|
||||||
|
field: 'formCreate$required',
|
||||||
|
title: '是否必填',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const localeProps = (
|
||||||
|
t: (msg: string) => any,
|
||||||
|
prefix: string,
|
||||||
|
rules: any[],
|
||||||
|
) => {
|
||||||
|
return rules.map((rule: { field: string; title: any }) => {
|
||||||
|
if (rule.field === 'formCreate$required') {
|
||||||
|
rule.title = t('props.required') || rule.title;
|
||||||
|
} else if (rule.field && rule.field !== '_optionType') {
|
||||||
|
rule.title = t(`components.${prefix}.${rule.field}`) || rule.title;
|
||||||
|
}
|
||||||
|
return rule;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析表单组件的 field, title 等字段(递归,如果组件包含子组件)
|
||||||
|
*
|
||||||
|
* @param rule 组件的生成规则 https://www.form-create.com/v3/guide/rule
|
||||||
|
* @param fields 解析后表单组件字段
|
||||||
|
* @param parentTitle 如果是子表单,子表单的标题,默认为空
|
||||||
|
*/
|
||||||
|
export const parseFormFields = (
|
||||||
|
rule: Record<string, any>,
|
||||||
|
fields: Array<Record<string, any>> = [],
|
||||||
|
parentTitle: string = '',
|
||||||
|
) => {
|
||||||
|
const { type, field, $required, title: tempTitle, children } = rule;
|
||||||
|
if (field && tempTitle) {
|
||||||
|
let title = tempTitle;
|
||||||
|
if (parentTitle) {
|
||||||
|
title = `${parentTitle}.${tempTitle}`;
|
||||||
|
}
|
||||||
|
let required = false;
|
||||||
|
if ($required) {
|
||||||
|
required = true;
|
||||||
|
}
|
||||||
|
fields.push({
|
||||||
|
field,
|
||||||
|
title,
|
||||||
|
type,
|
||||||
|
required,
|
||||||
|
});
|
||||||
|
// TODO 子表单 需要处理子表单字段
|
||||||
|
// if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
|
||||||
|
// // 解析子表单的字段
|
||||||
|
// rule.props.rule.forEach((item) => {
|
||||||
|
// parseFields(item, fieldsPermission, title)
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
if (children && Array.isArray(children)) {
|
||||||
|
children.forEach((rule) => {
|
||||||
|
parseFormFields(rule, fields);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单设计器增强 hook
|
||||||
|
* 新增
|
||||||
|
* - 文件上传
|
||||||
|
* - 单图上传
|
||||||
|
* - 多图上传
|
||||||
|
* - 字典选择器
|
||||||
|
* - 用户选择器
|
||||||
|
* - 部门选择器
|
||||||
|
* - 富文本
|
||||||
|
*/
|
||||||
|
export const useFormCreateDesigner = async (designer: Ref) => {
|
||||||
|
const editorRule = useEditorRule();
|
||||||
|
const uploadFileRule = useUploadFileRule();
|
||||||
|
const uploadImageRule = useUploadImageRule();
|
||||||
|
const uploadImagesRule = useUploadImagesRule();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建表单组件
|
||||||
|
*/
|
||||||
|
const buildFormComponents = () => {
|
||||||
|
// 移除自带的上传组件规则,使用 uploadFileRule、uploadImgRule、uploadImgsRule 替代
|
||||||
|
designer.value?.removeMenuItem('upload');
|
||||||
|
// 移除自带的富文本组件规则,使用 editorRule 替代
|
||||||
|
designer.value?.removeMenuItem('fc-editor');
|
||||||
|
const components = [
|
||||||
|
editorRule,
|
||||||
|
uploadFileRule,
|
||||||
|
uploadImageRule,
|
||||||
|
uploadImagesRule,
|
||||||
|
];
|
||||||
|
components.forEach((component) => {
|
||||||
|
// 插入组件规则
|
||||||
|
designer.value?.addComponent(component);
|
||||||
|
// 插入拖拽按钮到 `main` 分类下
|
||||||
|
designer.value?.appendMenuItem('main', {
|
||||||
|
icon: component.icon,
|
||||||
|
name: component.name,
|
||||||
|
label: component.label,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const userSelectRule = useSelectRule({
|
||||||
|
name: 'UserSelect',
|
||||||
|
label: '用户选择器',
|
||||||
|
icon: 'icon-eye',
|
||||||
|
});
|
||||||
|
const deptSelectRule = useSelectRule({
|
||||||
|
name: 'DeptSelect',
|
||||||
|
label: '部门选择器',
|
||||||
|
icon: 'icon-tree',
|
||||||
|
});
|
||||||
|
const dictSelectRule = useDictSelectRule();
|
||||||
|
const apiSelectRule0 = useSelectRule({
|
||||||
|
name: 'ApiSelect',
|
||||||
|
label: '接口选择器',
|
||||||
|
icon: 'icon-json',
|
||||||
|
props: [...apiSelectRule],
|
||||||
|
event: ['click', 'change', 'visibleChange', 'clear', 'blur', 'focus'],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建系统字段菜单
|
||||||
|
*/
|
||||||
|
const buildSystemMenu = () => {
|
||||||
|
// 移除自带的下拉选择器组件,使用 currencySelectRule 替代
|
||||||
|
// designer.value?.removeMenuItem('select')
|
||||||
|
// designer.value?.removeMenuItem('radio')
|
||||||
|
// designer.value?.removeMenuItem('checkbox')
|
||||||
|
const components = [
|
||||||
|
userSelectRule,
|
||||||
|
deptSelectRule,
|
||||||
|
dictSelectRule,
|
||||||
|
apiSelectRule0,
|
||||||
|
];
|
||||||
|
const menu: Menu = {
|
||||||
|
name: 'system',
|
||||||
|
title: '系统字段',
|
||||||
|
list: components.map((component) => {
|
||||||
|
// 插入组件规则
|
||||||
|
designer.value?.addComponent(component);
|
||||||
|
// 插入拖拽按钮到 `system` 分类下
|
||||||
|
return {
|
||||||
|
icon: component.icon,
|
||||||
|
name: component.name,
|
||||||
|
label: component.label,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
designer.value?.addMenu(menu);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await nextTick();
|
||||||
|
buildFormComponents();
|
||||||
|
buildSystemMenu();
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { useApiSelect } from './components/use-api-select';
|
||||||
|
|
||||||
|
export { useFormCreateDesigner } from './helpers';
|
|
@ -0,0 +1,182 @@
|
||||||
|
/* eslint-disable no-template-curly-in-string */
|
||||||
|
const selectRule = [
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
field: 'selectType',
|
||||||
|
title: '选择器类型',
|
||||||
|
value: 'select',
|
||||||
|
options: [
|
||||||
|
{ label: '下拉框', value: 'select' },
|
||||||
|
{ label: '单选框', value: 'radio' },
|
||||||
|
{ label: '多选框', value: 'checkbox' },
|
||||||
|
],
|
||||||
|
// 参考 https://www.form-create.com/v3/guide/control 组件联动,单选框和多选框不需要多选属性
|
||||||
|
control: [
|
||||||
|
{
|
||||||
|
value: 'select',
|
||||||
|
condition: '==',
|
||||||
|
method: 'hidden',
|
||||||
|
rule: [
|
||||||
|
'multiple',
|
||||||
|
'clearable',
|
||||||
|
'collapseTags',
|
||||||
|
'multipleLimit',
|
||||||
|
'allowCreate',
|
||||||
|
'filterable',
|
||||||
|
'noMatchText',
|
||||||
|
'remote',
|
||||||
|
'remoteMethod',
|
||||||
|
'reserveKeyword',
|
||||||
|
'defaultFirstOption',
|
||||||
|
'automaticDropdown',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'filterable',
|
||||||
|
title: '是否可搜索',
|
||||||
|
},
|
||||||
|
{ type: 'switch', field: 'multiple', title: '是否多选' },
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'disabled',
|
||||||
|
title: '是否禁用',
|
||||||
|
},
|
||||||
|
{ type: 'switch', field: 'clearable', title: '是否可以清空选项' },
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'collapseTags',
|
||||||
|
title: '多选时是否将选中值按文字的形式展示',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'inputNumber',
|
||||||
|
field: 'multipleLimit',
|
||||||
|
title: '多选时用户最多可以选择的项目数,为 0 则不限制',
|
||||||
|
props: { min: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'autocomplete',
|
||||||
|
title: 'autocomplete 属性',
|
||||||
|
},
|
||||||
|
{ type: 'input', field: 'placeholder', title: '占位符' },
|
||||||
|
{ type: 'switch', field: 'allowCreate', title: '是否允许用户创建新条目' },
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'noMatchText',
|
||||||
|
title: '搜索条件无匹配时显示的文字',
|
||||||
|
},
|
||||||
|
{ type: 'input', field: 'noDataText', title: '选项为空时显示的文字' },
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'reserveKeyword',
|
||||||
|
title: '多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'defaultFirstOption',
|
||||||
|
title: '在输入框按下回车,选择第一个匹配项',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'popperAppendToBody',
|
||||||
|
title: '是否将弹出框插入至 body 元素',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'automaticDropdown',
|
||||||
|
title: '对于不可搜索的 Select,是否在输入框获得焦点后自动弹出选项菜单',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const apiSelectRule = [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'url',
|
||||||
|
title: 'url 地址',
|
||||||
|
props: {
|
||||||
|
placeholder: '/system/user/simple-list',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
field: 'method',
|
||||||
|
title: '请求类型',
|
||||||
|
value: 'GET',
|
||||||
|
options: [
|
||||||
|
{ label: 'GET', value: 'GET' },
|
||||||
|
{ label: 'POST', value: 'POST' },
|
||||||
|
],
|
||||||
|
control: [
|
||||||
|
{
|
||||||
|
value: 'GET',
|
||||||
|
condition: '!=',
|
||||||
|
method: 'hidden',
|
||||||
|
rule: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'data',
|
||||||
|
title: '请求参数 JSON 格式',
|
||||||
|
props: {
|
||||||
|
autosize: true,
|
||||||
|
type: 'textarea',
|
||||||
|
placeholder: '{"type": 1}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'labelField',
|
||||||
|
title: 'label 属性',
|
||||||
|
info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}',
|
||||||
|
props: {
|
||||||
|
placeholder: 'nickname',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'valueField',
|
||||||
|
title: 'value 属性',
|
||||||
|
info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}',
|
||||||
|
props: {
|
||||||
|
placeholder: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'parseFunc',
|
||||||
|
title: '选项解析函数',
|
||||||
|
info: `data 为接口返回值,需要写一个匿名函数解析返回值为选择器 options 列表
|
||||||
|
(data: any)=>{ label: string; value: any }[]`,
|
||||||
|
props: {
|
||||||
|
autosize: true,
|
||||||
|
rows: { minRows: 2, maxRows: 6 },
|
||||||
|
type: 'textarea',
|
||||||
|
placeholder: `
|
||||||
|
function (data) {
|
||||||
|
console.log(data)
|
||||||
|
return data.list.map(item=> ({label: item.nickname,value: item.id}))
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'remote',
|
||||||
|
info: '是否可搜索',
|
||||||
|
title: '其中的选项是否从服务器远程加载',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'remoteField',
|
||||||
|
title: '请求参数',
|
||||||
|
info: '远程请求时请求携带的参数名称,如:name',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export { apiSelectRule, selectRule };
|
|
@ -0,0 +1,6 @@
|
||||||
|
export { useDictSelectRule } from './use-dict-select';
|
||||||
|
export { useEditorRule } from './use-editor-rule';
|
||||||
|
export { useSelectRule } from './use-select-rule';
|
||||||
|
export { useUploadFileRule } from './use-upload-file-rule';
|
||||||
|
export { useUploadImageRule } from './use-upload-image-rule';
|
||||||
|
export { useUploadImagesRule } from './use-upload-images-rule';
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { buildUUID, cloneDeep } from '@vben/utils';
|
||||||
|
|
||||||
|
import * as DictDataApi from '#/api/system/dict/type';
|
||||||
|
import {
|
||||||
|
localeProps,
|
||||||
|
makeRequiredRule,
|
||||||
|
} from '#/components/form-create/helpers';
|
||||||
|
import { selectRule } from '#/components/form-create/rules/data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典选择器规则,如果规则使用到动态数据则需要单独配置不能使用 useSelectRule
|
||||||
|
*/
|
||||||
|
export const useDictSelectRule = () => {
|
||||||
|
const label = '字典选择器';
|
||||||
|
const name = 'DictSelect';
|
||||||
|
const rules = cloneDeep(selectRule);
|
||||||
|
const dictOptions = ref<{ label: string; value: string }[]>([]); // 字典类型下拉数据
|
||||||
|
onMounted(async () => {
|
||||||
|
const data = await DictDataApi.getSimpleDictTypeList();
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dictOptions.value =
|
||||||
|
data?.map((item: DictDataApi.SystemDictTypeApi.DictType) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.type,
|
||||||
|
})) ?? [];
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
icon: 'icon-descriptions',
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
rule() {
|
||||||
|
return {
|
||||||
|
type: name,
|
||||||
|
field: buildUUID(),
|
||||||
|
title: label,
|
||||||
|
info: '',
|
||||||
|
$required: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props(_: any, { t }: any) {
|
||||||
|
return localeProps(t, `${name}.props`, [
|
||||||
|
makeRequiredRule(),
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
field: 'dictType',
|
||||||
|
title: '字典类型',
|
||||||
|
value: '',
|
||||||
|
options: dictOptions.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
field: 'valueType',
|
||||||
|
title: '字典值类型',
|
||||||
|
value: 'str',
|
||||||
|
options: [
|
||||||
|
{ label: '数字', value: 'int' },
|
||||||
|
{ label: '字符串', value: 'str' },
|
||||||
|
{ label: '布尔值', value: 'bool' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...rules,
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { buildUUID } from '@vben/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
localeProps,
|
||||||
|
makeRequiredRule,
|
||||||
|
} from '#/components/form-create/helpers';
|
||||||
|
|
||||||
|
export const useEditorRule = () => {
|
||||||
|
const label = '富文本';
|
||||||
|
const name = 'Tinymce';
|
||||||
|
return {
|
||||||
|
icon: 'icon-editor',
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
rule() {
|
||||||
|
return {
|
||||||
|
type: name,
|
||||||
|
field: buildUUID(),
|
||||||
|
title: label,
|
||||||
|
info: '',
|
||||||
|
$required: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props(_: any, { t }: any) {
|
||||||
|
return localeProps(t, `${name}.props`, [
|
||||||
|
makeRequiredRule(),
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'height',
|
||||||
|
title: '高度',
|
||||||
|
},
|
||||||
|
{ type: 'switch', field: 'readonly', title: '是否只读' },
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,45 @@
|
||||||
|
import type { SelectRuleOption } from '#/components/form-create/typing';
|
||||||
|
|
||||||
|
import { buildUUID, cloneDeep } from '@vben/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
localeProps,
|
||||||
|
makeRequiredRule,
|
||||||
|
} from '#/components/form-create/helpers';
|
||||||
|
import { selectRule } from '#/components/form-create/rules/data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用选择器规则 hook
|
||||||
|
*
|
||||||
|
* @param option 规则配置
|
||||||
|
*/
|
||||||
|
export const useSelectRule = (option: SelectRuleOption) => {
|
||||||
|
const label = option.label;
|
||||||
|
const name = option.name;
|
||||||
|
const rules = cloneDeep(selectRule);
|
||||||
|
return {
|
||||||
|
icon: option.icon,
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
event: option.event,
|
||||||
|
rule() {
|
||||||
|
return {
|
||||||
|
type: name,
|
||||||
|
field: buildUUID(),
|
||||||
|
title: label,
|
||||||
|
info: '',
|
||||||
|
$required: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props(_: any, { t }: any) {
|
||||||
|
if (!option.props) {
|
||||||
|
option.props = [];
|
||||||
|
}
|
||||||
|
return localeProps(t, `${name}.props`, [
|
||||||
|
makeRequiredRule(),
|
||||||
|
...option.props,
|
||||||
|
...rules,
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { buildUUID } from '@vben/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
localeProps,
|
||||||
|
makeRequiredRule,
|
||||||
|
} from '#/components/form-create/helpers';
|
||||||
|
|
||||||
|
export const useUploadFileRule = () => {
|
||||||
|
const label = '文件上传';
|
||||||
|
const name = 'FileUpload';
|
||||||
|
return {
|
||||||
|
icon: 'icon-upload',
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
rule() {
|
||||||
|
return {
|
||||||
|
type: name,
|
||||||
|
field: buildUUID(),
|
||||||
|
title: label,
|
||||||
|
info: '',
|
||||||
|
$required: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props(_: any, { t }: any) {
|
||||||
|
return localeProps(t, `${name}.props`, [
|
||||||
|
makeRequiredRule(),
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
field: 'fileType',
|
||||||
|
title: '文件类型',
|
||||||
|
value: ['doc', 'xls', 'ppt', 'txt', 'pdf'],
|
||||||
|
options: [
|
||||||
|
{ label: 'doc', value: 'doc' },
|
||||||
|
{ label: 'xls', value: 'xls' },
|
||||||
|
{ label: 'ppt', value: 'ppt' },
|
||||||
|
{ label: 'txt', value: 'txt' },
|
||||||
|
{ label: 'pdf', value: 'pdf' },
|
||||||
|
],
|
||||||
|
props: {
|
||||||
|
multiple: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'autoUpload',
|
||||||
|
title: '是否在选取文件后立即进行上传',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'drag',
|
||||||
|
title: '拖拽上传',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'isShowTip',
|
||||||
|
title: '是否显示提示',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'inputNumber',
|
||||||
|
field: 'fileSize',
|
||||||
|
title: '大小限制(MB)',
|
||||||
|
value: 5,
|
||||||
|
props: { min: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'inputNumber',
|
||||||
|
field: 'limit',
|
||||||
|
title: '数量限制',
|
||||||
|
value: 5,
|
||||||
|
props: { min: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'disabled',
|
||||||
|
title: '是否禁用',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { buildUUID } from '@vben/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
localeProps,
|
||||||
|
makeRequiredRule,
|
||||||
|
} from '#/components/form-create/helpers';
|
||||||
|
|
||||||
|
export const useUploadImageRule = () => {
|
||||||
|
const label = '单图上传';
|
||||||
|
const name = 'ImageUpload';
|
||||||
|
return {
|
||||||
|
icon: 'icon-image',
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
rule() {
|
||||||
|
return {
|
||||||
|
type: name,
|
||||||
|
field: buildUUID(),
|
||||||
|
title: label,
|
||||||
|
info: '',
|
||||||
|
$required: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props(_: any, { t }: any) {
|
||||||
|
return localeProps(t, `${name}.props`, [
|
||||||
|
makeRequiredRule(),
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'drag',
|
||||||
|
title: '拖拽上传',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
field: 'fileType',
|
||||||
|
title: '图片类型限制',
|
||||||
|
value: ['image/jpeg', 'image/png', 'image/gif'],
|
||||||
|
options: [
|
||||||
|
{ label: 'image/apng', value: 'image/apng' },
|
||||||
|
{ label: 'image/bmp', value: 'image/bmp' },
|
||||||
|
{ label: 'image/gif', value: 'image/gif' },
|
||||||
|
{ label: 'image/jpeg', value: 'image/jpeg' },
|
||||||
|
{ label: 'image/pjpeg', value: 'image/pjpeg' },
|
||||||
|
{ label: 'image/svg+xml', value: 'image/svg+xml' },
|
||||||
|
{ label: 'image/tiff', value: 'image/tiff' },
|
||||||
|
{ label: 'image/webp', value: 'image/webp' },
|
||||||
|
{ label: 'image/x-icon', value: 'image/x-icon' },
|
||||||
|
],
|
||||||
|
props: {
|
||||||
|
multiple: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'inputNumber',
|
||||||
|
field: 'fileSize',
|
||||||
|
title: '大小限制(MB)',
|
||||||
|
value: 5,
|
||||||
|
props: { min: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'height',
|
||||||
|
title: '组件高度',
|
||||||
|
value: '150px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'width',
|
||||||
|
title: '组件宽度',
|
||||||
|
value: '150px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'borderradius',
|
||||||
|
title: '组件边框圆角',
|
||||||
|
value: '8px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'disabled',
|
||||||
|
title: '是否显示删除按钮',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'showBtnText',
|
||||||
|
title: '是否显示按钮文字',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,89 @@
|
||||||
|
import { buildUUID } from '@vben/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
localeProps,
|
||||||
|
makeRequiredRule,
|
||||||
|
} from '#/components/form-create/helpers';
|
||||||
|
|
||||||
|
export const useUploadImagesRule = () => {
|
||||||
|
const label = '多图上传';
|
||||||
|
const name = 'ImagesUpload';
|
||||||
|
return {
|
||||||
|
icon: 'icon-image',
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
rule() {
|
||||||
|
return {
|
||||||
|
type: name,
|
||||||
|
field: buildUUID(),
|
||||||
|
title: label,
|
||||||
|
info: '',
|
||||||
|
$required: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props(_: any, { t }: any) {
|
||||||
|
return localeProps(t, `${name}.props`, [
|
||||||
|
makeRequiredRule(),
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'drag',
|
||||||
|
title: '拖拽上传',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
field: 'fileType',
|
||||||
|
title: '图片类型限制',
|
||||||
|
value: ['image/jpeg', 'image/png', 'image/gif'],
|
||||||
|
options: [
|
||||||
|
{ label: 'image/apng', value: 'image/apng' },
|
||||||
|
{ label: 'image/bmp', value: 'image/bmp' },
|
||||||
|
{ label: 'image/gif', value: 'image/gif' },
|
||||||
|
{ label: 'image/jpeg', value: 'image/jpeg' },
|
||||||
|
{ label: 'image/pjpeg', value: 'image/pjpeg' },
|
||||||
|
{ label: 'image/svg+xml', value: 'image/svg+xml' },
|
||||||
|
{ label: 'image/tiff', value: 'image/tiff' },
|
||||||
|
{ label: 'image/webp', value: 'image/webp' },
|
||||||
|
{ label: 'image/x-icon', value: 'image/x-icon' },
|
||||||
|
],
|
||||||
|
props: {
|
||||||
|
multiple: true,
|
||||||
|
maxNumber: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'inputNumber',
|
||||||
|
field: 'fileSize',
|
||||||
|
title: '大小限制(MB)',
|
||||||
|
value: 5,
|
||||||
|
props: { min: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'inputNumber',
|
||||||
|
field: 'limit',
|
||||||
|
title: '数量限制',
|
||||||
|
value: 5,
|
||||||
|
props: { min: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'height',
|
||||||
|
title: '组件高度',
|
||||||
|
value: '150px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'width',
|
||||||
|
title: '组件宽度',
|
||||||
|
value: '150px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'borderradius',
|
||||||
|
title: '组件边框圆角',
|
||||||
|
value: '8px',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,60 @@
|
||||||
|
import type { Rule } from '@form-create/element-ui'; // 左侧拖拽按钮
|
||||||
|
|
||||||
|
/** 数据字典 Select 选择器组件 Props 类型 */
|
||||||
|
export interface DictSelectProps {
|
||||||
|
dictType: string; // 字典类型
|
||||||
|
valueType?: 'bool' | 'int' | 'str'; // 字典值类型 TODO @芋艿:'boolean' | 'number' | 'string';需要和 vue3 一起统一!
|
||||||
|
selectType?: 'checkbox' | 'radio' | 'select'; // 选择器类型,下拉框 select、多选框 checkbox、单选框 radio
|
||||||
|
formCreateInject?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 左侧拖拽按钮 */
|
||||||
|
export interface MenuItem {
|
||||||
|
label: string;
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 左侧拖拽按钮分类 */
|
||||||
|
export interface Menu {
|
||||||
|
title: string;
|
||||||
|
name: string;
|
||||||
|
list: MenuItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MenuList = Array<Menu>;
|
||||||
|
|
||||||
|
// TODO @dhb52:MenuList、Menu、MenuItem、DragRule 这几个,是不是没用到呀?
|
||||||
|
// 拖拽组件的规则
|
||||||
|
export interface DragRule {
|
||||||
|
icon: string;
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
children?: string;
|
||||||
|
inside?: true;
|
||||||
|
drag?: string | true;
|
||||||
|
dragBtn?: false;
|
||||||
|
mask?: false;
|
||||||
|
|
||||||
|
rule(): Rule;
|
||||||
|
|
||||||
|
props(v: any, v1: any): Rule[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 通用 API 下拉组件 Props 类型 */
|
||||||
|
export interface ApiSelectProps {
|
||||||
|
name: string; // 组件名称
|
||||||
|
labelField?: string; // 选项标签
|
||||||
|
valueField?: string; // 选项的值
|
||||||
|
url?: string; // url 接口
|
||||||
|
isDict?: boolean; // 是否字典选择器
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 选择组件规则配置类型 */
|
||||||
|
export interface SelectRuleOption {
|
||||||
|
label: string; // label 名称
|
||||||
|
name: string; // 组件名称
|
||||||
|
icon: string; // 组件图标
|
||||||
|
props?: any[]; // 组件规则
|
||||||
|
event?: any[]; // 事件配置
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
import type { App, Component } from 'vue';
|
||||||
|
|
||||||
|
import FcDesigner from '@form-create/designer';
|
||||||
|
import formCreate from '@form-create/element-ui';
|
||||||
|
import install from '@form-create/element-ui/auto-import';
|
||||||
|
// 👇使用 form-create 需额外全局引入 element plus 组件
|
||||||
|
import {
|
||||||
|
ElAlert,
|
||||||
|
ElAside,
|
||||||
|
ElBadge,
|
||||||
|
ElCard,
|
||||||
|
ElCollapse,
|
||||||
|
ElCollapseItem,
|
||||||
|
ElContainer,
|
||||||
|
ElDivider,
|
||||||
|
ElDropdown,
|
||||||
|
ElDropdownItem,
|
||||||
|
ElDropdownMenu,
|
||||||
|
ElFooter,
|
||||||
|
ElHeader,
|
||||||
|
ElMain,
|
||||||
|
ElMenu,
|
||||||
|
ElMenuItem,
|
||||||
|
ElMessage,
|
||||||
|
ElPopconfirm,
|
||||||
|
ElTable,
|
||||||
|
ElTableColumn,
|
||||||
|
ElTabPane,
|
||||||
|
ElTabs,
|
||||||
|
ElTag,
|
||||||
|
ElText,
|
||||||
|
ElTransfer,
|
||||||
|
} from 'element-plus';
|
||||||
|
|
||||||
|
// ======================= 自定义组件 =======================
|
||||||
|
import { useApiSelect } from '#/components/form-create';
|
||||||
|
import DictSelect from '#/components/form-create/components/dict-select.vue';
|
||||||
|
import { useImagesUpload } from '#/components/form-create/components/use-images-upload';
|
||||||
|
import { Tinymce } from '#/components/tinymce';
|
||||||
|
import { FileUpload, ImageUpload } from '#/components/upload';
|
||||||
|
|
||||||
|
const UserSelect = useApiSelect({
|
||||||
|
name: 'UserSelect',
|
||||||
|
labelField: 'nickname',
|
||||||
|
valueField: 'id',
|
||||||
|
url: '/system/user/simple-list',
|
||||||
|
});
|
||||||
|
const DeptSelect = useApiSelect({
|
||||||
|
name: 'DeptSelect',
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'id',
|
||||||
|
url: '/system/dept/simple-list',
|
||||||
|
});
|
||||||
|
const ApiSelect = useApiSelect({
|
||||||
|
name: 'ApiSelect',
|
||||||
|
});
|
||||||
|
const ImagesUpload = useImagesUpload();
|
||||||
|
|
||||||
|
const components = [
|
||||||
|
ImageUpload,
|
||||||
|
ImagesUpload,
|
||||||
|
FileUpload,
|
||||||
|
Tinymce,
|
||||||
|
DictSelect,
|
||||||
|
UserSelect,
|
||||||
|
DeptSelect,
|
||||||
|
ApiSelect,
|
||||||
|
ElAlert,
|
||||||
|
ElTransfer,
|
||||||
|
ElAside,
|
||||||
|
ElContainer,
|
||||||
|
ElDivider,
|
||||||
|
ElHeader,
|
||||||
|
ElMain,
|
||||||
|
ElPopconfirm,
|
||||||
|
ElTable,
|
||||||
|
ElTableColumn,
|
||||||
|
ElTabPane,
|
||||||
|
ElTabs,
|
||||||
|
ElDropdown,
|
||||||
|
ElDropdownMenu,
|
||||||
|
ElDropdownItem,
|
||||||
|
ElBadge,
|
||||||
|
ElTag,
|
||||||
|
ElText,
|
||||||
|
ElMenu,
|
||||||
|
ElMenuItem,
|
||||||
|
ElFooter,
|
||||||
|
ElMessage,
|
||||||
|
ElCollapse,
|
||||||
|
ElCollapseItem,
|
||||||
|
ElCard,
|
||||||
|
];
|
||||||
|
|
||||||
|
// 参考 http://www.form-create.com/v3/element-ui/auto-import.html 文档
|
||||||
|
export const setupFormCreate = (app: App<Element>) => {
|
||||||
|
components.forEach((component) => {
|
||||||
|
app.component(component.name as string, component as Component);
|
||||||
|
});
|
||||||
|
formCreate.use(install);
|
||||||
|
app.use(formCreate);
|
||||||
|
app.use(FcDesigner);
|
||||||
|
};
|
|
@ -0,0 +1,182 @@
|
||||||
|
<!-- eslint-disable no-useless-escape -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref, unref } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { isString } from '@vben/utils';
|
||||||
|
|
||||||
|
import FcDesigner from '@form-create/designer';
|
||||||
|
import formCreate from '@form-create/element-ui';
|
||||||
|
import { useClipboard } from '@vueuse/core';
|
||||||
|
import { ElButton, ElMessage } from 'element-plus';
|
||||||
|
import hljs from 'highlight.js';
|
||||||
|
import xml from 'highlight.js/lib/languages/java';
|
||||||
|
import json from 'highlight.js/lib/languages/json';
|
||||||
|
|
||||||
|
import { useFormCreateDesigner } from '#/components/form-create';
|
||||||
|
|
||||||
|
import 'highlight.js/styles/github.css';
|
||||||
|
|
||||||
|
defineOptions({ name: 'InfraBuild' });
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal();
|
||||||
|
|
||||||
|
const designer = ref(); // 表单设计器
|
||||||
|
|
||||||
|
// 表单设计器配置
|
||||||
|
const designerConfig = ref({
|
||||||
|
switchType: [], // 是否可以切换组件类型,或者可以相互切换的字段
|
||||||
|
autoActive: true, // 是否自动选中拖入的组件
|
||||||
|
useTemplate: false, // 是否生成vue2语法的模板组件
|
||||||
|
formOptions: {
|
||||||
|
form: {
|
||||||
|
labelWidth: '100px', // 设置默认的 label 宽度为 100px
|
||||||
|
},
|
||||||
|
}, // 定义表单配置默认值
|
||||||
|
fieldReadonly: false, // 配置field是否可以编辑
|
||||||
|
hiddenDragMenu: false, // 隐藏拖拽操作按钮
|
||||||
|
hiddenDragBtn: false, // 隐藏拖拽按钮
|
||||||
|
hiddenMenu: [], // 隐藏部分菜单
|
||||||
|
hiddenItem: [], // 隐藏部分组件
|
||||||
|
hiddenItemConfig: {}, // 隐藏组件的部分配置项
|
||||||
|
disabledItemConfig: {}, // 禁用组件的部分配置项
|
||||||
|
showSaveBtn: false, // 是否显示保存按钮
|
||||||
|
showConfig: true, // 是否显示右侧的配置界面
|
||||||
|
showBaseForm: true, // 是否显示组件的基础配置表单
|
||||||
|
showControl: true, // 是否显示组件联动
|
||||||
|
showPropsForm: true, // 是否显示组件的属性配置表单
|
||||||
|
showEventForm: true, // 是否显示组件的事件配置表单
|
||||||
|
showValidateForm: true, // 是否显示组件的验证配置表单
|
||||||
|
showFormConfig: true, // 是否显示表单配置
|
||||||
|
showInputData: true, // 是否显示录入按钮
|
||||||
|
showDevice: true, // 是否显示多端适配选项
|
||||||
|
appendConfigData: [], // 定义渲染规则所需的formData
|
||||||
|
});
|
||||||
|
|
||||||
|
const dialogVisible = ref(false); // 弹窗的是否展示
|
||||||
|
const dialogTitle = ref(''); // 弹窗的标题
|
||||||
|
const formType = ref(-1); // 表单的类型:0 - 生成 JSON;1 - 生成 Options;2 - 生成组件
|
||||||
|
const formData = ref(''); // 表单数据
|
||||||
|
useFormCreateDesigner(designer); // 表单设计器增强
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const openModel = (title: string) => {
|
||||||
|
dialogVisible.value = true;
|
||||||
|
dialogTitle.value = title;
|
||||||
|
modalApi.open();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 生成 JSON */
|
||||||
|
const showJson = () => {
|
||||||
|
openModel('生成 JSON');
|
||||||
|
formType.value = 0;
|
||||||
|
formData.value = designer.value.getRule();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 生成 Options */
|
||||||
|
const showOption = () => {
|
||||||
|
openModel('生成 Options');
|
||||||
|
formType.value = 1;
|
||||||
|
formData.value = designer.value.getOption();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 生成组件 */
|
||||||
|
const showTemplate = () => {
|
||||||
|
openModel('生成组件');
|
||||||
|
formType.value = 2;
|
||||||
|
formData.value = makeTemplate();
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeTemplate = () => {
|
||||||
|
const rule = designer.value.getRule();
|
||||||
|
const opt = designer.value.getOption();
|
||||||
|
return `<template>
|
||||||
|
<form-create
|
||||||
|
v-model:api="fApi"
|
||||||
|
:rule="rule"
|
||||||
|
:option="option"
|
||||||
|
@submit="onSubmit"
|
||||||
|
></form-create>
|
||||||
|
</template>
|
||||||
|
<script setup lang=ts>
|
||||||
|
const faps = ref(null)
|
||||||
|
const rule = ref('')
|
||||||
|
const option = ref('')
|
||||||
|
const init = () => {
|
||||||
|
rule.value = formCreate.parseJson('${formCreate.toJson(rule).replaceAll('\\', '\\\\')}')
|
||||||
|
option.value = formCreate.parseJson('${JSON.stringify(opt, null, 2)}')
|
||||||
|
}
|
||||||
|
const onSubmit = (formData) => {
|
||||||
|
//todo 提交表单
|
||||||
|
}
|
||||||
|
init()
|
||||||
|
<\/script>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 复制 */
|
||||||
|
const copy = async (text: string) => {
|
||||||
|
const textToCopy = JSON.stringify(text, null, 2);
|
||||||
|
const { copy, copied, isSupported } = useClipboard({ source: textToCopy });
|
||||||
|
if (isSupported) {
|
||||||
|
await copy();
|
||||||
|
if (unref(copied)) {
|
||||||
|
ElMessage.success('复制成功');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.error('复制失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码高亮
|
||||||
|
*/
|
||||||
|
const highlightedCode = (code: string) => {
|
||||||
|
// 处理语言和代码
|
||||||
|
let language = 'json';
|
||||||
|
if (formType.value === 2) {
|
||||||
|
language = 'xml';
|
||||||
|
}
|
||||||
|
// debugger
|
||||||
|
if (!isString(code)) {
|
||||||
|
code = JSON.stringify(code, null, 2);
|
||||||
|
}
|
||||||
|
// 高亮
|
||||||
|
const result = hljs.highlight(code, { language, ignoreIllegals: true });
|
||||||
|
return result.value || ' ';
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
onMounted(async () => {
|
||||||
|
// 注册代码高亮的各种语言
|
||||||
|
hljs.registerLanguage('xml', xml);
|
||||||
|
hljs.registerLanguage('json', json);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FcDesigner ref="designer" height="90vh" :config="designerConfig">
|
||||||
|
<template #handle>
|
||||||
|
<ElButton size="small" type="primary" plain @click="showJson">
|
||||||
|
生成JSON
|
||||||
|
</ElButton>
|
||||||
|
<ElButton size="small" type="primary" plain @click="showOption">
|
||||||
|
生成Options
|
||||||
|
</ElButton>
|
||||||
|
<ElButton size="small" type="primary" plain @click="showTemplate">
|
||||||
|
生成组件
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</FcDesigner>
|
||||||
|
|
||||||
|
<!-- 弹窗:表单预览 -->
|
||||||
|
<Modal :title="dialogTitle" :footer="false" :fullscreen-button="false">
|
||||||
|
<div>
|
||||||
|
<ElButton style="float: right" @click="copy(formData)"> 复制 </ElButton>
|
||||||
|
<div>
|
||||||
|
<pre><code v-dompurify-html="highlightedCode(formData)" class="hljs"></code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</Page>
|
||||||
|
</template>
|
|
@ -762,6 +762,12 @@ importers:
|
||||||
|
|
||||||
apps/web-ele:
|
apps/web-ele:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@form-create/designer':
|
||||||
|
specifier: ^3.2.6
|
||||||
|
version: 3.2.11(vue@3.5.13(typescript@5.8.3))
|
||||||
|
'@form-create/element-ui':
|
||||||
|
specifier: ^3.2.11
|
||||||
|
version: 3.2.22(vue@3.5.13(typescript@5.8.3))
|
||||||
'@tinymce/tinymce-vue':
|
'@tinymce/tinymce-vue':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 6.1.0(vue@3.5.13(typescript@5.8.3))
|
version: 6.1.0(vue@3.5.13(typescript@5.8.3))
|
||||||
|
@ -3630,6 +3636,27 @@ packages:
|
||||||
'@form-create/component-antdv-upload@3.2.18':
|
'@form-create/component-antdv-upload@3.2.18':
|
||||||
resolution: {integrity: sha512-cobjChcblnfO0ph4MunJDUiBLyRwpzekXo6MFRsB5iq9ln73UjLnyLps4YuM2KRZ/Cn9FEoWN1kYvTFf1zKdjg==}
|
resolution: {integrity: sha512-cobjChcblnfO0ph4MunJDUiBLyRwpzekXo6MFRsB5iq9ln73UjLnyLps4YuM2KRZ/Cn9FEoWN1kYvTFf1zKdjg==}
|
||||||
|
|
||||||
|
'@form-create/component-elm-checkbox@3.2.18':
|
||||||
|
resolution: {integrity: sha512-W8v4o+MZWPEJmIIWojKmnn87tFWpxyTbaIhWJU4Ca0S99YoXR7RdHKLt06HYwJixVLZqytNRj9BMxR4UZQ6JNg==}
|
||||||
|
|
||||||
|
'@form-create/component-elm-frame@3.2.18':
|
||||||
|
resolution: {integrity: sha512-yob3jmO1xBbKfVfFNeO/xh80o1E2IbVx8NsnpTaaK9X0ARJFvhPvW53qX6TgJxdvzCGTsm/W6P6a4SaebQJtVQ==}
|
||||||
|
|
||||||
|
'@form-create/component-elm-group@3.2.22':
|
||||||
|
resolution: {integrity: sha512-hy6ZqLpDITqDoTMc4Es2twVNffjXuX0HW7aY36+iyicnJj1Y9hMRj2HPbT4DNlQWZ3ybOb/AlcYN0BwIpW40qw==}
|
||||||
|
|
||||||
|
'@form-create/component-elm-radio@3.2.18':
|
||||||
|
resolution: {integrity: sha512-kkb6xFOviqgoBRRLzsoZTnqKX9GSw2jaLCWWRPkwqEwA/aLNHRX0MuBdGNvpaaLaD1ph5g+N86GekHvvanbJlQ==}
|
||||||
|
|
||||||
|
'@form-create/component-elm-select@3.2.18':
|
||||||
|
resolution: {integrity: sha512-gqBzPgNGJ6GwQ/pK/qCuoxQeM/fflNv7IqibETt2IFgutsGVM1lXYic8QJ/51YkuI0afkDKF+wAEbfB6zqaKIA==}
|
||||||
|
|
||||||
|
'@form-create/component-elm-tree@3.2.18':
|
||||||
|
resolution: {integrity: sha512-s+0+NPh2t500pv4CA51dtwuWWlY2wW0qbL7ZE3steTBh8Z++7s+n/6y6joGPxTeP+7FkpfruA1TfJ7+5Ntpe1Q==}
|
||||||
|
|
||||||
|
'@form-create/component-elm-upload@3.2.18':
|
||||||
|
resolution: {integrity: sha512-FVFJYarlk5+/Kjg9kJ9ElwyP8bt+DR0m/pPUMqmkEoUtv0Sr6nkk596THLjfyXV1WRFeoZMjzIS6qCyTnqWksQ==}
|
||||||
|
|
||||||
'@form-create/component-subform@3.1.34':
|
'@form-create/component-subform@3.1.34':
|
||||||
resolution: {integrity: sha512-OJcFH/7MTHx7JLEjDK/weS27qfuFWAI+OK+gXTJ2jIt9aZkGWF/EWkjetiJLt5a0KMw4Z15wOS2XCY9pVK9vlA==}
|
resolution: {integrity: sha512-OJcFH/7MTHx7JLEjDK/weS27qfuFWAI+OK+gXTJ2jIt9aZkGWF/EWkjetiJLt5a0KMw4Z15wOS2XCY9pVK9vlA==}
|
||||||
|
|
||||||
|
@ -3641,6 +3668,16 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.5.13
|
vue: ^3.5.13
|
||||||
|
|
||||||
|
'@form-create/designer@3.2.11':
|
||||||
|
resolution: {integrity: sha512-5mPyeHFOj8n01LOVhibjX8OujD6RYBH8TF2Ol7n8QxaSqIcAFTz9PADIiX982REPxiZ6I8BqZa2t0OtYQtETpA==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.5.13
|
||||||
|
|
||||||
|
'@form-create/element-ui@3.2.22':
|
||||||
|
resolution: {integrity: sha512-6UfJloHWwCDkei4dQjigk5JzaFQiwEISpY0Tc5plSyJg8bt7JdqCp6C9+OQYmjTYaurwzdTvgD9NfbKDFC8xEQ==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.5.13
|
||||||
|
|
||||||
'@form-create/utils@3.2.18':
|
'@form-create/utils@3.2.18':
|
||||||
resolution: {integrity: sha512-C98bFPdFVMltiHQvEZqv4rVdhcqthJgvxMbWDlniL03HS5oyusnUvxUE8jf0I9zk5dZRDGmxKOUtzE3JDWP9nQ==}
|
resolution: {integrity: sha512-C98bFPdFVMltiHQvEZqv4rVdhcqthJgvxMbWDlniL03HS5oyusnUvxUE8jf0I9zk5dZRDGmxKOUtzE3JDWP9nQ==}
|
||||||
|
|
||||||
|
@ -13298,6 +13335,34 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@form-create/utils': 3.2.18
|
'@form-create/utils': 3.2.18
|
||||||
|
|
||||||
|
'@form-create/component-elm-checkbox@3.2.18':
|
||||||
|
dependencies:
|
||||||
|
'@form-create/utils': 3.2.18
|
||||||
|
|
||||||
|
'@form-create/component-elm-frame@3.2.18':
|
||||||
|
dependencies:
|
||||||
|
'@form-create/utils': 3.2.18
|
||||||
|
|
||||||
|
'@form-create/component-elm-group@3.2.22':
|
||||||
|
dependencies:
|
||||||
|
'@form-create/utils': 3.2.18
|
||||||
|
|
||||||
|
'@form-create/component-elm-radio@3.2.18':
|
||||||
|
dependencies:
|
||||||
|
'@form-create/utils': 3.2.18
|
||||||
|
|
||||||
|
'@form-create/component-elm-select@3.2.18':
|
||||||
|
dependencies:
|
||||||
|
'@form-create/utils': 3.2.18
|
||||||
|
|
||||||
|
'@form-create/component-elm-tree@3.2.18':
|
||||||
|
dependencies:
|
||||||
|
'@form-create/utils': 3.2.18
|
||||||
|
|
||||||
|
'@form-create/component-elm-upload@3.2.18':
|
||||||
|
dependencies:
|
||||||
|
'@form-create/utils': 3.2.18
|
||||||
|
|
||||||
'@form-create/component-subform@3.1.34': {}
|
'@form-create/component-subform@3.1.34': {}
|
||||||
|
|
||||||
'@form-create/component-wangeditor@3.2.14':
|
'@form-create/component-wangeditor@3.2.14':
|
||||||
|
@ -13309,6 +13374,33 @@ snapshots:
|
||||||
'@form-create/utils': 3.2.18
|
'@form-create/utils': 3.2.18
|
||||||
vue: 3.5.13(typescript@5.8.3)
|
vue: 3.5.13(typescript@5.8.3)
|
||||||
|
|
||||||
|
'@form-create/designer@3.2.11(vue@3.5.13(typescript@5.8.3))':
|
||||||
|
dependencies:
|
||||||
|
'@form-create/component-wangeditor': 3.2.14
|
||||||
|
'@form-create/element-ui': 3.2.22(vue@3.5.13(typescript@5.8.3))
|
||||||
|
'@form-create/utils': 3.2.18
|
||||||
|
codemirror: 6.65.7
|
||||||
|
element-plus: 2.9.9(vue@3.5.13(typescript@5.8.3))
|
||||||
|
js-beautify: 1.15.4
|
||||||
|
vue: 3.5.13(typescript@5.8.3)
|
||||||
|
vuedraggable: 4.1.0(vue@3.5.13(typescript@5.8.3))
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@vue/composition-api'
|
||||||
|
|
||||||
|
'@form-create/element-ui@3.2.22(vue@3.5.13(typescript@5.8.3))':
|
||||||
|
dependencies:
|
||||||
|
'@form-create/component-elm-checkbox': 3.2.18
|
||||||
|
'@form-create/component-elm-frame': 3.2.18
|
||||||
|
'@form-create/component-elm-group': 3.2.22
|
||||||
|
'@form-create/component-elm-radio': 3.2.18
|
||||||
|
'@form-create/component-elm-select': 3.2.18
|
||||||
|
'@form-create/component-elm-tree': 3.2.18
|
||||||
|
'@form-create/component-elm-upload': 3.2.18
|
||||||
|
'@form-create/component-subform': 3.1.34
|
||||||
|
'@form-create/core': 3.2.22(vue@3.5.13(typescript@5.8.3))
|
||||||
|
'@form-create/utils': 3.2.18
|
||||||
|
vue: 3.5.13(typescript@5.8.3)
|
||||||
|
|
||||||
'@form-create/utils@3.2.18': {}
|
'@form-create/utils@3.2.18': {}
|
||||||
|
|
||||||
'@gar/promisify@1.1.3': {}
|
'@gar/promisify@1.1.3': {}
|
||||||
|
|
Loading…
Reference in New Issue