├── webpack-assets.json
├── src
├── pages
│ ├── coms
│ │ ├── common
│ │ │ └── style.config.js
│ │ ├── RJImgUploader.js
│ │ ├── Layout.Header.js
│ │ ├── Badge.js
│ │ ├── Layout.Content.js
│ │ ├── Calendar.js
│ │ ├── Form.Item.Submit.js
│ │ ├── RJPagination.js
│ │ ├── Switch.js
│ │ ├── DatePicker.js
│ │ ├── Form.Item.Inline.js
│ │ ├── InputNumber.js
│ │ ├── Layout.Sider.js
│ │ ├── Form.Item.js
│ │ ├── RJFormItem.js
│ │ ├── Table.Column.js
│ │ ├── Tag.js
│ │ ├── Modal.js
│ │ ├── Input.js
│ │ ├── RJBreadcrumb.js
│ │ ├── Breadcrumb.js
│ │ ├── div.js
│ │ ├── Form.js
│ │ ├── Radio.Group.js
│ │ ├── Button.js
│ │ ├── RJSelect.js
│ │ ├── Row2.js
│ │ ├── Layout.js
│ │ ├── RJMenu.js
│ │ ├── Row4.js
│ │ ├── Table_Data.js
│ │ ├── Row8.js
│ │ └── Table.js
│ ├── page.json
│ ├── preview.jsx
│ ├── ant-coms.js
│ └── editor.jsx
├── components
│ ├── index.jsx
│ ├── RJPagination.jsx
│ ├── RJSelect.jsx
│ ├── es5
│ │ └── image.js
│ ├── RJMenu.jsx
│ └── RJImgUploader.jsx
├── App.css
├── index.jsx
├── index.css
└── logo.svg
├── .babelrc
├── .gitignore
├── public
└── template.html
├── docs
└── index.html
├── SECURITY.md
├── package.json
├── webpack.loaders.js
├── webpack.production.config.js
├── webpack.config.js
└── README.md
/webpack-assets.json:
--------------------------------------------------------------------------------
1 | {"index":{"js":"/index_2969feb91e0758ea6cb3.js"}}
--------------------------------------------------------------------------------
/src/pages/coms/common/style.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | background:{
3 | text:'背景色',
4 | type:'color'
5 | },
6 | }
--------------------------------------------------------------------------------
/src/pages/coms/RJImgUploader.js:
--------------------------------------------------------------------------------
1 | export default {
2 | type:'RJImgUploader',
3 | title:'图片上传',
4 | props:{
5 | action:"#"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/coms/Layout.Header.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type":"Layout.Header",
3 | "title":"头部",
4 | props:{
5 | style:{
6 | minHeight:20,
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/pages/coms/Badge.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type":"Badge",
3 | "title":"圆点",
4 | "props":{
5 | "count":10
6 | },
7 | config:{
8 | count:{
9 | text:"数值"
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/pages/coms/Layout.Content.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type":"Layout.Content",
3 | "title":"内容区域",
4 | can_place:true,
5 | "props":{
6 | style:{
7 | padding:20,
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/pages/coms/Calendar.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type":"Calendar",
3 | "title":"日历",
4 | "props":{
5 | "fullscreen":false,
6 | "style":{
7 | "width":300,
8 | "height":300
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/pages/coms/Form.Item.Submit.js:
--------------------------------------------------------------------------------
1 | export default {
2 | type:"Form.Item",
3 | "title":"不带描述的表单项(例如提交按钮用的)",
4 | can_place:true,
5 | wrap_inner:true,
6 | props:{
7 | wrapperCol: { span: 20 ,offset:4},
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/pages/coms/RJPagination.js:
--------------------------------------------------------------------------------
1 | export default {
2 | type:"RJPagination",
3 | "title":"分页",
4 | props:{
5 | pageData:{
6 | currentIndex:1,
7 | pageSize:20,
8 | totalNumber:200
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react","stage-3"],
3 | "plugins": [
4 | "transform-runtime",
5 | "transform-decorators-legacy",
6 | "transform-class-properties",
7 | "react-hot-loader/babel"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/src/pages/coms/Switch.js:
--------------------------------------------------------------------------------
1 | export default {
2 | type:'Switch',
3 | title:'开关切换',
4 | props:{
5 | size:'default'
6 | },
7 | config:{
8 | size:{
9 | text:'大小',
10 | enum:[
11 | 'default',
12 | 'small'
13 | ]
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/pages/coms/DatePicker.js:
--------------------------------------------------------------------------------
1 | export default {
2 | type:"DatePicker",
3 | title:"日期选择器",
4 | wrap:true,
5 | props:{
6 | style:{
7 | width:'200px'
8 | }
9 | },
10 | config:{
11 | style:{
12 | width:{
13 | text:"宽度"
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/pages/coms/Form.Item.Inline.js:
--------------------------------------------------------------------------------
1 | export default {
2 | type:"Form.Item",
3 | "title":"内联的表单项",
4 | can_place:true,
5 | wrap_inner:true,
6 | props:{
7 | label:"表单项描述",
8 | style:{
9 | }
10 | },
11 | config:{
12 | "label":{
13 | text:"表单项描述"
14 | },
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/pages/coms/InputNumber.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type":"InputNumber",
3 | "title":"数字输入框",
4 | "props":{
5 | defaultValue:10,
6 | style:{
7 | // width:200
8 | }
9 | },
10 | config:{
11 | style:{
12 | width:{
13 | text:"宽度"
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/pages/coms/Layout.Sider.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type":"Layout.Sider",
3 | "title":"侧边栏",
4 | can_place:true,
5 | props:{
6 | style:{
7 | width:"300px",
8 | border:"1px solid #ff6600"
9 | }
10 | },
11 | config:{
12 | style:{
13 | width:"宽度"
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 | .idea
20 |
--------------------------------------------------------------------------------
/src/pages/coms/Form.Item.js:
--------------------------------------------------------------------------------
1 | export default {
2 | type:"Form.Item",
3 | "title":"表单项",
4 | can_place:true,
5 | wrap_inner:true,
6 | props:{
7 | label:"表单项描述",
8 | labelCol: { span: 4 },
9 | wrapperCol: { span: 20 },
10 | style:{
11 | }
12 | },
13 | config:{
14 | "label":{
15 | text:"表单项描述"
16 | },
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/pages/coms/RJFormItem.js:
--------------------------------------------------------------------------------
1 | export default {
2 | type:"Form.Item",
3 | "title":"表单项",
4 | can_place:true,
5 | wrap_inner:true,
6 | props:{
7 | label:"表单项描述",
8 | labelCol: { span: 4 },
9 | wrapperCol: { span: 20 },
10 | style:{
11 | }
12 | },
13 | config:{
14 | "label":{
15 | text:"表单项描述"
16 | },
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/pages/coms/Table.Column.js:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | type:"Table.Column",
4 | title:"表格列",
5 | props:{
6 | title:"名称",
7 | dataIndex:"1",
8 | key:"1"
9 | },
10 | config:{
11 | title:{
12 | text:"列名",
13 | },
14 | dataIndex:{
15 | text:"唯一key"
16 | },
17 | key:{
18 | text:"唯一key"
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/public/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%= htmlWebpackPlugin.options.title %>
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/index.jsx:
--------------------------------------------------------------------------------
1 | import RJImgUploader from './RJImgUploader';
2 | import RJPagination from './RJPagination';
3 | import RJSelect from './RJSelect';
4 | import RJMenu from './RJMenu';
5 |
6 |
7 | var antd = require('antd');
8 |
9 | Object.assign(antd, {
10 | RJImgUploader,
11 | RJPagination,
12 | RJSelect,
13 | RJMenu
14 | });
15 | module.exports = antd;
16 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Antd Editor
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/pages/coms/Tag.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type":"Tag",
3 | "title":"标签",
4 |
5 | props:{
6 | closable:false,
7 | "content":"标签",
8 | },
9 | config:{
10 | closable:{
11 | text:"是否可关闭",
12 | enum:["true","false"]
13 | },
14 | color:{
15 | text:"颜色",
16 | enum:[
17 | 'red',
18 | 'orange',
19 | 'green',
20 | 'blue'
21 | ]
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/pages/coms/Modal.js:
--------------------------------------------------------------------------------
1 | export default {
2 | type:"Modal",
3 | title:"模态窗口",
4 | can_place:true,
5 | wrap:true,
6 | props:{
7 | visible:true,
8 | title:"标题",
9 | width:500,
10 | event_onOk:'',
11 | event_onCancel:''
12 | },
13 | config:{
14 | visible:{
15 | text:"是否可见",
16 | type:"Boolean"
17 | },
18 | title:{
19 | text:"标题"
20 | },
21 | width:{
22 | text:"宽度"
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | @import '~antd/dist/antd.css';
2 | .App {
3 | text-align: center;
4 | }
5 |
6 | .App-logo {
7 | animation: App-logo-spin infinite 20s linear;
8 | height: 80px;
9 | }
10 |
11 | .App-header {
12 | background-color: #222;
13 | height: 150px;
14 | padding: 20px;
15 | color: white;
16 | }
17 |
18 | .App-intro {
19 | font-size: large;
20 | }
21 |
22 | @keyframes App-logo-spin {
23 | from { transform: rotate(0deg); }
24 | to { transform: rotate(360deg); }
25 | }
26 |
--------------------------------------------------------------------------------
/src/pages/coms/Input.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type":"Input",
3 | "title":"输入框",
4 | "props":{
5 | "placeholder":"这是一个输入框",
6 | type:"text",
7 | style:{
8 | // width:200
9 | }
10 | },
11 | config:{
12 | "placeholder":{
13 | text:"默认提醒"
14 | },
15 | type:{
16 | text:"是否多行文本",
17 | enum:[
18 | 'textarea',
19 | 'text'
20 | ]
21 | },
22 | style:{
23 | width:{
24 | text:"宽度"
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/pages/page.json:
--------------------------------------------------------------------------------
1 | [{
2 | type:'Layout',
3 | childrens:[
4 | {
5 | type:'Layout.Header',
6 | props:{
7 |
8 | }
9 | },
10 | ]
11 | },{
12 | type:'Layout',
13 | childrens:[
14 | {
15 | type:'Layout.Sider',
16 | col:4,
17 | props:{
18 |
19 | }
20 | },
21 | {
22 | type:'Layout.Content',
23 | col:8,
24 | props:{
25 |
26 | }
27 | }
28 | ]
29 | },{
30 | type:'Layout',
31 | },{
32 | type:'Layout',
33 | },
34 | ]
35 |
--------------------------------------------------------------------------------
/src/index.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import './index.css';
5 | import './App.css';
6 |
7 | import Editor from './pages/editor.jsx';
8 | import preview from './pages/preview.jsx';
9 | import { Router, Route, hashHistory,IndexRoute } from 'react-router'
10 | ReactDOM.render(
11 | (
12 |
13 |
14 |
15 |
16 | ),
17 | document.getElementById('root')
18 | );
19 |
--------------------------------------------------------------------------------
/src/components/RJPagination.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Pagination } from 'antd';
3 |
4 | class RJPagination extends React.Component {
5 | constructor() {
6 | super();
7 | }
8 |
9 | render() {
10 | let pageData = this.props.pageData;
11 |
12 | return (
13 |
19 | );
20 | }
21 | }
22 |
23 | export default RJPagination;
24 |
--------------------------------------------------------------------------------
/src/pages/coms/RJBreadcrumb.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type":"RJBreadcrumb",
3 | "title":"面包屑",
4 | props:{
5 | data:[
6 | {
7 | title:"一级目录",
8 | href:"#",
9 | key:1
10 | },
11 | {
12 | title:"二级目录",
13 | href:"#",
14 | key:2
15 | }
16 | ]
17 | },
18 | config:{
19 | data:{
20 | text:"项目配置",
21 | enumobject:[{
22 | key:1,
23 | dataIndex:"title",
24 | title:"显示文本",
25 | type:'String',
26 | },{
27 | key:2,
28 | dataIndex:"href",
29 | title:"链接",
30 | type:'String',
31 | }]
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Use this section to tell people about which versions of your project are
6 | currently being supported with security updates.
7 |
8 | | Version | Supported |
9 | | ------- | ------------------ |
10 | | 5.1.x | :white_check_mark: |
11 | | 5.0.x | :x: |
12 | | 4.0.x | :white_check_mark: |
13 | | < 4.0 | :x: |
14 |
15 | ## Reporting a Vulnerability
16 |
17 | Use this section to tell people how to report a vulnerability.
18 |
19 | Tell them where to go, how often they can expect to get an update on a
20 | reported vulnerability, what to expect if the vulnerability is accepted or
21 | declined, etc.
22 |
--------------------------------------------------------------------------------
/src/pages/coms/Breadcrumb.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type":"Breadcrumb",
3 | "title":"面包屑",
4 | props:{
5 | routes:[
6 | {
7 | breadcrumbName:"一级目录",
8 | path:"#",
9 | key:1
10 | },
11 | {
12 | breadcrumbName:"二级目录",
13 | path:"#",
14 | key:2
15 | }
16 | ]
17 | },
18 | config:{
19 | routes:{
20 | text:"项目配置",
21 | enumobject:[{
22 | key:1,
23 | dataIndex:"breadcrumbName",
24 | title:"显示文本",
25 | type:'String',
26 | },{
27 | key:2,
28 | dataIndex:"path",
29 | title:"链接",
30 | type:'String',
31 | }]
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/pages/coms/div.js:
--------------------------------------------------------------------------------
1 | export default {
2 | type:"div",
3 | title:"通用布局块",
4 | is_native:true,
5 | can_place:true,
6 | props:{
7 | style:{
8 | minHeight:20,
9 | padding:"20px",
10 | }
11 | },
12 | config:{
13 | style:{
14 | padding:{
15 | text:"内间距",
16 | type:"4-value"
17 | },
18 | margin:{
19 | text:"外边距",
20 | type:"4-value"
21 | },
22 | backgroundColor:{
23 | text:"背景色",
24 | type:"color"
25 | },
26 | }
27 |
28 | }
29 | }
--------------------------------------------------------------------------------
/src/pages/coms/Form.js:
--------------------------------------------------------------------------------
1 | import Common_Style from './common/style.config';
2 | export default {
3 | type:"Form",
4 | "title":"表单容器",
5 | can_place:true,
6 | props:{
7 | layout:'horizontal',
8 | style:{
9 | padding:"20px",
10 | margin:"0px"
11 | }
12 | },
13 | config:{
14 | layout:{
15 | text:"布局方式",
16 | enum:[
17 | 'inline',
18 | 'horizontal',
19 | 'vertical'
20 | ]
21 | },
22 | style:{
23 | width:{
24 | text:"宽度"
25 | },
26 | padding:{
27 | text:"内边距",
28 | type:"4-value"
29 | },
30 | margin:{
31 | text:"外边距",
32 | type:"4-value"
33 | },
34 | ...Common_Style
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/pages/coms/Radio.Group.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type":"Radio.Group",
3 | "title":"单选框",
4 | "props":{
5 | options:[
6 | { label: 'Apple', value: 'Apple',key:1 },
7 | { label: 'Pear', value: 'Pear',key:2 },
8 | { label: 'Orange', value: 'Orange', disabled: true,key:3 },
9 | ]
10 | },
11 | config:{
12 | options:{
13 | text:"项目配置",
14 | enumobject:[{
15 | key:1,
16 | dataIndex:"label",
17 | title:"显示文本",
18 | type:'String',
19 | },{
20 | key:2,
21 | dataIndex:"value",
22 | title:"真实值",
23 | type:'String',
24 | },{
25 | key:3,
26 | dataIndex:"disabled",
27 | title:"是否禁用",
28 | type:'Boolean',
29 | render: (v) => {
30 | return v?'是':'否';
31 | }
32 | }]
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/pages/coms/Button.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type": "Button",
3 | "title": "按钮",
4 | "props": {
5 | type: 'primary',
6 | content: '按钮一只',
7 | style: {
8 | margin: "0px 10px 0px 0px"
9 | }
10 | },
11 | config: {
12 | type: {
13 | text: "主题",
14 | enum: [
15 | 'primary',
16 | 'default',
17 | 'dashed',
18 | 'danger'
19 | ]
20 | },
21 | icon: {
22 | text: "图标",
23 | },
24 | content: {
25 | text: '文案',
26 | },
27 | style: {
28 | width: {
29 | text: "宽度",
30 | },
31 | margin: {
32 | text: "外边距",
33 | type: "4-value"
34 | }
35 | }
36 | },
37 | }
38 |
--------------------------------------------------------------------------------
/src/pages/coms/RJSelect.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type":"RJSelect",
3 | "title":"下拉列表",
4 | props:{
5 | selectData:[{
6 | key:'1',
7 | value:'第一个',
8 | },{
9 | key:'2',
10 | value:'第二个'
11 | }],
12 | mode: "",
13 | placeholder:'请选择',
14 | style:{}
15 | },
16 | config:{
17 | style:{
18 | width:{
19 | text:"宽度"
20 | }
21 | },
22 | value:{
23 | text:"默认值"
24 | },
25 | mode:{
26 | text:"类型",
27 | enum:[
28 | '','multiple', 'tags', 'combobox'
29 | ]
30 | },
31 | selectData:{
32 | text:"数据源",
33 | enumobject:[
34 | {
35 | title: '文本',
36 | dataIndex: 'value',
37 | type:"String"
38 | },
39 | {
40 | title: '真实值',
41 | dataIndex: 'key',
42 | type:"String"
43 | }
44 | ]
45 | }
46 |
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/pages/coms/Row2.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type":"Row",
3 | "title":"2列栅格",
4 | childrens:[
5 | {
6 | type:'Col',
7 | title:'栅格单元',
8 | can_place:true,
9 | props:{
10 | span:4,
11 | style:{
12 | minHeight:30,
13 | },
14 | },
15 | config:{
16 | span:{
17 | text:"栅格",
18 | enum:[
19 | 2,4,6,8,10,12,14,16,18,20,22,24
20 | ]
21 | }
22 | }
23 | },
24 | {
25 | type:'Col',
26 | title:'栅格单元',
27 | can_place:true,
28 | props:{
29 | span:20,
30 | style:{
31 | minHeight:30,
32 | },
33 | },
34 | config:{
35 | span:{
36 | text:"栅格",
37 | enum:[
38 | 2,4,6,8,10,12,14,16,18,20,22,24
39 | ]
40 | }
41 | }
42 | }
43 | ],
44 | props:{
45 |
46 | },
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | overflow: visible !important;
6 | }
7 |
8 | .editor .ant-tag{
9 | margin-bottom:8px;
10 | }
11 |
12 | .editor .edit_layer,.ant-modal .edit_layer{
13 | opacity: 0.5;
14 | background: #eee;
15 | border:1px dashed #999;
16 | pointer-events:none;
17 | position:absolute;
18 | z-index: 10000000;
19 | }
20 | .editor div[class^="ant-col-"],.editor .ant-layout{
21 | border:1px dashed #ccc;
22 | }
23 | .editor .isdroping,.ant-modal .isdroping{
24 | border:1px solid #ff0000 !important;
25 | }
26 | .ant-modal-wrap {
27 | position: relative !important;
28 | overflow: visible !important;
29 | margin-right:500px !important;
30 | }
31 | .ant-modal{
32 | top:20px !important;
33 | }
34 | .ant-modal-mask{
35 | display: none !important;
36 | }
37 |
38 | .editor .draggable {
39 | border:1px dashed #f7bebe;
40 | }
41 |
--------------------------------------------------------------------------------
/src/pages/coms/Layout.js:
--------------------------------------------------------------------------------
1 | import Common_Style from './common/style.config';
2 | console.log({
3 | "type":"Layout",
4 | "title":"外层布局块",
5 | can_place:true,
6 | props:{
7 | style:{
8 | minHeight:20,
9 | padding:"20px",
10 | backgroundColor:"#fff"
11 | }
12 | },
13 | config:{
14 | style:{
15 | padding:{
16 | text:"内间距",
17 | type:"4-value"
18 | },
19 | margin:{
20 | text:"外边距",
21 | type:"4-value"
22 | },
23 | ...Common_Style
24 | }
25 | }
26 | })
27 | export default {
28 | "type":"Layout",
29 | "title":"外层布局块",
30 | can_place:true,
31 | props:{
32 | style:{
33 | minHeight:20,
34 | padding:"20px",
35 | backgroundColor:"#fff"
36 | }
37 | },
38 | config:{
39 | style:{
40 | padding:{
41 | text:"内间距",
42 | type:"4-value"
43 | },
44 | margin:{
45 | text:"外边距",
46 | type:"4-value"
47 | },
48 | ...Common_Style
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/pages/preview.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | var antd =require('../components/index.jsx');
3 | import Editor from './editor';
4 |
5 | class Preview extends Editor {
6 | constructor() {
7 | super();
8 | this.state = {
9 | dependComponents:[],
10 | comNowIndex:0,
11 | indent_space:'',
12 | data:[{
13 | type:'Layout',
14 | title:'布局块',
15 | can_place:true,
16 | props:{
17 | style:{
18 | paddingBottom:'100px',
19 | background:"#fff"
20 | }
21 | },
22 | }]
23 | }
24 | var previewData = localStorage.getItem('preview_data')
25 | this.state.data = JSON.parse(previewData)
26 | setTimeout(()=>{
27 | this.forceUpdate();
28 | },100)
29 | }
30 | render() {
31 | return (
32 |
33 | {
34 | this.renderJSON(this.state.data)
35 | }
36 |
37 |
40 |
)
41 | }
42 |
43 | }
44 |
45 | export default Preview;
46 |
--------------------------------------------------------------------------------
/src/components/RJSelect.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Select } from 'antd';
3 |
4 | const Option = Select.Option;
5 |
6 | class RJSelect extends React.Component {
7 | constructor() {
8 | super();
9 | }
10 |
11 | /**
12 | * 获取 Option Jsx 组件数组
13 | * @return [type] [description]
14 | */
15 | _getOptions(data) {
16 | if (Array.isArray(data) && data.length > 0) {
17 | return data.map((item, index) => {
18 | if (typeof item !== 'object') {
19 | return (
20 |
23 | );
24 | }
25 |
26 | return (
27 |
30 | );
31 | });
32 | } else {
33 | console.warn('Warnnig: selectData must be Array');
34 | return null;
35 | }
36 | }
37 |
38 | render() {
39 | let selectData = this.props.selectData;
40 | let options = this._getOptions(selectData);
41 |
42 | return ;
43 | }
44 | }
45 |
46 | export default RJSelect;
47 |
--------------------------------------------------------------------------------
/src/pages/coms/RJMenu.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type":"RJMenu",
3 | "title":"导航菜单",
4 | props:{
5 | mode:"horizontal",
6 | theme:"light",
7 | items:[{
8 | key:'1',
9 | icon:'mail',
10 | title:'第一级菜单',
11 | disabled:false,
12 | link:'#',
13 |
14 | },{
15 | key:'2',
16 | icon:'plus',
17 | title:'第1级菜单',
18 | disabled:false,
19 | link:'#',
20 | },{
21 | key:'3',
22 | title:'第3级菜单',
23 | disabled:true,
24 | link:'#',
25 | }]
26 | },
27 | config:{
28 | mode:{
29 | text:'模式',
30 | enum:[
31 | 'inline',
32 | 'horizontal',
33 | 'vertical'
34 | ]
35 | },
36 | theme:{
37 | text:'主题色',
38 | enum:[
39 | 'light',
40 | 'dark'
41 | ]
42 | },
43 | items:{
44 | text:"数据源",
45 | enumobject:[
46 | {
47 | title: '菜单文本',
48 | dataIndex: 'title',
49 | type:"String"
50 | },
51 | {
52 | title: '链接',
53 | dataIndex: 'link',
54 | type:"String"
55 | },
56 | {
57 | title: '图标',
58 | dataIndex: 'icon',
59 | type:"String"
60 | },
61 | {
62 | title: '是否禁用',
63 | dataIndex: 'disabled',
64 | type:"Boolean"
65 | }
66 | ]
67 | }
68 |
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/pages/coms/Row4.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type":"Row",
3 | "title":"4列栅格",
4 | childrens:[
5 | {
6 | type:'Col',
7 | title:'栅格单元',
8 | can_place:true,
9 | props:{
10 | span:6,
11 | style:{
12 | minHeight:30,
13 | },
14 | },
15 | config:{
16 | span:{
17 | text:"栅格",
18 | enum:[
19 | 2,4,6,8,10,12,14,16,18,20,22,24
20 | ]
21 | }
22 | }
23 | },
24 | {
25 | type:'Col',
26 | title:'栅格单元',
27 | can_place:true,
28 | props:{
29 | span:6,
30 | style:{
31 | minHeight:30,
32 | },
33 | },
34 | config:{
35 | span:{
36 | text:"栅格",
37 | enum:[
38 | 2,4,6,8,10,12,14,16,18,20,22,24
39 | ]
40 | }
41 | }
42 | },
43 | {
44 | type:'Col',
45 | title:'栅格单元',
46 | can_place:true,
47 | props:{
48 | span:6,
49 | style:{
50 | minHeight:30,
51 | },
52 | },
53 | config:{
54 | span:{
55 | text:"栅格",
56 | enum:[
57 | 2,4,6,8,10,12,14,16,18,20,22,24
58 | ]
59 | }
60 | }
61 | },
62 | {
63 | type:'Col',
64 | title:'栅格单元',
65 | can_place:true,
66 | props:{
67 | span:6,
68 | style:{
69 | minHeight:30,
70 | },
71 | },
72 | config:{
73 | span:{
74 | text:"栅格",
75 | enum:[
76 | 2,4,6,8,10,12,14,16,18,20,22,24
77 | ]
78 | }
79 | }
80 | }
81 | ],
82 | props:{
83 |
84 | },
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/src/components/es5/image.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _antd = require('antd');
8 |
9 | var IMAGE_PREFIX = 'http://img.souche.com/';
10 |
11 | /**
12 | * 从 antd 的 Upload 获取图片地址
13 | */
14 | function getImagesUrl(files) {
15 | var UPLOAD_ERRORS = [];
16 |
17 | // 过滤掉没有返回 response 的 file
18 | files = files.filter(function (file) {
19 | return !!file.response;
20 | });
21 |
22 | var images = files.map(function (file) {
23 | if (!file.response.success) {
24 | var failMessage = '[' + file.name + ']: ' + file.response.msg;
25 | UPLOAD_ERRORS.push(failMessage);
26 |
27 | return new Error(failMessage);
28 | }
29 |
30 | // 后台返回了完整的 url
31 | if (file.response.data && file.response.data.fullFilePath) {
32 | return file.response.data.fullFilePath;
33 | }
34 |
35 | // 后台只返回了 path,需要自己拼域名
36 | if (file.response.data && file.response.data.relativeFilepath) {
37 | return IMAGE_PREFIX + file.response.data.relativeFilepath;
38 | }
39 | });
40 |
41 | // 错误提示
42 | if (UPLOAD_ERRORS.length > 0) {
43 | _antd.Modal.error({
44 | title: 'Error',
45 | content: UPLOAD_ERRORS.join('\n\n')
46 | });
47 | }
48 |
49 | // 过滤掉错误的文件
50 | images = images.filter(function (image) {
51 | return image instanceof Error === false;
52 | });
53 |
54 | // 只有一张图片的话,直接返回 url;空数组的话返回 ''
55 | if (images.length < 2) {
56 | return images[0] || '';
57 | }
58 |
59 | return images;
60 | }
61 |
62 | exports.default = {
63 | getImagesUrl: getImagesUrl
64 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "antd-visual-editor",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "antd": "^2.10.2",
7 | "assets-webpack-plugin": "^3.5.1",
8 | "babel-plugin-transform-object-assign": "^6.22.0",
9 | "html-webpack-plugin": "^2.28.0",
10 | "lodash": "^4.17.4",
11 | "rc-color-picker": "^1.1.5",
12 | "react": "^15.5.4",
13 | "react-dom": "^15.5.4",
14 | "react-drag-and-drop": "^2.4.0",
15 | "react-get-element": "^0.9.0",
16 | "react-native-listener": "^1.0.2",
17 | "react-router": "^2.8.1"
18 | },
19 | "devDependencies": {
20 | "babel-core": "^6.23.1",
21 | "babel-loader": "^6.3.2",
22 | "babel-plugin-import": "^1.1.1",
23 | "babel-plugin-transform-class-properties": "^6.22.0",
24 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
25 | "babel-plugin-transform-runtime": "^6.22.0",
26 | "babel-preset-es2015": "6.22.0",
27 | "babel-preset-react": "^6.23.0",
28 | "babel-preset-stage-3": "^6.24.1",
29 | "babel-runtime": "^6.22.0",
30 | "css-loader": "0.26.1",
31 | "eslint": "^3.17.1",
32 | "extract-text-webpack-plugin": "^v2.0.0-rc.1",
33 | "file-loader": "^0.10.0",
34 | "glob": "^7.1.1",
35 | "html-webpack-plugin": "^2.26.0",
36 | "less": "^2.7.2",
37 | "less-loader": "^2.2.3",
38 | "react-hot-loader": "^3.0.0-beta.6",
39 | "style-loader": "0.13.1",
40 | "url-loader": "0.5.7",
41 | "webpack": "^2.2.1",
42 | "webpack-cleanup-plugin": "^0.4.2",
43 | "webpack-dashboard": "^0.3.0",
44 | "webpack-dev-server": "^2.4.1"
45 | },
46 | "scripts": {
47 | "i": "npm install",
48 | "build": "webpack --config webpack.production.config.js --progress --profile --colors",
49 | "start": "webpack-dev-server --progress --profile --colors"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/webpack.loaders.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | {
3 | test: /\.jsx$/,
4 | loader: "babel-loader",
5 | query: {
6 | presets: [
7 | 'es2015',
8 | 'react'
9 | ].map(dep => require.resolve(`babel-preset-${dep}`)),
10 | plugins: [
11 | 'transform-runtime',
12 | 'transform-class-properties',
13 | ].map(dep => require.resolve(`babel-plugin-${dep}`))
14 | }
15 | },
16 | {
17 | test: /\.js$/,
18 | exclude: /(node_modules|bower_components|public\/)/,
19 | loader: "babel-loader"
20 | },
21 | {
22 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
23 | exclude: /(node_modules|bower_components)/,
24 | loader: "file-loader"
25 | },
26 | {
27 | test: /\.(woff|woff2)$/,
28 | exclude: /(node_modules|bower_components)/,
29 | loader: "url-loader?prefix=font/&limit=5000"
30 | },
31 | {
32 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
33 | exclude: /(node_modules|bower_components)/,
34 | loader: "url-loader?limit=10000&mimetype=application/octet-stream"
35 | },
36 | {
37 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
38 | exclude: /(node_modules|bower_components)/,
39 | loader: "url-loader?limit=10000&mimetype=image/svg+xml"
40 | },
41 | {
42 | test: /\.gif/,
43 | exclude: /(node_modules|bower_components)/,
44 | loader: "url-loader?limit=10000&mimetype=image/gif"
45 | },
46 | {
47 | test: /\.jpg/,
48 | exclude: /(node_modules|bower_components)/,
49 | loader: "url-loader?limit=10000&mimetype=image/jpg"
50 | },
51 | {
52 | test: /\.png/,
53 | exclude: /(node_modules|bower_components)/,
54 | loader: "url-loader?limit=10000&mimetype=image/png"
55 | }
56 | ];
57 |
--------------------------------------------------------------------------------
/webpack.production.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const loaders = require('./webpack.loaders');
4 | const HtmlWebpackPlugin = require('html-webpack-plugin');
5 | const WebpackCleanupPlugin = require('webpack-cleanup-plugin');
6 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
7 | const glob = require('glob');
8 | loaders.push({
9 | test: /\.css$/,
10 | loaders: ['style-loader', 'css-loader?importLoaders=1'],
11 | exclude: ['node_modules']
12 | });
13 |
14 | loaders.push({
15 | test: /\.less$/,
16 | loaders: ['style-loader', 'css-loader?importLoaders=1', 'less-loader'],
17 | exclude: ['node_modules']
18 | });
19 |
20 | const srcFiles = glob.sync('./src/index.jsx');
21 | const entries = {};
22 | srcFiles.forEach(entry => {
23 | let name = entry.replace('./src/', '').replace('.jsx', '').replace('.js', '');
24 |
25 | entries[name] = entry;
26 | });
27 | module.exports = {
28 | entry: entries,
29 | output: {
30 | publicPath: './',
31 | path: path.join(__dirname, './docs'),
32 | filename: '[name]_[hash].js'
33 | },
34 | resolve: {
35 | extensions: ['.js', '.jsx']
36 | },
37 | module: {
38 | loaders
39 | },
40 | plugins: [
41 | new webpack.DefinePlugin({
42 | 'process.env': {
43 | NODE_ENV: '"production"'
44 | }
45 | }),
46 | new webpack.optimize.UglifyJsPlugin({
47 | compress: {
48 | warnings: false,
49 | screw_ie8: true,
50 | drop_console: false,
51 | drop_debugger: false
52 | }
53 | }),
54 | new webpack.optimize.OccurrenceOrderPlugin(),
55 | new ExtractTextPlugin({
56 | filename: 'style.css',
57 | allChunks: true
58 | }),
59 | new HtmlWebpackPlugin({
60 | title:"Antd Editor",
61 | template:"public/template.html"
62 | })
63 | ]
64 | };
65 |
--------------------------------------------------------------------------------
/src/pages/coms/Table_Data.js:
--------------------------------------------------------------------------------
1 |
2 | var _ = require('lodash');
3 | import Common_Style from './common/style.config';
4 |
5 | var com = {
6 | "type":"Table",
7 | "title":"纯数据表格",
8 | "props":{
9 | "columns":[
10 | {
11 | title: '姓名',
12 | dataIndex: 'name',
13 | key: 'name',
14 | }, {
15 | title: '年龄',
16 | dataIndex: 'age',
17 | key: 'age',
18 | }, {
19 | title: '住址',
20 | dataIndex: 'address',
21 | key: 'address',
22 | }
23 | ],
24 | "dataSource":[{
25 | name: '胡彦斌',
26 | age: 32,
27 | address: '西湖区湖底公园1号'
28 | },{
29 | name: '胡彦斌',
30 | age: 32,
31 | address: '西湖区湖底公园1号'
32 | }],
33 | bordered:true,
34 | size:"small",
35 | style:{
36 | margin:"0px",
37 | padding:"0px"
38 | },
39 | showRowSelection:false,
40 | pagination:false,
41 | rowSelection:{
42 | type:'relative',
43 | target:'showRowSelection',
44 | true:{
45 | selectedRowKeys:[],
46 | onChange:(selectedRowKeys)=>{
47 |
48 | }
49 | },
50 | false:null
51 | }
52 | },
53 | config:{
54 | columns:{
55 | text:"列管理",
56 | enumobject:[
57 | {
58 | title: '列文本',
59 | dataIndex: 'title',
60 | type:"String"
61 | },
62 | {
63 | title: '列key',
64 | dataIndex: 'dataIndex',
65 | type:"String"
66 | }
67 | ]
68 | },
69 | dataSource:{
70 | text:"值管理",
71 | enumobject:{
72 | type:'relative_props_object',
73 | target:'columns'
74 | }
75 | },
76 | showRowSelection:{
77 | text:"是否显示选择框",
78 | type:'Boolean'
79 | },
80 | pagination:{
81 | text:"是否显示分页",
82 | type:'Boolean'
83 | },
84 | style:{
85 | padding:{
86 | text:"内间距",
87 | type:"4-value"
88 | },
89 | margin:{
90 | text:"外边距",
91 | type:"4-value"
92 | },
93 | ...Common_Style
94 | },
95 | }
96 |
97 | };
98 | export default com;
99 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | const webpack = require('webpack');
3 | const path = require('path');
4 | const loaders = require('./webpack.loaders.js');
5 | const HtmlWebpackPlugin = require('html-webpack-plugin');
6 | const DashboardPlugin = require('webpack-dashboard/plugin');
7 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
8 | const glob = require('glob');
9 |
10 | const HOST = process.env.HOST || "127.0.0.1";
11 | const PORT = process.env.PORT || "3000";
12 |
13 | loaders.push({
14 | test: /\.css$/,
15 | loaders: ['style-loader', 'css-loader?importLoaders=1'],
16 | exclude: ['node_modules']
17 | });
18 |
19 | loaders.push({
20 | test: /\.less$/,
21 | loaders: ['style-loader', 'css-loader?importLoaders=1', 'less-loader'],
22 | exclude: ['node_modules']
23 | });
24 |
25 |
26 | const srcFiles = glob.sync('./src/**/*.+(jsx|js)');
27 | const entries = {};
28 | srcFiles.forEach(entry => {
29 | let name = entry.replace('./src/', '').replace('.jsx', '').replace('.js', '');
30 |
31 | entries[name] = entry;
32 | });
33 | // const srcFiles2 = glob.sync('./node_modules/@souche-f2e/**/*.jsx');
34 | // srcFiles2.forEach(entry => {
35 | // let name = entry.replace('./node_modules/', '').replace('.jsx', '');
36 | //
37 | // entries[name] = entry;
38 | // });
39 |
40 | // const htmls = srcFiles.map(entry => {
41 | // let name = entry.replace('./pages/', '').replace('.jsx', '');
42 | //
43 | // return new HtmlWebpackPlugin({
44 | // filename: `${name}.html`,
45 | // template: './template.html',
46 | // chunks: [ name ]
47 | // })
48 | // });
49 | console.log(entries)
50 | module.exports = {
51 | entry: entries,
52 | devtool: process.env.WEBPACK_DEVTOOL || 'eval-source-map',
53 | output: {
54 | publicPath: '/',
55 | path: path.join(__dirname, 'public'),
56 | filename: '[name].js'
57 | },
58 | resolve: {
59 | extensions: ['.js', '.jsx']
60 | },
61 | module: {
62 | loaders
63 | },
64 | devServer: {
65 | contentBase: "./public",
66 | // do not print bundle build stats
67 | noInfo: true,
68 | // enable HMR
69 | hot: true,
70 | // embed the webpack-dev-server runtime into the bundle
71 | inline: true,
72 | // serve index.html in place of 404 responses to allow HTML5 history
73 | historyApiFallback: true,
74 | port: PORT,
75 | host: HOST,
76 |
77 | },
78 |
79 | plugins: [
80 | new webpack.NoEmitOnErrorsPlugin(),
81 | new webpack.HotModuleReplacementPlugin(),
82 | new ExtractTextPlugin({
83 | filename: 'style.css',
84 | allChunks: true
85 | }),
86 | new HtmlWebpackPlugin({
87 | title:"Antd Editor",
88 | template:"public/template.html"
89 | }),
90 | new DashboardPlugin()
91 | ]
92 | };
93 |
--------------------------------------------------------------------------------
/src/pages/coms/Row8.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "type":"Row",
3 | "title":"8列栅格",
4 | childrens:[
5 | {
6 | type:'Col',
7 | title:'栅格单元',
8 | can_place:true,
9 | props:{
10 | span:3,
11 | style:{
12 | minHeight:30,
13 | },
14 | },
15 | config:{
16 | span:{
17 | text:"栅格",
18 | enum:[
19 | 2,4,6,8,10,12,14,16,18,20,22,24
20 | ]
21 | }
22 | }
23 | },
24 | {
25 | type:'Col',
26 | title:'栅格单元',
27 | can_place:true,
28 | props:{
29 | span:3,
30 | style:{
31 | minHeight:30,
32 | },
33 | },
34 | config:{
35 | span:{
36 | text:"栅格",
37 | enum:[
38 | 2,4,6,8,10,12,14,16,18,20,22,24
39 | ]
40 | }
41 | }
42 | },
43 | {
44 | type:'Col',
45 | title:'栅格单元',
46 | can_place:true,
47 | props:{
48 | span:3,
49 | style:{
50 | minHeight:30,
51 | },
52 | },
53 | config:{
54 | span:{
55 | text:"栅格",
56 | enum:[
57 | 2,4,6,8,10,12,14,16,18,20,22,24
58 | ]
59 | }
60 | }
61 | },
62 | {
63 | type:'Col',
64 | title:'栅格单元',
65 | can_place:true,
66 | props:{
67 | span:3,
68 | style:{
69 | minHeight:30,
70 | },
71 | },
72 | config:{
73 | span:{
74 | text:"栅格",
75 | enum:[
76 | 2,4,6,8,10,12,14,16,18,20,22,24
77 | ]
78 | }
79 | }
80 | },
81 | {
82 | type:'Col',
83 | title:'栅格单元',
84 | can_place:true,
85 | props:{
86 | span:3,
87 | style:{
88 | minHeight:30,
89 | },
90 | },
91 | config:{
92 | span:{
93 | text:"栅格",
94 | enum:[
95 | 2,4,6,8,10,12,14,16,18,20,22,24
96 | ]
97 | }
98 | }
99 | },
100 | {
101 | type:'Col',
102 | title:'栅格单元',
103 | can_place:true,
104 | props:{
105 | span:3,
106 | style:{
107 | minHeight:30,
108 | },
109 | },
110 | config:{
111 | span:{
112 | text:"栅格",
113 | enum:[
114 | 2,4,6,8,10,12,14,16,18,20,22,24
115 | ]
116 | }
117 | }
118 | },
119 | {
120 | type:'Col',
121 | title:'栅格单元',
122 | can_place:true,
123 | props:{
124 | span:3,
125 | style:{
126 | minHeight:30,
127 | },
128 | },
129 | config:{
130 | span:{
131 | text:"栅格",
132 | enum:[
133 | 2,4,6,8,10,12,14,16,18,20,22,24
134 | ]
135 | }
136 | }
137 | },
138 | {
139 | type:'Col',
140 | title:'栅格单元',
141 | can_place:true,
142 | props:{
143 | span:3,
144 | style:{
145 | minHeight:30,
146 | },
147 | },
148 | config:{
149 | span:{
150 | text:"栅格",
151 | enum:[
152 | 2,4,6,8,10,12,14,16,18,20,22,24
153 | ]
154 | }
155 | }
156 | }
157 | ],
158 | props:{
159 |
160 | },
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/src/pages/coms/Table.js:
--------------------------------------------------------------------------------
1 |
2 | var _ = require('lodash');
3 | import Common_Style from './common/style.config';
4 |
5 | var com = {
6 | "type":"Table",
7 | "title":"表格",
8 | "sub_type":"table_container",
9 | "props":{
10 | "columns":[{
11 | title: '姓名',
12 | dataIndex: 'name',
13 | key: 'name',
14 | childrens:[{
15 | type:"div",
16 | title:"通用布局块",
17 | is_native:true,
18 | can_place:true,
19 | props:{
20 | style:{
21 | minHeight:20,
22 | padding:"0px",
23 | }
24 | },
25 | config:{
26 | padding:{
27 | text:"内间距",
28 | type:"4-value"
29 | },
30 | margin:{
31 | text:"外边距",
32 | type:"4-value"
33 | },
34 | ...Common_Style
35 | }
36 | }]
37 |
38 | }, {
39 | title: '年龄',
40 | dataIndex: 'age',
41 | key: 'age',
42 | childrens:[{
43 | type:"div",
44 | title:"通用布局块",
45 | is_native:true,
46 | can_place:true,
47 | props:{
48 | style:{
49 | minHeight:20,
50 | padding:"0px",
51 | }
52 | },
53 | config:{
54 | padding:{
55 | text:"内间距",
56 | type:"4-value"
57 | },
58 | margin:{
59 | text:"外边距",
60 | type:"4-value"
61 | },
62 | ...Common_Style
63 | }
64 | }]
65 | }, {
66 | title: '住址',
67 | dataIndex: 'address',
68 | key: 'address',
69 | childrens:[{
70 | type:"div",
71 | title:"通用布局块",
72 | is_native:true,
73 | can_place:true,
74 | props:{
75 | style:{
76 | minHeight:20,
77 | padding:"0px",
78 | }
79 | },
80 | config:{
81 | padding:{
82 | text:"内间距",
83 | type:"4-value"
84 | },
85 | margin:{
86 | text:"外边距",
87 | type:"4-value"
88 | },
89 | ...Common_Style
90 | }
91 | }]
92 | }],
93 | "dataSource":[{
94 | key: '1',
95 | name: '胡彦斌',
96 | age: 32,
97 | address: '西湖区湖底公园1号'
98 | }],
99 | bordered:true,
100 | size:"small",
101 | style:{
102 | margin:"0px",
103 | padding:"0px"
104 | },
105 | showRowSelection:false,
106 | pagination:false,
107 | rowSelection:{
108 | type:'relative',
109 | target:'showRowSelection',
110 | true:{
111 | selectedRowKeys:[],
112 | onChange:(selectedRowKeys)=>{
113 |
114 | }
115 | },
116 | false:null
117 | }
118 | },
119 | config:{
120 | columns:{
121 | text:"列管理",
122 | enumobject:[
123 | {
124 | title: '列文本',
125 | dataIndex: 'title',
126 | type:"String"
127 | },
128 | {
129 | title: '列key',
130 | dataIndex: 'dataIndex',
131 | type:"String"
132 | }
133 | ]
134 | },
135 |
136 | showRowSelection:{
137 | text:"是否显示选择框",
138 | type:'Boolean'
139 | },
140 | pagination:{
141 | text:"是否显示分页",
142 | type:'Boolean'
143 | },
144 | style:{
145 | padding:{
146 | text:"内间距",
147 | type:"4-value"
148 | },
149 | margin:{
150 | text:"外边距",
151 | type:"4-value"
152 | },
153 | ...Common_Style
154 | },
155 | }
156 |
157 | };
158 | export default com;
159 |
--------------------------------------------------------------------------------
/src/components/RJMenu.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Menu, Icon } from 'antd';
3 |
4 | const PropTypes = React.PropTypes;
5 |
6 | class RJMenu extends React.Component {
7 | constructor() {
8 | super();
9 | /**
10 | this.state = {
11 | data:{
12 | mode:"horizontal",
13 | items:[{
14 | key:'1',
15 | icon:'mail',
16 | title:'第一级菜单',
17 | disabled:false,
18 | link:'#',
19 | subs:[
20 | {
21 | key:'',
22 | icon:'mail',
23 | title:'第一级菜单',
24 | type:"menu"
25 | },
26 | {
27 | type:'group',
28 | title:'分组1',
29 | subs:[
30 | {
31 | key:'',
32 | icon:'mail',
33 | title:'第一级菜单'
34 | },
35 | {
36 | key:'',
37 | icon:'mail',
38 | title:'第一级菜单'
39 | }
40 | ]
41 | },
42 | {
43 | type:'group',
44 | title:'分组2',
45 | subs:[
46 | {
47 | key:'',
48 | icon:'mail',
49 | title:'第一级菜单'
50 | },
51 | {
52 | key:'',
53 | icon:'mail',
54 | title:'第一级菜单'
55 | }
56 | ]
57 | }
58 | ]
59 | },{
60 | key:'2',
61 | icon:'plus',
62 | title:'第1级菜单',
63 | disabled:false,
64 | link:'#',
65 | },{
66 | key:'3',
67 | title:'第1级菜单',
68 | disabled:true,
69 | link:'#',
70 | }]
71 | }
72 | }
73 | */
74 | }
75 |
76 | render() {
77 | var data = this.props;
78 | return (
79 |
118 | );
119 | }
120 | }
121 |
122 | export default RJMenu;
123 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 一个 可视化实时渲染的 ant-design 页面搭建工具
2 |
3 | > 此项目是上古项目(2017年),代码基本很难维护,现在发布出来仅供参考思路,感兴趣的可以根据原理重构一版,实现一个更完备的可视化编辑
4 |
5 |
6 | ## 线上实例
7 |
8 | https://yu-tou.github.io/antd-visual-editor/
9 |
10 | 托管在 github,第一次加载会比较慢
11 |
12 | 截图:
13 |
14 | 
15 |
16 | ## 运行
17 |
18 | ```bash
19 | npm run build;
20 | npm run start;
21 | # (已修复)因为我不太懂 webpack ,不太会配置,这个项目修改代码后实时生效还有问题。。求 pr
22 | ```
23 |
24 | ## 特性
25 |
26 | * 可视化编辑,同时实时生成结果代码,还可以单独预览
27 | * 丰富的数据编辑能力,可以编辑组件的二维属性
28 | * 组件可嵌套
29 | * 自适应布局
30 | * 除了 antd 的组件,还有一些原生 html 元素可使用
31 |
32 | ## 原理解析
33 |
34 | ### 1.如何实现实时编辑
35 |
36 | 第一步,抽象整个可视化工作台的数据表达,无非是放了一个什么组件在什么位置,这个组件的父组件是谁,这个组件的属性是什么
37 |
38 | 如下图:
39 |
40 | 
41 |
42 | 一个组件的基础定义:
43 |
44 | ```
45 | title 组件名
46 | type 组件类型(组件真实类名)
47 | can_place 组件是否可以包含子组件
48 | children 组件的子组件,数组类型
49 | is_native 组件是否是原生 html 元素
50 | config 组件可用的配置信息
51 | props 组件配置信息的值,包含样式和属性等
52 | ```
53 |
54 | 根据这些值,我们就可以渲染和编辑组件了,编辑组件后,
55 | 会有一个大的表示画布当前状态的数据结构存储到 state 中,
56 | 另外,有一个方法可以根据这个数据结构渲染出整个画布,
57 | 所以每次有任何编辑动作之后,我们会触发 forceUpdate,重新绘制画布
58 | 也就是说,添加组件,编辑属性,和画布的显示是分离的,中间由一个大的数据结构连接(就是图片里这个)
59 |
60 | ### 2.如何反向生成 react 代码
61 |
62 | 根据上图中的数据结构,反向遍历,可轻易的生成 React 代码
63 |
64 | ### 3.如何定义组件可用的配置
65 |
66 | 在 pages/coms/xxx 里面定义一个组件的可用配置,然后即可在主界面中选择组件后在右侧"属性编辑区"中编辑属性。
67 |
68 | 来看看我们可以定义哪些属性吧
69 |
70 | 以一个按钮为例
71 |
72 | ```javascript
73 | export default {
74 | "type": "Button",
75 | "title": "按钮",
76 | "props": {
77 | type: 'primary', // 定义可以配置的 props
78 | content: '按钮一只', // 定义可以配置的 props
79 | style: { // 定义可以配置的样式
80 | margin: "0px 10px 0px 0px"
81 | }
82 | },
83 | config: { // 可用的配置项
84 | type: { // type 这个配置的描述
85 | text: "主题", // 配置的标题
86 | enum: [ // 可用的枚举,配置时会显示成下拉框
87 | 'primary',
88 | 'default',
89 | 'dashed',
90 | 'danger'
91 | ]
92 | },
93 | icon: {
94 | text: "图标",
95 | },
96 | content: {
97 | text: '文案',
98 | },
99 | style: { // 可用的样式配置
100 | width: {
101 | text: "宽度",
102 | },
103 | margin: {
104 | text: "外边距",
105 | type: "4-value" // 一种定制类型,会渲染成 4 个输入框
106 | }
107 | }
108 | },
109 | }
110 | ```
111 |
112 | 这是最基本的配置项,只能适用于最基本的组件,但是遇到像 table 或者 Breadcrumb 这种组件就不行了
113 |
114 | ### 4.高级配置(二维数据)
115 |
116 | 以 Breadcrumb 为例,他有一个数据源的属性,数据源是一个数组+对象的混合表达,这种组件不少,应该如何定义呢
117 |
118 | ```javascript
119 | export default {
120 | "type":"Breadcrumb",
121 | "title":"面包屑",
122 | props:{
123 | routes:[ // 这里是数据源的属性,和默认值
124 | {
125 | breadcrumbName:"一级目录",
126 | path:"#",
127 | key:1
128 | },
129 | {
130 | breadcrumbName:"二级目录",
131 | path:"#",
132 | key:2
133 | }
134 | ]
135 | },
136 | config:{
137 | routes:{ // 如何表达这个属性应该如何配置
138 | text:"项目配置",
139 | enumobject:[{ // 一种新的类型,enumobject,对象枚举
140 | key:1,
141 | dataIndex:"breadcrumbName", // 枚举的对象的第一个 key 是什么
142 | title:"显示文本", // 枚举的对象的第一个 key 的文本描述
143 | type:'String', // 枚举的对象的第一个 key 的类型
144 | },{
145 | key:2,
146 | dataIndex:"path", // 枚举的对象的第二个 key 是什么
147 | title:"链接", // 枚举的对象的第二个 key 的文本描述
148 | type:'String', //枚举的对象的第二个 key 的类型
149 | }]
150 | }
151 | }
152 | }
153 | ```
154 |
155 | 最终的属性编辑区:
156 |
157 | 
158 |
159 | 即可边界对象枚举属性
160 |
161 | ### 5.更复杂的组件
162 |
163 | 大家会发现,table 这种组件和上述的组件都不太一样,首先看纯数据表格
164 |
165 | 
166 |
167 | 其实这里还好,只是 table 有两个属性,一个表达列的数据,一个表达行的数据,我们只需要两个对象枚举即可
168 |
169 | ```javascript
170 | {
171 | config:{
172 | columns:{
173 | text:"列管理",
174 | enumobject:[
175 | {
176 | title: '列文本',
177 | dataIndex: 'title',
178 | type:"String"
179 | },
180 | {
181 | title: '列key',
182 | dataIndex: 'dataIndex',
183 | type:"String"
184 | }
185 | ]
186 | },
187 | dataSource:{
188 | text:"值管理",
189 | enumobject:{
190 | type:'relative_props_object',
191 | target:'columns'
192 | }
193 | }
194 | }
195 | }
196 | ```
197 |
198 | 这里实现了一个 关联,可以把 dataSource 的配置和 columns 关联起来 (relative_props_object)
199 |
200 | ### 6.更更复杂的表格
201 |
202 | 如果只是数据,还好,
203 | 但是 table 里可以还可以嵌套其他组件,每行每列,想想是不是头疼。。如下图
204 |
205 | 
206 |
207 | table 的每个 column 其实可以定义内部显示的元素,我们在默认值里就给他塞一个空的 layout 进去,
208 | 这样之后这里就会变成一个可以放置其他子元素的坑,具体不展开了,这里的逻辑比较复杂。
209 |
210 |
211 |
--------------------------------------------------------------------------------
/src/components/RJImgUploader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Upload, Icon, Modal } from 'antd';
3 |
4 | import imageUtil from './es5/image.js';
5 |
6 | class RJImgUploader extends React.Component {
7 | state = {
8 | fileList: [],
9 | previewVisible: false,
10 | previewImage: '',
11 | updatedDefaultValue: null,
12 | };
13 |
14 | static propTypes = {
15 | onChange: React.PropTypes.func.isRequired,
16 | num: React.PropTypes.number,
17 | action: React.PropTypes.string.isRequired,
18 | defaultValue: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.array]),
19 | onRemove: React.PropTypes.func,
20 | };
21 |
22 | componentDidMount() {
23 | let fileList = this.getDefauleFilelist(this.props.defaultValue);
24 | this.setState({
25 | fileList: fileList,
26 | });
27 | }
28 |
29 | onChange = ({ fileList }) => {
30 | // 当为done的时候,如果success 为false,不应该加入fileList中
31 | let files = fileList.filter(
32 | file => (file.status === 'done' && file.response && file.response.success) || file.status === 'uploading'
33 | );
34 | this.setState({ fileList: files });
35 |
36 | this.props.onChange(imageUtil.getImagesUrl(fileList));
37 | };
38 |
39 | handleCancel = () =>
40 | this.setState({
41 | previewVisible: false,
42 | });
43 |
44 | handlePreview = file => {
45 | this.setState({
46 | previewImage: file.url || file.thumbUrl,
47 | previewVisible: true,
48 | });
49 | };
50 |
51 | getDefauleFilelist = defaultValue => {
52 | if (!defaultValue) {
53 | return [];
54 | }
55 |
56 | if (Array.isArray(defaultValue)) {
57 | // 数组情况 ['http://img.souche.com/a.png', 'http://img.souche.com/b.png']
58 | return defaultValue.map((value, index) => {
59 | return {
60 | /**
61 | * uid 不可为0
62 | * 否则在删除 uid 为0的图片时,会清空整个 fileList
63 | */
64 | uid: index + 1,
65 | status: 'done',
66 | url: value,
67 | response: {
68 | success: true,
69 | },
70 | };
71 | });
72 | } else {
73 | // 字符串情况 'http://img.souche.com/a.png,http://img.souche.com/b.png'
74 | if (defaultValue.indexOf(',') !== -1) {
75 | let t = defaultValue.split(',');
76 |
77 | return t.map((value, index) => {
78 | return {
79 | uid: index + 1,
80 | status: 'done',
81 | url: value,
82 | response: {
83 | success: true,
84 | },
85 | };
86 | });
87 | } else {
88 | // 'http://img.souche.com/a.png'
89 | return [
90 | {
91 | uid: -1,
92 | status: 'done',
93 | url: defaultValue,
94 | response: {
95 | success: true,
96 | },
97 | },
98 | ];
99 | }
100 | }
101 | };
102 |
103 | updateDefaultValue = (defaultValue, url) => {
104 | if (!defaultValue) {
105 | return null;
106 | }
107 |
108 | if (Array.isArray(defaultValue)) {
109 | // 数组情况 ['http://img.souche.com/a.png', 'http://img.souche.com/b.png']
110 | return defaultValue.filter(defalutURL => {
111 | return defalutURL !== url;
112 | });
113 | } else {
114 | // 字符串情况 'http://img.souche.com/a.png,http://img.souche.com/b.png'
115 | if (defaultValue.indexOf(',') !== -1) {
116 | let t = defaultValue.split(',');
117 | return t
118 | .filter(defalutURL => {
119 | return defalutURL !== url;
120 | })
121 | .toString();
122 | } else {
123 | // 'http://img.souche.com/a.png'
124 | if (defaultValue === url) {
125 | return null;
126 | } else {
127 | return defaultValue;
128 | }
129 | }
130 | }
131 | };
132 |
133 | handleRemove = file => {
134 | this.props.onRemove({ file, updatedDefaultValues: this.updateDefaultValue(this.props.defaultValue, file.url) });
135 | };
136 |
137 | render() {
138 | const { previewVisible, previewImage, fileList } = this.state;
139 |
140 | const UploadButton = (
141 |
145 | );
146 |
147 | let action = '';
148 | if (this.props.action.indexOf('http') === 0) {
149 | action = this.props.action;
150 | } else {
151 | action = window.SERVER_URL + this.props.action;
152 | }
153 |
154 | let num = this.props.num;
155 | let accept = this.props.accept || 'image/*';
156 |
157 | return (
158 |
159 |
170 | {fileList.length >= num ? null : UploadButton}
171 |
172 |
173 |
174 |
175 |
176 | );
177 | }
178 | }
179 |
180 | export default RJImgUploader;
181 |
--------------------------------------------------------------------------------
/src/pages/ant-coms.js:
--------------------------------------------------------------------------------
1 | import Layout from './coms/Layout';
2 | import Layout_Header from './coms/Layout.Header';
3 | import Layout_Sider from './coms/Layout.Sider';
4 | import Layout_Content from './coms/Layout.Content';
5 | import Breadcrumb from './coms/Breadcrumb';
6 | import Form from './coms/Form';
7 | import Form_Item from './coms/Form.Item';
8 | import Form_Item_Inline from './coms/Form.Item.Inline';
9 | import Form_Item_Submit from './coms/Form.Item.Submit';
10 | import Button from './coms/Button';
11 | import Input from './coms/Input';
12 | import DatePicker from './coms/DatePicker';
13 | import Radio_Group from './coms/Radio.Group';
14 | import Badge from './coms/Badge';
15 | import Calendar from './coms/Calendar';
16 | import Tag from './coms/Tag';
17 | import Table from './coms/Table';
18 | import Table_Data from './coms/Table_Data';
19 | import InputNumber from './coms/InputNumber';
20 | import Switch from './coms/Switch';
21 | import Row2 from './coms/Row2';
22 | import Row4 from './coms/Row4';
23 | import Row8 from './coms/Row8';
24 | import Table_Column from './coms/Table.Column';
25 | import Modal from './coms/Modal';
26 | import div from './coms/div';
27 |
28 | import RJImageUploader from './coms/RJImgUploader';
29 | import RJMenu from './coms/RJMenu';
30 | import RJPagination from './coms/RJPagination';
31 | import RJSelect from './coms/RJSelect';
32 |
33 | var _ = require('lodash');
34 |
35 | export default [
36 | {
37 | "group_title": "栅格组件(横向百分比)",
38 | "coms": [
39 | Row2,
40 | Row4,
41 | Row8
42 | ]
43 | },
44 | {
45 | "group_title": "布局容器组件",
46 | "coms": [
47 | div,
48 | Layout,
49 | Layout_Header,
50 | Layout_Sider,
51 | Layout_Content,
52 | {
53 | type: "div",
54 | title: "分割线",
55 | is_native: true,
56 | props: {
57 | style: {
58 | backgroundColor: "#bbb",
59 | height: '1px'
60 | }
61 | },
62 | config: {
63 | backgroundColor: {
64 | text: "背景色",
65 | },
66 | margin: {
67 | text: "外边距",
68 | type: "4-value"
69 | },
70 | }
71 | },
72 |
73 | ]
74 | },
75 | {
76 | "group_title": "导引组件",
77 | "coms": [
78 | Breadcrumb,
79 | RJMenu,
80 |
81 | {
82 | type: "Tabs",
83 | title: "Tabs",
84 | can_place: true,
85 | props: {},
86 | childrens: [
87 | {
88 | type: "Tabs.TabPane",
89 | title: "tab项",
90 | props: {
91 | tab: '测试',
92 | key: 1
93 | }
94 | }
95 | ]
96 | },
97 | {
98 | type: "Tabs.TabPane",
99 | title: "tab项",
100 | props: {
101 | tab: '测试'
102 | }
103 | }
104 | ]
105 | },
106 | {
107 | "group_title": "表单容器和表单项",
108 | "coms": [
109 | Form,
110 | Form_Item,
111 | Form_Item_Submit,
112 | (() => {
113 | var inlineForm = _.cloneDeep(Form);
114 | inlineForm.title = '内联的表单容器'
115 | inlineForm.props.layout = 'inline'
116 | return inlineForm
117 | })(),
118 | Form_Item_Inline,
119 | Button,
120 | Input,
121 | InputNumber,
122 | RJSelect,
123 | Radio_Group,
124 | DatePicker,
125 | {
126 | type: "DatePicker.MonthPicker",
127 | title: "月份选择器",
128 | },
129 | {
130 | type: "DatePicker.RangePicker",
131 | title: "日期范围选择器",
132 | },
133 | {
134 | type: "TimePicker",
135 | title: "时间选择器",
136 | },
137 | Switch,
138 | RJImageUploader
139 | ]
140 | },
141 | {
142 | "group_title": "数据组件/图片/标签",
143 | "coms": [
144 | Table,
145 | Table_Data,
146 | Badge,
147 | Calendar,
148 | Tag,
149 | RJPagination,
150 | {
151 | type: "img",
152 | title: "图片",
153 | is_native: true,
154 | props: {
155 | src: "http://dummyimage.com/200x100/894FC4/FFF.png&text=!",
156 | style: {
157 | width: 50,
158 | height: 50
159 | }
160 | },
161 | config: {
162 | style: {
163 | width: {
164 | text: '宽度'
165 | },
166 | height: {
167 | text: '高度'
168 | },
169 | margin: {
170 | text: "外边距",
171 | type: "4-value"
172 | }
173 | }
174 | }
175 | }
176 | ]
177 | },
178 | {
179 | group_title: "弹窗",
180 | coms: [
181 | Modal
182 | ]
183 | }, {
184 | group_title: "文本组件/标题",
185 | coms: [
186 | {
187 | type: "a",
188 | title: "链接",
189 | is_native: true,
190 | props: {
191 | content: "链接",
192 | style: {
193 | marginLeft: "10px"
194 | }
195 | },
196 | config: {
197 | content: {
198 | text: "文本内容"
199 | },
200 | style: {
201 | marginLeft: {
202 | text: "左边距"
203 | },
204 | lineHeight: {
205 | text: "行高"
206 | },
207 | fontSize: {
208 | text: "字体大小"
209 | },
210 | color: {
211 | text: '字体颜色',
212 | type: 'color'
213 | },
214 | }
215 | }
216 | },
217 | {
218 | type: "h1",
219 | title: "标题H1",
220 | is_native: true,
221 | props: {
222 | content: "标题",
223 | style: {}
224 | },
225 | config: {
226 | content: {
227 | text: "文本内容"
228 | },
229 | style: {
230 | color: {
231 | text: '字体颜色',
232 | type: 'color'
233 | },
234 | }
235 | }
236 | },
237 | {
238 | type: "h2",
239 | title: "标题H2",
240 | is_native: true,
241 | props: {
242 | content: "标题",
243 | style: {}
244 | },
245 | config: {
246 | content: {
247 | text: "文本内容"
248 | },
249 | style: {
250 | color: {
251 | text: '字体颜色',
252 | type: 'color'
253 | },
254 | }
255 | }
256 | },
257 | {
258 | type: "h3",
259 | title: "标题H3",
260 | is_native: true,
261 | props: {
262 | content: "标题",
263 | style: {}
264 | },
265 | config: {
266 | content: {
267 | text: "文本内容"
268 | },
269 | style: {
270 | color: {
271 | text: '字体颜色',
272 | type: 'color'
273 | },
274 | }
275 | }
276 | },
277 | {
278 | type: "h4",
279 | title: "标题H4",
280 | is_native: true,
281 | props: {
282 | content: "标题",
283 | style: {}
284 | },
285 | config: {
286 | content: {
287 | text: "文本内容"
288 | },
289 | style: {
290 | color: {
291 | text: '字体颜色',
292 | type: 'color'
293 | },
294 | }
295 | }
296 | },
297 | {
298 | type: "h5",
299 | title: "标题H5",
300 | is_native: true,
301 | props: {
302 | content: "标题",
303 | style: {}
304 | },
305 | config: {
306 | content: {
307 | text: "文本内容"
308 | },
309 | style: {
310 | color: {
311 | text: '字体颜色',
312 | type: 'color'
313 | },
314 | }
315 | }
316 | },
317 | {
318 | type: "span",
319 | title: "内联文本",
320 | is_native: true,
321 | props: {
322 | content: "文本",
323 | style: {}
324 | },
325 | config: {
326 | content: {
327 | text: "文本内容"
328 | },
329 | style: {
330 | lineHeight: {
331 | text: "行高"
332 | },
333 | fontSize: {
334 | text: "字体大小"
335 | },
336 | color: {
337 | text: '字体颜色',
338 | type: 'color'
339 | },
340 | }
341 | }
342 | },
343 | {
344 | type: "div",
345 | title: "块状文本",
346 | is_native: true,
347 | props: {
348 | content: "文本",
349 | style: {}
350 | },
351 | config: {
352 | content: {
353 | text: "文本内容"
354 | },
355 | style: {
356 | fontSize: {
357 | text: "字体大小"
358 | },
359 | lineHeight: {
360 | text: "行高"
361 | },
362 | background: {
363 | text: '背景色',
364 | type: 'color'
365 | },
366 | color: {
367 | text: '字体颜色',
368 | type: 'color'
369 | },
370 | margin: {
371 | text: "外边距",
372 | type: '4-value'
373 | },
374 | padding: {
375 | text: "内边距",
376 | type: '4-value'
377 | }
378 | }
379 | }
380 | }
381 | ]
382 | }
383 | ]
384 |
--------------------------------------------------------------------------------
/src/pages/editor.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | var antd =require('./../components/index.jsx');
3 | import antComponents from './ant-coms.js';
4 | import { Collapse,Modal,Form,Table,Button } from 'antd';
5 | const Panel = Collapse.Panel;
6 | import { Tag,Input,InputNumber,Alert,Select,Checkbox } from 'antd';
7 | import _ from 'lodash';
8 | import NativeListener from 'react-native-listener';
9 | import ColorPicker from 'rc-color-picker';
10 |
11 | import com_div from './coms/div';
12 |
13 | import 'rc-color-picker/assets/index.css'
14 | class Editor extends Component {
15 | constructor() {
16 | super();
17 | this.state = {
18 | dependComponents:[],
19 | draggingData:null,
20 | modal_visible:true,
21 | resultJSX:'',
22 | editCom:{},
23 | value4EditResult:{}, // 属性编辑器中4值编辑的临时存储
24 | comNowIndex:1, // 用来给每个组件唯一编号
25 | activeCom:{},
26 | editTarget:null,
27 | showAddList:false,
28 | hasBeginEdit:false,
29 | isDragLayer:false,
30 | addListData:{
31 |
32 | },
33 | mouse_x:0,
34 | mouse_y:0,
35 | dragdiv_x:0,
36 | dragdiv_y:0,
37 | layer_x:0,
38 | layer_y:0,
39 | layer_show:false,
40 | layer_w:0,
41 | layer_h:0,
42 | layer_isTop:true,
43 | indent_space:'',
44 | data:[
45 | (function(){
46 | var div = _.cloneDeep(com_div);
47 | div.props.style.height = 200;
48 | return div;
49 | })()
50 | ]
51 | };
52 | var v = localStorage.getItem('cache_data');
53 | if(v){
54 | this.state.data = JSON.parse(v);
55 | }
56 |
57 | setTimeout(()=>{
58 | this.setState({
59 | hasBeginEdit:true
60 | })
61 | },3000)
62 | }
63 |
64 | _getComponent(types) {
65 | if(types.length==1){
66 | return antd[types[0]]
67 | }else{
68 | var lastT = types.pop();
69 | var com = this._getComponent(types)[lastT];
70 | return com;
71 | }
72 | }
73 | findCanDropTarget(target){
74 | if(target.className.indexOf('draggable')!=-1){
75 | return target;
76 | }else{
77 | return this.findCanDropTarget(target.parentNode);
78 | }
79 | }
80 | renderJSON(json){
81 | return (
82 | json.map((d,i)=>{
83 | d.id = this.state.comNowIndex++;
84 | if(d.hasDelete) return;
85 |
86 | var component;
87 | if(d.is_native){
88 | component = d.type;
89 | }else{
90 | component = this._getComponent(d.type.split('.'));
91 | this.state.dependComponents.push(d.type.split('.')[0]);
92 | }
93 |
94 | var props = {};
95 | d.props = d.props||{};
96 |
97 | if(d.can_place){
98 |
99 | props.className = 'draggable';
100 | props.onDragOver = (e)=>{
101 | e.preventDefault();
102 | }
103 | props.onDrop = (e)=>{
104 | e.preventDefault();
105 | e.stopPropagation();
106 | this.findCanDropTarget(e.target).className = this.findCanDropTarget(e.target).className.replace('isdroping','')
107 | var com = this.state.draggingData ;
108 | d.childrens = d.childrens?d.childrens:[];
109 | d.childrens.push(_.cloneDeep(com));
110 | this.forceUpdate();
111 | }
112 | props.onDragOver = (e)=>{
113 | e.preventDefault();
114 | if(this.findCanDropTarget(e.target).className.indexOf('isdroping')==-1){
115 | this.findCanDropTarget(e.target).className += (' isdroping')
116 | }
117 |
118 | }
119 | props.onDragLeave = (e)=>{
120 | e.preventDefault();
121 | this.findCanDropTarget(e.target).className = this.findCanDropTarget(e.target).className.replace('isdroping','')
122 | }
123 | }
124 | var outerProps = {};
125 | outerProps.onMouseOver = (e)=>{
126 | e.stopPropagation();
127 | e.preventDefault()
128 | if(!this.state.isDragLayer){
129 | this.showLayer(e.target,d);
130 | }
131 | }
132 | outerProps.onMouseLeave = (e)=>{
133 | e.stopPropagation();
134 |
135 | if(!this.state.isDragLayer){
136 | this.hideLayer();
137 | }
138 | }
139 | outerProps.onClick = (e)=>{
140 | e.stopPropagation();
141 | this.setState({
142 | editCom:d
143 | })
144 | this.forceUpdate();
145 | }
146 | var realProps = Object.assign({},d.props);
147 | for(var i in realProps){
148 | if(typeof(realProps[i]) == 'object' && realProps[i].type == 'relative'){
149 | realProps[i] = realProps[realProps[i].target]?realProps[i].true:realProps[i].false;
150 | }
151 | }
152 | if(d.sub_type == 'table_container') {
153 | realProps.columns.forEach((c)=>{
154 | if(c.childrens){
155 | c.render = ()=>{
156 | return this.renderJSON(c.childrens)
157 | }
158 | }else if(c.type == '图文') {
159 | c.render = ()=>{
160 | return
161 |

162 |
{c.title}
163 |
164 | }
165 | }else if(c.type == '图片'){
166 | c.render = ()=>{
167 | return
168 | }
169 | }else if(c.type == '链接'){
170 | c.render = ()=>{
171 | return 动作
172 | }
173 | }
174 | })
175 | }
176 | if(d.wrap_inner){
177 | return
178 | {React.createElement(
179 | component,
180 | realProps,
181 | [{
182 | d.props.content?[d.props.content]:(d.childrens?this.renderJSON(d.childrens):null)
183 | }
]
184 | )}
185 |
186 | }else if(d.wrap){
187 | return
188 |
189 | {React.createElement(
190 | component,
191 | realProps,
192 | d.props.content?[d.props.content]:(d.childrens?this.renderJSON(d.childrens):null)
193 | )}
194 |
195 |
196 | }else{
197 | Object.assign(realProps,props);
198 | return
199 | {React.createElement(
200 | component,
201 | realProps,
202 | d.props.content?[d.props.content]:(d.childrens?this.renderJSON(d.childrens):null)
203 | )}
204 |
205 | }
206 | })
207 | )
208 | }
209 | handleOk(){
210 | this.setState({
211 | modal_visible:false
212 | })
213 | }
214 | handleCancel(){
215 | this.setState({
216 | modal_visible:false
217 | })
218 | }
219 |
220 | showLayer(target,d) {
221 | var pos = this.getDOMPOS(target);
222 | this.setState({
223 | activeCom:d,
224 | layer_x:pos.x,
225 | layer_y:pos.y,
226 | layer_w:target.offsetWidth,
227 | layer_h:target.offsetHeight,
228 | layer_show:true,
229 | layer_isTop:true,
230 | dragdiv_x: target.offsetWidth+pos.x,
231 | dragdiv_y:target.offsetHeight+pos.y
232 | })
233 |
234 | document.getElementById("dragdiv").style.left = (target.offsetWidth+pos.x - 10) + 'px'
235 | document.getElementById("dragdiv").style.top = (target.offsetHeight+pos.y - 10) + 'px'
236 |
237 | }
238 |
239 | hideLayer(){
240 | this.setState({
241 | layer_show:false
242 | })
243 | }
244 |
245 | getDOMPOS(target) {
246 | var actualLeft = target.offsetLeft;
247 | var current = target.offsetParent;
248 | while (current !== null){
249 | actualLeft += current.offsetLeft;
250 | current = current.offsetParent;
251 | }
252 | var actualTop = target.offsetTop;
253 | var current = target.offsetParent;
254 | while (current !== null){
255 | actualTop += current.offsetTop;
256 | current = current.offsetParent;
257 | }
258 | return {
259 | x:actualLeft,
260 | y:actualTop
261 | }
262 | }
263 |
264 | renderEnumObject(editcom,key){
265 | var props = editcom.props[key];
266 | var config = editcom.config[key].enumobject;
267 | if(config.type == 'relative_props_object') {
268 | config = editcom.props[config.target];
269 | }
270 | return
271 |
{editcom.config[key].text}
272 |
{
281 | return {
282 | props.splice(index,1)
283 | editcom.props[key] = _.cloneDeep(props);
284 | this.forceUpdate();
285 | }}>Delete
286 | }
287 | }])}
288 | dataSource={props}
289 | />
290 |
296 | {
297 | this.state.showAddList?
302 | {
303 | (()=>{
304 | if(c.type == 'String')
305 | return {
306 | this.state.addListData[c.dataIndex] = v.target.value+"";
307 | }}>
308 | if(c.type == 'Number')
309 | return {
310 | this.state.addListData[c.dataIndex] = v;
311 | }}>
312 | if(c.type == 'Boolean')
313 | return {
314 | this.state.addListData[c.dataIndex] = v;
315 | }}>
316 | else
317 | return {
318 | this.state.addListData[c.dataIndex] = v.target.value+"";
319 | }}>
320 | })()
321 | }
322 |
323 | })
324 | }
325 |
326 |
365 |
366 | :null
367 | }
368 |
369 |
370 | }
371 |
372 | findIdFromComs(id,coms,parent){
373 | var result = null;
374 | for(var i=0;i{
402 | if(this.state.isDragLayer){
403 | e.stopPropagation();
404 | this.state.mouse_x = e.clientX;
405 | this.state.mouse_y = e.clientY;
406 |
407 | this.state.dragdiv_x = this.state.mouse_x
408 | this.state.dragdiv_y = this.state.mouse_y
409 | document.getElementById("dragdiv").style.left = this.state.mouse_x-5 +'px'
410 | document.getElementById("dragdiv").style.top = this.state.mouse_y-5 +'px'
411 | if(!this.state.activeCom.props.style) {
412 | this.state.activeCom.props.style = {}
413 | }
414 | this.state.layer_w = this.state.activeCom.props.style.width = this.state.mouse_x + 5 - this.state.layer_x;
415 | this.state.layer_h = this.state.activeCom.props.style.height = this.state.mouse_y + 5 - this.state.layer_y;
416 | this.forceUpdate()
417 | }
418 | }} onMouseUp={()=>{
419 | this.state.isDragLayer = false;
420 | }}>
421 |
422 |
{this.state.activeCom.title}
423 |
424 |
425 |
426 | {
427 | e.stopPropagation();
428 | this.state.isDragLayer = true;
429 | }}>
430 |
431 |
432 |
433 | {
434 | (!this.state.hasBeginEdit)?
435 | 设计板,拖拽元素到此,点击元素可以编辑属性,红色虚线区域可以放置子组件
436 |
:null
437 | }
438 | {
439 | this.renderJSON(this.state.data)
440 | }
441 |
442 |
596 |
597 |
598 | );
599 | }
600 |
601 | collectCom(data){
602 |
603 | }
604 |
605 | renderJSONtoJSX(){
606 | this.state.indent_space = ''
607 |
608 | return `import React, { Component } from 'react';
609 |
610 | /*
611 | * 这里声明要引入的组件
612 | */
613 | import { ${_.uniq(this.state.dependComponents).join(', ')} } from '../components/index.jsx';
614 |
615 | /**
616 | * Index 一般是当前页面名称 index.html
617 | */
618 | class Index extends Component {
619 | constructor () {
620 | super();
621 | }
622 | render(){
623 | return ${this.renderElementtoJSX(this.state.data).replace(/\n /,'')}
624 | }
625 | }
626 | export default Index;
627 |
628 | `
629 | }
630 |
631 | renderElementtoJSX(data){
632 | var result = '';
633 | this.state.indent_space += " ";
634 | data.forEach((d)=>{
635 | if(d.hasDelete) return;
636 | console.log(d)
637 | console.log(d.props)
638 | result += `
639 | ${this.state.indent_space}<${d.type}${this.renderProps(d.props,d)}>${d.props.content?[d.props.content]:(d.childrens?this.renderElementtoJSX(d.childrens):'')}${d.type}>
640 | `
641 | })
642 | this.state.indent_space = this.state.indent_space.replace(' ','');
643 | result += `${this.state.indent_space}`;
644 | return result;
645 | }
646 |
647 | renderProps(props,d){
648 | var result = '';
649 | var props = _.cloneDeep(props);
650 | for(var i in props){
651 | if(!(/^on[A-Z]/.test(i)||/draggable/.test(i)||/content/.test(i))){
652 | if(/^event_(.*?)/.test(i)){
653 | result += ` ${i.replace('event_','')}={()=>{ }}`
654 | }else if(typeof(props[i])=='object' && props[i].type == 'relative'){
655 | result += ` ${i}={${JSON.stringify(props[props[i].target]?props[i].true:props[i].false)}}`
656 | }else if(d.sub_type=="table_container" && i == 'columns'){
657 | var renderCache = {
658 |
659 | }
660 | props[i].forEach((p,pi)=>{
661 | if(p.childrens){
662 | var indentCache = this.state.indent_space;
663 | this.state.indent_space = '';
664 |
665 | renderCache['$$'+pi+'$$'] = `()=>{ return ${this.renderElementtoJSX(p.childrens).replace(/\n /,'')}}`
666 | this.state.indent_space = indentCache
667 | p.render = '$$'+pi+'$$'
668 | delete p.childrens;
669 | }
670 | })
671 | var noindent = JSON.stringify(props[i])
672 | var indent = JSON.stringify(props[i],null,2);
673 | var r = noindent.length>100?indent:noindent;
674 | for(var m in renderCache){
675 | r = r.replace(`"${m}"`,renderCache[m])
676 | }
677 | result += ` ${i}={${r}}`
678 | }else{
679 | var noindent = JSON.stringify(props[i])
680 | var indent = JSON.stringify(props[i],null,2);
681 | result += ` ${i}={${noindent.length>100?indent:noindent}}`
682 | }
683 |
684 | }
685 | }
686 | return result;
687 | }
688 | }
689 | export default Editor;
690 |
--------------------------------------------------------------------------------