├── .husky ├── .gitignore └── pre-commit ├── docs ├── guide │ ├── listeners.md │ ├── setup.md │ ├── functional.md │ ├── prop-render.md │ ├── prop-transform.md │ ├── examples.md │ ├── repo.md │ ├── getting-started.md │ ├── index.md │ ├── reference-component.md │ ├── datasource.md │ └── configs.md ├── reference │ ├── designer │ │ └── index.md │ └── json-to-render │ │ └── index.md ├── .vitepress │ ├── theme │ │ ├── components │ │ │ └── demo-show │ │ │ │ ├── index.js │ │ │ │ ├── imgs │ │ │ │ ├── codesandbox.svg │ │ │ │ ├── copy.svg │ │ │ │ └── codepen.svg │ │ │ │ ├── Options.vue │ │ │ │ └── Component.vue │ │ └── index.js │ ├── config.js │ └── configs │ │ ├── sidebar.js │ │ └── navbar.js └── index.md ├── .eslintignore ├── .browserslistrc ├── .prettierrc.yaml ├── src ├── components │ └── JsonEditor │ │ ├── index.ts │ │ └── Component.tsx ├── App.css ├── utils │ └── helpers.ts ├── main.ts ├── router │ └── index.ts ├── views │ ├── Designer.tsx │ ├── Test.tsx │ ├── Tabs.tsx │ ├── Home.tsx │ ├── Element.vue │ └── Yaml.tsx └── App.tsx ├── packages ├── core │ ├── src │ │ ├── di │ │ │ ├── empty.ts │ │ │ ├── types │ │ │ │ ├── constructable.ts │ │ │ │ ├── abstractConstructable.ts │ │ │ │ └── serviceIdentifier.ts │ │ │ ├── token.ts │ │ │ ├── interfaces │ │ │ │ ├── serviceOptions.ts │ │ │ │ ├── handler.ts │ │ │ │ └── serviceMetadata.ts │ │ │ ├── error │ │ │ │ ├── cannotInjectValue.ts │ │ │ │ ├── serviceNotFound.ts │ │ │ │ └── cannotInstantiateValue.ts │ │ │ ├── index.ts │ │ │ ├── decorators │ │ │ │ ├── injectMany.ts │ │ │ │ ├── inject.ts │ │ │ │ └── service.ts │ │ │ └── utils │ │ │ │ └── resolveToTypeWrapper.ts │ │ ├── feature │ │ │ ├── index.ts │ │ │ ├── functional.ts │ │ │ ├── datasource.ts │ │ │ └── proxy.ts │ │ ├── index.ts │ │ ├── service │ │ │ ├── token.ts │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── dependencyInjection.ts │ │ │ ├── index.ts │ │ │ ├── domTags.ts │ │ │ └── helpers.ts │ │ └── types.ts │ ├── README.md │ ├── rollup.config.js │ └── package.json ├── vue │ ├── src │ │ ├── utils │ │ │ ├── enums.ts │ │ │ ├── helper.ts │ │ │ ├── render.ts │ │ │ └── pipeline.ts │ │ ├── feature │ │ │ ├── index.ts │ │ │ ├── slots.ts │ │ │ ├── component.ts │ │ │ └── hook.ts │ │ ├── store │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── types.ts │ │ ├── service │ │ │ └── index.ts │ │ └── components │ │ │ ├── jNode.ts │ │ │ └── jRender.ts │ ├── README.md │ ├── rollup.config.js │ └── package.json ├── vue-designer │ ├── src │ │ ├── Main.css │ │ ├── components │ │ │ ├── Designer │ │ │ │ ├── index.ts │ │ │ │ └── Component.tsx │ │ │ ├── Toolbox │ │ │ │ ├── index.ts │ │ │ │ └── Component.tsx │ │ │ ├── Container │ │ │ │ ├── index.ts │ │ │ │ └── Component.tsx │ │ │ └── PropertyBox │ │ │ │ ├── index.ts │ │ │ │ └── Component.tsx │ │ ├── types.ts │ │ ├── index.ts │ │ ├── hooks │ │ │ └── container.ts │ │ └── Main.tsx │ ├── tailwind.config.js │ ├── README.md │ ├── rollup.config.js │ ├── package.json │ └── CHANGELOG.md ├── plugin-classics │ ├── src │ │ ├── functional │ │ │ ├── map.ts │ │ │ ├── filter.ts │ │ │ ├── includes.ts │ │ │ ├── equal.ts │ │ │ ├── reduce.ts │ │ │ ├── text.ts │ │ │ ├── find.ts │ │ │ ├── get.ts │ │ │ ├── if.ts │ │ │ ├── multiply.ts │ │ │ ├── subtraction.ts │ │ │ ├── division.ts │ │ │ └── add.ts │ │ ├── proxy │ │ │ ├── computed.ts │ │ │ └── method.ts │ │ └── index.ts │ ├── README.md │ ├── rollup.config.js │ └── package.json ├── vue-full │ ├── README.md │ ├── src │ │ └── index.ts │ ├── rollup.config.js │ ├── package.json │ └── CHANGELOG.md ├── plugin-vue │ ├── README.md │ ├── src │ │ ├── render │ │ │ ├── text.ts │ │ │ └── condition.ts │ │ ├── index.ts │ │ ├── prerender │ │ │ ├── value.ts │ │ │ ├── model.ts │ │ │ ├── checked.ts │ │ │ └── events.ts │ │ └── datasource │ │ │ └── fetch.ts │ ├── rollup.config.js │ ├── package.json │ └── CHANGELOG.md ├── plugin-modern │ ├── README.md │ ├── src │ │ ├── index.ts │ │ └── proxy │ │ │ ├── expression.ts │ │ │ ├── template.ts │ │ │ └── method.ts │ ├── rollup.config.js │ └── package.json └── plugin-elementui │ ├── README.md │ ├── src │ ├── index.ts │ ├── prerender │ │ ├── rowProps.ts │ │ ├── colProps.ts │ │ ├── vmodel.ts │ │ └── formProps.ts │ └── render │ │ └── options.ts │ ├── rollup.config.js │ ├── package.json │ └── CHANGELOG.md ├── public ├── favicon.ico └── data │ ├── components │ ├── cmp1.json │ ├── cmp2.json │ ├── slotcmp.json │ └── listeners.json │ ├── listdata.json │ ├── basic │ ├── input.json │ ├── events.json │ ├── slots.json │ ├── relation.json │ ├── simple.json │ ├── listeners.json │ ├── templateload.json │ ├── nest.json │ └── full.json │ ├── condition.json │ ├── dev.json │ ├── yaml │ └── testform.yaml │ └── sub.json ├── postcss.config.js ├── .travis.yml ├── .gitignore ├── typings └── shims-vue.d.ts ├── tailwind.config.js ├── lerna.json ├── scripts └── deploy.sh ├── index.html ├── vite.config.js ├── .eslintrc.js ├── tsconfig.json ├── LICENSE ├── README.md └── package.json /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /docs/guide/listeners.md: -------------------------------------------------------------------------------- 1 | # 监听 2 | -------------------------------------------------------------------------------- /docs/guide/setup.md: -------------------------------------------------------------------------------- 1 | # 自定义 2 | -------------------------------------------------------------------------------- /docs/guide/functional.md: -------------------------------------------------------------------------------- 1 | # 功能函数 2 | -------------------------------------------------------------------------------- /docs/guide/prop-render.md: -------------------------------------------------------------------------------- 1 | # 快捷属性 2 | -------------------------------------------------------------------------------- /docs/reference/designer/index.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | build 3 | node_modules 4 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | semi: false 2 | singleQuote: true 3 | -------------------------------------------------------------------------------- /docs/reference/json-to-render/index.md: -------------------------------------------------------------------------------- 1 | # JsonToRender 参考 2 | -------------------------------------------------------------------------------- /src/components/JsonEditor/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Component' 2 | -------------------------------------------------------------------------------- /packages/core/src/di/empty.ts: -------------------------------------------------------------------------------- 1 | export const EMPTY_VALUE = Symbol('EMPTY_VALUE') 2 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint-staged 5 | -------------------------------------------------------------------------------- /packages/vue/src/utils/enums.ts: -------------------------------------------------------------------------------- 1 | export const innerDataNames = ['model', 'scope', 'arguments'] 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fyl080801/json-to-render/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /packages/vue-designer/src/Main.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /public/data/components/cmp1.json: -------------------------------------------------------------------------------- 1 | { 2 | "fields": [{ "component": "p", "text": "段落文本" }] 3 | } 4 | -------------------------------------------------------------------------------- /public/data/listdata.json: -------------------------------------------------------------------------------- 1 | [{ "name": "aaa" }, { "name": "bbb" }, { "name": "ccc" }, { "name": "ddd" }] 2 | -------------------------------------------------------------------------------- /packages/vue/src/feature/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hook' 2 | export * from './component' 3 | export * from './slots' 4 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/core/src/feature/index.ts: -------------------------------------------------------------------------------- 1 | export * from './proxy' 2 | export * from './functional' 3 | export * from './datasource' 4 | -------------------------------------------------------------------------------- /packages/vue-designer/src/components/Designer/index.ts: -------------------------------------------------------------------------------- 1 | import Component from './Component' 2 | 3 | export default Component 4 | -------------------------------------------------------------------------------- /packages/vue-designer/src/components/Toolbox/index.ts: -------------------------------------------------------------------------------- 1 | import Component from './Component' 2 | 3 | export default Component 4 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/components/demo-show/index.js: -------------------------------------------------------------------------------- 1 | import Component from './Component.vue' 2 | 3 | export default Component 4 | -------------------------------------------------------------------------------- /packages/vue-designer/src/components/Container/index.ts: -------------------------------------------------------------------------------- 1 | import Component from './Component' 2 | 3 | export default Component 4 | -------------------------------------------------------------------------------- /packages/vue-designer/src/components/PropertyBox/index.ts: -------------------------------------------------------------------------------- 1 | import Component from './Component' 2 | 3 | export default Component 4 | -------------------------------------------------------------------------------- /packages/plugin-classics/src/functional/map.ts: -------------------------------------------------------------------------------- 1 | export default (array: any[], mapper: any) => { 2 | return array.map(mapper) 3 | } 4 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './feature' 2 | export * from './service' 3 | export * from './utils' 4 | export * from './types' 5 | -------------------------------------------------------------------------------- /packages/plugin-classics/src/functional/filter.ts: -------------------------------------------------------------------------------- 1 | export default (array: any[], filter: any) => { 2 | return array.filter(filter) 3 | } 4 | -------------------------------------------------------------------------------- /packages/plugin-classics/src/functional/includes.ts: -------------------------------------------------------------------------------- 1 | export default (data: any[] = [], value: any) => { 2 | return data.indexOf(value) >= 0 3 | } 4 | -------------------------------------------------------------------------------- /packages/vue/src/utils/helper.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 2 | export const nonArrayFunction = (...args: any[]) => [] 3 | -------------------------------------------------------------------------------- /packages/core/src/service/token.ts: -------------------------------------------------------------------------------- 1 | import { createToken } from '../utils' 2 | 3 | export const servicesToken = createToken>('services') 4 | -------------------------------------------------------------------------------- /packages/plugin-classics/src/functional/equal.ts: -------------------------------------------------------------------------------- 1 | // import { isEqual } from 'lodash-es' 2 | 3 | export default (v1: any, v2: any) => { 4 | return v1 === v2 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin-classics/src/functional/reduce.ts: -------------------------------------------------------------------------------- 1 | export default (array = [], init = {}, mapper = () => true) => { 2 | return array.reduce(mapper, init) 3 | } 4 | -------------------------------------------------------------------------------- /docs/guide/prop-transform.md: -------------------------------------------------------------------------------- 1 | # 属性表达式 2 | 3 | ## 为什么要使用表达式? 4 | 5 | json 只是一种数据格式,不能描述 javascript 的对象,如果属性值和另一个数据关联或者属性是一个函数则在序列化后关联关系和函数会丢失,这样不利于存储,表达式就是为了实现既能简单的表示定义也能存储的作用 6 | -------------------------------------------------------------------------------- /packages/vue/src/feature/slots.ts: -------------------------------------------------------------------------------- 1 | import { createToken } from '@json2render/core' 2 | import { Slots } from 'vue' 3 | 4 | export const slotsToken = createToken('slots') 5 | -------------------------------------------------------------------------------- /packages/vue-designer/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface DesignerConfig { 2 | datasource: { [key: string]: any } 3 | } 4 | 5 | export interface DesignerProps { 6 | config: DesignerConfig 7 | } 8 | -------------------------------------------------------------------------------- /packages/vue/README.md: -------------------------------------------------------------------------------- 1 | # `@json2render/vue` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const vue = require('@json2render/vue'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/plugin-classics/src/functional/text.ts: -------------------------------------------------------------------------------- 1 | export default (...args: any[]) => { 2 | let str = '' 3 | for (let i = 0; i < args.length; i++) { 4 | str += `${args[i]}` 5 | } 6 | return str 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # `@json2render/core` 2 | 3 | json to render 核心库 4 | 5 | ## Usage 6 | 7 | ```javascript 8 | const core = require('@json2render/core') 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/plugin-classics/src/functional/find.ts: -------------------------------------------------------------------------------- 1 | export default (list: any[], exp: any, def: any) => { 2 | const result = list.find(exp) 3 | return result !== undefined && result !== null ? result : def 4 | } 5 | -------------------------------------------------------------------------------- /packages/vue-designer/src/components/Designer/Component.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | 3 | export default defineComponent({ 4 | setup() { 5 | return () =>
designer
6 | }, 7 | }) 8 | -------------------------------------------------------------------------------- /public/data/components/cmp2.json: -------------------------------------------------------------------------------- 1 | { 2 | "modelValue": {}, 3 | "fields": [ 4 | { "component": "el-input", "model": "model.text" }, 5 | { "component": "p", "text": "#:输入了: ${model.text||''}" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /docs/guide/examples.md: -------------------------------------------------------------------------------- 1 | # 在线示例 2 | 3 | [简单示例](http://jsrun.net/2PaKp) 4 | 5 | [ElementPlus 组件](http://jsrun.net/8PaKp) 6 | 7 | [Vant 移动端组件](http://jsrun.net/WEaKp) 8 | 9 | [Ant Design 组件](http://jsrun.net/G7aKp) 10 | -------------------------------------------------------------------------------- /packages/vue-designer/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('lodash') 2 | const config = require('../../tailwind.config') 3 | 4 | module.exports = merge({}, config, { 5 | purge: ['./src/**/*.{vue,ts,tsx}'], 6 | }) 7 | -------------------------------------------------------------------------------- /packages/vue-full/README.md: -------------------------------------------------------------------------------- 1 | # `@json2render/vue-full` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const vueFull = require('@json2render/vue-full'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/plugin-vue/README.md: -------------------------------------------------------------------------------- 1 | # `@json2render/plugin-vue` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const pluginVue = require('@json2render/plugin-vue'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/plugin-classics/src/functional/get.ts: -------------------------------------------------------------------------------- 1 | // import { get } from 'lodash-es' 2 | 3 | import { deepGet } from '@json2render/core' 4 | 5 | export default (source: any, path: string) => { 6 | return deepGet(source, path) 7 | } 8 | -------------------------------------------------------------------------------- /packages/vue-designer/README.md: -------------------------------------------------------------------------------- 1 | # `@json2render/vue-designer` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const vueDesigner = require('@json2render/vue-designer'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/plugin-classics/src/functional/if.ts: -------------------------------------------------------------------------------- 1 | export default (logic: any, v1: any, v2: any) => { 2 | const result = (typeof logic === 'function' ? logic() : logic) ? v1 : v2 3 | return typeof result === 'function' ? result() : result 4 | } 5 | -------------------------------------------------------------------------------- /packages/plugin-modern/README.md: -------------------------------------------------------------------------------- 1 | # `@json2render/plugin-modern` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const pluginModern = require('@json2render/plugin-modern'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/plugin-classics/README.md: -------------------------------------------------------------------------------- 1 | # `@json2render/plugin-classics` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const pluginClassics = require('@json2render/plugin-classics'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/vue-designer/src/components/PropertyBox/Component.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | 3 | export default defineComponent({ 4 | name: 'v-jdesigner-propertybox', 5 | setup() { 6 | return () =>
7 | }, 8 | }) 9 | -------------------------------------------------------------------------------- /packages/vue-designer/src/components/Toolbox/Component.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | 3 | export default defineComponent({ 4 | name: 'v-jdesigner-toolbox', 5 | setup() { 6 | return () =>
toolbox
7 | }, 8 | }) 9 | -------------------------------------------------------------------------------- /packages/plugin-elementui/README.md: -------------------------------------------------------------------------------- 1 | # `@json2render/plugin-elementui` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const pluginElementui = require('@json2render/plugin-elementui'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/core/src/utils/dependencyInjection.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata' 2 | import { Token, ContainerInstance } from '../di' 3 | 4 | export const createToken = (key?: string) => { 5 | return new Token(key) 6 | } 7 | 8 | export { ContainerInstance } 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | install: 10 | - yarn install 11 | 12 | script: 13 | - yarn dist 14 | - chmod u+x ./scripts/deploy.sh 15 | - bash ./scripts/deploy.sh 16 | -------------------------------------------------------------------------------- /packages/plugin-modern/src/index.ts: -------------------------------------------------------------------------------- 1 | import expression from './proxy/expression' 2 | import method from './proxy/method' 3 | import template from './proxy/template' 4 | 5 | export default ({ proxy }: any) => { 6 | proxy(expression) 7 | proxy(method) 8 | proxy(template) 9 | } 10 | -------------------------------------------------------------------------------- /packages/vue-designer/src/index.ts: -------------------------------------------------------------------------------- 1 | import Main from './Main' 2 | // import Toolbox from './components/Toolbox' 3 | 4 | const plugin = { 5 | ...Main, 6 | install: (app: any) => { 7 | app.component(Main.name, Main) 8 | }, 9 | } 10 | 11 | // export { Toolbox } 12 | 13 | export default plugin 14 | -------------------------------------------------------------------------------- /packages/vue/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { provide, inject } from 'vue' 2 | 3 | const stateSymbol = Symbol('store') 4 | 5 | export const getState = () => inject(stateSymbol) 6 | 7 | export const createStore = (state: T) => { 8 | provide(stateSymbol, state) 9 | 10 | return getState 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { isOriginTag } from './domTags' 2 | 3 | export { 4 | assignArray, 5 | assignObject, 6 | cloneDeep, 7 | deepSet, 8 | deepGet, 9 | forEachTarget, 10 | isArray, 11 | isObject, 12 | isFunction, 13 | } from './helpers' 14 | 15 | export * from './dependencyInjection' 16 | -------------------------------------------------------------------------------- /packages/core/src/di/types/constructable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generic type for class definitions. 3 | * Example usage: 4 | * ``` 5 | * function createSomeInstance(myClassDefinition: Constructable) { 6 | * return new myClassDefinition() 7 | * } 8 | * ``` 9 | */ 10 | export type Constructable = new (...args: any[]) => T 11 | -------------------------------------------------------------------------------- /packages/core/src/di/types/abstractConstructable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generic type for abstract class definitions. 3 | * 4 | * Explanation: This describes a callable Function with a prototype Which is 5 | * what an abstract class is - no constructor, just the prototype. 6 | */ 7 | export type AbstractConstructable = CallableFunction & { prototype: T } 8 | -------------------------------------------------------------------------------- /packages/vue-full/src/index.ts: -------------------------------------------------------------------------------- 1 | import JRender from '@json2render/vue' 2 | import classics from '@json2render/plugin-classics' 3 | import modern from '@json2render/plugin-modern' 4 | import vueplugin from '@json2render/plugin-vue' 5 | 6 | JRender.use(classics) 7 | JRender.use(modern) 8 | JRender.use(vueplugin) 9 | 10 | export default JRender 11 | -------------------------------------------------------------------------------- /packages/core/src/di/token.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to create unique typed service identifier. 3 | * Useful when service has only interface, but don't have a class. 4 | */ 5 | export class Token { 6 | /** 7 | * @param name Token name, optional and only used for debugging purposes. 8 | */ 9 | constructor(public name?: string) {} 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | export const debounce = (fn: (...args: any[]) => void, delay: number) => { 2 | let timer: NodeJS.Timeout | null = null 3 | 4 | return (...args: any[]) => { 5 | if (timer) { 6 | clearTimeout(timer) 7 | timer = null 8 | } 9 | 10 | timer = setTimeout(() => { 11 | fn(...args) 12 | }, delay) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | 25 | # docs 26 | .temp 27 | .cache 28 | -------------------------------------------------------------------------------- /typings/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | import { Environment } from 'monaco-editor' 2 | 3 | declare module '*.css' 4 | 5 | // declare module '*.vue' { 6 | // import type { DefineComponent } from 'vue' 7 | // const component: DefineComponent<{}, {}, any> 8 | // export default component 9 | // } 10 | 11 | declare global { 12 | interface Window { 13 | MonacoEnvironment: Environment 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/vue/src/index.ts: -------------------------------------------------------------------------------- 1 | import jRender from './components/jRender' 2 | import { globalSetup } from './service' 3 | import { JRenderPlugin } from './types' 4 | 5 | export * from './types' 6 | 7 | const plugin: JRenderPlugin = { 8 | ...jRender, 9 | install: (app: any) => { 10 | app.component(jRender.name, jRender) 11 | }, 12 | use: globalSetup, 13 | } 14 | 15 | export default plugin 16 | -------------------------------------------------------------------------------- /packages/plugin-vue/src/render/text.ts: -------------------------------------------------------------------------------- 1 | import { HookInvoker } from '@json2render/vue' 2 | 3 | export default (): HookInvoker => (field, next) => { 4 | const textProp = Reflect.getOwnPropertyDescriptor(field, 'text') 5 | 6 | if (!textProp) { 7 | next(field) 8 | return 9 | } 10 | 11 | field.props = field.props || {} 12 | field.props.innerText = textProp.value 13 | 14 | next(field) 15 | } 16 | -------------------------------------------------------------------------------- /docs/guide/repo.md: -------------------------------------------------------------------------------- 1 | # 仓库说明 2 | 3 | ## 核心库 4 | 5 | @json2render/core - 核心功能 6 | 7 | ## Plugins 8 | 9 | @json2render/plugin-classics - 基本的 json 数据解析方式和渲染处理 10 | 11 | @json2render/plugin-modern - 更简便的 json 数据解析方式 12 | 13 | @json2render/plugin-vue - 支持 vue 专有特性 14 | 15 | @json2render/plugin-elementui - 针对 element-ui 组件库扩展的配置方式 16 | 17 | ## 框架支持 18 | 19 | @json2render/vue - 基于 vue 的实现 20 | 21 | @json2render/vue-full - 集成了所有 plugin 的 vue 实现 22 | -------------------------------------------------------------------------------- /packages/plugin-vue/src/render/condition.ts: -------------------------------------------------------------------------------- 1 | import { getProxyDefine } from '@json2render/core' 2 | import { HookInvoker } from '@json2render/vue' 3 | 4 | export default (): HookInvoker => (field, next) => { 5 | const defined = getProxyDefine(field.condition) 6 | 7 | if (defined === undefined) { 8 | next(field) 9 | return 10 | } 11 | 12 | field.component = field.condition ? field.component : null 13 | 14 | next(field) 15 | } 16 | -------------------------------------------------------------------------------- /public/data/components/slotcmp.json: -------------------------------------------------------------------------------- 1 | { 2 | "fields": [ 3 | { 4 | "component": "h1", 5 | "props": { 6 | "style": { "fontSize": "40pt" } 7 | }, 8 | "children": [ 9 | { "component": "span", "text": "标题: " }, 10 | { "component": "slot", "name": "header" } 11 | ] 12 | }, 13 | { "component": "p", "text": "正文" }, 14 | { "component": "slot", "props": { "attrValue": "aaaaa" } } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | container: { 6 | center: true, 7 | }, 8 | extend: {}, 9 | }, 10 | variants: { 11 | backgroundColor: ['focus', 'active'], 12 | borderColor: ['active'], 13 | extend: {}, 14 | }, 15 | plugins: [ 16 | // require('@tailwindcss/forms') 17 | ], 18 | } 19 | -------------------------------------------------------------------------------- /packages/vue/rollup.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const { rollups } = require('../../build') 3 | 4 | const configs = { 5 | types: ['umd', 'iife', 'esm'], 6 | external: [], 7 | } 8 | 9 | const entries = (() => { 10 | const entries = {} 11 | entries['JRender'] = './src/index.ts' 12 | 13 | const result = rollups.establish(entries, configs) 14 | return result 15 | })() 16 | 17 | export default entries 18 | -------------------------------------------------------------------------------- /packages/core/rollup.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const { rollups } = require('../../build') 3 | 4 | const configs = { 5 | types: ['umd', 'iife', 'esm'], 6 | external: [], 7 | } 8 | 9 | const entries = (() => { 10 | const entries = {} 11 | entries['JRenderCore'] = './src/index.ts' 12 | 13 | const result = rollups.establish(entries, configs) 14 | return result 15 | })() 16 | 17 | export default entries 18 | -------------------------------------------------------------------------------- /packages/plugin-elementui/src/index.ts: -------------------------------------------------------------------------------- 1 | import formProps from './prerender/formProps' 2 | import rowProps from './prerender/rowProps' 3 | import colProps from './prerender/colProps' 4 | import options from './render/options' 5 | import vmodel from './prerender/vmodel' 6 | 7 | export default ({ prerender, render }: any) => { 8 | prerender(formProps, 2) 9 | prerender(rowProps) 10 | prerender(colProps, 1) 11 | prerender(vmodel) 12 | render(options) 13 | } 14 | -------------------------------------------------------------------------------- /packages/vue-designer/rollup.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const { rollups } = require('../../build') 3 | 4 | const configs = { 5 | types: ['umd', 'iife', 'esm'], 6 | external: [], 7 | } 8 | 9 | const entries = (() => { 10 | const entries = {} 11 | entries['JDesigner'] = './src/index.ts' 12 | 13 | const result = rollups.establish(entries, configs) 14 | return result 15 | })() 16 | 17 | export default entries 18 | -------------------------------------------------------------------------------- /packages/vue-full/rollup.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const { rollups } = require('../../build') 3 | 4 | const configs = { 5 | types: ['umd', 'iife', 'esm'], 6 | external: [], 7 | } 8 | 9 | const entries = (() => { 10 | const entries = {} 11 | entries['JRenderFull'] = './src/index.ts' 12 | 13 | const result = rollups.establish(entries, configs) 14 | return result 15 | })() 16 | 17 | export default entries 18 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App' 3 | import router from './router' 4 | import Element from 'element-plus' 5 | 6 | import JRender from '@json2render/vue-full' 7 | 8 | import 'element-plus/lib/theme-chalk/index.css' 9 | 10 | import JsonEditor from './components/JsonEditor' 11 | 12 | createApp(App) 13 | .use(router) 14 | .use(Element) 15 | .use(JRender) 16 | .component(JsonEditor.name, JsonEditor) 17 | .mount('#app') 18 | -------------------------------------------------------------------------------- /packages/plugin-vue/rollup.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const { rollups } = require('../../build') 3 | 4 | const configs = { 5 | types: ['umd', 'iife', 'esm'], 6 | external: [], 7 | } 8 | 9 | const entries = (() => { 10 | const entries = {} 11 | entries['JRenderPluginVue'] = './src/index.ts' 12 | 13 | const result = rollups.establish(entries, configs) 14 | return result 15 | })() 16 | 17 | export default entries 18 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "independent", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "useWorkspaces": true, 7 | "npmClient": "yarn", 8 | "command": { 9 | "publish": { 10 | "registry": "http://registry.npmjs.org/", 11 | "ignoreChanges": [ 12 | ".gitignore", 13 | ".eslintignore", 14 | "*.log", 15 | "*.md" 16 | ] 17 | }, 18 | "version": { 19 | "conventionalCommits": true 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/plugin-classics/rollup.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const { rollups } = require('../../build') 3 | 4 | const configs = { 5 | types: ['umd', 'iife', 'esm'], 6 | external: [], 7 | } 8 | 9 | const entries = (() => { 10 | const entries = {} 11 | entries['JRenderPluginClassics'] = './src/index.ts' 12 | 13 | const result = rollups.establish(entries, configs) 14 | return result 15 | })() 16 | 17 | export default entries 18 | -------------------------------------------------------------------------------- /packages/plugin-modern/rollup.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const { rollups } = require('../../build') 3 | 4 | const configs = { 5 | types: ['umd', 'iife', 'esm'], 6 | external: [], 7 | } 8 | 9 | const entries = (() => { 10 | const entries = {} 11 | entries['JRenderPluginModern'] = './src/index.ts' 12 | 13 | const result = rollups.establish(entries, configs) 14 | return result 15 | })() 16 | 17 | export default entries 18 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 确保脚本抛出遇到的错误 4 | set -e 5 | 6 | yarn docs:build 7 | yarn build 8 | 9 | # mkdir docs/.vuepress/dist/example 10 | # cp -r example docs/.vuepress/dist 11 | 12 | cp -r dist docs/.vitepress/dist 13 | cd docs/.vitepress/dist 14 | 15 | # 如果是发布到自定义域名 16 | # echo 'www.example.com' > CNAME 17 | 18 | git init 19 | git add -A 20 | git commit -m 'polish: docs' 21 | git push --force --quiet "https://${GITHUB_TOKEN}@${GITHUB_REF}" master:gh-pages 22 | -------------------------------------------------------------------------------- /packages/core/src/di/types/serviceIdentifier.ts: -------------------------------------------------------------------------------- 1 | import { Token } from '../token' 2 | import { Constructable } from './constructable' 3 | import { AbstractConstructable } from './abstractConstructable' 4 | 5 | /** 6 | * Unique service identifier. 7 | * Can be some class type, or string id, or instance of Token. 8 | */ 9 | export type ServiceIdentifier = 10 | | Constructable 11 | | AbstractConstructable 12 | | CallableFunction 13 | | Token 14 | | string 15 | -------------------------------------------------------------------------------- /packages/plugin-elementui/rollup.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const { rollups } = require('../../build') 3 | 4 | const configs = { 5 | types: ['umd', 'iife', 'esm'], 6 | external: [], 7 | } 8 | 9 | const entries = (() => { 10 | const entries = {} 11 | entries['JRenderPluginElementUI'] = './src/index.ts' 12 | 13 | const result = rollups.establish(entries, configs) 14 | return result 15 | })() 16 | 17 | export default entries 18 | -------------------------------------------------------------------------------- /packages/vue-designer/src/hooks/container.ts: -------------------------------------------------------------------------------- 1 | import { HookInvoker } from '@json2render/vue' 2 | 3 | export default (): HookInvoker => { 4 | return (field, next) => { 5 | if (!field.location) { 6 | next(field) 7 | return 8 | } 9 | 10 | const containered = { 11 | component: 'v-jdesigner-container', 12 | props: field.location, 13 | children: [field], 14 | } 15 | 16 | delete field.location 17 | 18 | next(containered) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/src/di/interfaces/serviceOptions.ts: -------------------------------------------------------------------------------- 1 | import { ServiceMetadata } from './serviceMetadata' 2 | 3 | /** 4 | * The public ServiceOptions is partial object of ServiceMetadata and either one 5 | * of the following is set: `type`, `factory`, `value` but not more than one. 6 | */ 7 | export type ServiceOptions = 8 | | Omit>, 'type' | 'factory'> 9 | | Omit>, 'value' | 'factory'> 10 | | Omit>, 'value' | 'type'> 11 | -------------------------------------------------------------------------------- /public/data/basic/input.json: -------------------------------------------------------------------------------- 1 | { 2 | "fields": [ 3 | { 4 | "component": "el-input", 5 | "model": "model.text", 6 | "props": { "type": "text", "placeholder": "请输入" } 7 | }, 8 | { 9 | "component": "p", 10 | "props": { "innerText": "#:输入了:${model.text || ''}" } 11 | }, 12 | { 13 | "component": "el-input-number", 14 | "model": "model.num" 15 | }, 16 | { 17 | "component": "p", 18 | "text": "#:输入了:${model.num || 0}" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /docs/.vitepress/config.js: -------------------------------------------------------------------------------- 1 | const nav = require('./configs/navbar') 2 | const sidebar = require('./configs/sidebar') 3 | // const demo = require('markdown-it-vue-demo-block') 4 | 5 | module.exports = { 6 | lang: 'zh-CN', 7 | title: 'Json to Render', 8 | description: '功能强大的动态界面渲染组件', 9 | base: '/json-to-render/', 10 | themeConfig: { 11 | nav, 12 | sidebar, 13 | lastUpdated: false, 14 | }, 15 | markdown: { 16 | lineNumbers: true, 17 | config: (md) => { 18 | // md.use(demo) 19 | }, 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /packages/plugin-elementui/src/prerender/rowProps.ts: -------------------------------------------------------------------------------- 1 | import { HookInvoker } from '@json2render/vue' 2 | 3 | export default (): HookInvoker => (field, next) => { 4 | if (!field.row) { 5 | next(field) 6 | return 7 | } 8 | 9 | const rowProps = field.row 10 | 11 | delete field.row 12 | 13 | field.children = [ 14 | { 15 | component: 'el-row', 16 | props: typeof rowProps === 'number' ? { gutter: rowProps } : rowProps, 17 | children: field.children, 18 | }, 19 | ] 20 | 21 | next(field) 22 | } 23 | -------------------------------------------------------------------------------- /packages/plugin-classics/src/functional/multiply.ts: -------------------------------------------------------------------------------- 1 | export default (value1 = 0, value2 = 0, rnd = -1) => { 2 | let m = 0 3 | const s1 = value1.toString() 4 | const s2 = value2.toString() 5 | try { 6 | m += s1.split('.')[1].length 7 | } catch { 8 | // 9 | } 10 | try { 11 | m += s2.split('.')[1].length 12 | } catch { 13 | // 14 | } 15 | const result = 16 | (Number(s1.replace('.', '')) * Number(s2.replace('.', ''))) / 17 | Math.pow(10, m) 18 | 19 | return rnd <= 0 ? result : +result.toFixed(rnd) 20 | } 21 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.js: -------------------------------------------------------------------------------- 1 | import DefaultTheme from 'vitepress/theme' 2 | // import DemoShow from './components/demo-show' 3 | 4 | export default { 5 | ...DefaultTheme, 6 | // Layout, 7 | // NotFound: () => 'custom 404', // <- this is a Vue 3 functional component 8 | enhanceApp({ app, router, siteData }) { 9 | // app.component(DemoShow.name, DemoShow) 10 | // app is the Vue 3 app instance from `createApp()`. router is VitePress' 11 | // custom router. `siteData`` is a `ref`` of current site-level metadata. 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/plugin-classics/src/functional/subtraction.ts: -------------------------------------------------------------------------------- 1 | import multiply from './multiply' 2 | import division from './division' 3 | 4 | export default (value1 = 0, value2 = 0) => { 5 | let r1, r2 6 | try { 7 | r1 = value1.toString().split('.')[1].length 8 | } catch { 9 | r1 = 0 10 | } 11 | try { 12 | r2 = value2.toString().split('.')[1].length 13 | } catch { 14 | r2 = 0 15 | } 16 | const m = Math.pow(10, Math.max(r1, r2)) 17 | const n = r1 >= r2 ? r1 : r2 18 | return (+division(+multiply(value1, m) - +multiply(value2, m), m)).toFixed(n) 19 | } 20 | -------------------------------------------------------------------------------- /packages/plugin-classics/src/functional/division.ts: -------------------------------------------------------------------------------- 1 | // 除法 2 | import multiply from './multiply' 3 | 4 | export default (value1 = 0, value2 = 1, rnd = -1) => { 5 | let t1 = 0, 6 | t2 = 0 7 | 8 | try { 9 | t1 = value1.toString().split('.')[1].length 10 | } catch { 11 | // 12 | } 13 | try { 14 | t2 = value2.toString().split('.')[1].length 15 | } catch { 16 | // 17 | } 18 | const r1 = Number(value1.toString().replace('.', '')) 19 | const r2 = Number(value2.toString().replace('.', '')) 20 | return +multiply(r1 / r2, Math.pow(10, t2 - t1), rnd) 21 | } 22 | -------------------------------------------------------------------------------- /packages/vue/src/utils/render.ts: -------------------------------------------------------------------------------- 1 | import { resolveComponent } from 'vue' 2 | import { isOriginTag } from '@json2render/core' 3 | 4 | export const resolveChildren = (children: any[]) => { 5 | return children 6 | ? children.reduce((pre: any, cur: any) => { 7 | const currentSlot = cur.slot || 'default' 8 | pre[currentSlot] = pre[currentSlot] || [] 9 | pre[currentSlot].push(cur) 10 | return pre 11 | }, {}) 12 | : null 13 | } 14 | 15 | export const resolveRenderComponent = (name: string) => { 16 | return isOriginTag(name) ? name : resolveComponent(name) 17 | } 18 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | 4 | actionText: 指南 5 | actionLink: /guide/ 6 | 7 | altActionText: 快速上手 8 | altActionLink: /guide/getting-started 9 | 10 | features: 11 | - title: JSON 12 | details: 通过 json 数据渲染界面并实现交互功能 13 | 14 | - title: 通用性 15 | details: 支持任何 html 标签、组件库以及自定义组件 16 | 17 | - title: 交互灵活 18 | details: 支持组件属性与数据的联动,数据与数据之间的联动 19 | 20 | - title: 扩展性 21 | details: 支持自定义 json 数据渲染行为 22 | 23 | - title: 生态支持 24 | details: 提供 vue3 支持以及相关扩展 25 | 26 | - title: 持续改进 27 | details: 不断完善功能 28 | 29 | footer: MIT Licensed | Copyright © 2021-present fyl080801 30 | --- 31 | -------------------------------------------------------------------------------- /packages/plugin-elementui/src/prerender/colProps.ts: -------------------------------------------------------------------------------- 1 | import { HookInvoker } from '@json2render/vue' 2 | 3 | export default ({ proxy, context }: any): HookInvoker => 4 | (field, next) => { 5 | if (!field.col) { 6 | next(field) 7 | return 8 | } 9 | 10 | const colProps = field.col 11 | 12 | delete field.col 13 | 14 | next( 15 | proxy.inject( 16 | { 17 | component: 'el-col', 18 | props: typeof colProps === 'number' ? { span: colProps } : colProps, 19 | children: [field], 20 | }, 21 | context 22 | ) 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /packages/plugin-vue/src/index.ts: -------------------------------------------------------------------------------- 1 | import fetchDatasource from './datasource/fetch' 2 | import model from './prerender/model' 3 | import checked from './prerender/checked' 4 | import events from './prerender/events' 5 | import value from './prerender/value' 6 | import text from './render/text' 7 | import condition from './render/condition' 8 | 9 | export default ({ datasource, prerender, render }: any) => { 10 | datasource('fetch', fetchDatasource) 11 | 12 | prerender(model) 13 | prerender(value) 14 | prerender(checked) 15 | prerender(events) 16 | 17 | render(condition, -65535) 18 | render(text) 19 | } 20 | -------------------------------------------------------------------------------- /public/data/basic/events.json: -------------------------------------------------------------------------------- 1 | { 2 | "fields": [ 3 | { 4 | "component": "el-button", 5 | "text": "button", 6 | "props": { "onClick": "@:alert('clicked')" } 7 | }, 8 | { 9 | "component": "el-button", 10 | "text": "button", 11 | "events": [{ "name": "click", "handler": "@:alert('clicked')" }] 12 | }, 13 | { 14 | "component": "el-button", 15 | "text": "button", 16 | "events": [ 17 | { "name": "click", "handler": "@:alert('clicked 1')" }, 18 | { "name": "click", "handler": "@:alert('clicked 2')" } 19 | ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/plugin-elementui/src/prerender/vmodel.ts: -------------------------------------------------------------------------------- 1 | import { HookInvoker } from '@json2render/vue' 2 | import { deepGet, deepSet } from '@json2render/core' 3 | 4 | const elements: Record = { 5 | 'el-checkbox-group': [], 6 | 'el-select': null, 7 | } 8 | 9 | export default ({ context }: any): HookInvoker => 10 | (field, next) => { 11 | if (elements[field.component] === undefined || !field.model) { 12 | next(field) 13 | return 14 | } 15 | 16 | if (deepGet(context, field.model) === undefined) { 17 | deepSet(context, field.model, elements[field.component]) 18 | } 19 | 20 | next(field) 21 | } 22 | -------------------------------------------------------------------------------- /packages/vue-designer/src/components/Container/Component.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, reactive } from 'vue' 2 | 3 | export default defineComponent({ 4 | name: 'v-jdesigner-container', 5 | props: { 6 | type: { type: String, default: 'dock' }, // dock, float 7 | position: { type: String }, // left, right, top, bottom 8 | side: { type: String }, // outer, inner 9 | }, 10 | setup(props, ctx) { 11 | const reactived: any = reactive({}) 12 | 13 | // 这里实现标题栏和布局设置 14 | return () => ( 15 |
16 |
header
17 | {ctx.slots.default && ctx.slots.default()} 18 |
19 | ) 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /packages/plugin-elementui/src/prerender/formProps.ts: -------------------------------------------------------------------------------- 1 | import { HookInvoker } from '@json2render/vue' 2 | 3 | export default ({ proxy, context }: any): HookInvoker => { 4 | return (field, next) => { 5 | if (!field.form) { 6 | next(field) 7 | return 8 | } 9 | 10 | const formProps = field.form 11 | 12 | delete field.form 13 | 14 | next( 15 | proxy.inject( 16 | { 17 | component: 'el-form-item', 18 | props: 19 | typeof formProps === 'string' ? { label: formProps } : formProps, 20 | children: [field], 21 | }, 22 | context 23 | ) 24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/plugin-classics/src/functional/add.ts: -------------------------------------------------------------------------------- 1 | import multiply from './multiply' 2 | import division from './division' 3 | 4 | export default (...args: any[]) => { 5 | const ms = [] 6 | const values = [] 7 | 8 | for (let i = 0; i < args.length; i++) { 9 | let value 10 | try { 11 | value = args[i].toString().split('.')[1].length 12 | } catch { 13 | value = 0 14 | } 15 | ms.push(value) 16 | values.push(args[i] || 0) 17 | } 18 | 19 | const m = Math.pow(10, Math.max.apply(null, ms)) 20 | 21 | return division( 22 | values.reduce((pre, cur) => { 23 | return pre + multiply(cur, m) 24 | }, 0), 25 | m 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import jsx from '@vitejs/plugin-vue-jsx' 4 | 5 | const prefix = `monaco-editor/esm/vs` 6 | 7 | export default defineConfig({ 8 | base: './', 9 | build: { 10 | rollupOptions: { 11 | output: { 12 | manualChunks: { 13 | jsonWorker: [`${prefix}/language/json/json.worker`], 14 | cssWorker: [`${prefix}/language/css/css.worker`], 15 | htmlWorker: [`${prefix}/language/html/html.worker`], 16 | tsWorker: [`${prefix}/language/typescript/ts.worker`], 17 | editorWorker: [`${prefix}/editor/editor.worker`], 18 | }, 19 | }, 20 | }, 21 | }, 22 | plugins: [vue(), jsx()], 23 | }) 24 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/components/demo-show/imgs/codesandbox.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/core/src/di/error/cannotInjectValue.ts: -------------------------------------------------------------------------------- 1 | import { Constructable } from '../types/constructable' 2 | 3 | /** 4 | * Thrown when DI cannot inject value into property decorated by @Inject decorator. 5 | */ 6 | export class CannotInjectValueError extends Error { 7 | public name = 'CannotInjectValueError' 8 | 9 | get message(): string { 10 | return ( 11 | `Cannot inject value into "${this.target.constructor.name}.${this.propertyName}". ` + 12 | `Please make sure you setup reflect-metadata properly and you don't use interfaces without service tokens as injection value.` 13 | ) 14 | } 15 | 16 | constructor( 17 | private target: Constructable, 18 | private propertyName: string 19 | ) { 20 | super() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/core/src/feature/functional.ts: -------------------------------------------------------------------------------- 1 | import { createToken, ContainerInstance } from '../utils' 2 | import { FunctionalMeta } from '../types' 3 | 4 | export const functionalToken = createToken('functional') 5 | 6 | export const functionalServiceToken = 7 | createToken('functionalService') 8 | 9 | export class FunctionalService { 10 | private functionals: FunctionalMeta[] = [] 11 | 12 | constructor(container: ContainerInstance) { 13 | this.functionals = container.getMany(functionalToken) 14 | } 15 | 16 | getMap() { 17 | return this.functionals.reduce((pre, cur) => { 18 | pre[cur.name] = (...args: unknown[]) => cur.invoke(...args) 19 | return pre 20 | }, {} as { [key: string]: (...args: unknown[]) => unknown }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/data/basic/slots.json: -------------------------------------------------------------------------------- 1 | { 2 | "datasource": { 3 | "template": { 4 | "type": "fetch", 5 | "url": "/data/components/slotcmp.json", 6 | "auto": true, 7 | "props": { "method": "GET", "params": {} }, 8 | "defaultData": false 9 | } 10 | }, 11 | "fields": [ 12 | { 13 | "component": "div", 14 | "children": [ 15 | { 16 | "component": "v-jrender", 17 | "condition": "$:template.data", 18 | "props": "$:template.data", 19 | "children": { 20 | "default": [ 21 | { "component": "span", "text": "#:inner text ${scope.attrValue}" } 22 | ], 23 | "header": [{ "component": "span", "text": "标题slot内容" }] 24 | } 25 | } 26 | ] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | extends: [ 7 | 'plugin:vue/vue3-essential', 8 | 'eslint:recommended', 9 | '@vue/typescript/recommended', 10 | '@vue/prettier', 11 | '@vue/prettier/@typescript-eslint', 12 | ], 13 | parserOptions: { 14 | ecmaVersion: 2020, 15 | }, 16 | rules: { 17 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 18 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 19 | 'prettier/prettier': ['error', { singleQuote: true, semi: false }], 20 | '@typescript-eslint/no-explicit-any': 'off', 21 | '@typescript-eslint/explicit-module-boundary-types': 'off', 22 | '@typescript-eslint/no-var-requires': 'off', 23 | '@typescript-eslint/ban-types': 'off', 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /packages/vue/src/types.ts: -------------------------------------------------------------------------------- 1 | import { Setup } from '@json2render/core' 2 | import { Plugin } from 'vue' 3 | 4 | // component 5 | export interface ComponentMeta { 6 | name: string 7 | define?: any 8 | provider?: string 9 | } 10 | 11 | // hook 12 | export interface HookNext { 13 | (scope: any): void 14 | } 15 | 16 | export interface HookInvoker { 17 | (scope: any, next: HookNext): void 18 | } 19 | 20 | export interface Hook { 21 | (services: unknown): HookInvoker 22 | } 23 | 24 | export interface HookMeta { 25 | index: number 26 | invoke: Hook 27 | } 28 | 29 | export declare type FunctionPipeLine = ( 30 | hooks: HookMeta[], 31 | services: unknown 32 | ) => HookNext 33 | 34 | export interface HookService { 35 | process: (value: any, extra: HookMeta[]) => void 36 | } 37 | 38 | export declare type JRenderPlugin = Plugin & { use: Setup } 39 | -------------------------------------------------------------------------------- /public/data/basic/relation.json: -------------------------------------------------------------------------------- 1 | { 2 | "fields": [ 3 | { "component": "el-input", "model": "model.text" }, 4 | { 5 | "component": "div", 6 | "children": [ 7 | { 8 | "component": "el-checkbox", 9 | "condition": "$:!!model.text", 10 | "model": "model.checked", 11 | "children": [{ "component": "span", "text": "选中" }] 12 | } 13 | ] 14 | }, 15 | { 16 | "component": "el-select", 17 | "condition": "$:!!model.checked", 18 | "model": "model.selected", 19 | "children": [ 20 | { 21 | "component": "el-option", 22 | "props": { "value": 1, "label": "select1" } 23 | }, 24 | { 25 | "component": "el-option", 26 | "props": { "value": 2, "label": "select2" } 27 | } 28 | ] 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "experimentalDecorators": true, 8 | "importHelpers": true, 9 | "moduleResolution": "node", 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "allowJs": true, 14 | "sourceMap": true, 15 | "baseUrl": ".", 16 | "types": ["vite/client"], 17 | "paths": { 18 | "@/*": ["src/*"] 19 | }, 20 | "lib": ["ES2020", "ES2015", "dom", "dom.iterable", "scripthost"] 21 | }, 22 | "include": [ 23 | "src/**/*.ts", 24 | "src/**/*.tsx", 25 | "src/**/*.vue", 26 | "packages/**/*.ts", 27 | "tests/**/*.ts", 28 | "tests/**/*.tsx", 29 | "typings/**/*.d.ts" 30 | ], 31 | "exclude": ["node_modules"] 32 | } 33 | -------------------------------------------------------------------------------- /packages/plugin-vue/src/prerender/value.ts: -------------------------------------------------------------------------------- 1 | import { assignObject } from '@json2render/core' 2 | import { HookInvoker } from '@json2render/vue' 3 | 4 | export default (): HookInvoker => { 5 | return (field, next) => { 6 | if (field.value === undefined) { 7 | next(field) 8 | return 9 | } 10 | 11 | if (typeof field.value !== 'string' || field.value.length <= 0) { 12 | next(field) 13 | return 14 | } 15 | 16 | const valueDefine = { 17 | $type: 'computed', 18 | $result: field.value, 19 | } 20 | 21 | const updateDefine = { 22 | $type: 'method', 23 | $context: field.value, 24 | $result: 'arguments[0].target.value', 25 | } 26 | 27 | field.props = assignObject(field.props || {}, { 28 | value: valueDefine, 29 | onInput: updateDefine, 30 | }) 31 | 32 | next(field) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docs/guide/getting-started.md: -------------------------------------------------------------------------------- 1 | # 快速上手 2 | 3 | 通过 npm 安装 4 | 5 | ```bash 6 | npm i @json2render/vue-full 7 | ``` 8 | 9 | 实现一个简单示例 10 | 11 | main.js 12 | 13 | ```javascript 14 | import { createApp } from 'vue' 15 | import App from './App.vue' 16 | import JRender from '@json2render/vue-full' 17 | 18 | createApp(App).use(JRender).mount('#app') 19 | ``` 20 | 21 | App.vue 22 | 23 | ```vue 24 | 29 | 30 | 44 | ``` 45 | -------------------------------------------------------------------------------- /packages/plugin-vue/src/prerender/model.ts: -------------------------------------------------------------------------------- 1 | import { assignObject } from '@json2render/core' 2 | import { HookInvoker } from '@json2render/vue' 3 | 4 | export default (): HookInvoker => { 5 | return (field, next) => { 6 | if (field.model === undefined) { 7 | next(field) 8 | return 9 | } 10 | 11 | if (typeof field.model !== 'string' || field.model.length <= 0) { 12 | next(field) 13 | return 14 | } 15 | 16 | const valueDefine = { 17 | $type: 'computed', 18 | $result: field.model, 19 | } 20 | 21 | const updateDefine = { 22 | $type: 'method', 23 | $context: field.model, 24 | $result: 'arguments[0]', 25 | } 26 | 27 | field.props = assignObject(field.props || {}, { 28 | modelValue: valueDefine, 29 | 'onUpdate:modelValue': updateDefine, 30 | }) 31 | 32 | next(field) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/plugin-vue/src/prerender/checked.ts: -------------------------------------------------------------------------------- 1 | import { assignObject } from '@json2render/core' 2 | import { HookInvoker } from '@json2render/vue' 3 | 4 | export default (): HookInvoker => { 5 | return (field, next) => { 6 | if (field.checked == undefined) { 7 | next(field) 8 | return 9 | } 10 | 11 | if (typeof field.checked !== 'string' || field.checked.length <= 0) { 12 | next(field) 13 | return 14 | } 15 | 16 | const valueDefine = { 17 | $type: 'computed', 18 | $result: field.checked, 19 | } 20 | 21 | const updateDefine = { 22 | $type: 'method', 23 | $context: field.checked, 24 | $result: 'arguments[0].target.checked', 25 | } 26 | 27 | field.props = assignObject(field.props || {}, { 28 | value: valueDefine, 29 | onChange: updateDefine, 30 | }) 31 | 32 | next(field) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/components/demo-show/imgs/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /packages/plugin-vue/src/datasource/fetch.ts: -------------------------------------------------------------------------------- 1 | import { cloneDeep } from '@json2render/core' 2 | import { reactive } from 'vue' 3 | 4 | export default (options: any) => { 5 | const { props } = options 6 | 7 | const instance = reactive({ 8 | data: 9 | options.defaultData !== undefined ? cloneDeep(options.defaultData) : [], 10 | loading: false, 11 | request: async () => { 12 | instance.loading = true 13 | 14 | try { 15 | const response: any = await fetch(options.url, props) 16 | 17 | const result = 18 | !props.dataType || props.dataType === 'json' 19 | ? await response.json() 20 | : await response.text() 21 | 22 | instance.data = result 23 | } finally { 24 | instance.loading = false 25 | } 26 | }, 27 | }) 28 | 29 | if (options.auto) { 30 | instance.request() 31 | } 32 | 33 | return instance 34 | } 35 | -------------------------------------------------------------------------------- /public/data/condition.json: -------------------------------------------------------------------------------- 1 | { 2 | "fields": [ 3 | { 4 | "component": "p", 5 | "condition": { "$type": "computed", "$result": "model.obj.selected" }, 6 | "text": { "$type": "computed", "$result": "model.text1" } 7 | }, 8 | { 9 | "component": "el-select", 10 | "props": { 11 | "modelValue": { 12 | "$type": "computed", 13 | "$result": "model.obj.selected" 14 | }, 15 | "onUpdate:modelValue": { 16 | "$type": "method", 17 | "$context": "model.obj.selected", 18 | "$result": "arguments[0]" 19 | } 20 | }, 21 | "children": [ 22 | { 23 | "component": "el-option", 24 | "props": { "value": 0, "label": "选项1" } 25 | }, 26 | { 27 | "component": "el-option", 28 | "props": { "value": 1, "label": "选项2" } 29 | } 30 | ] 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /public/data/basic/simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "fields": [ 3 | { 4 | "component": "h1", 5 | "props": { "style": { "fontSize": "40pt" }, "innerText": "H1 text" } 6 | }, 7 | { 8 | "component": "h2", 9 | "props": { "style": { "fontSize": "30pt" }, "innerText": "H2 text" } 10 | }, 11 | { 12 | "component": "h3", 13 | "props": { "style": { "fontSize": "20pt" }, "innerText": "H3 text" } 14 | }, 15 | { "component": "p", "props": { "innerText": "p text" } }, 16 | { 17 | "component": "ul", 18 | "props": { 19 | "style": { 20 | "marginTop": "0.25rem", 21 | "marginLeft": "1.25rem", 22 | "listStyle": "disc" 23 | } 24 | }, 25 | "children": [ 26 | { "component": "li", "text": "item1" }, 27 | { "component": "li", "text": "item2" }, 28 | { "component": "li", "text": "item3" } 29 | ] 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /packages/plugin-classics/src/proxy/computed.ts: -------------------------------------------------------------------------------- 1 | import { assignArray, ProxyHandler, ProxyMatcher } from '@json2render/core' 2 | 3 | const computed: ProxyMatcher = (value: any, { functional }) => { 4 | const func: ProxyHandler = (context) => { 5 | try { 6 | const contextKeys = Object.keys(context) 7 | const functionals = functional.getMap() 8 | const functionalKeys = Object.keys(functionals) 9 | 10 | return new Function( 11 | ...assignArray(contextKeys, functionalKeys, [`return ${value.$result}`]) 12 | )( 13 | ...assignArray( 14 | contextKeys.map((key) => context[key]), 15 | functionalKeys.map((key) => functionals[key]) 16 | ) 17 | ) 18 | } catch { 19 | // 20 | } 21 | } 22 | 23 | if ( 24 | typeof value === 'object' && 25 | value && 26 | value.$type === 'computed' && 27 | value.$result 28 | ) { 29 | return func 30 | } 31 | } 32 | 33 | export default computed 34 | -------------------------------------------------------------------------------- /packages/plugin-modern/src/proxy/expression.ts: -------------------------------------------------------------------------------- 1 | import { assignArray, ProxyMatcher, ProxyHandler } from '@json2render/core' 2 | 3 | const expression: ProxyMatcher = (value: any, { functional }) => { 4 | const func: ProxyHandler = (context) => { 5 | try { 6 | const expr = value.slice(value.indexOf(':') + 1, value.length) 7 | const contextKeys = Object.keys(context) 8 | const functionals = functional.getMap() 9 | const functionalKeys = Object.keys(functionals) 10 | 11 | return new Function( 12 | ...assignArray(contextKeys, functionalKeys, [`return ${expr}`]) 13 | )( 14 | ...assignArray( 15 | contextKeys.map((key) => context[key]), 16 | functionalKeys.map((key) => functionals[key]) 17 | ) 18 | ) 19 | } catch { 20 | // 21 | } 22 | } 23 | 24 | if (typeof value === 'string' && value.indexOf('$:') === 0) { 25 | return func 26 | } 27 | } 28 | 29 | export default expression 30 | -------------------------------------------------------------------------------- /packages/plugin-elementui/src/render/options.ts: -------------------------------------------------------------------------------- 1 | import { HookInvoker } from '@json2render/vue' 2 | 3 | export default (): HookInvoker => (field, next) => { 4 | if (!field.options || !field.options.component || !field.options.items) { 5 | next(field) 6 | return 7 | } 8 | 9 | const options = field.options 10 | 11 | delete field.options 12 | 13 | const labelProp = options.labelProp || 'label' 14 | const valueProp = options.valueProp || 'value' 15 | const textProp = options.textProp || 'text' 16 | 17 | field.children = options.items.map((item: any) => { 18 | const child: any = { 19 | component: options.component, 20 | props: { 21 | label: item[labelProp], 22 | value: item[valueProp], 23 | }, 24 | } 25 | 26 | if (item[textProp]) { 27 | child.children = [ 28 | { component: 'span', props: { innerText: item[textProp] } }, 29 | ] 30 | } 31 | 32 | return child 33 | }) 34 | 35 | next(field) 36 | } 37 | -------------------------------------------------------------------------------- /packages/plugin-modern/src/proxy/template.ts: -------------------------------------------------------------------------------- 1 | import { assignArray, ProxyHandler, ProxyMatcher } from '@json2render/core' 2 | 3 | const template: ProxyMatcher = (value: any, { functional }) => { 4 | const func: ProxyHandler = (context) => { 5 | try { 6 | const expr = value.slice(value.indexOf(':') + 1, value.length) 7 | const contextKeys = Object.keys(context) 8 | const functionals = functional.getMap() 9 | const functionalKeys = Object.keys(functionals) 10 | const exprStr = '`' + expr + '`' 11 | 12 | return new Function( 13 | ...assignArray(contextKeys, functionalKeys, [`return ${exprStr}`]) 14 | )( 15 | ...assignArray( 16 | contextKeys.map((key) => context[key]), 17 | functionalKeys.map((key) => functionals[key]) 18 | ) 19 | ) 20 | } catch { 21 | // 22 | } 23 | } 24 | 25 | if (typeof value === 'string' && /^([#]:)/g.test(value)) { 26 | return func 27 | } 28 | } 29 | 30 | export default template 31 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' 2 | // import Test from '../views/Test' 3 | import Home from '../views/Home' 4 | 5 | const routes: Array = [ 6 | // { 7 | // path: '/', 8 | // name: 'Test', 9 | // component: Test, 10 | // }, 11 | { 12 | path: '/', 13 | name: 'Home', 14 | component: Home, 15 | }, 16 | { 17 | path: '/element', 18 | name: 'Element', 19 | component: () => import('../views/Element.vue'), 20 | }, 21 | { 22 | path: '/tabs', 23 | name: 'Tabs', 24 | component: () => import('../views/Tabs'), 25 | }, 26 | { 27 | path: '/designer', 28 | name: 'Designer', 29 | component: () => import('../views/Designer'), 30 | }, 31 | { 32 | path: '/yaml', 33 | name: 'Yaml', 34 | component: () => import('../views/Yaml'), 35 | }, 36 | ] 37 | 38 | const router = createRouter({ 39 | history: createWebHistory(), 40 | routes, 41 | }) 42 | 43 | export default router 44 | -------------------------------------------------------------------------------- /docs/.vitepress/configs/sidebar.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '/guide/': [ 3 | { 4 | text: '指南', 5 | children: [ 6 | { text: '介绍', link: '/guide/' }, 7 | { text: '快速上手', link: '/guide/getting-started' }, 8 | { text: '在线示例', link: '/guide/examples' }, 9 | { text: '仓库说明', link: '/guide/repo' }, 10 | ], 11 | }, 12 | { 13 | text: '组件', 14 | children: [ 15 | { text: '使用组件', link: '/guide/reference-component' }, 16 | { text: '配置', link: '/guide/configs' }, 17 | { text: '数据源', link: '/guide/datasource' }, 18 | { text: '属性表达式', link: '/guide/prop-transform' }, 19 | { text: '快捷属性', link: '/guide/prop-render' }, 20 | { text: '功能函数', link: '/guide/functional' }, 21 | { text: '监听', link: '/guide/listeners' }, 22 | ], 23 | }, 24 | { 25 | text: '高级', 26 | children: [{ text: '自定义', link: '/guide/setup' }], 27 | }, 28 | ], 29 | '/reference/json-to-render/': 'auto', 30 | '/reference/designer/': 'auto', 31 | } 32 | -------------------------------------------------------------------------------- /src/views/Designer.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import Designer from '@json2render/vue-designer' 3 | 4 | export default defineComponent({ 5 | components: { Designer }, 6 | setup() { 7 | // const config = { 8 | // fields: [ 9 | // { 10 | // component: 'v-jdesigner-toolbox', 11 | // props: { style: { width: '200px' } }, 12 | // }, 13 | // { 14 | // component: 'v-jdesigner-container', 15 | // props: { style: {} }, 16 | // }, 17 | // { 18 | // component: 'v-jdesigner-container', 19 | // props: { style: {} }, 20 | // }, 21 | // ], 22 | // } 23 | const config: any[] = [ 24 | { 25 | component: 'v-jdesigner-toolbox', 26 | location: {}, 27 | }, 28 | { 29 | component: 'v-jdesigner-propertybox', 30 | location: {}, 31 | }, 32 | { 33 | component: 'property', 34 | location: {}, 35 | }, 36 | ] 37 | 38 | return () => 39 | }, 40 | }) 41 | -------------------------------------------------------------------------------- /packages/core/src/di/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * We have a hard dependency on reflect-metadata package. 3 | * Without the dependency lookup wont work. So we should warn the users 4 | * when it's not loaded. 5 | */ 6 | // if(!Reflect || !(Reflect as any).getMetadata) { 7 | // throw new Error('Reflect.getMetadata is not a function. Please import the "reflect-metadata" package at the first line of your application.'); 8 | // } 9 | 10 | import { Container } from './container' 11 | 12 | export * from './decorators/injectMany' 13 | export * from './decorators/inject' 14 | export * from './decorators/service' 15 | 16 | export * from './error/cannotInjectValue' 17 | export * from './error/cannotInstantiateValue' 18 | export * from './error/serviceNotFound' 19 | 20 | export * from './interfaces/handler' 21 | export * from './interfaces/serviceMetadata' 22 | export * from './interfaces/serviceOptions' 23 | export * from './types/constructable' 24 | export * from './types/serviceIdentifier' 25 | 26 | export * from './container' 27 | export * from './container' 28 | export * from './token' 29 | 30 | export default Container 31 | -------------------------------------------------------------------------------- /packages/core/src/di/interfaces/handler.ts: -------------------------------------------------------------------------------- 1 | import { ContainerInstance } from '../container' 2 | import { Constructable } from '../types/constructable' 3 | 4 | /** 5 | * Used to register special "handler" which will be executed on a service class during its initialization. 6 | * It can be used to create custom decorators and set/replace service class properties and constructor parameters. 7 | */ 8 | export interface Handler { 9 | /** 10 | * Service object used to apply handler to. 11 | */ 12 | object: Constructable 13 | 14 | /** 15 | * Class property name to set/replace value of. 16 | * Used if handler is applied on a class property. 17 | */ 18 | propertyName?: string 19 | 20 | /** 21 | * Parameter index to set/replace value of. 22 | * Used if handler is applied on a constructor parameter. 23 | */ 24 | index?: number 25 | 26 | /** 27 | * Factory function that produces value that will be set to class property or constructor parameter. 28 | * Accepts container instance which requested the value. 29 | */ 30 | value: (container: ContainerInstance) => any 31 | } 32 | -------------------------------------------------------------------------------- /packages/plugin-modern/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@json2render/plugin-modern", 3 | "version": "1.0.2", 4 | "description": "> TODO: description", 5 | "author": "fyl080801 ", 6 | "homepage": "https://github.com/fyl080801/json-to-render#readme", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/fyl080801/json-to-render.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/fyl080801/json-to-render/issues" 14 | }, 15 | "main": "src/index.ts", 16 | "module": "src/index.ts", 17 | "browser": "dist/index.umd.js", 18 | "unpkg": "dist/index.min.js", 19 | "directories": { 20 | "src": "src" 21 | }, 22 | "files": [ 23 | "src", 24 | "dist" 25 | ], 26 | "publishConfig": { 27 | "access": "public" 28 | }, 29 | "scripts": { 30 | "clean": "rimraf dist", 31 | "dist": "yarn clean && cross-env NODE_ENV=production rollup --config rollup.config.js" 32 | }, 33 | "dependencies": { 34 | "@json2render/core": "^1.0.2" 35 | }, 36 | "gitHead": "6b7755b42a7f2c4e549d815da91bb3727594ef03" 37 | } 38 | -------------------------------------------------------------------------------- /packages/plugin-classics/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@json2render/plugin-classics", 3 | "version": "1.0.2", 4 | "description": "> TODO: description", 5 | "author": "fyl080801 ", 6 | "homepage": "https://github.com/fyl080801/json-to-render#readme", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/fyl080801/json-to-render.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/fyl080801/json-to-render/issues" 14 | }, 15 | "main": "src/index.ts", 16 | "module": "src/index.ts", 17 | "browser": "dist/index.umd.js", 18 | "unpkg": "dist/index.min.js", 19 | "directories": { 20 | "src": "src" 21 | }, 22 | "files": [ 23 | "src", 24 | "dist" 25 | ], 26 | "publishConfig": { 27 | "access": "public" 28 | }, 29 | "scripts": { 30 | "clean": "rimraf dist", 31 | "dist": "yarn clean && cross-env NODE_ENV=production rollup --config rollup.config.js" 32 | }, 33 | "dependencies": { 34 | "@json2render/core": "^1.0.2" 35 | }, 36 | "gitHead": "6b7755b42a7f2c4e549d815da91bb3727594ef03" 37 | } 38 | -------------------------------------------------------------------------------- /packages/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@json2render/vue", 3 | "version": "1.2.1", 4 | "description": "> TODO: description", 5 | "author": "fyl080801 ", 6 | "homepage": "https://github.com/fyl080801/json-to-render#readme", 7 | "license": "MIT", 8 | "main": "src/index.ts", 9 | "module": "src/index.ts", 10 | "browser": "dist/index.umd.js", 11 | "unpkg": "dist/index.min.js", 12 | "directories": { 13 | "src": "src" 14 | }, 15 | "files": [ 16 | "src", 17 | "dist" 18 | ], 19 | "publishConfig": { 20 | "access": "public" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/fyl080801/json-to-render.git" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/fyl080801/json-to-render/issues" 28 | }, 29 | "scripts": { 30 | "clean": "rimraf dist", 31 | "dist": "yarn clean && cross-env NODE_ENV=production rollup --config rollup.config.js" 32 | }, 33 | "dependencies": { 34 | "@json2render/core": "^1.0.2", 35 | "vue": "3.1.4" 36 | }, 37 | "gitHead": "6b7755b42a7f2c4e549d815da91bb3727594ef03" 38 | } 39 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@json2render/core", 3 | "version": "1.0.2", 4 | "description": "> TODO: description", 5 | "author": "fyl080801 ", 6 | "homepage": "https://github.com/fyl080801/json-to-render#readme", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/fyl080801/json-to-render.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/fyl080801/json-to-render/issues" 14 | }, 15 | "main": "src/index.ts", 16 | "module": "src/index.ts", 17 | "browser": "dist/index.umd.js", 18 | "unpkg": "dist/index.min.js", 19 | "directories": { 20 | "src": "src" 21 | }, 22 | "files": [ 23 | "src", 24 | "dist" 25 | ], 26 | "publishConfig": { 27 | "access": "public" 28 | }, 29 | "scripts": { 30 | "clean": "rimraf dist", 31 | "dist": "yarn clean && cross-env NODE_ENV=production rollup --config rollup.config.js" 32 | }, 33 | "dependencies": { 34 | "reflect-metadata": "^0.1.13", 35 | "uuid": "^8.3.2" 36 | }, 37 | "gitHead": "6b7755b42a7f2c4e549d815da91bb3727594ef03" 38 | } 39 | -------------------------------------------------------------------------------- /packages/vue-designer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@json2render/vue-designer", 3 | "version": "1.0.4", 4 | "description": "> TODO: description", 5 | "author": "fengyuanliang3 ", 6 | "homepage": "https://github.com/fyl080801/json-to-render/tree/main/packages/vue-designer#readme", 7 | "license": "MIT", 8 | "main": "src/index.ts", 9 | "module": "src/index.ts", 10 | "browser": "dist/index.umd.js", 11 | "unpkg": "dist/index.min.js", 12 | "directories": { 13 | "src": "src" 14 | }, 15 | "files": [ 16 | "src", 17 | "dist" 18 | ], 19 | "publishConfig": { 20 | "access": "public" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/fyl080801/json-to-render.git" 25 | }, 26 | "scripts": { 27 | "clean": "rimraf dist", 28 | "dist": "yarn clean && cross-env NODE_ENV=production rollup --config rollup.config.js" 29 | }, 30 | "dependencies": { 31 | "@json2render/core": "^1.0.2", 32 | "@json2render/vue": "^1.2.1" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/fyl080801/json-to-render/issues" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 yuanliang feng 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. -------------------------------------------------------------------------------- /packages/plugin-vue/src/prerender/events.ts: -------------------------------------------------------------------------------- 1 | import { getProxyDefine, isArray } from '@json2render/core' 2 | import { HookInvoker } from '@json2render/vue' 3 | 4 | export default (services: any): HookInvoker => { 5 | return (field, next) => { 6 | if (field.events == undefined) { 7 | next(field) 8 | return 9 | } 10 | 11 | const define = getProxyDefine(field.events) 12 | 13 | field.props = field.props || {} 14 | 15 | if (isArray(define)) { 16 | const events = define.reduce((props: any, evt: any) => { 17 | props[evt.name] = props[evt.name] || [] 18 | 19 | props[evt.name].push(evt) 20 | 21 | return props 22 | }, {}) 23 | 24 | field.props = Object.keys(events).reduce((props, key) => { 25 | props[`on${key.charAt(0).toUpperCase() + key.slice(1)}`] = ( 26 | ...args: any 27 | ) => { 28 | events[key].forEach((evt: any) => { 29 | services.proxy.inject(evt, services.context)?.handler(...args) 30 | }) 31 | } 32 | return props 33 | }, field.props) 34 | } 35 | 36 | next(field) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /docs/.vitepress/configs/navbar.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { text: '指南', link: '/guide/' }, 3 | // { 4 | // text: '参考', 5 | // items: [ 6 | // { 7 | // text: 'JsonToRender', 8 | // // link: '/reference/json-to-render/', 9 | // items: [{ text: '属性', link: '/reference/json-to-render/00.props' }], 10 | // }, 11 | // { 12 | // text: '设计器', 13 | // link: '/reference/designer/', 14 | // // items: [{ text: '说明', link: '/reference/designer/' }], 15 | // }, 16 | // ], 17 | // }, 18 | // { text: '指南', link: '/guide/00.intro.html' }, 19 | // { 20 | // text: '参考', 21 | // items: [ 22 | // { 23 | // text: 'JsonToRender', 24 | // items: [{ text: 'a', link: '/reference/json-to-render/00.props.html' }], 25 | // }, 26 | // { 27 | // text: '设计器', 28 | // items: [{ text: 'b', link: '/reference/designer/00.intro.html' }], 29 | // }, 30 | // ], 31 | // }, 32 | { text: 'Github', link: 'https://github.com/fyl080801/json-to-render' }, 33 | { text: 'Gitee', link: 'https://gitee.com/fyl080801/json-to-render' }, 34 | ] 35 | -------------------------------------------------------------------------------- /packages/plugin-vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@json2render/plugin-vue", 3 | "version": "1.1.2", 4 | "description": "> TODO: description", 5 | "author": "fyl080801 ", 6 | "homepage": "https://github.com/fyl080801/json-to-render#readme", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/fyl080801/json-to-render.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/fyl080801/json-to-render/issues" 14 | }, 15 | "main": "src/index.ts", 16 | "module": "src/index.ts", 17 | "browser": "dist/index.umd.js", 18 | "unpkg": "dist/index.min.js", 19 | "directories": { 20 | "src": "src" 21 | }, 22 | "files": [ 23 | "src", 24 | "dist" 25 | ], 26 | "publishConfig": { 27 | "access": "public" 28 | }, 29 | "scripts": { 30 | "clean": "rimraf dist", 31 | "dist": "yarn clean && cross-env NODE_ENV=production rollup --config rollup.config.js" 32 | }, 33 | "dependencies": { 34 | "@json2render/core": "^1.0.2", 35 | "@json2render/vue": "^1.2.1" 36 | }, 37 | "gitHead": "6b7755b42a7f2c4e549d815da91bb3727594ef03" 38 | } 39 | -------------------------------------------------------------------------------- /packages/plugin-elementui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@json2render/plugin-elementui", 3 | "version": "1.2.2", 4 | "description": "> TODO: description", 5 | "author": "fengyuanliang3 ", 6 | "homepage": "https://github.com/fyl080801/json-to-render/tree/main/packages/plugin-elementui#readme", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/fyl080801/json-to-render.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/fyl080801/json-to-render/issues" 14 | }, 15 | "main": "src/index.ts", 16 | "module": "src/index.ts", 17 | "browser": "dist/index.umd.js", 18 | "unpkg": "dist/index.min.js", 19 | "directories": { 20 | "src": "src" 21 | }, 22 | "files": [ 23 | "src", 24 | "dist" 25 | ], 26 | "publishConfig": { 27 | "access": "public" 28 | }, 29 | "scripts": { 30 | "clean": "rimraf dist", 31 | "dist": "yarn clean && cross-env NODE_ENV=production rollup --config rollup.config.js" 32 | }, 33 | "dependencies": { 34 | "@json2render/core": "^1.0.2", 35 | "@json2render/vue": "^1.2.1", 36 | "element-plus": "^1.0.2-beta.58" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/vue-full/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@json2render/vue-full", 3 | "version": "1.0.4", 4 | "description": "> TODO: description", 5 | "author": "fyl080801 ", 6 | "homepage": "https://github.com/fyl080801/json-to-render#readme", 7 | "license": "MIT", 8 | "main": "src/index.ts", 9 | "module": "src/index.ts", 10 | "browser": "dist/index.umd.js", 11 | "unpkg": "dist/index.min.js", 12 | "directories": { 13 | "src": "src" 14 | }, 15 | "files": [ 16 | "src", 17 | "dist" 18 | ], 19 | "publishConfig": { 20 | "access": "public" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/fyl080801/json-to-render.git" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/fyl080801/json-to-render/issues" 28 | }, 29 | "scripts": { 30 | "clean": "rimraf dist", 31 | "dist": "yarn clean && cross-env NODE_ENV=production rollup --config rollup.config.js" 32 | }, 33 | "dependencies": { 34 | "@json2render/plugin-classics": "^1.0.2", 35 | "@json2render/plugin-modern": "^1.0.2", 36 | "@json2render/plugin-vue": "^1.1.2", 37 | "@json2render/vue": "^1.2.1" 38 | }, 39 | "gitHead": "6b7755b42a7f2c4e549d815da91bb3727594ef03" 40 | } 41 | -------------------------------------------------------------------------------- /public/data/dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "fields": [ 3 | { 4 | "component": "p", 5 | "condition": "$:model.obj.selected", 6 | "props": { 7 | "innerText": { "$type": "computed", "$result": "model.text1" } 8 | } 9 | }, 10 | { 11 | "component": "input", 12 | "props": { 13 | "value": { "$type": "computed", "$result": "model.text1" }, 14 | "onInput": { 15 | "$type": "method", 16 | "$context": "model.text1", 17 | "$result": "arguments[0].target.value" 18 | } 19 | } 20 | }, 21 | { 22 | "component": "input", 23 | "value": "model.text1" 24 | }, 25 | { 26 | "component": "el-input", 27 | "model": "model.text1" 28 | }, 29 | { 30 | "component": "el-select", 31 | "model": "model.obj.selected", 32 | "children": [ 33 | { 34 | "component": "el-option", 35 | "props": { "value": 0, "label": "选项1" } 36 | }, 37 | { 38 | "component": "el-option", 39 | "props": { "value": 1, "label": "选项2" } 40 | } 41 | ] 42 | }, 43 | { 44 | "component": "span", 45 | "condition": "$:model.obj.selected", 46 | "text": "$:model.text1" 47 | } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /packages/plugin-classics/src/index.ts: -------------------------------------------------------------------------------- 1 | import computed from './proxy/computed' 2 | import method from './proxy/method' 3 | 4 | import add from './functional/add' 5 | import division from './functional/division' 6 | import equal from './functional/equal' 7 | import filter from './functional/filter' 8 | import find from './functional/find' 9 | import get from './functional/get' 10 | import ifFunctional from './functional/if' 11 | import includes from './functional/includes' 12 | import map from './functional/map' 13 | import multiply from './functional/multiply' 14 | import reduce from './functional/reduce' 15 | import subtraction from './functional/subtraction' 16 | import textFunctional from './functional/text' 17 | 18 | export default ({ proxy, functional }: any) => { 19 | proxy(computed) 20 | proxy(method) 21 | 22 | functional('ADD', add) 23 | functional('DIVISION', division) 24 | functional('EQUAL', equal) 25 | functional('FILTER', filter) 26 | functional('FIND', find) 27 | functional('GET', get) 28 | functional('IF', ifFunctional) 29 | functional('INCLUDES', includes) 30 | functional('MAP', map) 31 | functional('MULTIPLY', multiply) 32 | functional('REDUCE', reduce) 33 | functional('SUBTRACTION', subtraction) 34 | functional('TEXT', textFunctional) 35 | } 36 | -------------------------------------------------------------------------------- /docs/guide/index.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | json to render 是一个前端界面渲染库,采用 [vjform](https://github.com/fyl080801/vjform) 的思想使用 typescript 重构,可以将 json 定义渲染成可交互界面,目前提供基于 vue3 版本支持 4 | 5 | 支持任何 html 标签和组件库以及自定义组件 6 | 7 | ## 开发动机 8 | 9 | ### json 数据的限制 10 | 11 | json 是一种在前端开发中广泛应用的数据格式,但 json 数据具有局限性,描述对象时 json 只能使用几种基本类型数据作为对象属性值,而且 json 数据无法表示属性与数据之间的关联关系以及函数,json 不是 js 对象存储时特殊属性值会丢失 12 | 13 | ### 组件库限制 14 | 15 | 常规的 json 动态渲染库一般都是基于某个组件库开发或提供一组固定的组件支持,如果想支持新的组件库或新的组件则需要额外开发 16 | 17 | ### 基于 vue2 的版本 18 | 19 | 之前开发过基于 vue2 的动态渲染组件 [vjform](https://github.com/fyl080801/vjform) 以及设计器 [vjdesign](https://fyl080801.github.io/vjdesign/),由于 vue2 和 vue3 存在一些本质的区别,例如数据变化的监听方式,而且 vue3 基于 Proxy 的实现也给动态渲染提供了一个思路,优化以前的实现 20 | 21 | ## 目标及特性 22 | 23 | json to render 的核心思想主要有两点: 24 | 25 | 1. 解析 json 数据特殊定义,用来表示组件属性与数据的关联关系、联动关系以及数据计算处理 26 | 2. 在渲染前根据特殊条件对 json 进行操作,实现自定义的规则 27 | 28 | 要实现的目标包括: 29 | 30 | - 通过 json 动态渲染界面(必要) 31 | - 支持通过 json 渲染任何 html 标签、组件库、自定义组件(必要) 32 | - 通过特定表达式表示数据与组件属性的关联关系和函数的定义(必要) 33 | - 通过二次开发支持更多的表达式解析方式和渲染前对 json 数据的处理方式(必要) 34 | - 可视化设计器(计划中) 35 | - react 支持(计划中) 36 | - angular 支持(计划中) 37 | 38 | ## 它是如何工作的? 39 | 40 | 界面元素被定义成组件,每个组件包含组件的属性和子集,用专门的 Node 节点根据当前节点的 json 数据处理每个组件的渲染,在渲染之前利用 `Proxy` 将节点数据转换成代理对象,节点属性值如果是特殊的定义格式(比如关联关系、函数)则会在渲染时将定义转换成真实结果,在渲染前也允许对 json 数据进行改变,实现只操作 json 数据就可以改变最终渲染结果 41 | -------------------------------------------------------------------------------- /packages/vue/src/utils/pipeline.ts: -------------------------------------------------------------------------------- 1 | import { HookMeta, HookNext, FunctionPipeLine } from '../types' 2 | 3 | const pipeline: FunctionPipeLine = (hooks, services) => { 4 | return (scope: any) => { 5 | // 全局索引 6 | let index = -1 7 | let currentScope = scope 8 | 9 | // 索引遍历的递归委托,返回当前索引函数执行的 Promise 对象 10 | const dispatch = (i: number) => { 11 | // 全局索引比当前索引大,说明已经执行过一次方法了,全局索引已经被加 1 12 | if (index >= i) return Promise.reject(new Error('next 执行了多次')) 13 | 14 | // 更新全局索引 15 | index = i 16 | 17 | // 下个方法委托,返回索引遍历的递归委托执行结果 18 | const next: HookNext = (nextScope: any) => { 19 | currentScope = nextScope 20 | // 执行时索引加 1 21 | return dispatch(i + 1) 22 | } 23 | 24 | // 方法执行当前索引是否超过函数数组索引 25 | // 如果超过了则当前函数是空 26 | // 否则指针移到当前索引位置,得到当前需要执行的函数 27 | const current: HookMeta | null = i >= hooks.length ? null : hooks[i] 28 | 29 | // 当前函数是空的,说明最后一个索引的函数已经执行完毕 30 | if (!current) { 31 | return Promise.resolve() 32 | } 33 | 34 | // 返回一个 Promise 对象,任务是执行当前指针处的函数 35 | // 将上下文对象和返回下个方法的委托函数作为参数传递进去 36 | return Promise.resolve(current.invoke(services)(currentScope, next)) 37 | } 38 | 39 | // 传递索引 0 开始执行索引遍历递归委托 40 | return dispatch(0) 41 | } 42 | } 43 | 44 | export default pipeline 45 | -------------------------------------------------------------------------------- /packages/core/src/di/error/serviceNotFound.ts: -------------------------------------------------------------------------------- 1 | import { ServiceIdentifier } from '../types/serviceIdentifier' 2 | import { Token } from '../token' 3 | 4 | /** 5 | * Thrown when requested service was not found. 6 | */ 7 | export class ServiceNotFoundError extends Error { 8 | public name = 'ServiceNotFoundError' 9 | 10 | /** Normalized identifier name used in the error message. */ 11 | private normalizedIdentifier = '' 12 | 13 | get message(): string { 14 | return ( 15 | `Service with "${this.normalizedIdentifier}" identifier was not found in the container. ` + 16 | `Register it before usage via explicitly calling the "Container.set" function or using the "@Service()" decorator.` 17 | ) 18 | } 19 | 20 | constructor(identifier: ServiceIdentifier) { 21 | super() 22 | 23 | if (typeof identifier === 'string') { 24 | this.normalizedIdentifier = identifier 25 | } else if (identifier instanceof Token) { 26 | this.normalizedIdentifier = `Token<${identifier.name || 'UNSET_NAME'}>` 27 | } else if (identifier && (identifier.name || identifier.prototype?.name)) { 28 | this.normalizedIdentifier = 29 | `MaybeConstructable<${identifier.name}>` || 30 | `MaybeConstructable<${ 31 | (identifier.prototype as { name: string })?.name 32 | }>` 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/data/basic/listeners.json: -------------------------------------------------------------------------------- 1 | { 2 | "listeners": [ 3 | { 4 | "watch": "$:model.on", 5 | "actions": [ 6 | { 7 | "condition": "$:!!model.on", 8 | "handler": "@model.value:model.value + 1" 9 | }, 10 | { 11 | "condition": "$:!model.on", 12 | "handler": "@model.value:0" 13 | } 14 | ] 15 | }, 16 | { 17 | "watch": "$:model.value", 18 | "actions": [ 19 | { 20 | "condition": "$:!!model.on && model.value < 100", 21 | "timeout": "$:model.num * 100", 22 | "handler": "@model.value:model.value + 1" 23 | } 24 | ] 25 | } 26 | ], 27 | "fields": [ 28 | { 29 | "component": "el-input-number", 30 | "model": "model.num", 31 | "props": { "min": 0, "max": 5 } 32 | }, 33 | { 34 | "component": "el-button", 35 | "text": "Go", 36 | "props": { "onClick": "@model.on:true" } 37 | }, 38 | { 39 | "component": "el-button", 40 | "text": "Reset", 41 | "props": { "onClick": "@model.on:false" } 42 | }, 43 | { 44 | "component": "el-progress", 45 | "props": { 46 | "strokeWidth": 24, 47 | "textInside": true, 48 | "percentage": "$:model.value" 49 | } 50 | } 51 | ], 52 | "model": { "num": 0, "value": 0 } 53 | } 54 | -------------------------------------------------------------------------------- /src/views/Test.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, ref } from 'vue' 2 | 3 | export default defineComponent({ 4 | setup() { 5 | const active = ref({ 6 | model: { text: 'xxx' }, 7 | fields: [ 8 | { 9 | component: 'p', 10 | props: { innerText: '$:raw.zzz' }, 11 | }, 12 | { 13 | component: 'p', 14 | props: { innerText: '$:model.text' }, 15 | }, 16 | { 17 | component: 'input', 18 | props: { 19 | onInput: '@raw.zzz:arguments[0].target.value', 20 | }, 21 | }, 22 | ], 23 | datasource: { 24 | raw: { 25 | type: 'rawdata', 26 | props: { 27 | data: { zzz: 'zzz' }, 28 | }, 29 | }, 30 | }, 31 | listeners: [], 32 | }) 33 | 34 | const onSetup = ({ datasource }: any) => { 35 | datasource('rawdata', (props: any) => { 36 | return props.data 37 | }) 38 | } 39 | 40 | return () => ( 41 |
42 | 50 |
51 | ) 52 | }, 53 | }) 54 | -------------------------------------------------------------------------------- /public/data/components/listeners.json: -------------------------------------------------------------------------------- 1 | { 2 | "listeners": [ 3 | { 4 | "watch": "$:model.on", 5 | "actions": [ 6 | { 7 | "condition": "$:!!model.on", 8 | "handler": "@model.value:model.value + 1" 9 | }, 10 | { 11 | "condition": "$:!model.on", 12 | "handler": "@model.value:0" 13 | } 14 | ] 15 | }, 16 | { 17 | "watch": "$:model.value", 18 | "actions": [ 19 | { 20 | "condition": "$:!!model.on && model.value < 100", 21 | "timeout": "$:model.num * 100", 22 | "handler": "@model.value:model.value + 1" 23 | } 24 | ] 25 | } 26 | ], 27 | "fields": [ 28 | { 29 | "component": "el-input-number", 30 | "model": "model.num", 31 | "props": { "min": 0, "max": 5 } 32 | }, 33 | { 34 | "component": "el-button", 35 | "text": "Go", 36 | "props": { "onClick": "@model.on:true" } 37 | }, 38 | { 39 | "component": "el-button", 40 | "text": "Reset", 41 | "props": { "onClick": "@model.on:false" } 42 | }, 43 | { 44 | "component": "el-progress", 45 | "props": { 46 | "strokeWidth": 24, 47 | "textInside": true, 48 | "percentage": "$:model.value" 49 | } 50 | } 51 | ], 52 | "modelValue": { "num": 0, "value": 0 } 53 | } 54 | -------------------------------------------------------------------------------- /src/views/Tabs.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, reactive } from 'vue' 2 | 3 | export default defineComponent({ 4 | setup() { 5 | const data = reactive({ active: 'first' }) 6 | const fields = reactive([ 7 | { 8 | component: 'el-input', 9 | model: 'model.text', 10 | props: { style: { width: '200px' }, placeholder: '请输入' }, 11 | }, 12 | { 13 | component: 'el-tabs', 14 | model: 'model.active', 15 | children: [ 16 | { 17 | component: 'el-tab-pane', 18 | props: { label: 'aaa', name: 'first' }, 19 | children: [ 20 | { component: 'p', text: '$:model.text' }, 21 | { component: 'p', text: '$:ADD(1, 1)' }, 22 | ], 23 | }, 24 | { 25 | component: 'el-tab-pane', 26 | props: { label: 'bbb', name: 'second' }, 27 | children: [{ component: 'p', text: 'bbb' }], 28 | }, 29 | ], 30 | }, 31 | ]) 32 | 33 | const onSetup = ({ component }: any) => { 34 | component('el-tab-pane', { provider: 'direct' }) 35 | } 36 | 37 | return () => ( 38 |
39 | 45 |
46 | ) 47 | }, 48 | }) 49 | -------------------------------------------------------------------------------- /packages/vue/src/service/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assignObject, 3 | createServiceContainer, 4 | createCoreSetup, 5 | Setup, 6 | } from '@json2render/core' 7 | import { componentToken } from '../feature' 8 | import { prerenderToken, renderToken } from '../feature/hook' 9 | import { ComponentMeta, Hook } from '../types' 10 | 11 | export const containerBuilder = createServiceContainer() 12 | 13 | export const createSetup = (container: any) => { 14 | const prerenderSetup = (value: Hook, index?: number) => { 15 | container.addValue(prerenderToken, { index: index || 0, invoke: value }) 16 | } 17 | 18 | const renderSetup = (value: Hook, index?: number) => { 19 | container.addValue(renderToken, { 20 | index: index || 0, 21 | invoke: value, 22 | }) 23 | } 24 | 25 | const componentSetup = (name: string, options?: any) => { 26 | const meta: ComponentMeta = { 27 | name, 28 | provider: options?.provider, 29 | } 30 | 31 | container.addValue(componentToken, meta) 32 | 33 | return (define: any) => { 34 | meta.define = define 35 | } 36 | } 37 | 38 | return assignObject(createCoreSetup(container), { 39 | prerender: prerenderSetup, 40 | render: renderSetup, 41 | component: componentSetup, 42 | }) 43 | } 44 | 45 | export const globalSetup: Setup = (handler) => { 46 | handler(createSetup(containerBuilder)) 47 | } 48 | -------------------------------------------------------------------------------- /packages/plugin-classics/src/proxy/method.ts: -------------------------------------------------------------------------------- 1 | import { assignArray, deepSet } from '@json2render/core' 2 | import { getProxyDefine, ProxyMatcher, ProxyHandler } from '@json2render/core' 3 | 4 | const method: ProxyMatcher = (value: any, { functional, proxy }) => { 5 | const execute: ProxyHandler = (context) => { 6 | return (...args: any) => { 7 | try { 8 | const functionals = functional.getMap() 9 | 10 | const functionalKeys = Object.keys(functionals) 11 | 12 | const params = assignArray(Object.keys(context), functionalKeys, [ 13 | 'arguments', 14 | `return ${value.$result}`, 15 | ]) 16 | 17 | const inputs = assignArray( 18 | Object.keys(context).map((key) => context[key]), 19 | functionalKeys.map((key) => functionals[key]), 20 | [args] 21 | ) 22 | 23 | const result = new Function(...params)(...inputs) 24 | 25 | const define = getProxyDefine(value) 26 | 27 | if (define.$context !== undefined) { 28 | const rejected = proxy.inject({ $context: define.$context }, context) 29 | deepSet(context, rejected.$context, result) 30 | } else { 31 | return result 32 | } 33 | } catch { 34 | // 35 | } 36 | } 37 | } 38 | 39 | if (typeof value === 'object' && value && value.$type === 'method') { 40 | return execute 41 | } 42 | } 43 | 44 | export default method 45 | -------------------------------------------------------------------------------- /packages/core/src/di/error/cannotInstantiateValue.ts: -------------------------------------------------------------------------------- 1 | import { ServiceIdentifier } from '../types/serviceIdentifier' 2 | import { Token } from '../token' 3 | 4 | /** 5 | * Thrown when DI cannot inject value into property decorated by @Inject decorator. 6 | */ 7 | export class CannotInstantiateValueError extends Error { 8 | public name = 'CannotInstantiateValueError' 9 | 10 | /** Normalized identifier name used in the error message. */ 11 | private normalizedIdentifier = '' 12 | 13 | get message(): string { 14 | return ( 15 | `Cannot instantiate the requested value for the "${this.normalizedIdentifier}" identifier. ` + 16 | `The related metadata doesn't contain a factory or a type to instantiate.` 17 | ) 18 | } 19 | 20 | constructor(identifier: ServiceIdentifier) { 21 | super() 22 | 23 | // TODO: Extract this to a helper function and share between this and NotFoundError. 24 | if (typeof identifier === 'string') { 25 | this.normalizedIdentifier = identifier 26 | } else if (identifier instanceof Token) { 27 | this.normalizedIdentifier = `Token<${identifier.name || 'UNSET_NAME'}>` 28 | } else if (identifier && (identifier.name || identifier.prototype?.name)) { 29 | this.normalizedIdentifier = 30 | `MaybeConstructable<${identifier.name}>` || 31 | `MaybeConstructable<${ 32 | (identifier.prototype as { name: string })?.name 33 | }>` 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/guide/reference-component.md: -------------------------------------------------------------------------------- 1 | # 使用组件 2 | 3 | ## 引用组件 4 | 5 | 在 vue3 下全局引用组件 6 | 7 | ```javascript 8 | import { createApp } from 'vue' 9 | import JRender from 'json2render/vue-full' 10 | 11 | createApp({}).use(JRender).mount('#app') 12 | ``` 13 | 14 | 在组件中单独引用 15 | 16 | ```vue 17 | 20 | 21 | 28 | ``` 29 | 30 | 在 vue 中可以通过引用 `json2render/vue-full` 或 `json2render/vue` 两个库,区别是 `json2render/vue-full` 包含了全部插件功能而 `json2render/vue` 31 | 只包含核心渲染功能不包含插件 32 | 33 | ::: tip 34 | 插件是 `json2render` 一系列扩展功能,使组件支持更多的渲染方式 35 | ::: 36 | 37 | ## 引用插件 38 | 39 | 插件引用分为全局方式和局部方式 40 | 41 | 全局方式引用插件后项目中所有用到 `json2render` 组件的地方都会启用该插件提供的功能 42 | 43 | ```javascript {3,5} 44 | import { createApp } from 'vue' 45 | import JRender from 'json2render/vue' 46 | import Classics from 'json2render/plugin-classics' 47 | 48 | JRender.use(Classics) 49 | 50 | createApp({}).use(JRender).mount('#app') 51 | ``` 52 | 53 | 局部方式引用是只有当前引用该插件的组件才会启用该插件提供的功能 54 | 55 | ```vue {7,12,13,14} 56 | 59 | 60 | 73 | ``` 74 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/components/demo-show/Options.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | 21 | 62 | -------------------------------------------------------------------------------- /public/data/yaml/testform.yaml: -------------------------------------------------------------------------------- 1 | # 数据源 2 | datasource: 3 | listdata: 4 | type: fetch 5 | url: /data.json 6 | 7 | # 组件 8 | fields: 9 | - component: p 10 | text: 渲染 yaml 11 | 12 | - component: el-button 13 | text: alert 14 | props: 15 | onClick: | 16 | $: () => { 17 | console.log('xxxxxx') 18 | alert('xxx') 19 | } 20 | 21 | - component: el-form 22 | props: 23 | labelWidth: 120px 24 | row: 20 # gutter 25 | children: 26 | - component: el-input 27 | model: model.text 28 | col: 12 # span 29 | form: 输入 # label 30 | 31 | - component: el-select 32 | model: model.selected 33 | col: 12 34 | form: 选择 35 | 36 | - component: p 37 | text: $:`输入了:${model.text || ''}` 38 | col: 12 39 | form: ' ' 40 | 41 | - component: el-checkbox 42 | model: model.show 43 | col: 12 44 | form: 显示 45 | 46 | - component: el-form-item 47 | children: 48 | - component: span 49 | condition: $:!!model.show 50 | text: showed 51 | 52 | - component: el-table 53 | props: 54 | data: $:listdata.data 55 | children: 56 | - component: el-table-column 57 | props: 58 | prop: data1 59 | label: 名称 60 | - component: el-table-column 61 | children: 62 | - component: el-button 63 | text: 明细 64 | props: 65 | type: text 66 | onClick: $:() => alert(JSON.stringify(scope.row)) 67 | -------------------------------------------------------------------------------- /packages/core/src/types.ts: -------------------------------------------------------------------------------- 1 | // proxy 2 | export enum ProxyFlags { 3 | IS_PROXY = '__jr_isProxy', 4 | NOT_PROXY = '__jr_notProxy', 5 | PROXY_DEFINE = '__jr_proxyDefine', 6 | } 7 | 8 | export interface ProxyTarget { 9 | [ProxyFlags.IS_PROXY]?: boolean 10 | [ProxyFlags.NOT_PROXY]?: boolean 11 | } 12 | 13 | export interface ProxyContext { 14 | model: unknown 15 | scope: unknown 16 | [key: string]: unknown 17 | } 18 | 19 | export interface ProxyMatcher { 20 | (value: unknown, services: Record): ProxyHandler | undefined 21 | } 22 | 23 | export interface ProxyHandler { 24 | (context: Record): unknown 25 | } 26 | 27 | // functional 28 | export interface Functional { 29 | (...args: unknown[]): unknown 30 | } 31 | 32 | export interface FunctionalMeta { 33 | name: string 34 | invoke: Functional 35 | } 36 | 37 | // datasource 38 | export interface DatasourceMeta { 39 | type: string 40 | build: DatasourceBuilder 41 | } 42 | 43 | export interface DatasourceBuilder { 44 | (props: Record, services: Record): unknown 45 | } 46 | 47 | export interface DatasourceOptions { 48 | type: string 49 | props: Record 50 | } 51 | 52 | export interface ServiceBuilder { 53 | proxy: (value: ProxyMatcher) => void 54 | functional: (name: string, value: Functional) => void 55 | datasource: (name: string, value: DatasourceBuilder) => void 56 | } 57 | 58 | export interface SetupHandler { 59 | (setups: ServiceBuilder): void 60 | } 61 | 62 | export interface Setup { 63 | (handler: SetupHandler): void 64 | } 65 | -------------------------------------------------------------------------------- /packages/plugin-modern/src/proxy/method.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assignArray, 3 | deepSet, 4 | ProxyMatcher, 5 | ProxyHandler, 6 | } from '@json2render/core' 7 | 8 | const method: ProxyMatcher = (value: any, { functional }) => { 9 | const execute: ProxyHandler = (context) => { 10 | return (...args: any) => { 11 | try { 12 | const expr = value.slice(value.indexOf(':') + 1, value.length) 13 | const expProp = value.slice(1, value.indexOf(':')) 14 | const contextkeys = Object.keys(context) 15 | const functionals = functional.getMap() 16 | const functionalKeys = Object.keys(functionals) 17 | const inputs = assignArray( 18 | contextkeys.map((key) => context[key]), 19 | functionalKeys.map((key) => functionals[key]), 20 | [args] 21 | ) 22 | 23 | const result = new Function( 24 | ...assignArray(contextkeys, functionalKeys, [ 25 | 'arguments', 26 | `return ${expr}`, 27 | ]) 28 | )(...inputs) 29 | 30 | if (expProp && expProp.length > 0) { 31 | const keyExpr = '`' + expProp + '`' 32 | const expKey = new Function( 33 | ...assignArray(contextkeys, ['arguments', `return ${keyExpr}`]) 34 | )(...inputs) 35 | deepSet(context, expKey, result) 36 | } else { 37 | return result 38 | } 39 | } catch { 40 | // console.log(e) 41 | } 42 | } 43 | } 44 | 45 | if (typeof value === 'string' && /^(@[\s\S]*:)/g.test(value)) { 46 | return execute 47 | } 48 | } 49 | 50 | export default method 51 | -------------------------------------------------------------------------------- /public/data/basic/templateload.json: -------------------------------------------------------------------------------- 1 | { 2 | "datasource": { 3 | "template": { 4 | "type": "fetch", 5 | "url": "#:/data/components/${model.name}.json", 6 | "auto": false, 7 | "props": { "method": "GET", "params": {} }, 8 | "defaultData": false 9 | } 10 | }, 11 | "listeners": [ 12 | { 13 | "watch": "$:model.name", 14 | "actions": [{ "handler": "@:template.request()" }] 15 | } 16 | ], 17 | "fields": [ 18 | { 19 | "component": "h1", 20 | "text": "加载模板组件" 21 | }, 22 | { 23 | "component": "el-select", 24 | "model": "model.name", 25 | "children": [ 26 | { 27 | "component": "el-option", 28 | "props": { "label": "组件1", "value": "cmp1" } 29 | }, 30 | { 31 | "component": "el-option", 32 | "props": { "label": "组件2", "value": "cmp2" } 33 | }, 34 | { 35 | "component": "el-option", 36 | "props": { "label": "监听触发", "value": "listeners" } 37 | } 38 | ] 39 | }, 40 | { 41 | "component": "div", 42 | "props": { 43 | "style": { 44 | "border": "1px dashed silver", 45 | "padding": "1.25rem", 46 | "marginTop": "0.75rem" 47 | } 48 | }, 49 | "children": [ 50 | { 51 | "component": "p", 52 | "condition": "$:!template.data", 53 | "text": "加载自定义组件", 54 | "props": { "style": { "textAlign": "center" } } 55 | }, 56 | { 57 | "component": "v-jrender", 58 | "condition": "$:template.data", 59 | "props": "$:template.data" 60 | } 61 | ] 62 | } 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /packages/core/src/feature/datasource.ts: -------------------------------------------------------------------------------- 1 | import { servicesToken } from '../service/token' 2 | import { 3 | DatasourceBuilder, 4 | DatasourceMeta, 5 | DatasourceOptions, 6 | ProxyContext, 7 | } from '../types' 8 | import { assignObject, ContainerInstance, createToken } from '../utils' 9 | import { proxyContextToken, ProxyService, proxyServiceToken } from './proxy' 10 | 11 | export const datasourceToken = createToken('datasource') 12 | 13 | export const datasourceServiceToken = 14 | createToken('datasourceService') 15 | 16 | export class DatasourceService { 17 | private readonly proxyService: ProxyService 18 | private readonly datasources: DatasourceMeta[] 19 | private readonly context: ProxyContext 20 | private readonly services: Record 21 | 22 | constructor(container: ContainerInstance) { 23 | this.proxyService = container.get(proxyServiceToken) 24 | this.datasources = container.getMany(datasourceToken) 25 | this.context = container.get(proxyContextToken) 26 | this.services = container.get(servicesToken) 27 | } 28 | 29 | private getMap() { 30 | return this.datasources.reduce((pre, cur) => { 31 | pre[cur.type] = cur.build 32 | return pre 33 | }, {} as { [key: string]: DatasourceBuilder }) 34 | } 35 | 36 | release(key: string) { 37 | delete this.context[key] 38 | } 39 | 40 | build(key: string, options: DatasourceOptions) { 41 | const injected = this.proxyService.inject( 42 | assignObject(options), 43 | this.context 44 | ) 45 | 46 | const maped = this.getMap() 47 | 48 | const build = maped[injected.type] 49 | 50 | if (build) { 51 | this.context[key] = build(injected, this.services) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/components/JsonEditor/Component.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, onBeforeUnmount, onMounted, ref, watch } from 'vue' 2 | import { editor } from 'monaco-editor' 3 | 4 | import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker' 5 | import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker' 6 | 7 | self.MonacoEnvironment = { 8 | getWorker(workerId, label) { 9 | if (label === 'json') { 10 | return new JsonWorker() 11 | } 12 | return new EditorWorker() 13 | }, 14 | } 15 | 16 | export default defineComponent({ 17 | name: 'json-editor', 18 | props: { 19 | modelValue: { type: String, required: true }, 20 | }, 21 | emits: ['update:modelValue'], 22 | setup(props, ctx) { 23 | const dom = ref() 24 | let instance: editor.IStandaloneCodeEditor 25 | 26 | onMounted(async () => { 27 | instance = editor.create(dom.value, { 28 | model: editor.createModel(props.modelValue, 'json'), 29 | tabSize: 2, 30 | automaticLayout: true, 31 | scrollBeyondLastLine: false, 32 | }) 33 | 34 | instance?.onDidChangeModelContent(() => { 35 | ctx.emit('update:modelValue', instance?.getValue()) 36 | }) 37 | 38 | setTimeout(() => { 39 | try { 40 | instance?.getAction('editor.action.formatDocument')?.run() 41 | } catch { 42 | // 43 | } 44 | }, 1000) 45 | }) 46 | 47 | onBeforeUnmount(() => { 48 | instance?.getModel()?.dispose() 49 | instance?.dispose() 50 | }) 51 | 52 | watch( 53 | () => props.modelValue, 54 | () => { 55 | instance?.setValue(props.modelValue) 56 | 57 | instance?.getAction('editor.action.formatDocument').run() 58 | } 59 | ) 60 | 61 | return () =>
62 | }, 63 | }) 64 | -------------------------------------------------------------------------------- /packages/core/src/di/interfaces/serviceMetadata.ts: -------------------------------------------------------------------------------- 1 | import { Constructable } from '../types/constructable' 2 | import { ServiceIdentifier } from '../types/serviceIdentifier' 3 | 4 | /** 5 | * Service metadata is used to initialize service and store its state. 6 | */ 7 | export interface ServiceMetadata { 8 | /** Unique identifier of the referenced service. */ 9 | id: ServiceIdentifier 10 | 11 | /** 12 | * Class definition of the service what is used to initialize given service. 13 | * This property maybe null if the value of the service is set manually. 14 | * If id is not set then it serves as service id. 15 | */ 16 | type: Constructable | null 17 | 18 | /** 19 | * Indicates if this service must be global and same instance must be used across all containers. 20 | */ 21 | global: boolean 22 | 23 | /** 24 | * Indicates whether a new instance of this class must be created for each class injecting this class. 25 | * Global option is ignored when this option is used. 26 | */ 27 | transient: boolean 28 | 29 | /** 30 | * Allows to setup multiple instances the different classes under a single service id string or token. 31 | */ 32 | multiple: boolean 33 | 34 | /** 35 | * Indicates whether a new instance should be created as soon as the class is registered. 36 | * By default the registered classes are only instantiated when they are requested from the container. 37 | */ 38 | eager?: boolean 39 | 40 | /** 41 | * Factory function used to initialize this service. 42 | * Can be regular function ("createCar" for example), 43 | * or other service which produces this instance ([CarFactory, "createCar"] for example). 44 | */ 45 | factory: [Constructable, string] | CallableFunction | undefined 46 | 47 | /** 48 | * Instance of the target class. 49 | */ 50 | value: unknown | Symbol 51 | } 52 | -------------------------------------------------------------------------------- /docs/guide/datasource.md: -------------------------------------------------------------------------------- 1 | # 数据源 2 | 3 | ## 概念 4 | 5 | 数据源的意义就是数据的来源,是属性表达式里能够使用的数据,`json2render` 6 | 7 | ```vue 8 | 15 | 16 | 60 | ``` 61 | 62 | ## 数据源定义 63 | 64 | `json2render` 支持 `model` `arguments` `scope` 三种数据来源 65 | 66 | #### model 67 | 68 | 是组件 `modelValue` 属性传递过来的值 69 | 70 | #### arguments 71 | 72 | 如果属性是一个事件,则 `arguments` 代表事件传过来的参数集合 73 | 74 | #### scope 75 | 76 | 如果组件是由数组数据渲染出来的或者父级组件有值传递过来,`scope` 就作为当前数据项的引用 77 | 78 | ::: tip 79 | 类似 vue `scope-slot` 的作用 80 | ::: 81 | 82 | ## 自定义数据源 83 | 84 | 除了固定数据来源以外,可使用的数据也包括通过插件定义的或自定义的数据来源,具体参考[高级用法](./setup.html) 85 | -------------------------------------------------------------------------------- /packages/vue-designer/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [1.0.4](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-designer@1.0.3...@json2render/vue-designer@1.0.4) (2021-07-17) 7 | 8 | **Note:** Version bump only for package @json2render/vue-designer 9 | 10 | 11 | 12 | 13 | 14 | ## [1.0.3](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-designer@1.0.2...@json2render/vue-designer@1.0.3) (2021-07-13) 15 | 16 | **Note:** Version bump only for package @json2render/vue-designer 17 | 18 | 19 | 20 | 21 | 22 | ## [1.0.2](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-designer@1.0.1...@json2render/vue-designer@1.0.2) (2021-07-04) 23 | 24 | **Note:** Version bump only for package @json2render/vue-designer 25 | 26 | 27 | 28 | 29 | 30 | ## [1.0.1](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-designer@1.0.0...@json2render/vue-designer@1.0.1) (2021-06-29) 31 | 32 | **Note:** Version bump only for package @json2render/vue-designer 33 | 34 | 35 | 36 | 37 | 38 | # [1.0.0](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-designer@0.1.0...@json2render/vue-designer@1.0.0) (2021-06-16) 39 | 40 | 41 | ### Features 42 | 43 | * di ([4d048d3](https://github.com/fyl080801/json-to-render/commit/4d048d354c4930ad6e4aa3e57a1a03f59362bcc0)) 44 | * di 实现直接拿过来 ([4efbfe9](https://github.com/fyl080801/json-to-render/commit/4efbfe98750a20169e84a9af38c27e2da6513e6b)) 45 | 46 | 47 | 48 | 49 | 50 | # 0.1.0 (2021-04-06) 51 | 52 | 53 | ### Features 54 | 55 | * **Designer:** 初始化设计器功能 ([66f15ed](https://github.com/fyl080801/json-to-render/commit/66f15ed6e31bf74344f22050a736b29f829a9f8a)) 56 | * **vue-designer:** 编辑集成 tailwind ([c3dad4b](https://github.com/fyl080801/json-to-render/commit/c3dad4bc41b594362d59b34e12fe7d2dfc82fdbb)) 57 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/components/demo-show/imgs/codepen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | codepen 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import { useRoute } from 'vue-router' 3 | import './App.css' 4 | 5 | export default defineComponent({ 6 | setup() { 7 | const route = useRoute() 8 | 9 | return () => ( 10 |
11 |
12 |
13 | 20 | Home 21 | 22 | 29 | Element 30 | 31 | 38 | Tabs 39 | 40 | 47 | Designer 48 | 49 | 56 | Yaml 57 | 58 |
59 |
60 |
61 | 62 |
63 |
64 | ) 65 | }, 66 | }) 67 | -------------------------------------------------------------------------------- /packages/core/src/di/decorators/injectMany.ts: -------------------------------------------------------------------------------- 1 | import { Container } from '../container' 2 | import { Token } from '../token' 3 | import { CannotInjectValueError } from '../error/cannotInjectValue' 4 | import { resolveToTypeWrapper } from '../utils/resolveToTypeWrapper' 5 | import { Constructable } from '../types/constructable' 6 | import { ServiceIdentifier } from '../types/serviceIdentifier' 7 | 8 | /** 9 | * Injects a list of services into a class property or constructor parameter. 10 | */ 11 | export function InjectMany(): Function 12 | export function InjectMany(type?: (type?: any) => Function): Function 13 | export function InjectMany(serviceName?: string): Function 14 | export function InjectMany(token: Token): Function 15 | export function InjectMany( 16 | typeOrIdentifier?: 17 | | ((type?: never) => Constructable) 18 | | ServiceIdentifier 19 | ): Function { 20 | return function ( 21 | target: Object, 22 | propertyName: string | Symbol, 23 | index?: number 24 | ): void { 25 | const typeWrapper = resolveToTypeWrapper( 26 | typeOrIdentifier, 27 | target, 28 | propertyName, 29 | index 30 | ) 31 | 32 | /** If no type was inferred, or the general Object type was inferred we throw an error. */ 33 | if ( 34 | typeWrapper === undefined || 35 | typeWrapper.eagerType === undefined || 36 | typeWrapper.eagerType === Object 37 | ) { 38 | throw new CannotInjectValueError( 39 | target as Constructable, 40 | propertyName as string 41 | ) 42 | } 43 | 44 | Container.registerHandler({ 45 | object: target as Constructable, 46 | propertyName: propertyName as string, 47 | index: index, 48 | value: (containerInstance) => { 49 | const evaluatedLazyType = typeWrapper.lazyType() 50 | 51 | /** If no type was inferred lazily, or the general Object type was inferred we throw an error. */ 52 | if (evaluatedLazyType === undefined || evaluatedLazyType === Object) { 53 | throw new CannotInjectValueError( 54 | target as Constructable, 55 | propertyName as string 56 | ) 57 | } 58 | 59 | return containerInstance.getMany(evaluatedLazyType) 60 | }, 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/vue/src/components/jNode.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent, watch, ref,nextTick } from 'vue' 2 | import { 3 | assignObject, 4 | proxyContextToken, 5 | proxyServiceToken, 6 | } from '@json2render/core' 7 | import { getState } from '../store' 8 | import { 9 | childrenPrerender, 10 | componentServiceToken, 11 | injectPrerender, 12 | prerenderServiceToken, 13 | renderServiceToken, 14 | slotsPrerender, 15 | slotsToken, 16 | } from '../feature' 17 | 18 | export default defineComponent({ 19 | name: 'vJnode', 20 | props: { 21 | field: { type: Object, required: true }, 22 | scope: { type: Object, required: true }, 23 | }, 24 | setup: (props) => { 25 | const container: any = getState() 26 | 27 | const nodeField = ref({}) 28 | const prerender = container.resolve(prerenderServiceToken) 29 | const render = container.resolve(renderServiceToken) 30 | const proxy = container.resolve(proxyServiceToken) 31 | const context = container.resolve(proxyContextToken) 32 | const component = container.resolve(componentServiceToken) 33 | const injectedContext = assignObject(context, { 34 | scope: props.scope, 35 | }) 36 | const slots = container.resolve(slotsToken) 37 | 38 | watch( 39 | () => props.field, 40 | (value) => { 41 | prerender.process( 42 | proxy.inject(value, injectedContext), 43 | injectedContext 44 | )( 45 | ...[ 46 | childrenPrerender, 47 | slotsPrerender(slots), 48 | injectPrerender(nodeField), 49 | ].map((invoke) => ({ invoke })) 50 | ) 51 | }, 52 | { deep: false, immediate: true } 53 | ) 54 | 55 | return () => { 56 | let renderField = assignObject(nodeField.value) 57 | 58 | render.process( 59 | renderField, 60 | injectedContext 61 | )({ 62 | invoke: () => (field: any, next: any) => { 63 | renderField = field 64 | next(renderField) 65 | }, 66 | }) 67 | 68 | const rendered = component.renderNode(renderField, props.scope) 69 | 70 | if (rendered?.ref) { 71 | nextTick(()=>{ 72 | const { r, i }: any = rendered.ref 73 | context.refs[r] = i.refs[r] 74 | }) 75 | } 76 | 77 | return rendered 78 | } 79 | }, 80 | }) 81 | -------------------------------------------------------------------------------- /packages/core/src/di/decorators/inject.ts: -------------------------------------------------------------------------------- 1 | import { Container } from '../container' 2 | import { Token } from '../token' 3 | import { CannotInjectValueError } from '../error/cannotInjectValue' 4 | import { ServiceIdentifier } from '../types/serviceIdentifier' 5 | import { Constructable } from '../types/constructable' 6 | import { resolveToTypeWrapper } from '../utils/resolveToTypeWrapper' 7 | 8 | /** 9 | * Injects a service into a class property or constructor parameter. 10 | */ 11 | export function Inject(): Function 12 | export function Inject( 13 | typeFn: (type?: never) => Constructable 14 | ): Function 15 | export function Inject(serviceName?: string): Function 16 | export function Inject(token: Token): Function 17 | export function Inject( 18 | typeOrIdentifier?: 19 | | ((type?: never) => Constructable) 20 | | ServiceIdentifier 21 | ): ParameterDecorator | PropertyDecorator { 22 | return function ( 23 | target: Object, 24 | propertyName: string | Symbol, 25 | index?: number 26 | ): void { 27 | const typeWrapper = resolveToTypeWrapper( 28 | typeOrIdentifier, 29 | target, 30 | propertyName, 31 | index 32 | ) 33 | 34 | /** If no type was inferred, or the general Object type was inferred we throw an error. */ 35 | if ( 36 | typeWrapper === undefined || 37 | typeWrapper.eagerType === undefined || 38 | typeWrapper.eagerType === Object 39 | ) { 40 | throw new CannotInjectValueError( 41 | target as Constructable, 42 | propertyName as string 43 | ) 44 | } 45 | 46 | Container.registerHandler({ 47 | object: target as Constructable, 48 | propertyName: propertyName as string, 49 | index: index, 50 | value: (containerInstance) => { 51 | const evaluatedLazyType = typeWrapper.lazyType() 52 | 53 | /** If no type was inferred lazily, or the general Object type was inferred we throw an error. */ 54 | if (evaluatedLazyType === undefined || evaluatedLazyType === Object) { 55 | throw new CannotInjectValueError( 56 | target as Constructable, 57 | propertyName as string 58 | ) 59 | } 60 | 61 | return containerInstance.get(evaluatedLazyType) 62 | }, 63 | }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/core/src/utils/domTags.ts: -------------------------------------------------------------------------------- 1 | const HTML_TAGS = 2 | 'html,body,base,head,link,meta,style,title,address,article,aside,footer,' + 3 | 'header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,div,dd,dl,dt,figcaption,' + 4 | 'figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,' + 5 | 'data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,s,samp,small,span,strong,sub,sup,' + 6 | 'time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,' + 7 | 'canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,' + 8 | 'th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,' + 9 | 'option,output,progress,select,textarea,details,dialog,menu,' + 10 | 'summary,template,blockquote,iframe,tfoot' 11 | 12 | // https://developer.mozilla.org/en-US/docs/Web/SVG/Element 13 | const SVG_TAGS = 14 | 'svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,' + 15 | 'defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,' + 16 | 'feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,' + 17 | 'feDistanceLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,' + 18 | 'feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,' + 19 | 'fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,' + 20 | 'foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,' + 21 | 'mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,' + 22 | 'polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,' + 23 | 'text,textPath,title,tspan,unknown,use,view' 24 | 25 | const VOID_TAGS = 26 | 'area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr' 27 | 28 | export const makeMap = ( 29 | str: string, 30 | expectsLowerCase?: boolean 31 | ): ((key: string) => boolean) => { 32 | const map: Record = Object.create(null) 33 | const list: Array = str.split(',') 34 | for (let i = 0; i < list.length; i++) { 35 | map[list[i]] = true 36 | } 37 | return expectsLowerCase 38 | ? (val) => !!map[val.toLowerCase()] 39 | : (val) => !!map[val] 40 | } 41 | 42 | export const isHTMLTag = makeMap(HTML_TAGS) 43 | export const isSVGTag = makeMap(SVG_TAGS) 44 | export const isVoidTag = makeMap(VOID_TAGS) 45 | export const isOriginTag = (key: string) => 46 | isHTMLTag(key) || isSVGTag(key) || isVoidTag(key) 47 | -------------------------------------------------------------------------------- /packages/core/src/di/decorators/service.ts: -------------------------------------------------------------------------------- 1 | import { Container } from '../container' 2 | import { Token } from '../token' 3 | import { ServiceMetadata } from '../interfaces/serviceMetadata' 4 | import { ServiceOptions } from '../interfaces/serviceOptions' 5 | import { EMPTY_VALUE } from '../empty' 6 | import { Constructable } from '../types/constructable' 7 | 8 | /** 9 | * Marks class as a service that can be injected using Container. 10 | */ 11 | export function Service(): Function 12 | export function Service(name: string): Function 13 | export function Service(token: Token): Function 14 | export function Service(options?: ServiceOptions): Function 15 | export function Service( 16 | optionsOrServiceIdentifier?: ServiceOptions | Token | string 17 | ): ClassDecorator { 18 | return (targetConstructor) => { 19 | const serviceMetadata: ServiceMetadata = { 20 | id: targetConstructor, 21 | // TODO: Let's investigate why we receive Function type instead of a constructable. 22 | type: targetConstructor as unknown as Constructable, 23 | factory: undefined, 24 | multiple: false, 25 | global: false, 26 | eager: false, 27 | transient: false, 28 | value: EMPTY_VALUE, 29 | } 30 | 31 | if ( 32 | optionsOrServiceIdentifier instanceof Token || 33 | typeof optionsOrServiceIdentifier === 'string' 34 | ) { 35 | /** We received a Token or string ID. */ 36 | serviceMetadata.id = optionsOrServiceIdentifier 37 | } else if (optionsOrServiceIdentifier) { 38 | /** We received a ServiceOptions object. */ 39 | serviceMetadata.id = 40 | (optionsOrServiceIdentifier as ServiceMetadata).id || targetConstructor 41 | serviceMetadata.factory = 42 | (optionsOrServiceIdentifier as ServiceMetadata).factory || undefined 43 | serviceMetadata.multiple = 44 | (optionsOrServiceIdentifier as ServiceMetadata).multiple || false 45 | serviceMetadata.global = 46 | (optionsOrServiceIdentifier as ServiceMetadata).global || false 47 | serviceMetadata.eager = 48 | (optionsOrServiceIdentifier as ServiceMetadata).eager || false 49 | serviceMetadata.transient = 50 | (optionsOrServiceIdentifier as ServiceMetadata).transient || false 51 | } 52 | 53 | Container.set(serviceMetadata) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /public/data/basic/nest.json: -------------------------------------------------------------------------------- 1 | { 2 | "datasource": { 3 | "subform": { 4 | "type": "fetch", 5 | "url": "/data/basic/nest.json", 6 | "auto": false, 7 | "props": { "method": "GET", "params": {} }, 8 | "defaultData": false 9 | } 10 | }, 11 | "listeners": [ 12 | { 13 | "watch": "$:subform.data", 14 | "actions": [ 15 | { 16 | "condition": "$:subform.data", 17 | "handler": "@model.submodel:subform.data.model" 18 | } 19 | ] 20 | } 21 | ], 22 | "fields": [ 23 | { 24 | "component": "el-form", 25 | "children": [ 26 | { 27 | "component": "el-form-item", 28 | "children": [{ "component": "el-input", "model": "model.text" }] 29 | }, 30 | { 31 | "component": "el-form-item", 32 | "children": [ 33 | { 34 | "component": "el-button", 35 | "text": "加载表单", 36 | "props": { "onClick": "@:subform.request()", "type": "primary" } 37 | }, 38 | { 39 | "component": "el-button", 40 | "text": "显示数据", 41 | "props": { "onClick": "@:alert(JSON.stringify(model))" } 42 | }, 43 | { 44 | "component": "el-button", 45 | "text": "清除当前", 46 | "props": { "onClick": "@subform.data:{}", "type": "danger" } 47 | } 48 | ] 49 | } 50 | ] 51 | }, 52 | 53 | { 54 | "component": "div", 55 | "props": { 56 | "style": { 57 | "border": "1px dashed silver", 58 | "padding": "1.25rem", 59 | "marginTop": "0.75rem" 60 | } 61 | }, 62 | "children": [ 63 | { 64 | "component": "p", 65 | "condition": "$:!model.submodel", 66 | "text": "加载远程表单区域", 67 | "props": { "style": { "textAlign": "center" } } 68 | }, 69 | { 70 | "component": "v-jrender", 71 | "condition": "$:model.submodel", 72 | "props": { 73 | "modelValue": "$:subform.data.model", 74 | "datasource": "$:subform.data.datasource", 75 | "listeners": "$:subform.data.listeners", 76 | "fields": "$:subform.data.fields" 77 | } 78 | } 79 | ] 80 | } 81 | ], 82 | "model": { "text": "" } 83 | } 84 | -------------------------------------------------------------------------------- /packages/vue-designer/src/Main.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, reactive, ref } from 'vue' 2 | import Jrender from '@json2render/vue' 3 | import Toolbox from './components/Toolbox' 4 | import PropertyBox from './components/PropertyBox' 5 | import Container from './components/Container' 6 | import Designer from './components/Designer' 7 | import './Main.css' 8 | 9 | import containerHook from './hooks/container' 10 | 11 | export default defineComponent({ 12 | name: 'vJdesigner', 13 | components: { Jrender }, 14 | props: { config: { type: Array, required: true } }, 15 | setup() { 16 | // const { config } = props 17 | 18 | // const reactived = reactive(config) 19 | 20 | const layout = [ 21 | { 22 | component: 'toolbox', 23 | location: { 24 | type: 'dock', 25 | }, 26 | }, 27 | { 28 | component: 'propertybox', 29 | location: { 30 | type: 'dock', 31 | }, 32 | }, 33 | { 34 | component: 'designer', 35 | }, 36 | ] 37 | 38 | const onSetup = ({ component, prerender }: any) => { 39 | component('toolbox')(Toolbox) 40 | component('propertybox')(PropertyBox) 41 | component(Container.name)(Container) 42 | component('designer')(Designer) 43 | 44 | prerender(containerHook) 45 | } 46 | 47 | // return () => ( 48 | //
49 | //
top
50 | //
51 | //
m-left
52 | //
53 | //
m-top
54 | //
55 | // {/* design */} 56 | // middle 57 | //
58 | //
m-bottom
59 | //
60 | //
m-right
61 | //
62 | //
bottom
63 | //
64 | // ) 65 | return () => ( 66 | 71 | ) 72 | }, 73 | }) 74 | -------------------------------------------------------------------------------- /packages/plugin-elementui/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [1.2.2](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-elementui@1.2.1...@json2render/plugin-elementui@1.2.2) (2021-07-17) 7 | 8 | **Note:** Version bump only for package @json2render/plugin-elementui 9 | 10 | 11 | 12 | 13 | 14 | ## [1.2.1](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-elementui@1.2.0...@json2render/plugin-elementui@1.2.1) (2021-07-13) 15 | 16 | **Note:** Version bump only for package @json2render/plugin-elementui 17 | 18 | 19 | 20 | 21 | 22 | # [1.2.0](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-elementui@1.1.0...@json2render/plugin-elementui@1.2.0) (2021-07-04) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * 依赖问题 ([b683150](https://github.com/fyl080801/json-to-render/commit/b683150dd5b72b141bf35ac7bf4dba59e1a33941)) 28 | 29 | 30 | ### Features 31 | 32 | * element-ui 的 options属性可以响应数据变化 ([539f986](https://github.com/fyl080801/json-to-render/commit/539f986e0b633c6bb18eb00ffd6e68dc3ab5fbc7)) 33 | * 节点渲染实现 ([dd473ea](https://github.com/fyl080801/json-to-render/commit/dd473ea22447e677f9f74d3bb07bb68de054c87d)) 34 | 35 | 36 | 37 | 38 | 39 | # [1.1.0](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-elementui@1.0.0...@json2render/plugin-elementui@1.1.0) (2021-06-29) 40 | 41 | 42 | ### Bug Fixes 43 | 44 | * checkbox-group 组件 model 问题 ([2a26524](https://github.com/fyl080801/json-to-render/commit/2a265242bacf9178a502429cb2f7672841bc780f)) 45 | 46 | 47 | ### Features 48 | 49 | * element-ui 扩展 ([0bebf56](https://github.com/fyl080801/json-to-render/commit/0bebf562bd0d73565a916ed67bc6a9ae16d7b9fa)) 50 | 51 | 52 | 53 | 54 | 55 | # [1.0.0](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-elementui@0.2.0...@json2render/plugin-elementui@1.0.0) (2021-06-16) 56 | 57 | 58 | ### Features 59 | 60 | * di ([4d048d3](https://github.com/fyl080801/json-to-render/commit/4d048d354c4930ad6e4aa3e57a1a03f59362bcc0)) 61 | * 完成di迁移 ([2dd3725](https://github.com/fyl080801/json-to-render/commit/2dd372528cbc5d87852946b00f56f8b984464cdf)) 62 | 63 | 64 | 65 | 66 | 67 | # 0.2.0 (2021-04-06) 68 | 69 | 70 | ### Features 71 | 72 | * element plugin ([c287c59](https://github.com/fyl080801/json-to-render/commit/c287c596ed70bb97238be64c3410a778f012ba9a)) 73 | -------------------------------------------------------------------------------- /docs/guide/configs.md: -------------------------------------------------------------------------------- 1 | # 配置 2 | 3 | ## 概览 4 | 5 | 在了解配置之前需要先用一组示例代码表示一下 `json2render` 使用上的一些重要特征 6 | 7 | ```vue 8 | 15 | 16 | 58 | ``` 59 | 60 | ## 组件相关 61 | 62 | #### modelValue (Object) 63 | 64 | 动态渲染的组件成员使用的数据以及对外输出的数据,可通过传入值实现数据初始化或整体变更数据 65 | 66 | #### fields (Array) 67 | 68 | 用于动态渲染的组件集合,`json2render` 会渲染什么样的视图以及支持的操作全在这个属性里定义 69 | 70 | #### datasource (Object) 71 | 72 | 组件内部可使用的额外数据来源,和插件定义的数据源相对应 73 | 74 | #### listeners (Array) 75 | 76 | 对数据的监听集合,定义 `listeners` 可实现监听组件内各种数据,在数据变化后触发一组操作实现联动 77 | 78 | #### setup (Function) 79 | 80 | 组件初始化的事件,在事件中可以单独引用插件或者实现自定义功能,关于如何自定义请参考高级用法 81 | 82 | ## 组件集合定义 83 | 84 | 组件 `fields` 属性是一个数组,成员具有如下基本属性 85 | 86 | #### component (String) 87 | 88 | 表示组件的名称,任何 html 标签、组件库组件或自定义组件的名称都可以作为该属性值 89 | 90 | ::: tip 91 | 如果是组件库或者是自定义组件,至少要保证在当前项目中是可用的,就是说要先引用相关组件 92 | ::: 93 | 94 | #### props (Object) 95 | 96 | 定义组件属性,组件所有属性都在这里定义 97 | 98 | 在 vue2 版本中 vue 渲染调用的 createElement 方法的第二个参数是一个对象,定义了组件的相关属性,这些属性还分 `attrs` `props` `domProps` `on` `nativeOn` 等等,在 vue3 中这种定义得到了改善,无论是 html 元素还是 vue 组件属性一律定义在一个对象中,事件也通过名称加上 `on` 前缀来表示,因此这里的 `props` 就是表示该组件的所有属性及事件的定义,具体看 vue3 关于此处的[概念描述](https://www.vue3js.cn/docs/zh/api/global-api.html#%E5%8F%82%E6%95%B0-2) 99 | 100 | ::: tip 101 | props 里该写什么值可参考对应 html 元素的属性和相关组件库里组件定义的属性 102 | ::: 103 | 104 | #### options (Object) 105 | 106 | 是一个预留的属性,用于定义一些额外的选项,一般用不到 107 | 108 | ## 监听定义 109 | 110 | 监听的作用是为了响应数据的变化,触发一组联动操作,比如当遇到级联选项的情况,在了解监听的用法之前最好先了解一下[属性表达式](./prop-transform.html)的用法 111 | -------------------------------------------------------------------------------- /packages/vue/src/feature/component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assignObject, 3 | ContainerInstance, 4 | createToken, 5 | isArray, 6 | isFunction, 7 | isObject, 8 | } from '@json2render/core' 9 | import { h, resolveComponent, isVNode } from 'vue' 10 | import { ComponentMeta } from '../types' 11 | import { resolveRenderComponent } from '../utils/render' 12 | 13 | export const componentServiceToken = 14 | createToken('componentService') 15 | 16 | export const componentToken = createToken('component') 17 | 18 | export class ComponentService { 19 | private store: Record 20 | 21 | private providers: Record = { 22 | direct: (field: any, scope: any) => { 23 | return h( 24 | resolveComponent(field.component), 25 | field.props, 26 | this.renderMany(field.children, scope) 27 | ) 28 | }, 29 | default: (field: any, scope: any) => { 30 | return h(resolveComponent('vJnode'), { 31 | field, 32 | scope, 33 | }) 34 | }, 35 | } 36 | 37 | constructor(container: ContainerInstance) { 38 | this.store = container.getMany(componentToken).reduce((pre, cur) => { 39 | pre[cur.name] = cur 40 | return pre 41 | }, {} as Record) 42 | } 43 | 44 | render(components: Array, scope: Record): any { 45 | const renders: Array = [] 46 | components.forEach((item) => { 47 | if (isFunction(item)) { 48 | item(item.props).forEach((i: any) => { 49 | renders.push(i) 50 | }) 51 | } else { 52 | renders.push(item) 53 | } 54 | }) 55 | return renders.map((child) => { 56 | if (isVNode(child)) { 57 | return child 58 | } else { 59 | const meta = this.store[child.component] || { provider: 'default' } 60 | return this.providers[meta.provider || 'default'](child, scope) 61 | } 62 | }) 63 | } 64 | 65 | renderMany( 66 | components: Array | Record>, 67 | scope: Record 68 | ) { 69 | if (!components) { 70 | return 71 | } 72 | 73 | if (isArray(components)) { 74 | return { default: () => this.render(components as Array, scope) } 75 | } 76 | 77 | if (isObject(components)) { 78 | return Object.keys(components as Record>).reduce( 79 | (pre, key) => { 80 | pre[key] = (scope: any) => 81 | this.render( 82 | (components as Record>)[key], 83 | assignObject(scope, Object.keys(scope || {})) 84 | ) 85 | return pre 86 | }, 87 | {} as Record 88 | ) 89 | } 90 | } 91 | 92 | renderNode(field: any, scope: Record) { 93 | const node = 94 | field?.component && 95 | (this.store[field.component]?.define || 96 | resolveRenderComponent(field.component)) 97 | 98 | return node && h(node, field.props, this.renderMany(field.children, scope)) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/views/Home.tsx: -------------------------------------------------------------------------------- 1 | // import { debounce } from '../utils/helpers' 2 | import { defineComponent, onMounted, ref, watch } from 'vue' 3 | 4 | export default defineComponent({ 5 | setup() { 6 | const current = ref('') 7 | const basics = ref([ 8 | 'simple', 9 | 'input', 10 | 'events', 11 | 'relation', 12 | 'listeners', 13 | 'nest', 14 | 'templateload', 15 | 'slots', 16 | 'full', 17 | ]) 18 | const active = ref({ fields: [], datasource: {}, listeners: [], model: {} }) 19 | const code = ref('') 20 | // const updater = debounce((value) => { 21 | // try { 22 | // Object.assign(active.value, JSON.parse(value)) 23 | // } catch (err) { 24 | // console.log(err) 25 | // } 26 | // }, 1000) 27 | 28 | const onSetup = ({ datasource }: any) => { 29 | datasource('rawdata', (options: any) => { 30 | return options.data 31 | }) 32 | } 33 | 34 | watch( 35 | () => current.value, 36 | (value) => { 37 | fetch(`/data/basic/${value}.json`).then((response) => { 38 | response.json().then((json) => { 39 | Object.assign( 40 | active.value, 41 | { fields: [], datasource: {}, listeners: [], model: {} }, 42 | json 43 | ) 44 | 45 | const display = Object.assign({}, json) 46 | delete display.model 47 | 48 | code.value = JSON.stringify(display) 49 | }) 50 | }) 51 | } 52 | ) 53 | 54 | // watch( 55 | // () => code.value, 56 | // (value) => { 57 | // // debugger 58 | // // updater(value) 59 | // console.log(value) 60 | // } 61 | // ) 62 | 63 | onMounted(() => { 64 | current.value = 'full' 65 | }) 66 | 67 | return () => ( 68 |
69 |
70 | 85 |
86 |
87 | 88 |
89 |
90 |
91 | 99 |
100 |
101 |
102 | ) 103 | }, 104 | }) 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Json to Render 2 | 3 | [![Build Status](https://travis-ci.com/fyl080801/json-to-render.svg?branch=master)](https://travis-ci.com/fyl080801/json-to-render) 4 | 5 | 将 json 数据渲染成界面的 vue 组件库,是根据 [vjform 动态表单](https://github.com/fyl080801/vjform)、[jformer 动态表单](https://github.com/fyl080801/jformer) 以及[vjdesign 设计器](https://github.com/fyl080801/vjdesign) 相关项目开发的 vue3 版本 6 | 7 | 详细参考[说明文档](https://fyl080801.github.io/json-to-render/) 8 | 9 | ## 特性 10 | 11 | - 将 json 数据渲染成界面 12 | - 支持 vue3 13 | - 支持任何 html 组件和 vue 项目中引用的组件进行渲染,支持组件任何属性 14 | - 支持将 json 数据特殊对象转换成数据关联关系实现联动 15 | - 支持二次开发 json 属性解析方式、数据交互来源与渲染逻辑 16 | 17 | ## 示例项目 18 | 19 | 此项目安装依赖 20 | 21 | ```bash 22 | npm install 23 | ``` 24 | 25 | ```bash 26 | npm run dev 27 | ``` 28 | 29 | 或 30 | 31 | ```bash 32 | yarn install 33 | 34 | ``` 35 | 36 | ```bash 37 | yarn run dev 38 | ``` 39 | 40 | ## 快速上手 41 | 42 | 使用 npm 安装 43 | 44 | ```bash 45 | npm i @json2render/vue-full 46 | ``` 47 | 48 | 实现一个简单示例 49 | 50 | main.js 51 | 52 | ```javascript 53 | import { createApp } from 'vue' 54 | import App from './App.vue' 55 | import JRender from '@json2render/vue-full' 56 | 57 | createApp(App).use(JRender).mount('#app') 58 | ``` 59 | 60 | App.vue 61 | 62 | ```vue 63 | 68 | 69 | 83 | ``` 84 | 85 | ## 示例 86 | 87 | 示例 1: [简单示例](http://jsrun.net/2PaKp) 88 | 89 | 示例 2: [Element 组件](http://jsrun.net/8PaKp) 90 | 91 | ## 说明 92 | 93 | ### 渲染组件 94 | 95 | 一般的定义形式如下 96 | 97 | ```html 98 | 105 | ``` 106 | 107 | - v-model: 数据 108 | - fields: 组件集合 109 | - datasource: 自定义数据源集合 110 | - listeners: 监听集合 111 | - setup: setup 事件 112 | 113 | ### 组件定义 114 | 115 | 组件定义包括 `component` `props` `children` 三个基本属性 116 | 117 | - component: 组件类型名,只要 html 标签或是项目中引用的组件都可以作为类型名 118 | - props: 组件的属性,vue3.0 中组件属性、html 属性、事件的定义可以直接定义到一个对象里 119 | - children: 组件嵌套的下级组件集合 120 | 121 | ### 组件属性代理 122 | 123 | 组件定义会被转换成代理对象,组件属性值如果是符合特定的表达式则在运行时会被转换成真实逻辑 124 | 125 | ### 渲染钩子 126 | 127 | 在组件被渲染之前会触发传渲染钩子行为,可在渲染之前改变组件的属性,有两个钩子执行的时机 128 | 129 | - prerender: 相当于组件 setup 阶段,如果组件定义不被改变则只会执行一次 130 | - render: 相当于每次渲染之前都会被执行 131 | 132 | ### 数据源 133 | 134 | 数据源就是数据的来源,可在组件属性表达式里使用的数据,默认支持 `model` `scope` `arguments` `refs` 这几种数据来源 135 | 136 | - model: 通过 v-model 传递过来的数据 137 | - scope: 当前组件渲染时候由父级数据传递过来的当前数据成员,相当于 scoped-slot 138 | - arguments: 如果当前属性表达式是一个函数,则 arguments 就是函数接收的参数数组 139 | - refs: 如果在组件的 props 里设置 ref 属性,则可以通过 refs 获取组件的实例 140 | 141 | 除了以上几种数据源外,还支持自定义数据源 142 | 143 | ### 扩展行为 144 | 145 | 支持扩展组件属性代理行为、渲染钩子、数据源,实现自定义渲染规则 146 | 147 | ## 相关链接 148 | 149 | [说明文档](https://fyl080801.github.io/json-to-render/) 完善中... 150 | 151 | ## 关于 152 | 153 | - 基于 vue2.0 的 vjdesign 设计器定义的配置是否能在这里使用 154 | 155 | 因为基于 vue2.0 的组件如果不做特殊适配基本上不能在 vue3.0 使用因此不能兼容,但是如果组件库 vue2.0 的属性和 vue3.0 的属性一致,则可以使用自定义渲染钩子将组件属性转换成适用于 vue3.0 的定义实现兼容 156 | -------------------------------------------------------------------------------- /packages/core/src/feature/proxy.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assignObject, 3 | isFunction, 4 | assignArray, 5 | forEachTarget, 6 | isArray, 7 | isObject, 8 | createToken, 9 | ContainerInstance, 10 | } from '../utils' 11 | import { ProxyHandler, ProxyMatcher, ProxyContext, ProxyFlags } from '../types' 12 | import { servicesToken } from '../service/token' 13 | 14 | export const proxyToken = createToken('proxy') 15 | 16 | export const proxyServiceToken = createToken('proxyService') 17 | 18 | export const proxyContextToken = createToken('proxyContext') 19 | 20 | export class ProxyService { 21 | private proxies: ProxyMatcher[] = [] 22 | private services: Record = {} 23 | 24 | constructor(container: ContainerInstance) { 25 | this.proxies = container.getMany(proxyToken) 26 | this.services = container.get(servicesToken) as Record 27 | } 28 | 29 | private getHandler() { 30 | return (value: any) => { 31 | for (const index in this.proxies) { 32 | const handler: ProxyHandler | undefined = this.proxies[index]( 33 | value, 34 | this.services 35 | ) 36 | if (handler) { 37 | return handler 38 | } 39 | } 40 | } 41 | } 42 | 43 | private createProxy(origin: unknown, context: ProxyContext) { 44 | const get = (target: any, p: any, receiver: any): any => { 45 | if (p === ProxyFlags.PROXY_DEFINE) { 46 | return target 47 | } 48 | 49 | if (p === ProxyFlags.IS_PROXY) { 50 | return true 51 | } 52 | 53 | const value = Reflect.get(target, p, receiver) 54 | 55 | const handler: ProxyHandler | undefined = this.getHandler()(value) 56 | 57 | return this.inject( 58 | handler && isFunction(handler) ? handler(context) : value, 59 | context 60 | ) 61 | } 62 | 63 | return new Proxy(origin, { get }) 64 | } 65 | 66 | inject(target: unknown, context: ProxyContext) { 67 | if (!isAllowedProxy(target)) { 68 | return target 69 | } 70 | 71 | if (isProxy(target)) { 72 | return this.createProxy(getProxyDefine(target), context) 73 | } 74 | 75 | return this.createProxy(target, context) 76 | } 77 | } 78 | 79 | export const isProxy = (target: unknown) => { 80 | const proxy: any = target 81 | return !!((isObject(proxy) || isArray(proxy)) && proxy[ProxyFlags.IS_PROXY]) 82 | } 83 | 84 | export const isRejectProxy = (target: unknown) => { 85 | const proxy: any = target 86 | return !!((isObject(proxy) || isArray(proxy)) && proxy[ProxyFlags.NOT_PROXY]) 87 | } 88 | 89 | export const isAllowedProxy = (target: unknown) => { 90 | return ( 91 | !!target && !isRejectProxy(target) && (isObject(target) || isArray(target)) 92 | ) 93 | } 94 | 95 | export const getProperty = (target: any, prop: any) => { 96 | return Reflect.getOwnPropertyDescriptor(target, prop) 97 | } 98 | 99 | export const getProxyDefine = (target: any) => { 100 | if (!isArray(target) && !isObject(target)) { 101 | return target 102 | } 103 | 104 | const assign = isArray(target) ? assignArray : assignObject 105 | 106 | const result = assign( 107 | isProxy(target) ? target[ProxyFlags.PROXY_DEFINE] : target 108 | ) 109 | 110 | forEachTarget(result, (value: any, prop: any) => { 111 | result[prop] = getProxyDefine(value) 112 | }) 113 | 114 | return result 115 | } 116 | -------------------------------------------------------------------------------- /src/views/Element.vue: -------------------------------------------------------------------------------- 1 | 125 | 126 | 138 | -------------------------------------------------------------------------------- /public/data/sub.json: -------------------------------------------------------------------------------- 1 | { 2 | "fields": [ 3 | { 4 | "component": "v-jrender", 5 | "props": { 6 | "class": "j-form", 7 | "modelValue": { "$type": "computed", "$result": "model.obj" }, 8 | "fields": [ 9 | { "component": "p", "text": "嵌套渲染" }, 10 | { 11 | "component": "p", 12 | "text": { "$type": "computed", "$result": "model.value" } 13 | }, 14 | { 15 | "component": "p", 16 | "text": { "$type": "computed", "$result": "model.selected" } 17 | } 18 | ] 19 | } 20 | }, 21 | { 22 | "component": "div", 23 | "children": [ 24 | { 25 | "component": "el-form", 26 | "props": { 27 | "labelWidth": "120px" 28 | }, 29 | "children": [ 30 | { 31 | "component": "el-form-item", 32 | "props": { "label": "input1" }, 33 | "children": [ 34 | { 35 | "component": "el-input", 36 | "props": { 37 | "modelValue": { 38 | "$type": "computed", 39 | "$result": "model.text1" 40 | }, 41 | "onUpdate:modelValue": { 42 | "$type": "method", 43 | "$context": "model.text1", 44 | "$result": "arguments[0]" 45 | } 46 | } 47 | } 48 | ] 49 | }, 50 | { 51 | "component": "el-form-item", 52 | "props": { "label": "input2" }, 53 | "children": [ 54 | { 55 | "component": "el-input", 56 | "props": { 57 | "modelValue": { 58 | "$type": "computed", 59 | "$result": "model.obj.value" 60 | }, 61 | "onUpdate:modelValue": { 62 | "$type": "method", 63 | "$context": "model.obj.value", 64 | "$result": "arguments[0]" 65 | } 66 | } 67 | } 68 | ] 69 | }, 70 | { 71 | "component": "el-form-item", 72 | "props": { "label": "select1" }, 73 | "children": [ 74 | { 75 | "component": "el-select", 76 | "props": { 77 | "modelValue": { 78 | "$type": "computed", 79 | "$result": "model.obj.selected" 80 | }, 81 | "onUpdate:modelValue": { 82 | "$type": "method", 83 | "$context": "model.obj.selected", 84 | "$result": "arguments[0]" 85 | } 86 | }, 87 | "children": [ 88 | { 89 | "component": "el-option", 90 | "props": { "value": 0, "label": "选项1" } 91 | }, 92 | { 93 | "component": "el-option", 94 | "props": { "value": 1, "label": "选项2" } 95 | } 96 | ] 97 | } 98 | ] 99 | } 100 | ] 101 | } 102 | ] 103 | } 104 | ] 105 | } 106 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-to-render", 3 | "version": "0.4.0", 4 | "private": true, 5 | "author": "fyl080801 ", 6 | "homepage": "https://github.com/fyl080801/json-to-render#readme", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/fyl080801/json-to-render.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/fyl080801/json-to-render/issues" 14 | }, 15 | "workspaces": [ 16 | "packages/*" 17 | ], 18 | "scripts": { 19 | "dev": "vite -c ./vite.config.js", 20 | "build": "vite build -c ./vite.config.js", 21 | "clean": "yarn workspaces run clean", 22 | "dist": "yarn workspaces run dist", 23 | "docs:dev": "vitepress dev docs", 24 | "docs:build": "vitepress build docs", 25 | "commit": "git add . && git status && git-cz", 26 | "publish": "lerna publish", 27 | "prepack": "yarn run dist", 28 | "postinstall": "husky install", 29 | "postpublish": "yarn run clean", 30 | "lint-staged": "lint-staged" 31 | }, 32 | "dependencies": { 33 | "core-js": "^3.15.2", 34 | "reflect-metadata": "^0.1.13", 35 | "uuid": "^8.3.2", 36 | "vue": "3.1.4" 37 | }, 38 | "devDependencies": { 39 | "@babel/core": "^7.14.8", 40 | "@babel/plugin-proposal-decorators": "^7.14.5", 41 | "@babel/plugin-transform-runtime": "^7.14.5", 42 | "@babel/preset-typescript": "^7.14.5", 43 | "@octokit/core": "^3.5.1", 44 | "@rollup/plugin-alias": "^3.1.4", 45 | "@rollup/plugin-babel": "^5.2.3", 46 | "@rollup/plugin-commonjs": "^19.0.1", 47 | "@rollup/plugin-node-resolve": "^13.0.2", 48 | "@rollup/plugin-typescript": "^8.2.3", 49 | "@tailwindcss/forms": "^0.3.3", 50 | "@types/js-yaml": "^4.0.2", 51 | "@types/uuid": "^8.3.1", 52 | "@typescript-eslint/eslint-plugin": "^4.28.4", 53 | "@typescript-eslint/parser": "^4.28.4", 54 | "@vitejs/plugin-vue": "^1.2.5", 55 | "@vitejs/plugin-vue-jsx": "^1.1.6", 56 | "@vue/babel-plugin-jsx": "^1.0.6", 57 | "@vue/compiler-sfc": "3.1.4", 58 | "@vue/eslint-config-prettier": "^6.0.0", 59 | "@vue/eslint-config-typescript": "^7.0.0", 60 | "autoprefixer": "^10.3.1", 61 | "axios": "^0.21.1", 62 | "commitizen": "^4.2.4", 63 | "cross-env": "^7.0.3", 64 | "cz-conventional-changelog": "^3.3.0", 65 | "element-plus": "^1.0.2-beta.58", 66 | "eslint": "^7.31.0", 67 | "eslint-plugin-prettier": "^3.4.0", 68 | "eslint-plugin-vue": "^7.14.0", 69 | "husky": "^7.0.1", 70 | "js-yaml": "^4.1.0", 71 | "lerna": "^4.0.0", 72 | "lint-staged": "^11.1.0", 73 | "lodash": "^4.17.21", 74 | "monaco-editor": "^0.25.2", 75 | "node-sass": "^6.0.1", 76 | "postcss": "^8.3.6", 77 | "prettier": "^2.3.2", 78 | "rimraf": "^3.0.2", 79 | "rollup": "^2.53.3", 80 | "rollup-plugin-postcss": "^4.0.0", 81 | "rollup-plugin-scss": "^3.0.0", 82 | "rollup-plugin-sizes": "^1.0.4", 83 | "rollup-plugin-terser": "^7.0.2", 84 | "rollup-plugin-vue": "^6.0.0", 85 | "sass": "^1.36.0", 86 | "tailwindcss": "^2.2.7", 87 | "tslib": "^2.3.0", 88 | "typescript": "^4.3.5", 89 | "vite": "^2.4.3", 90 | "vitepress": "^0.15.6", 91 | "vue-router": "^4.0.10" 92 | }, 93 | "config": { 94 | "commitizen": { 95 | "path": "./node_modules/cz-conventional-changelog" 96 | } 97 | }, 98 | "gitHooks": { 99 | "pre-commit": "lint-staged" 100 | }, 101 | "lint-staged": { 102 | "*.(vue|tsx|js|ts)": [ 103 | "prettier --write" 104 | ] 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /packages/core/src/di/utils/resolveToTypeWrapper.ts: -------------------------------------------------------------------------------- 1 | import { Token } from '../token' 2 | import { Constructable } from '../types/constructable' 3 | import { ServiceIdentifier } from '../types/serviceIdentifier' 4 | 5 | /** 6 | * Helper function used in inject decorators to resolve the received identifier to 7 | * an eager type when possible or to a lazy type when cyclic dependencies are possibly involved. 8 | * 9 | * @param typeOrIdentifier a service identifier or a function returning a type acting as service identifier or nothing 10 | * @param target the class definition of the target of the decorator 11 | * @param propertyName the name of the property in case of a PropertyDecorator 12 | * @param index the index of the parameter in the constructor in case of ParameterDecorator 13 | */ 14 | export function resolveToTypeWrapper( 15 | typeOrIdentifier: 16 | | ((type?: never) => Constructable) 17 | | ServiceIdentifier 18 | | undefined, 19 | target: Object, 20 | propertyName: string | Symbol, 21 | index?: number 22 | ): { 23 | eagerType: ServiceIdentifier | null 24 | lazyType: (type?: never) => ServiceIdentifier 25 | } { 26 | /** 27 | * ? We want to error out as soon as possible when looking up services to inject, however 28 | * ? we cannot determine the type at decorator execution when cyclic dependencies are involved 29 | * ? because calling the received `() => MyType` function right away would cause a JS error: 30 | * ? "Cannot access 'MyType' before initialization", so we need to execute the function in the handler, 31 | * ? when the classes are already created. To overcome this, we use a wrapper: 32 | * ? - the lazyType is executed in the handler so we never have a JS error 33 | * ? - the eagerType is checked when decorator is running and an error is raised if an unknown type is encountered 34 | */ 35 | let typeWrapper!: { 36 | eagerType: ServiceIdentifier | null 37 | lazyType: (type?: never) => ServiceIdentifier 38 | } 39 | 40 | /** If requested type is explicitly set via a string ID or token, we set it explicitly. */ 41 | if ( 42 | (typeOrIdentifier && typeof typeOrIdentifier === 'string') || 43 | typeOrIdentifier instanceof Token 44 | ) { 45 | typeWrapper = { 46 | eagerType: typeOrIdentifier, 47 | lazyType: () => typeOrIdentifier, 48 | } 49 | } 50 | 51 | /** If requested type is explicitly set via a () => MyClassType format, we set it explicitly. */ 52 | if (typeOrIdentifier && typeof typeOrIdentifier === 'function') { 53 | /** We set eagerType to null, preventing the raising of the CannotInjectValueError in decorators. */ 54 | typeWrapper = { 55 | eagerType: null, 56 | lazyType: () => (typeOrIdentifier as CallableFunction)(), 57 | } 58 | } 59 | 60 | /** If no explicit type is set and handler registered for a class property, we need to get the property type. */ 61 | if (!typeOrIdentifier && propertyName) { 62 | const identifier = (Reflect as any).getMetadata( 63 | 'design:type', 64 | target, 65 | propertyName 66 | ) 67 | 68 | typeWrapper = { eagerType: identifier, lazyType: () => identifier } 69 | } 70 | 71 | /** If no explicit type is set and handler registered for a constructor parameter, we need to get the parameter types. */ 72 | if ( 73 | !typeOrIdentifier && 74 | typeof index == 'number' && 75 | Number.isInteger(index) 76 | ) { 77 | const paramTypes: ServiceIdentifier[] = (Reflect as any).getMetadata( 78 | 'design:paramtypes', 79 | target, 80 | propertyName 81 | ) 82 | /** It's not guaranteed, that we find any types for the constructor. */ 83 | const identifier = paramTypes?.[index] 84 | 85 | typeWrapper = { eagerType: identifier, lazyType: () => identifier } 86 | } 87 | 88 | return typeWrapper 89 | } 90 | -------------------------------------------------------------------------------- /packages/vue/src/feature/hook.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assignArray, 3 | assignObject, 4 | createToken, 5 | ContainerInstance, 6 | servicesToken, 7 | isArray, 8 | } from '@json2render/core' 9 | import { Slots } from '@vue/runtime-core' 10 | import { HookMeta } from '../types' 11 | import { nonArrayFunction } from '../utils/helper' 12 | import pipeline from '../utils/pipeline' 13 | import { resolveChildren } from '../utils/render' 14 | 15 | export const prerenderToken = createToken('prerender') 16 | 17 | export const prerenderServiceToken = 18 | createToken('prerenderService') 19 | 20 | export const renderToken = createToken('render') 21 | 22 | export const renderServiceToken = createToken('renderService') 23 | 24 | export class PrerenderService { 25 | private hooks: HookMeta[] = [] 26 | private services: Record 27 | 28 | constructor(container: ContainerInstance) { 29 | this.hooks = container 30 | .getMany(prerenderToken) 31 | .sort((a, b) => a.index - b.index) 32 | this.services = container.get(servicesToken) 33 | } 34 | 35 | process(value: any, context: Record) { 36 | const innerServices = new Proxy( 37 | { context }, 38 | { 39 | get: (target, p, receiver) => { 40 | if (p === 'context') { 41 | return Reflect.get(target, p, receiver) 42 | } else { 43 | return this.services[p as string] 44 | } 45 | }, 46 | } 47 | ) 48 | 49 | return (...extra: HookMeta[]) => { 50 | pipeline(assignArray(this.hooks, extra || []), innerServices)(value) 51 | } 52 | } 53 | } 54 | 55 | export class RenderService { 56 | private hooks: HookMeta[] = [] 57 | 58 | constructor(container: ContainerInstance) { 59 | this.hooks = container 60 | .getMany(renderToken) 61 | .sort((a, b) => a.index - b.index) 62 | } 63 | 64 | process(value: any, context: Record) { 65 | return (...extra: HookMeta[]) => { 66 | pipeline( 67 | assignArray(this.hooks, extra || []), 68 | assignObject({ context }) 69 | )(value) 70 | } 71 | } 72 | } 73 | 74 | export const childrenPrerender = () => (field: any, next: any) => { 75 | if (!isArray(field.children)) { 76 | next(field) 77 | return 78 | } 79 | 80 | const children = field.children?.filter((child: any) => child) ?? [] 81 | 82 | if (children.length <= 0) { 83 | next(field) 84 | return 85 | } 86 | 87 | field.children = resolveChildren(children) 88 | 89 | next(field) 90 | } 91 | 92 | export const injectPrerender = 93 | (nodeField: any) => 94 | ({ proxy, context }: any) => 95 | (field: any, next: any) => { 96 | nodeField.value = proxy.inject(field, context) 97 | next(nodeField.value) 98 | } 99 | 100 | export const slotsPrerender = 101 | (slots: Slots) => () => (field: any, next: any) => { 102 | if (!field.children) { 103 | next(field) 104 | return 105 | } 106 | 107 | for (const key in field.children) { 108 | const slotIncludes = [] 109 | 110 | for (let i = 0; i < field.children[key].length; i++) { 111 | const current = field.children[key][i] 112 | 113 | if (current.component === 'slot') { 114 | const slotName = current.name || 'default' 115 | if (typeof slots[slotName] === 'function') { 116 | slotIncludes.push(() => 117 | (slots[slotName] || nonArrayFunction)(current.props) 118 | ) 119 | } 120 | } else { 121 | slotIncludes.push(field.children[key][i]) 122 | } 123 | } 124 | 125 | field.children[key] = slotIncludes 126 | } 127 | 128 | next(field) 129 | } 130 | -------------------------------------------------------------------------------- /src/views/Yaml.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, ref } from 'vue' 2 | import yaml from 'js-yaml' 3 | import { assignArray, deepSet } from '@json2render/core' 4 | import pluginElementUI from '@json2render/plugin-elementui' 5 | 6 | export default defineComponent({ 7 | setup() { 8 | const json = ref({ datasource: {}, listeners: [], fields: [], model: {} }) 9 | 10 | fetch(`/data/yaml/testform.yaml`).then(async (response) => { 11 | const text = await response.text() 12 | 13 | const obj = yaml.load(text) 14 | 15 | Object.assign( 16 | json.value, 17 | { datasource: {}, listeners: [], fields: [], model: {} }, 18 | obj 19 | ) 20 | }) 21 | 22 | const onSetup = (hooks: any) => { 23 | const { proxy } = hooks 24 | 25 | pluginElementUI(hooks) 26 | 27 | // 用 = 代替 # 表示模板字符 28 | proxy((value: any, { functional }: any) => { 29 | const func = (context: any) => { 30 | try { 31 | const expr = value.slice(value.indexOf(':') + 1, value.length) 32 | const contextKeys = Object.keys(context) 33 | const functionals = functional() 34 | const exprStr = '`' + expr + '`' 35 | 36 | return new Function( 37 | ...assignArray(contextKeys, functionals.names, [ 38 | `return ${exprStr}`, 39 | ]) 40 | )( 41 | ...assignArray( 42 | contextKeys.map((key) => context[key]), 43 | functionals.executers 44 | ) 45 | ) 46 | } catch { 47 | // 48 | } 49 | } 50 | 51 | return typeof value === 'string' && /^([=]:)/g.test(value) 52 | ? func 53 | : undefined 54 | }) 55 | 56 | // yaml 里用 ~ 代替 @ 表示事件 57 | proxy((value: any, { functional }: any) => { 58 | const execute = (context: any) => { 59 | return (...args: any) => { 60 | try { 61 | const expr = value.slice(value.indexOf(':') + 1, value.length) 62 | const expProp = value.slice(1, value.indexOf(':')) 63 | const contextkeys = Object.keys(context) 64 | const functionals = functional() 65 | const inputs = assignArray( 66 | contextkeys.map((key) => context[key]), 67 | functionals.executers, 68 | [args] 69 | ) 70 | 71 | const result = new Function( 72 | ...assignArray(contextkeys, functionals.names, [ 73 | 'arguments', 74 | `return ${expr}`, 75 | ]) 76 | )(...inputs) 77 | 78 | if (expProp && expProp.length > 0) { 79 | const keyExpr = '`' + expProp + '`' 80 | const expKey = new Function( 81 | ...assignArray(contextkeys, [ 82 | 'arguments', 83 | `return ${keyExpr}`, 84 | ]) 85 | )(...inputs) 86 | deepSet(context, expKey, result) 87 | } else { 88 | return result 89 | } 90 | } catch { 91 | // console.log(e) 92 | } 93 | } 94 | } 95 | 96 | return typeof value === 'string' && /^(~[\s\S]*:)/g.test(value) 97 | ? execute 98 | : undefined 99 | }) 100 | } 101 | 102 | return () => ( 103 |
104 | 112 |
113 | ) 114 | }, 115 | }) 116 | -------------------------------------------------------------------------------- /packages/core/src/service/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Container, 3 | Constructable, 4 | Token, 5 | ServiceOptions, 6 | ContainerInstance, 7 | } from '../di' 8 | import { v4 } from 'uuid' 9 | import { 10 | ProxyService, 11 | proxyServiceToken, 12 | functionalServiceToken, 13 | FunctionalService, 14 | datasourceServiceToken, 15 | DatasourceService, 16 | proxyToken, 17 | functionalToken, 18 | datasourceToken, 19 | } from '../feature' 20 | import { DatasourceBuilder, Functional, ProxyMatcher } from '../types' 21 | import { assignArray, assignObject } from '../utils' 22 | import { servicesToken } from './token' 23 | 24 | const createServices = ( 25 | container: ContainerInstance, 26 | tokenMap: { 27 | [key: string]: unknown 28 | } 29 | ) => { 30 | return new Proxy( 31 | {}, 32 | { 33 | get(target, p, receiver) { 34 | if (typeof p === 'string') { 35 | const token: any = tokenMap[p] 36 | 37 | if (token) { 38 | return container.get(token) 39 | } 40 | } 41 | 42 | return Reflect.get(target, p, receiver) 43 | }, 44 | } 45 | ) 46 | } 47 | 48 | const getProvider = (container: ContainerInstance) => { 49 | const instance = { 50 | addService(token: Token | Constructable, type: Constructable) { 51 | container.set({ 52 | id: token, 53 | multiple: false, 54 | type, 55 | }) 56 | return instance 57 | }, 58 | addValue(token: Token | string, value: T) { 59 | container.set({ 60 | id: token, 61 | multiple: true, 62 | value: value, 63 | }) 64 | return instance 65 | }, 66 | resolve(token: Token | Constructable | string) { 67 | return container.get(token) 68 | }, 69 | resolveAll(token: Token | string) { 70 | return container.getMany(token) 71 | }, 72 | } 73 | return instance 74 | } 75 | 76 | export const baseTokenMaps = { 77 | functional: functionalServiceToken, 78 | proxy: proxyServiceToken, 79 | } 80 | 81 | export const createServiceContainer = (tokenMap?: Record) => { 82 | const stored: ServiceOptions[] = [] 83 | 84 | const tokens = assignObject(baseTokenMaps, tokenMap) 85 | 86 | const instance = { 87 | addService(token: Token | Constructable, type: Constructable) { 88 | stored.push({ 89 | id: token, 90 | multiple: false, 91 | type, 92 | }) 93 | return instance 94 | }, 95 | addValue(token: Token | string, value: T) { 96 | stored.push({ 97 | id: token, 98 | multiple: true, 99 | value: value, 100 | }) 101 | return instance 102 | }, 103 | build(id?: string) { 104 | const container = Container.of(id || v4()) 105 | 106 | assignArray( 107 | [ 108 | { 109 | id: servicesToken, 110 | multiple: false, 111 | value: createServices(container, tokens), 112 | }, 113 | { 114 | id: proxyServiceToken, 115 | multiple: false, 116 | type: ProxyService, 117 | }, 118 | { 119 | id: functionalServiceToken, 120 | multiple: false, 121 | type: FunctionalService, 122 | }, 123 | { 124 | id: datasourceServiceToken, 125 | multiple: false, 126 | type: DatasourceService, 127 | }, 128 | ], 129 | stored 130 | ).forEach((item) => container.set(item)) 131 | 132 | return getProvider(container) 133 | }, 134 | } 135 | 136 | return instance 137 | } 138 | 139 | export const createCoreSetup = (container: any) => { 140 | const proxySetup = (value: ProxyMatcher) => { 141 | container.addValue(proxyToken, value) 142 | } 143 | 144 | const functionalSetup = (name: string, value: Functional) => { 145 | container.addValue(functionalToken, { 146 | name, 147 | invoke: value, 148 | }) 149 | } 150 | 151 | const datasourceSetup = (name: string, value: DatasourceBuilder) => { 152 | container.addValue(datasourceToken, { 153 | type: name, 154 | build: value, 155 | }) 156 | } 157 | 158 | return { 159 | proxy: proxySetup, 160 | functional: functionalSetup, 161 | datasource: datasourceSetup, 162 | } 163 | } 164 | 165 | export { servicesToken } 166 | -------------------------------------------------------------------------------- /packages/core/src/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | export const isArray = (target: any) => { 2 | return ( 3 | target !== undefined && 4 | target !== null && 5 | Object.prototype.toString.call(target) === '[object Array]' 6 | ) 7 | } 8 | 9 | export const isObject = (target: any) => { 10 | return target !== undefined && typeof target === 'object' && target !== null 11 | } 12 | 13 | export const isFunction = (target: any) => { 14 | return target !== undefined && typeof target === 'function' 15 | } 16 | 17 | export const assignArray = (...targets: Array>) => { 18 | return targets.reduce((pre: Array, cur: Array) => { 19 | return pre.concat(cur) 20 | }, []) 21 | } 22 | 23 | export const assignObject = (...targets: any) => { 24 | return Object.assign({}, ...targets) 25 | } 26 | 27 | export const forEachTarget = (target: any, iteratee: any) => { 28 | if (isArray(target)) { 29 | for (let i = 0; i < target.length; i++) { 30 | iteratee(target[i], i, target) 31 | } 32 | } else if (typeof target === 'object' && target !== undefined) { 33 | for (const key in target) { 34 | iteratee(target[key], key, target) 35 | } 36 | } 37 | } 38 | 39 | const isNumberLike = (value: any) => { 40 | return String(value).match(/^\d+$/) 41 | } 42 | 43 | const toPath = ( 44 | pathString: string | Array 45 | ): Array => { 46 | if (isArray(pathString)) { 47 | return pathString as Array 48 | } 49 | if (typeof pathString === 'number') { 50 | return [pathString] as Array 51 | } 52 | pathString = String(pathString) 53 | 54 | // lodash 的实现 - https://github.com/lodash/lodash 55 | const pathRx = 56 | /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(\.|\[\])(?:\4|$))/g 57 | const pathArray: Array = [] 58 | 59 | const replacer: any = (match: any, num: any, quote: any, str: any) => { 60 | pathArray.push(quote ? str : num !== undefined ? Number(num) : match) 61 | return pathArray[pathArray.length - 1] 62 | } 63 | 64 | pathString.replace(pathRx, replacer) 65 | 66 | return pathArray 67 | } 68 | 69 | const hasOwnProperty = (target: any, prop: any) => { 70 | return Object.prototype.hasOwnProperty.call(target, prop) 71 | } 72 | 73 | export const deepSet = (target: any, path: any, value: any) => { 74 | const fields = isArray(path) ? path : toPath(path) 75 | const prop = fields.shift() 76 | 77 | if (!fields.length) { 78 | return (target[prop] = value) 79 | } 80 | 81 | if (!hasOwnProperty(target, prop) || target[prop] === null) { 82 | // 当前下标是数字则认定是数组 83 | const objVal = fields.length >= 1 && isNumberLike(fields[0]) ? [] : {} 84 | target[prop] = objVal 85 | } 86 | 87 | deepSet(target[prop], fields, value) 88 | } 89 | 90 | export const deepGet = (target: any, path: string | Array) => { 91 | const fields = isArray(path) ? (path as Array) : toPath(path) 92 | 93 | if (!fields.length) { 94 | return target 95 | } 96 | 97 | let prop = fields.shift() 98 | let result = target 99 | 100 | while (prop) { 101 | result = result[prop] 102 | prop = fields.shift() 103 | } 104 | 105 | return result 106 | } 107 | 108 | export const cloneDeep = (item: any) => { 109 | if (!item) { 110 | return item 111 | } // null, undefined values check 112 | 113 | const types = [Number, String, Boolean] 114 | let result: any 115 | 116 | // normalizing primitives if someone did new String('aaa'), or new Number('444'); 117 | types.forEach((type) => { 118 | if (item instanceof type) { 119 | result = type(item) 120 | } 121 | }) 122 | 123 | if (typeof result == 'undefined') { 124 | if (Object.prototype.toString.call(item) === '[object Array]') { 125 | result = [] 126 | item.forEach((child: any, index: any) => { 127 | result[index] = cloneDeep(child) 128 | }) 129 | } else if (typeof item == 'object') { 130 | // testing that this is DOM 131 | if (item.nodeType && typeof item.cloneNode == 'function') { 132 | result = item.cloneNode(true) 133 | } else if (!item.prototype) { 134 | // check that this is a literal 135 | if (item instanceof Date) { 136 | result = new Date(item) 137 | } else { 138 | // it is an object literal 139 | result = {} 140 | for (const i in item) { 141 | result[i] = cloneDeep(item[i]) 142 | } 143 | } 144 | } else { 145 | // depending what you would like here, 146 | // just keep the reference, or create new object 147 | if (item.constructor) { 148 | // would not advice to do that, reason? Read below 149 | result = new item.constructor() 150 | } else { 151 | result = item 152 | } 153 | } 154 | } else { 155 | result = item 156 | } 157 | } 158 | 159 | return result 160 | } 161 | -------------------------------------------------------------------------------- /packages/vue/src/components/jRender.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, 3 | h, 4 | ref, 5 | resolveComponent, 6 | watch, 7 | toRaw, 8 | nextTick, 9 | onBeforeUnmount, 10 | reactive, 11 | Slots, 12 | } from 'vue' 13 | import { containerBuilder, createSetup } from '../service' 14 | import { createStore } from '../store' 15 | import { innerDataNames } from '../utils/enums' 16 | import JNode from './jNode' 17 | import { 18 | ProxyContext, 19 | proxyContextToken, 20 | datasourceServiceToken, 21 | proxyServiceToken, 22 | isArray, 23 | isFunction, 24 | } from '@json2render/core' 25 | import { 26 | ComponentService, 27 | componentServiceToken, 28 | PrerenderService, 29 | prerenderServiceToken, 30 | RenderService, 31 | renderServiceToken, 32 | slotsToken, 33 | } from '../feature' 34 | 35 | export default defineComponent({ 36 | name: 'vJrender', 37 | components: { [JNode.name]: JNode }, 38 | props: { 39 | modelValue: { type: Object, default: () => ({}) }, 40 | component: { type: String, default: 'div' }, 41 | fields: { type: Array, default: () => [] }, 42 | datasource: { type: Object, default: () => ({}) }, 43 | listeners: { type: Array, default: () => [] }, 44 | }, 45 | emits: ['setup', 'update:modelValue'], 46 | setup: (props, ctx) => { 47 | const context: ProxyContext = reactive({ 48 | model: toRaw(props.modelValue), 49 | scope: {}, 50 | refs: {}, 51 | }) 52 | 53 | const root = reactive({ field: {}, scope: {} }) 54 | 55 | const updating = ref(false) // 为了更新 fields 时从根节点刷新 56 | 57 | //#region 初始化服务相关 58 | const container = containerBuilder 59 | .build() 60 | .addService(prerenderServiceToken, PrerenderService) 61 | .addService(renderServiceToken, RenderService) 62 | .addService(componentServiceToken, ComponentService) 63 | .addValue(proxyContextToken, context) 64 | .addValue(slotsToken, ctx.slots) 65 | 66 | ctx.emit('setup', createSetup(container)) 67 | 68 | createStore(container) 69 | //#endregion 70 | 71 | //#region 相关监听 72 | watch( 73 | () => props.fields, 74 | (value) => { 75 | updating.value = true 76 | 77 | root.field = { 78 | component: props.component, 79 | children: toRaw(value || []), 80 | } 81 | 82 | root.scope = {} 83 | 84 | nextTick(() => { 85 | updating.value = false 86 | }) 87 | }, 88 | { deep: false, immediate: true } 89 | ) 90 | 91 | watch( 92 | () => props.modelValue, 93 | (value) => { 94 | context.model = toRaw(value || {}) 95 | }, 96 | { deep: false, immediate: false } 97 | ) 98 | 99 | watch( 100 | () => props.datasource, 101 | (value, origin) => { 102 | const datasourceService = container.resolve(datasourceServiceToken) 103 | 104 | Object.keys(origin || {}) 105 | .filter((item) => !(innerDataNames.indexOf(item) >= 0)) 106 | .forEach((key) => { 107 | datasourceService.release(key) 108 | }) 109 | 110 | Object.keys(value || {}) 111 | .filter((item) => !(innerDataNames.indexOf(item) >= 0)) 112 | .forEach((key) => { 113 | datasourceService.build(key, value[key]) 114 | }) 115 | }, 116 | { deep: false, immediate: true } 117 | ) 118 | //#endregion 119 | 120 | //#region listeners 监听 121 | const watchs = ref([] as any[]) 122 | 123 | watch( 124 | () => props.listeners, 125 | (value) => { 126 | watchs.value.forEach((watcher) => watcher()) 127 | 128 | if (!value || !isArray(value)) { 129 | return 130 | } 131 | 132 | watchs.value = value.map((item) => { 133 | const proxy = container.resolve(proxyServiceToken) 134 | 135 | const injected = proxy.inject(item, context) 136 | 137 | const watcher = isFunction(injected.watch) 138 | ? injected.watch 139 | : () => injected.watch 140 | 141 | return watch( 142 | watcher, 143 | () => { 144 | injected.actions.forEach((act: any) => { 145 | if (act.condition === undefined || !!act.condition) { 146 | if (act.timeout) { 147 | setTimeout(() => act.handler(), act.timeout) 148 | } else { 149 | act.handler() 150 | } 151 | } 152 | }) 153 | }, 154 | { 155 | deep: injected.deep, 156 | immediate: injected.immediate, 157 | } 158 | ) 159 | }) 160 | }, 161 | { deep: false, immediate: true } 162 | ) 163 | 164 | onBeforeUnmount(() => { 165 | watchs.value.forEach((watcher) => watcher()) 166 | }) 167 | //#endregion 168 | 169 | return () => 170 | !updating.value ? h(resolveComponent(JNode.name), root) : null 171 | }, 172 | }) 173 | -------------------------------------------------------------------------------- /packages/plugin-vue/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [1.1.2](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-vue@1.1.1...@json2render/plugin-vue@1.1.2) (2021-07-17) 7 | 8 | **Note:** Version bump only for package @json2render/plugin-vue 9 | 10 | 11 | 12 | 13 | 14 | ## [1.1.1](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-vue@1.1.0...@json2render/plugin-vue@1.1.1) (2021-07-13) 15 | 16 | **Note:** Version bump only for package @json2render/plugin-vue 17 | 18 | 19 | 20 | 21 | 22 | # [1.1.0](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-vue@1.0.1...@json2render/plugin-vue@1.1.0) (2021-07-04) 23 | 24 | 25 | ### Features 26 | 27 | * fetch默认数据 ([04f1d13](https://github.com/fyl080801/json-to-render/commit/04f1d1364d86b5858048d49e747f48c6f7422937)) 28 | 29 | 30 | 31 | 32 | 33 | ## [1.0.1](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-vue@1.0.0...@json2render/plugin-vue@1.0.1) (2021-06-29) 34 | 35 | **Note:** Version bump only for package @json2render/plugin-vue 36 | 37 | 38 | 39 | 40 | 41 | # [1.0.0](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-vue@0.1.5...@json2render/plugin-vue@1.0.0) (2021-06-16) 42 | 43 | 44 | ### Features 45 | 46 | * di ([77ce3d9](https://github.com/fyl080801/json-to-render/commit/77ce3d9f63659c72470065c94362e97558cf3c90)) 47 | * di ([4d048d3](https://github.com/fyl080801/json-to-render/commit/4d048d354c4930ad6e4aa3e57a1a03f59362bcc0)) 48 | * 完成di迁移 ([2dd3725](https://github.com/fyl080801/json-to-render/commit/2dd372528cbc5d87852946b00f56f8b984464cdf)) 49 | * 重新划分功能结构 ([dbe38be](https://github.com/fyl080801/json-to-render/commit/dbe38be44edc2ea11848529a0a3fd52a4250fce0)) 50 | 51 | 52 | 53 | 54 | 55 | ## [0.1.5](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-vue@0.1.5-alpha.5...@json2render/plugin-vue@0.1.5) (2021-02-20) 56 | 57 | **Note:** Version bump only for package @json2render/plugin-vue 58 | 59 | 60 | 61 | 62 | 63 | ## [0.1.5-alpha.5](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-vue@0.1.5-alpha.4...@json2render/plugin-vue@0.1.5-alpha.5) (2021-02-20) 64 | 65 | **Note:** Version bump only for package @json2render/plugin-vue 66 | 67 | 68 | 69 | 70 | 71 | ## [0.1.5-alpha.4](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-vue@0.1.5-alpha.3...@json2render/plugin-vue@0.1.5-alpha.4) (2021-02-20) 72 | 73 | **Note:** Version bump only for package @json2render/plugin-vue 74 | 75 | 76 | 77 | 78 | 79 | ## [0.1.5-alpha.3](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-vue@0.1.5-alpha.2...@json2render/plugin-vue@0.1.5-alpha.3) (2021-02-20) 80 | 81 | **Note:** Version bump only for package @json2render/plugin-vue 82 | 83 | 84 | 85 | 86 | 87 | ## [0.1.5-alpha.2](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-vue@0.1.5-alpha.1...@json2render/plugin-vue@0.1.5-alpha.2) (2021-02-20) 88 | 89 | **Note:** Version bump only for package @json2render/plugin-vue 90 | 91 | 92 | 93 | 94 | 95 | ## [0.1.5-alpha.1](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-vue@0.1.5-alpha.0...@json2render/plugin-vue@0.1.5-alpha.1) (2021-02-20) 96 | 97 | **Note:** Version bump only for package @json2render/plugin-vue 98 | 99 | 100 | 101 | 102 | 103 | ## [0.1.5-alpha.0](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-vue@0.1.4...@json2render/plugin-vue@0.1.5-alpha.0) (2021-02-20) 104 | 105 | **Note:** Version bump only for package @json2render/plugin-vue 106 | 107 | 108 | 109 | 110 | 111 | ## [0.1.4](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-vue@0.1.3...@json2render/plugin-vue@0.1.4) (2021-02-13) 112 | 113 | **Note:** Version bump only for package @json2render/plugin-vue 114 | 115 | 116 | 117 | 118 | 119 | ## [0.1.3](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-vue@0.1.2...@json2render/plugin-vue@0.1.3) (2021-02-13) 120 | 121 | **Note:** Version bump only for package @json2render/plugin-vue 122 | 123 | 124 | 125 | 126 | 127 | ## [0.1.2](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-vue@0.1.1...@json2render/plugin-vue@0.1.2) (2021-02-08) 128 | 129 | **Note:** Version bump only for package @json2render/plugin-vue 130 | 131 | 132 | 133 | 134 | 135 | ## [0.1.1](https://github.com/fyl080801/json-to-render/compare/@json2render/plugin-vue@0.1.1-alpha.0...@json2render/plugin-vue@0.1.1) (2021-02-07) 136 | 137 | 138 | ### Features 139 | 140 | * 拆分原生支持的和vue支持的plugin ([18dfa2f](https://github.com/fyl080801/json-to-render/commit/18dfa2f42db009d39f515910008319e582b0364c)) 141 | 142 | 143 | 144 | 145 | 146 | ## 0.1.1-alpha.0 (2021-02-07) 147 | 148 | 149 | ### Features 150 | 151 | * **plugin-vue:** 引用vue的plugin单独出来 ([117e65c](https://github.com/fyl080801/json-to-render/commit/117e65c4f8f11e519e9268708c9632483af78c2d)) 152 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/components/demo-show/Component.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 33 | 34 | 242 | -------------------------------------------------------------------------------- /packages/vue-full/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [1.0.4](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@1.0.3...@json2render/vue-full@1.0.4) (2021-07-17) 7 | 8 | **Note:** Version bump only for package @json2render/vue-full 9 | 10 | 11 | 12 | 13 | 14 | ## [1.0.3](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@1.0.2...@json2render/vue-full@1.0.3) (2021-07-13) 15 | 16 | **Note:** Version bump only for package @json2render/vue-full 17 | 18 | 19 | 20 | 21 | 22 | ## [1.0.2](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@1.0.1...@json2render/vue-full@1.0.2) (2021-07-04) 23 | 24 | **Note:** Version bump only for package @json2render/vue-full 25 | 26 | 27 | 28 | 29 | 30 | ## [1.0.1](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@1.0.0...@json2render/vue-full@1.0.1) (2021-06-29) 31 | 32 | **Note:** Version bump only for package @json2render/vue-full 33 | 34 | 35 | 36 | 37 | 38 | # [1.0.0](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.11...@json2render/vue-full@1.0.0) (2021-06-16) 39 | 40 | **Note:** Version bump only for package @json2render/vue-full 41 | 42 | 43 | 44 | 45 | 46 | ## [0.1.11](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.10...@json2render/vue-full@0.1.11) (2021-04-06) 47 | 48 | **Note:** Version bump only for package @json2render/vue-full 49 | 50 | 51 | 52 | 53 | 54 | ## [0.1.10](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.9...@json2render/vue-full@0.1.10) (2021-02-23) 55 | 56 | **Note:** Version bump only for package @json2render/vue-full 57 | 58 | 59 | 60 | 61 | 62 | ## [0.1.9](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.9-alpha.5...@json2render/vue-full@0.1.9) (2021-02-20) 63 | 64 | **Note:** Version bump only for package @json2render/vue-full 65 | 66 | 67 | 68 | 69 | 70 | ## [0.1.9-alpha.5](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.9-alpha.4...@json2render/vue-full@0.1.9-alpha.5) (2021-02-20) 71 | 72 | **Note:** Version bump only for package @json2render/vue-full 73 | 74 | 75 | 76 | 77 | 78 | ## [0.1.9-alpha.4](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.9-alpha.3...@json2render/vue-full@0.1.9-alpha.4) (2021-02-20) 79 | 80 | **Note:** Version bump only for package @json2render/vue-full 81 | 82 | 83 | 84 | 85 | 86 | ## [0.1.9-alpha.3](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.9-alpha.2...@json2render/vue-full@0.1.9-alpha.3) (2021-02-20) 87 | 88 | **Note:** Version bump only for package @json2render/vue-full 89 | 90 | 91 | 92 | 93 | 94 | ## [0.1.9-alpha.2](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.9-alpha.1...@json2render/vue-full@0.1.9-alpha.2) (2021-02-20) 95 | 96 | **Note:** Version bump only for package @json2render/vue-full 97 | 98 | 99 | 100 | 101 | 102 | ## [0.1.9-alpha.1](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.9-alpha.0...@json2render/vue-full@0.1.9-alpha.1) (2021-02-20) 103 | 104 | **Note:** Version bump only for package @json2render/vue-full 105 | 106 | 107 | 108 | 109 | 110 | ## [0.1.9-alpha.0](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.8...@json2render/vue-full@0.1.9-alpha.0) (2021-02-20) 111 | 112 | **Note:** Version bump only for package @json2render/vue-full 113 | 114 | 115 | 116 | 117 | 118 | ## [0.1.8](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.7...@json2render/vue-full@0.1.8) (2021-02-13) 119 | 120 | **Note:** Version bump only for package @json2render/vue-full 121 | 122 | 123 | 124 | 125 | 126 | ## [0.1.7](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.6...@json2render/vue-full@0.1.7) (2021-02-13) 127 | 128 | **Note:** Version bump only for package @json2render/vue-full 129 | 130 | 131 | 132 | 133 | 134 | ## [0.1.6](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.5...@json2render/vue-full@0.1.6) (2021-02-09) 135 | 136 | **Note:** Version bump only for package @json2render/vue-full 137 | 138 | 139 | 140 | 141 | 142 | ## [0.1.5](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.4...@json2render/vue-full@0.1.5) (2021-02-08) 143 | 144 | **Note:** Version bump only for package @json2render/vue-full 145 | 146 | 147 | 148 | 149 | 150 | ## [0.1.4](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.4-alpha.0...@json2render/vue-full@0.1.4) (2021-02-07) 151 | 152 | 153 | ### Features 154 | 155 | * 拆分原生支持的和vue支持的plugin ([18dfa2f](https://github.com/fyl080801/json-to-render/commit/18dfa2f42db009d39f515910008319e582b0364c)) 156 | 157 | 158 | 159 | 160 | 161 | ## [0.1.4-alpha.0](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.3-alpha.0...@json2render/vue-full@0.1.4-alpha.0) (2021-02-07) 162 | 163 | 164 | ### Features 165 | 166 | * **plugin-vue:** 引用vue的plugin单独出来 ([117e65c](https://github.com/fyl080801/json-to-render/commit/117e65c4f8f11e519e9268708c9632483af78c2d)) 167 | 168 | 169 | 170 | 171 | 172 | ## [0.1.3-alpha.0](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.2-alpha.3...@json2render/vue-full@0.1.3-alpha.0) (2021-02-07) 173 | 174 | 175 | ### Features 176 | 177 | * package ([21a232a](https://github.com/fyl080801/json-to-render/commit/21a232a82766424503b2fb7aa78d0a3b5704ecfd)) 178 | 179 | 180 | 181 | 182 | 183 | ## [0.1.2-alpha.3](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.2-alpha.2...@json2render/vue-full@0.1.2-alpha.3) (2021-02-07) 184 | 185 | **Note:** Version bump only for package @json2render/vue-full 186 | 187 | 188 | 189 | 190 | 191 | ## [0.1.2-alpha.2](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.2-alpha.0...@json2render/vue-full@0.1.2-alpha.2) (2021-02-05) 192 | 193 | **Note:** Version bump only for package @json2render/vue-full 194 | 195 | 196 | 197 | 198 | 199 | ## [0.1.2-alpha.0](https://github.com/fyl080801/json-to-render/compare/@json2render/vue-full@0.1.1-alpha.0...@json2render/vue-full@0.1.2-alpha.0) (2021-02-05) 200 | 201 | **Note:** Version bump only for package @json2render/vue-full 202 | 203 | 204 | 205 | 206 | 207 | ## 0.1.1-alpha.0 (2021-02-05) 208 | 209 | 210 | ### Features 211 | 212 | * **vue-full:** 全插件 ([fcee879](https://github.com/fyl080801/json-to-render/commit/fcee879876d95b1dee572e2442179251b195f2ad)) 213 | -------------------------------------------------------------------------------- /public/data/basic/full.json: -------------------------------------------------------------------------------- 1 | { 2 | "datasource": { 3 | "remotedata": { 4 | "type": "fetch", 5 | "url": "/data/listdata.json", 6 | "auto": false, 7 | "props": { "method": "GET", "params": {} } 8 | }, 9 | "raws": { 10 | "type": "rawdata", 11 | "data": { 12 | "xxx": "123", 13 | "yyy": "$:model.text1" 14 | } 15 | } 16 | }, 17 | "model": { "selects": [] }, 18 | "listeners": [ 19 | { 20 | "watch": "$:model.text1", 21 | "actions": [ 22 | { "handler": "@:console.log('xxx')" }, 23 | { 24 | "condition": "$:model.text1.length > 5 && model.text1.length < 7", 25 | "handler": "@:remotedata.request()" 26 | } 27 | ] 28 | }, 29 | { 30 | "watch": "$:remotedata.data", 31 | "actions": [{ "handler": "@:console.log(remotedata.data)" }] 32 | } 33 | ], 34 | "fields": [ 35 | { 36 | "component": "fieldset", 37 | "children": [ 38 | { "component": "legend", "text": "html 组件" }, 39 | { "component": "input", "value": "model.text1" }, 40 | { 41 | "component": "input", 42 | "props": { "type": "checkbox" }, 43 | "checked": "model.checked" 44 | }, 45 | { "component": "span", "text": "#:checkedstatus: ${!!model.checked}" } 46 | ] 47 | }, 48 | { 49 | "component": "fieldset", 50 | "children": [ 51 | { "component": "legend", "text": "element 组件" }, 52 | { 53 | "component": "el-form", 54 | "props": { 55 | "labelWidth": "120px" 56 | }, 57 | "children": [ 58 | { 59 | "component": "el-form-item", 60 | "props": { "label": "input1" }, 61 | "children": [ 62 | { 63 | "component": "el-input", 64 | "model": "model.text1" 65 | }, 66 | { 67 | "component": "p", 68 | "text": "$:model.text1" 69 | } 70 | ] 71 | }, 72 | { 73 | "component": "el-form-item", 74 | "props": { "label": "input2" }, 75 | "children": [ 76 | { 77 | "component": "el-input", 78 | "model": "model.obj.value" 79 | } 80 | ] 81 | }, 82 | { 83 | "component": "el-form-item", 84 | "props": { "label": "select1" }, 85 | "children": [ 86 | { 87 | "component": "el-select", 88 | "model": "model.obj.selected", 89 | "children": [ 90 | { 91 | "component": "el-option", 92 | "props": { "value": 0, "label": "选项1" } 93 | }, 94 | { 95 | "component": "el-option", 96 | "props": { "value": 1, "label": "选项2" } 97 | } 98 | ] 99 | }, 100 | { 101 | "component": "span", 102 | "condition": "$:model.obj.selected", 103 | "text": "$:model.text1" 104 | }, 105 | { "component": "p", "text": "$:GET(model, 'obj.selected')" } 106 | ] 107 | }, 108 | { 109 | "component": "el-form-item", 110 | "props": { "label": "多选" }, 111 | "children": [ 112 | { 113 | "component": "el-checkbox-group", 114 | "model": "model.selects", 115 | "children": [ 116 | { 117 | "component": "el-checkbox", 118 | "props": { "label": 1 }, 119 | "children": [{ "component": "span", "text": "选项1" }] 120 | }, 121 | { 122 | "component": "el-checkbox", 123 | "props": { "label": 2 }, 124 | "children": [{ "component": "span", "text": "选项2" }] 125 | }, 126 | { 127 | "component": "el-checkbox", 128 | "props": { "label": 3 }, 129 | "children": [{ "component": "span", "text": "选项3" }] 130 | } 131 | ] 132 | } 133 | ] 134 | } 135 | ] 136 | }, 137 | { 138 | "component": "el-button", 139 | "text": "click1", 140 | "events": [ 141 | { "name": "click", "handler": "@:alert(GET(model, 'text1'))" }, 142 | { "name": "click", "handler": "@:alert('bbb')" } 143 | ], 144 | "props": { 145 | "type": "primary" 146 | } 147 | }, 148 | { 149 | "component": "el-button", 150 | "text": "request", 151 | "props": { 152 | "type": "primary", 153 | "onClick": "@:remotedata.request()" 154 | } 155 | }, 156 | { 157 | "component": "p", 158 | "text": "$:raws.xxx" 159 | }, 160 | { 161 | "component": "p", 162 | "text": "#:绑定得到的数据: ${raws.yyy}" 163 | } 164 | ] 165 | }, 166 | { 167 | "component": "input", 168 | "value": "model.text1", 169 | "props": { "ref": "oriinput" } 170 | }, 171 | { "component": "span", "text": "$:JSON.stringify(refs.tb.data)" }, 172 | { 173 | "component": "el-select", 174 | "model": "model.select2", 175 | "children": "$:[1,2,3].map(item=>({ component: 'el-option', props: { value: item, label: `select ${item}` } }))" 176 | }, 177 | { 178 | "component": "el-table", 179 | "props": { 180 | "ref": "tb", 181 | "data": "$:remotedata.data" 182 | }, 183 | "children": [ 184 | { 185 | "component": "el-table-column", 186 | "props": { "label": "col1", "prop": "name" } 187 | }, 188 | { 189 | "component": "el-table-column", 190 | "props": { "label": "col2" }, 191 | "children": [ 192 | { 193 | "component": "el-input", 194 | "model": "#:remotedata.data[${scope.$index}].name" 195 | } 196 | ] 197 | }, 198 | { 199 | "component": "el-table-column", 200 | "props": { "label": "col3" }, 201 | "children": [ 202 | { 203 | "component": "el-input", 204 | "events": [ 205 | { 206 | "name": "update:modelValue", 207 | "handler": "@remotedata.data[${scope.$index}].name:arguments[0]" 208 | } 209 | ], 210 | "props": { 211 | "modelValue": "$:remotedata.data[scope.$index].name" 212 | } 213 | } 214 | ] 215 | }, 216 | { 217 | "component": "el-table-column", 218 | "props": { "label": "操作" }, 219 | "children": [ 220 | { 221 | "component": "el-button", 222 | "text": "明细", 223 | "events": [ 224 | { 225 | "name": "click", 226 | "handler": "@:alert(JSON.stringify(remotedata.data[scope.$index]))" 227 | } 228 | ], 229 | "props": { 230 | "type": "text" 231 | } 232 | } 233 | ] 234 | } 235 | ] 236 | } 237 | ] 238 | } 239 | --------------------------------------------------------------------------------