import formBuilderMap from "./formBuilderMap"; import { CreateElement, VNode } from "vue"; import moment from "moment"; import { getToken } from "@/utils/auth"; import { deepCopy } from "@/utils/index"; /** * @param {String} device 'desktop' or 'mobile' * @param {Object} info field参数 * @param {CreateElement} h * @param {Object} row 子表单的row * @param {Boolean} pWrite 主表单中子表单字段是否可写 * @param {Boolean} pReadable * @return {VNode} 主表单包含el-form-item 子表单表单组件 **/ export default function formBuilder(device, info, h, row, pWrite = false,pReadable = false) { let formItem; //下拉选项 if (info?.selection_model) { options = info.selection_model_items; } else if (info?.stub) { options = info?.stub?.split(/\n/) || []; } let options; if (device === "desktop") { // 可写并且不为查看和子表单下 if ( info._writeable || (info.type === "relation" && info._readable) || pWrite ) { switch (info.type) { case "text": formItem = h(formBuilderMap(device).get(info.type), { props: { value: row ? row[info.name] : this.form[info.name], clearable: true, placeholder: info.help_text, }, attrs: { placeholder: info.help_text, }, on: { input: (e) => { row ? this.$set(row, info.name, e) : this.$set(this.form, info.name, e); }, }, }); break; case "textarea": formItem = h(formBuilderMap(device).get(info.type), { props: { type: "textarea", autosize: { minRows: 2, }, value: row ? row[info.name] : this.form[info.name], clearable: true, placeholder: info.help_text, }, attrs: { placeholder: info.help_text, }, on: { input: (e) => { row ? this.$set(row, info.name, e) : this.$set(this.form, info.name, e); }, }, }); break; case "date": formItem = h(formBuilderMap(device).get(info.type), { props: { type: "date", "value-format": "yyyy-MM-dd", format: "yyyy年MM月dd日", value: row ? row[info.name] : this.form[info.name], clearable: true, placeholder: info.help_text, "picker-options": { shortcuts: [ { text: "一年前", onClick(picker) { picker.$emit( "pick", moment().subtract(1, "years").toDate() ); }, }, { text: "一月前", onClick(picker) { picker.$emit( "pick", moment().subtract(1, "months").toDate() ); }, }, { text: "一周前", onClick(picker) { picker.$emit( "pick", moment().subtract(1, "weeks").toDate() ); }, }, { text: "今天", onClick(picker) { picker.$emit("pick", new Date()); }, }, { text: "一周后", onClick(picker) { picker.$emit("pick", moment().add(1, "weeks").toDate()); }, }, { text: "一月后", onClick(picker) { picker.$emit("pick", moment().add(1, "months").toDate()); }, }, { text: "一年后", onClick(picker) { picker.$emit("pick", moment().add(1, "years").toDate()); }, }, ], }, }, attrs: { placeholder: info.help_text, }, style: { width: "100%", }, on: { input: (e) => { row ? this.$set(row, info.name, e) : this.$set(this.form, info.name, e); }, }, }); break; case "datetime": formItem = h(formBuilderMap(device).get(info.type), { props: { type: "datetime", "value-format": "yyyy-MM-dd", format: "yyyy年MM月dd日", value: row ? row[info.name] : this.form[info.name], clearable: true, placeholder: info.help_text, "picker-options": { shortcuts: [ { text: "一年前", onClick(picker) { picker.$emit( "pick", moment().subtract(1, "years").toDate() ); }, }, { text: "一月前", onClick(picker) { picker.$emit( "pick", moment().subtract(1, "months").toDate() ); }, }, { text: "一周前", onClick(picker) { picker.$emit( "pick", moment().subtract(1, "weeks").toDate() ); }, }, { text: "今天", onClick(picker) { picker.$emit("pick", new Date()); }, }, { text: "一周后", onClick(picker) { picker.$emit("pick", moment().add(1, "weeks").toDate()); }, }, { text: "一月后", onClick(picker) { picker.$emit("pick", moment().add(1, "months").toDate()); }, }, { text: "一年后", onClick(picker) { picker.$emit("pick", moment().add(1, "years").toDate()); }, }, ], }, }, style: { width: "100%", }, attrs: { placeholder: info.help_text, }, on: { input: (e) => { row ? this.$set(row, info.name, e) : this.$set(this.form, info.name, e); }, }, }); break; case "select": formItem = h( formBuilderMap(device).get(info.type), { props: { value: row ? row[info.name] : this.form[info.name], clearable: true, placeholder: info.help_text, multiple: !!info.multiple, 'multiple-limit': info.multiple, }, style: { width: "100%", }, attrs: { placeholder: info.help_text, }, on: { input: (e) => { row ? this.$set(row, info.name, e) : this.$set(this.form, info.name, e); }, }, }, options.map((option) => h("el-option", { props: { label: typeof option === "object" ? option.name : option, value: typeof option === "object" ? option.id : option, }, }) ) ); break; case "radio": formItem = h( formBuilderMap(device).get(info.type), { props: { value: row ? row[info.name] : this.form[info.name], }, attrs: { placeholder: info.help_text, }, on: { input: (e) => { row ? this.$set(row, info.name, e) : this.$set(this.form, info.name, e); }, }, }, options.map((option) => h( "el-radio", { props: { label: typeof option === "object" ? option.id : option, }, }, typeof option === "object" ? option.name : option ) ) ); break; case "file": formItem = h( formBuilderMap(device).get(info.type), { props: { action: process.env.VUE_APP_UPLOAD_API, headers: { Authorization: `Bearer ${getToken()}`, }, accept: "application/msword,image/jpeg,application/pdf,image/png,application/vnd.ms-powerpoint,text/plain,application/x-zip-compressed,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document", multiple: true, fileList: this.file[info.name], beforeUpload: (file) => { if (file.size / 1024 / 1024 > 20) { this.$message({ type: "warning", message: "上传图片大小超过20Mb!", }); return false; } }, onSuccess: (response, file, fileList) => { fileList.forEach((file) => { if (file.response?.data && !file.response?.code) { file.response = file.response.data; } }); this.file[info.name] = fileList; }, onRemove: (file, fileList) => { this.file[info.name] = fileList; }, onError: (err, file, fileList) => { this.file[info.name] = fileList; this.$message({ type: "warning", message: err, }); }, }, scopedSlots: { file: (scope) => { let { file } = scope; return [ h("div", {}, [ h("i", { class: { "el-icon-circle-check": file.status === "success", "el-icon-loading": file.status === "uploading", }, style: { color: file.status === "success" ? "green" : "", }, }), h( "a", { attrs: { href: file.url, download: file.name, }, class: { "uploaded-a": file.status === "success", }, style: { padding: "0 4px", }, }, file.name ), ]), h("i", { class: "el-icon-close", on: { ["click"]: () => { this.$set( this.file, info.field, this.file[info.field].filter( (item) => item !== file ) ); }, }, }), ]; }, }, }, [ h( "el-button", { slot: "trigger", props: { size: "small", type: "primary", }, }, "选取文件" ), h( "div", { class: "el-upload__tip", slot: "tip", }, "文件不超过20Mb" ), ] ); break; case "label": formItem = h( formBuilderMap(device).get(info.type), { props: { type: "primary", }, }, info.label ); break; case "static": formItem = h( formBuilderMap(device).get(info.type), { props: { type: "primary", }, attrs: { href: row ? row[info.name] : this.form[info.name], target: "_blank", }, }, info.label ); break; case "hr": formItem = h(formBuilderMap(device).get(info.type), {}, info.label); break; case "choice": formItem = h( formBuilderMap(device).get(info.type), { props: { value: row ? row[info.name] : this.form[info.name], clearable: true, placeholder: info.help_text, multiple: !!info.multiple, 'multiple-limit': info.multiple, }, attrs: { placeholder: info.help_text, }, style: { width: "100%", }, on: { input: (e) => { row ? this.$set(row, info.name, e) : this.$set(this.form, info.name, e); }, }, }, options.map((option) => h("el-option", { props: { label: typeof option === "object" ? option.name : option, value: typeof option === "object" ? option.id : option, }, }) ) ); break; case "choices": formItem = h( formBuilderMap(device).get(info.type), { props: { value: row ? row[info.name] : this.form[info.name], clearable: true, placeholder: info.help_text, multiple: true, }, attrs: { placeholder: info.help_text, }, style: { width: '100%' }, on: { input: (e) => { row ? this.$set(row, info.name, e) : this.$set(this.form, info.name, e); }, }, }, options.map((option) => h("el-option", { props: { label: option, value: option, }, }) ) ); break; case "relation": formItem = h( "vxe-table", { ref: `subForm-${info.name}`, style: { "margin-top": "10px", }, props: { "min-height": "200px", border: true, stripe: true, data: this.form[info.name], "keep-source": true, "column-config": { resizable: true }, "show-overflow": true, "edit-config": info._writeable ? { trigger: "click", mode: "row", showStatus: false, isHover: true, autoClear: false, } : {}, }, on: { "edit-closed": ({ row, column }) => { const $table = this.$refs[`subForm-${info.name}`]; if ($table) { this.$set( this.form, info.name, this.$refs[`subForm-${info.name}`].tableData ); } }, }, }, [ info._writeable ? h( "vxe-column", { props: { width: 56, align: "center", }, scopedSlots: { default: ({ row }) => { return h("el-button", { slot: "default", style: { padding: "9px", }, props: { type: "danger", size: "small", icon: "el-icon-minus", }, on: { click: async (_) => { const $table = this.$refs[`subForm-${info.name}`]; if ($table) { await $table.remove(row); } }, }, }); }, }, }, [ h("el-button", { slot: "header", style: { padding: "9px", }, props: { type: "primary", size: "small", icon: "el-icon-plus", }, on: { click: async (_) => { const $table = this.$refs[`subForm-${info.name}`]; if ($table) { const record = {}; const { row: newRow } = await $table.insert( record ); await this.$nextTick(); await $table.setEditRow(newRow); } }, }, }), ] ) : "", ...this.subForm .get(info.sub_custom_model_id) ?.customModel?.fields?.map((subField, subIndex) => h("vxe-column", { props: { field: subField.name, title: subField.label, align: "center", "edit-render": {}, }, scopedSlots: { edit: ({ row }) => { return formBuilder.bind(this)( device, subField, h, row, info._writeable, info._readable, ); }, }, }) ), ] ); break; } } else if (info._readable) { switch (info.type) { case "date": formItem = h( "span", { style: { color: "#333", }, }, moment(this.form[info.name]).format("YYYY年MM月DD日") ); break; case "datetime": formItem = h( "span", { style: { color: "#333", }, }, moment(this.form[info.name]).format("YYYY年MM月DD日 HH时mm分ss秒") ); break; case "select": let findValue = options.find((i) => typeof i === "object" ? i.id === this.form[info.name] : i === this.form[info.name] ); formItem = h( "span", { style: { color: "#333", }, }, typeof findValue === "object" ? findValue.name : findValue ); break; case "file": formItem = h( formBuilderMap(device).get(info.type), { props: { action: process.env.VUE_APP_UPLOAD_API, headers: { Authorization: `Bearer ${getToken()}`, }, disabled: true, accept: "application/msword,image/jpeg,application/pdf,image/png,application/vnd.ms-powerpoint,text/plain,application/x-zip-compressed,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document", multiple: true, fileList: this.file[info.name], }, scopedSlots: { file: (scope) => { let { file } = scope; return [ h("div", {}, [ h("i", { class: "el-icon-circle-check", style: { color: "green", }, }), h( "a", { attrs: { href: file.url, download: file.name, }, class: { "uploaded-a": file.status === "success", }, style: { padding: "0 4px", }, }, file.name ), ]), ]; }, }, }, [ h( "span", { slot: "trigger", }, `数量:${this.file[info.name].length}` ), ] ); break; default: formItem = h( "span", { style: { color: "#333", }, }, this.form[info.name] ); } } if (formItem) { return row ? formItem : h( "el-form-item", { props: { prop: info.name, label: info.label, }, style: { "grid-column-start": info.gs_x + 1, "grid-column-end": info.gs_x + 1 + info.gs_width, "grid-row-start": info.gs_y + 1, "grid-row-end": info.gs_y + 1 + info.gs_height, }, }, [formItem] ); } } if (device === "mobile") { if (info._writeable || pWrite) { switch (info.type) { case "text": formItem = h(formBuilderMap(device).get(info.type), { props: { name: info.name, label: info.label, value: row ? row[info.name] : this.form[info.name], clearable: true, placeholder: info.help_text, }, attrs: { placeholder: info.help_text, }, on: { input: (e) => { row ? this.$set(row, info.name, e) : this.$set(this.form, info.name, e); }, }, }); break; case "textarea": formItem = h(formBuilderMap(device).get(info.type), { props: { name: info.name, label: info.label, type: "textarea", value: row ? row[info.name] : this.form[info.name], clearable: true, placeholder: info.help_text, }, attrs: { placeholder: info.help_text, }, on: { input: (e) => { row ? this.$set(row, info.name, e) : this.$set(this.form, info.name, e); }, }, }); break; case "date": formItem = h("van-field", { props: { readonly: true, clickable: true, name: info.name, label: info.label, value: row ? row[info.name] : this.form[info.name], clearable: true, placeholder: info.help_text, }, attrs: { placeholder: info.help_text, }, on: { click: (_) => { this.vanCalendarOption.forFormName = info.name; this.vanCalendarOption.originalObj = row; this.$set(this.vanCalendarOption, "isShow", true); }, }, }); break; case "datetime": formItem = h("van-field", { props: { readonly: true, clickable: true, name: info.name, label: info.label, value: row ? row[info.name] : this.form[info.name], clearable: true, placeholder: info.help_text, }, on: { click: (_) => { this.vanTimePickerOption.forFormName = info.name; this.vanTimePickerOption.originalObj = row; this.$set(this.vanTimePickerOption, "isShow", true); }, }, }); break; case "select": let findValue = options.find((i) => typeof i === "object" ? i.id === (row ? row[info.name] : this.form[info.name]) : i === (row ? row[info.name] : this.form[info.name]) ); formItem = h("van-field", { props: { readonly: true, clickable: true, name: info.name, label: info.label, value: typeof findValue === "object" ? findValue.name : findValue, clearable: true, placeholder: info.help_text, }, on: { click: (_) => { this.vanPopupOption.forFormName = info.name; this.vanPopupOption.originalObj = row; this.$set(this.vanPopupOption, "columns", options); this.$set(this.vanPopupOption, "isShow", true); }, }, }); break; case "file": formItem = h('van-cell',{ props: { title: info.label, } },[ h( formBuilderMap(device).get(info.type), { props: { action: process.env.VUE_APP_UPLOAD_API, headers: { Authorization: `Bearer ${getToken()}`, }, accept: "application/msword,image/jpeg,application/pdf,image/png,application/vnd.ms-powerpoint,text/plain,application/x-zip-compressed,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document", multiple: true, fileList: this.file[info.name], beforeUpload: (file) => { if (file.size / 1024 / 1024 > 20) { this.$message({ type: "warning", message: "上传图片大小超过20Mb!", }); return false; } }, onSuccess: (response, file, fileList) => { fileList.forEach((file) => { if (file.response?.data && !file.response?.code) { file.response = file.response.data; } }); this.file[info.name] = fileList; }, onRemove: (file, fileList) => { this.file[info.name] = fileList; }, onError: (err, file, fileList) => { this.file[info.name] = fileList; this.$message({ type: "warning", message: err, }); }, }, scopedSlots: { file: (scope) => { let { file } = scope; return [ h("div", {}, [ h("i", { class: { "el-icon-circle-check": file.status === "success", "el-icon-loading": file.status === "uploading", }, style: { color: file.status === "success" ? "green" : "", }, }), h( "a", { attrs: { href: file.url, download: file.name, }, class: { "uploaded-a": file.status === "success", }, style: { padding: "0 4px", }, }, file.name ), ]), h("i", { class: "el-icon-close", on: { ["click"]: () => { this.$set( this.file, info.field, this.file[info.field].filter( (item) => item !== file ) ); }, }, }), ]; }, }, }, [ h( "el-button", { slot: "trigger", props: { size: "mini", type: "primary", }, }, "选取文件" ), h( "div", { class: "el-upload__tip", slot: "tip", }, "文件不超过20Mb" ), ] ) ]) break; case "relation": let copySubForm = deepCopy(this.form[info.name][0]) formItem = h("div", [ h("van-cell", { props: { "arrow-direction": "down", title: info.label, }, },[ h('van-button',{ props: { type: 'info', icon: 'plus', size: 'small' }, on: { 'click':_ => { this.form[info.name].push(copySubForm) } } },'新增') ]), h( "div", this.form[info.name].map((sForm, sIndex) => h( "van-cell-group", { props: { inset: false } }, [ h("van-cell", { props: { title: info.label + "-" + (sIndex + 1) } },[ h('van-button',{ props: { size: 'mini', type: 'danger', icon: 'minus' }, on: { click: _ => { this.form[info.name].splice(sIndex, 1) } } },'删除') ]), h( "van-form", { props: { "scroll-to-error": true, }, style: { margin: '0 10px' } }, Array.from(this.subForm) .map((i) => i[1]?.customModel?.fields) ?.flat() .map((subField) => formBuilder.bind(this)( device, subField, h, sForm, info._writeable, ) ) ), ] ) ) ), ]); break; } } else if(info._readable || pReadable) { switch (info.type) { case "date": formItem = h( "van-cell", { props: { title: info.label, value: this.form[info.name] ? moment(this.form[info.name]).format("YYYY年MM月DD日") : '' } } ); break; case "datetime": formItem = h( "van-cell", { props: { title: info.label, value: this.form[info.name] ? moment(this.form[info.name]).format("YYYY年MM月DD日 HH时mm分ss秒") : '' } }, ); break; case "select": let findValue = options.find((i) => typeof i === "object" ? i.id === this.form[info.name] : i === this.form[info.name] ); formItem = h( "van-cell", { props: { title: info.label, value: typeof findValue === "object" ? findValue.name : findValue } }, ); break; case 'file': formItem = h( "van-cell", { props: { title: info.label, } }, this.file[info.name].map(file => h("div", {}, [ h( "a", { attrs: { href: file.url, download: file.name, }, class: { "uploaded-a": file.status === "success", }, style: { padding: "0 4px", }, }, file.name ), ])) ); break case 'relation': formItem = h("div", [ h("van-cell", { props: { "arrow-direction": "down", title: info.label, }, }), h( "div", this.form[info.name].map((sForm, sIndex) => h( "van-cell-group", { props: { inset: false } }, [ h("van-cell", { props: { title: info.label + "-" + (sIndex + 1) } }), h( "van-form", { props: { "scroll-to-error": true, }, style: { margin: '0 10px' } }, Array.from(this.subForm) .map((i) => i[1]?.customModel?.fields) ?.flat() .map((subField) => formBuilder.bind(this)( device, subField, h, sForm, info._writeable, info._readable ) ) ), ] ) ) ), ]); break; default: formItem = h('van-cell',{ props: { title: info.label, value: row ? row[info.name] : this.form[info.name] } }) } } return formItem; } }