一、下载依赖包
1.我这边使用的是elementui版本,vue版本要在2.7.0以上
npm i @form-create/element-ui@super二、封装生成组件
1.我这边对部分功能进行了删减,里面有很多配置项,具体的可以看看文档及源码,源码配置文件位置在@form-create/designer/dist/index.es.js,可以在里面改然后通过打补丁的方式进行更改。
<template> <fc-designer class="form-create-designer" ref="designer" height="100vh" :config="formConfig" @save="handleSave" /> </template> <script> import { getToken } from "@/utils/auth"; import formCreate from "@form-create/element-ui"; // 假设使用 Element UI export default { name: "FormCreateDesigner", components: { formCreate: formCreate.$form(), }, props: { // 设计器配置项 config: { type: Object, default: () => ({}), }, // 设计器表单项配置,JSON字符串 options: { type: Object, default: () => ({}), }, // 表单内容,数组 rule: { type: [Array], default: () => ([]), }, }, data() { return {}; }, computed: { formConfig() { return { // 显示保存按钮 showSaveBtn: true, // 是否显示多端适配 showDevice: false, // 是否显示左侧 showMenuBar: false, // 隐藏哪些表单项 hiddenMenu: ["subform", "aide", "layout"], // 影藏某些组件 hiddenItem: [ "html", // 富文本 "slider", // 滑块 "rate", // 评分 "colorPicker", // 颜色选择器 "elTransfer", // 穿梭框 "tree", // 树形选择 "signaturePad", // 签名 "fcTable", // 表格 "space", // 间距 "elTabs", // 标签页 "elCard", // 卡片 "elCollapse", // 折叠面板 "cascader", // 级联选择 "timePicker", // 时间选择 "timeRange", // 时间区间 "fcEditor", // 富文本编辑器 "password", // 密码输入框 'datePicker', // 日期选择 'dateRange', // 日期时间选择区间 ], // 关闭事件配置 showEventForm: false, // 关闭样式配置 showStyleForm: false, // 关闭组件联动 showControl: false, // 隐藏表单的部分配置项 hiddenFormConfig: [ "event", "_resetBtn>show", "_submitBtn>show", "inlineMessage", "showMessage", "hideRequiredAsterisk", ], // 只显示必填 validateOnlyRequired: true, // 关闭国际化 showLanguage: false, // 关闭属性配置 // showPropsForm: false, // 关闭时是否提示 exitConfirm: false, // 关闭预览 showPreviewBtn: false, ...this.config, }; }, formOption() { return { global: { "*": { props: { action: process.env.VUE_APP_UPLOAD_API + "/file/upload", headers: { Authorization: "Bearer " + getToken(), }, }, }, // 上传重定义 upload: { props: { action: process.env.VUE_APP_UPLOAD_API + "/file/upload", headers: { Authorization: "Bearer " + getToken(), }, onSuccess: function (res, file) { // 强制覆盖规则中的 onSuccess file.url = res.data.url; file.name = res.data.name; }, onError: function (err) { console.error("统一的上传失败处理:", err); }, }, }, ...this.options, }, }; }, }, watch: { rule: { handler(val) { this.$nextTick(() => { this.$refs.designer.setRule(val); }); }, immediate: true, deep: true, }, formOption: { handler(val) { this.$nextTick(() => { this.$refs.designer.setOptions(val); }); }, immediate: true, deep: true, }, }, beforeDestroy() { // 销毁时清理数据 this.$refs.designer?.setRule([]); this.$refs.designer?.setOptions({}); this.$refs.designer?.destroy?.(); }, methods: { handleSave(data) { const { options, rule } = data; this.$emit("save", data); }, }, }; </script> <style lang="scss" scoped> .form-create-designer { ::v-deep { .el-aside { width: 400px !important; } } } </style>三、页面使用
<template> <el-dialog title="设置表单" :visible.sync="isOpen" width="86%" append-to-body class="common-dialog form-config-dialog" > <!-- 表单设计器 --> <FormCreateDesigner ref="formDesigner" :rule="formRule" @save="handleFormSave" /> </el-dialog> </template> <script> import { getPurchaseFormConfig, addPurchaseFormConfig } from "@/api/system/approvalTransaction/approvalTransactionSettings"; import FormCreateDesigner from "@/components/FormCreateDesigner"; import formCreate from "@form-create/element-ui"; export default { name: "ApprovalConfigDialog", components: { FormCreateDesigner, formCreate: formCreate.$form(), }, data() { return { isOpen: false, affairId: null, orgId: null, villageId: null, formRule: [], }; }, methods: { open(row) { this.isOpen = true; this.formRule = []; this.affairId = row?.affairId; this.orgId = row?.orgId; this.villageId = row?.villageId; if (this.affairId && this.orgId && this.villageId) { this.getFormConfig(); } }, // 获取表单配置 getFormConfig() { getPurchaseFormConfig({ affairId: this.affairId, orgId: this.orgId, villageId: this.villageId }).then(res => { const resData = res?.data || {}; this.formRule = resData?.fields || []; }) }, handleFormSave(data) { addPurchaseFormConfig({ affairId: this.affairId, orgId: this.orgId, villageId: this.villageId, fields: formCreate.parseJson(data.rule || "[]"), }).then(res => { this.$message.success(res?.msg || "操作成功"); this.$emit("submit"); this.close(); }) }, close() { this.isOpen = false; this.formRule = []; }, }, }; </script> <style lang="scss" scoped> .form-config-dialog { ::v-deep .el-dialog__body { max-height: 80vh; } } </style>四、设置完表单后页面如何展示
1.插件自带渲染的组件,可直接渲染提交
<template> <form-create :rule="rule" v-model="fApi" :option="options"/> </template> <script> export default { data(){ return { fApi:{}, options:{ onSubmit:(formData)=>{ alert(JSON.stringify(formData)) }, resetBtn:true }, rule:[ { type:'input', field:'goods_name', title:'商品名称', value:'form-create' }, { type:'checkbox', field:'label', title:'标签', value:[0,1,2,3], options: [ {label:'好用',value:0}, {label:'快速',value:1}, {label:'高效',value:2}, {label:'全能',value:3}, ] }, ] } } } </script>2.自定义渲染,我项目是有别的需求,所以我自己封装了一个简陋的
<template> <el-form :model="formData" v-bind="formOptions" class="form-create-render"> <el-row> <template v-for="(field, index) in fields"> <el-col :span="colSpan"> <el-form-item v-if="field.display && !field.hidden" :label="field.title" :prop="field.field" :rules="getRules(field)" :label-width=" field.wrap?.labelWidth || formOptions.labelWidth || '120px' " :key="field.name" > <template #label> <div class="form-item-label"> <el-tooltip v-if="field.info" class="item" effect="dark" :content="field.info" placement="top-end" > <i class="el-icon-warning" /> </el-tooltip> <span >{{ field.title }}{{ formOptions.labelSuffix || "" }}</span > </div> </template> <!-- Input 组件 --> <el-input v-if="field.type === 'input'" v-model="formData[field.field]" v-bind="field.props" :ref="field.name" /> <!-- 计数器 组件 --> <el-input-number v-else-if="field.type === 'inputNumber'" v-model="formData[field.field]" v-bind="field.props" :ref="field.name" /> <!-- 单选框 组件 --> <el-radio-group v-else-if="field.type === 'radio'" v-model="formData[field.field]" v-bind="field.props" :ref="field.name" > <template v-if="field.props && field.props.type === 'button'"> <el-radio-button v-for="option in getOptions(field)" :key="option.value" :label="option.value" >{{ option.label }}</el-radio-button > </template> <template v-else> <el-radio v-for="option in getOptions(field)" :key="option.value" :label="option.value" >{{ option.label }}</el-radio > </template> </el-radio-group> <!-- 多选框 组件 --> <el-checkbox-group v-else-if="field.type === 'checkbox'" v-model="formData[field.field]" v-bind="field.props" :ref="field.name" > <template v-if="field.props && field.props.type === 'button'"> <el-checkbox-button v-for="(option, index) in getOptions(field)" :key="option.value" :label="option.value" >{{ option.label }}</el-checkbox-button > </template> <template v-else> <el-checkbox v-for="option in getOptions(field)" :key="option.value" :label="option.value" >{{ option.label }}</el-checkbox > </template> </el-checkbox-group> <!-- Select 组件 --> <el-select v-else-if="field.type === 'select'" v-model="formData[field.field]" v-bind="field.props" :ref="field.name" > <el-option v-for="option in getOptions(field)" :key="option.value" :label="option.label" :value="option.value" /> </el-select> <!-- 开关 组件 --> <el-switch v-else-if="field.type === 'switch'" v-model="formData[field.field]" v-bind="field.props" :ref="field.name" > </el-switch> <!-- 日期 组件 --> <el-date-picker v-else-if="field.type === 'datePicker'" v-model="formData[field.field]" v-bind="field.props" :ref="field.name" > </el-date-picker> <!-- 上传 组件 --> <FileUpload v-else-if="field.type === 'upload'" v-model="formData[field.field]" :limit="field.props.limit" :fileType="(field.props.accept || '').split(',')" v-bind="field.props" :ref="field.name" /> <!-- 其他类型可扩展 --> <span v-else>未支持的组件类型: {{ field.type }}</span> </el-form-item> </el-col> </template> </el-row> </el-form> </template> <script> import request from "@/utils/request"; export default { name: "FormCreateRender", props: { formOptions: { type: Object, default: () => ({ inline: false, labelPosition: "top", size: "default", labelWidth: "125px", labelSuffix: ":", }), }, schema: { type: String, required: true, validator: (value) => { try { JSON.parse(value); return true; } catch (e) { return false; } }, }, colSpan: { type: Number, default: 8, }, }, data() { return { formData: {}, optionsCache: {}, initialized: false, }; }, computed: { fields() { try { return JSON.parse(this.schema); } catch (e) { console.error("表单配置解析失败:", e); return []; } }, }, mounted() { this.initializeForm(); }, watch: { schema: { immediate: true, handler() { this.initializeForm(); }, }, formData: { handler(val) { console.log(val); }, deep: true, }, }, methods: { initializeForm() { const newFields = this.fields.filter((f) => f.display && !f.hidden); // 初始化或更新字段 newFields.forEach((field) => { const fieldKey = field.field; // 如果字段不存在或值为undefined,初始化为空字符串 if (this.formData[fieldKey] === undefined) { this.$set( this.formData, fieldKey, field.type === "checkbox" ? [] : "" ); } }); // 清理已删除的字段 Object.keys(this.formData).forEach((key) => { const exists = newFields.some((f) => f.field === key); if (!exists) { this.$delete(this.formData, key); } }); this.loadOptions(); }, getRules(field) { const rules = []; if (field.$required) { rules.push({ required: true, message: typeof field.$required === "string" ? field.$required : `请输入${field.title}`, }); } return rules; }, getOptions(field) { return this.optionsCache[field.field] || []; }, async loadOptions() { const types = ["select", "radio", "checkbox"]; for (const field of this.fields) { if (types.includes(field.type)) { if (field.effect?.fetch) { try { const options = await this.fetchOptions(field.effect.fetch); this.$set(this.optionsCache, field.field, options); } catch (e) { this.$set(this.optionsCache, field.field, []); } } else { this.$set(this.optionsCache, field.field, field?.options || []); } } } }, fetchOptions(fetchConfig) { return new Promise((resolve, reject) => { request({ url: fetchConfig.action, method: fetchConfig.method || "get", params: fetchConfig.query || {}, data: fetchConfig.data || {}, headers: fetchConfig.headers || {}, }) .then((res) => { const list = (res?.data || []).map((item) => { return { label: item.dictLabel, value: item.dictValue, raw: item, }; }); resolve(list); }) .catch((err) => { reject(err); }); }); }, }, }; </script> <style lang="scss" scoped> .form-create-render { width: 100%; ::v-deep { .form-item-label { display: inline-block; } } } </style>