UNPKG

@form-create/core

Version:

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

441 lines (398 loc) 13.1 kB
import $FormCreate from '../components/formCreate'; import Vue from 'vue'; import makerFactory from '../factory/maker'; import Handle from '../handler'; import fetch from './fetch'; import {creatorFactory} from '..'; import BaseParser from '../factory/parser'; import {copyRule, copyRules, mergeGlobal, parseJson, toJson, parseFn} from './util'; import fragment from '../components/fragment'; import is, {hasProperty} from '@form-create/utils/lib/type'; import toCase from '@form-create/utils/lib/tocase'; import extend from '@form-create/utils/lib/extend'; import {CreateNodeFactory} from '../factory/node'; import {createManager} from '../factory/manager'; import {arrayAttrs, keyAttrs, normalAttrs} from './attrs'; import {appendProto} from '../factory/creator'; import $provider from './provider'; import {deepCopy} from '@form-create/utils/lib/deepextend'; import html from '../parser/html'; import uniqueId from '@form-create/utils/lib/unique'; function _parseProp(name, id) { let prop; if (arguments.length === 2) { prop = arguments[1]; id = prop[name]; } else { prop = arguments[2]; } return {id, prop}; } function nameProp() { return _parseProp('name', ...arguments); } function _getEl(options) { if (!options || !options.el) return window.document.body; return is.Element(options.el) ? options.el : document.querySelector(options.el); } function mountForm(rules, option) { const $vm = new Vue({ data() { //todo 外部无法修改 return {rule: rules, option: option || {}}; }, render(h) { return h('FormCreate', {ref: 'fc', props: this.$data}); } }); $vm.$mount(); return $vm; } function exportAttrs(attrs) { const key = attrs.key || []; const array = attrs.array || []; const normal = attrs.normal || []; keyAttrs.push(...key); arrayAttrs.push(...array); normalAttrs.push(...normal); appendProto([...key, ...array, ...normal]); } let id = 1; const instance = {}; //todo 表单嵌套 export default function FormCreateFactory(config) { const components = { [fragment.name]: fragment }; const parsers = {}; const directives = {}; const providers = { ...$provider }; const maker = makerFactory(); let globalConfig = {global: {}}; const loadData = {}; const CreateNode = CreateNodeFactory(); const prototype = {}; exportAttrs(config.attrs || {}); function getApi(name) { const val = instance[name]; if (Array.isArray(val)) { return val.map(v => { return v.api(); }); } else if (val) { return val.api(); } } function directive() { const data = nameProp(...arguments); if (data.id && data.prop) directives[data.id] = data.prop; } function register() { const data = nameProp(...arguments); if (data.id && data.prop) providers[data.id] = {...data.prop, name: data.id}; } function componentAlias(alias) { CreateNode.use(alias); } function parser() { const data = nameProp(...arguments); if (!data.id || !data.prop) return BaseParser; const name = toCase(data.id); const parser = data.prop; const base = parser.merge === true ? parsers[name] : undefined; parsers[name] = parser; Object.setPrototypeOf(parser, base || BaseParser); maker[name] = creatorFactory(name); parser.maker && extend(maker, parser.maker); } function component(id, component) { let name; if (is.String(id)) { name = toCase(id); if (['form-create', 'formcreate'].indexOf(name) > -1) { return $form(); } else if (component === undefined) { return components[name]; } } else { name = toCase(id.name); component = id; } if (!name || !component) return; const nameAlias = toCase(name); components[name] = component; components[nameAlias] = component; delete CreateNode.aliasMap[name]; delete CreateNode.aliasMap[nameAlias]; delete parsers[name]; delete parsers[nameAlias]; if (component.formCreateParser) parser(name, component.formCreateParser); } function _emitData(id) { Object.keys(instance).forEach(v => { const apis = Array.isArray(instance[v]) ? instance[v] : [instance[v]]; apis.forEach(that => { that.bus.$emit('p.loadData.' + id); }) }) } function setData(id, data) { loadData[id] = data; _emitData(id); } function getData(id, def) { return hasProperty(loadData, id) ? loadData[id] : def; } function removeData(id) { delete loadData[id]; _emitData(id); } function $form() { return $FormCreate(FormCreate, components, directives); } function $vnode() { return fragment; } //todo 检查回调函数作用域 function use(fn, opt) { if (is.Function(fn.install)) fn.install(create, opt); else if (is.Function(fn)) fn(create, opt); return this; } function create(rules, _opt, parent) { let $vm = mountForm(rules, _opt || {}); const _this = $vm.$refs.fc.formCreate; _this.$parent = parent; _getEl(_this.options).appendChild($vm.$el); return _this.api(); } Object.setPrototypeOf(create, prototype); function factory(inherit) { let _config = {...config}; if (inherit) { _config.inherit = { components, parsers, directives, providers, maker, loadData } } else { delete _config.inherit; } return FormCreateFactory(_config); } function FormCreate(vm) { const rules = vm.$options.propsData.rule; extend(this, { id: id++, vm, create, manager: createManager(config.manager), parsers, providers, rules: Array.isArray(rules) ? rules : [], name: vm.$options.propsData.name || uniqueId(), inFor: vm.$options.propsData.inFor, prop: { components, directives, }, setData, getData, loadData, CreateNode, bus: new Vue, unwatch: null, options: {}, extendApi: config.extendApi || (api => api) }) this.init(); this.initOptions(); if (this.name) { if (this.inFor) { if (!instance[this.name]) instance[this.name] = []; instance[this.name].push(this); } else { instance[this.name] = this; } } } extend(FormCreate.prototype, { init() { const vm = this.vm; const h = new Handle(this); this.$handle = h; vm.$f = h.api; vm.$emit('input', h.api); vm.$on('hook:created', () => { if (this.isSub()) { this.unwatch = vm.$watch(() => vm.$pfc.option, () => { this.initOptions(); vm.$f.refresh(); }, {deep: true}); this.initOptions(); } this.created(); }) vm.$on('hook:mounted', () => { this.mounted(); }); vm.$on('hook:beforeDestroy', () => { vm.destroyed = true; this.unwatch && this.unwatch(); h.reloadRule([]); if (this.name) { if (this.inFor) { const idx = instance[this.name].indexOf(this); instance[this.name].splice(idx, 1); if (!instance[this.name].length) { delete instance[this.name]; } } else { delete instance[this.name]; } } }); vm.$on('hook:updated', () => { h.bindNextTick(() => this.bus.$emit('next-tick', h.api)); }); }, isSub() { return this.vm.$pfc && this.vm.extendOption; }, initOptions() { this.options = {}; let options = {formData: {}, submitBtn: {}, resetBtn: {}, ...deepCopy(globalConfig)}; if (this.isSub()) { this.mergeOptions(this.options, this.vm.$pfc.$f.config || {}, true); } options = this.mergeOptions(options, this.vm.$options.propsData.option); this.updateOptions(options); }, mergeOptions(target, opt, parent) { opt = deepCopy(opt); parent && ['page', 'onSubmit', 'onReset', 'onCreated', 'onChange', 'onMounted', 'mounted', 'onReload', 'reload', 'formData', 'el'].forEach((n) => { delete opt[n]; }); if (opt.global) { target.global = mergeGlobal(target.global, opt.global); delete opt.global; } this.$handle.$manager.mergeOptions([opt], target); return target; }, updateOptions(options) { this.mergeOptions(this.options, options); this.$handle.$manager.updateOptions(this.options); }, created() { this.$handle.init(); this.vm.$emit('created', this.api()); }, api() { return this.$handle.api; }, render() { return this.$handle.render(); }, mounted() { this.$handle.mounted(); }, }) function useAttr(formCreate) { extend(formCreate, { version: config.version, ui: config.ui, setData, getData, removeData, maker, component, directive, register, $vnode, parser, use, factory, componentAlias, copyRule, copyRules, fetch, $form, parseFn, parseJson, toJson, getApi, init(rules, _opt = {}) { let $vm = mountForm(rules, _opt), _this = $vm.$refs.fc.formCreate; return { mount($el) { if ($el && is.Element($el)) _this.options.el = $el; _getEl(_this.options).appendChild($vm.$el); return _this.api(); }, remove() { $vm.$el.parentNode && $vm.$el.parentNode.removeChild($vm.$el); }, destroy() { this.remove(); $vm.$destroy(); }, $f: _this.api() }; } }); } function useStatic(formCreate) { extend(formCreate, { create, install(Vue, options) { globalConfig = {...globalConfig, ...(options || {})} if (Vue._installedFormCreate === true) return; Vue._installedFormCreate = true; const $formCreate = function (rules, opt = {}) { return create(rules, opt, this); }; useAttr($formCreate); Vue.prototype.$formCreate = $formCreate; Vue.component('FormCreate', $form()); Vue.component('FcFragment', $vnode()); } }) } useAttr(prototype); useStatic(prototype); CreateNode.use({fragment: 'fcFragment'}); parser(html); if (config.install) create.use(config); if (config.inherit) { const inherit = config.inherit; inherit.components && extend(components, inherit.components); inherit.parsers && extend(parsers, inherit.parsers); inherit.directives && extend(directives, inherit.directives); inherit.providers && extend(providers, inherit.providers); inherit.maker && extend(maker, inherit.maker); inherit.loadData && extend(loadData, inherit.loadData); } const FcComponent = $form(); Object.setPrototypeOf(FcComponent, prototype); Object.defineProperties(FcComponent, { fetch: { get() { return prototype.fetch; }, set(val) { prototype.fetch = val; } } }) FcComponent.util = prototype; return FcComponent; }