├── src ├── component │ ├── main.js │ ├── styled │ │ └── index.jsx │ ├── Context │ │ └── index.jsx │ ├── FormBuilder │ │ ├── EditingContent │ │ │ ├── OptionRowShow │ │ │ │ └── index.jsx │ │ │ ├── FieldNameInput │ │ │ │ └── index.jsx │ │ │ ├── RequiredCheckBox │ │ │ │ └── index.jsx │ │ │ ├── LabelEditor │ │ │ │ └── index.jsx │ │ │ ├── DataOptions │ │ │ │ └── index.jsx │ │ │ └── index.jsx │ │ ├── ElementList │ │ │ ├── index.jsx │ │ │ └── Element │ │ │ │ └── index.jsx │ │ ├── DeveloperContent │ │ │ ├── UrlInput │ │ │ │ └── index.jsx │ │ │ └── index.jsx │ │ ├── Priview │ │ │ ├── index.jsx │ │ │ └── PriviewItem │ │ │ │ └── index.jsx │ │ └── index.jsx │ ├── tool │ │ └── index.js │ └── Store │ │ └── index.js ├── dist │ ├── main.js │ ├── Context │ │ └── index.js │ ├── styled │ │ └── index.js │ ├── FormBuilder │ │ ├── ElementList │ │ │ ├── index.js │ │ │ └── Element │ │ │ │ └── index.js │ │ ├── EditingContent │ │ │ ├── LabelEditor │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── Priview │ │ │ ├── index.js │ │ │ └── PriviewItem │ │ │ │ └── index.js │ │ ├── DeveloperContent │ │ │ └── index.js │ │ └── index.js │ └── tool │ │ └── index.js ├── .babelrc ├── index.js └── registerServiceWorker.js ├── README.md ├── config ├── jest │ ├── fileTransform.js │ └── cssTransform.js ├── polyfills.js ├── paths.js ├── env.js ├── webpackDevServer.config.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── public ├── manifest.json └── index.html ├── .babelrc ├── scripts ├── test.js ├── start.js └── build.js └── package.json /src/component/main.js: -------------------------------------------------------------------------------- 1 | import FormBuilder from './FormBuilder'; 2 | export default FormBuilder; 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 表单设计器 test 2 | ## antd 3 | ### 安装依赖 4 | > **npm install / cnpm install** 5 | ### 启动项目 6 | > **npm start** 7 | ### DEMO 8 | [简单演示](https://fred8617.github.io/react-form-builder-antd) 9 | -------------------------------------------------------------------------------- /src/dist/main.js: -------------------------------------------------------------------------------- 1 | 'use strict';Object.defineProperty(exports,'__esModule',{value:true});var _FormBuilder=require('./FormBuilder');var _FormBuilder2=_interopRequireDefault(_FormBuilder);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}exports.default=_FormBuilder2.default; -------------------------------------------------------------------------------- /src/component/styled/index.jsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { 3 | Icon 4 | } from 'antd'; 5 | import {Row} from 'antd'; 6 | export const CursorIcon=styled(Icon)` 7 | cursor: pointer; 8 | `; 9 | 10 | export const SpanLH32=styled.span` 11 | line-height: 32px; 12 | `; 13 | 14 | export const RowMB10=styled(Row)` 15 | margin-bottom: 10px; 16 | `; 17 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react-app", 4 | ], 5 | "plugins": [ 6 | "styled-components", 7 | [ 8 | "import", 9 | { 10 | "libraryName": "antd", 11 | "libraryDirectory": "es", 12 | "style": "css" 13 | } 14 | ], 15 | [ 16 | "transform-decorators-legacy" 17 | ], 18 | [ 19 | "transform-do-expressions" 20 | ] 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /src/component/Context/index.jsx: -------------------------------------------------------------------------------- 1 | import React,{Component,createContext} from 'react'; 2 | const {Provider:Pro,Consumer}=createContext({ 3 | 4 | }); 5 | export let changField; 6 | export const setChangeField=(fieldName)=>{ 7 | changField=fieldName; 8 | } 9 | export const Provider=Pro; 10 | export const FormConsume=(Component)=>{ 11 | return (props)=>( 12 | 13 | { 14 | e=> 15 | } 16 | 17 | ) 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "react" 5 | ], 6 | "plugins": [ 7 | "styled-components", 8 | [ 9 | "import", 10 | { 11 | "libraryName": "antd", 12 | "libraryDirectory": "es", 13 | "style": "css" 14 | } 15 | ], 16 | [ 17 | "transform-decorators-legacy" 18 | ], 19 | [ 20 | "transform-do-expressions" 21 | ], 22 | [ 23 | "transform-class-properties" 24 | ], 25 | [ 26 | "transform-object-rest-spread" 27 | ] 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | const jest = require('jest'); 19 | let argv = process.argv.slice(2); 20 | 21 | // Watch unless on CI or in coverage mode 22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 23 | argv.push('--watch'); 24 | } 25 | 26 | 27 | jest.run(argv); 28 | -------------------------------------------------------------------------------- /src/dist/Context/index.js: -------------------------------------------------------------------------------- 1 | 'use strict';Object.defineProperty(exports,'__esModule',{value:true});exports.FormConsume=exports.Provider=undefined;var _extends=Object.assign||function(target){for(var i=1;i{ 14 | const { 15 | props:{ 16 | store:{ 17 | editingData:data, 18 | setEditingData 19 | }, 20 | }, 21 | }=this; 22 | setEditingData(`optionRowShow`,e); 23 | } 24 | render(){ 25 | const { 26 | store:{ 27 | editingData:{ 28 | optionRowShow 29 | } 30 | } 31 | }=this.props; 32 | return ( 33 | 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof Promise === 'undefined') { 4 | // Rejection tracking prevents a common issue where React gets into an 5 | // inconsistent state due to an error, but it gets swallowed by a Promise, 6 | // and the user has no idea what causes React's erratic future behavior. 7 | require('promise/lib/rejection-tracking').enable(); 8 | window.Promise = require('promise/lib/es6-extensions.js'); 9 | } 10 | 11 | // fetch() polyfill for making API calls. 12 | require('whatwg-fetch'); 13 | 14 | // Object.assign() is commonly used with React. 15 | // It will use the native implementation if it's present and isn't buggy. 16 | Object.assign = require('object-assign'); 17 | 18 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet. 19 | // We don't polyfill it in the browser--this is user's responsibility. 20 | if (process.env.NODE_ENV === 'test') { 21 | require('raf').polyfill(global); 22 | } 23 | -------------------------------------------------------------------------------- /src/component/FormBuilder/ElementList/index.jsx: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import {List} from 'antd'; 3 | import Element from './Element'; 4 | import {FormConsume} from '../../Context'; 5 | import { 6 | inject, 7 | observer, 8 | } from 'mobx-react'; 9 | 10 | @inject('store') 11 | @observer 12 | export default class ElementList extends Component{ 13 | render(){ 14 | const { 15 | store:{ 16 | elementTypes:data 17 | }, 18 | }=this.props; 19 | return ( 20 | 23 | 元素 24 | } 25 | bordered 26 | dataSource={data} 27 | renderItem={ 28 | item => 29 | ( 30 | 31 | 34 | 35 | ) 36 | } 37 | /> 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/dist/styled/index.js: -------------------------------------------------------------------------------- 1 | 'use strict';Object.defineProperty(exports,'__esModule',{value:true});exports.RowMB10=exports.SpanLH32=exports.CursorIcon=undefined;var _row=require('antd/es/row');var _row2=_interopRequireDefault(_row);var _icon=require('antd/es/icon');var _icon2=_interopRequireDefault(_icon);require('antd/es/row/style/css');require('antd/es/icon/style/css');var _styledComponents=require('styled-components');var _styledComponents2=_interopRequireDefault(_styledComponents);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}var CursorIcon=exports.CursorIcon=(0,_styledComponents2.default)(_icon2.default).withConfig({displayName:'styled__CursorIcon',componentId:'ks49l4-0'})(['cursor:pointer;']);var SpanLH32=exports.SpanLH32=_styledComponents2.default.span.withConfig({displayName:'styled__SpanLH32',componentId:'ks49l4-1'})(['line-height:32px;']);var RowMB10=exports.RowMB10=(0,_styledComponents2.default)(_row2.default).withConfig({displayName:'styled__RowMB10',componentId:'ks49l4-2'})(['margin-bottom:10px;']); -------------------------------------------------------------------------------- /src/component/FormBuilder/DeveloperContent/UrlInput/index.jsx: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import {observer,inject} from 'mobx-react'; 3 | import { 4 | Input, 5 | Modal 6 | } from 'antd'; 7 | import { 8 | action 9 | } from 'mobx'; 10 | const Search = Input.Search; 11 | 12 | @inject('store') 13 | @observer 14 | export default class UrlInput extends Component{ 15 | testSubmit=()=>{ 16 | const { 17 | form, 18 | }=this.props; 19 | Modal.info({title:'测试表单提交',content:
{JSON.stringify(form.getFieldsValue(),null,2)}
}); 20 | console.log(form.getFieldsValue()); 21 | } 22 | @action urlChange=(e)=>{ 23 | this.props.store.submitUrl=e.target.value; 24 | }; 25 | render(){ 26 | const { 27 | store:{ 28 | submitUrl 29 | } 30 | }=this.props; 31 | return ( 32 | 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/component/FormBuilder/EditingContent/FieldNameInput/index.jsx: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import { 3 | inject, 4 | observer, 5 | } from 'mobx-react'; 6 | import {action} from 'mobx'; 7 | import {Input} from 'antd'; 8 | @inject('store') 9 | @observer 10 | export default class FieldNameInput extends Component{ 11 | @action handleChange=(e)=>{ 12 | const { 13 | store 14 | }=this.props; 15 | store.editingData.fieldName=e.target.value; 16 | } 17 | 18 | @action handleBlur=()=>{ 19 | const { 20 | store:{ 21 | editingData, 22 | editingData:{ 23 | fieldName 24 | }, 25 | checkName, 26 | } 27 | }=this.props; 28 | editingData.fieldName=checkName(fieldName,editingData.type,1); 29 | } 30 | 31 | render(){ 32 | const { 33 | store:{ 34 | editingData:{ 35 | fieldName 36 | } 37 | } 38 | }=this.props; 39 | return ( 40 | 47 | ) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/component/FormBuilder/ElementList/Element/index.jsx: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import {List} from 'antd'; 3 | import {FormConsume} from '../../../Context'; 4 | import { 5 | DragSource, 6 | } from 'react-dnd'; 7 | import { 8 | inject, 9 | observer, 10 | } from 'mobx-react'; 11 | import {action} from 'mobx'; 12 | 13 | const type=`ELEMENT`; 14 | //拖拽目标处理集合 15 | const source={ 16 | canDrag(props){ 17 | return true; 18 | }, 19 | beginDrag(props,monitor,component) { 20 | return props; 21 | } 22 | } 23 | 24 | @inject('store') 25 | @observer 26 | @FormConsume 27 | @DragSource( 28 | type, 29 | source, 30 | (connect, monitor) => ({ 31 | connectDragSource: connect.dragSource(), 32 | isDragging: monitor.isDragging(), 33 | connectDragPreview: connect.dragPreview(), 34 | }), 35 | ) 36 | export default class Element extends Component{ 37 | 38 | render(){ 39 | const { 40 | connectDragSource, 41 | item, 42 | item:{ 43 | name, 44 | }, 45 | store:{ 46 | addElement 47 | } 48 | }=this.props; 49 | return ( 50 | connectDragSource && 51 | connectDragSource( 52 |
addElement(item)} 54 | style={{cursor:`pointer`}} 55 | > 56 | {name} 57 |
58 | ) 59 | ) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/component/FormBuilder/EditingContent/RequiredCheckBox/index.jsx: -------------------------------------------------------------------------------- 1 | import React,{Component,Fragment} from 'react'; 2 | import { 3 | inject, 4 | observer, 5 | } from 'mobx-react'; 6 | import {action} from 'mobx'; 7 | import { 8 | Checkbox, 9 | Input 10 | } from 'antd'; 11 | @inject('store') 12 | @observer 13 | export default class RequiredCheckBox extends Component{ 14 | @action requireChange=(e)=>{ 15 | const { 16 | store 17 | }=this.props; 18 | store.editingData.required=e.target.checked; 19 | } 20 | @action requiredMessageChange=(e)=>{ 21 | const { 22 | store, 23 | store:{ 24 | editingData:data 25 | }, 26 | form 27 | }=this.props; 28 | store.editingData.requiredMessage=e.target.value; 29 | // debugger 30 | const fName=data.fieldName||`invalidField`; 31 | if(!form.getFieldValue(fName)){ 32 | form.setFieldsValue({[fName]:1}) 33 | form.setFieldsValue({[fName]:``}); 34 | setTimeout(()=>{ 35 | form.validateFields(); 36 | },1) 37 | } 38 | } 39 | render(){ 40 | const { 41 | store:{ 42 | editingData:{ 43 | required, 44 | requiredMessage 45 | } 46 | } 47 | }=this.props; 48 | return ( 49 | 50 | 53 | 必填 54 | 55 | {do{ 56 | if(required){ 57 | 63 | } 64 | }} 65 | 66 | 67 | ) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/component/FormBuilder/EditingContent/LabelEditor/index.jsx: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import { 3 | Drawer, 4 | Radio , 5 | Row, 6 | Col, 7 | Divider, 8 | } from 'antd'; 9 | import { Editor } from 'react-draft-wysiwyg'; 10 | import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'; 11 | import { EditorState, convertToRaw, ContentState,convertFromHTML } from 'draft-js'; 12 | import draftToHtml from 'draftjs-to-html'; 13 | import htmlToDraft from 'html-to-draftjs'; 14 | import update from 'immutability-helper'; 15 | import {FormConsume} from '../../../Context'; 16 | 17 | import {observer,inject} from 'mobx-react'; 18 | import {observable,action} from 'mobx'; 19 | 20 | @inject('store') 21 | @observer 22 | export default class LabelEditor extends Component{ 23 | 24 | state={ 25 | toolbar:[ 26 | 'inline', 27 | 'blockType', 28 | 'fontSize', 29 | 'fontFamily', 30 | //'list', 31 | //'textAlign', 32 | 'colorPicker', 33 | 'link', 34 | //'embedded', 35 | 'emoji', 36 | 'image', 37 | 'remove', 38 | 'history' 39 | ], 40 | } 41 | 42 | @action onEditorStateChange=(editorState)=>{ 43 | const { 44 | store, 45 | store:{ 46 | editingData:data, 47 | setEditingData 48 | } 49 | }=this.props; 50 | const label=draftToHtml(convertToRaw(editorState.getCurrentContent())) 51 | setEditingData(`label`,label); 52 | store.editorState=editorState; 53 | } 54 | render(){ 55 | const { 56 | state:{ 57 | toolbar 58 | }, 59 | props:{ 60 | store:{ 61 | editorState 62 | } 63 | } 64 | }=this; 65 | return ( 66 | 75 | ) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right