├── antd-designer ├── src │ ├── designer │ │ ├── widgets │ │ │ ├── index.ts │ │ │ ├── OptionGroupEditor.tsx │ │ │ └── PropsEditor.tsx │ │ ├── layout │ │ │ ├── index.ts │ │ │ ├── LinearLayoutFactory.tsx │ │ │ └── ColumnLayoutFactory.tsx │ │ ├── helper │ │ │ ├── index.ts │ │ │ ├── FactoryRenders.ts │ │ │ └── FormHelper.ts │ │ ├── components │ │ │ ├── index.ts │ │ │ ├── InputFactory.tsx │ │ │ ├── TextAreaFactory.tsx │ │ │ ├── CheckboxFactory.tsx │ │ │ ├── RadioFactory.tsx │ │ │ ├── RateFactory.tsx │ │ │ └── SelectFactory.tsx │ │ ├── types.ts │ │ ├── ComponentPropsEditor.tsx │ │ ├── wrapper │ │ │ ├── index.tsx │ │ │ ├── ComponentWrapper.tsx │ │ │ └── LayoutWrapper.tsx │ │ ├── FormView.tsx │ │ ├── FormPropsEditor.tsx │ │ ├── FormDesigner.tsx │ │ └── ReactComponent.tsx │ ├── lib │ │ ├── jquery.ts │ │ └── sortable.ts │ ├── util │ │ └── MiscUtil.ts │ └── index.tsx ├── package.json ├── webpack.config.ts └── tsconfig.json ├── src ├── style │ ├── component.module.less │ ├── component.less │ └── formView.less ├── const │ └── externals.d.ts ├── props.ts ├── factory │ ├── ColumnLayoutFactory.ts │ ├── LinearLayoutFactory.ts │ ├── RateFactory.ts │ ├── InputFactory.ts │ ├── TextAreaFactory.ts │ ├── SelectFactory.ts │ ├── RadioFactory.ts │ └── CheckboxFactory.ts ├── FormStudio.ts ├── types.ts └── utils │ └── index.ts ├── postcss.config.js ├── antdv-designer ├── src │ ├── style.less │ ├── lib │ │ ├── jquery.ts │ │ └── sortable.ts │ ├── main.ts │ ├── designer │ │ ├── components │ │ │ ├── rate │ │ │ │ ├── index.vue │ │ │ │ └── PropEditor.vue │ │ │ ├── input │ │ │ │ ├── index.vue │ │ │ │ └── PropEditor.vue │ │ │ ├── textarea │ │ │ │ ├── index.vue │ │ │ │ └── PropEditor.vue │ │ │ ├── radio │ │ │ │ ├── PropEditor.vue │ │ │ │ └── index.vue │ │ │ ├── select │ │ │ │ ├── PropEditor.vue │ │ │ │ └── index.vue │ │ │ └── checkbox │ │ │ │ ├── PropEditor.vue │ │ │ │ └── index.vue │ │ ├── wrapper │ │ │ ├── PropsEditorRender.vue │ │ │ ├── ComponentWrapper.vue │ │ │ ├── ComponentRender.vue │ │ │ └── LayoutWrapper.vue │ │ ├── FormWidget.vue │ │ ├── types.ts │ │ ├── layout │ │ │ ├── column │ │ │ │ ├── PropEditor.vue │ │ │ │ ├── Cell.vue │ │ │ │ └── index.vue │ │ │ └── linear │ │ │ │ └── index.vue │ │ ├── installModule.ts │ │ ├── FormView.vue │ │ ├── FormPropsEditor.vue │ │ ├── FormDesigner.vue │ │ ├── helper │ │ │ ├── FormHelper.ts │ │ │ └── index.ts │ │ ├── widget │ │ │ └── OptionsEditor.vue │ │ └── ComponentPropsEditor.vue │ ├── App.vue │ └── VueComponents.ts ├── package.json ├── tsconfig.json └── webpack.config.ts ├── preview-react.png ├── preview-vue.png ├── .env ├── .env.dev ├── .env.prod ├── config ├── envs.ts ├── index.ejs └── webpack.base.config.ts ├── .gitignore ├── babel.config.js ├── tsconfig.json ├── package.json ├── LICENSE ├── .eslintrc.js └── README.md /antd-designer/src/designer/widgets/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/style/component.module.less: -------------------------------------------------------------------------------- 1 | @import "component.less"; 2 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [] 3 | } 4 | -------------------------------------------------------------------------------- /antdv-designer/src/style.less: -------------------------------------------------------------------------------- 1 | @import '~ant-design-vue/dist/antd.less'; 2 | -------------------------------------------------------------------------------- /preview-react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lumengwei/form-designer/HEAD/preview-react.png -------------------------------------------------------------------------------- /preview-vue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lumengwei/form-designer/HEAD/preview-vue.png -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | titleVue=Form Designer Vue 3 | titleReact=Form Designer React 4 | -------------------------------------------------------------------------------- /.env.dev: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | titleVue=Form Designer Vue 3 | titleReact=Form Designer React 4 | -------------------------------------------------------------------------------- /.env.prod: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | titleVue=Form Designer Vue 3 | titleReact=Form Designer React 4 | -------------------------------------------------------------------------------- /config/envs.ts: -------------------------------------------------------------------------------- 1 | enum envs { 2 | DEV = 'dev', 3 | PROD = 'prod' 4 | } 5 | 6 | export default envs; 7 | -------------------------------------------------------------------------------- /antd-designer/src/designer/layout/index.ts: -------------------------------------------------------------------------------- 1 | require('./LinearLayoutFactory') 2 | require('./ColumnLayoutFactory') 3 | 4 | -------------------------------------------------------------------------------- /antd-designer/src/lib/jquery.ts: -------------------------------------------------------------------------------- 1 | import $ from "jquery"; 2 | 3 | require('jquery-ui-dist/jquery-ui') 4 | 5 | export default $; 6 | -------------------------------------------------------------------------------- /antdv-designer/src/lib/jquery.ts: -------------------------------------------------------------------------------- 1 | import $ from "jquery"; 2 | 3 | require('jquery-ui-dist/jquery-ui') 4 | 5 | export default $; 6 | -------------------------------------------------------------------------------- /src/const/externals.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.module.less' { 2 | const classes: { readonly [key: string]: string } 3 | 4 | export default classes; 5 | } 6 | 7 | declare module '*.vue' 8 | -------------------------------------------------------------------------------- /config/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= htmlWebpackPlugin.options.title %> 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /antd-designer/src/designer/helper/index.ts: -------------------------------------------------------------------------------- 1 | import Renders from "./FactoryRenders"; 2 | import formHelper from "./FormHelper"; 3 | 4 | export const FactoryRenders = Renders; 5 | 6 | export const FormHelper = formHelper; 7 | -------------------------------------------------------------------------------- /antd-designer/src/designer/components/index.ts: -------------------------------------------------------------------------------- 1 | require('./InputFactory') 2 | require('./TextAreaFactory') 3 | require('./CheckboxFactory') 4 | require('./RadioFactory') 5 | require('./SelectFactory') 6 | require('./RateFactory') 7 | -------------------------------------------------------------------------------- /antdv-designer/src/main.ts: -------------------------------------------------------------------------------- 1 | import App from './App.vue' 2 | import {createApp} from 'vue' 3 | import envs from "../../config/envs"; 4 | 5 | require('./style.less') 6 | const app = createApp(App) 7 | 8 | const win: any = window; 9 | 10 | if (process.env.ENV_MODE == envs.DEV) { 11 | if ('__VUE_DEVTOOLS_GLOBAL_HOOK__' in win) { 12 | // @ts-ignore 13 | win['__VUE_DEVTOOLS_GLOBAL_HOOK__'].Vue = app; 14 | } 15 | } 16 | 17 | 18 | app.mount('body') 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | **/node_modules 5 | # roadhog-api-doc ignore 6 | /src/utils/request-temp.js 7 | _roadhog-api-doc 8 | 9 | # production 10 | **/dist 11 | /.vscode 12 | 13 | # misc 14 | .DS_Store 15 | npm-debug.log* 16 | yarn-error.log 17 | 18 | /coverage 19 | .idea 20 | yarn.lock 21 | package-lock.json 22 | *bak 23 | .vscode 24 | 25 | # visual studio code 26 | .history 27 | *.log 28 | 29 | functions/mock 30 | .temp/** 31 | 32 | # umi 33 | .umi 34 | .umi-production 35 | 36 | # screenshot 37 | screenshot 38 | .firebase 39 | -------------------------------------------------------------------------------- /antdv-designer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "antdv-designer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "cross-env ENV_MODE=dev npx webpack serve", 7 | "build": "cross-env ENV_MODE=prod npx webpack build" 8 | }, 9 | "main": "./src/main.ts", 10 | "dependencies": { 11 | "ant-design-vue": "3.2.17", 12 | "jquery": "^3.4.0", 13 | "jquery-ui-dist": "^1.12.1", 14 | "vue": "^3.2.47", 15 | "@ant-design/icons-vue": "^6.1.0" 16 | }, 17 | "devDependencies": { 18 | "vue-loader": "^17.0.1", 19 | "vue-template-compiler": "^2.7.14", 20 | "vue-style-loader": "^4.1.3", 21 | "cross-env": "^5.2.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/props.ts: -------------------------------------------------------------------------------- 1 | export type OptionType = { 2 | label: string; 3 | value: string; 4 | checked: boolean; 5 | disabled: boolean; 6 | } 7 | 8 | 9 | export type CheckboxProps = { 10 | options: OptionType[]; 11 | }; 12 | 13 | export type RadioProps = { 14 | options: OptionType[]; 15 | }; 16 | 17 | 18 | export type RateProps = { 19 | count: number 20 | } 21 | 22 | export type SelectProps = { 23 | placeholder: string; 24 | options: OptionType[]; 25 | } 26 | 27 | export type TextAreaProps = { 28 | placeholder: string; 29 | } 30 | 31 | export type LinearLayoutProps = { 32 | direction: 'row' | 'column' 33 | }; 34 | 35 | export type ColumnLayoutProps = { 36 | columnNum: number 37 | }; 38 | 39 | 40 | export type InputProps = { 41 | placeholder: string 42 | }; 43 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/components/rate/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /antd-designer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "antd-designer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "cross-env ENV_MODE=dev npx webpack serve", 7 | "build": "cross-env ENV_MODE=prod npx webpack build" 8 | }, 9 | "main": "./src/index.tsx", 10 | "dependencies": { 11 | "@ant-design/icons": "^5.0.1", 12 | "antd": "^5.3.1", 13 | "classnames": "^2.3.2", 14 | "jquery": "^3.4.0", 15 | "jquery-ui-dist": "^1.12.1", 16 | "react": "latest", 17 | "react-dom": "^18.2.0" 18 | }, 19 | "devDependencies": { 20 | "@types/create-react-class": "^15.6.3", 21 | "@types/react-dom": "^18.0.11", 22 | "@webpack-cli/serve": "^2.0.1", 23 | "create-react-class": "^15.7.0", 24 | "hoist-non-react-statics": "^3.3.2", 25 | "cross-env": "^5.2.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/components/input/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/wrapper/PropsEditorRender.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 31 | 32 | 35 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/components/textarea/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /src/style/component.less: -------------------------------------------------------------------------------- 1 | @border-color: #efefef; 2 | 3 | .widgetList { 4 | display: flex; 5 | flex-wrap: wrap; 6 | justify-content: space-around; 7 | width: 340px; 8 | background: white; 9 | } 10 | 11 | .widgetItem { 12 | width: 150px; 13 | height: 35px; 14 | margin: 6px 10px; 15 | padding: 5px 15px; 16 | background-color: #f5f5f5; 17 | border: 1px dashed #999; 18 | border-radius: 2px; 19 | cursor: move; 20 | } 21 | 22 | 23 | .optionsTools { 24 | margin-bottom: 5px; 25 | } 26 | 27 | .options { 28 | border-top: 1px solid @border-color; 29 | border-left: 1px solid @border-color; 30 | 31 | thead th, 32 | tbody td { 33 | padding: 5px; 34 | text-align: left; 35 | border-bottom: 1px solid @border-color; 36 | border-right: 1px solid @border-color; 37 | font-size: 12px; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/FormWidget.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 36 | 37 | 40 | -------------------------------------------------------------------------------- /antd-designer/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import type {Configuration} from "webpack"; 2 | import defaultConfig from '../config/webpack.base.config' 3 | 4 | const {merge} = require('webpack-merge'); 5 | const path = require('path') 6 | const config: Configuration = { 7 | entry: path.resolve(__dirname, './src/index.tsx'), 8 | output: { 9 | path: path.resolve(__dirname, "dist"), 10 | }, 11 | resolve: { 12 | alias: { 13 | '@': path.resolve(__dirname, 'src') 14 | } 15 | }, 16 | devServer: { 17 | static: { 18 | directory: path.join(__dirname, 'public'), 19 | }, 20 | compress: true, 21 | hot: true, 22 | port: 9002 23 | } 24 | }; 25 | 26 | const finalConfig = merge(defaultConfig, config); 27 | 28 | console.log(finalConfig) 29 | 30 | export default finalConfig; 31 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = api => { 2 | return { 3 | plugins: [ 4 | "@babel/plugin-transform-react-jsx", 5 | require("@babel/plugin-syntax-dynamic-import"), 6 | [require("@babel/plugin-proposal-decorators"), {"legacy": true}], 7 | [require("@babel/plugin-proposal-class-properties"), {"loose": false}] 8 | ], 9 | presets: [ 10 | [ 11 | "@babel/preset-env", 12 | { 13 | useBuiltIns: "entry", 14 | // caller.target 等于 webpack 配置的 target 选项 15 | targets: api.caller(caller => caller && caller.target === "node") 16 | ? {node: "current"} 17 | : {chrome: "58", ie: "11"} 18 | } 19 | ] 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /antd-designer/src/designer/helper/FactoryRenders.ts: -------------------------------------------------------------------------------- 1 | import {ComponentFactoryRender} from "../types"; 2 | 3 | class FactoryRenders { 4 | private _renders: Map>; 5 | 6 | constructor() { 7 | this._renders = new Map>(); 8 | } 9 | 10 | register(factory: ComponentFactoryRender, factoryType: string) { 11 | if (this._renders.has(factoryType)) { 12 | console.log(`[warn] factory ${factoryType} is exist, ignore`); 13 | return; 14 | } 15 | 16 | this._renders.set(factoryType, factory); 17 | } 18 | 19 | getRender(factoryType: string): ComponentFactoryRender { 20 | return this._renders.get(factoryType) as ComponentFactoryRender; 21 | } 22 | } 23 | 24 | export default new FactoryRenders(); 25 | -------------------------------------------------------------------------------- /src/factory/ColumnLayoutFactory.ts: -------------------------------------------------------------------------------- 1 | import {ComponentDefinition, ComponentFactory, FactoryGroup} from "@@/types"; 2 | import {ColumnLayoutProps} from "@@/props"; 3 | import {makeComponentId} from "@@/utils"; 4 | 5 | class ColumnLayoutFactory implements ComponentFactory { 6 | readonly type = "ColumnLayout" 7 | readonly group = FactoryGroup.Layout; 8 | title = "列布局" 9 | 10 | 11 | /** 12 | * 初始化一个组件定义 13 | * @returns {{type: string, title: string}} 14 | */ 15 | createComponentDefinition(): ComponentDefinition { 16 | return { 17 | id: makeComponentId(), 18 | type: this.type, 19 | title: this.title, 20 | props: { 21 | 'columnNum': 2 22 | }, 23 | children: [] 24 | } 25 | } 26 | } 27 | 28 | export default new ColumnLayoutFactory(); 29 | -------------------------------------------------------------------------------- /src/factory/LinearLayoutFactory.ts: -------------------------------------------------------------------------------- 1 | import {ComponentDefinition, ComponentFactory, FactoryGroup} from "@@/types"; 2 | import {LinearLayoutProps} from "@@/props"; 3 | import {makeComponentId} from "@@/utils"; 4 | 5 | class LinearLayoutFactory implements ComponentFactory { 6 | readonly type = "LinearLayout" 7 | readonly group = FactoryGroup.Layout; 8 | title = "流式布局" 9 | 10 | 11 | /** 12 | * 初始化一个组件定义 13 | * @returns {{type: string, title: string}} 14 | */ 15 | createComponentDefinition(): ComponentDefinition { 16 | return { 17 | id: makeComponentId(), 18 | type: this.type, 19 | title: this.title, 20 | props: { 21 | direction: 'column' 22 | }, 23 | children: [] 24 | } 25 | } 26 | } 27 | 28 | export default new LinearLayoutFactory(); 29 | -------------------------------------------------------------------------------- /src/factory/RateFactory.ts: -------------------------------------------------------------------------------- 1 | import {FactoryGroup, FieldFactory, FieldType} from "@@/types"; 2 | import {RateProps} from "@@/props"; 3 | import {makeComponentId, makeFieldId} from "@@/utils"; 4 | 5 | class RateFactory implements FieldFactory { 6 | readonly type = "Rate" 7 | readonly group = FactoryGroup.Component; 8 | title = "评分" 9 | 10 | /** 11 | * 初始化一个组件定义 12 | * @returns {{type: string, title: string}} 13 | */ 14 | createComponentDefinition() { 15 | return { 16 | id: makeComponentId(), 17 | type: this.type, 18 | title: this.title, 19 | fieldDef: { 20 | fieldId: makeFieldId(), 21 | fieldType: 'varchar' as FieldType, 22 | fieldName: '', 23 | }, 24 | props: { 25 | count: 5 26 | }, 27 | } 28 | } 29 | } 30 | 31 | export default new RateFactory(); 32 | -------------------------------------------------------------------------------- /antd-designer/src/designer/types.ts: -------------------------------------------------------------------------------- 1 | import type {ComponentDefinition} from "@@/types"; 2 | import {Component, ReactElement} from "react"; 3 | 4 | export interface ComponentFactoryRender { 5 | renderComponent(definition: ComponentDefinition): (props: P) => ReactElement; 6 | 7 | renderEditor(definition: ComponentDefinition): (props: P) => ReactElement; 8 | } 9 | 10 | export interface ComponentEditor

, T> extends Component

{ 11 | } 12 | 13 | export interface ReactComponentProps { 14 | definition: ComponentDefinition; 15 | 16 | onRemove(): void; 17 | } 18 | 19 | 20 | export interface ReactComponentState { 21 | renderCounter: number; 22 | } 23 | 24 | export interface ReactComponentGroupState extends ReactComponentState { 25 | definition: ComponentDefinition | null; 26 | } 27 | 28 | 29 | export interface Activatable { 30 | onActive(): void; 31 | 32 | unActive(): void; 33 | } 34 | -------------------------------------------------------------------------------- /src/factory/InputFactory.ts: -------------------------------------------------------------------------------- 1 | import {FactoryGroup, FieldFactory, FieldType} from "@@/types"; 2 | import {InputProps} from "@@/props"; 3 | import {makeComponentId, makeFieldId} from "@@/utils"; 4 | 5 | class InputFactory implements FieldFactory { 6 | readonly type = "Input" 7 | readonly group = FactoryGroup.Component; 8 | 9 | title = "单行输入框" 10 | 11 | /** 12 | * 初始化一个组件定义 13 | * @returns {{type: string, title: string}} 14 | */ 15 | createComponentDefinition() { 16 | return { 17 | id: makeComponentId(), 18 | type: this.type, 19 | title: this.title, 20 | fieldDef: { 21 | fieldId: makeFieldId(), 22 | fieldType: 'varchar' as FieldType, 23 | fieldName: '' 24 | }, 25 | props: { 26 | placeholder: '请输入' 27 | }, 28 | } 29 | } 30 | } 31 | 32 | export default new InputFactory(); 33 | -------------------------------------------------------------------------------- /antd-designer/src/util/MiscUtil.ts: -------------------------------------------------------------------------------- 1 | export function guaranteeNumber(value: number, min: number, max: number) { 2 | if (value === undefined || value == null) { 3 | return value; 4 | } 5 | if (value < min) { // 最小值 6 | return min; 7 | } 8 | 9 | if (value > max) {// 最大值 10 | return max; 11 | } 12 | return value; 13 | } 14 | 15 | export function isNull() { 16 | for (let i = 0; i < arguments.length; i++) { 17 | if (arguments[i] === undefined || arguments[i] == null) { 18 | return true; 19 | } 20 | } 21 | return false; 22 | } 23 | 24 | /** 25 | * 获取属性值并擦除属性 26 | * @param obj 27 | * @param prop 28 | * @returns {*} 29 | */ 30 | export function getErasure(obj: any, prop: string) { 31 | if (typeof obj !== 'object') { 32 | throw new Error(`not expected type:${typeof obj}, must be object`); 33 | } 34 | const value = obj[prop]; 35 | delete obj[prop]; 36 | return value; 37 | } 38 | -------------------------------------------------------------------------------- /src/factory/TextAreaFactory.ts: -------------------------------------------------------------------------------- 1 | import {FactoryGroup, FieldFactory, FieldType} from "@@/types"; 2 | import {TextAreaProps} from "@@/props"; 3 | import {makeComponentId, makeFieldId} from "@@/utils"; 4 | 5 | class TextAreaFactory implements FieldFactory { 6 | readonly type = "TextArea" 7 | readonly group = FactoryGroup.Component; 8 | title = "多行输入框" 9 | 10 | 11 | /** 12 | * 初始化一个组件定义 13 | * @returns {{type: string, title: string}} 14 | */ 15 | createComponentDefinition() { 16 | return { 17 | id: makeComponentId(), 18 | type: this.type, 19 | title: this.title, 20 | fieldDef: { 21 | fieldId: makeFieldId(), 22 | fieldType: 'varchar' as FieldType, 23 | fieldName: '', 24 | }, 25 | props: { 26 | placeholder: '请输入' 27 | }, 28 | } 29 | } 30 | } 31 | 32 | export default new TextAreaFactory(); 33 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "ts-node": { 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | }, 7 | "compilerOptions": { 8 | "sourceMap": true, 9 | // "outDir": "./dist", 10 | "target": "es6", 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "esModuleInterop": true, 14 | "allowSyntheticDefaultImports": true, 15 | "allowJs": true, 16 | "jsx": "react", 17 | "strict": true, 18 | "skipLibCheck": true, 19 | "stripInternal": true, 20 | "experimentalDecorators": true, 21 | "noImplicitAny": false, 22 | "lib": [ 23 | "dom", 24 | "es2017" 25 | ], 26 | "baseUrl": ".", 27 | "paths": { 28 | "@@/*": [ 29 | "./src/*" 30 | ] 31 | } 32 | }, 33 | "path": [], 34 | "exclude": [ 35 | "node_modules", 36 | "dist", 37 | "**/*.js" 38 | ], 39 | "include": [ 40 | "./src/**/*.d.ts", 41 | "./src/**/*.ts", 42 | "./src/**/*.tsx", 43 | "./src/**/*.vue" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/components/input/PropEditor.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 39 | 40 | 43 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/types.ts: -------------------------------------------------------------------------------- 1 | import {ComponentDefinition} from "@@/types"; 2 | import {ComponentInternalInstance} from "@vue/runtime-core"; 3 | 4 | export interface ComponentFactoryRender { 5 | renderComponent(definition: ComponentDefinition): (props: P) => ComponentInternalInstance; 6 | 7 | renderEditor(definition: ComponentDefinition): (props: P) => ComponentInternalInstance; 8 | } 9 | 10 | export interface ComponentEditor

, T> extends ComponentInternalInstance { 11 | } 12 | 13 | export interface VueComponentProps { 14 | definition: ComponentDefinition; 15 | 16 | onRemove?: () => void; 17 | } 18 | 19 | 20 | export interface ReactComponentState { 21 | renderCounter: number; 22 | } 23 | 24 | export interface ReactComponentGroupState extends ComponentInternalInstance { 25 | definition: ComponentDefinition | null; 26 | } 27 | 28 | 29 | export interface Activatable { 30 | onActive(): void; 31 | 32 | unActive(): void; 33 | } 34 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/components/textarea/PropEditor.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 39 | 40 | 43 | -------------------------------------------------------------------------------- /antd-designer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": false, 5 | "sourceMap": true, 6 | // "outDir": "./dist", 7 | "target": "es6", 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "esModuleInterop": true, 11 | "noImplicitOverride": true, 12 | "allowSyntheticDefaultImports": true, 13 | "allowJs": true, 14 | "jsx": "react", 15 | "strict": true, 16 | "skipLibCheck": true, 17 | "stripInternal": true, 18 | "experimentalDecorators": true, 19 | "noImplicitAny": false, 20 | "lib": [ 21 | "dom", 22 | "es2017" 23 | ], 24 | "baseUrl": ".", 25 | "paths": { 26 | "@@/*": [ 27 | "../src/*" 28 | ], 29 | "@/*": [ 30 | "./src/*" 31 | ] 32 | } 33 | }, 34 | "exclude": [ 35 | "./node_modules" 36 | ], 37 | "include": [ 38 | "./**/*.d.ts", 39 | "./**/*.ts", 40 | "./**/*.tsx", 41 | "../src/**/*.ts", 42 | "../src/**/*.d.ts" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/components/radio/PropEditor.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 40 | 41 | 44 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/components/select/PropEditor.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 40 | 41 | 44 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/components/checkbox/PropEditor.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 40 | 41 | 44 | -------------------------------------------------------------------------------- /antdv-designer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": false, 5 | "sourceMap": true, 6 | // "outDir": "./dist", 7 | "target": "es6", 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "esModuleInterop": true, 11 | "noImplicitOverride": true, 12 | "allowSyntheticDefaultImports": true, 13 | "allowJs": true, 14 | "jsx": "react", 15 | "strict": true, 16 | "skipLibCheck": true, 17 | "stripInternal": true, 18 | "experimentalDecorators": true, 19 | "noImplicitAny": false, 20 | "lib": [ 21 | "dom", 22 | "es2017" 23 | ], 24 | "baseUrl": ".", 25 | "paths": { 26 | "@@/*": [ 27 | "../src/*" 28 | ], 29 | "@/*": [ 30 | "./src/*" 31 | ] 32 | } 33 | }, 34 | "exclude": [ 35 | "./node_modules" 36 | ], 37 | "include": [ 38 | "./src/**/*.d.ts", 39 | "./src/**/*.ts", 40 | "./src/**/*.tsx", 41 | "./src/**/*.vue", 42 | "../src/**/*.ts", 43 | "../src/**/*.d.ts" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/components/rate/PropEditor.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 42 | 43 | 46 | -------------------------------------------------------------------------------- /src/factory/SelectFactory.ts: -------------------------------------------------------------------------------- 1 | import {FactoryGroup, FieldFactory, FieldType} from "@@/types"; 2 | import {SelectProps} from "@@/props"; 3 | import {makeComponentId, makeFieldId} from "@@/utils"; 4 | 5 | class SelectFactory implements FieldFactory { 6 | readonly type = "Select" 7 | readonly group = FactoryGroup.Component; 8 | title = "下拉选择" 9 | 10 | /** 11 | * 初始化一个组件定义 12 | * @returns {{type: string, title: string}} 13 | */ 14 | createComponentDefinition() { 15 | return { 16 | id: makeComponentId(), 17 | type: this.type, 18 | title: this.title, 19 | fieldDef: { 20 | fieldId: makeFieldId(), 21 | fieldType: '' as FieldType, 22 | fieldName: '', 23 | }, 24 | props: { 25 | placeholder: '请输入', 26 | options: [ 27 | {label: '显示值', value: '真值', checked: false, disabled: false} 28 | ] 29 | }, 30 | } 31 | } 32 | } 33 | 34 | export default new SelectFactory(); 35 | -------------------------------------------------------------------------------- /src/factory/RadioFactory.ts: -------------------------------------------------------------------------------- 1 | import {FactoryGroup, FieldFactory, FieldType} from "@@/types"; 2 | import {RadioProps} from "@@/props"; 3 | import {makeComponentId, makeFieldId} from "@@/utils"; 4 | 5 | class RadioFactory implements FieldFactory { 6 | readonly type = "Radio" 7 | readonly group = FactoryGroup.Component; 8 | 9 | title = "单选框" 10 | 11 | /** 12 | * 初始化一个组件定义 13 | * @returns {{type: string, title: string}} 14 | */ 15 | createComponentDefinition() { 16 | return { 17 | id: makeComponentId(), 18 | type: this.type, 19 | title: this.title, 20 | fieldDef: { 21 | fieldId: makeFieldId(), 22 | fieldType: 'varchar' as FieldType, 23 | fieldName: '', 24 | }, 25 | props: { 26 | placeholder: '请输入', 27 | options: [ 28 | {label: '显示值', value: '真值', checked: false, disabled: false} 29 | ] 30 | }, 31 | } 32 | } 33 | } 34 | 35 | export default new RadioFactory(); 36 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/layout/column/PropEditor.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 42 | 43 | 46 | -------------------------------------------------------------------------------- /antd-designer/src/designer/components/InputFactory.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Input} from 'antd'; 3 | import {ComponentWrapper, FactoryRegister} from '../wrapper'; 4 | import {ReactComponent} from '../ReactComponent'; 5 | import {PropsEditor} from '../widgets/PropsEditor'; 6 | import {ReactComponentProps, ReactComponentState} from "../types"; 7 | import {InputProps} from "@@/props"; 8 | import InputFactory from "@@/factory/InputFactory"; 9 | 10 | /** 11 | * 组件 12 | */ 13 | @ComponentWrapper 14 | class InputComponent extends ReactComponent, InputProps, ReactComponentState> { 15 | 16 | override render() { 17 | const {definition: {props}} = this.props; 18 | return ( 19 | 20 | ) 21 | } 22 | } 23 | 24 | /** 25 | * 组件属性编辑器 26 | */ 27 | class InputComponentEditor extends PropsEditor { 28 | doRender() { 29 | return ( 30 | <> 31 | ); 32 | } 33 | } 34 | 35 | /** 36 | * 组件工厂 37 | */ 38 | FactoryRegister(InputComponent, InputComponentEditor)(InputFactory) 39 | 40 | -------------------------------------------------------------------------------- /src/factory/CheckboxFactory.ts: -------------------------------------------------------------------------------- 1 | import {FactoryGroup, FieldFactory, FieldType} from "@@/types"; 2 | import {CheckboxProps} from "@@/props"; 3 | import {makeComponentId, makeFieldId} from "@@/utils"; 4 | 5 | class CheckboxFactory implements FieldFactory { 6 | readonly type = "Checkbox" 7 | 8 | readonly group = FactoryGroup.Component; 9 | 10 | title = "多选框" 11 | 12 | /** 13 | * 初始化一个组件定义 14 | * @returns {{type: string, title: string}} 15 | */ 16 | createComponentDefinition() { 17 | return { 18 | id: makeComponentId(), 19 | type: this.type, 20 | title: this.title, 21 | fieldDef: { 22 | fieldId: makeFieldId(), 23 | fieldType: "array" as FieldType, 24 | fieldName: '', 25 | }, 26 | props: { 27 | placeholder: '请输入', 28 | options: [ 29 | {label: '显示值', value: '真值', checked: false, disabled: false} 30 | ] 31 | }, 32 | } 33 | } 34 | } 35 | 36 | export default new CheckboxFactory(); 37 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/components/radio/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 40 | 41 | 43 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/layout/column/Cell.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 42 | 43 | 46 | -------------------------------------------------------------------------------- /antd-designer/src/designer/components/TextAreaFactory.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Input} from 'antd'; 3 | import {ComponentWrapper, FactoryRegister} from '../wrapper'; 4 | import {ReactComponent} from '../ReactComponent'; 5 | import {PropsEditor} from '../widgets/PropsEditor'; 6 | import {ReactComponentProps, ReactComponentState} from "../types"; 7 | import {TextAreaProps} from "../../../../src/props"; 8 | import TextAreaFactory from "@@/factory/TextAreaFactory"; 9 | 10 | const TexArea = Input.TextArea; 11 | 12 | @ComponentWrapper 13 | class TextAreaComponent extends ReactComponent, TextAreaProps, ReactComponentState> { 14 | 15 | override render() { 16 | const {definition: {props}} = this.props; 17 | return ( 18 | 19 | ) 20 | } 21 | } 22 | 23 | class TextAreaComponentEditor extends PropsEditor { 24 | doRender() { 25 | return ( 26 | <> 27 | 28 | ); 29 | } 30 | } 31 | 32 | /** 33 | * 注册 34 | */ 35 | FactoryRegister(TextAreaComponent, TextAreaComponentEditor)(TextAreaFactory) 36 | 37 | -------------------------------------------------------------------------------- /antd-designer/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {PureComponent} from 'react'; 2 | import {Button, Layout} from 'antd'; 3 | import {createRoot} from 'react-dom/client'; 4 | import FormDesigner from './designer/FormDesigner'; 5 | import FormStudio from "../../src/FormStudio"; 6 | 7 | require('@@/style/formView.less') 8 | const {Header} = Layout; 9 | 10 | class App extends PureComponent { 11 | getJsonData() { 12 | alert(JSON.stringify(FormStudio.getJsonData())); 13 | } 14 | 15 | override render() { 16 | return ( 17 | 18 |

24 | 25 |
26 |
27 | 28 |
29 | 30 | ); 31 | } 32 | } 33 | 34 | const domNode = createRoot(document.getElementsByTagName('body')[0]) 35 | domNode.render() 36 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/installModule.ts: -------------------------------------------------------------------------------- 1 | import {FormHelper} from '@/designer/helper'; 2 | import FormStudio from "@@/FormStudio"; 3 | 4 | function installFactory(modules: __WebpackModuleApi.RequireContext) { 5 | modules.keys().forEach((key) => { 6 | const mod = modules(key); 7 | FormStudio.registerFactory(mod.default); 8 | }); 9 | } 10 | 11 | function installDef(modules: __WebpackModuleApi.RequireContext) { 12 | modules.keys().forEach((key) => { 13 | const mod = modules(key); 14 | FormHelper.registerComponent(mod.default); 15 | }); 16 | } 17 | 18 | function installEditor(modules: __WebpackModuleApi.RequireContext) { 19 | modules.keys().forEach((key) => { 20 | const mod = modules(key); 21 | FormHelper.registerEditor(mod.default); 22 | }); 23 | } 24 | 25 | 26 | installFactory(require.context('@@/factory', true, /Factory.ts/)) 27 | installDef(require.context('./components', true, /index.vue/)) 28 | installDef(require.context('./layout', true, /index.vue/)) 29 | installEditor(require.context('./components', true, /PropEditor.vue/)) 30 | installEditor(require.context('./layout', true, /PropEditor.vue/)) 31 | 32 | export default function install(modules: string[]) { 33 | console.log(modules); 34 | } 35 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/components/select/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 44 | 45 | 47 | -------------------------------------------------------------------------------- /antdv-designer/src/App.vue: -------------------------------------------------------------------------------- 1 | 11 | 39 | 51 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/wrapper/ComponentWrapper.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 52 | 53 | 56 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/components/checkbox/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 45 | 46 | 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "form-designer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "npx webpack serve" 7 | }, 8 | "dependencies": { 9 | }, 10 | "devDependencies": { 11 | "@babel/core": "^7.4.3", 12 | "@babel/plugin-proposal-class-properties": "^7.4.0", 13 | "@babel/plugin-proposal-decorators": "^7.4.0", 14 | "@babel/plugin-syntax-dynamic-import": "^7.2.0", 15 | "@babel/plugin-transform-react-jsx": "^7.3.0", 16 | "@babel/preset-env": "^7.4.3", 17 | "@types/jquery": "^3.3.6", 18 | "@types/jqueryui": "^1.12.3", 19 | "@types/node": "^18.15.3", 20 | "@types/webpack": "^5.28.0", 21 | "@types/webpack-env": "^1.18.0", 22 | "babel-loader": "^8.0.5", 23 | "css-loader": "^6.7.3", 24 | "html-webpack-plugin": "^5.5.0", 25 | "less": "^3.9.0", 26 | "less-loader": "^11.1.0", 27 | "mini-css-extract-plugin": "^2.7.3", 28 | "postcss-loader": "^3.0.0", 29 | "style-loader": "^0.23.1", 30 | "ts-node": "^10.8.2", 31 | "ts-loader": "^9.4.2", 32 | "typescript": "~4.9.3", 33 | "webpack": "^5.76.1", 34 | "@webpack-cli/serve": "^2.0.1", 35 | "webpack-dev-server": "^4.11.1", 36 | "webpack-cli": "^5.0.1", 37 | "eslint": "^8.37.0", 38 | "eslint-plugin-import": "^2.27.5", 39 | "webpack-merge": "^5.8.0", 40 | "dotenv": "^16.0.3", 41 | "cross-env": "^5.2.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /antd-designer/src/designer/components/CheckboxFactory.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Checkbox} from 'antd'; 3 | import {ReactComponent} from '../ReactComponent'; 4 | import {ComponentWrapper, FactoryRegister} from '../wrapper'; 5 | import {CheckboxProps} from "../../../../src/props"; 6 | import {ReactComponentProps, ReactComponentState} from "../types"; 7 | import {OptionGroupEditor} from "../widgets/OptionGroupEditor"; 8 | import CheckboxFactory from "@@/factory/CheckboxFactory"; 9 | 10 | const CheckboxGroup = Checkbox.Group; 11 | 12 | @ComponentWrapper 13 | class CheckboxComponent extends ReactComponent, CheckboxProps, ReactComponentState> { 14 | 15 | override render() { 16 | const definition = this.props.definition; 17 | const props: CheckboxProps = definition.props!; 18 | const defaultValue = props.options.filter(item => { 19 | return item.checked; 20 | }).map(item => { 21 | return item.value; 22 | }); 23 | 24 | return ( 25 | 26 | ) 27 | } 28 | } 29 | 30 | class CheckboxComponentEditor extends OptionGroupEditor { 31 | 32 | doRender() { 33 | return ( 34 | <> 35 | {this.renderOptions()} 36 | 37 | ); 38 | } 39 | } 40 | 41 | FactoryRegister(CheckboxComponent, CheckboxComponentEditor)(CheckboxFactory) 42 | -------------------------------------------------------------------------------- /antdv-designer/src/VueComponents.ts: -------------------------------------------------------------------------------- 1 | import type {ComponentDefinition} from "@@/types"; 2 | import {ComponentInternalInstance, DefineComponent} from "@vue/runtime-core"; 3 | import {ComponentPublicInstance} from "vue"; 4 | 5 | 6 | export type VueComponents = { 7 | 8 | /** 9 | * shouldComponentUpdate 默认不是对象的深层比较,采用标志位的方式 10 | * 此方法代替foreUpdate 11 | */ 12 | forceRender(): void; 13 | } & ComponentPublicInstance; 14 | 15 | 16 | export interface ComponentGroupMethods { 17 | 18 | /** 19 | * 添加子组件 20 | * @param index 21 | * @param componentDefinition 22 | */ 23 | addChildBySlot(slot: number, componentDefinition: ComponentDefinition): void; 24 | 25 | /** 26 | * 移除子组件 27 | * @param index 28 | */ 29 | removeChildBySlot(slot: number): void; 30 | 31 | /** 32 | * 移除子组件 33 | * @param index 34 | */ 35 | removeChild(index: number): void; 36 | 37 | addChild(index: number, componentDefinition: ComponentDefinition): void; 38 | 39 | changeIndex(source: number, target: number): void; 40 | 41 | /** 42 | * 获取插槽上的组件 43 | * @param index 44 | * @param componentDefinition 45 | */ 46 | getChildBySlot(slot: number): ComponentDefinition | null; 47 | 48 | forceRender(): void; 49 | 50 | getComponent(type: string): DefineComponent; 51 | 52 | } 53 | 54 | export type ComponentGroup = {} & VueComponents & ComponentGroupMethods; 55 | 56 | export type Layout = ComponentGroup; 57 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/FormView.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 52 | 53 | 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, lumengwei 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /antdv-designer/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import {Configuration, DefinePlugin} from "webpack"; 2 | import defaultConfig from '../config/webpack.base.config' 3 | 4 | const {merge} = require('webpack-merge'); 5 | 6 | const {VueLoaderPlugin} = require('vue-loader'); 7 | const path = require('path'); 8 | 9 | const config: Configuration = { 10 | entry: './src/main.ts', 11 | output: { 12 | path: path.resolve(__dirname, 'dist'), 13 | filename: 'foo.bundle.js', 14 | }, 15 | // stats: 'verbose', 16 | stats: { 17 | // warnings: false, 18 | logging: 'error', 19 | errorDetails: true 20 | }, 21 | resolve: { 22 | alias: { 23 | '@': path.resolve(__dirname, 'src') 24 | } 25 | }, 26 | module: { 27 | parser: { 28 | javascript: { 29 | exportsPresence: 'auto', 30 | }, 31 | }, 32 | rules: [ 33 | { 34 | test: /\.vue$/, 35 | use: [{ 36 | loader: 'vue-loader', 37 | options: { 38 | reactivityTransform: false 39 | } 40 | }], 41 | 42 | } 43 | ] 44 | }, 45 | plugins: [ 46 | new VueLoaderPlugin(), 47 | new DefinePlugin({ 48 | __VUE_PROD_DEVTOOLS__: process.env.NODE_ENV == 'development', 49 | __VUE_OPTIONS_API__: process.env.NODE_ENV == 'development', 50 | }), 51 | ] 52 | }; 53 | 54 | const finalConfig = merge(defaultConfig, config); 55 | 56 | console.log(finalConfig) 57 | export default finalConfig; 58 | -------------------------------------------------------------------------------- /antd-designer/src/designer/helper/FormHelper.ts: -------------------------------------------------------------------------------- 1 | import {Activatable, ReactComponentProps} from "../types"; 2 | import ComponentPropsEditor from "../ComponentPropsEditor"; 3 | import {ComponentFactory} from "../../../../src/types"; 4 | import {ComponentSpec} from "react"; 5 | 6 | class FormHelper { 7 | private _activeComponentIns: Activatable & ComponentSpec, any> | null = null; 8 | private _formEditorIns: ComponentPropsEditor | null = null; 9 | private _componentFactory: ComponentFactory | null = null; 10 | private _formView: ComponentSpec | null = null; 11 | 12 | get activeComponentIns() { 13 | return this._activeComponentIns; 14 | } 15 | 16 | set activeComponentIns(inst) { 17 | this._activeComponentIns?.unActive(); 18 | this._activeComponentIns = inst; 19 | this._activeComponentIns?.onActive(); 20 | if (inst?.props) { 21 | this._formEditorIns?.setDefinition(inst?.props.definition) 22 | } else { 23 | this._formEditorIns?.setDefinition(null) 24 | } 25 | } 26 | 27 | get formEditorIns() { 28 | return this._formEditorIns; 29 | } 30 | 31 | set formEditorIns(inst) { 32 | this._formEditorIns = inst; 33 | } 34 | 35 | 36 | get componentFactory() { 37 | return this._componentFactory; 38 | } 39 | 40 | set componentFactory(inst) { 41 | this._componentFactory = inst; 42 | } 43 | 44 | get formView() { 45 | return this._formView; 46 | } 47 | 48 | set formView(inst) { 49 | this._formView = inst; 50 | } 51 | 52 | } 53 | 54 | export default new FormHelper(); 55 | -------------------------------------------------------------------------------- /antd-designer/src/designer/components/RadioFactory.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Radio,} from 'antd'; 3 | import {ComponentWrapper, FactoryRegister} from '../wrapper'; 4 | import {ReactComponent} from '../ReactComponent'; 5 | import {RadioProps} from "../../../../src/props"; 6 | import {ReactComponentProps, ReactComponentState} from "../types"; 7 | import {FactoryGroup, FieldFactory, FieldType} from "../../../../src/types"; 8 | import {makeComponentId, makeFieldId} from "../../../../src/utils"; 9 | import {OptionGroupEditor} from "../widgets/OptionGroupEditor"; 10 | import RadioFactory from "@@/factory/RadioFactory"; 11 | 12 | 13 | @ComponentWrapper 14 | class RadioComponent extends ReactComponent, RadioProps, ReactComponentState> { 15 | 16 | override render() { 17 | const definition = this.props.definition; 18 | const props: RadioProps = definition.props!; 19 | const defaultValue = props.options.filter(item => { 20 | return item.checked; 21 | }).map(item => { 22 | return item.value; 23 | })[0] || null; 24 | 25 | const opts = props.options.map(it => { 26 | return ({it.label}) 27 | }) 28 | return ( 29 | 30 | {opts} 31 | 32 | ) 33 | } 34 | } 35 | 36 | class RadioComponentEditor extends OptionGroupEditor { 37 | 38 | doRender() { 39 | return ( 40 | <> 41 | {this.renderOptions()} 42 | 43 | ); 44 | } 45 | } 46 | 47 | FactoryRegister(RadioComponent, RadioComponentEditor)(RadioFactory) 48 | 49 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/layout/column/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 53 | 54 | 72 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | extends: [ 4 | 'airbnb', 5 | 'prettier', 6 | 'plugin:compat/recommended', 7 | 'plugin:vue/vue3-recommended', 8 | 'eslint:recommended' 9 | ], 10 | plugins: ['@typescript-eslint', 'import'], 11 | env: { 12 | browser: true, 13 | node: true, 14 | es6: true, 15 | mocha: true, 16 | jest: true, 17 | jasmine: true, 18 | }, 19 | globals: { 20 | APP_TYPE: true, 21 | page: true, 22 | }, 23 | rules: { 24 | 'react/jsx-filename-extension': [1, {extensions: ['.js']}], 25 | 'react/jsx-wrap-multilines': 0, 26 | 'react/prop-types': 0, 27 | 'react/forbid-prop-types': 0, 28 | 'react/jsx-one-expression-per-line': 0, 29 | 'import/no-unresolved': [2, {ignore: ['^@/', '^@@/']}], 30 | 'import/no-extraneous-dependencies': [ 31 | 2, 32 | { 33 | optionalDependencies: true, 34 | devDependencies: ['**/tests/**.js', '/mock/**/**.js', '**/**.test.js'], 35 | }, 36 | ], 37 | 'jsx-a11y/no-noninteractive-element-interactions': 0, 38 | 'jsx-a11y/click-events-have-key-events': 0, 39 | 'jsx-a11y/no-static-element-interactions': 0, 40 | 'jsx-a11y/anchor-is-valid': 0, 41 | 'linebreak-style': 0, 42 | }, 43 | settings: { 44 | polyfills: ['fetch', 'promises', 'url', 'object-assign'], 45 | "import/resolver": { 46 | alias: { 47 | map: [ 48 | ['@/', './src/'] 49 | ], 50 | extensions: ['.ts', '.js', '.jsx', '.vue', '.tsx'] 51 | }, 52 | }, 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /antd-designer/src/designer/ComponentPropsEditor.tsx: -------------------------------------------------------------------------------- 1 | import React, {PureComponent} from 'react' 2 | import {FactoryRenders, FormHelper} from "./helper"; 3 | import {ReactComponentGroupState} from "./types"; 4 | import {ComponentDefinition} from "../../../src/types"; 5 | 6 | require('../../../src/style/formView.less'); 7 | 8 | /** 9 | * 属性编辑器 10 | */ 11 | class ComponentPropsEditor extends PureComponent<{}, ReactComponentGroupState> { 12 | constructor(props: {}, context: any) { 13 | super(props, context); 14 | this.state = { 15 | definition: null, 16 | renderCounter: 0 17 | } 18 | } 19 | 20 | override componentDidMount() { 21 | FormHelper.formEditorIns = this; 22 | } 23 | 24 | 25 | setDefinition(definition: ComponentDefinition | null) { 26 | this.setState({ 27 | definition 28 | }) 29 | } 30 | 31 | onValuesChange(props: any, values: any, _: any) { 32 | /* setTimeout(() => {// validateFields 的调用放在最后 33 | props.form.validateFields((err, allValues) => { 34 | if (err) return; 35 | 36 | const next = this.componentEditor.onChange(values, allValues); 37 | 38 | if (this.componentIns && next !== false) { 39 | this.componentIns.forceRender(); 40 | } 41 | }) 42 | })*/ 43 | } 44 | 45 | 46 | renderProps() { 47 | const {definition} = this.state; 48 | if (definition) { 49 | return FactoryRenders.getRender(definition.type).renderEditor(definition)({ 50 | onValuesChange: this.onValuesChange.bind(this), 51 | }); 52 | } 53 | } 54 | 55 | override render() { 56 | return ( 57 |
58 | {this.renderProps()} 59 |
60 | ); 61 | } 62 | } 63 | 64 | export default ComponentPropsEditor; 65 | -------------------------------------------------------------------------------- /antd-designer/src/designer/components/RateFactory.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Form, InputNumber, Rate} from 'antd'; 3 | import {ComponentWrapper, FactoryRegister} from '../wrapper'; 4 | import {ReactComponent} from '../ReactComponent'; 5 | import {PropsEditor} from '../widgets/PropsEditor'; 6 | import {RateProps} from "../../../../src/props"; 7 | import {FactoryGroup, FieldFactory, FieldType} from "../../../../src/types"; 8 | import {ReactComponentProps, ReactComponentState} from "../types"; 9 | import {makeComponentId, makeFieldId} from "../../../../src/utils"; 10 | import RateFactory from "@@/factory/RateFactory"; 11 | 12 | @ComponentWrapper 13 | class RateComponent extends ReactComponent, RateProps, ReactComponentState> { 14 | 15 | override render() { 16 | const {definition: {props}} = this.props; 17 | return ( 18 | <> 19 | 22 | 23 | 24 | ) 25 | } 26 | } 27 | 28 | class RateComponentEditor extends PropsEditor { 29 | doRender() { 30 | const definition = this.props.definition; 31 | const props: RateProps = definition.props!; 32 | return ( 33 | 46 | 47 | 48 | ); 49 | } 50 | } 51 | 52 | FactoryRegister(RateComponent, RateComponentEditor)(RateFactory) 53 | 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Form Designer

2 | 3 |
4 | 5 | 基于Ant Design 6 | 和 jQuery UI 的表单设计器 7 | 8 | ![](https://github.com/lumengwei/form-designer/blob/master/preview-react.png) 9 | 10 |
11 | 12 | ### 特性 13 | 14 | - [x] React 15 | - [x] Vue 3.x 16 | - [x] Typescript 17 | - [x] 统一的组件定义,对Vue 和React 的实现提供一个统一的组件定义描述 18 | 19 | ### 概念 20 | 21 | - Component 组件 22 | - Layout 布局,一种特殊的Component 23 | - Component Editor 组件属性编辑器 24 | - Component Factory 提供组件定义 25 | 26 | ### 扩展组件 27 | 28 | #### 定义组件属性类型 29 | 30 | 在src/props.ts 文件中定义组件属性 31 | 32 | ``` 33 | export type RateProps = { 34 | count: number 35 | } 36 | 37 | ``` 38 | 39 | #### 定义一个ComponentFactory 40 | 41 | 在src/factory目录下定义。 提供了makeFieldId()和makeComponentId()两个方法用于生成id 42 | 43 | ``` 44 | class RateFactory implements FieldFactory { 45 | readonly type = "Rate" 46 | readonly group = FactoryGroup.Component; 47 | title = "评分" 48 | 49 | /** 50 | * 初始化一个组件定义 51 | * @returns {{type: string, title: string}} 52 | */ 53 | createComponentDefinition() { 54 | return { 55 | id: makeComponentId(), 56 | type: this.type, 57 | title: this.title, 58 | fieldDef: { 59 | fieldId: makeFieldId(), 60 | fieldType: 'varchar' as FieldType, 61 | fieldName: '', 62 | }, 63 | props: { 64 | count: 5 65 | }, 66 | } 67 | } 68 | } 69 | 70 | export default new RateFactory(); 71 | ``` 72 | #### 创建一个vue组件或布局 73 | 74 | - 在src/antdv-designer/src/designer/components目录下添加vue组件 75 | - 在src/antdv-designer/src/designer/layout目录下添加vue布局 76 | 77 | 78 | #### 创建一个React组件或布局 79 | 80 | - 在src/antd-designer/src/designer/components目录下添加React组件 81 | - 在src/antd-designer/src/designer/layout目录下添加React布局 82 | 83 | 84 | -------------------------------------------------------------------------------- /antd-designer/src/designer/wrapper/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {ComponentClass} from 'react'; 2 | import {ComponentDefinition, ComponentFactory, ComponentFactoryConstructor} from "../../../../src/types"; 3 | import FormStudio from "../../../../src/FormStudio"; 4 | import FactoryRenders from "../helper/FactoryRenders"; 5 | import {ComponentFactoryRender, ReactComponentProps} from "../types"; 6 | 7 | 8 | /** 9 | * 注册ComponentFactory 10 | * @param Factory 11 | * @constructor 12 | */ 13 | export function FactoryRegister

, T>(Component: ComponentClass

14 | , ComponentEditor?: ComponentClass>) { 15 | 16 | return function FactoryWrapper(factory: ComponentFactory) { 17 | const render: ComponentFactoryRender = { 18 | renderComponent(componentDefinition: ComponentDefinition) { 19 | 20 | return function (props: any) { 21 | return React.createElement(Component, { 22 | ...props, 23 | definition: componentDefinition 24 | }) 25 | } 26 | }, 27 | renderEditor(componentDefinition: ComponentDefinition) { 28 | return function (props: any) { 29 | if (ComponentEditor) { 30 | return React.createElement(ComponentEditor, { 31 | ...props, 32 | definition: componentDefinition 33 | }); 34 | } else { 35 | return (<>); 36 | } 37 | } 38 | } 39 | } 40 | 41 | //const factory: ComponentFactory = new Factory(); 42 | FactoryRenders.register(render, factory.type) 43 | FormStudio.registerFactory(factory) 44 | } 45 | } 46 | 47 | export const LayoutWrapper = require('./LayoutWrapper').default; 48 | export const ComponentWrapper = require('./ComponentWrapper').default; 49 | -------------------------------------------------------------------------------- /src/FormStudio.ts: -------------------------------------------------------------------------------- 1 | import {ComponentDefinition, ComponentFactory, FactoryGroup, FormDefinition} from './types'; 2 | 3 | 4 | class FormStudio { 5 | private factoryList: ComponentFactory[] = []; 6 | private factoryStore = new Map>(); 7 | private _definition: ComponentDefinition | null = null; 8 | private _formDef: FormDefinition = { 9 | title: '', 10 | description: '', 11 | width: '' 12 | }; 13 | 14 | 15 | /** 16 | * 注册组件工厂 17 | * @param factory 18 | */ 19 | registerFactory(factory: ComponentFactory) { 20 | if (this.factoryStore.has(factory.type)) { 21 | console.log(`[warn] factory ${factory.type} is exist, ignore`); 22 | return; 23 | } 24 | 25 | this.factoryStore.set(factory.type, factory); 26 | this.factoryList.push(factory); 27 | 28 | if (factory.type == 'LinearLayout') { 29 | this._definition = factory.createComponentDefinition() as ComponentDefinition; 30 | } 31 | } 32 | 33 | getFactoryList(group: FactoryGroup): ComponentFactory[] { 34 | return this.factoryList.filter(it => it.group == group) 35 | } 36 | 37 | /** 38 | * 根据类型获取工厂 39 | * @param factoryType 40 | * @returns {*} 41 | */ 42 | getFactory(factoryType: string): ComponentFactory { 43 | return this.factoryStore.get(factoryType) as ComponentFactory; 44 | } 45 | 46 | factoryFilter(filter: (it: ComponentFactory) => boolean) { 47 | return this.factoryList.filter(filter); 48 | } 49 | 50 | get definition() { 51 | return this._definition; 52 | } 53 | 54 | get formDef() { 55 | return this._formDef; 56 | } 57 | 58 | 59 | getJsonData() { 60 | return { 61 | props: JSON.parse(JSON.stringify(this._definition)), 62 | form: JSON.parse(JSON.stringify(this._formDef)), 63 | }; 64 | } 65 | } 66 | 67 | export default new FormStudio(); 68 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/layout/linear/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 64 | 65 | 71 | -------------------------------------------------------------------------------- /antd-designer/src/designer/FormView.tsx: -------------------------------------------------------------------------------- 1 | import React, {PureComponent, ReactElement} from 'react' 2 | import {FormDefinition} from "../../../src/types"; 3 | import FormStudio from "../../../src/FormStudio"; 4 | import {FactoryRenders, FormHelper} from "./helper"; 5 | import {ReactComponentState} from "./types"; 6 | 7 | require('../../../src/style/formView.less'); 8 | 9 | type FormViewProps = { 10 | formDefinition: FormDefinition 11 | } 12 | export default class FormView extends PureComponent { 13 | 14 | private LinearLayout: ReactElement | null = null; 15 | 16 | 17 | constructor(props: FormViewProps, context: any) { 18 | super(props, context); 19 | this.state = { 20 | renderCounter: 0 21 | } 22 | } 23 | 24 | /** 25 | * shouldComponentUpdate 默认不是对象的深层比较,采用标志位的方式 26 | * 此方法代替foreUpdate 27 | */ 28 | forceRender() { 29 | const {renderCounter} = this.state; 30 | this.setState({ 31 | renderCounter: (renderCounter || 0) + 1 32 | }); 33 | } 34 | 35 | override componentWillMount() { 36 | const componentRender = FactoryRenders.getRender("LinearLayout"); 37 | this.LinearLayout = componentRender.renderComponent(FormStudio.definition!)({ 38 | focusAble: false, 39 | toolbarAble: false 40 | }); 41 | 42 | FormHelper.formView = this; 43 | } 44 | 45 | override render() { 46 | const {formDefinition} = this.props; 47 | 48 | return ( 49 |

50 |
51 |
{formDefinition.title}
52 |
{formDefinition.description}
53 |
54 |
57 | {this.LinearLayout} 58 |
59 |
60 | ); 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /antd-designer/src/designer/components/SelectFactory.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Select} from 'antd'; 3 | import {ComponentWrapper, FactoryRegister} from '../wrapper'; 4 | import {ReactComponent} from '../ReactComponent'; 5 | import {SelectProps} from "../../../../src/props"; 6 | import {ReactComponentProps, ReactComponentState} from "../types"; 7 | import {FactoryGroup, FieldFactory, FieldType} from "../../../../src/types"; 8 | import {makeComponentId, makeFieldId} from "../../../../src/utils"; 9 | import {OptionGroupEditor} from "../widgets/OptionGroupEditor"; 10 | import SelectFactory from "@@/factory/SelectFactory"; 11 | 12 | @ComponentWrapper 13 | class SelectComponent extends ReactComponent, SelectProps, ReactComponentState> { 14 | 15 | renderOptions() { 16 | const {definition: {props}} = this.props; 17 | return props!.options.map(item => { 18 | return {item.label} 19 | }) 20 | } 21 | 22 | override render() { 23 | const {definition: {props}} = this.props; 24 | const defaultValue = props!.options.filter(item => { 25 | return item.checked; 26 | }).map(item => { 27 | return item.value; 28 | }); 29 | 30 | return ( 31 | 41 | ) 42 | } 43 | } 44 | 45 | class SelectComponentEditor extends OptionGroupEditor { 46 | 47 | doRender() { 48 | return ( 49 | <> 50 | {this.renderOptions()} 51 | 52 | ); 53 | } 54 | } 55 | 56 | FactoryRegister(SelectComponent, SelectComponentEditor)(SelectFactory) 57 | 58 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/wrapper/ComponentRender.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 82 | 83 | 86 | -------------------------------------------------------------------------------- /antd-designer/src/designer/layout/LinearLayoutFactory.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {sortable} from '../../lib/sortable'; 3 | import {Layout} from '../ReactComponent'; 4 | import {FactoryRegister, LayoutWrapper} from '../wrapper'; 5 | import {ComponentDefinition, ComponentFactory, FactoryGroup} from "../../../../src/types"; 6 | import {LinearLayoutProps} from "../../../../src/props"; 7 | import {ReactComponentGroupState, ReactComponentProps} from "../types"; 8 | import FactoryRenders from "../helper/FactoryRenders"; 9 | import className from "classnames"; 10 | import {makeComponentId} from "../../../../src/utils"; 11 | import LinearLayoutFactory from "@@/factory/LinearLayoutFactory"; 12 | 13 | /** 14 | * 这是一个特殊的布局 15 | */ 16 | @LayoutWrapper({ 17 | focusAble: true, 18 | toolbarAble: true, 19 | layoutStyle: { 20 | display: 'flex', 21 | position: 'relative', 22 | width: '100%', 23 | minHeight: '50px', 24 | } 25 | }) 26 | class LinearLayout extends Layout, 27 | LinearLayoutProps 28 | , ReactComponentGroupState> { 29 | 30 | private _node: HTMLElement | null = null; 31 | 32 | 33 | override componentDidMount() { 34 | sortable(this._node, this); 35 | } 36 | 37 | ref = (node: HTMLElement | null) => { 38 | this._node = node; 39 | } 40 | 41 | renderChildren() { 42 | const definition: ComponentDefinition = this.props.definition; 43 | return definition.children!.map((item, index) => { 44 | const factory = FactoryRenders.getRender(item.type); 45 | return factory.renderComponent(item)({ 46 | key: `children-${index}`, 47 | onRemove: () => { 48 | this.removeChild(index) 49 | } 50 | }); 51 | }); 52 | } 53 | 54 | override render() { 55 | const {definition} = this.props; 56 | return ( 57 |
62 | {this.renderChildren()} 63 |
64 | ) 65 | } 66 | } 67 | 68 | FactoryRegister(LinearLayout)(LinearLayoutFactory) 69 | 70 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/wrapper/LayoutWrapper.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 64 | 65 | 101 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/FormPropsEditor.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 87 | 88 | 91 | -------------------------------------------------------------------------------- /antdv-designer/src/designer/FormDesigner.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 75 | 76 | 80 | -------------------------------------------------------------------------------- /antd-designer/src/designer/FormPropsEditor.tsx: -------------------------------------------------------------------------------- 1 | import React, {PureComponent} from 'react' 2 | import {FormHelper} from "./helper"; 3 | import {ReactComponentGroupState} from "./types"; 4 | import {FormDefinition} from "../../../src/types"; 5 | import {Form, FormInstance, Input, InputNumber} from "antd"; 6 | import {mergeObject} from "../../../src/utils"; 7 | import TextArea from "antd/es/input/TextArea"; 8 | 9 | require('../../../src/style/formView.less'); 10 | 11 | type FormPropsEditorProps = { 12 | definition: FormDefinition; 13 | } 14 | 15 | /** 16 | * 属性编辑器 17 | */ 18 | class FormPropsEditor extends PureComponent> { 19 | 20 | private readonly refForm = React.createRef(); 21 | 22 | 23 | onValuesChange(changedValues: any) { 24 | console.log('onValuesChange', changedValues) 25 | const {definition} = this.props; 26 | for (const field of Object.keys(changedValues)) { 27 | mergeObject(field, changedValues[field], definition); 28 | } 29 | 30 | FormHelper.formView!.forceRender(); 31 | } 32 | 33 | override render() { 34 | const {definition} = this.props; 35 | return ( 36 |
37 |
this.onValuesChange(changedValues)} 42 | > 43 | 51 | 52 | 53 | 58 | 59 | 60 | 65 |