UNPKG

@form-create/core

Version:

FormCreate低代码表单渲染引擎,可以通过 JSON 生成具有动态渲染、数据收集、验证和提交功能的低代码表单。支持6个UI框架,适配移动端,并且支持生成任何 Vue 组件。

446 lines (417 loc) 16.6 kB
import extend from '@form-create/utils/lib/extend'; import mergeProps from '@form-create/utils/lib/mergeprops'; import is, {hasProperty} from '@form-create/utils/lib/type'; import Vue from 'vue'; import {tip} from '@form-create/utils/lib/console'; import {invoke, mergeRule} from '../frame/util'; import toCase, {lower} from '@form-create/utils/lib/tocase'; import {$set, deepSet, toLine} from '@form-create/utils'; export default function useRender(Render) { extend(Render.prototype, { initRender() { this.tempList = {}; this.clearOrgChildren(); }, initOrgChildren() { const ctxs = this.$handle.ctxs; this.orgChildren = Object.keys(ctxs).reduce((initial, id) => { if (ctxs[id].parser.loadChildren !== false) { const children = ctxs[id].rule.children; initial[id] = is.trueArray(children) ? [...children] : []; } return initial; }, {}); }, clearOrgChildren() { this.orgChildren = {}; }, getTypeSlot(ctx) { const _fn = (vm) => { if (vm) { let slot = undefined; if (ctx.rule.field) { slot = vm.$scopedSlots['field-' + toLine(ctx.rule.field)] || vm.$scopedSlots['field-' + ctx.rule.field]; } if (!slot) { slot = vm.$scopedSlots['type-' + toLine(ctx.type)] || vm.$scopedSlots['type-' + ctx.type]; } if (slot) { return slot; } return _fn(vm.$pfc); } } return _fn(this.vm); }, render() { if (!this.vm.isShow) { return; } this.$h = this.vm.$createElement; this.$manager.beforeRender(); let vn; const make = () => this.renderList(); make.renderSlot = slot => this.renderList(slot); make.renderName = name => this.renderId(name); make.renderField = field => this.renderId(field, 'field'); if (this.vm.$scopedSlots.container) { vn = [this.vm.$scopedSlots.container(make)]; } else { vn = make(); } return this.$manager.render(vn); }, renderList(slot) { return this.sort.map((id) => { return slot ? this.renderSlot(this.$handle.ctxs[id], slot) : this.renderCtx(this.$handle.ctxs[id]); }).filter((val) => val !== undefined); }, makeVm(rule) { const vm = rule.vm; if (!vm) return new Vue; else if (is.Function(vm)) return invoke(() => rule.vm(this.$handle.getInjectData(rule))); else if (!vm._isVue) return new Vue(vm); return vm; }, mergeGlobal(ctx) { const g = this.$handle.options.global; if (!g) return; //todo 缓存配置,更新 option 更新 if (!ctx.cacheConfig) ctx.cacheConfig = g[ctx.originType] || g[ctx.type] || g[ctx.trueType] || {}; ctx.prop = mergeRule({}, [g['*'], ctx.cacheConfig, ctx.prop]); }, setOptions(ctx) { if (ctx.prop.optionsTo && ctx.prop.options) { deepSet(ctx.prop, ctx.prop.optionsTo, ctx.prop.options); } }, deepSet(ctx) { const deep = ctx.rule.deep; deep && Object.keys(deep).sort((a, b) => a.length < b.length ? -1 : 1).forEach(str => { deepSet(ctx.prop, str, deep[str]); }); }, setTempProps(vm, ctx) { if (!vm.$props) return; const {prop} = ctx; const keys = Object.keys(vm.$props); const inject = this.injectProp(ctx); const injectKeys = Object.keys(inject); keys.forEach(key => { if (hasProperty(prop.props, key)) vm.$props[key] = prop.props[key]; else if (injectKeys.indexOf(key) > -1) vm.$props[key] = inject[key]; }); const key = (vm.$options.model && vm.$options.model.prop) || 'value'; if (keys.indexOf(key) > -1) { vm.$props[key] = prop.value; } }, renderTemp(ctx) { if (!Vue.compile) { tip('当前使用的Vue构建版本不支持compile,无法使用template功能'); return []; } const rule = ctx.prop; const {id, key} = ctx; if (!this.tempList[id]) { if (!ctx.el) { ctx.el = this.makeVm(rule); this.vm.$nextTick(() => ctx.parser.mounted(ctx)); } let vm = ctx.el; if (ctx.input) vm.$on((vm.$options.model && vm.$options.model.event) || 'input', (value) => { this.onInput(ctx, value); }); this.tempList[id] = { vm, template: Vue.compile(rule.template) }; } const {vm, template} = this.tempList[id]; this.setTempProps(vm, ctx); const vn = template.render.call(vm); if (is.Undef(vn.data)) vn.data = {}; vn.key = key; vn.data.ref = ctx.ref; vn.data.key = key; return vn; }, parseSide(side, ctx) { return is.Object(side) ? mergeRule({props: {formCreateInject: ctx.prop.props.formCreateInject}}, side) : side; }, renderSides(vn, ctx, temp) { const prop = ctx[temp ? 'rule' : 'prop']; return [this.renderRule(this.parseSide(prop.prefix, ctx)), vn, this.renderRule(this.parseSide(prop.suffix, ctx))]; }, renderSlot(ctx, slot) { return ctx.rule.slot === slot ? this.renderCtx(ctx) : undefined; }, renderId(name, type) { const ctxs = this.$handle[type === 'field' ? 'fieldCtx' : 'nameCtx'][name] return ctxs ? ctxs.map(ctx => this.renderCtx(ctx, ctx.parent)) : undefined; }, renderCtx(ctx, parent) { if (ctx.type === 'hidden') return; const rule = ctx.rule; if ((!this.cache[ctx.id]) || this.cache[ctx.id].slot !== rule.slot) { let vn; let cacheFlag = rule.cache !== false; const _type = ctx.trueType; const none = !(is.Undef(rule.display) || !!rule.display); if (_type === 'template' && !rule.template) { vn = this.renderSides(this.renderChildren(ctx), ctx, true); if (none) { this.display(vn); } vn = this.item(ctx, vn); } else if (_type === 'fcFragment') { vn = this.renderChildren(ctx); } else { ctx.initProp(); this.mergeGlobal(ctx); this.$manager.tidyRule(ctx); this.deepSet(ctx); this.setOptions(ctx); this.ctxProp(ctx); let prop = ctx.prop; prop.preview = !!(hasProperty(prop, 'preview') ? prop.preview : (this.options.preview || false)) prop.props.formCreateInject = this.injectProp(ctx); const preview = prop.preview; if (prop.hidden) { this.setCache(ctx, undefined, parent); return; } if (_type === 'template' && prop.template) { vn = this.renderTemp(ctx); cacheFlag = false; } else { let children = []; if (ctx.parser.renderChildren) { children = ctx.parser.renderChildren(ctx); } else if (ctx.parser.loadChildren !== false) { children = this.renderChildren(ctx); } const slot = this.getTypeSlot(ctx); if (slot) { vn = slot({ rule, prop, preview, children, api: this.$handle.api, model: prop.model || {} }); } else { vn = preview ? ctx.parser.preview(children, ctx) : ctx.parser.render(children, ctx); } } vn = this.renderSides(vn, ctx); if ((!(!ctx.input && is.Undef(prop.native))) && prop.native !== true) { vn = this.$manager.makeWrap(ctx, vn); } if (none) { vn = this.display(vn); } vn = this.item(ctx, vn) } if (cacheFlag) { this.setCache(ctx, vn, parent); } return vn; } return this.getCache(ctx); }, display(vn) { if (Array.isArray(vn)) { const data = []; vn.forEach(v => { if (Array.isArray(v)) return this.display(v); if (this.none(v)) data.push(v); }) return data; } else { return this.none(vn); } }, none(vn) { if (vn && vn.data) { if (Array.isArray(vn.data.style)) { vn.data.style.push({display: 'none'}); } else if(is.String(vn.data.style)) { vn.data.style += ';display:none;'; } else { vn.data.style = [vn.data.style, {display: 'none'}]; } return vn; } }, item(ctx, vn) { return this.$h('fcFragment', { slot: ctx.rule.slot, key: ctx.key, }, [vn]); }, injectProp(ctx) { if (!this.vm.ctxInject[ctx.id]) { $set(this.vm.ctxInject, ctx.id, { api: this.$handle.api, form: this.fc.create, subForm: subForm => { this.$handle.addSubForm(ctx, subForm); }, getSubForm: () => { return this.$handle.subForm[ctx.id]; }, options: [], children: [], prop: {}, preview: false, id: ctx.id, field: ctx.field, rule: ctx.rule, input: ctx.input, }); } const inject = this.vm.ctxInject[ctx.id]; extend(inject, { preview: ctx.prop.preview, options: ctx.prop.options, children: ctx.rule.children, prop: (function () { const temp = {...ctx.prop}; temp.on = temp.on ? {...temp.on} : {}; delete temp.model; return temp; }()), }); return inject; }, ctxProp(ctx) { const {ref, key, rule} = ctx; this.$manager.mergeProp(ctx); ctx.parser.mergeProp(ctx); const props = [ { ref: ref, key: rule.key || `${key}fc`, slot: undefined, on: { 'hook:mounted': () => { this.onMounted(ctx); }, 'fc.sub-form': (subForm) => { this.$handle.addSubForm(ctx, subForm); }, 'fc.el': (el) => { ctx.exportEl = el; if (el) { (el.$el || el).__rule__ = ctx.rule; } } }, } ] if (ctx.input) { if (this.vm.$props.disabled === true) { ctx.prop.props.disabled = true; } ctx.prop.model = { value: this.$handle.getFormData(ctx), callback: (value) => { this.onInput(ctx, value); }, expression: `formData.${ctx.id}` }; } mergeProps(props, ctx.prop); return ctx.prop; }, onMounted(ctx) { ctx.el = this.vm.$refs[ctx.ref]; if (ctx.el) { (ctx.el.$el || ctx.el).__rule__ = ctx.rule; } ctx.parser.mounted(ctx); this.$handle.effect(ctx, 'mounted'); }, onInput(ctx, value) { this.$handle.onInput(ctx, value); }, renderChildren(ctx) { const {children} = ctx.rule, orgChildren = this.orgChildren[ctx.id]; const isRm = child => { return !is.String(child) && child.__fc__ && !this.$handle.ctxs[child.__fc__.id]; } if (!is.trueArray(children) && orgChildren) { this.$handle.deferSyncValue(() => { orgChildren.forEach(child => { if (!child) return; if (isRm(child)) { this.$handle.rmCtx(child.__fc__); } }); }); this.orgChildren[ctx.id] = []; return []; } orgChildren && this.$handle.deferSyncValue(() => { orgChildren.forEach(child => { if (!child) return; if (children.indexOf(child) === -1 && isRm(child)) { this.$handle.rmCtx(child.__fc__); } }); }); return children.map(child => { if (!child) return; if (is.String(child)) return child; if (child.__fc__) { return this.renderCtx(child.__fc__, ctx); } if (child.type) { this.vm.$nextTick(() => { this.$handle.loadChildren(children, ctx); this.$handle.refresh(); }); } }); }, defaultRender(ctx, children) { const prop = ctx.prop; if(prop.component) return this.vNode.makeComponent(prop.component, prop, children) if (this.vNode[ctx.type]) return this.vNode[ctx.type](prop, children); if (this.vNode[ctx.originType]) return this.vNode[ctx.originType](prop, children); return this.vNode.make(lower(ctx.originType), prop, children); }, renderRule(rule, children, origin) { if (!rule) return undefined; if (is.String(rule)) return rule; let type; if (origin) { type = rule.type; } else { type = rule.is; if (rule.type) { type = toCase(rule.type); const alias = this.vNode.aliasMap[type]; if (alias) type = toCase(alias); } } if (!type) return undefined; let data = [[children]]; if (is.trueArray(rule.children)) { data.push(rule.children.map(v => this.renderRule(v))); } return this.$h(type, {...rule}, data); } }) }