├── .eslintignore
├── src
├── drag
│ └── index.js
├── schema
│ ├── base
│ │ ├── button
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── cascader
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── checkbox
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── grid
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── input
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── line
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── radio
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── rate
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── select
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── slider
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── switch
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── table
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── text
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── textarea
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── upload
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── autoComplete
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── datePicker
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── formButton
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── inputNumber
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ ├── timePicker
│ │ │ ├── index.js
│ │ │ └── Schema.js
│ │ └── index.js
│ ├── index.js
│ ├── RootSchema.js
│ ├── FormSchema.js
│ └── BaseSchema.js
├── render
│ ├── index.js
│ ├── VueRender.js
│ └── ReactRender.js
├── style
│ ├── index.js
│ └── Background.js
├── rule
│ ├── index.js
│ ├── rules
│ │ ├── index.js
│ │ ├── URLRule.js
│ │ ├── EmailRule.js
│ │ ├── StringRule.js
│ │ ├── JSONRule.js
│ │ ├── NumberRule.js
│ │ └── PatternRule.js
│ └── Rule.js
├── constant
│ ├── static.js
│ ├── default-schema.js
│ └── index.js
├── logic
│ ├── Properties.js
│ ├── index.js
│ ├── Effect.js
│ ├── EventLogic.js
│ ├── Logic.js
│ └── ValueLogic.js
├── hook
│ ├── index.js
│ ├── Hook.js
│ ├── SyncHook.js
│ └── SyncWaterfallHook.js
├── store
│ ├── Dict.js
│ ├── API.js
│ ├── mutations
│ │ ├── index.js
│ │ ├── style.js
│ │ ├── logic.js
│ │ ├── model.js
│ │ ├── rule.js
│ │ ├── schema.js
│ │ ├── store.js
│ │ └── widget.js
│ ├── TypeBuilder.js
│ ├── types.js
│ ├── StoreConf.js
│ ├── Fetch.js
│ └── index.js
├── script
│ └── index.js
├── context
│ └── index.js
├── main.js
├── event
│ └── index.js
├── helper
│ ├── index.js
│ ├── util.js
│ └── epUtil.js
└── worker
│ └── index.js
├── public
└── imgs
│ └── epage-qrcode.png
├── .babelrc
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── index.html
├── .github
└── ISSUE_TEMPLATE
│ └── bug_report.md
├── script
├── webpack.base.js
├── webpack.dev.js
├── color.js
├── verify-commit-msg.js
└── webpack.build.js
├── CONTRIBUTING.md
├── README.md
├── LICENSE
├── package.json
└── CHANGELOG.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/*.js
2 | .vscode/
3 | node_modules/
4 |
--------------------------------------------------------------------------------
/src/drag/index.js:
--------------------------------------------------------------------------------
1 | import VueDrag from 'vuedraggable'
2 |
3 | export {
4 | VueDrag
5 | }
6 |
--------------------------------------------------------------------------------
/src/schema/base/button/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/cascader/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/checkbox/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/grid/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/input/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/line/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/radio/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/rate/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/select/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/slider/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/switch/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/table/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/text/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/textarea/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/upload/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/render/index.js:
--------------------------------------------------------------------------------
1 | import VueRender from './VueRender'
2 |
3 | export {
4 | VueRender
5 | }
6 |
--------------------------------------------------------------------------------
/src/schema/base/autoComplete/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/datePicker/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/formButton/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/inputNumber/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/schema/base/timePicker/index.js:
--------------------------------------------------------------------------------
1 | import Schema from './Schema'
2 |
3 | export default Schema
4 |
--------------------------------------------------------------------------------
/src/style/index.js:
--------------------------------------------------------------------------------
1 | import Background from './Background'
2 |
3 | export {
4 | Background
5 | }
6 |
--------------------------------------------------------------------------------
/public/imgs/epage-qrcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epage-team/epage-core/HEAD/public/imgs/epage-qrcode.png
--------------------------------------------------------------------------------
/src/rule/index.js:
--------------------------------------------------------------------------------
1 | import Rule from './Rule'
2 | import * as rules from './rules'
3 |
4 | Rule.set(rules)
5 |
6 | export default Rule
7 |
--------------------------------------------------------------------------------
/src/constant/static.js:
--------------------------------------------------------------------------------
1 | // 表单可展示的模式
2 | export const modes = () => ['edit', 'display']
3 | export const defaultProps = () => ['hidden', 'disabled']
4 |
--------------------------------------------------------------------------------
/src/constant/default-schema.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 生成默认schema对象
3 | */
4 | export default () => ({
5 | key: '',
6 | type: 'object',
7 | widget: ''
8 | })
9 |
--------------------------------------------------------------------------------
/src/logic/Properties.js:
--------------------------------------------------------------------------------
1 | export default class Properties {
2 | constructor (key, value) {
3 | this.key = key
4 | this.value = value
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/hook/index.js:
--------------------------------------------------------------------------------
1 | import SyncWaterfallHook from './SyncWaterfallHook'
2 | import SyncHook from './SyncHook'
3 |
4 | export {
5 | SyncHook,
6 | SyncWaterfallHook
7 | }
8 |
--------------------------------------------------------------------------------
/src/constant/index.js:
--------------------------------------------------------------------------------
1 | import { modes, defaultProps } from './static'
2 |
3 | export { default as defaultSchema } from './default-schema'
4 | export {
5 | modes,
6 | defaultProps
7 | }
8 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", { "modules": "umd" }]
4 | ],
5 | "ignore": [
6 | // "src/modules/worker/index.js"
7 | ],
8 | "comments": false
9 | }
10 |
--------------------------------------------------------------------------------
/src/store/Dict.js:
--------------------------------------------------------------------------------
1 | import Fetch from './Fetch'
2 | export default class Dict extends Fetch {
3 | /* eslint no-useless-constructor: 0 */
4 | constructor (props) {
5 | super(props)
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/src/store/API.js:
--------------------------------------------------------------------------------
1 | import Fetch from './Fetch'
2 |
3 | export default class API extends Fetch {
4 | /* eslint no-useless-constructor: 0 */
5 | constructor (props) {
6 | super(props)
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["standard", "plugin:vue/recommended"],
3 | // add your custom rules here
4 | 'rules': {
5 | "vue/no-parsing-error": [0, {
6 | "x-invalid-end-tag": false
7 | }]
8 | }
9 | }
--------------------------------------------------------------------------------
/src/hook/Hook.js:
--------------------------------------------------------------------------------
1 | // Hook基础类
2 | export default class Hook {
3 | constructor () {
4 | this.tasks = []
5 | }
6 |
7 | call () {}
8 | tap (cb) {
9 | if (typeof cb !== 'function') return
10 | this.tasks.push(cb)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/schema/index.js:
--------------------------------------------------------------------------------
1 | import BaseSchema from './BaseSchema'
2 | import FormSchema from './FormSchema'
3 | import RootSchema from './RootSchema'
4 | import base from './base'
5 |
6 | export {
7 | BaseSchema,
8 | FormSchema,
9 | RootSchema,
10 | base
11 | }
12 |
--------------------------------------------------------------------------------
/src/hook/SyncHook.js:
--------------------------------------------------------------------------------
1 | import Hook from './Hook'
2 | // 串行同步
3 | export default class SyncHook extends Hook {
4 | constructor () {
5 | super()
6 | this.tasks = []
7 | }
8 |
9 | call (...args) {
10 | this.tasks.forEach(hook => hook(...args))
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *node_modules/
3 | */**/node_modules/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | test/unit/coverage
8 | test/e2e/reports
9 | selenium-debug.log
10 | output/
11 | web-ui.tar.gz
12 | .vscode
13 | *.iml
14 | .idea/
15 | package-lock.json
16 | yarn.lock
17 |
--------------------------------------------------------------------------------
/src/logic/index.js:
--------------------------------------------------------------------------------
1 | import Effect from './Effect'
2 | import EventLogic from './EventLogic'
3 | import ValueLogic from './ValueLogic'
4 | import Logic from './Logic'
5 |
6 | Logic.Effect = Effect
7 | Logic.EventLogic = EventLogic
8 | Logic.ValueLogic = ValueLogic
9 |
10 | export default Logic
11 |
--------------------------------------------------------------------------------
/src/rule/rules/index.js:
--------------------------------------------------------------------------------
1 | export { default as StringRule } from './StringRule'
2 | export { default as NumberRule } from './NumberRule'
3 | export { default as EmailRule } from './EmailRule'
4 | export { default as URLRule } from './URLRule'
5 | export { default as PatternRule } from './PatternRule'
6 | export { default as JSONRule } from './JSONRule'
7 |
--------------------------------------------------------------------------------
/src/script/index.js:
--------------------------------------------------------------------------------
1 |
2 | export default class Script {
3 | constructor (ctx) {
4 | this.ctx = ctx
5 | }
6 |
7 | exec (script) {
8 | try {
9 | /* eslint-disable no-new-func */
10 | const fun = new Function('ctx', script)
11 | return fun(this.ctx)
12 | } catch (e) {
13 | console.log(e)
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | epage core test
7 |
8 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/logic/Effect.js:
--------------------------------------------------------------------------------
1 | import Properties from './Properties'
2 |
3 | export default class Effect {
4 | constructor () {
5 | // schema key for effected widgets
6 | this.key = ''
7 | // properties for effected effected widgets
8 | this.properties = [
9 | new Properties('hidden', false),
10 | new Properties('disabled', false)
11 | ]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/store/mutations/index.js:
--------------------------------------------------------------------------------
1 | import model from './model'
2 | import schema from './schema'
3 | import widget from './widget'
4 | import logic from './logic'
5 | import rule from './rule'
6 | import store from './store'
7 | import style from './style'
8 |
9 | export default {
10 | model,
11 | schema,
12 | widget,
13 | logic,
14 | rule,
15 | store,
16 | style
17 | }
18 |
--------------------------------------------------------------------------------
/src/hook/SyncWaterfallHook.js:
--------------------------------------------------------------------------------
1 | import Hook from './Hook'
2 | // 串行同步
3 | export default class SyncWaterfallHook extends Hook {
4 | constructor () {
5 | super()
6 | this.tasks = []
7 | }
8 |
9 | call (parm) {
10 | const [first, ...others] = this.tasks
11 | if (typeof first !== 'function') return parm
12 | return others.reduce((ret, task) => task(ret), first(parm))
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/schema/base/text/Schema.js:
--------------------------------------------------------------------------------
1 | import BaseSchema from '../../BaseSchema'
2 |
3 | export default class TextSchema extends BaseSchema {
4 | constructor (props) {
5 | super()
6 | this.option = {
7 | content: ''
8 | }
9 | this.create(props)
10 | }
11 | }
12 |
13 | // 静态配置,同类widget公有
14 | Object.assign(TextSchema, {
15 | title: '文本',
16 | widget: 'text',
17 | preview: ''
18 | })
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 | 1. Go to '...'
15 | 2. Click on '....'
16 |
17 | **Expected behavior**
18 |
19 | **Device (please complete the following information):**
20 | - OS: [e.g. iOS8.1]
21 | - Browser [e.g. stock browser, safari]
22 | - Version [e.g. 22]
23 |
--------------------------------------------------------------------------------
/script/webpack.base.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const scirptPath = [
4 | path.resolve(__dirname, '../src')
5 | ]
6 |
7 | module.exports = {
8 | module: {
9 | rules: [
10 | {
11 | test: /\.js$/,
12 | loader: 'babel-loader',
13 | include: scirptPath
14 | }
15 | ]
16 | },
17 | resolve: {
18 | extensions: ['.js', '.vue'],
19 | alias: {
20 | '@': path.resolve(__dirname, '../src')
21 | }
22 | },
23 | stats: { children: false }
24 | }
25 |
--------------------------------------------------------------------------------
/src/store/mutations/style.js:
--------------------------------------------------------------------------------
1 | import types from '../types'
2 |
3 | export default {
4 | // 更新style
5 | [types.$STYLE_UPDATE] ({ rootSchema, flatSchemas }, { key, style }) {
6 | let schema = null
7 | if (key) { // update widget
8 | if (key in flatSchemas) schema = flatSchemas[key]
9 | } else { // update root
10 | schema = rootSchema
11 | }
12 | if (!schema) return
13 | const originStyle = { ...schema.style }
14 | schema.style = Object.assign(originStyle, style)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/schema/base/slider/Schema.js:
--------------------------------------------------------------------------------
1 | import FormSchema from '../../FormSchema'
2 |
3 | export default class SliderSchema extends FormSchema {
4 | constructor (props) {
5 | super()
6 | this.label = '滑块'
7 | this.default = 0
8 | this.create(props)
9 | }
10 | }
11 |
12 | // 静态配置,同类widget公有
13 | Object.assign(SliderSchema, {
14 | title: '滑块',
15 | widget: 'slider',
16 | preview: '',
17 | type: 'number',
18 | validators: 'number',
19 | logic: {
20 | value: ['=', '!='],
21 | // event: ['change']
22 | event: ['change']
23 | }
24 | })
25 |
--------------------------------------------------------------------------------
/src/store/mutations/logic.js:
--------------------------------------------------------------------------------
1 | import types from '../types'
2 | import {
3 | isArray
4 | } from '../../helper'
5 |
6 | export default {
7 | // 逻辑模式添加一个逻辑规则
8 | [types.$LOGIC_ADD] ({ rootSchema }, { logic }) {
9 | if (isArray(rootSchema.logics)) {
10 | rootSchema.logics.push(logic)
11 | } else {
12 | rootSchema.logics = [logic]
13 | }
14 | },
15 |
16 | // 逻辑模式更新一个逻辑规则
17 | [types.$LOGIC_UPDATE] ({ rootSchema }, { logic, index }) {
18 | rootSchema.logics.splice(index, 1, logic)
19 | },
20 |
21 | // 逻辑模式删除一个逻辑规则
22 | [types.$LOGIC_DELETE] ({ rootSchema }, { index }) {
23 | rootSchema.logics.splice(index, 1)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/rule/rules/URLRule.js:
--------------------------------------------------------------------------------
1 |
2 | export default class URLRule {
3 | static get type () {
4 | return 'url'
5 | }
6 |
7 | static get name () {
8 | return '网址'
9 | }
10 |
11 | constructor (rule = {}) {
12 | const defaultRule = {
13 | type: 'url',
14 | message: '非法URL'
15 | }
16 | this.origin = Object.assign({}, defaultRule, rule)
17 | this.rule = {
18 | type: 'url',
19 | trigger: 'blur',
20 | message: ''
21 | }
22 | this.update(this.origin)
23 | }
24 |
25 | update (rule) {
26 | if (rule) {
27 | this.rule.message = rule.message
28 | Object.assign(this.origin, rule)
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/rule/rules/EmailRule.js:
--------------------------------------------------------------------------------
1 |
2 | export default class EmailRule {
3 | static get type () {
4 | return 'email'
5 | }
6 |
7 | static get name () {
8 | return '邮箱'
9 | }
10 |
11 | constructor (rule = {}) {
12 | const defaultRule = {
13 | type: 'email',
14 | message: '邮箱格式不正确'
15 | }
16 | this.origin = Object.assign({}, defaultRule, rule)
17 | this.rule = {
18 | type: 'email',
19 | trigger: 'blur',
20 | message: ''
21 | }
22 | this.update(this.origin)
23 | }
24 |
25 | update (rule) {
26 | if (rule) {
27 | this.rule.message = rule.message
28 | Object.assign(this.origin, rule)
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/logic/EventLogic.js:
--------------------------------------------------------------------------------
1 | import Effect from './Effect'
2 |
3 | export default class EventLogic {
4 | constructor () {
5 | this.type = 'event'
6 | this.title = '事件逻辑'
7 | this.map = {
8 | click: { key: 'click', value: '点击' },
9 | focus: { key: 'focus', value: '聚焦' },
10 | blur: { key: 'blur', value: '失焦' },
11 | change: { key: 'change', value: '改变' }
12 | }
13 | }
14 |
15 | get () {
16 | return {
17 | type: 'event',
18 | // schema.key
19 | key: '',
20 | // event type: click | change or others
21 | action: '',
22 | trigger: 'prop', // prop | script
23 | script: '',
24 | effects: [new Effect()]
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/script/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const merge = require('webpack-merge')
3 | const HtmlWebpackPlugin = require('html-webpack-plugin')
4 | const webpackBaseConfig = require('./webpack.base.js')
5 |
6 | const webpackConfig = merge(webpackBaseConfig, {
7 | mode: 'development',
8 | entry: {
9 | vendor: './examples/index.js'
10 | },
11 | devtool: 'source-map',
12 | output: {
13 | filename: '[name].js'
14 | },
15 | plugins: [
16 | new webpack.HotModuleReplacementPlugin(),
17 | new HtmlWebpackPlugin({
18 | filename: 'index.html',
19 | template: './examples/index.html',
20 | inject: true
21 | })
22 | ]
23 | })
24 |
25 | module.exports = webpackConfig
26 |
--------------------------------------------------------------------------------
/src/schema/base/textarea/Schema.js:
--------------------------------------------------------------------------------
1 | import FormSchema from '../../FormSchema'
2 |
3 | export default class TextareaSchema extends FormSchema {
4 | constructor (props) {
5 | super()
6 | this.label = '多行文本'
7 | this.placeholder = '请输入'
8 | this.default = ''
9 | this.option = {
10 | rows: 3
11 | }
12 | this.create(props)
13 | }
14 | }
15 |
16 | // 静态配置,同类widget公有
17 | Object.assign(TextareaSchema, {
18 | title: '多行文本',
19 | widget: 'textarea',
20 | preview: '',
21 | type: 'string',
22 | validators: ['string', 'email', 'url', 'pattern'],
23 | logic: {
24 | value: ['=', '!=', '<>', '><'],
25 | // event: ['focus', 'blur', 'change']
26 | event: ['change']
27 | }
28 | })
29 |
--------------------------------------------------------------------------------
/src/schema/base/line/Schema.js:
--------------------------------------------------------------------------------
1 | import BaseSchema from '../../BaseSchema'
2 |
3 | export default class LineSchema extends BaseSchema {
4 | constructor (props) {
5 | super()
6 | this.option = {
7 | direction: 'horizontal', // vertical
8 | // hposition => top, middle, bottom
9 | hposition: 'middle',
10 | // vposition: left, center, right
11 | vposition: 'center',
12 | length: 100,
13 | thickness: 1,
14 | type: 'dashed', // solid | dashed | dotted
15 | color: '#aaa'
16 | }
17 | this.create(props)
18 | }
19 | }
20 |
21 | // 静态配置,同类widget公有
22 | Object.assign(LineSchema, {
23 | title: '线条',
24 | widget: 'line',
25 | preview: '',
26 | logic: {
27 | value: null,
28 | event: null
29 | }
30 | })
31 |
--------------------------------------------------------------------------------
/src/schema/base/input/Schema.js:
--------------------------------------------------------------------------------
1 | import FormSchema from '../../FormSchema'
2 |
3 | export default class InputSchema extends FormSchema {
4 | constructor (props) {
5 | super()
6 | this.label = '输入框'
7 | this.placeholder = '请输入'
8 | this.default = ''
9 | this.option = {
10 | password: false,
11 | prefix: '', // 前缀字符
12 | suffix: '' // 后缀字符
13 | }
14 | this.create(props)
15 | }
16 | }
17 |
18 | // 静态配置,同类widget公有
19 | Object.assign(InputSchema, {
20 | title: '单行文本',
21 | widget: 'input',
22 | preview: '',
23 | type: 'string',
24 | validators: ['string', 'email', 'url', 'pattern'],
25 | logic: {
26 | value: ['=', '!=', '<>', '><'],
27 | // event: ['focus', 'blur', 'change']
28 | event: ['change']
29 | }
30 | })
31 |
--------------------------------------------------------------------------------
/src/schema/base/switch/Schema.js:
--------------------------------------------------------------------------------
1 | import FormSchema from '../../FormSchema'
2 |
3 | export default class SwitchSchema extends FormSchema {
4 | constructor (props) {
5 | super()
6 | this.label = '开关'
7 | this.default = false
8 | this.rules = [{
9 | type: 'boolean',
10 | message: '必填',
11 | trigger: 'change',
12 | required: false
13 | }]
14 | this.option = {
15 | open: '是',
16 | close: '否'
17 | }
18 | this.create(props)
19 | }
20 | }
21 |
22 | // 静态配置,同类widget公有
23 | Object.assign(SwitchSchema, {
24 | title: '开关',
25 | widget: 'switch',
26 | preview: '',
27 | type: 'boolean',
28 | validators: [],
29 | logic: {
30 | value: ['=', '!='],
31 | // event: ['change']
32 | event: ['change']
33 | }
34 | })
35 |
--------------------------------------------------------------------------------
/src/schema/base/formButton/Schema.js:
--------------------------------------------------------------------------------
1 | import BaseSchema from '../../BaseSchema'
2 |
3 | export default class ButtonSchema extends BaseSchema {
4 | constructor (props) {
5 | super()
6 | this.option = {
7 | // 动作类型:submit、goback、reset
8 | actionType: 'submit',
9 | text: '提交',
10 | // 按钮类型:primary、ghost、dashed、text、default
11 | type: 'primary',
12 | // 是否通栏
13 | long: false,
14 | // 形状,可选 'square': 方角;'circle': 圆角
15 | shape: 'square',
16 | url: '',
17 | method: 'POST'
18 | }
19 | this.create(props)
20 | }
21 | }
22 |
23 | // 静态配置,同类widget公有
24 | Object.assign(ButtonSchema, {
25 | title: '表单按钮',
26 | widget: 'formButton',
27 | preview: '',
28 | logic: {
29 | value: null,
30 | event: []
31 | }
32 | })
33 |
--------------------------------------------------------------------------------
/src/schema/base/button/Schema.js:
--------------------------------------------------------------------------------
1 | import BaseSchema from '../../BaseSchema'
2 |
3 | export default class ButtonSchema extends BaseSchema {
4 | constructor (props) {
5 | super()
6 | this.disabled = false
7 | this.option = {
8 | text: '提交',
9 | // 按钮类型:primary、dashed、text、default
10 | type: 'primary',
11 | // 按钮图标
12 | icon: '',
13 | // 是否通栏
14 | long: false,
15 | // 是否幽灵按钮
16 | ghost: false,
17 | // 形状,可选 'square': 方角;'circle': 圆角
18 | shape: 'square',
19 | script: ''
20 | }
21 | this.create(props)
22 | }
23 | }
24 |
25 | // 静态配置,同类widget公有
26 | Object.assign(ButtonSchema, {
27 | title: '按钮',
28 | widget: 'button',
29 | preview: '',
30 | logic: {
31 | value: null,
32 | event: ['click']
33 | }
34 | })
35 |
--------------------------------------------------------------------------------
/src/context/index.js:
--------------------------------------------------------------------------------
1 | import { isPlainObject } from '../helper'
2 |
3 | export default class Context {
4 | constructor (props = {}) {
5 | this.store = props.store
6 | this.state = Object.assign({}, props.state)
7 | this.$el = props.$el
8 | this.$render = props.$render
9 | this.instance = props.instance
10 | }
11 |
12 | setState (state) {
13 | return new Promise((resolve, reject) => {
14 | if (isPlainObject(state)) {
15 | const newState = {}
16 | for (const i in state) {
17 | if (i in this.state) {
18 | newState[i] = state[i]
19 | }
20 | }
21 | Object.assign(this.instance, newState)
22 | resolve()
23 | } else {
24 | reject(new Error('state should be plain object!'))
25 | }
26 | })
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/schema/base/rate/Schema.js:
--------------------------------------------------------------------------------
1 | import FormSchema from '../../FormSchema'
2 |
3 | export default class RateSchema extends FormSchema {
4 | constructor (props) {
5 | super()
6 | this.label = '评分'
7 | this.default = 0
8 | this.rules = [{
9 | required: false,
10 | message: '必填',
11 | min: 0,
12 | type: 'number',
13 | trigger: 'change'
14 | }]
15 | this.option = {
16 | count: 5,
17 | allowHalf: false,
18 | showText: false
19 | }
20 | this.create(props)
21 | }
22 | }
23 |
24 | // 静态配置,同类widget公有
25 | Object.assign(RateSchema, {
26 | title: '评分',
27 | widget: 'rate',
28 | preview: '',
29 | type: 'number',
30 | validators: [],
31 | logic: {
32 | value: ['=', '!='],
33 | // event: ['change']
34 | event: ['change']
35 | }
36 | })
37 |
--------------------------------------------------------------------------------
/src/schema/base/datePicker/Schema.js:
--------------------------------------------------------------------------------
1 | import FormSchema from '../../FormSchema'
2 |
3 | export default class DatePickerSchema extends FormSchema {
4 | constructor (props) {
5 | super()
6 | this.label = '日期'
7 | this.placeholder = '请选择'
8 | this.default = ''
9 | this.rules = [{
10 | required: false,
11 | message: '必填',
12 | trigger: 'change'
13 | }]
14 | this.option = {
15 | range: false,
16 | format: 'yyyy-MM-dd'
17 | }
18 | this.create(props)
19 | }
20 | }
21 |
22 | // 静态配置,同类widget公有
23 | Object.assign(DatePickerSchema, {
24 | title: '日期选择',
25 | widget: 'datePicker',
26 | preview: '',
27 | type: ['string', 'array'],
28 | validators: [],
29 | logic: {
30 | value: ['=', '!='],
31 | // event: ['focus', 'blur', 'change']
32 | event: ['change']
33 | }
34 | })
35 |
--------------------------------------------------------------------------------
/src/schema/base/timePicker/Schema.js:
--------------------------------------------------------------------------------
1 | import FormSchema from '../../FormSchema'
2 |
3 | export default class TimePickerSchema extends FormSchema {
4 | constructor (props) {
5 | super()
6 | this.label = '时间'
7 | this.placeholder = '请选择'
8 | this.default = ''
9 | this.rules = [{
10 | required: false,
11 | message: '必填',
12 | trigger: 'change'
13 | }]
14 | this.option = {
15 | range: false,
16 | format: 'HH:mm:ss'
17 | }
18 | this.create(props)
19 | }
20 | }
21 |
22 | // 静态配置,同类widget公有
23 | Object.assign(TimePickerSchema, {
24 | title: '时间选择',
25 | widget: 'timePicker',
26 | preview: '',
27 | type: ['string', 'array'],
28 | validators: [],
29 | logic: {
30 | value: ['=', '!='],
31 | // event: ['focus', 'blur', 'change']
32 | event: ['change']
33 | }
34 | })
35 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # 如何贡献代码
2 |
3 | 欢迎一起共建 epage-core
4 |
5 | ## Branch 说明
6 |
7 | ```
8 | publish
9 | ↑
10 | dev --> master
11 | ↑
12 | PR
13 | ```
14 |
15 | 主要有两个分支:`dev` 及 `master`
16 |
17 | - `dev` 分支:开发及发版均在此分支,提PR也提交到这里
18 | - `master` 分支:稳定分支,在`dev`开发并发布后合并到`master`,然后打上tag
19 |
20 | **说明:**
21 |
22 | 有较大新功能会以 `feat/xxx` 创建分支
23 |
24 | ## Commit 格式
25 |
26 | ```
27 | [{keyword}]({scope}): {description}
28 | ```
29 |
30 | - `{keyword}`
31 | - `revert` 撤销某些提交
32 | - `feat` 新增功能
33 | - `fix` 更新或者修复 bug
34 | - `docs` 文档修改更新
35 | - `style` 代码格式(不影响代码运行的变动)
36 | - `refactor` 重构(即不是新增功能,也不是修改bug的代码变动)
37 | - `perf` 性能优化
38 | - `test` 测试新增或更新
39 | - `chore` 构建过程或辅助工具的变动
40 | - `{scope}`: 可选,用于说明 commit 影响的范围
41 | - `{description}`
42 | - 尽可能详细的描述就好,1-50个字符
43 |
44 | for example:
45 |
46 | - [feat]: 新增富文本widget
47 | - [fix]: 修复下拉框展开后点击空白不消失
48 |
49 |
--------------------------------------------------------------------------------
/src/schema/base/table/Schema.js:
--------------------------------------------------------------------------------
1 | import BaseSchema from '../../BaseSchema'
2 |
3 | export default class TableSchema extends BaseSchema {
4 | constructor (props) {
5 | super()
6 | this.label = '表格'
7 | this.option = {
8 | type: 'static',
9 | columns: [{
10 | type: 'index',
11 | title: 'No',
12 | key: '',
13 | align: 'left'
14 | }],
15 | page: {
16 | total: 0,
17 | current: 1,
18 | size: 10,
19 | showTotal: true,
20 | position: 'right'
21 | },
22 | data: [],
23 | dynamicData: [],
24 | noDataText: '暂无内容'
25 | }
26 | this.create(props)
27 | }
28 | }
29 |
30 | // 静态配置,同类widget公有
31 | Object.assign(TableSchema, {
32 | title: '表格',
33 | widget: 'table',
34 | preview: '',
35 | logic: {
36 | // value: [],
37 | // event: []
38 | }
39 | })
40 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Worker from './worker'
2 | import Rule from './rule'
3 | import Event from './event'
4 | import Store from './store'
5 | import Logic from './logic'
6 | import Context from './context'
7 | import Script from './script'
8 | import TypeBuilder from './store/TypeBuilder'
9 | import Dict from './store/Dict'
10 | import API from './store/API'
11 | import * as helper from './helper'
12 | import * as constant from './constant'
13 | import * as schema from './schema'
14 | import * as style from './style'
15 | import * as hook from './hook'
16 | import * as render from './render'
17 | import * as drag from './drag'
18 |
19 | export {
20 | Worker,
21 | Rule,
22 | Event,
23 | Store,
24 | Logic,
25 | Context,
26 | Script,
27 | TypeBuilder,
28 | Dict,
29 | API,
30 | helper,
31 | constant,
32 | schema,
33 | style,
34 | hook,
35 | render,
36 | drag
37 | }
38 |
--------------------------------------------------------------------------------
/src/schema/base/inputNumber/Schema.js:
--------------------------------------------------------------------------------
1 | import FormSchema from '../../FormSchema'
2 |
3 | export default class InputNumberSchema extends FormSchema {
4 | constructor (props) {
5 | super()
6 | this.label = '数字'
7 | this.placeholder = '请输入'
8 | this.default = 0
9 | this.rules = [{
10 | required: false,
11 | message: '必填',
12 | trigger: 'change',
13 | type: 'number'
14 | }]
15 | this.option = {
16 | min: 0,
17 | max: 100,
18 | step: 1,
19 | precision: 1
20 | }
21 | this.create(props)
22 | }
23 | }
24 |
25 | // 静态配置,同类widget公有
26 | Object.assign(InputNumberSchema, {
27 | title: '数字框',
28 | widget: 'inputNumber',
29 | preview: '',
30 | type: 'number',
31 | validators: [],
32 | logic: {
33 | value: ['=', '>', '<', '!='],
34 | // event: ['focus', 'blur', 'change']
35 | event: ['change']
36 | }
37 | })
38 |
--------------------------------------------------------------------------------
/src/schema/RootSchema.js:
--------------------------------------------------------------------------------
1 | import Schema from './base/grid/Schema'
2 |
3 | /**
4 | * root schema
5 | */
6 | export default class RootSchema extends Schema {
7 | constructor (props) {
8 | super()
9 | this.title = ''
10 | this.description = ''
11 | this.size = 'default'
12 | this.container = true
13 | this.children = [{
14 | span: 24,
15 | list: []
16 | }]
17 | this.logics = []
18 | // global setting for label
19 | this.label = {
20 | width: 80,
21 | position: 'right',
22 | colon: false
23 | }
24 | this.store = {
25 | dicts: []
26 | }
27 | this.style = {
28 | 'margin-right': 'auto',
29 | 'margin-left': 'auto',
30 | background: [],
31 | container: {
32 | 'background-color': '',
33 | background: []
34 | }
35 | }
36 |
37 | this.create(props)
38 | this.createChildren(props)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/style/Background.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 解析背景样式
3 | */
4 | export default class Background {
5 | constructor (background) {
6 | const {
7 | position,
8 | repeat,
9 | size,
10 | image,
11 | attachment
12 | } = background || {}
13 |
14 | this.position = position || 'top center'
15 | this.repeat = repeat || 'no-repeat' // 'no-repeat' | 'repeat-x' | 'repeat-y'
16 | this.size = size || 'contain' // 'cover' | 'contain'
17 | // this.color = color || '' // 'cover' | ''
18 | this.image = image || '' // 'cover' | ''
19 | this.attachment = attachment || 'scroll' // 'scroll' | 'fixed'
20 | }
21 |
22 | toString () {
23 | const {
24 | position,
25 | repeat,
26 | size,
27 | image,
28 | attachment
29 | } = this
30 | const img = image ? ` ${repeat} ${position} /${size} ${attachment} url(${image})` : ''
31 |
32 | return `${img}`
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/schema/base/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | autoComplete: require('./autoComplete').default,
3 | button: require('./button').default,
4 | cascader: require('./cascader').default,
5 | checkbox: require('./checkbox').default,
6 | datePicker: require('./datePicker').default,
7 | // formButton: require('./formButton').default,
8 | grid: require('./grid').default,
9 | input: require('./input').default,
10 | inputNumber: require('./inputNumber').default,
11 | radio: require('./radio').default,
12 | rate: require('./rate').default,
13 | select: require('./select').default,
14 | slider: require('./slider').default,
15 | table: require('./table').default,
16 | switch: require('./switch').default,
17 | textarea: require('./textarea').default,
18 | text: require('./text').default,
19 | timePicker: require('./timePicker').default,
20 | upload: require('./upload').default,
21 | line: require('./line').default
22 | }
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Epage Core
2 |
3 | [Epage](https://github.com/didi/epage) 可视化配置工具核心依赖。
4 |
5 | ## 文档
6 |
7 | 官网:[http://epage.didichuxing.com](http://epage.didichuxing.com)
8 |
9 | - [开发文档](http://epage.didichuxing.com/developer/)
10 |
11 |
12 | ## 演示地址
13 |
14 | - **[Demo](http://epage.didichuxing.com/examples/epage.html)**
15 |
16 | ## 安装
17 |
18 | ```sh
19 | npm install epage-core -S
20 | # 或者 yarn add epage
21 | ```
22 |
23 | ## 仓库更新说明
24 |
25 | 本仓库为`Epage渲染器`及`Epage设计器`核心依赖,更新日志查看[CHANGLOG](./CHANGELOG.md)。
26 |
27 | 更多`Epage渲染器`及相关工具参见:[https://github.com/epage-team](https://github.com/epage-team)。
28 |
29 | ## 设计器及渲染器示例
30 |
31 | ```js
32 | import {
33 | Event,
34 | Worker,
35 | Logic,
36 | Rule,
37 | Store,
38 | schema,
39 | helper,
40 | constant
41 | } from 'epage-core'
42 |
43 | ```
44 |
45 | ## 交流群
46 |
47 | QQ群
48 |
49 |
50 |
51 | ## License
52 |
53 | [MIT](http://opensource.org/licenses/MIT)
54 |
--------------------------------------------------------------------------------
/src/rule/rules/StringRule.js:
--------------------------------------------------------------------------------
1 | export default class StringRule {
2 | static get type () {
3 | return 'string'
4 | }
5 |
6 | static get name () {
7 | return '字符串'
8 | }
9 |
10 | constructor (rule = {}) {
11 | const defaultRule = {
12 | type: 'string',
13 | message: '字符串不符合规则'
14 | }
15 | this.origin = Object.assign({}, defaultRule, rule)
16 | this.rule = {
17 | type: 'string',
18 | trigger: 'blur',
19 | message: ''
20 | }
21 | this.update(this.origin)
22 | }
23 |
24 | update (rule) {
25 | if (rule) {
26 | const { min, max } = rule
27 | const intMin = parseInt(min)
28 | const intMax = parseInt(max)
29 |
30 | this.rule.message = rule.message
31 | if (isNaN(intMin)) {
32 | delete this.rule.min
33 | } else {
34 | this.rule.min = intMin
35 | }
36 | if (isNaN(intMax)) {
37 | delete this.rule.max
38 | } else {
39 | this.rule.max = intMax
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/rule/rules/JSONRule.js:
--------------------------------------------------------------------------------
1 |
2 | export default class JSONRule {
3 | static get type () {
4 | return 'json'
5 | }
6 |
7 | static get name () {
8 | return 'JSON'
9 | }
10 |
11 | constructor (rule = {}) {
12 | const defaultRule = {
13 | type: 'string',
14 | message: '非法的json格式!'
15 | }
16 | this.origin = Object.assign({}, defaultRule, rule)
17 | this.rule = {
18 | type: 'string',
19 | trigger: 'blur',
20 | validator: function (rule, value, callback) {
21 | if (!value || !(value + '').trim()) {
22 | return callback()
23 | }
24 | try {
25 | JSON.parse(value)
26 | callback()
27 | } catch (e) {
28 | return callback(new Error(rule.message))
29 | }
30 | },
31 | message: '非法的json格式!'
32 | }
33 | this.update(this.origin)
34 | }
35 |
36 | update (rule) {
37 | if (rule) {
38 | this.rule.message = rule.message
39 | Object.assign(this.origin, rule)
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/schema/base/autoComplete/Schema.js:
--------------------------------------------------------------------------------
1 | import FormSchema from '../../FormSchema'
2 | import { getRuleValidator } from '../../../helper'
3 |
4 | export default class AutoCompleteSchema extends FormSchema {
5 | constructor (props) {
6 | super()
7 | this.label = '自动完成'
8 | this.placeholder = '请输入'
9 | this.default = ''
10 | this.option = {
11 | type: 'static',
12 | url: '',
13 | adapter: 'return data',
14 | data: ['A'],
15 | dynamicData: [],
16 | clearable: true
17 | }
18 | this.create(props)
19 | const rule = {
20 | trigger: 'change',
21 | validator: getRuleValidator(this.rules[0], this.type)
22 | }
23 | this.updateRequiredRule(rule, new.target)
24 | }
25 | }
26 |
27 | // 静态配置,同类widget公有
28 | Object.assign(AutoCompleteSchema, {
29 | title: '自动完成',
30 | widget: 'autoComplete',
31 | preview: '',
32 | type: 'string',
33 | validators: [],
34 | logic: {
35 | value: ['=', '!='],
36 | // event: ['focus', 'blur', 'change']
37 | event: []
38 | }
39 | })
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Shoucheng (Chengzi) Jia
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/script/color.js:
--------------------------------------------------------------------------------
1 |
2 | const styles = {
3 | bold: ['\x1B[1m', '\x1B[22m'],
4 | italic: ['\x1B[3m', '\x1B[23m'],
5 | underline: ['\x1B[4m', '\x1B[24m'],
6 | inverse: ['\x1B[7m', '\x1B[27m'],
7 | strikethrough: ['\x1B[9m', '\x1B[29m'],
8 | white: ['\x1B[37m', '\x1B[39m'],
9 | grey: ['\x1B[90m', '\x1B[39m'],
10 | black: ['\x1B[30m', '\x1B[39m'],
11 | blue: ['\x1B[34m', '\x1B[39m'],
12 | cyan: ['\x1B[36m', '\x1B[39m'],
13 | green: ['\x1B[32m', '\x1B[39m'],
14 | magenta: ['\x1B[35m', '\x1B[39m'],
15 | red: ['\x1B[31m', '\x1B[39m'],
16 | yellow: ['\x1B[33m', '\x1B[39m'],
17 | whiteBG: ['\x1B[47m', '\x1B[49m'],
18 | greyBG: ['\x1B[49;5;8m', '\x1B[49m'],
19 | blackBG: ['\x1B[40m', '\x1B[49m'],
20 | blueBG: ['\x1B[44m', '\x1B[49m'],
21 | cyanBG: ['\x1B[46m', '\x1B[49m'],
22 | greenBG: ['\x1B[42m', '\x1B[49m'],
23 | magentaBG: ['\x1B[45m', '\x1B[49m'],
24 | redBG: ['\x1B[41m', '\x1B[49m'],
25 | yellowBG: ['\x1B[43m', '\x1B[49m']
26 | }
27 | const color = {}
28 |
29 | Object.keys(styles).forEach(function (key) {
30 | color[key] = function (msg) {
31 | return styles[key].join(msg)
32 | }
33 | })
34 |
35 | module.exports = color
36 |
--------------------------------------------------------------------------------
/script/verify-commit-msg.js:
--------------------------------------------------------------------------------
1 | const msgPath = process.env.HUSKY_GIT_PARAMS
2 | const msg = require('fs').readFileSync(msgPath, 'utf-8').trim()
3 | const child = require('child_process')
4 | const color = require('./color')
5 |
6 | function checkGitAccount () {
7 | child.exec('git config user.email', function (err, sto) {
8 | const unsafeStr = /@didi(chuxing|global)?\.com$/
9 |
10 | if (err) process.exit(1)
11 | const account = (sto || '').trim()
12 |
13 | if (unsafeStr.test(account)) {
14 | console.error(color.red('Please switch to your personal github account !!!'))
15 | process.exit(1)
16 | } else {
17 | checkCommitMsg(msg)
18 | }
19 | })
20 | }
21 |
22 | function checkCommitMsg (msg) {
23 | const commitRE = /^(revert|feat|fix|docs|style|refactor|perf|test|chore)(\(.+\))?: .{1,50}/
24 |
25 | if (!commitRE.test(msg)) {
26 | console.log()
27 | console.error(color.red('Invalid commit message format.') + '\nmust start with revert|feat|fix|docs|style|refactor|perf|test|chore:\nExample: feat: upgrade v0.1 xxx\n')
28 | process.exit(1)
29 | } else {
30 | return true
31 | }
32 | }
33 |
34 | checkGitAccount()
35 |
--------------------------------------------------------------------------------
/src/rule/rules/NumberRule.js:
--------------------------------------------------------------------------------
1 |
2 | export default class NumberRule {
3 | static get type () {
4 | return 'number'
5 | }
6 |
7 | static get name () {
8 | return '数字'
9 | }
10 |
11 | constructor (rule = {}) {
12 | const defaultRule = {
13 | type: 'number',
14 | trigger: 'blur',
15 | message: '数字不正确'
16 | }
17 | this.origin = Object.assign({}, defaultRule, rule)
18 | this.rule = {
19 | type: 'number',
20 | trigger: 'blur',
21 | message: ''
22 | }
23 | this.update(this.origin)
24 | }
25 |
26 | update (rule) {
27 | if (rule) {
28 | this.rule.message = rule.message
29 | const { min, max } = rule
30 | const floatMin = parseFloat(min)
31 | const floatMax = parseFloat(max)
32 | if (isNaN(floatMin)) {
33 | delete this.rule.min
34 | } else {
35 | this.rule.min = floatMin
36 | }
37 | if (isNaN(floatMax)) {
38 | delete this.rule.max
39 | } else {
40 | this.rule.max = floatMax
41 | }
42 | // this.rule.min = isNaN(floatMin) ? undefined : floatMin
43 | // this.rule.max = isNaN(floatMax) ? undefined : floatMax
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/rule/rules/PatternRule.js:
--------------------------------------------------------------------------------
1 |
2 | export default class PatternRule {
3 | static get type () {
4 | return 'pattern'
5 | }
6 |
7 | static get name () {
8 | return '正则表达式'
9 | }
10 |
11 | constructor (rule = {}) {
12 | const defaultRule = {
13 | type: 'pattern',
14 | pattern: '',
15 | message: '',
16 | ignoreCase: false,
17 | global: false,
18 | multiline: false
19 | }
20 | this.origin = Object.assign({}, defaultRule, rule)
21 | this.rule = {
22 | type: 'pattern',
23 | trigger: 'blur',
24 | pattern: new RegExp(),
25 | message: ''
26 | }
27 | this.update(this.origin)
28 | }
29 |
30 | update (rule) {
31 | if (rule) {
32 | const { pattern, ignoreCase, global, multiline } = rule
33 | this.rule.message = rule.message
34 | const flags = [ignoreCase ? 'i' : '', global ? 'g' : '', multiline ? 'm' : ''].join('')
35 | let reg = new RegExp()
36 |
37 | if (typeof pattern === 'string' && pattern.length) {
38 | try {
39 | reg = new RegExp(rule.pattern, flags)
40 | } catch (e) {
41 | // console.log(e)
42 | }
43 | }
44 | this.rule.pattern = reg
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/schema/base/radio/Schema.js:
--------------------------------------------------------------------------------
1 | import FormSchema from '../../FormSchema'
2 | import { getRuleValidator } from '../../../helper'
3 |
4 | export default class RadioSchema extends FormSchema {
5 | constructor (props) {
6 | super()
7 | this.type = 'string'
8 | this.label = '单选框'
9 | this.placeholder = '请选择'
10 | this.default = ''
11 | this.option = {
12 | type: 'static',
13 | dict: {
14 | type: 'dict', // dict | api
15 | dict: '',
16 | dictAPI: '',
17 | api: ''
18 | },
19 | direction: 'horizontal',
20 | url: '',
21 | adapter: 'return data',
22 | data: [
23 | { key: 'A', value: 'A' }
24 | ],
25 | dynamicData: []
26 | }
27 | this.create(props)
28 | const rule = {
29 | trigger: 'change',
30 | validator: getRuleValidator(this.rules[0], this.type)
31 | }
32 | this.updateRequiredRule(rule, new.target)
33 | }
34 | }
35 |
36 | // 静态配置,同类widget公有
37 | Object.assign(RadioSchema, {
38 | title: '单选框',
39 | widget: 'radio',
40 | preview: '',
41 | type: ['string', 'number'],
42 | validators: [],
43 | logic: {
44 | value: ['=', '!='],
45 | // event: ['change']
46 | event: ['change']
47 | }
48 | })
49 |
--------------------------------------------------------------------------------
/src/schema/base/checkbox/Schema.js:
--------------------------------------------------------------------------------
1 | import FormSchema from '../../FormSchema'
2 | import { getRuleValidator } from '../../../helper'
3 |
4 | export default class CheckboxSchema extends FormSchema {
5 | constructor (props) {
6 | super()
7 | this.type = 'array'
8 | this.label = '多选框'
9 | this.default = []
10 | this.rules = [{
11 | required: false,
12 | message: '必填',
13 | type: 'array',
14 | trigger: 'change'
15 | }]
16 | this.option = {
17 | type: 'static',
18 | direction: 'horizontal',
19 | url: '',
20 | adapter: 'return data',
21 | dynamicData: [],
22 | data: [
23 | { key: 'A', value: 'A' }
24 | ]
25 | }
26 | this.create(props)
27 | const rule = {
28 | trigger: 'change',
29 | validator: getRuleValidator(this.rules[0], this.type)
30 | }
31 | this.updateRequiredRule(rule, new.target)
32 | }
33 | }
34 |
35 | // 静态配置,同类widget公有
36 | Object.assign(CheckboxSchema, {
37 | title: '多选框',
38 | widget: 'checkbox',
39 | preview: '',
40 | type: ['array', 'array'],
41 | validators: [],
42 | logic: {
43 | value: ['<>', '><'],
44 | // event: ['change']
45 | event: ['change']
46 | }
47 | })
48 |
--------------------------------------------------------------------------------
/src/store/mutations/model.js:
--------------------------------------------------------------------------------
1 | import types from '../types'
2 | import {
3 | isFunction,
4 | isString,
5 | checkValueType,
6 | convertNameModelToKeyModel
7 | } from '../../helper'
8 |
9 | export default {
10 | // 设置表单model,即用户填写的表单数据
11 | [types.$MODEL_SET] (state, { model, useName }) {
12 | const { flatSchemas } = state
13 | const { flatWidgets } = this.getters
14 | const _model = {}
15 | let keyModel = model // 以schema.key为key组成的model对象
16 |
17 | if (useName) {
18 | keyModel = convertNameModelToKeyModel(model, flatSchemas)
19 | }
20 | const conbinedModel = Object.assign({}, state.model, keyModel)
21 |
22 | for (const i in conbinedModel) {
23 | const schema = flatSchemas[i]
24 |
25 | if (!schema) {
26 | continue
27 | }
28 |
29 | const Widget = flatWidgets[schema.widget]
30 |
31 | if (!Widget || !isFunction(Widget.Schema)) {
32 | continue
33 | }
34 |
35 | if (schema.type === 'json' && !isString(conbinedModel[i])) {
36 | conbinedModel[i] = JSON.stringify(conbinedModel[i])
37 | }
38 |
39 | if (checkValueType(conbinedModel[i], Widget.Schema.type, schema.dynamic)) {
40 | _model[i] = conbinedModel[i]
41 | }
42 | }
43 | state.model = _model
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/schema/FormSchema.js:
--------------------------------------------------------------------------------
1 | import Schema from './BaseSchema'
2 | import {
3 | setKeyAndName,
4 | updateRequiredRule,
5 | getSchemaType
6 | } from '../helper'
7 |
8 | export default class FormSchema extends Schema {
9 | constructor (props) {
10 | const { schema, widgets = {} } = props || {}
11 | super()
12 | // form name
13 | this.name = ''
14 | // type of widget value
15 | this.type = getSchemaType(schema, new.target)
16 | // label name
17 | this.label = ''
18 | this.description = ''
19 | this.help = ''
20 | this.disabled = false
21 | // 校验规则,参考 [async-validator](https://github.com/yiminghe/async-validator)
22 | this.rules = [{
23 | required: false,
24 | message: '必填',
25 | type: 'string',
26 | trigger: 'blur'
27 | }]
28 | this.create(props)
29 |
30 | if (widgets[this.widget]) {
31 | updateRequiredRule(this, widgets[this.widget].Schema)
32 | }
33 | }
34 |
35 | // over write the base Schema method
36 | create (props) {
37 | const { clone, dynamic, flatSchemas } = props || {}
38 | Schema.prototype.create.call(this, props)
39 | setKeyAndName(this, clone, dynamic, flatSchemas)
40 | }
41 |
42 | updateRequiredRule (rule, Schema) {
43 | updateRequiredRule(this, Schema, rule)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/schema/base/select/Schema.js:
--------------------------------------------------------------------------------
1 | import FormSchema from '../../FormSchema'
2 | import { getRuleValidator } from '../../../helper'
3 |
4 | export default class SelectSchema extends FormSchema {
5 | constructor (props) {
6 | super()
7 | this.label = '下拉框'
8 | this.placeholder = '请选择'
9 | this.default = ''
10 | this.rules = [{
11 | required: false,
12 | message: '必填',
13 | trigger: 'change'
14 | }]
15 | this.option = {
16 | type: 'static',
17 | url: '',
18 | adapter: 'return data',
19 | dynamicData: [],
20 | data: [
21 | { key: 'A', value: 'A' },
22 | { key: 'B', value: 'B' }
23 | ],
24 | multiple: false,
25 | clearable: true
26 | }
27 | this.create(props)
28 | const rule = {
29 | trigger: 'change',
30 | validator: getRuleValidator(this.rules[0], this.type)
31 | }
32 | this.updateRequiredRule(rule, new.target)
33 | }
34 | }
35 |
36 | // 静态配置,同类widget公有
37 | Object.assign(SelectSchema, {
38 | title: '下拉框',
39 | widget: 'select',
40 | preview: '',
41 | type: ['string', 'number', 'array', 'array', 'array'],
42 | validators: [],
43 | logic: {
44 | value: ['=', '!=', '<>', '><'],
45 | // event: ['focus', 'blur', 'change']
46 | event: ['change']
47 | }
48 | })
49 |
--------------------------------------------------------------------------------
/src/schema/base/cascader/Schema.js:
--------------------------------------------------------------------------------
1 | import FormSchema from '../../FormSchema'
2 | import { getRuleValidator } from '../../../helper'
3 |
4 | export default class CascaderSchema extends FormSchema {
5 | constructor (props) {
6 | super()
7 | this.type = 'array'
8 | this.label = '级联选择'
9 | this.placeholder = '请选择'
10 | this.default = []
11 | this.rules = [{ required: false, message: '必填', type: 'array', trigger: 'change' }]
12 | this.option = {
13 | type: 'static',
14 | url: '',
15 | adapter: 'return data',
16 | dynamicData: [],
17 | delimiter: '/', // 展示时的分隔符
18 | clearable: true,
19 | notFoundText: '无匹配数据',
20 | filterable: false,
21 | data: [
22 | {
23 | key: 'A',
24 | value: 'option A',
25 | children: [{
26 | key: 'B',
27 | value: 'option B'
28 | }]
29 | }
30 | ]
31 | }
32 | this.create(props)
33 | const rule = {
34 | trigger: 'change',
35 | validator: getRuleValidator(this.rules[0], this.type)
36 | }
37 | this.updateRequiredRule(rule, new.target)
38 | }
39 | }
40 |
41 | // 静态配置,同类widget公有
42 | Object.assign(CascaderSchema, {
43 | title: '级联选择',
44 | widget: 'cascader',
45 | preview: '',
46 | type: ['array', 'array'],
47 | validators: [],
48 | logic: {
49 | value: ['=', '!='],
50 | // event: ['focus', 'blur', 'change']
51 | event: ['change']
52 | }
53 | })
54 |
--------------------------------------------------------------------------------
/src/schema/base/upload/Schema.js:
--------------------------------------------------------------------------------
1 | import FormSchema from '../../FormSchema'
2 |
3 | export default class UploadSchema extends FormSchema {
4 | constructor (props) {
5 | super()
6 | this.label = '上传'
7 | this.placeholder = '请选择文件'
8 | this.default = []
9 | this.rules = [{
10 | required: false,
11 | message: '必填',
12 | trigger: 'change',
13 | type: 'array'
14 | }]
15 | this.option = {
16 | // 上传的地址,必填
17 | action: '',
18 | // 匹配上传接口返回的数据格式
19 | adapter: 'return { name: data.filename, url:data.url }',
20 | // 支持的文件类型,与 accept 不同的是,format 是识别文件的后缀名
21 | // accept 为 input 标签原生的 accept 属性,会在选择文件时过滤
22 | // 可以两者结合使用
23 | format: [],
24 | // 接受上传的文件类型
25 | accept: '',
26 | // 上传控件的类型,可选值为 select(点击选择),drag(支持拖拽)
27 | type: 'select',
28 | // 是否支持多选文件
29 | multiple: true,
30 | // 文件大小限制,单位 kb
31 | maxSize: 5000,
32 | // 是否显示已上传文件列表
33 | showUploadList: true,
34 | // 设置上传的请求头部
35 | headers: [],
36 | // 上传的文件字段名
37 | name: 'file',
38 | // 支持发送 cookie 凭证信息
39 | withCredentials: false,
40 | // 上传时附带的额外参数
41 | data: {}
42 | }
43 | this.create(props)
44 | }
45 | }
46 |
47 | // 静态配置,同类widget公有
48 | Object.assign(UploadSchema, {
49 | title: '上传',
50 | widget: 'upload',
51 | preview: '',
52 | type: 'array',
53 | validators: [],
54 | logic: {
55 | value: [],
56 | // event: ['change']
57 | event: []
58 | }
59 | })
60 |
--------------------------------------------------------------------------------
/src/event/index.js:
--------------------------------------------------------------------------------
1 | import { include } from '../helper'
2 | /**
3 | * 表单设计器事件处理
4 | */
5 | export default class EpEvent {
6 | constructor () {
7 | this.$events = {}
8 | }
9 |
10 | /**
11 | * add event listener for widget
12 | * @param {String} key [required] schema.key
13 | * @param {String} type [required] event type
14 | * @param {Function} callback [required] event callback
15 | * @return {event} this
16 | */
17 | on (key, type, callback) {
18 | if (!(key in this.$events)) {
19 | this.$events[key] = { [type]: [callback] }
20 | }
21 | if (!(type in this.$events[key])) {
22 | this.$events[key][type] = [callback]
23 | }
24 | const callbacks = this.$events[key][type]
25 | if (!include(callbacks, callback)) {
26 | this.$events[key][type].push(callback)
27 | }
28 |
29 | return this
30 | }
31 |
32 | /**
33 | * remove event listener for widget
34 | * @param {String} key [required] schema.key
35 | * @param {String} type event type
36 | * @param {Function} callback event callback
37 | * @return {event} this
38 | */
39 | off (key, type, callback) {
40 | if (key in this.$events) {
41 | if (type in this.$events[key]) {
42 | const callbacks = this.$events[key][type]
43 | const index = callbacks.indexOf(callback)
44 | if (index > -1) {
45 | callbacks.splice(index, 1)
46 | } else {
47 | this.$events[key][type] = []
48 | }
49 | } else if (typeof type === 'undefined') {
50 | delete this.$events[key]
51 | }
52 | }
53 | return this
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/helper/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | getRootSchemaChildren,
3 | getParentListByKey,
4 | getIndexByKey,
5 | replaceSchemaKey,
6 | flattenSchema,
7 | getWidgetType,
8 | getWidgetModel,
9 | setKeyAndName,
10 | getFormData,
11 | getSchema,
12 | storeFetchClean,
13 | mergeWidgets,
14 | mergeWidget,
15 | setValidators,
16 | updateRequiredRule,
17 | getSchemaType,
18 | getRequiredRuleType,
19 | getRuleValidator,
20 | usePlugins,
21 | convertNameModelToKeyModel,
22 | cleanDefaultValue,
23 | getDefaults
24 | } from './epUtil'
25 |
26 | import {
27 | randomStr,
28 | isPlainObject,
29 | isFunction,
30 | isString,
31 | isNumber,
32 | jsonClone,
33 | isArray,
34 | ajax,
35 | isNotEmptyString,
36 | include,
37 | debounce,
38 | getValueType,
39 | checkValueType,
40 | formatDate,
41 | isNumberString,
42 | copy
43 | } from './util'
44 |
45 | export {
46 | getRootSchemaChildren,
47 | getParentListByKey,
48 | getIndexByKey,
49 | replaceSchemaKey,
50 | flattenSchema,
51 | getWidgetType,
52 | getWidgetModel,
53 | setKeyAndName,
54 | getFormData,
55 | getSchema,
56 | storeFetchClean,
57 | mergeWidgets,
58 | mergeWidget,
59 | setValidators,
60 | updateRequiredRule,
61 | getSchemaType,
62 | getRequiredRuleType,
63 | getRuleValidator,
64 | usePlugins,
65 | convertNameModelToKeyModel,
66 | cleanDefaultValue,
67 | getDefaults,
68 |
69 | randomStr,
70 | isPlainObject,
71 | isFunction,
72 | isString,
73 | isNumber,
74 | isArray,
75 | jsonClone,
76 | ajax,
77 | isNotEmptyString,
78 | include,
79 | debounce,
80 | getValueType,
81 | checkValueType,
82 | formatDate,
83 | isNumberString,
84 | copy
85 | }
86 |
--------------------------------------------------------------------------------
/src/schema/BaseSchema.js:
--------------------------------------------------------------------------------
1 | import {
2 | isPlainObject,
3 | isArray,
4 | randomStr,
5 | include,
6 | jsonClone
7 | } from '../helper'
8 |
9 | // this prop list should be ignore when copy schema
10 | const IGNORE_PROP_LIST = ['list']
11 |
12 | /**
13 | * the basic Schema
14 | */
15 | export default class BaseSchema {
16 | constructor (props) {
17 | const { schema } = props || {}
18 | // globally unique
19 | this.key = randomStr()
20 | // widget type
21 | this.widget = ''
22 | // should be hidden
23 | this.hidden = false
24 | // extra options for this schema
25 | this.option = {}
26 | this.style = {}
27 | // create schema instance from schema object
28 | this.create(props)
29 | if (!this.widget) {
30 | this.widget = new.target
31 | ? new.target.widget
32 | : (schema ? schema.widget : '')
33 | }
34 | }
35 |
36 | create (props) {
37 | const { schema, clone } = props || {}
38 | if (isPlainObject(schema)) {
39 | // 拷贝所有属性,没有的遵循默认声明
40 | for (const i in schema) {
41 | if (include(IGNORE_PROP_LIST, i) || !(i in this)) {
42 | continue
43 | }
44 | let item = schema[i]
45 | if (isArray(item)) {
46 | // item = Object.assign([], this[i], item)
47 | item = jsonClone(item)
48 | } else if (isPlainObject(item)) {
49 | if (isPlainObject(this[i])) {
50 | item = jsonClone({ ...this[i], ...item })
51 | } else {
52 | item = jsonClone(item)
53 | }
54 | }
55 | this[i] = item
56 | }
57 | // 复制整个schema时
58 | if (clone || !this.key) {
59 | this.key = randomStr()
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/store/mutations/rule.js:
--------------------------------------------------------------------------------
1 | import types from '../types'
2 | import Rule from '../../rule'
3 | import {
4 | isFunction,
5 | isArray,
6 | updateRequiredRule
7 | } from '../../helper'
8 |
9 | export default {
10 | [types.$RULE_INIT] (state, { rootSchema }) {
11 | state.flatRules = new Rule(rootSchema).rules
12 | },
13 | // 设计模式为选中widget修改规则类型
14 | [types.$RULE_TYPE_UPDATE] ({ flatRules, flatSchemas }, { key, type, index }) {
15 | const schema = flatSchemas[key]
16 | const { widgetsValidators } = this.getters
17 | const widgetValidators = widgetsValidators[schema.widget]
18 |
19 | if (!isArray(widgetValidators)) {
20 | return
21 | }
22 | const Rule = widgetValidators.find(v => v.type === type)
23 | if (!isFunction(Rule)) {
24 | return
25 | }
26 | const newRule = new Rule().rule
27 | const currentRules = flatRules[key]
28 | const originRules = [...schema.rules]
29 |
30 | currentRules.splice(index, 1, newRule)
31 | originRules.splice(index, 1, { message: newRule.message, type })
32 | schema.rules = originRules
33 | },
34 |
35 | [types.$RULE_REQUIRED_RULE_UPDATE] ({ flatRules, flatSchemas }, { key, rule }) {
36 | const schema = flatSchemas[key]
37 | const WidgetSchema = this.getters.flatWidgets[schema.widget].Schema
38 |
39 | updateRequiredRule(schema, WidgetSchema, rule)
40 | },
41 |
42 | // 设计模式为选中widget修改规则错误消息
43 | [types.$RULE_MESSAGE_UPDATE] ({ flatRules }, { key, index, message }) {
44 | const currentRules = flatRules[key]
45 |
46 | if (!isArray(currentRules)) {
47 | return
48 | }
49 | currentRules[index].message = message
50 | },
51 |
52 | // 设计模式为选中widget添加规则
53 | [types.$RULE_ADD] ({ flatSchemas }, { key }) {
54 | const schema = flatSchemas[key]
55 | const defaultRule = { type: '', message: '' }
56 |
57 | schema.rules.push(defaultRule)
58 | },
59 |
60 | // 设计模式为选中widget删除规则
61 | [types.$RULE_REMOVE] ({ flatRules, flatSchemas }, { key, index }) {
62 | const schema = flatSchemas[key]
63 | const rules = flatRules[key]
64 |
65 | schema.rules.splice(index, 1)
66 | if (!rules) {
67 | return
68 | }
69 | rules.splice(index, 1)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/store/TypeBuilder.js:
--------------------------------------------------------------------------------
1 | import { isPlainObject, isFunction } from '../helper'
2 |
3 | export default class TypeBuilder {
4 | /**
5 | * 构建不同类型数据的默认值,builder必须返回全新数据
6 | * @param {Object} types 新builder类型,与内置类型重复将被忽略
7 | * 内置:string | boolean | number | array | object | null
8 | */
9 | constructor (types) {
10 | this.types = {
11 | string: () => '',
12 | boolean: () => false,
13 | number: () => 0,
14 | array: () => [],
15 | object: () => ({}),
16 | null: () => null,
17 | date: () => new Date(),
18 | json: () => '{}',
19 | // composition type
20 | 'array': () => [],
21 | 'array': () => [],
22 | 'array': () => [],
23 | 'array': () => []
24 | }
25 | this.add(types)
26 | this.convert = {}
27 | }
28 |
29 | /**
30 | * 添加自定义数据类型的builder
31 | * @param {Object} types builder 对应类型
32 | * 如:{ string: () => ''}
33 | */
34 | add (types) {
35 | if (!isPlainObject(types)) {
36 | return
37 | }
38 | for (const i in types) {
39 | const builder = types[i]
40 | if (!(i in this.types) && isFunction(builder)) {
41 | this.types[i] = builder
42 | }
43 | }
44 | }
45 | }
46 |
47 | TypeBuilder.convert = {
48 | string2number: s => {
49 | const r = parseFloat(s)
50 | return isNaN(r) ? null : r
51 | },
52 | string2boolean: s => !!s,
53 | number2string: n => {
54 | const r = String(n)
55 | return r === '[object Object]' ? null : r
56 | },
57 | number2boolean: n => !!n,
58 | boolean2string: b => String(b),
59 | boolean2number: b => Number(b),
60 | string2json: s => {
61 | try {
62 | return JSON.parse(s)
63 | } catch (e) {
64 | return null
65 | }
66 | }
67 | }
68 |
69 | /**
70 | * resolve schema type
71 | * @param {String} type Schema.type
72 | * @param {Boolean} structure data structure,
73 | * @example
74 | * resolve('array', true) // array
75 | * resolve('string', true) // string
76 | */
77 | TypeBuilder.resolve = function (type, structure) {
78 | const matched = type.match(/<(\w+)>/)
79 | if (structure) {
80 | return type.split('<')[0]
81 | }
82 | return matched ? matched[1].toLowerCase() : type.toLowerCase()
83 | }
84 |
--------------------------------------------------------------------------------
/src/schema/base/grid/Schema.js:
--------------------------------------------------------------------------------
1 | import BaseSchema from '../../BaseSchema'
2 | import {
3 | isArray,
4 | isPlainObject,
5 | isNotEmptyString,
6 | jsonClone,
7 | isNumber,
8 | isFunction
9 | } from '../../../helper'
10 |
11 | const getDefaultChildren = () => [
12 | { span: 12, list: [] },
13 | { span: 12, list: [] }
14 | ]
15 | export default class GridSchema extends BaseSchema {
16 | constructor (props) {
17 | super()
18 | this.label = '布局'
19 | this.container = true
20 | // 设置form data数据是否平级展开还是层级嵌套
21 | // this.group = false
22 | this.children = getDefaultChildren()
23 | this.option = {
24 | gutter: 0,
25 | align: 'top',
26 | justify: 'start'
27 | }
28 | // this.style = {
29 | // background: 'rgba(255, 255, 255, 0)',
30 | // border: '0px solid #ccc',
31 | // padding: '0 0 0 0',
32 | // margin: '0 0 0 0',
33 | // 'border-radius': '4px'
34 | // }
35 | this.create(props)
36 | this.createChildren(props)
37 | }
38 |
39 | createChildren (props) {
40 | let { schema, widgets = {}, clone, dynamic } = props || {}
41 | if (isPlainObject(schema) && isNotEmptyString(schema.widget)) {
42 | // 避免影响到原schema对象,这里需要深度clone一份
43 | schema = jsonClone(schema)
44 | const children = isArray(schema.children) ? schema.children : []
45 | if (!children.length) {
46 | children.push(getDefaultChildren())
47 | }
48 | children.forEach(child => {
49 | if (!isNumber(child.span) || child.span < 0) {
50 | child.span = 12
51 | }
52 | const list = []
53 | if (isArray(child.list)) {
54 | child.list.forEach(sub => {
55 | const widget = widgets[sub.widget]
56 | if (widget && isFunction(widget.Schema)) {
57 | const newSub = new widget.Schema({ schema: sub, widgets, clone, dynamic })
58 | list.push(newSub)
59 | }
60 | })
61 | }
62 | child.list = list
63 | })
64 | this.children = children
65 | }
66 | }
67 | }
68 |
69 | // 静态配置,同类widget公有
70 | Object.assign(GridSchema, {
71 | title: '栅格',
72 | widget: 'grid',
73 | preview: '',
74 | logic: {
75 | value: [],
76 | event: []
77 | }
78 | })
79 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "epage-core",
3 | "version": "0.5.6",
4 | "description": "The core of epage",
5 | "author": "Chengzi ",
6 | "main": "./dist/epage-core.min.js",
7 | "main:epage": "./src/main.js",
8 | "scripts": {
9 | "build": "webpack --config script/webpack.build.js",
10 | "lint": "eslint --ext .vue --ext .js src/ --fix",
11 | "analyz": "npm_config_report=true npm run build"
12 | },
13 | "files": [
14 | "dist",
15 | "src"
16 | ],
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/epage-team/epage-core.git"
20 | },
21 | "keywords": [
22 | "epage",
23 | "core"
24 | ],
25 | "bugs": {
26 | "url": "https://github.com/epage-team/epage-core/issues"
27 | },
28 | "homepage": "https://github.com/epage-team/epage-core#readme",
29 | "license": "MIT",
30 | "husky": {
31 | "hooks": {
32 | "pre-commit": "npm run lint && git add .",
33 | "commit-msg": "node script/verify-commit-msg.js"
34 | }
35 | },
36 | "peerDependencies": {
37 | "vue": ">=2.5",
38 | "vuex": ">=3.1"
39 | },
40 | "dependencies": {
41 | "vuedraggable": ">=2.20.0"
42 | },
43 | "devDependencies": {
44 | "@babel/core": "^7.8.4",
45 | "@babel/preset-env": "^7.8.4",
46 | "babel-loader": "^8.0.6",
47 | "babel-plugin-transform-runtime": "^6.23.0",
48 | "clean-webpack-plugin": "^2.0.2",
49 | "cross-env": "^5.2.0",
50 | "eslint": "^6.6.0",
51 | "eslint-config-standard": "^14.1.0",
52 | "eslint-plugin-import": "^2.18.2",
53 | "eslint-plugin-node": "^10.0.0",
54 | "eslint-plugin-promise": "^4.2.1",
55 | "eslint-plugin-standard": "^4.0.1",
56 | "eslint-plugin-vue": "^6.0.1",
57 | "husky": "^4.2.3",
58 | "react": "^17.0.1",
59 | "react-dom": "^17.0.1",
60 | "uglifyjs-webpack-plugin": "^1.0.0-rc.0",
61 | "vue": "^2.5",
62 | "vuex": "^3.1.1",
63 | "webpack": "^4.35.2",
64 | "webpack-bundle-analyzer": "^3.3.2",
65 | "webpack-cli": "^3.3.5",
66 | "webpack-dev-server": "^3.3.1",
67 | "webpack-merge": "^4.2.1"
68 | },
69 | "engines": {
70 | "node": ">= 8.10.0",
71 | "npm": ">= 4.0.0"
72 | },
73 | "browserslist": [
74 | "> 1%",
75 | "last 2 versions",
76 | "not ie <= 8"
77 | ]
78 | }
79 |
--------------------------------------------------------------------------------
/src/rule/Rule.js:
--------------------------------------------------------------------------------
1 |
2 | import { isArray, isFunction } from '../helper'
3 | /**
4 | * 规则引擎,处理schema规则并返回validator规则
5 | */
6 |
7 | export default class Rule {
8 | constructor (schema) {
9 | this.schema = schema
10 | this.rules = this.recursiveResolve(this.schema)
11 | }
12 |
13 | recursiveResolve (schema) {
14 | let rules = {}
15 | const recursive = (schema, rules) => {
16 | const { key, container, children, dynamic, list } = schema
17 |
18 | if (dynamic && isArray(list)) {
19 | list.forEach(sc => recursive(sc, rules))
20 | }
21 |
22 | if (container && isArray(children)) {
23 | children.forEach(child => {
24 | if (isArray(child.list)) {
25 | child.list.forEach(item => recursive(item, rules))
26 | }
27 | })
28 | } else {
29 | if (key in rules) {
30 | console.warn(`${key} is already in rules`)
31 | } else {
32 | if (key) {
33 | rules[key] = this.resolve(schema)
34 | }
35 | }
36 | }
37 |
38 | return rules
39 | }
40 | rules = recursive(schema, rules)
41 | return rules
42 | }
43 |
44 | resolve (schema) {
45 | const rules = schema.rules || []
46 | const rulesWithValidator = []
47 | const len = rules.length
48 |
49 | for (let i = 0; i < len; i++) {
50 | // 默认是否必填作为第一个规则,直接copy到规则列表内
51 | if (i === 0) {
52 | rulesWithValidator.push(rules[0])
53 | } else {
54 | const TmpRule = Rule.rules[rules[i].type]
55 |
56 | if (isFunction(TmpRule)) {
57 | rulesWithValidator.push(new TmpRule(rules[i]).rule)
58 | }
59 | }
60 | }
61 | return rulesWithValidator
62 | }
63 |
64 | static set (rules) {
65 | if (rules) {
66 | const map = {}
67 | Object.keys(rules).forEach(k => {
68 | const type = rules[k].type
69 | if (type in map) {
70 | return console.log(`rule warning: ${type} is already exist!`)
71 | }
72 | map[type] = rules[k]
73 | })
74 |
75 | if (Rule.rules) {
76 | Object.assign(Rule.rules, map)
77 | // conbineList(Rule.rule.list, list)
78 | } else {
79 | Rule.rules = map
80 | }
81 | }
82 | }
83 | }
84 |
85 | Rule.rules = {}
86 |
--------------------------------------------------------------------------------
/src/store/types.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @type {Object} mutation type 全局唯一
4 | */
5 | const types = {}
6 | const list = [
7 | // 更新设计模式当前tab
8 | '$TAB_UPDATE',
9 | // 更新可用widgets
10 | '$WIDGETS_SET',
11 | // 设置全局schema,
12 | '$ROOT_SCHEMA_SET',
13 | // 拍平root schema
14 | '$ROOT_SCHEMA_FLAT',
15 | // 用户数据设置,kv形式的对象
16 | '$MODEL_SET',
17 | // 切换表单展示模式
18 | '$MODE_CHANGE',
19 |
20 | // 添加逻辑,值逻辑或事件逻辑
21 | '$LOGIC_ADD',
22 | // 更新逻辑
23 | '$LOGIC_UPDATE',
24 | // 删除逻辑
25 | '$LOGIC_DELETE',
26 |
27 | // 规则初始化
28 | '$RULE_INIT',
29 | // 更新widget规则类型
30 | '$RULE_TYPE_UPDATE',
31 | // 是否必填规则更新
32 | '$RULE_REQUIRED_RULE_UPDATE',
33 | // 更新widget规则错误消息
34 | '$RULE_MESSAGE_UPDATE',
35 | // 添加widget规则
36 | '$RULE_ADD',
37 | // 删除widget规则
38 | '$RULE_REMOVE',
39 |
40 | // 选中store中dict
41 | '$STORE_DICT_SELECT',
42 | // 更新store中dict
43 | '$STORE_DICT_UPDATE',
44 | // 添加store中dict
45 | '$STORE_DICT_ADD',
46 | // 删除store中dict
47 | '$STORE_DICT_DELETE',
48 | // 复制store中dict
49 | '$STORE_DICT_COPY',
50 | // 选中store中api
51 | '$STORE_API_SELECT',
52 | // 更新store中api
53 | '$STORE_API_UPDATE',
54 | // 添加store中api
55 | '$STORE_API_ADD',
56 | // 删除store中api
57 | '$STORE_API_DELETE',
58 | // 复制store中api
59 | '$STORE_API_COPY',
60 |
61 | // 更新widget默认属性
62 | '$WIDGET_DEFAULT_PROPS_UPDATE',
63 | // 通过值逻辑关系触发的widget属性改变
64 | '$WIDGET_UPDATE_BY_VALUE_LOGIC',
65 | // 通过事件逻辑关系触发的widget属性改变
66 | '$WIDGET_UPDATE_BY_EVENT_LOGIC',
67 | // 添加widget
68 | '$WIDGET_ADD',
69 | // 删除widget
70 | '$WIDGET_REMOVE',
71 | // 用户动态添加widget
72 | '$WIDGET_DYNAMIC_ADD',
73 | // 用户动态移除widget
74 | '$WIDGET_DYNAMIC_REMOVE',
75 | // 复制widget
76 | '$WIDGET_COPY',
77 | // 更改widget值类型
78 | '$WIDGET_TYPE_UPDATE',
79 | // 设置widget option
80 | '$WIDGET_OPTION_UPDATE',
81 | // 更新表单类型widget默认值
82 | '$WIDGET_DEFAULT_UPDATE',
83 | // 选中widget
84 | '$WIDGET_SELECT',
85 | // 取消选中widget
86 | '$WIDGET_DESELECT',
87 |
88 | // 容器widget添加子容器
89 | '$WIDGET_CHILD_ADD',
90 | // 容器widget删除子容器
91 | '$WIDGET_CHILD_REMOVE',
92 | // 容器widget移动子容器
93 | '$WIDGET_CHILD_MOVE',
94 |
95 | // 更新全局样式
96 | '$STYLE_UPDATE'
97 | ]
98 |
99 | list.forEach(i => (types[i] = i))
100 |
101 | export default types
102 |
--------------------------------------------------------------------------------
/script/webpack.build.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const merge = require('webpack-merge')
4 | const CleanWebpackPlugin = require('clean-webpack-plugin')
5 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
6 | const webpackBaseConfig = require('./webpack.base.js')
7 | const pkg = require('../package.json')
8 |
9 | const banner = `epage-core v${pkg.version}
10 | (c) 2020-present Chengzi
11 | Released under the MIT License.`
12 |
13 | const webpackConfig = merge(webpackBaseConfig, {
14 | mode: 'production',
15 | entry: {
16 | 'epage-core': './src/main.js'
17 | },
18 | output: {
19 | path: path.resolve(__dirname, '../dist'),
20 | publicPath: '',
21 | filename: '[name].min.js',
22 | library: 'EpageCore',
23 | libraryTarget: 'umd',
24 | umdNamedDefine: true
25 | },
26 | externals: {
27 | react: {
28 | root: 'React',
29 | commonjs: 'react',
30 | commonjs2: 'react',
31 | amd: 'react'
32 | },
33 | 'react-dom': {
34 | root: 'ReactDOM',
35 | commonjs: 'react-dom',
36 | commonjs2: 'react-dom',
37 | amd: 'react-dom'
38 | },
39 | vuex: {
40 | root: 'Vuex',
41 | commonjs: 'vuex',
42 | commonjs2: 'vuex',
43 | amd: 'vuex'
44 | },
45 | vue: {
46 | root: 'Vue',
47 | commonjs: 'vue',
48 | commonjs2: 'vue',
49 | amd: 'vue'
50 | },
51 | sortablejs: {
52 | root: 'Sortable',
53 | commonjs: 'sortablejs',
54 | commonjs2: 'sortablejs',
55 | amd: 'sortablejs'
56 | },
57 | vuedraggable: {
58 | root: 'vuedraggable',
59 | commonjs: 'vuedraggable',
60 | commonjs2: 'vuedraggable',
61 | amd: 'vuedraggable'
62 | }
63 | },
64 | // devtool: 'source-map',
65 | optimization: {
66 | minimizer: [new UglifyJsPlugin({
67 | parallel: true,
68 | sourceMap: false,
69 | uglifyOptions: {
70 | ecma: 8,
71 | warnings: false
72 | }
73 | })]
74 | },
75 | plugins: [
76 | new CleanWebpackPlugin({
77 | cleanOnceBeforeBuildPatterns: path.resolve(__dirname, '../dist')
78 | }),
79 | new webpack.BannerPlugin(banner),
80 | new webpack.optimize.ModuleConcatenationPlugin()
81 | ]
82 | })
83 |
84 | if (process.env.npm_config_report) {
85 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
86 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
87 | }
88 |
89 | module.exports = webpackConfig
90 |
--------------------------------------------------------------------------------
/src/render/VueRender.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import Rule from '../rule'
4 | import Store from '../store'
5 | import {
6 | isArray,
7 | isFunction,
8 | isPlainObject,
9 | usePlugins
10 | } from '../helper'
11 |
12 | export default class Render {
13 | constructor (option) {
14 | const {
15 | el,
16 | store,
17 | widgets = [],
18 | mode,
19 | schema,
20 | component,
21 | Rule: CustomRule,
22 | callPlugin
23 | } = option
24 |
25 | this.el = el
26 | this.mode = mode || 'edit'
27 | this.$$origin = null
28 | this.store = null
29 | // 优先自定义组件渲染
30 | this.component = component || null
31 | this.callPlugin = callPlugin || (() => 0)
32 | usePlugins(Vue, [Vuex])
33 | this.callPlugin('render', 'init', { Vue, ctx: this })
34 | // 设计模式下,状态共享epage设计器内store
35 | if (store) {
36 | this.store = store
37 | this.$$origin = this.render()
38 | this.callPlugin('render', 'created', { ctx: this })
39 | } else {
40 | this.store = new Store({ Rule: CustomRule || Rule })
41 | if (isArray(widgets)) {
42 | this.store.initWidgets(widgets)
43 | if (isPlainObject(schema)) {
44 | this.store.initRootSchema(schema)
45 | }
46 | this.$$origin = this.render()
47 | this.callPlugin('render', 'created', { ctx: this })
48 | } else {
49 | console.error('widgets must be an array')
50 | }
51 | }
52 | this.on = component.on
53 | this.off = component.off
54 | }
55 |
56 | validateFields () {
57 | const { $children } = this.$$origin
58 |
59 | if (isArray($children) && $children[0]) {
60 | return $children[0].validateFields(...arguments)
61 | }
62 | }
63 |
64 | resetFields () {
65 | const { $children } = this.$$origin
66 |
67 | if (isArray($children) && $children[0]) {
68 | return new Promise((resolve) => {
69 | setTimeout(() => {
70 | resolve($children[0].resetFields())
71 | }, 0)
72 | })
73 | }
74 | }
75 |
76 | render (option = {}) {
77 | const { el, store, mode, component } = this
78 | const extension = { store, $render: this, mode: option.mode || mode }
79 | const root = document.createElement('div')
80 |
81 | el.appendChild(root)
82 | this.callPlugin('render', 'beforeCreate', { ctx: this })
83 |
84 | const ins = new Vue({
85 | extension,
86 | el: root,
87 | render: h => h(component)
88 | })
89 |
90 | return ins
91 | }
92 |
93 | destroy () {
94 | if (this.$$origin && isFunction(this.$$origin.$destroy)) {
95 | this.callPlugin('render', 'beforeDestroy', { ctx: this })
96 | this.$$origin.$destroy()
97 | this.$$origin.$off()
98 | if (!this.el.contains(this.$$origin.$el)) return
99 | this.el.removeChild(this.$$origin.$el)
100 | this.callPlugin('render', 'destroyed', { ctx: this })
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/render/ReactRender.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import Rule from '../rule'
4 | import Store from '../store'
5 | import {
6 | isArray,
7 | isFunction,
8 | isPlainObject,
9 | usePlugins
10 | } from '../helper'
11 |
12 | export default class Render {
13 | constructor (option) {
14 | const {
15 | el,
16 | store,
17 | widgets = [],
18 | mode,
19 | schema,
20 | component,
21 | Rule: CustomRule,
22 | callPlugin
23 | } = option
24 |
25 | this.el = el
26 | this.mode = mode || 'edit'
27 | this.$$origin = null
28 | this.store = null
29 | this.component = component
30 | this.callPlugin = callPlugin || (() => 0)
31 |
32 | const React = require('react').default
33 | this.formRef = React.createRef()
34 |
35 | usePlugins(Vue, [Vuex])
36 | this.callPlugin('render', 'init', { Vue, ctx: this })
37 |
38 | if (store) {
39 | this.store = store
40 | this.$$origin = this.render()
41 | } else {
42 | this.store = new Store({ Rule: CustomRule || Rule })
43 | if (isArray(widgets)) {
44 | this.store.initWidgets(widgets)
45 | if (isPlainObject(schema)) {
46 | this.store.initRootSchema(schema)
47 | }
48 | this.$$origin = this.render()
49 | } else {
50 | console.error('widgets must be an array')
51 | }
52 | }
53 | this.on = component.on
54 | this.off = component.off
55 | }
56 |
57 | validateFields () {
58 | return new Promise((resolve, reject) => {
59 | return this.formRef.current
60 | .validateFields(...arguments)
61 | .then(values => {
62 | resolve(true)
63 | })
64 | .catch(err => {
65 | resolve({ err })
66 | })
67 | })
68 | }
69 |
70 | resetFields () {
71 | return new Promise((resolve, reject) => {
72 | this.formRef.current.resetFields()
73 | resolve(true)
74 | })
75 | }
76 |
77 | render (option = {}) {
78 | const { el, store, mode, formRef, component } = this
79 | const props = {
80 | store,
81 | mode: option.mode || mode,
82 | formRef,
83 | onSubmit: this.onSubmit
84 | }
85 | this.callPlugin('render', 'beforeCreate', { ctx: this })
86 | const ReactDOM = require('react-dom').default
87 | const React = require('react').default
88 | const ins = ReactDOM.render(
89 | React.createElement(component, { ...props }),
90 | el
91 | )
92 | this.callPlugin('render', 'created', { ctx: this, ins })
93 | return ins
94 | }
95 |
96 | destrory () {
97 | const ReactDOM = require('react-dom').default
98 | if (this.$$origin && isFunction(this.$$origin.$destroy)) {
99 | this.callPlugin('render', 'beforeDestroy', { ctx: this })
100 | ReactDOM.unmountComponentAtNode(this.el)
101 | // this.$$origin.$off()
102 | // if(!this.el.contains(this.$$origin.$el)) return
103 | // this.el.removeChild(this.$$origin.$el)
104 | this.callPlugin('render', 'destroyed', { ctx: this })
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ### 0.5.6(2021/4/29)
4 |
5 | - [fix] : 修复根据schema添加widget时name被重置
6 |
7 | ### 0.5.5(2021/4/14)
8 |
9 | - [feat] : 增加基础表单组件`Schema`的`change`事件暴露,可通过逻辑面板配置`change`逻辑
10 | - [fix] : 修复逻辑配置无法选择widget问题
11 |
12 | ### 0.5.4(2021/4/6)
13 |
14 | - [fix] : 修复插件使用`copy`勾子复制schema未生效问题
15 | - [fix] : 渲染器生命周期`created`位置不正确问题
16 |
17 | ### 0.5.3(2021/4/2)
18 |
19 | - [fix] : 修复`store.addWidget()`后更新`store.updateWidgetOption(rootSchema.key, {})`选项不生效问题
20 |
21 |
22 | ### 0.5.2(2021/4/1)
23 |
24 | - [feat] : `store.addWidget(widget)` 优化,widget可以为如`input`形式字符串,也可以为`{ widget: 'input', ...}` 完整schema
25 | - [feat] : `util.flattenSchema` 导出增加对象增加 `rootSchema`引用。可以通过`store.updateWidgetOption(rootSchema.key, {...})`更新`rootSchema.option`选项
26 |
27 | ### 0.5.1(2021/2/21)
28 |
29 | - [fix] : 添加`vuedraggable`依赖,作为拖拽能力
30 |
31 | ### 0.5.0(2021/2/20)
32 |
33 | - [feat] : 对外暴露`drag`模块,当前支持vue模块拖动,为自定义容器组件提供能力,依赖`vuedraggable`
34 | - [feat] : 对外暴露`hook`模块,定制设计器及渲染器时可使用,一般用于设计器插件中,可参考`epage`设计器hook使用,能力如下:
35 |
36 | ```js
37 | const syncHook = new hook.SyncHook() // 同步串行任务,不接受上一个任务结果
38 | syncHook.tap(callback) // 将callback添加到同步任务队列
39 | syncHook.call(args) // 将arguments分别传给任务队列的callback中
40 |
41 | const syncWaterHook = new hook.SyncWaterfallHook() // 同步串行任务,接受上一个任务结果
42 | // 用法同上,只是会将每个任务结果作为参数传给下个任务
43 | ```
44 | - [feat] : 对外暴露`render`模块,直接提供`render.VueRender`,实验阶段的`ReactRender`可通过以下方式引入
45 |
46 | ```js
47 | import ReactRender from 'epage-core/src/render/ReactRender'
48 | ```
49 | 方式
50 | -
51 | ### 0.4.1(2021/1/18)
52 |
53 | - [fix] : 修复widget被二次添加时,widget.Setting生命周期没有再次使用问题
54 |
55 | ### 0.4.0(2020/12/31)
56 |
57 | - [feat] : 开放单一widget样式配置,自定义高级背景配置
58 | - [feat] : 新增`text` widget能力,支持`{{$f[schema.name]]}}`表达式运算
59 |
60 | ### 0.3.0(2020/11/04)
61 |
62 | - [feat] : 增加整体页面配置(背景色、间距等),对外暴露`style`模块,目前包含`{ Background }`
63 | - [feat] : 字典能力补充,未发版
64 |
65 | ### 0.2.1~0.2.2(2020/10/20)
66 |
67 | - [feat] : 逻辑关系中,被控组件存在多值时,增加值的 `或`、`且` 关系
68 | - [feat] : 逻辑关系比较增加值类型条件
69 | - [fix] : 修复 `cascader` 值类型转换未递归问题
70 | - [fix] : 多个逻辑关系,改变值时出现未知现象,添加容错
71 |
72 |
73 | ### 0.2.0(2020/10/12)
74 |
75 | - [feat] : 增加widget显隐属性默认值,逻辑配置面板,不符合条件时回退到默认值
76 | - [feat] : 逻辑配置面板,可配置自定义脚本,可直接使用ctx全局变量,关于ctx可参考button的[ctx](http://epage.didichuxing.com/examples/widgets/button.html#schema-option%E5%AE%9A%E4%B9%89)
77 |
78 | ### 0.1.8(2020/09/22)
79 |
80 | - [fix] : 修复包内部import依赖大小写问题 [epage#8](https://github.com/didi/epage/issues/8)
81 |
82 | ### 0.1.7(2020/08/18)
83 |
84 | - [fix] : 修复上个版本增加`json`类型忽略`undefined`情况导致渲染报错
85 |
86 | ### 0.1.6(2020/08/17)
87 |
88 | - [feat] : 增加`json`表单类型校验
89 |
90 | ### 0.1.5(2020/08/10)
91 |
92 | - [feat] : 增加`Context`及`Scirpt`模块,方便在自定义脚本中增加context环境
93 |
94 | ```js
95 | import { Context, Script } from 'epage-core'
96 | const { $render } = this.$root.$options.extension
97 | const { script } = this.schema.option
98 | const ctx = new Context({
99 | $el,
100 | $render,
101 | store,
102 | instance: this,
103 | state: {
104 | loading: this.loading
105 | }
106 | })
107 | const sc = new Script(ctx)
108 | sc.exec(script)
109 | ```
110 |
111 |
112 | ### 0.1.4(2020/08/01)
113 |
114 | - [feat] : 添加基础widget默认值
115 |
116 | ### 0.1.3(2020/07/22)
117 |
118 | - [feat] : 添加 `schema.base.RootSchema` 模块默认样式属性
119 |
120 | ### 0.1.2
121 |
122 | - [feat] : 添加 `schema.base` 子模块导出
123 |
124 | ```js
125 | import { schema } from 'epage-core'
126 |
127 | console.log(schema.base)
128 | // { input: Schema, select: Schema, ...}
129 | ```
130 |
131 | ### 0.1.1
132 |
133 | - [feat] : 添加 `TypeBuilder` 导出
134 |
135 | ### 0.1.0
136 |
137 | - [feat] : 从 [epage](https://github.com/didi/epage) 项目抽离核心项目,作为设计器(epage)及渲染器(如epage-iview)的核心依赖
--------------------------------------------------------------------------------
/src/store/mutations/schema.js:
--------------------------------------------------------------------------------
1 | import types from '../types'
2 | import RootSchema from '../../schema/RootSchema'
3 | import API from '../API'
4 | import Dict from '../Dict'
5 | import TypeBuilder from '../TypeBuilder'
6 | import {
7 | flattenSchema,
8 | jsonClone,
9 | cleanDefaultValue,
10 | isArray,
11 | getWidgetType,
12 | getWidgetModel
13 | } from '../../helper'
14 | const typeBuilder = new TypeBuilder()
15 |
16 | export default {
17 | [types.$ROOT_SCHEMA_FLAT] (state, { rootSchema }) {
18 | state.rootSchema = rootSchema
19 | state.flatSchemas = flattenSchema(rootSchema)
20 | },
21 |
22 | // 添加全局schema,会重新修改store中model数据
23 | [types.$ROOT_SCHEMA_SET] (state, { rootSchema }) {
24 | const { flatWidgets } = this.getters
25 | const model = {} // 最终值
26 | const defaultModel = {} // 根据schema实例定义的默认值
27 | const _rootSchema = new RootSchema({ schema: rootSchema, widgets: flatWidgets })
28 |
29 | // fix logic field
30 | _rootSchema.logics.map(logic => {
31 | logic.trigger = logic.trigger || 'prop'
32 | logic.script = logic.script || ''
33 | logic.relation = logic.relation || 'or'
34 | })
35 |
36 | // 初始化 model
37 | state.rootSchema = Object.assign({}, state.rootSchema, _rootSchema)
38 | this.commit(types.$ROOT_SCHEMA_FLAT, { rootSchema: state.rootSchema })
39 |
40 | // 初始化必要的api数据
41 | const usedDictAPI = {}
42 | for (const key in state.flatSchemas) {
43 | const opt = state.flatSchemas[key].option || {}
44 | if (
45 | opt.type === 'dict' &&
46 | opt.dict &&
47 | opt.dict.dict &&
48 | opt.dict.type === 'dict' &&
49 | opt.dict.dictAPI
50 | ) {
51 | const dictName = opt.dict.dict
52 | if (usedDictAPI[dictName]) {
53 | if (usedDictAPI[dictName].indexOf(opt.dict.dictAPI) === -1) {
54 | usedDictAPI[dictName].push(opt.dict.dictAPI)
55 | }
56 | } else {
57 | usedDictAPI[dictName] = [opt.dict.dictAPI]
58 | }
59 | }
60 | }
61 |
62 | // 初始化schema store
63 | const schemaStore = jsonClone(rootSchema.store || {})
64 | schemaStore.current = { type: '', dict: {}, api: {} }
65 | schemaStore.apis = (schemaStore.apis || []).map(api => {
66 | const ins = new API(api)
67 | ins.getData()
68 | return ins
69 | })
70 | schemaStore.dicts = (schemaStore.dicts || []).map(dict => {
71 | const ins = new Dict(dict)
72 | ins.getData().then(() => {
73 | if (!isArray(usedDictAPI[ins.name])) return
74 | usedDictAPI[ins.name].forEach(dictAPI => {
75 | for (let i = 0; i < ins.data.length; i++) {
76 | const item = ins.data[i]
77 | if (item.name !== dictAPI) continue
78 | const apiIns = item instanceof API ? item : new API(item)
79 | apiIns.getData()
80 | ins.data.splice(i, 1, apiIns)
81 | }
82 | })
83 | })
84 | return ins
85 | })
86 |
87 | state.store = schemaStore
88 |
89 | this.commit(types.$RULE_INIT, { rootSchema: state.rootSchema })
90 |
91 | // 遍历schema获取model默认值
92 | for (const i in state.flatSchemas) {
93 | const schema = state.flatSchemas[i]
94 | // 特殊默认值需要处理,尤其动态时间值
95 | defaultModel[i] = cleanDefaultValue(schema)
96 | if (!(i in state.model)) {
97 | // 容器widget不保留model值
98 | if (schema.container) {
99 | continue
100 | }
101 | const type = getWidgetType(flatWidgets, schema.widget)
102 | const widgetModel = getWidgetModel(type, schema, typeBuilder) // 缺省值
103 |
104 | Object.assign(model, widgetModel, defaultModel)
105 | }
106 | }
107 | this.commit(types.$MODEL_SET, { model })
108 | this.commit(types.$WIDGET_DEFAULT_PROPS_UPDATE)
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/store/StoreConf.js:
--------------------------------------------------------------------------------
1 | import types from './types'
2 | import { defaultSchema } from '../constant'
3 | import RootSchema from '../schema/RootSchema'
4 | import {
5 | isFunction,
6 | isArray
7 | } from '../helper'
8 | /* eslint no-unused-vars: 0 */
9 | import Rule from '../rule'
10 | import mutations from './mutations'
11 |
12 | const rootSchema = new RootSchema()
13 | const selectedSchema = defaultSchema()
14 |
15 | export default class StoreConf {
16 | constructor (option) {
17 | const { Rule } = option || {}
18 | return {
19 | state: {
20 | // 设计模式下当前tab,可选 design | preview
21 | tab: '',
22 | mode: 'edit',
23 | selectedSchema,
24 | rootSchema,
25 | model: {},
26 | widgets: [],
27 | // 拍平所有schema
28 | flatSchemas: {},
29 | // 拍平所有规则
30 | flatRules: {},
31 | // 运行时状态
32 | // { [key]: { hide: false, readonly: false }}
33 | defaults: {},
34 | store: {
35 | baseURL: '',
36 | current: {
37 | // 当前 new | static | dynamic
38 | // type: 'static',
39 | type: '', // dict | api 表示当前选择的类型
40 | dict: {
41 | index: -1, // selected dict index
42 | value: {}
43 | },
44 | api: {
45 | index: -1, // selected api index
46 | value: {}
47 | }
48 | },
49 | dicts: [],
50 | apis: []
51 | }
52 | },
53 | getters: {
54 | isSelected: state => !!state.selectedSchema.key,
55 | flatWidgets: ({ widgets }) => {
56 | const serialized = {}
57 | if (!isArray(widgets)) {
58 | return serialized
59 | }
60 | widgets.forEach(item => {
61 | if (!isArray(item.widgets)) {
62 | return
63 | }
64 | item.widgets.forEach(w => {
65 | if (isFunction(w.Schema)) {
66 | serialized[w.Schema.widget] = w
67 | }
68 | })
69 | })
70 | return serialized
71 | },
72 |
73 | settingWidget: (state, getters) => {
74 | const { widget: widgetName, key } = state.selectedSchema
75 | const widget = getters.flatWidgets[widgetName] || null
76 |
77 | if (!key || !widget || !widget.Setting) return null
78 |
79 | return isArray(widget.Setting)
80 | ? [...widget.Setting]
81 | : { ...widget.Setting }
82 | },
83 |
84 | // widget schema 声明需要哪些规则validator
85 | widgetsValidators: (state, { flatWidgets }) => {
86 | const map = {}
87 | for (const i in flatWidgets) {
88 | const { Schema } = flatWidgets[i]
89 | if (!isFunction(Schema) || !isArray(Schema.validators)) {
90 | continue
91 | }
92 | map[i] = []
93 | Schema.validators.forEach(validator => {
94 | const val = Rule.rules[validator]
95 |
96 | if (!val) {
97 | return console.log(`validator ${validator} is not exist`)
98 | }
99 | map[i].push(val)
100 | })
101 | }
102 | return map
103 | }
104 | },
105 | mutations: {
106 | [types.$TAB_UPDATE] (state, { tab }) {
107 | if (tab && typeof tab === 'string') {
108 | state.tab = tab
109 | }
110 | },
111 |
112 | // 添加表单展示模式
113 | [types.$MODE_CHANGE] (state, { mode }) {
114 | state.mode = mode
115 | },
116 |
117 | ...mutations.model,
118 | ...mutations.schema,
119 | ...mutations.widget,
120 | ...mutations.rule,
121 | ...mutations.logic,
122 | ...mutations.store,
123 | ...mutations.style
124 | },
125 | actions: {},
126 | modules: {}
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/logic/Logic.js:
--------------------------------------------------------------------------------
1 | import EventLogic from './EventLogic'
2 | import ValueLogic from './ValueLogic'
3 |
4 | export default class Logic {
5 | constructor (defaults) {
6 | this.defaults = defaults || {}
7 | this.map = {
8 | event: new EventLogic(),
9 | value: new ValueLogic()
10 | }
11 | }
12 |
13 | /**
14 | * compare the difference between logic value and model value
15 | * @param {Array} valueLogics list for value logics
16 | * @param {Object} model form data
17 | */
18 | diffValueLogics (valueLogics, model, valueTypes) {
19 | const patches = []
20 | const scripts = []
21 |
22 | for (let j = 0; j < valueLogics.length; j++) {
23 | const logic = valueLogics[j]
24 | const valueType = valueTypes[logic.key]
25 | const valueValidator = this.map.value.map[logic.action]
26 | if (!valueValidator) continue
27 |
28 | const validation = valueValidator.validator(model[logic.key], logic.value, { logic, valueType })
29 |
30 | if (!validation) continue
31 | // should be the same key
32 | if (!(logic.key in model)) continue
33 |
34 | /* eslint no-case-declarations: 0 */
35 | switch (logic.trigger) {
36 | case 'script':
37 | logic.script && scripts.push(logic.script)
38 | break
39 | case 'prop':
40 | const patch = {}
41 |
42 | for (let k = 0; k < logic.effects.length; k++) {
43 | const effect = logic.effects[k]
44 | const props = {}
45 |
46 | if (!this.checkEffect(effect, logic.key)) continue
47 |
48 | effect.properties.forEach(_ => {
49 | props[_.key] = _.value
50 | })
51 | patch[effect.key] = props
52 | }
53 | Object.keys(patch).length && patches.push(patch)
54 | break
55 | default:
56 | break
57 | }
58 | }
59 | return { patches, scripts }
60 | }
61 |
62 | /**
63 | * compare the difference between logic event and real event
64 | * @param {Array} eventLogics list for value logics
65 | * @param {String} eventType event type like on-click. should start with on-
66 | */
67 | diffEventLogics (eventLogics, eventType) {
68 | const patches = []
69 | const scripts = []
70 |
71 | for (let i = 0; i < eventLogics.length; i++) {
72 | const logic = eventLogics[i]
73 |
74 | /* eslint no-case-declarations: 0 */
75 | switch (logic.trigger) {
76 | case 'script':
77 | logic.script && scripts.push(logic.script)
78 | break
79 | case 'prop':
80 | const patch = {}
81 | // get event type: on-click => click
82 | if (logic.action !== eventType.slice(3)) continue
83 |
84 | for (let j = 0; j < logic.effects.length; j++) {
85 | const effect = logic.effects[j]
86 | const props = {}
87 |
88 | if (!this.checkEffect(effect, logic.key)) continue
89 |
90 | effect.properties.forEach(_ => {
91 | props[_.key] = _.value
92 | })
93 | patch[effect.key] = props
94 | }
95 |
96 | Object.keys(patch).length && patches.push(patch)
97 | break
98 | default:
99 | break
100 | }
101 | }
102 |
103 | return { patches, scripts }
104 | }
105 |
106 | /**
107 | * apply properties to schema
108 | * @param {Object} flatSchemas flated schema like { 'ksddf': { widget: 'input', ...}}
109 | * @param {Array} patches properties that should be updated
110 | */
111 | applyPatches (flatSchemas, patches = [], controlledKeys) {
112 | const propsMerged = {}
113 | patches.forEach(patch => {
114 | for (const key in patch) {
115 | if (!(key in flatSchemas)) continue
116 | propsMerged[key] = propsMerged[key] || {}
117 | Object.assign(propsMerged[key], patch[key])
118 | }
119 | })
120 | const controlledDefaults = {}
121 | controlledKeys.forEach(key => {
122 | if (!(key in this.defaults)) return
123 | controlledDefaults[key] = this.defaults[key]
124 | })
125 | const result = Object.assign({}, controlledDefaults, propsMerged)
126 | for (const key in result) {
127 | Object.assign(flatSchemas[key], controlledDefaults[key], result[key])
128 | }
129 | }
130 |
131 | /**
132 | * check the validity of the effect
133 | * @param {Object} effect affected properties
134 | * @param {String} key schema key
135 | */
136 | checkEffect (effect, key) {
137 | return effect.key && key !== effect.key && Array.isArray(effect.properties)
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/store/mutations/store.js:
--------------------------------------------------------------------------------
1 | import types from '../types'
2 | import Dict from '../Dict'
3 | import API from '../API'
4 | import {
5 | jsonClone,
6 | storeFetchClean
7 | } from '../../helper'
8 |
9 | export default {
10 | // 选中STORE中dict
11 | [types.$STORE_DICT_SELECT] ({ store }, { dict, index, action }) {
12 | store.current.dict.value = jsonClone(dict)
13 | store.current.dict.index = index
14 | store.current.dict.action = action
15 | store.current.type = 'dict'
16 | store.current.api = {}
17 | },
18 |
19 | // 更新dict
20 | [types.$STORE_DICT_UPDATE] ({ store, rootSchema }, { dict, index }) {
21 | const instance = new Dict(jsonClone(dict))
22 | instance.getData()
23 | store.dicts.splice(index, 1, instance)
24 | rootSchema.store.dicts = storeFetchClean(store.dicts)
25 | },
26 |
27 | // 添加dict
28 | [types.$STORE_DICT_ADD] ({ store, rootSchema }, { dict }) {
29 | const matched = store.dicts.filter(item => item.name === dict.name)
30 | if (matched.length === 0) {
31 | const instance = new Dict(jsonClone(dict))
32 | instance.getData()
33 | store.dicts.push(instance)
34 | store.current.dict.index = store.dicts.length - 1
35 | store.current.dict.action = 'update'
36 | rootSchema.store.dicts = storeFetchClean(store.dicts)
37 | }
38 | },
39 |
40 | // 更新dict
41 | [types.$STORE_DICT_DELETE] ({ store, rootSchema }, { index }) {
42 | const currentIndex = store.current.dict.index
43 | if (currentIndex === index) {
44 | store.current.type = ''
45 | store.current.dict = {
46 | index: -1,
47 | value: {},
48 | action: ''
49 | }
50 | } else if (currentIndex > index) {
51 | store.current.dict.index--
52 | }
53 | store.dicts.splice(index, 1)
54 | rootSchema.store.dicts = storeFetchClean(store.dicts)
55 | },
56 | // 复制dict
57 | [types.$STORE_DICT_COPY] ({ store, rootSchema }, { index }) {
58 | const dict = jsonClone(store.dicts[index] || {})
59 | do {
60 | dict.name += '_copy'
61 | } while (store.dicts.filter(item => item.name === dict.name).length > 0)
62 |
63 | const newDict = new Dict(dict)
64 | newDict.getData()
65 | store.dicts.splice(index + 1, 0, newDict)
66 | rootSchema.store.dicts = storeFetchClean(store.dicts)
67 | },
68 | // 选中STORE中api
69 | [types.$STORE_API_SELECT] ({ store }, { api, index, dictIndex }) {
70 | const action = dictIndex >= 0
71 | ? ''
72 | : (
73 | index >= 0
74 | ? 'update'
75 | : 'create'
76 | )
77 | store.current.api = {
78 | value: jsonClone(api),
79 | index,
80 | action
81 | }
82 | store.current.type = 'api'
83 | store.current.dict = { index: dictIndex }
84 | },
85 |
86 | // 更新api
87 | [types.$STORE_API_UPDATE] ({ store, rootSchema }, { api, index }) {
88 | const instance = new API(jsonClone(api))
89 | instance.getData()
90 | store.apis.splice(index, 1, instance)
91 | rootSchema.store.apis = storeFetchClean(store.apis)
92 | },
93 |
94 | // 添加api
95 | [types.$STORE_API_ADD] ({ store, rootSchema }, { api }) {
96 | const matched = store.apis.filter(item => item.name === api.name)
97 | if (matched.length === 0) {
98 | const instance = new API(jsonClone(api))
99 | instance.getData()
100 | store.apis.push(instance)
101 | store.current.api.index = store.apis.length - 1
102 | store.current.api.action = 'update'
103 | store.current.dict = {
104 | index: -1,
105 | value: {},
106 | action: ''
107 | }
108 | rootSchema.store.apis = storeFetchClean(store.apis)
109 | }
110 | },
111 |
112 | // 删除api
113 | [types.$STORE_API_DELETE] ({ store, rootSchema }, { index }) {
114 | const currentIndex = store.current.api.index
115 | if (currentIndex === index) {
116 | store.current.type = ''
117 | store.current.api = {
118 | index: -1,
119 | value: {},
120 | action: ''
121 | }
122 | } else if (currentIndex > index) {
123 | store.current.api.index--
124 | }
125 | store.apis.splice(index, 1)
126 | rootSchema.store.apis = storeFetchClean(store.apis)
127 | },
128 | // 复制api
129 | [types.$STORE_API_COPY] ({ store, rootSchema }, { index }) {
130 | const api = jsonClone(store.apis[index] || {})
131 | do {
132 | api.name += '_copy'
133 | } while (store.apis.filter(item => item.name === api.name).length > 0)
134 |
135 | const newAPI = new API(api)
136 | newAPI.getData()
137 | store.apis.splice(index + 1, 0, newAPI)
138 | rootSchema.store.apis = storeFetchClean(store.apis)
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/store/Fetch.js:
--------------------------------------------------------------------------------
1 | const defaultKV = () => [{ key: '', value: '', description: '' }]
2 | export default class Fetch {
3 | constructor (props) {
4 | const {
5 | name = '',
6 | desc = '',
7 | header = defaultKV(),
8 | body = '{}',
9 | query = defaultKV(),
10 | params = defaultKV(),
11 | url = '',
12 | method = 'GET',
13 | adapter = ''
14 | } = props || {}
15 |
16 | this.name = name
17 | this.desc = desc
18 | this.header = header
19 | this.body = body
20 | this.query = query
21 | this.params = params
22 | // /:userId/test 其中userId为 form name
23 | this.url = url
24 | this.method = method
25 | this.adapter = adapter
26 | this.source = []
27 | this.data = []
28 | this.format = 'array'
29 | this.response = {
30 | header: []
31 | }
32 | }
33 |
34 | getData () {
35 | const { url, body, header = [], method, adapter } = this
36 | if (!url) return Promise.reject(new Error('invalid url!'))
37 |
38 | const urlObj = this.resolveURL(url, this.params)
39 | if (!urlObj.valid) return Promise.reject(urlObj.message)
40 |
41 | let queryString = this.joinQuery()
42 | let resolvedURL = urlObj.url
43 | const headers = {}
44 | header
45 | .filter(h => h.key && h.value)
46 | .forEach(h => { headers[h.key] = h.value })
47 |
48 | const option = { headers, method }
49 | const METHOD = method.toUpperCase()
50 | const withBodyMethods = ['POST', 'PUT']
51 |
52 | if (withBodyMethods.indexOf(METHOD) > -1) {
53 | option.body = body
54 | }
55 | if (urlObj.widthQuery) {
56 | resolvedURL += queryString
57 | } else {
58 | queryString = queryString.slice(1)
59 | resolvedURL += `?${queryString}`
60 | }
61 |
62 | return fetch(resolvedURL, option).then(res => {
63 | const header = []
64 | res.headers.forEach((value, key) => header.push({ key, value }))
65 | this.response.header = header
66 | /* eslint no-useless-catch: 0 */
67 | try {
68 | return res.json()
69 | } catch (err) {
70 | throw err
71 | }
72 | }).then(res => {
73 | const _adapter = res.adapter || adapter
74 | /* eslint no-new-func: 0 */
75 | const data = _adapter ? new Function('data', _adapter)(res) : res
76 | this.source = res
77 | this.data = data
78 | this.format = this.getFormat(data)
79 | return res
80 | })
81 | }
82 |
83 | resolveURL (url = '', model = []) {
84 | const names = (url.match(/\/:[^:\s/?&]+/g) || []).map(_ => _.split(':')[1])
85 | const result = {
86 | valid: true,
87 | message: '',
88 | widthQuery: false,
89 | url
90 | }
91 | const isValidURL = names.filter(name => {
92 | const tmp = model.filter(m => m.key === name)[0]
93 | if (!tmp) return false
94 |
95 | const type = typeof tmp.value
96 |
97 | return tmp.value !== '' && (type === 'string' || type === 'number' || type === 'boolean')
98 | }).length === names.length
99 |
100 | // let isValidURL = names.filter(name => {
101 | // const type = typeof model[name]
102 | // return name in model
103 | // && (type === 'string' || type === 'number' || type === 'boolean')
104 | // && model[name] !== ''
105 | // }).length === names.length
106 |
107 | if (!isValidURL) {
108 | result.valid = false
109 | result.message = 'invalid url params!'
110 | return result
111 | }
112 | names.forEach(name => {
113 | const tmp = model.filter(m => m.key === name)[0]
114 | if (!tmp) return
115 | /* eslint no-useless-escape: 0 */
116 | result.url = result.url.replace(new RegExp(`\B?:${name}\B?`, 'g'), tmp.value)
117 | })
118 | result.url = result.url.replace(/(&|\?)+$/, '')
119 | result.widthQuery = !!(result.url.indexOf('?') > -1)
120 | return result
121 | }
122 |
123 | joinQuery () {
124 | return (this.query || [])
125 | .filter(q => q.key && q.value)
126 | .map(q => `&${q.key}=${q.value}`)
127 | .join('')
128 | }
129 |
130 | getFormat (data, num = 2, loop = 1) {
131 | const type = Object.prototype.toString.call(data).split(' ')[1].split(']')[0].toLowerCase()
132 | let format = ''
133 | const space = new Array(num * loop + 1).join(' ')
134 |
135 | /* eslint no-case-declarations: 0 */
136 | switch (type) {
137 | case 'number':
138 | case 'boolean':
139 | case 'string':
140 | format = type
141 | break
142 | case 'object':
143 | format = type
144 | const keys = Object.keys(data)
145 | if (keys.length) {
146 | format += '{\n'
147 | const len = keys.length
148 | loop++
149 | keys.forEach((k, index) => {
150 | const sub = this.getFormat(data[k], num, loop)
151 | const isLast = index === len - 1
152 | const indentSpace = space.substr(0, space.length - 2)
153 | format += `${space}${k}: ${sub}`
154 | format += isLast ? `\n${indentSpace}}` : ',\n'
155 | })
156 | }
157 | break
158 | case 'array':
159 | format = type
160 | const sub = this.getFormat(data[0], num, loop)
161 | if (sub !== 'undefined') {
162 | format = `${format}<${sub}>`
163 | }
164 | break
165 | default:
166 | break
167 | }
168 | return format
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/src/logic/ValueLogic.js:
--------------------------------------------------------------------------------
1 | import Effect from './Effect'
2 |
3 | export default class ValueLogic {
4 | constructor () {
5 | const booleanMap = { true: true, false: false }
6 | function isSameArray (arr1, arr2, matchedType) {
7 | if (!Array.isArray(arr1)) return false
8 | if (!Array.isArray(arr2)) return false
9 | const _arr1 = arr1.map(v => matchedType === 'number' ? parseFloat(v) : v)
10 | const _arr2 = arr2.map(v => matchedType === 'number' ? parseFloat(v) : v)
11 |
12 | if (_arr1.length !== _arr2.length) return false
13 |
14 | let result = true
15 | for (let i = 0; i < _arr1.length; i++) {
16 | if (_arr1[i] !== _arr2[i]) {
17 | result = false
18 | break
19 | }
20 | }
21 | return result
22 | }
23 | const resolveArrayType = type => {
24 | const matched = (type || '').match(/^array(?:<(.+)>)?/)
25 | if (matched && matched[1]) {
26 | return matched[1]
27 | }
28 | }
29 | const resolveValue = (value, type) => {
30 | return (value || '')
31 | .split(',')
32 | .map(v => v.trim())
33 | .filter(_ => _)
34 | .map(v => {
35 | if (type === 'number') {
36 | v = parseFloat(v)
37 | } else if (type === 'boolean') {
38 | v = booleanMap[v]
39 | }
40 | return v
41 | })
42 | }
43 | const include = (arr = [], v) => arr.indexOf(v) > -1
44 | const relationFn = {
45 | or: (arr1, arr2, action) => {
46 | let result = false
47 | for (let i = 0; i < arr2.length; i++) {
48 | // 包含
49 | if (action === '<>') {
50 | if (include(arr1, arr2[i])) {
51 | result = true
52 | break
53 | }
54 | // 不包含
55 | } else if (action === '><') {
56 | if (!include(arr1, arr2[i])) {
57 | result = true
58 | break
59 | }
60 | }
61 | }
62 | return result
63 | },
64 | and: (arr1, arr2, action) => {
65 | let result = true
66 | for (let i = 0; i < arr2.length; i++) {
67 | // 包含
68 | if (action === '<>') {
69 | if (!include(arr1, arr2[i])) {
70 | result = false
71 | break
72 | }
73 | // 不包含
74 | } else if (action === '><') {
75 | if (include(arr1, arr2[i])) {
76 | result = false
77 | break
78 | }
79 | }
80 | }
81 | return result
82 | }
83 | }
84 | this.type = 'value'
85 | this.title = '逻辑'
86 | this.map = {
87 | '=': {
88 | key: '=',
89 | value: '等于',
90 | validator: (left, right, { valueType }) => {
91 | let leftValue = left
92 | let rightValue = right
93 | const matchedType = resolveArrayType(valueType)
94 |
95 | if (valueType === 'number') {
96 | leftValue = parseFloat(left)
97 | rightValue = parseFloat(right)
98 |
99 | return (isNaN(leftValue) || isNaN(leftValue)) ? false : leftValue === rightValue
100 | } else if (valueType === 'boolean') {
101 | if (right in booleanMap) {
102 | rightValue = booleanMap[right]
103 | }
104 | } else if (matchedType) {
105 | const values = resolveValue(right, matchedType)
106 |
107 | return isSameArray(left, values, matchedType)
108 | }
109 | return leftValue === rightValue
110 | }
111 | },
112 | '!=': {
113 | key: '!=',
114 | value: '不等于',
115 | validator: (left, right, { valueType }) => {
116 | let leftValue = left
117 | let rightValue = right
118 | const matchedType = resolveArrayType(valueType)
119 |
120 | if (valueType === 'number') {
121 | leftValue = parseFloat(left)
122 | rightValue = parseFloat(right)
123 | return (isNaN(leftValue) || isNaN(leftValue)) ? true : leftValue !== rightValue
124 | } else if (valueType === 'boolean') {
125 | if (right in booleanMap) {
126 | rightValue = booleanMap[right]
127 | }
128 | } else if (matchedType) {
129 | const values = resolveValue(right, matchedType)
130 |
131 | return !isSameArray(left, values, matchedType)
132 | }
133 | return leftValue !== rightValue
134 | }
135 | },
136 | '>': {
137 | key: '>',
138 | value: '大于',
139 | validator: (left, right) => {
140 | const leftValue = parseFloat(left)
141 | const rightValue = parseFloat(right)
142 |
143 | return (isNaN(leftValue) || isNaN(leftValue)) ? false : leftValue > rightValue
144 | }
145 | },
146 | '<': {
147 | key: '<',
148 | value: '小于',
149 | validator: (left, right) => {
150 | const leftValue = parseFloat(left)
151 | const rightValue = parseFloat(right)
152 |
153 | return (isNaN(leftValue) || isNaN(leftValue)) ? false : leftValue < rightValue
154 | }
155 | },
156 | '<>': {
157 | key: '<>',
158 | value: '包含',
159 | validator: (left, right, { logic, valueType }) => {
160 | let result = false
161 | const matchedType = resolveArrayType(valueType)
162 | const { relation } = logic
163 | const values = resolveValue(right, matchedType)
164 |
165 | if (relation in relationFn) {
166 | result = relationFn[relation](left, values, '<>')
167 | }
168 | return result
169 | }
170 | },
171 | '><': {
172 | key: '><',
173 | value: '不包含',
174 | validator: (left, right, { logic, valueType }) => {
175 | let result = false
176 | const matchedType = resolveArrayType(valueType)
177 | const { relation } = logic
178 | const values = resolveValue(right, matchedType)
179 |
180 | if (relation in relationFn) {
181 | result = relationFn[relation](left, values, '><')
182 | }
183 | return result
184 | }
185 | }
186 | }
187 | }
188 |
189 | get () {
190 | return {
191 | type: 'value',
192 | key: '',
193 | action: '',
194 | value: '',
195 | relation: 'or', // or | and 或关系 | 且关系
196 | trigger: 'prop', // prop | script
197 | script: '',
198 | effects: [new Effect()]
199 | }
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/src/worker/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 创建worker
3 | *
4 | * 一、用途:
5 | * - 处理用户输入的脚本
6 | * - 避免用户恶意获取window或document对象数据,增加安全性
7 | * - 避免用户输入死循环等错误逻辑阻塞主线程执行
8 | *
9 | * 二、用法:
10 | * ```
11 | * let worker = new EpWorker()
12 | * worker.onmessage = function (e) {
13 | * const {
14 | * message, // 错误消息
15 | * success, // 是否执行成功 布尔类型
16 | * data // 处理后返回的数据
17 | * } = e.data
18 | * }
19 | * worker.postMessage({
20 | * action, // 需要worker处理的类型,可选值:'fetch',其对应fn字段使用的变量为 `data`
21 | * data, // 发送给进程的数据,可以是任意类型
22 | * fn // 处理后返回的数据,可以执行的字符串函数,不包含`function`关键字(同new Function最后一个参数),需要 return处理后的数据
23 | * }
24 | * ```
25 | */
26 | export default class EpWorker {
27 | constructor () {
28 | return this.createWorker()
29 | }
30 |
31 | createWorker () {
32 | const blob = new Blob(['(' + this.createFunction().toString() + ')()'])
33 | const url = window.URL.createObjectURL(blob)
34 | return new Worker(url)
35 | }
36 |
37 | createFunction () {
38 | return function () {
39 | // 对注入参数进行转化
40 | self.util = {
41 | convert2kv (data, fn, children) {
42 | if (!children) {
43 | return data.map(fn)
44 | } else {
45 | return __recursive(data, fn)
46 | }
47 | function isArray (arr) {
48 | return Array.isArray(arr)
49 | }
50 | // 递归转换数据
51 | function __recursive (data, fn) {
52 | if (!isArray(data)) return []
53 | return data.map(function (item) {
54 | const newItem = Object.assign({}, fn(item))
55 | const rawChildren = isArray(item[children]) ? item[children] : []
56 | newItem.children = __recursive(rawChildren, fn)
57 | return newItem
58 | })
59 | }
60 | }
61 | }
62 | function isObj (arg) {
63 | return Object.prototype.toString.call(arg) === '[object Object]'
64 | }
65 |
66 | function t (e, r, o) {
67 | return ((function () {
68 | if (typeof Reflect === 'undefined' || !Reflect.construct) {
69 | return false
70 | }
71 | if (Reflect.construct.sham) {
72 | return false
73 | }
74 | if (typeof Proxy === 'function') {
75 | return true
76 | }
77 | try {
78 | Date.prototype.toString.call(Reflect.construct(Date, [], function () {}))
79 | return true
80 | } catch (e) {
81 | return false
82 | }
83 | }()) ? Reflect.construct : function (e, t, r) {
84 | var o = [null]
85 | o.push.apply(o, t)
86 | var C = Function.bind.apply(e, o)
87 | var u = new C()
88 | // r && n(u, r.prototype)
89 | return u
90 | }).apply(null, arguments)
91 | }
92 | function r (e) {
93 | return (function (e) {
94 | if (Array.isArray(e)) {
95 | for (var t = 0, n = new Array(e.length); t < e.length; t++) {
96 | n[t] = e[t]
97 | }
98 | return n
99 | }
100 | }(e)) || (function (e) {
101 | if (Symbol.iterator in Object(e) || Object.prototype.toString.call(e) === '[object Arguments]') {
102 | return Array.from(e)
103 | }
104 | }(e)) || (function () {
105 | throw new TypeError('Invalid attempt to spread non-iterable instance')
106 | }())
107 | }
108 |
109 | /**
110 | * 检查转换后返回值是否含有kv属性的对象列表
111 | * @param {*} data
112 | * @returns {success: Boolean, message: String} 返回值
113 | */
114 | function checkKVList (data) {
115 | let success = true
116 | let message = ''
117 |
118 | if (Array.isArray(data)) {
119 | for (let i = 0; i < data.length; i++) {
120 | if (!isObj(data[i]) || !('key' in data[i]) || !('value' in data[i])) {
121 | success = false
122 | message = `${data} 在索引 ${i} 不符合格式规范,请转换成[{key: '', value: ''}, ...] 形式并返回`
123 | break
124 | }
125 | }
126 | } else {
127 | success = false
128 | message = '需要返回数组'
129 | }
130 | return { success, message }
131 | }
132 |
133 | function checkTableList (data) {
134 | function isAvailableNumber (n) {
135 | return typeof n === 'number' && n >= 0
136 | }
137 | const pageErrorMsg = 'page 格式不符合规范,应为: { current: Number, size: Number, total: Number }'
138 | if (isObj(data)) {
139 | // check page
140 | const { current, size, total } = data.page || {}
141 | if (!isObj(data.page) ||
142 | !isAvailableNumber(current) ||
143 | !isAvailableNumber(size) ||
144 | !isAvailableNumber(total)
145 | ) {
146 | return {
147 | success: false,
148 | message: pageErrorMsg
149 | }
150 | }
151 | // check data
152 | const innerData = data.data || []
153 | for (let i = 0; i < innerData.length; i++) {
154 | if (!isObj(innerData[i])) {
155 | return {
156 | success: false,
157 | message: innerData[i] + ' 不符合格式规范'
158 | }
159 | }
160 | }
161 | } else {
162 | return {
163 | success: false,
164 | message: '需要返回对象{ page: {}, data: []}'
165 | }
166 | }
167 | return {
168 | success: true,
169 | message: ''
170 | }
171 | }
172 |
173 | // 可以后续扩展 action 及参数、检查函数
174 | const actionMap = {
175 | fetch: {
176 | args: ['data'],
177 | check: checkKVList
178 | },
179 | tableFetch: {
180 | args: ['data'],
181 | check: checkTableList
182 | },
183 | custom: {
184 | args: ['data'],
185 | check: function () {
186 | return {
187 | success: true,
188 | message: ''
189 | }
190 | }
191 | }
192 | }
193 |
194 | // 监听接受到的消息
195 | self.onmessage = function (e) {
196 | let result = { success: true, message: '' }
197 | const { fn, action, data } = e.data
198 | let fun = res => res
199 |
200 | if (!(action in actionMap)) {
201 | result = {
202 | success: false,
203 | /* eslint no-template-curly-in-string: 0 */
204 | message: 'action: ' + action + ' 无法识别,只能为以下其中一种action: \n${Object.keys(actionMap).toString()}'
205 | }
206 | return self.postMessage(result)
207 | }
208 | if (!fn) {
209 | result = {
210 | success: false,
211 | message: '请传入 fn 参数'
212 | }
213 | return self.postMessage(result)
214 | }
215 |
216 | try {
217 | /* eslint-disable no-new-func */
218 | // fun = new Function(...actionMap[action].args, fn)
219 | fun = t(Function, r(actionMap[action].args).concat([fn]))
220 | } catch (e) {
221 | result = {
222 | success: false,
223 | message: e
224 | }
225 | return self.postMessage(result)
226 | }
227 | const _data = fun(data)
228 | result = actionMap[action].check(_data)
229 | if (result.success) {
230 | const newData = {
231 | success: result.success,
232 | data: _data
233 | }
234 | self.postMessage(newData)
235 | } else {
236 | return self.postMessage(result)
237 | }
238 | }
239 | }
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/src/helper/util.js:
--------------------------------------------------------------------------------
1 |
2 | export function isFunction (value) {
3 | return typeof value === 'function'
4 | }
5 |
6 | export function isString (value) {
7 | return typeof value === 'string'
8 | }
9 |
10 | export function isNumber (value) {
11 | return typeof value === 'number'
12 | }
13 |
14 | export function isArray (value) {
15 | return Array.isArray(value)
16 | }
17 |
18 | export function jsonClone (value) {
19 | return JSON.parse(JSON.stringify(value))
20 | }
21 |
22 | export function include (list, i) {
23 | return list.indexOf(i) !== -1
24 | }
25 |
26 | /**
27 | * get primitive value type
28 | * @param {any} value shoule be checked
29 | * @return {Boolean}
30 | */
31 | export function getValueType (value) {
32 | return ({}).toString.call(value).slice(8).split(']')[0].toLowerCase()
33 | }
34 |
35 | /**
36 | * check if array value type is as expected
37 | * @param {Array} value
38 | * @param {String} expectedType Schema.type
39 | */
40 | function checkValueTypeWithStringExpectedType (value, expectedType) {
41 | const matched = expectedType.match(/array<([^<>]+)>/)
42 | if (expectedType === 'array') {
43 | return true
44 | }
45 | if (!matched || !matched[1]) {
46 | return false
47 | }
48 | const innerType = matched[1]
49 |
50 | return value.filter(v => getValueType(v) === innerType).length === value.length
51 | }
52 | /**
53 | * 判断value参数值类型是否符合预期expectedType
54 | * @param {any} value 待判断值
55 | * @param {String|Number|Boolean|Undefined|Array} expectedType 期望的value值类型
56 | * @param {Boolean} dynamic 是否允许动态添加widget,由schema.dynamic决定,为true时,value必须为数组类型
57 | * @returns {Boolean}
58 | */
59 | export function checkValueType (value, expectedType, dynamic) {
60 | const type = getValueType(value)
61 | function checkExpectedType (expectedType, type) {
62 | const map = {
63 | string: ['json'],
64 | number: ['json'],
65 | boolean: ['json'],
66 | object: ['json'],
67 | array: ['json'],
68 | null: ['json'],
69 | undefined: []
70 | }
71 | if (isArray(expectedType)) {
72 | return include(expectedType, type) || !!expectedType.filter(t => include(map[type], t)).length
73 | } else {
74 | return type === expectedType || include(map[type], expectedType)
75 | }
76 | }
77 | if (dynamic) {
78 | if (type !== 'array') {
79 | return false
80 | }
81 | return value.filter(v => checkValueType(v, expectedType, false)).length === value.length
82 | } else {
83 | if (type === 'array') {
84 | if (isArray(expectedType)) {
85 | return !!expectedType.filter(t => checkValueTypeWithStringExpectedType(value, t)).length
86 | } else {
87 | return checkValueTypeWithStringExpectedType(value, expectedType)
88 | }
89 | } else {
90 | return checkExpectedType(expectedType, type)
91 | }
92 | }
93 | }
94 | /**
95 | * 生成指定长度字符串长度,大小写字母及数字
96 | * @param {Number} len 字符串长度
97 | */
98 | export function randomStr (len = 8) {
99 | let result = ''
100 | const char = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
101 | for (var i = 0; i < len; i++) {
102 | result += char.charAt(Math.floor(Math.random() * char.length))
103 | }
104 | return 'k' + result
105 | }
106 |
107 | /**
108 | * 判断一个变量是否为原生对象
109 | * @param {any} value 待判断参数
110 | */
111 | export function isPlainObject (value) {
112 | if (!value || typeof value !== 'object' || ({}).toString.call(value) !== '[object Object]') {
113 | return false
114 | }
115 | const proto = Object.getPrototypeOf(value)
116 | if (proto === null) {
117 | return true
118 | }
119 | const Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor
120 | return typeof Ctor === 'function'
121 | }
122 |
123 | /**
124 | * 判断是否非空字符串
125 | * @param {String} value 待判断值
126 | */
127 | export function isNotEmptyString (value) {
128 | if (
129 | !isString(value) ||
130 | !value.trim().length
131 | ) return false
132 | return true
133 | }
134 |
135 | /**
136 | * 判断是否为数字字符串
137 | * @param {String} value 待判断值
138 | */
139 | export function isNumberString (value) {
140 | if (typeof value !== 'string') {
141 | return false
142 | }
143 | return /^[-+]?\d+(.\d+)?$/.test(value)
144 | }
145 |
146 | /**
147 | * 简单的http请求方法
148 | * @param {String} url 请求url
149 | * @param {Object} option 请求参数
150 | */
151 | export function ajax (url, option) {
152 | let pro = fetch(url, option)
153 | .then(res => res.json())
154 |
155 | if (option) {
156 | const { before } = option
157 | if (isFunction(before)) {
158 | pro = pro.then(before)
159 | }
160 | }
161 | return pro
162 | }
163 |
164 | /**
165 | * 对函数进行防抖
166 | * @param {function} func 防抖的函数
167 | * @param {number} wait 防抖的时间
168 | * @param {object} options 防抖参数
169 | * @param {boolean} options.leading 指定在延迟开始前调用
170 | * @param {boolean} options.trailing 指定在延迟结束后调用
171 | * @param {boolean} options.maxWait 允许被延迟的最大值
172 | *
173 | */
174 | export function debounce (func, wait, options) {
175 | let lastArgs
176 | let lastThis
177 | let maxWait
178 | let result
179 | let timerId
180 | let lastCallTime
181 | let lastInvokeTime = 0
182 | let leading = false
183 | let maxing = false
184 | let trailing = true
185 |
186 | if (typeof func !== 'function') {
187 | throw new TypeError('Expected a function')
188 | }
189 | wait = parseInt(wait, 10) || 0
190 | if (isPlainObject(options)) {
191 | leading = !!options.leading
192 | maxing = 'maxWait' in options
193 | maxWait = maxing ? Math.max(parseInt(options.maxWait) || 0, wait) : maxWait
194 | trailing = 'trailing' in options ? !!options.trailing : trailing
195 | }
196 |
197 | function invokeFunc (time) {
198 | var args = lastArgs
199 | var thisArg = lastThis
200 |
201 | lastArgs = lastThis = undefined
202 | lastInvokeTime = time
203 | result = func.apply(thisArg, args)
204 | return result
205 | }
206 |
207 | function leadingEdge (time) {
208 | // Reset any `maxWait` timer.
209 | lastInvokeTime = time
210 | // Start the timer for the trailing edge.
211 | timerId = setTimeout(timerExpired, wait)
212 | // Invoke the leading edge.
213 | return leading ? invokeFunc(time) : result
214 | }
215 |
216 | function remainingWait (time) {
217 | var timeSinceLastCall = time - lastCallTime
218 | var timeSinceLastInvoke = time - lastInvokeTime
219 | var timeWaiting = wait - timeSinceLastCall
220 |
221 | return maxing
222 | ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
223 | : timeWaiting
224 | }
225 |
226 | function shouldInvoke (time) {
227 | var timeSinceLastCall = time - lastCallTime
228 | var timeSinceLastInvoke = time - lastInvokeTime
229 |
230 | // Either this is the first call, activity has stopped and we're at the
231 | // trailing edge, the system time has gone backwards and we're treating
232 | // it as the trailing edge, or we've hit the `maxWait` limit.
233 | return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
234 | (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait))
235 | }
236 |
237 | function timerExpired () {
238 | var time = Date.now()
239 | if (shouldInvoke(time)) {
240 | return trailingEdge(time)
241 | }
242 | // Restart the timer.
243 | timerId = setTimeout(timerExpired, remainingWait(time))
244 | }
245 |
246 | function trailingEdge (time) {
247 | timerId = undefined
248 |
249 | // Only invoke if we have `lastArgs` which means `func` has been
250 | // debounced at least once.
251 | if (trailing && lastArgs) {
252 | return invokeFunc(time)
253 | }
254 | lastArgs = lastThis = undefined
255 | return result
256 | }
257 |
258 | function cancel () {
259 | if (timerId !== undefined) {
260 | clearTimeout(timerId)
261 | }
262 | lastInvokeTime = 0
263 | lastArgs = lastCallTime = lastThis = timerId = undefined
264 | }
265 |
266 | function flush () {
267 | return timerId === undefined ? result : trailingEdge(Date.now()())
268 | }
269 |
270 | function debounced () {
271 | var time = Date.now()
272 | var isInvoking = shouldInvoke(time)
273 |
274 | lastArgs = arguments
275 | lastThis = this
276 | lastCallTime = time
277 |
278 | if (isInvoking) {
279 | if (timerId === undefined) {
280 | return leadingEdge(lastCallTime)
281 | }
282 | if (maxing) {
283 | // Handle invocations in a tight loop.
284 | clearTimeout(timerId)
285 | timerId = setTimeout(timerExpired, wait)
286 | return invokeFunc(lastCallTime)
287 | }
288 | }
289 | if (timerId === undefined) {
290 | timerId = setTimeout(timerExpired, wait)
291 | }
292 | return result
293 | }
294 | debounced.cancel = cancel
295 | debounced.flush = flush
296 | return debounced
297 | }
298 |
299 | export function formatDate (_date, _format) {
300 | const date = _date instanceof Date ? _date : new Date()
301 | let year = date.getFullYear()
302 | let month = date.getMonth() + 1
303 | let day = date.getDate()
304 | let minute = date.getMinutes()
305 | let hour = date.getHours()
306 | let second = date.getSeconds()
307 | const addPrefix = function (num) {
308 | const n = num + ''
309 | return n.length === 1 ? '0' + n : n
310 | }
311 |
312 | year = addPrefix(year)
313 | month = addPrefix(month)
314 | day = addPrefix(day)
315 | minute = addPrefix(minute)
316 | hour = addPrefix(hour)
317 | second = addPrefix(second)
318 |
319 | const format = _format || 'yyyy-MM-dd'
320 | return format
321 | .replace('yyyy', year)
322 | .replace('MM', month)
323 | .replace('dd', day)
324 | .replace('HH', hour)
325 | .replace('mm', minute)
326 | .replace('ss', second)
327 | }
328 |
329 | export function copy (str) {
330 | const input = document.createElement('textArea')
331 | input.setAttribute('style', 'position: absolute;z-index: -1;height: 0;width: 0;')
332 | document.body.appendChild(input)
333 | input.value = str
334 | input.select()
335 | const isCopied = document.execCommand('copy', 'false', null)
336 |
337 | return new Promise((resolve, reject) => {
338 | if (isCopied) {
339 | resolve(str)
340 | } else {
341 | reject(new Error('error'))
342 | }
343 | document.body.removeChild(input)
344 | })
345 | }
346 |
--------------------------------------------------------------------------------
/src/store/mutations/widget.js:
--------------------------------------------------------------------------------
1 | import types from '../types'
2 | import { defaultSchema, defaultProps } from '../../constant'
3 | import Logic from '../../logic'
4 | import Rule from '../../rule'
5 | import TypeBuilder from '../TypeBuilder'
6 | import {
7 | flattenSchema,
8 | isFunction,
9 | isArray,
10 | include,
11 | getParentListByKey,
12 | getIndexByKey,
13 | getRootSchemaChildren,
14 | getWidgetType,
15 | isNotEmptyString,
16 | getWidgetModel,
17 | updateRequiredRule,
18 | convertNameModelToKeyModel,
19 | getDefaults
20 | } from '../../helper'
21 |
22 | const typeBuilder = new TypeBuilder()
23 |
24 | export default {
25 | // 配置供展示的所有widget列表
26 | [types.$WIDGETS_SET] (state, { widgets }) {
27 | state.widgets = widgets
28 | },
29 |
30 | // 更新所有widget默认属性,在new Render时调用
31 | [types.$WIDGET_DEFAULT_PROPS_UPDATE] (state) {
32 | state.defaults = getDefaults(state.flatSchemas, defaultProps())
33 | },
34 |
35 | // value logic改变影响 widget 属性改变
36 | [types.$WIDGET_UPDATE_BY_VALUE_LOGIC] (state, { model, callback }) {
37 | const { flatSchemas } = state
38 | const valueTypes = {}
39 | Object.keys(model || {}).forEach(k => { valueTypes[k] = flatSchemas[k].type })
40 | const valueLogics = state.rootSchema.logics.filter(logic => logic.key && logic.type === 'value')
41 | const logic = new Logic(state.defaults)
42 | const { patches, scripts } = logic.diffValueLogics(valueLogics, model, valueTypes)
43 | const controlledKeys = []
44 |
45 | for (const key in model) {
46 | valueLogics.filter(logic => logic.key === key).map(logic => {
47 | logic.effects.forEach(effect => {
48 | if (controlledKeys.indexOf(effect.key) === -1) {
49 | controlledKeys.push(effect.key)
50 | }
51 | })
52 | })
53 | }
54 |
55 | logic.applyPatches(state.flatSchemas, patches, controlledKeys)
56 | isFunction(callback) && callback(scripts)
57 | },
58 |
59 | // event logic改变影响 widget 属性改变
60 | [types.$WIDGET_UPDATE_BY_EVENT_LOGIC] (state, { key, eventType, callback }) {
61 | // key 为当前触发的widget key
62 | const eventLogics = state.rootSchema.logics.filter(logic => logic.key === key && logic.type === 'event')
63 | const logic = new Logic(state.defaults)
64 | const { patches, scripts } = logic.diffEventLogics(eventLogics, eventType)
65 | const controlledKeys = []
66 | eventLogics.forEach(logic => {
67 | if (logic.key === key) {
68 | logic.effects.forEach(effect => {
69 | if (controlledKeys.indexOf(effect.key) === -1) {
70 | controlledKeys.push(effect.key)
71 | }
72 | })
73 | }
74 | })
75 |
76 | logic.applyPatches(state.flatSchemas, patches, controlledKeys)
77 | isFunction(callback) && callback(scripts)
78 | },
79 |
80 | // 设计模式向表单添加一个widget,schema为此widget对应的默认schema
81 | // 目前默认添加到整个表单最后
82 | [types.$WIDGET_ADD] (state, { widget, schema }) {
83 | const { selectedSchema, rootSchema, flatSchemas } = state
84 | const { flatWidgets, isSelected } = this.getters
85 | const wid = widget || schema.widget
86 | const WidgetSchema = flatWidgets[wid].Schema
87 |
88 | if (!isFunction(WidgetSchema)) {
89 | return console.error('Schema should be a constructor')
90 | }
91 |
92 | let childrenSchema = []
93 | // 根据传入的完整schema添加,需额外配置args参数
94 | const args = (!widget && schema) ? { clone: true, schema, flatSchemas } : {}
95 | const newSchema = new WidgetSchema({ widgets: flatWidgets, ...args })
96 |
97 | if (isSelected) {
98 | childrenSchema = getParentListByKey(selectedSchema.key, rootSchema)
99 | const index = getIndexByKey(selectedSchema.key, childrenSchema)
100 | childrenSchema.splice(index + 1, 0, newSchema)
101 | } else {
102 | childrenSchema = getRootSchemaChildren(rootSchema)
103 | childrenSchema.push(newSchema)
104 | }
105 | this.commit(types.$ROOT_SCHEMA_FLAT, { rootSchema: Object.assign({}, rootSchema) })
106 | this.commit(types.$RULE_INIT, { rootSchema: Object.assign({}, rootSchema) })
107 |
108 | const type = getWidgetType(flatWidgets, newSchema.widget)
109 | const model = getWidgetModel(type, newSchema, typeBuilder)
110 | this.commit(types.$MODEL_SET, { model })
111 | state.selectedSchema = newSchema
112 | },
113 |
114 | [types.$WIDGET_DYNAMIC_ADD] (state, { key }) {
115 | const widgets = this.getters.flatWidgets
116 | const { flatSchemas } = state
117 | const currentSchema = flatSchemas[key]
118 | const WidgetSchema = widgets[currentSchema.widget].Schema
119 | const schema = Object.assign({}, currentSchema, { list: [], dynamic: false })
120 | const newSchema = new WidgetSchema({ schema, widgets, clone: true, dynamic: true })
121 | // 动态添加的子schema不能为dynamic
122 | newSchema.dynamic = false
123 | const flatedSchema = flattenSchema(newSchema)
124 | const flatedRules = new Rule(newSchema).rules
125 |
126 | currentSchema.list.push(newSchema)
127 | const newSchemas = {
128 | [newSchema.key]: newSchema,
129 | ...flatedSchema
130 | }
131 | state.flatSchemas = Object.assign({}, state.flatSchemas, newSchemas)
132 | state.flatRules = Object.assign({}, state.flatRules, { ...flatedRules, [newSchema.key]: newSchema.rules })
133 |
134 | // 初始默认值即可,获取表单数据通过方法组装嵌套格式
135 | const model = {}
136 | if (!isArray(state.model[key])) {
137 | model[key] = []
138 | }
139 | // 新建的schema及可能的子孙schema
140 | for (const k in newSchemas) {
141 | const { type, key } = newSchemas[k]
142 | const builder = typeBuilder.types[type]
143 | if (type && isFunction(builder)) {
144 | model[key] = builder()
145 | }
146 | }
147 | state.model = Object.assign({}, state.model, model)
148 | },
149 |
150 | [types.$WIDGET_DYNAMIC_REMOVE] (state, { key, index }) {
151 | const { flatSchemas, flatRules, model } = state
152 | const currentSchema = flatSchemas[key]
153 | const removedSchema = currentSchema.list.splice(index, 1)[0]
154 | const { key: skey } = removedSchema
155 |
156 | delete flatSchemas[skey]
157 | delete flatRules[skey]
158 | delete model[skey]
159 | state.flatSchemas = Object.assign({}, flatSchemas)
160 | state.flatRules = Object.assign({}, flatRules)
161 | state.model = Object.assign({}, model)
162 | },
163 |
164 | // 设计模式复制一个widget
165 | [types.$WIDGET_COPY] (state, { key }) {
166 | const { flatSchemas, rootSchema } = state
167 | const schema = flatSchemas[key]
168 | const { flatWidgets } = this.getters
169 | const WidgetSchema = flatWidgets[schema.widget].Schema
170 | const parentList = getParentListByKey(key, rootSchema)
171 | const index = getIndexByKey(key, parentList)
172 | const newSchema = new WidgetSchema({ schema, widgets: flatWidgets, clone: true, flatSchemas })
173 |
174 | parentList.splice(index + 1, 0, newSchema)
175 | this.commit(types.$ROOT_SCHEMA_FLAT, { rootSchema: Object.assign({}, rootSchema) })
176 | this.commit(types.$RULE_INIT, { rootSchema: Object.assign({}, rootSchema) })
177 |
178 | // set default value
179 | const type = getWidgetType(flatWidgets, newSchema.widget)
180 | const model = getWidgetModel(type, newSchema, typeBuilder)
181 | this.commit(types.$MODEL_SET, { model })
182 |
183 | // set selectedSchema
184 | state.selectedSchema = newSchema
185 | },
186 |
187 | // 设计模式删除一个widget
188 | [types.$WIDGET_REMOVE] (state, { key }) {
189 | const parentList = getParentListByKey(key, state.rootSchema)
190 | const index = getIndexByKey(key, parentList)
191 | const model = Object.assign({}, state.model)
192 |
193 | if (isNotEmptyString(key)) {
194 | delete model[key]
195 | state.model = model
196 | }
197 | parentList.splice(index, 1)
198 | this.commit(types.$ROOT_SCHEMA_FLAT, { rootSchema: Object.assign({}, state.rootSchema) })
199 | this.commit(types.$RULE_INIT, { rootSchema: Object.assign({}, state.rootSchema) })
200 | this.commit(types.$WIDGET_DESELECT)
201 | },
202 |
203 | // 设计模式修改选中一个widget的type(返回值类型)
204 | [types.$WIDGET_TYPE_UPDATE] ({ model, flatSchemas }, { key, type }) {
205 | const schema = flatSchemas[key]
206 | const { widget } = schema
207 | const { flatWidgets } = this.getters
208 | const widgetType = getWidgetType(flatWidgets, widget)
209 | const WidgetSchema = flatWidgets[widget].Schema
210 |
211 | if (!widgetType || !type) {
212 | return
213 | }
214 |
215 | updateRequiredRule(schema, WidgetSchema, { type: TypeBuilder.resolve(type, true) })
216 | if (!isArray(widgetType)) {
217 | schema.type = type
218 | return
219 | }
220 | if (!include(widgetType, type)) {
221 | return console.warn(`${type}不是widget(${widget})值类型`)
222 | }
223 |
224 | schema.type = type
225 | const value = model[key]
226 | if (include(type, 'array')) {
227 | if (isArray(value)) {
228 | return
229 | }
230 | // model[key] = [value]
231 | model[key] = []
232 | } else {
233 | const builder = typeBuilder.types[type]
234 | if (isArray(value) && isFunction(builder)) {
235 | model[key] = builder()
236 | }
237 | }
238 | },
239 |
240 | // 修改widget的option
241 | [types.$WIDGET_OPTION_UPDATE] ({ flatSchemas }, { key, option }) {
242 | const schema = flatSchemas[key]
243 |
244 | if (!schema || !option) {
245 | return
246 | }
247 | schema.option = Object.assign({ ...schema.option }, option)
248 | },
249 |
250 | // 修改表单类型widget默认值
251 | [types.$WIDGET_DEFAULT_UPDATE] ({ flatSchemas }, { defaults, useName }) {
252 | let keyModel = defaults
253 | if (useName) {
254 | keyModel = convertNameModelToKeyModel(defaults, flatSchemas)
255 | }
256 | const keys = Object.keys(keyModel).filter(key => !!flatSchemas[key])
257 | keys.forEach(key => {
258 | flatSchemas[key].default = keyModel[key]
259 | })
260 | },
261 |
262 | // 设计模式选中一个widget
263 | [types.$WIDGET_SELECT] (state, { key }) {
264 | const schema = state.flatSchemas[key]
265 | const isSameWidget = state.selectedSchema.key === key
266 |
267 | if (state.tab !== 'design' || isSameWidget || !schema) {
268 | return
269 | }
270 | state.selectedSchema = schema
271 | },
272 |
273 | // 设计模式取消选中一个widget
274 | [types.$WIDGET_DESELECT] (state) {
275 | state.selectedSchema = defaultSchema()
276 | },
277 |
278 | // 设计模式为选中widget的可变列表属性,执行添加操作
279 | [types.$WIDGET_CHILD_ADD] ({ flatSchemas }, { key, index, child }) {
280 | const schema = flatSchemas[key]
281 |
282 | schema.children.splice(index + 1, 0, child)
283 | },
284 |
285 | // 设计模式为选中widget的可变列表属性,执行删除操作
286 | [types.$WIDGET_CHILD_REMOVE] ({ flatSchemas }, { key, index }) {
287 | const schema = flatSchemas[key]
288 |
289 | schema.children.splice(index, 1)
290 | },
291 |
292 | // 设计模式为选中widget的可变列表属性,执行移动操作
293 | [types.$WIDGET_CHILD_MOVE] ({ flatSchemas }, { key, preIndex, index }) {
294 | const schema = flatSchemas[key]
295 | const children = [...schema.children]
296 |
297 | children.splice(preIndex, 1, ...children.splice(index, 1, children[preIndex]))
298 | schema.children = children
299 | }
300 | }
301 |
--------------------------------------------------------------------------------
/src/helper/epUtil.js:
--------------------------------------------------------------------------------
1 |
2 | import {
3 | randomStr,
4 | isNotEmptyString,
5 | isArray,
6 | isFunction,
7 | include,
8 | jsonClone,
9 | checkValueType,
10 | formatDate
11 | } from './util'
12 | import TypeBuilder from '../store/TypeBuilder'
13 |
14 | /**
15 | * 获取根 schema 的子schema列表
16 | * @param {Object} schema 根schema
17 | */
18 | export function getRootSchemaChildren (schema) {
19 | const list = []
20 |
21 | if (!schema || !schema.container) {
22 | return list
23 | }
24 | if (!isArray(schema.children)) {
25 | schema.children = []
26 | }
27 |
28 | const row = schema.children[0]
29 | if (!row) {
30 | return list
31 | }
32 | if (!isArray(row.list)) {
33 | row.list = []
34 | }
35 | return row.list
36 | }
37 |
38 | /**
39 | * 获表单待提交的form data值
40 | * @param {Object} model store中或初始化的model数据
41 | * @param {Schema} rootSchema 根节点schema
42 | */
43 | export function getFormData (model, rootSchema) {
44 | const formData = {}
45 | const baseTypes = ['string', 'number', 'boolean', 'json']
46 | const convert = (t1, t2, v) => {
47 | if (include(baseTypes, t1) && include(baseTypes, t2)) {
48 | return t1 === t2 ? v : TypeBuilder.convert[`${t1}2${t2}`](v)
49 | }
50 | return v
51 | }
52 | const cleanValue = (value, schema) => {
53 | if (!schema.type) return value
54 | const resolvedType = TypeBuilder.resolve(schema.type)
55 | return isArray(value)
56 | ? value.map(v => convert(typeof v, resolvedType, v))
57 | : convert(typeof value, resolvedType, value)
58 | }
59 |
60 | const getSchemaForm = function (schema, listItem) {
61 | const result = {}
62 | const { container, name, type, dynamic, list = [], key, group, children, hidden } = schema
63 | if (hidden) return result
64 | if (dynamic) {
65 | if (container) {
66 | result[name] = list.map(sc => {
67 | return getSchemaForm(sc, dynamic)
68 | })
69 | } else if (type) {
70 | result[name] = list.map(sc => {
71 | return cleanValue(model[sc.key], sc)
72 | })
73 | }
74 | } else {
75 | if (container) {
76 | if (group) {
77 | const tmpResult = {}
78 | children.forEach(child => {
79 | child.list.forEach(sc => {
80 | Object.assign(tmpResult, getSchemaForm(sc, false))
81 | })
82 | })
83 | if (listItem) {
84 | Object.assign(result, tmpResult)
85 | } else {
86 | result[name] = tmpResult
87 | }
88 | } else {
89 | children.forEach(child => {
90 | child.list.forEach(sc => {
91 | Object.assign(result, getSchemaForm(sc))
92 | })
93 | })
94 | }
95 | } else if (type) {
96 | result[name] = cleanValue(model[key], schema)
97 | }
98 | }
99 | return result
100 | }
101 |
102 | const { children = [] } = rootSchema
103 | children.forEach(child => {
104 | child.list.forEach(sc => {
105 | Object.assign(formData, getSchemaForm(sc))
106 | // if (sc.container && sc.group) {
107 | // formData[sc.name] = getSchemaForm(sc)
108 | // } else {
109 | // Object.assign(formData, getSchemaForm(sc))
110 | // }
111 | })
112 | })
113 |
114 | return formData
115 | }
116 | /**
117 | * get root schema and remove unused field
118 | * @param {Schema} schema root schema
119 | */
120 | export function getSchema (_schema, stateStore = {}) {
121 | if (!_schema) {
122 | return {}
123 | }
124 | const { store, ...othersSchema } = _schema
125 | let _store = {
126 | dicts: [],
127 | apis: []
128 | }
129 | _store.dicts = storeFetchClean(stateStore.dicts)
130 | _store.apis = storeFetchClean(stateStore.apis)
131 |
132 | _store = jsonClone(_store)
133 |
134 | const schema = jsonClone(othersSchema)
135 | schema.store = _store
136 | function clean (schema) {
137 | const option = schema.option || {}
138 | for (const i in option) {
139 | if (i === 'dynamicData') {
140 | option[i] = []
141 | }
142 | // iview table field
143 | if (i === 'columns' && isArray(option[i])) {
144 | option[i].forEach(col => delete col.__id)
145 | }
146 | }
147 | if (schema.container && isArray(schema.children)) {
148 | schema.children.forEach(child => {
149 | if (isArray(child.list)) {
150 | child.list.forEach(s => clean(s))
151 | }
152 | })
153 | }
154 | }
155 | clean(schema)
156 | return schema
157 | }
158 |
159 | export function storeFetchClean (list = []) {
160 | return list.map(item => {
161 | const { source, data, response, ...others } = item
162 | return Object.assign(others, {
163 | source: [],
164 | data: [],
165 | response: {
166 | header: []
167 | }
168 | })
169 | })
170 | }
171 |
172 | /**
173 | * 通过schema key 在指定容器schame或根schema找到key对应schema最近一层父级子列表
174 | * @param {String} key schema key
175 | * @param {Schema} schema 根schema或容器级schema
176 | */
177 | export function getParentListByKey (key, schema) {
178 | const tmpSchema = schema
179 | let parentList = null
180 | function getParent (key, schema) {
181 | if (schema.container) {
182 | const { children } = schema
183 | for (let i = 0; i < children.length; i++) {
184 | const child = children[i]
185 |
186 | for (let k = 0; k < child.list.length; k++) {
187 | const subSchema = child.list[k]
188 | if (key === subSchema.key) {
189 | parentList = child.list
190 | break
191 | } else {
192 | getParent(key, subSchema)
193 | }
194 | }
195 | if (parentList) {
196 | break
197 | }
198 | }
199 | }
200 | }
201 | if (key) {
202 | getParent(key, tmpSchema)
203 | }
204 | if (!parentList) {
205 | const { children } = tmpSchema
206 | if (
207 | isArray(children) &&
208 | children[0] &&
209 | isArray(children[0].list)
210 | ) parentList = children[0].list
211 | }
212 | return parentList
213 | }
214 |
215 | /**
216 | * 在schema列表中找到指定schema key对应的index值
217 | * @param {String} key shchema key
218 | * @param {Array} list schema 列表
219 | */
220 | export function getIndexByKey (key, list) {
221 | let index = -1
222 | if (isNotEmptyString(key) && isArray(list)) {
223 | for (let i = 0, len = list.length; i < len; i++) {
224 | if (list[i].key === key) {
225 | index = i
226 | break
227 | }
228 | }
229 | }
230 | return index
231 | }
232 |
233 | /**
234 | * 替换指定schema极其所有子schema的key,一般复制widget用到
235 | * @param {Schema} schema 根schema或子schema
236 | */
237 | export function replaceSchemaKey (schema) {
238 | function recursiveSchema (schema) {
239 | const { container, children } = schema
240 | if (container && isArray(children)) {
241 | children.forEach(child => {
242 | if (isArray(child.list)) {
243 | child.list.forEach(item => recursiveSchema(item))
244 | }
245 | })
246 | }
247 | schema.key = randomStr()
248 | schema.name = schema.key
249 | }
250 | recursiveSchema(schema)
251 | return schema
252 | }
253 |
254 | /**
255 | * 递归遍历对象,拉平指定key
256 | * @param {Schema Object} obj 待遍历的对象
257 | */
258 | export function flattenSchema (schema) {
259 | const result = {}
260 | if (schema && schema.key) {
261 | result[schema.key] = schema
262 | }
263 | const ecursive = (schema, result) => {
264 | const { dynamic, list, container, children } = schema || {}
265 |
266 | if (dynamic && isArray(list)) {
267 | list.forEach(sc => ecursive(sc, result))
268 | }
269 |
270 | if (!container || !isArray(children)) {
271 | return
272 | }
273 |
274 | children.forEach(child => {
275 | const list = child.list || []
276 | list.forEach(item => {
277 | const { key } = item
278 |
279 | if (isNotEmptyString(key) && result[key] === undefined) {
280 | result[key] = item
281 | ecursive(item, result)
282 | }
283 | })
284 | })
285 | }
286 | ecursive(schema, result)
287 | return result
288 | }
289 |
290 | // 获取指定widget的数据类型,相同widget应该是统一的数据类型
291 | export function getWidgetType (flatWidgets = {}, widget) {
292 | let type = null
293 | for (const i in flatWidgets) {
294 | const { Schema } = flatWidgets[i]
295 | if (isFunction(Schema) && Schema.widget === widget) {
296 | type = Schema.type
297 | break
298 | }
299 | }
300 | return type
301 | }
302 |
303 | // 根据Schema.type及schema.key获取默认model值
304 | export function getWidgetModel (SchemaType, schema, typeBuilder) {
305 | if (!SchemaType || !isNotEmptyString(schema.key)) {
306 | return {}
307 | }
308 | // 初始化数据默认model值
309 | const defaultType = isArray(SchemaType) ? SchemaType[0] : SchemaType
310 | // 优先schema 中type字段指定值类型,没有设定使用当前widget类型的默认值类型
311 | const builder = typeBuilder.types[schema.type || defaultType]
312 | const model = { [schema.key]: '' }
313 | if (isFunction(builder)) {
314 | model[schema.key] = builder()
315 | }
316 | return model
317 | }
318 |
319 | /**
320 | * 检查并设置schema key 及 name
321 | * @param {Object} schema 待实例化的JSON
322 | * @param {Boolean} clone 是否复制
323 | * @param {Boolean} dynamic 是否为动态添加
324 | * @param {Object} 打平的schema集合
325 | */
326 | export function setKeyAndName (schema, clone, dynamic, flatSchemas = {}) {
327 | if (!isNotEmptyString(schema.key)) {
328 | schema.key = randomStr()
329 | if (!schema.name) {
330 | schema.name = schema.key
331 | }
332 | } else {
333 | if (clone) {
334 | schema.key = randomStr()
335 | }
336 | if (!schema.name) {
337 | schema.name = schema.key
338 | }
339 | }
340 | if (clone && !dynamic) {
341 | const names = Object.keys(flatSchemas)
342 | .map(i => flatSchemas[i].name)
343 | .filter(_ => _)
344 | if (include(names, schema.name)) {
345 | schema.name = schema.key
346 | }
347 | }
348 | }
349 |
350 | /**
351 | * merge widget group
352 | * @param {Array} widgets 所有 widget 组,后面支持多个group级参数
353 | * @returns {Array} 返回新widget组
354 | */
355 | export function mergeWidgets (widgets) {
356 | const result = isArray(widgets) ? [...widgets] : []
357 | const groups = Array.prototype.slice.call(arguments, 1)
358 | const filterWidgets = result.filter(w => !include(groups.map(k => k.key), w.key))
359 |
360 | return filterWidgets.concat(groups)
361 | }
362 |
363 | /**
364 | * merge 单个 widget
365 | * @param {Array} widgets 所有widget 组
366 | * @param {Object} widget widget对象 { type, key, widget: { Widget, Setting, schema }}
367 | * @returns {Array} 返回widget组
368 | */
369 | export function mergeWidget (widgets, widget) {
370 | let hasWidget = false
371 | const groups = isArray(widgets) ? widgets : []
372 |
373 | for (let i = 0; i < groups.length; i++) {
374 | const group = groups[i]
375 | if (!isArray(group.widgets)) {
376 | group.widgets = []
377 | }
378 | for (let j = 0; j < group.widgets; j++) {
379 | const wSchema = widget.Schema
380 | const gSchema = group.widgets[j].Schema
381 |
382 | if (wSchema && gSchema && wSchema.widget === gSchema.widget) {
383 | hasWidget = true
384 | group.widgets[j] = widget
385 | break
386 | }
387 | }
388 | if (hasWidget) {
389 | break
390 | }
391 | }
392 | if (!hasWidget) {
393 | if (groups.length === 0) {
394 | groups.push({ title: '基础', key: 'base', widgets: [widget] })
395 | }
396 | if (isArray(groups[0].widgets)) {
397 | groups[0].widgets.push(widget)
398 | } else {
399 | groups[0].widgets = [widget]
400 | }
401 | }
402 | return groups
403 | }
404 |
405 | /**
406 | * 设置validators
407 | * @param {Array} groups widgets 分组
408 | * @param {Object} validator 自定义的validator
409 | * @param {Boolean} replace 是否替换默认validator,还是与默认merge
410 | * example: 为input widget追加 phone规则,phone规则需要提前配置
411 | * setValidators(widgetsGroup, {input: ['phone']})
412 | */
413 | export function setValidators (groups, validator, replace) {
414 | if (!(isArray(groups) || !validator)) {
415 | return
416 | }
417 | // 循环widget group
418 | for (let i = 0, len = groups.length; i < len; i++) {
419 | const group = groups[i]
420 | const { widgets = [] } = group || {}
421 | // 循环特定组的widgets 列表
422 | widgets.forEach(widget => {
423 | // 循环自定义validator 对象
424 | for (const k in validator) {
425 | if (!widget.Schema || k !== widget.Schema.widget) {
426 | continue
427 | }
428 | const customValidators = validator[k]
429 | if (!isArray(customValidators)) {
430 | continue
431 | }
432 | const validators = widget.Schema.validators || []
433 | if (replace) {
434 | widget.Schema.validators = customValidators
435 | } else {
436 | widget.Schema.validators = validators
437 | // 检查自定义widget是否已经与默认重复,重复将不添加
438 | customValidators.forEach(v => {
439 | if (!include(validators, v)) {
440 | validators.push(v)
441 | }
442 | })
443 | }
444 | }
445 | })
446 | }
447 | }
448 |
449 | /**
450 | * get and check rule type
451 | * @param {schema} schema widget schema. required
452 | * @param {Schema} WidgetSchema Schema class. not required
453 | */
454 | export function getSchemaType (schema, WidgetSchema) {
455 | const widgetType = (WidgetSchema && WidgetSchema.type) ? WidgetSchema.type : null
456 | const schemaType = schema && isNotEmptyString(schema.type) ? schema.type : null
457 | // 缺省值
458 | let type = 'string'
459 | if (isArray(widgetType) && widgetType.length) {
460 | type = include(widgetType, schemaType) ? schemaType : widgetType[0]
461 | } else if (isNotEmptyString(widgetType)) {
462 | type = widgetType
463 | }
464 | return type
465 | }
466 |
467 | /**
468 | * get and check required rule type. schema.rules[0]
469 | * @param {schema} schema widget schema. required
470 | * @param {Schema} WidgetSchema Schema class. not required
471 | */
472 | export function getRequiredRuleType (schema, WidgetSchema) {
473 | // const widgetType = (WidgetSchema && WidgetSchema.type) ? WidgetSchema.type : null
474 | // const schemaType = schema && isNotEmptyString(schema.type) ? schema.type : null
475 | // // 缺省值
476 | // let type = 'string'
477 | // if (isArray(widgetType) && widgetType.length) {
478 | // type = include(widgetType, schemaType) ? schemaType : widgetType[0]
479 | // } else if (isNotEmptyString(widgetType)) {
480 | // type = widgetType
481 | // }
482 | const type = getSchemaType(schema, WidgetSchema)
483 | return TypeBuilder.resolve(type, true)
484 | }
485 |
486 | /**
487 | * update required rule and Check the validity of the type
488 | * @param {schema} schema widget shcema. required
489 | * @param {Schema} WidgetSchema Schema class. not required
490 | * @param {Object} rule should updated rule. not required
491 | */
492 | export function updateRequiredRule (schema, WidgetSchema, rule) {
493 | if (!schema || !isArray(schema.rules)) {
494 | return
495 | }
496 | const requiredRule = schema.rules[0]
497 | if (!requiredRule) {
498 | return
499 | }
500 | const { type, ...others } = rule || {}
501 | const newRule = Object.assign({}, requiredRule)
502 | if (type) {
503 | newRule.type = type
504 | } else {
505 | const requiredType = getRequiredRuleType(schema, WidgetSchema)
506 | if (requiredType) {
507 | newRule.type = requiredType
508 | }
509 | }
510 | schema.rules.splice(0, 1, Object.assign(newRule, others))
511 | }
512 |
513 | /**
514 | * 自定义必选规则校验结果
515 | * @param {object} rule
516 | * @param {string} type
517 | */
518 | export function getRuleValidator (rule, type) {
519 | const { message, required } = rule
520 | const emptyValues = [null, undefined, '']
521 | return function (rule, value, callback) {
522 | rule.message = ''
523 | if (required) {
524 | if (isArray(value)) {
525 | if (value.length === 0) {
526 | return callback(new Error(message))
527 | }
528 | } else {
529 | if (include(emptyValues, value)) {
530 | return callback(new Error(message))
531 | }
532 | }
533 | }
534 | if (!include(emptyValues, value) && !checkValueType(value, type)) {
535 | return callback(new Error('类型不匹配'))
536 | }
537 | callback()
538 | }
539 | }
540 | /**
541 | * 为Vue安装依赖,已use的不在use
542 | * @param {Vue} Vue原型
543 | * @param {Array} 待安装的vue plugins 列表
544 | */
545 | export function usePlugins (Vue, plugins) {
546 | if (!Vue || !isArray(plugins)) return
547 | const list = Vue._installedPlugins
548 | plugins.forEach(plugin => {
549 | if (isArray(list) && include(list, plugin)) return
550 | Vue.use(plugin)
551 | })
552 | }
553 |
554 | /**
555 | * 转换name为key的model为以schema.key为key的model
556 | * @param {Object} name为key的model
557 | * @param {Object} 扁平的schema对象
558 | */
559 | export function convertNameModelToKeyModel (model, flatSchemas) {
560 | const keyModel = {}
561 | for (const key in flatSchemas) {
562 | const schema = flatSchemas[key]
563 | for (const name in model) {
564 | if (schema.name !== name) continue
565 | keyModel[key] = model[name]
566 | }
567 | }
568 | return keyModel
569 | }
570 |
571 | /**
572 | * 根据schema.default默认值处理为最终可用默认值
573 | * @param {Schema} schema
574 | */
575 | export function cleanDefaultValue (schema) {
576 | const { widget, option = {}, default: defaultValue } = schema
577 | const { format, range } = option
578 | let result = defaultValue
579 |
580 | if ('default' in schema) {
581 | switch (widget) {
582 | case 'datePicker':
583 | if (defaultValue === 'usedate') {
584 | const date = formatDate(new Date(), format)
585 | result = range ? [date, date] : date
586 | } else {
587 | result = defaultValue
588 | }
589 | break
590 | case 'timePicker':
591 | if (defaultValue === 'usetime') {
592 | const time = formatDate(new Date(), format)
593 | result = range ? [time, time] : time
594 | } else {
595 | result = defaultValue
596 | }
597 | break
598 | default:
599 | result = defaultValue
600 | }
601 | }
602 | return result
603 | }
604 |
605 | /**
606 | * 获取所有schema指定字段的默认值
607 | * @param {Object} flatSchemas
608 | * @param {Object} props ['value', ...]
609 | */
610 | export function getDefaults (flatSchemas, props = []) {
611 | const result = {}
612 | for (const key in flatSchemas) {
613 | const schema = flatSchemas[key]
614 | result[key] = {}
615 | for (const prop in schema) {
616 | const item = schema[prop]
617 | if (props.indexOf(prop) === -1) continue
618 | if (typeof item === 'object') {
619 | result[key][prop] = jsonClone(item)
620 | } else {
621 | result[key][prop] = item
622 | }
623 | }
624 | }
625 | return result
626 | }
627 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 |
2 | import StoreConf from './StoreConf'
3 | import Rule from '../rule'
4 | import types from './types'
5 | import {
6 | isNotEmptyString,
7 | isArray,
8 | getSchema,
9 | getFormData,
10 | include,
11 | isPlainObject
12 | } from '../helper'
13 | import { modes } from '../constant/static'
14 | const Vuex = require('vuex')
15 |
16 | export default class Store {
17 | constructor (option) {
18 | this.$$store = {}
19 | this.$$types = Object.assign({}, types)
20 | this.$$init(option)
21 | }
22 |
23 | /**
24 | * Store init
25 | * @param {Object} option the specified index logic
26 | */
27 | $$init (option) {
28 | this.$$store = new Vuex.Store(new StoreConf({
29 | Rule: option.Rule || Rule
30 | }))
31 | }
32 |
33 | /**
34 | * get the state of store
35 | * @returns {Object} state of store
36 | */
37 | getState () {
38 | return this.$$store.state
39 | }
40 |
41 | /**
42 | * get the store of store
43 | * @returns {Object} store of store
44 | */
45 | getStore () {
46 | return this.$$store.state.store
47 | }
48 |
49 | /**
50 | * select the store of store
51 | * @param {Object} dict dict of store
52 | * @param {Number} index index of store.dicts
53 | * @param {String} action update or create
54 | */
55 | selectDict (dict, index, action) {
56 | this.$$store.commit(this.$$types.$STORE_DICT_SELECT, { dict, index, action })
57 | }
58 |
59 | /**
60 | * update the dict of store
61 | * @param {Object} dict dict of store
62 | */
63 | updateDict (dict, index) {
64 | this.$$store.commit(this.$$types.$STORE_DICT_UPDATE, { dict, index })
65 | }
66 |
67 | /**
68 | * add the dict of store
69 | * @param {Object} dict dict of store
70 | */
71 | addDict (dict) {
72 | this.$$store.commit(this.$$types.$STORE_DICT_ADD, { dict })
73 | }
74 |
75 | /**
76 | * delete the dict of store
77 | * @param {Number} index index of store.dicts
78 | */
79 | deleteDict (index) {
80 | this.$$store.commit(this.$$types.$STORE_DICT_DELETE, { index })
81 | }
82 |
83 | /**
84 | * copy the dict of store
85 | * @param {Number} index index of store.dicts
86 | */
87 | copyDict (index) {
88 | this.$$store.commit(this.$$types.$STORE_DICT_COPY, { index })
89 | }
90 |
91 | /**
92 | * select the api of store
93 | * @param {Object} api of store
94 | * @param {Number} index index of store.apis
95 | * @param {Number} dictIndex index of store.dicts
96 | */
97 | selectAPI (api, index, dictIndex, action) {
98 | this.$$store.commit(this.$$types.$STORE_API_SELECT, { api, index, dictIndex, action })
99 | }
100 |
101 | /**
102 | * update the api of store
103 | * @param {Object} api of store
104 | * @param {Number} index index of store.apis
105 | */
106 | updateAPI (api, index) {
107 | this.$$store.commit(this.$$types.$STORE_API_UPDATE, { api, index })
108 | }
109 |
110 | /**
111 | * add the api of store
112 | * @param {Object} api of store
113 | */
114 | addAPI (api) {
115 | this.$$store.commit(this.$$types.$STORE_API_ADD, { api })
116 | }
117 |
118 | /**
119 | * delete the api of store
120 | * @param {Number} index index of store.apis
121 | */
122 | deleteAPI (index) {
123 | this.$$store.commit(this.$$types.$STORE_API_DELETE, { index })
124 | }
125 |
126 | /**
127 | * copy the api of store
128 | * @param {Number} index
129 | */
130 | copyAPI (index) {
131 | this.$$store.commit(this.$$types.$STORE_API_COPY, { index })
132 | }
133 |
134 | /**
135 | * get current tab on design page
136 | * @returns {String} current tab
137 | */
138 | getTab () {
139 | return this.$$store.state.tab
140 | }
141 |
142 | /**
143 | * get form data
144 | * @returns {Object} form data
145 | */
146 | getFormData () {
147 | const { model, rootSchema } = this.$$store.state
148 | return getFormData(model, rootSchema)
149 | }
150 |
151 | /**
152 | * get cleaned root schema
153 | * @returns {Object} form data
154 | */
155 | getSchema () {
156 | const { rootSchema, store } = this.$$store.state
157 | return getSchema(rootSchema, store)
158 | }
159 |
160 | /**
161 | * get selected schema
162 | * @returns {Schema} selected schema
163 | */
164 | getSelectedSchema () {
165 | return this.$$store.state.selectedSchema
166 | }
167 |
168 | /**
169 | * get root schema
170 | * @returns {Schema} root schema
171 | */
172 | getRootSchema () {
173 | return this.$$store.state.rootSchema
174 | }
175 |
176 | /**
177 | * get the form content entered by the user
178 | * @returns {Object} form data
179 | */
180 | getModel (option) {
181 | if (!option) {
182 | return this.$$store.state.model
183 | }
184 | if (typeof option === 'string') {
185 | return this.$$store.state.model[option]
186 | }
187 | }
188 |
189 | /**
190 | * update form data
191 | * @param {Object} model the form data
192 | */
193 | updateModel (model = {}, useName = false) {
194 | this.$$store.commit(this.$$types.$MODEL_SET, { model, useName })
195 | }
196 |
197 | /**
198 | * reset the form
199 | * @returns {Object} form data
200 | */
201 | resetModel () {
202 | const { model } = this.$$store.state
203 | const newModel = {}
204 | for (const i in model) {
205 | const type = typeof model[i]
206 | switch (type) {
207 | case 'string':
208 | newModel[i] = ''
209 | break
210 | case 'number':
211 | newModel[i] = 0
212 | break
213 | case 'boolean':
214 | newModel[i] = false
215 | break
216 | case 'object':
217 | newModel[i] = isArray(model[i]) ? [] : {}
218 | break
219 | default:
220 | /* eslint no-self-assign: 0 */
221 | newModel[i] = newModel[i]
222 | break
223 | }
224 | }
225 | this.$$store.commit(this.$$types.$MODEL_SET, { model: newModel })
226 | }
227 |
228 | /**
229 | * get registered widgets
230 | * @returns {Array} the widget library returns
231 | */
232 | getWidgets () {
233 | return this.$$store.state.widgets
234 | }
235 |
236 | /**
237 | * get schema collection object
238 | * @returns {Object} flated schemas
239 | */
240 | getFlatSchemas () {
241 | return this.$$store.state.flatSchemas
242 | }
243 |
244 | /**
245 | * get key by name or custom prop
246 | * @param {String} name schema.name value
247 | * @param {String} prop 'name' is default value. schema[prop] = name
248 | * @returns {String} schema.key
249 | */
250 | getKeyByName (name, prop) {
251 | const flatSchemas = this.$$store.state.flatSchemas || {}
252 | const field = (prop && (typeof prop === 'string')) ? prop : 'name'
253 | let key
254 | for (const i in flatSchemas) {
255 | if (flatSchemas[i][field] === name) {
256 | key = i
257 | break
258 | }
259 | }
260 | return key
261 | }
262 |
263 | /**
264 | * get rule collection object
265 | * @returns {Object} flated rules
266 | */
267 | getFlatRules () {
268 | return this.$$store.state.flatRules
269 | }
270 |
271 | /**
272 | * get form rule
273 | * @returns {Object} rules object
274 | */
275 | getFormRules () {
276 | const { flatSchemas, flatRules } = this.$$store.state
277 | const rules = {}
278 | for (const key in flatSchemas) {
279 | if (!flatSchemas[key].hidden) {
280 | rules[key] = flatRules[key]
281 | }
282 | }
283 | return rules
284 | }
285 |
286 | /**
287 | * determine if the widget is selected,
288 | * generally used in design mode
289 | * @returns {Boolean} selected or unselected
290 | */
291 | isSelected () {
292 | return this.$$store.getters.isSelected
293 | }
294 |
295 | /**
296 | * get widget collection
297 | * @returns {Object} flated widget
298 | * @example
299 | * { input: { Setting, View, Schema }, ...}
300 | */
301 | getFlatWidgets () {
302 | return this.$$store.getters.flatWidgets
303 | }
304 |
305 | /**
306 | * get the setting form of the selected widget
307 | * @returns {Vue Component} vue component form
308 | */
309 | getSettingWidget () {
310 | return this.$$store.getters.settingWidget
311 | }
312 |
313 | /**
314 | * get the validators collection of registered widgets
315 | * @returns {Object} validators
316 | * @example
317 | * { input: [StringRule, EmailRule, URLRule, RegExpRule], ...}
318 | */
319 | getWidgetsValidators () {
320 | return this.$$store.getters.widgetsValidators
321 | }
322 |
323 | /**
324 | * update view panel in design mode
325 | * @param {String} tab view panel
326 | */
327 | updateTab (tab) {
328 | if (!isNotEmptyString(tab)) {
329 | return
330 | }
331 |
332 | this.$$store.commit(this.$$types.$TAB_UPDATE, { tab })
333 | }
334 |
335 | /**
336 | * root schema of epage, should be initialize once
337 | * @param {Object} rootSchema should be initialize
338 | */
339 | initRootSchema (rootSchema) {
340 | this.$$store.commit(this.$$types.$ROOT_SCHEMA_SET, { rootSchema })
341 | }
342 |
343 | /**
344 | * register widgets
345 | * @param {Array} widgets the widget library returns
346 | */
347 | initWidgets (widgets) {
348 | if (!isArray(widgets)) {
349 | return console.warn('widgets should be type of array')
350 | }
351 |
352 | this.$$store.commit(this.$$types.$WIDGETS_SET, { widgets })
353 | }
354 |
355 | /**
356 | * update epage mode
357 | * @enum {String} edit | display
358 | */
359 | updateMode (mode) {
360 | if (!include(modes(), mode)) {
361 | return console.warn(`mode must be one of ${modes().toString()}`)
362 | }
363 |
364 | this.$$store.commit(this.$$types.$MODE_CHANGE, { mode })
365 | }
366 |
367 | /**
368 | * add widget when has registered widgets
369 | * @param {String|Schema} widget the unique name or schema
370 | */
371 | addWidget (widget) {
372 | if (!isNotEmptyString(widget)) {
373 | if (isPlainObject(widget) && isNotEmptyString(widget.widget)) {
374 | this.$$store.commit(this.$$types.$WIDGET_ADD, { widget: null, schema: widget })
375 | } else {
376 | return console.warn('widget should be a non-empty string or schema object')
377 | }
378 | } else {
379 | this.$$store.commit(this.$$types.$WIDGET_ADD, { widget, schema: null })
380 | }
381 | }
382 |
383 | /**
384 | * dynamic add widget when has registered widgets
385 | * @param {String} key the unique key of widget
386 | */
387 | dynamicAddWidget (key) {
388 | if (!isNotEmptyString(key)) {
389 | return console.warn('key should be a non-empty string')
390 | }
391 |
392 | this.$$store.commit(this.$$types.$WIDGET_DYNAMIC_ADD, { key })
393 | }
394 |
395 | /**
396 | * dynamic remove widget when has registered widgets
397 | * @param {String} key the unique key of widget
398 | */
399 | dynamicRemoveWidget (key, index) {
400 | if (!isNotEmptyString(key)) {
401 | return console.warn('key should be a non-empty string')
402 | }
403 |
404 | this.$$store.commit(this.$$types.$WIDGET_DYNAMIC_REMOVE, { key, index })
405 | }
406 |
407 | /**
408 | * copy widget when has registered widgets
409 | * @param {String} key the unique key of widget
410 | */
411 | copyWidget (key) {
412 | if (!isNotEmptyString(key)) {
413 | return
414 | }
415 |
416 | this.$$store.commit(this.$$types.$WIDGET_COPY, { key })
417 | }
418 |
419 | /**
420 | * remove from specified widget
421 | * @param {String} key the unique key of widget
422 | */
423 | removeWidget (key) {
424 | if (!isNotEmptyString(key)) {
425 | return
426 | }
427 |
428 | this.$$store.commit(this.$$types.$WIDGET_REMOVE, { key })
429 | }
430 |
431 | /**
432 | * set widget to selected
433 | * @param {String} key the unique key of selected widget
434 | */
435 | selectWidget (key) {
436 | if (!isNotEmptyString(key)) {
437 | return
438 | }
439 |
440 | this.$$store.commit(this.$$types.$WIDGET_SELECT, { key })
441 | }
442 |
443 | /**
444 | * set widget to unselected
445 | * @param {String} key the unique key of widget
446 | */
447 | deselectWidget () {
448 | this.$$store.commit(this.$$types.$WIDGET_DESELECT)
449 | }
450 |
451 | /**
452 | * update the return value type of widget
453 | * @param {String} key the unique key of widget
454 | * @param {String} type the new return value type of widget
455 | */
456 | updateWidgetType (key, type) {
457 | if (!isNotEmptyString(key) || !isNotEmptyString(type)) {
458 | return
459 | }
460 |
461 | this.$$store.commit(this.$$types.$WIDGET_TYPE_UPDATE, { key, type })
462 | }
463 |
464 | /**
465 | * update the custom props of widget
466 | * @param {String} key the unique key of widget
467 | * @param {Object} option should be updated props object
468 | */
469 | updateWidgetOption (key, option) {
470 | if (!isNotEmptyString(key) || !option) {
471 | return
472 | }
473 |
474 | this.$$store.commit(this.$$types.$WIDGET_OPTION_UPDATE, { key, option })
475 | }
476 |
477 | /**
478 | * update default value
479 | * @param {Object} defaults {[schema.key]: defaultValue}
480 | */
481 | updateWidgetDefault (defaults, useName) {
482 | if (!isPlainObject(defaults)) {
483 | return
484 | }
485 |
486 | this.$$store.commit(this.$$types.$WIDGET_DEFAULT_UPDATE, { defaults, useName })
487 | }
488 |
489 | /**
490 | * add child schema
491 | * @param {String} key the unique key of widget
492 | * @param {Number} index the index of new widget
493 | * @param {Object} child child element, example: { span: 12, list: [] }
494 | */
495 | addWidgetChild (key, index, child) {
496 | if (!isNotEmptyString(key) || index < 0 || !child) {
497 | return
498 | }
499 |
500 | this.$$store.commit(this.$$types.$WIDGET_CHILD_ADD, { key, index, child })
501 | }
502 |
503 | /**
504 | * remove child schema
505 | * @param {String} key the unique key of widget
506 | * @param {Number} index the index of widget
507 | */
508 | removeWidgetChild (key, index) {
509 | if (!isNotEmptyString(key)) {
510 | return
511 | }
512 |
513 | this.$$store.commit(this.$$types.$WIDGET_CHILD_REMOVE, { key, index })
514 | }
515 |
516 | /**
517 | * move child schema
518 | * @param {String} key the unique key of widget
519 | * @param {Number} preIndex the previous index of widget
520 | * @param {Number} index the index of widget
521 | */
522 | moveWidgetChild (key, preIndex, index) {
523 | if (!isNotEmptyString(key) || preIndex < 0 || index < 0) {
524 | return
525 | }
526 |
527 | this.$$store.commit(this.$$types.$WIDGET_CHILD_MOVE, { key, preIndex, index })
528 | }
529 |
530 | /**
531 | * trigger other widget props changes based on value changes
532 | * @param {Object} model the form data
533 | */
534 | updateWidgetByModel (model, callback) {
535 | this.$$store.commit(this.$$types.$WIDGET_UPDATE_BY_VALUE_LOGIC, { model, callback })
536 | }
537 |
538 | /**
539 | * trigger other widget props changes based on event triggered
540 | * @param {String} key the unique key of widget
541 | * @param {String} eventType event type, looks like click | change | blur and etc.
542 | */
543 | updateWidgetByEvent (key, eventType, callback) {
544 | this.$$store.commit(this.$$types.$WIDGET_UPDATE_BY_EVENT_LOGIC, { key, eventType, callback })
545 | }
546 |
547 | /**
548 | * initialize rule
549 | * @param {Object} rootSchema initialized root schema
550 | */
551 | initRule (rootSchema) {
552 | this.$$store.commit(this.$$types.$RULE_INIT, { rootSchema })
553 | }
554 |
555 | /**
556 | * add rule
557 | * @param {String} key the unique key of widget
558 | */
559 | addRule (key) {
560 | if (!isNotEmptyString(key)) {
561 | return
562 | }
563 |
564 | this.$$store.commit(this.$$types.$RULE_ADD, { key })
565 | }
566 |
567 | /**
568 | * remove rule
569 | * @param {String} key the unique key of widget
570 | * @param {Number} index the specified index rule
571 | */
572 | removeRule (key, index) {
573 | if (!isNotEmptyString(key)) {
574 | return
575 | }
576 |
577 | this.$$store.commit(this.$$types.$RULE_REMOVE, { key, index })
578 | }
579 |
580 | /**
581 | * update rule
582 | * @param {String} key the unique key of widget
583 | * @param {Number} index the specified index rule
584 | * @param {Object} rule rule detail, https://github.com/yiminghe/async-validator
585 | * @example
586 | * rule = { message: 'required', type: 'email' }
587 | */
588 | updateRule (key, index, rule) {
589 | if (!isNotEmptyString(key) || typeof index !== 'number' || typeof rule !== 'object') {
590 | return
591 | }
592 | const { type, message } = rule
593 |
594 | // The first rule is to determine whether it is required
595 | if (index === 0) {
596 | this.$$store.commit(this.$$types.$RULE_REQUIRED_RULE_UPDATE, { key, rule })
597 | } else {
598 | if (isNotEmptyString(type)) {
599 | this.$$store.commit(this.$$types.$RULE_TYPE_UPDATE, { key, index, type })
600 | if (!isNotEmptyString(message)) {
601 | return
602 | }
603 | this.$$store.commit(this.$$types.$RULE_MESSAGE_UPDATE, { key, index, message })
604 | } else {
605 | if (!isNotEmptyString(message)) {
606 | return
607 | }
608 | this.$$store.commit(this.$$types.$RULE_MESSAGE_UPDATE, { key, index, message })
609 | }
610 | }
611 | }
612 |
613 | /**
614 | * add logic
615 | * @param {Object} logic logic object
616 | * @example
617 | * logic = {
618 | * type: "event",
619 | * key: "ktEAFSAXl",
620 | * action: "click",
621 | * effects: [{
622 | * "key": "kDFnqbKSW",
623 | * "properties": [
624 | * { key: "hidden", value: true },
625 | * { key: "disabled", value: false }
626 | * ]
627 | * }]
628 | * }
629 | */
630 | addLogic (logic) {
631 | if (!logic) {
632 | return
633 | }
634 |
635 | this.$$store.commit(this.$$types.$LOGIC_ADD, { logic })
636 | }
637 |
638 | /**
639 | * update logic
640 | * @param {Number} index the specified index logic
641 | * @param {Object} logic logic object
642 | */
643 | updateLogic (index, logic) {
644 | if (index < 0 || !logic) {
645 | return
646 | }
647 |
648 | this.$$store.commit(this.$$types.$LOGIC_UPDATE, { index, logic })
649 | }
650 |
651 | /**
652 | * remove logic
653 | * @param {Number} index the specified index logic
654 | */
655 | removeLogic (index) {
656 | if (index < 0) {
657 | return
658 | }
659 |
660 | this.$$store.commit(this.$$types.$LOGIC_DELETE, { index })
661 | }
662 |
663 | /**
664 | * update style
665 | * @param {String|null} key schema key
666 | * @param {Obect} style style of css attribute
667 | */
668 | updateStyle (key, style) {
669 | if (typeof style !== 'object') {
670 | return
671 | }
672 |
673 | this.$$store.commit(this.$$types.$STYLE_UPDATE, { key, style })
674 | }
675 | }
676 |
--------------------------------------------------------------------------------