<template> <ElForm :style="getStyle()" :model="modelFormData" :label-width="labelWidth" class="FormConfig" ref="ElFormRef" size="mini" > <template v-for="(item, index) in formList"> <ElFormItem class="formItem" :rules="getRules(item)" v-if="formItemVisible(item)" :key="`${item.key}|${index}`" :label="item.label" :style="gridStyle(item, index)" :prop="item.type !== 'MultipleLabelItem' ? item.key : void 0" :required="item.required" > <slot :name="item.key" :data="item" :formData="modelFormData"> <ProxyCom :value="getValue(item)" :item="item" @update:value="(data) => updateValue(item, data)" /> <!-- <component :is="getComponent(item.type)" v-bind="getBindData(item)" v-model="modelFormData[item.key]" v-on="resolveListeners(item.ons)" /> --> </slot> </ElFormItem> </template> </ElForm> </template> <script> import { resolveName, defaultComponentOptions, RegexpMap } from "./utils/index"; import { reduceDefaultValue } from "./utils/defaultValue"; import { set as pathSet, get as pathGet, cloneDeep } from "lodash"; import ProxyCom from "./Proxy.vue"; const files = require.context( "./components", true, /^.\/[^/]+\/index\.vue$|^.\/[^/]+.vue$/ ); const components = files.keys().reduce( (prev, key) => { prev[key.match(/[^./]+/g)[0]] = files(key).default; return prev; }, { Empty: "div", } ); export default { name: "FormConfig", components: { ProxyCom, }, provide() { return { getComponent: this.getComponent, getBindData: this.getBindData, getFormData: () => this.modelFormData, getValue: this.getValue, updateValue: this.updateValue, parent: this, getRules: this.getRules, }; }, props: { /** * { * label: String; * key: String; * type: 'input' | 'timePicker', * gridArea?: "", * gridColumn?: "", * gridRow?: "", * options?: {} * }[] */ dFormData: { type: Object, default: () => {}, }, /** * { * label: String; * key: String; * type: 'input' | 'timePicker', * gridArea?: "", * gridColumn?: "", * gridRow?: "", * options?: {} * }[] */ value: Object, formList: { type: Array, default: () => [], }, labelWidth: { type: String, default: "auto", }, rules: { type: Object, default: null, }, column: { type: [String, Number], default: "3", }, }, model: { prop: "value", event: "update:value", }, data() { return { formData: {}, }; }, watch: { formList: { immediate: true, handler() { this.reset(true); }, }, }, // created() { // this.reset(true); // }, computed: { modelFormData: { get() { return this.formData; }, set(data) { this.formData = data; this.$emit("update:value", this.formData); }, }, gridStyle() { return (item, index) => ({ gridRow: `span ${item.gridRow || 1}`, gridColumn: `span ${ item.gridColumn || (item.isAlone && this.column) || 1 }`, }); }, formItemVisible() { return (item) => { const result = item && item.visible ? item.visible(this.modelFormData) : true; // if (!result) { // delete this.formData[item.key]; // } return result; }; }, }, methods: { getValue(item) { return pathGet(this.formData, item.key); }, updateValue(item, data) { // 对于数组的修改无响应式 采用此方法 this.modelFormData = { ...pathSet(this.modelFormData, item.key, data) }; }, reset(isFirst) { return (this.modelFormData = reduceDefaultValue( this.formList, isFirst ? this.value || this.dFormData : {} )); }, getStyle() { return { gridTemplateColumns: `repeat(${this.column}, 1fr)`, }; }, getBindData(item) { let componentKey = resolveName(item.type || "input"); return { placeholder: "请输入", ...defaultComponentOptions[componentKey], ...item.options, }; }, getComponent(type) { if (!type) type = "input"; const componentKey = resolveName(type); const ElComponentKey = `El${componentKey}`; return ( components[componentKey] || components[ElComponentKey] || ElComponentKey ); }, getRules(item) { // 自定义的没添加 /** * validator 手动情况下 此方法需要改传参 ({ * // 当前总数据 * data, * // 当前字段的值 * value, * // 回调跟 el 一样 * callback, * // 当前的item config * config * }) */ const ruleMatch = (value) => { /** * @type {{ callback?: (value: any) => boolean; type?: "phone"; message: string; }[]} */ const rules = item.rules || []; for (let index = 0; index < rules.length; index++) { const rule = rules[index]; // 返回值 true 通过 false 异常 if (typeof rule.callback === "function") { if (!rule.callback(value, this.modelFormData)) return new Error(rule.message || "内容错误"); } else if (RegexpMap[rule.type]) { if (!RegexpMap[rule.type].reg.test(value)) return new Error(rule.message || RegexpMap[rule.type].message); } } }; if (item.required) return [ { validator: (_, __, callback) => { const value = pathGet(this.modelFormData, item.key); if ( (!value && typeof value != "number") || (typeof value === "string" && !value.trim()) || (typeof value === "object" && value.length == 0) ) return callback( new Error( `${item.options?.placeholder || `${item.label}不能为空`}` ) ); const err = ruleMatch(value); if (err) return callback(err); callback(); }, trigger: ["blur", "change"], }, ]; else if (item.rules?.length) return [ { validator: (_, __, callback) => { /** * @type {{ callback?: (value: any) => boolean; type?: "phone"; message: string; }[]} */ const err = ruleMatch(pathGet(this.modelFormData, item.key)); if (err) return callback(err); callback(); }, trigger: ["blur", "change"], }, ]; }, validate() { return new Promise((resolve, reject) => { this.$refs.ElFormRef.validate((bool) => { if (bool) resolve(cloneDeep(this.modelFormData)); else reject("表单验证未通过"); }); }); }, }, }; </script> <style lang="scss" scoped> .list-item { display: inline-block; margin-right: 10px; } .list-enter-active, .list-leave-active { transition: all 1s; position: absolute; } .list-enter, .list-leave-to { opacity: 0; transform: translateX(30px); } .FormConfig { display: grid; align-content: start; width: 100%; gap: 15px 15px; // overflow-x: hidden; padding-right: 9px; .formItem { display: flex; align-items: center; } ::v-deep { .el-form-item { align-items: center; margin: 0; height: 100%; width: 100%; &:first-child { .el-form-item__label-wrap { // padding-top: 9px; margin: 0 !important; } } .el-form-item__label-wrap { width: fit-content; } .el-form-item__label { height: 22px; font-size: 15px; // font-family: PingFang SC, PingFang SC; font-weight: 400; color: #3de8ff; line-height: unset; // -webkit-background-clip: text; // -webkit-text-fill-color: transparent; } .el-form-item__content { margin: 0 !important; flex: 1; height: 100%; .el-input__prefix { color: #fff; } } } } } </style>