From ada8ba9f56c2bb48eb792df444c2f4c8a3dc4eef Mon Sep 17 00:00:00 2001 From: SkyCai Date: Fri, 7 Jun 2019 20:53:43 +0800 Subject: [PATCH] feat(refactor): perfect test suites and add builder demo in docs (#100) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 拖拽组件替换成react-dnd * refactor: 调整交互 * test: update test cases * fix: 修复拖拽bug * chore: build * refactor: 完善测试用例 --- docs/SUMMARY.md | 1 + packages/builder/package.json | 9 +- packages/builder/src/App.js | 62 +- .../src/__tests__/actions/index.spec.js | 83 ++- packages/builder/src/__tests__/index.spec.js | 2 +- packages/builder/src/__tests__/lang.spec.js | 80 +- .../src/__tests__/reducers/codemode.spec.js | 27 + .../__tests__/reducers/componentId.spec.js | 29 + .../__tests__/reducers/componentProps.spec.js | 179 +++++ .../src/__tests__/reducers/gbConfig.spec.js | 54 ++ .../src/__tests__/reducers/index.spec.js | 704 ------------------ .../__tests__/reducers/initSchemaData.spec.js | 416 +++++++++++ .../src/__tests__/reducers/preview.spec.js | 31 + packages/builder/src/__tests__/util.spec.js | 494 ++++++------ packages/builder/src/actions/index.js | 94 ++- .../builder/src/components/fields/field.js | 103 +++ .../builder/src/components/fields/index.js | 151 +--- .../builder/src/components/fields/layout.js | 44 +- .../src/components/fields/layoutField.js | 85 +++ packages/builder/src/components/index.js | 13 + .../builder/src/components/preview/card.js | 191 +++++ .../src/components/preview/fieldMiddleware.js | 128 +--- .../builder/src/components/preview/index.js | 190 +---- .../builder/src/components/preview/mainBox.js | 133 ++++ .../builder/src/components/preview/style.js | 87 ++- .../fieldAttrEditors/dataSourceEditor.js | 8 +- .../defaultValueGenerator.js | 17 +- .../src/components/props/fileSetting.js | 12 +- .../src/components/props/propsSetting.js | 64 +- .../builder/src/configs/supportConfigList.js | 32 +- packages/builder/src/constants/context.js | 2 + packages/builder/src/constants/itemType.js | 5 + packages/builder/src/demo/index-1-x.js | 6 +- packages/builder/src/index.js | 9 +- packages/builder/src/reducers/componentId.js | 11 +- packages/builder/src/reducers/index.js | 4 +- .../builder/src/reducers/initSchemaData.js | 191 ++--- packages/builder/src/reducers/layoutId.js | 13 - packages/builder/src/style.js | 1 - packages/builder/src/utils/lang.js | 12 +- packages/builder/src/utils/util.js | 57 +- 41 files changed, 2066 insertions(+), 1768 deletions(-) create mode 100644 packages/builder/src/__tests__/reducers/codemode.spec.js create mode 100644 packages/builder/src/__tests__/reducers/componentId.spec.js create mode 100644 packages/builder/src/__tests__/reducers/componentProps.spec.js create mode 100644 packages/builder/src/__tests__/reducers/gbConfig.spec.js delete mode 100644 packages/builder/src/__tests__/reducers/index.spec.js create mode 100644 packages/builder/src/__tests__/reducers/initSchemaData.spec.js create mode 100644 packages/builder/src/__tests__/reducers/preview.spec.js create mode 100644 packages/builder/src/components/fields/field.js create mode 100644 packages/builder/src/components/fields/layoutField.js create mode 100644 packages/builder/src/components/index.js create mode 100644 packages/builder/src/components/preview/card.js create mode 100644 packages/builder/src/components/preview/mainBox.js create mode 100644 packages/builder/src/constants/context.js create mode 100644 packages/builder/src/constants/itemType.js delete mode 100644 packages/builder/src/reducers/layoutId.js diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index c49bced5456..21c7908aa48 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -53,3 +53,4 @@ - [国际化](./Examples/antd/International.md) - [知乎专栏](https://zhuanlan.zhihu.com/uform) - [GitHub](https://github.com/alibaba/uform) +- [PlayGround DEMO](../packages/builder/src/demo/index-1-x.js) diff --git a/packages/builder/package.json b/packages/builder/package.json index bd6ffb84dfc..b0a78c0eada 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -7,9 +7,6 @@ "type": "git", "url": "git+https://github.com/alibaba/uform.git" }, - "scripts": { - "start": "node scripts/start.js" - }, "bugs": { "url": "https://github.com/alibaba/uform/issues" }, @@ -28,6 +25,8 @@ "@uform/utils": "^0.1.15", "@uform/validator": "^0.1.15", "classnames": "^2.2.5", + "immutability-helper": "^3.0.0", + "lodash.flow": "^3.5.0", "lodash.isequal": "^4.5.0", "lodash.merge": "^4.6.1", "lodash.pick": "^4.4.0", @@ -37,6 +36,7 @@ "moment": "^2.24.0", "prop-types": "^15.6.1", "react-dnd": "^7.4.1", + "react-dnd-html5-backend": "^7.4.0", "react-powerplug": "^1.0.0", "react-redux": "^5.0.7", "redux": "^4.0.0", @@ -46,7 +46,8 @@ "uuid": "^3.2.1" }, "publishConfig": { - "access": "public" + "access": "public", + "registry": "https://registry.npm.alibaba-inc.com" }, "gitHead": "4d068dad6183e8da294a4c899a158326c0b0b050", "devDependencies": { diff --git a/packages/builder/src/App.js b/packages/builder/src/App.js index e9ec1332d28..3d638edc981 100644 --- a/packages/builder/src/App.js +++ b/packages/builder/src/App.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react' +import React, { Component, createRef } from 'react' import cls from 'classnames' import PropTypes from 'prop-types' import { connect } from 'react-redux' @@ -16,11 +16,13 @@ import isEqual from 'lodash.isequal' import AppStyle from './style' // components -import FieldList from './components/fields/index' -import Preview from './components/preview/index' -import generateGlobalBtnList from './components/globalBtnList/index' +import { + FieldList, + Preview, + GlobalBtnList, + PropsSetting +} from './components/index' -import PropsSetting from './components/props/propsSetting' import { SchemaForm, Field } from './utils/baseForm' import defaultGlobalCfgList from './configs/supportGlobalCfgList' @@ -33,6 +35,8 @@ class App extends Component { systemError: false, accordionList: [] } + this.appRef = createRef(null) + this.appHeaderRef = createRef(null) } generateGlobalCfgList = () => { @@ -63,10 +67,11 @@ class App extends Component { }} defaultValue={this.props.gbConfig} labelAlign='left' + labelCol={10} labelTextAlign='right' > {globalCfgList.map(props => ( - + ))} ) @@ -93,7 +98,7 @@ class App extends Component { list.unshift({ title: '全局配置', content: this.renderGlobalConfig(), - expanded: true + expanded: false }) } return list @@ -115,6 +120,17 @@ class App extends Component { this.setState({ accordionList: this.getAccordionList() }) + + const appDom = this.appRef.current + const appHeaderDom = this.appHeaderRef.current + + if (appDom.offsetTop !== 0) { + document.querySelector( + '.schamaform-content' + ).style.height = `${window.innerHeight - + appDom.offsetTop - + appHeaderDom.offsetHeight}px` + } } componentWillUnmount() { @@ -125,12 +141,12 @@ class App extends Component { }) } - componentWillReceiveProps(nextProps) { - let oldProperties = {} - if (this.props.schema && this.props.schema.properties) { - oldProperties = this.props.schema.properties - } - const { schema = {}, globalCfg = {}, initSchema: _initSchema } = nextProps + componentDidUpdate(prevProps) { + const oldProperties = + prevProps.schema && prevProps.schema.properties + ? prevProps.schema.properties + : {} + const { schema = {}, globalCfg = {}, initSchema: _initSchema } = this.props const { properties = {} } = schema @@ -205,21 +221,20 @@ class App extends Component { } } - renderGlobalBtnList() { - return generateGlobalBtnList(this.props) - } - render() { const { initSchemaData, renderEngine } = this.props const { Accordion, version: UIVersion } = this.props.UI - const contentHeight = window.innerHeight - 64 + const contentHeight = window.innerHeight return this.state.systemError ? (

系统发生异常

) : ( - -
+ +
@@ -253,7 +268,7 @@ class App extends Component { {UIVersion === '1.x' ? ( ) : ( ({ initSchema: data => dispatch(initSchema(data)), changeCodeMode: codemode => dispatch(changeCodeMode(codemode)), changeComponent: componentId => dispatch(changeComponent(componentId)), - editComponent: (id, propsData, containerId) => - dispatch(editComponent(id, propsData, containerId)) + editComponent: (...args) => dispatch(editComponent(...args)) }) class StyledAppComp extends React.Component { diff --git a/packages/builder/src/__tests__/actions/index.spec.js b/packages/builder/src/__tests__/actions/index.spec.js index 5c6cf3d4234..7f1e17e6952 100644 --- a/packages/builder/src/__tests__/actions/index.spec.js +++ b/packages/builder/src/__tests__/actions/index.spec.js @@ -1,20 +1,20 @@ import * as actions from '../../actions/index' describe('test actions', () => { - test('changeLayoutId action', () => { - expect(actions.changeLayoutId('111')).toEqual({ - type: 'CHANGE_LAYOUTID', - data: { - id: '111' - } - }) - }) - + // addComponent actions test('addComponent action', () => { - expect(actions.addComponent({ - type: 'string', - title: '111' - }, '222', '333', 'layout', '444')).toEqual({ + expect( + actions.addComponent( + { + type: 'string', + title: '111' + }, + '222', + '333', + 'layout', + '444' + ) + ).toEqual({ type: 'ADD_COMPONENT', data: { id: '333', @@ -41,50 +41,57 @@ describe('test actions', () => { }) test('editComponent action', () => { - expect(actions.editComponent('111', { - __id__: 222 - }, '333')).toEqual({ + expect( + actions.editComponent('111', { + __id__: 222 + }) + ).toEqual({ type: 'EDIT_COMPONENT', data: { id: '111', propsData: { __id__: 222 - }, - containerId: '333' + } } }) }) test('deleteComponent action', () => { - expect(actions.deleteComponent('111')).toEqual({ + expect(actions.deleteComponent(['111'])).toEqual({ type: 'DELETE_COMPONENT', data: { - id: '111' + id: ['111'] } }) }) test('showComponentProps action', () => { - expect(actions.showComponentProps('111', { - type: 'string', - title: '222' - }, '333')).toEqual({ + expect( + actions.showComponentProps( + ['111'], + { + type: 'string', + title: '222' + } + ) + ).toEqual({ type: 'SHOW_COMPONENT_PROPS', data: { - id: '111', + id: ['111'], comp: { type: 'string', title: '222' - }, - containerId: '333' + } } }) }) test('editComponentProps action', () => { - expect(actions.editComponentProps('111', { - __id__: '222' - })).toEqual({ + expect( + actions.editComponentProps('111', { + __id__: '222' + }) + ).toEqual({ type: 'EDIT_COMPONENT_PROPS', data: { id: '111', @@ -123,9 +130,11 @@ describe('test actions', () => { }) test('changeGbConfig action', () => { - expect(actions.changeGbConfig({ - aaa: 1 - })).toEqual({ + expect( + actions.changeGbConfig({ + aaa: 1 + }) + ).toEqual({ type: 'CHANGE_GB_CONFIG', data: { aaa: 1 @@ -134,9 +143,11 @@ describe('test actions', () => { }) test('initSchema action', () => { - expect(actions.initSchema({ - aaa: 1 - })).toEqual({ + expect( + actions.initSchema({ + aaa: 1 + }) + ).toEqual({ type: 'INIT_SCHEMA', data: { aaa: 1 diff --git a/packages/builder/src/__tests__/index.spec.js b/packages/builder/src/__tests__/index.spec.js index 7a6fa8cdeff..91d7caa8f78 100644 --- a/packages/builder/src/__tests__/index.spec.js +++ b/packages/builder/src/__tests__/index.spec.js @@ -10,6 +10,6 @@ test('shoud correct render APP', () => { } renderer.render() const result = renderer.getRenderOutput() - const matchProps = result.props.children.props.children.props + const matchProps = result.props.children.props expect(matchProps).toHaveProperty('renderEngine') }) diff --git a/packages/builder/src/__tests__/lang.spec.js b/packages/builder/src/__tests__/lang.spec.js index d5618960e90..fa076da304a 100644 --- a/packages/builder/src/__tests__/lang.spec.js +++ b/packages/builder/src/__tests__/lang.spec.js @@ -19,15 +19,15 @@ test('lang funtions', () => { test('should correct normalize schema', () => { var schema = { - 'type': 'object', - 'properties': { - 'wrapper': { + type: 'object', + properties: { + wrapper: { type: 'object', 'x-component': 'layout', properties: { '[startDate1,endDate1]': { - 'type': 'daterange', - 'default': [ + type: 'daterange', + default: [ { type: 'specify', value: '2019-01-01', flag: 'date' }, { type: 'specify', value: '2019-01-02', flag: 'date' } ], @@ -36,21 +36,47 @@ test('should correct normalize schema', () => { } }, '[startDate,endDate]': { - 'type': 'daterange', - 'default': [ + type: 'daterange', + default: [ { type: 'specify', value: '2019-01-01', flag: 'date' }, { type: 'specify', value: '2019-01-02', flag: 'date' } ], 'z-index': 0 } + // '[dateType, startBizDate, endBizDate]': { + // type: 'string', + // title: '请选择统计周期', + // default: [ + // { + // flag: '', + // type: 'specify', + // value: 'week' + // }, + // { + // flag: 'weekRange', + // type: 'pastStart', + // value: 1 + // }, + // { + // flag: 'weekRange', + // type: 'pastStart', + // value: 0 + // } + // ], + // 'x-component': 'demensionPicker', + // 'x-props': { + // direction: 'hoz', + // rangeTypes: ['week', 'month', 'day'] + // } + // } } } var schema1 = { - 'type': 'object', - 'properties': { + type: 'object', + properties: { '[startDate,endDate]': { - 'type': 'daterange', - 'default': [ + type: 'daterange', + default: [ { type: 'specify', value: '2019-01-01', flag: 'date' }, { type: 'specify', value: '2019-01-02', flag: 'date' } ], @@ -61,45 +87,37 @@ test('should correct normalize schema', () => { var value = lang.normalizeSchema(schema) var value1 = lang.normalizeSchema(schema1) var result = { - 'type': 'object', - 'properties': { - 'wrapper': { + type: 'object', + properties: { + wrapper: { type: 'object', 'x-component': 'layout', properties: { '[startDate1,endDate1]': { - 'type': 'daterange', - 'default': [ - '2019-01-01', - '2019-01-02' - ], + type: 'daterange', + default: ['2019-01-01', '2019-01-02'], 'z-index': 0 } } }, '[startDate,endDate]': { - 'type': 'daterange', - 'default': [ - '2019-01-01', - '2019-01-02' - ], + type: 'daterange', + default: ['2019-01-01', '2019-01-02'], 'z-index': 0 } } } var result1 = { - 'type': 'object', - 'properties': { + type: 'object', + properties: { '[startDate,endDate]': { - 'type': 'daterange', - 'default': [ - '2019-01-01', - '2019-01-02' - ], + type: 'daterange', + default: ['2019-01-01', '2019-01-02'], 'z-index': 0 } } } + expect(value).toEqual(result) expect(value1).toEqual(result1) expect(lang.normalizeSchema()).toEqual(null) diff --git a/packages/builder/src/__tests__/reducers/codemode.spec.js b/packages/builder/src/__tests__/reducers/codemode.spec.js new file mode 100644 index 00000000000..daf14847709 --- /dev/null +++ b/packages/builder/src/__tests__/reducers/codemode.spec.js @@ -0,0 +1,27 @@ +import codemodeReducer from '../../reducers/codemode' + +describe('codemode reducers', () => { + test('codemode reducers return initial state', () => { + expect(codemodeReducer(undefined, {})).toEqual(false) + }) + test('codemode reducers return custom state', () => { + const beforeState = false + const action = { + type: 'CHANGE_CODEMODE', + data: { + codemode: true + } + } + expect(codemodeReducer(beforeState, action)).toEqual(true) + }) + test('codemode reducers return custom state', () => { + const beforeState = true + const action = { + type: 'CHANGE_CODEMODE', + data: { + codemode: false + } + } + expect(codemodeReducer(beforeState, action)).toEqual(false) + }) +}) diff --git a/packages/builder/src/__tests__/reducers/componentId.spec.js b/packages/builder/src/__tests__/reducers/componentId.spec.js new file mode 100644 index 00000000000..c407a9f4c7b --- /dev/null +++ b/packages/builder/src/__tests__/reducers/componentId.spec.js @@ -0,0 +1,29 @@ +import componentIdReducer from '../../reducers/componentId' + +describe('componentId reducers', () => { + test('componentId reducers return initial state', () => { + expect(componentIdReducer(undefined, {})).toEqual([]) + }) + + test('componentId reducers return custom state', () => { + const beforeState = [] + const action = { + type: 'CHANGE_COMPONENT', + data: { + componentId: ['66666', '333'] + } + } + expect(componentIdReducer(beforeState, action)).toEqual(['66666', '333']) + }) + + test('componentId reducers return custom state', () => { + const beforeState = [] + const action = { + type: 'CHANGE_COMPONENT', + data: { + componentId: '333' + } + } + expect(componentIdReducer(beforeState, action)).toEqual(['333']) + }) +}) diff --git a/packages/builder/src/__tests__/reducers/componentProps.spec.js b/packages/builder/src/__tests__/reducers/componentProps.spec.js new file mode 100644 index 00000000000..61370d8706e --- /dev/null +++ b/packages/builder/src/__tests__/reducers/componentProps.spec.js @@ -0,0 +1,179 @@ +import componentPropsReducer from '../../reducers/componentProps' + +describe('componentProps reducers', () => { + test('componentProps reducers return initial state', () => { + expect(componentPropsReducer(undefined, {})).toEqual({}) + }) + + test('componentProps reducers return custom state when SHOW_COMPONENT_PROPS', () => { + const beforeState = {} + const action = { + type: 'SHOW_COMPONENT_PROPS', + data: { + id: '11111', + comp: { + key: 'input', + icon: 'info', + iconUrl: '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', + width: '58', + height: '28', + type: 'string', + title: '单行文本框', + placeholder: '请输入' + } + } + } + + const afterState = { + '11111': [ + { + name: '__id__', + title: '字段名称', + type: 'string', + description: '字段名称:发起请求时带上的参数id,必填,全局保证唯一。', + required: true + }, + { + name: 'title', + title: '标题', + type: 'string', + placeholder: '请输入字段名称,不超过50个字符', + value: '单行文本框' + }, + { + name: 'default', + title: '默认值', + type: 'object', + 'x-component': 'defaultValueCascader' + }, + { name: 'description', title: '提示文案', type: 'string' }, + { name: 'x-props.placeholder', title: '占位符', type: 'string' }, + { name: 'required', title: '是否必填', type: 'boolean' }, + { name: 'x-props.readOnly', title: '是否只读', type: 'boolean' }, + { name: 'x-props.disabled', title: '是否禁用', type: 'boolean' }, + { name: 'x-props.htmltype', title: '是否隐藏', type: 'boolean' } + ] + } + const result = componentPropsReducer(beforeState, action) + expect(result).toEqual(afterState) + }) + + test('componentProps reducers return custom state when DELETE_COMPONENT', () => { + const beforeState = { + '11111': [ + { + name: '__id__', + title: '字段名称', + type: 'string', + description: '字段名称:发起请求时带上的参数id,必填,全局保证唯一。', + required: true, + value: '11111' + }, + { name: 'description', title: '提示文案', type: 'string' }, + { + name: 'title', + title: '标题', + type: 'string', + placeholder: '请输入字段名称,不超过50个字符', + value: '单行文本框' + }, + { + name: 'default', + title: '默认值', + type: 'object', + 'x-component': 'defaultValueCascader' + }, + { name: 'required', title: '是否必填', type: 'boolean' }, + { name: 'x-props.readOnly', title: '是否只读', type: 'boolean' }, + { name: 'x-props.disabled', title: '是否禁用', type: 'boolean' }, + { name: 'x-props.htmltype', title: '是否隐藏', type: 'boolean' }, + { name: 'x-props.placeholder', title: '占位符', type: 'string' } + ] + } + const action = { + type: 'DELETE_COMPONENT', + data: { + id: '11111' + } + } + + const afterState = {} + expect(componentPropsReducer(beforeState, action)).toEqual(afterState) + }) + + test('componentProps reducers return custom state when EDIT_COMPONENT_PROPS', () => { + const beforeState = { + '11111': [ + { + name: '__id__', + title: '字段名称', + type: 'string', + description: '字段名称:发起请求时带上的参数id,必填,全局保证唯一。', + required: true, + value: '11111' + }, + { name: 'description', title: '提示文案', type: 'string' }, + { + name: 'title', + title: '标题', + type: 'string', + placeholder: '请输入字段名称,不超过50个字符', + value: '单行文本框' + }, + { + name: 'default', + title: '默认值', + type: 'object', + 'x-component': 'defaultValueCascader' + }, + { name: 'required', title: '是否必填', type: 'boolean' }, + { name: 'x-props.readOnly', title: '是否只读', type: 'boolean' }, + { name: 'x-props.disabled', title: '是否禁用', type: 'boolean' }, + { name: 'x-props.htmltype', title: '是否隐藏', type: 'boolean' }, + { name: 'x-props.placeholder', title: '占位符', type: 'string' } + ] + } + const action = { + type: 'EDIT_COMPONENT_PROPS', + data: { + id: '11111', + propsData: { + __id__: 'hello' + } + } + } + + const afterState = { + '11111': [ + { + name: '__id__', + value: 'hello', + title: '字段名称', + type: 'string', + description: '字段名称:发起请求时带上的参数id,必填,全局保证唯一。', + required: true + }, + { name: 'description', title: '提示文案', type: 'string' }, + { + name: 'title', + title: '标题', + type: 'string', + placeholder: '请输入字段名称,不超过50个字符', + value: '单行文本框' + }, + { + name: 'default', + title: '默认值', + type: 'object', + 'x-component': 'defaultValueCascader' + }, + { name: 'required', title: '是否必填', type: 'boolean' }, + { name: 'x-props.readOnly', title: '是否只读', type: 'boolean' }, + { name: 'x-props.disabled', title: '是否禁用', type: 'boolean' }, + { name: 'x-props.htmltype', title: '是否隐藏', type: 'boolean' }, + { name: 'x-props.placeholder', title: '占位符', type: 'string' } + ] + } + expect(componentPropsReducer(beforeState, action)).toEqual(afterState) + }) +}) diff --git a/packages/builder/src/__tests__/reducers/gbConfig.spec.js b/packages/builder/src/__tests__/reducers/gbConfig.spec.js new file mode 100644 index 00000000000..99fd4ccbe4b --- /dev/null +++ b/packages/builder/src/__tests__/reducers/gbConfig.spec.js @@ -0,0 +1,54 @@ +import gbConfigReducer from '../../reducers/gbConfig' + +describe('reducers', () => { + test('gbConfig reducers return initial state', () => { + expect(gbConfigReducer(undefined, {})).toEqual({ + labelAlign: 'left', + labelTextAlign: 'right', + autoAddColon: true, + needFormButtonGroup: false, + inline: false, + size: 'medium', + labelCol: 8, + wrapperCol: 16, + editable: true + }) + }) + + test('gbConfig reducers return custom state', () => { + const beforeState = { + labelAlign: 'left', + labelTextAlign: 'left', + autoAddColon: true, + needFormButtonGroup: false, + inline: false, + size: 'small', + labelCol: 8, + wrapperCol: 16 + } + + const action = { + type: 'CHANGE_GB_CONFIG', + data: { + size: 'large', + inline: true, + extra: 'extra' + } + } + + const afterState = { + labelAlign: 'left', + labelTextAlign: 'left', + autoAddColon: true, + needFormButtonGroup: false, + inline: true, + size: 'large', + extra: 'extra', + labelCol: 8, + wrapperCol: 16, + editable: true + } + + expect(gbConfigReducer(beforeState, action)).toEqual(afterState) + }) +}) diff --git a/packages/builder/src/__tests__/reducers/index.spec.js b/packages/builder/src/__tests__/reducers/index.spec.js deleted file mode 100644 index 1820e75ae50..00000000000 --- a/packages/builder/src/__tests__/reducers/index.spec.js +++ /dev/null @@ -1,704 +0,0 @@ -import codemodeReducer from '../../reducers/codemode' -import componentIdReducer from '../../reducers/componentId' -import componentPropsReducer from '../../reducers/componentProps' -import gbConfigReducer from '../../reducers/gbConfig' -import initSchemaDataReducer from '../../reducers/initSchemaData' -import layoutIdReducer from '../../reducers/layoutId' -import previewReducer from '../../reducers/preview' - -describe('reducers', () => { - // codemode reducer - test('codemode reducers return initial state', () => { - expect(codemodeReducer(undefined, {})).toEqual(false) - }) - test('codemode reducers return custom state', () => { - const beforeState = false - const action = { - type: 'CHANGE_CODEMODE', - data: { - codemode: true - } - } - expect(codemodeReducer(beforeState, action)).toEqual(true) - }) - - // componentId reducer - test('componentId reducers return initial state', () => { - expect(componentIdReducer(undefined, {})).toEqual('') - }) - test('componentId reducers return custom state', () => { - const beforeState = '' - const action = { - type: 'CHANGE_COMPONENT', - data: { - componentId: '66666' - } - } - expect(componentIdReducer(beforeState, action)).toEqual('66666') - }) - - // componentProps reducer - test('componentProps reducers return initial state', () => { - expect(componentPropsReducer(undefined, {})).toEqual({}) - }) - test('componentProps reducers return custom state when SHOW_COMPONENT_PROPS', () => { - const beforeState = {} - const action = { - type: 'SHOW_COMPONENT_PROPS', - data: { - id: '11111', - comp: { - key: 'input', - icon: 'info', - iconUrl: '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', - width: '58', - height: '28', - type: 'string', - title: '单行文本框', - placeholder: '请输入' - } - } - } - - const afterState = { - '11111': [ - { - name: '__id__', - title: '唯一标识', - type: 'string', - description: '唯一标识:发起请求时带上的参数id,必填,全局保证唯一。', - required: true - }, - { name: 'description', title: '提示文案', type: 'string' }, - { - name: 'title', - title: '标题', - type: 'string', - placeholder: '请输入字段名称,不超过50个字符', - value: '单行文本框' - }, - { - name: 'default', - title: '默认值', - type: 'object', - 'x-component': 'defaultValueCascader' - }, - { name: 'required', title: '是否必填', type: 'boolean' }, - { name: 'x-props.readOnly', title: '是否只读', type: 'boolean' }, - { name: 'x-props.disabled', title: '是否禁用', type: 'boolean' }, - { name: 'x-props.htmltype', title: '是否隐藏', type: 'boolean' }, - { name: 'x-props.placeholder', title: '占位符', type: 'string' } - ] - } - expect(componentPropsReducer(beforeState, action)).toEqual(afterState) - }) - test('componentProps reducers return custom state when DELETE_COMPONENT', () => { - const beforeState = { - '11111': [ - { - name: '__id__', - title: '唯一标识', - type: 'string', - description: '发起请求时带过去的参数字段', - required: true, - value: '11111' - }, - { name: 'description', title: '提示文案', type: 'string' }, - { - name: 'title', - title: '标题', - type: 'string', - placeholder: '请输入字段名称,不超过50个字符', - value: '单行文本框' - }, - { - name: 'default', - title: '默认值', - type: 'object', - 'x-component': 'defaultValueCascader' - }, - { name: 'required', title: '是否必填', type: 'boolean' }, - { name: 'x-props.readOnly', title: '是否只读', type: 'boolean' }, - { name: 'x-props.disabled', title: '是否禁用', type: 'boolean' }, - { name: 'x-props.htmltype', title: '是否隐藏', type: 'boolean' }, - { name: 'x-props.placeholder', title: '占位符', type: 'string' } - ] - } - const action = { - type: 'DELETE_COMPONENT', - data: { - id: '11111' - } - } - - const afterState = {} - expect(componentPropsReducer(beforeState, action)).toEqual(afterState) - }) - test('componentProps reducers return custom state when EDIT_COMPONENT_PROPS', () => { - const beforeState = { - '11111': [ - { - name: '__id__', - title: '唯一标识', - type: 'string', - description: '发起请求时带过去的参数字段', - required: true, - value: '11111' - }, - { name: 'description', title: '提示文案', type: 'string' }, - { - name: 'title', - title: '标题', - type: 'string', - placeholder: '请输入字段名称,不超过50个字符', - value: '单行文本框' - }, - { - name: 'default', - title: '默认值', - type: 'object', - 'x-component': 'defaultValueCascader' - }, - { name: 'required', title: '是否必填', type: 'boolean' }, - { name: 'x-props.readOnly', title: '是否只读', type: 'boolean' }, - { name: 'x-props.disabled', title: '是否禁用', type: 'boolean' }, - { name: 'x-props.htmltype', title: '是否隐藏', type: 'boolean' }, - { name: 'x-props.placeholder', title: '占位符', type: 'string' } - ] - } - const action = { - type: 'EDIT_COMPONENT_PROPS', - data: { - id: '11111', - propsData: { - __id__: 'hello' - } - } - } - - const afterState = { - '11111': [ - { - name: '__id__', - value: 'hello', - title: '唯一标识', - type: 'string', - description: '发起请求时带过去的参数字段', - required: true - }, - { name: 'description', title: '提示文案', type: 'string' }, - { - name: 'title', - title: '标题', - type: 'string', - placeholder: '请输入字段名称,不超过50个字符', - value: '单行文本框' - }, - { - name: 'default', - title: '默认值', - type: 'object', - 'x-component': 'defaultValueCascader' - }, - { name: 'required', title: '是否必填', type: 'boolean' }, - { name: 'x-props.readOnly', title: '是否只读', type: 'boolean' }, - { name: 'x-props.disabled', title: '是否禁用', type: 'boolean' }, - { name: 'x-props.htmltype', title: '是否隐藏', type: 'boolean' }, - { name: 'x-props.placeholder', title: '占位符', type: 'string' } - ] - } - expect(componentPropsReducer(beforeState, action)).toEqual(afterState) - }) - - // gbConfig reducers - test('gbConfig reducers return initial state', () => { - expect(gbConfigReducer(undefined, {})).toEqual({ - labelAlign: 'left', - labelTextAlign: 'right', - autoAddColon: true, - needFormButtonGroup: false, - inline: false, - size: 'medium', - labelCol: 8, - wrapperCol: 16, - editable: true - }) - }) - test('gbConfig reducers return custom state', () => { - const beforeState = { - labelAlign: 'left', - labelTextAlign: 'left', - autoAddColon: true, - needFormButtonGroup: false, - inline: false, - size: 'small', - labelCol: 8, - wrapperCol: 16 - } - - const action = { - type: 'CHANGE_GB_CONFIG', - data: { - size: 'large', - inline: true, - extra: 'extra' - } - } - - const afterState = { - labelAlign: 'left', - labelTextAlign: 'left', - autoAddColon: true, - needFormButtonGroup: false, - inline: true, - size: 'large', - extra: 'extra', - labelCol: 8, - wrapperCol: 16, - editable: true - } - - expect(gbConfigReducer(beforeState, action)).toEqual(afterState) - }) - - // layoutId reducer - test('layoutId reducers return initial state', () => { - expect(layoutIdReducer(undefined, {})).toEqual('') - }) - test('layoutId reducers return custom state', () => { - const beforeState = 'aaa' - const action = { - type: 'CHANGE_LAYOUTID', - data: { - id: 'bbb' - } - } - const afterState = 'bbb' - expect(layoutIdReducer(beforeState, action)).toEqual(afterState) - }) - - // preview reducer - test('preview reducers return initial state', () => { - expect(previewReducer(undefined, {})).toEqual(false) - }) - test('preview reducers return custom state', () => { - const beforeState = false - const action = { - type: 'CHANGE_PREVIEW', - data: { - preview: true - } - } - const afterState = true - expect(previewReducer(beforeState, action)).toEqual(afterState) - }) - - // initSchemaData reducer - test('initSchemaData reducers return initial state', () => { - expect(initSchemaDataReducer(undefined, {})).toEqual({}) - }) - test('initSchemaData reducers return custom state when INIT_SCHEMA', () => { - const beforeState = {} - const action = { - type: 'INIT_SCHEMA', - data: { - type: 'object', - properties: { - a: { - type: 'string', - title: 'a' - }, - b: { - type: 'string', - title: 'b' - } - } - } - } - const afterState = { - type: 'object', - properties: { - a: { - type: 'string', - title: 'a', - id: 'a', - 'x-index': 0 - }, - b: { - type: 'string', - title: 'b', - id: 'b', - 'x-index': 1 - } - } - } - expect(initSchemaDataReducer(beforeState, action)).toEqual(afterState) - }) - test('initSchemaData reducers return custom state when CHANGE_COMPONENT_ORDER', () => { - const beforeState = { - type: 'object', - properties: { - a: { - type: 'string', - title: 'a' - }, - b: { - type: 'string', - title: 'b' - } - } - } - const action = { - type: 'CHANGE_COMPONENT_ORDER', - data: { - id: 'a', - targetId: 'b' - } - } - const afterState = { - type: 'object', - properties: { - a: { - type: 'string', - title: 'a', - id: 'a', - 'x-index': 1 - }, - b: { - type: 'string', - title: 'b', - id: 'b', - 'x-index': 0 - } - } - } - expect(initSchemaDataReducer(beforeState, action)).toEqual(afterState) - }) - test('initSchemaData reducers return custom state when ADD_COMPONENT', () => { - const beforeState = { - type: 'object', - properties: {} - } - const beforeStateWithData = { - type: 'object', - properties: { - '111': { - type: 'string', - title: '111' - }, - '333': { - type: 'string', - title: '333' - } - } - } - const beforeStateWithLayout = { - type: 'object', - properties: { - '111': { - type: 'object', - 'x-component': 'layout', - properties: {} - } - } - } - const action1 = { - type: 'ADD_COMPONENT', - data: { - id: '111', - component: { - key: 'input', - icon: 'info', - iconUrl: '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', - width: '58', - height: '28', - type: 'string', - title: '单行文本框', - placeholder: '请输入' - } - } - } - const action2 = { - type: 'ADD_COMPONENT', - data: { - id: '111', - component: { - key: 'wrapper_layout', - icon: 'clock-circle-o', - type: 'object', - title: 'Layout布局', - __key__: 'layout', - __key__data__: { - 'x-component': 'layout', - 'x-props': { - labelCol: 8, - wrapperCol: 6 - } - } - } - } - } - const action3 = { - type: 'ADD_COMPONENT', - data: { - id: '222', - addType: 'layout', - containerId: '111', - component: { - key: 'input', - icon: 'info', - iconUrl: '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', - width: '58', - height: '28', - type: 'string', - title: '单行文本框', - placeholder: '请输入' - } - } - } - const action4 = { - type: 'ADD_COMPONENT', - data: { - id: '222', - existId: '333', - component: { - key: 'input', - icon: 'info', - iconUrl: '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', - width: '58', - height: '28', - type: 'string', - title: '单行文本框', - placeholder: '请输入' - } - } - } - - const afterState1 = { - type: 'object', - properties: { - '111': { - key: 'input', - icon: 'info', - iconUrl: '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', - width: '58', - height: '28', - type: 'string', - title: '单行文本框', - placeholder: '请输入', - 'x-index': 0, - id: '111' - } - } - } - const afterState2 = { - type: 'object', - properties: { - '111': { - type: 'object', - id: '111', - 'x-index': 0, - 'x-component': 'layout', - properties: {}, - 'x-props': { - labelCol: 8, - wrapperCol: 6, - _extra: { - key: 'wrapper_layout', - icon: 'clock-circle-o', - type: 'object', - title: 'Layout布局', - __key__: 'layout', - __key__data__: { - 'x-component': 'layout', - 'x-props': { - labelCol: 8, - wrapperCol: 6 - } - } - } - } - } - } - } - const afterState3 = { - type: 'object', - properties: { - '111': { - type: 'object', - 'x-component': 'layout', - properties: { - '222': { - key: 'input', - icon: 'info', - iconUrl: - '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', - width: '58', - height: '28', - type: 'string', - title: '单行文本框', - placeholder: '请输入', - 'x-index': 0, - id: '222' - } - } - } - } - } - const afterState4 = { - type: 'object', - properties: { - '111': { - type: 'string', - title: '111', - 'x-index': 0, - id: '111' - }, - '222': { - key: 'input', - icon: 'info', - iconUrl: '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', - width: '58', - height: '28', - type: 'string', - title: '单行文本框', - placeholder: '请输入', - 'x-index': 1, - id: '222' - }, - '333': { - type: 'string', - title: '333', - 'x-index': 2, - id: '333' - } - } - } - expect(initSchemaDataReducer(beforeState, action1)).toEqual(afterState1) - expect(initSchemaDataReducer(beforeState, action2)).toEqual(afterState2) - expect(initSchemaDataReducer(beforeStateWithLayout, action3)).toEqual( - afterState3 - ) - expect(initSchemaDataReducer(beforeStateWithData, action4)).toEqual( - afterState4 - ) - }) - test('initSchemaData reducers return custom state when DELETE_COMPONENT', () => { - const beforeState = { - type: 'object', - properties: { - '111': { - type: 'string', - title: '111' - } - } - } - const action = { - type: 'DELETE_COMPONENT', - data: { - id: '111' - } - } - const afterState = { - type: 'object', - properties: {} - } - expect(initSchemaDataReducer(beforeState, action)).toEqual(afterState) - }) - test('initSchemaData reducers return custom state when EDIT_COMPONENT', () => { - const beforeState = { - type: 'object', - properties: { - '111': { - type: 'string', - title: '111' - } - } - } - const beforeState1 = { - type: 'object', - properties: { - '111': { - type: 'object', - 'x-component': 'layout', - properties: { - '222': { - type: 'string', - title: '222' - } - } - } - } - } - const action = { - type: 'EDIT_COMPONENT', - data: { - id: '111', - propsData: { - __id__: '222' - } - } - } - const action1 = { - type: 'EDIT_COMPONENT', - data: { - id: '222', - containerId: '111', - propsData: { - __id__: '333', - 'x-props': { - enum: [ - { - value: 1, - label: 1 - } - ] - } - } - } - } - const afterState = { - type: 'object', - properties: { - '111': { - type: 'string', - title: '111', - __id__: '222' - } - } - } - const afterState1 = { - type: 'object', - properties: { - '111': { - type: 'object', - 'x-component': 'layout', - properties: { - '222': { - __id__: '333', - type: 'string', - title: '222', - enum: [ - { - value: 1, - label: 1 - } - ], - 'x-props': { - enum: [ - { - value: 1, - label: 1 - } - ] - } - } - } - } - } - } - - expect(initSchemaDataReducer(beforeState, action)).toEqual(afterState) - expect(initSchemaDataReducer(beforeState1, action1)).toEqual(afterState1) - }) -}) diff --git a/packages/builder/src/__tests__/reducers/initSchemaData.spec.js b/packages/builder/src/__tests__/reducers/initSchemaData.spec.js new file mode 100644 index 00000000000..91ad91e77b0 --- /dev/null +++ b/packages/builder/src/__tests__/reducers/initSchemaData.spec.js @@ -0,0 +1,416 @@ +import initSchemaDataReducer from '../../reducers/initSchemaData' + +describe('initSchemaData reducers', () => { + test('initSchemaData reducers return initial state', () => { + expect(initSchemaDataReducer(undefined, {})).toEqual({}) + }) + + test('initSchemaData reducers return custom state when INIT_SCHEMA', () => { + const beforeState = {} + const action = { + type: 'INIT_SCHEMA', + data: { + type: 'object', + properties: { + a: { + type: 'string', + title: 'a' + }, + b: { + type: 'string', + title: 'b' + } + } + } + } + const afterState = { + type: 'object', + properties: { + a: { + type: 'string', + title: 'a', + id: 'a', + 'x-index': 0 + }, + b: { + type: 'string', + title: 'b', + id: 'b', + 'x-index': 1 + } + } + } + expect(initSchemaDataReducer(beforeState, action)).toEqual(afterState) + }) + + test('initSchemaData reducers return custom state when CHANGE_COMPONENT_ORDER', () => { + const beforeState = { + type: 'object', + properties: { + a: { + type: 'string', + id: 'a', + title: 'a', + 'x-index': 0 + }, + b: { + type: 'string', + id: 'b', + title: 'b', + 'x-index': 1 + } + } + } + const action = { + type: 'CHANGE_COMPONENT_ORDER', + data: { + id: ['a'], + targetId: ['b'] + } + } + const afterState = { + type: 'object', + properties: { + a: { + type: 'string', + title: 'a', + id: 'a', + 'x-index': 1 + }, + b: { + type: 'string', + title: 'b', + id: 'b', + 'x-index': 0 + } + } + } + const result = initSchemaDataReducer(beforeState, action) + + expect(result).toEqual(afterState) + }) + + test('initSchemaData reducers return custom state when ADD_COMPONENT', () => { + const beforeState = { + type: 'object', + properties: {} + } + const beforeStateWithData = { + type: 'object', + properties: { + '111': { + type: 'string', + title: '111' + }, + '333': { + type: 'string', + title: '333' + } + } + } + const beforeStateWithLayout = { + type: 'object', + properties: { + '111': { + type: 'object', + 'x-component': 'layout', + properties: {} + } + } + } + const action1 = { + type: 'ADD_COMPONENT', + data: { + id: '111', + component: { + key: 'input', + icon: 'info', + iconUrl: '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', + width: '58', + height: '28', + type: 'string', + title: '单行文本框', + placeholder: '请输入' + } + } + } + const action2 = { + type: 'ADD_COMPONENT', + data: { + id: '111', + component: { + key: 'wrapper_layout', + icon: 'clock-circle-o', + type: 'object', + title: 'Layout布局', + __key__: 'layout', + __key__data__: { + 'x-component': 'layout', + 'x-props': { + labelCol: 8, + wrapperCol: 6 + } + } + } + } + } + const action3 = { + type: 'ADD_COMPONENT', + data: { + id: '222', + containerId: ['111'], + component: { + key: 'input', + icon: 'info', + iconUrl: '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', + width: '58', + height: '28', + type: 'string', + title: '单行文本框', + placeholder: '请输入' + } + } + } + const action4 = { + type: 'ADD_COMPONENT', + data: { + id: '222', + existId: '333', + component: { + key: 'input', + icon: 'info', + iconUrl: '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', + width: '58', + height: '28', + type: 'string', + title: '单行文本框', + placeholder: '请输入' + } + } + } + + const afterState1 = { + type: 'object', + properties: { + '111': { + key: 'input', + icon: 'info', + iconUrl: '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', + width: '58', + height: '28', + type: 'string', + title: '单行文本框', + placeholder: '请输入', + 'x-index': 0, + id: '111' + } + } + } + const afterState2 = { + type: 'object', + properties: { + '111': { + key: 'wrapper_layout', + icon: 'clock-circle-o', + type: 'object', + title: 'Layout布局', + __key__: 'layout', + __key__data__: { + 'x-component': 'layout', + 'x-props': { labelCol: 8, wrapperCol: 6 } + }, + id: '111', + 'x-index': 0 + } + } + } + const afterState3 = { + type: 'object', + properties: { + '111': { + type: 'object', + 'x-component': 'layout', + properties: { + '222': { + key: 'input', + icon: 'info', + iconUrl: + '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', + width: '58', + height: '28', + type: 'string', + title: '单行文本框', + placeholder: '请输入', + 'x-index': 0, + id: '222' + } + } + } + } + } + const afterState4 = { + type: 'object', + properties: { + '111': { + type: 'string', + title: '111', + 'x-index': 0, + id: '111' + }, + '222': { + key: 'input', + icon: 'info', + iconUrl: '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', + width: '58', + height: '28', + type: 'string', + title: '单行文本框', + placeholder: '请输入', + 'x-index': 1, + id: '222' + }, + '333': { + type: 'string', + title: '333', + 'x-index': 2, + id: '333' + } + } + } + + expect(initSchemaDataReducer(beforeState, action1)).toEqual(afterState1) + expect(initSchemaDataReducer(beforeState, action2)).toEqual(afterState2) + expect(initSchemaDataReducer(beforeStateWithLayout, action3)).toEqual( + afterState3 + ) + expect(initSchemaDataReducer(beforeStateWithData, action4)).toEqual( + afterState4 + ) + }) + + test('initSchemaData reducers return custom state when DELETE_COMPONENT', () => { + const beforeState = { + type: 'object', + properties: { + '111': { + type: 'string', + title: '111' + } + } + } + const action = { + type: 'DELETE_COMPONENT', + data: { + id: '111' + } + } + const afterState = { + type: 'object', + properties: {} + } + expect(initSchemaDataReducer(beforeState, action)).toEqual(afterState) + }) + + test('initSchemaData reducers return custom state when EDIT_COMPONENT case1', () => { + const beforeState = { + type: 'object', + properties: { + '111': { + type: 'string', + title: '111' + } + } + } + const action = { + type: 'EDIT_COMPONENT', + data: { + id: ['111'], + propsData: { + __id__: '222' + } + } + } + const afterState = { + type: 'object', + properties: { + '111': { + type: 'string', + title: '111', + __id__: '222', + active: true + } + } + } + + const result = initSchemaDataReducer(beforeState, action) + + expect(result).toEqual(afterState) + }) + + test('initSchemaData reducers return custom state when EDIT_COMPONENT case2', () => { + const beforeState1 = { + type: 'object', + properties: { + '111': { + type: 'object', + 'x-component': 'layout', + properties: { + '222': { + type: 'string', + title: '222' + } + } + } + } + } + const action1 = { + type: 'EDIT_COMPONENT', + data: { + id: ['111', '222'], + propsData: { + __id__: '333', + 'x-props': { + enum: [ + { + value: 1, + label: 1 + } + ] + } + } + } + } + const afterState1 = { + type: 'object', + properties: { + '111': { + type: 'object', + 'x-component': 'layout', + properties: { + '222': { + __id__: '333', + type: 'string', + title: '222', + enum: [ + { + value: 1, + label: 1 + } + ], + active: true, + 'x-props': { + enum: [ + { + value: 1, + label: 1 + } + ] + } + } + } + } + } + } + expect(initSchemaDataReducer(beforeState1, action1)).toEqual(afterState1) + }) +}) diff --git a/packages/builder/src/__tests__/reducers/preview.spec.js b/packages/builder/src/__tests__/reducers/preview.spec.js new file mode 100644 index 00000000000..e41012da792 --- /dev/null +++ b/packages/builder/src/__tests__/reducers/preview.spec.js @@ -0,0 +1,31 @@ +import previewReducer from '../../reducers/preview' + +describe('preview reducers', () => { + test('preview reducers return initial state', () => { + expect(previewReducer(undefined, {})).toEqual(false) + }) + + test('preview reducers return custom state', () => { + const beforeState = false + const action = { + type: 'CHANGE_PREVIEW', + data: { + preview: true + } + } + const afterState = true + expect(previewReducer(beforeState, action)).toEqual(afterState) + }) + + test('preview reducers return custom state', () => { + const beforeState = true + const action = { + type: 'CHANGE_PREVIEW', + data: { + preview: false + } + } + const afterState = false + expect(previewReducer(beforeState, action)).toEqual(afterState) + }) +}) diff --git a/packages/builder/src/__tests__/util.spec.js b/packages/builder/src/__tests__/util.spec.js index b8b7411a2b1..fe683917cf9 100644 --- a/packages/builder/src/__tests__/util.spec.js +++ b/packages/builder/src/__tests__/util.spec.js @@ -5,275 +5,285 @@ import { checkRepeatId } from '../utils/util' -test('should correct getCompDetailById', () => { - var schema = { - type: 'object', - properties: { - aaa: { - type: 'object', - properties: { - aaa1: { - type: 'string' +describe('util test suites', () => { + test('should correct getCompDetailById', () => { + var schema = { + type: 'object', + properties: { + aaa: { + type: 'object', + properties: { + aaa1: { + type: 'string' + } } + }, + bbb: { + type: 'string' } - }, - bbb: { - type: 'string' } } - } - var value1 = getCompDetailById('bbb', schema) - var value2 = getCompDetailById('aaa1', schema) + var value1 = getCompDetailById(['bbb'], schema) + var value2 = getCompDetailById(['aaa', 'aaa1'], schema) - var result1 = { type: 'string', id: 'bbb' } - var result2 = { type: 'string', id: 'aaa1' } + var result1 = { type: 'string', id: 'bbb' } + var result2 = { type: 'string', id: 'aaa1' } - expect(value1).toEqual(result1) - expect(value2).toEqual(result2) -}) + expect(value1).toEqual(result1) + expect(value2).toEqual(result2) + }) -test('should correct wrap schema', () => { - var schema1 - var schema2 = {} - var schema3 = { - properties: { - wrapper1: { - type: 'object', - id: 'wrapper1', - __id__: 'wrapper', - 'x-component': 'layout', - 'x-props': { - labelCol: 10, - wrapperCol: 6, - _extra: { - icon: 'clock-circle-o', - type: 'object', - title: 'Layout布局', - __key__: 'layout', - __key__data__: { - 'x-component': 'layout', - 'x-props': { - labelCol: 8, - wrapperCol: 6 + test('should correct wrap schema', () => { + var schema1 + var schema2 = {} + var schema3 = { + properties: { + wrapper1: { + type: 'object', + id: 'wrapper1', + __id__: 'wrapper', + 'x-component': 'layout', + 'x-props': { + labelCol: 10, + wrapperCol: 6, + _extra: { + icon: 'clock-circle-o', + type: 'object', + title: 'Layout布局', + __key__: 'layout', + __key__data__: { + 'x-component': 'layout', + 'x-props': { + labelCol: 8, + wrapperCol: 6 + } } } - } - }, - properties: { - '810dbe8a-d654-4e26-aaf1-861a6b20c269': { - key: 'input', - icon: 'info', - iconUrl: - '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', - width: '58', - height: '28', - type: 'string', - title: '姓名', - placeholder: '请输入', - id: '810dbe8a-d654-4e26-aaf1-861a6b20c269', - 'x-index': 0, - __id__: 'name', - active: false, - 'x-props': {} - } - }, - active: false, - 'x-props.labelCol': 10, - 'x-props.wrapperCol': 6 - }, - grid1: { - type: 'object', - id: 'grid1', - __id__: 'grid1', - 'x-component': 'grid', - 'x-props': { - gutter: 20, - _extra: { - icon: 'clock-circle-o', - type: 'object', - title: 'Grid布局', - __key__: 'layout', - __key__data__: { - 'x-component': 'grid', - 'x-props': { - gutter: 20 - } + }, + properties: { + '810dbe8a-d654-4e26-aaf1-861a6b20c269': { + key: 'input', + icon: 'info', + iconUrl: + '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', + width: '58', + height: '28', + type: 'string', + title: '姓名', + placeholder: '请输入', + id: '810dbe8a-d654-4e26-aaf1-861a6b20c269', + 'x-index': 0, + __id__: 'name', + active: false, + 'x-props': {} } }, - 'cols-num': 6, - cols: [4, 4, 4, 4, 4, 4] + active: false, + 'x-props.labelCol': 10, + 'x-props.wrapperCol': 6 }, - properties: { - '6412a07a-691a-443a-a89d-d759579e4892': { - key: 'input', - icon: 'info', - iconUrl: - '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', - width: '58', - height: '28', - type: 'string', - title: '单行文本框', - placeholder: '请输入', - id: '6412a07a-691a-443a-a89d-d759579e4892', - 'x-index': 0, - __id__: 'aaa', - active: false, - 'x-props': {} - }, - '77aa9e8a-7d4e-4b69-b2b8-4957c14d5cc7': { - key: 'input', - icon: 'info', - iconUrl: - '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', - width: '58', - height: '28', - type: 'string', - title: '单行文本框', - placeholder: '请输入', - id: '77aa9e8a-7d4e-4b69-b2b8-4957c14d5cc7', - 'x-index': 1, - __id__: 'bbb', - active: false, - 'x-props': {} + grid1: { + type: 'object', + id: 'grid1', + __id__: 'grid1', + 'x-component': 'grid', + 'x-props': { + gutter: 20, + _extra: { + icon: 'clock-circle-o', + type: 'object', + title: 'Grid布局', + __key__: 'layout', + __key__data__: { + 'x-component': 'grid', + 'x-props': { + gutter: 20 + } + } + }, + 'cols-num': 6, + cols: [4, 4, 4, 4, 4, 4] }, - 'cc714340-c559-4bf2-bc7b-825090e2a344': { - key: 'multipleInput', - icon: 'file-text', - iconUrl: - '//gw.alicdn.com/tfs/TB1zk14DjTpK1RjSZKPXXa3UpXa-116-78.png', - width: '58', - height: '39', - type: 'string', - title: '多行文本框', - placeholder: '请输入', - 'x-props': {}, - id: 'cc714340-c559-4bf2-bc7b-825090e2a344', - 'x-index': 2, - __id__: 'ccc', - active: true + properties: { + '6412a07a-691a-443a-a89d-d759579e4892': { + key: 'input', + icon: 'info', + iconUrl: + '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', + width: '58', + height: '28', + type: 'string', + title: '单行文本框', + placeholder: '请输入', + id: '6412a07a-691a-443a-a89d-d759579e4892', + 'x-index': 0, + __id__: 'aaa', + active: false, + 'x-props': {} + }, + '77aa9e8a-7d4e-4b69-b2b8-4957c14d5cc7': { + key: 'input', + icon: 'info', + iconUrl: + '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', + width: '58', + height: '28', + type: 'string', + title: '单行文本框', + placeholder: '请输入', + id: '77aa9e8a-7d4e-4b69-b2b8-4957c14d5cc7', + 'x-index': 1, + __id__: 'bbb', + active: false, + 'x-props': {} + }, + 'cc714340-c559-4bf2-bc7b-825090e2a344': { + key: 'multipleInput', + icon: 'file-text', + iconUrl: + '//gw.alicdn.com/tfs/TB1zk14DjTpK1RjSZKPXXa3UpXa-116-78.png', + width: '58', + height: '39', + type: 'string', + title: '多行文本框', + placeholder: '请输入', + 'x-props': {}, + id: 'cc714340-c559-4bf2-bc7b-825090e2a344', + 'x-index': 2, + __id__: 'ccc', + active: true + }, + 'e893cd93-e1a9-4834-91c0-6d56dc092495': { + key: 'checkbox', + icon: 'check-square-o', + iconUrl: + '//gw.alicdn.com/tfs/TB1ELO7DgHqK1RjSZFPXXcwapXa-56-56.png', + width: '28', + height: '28', + type: 'checkbox', + title: '复选框', + 'x-component': 'checkbox', + 'x-props': {}, + id: 'e893cd93-e1a9-4834-91c0-6d56dc092495', + 'x-index': 3, + __id__: 'e893cd93-e1a9-4834-91c0-6d56dc092495', + active: false + } }, - 'e893cd93-e1a9-4834-91c0-6d56dc092495': { - key: 'checkbox', - icon: 'check-square-o', - iconUrl: - '//gw.alicdn.com/tfs/TB1ELO7DgHqK1RjSZFPXXcwapXa-56-56.png', - width: '28', - height: '28', - type: 'checkbox', - title: '复选框', - 'x-component': 'checkbox', - 'x-props': {}, - id: 'e893cd93-e1a9-4834-91c0-6d56dc092495', - 'x-index': 3, - __id__: 'e893cd93-e1a9-4834-91c0-6d56dc092495', - active: false - } - }, - active: true, - 'x-props.gutter': 20, - 'x-props.cols-num': 6, - 'x-props.cols': [4, 4, 4, 4, 4, 4] - } - }, - type: 'object' - } + active: true, + 'x-props.gutter': 20, + 'x-props.cols-num': 6, + 'x-props.cols': [4, 4, 4, 4, 4, 4] + } + }, + type: 'object' + } - var value1 = wrapSubmitSchema(schema1) - var value2 = wrapSubmitSchema(schema2) - var value3 = wrapSubmitSchema(schema3) + var value1 = wrapSubmitSchema(schema1) + var value2 = wrapSubmitSchema(schema2) + var value3 = wrapSubmitSchema(schema3) - var result1 = { - type: 'object', - properties: {} - } + var result1 = { + type: 'object', + properties: {} + } - expect(JSON.stringify(value1)).toBe(JSON.stringify(result1)) - expect(JSON.stringify(value2)).toBe(JSON.stringify(result1)) + expect(JSON.stringify(value1)).toBe(JSON.stringify(result1)) + expect(JSON.stringify(value2)).toBe(JSON.stringify(result1)) - expect(value3).toHaveProperty('type', 'object') - expect(value3).toHaveProperty('properties') - expect(value3).not.toHaveProperty('properties.wrapper.id') - expect(value3).not.toHaveProperty('properties.wrapper.__id__') - // expect(value3).not.toHaveProperty('properties.wrapper.x-props.labelCol'); - // expect(value3).not.toHaveProperty('properties.wrapper.x-props.wrapperCol'); - expect(value3).not.toHaveProperty('properties.wrapper.active') - expect(value3).not.toHaveProperty( - 'properties.wrapper.properties.name.iconWidth' - ) - expect(value3).not.toHaveProperty( - 'properties.wrapper.properties.name.iconHeight' - ) - expect(value3).not.toHaveProperty('properties.wrapper.properties.name.__id__') - expect(value3).toHaveProperty('properties.wrapper.properties.name.key') - expect(value3).not.toHaveProperty('properties.wrapper.properties.name.width') - expect(value3).not.toHaveProperty('properties.wrapper.properties.name.height') - expect(value3).not.toHaveProperty('properties.wrapper.properties.name.active') -}) + expect(value3).toHaveProperty('type', 'object') + expect(value3).toHaveProperty('properties') + expect(value3).not.toHaveProperty('properties.wrapper.id') + expect(value3).not.toHaveProperty('properties.wrapper.__id__') + // expect(value3).not.toHaveProperty('properties.wrapper.x-props.labelCol'); + // expect(value3).not.toHaveProperty('properties.wrapper.x-props.wrapperCol'); + expect(value3).not.toHaveProperty('properties.wrapper.active') + expect(value3).not.toHaveProperty( + 'properties.wrapper.properties.name.iconWidth' + ) + expect(value3).not.toHaveProperty( + 'properties.wrapper.properties.name.iconHeight' + ) + expect(value3).not.toHaveProperty( + 'properties.wrapper.properties.name.__id__' + ) + expect(value3).toHaveProperty('properties.wrapper.properties.name.key') + expect(value3).not.toHaveProperty( + 'properties.wrapper.properties.name.width' + ) + expect(value3).not.toHaveProperty( + 'properties.wrapper.properties.name.height' + ) + expect(value3).not.toHaveProperty( + 'properties.wrapper.properties.name.active' + ) + }) -test('should correct flatObj', () => { - var obj1 = { - 'a.b.c': '3', - 'a1.b1': '2', - a2: 3, - 'x-props.htmltype': true, - 'x-props.a': 2, - 'x-props': { - a: 1 + test('should correct flatObj', () => { + var obj1 = { + 'a.b.c': '3', + 'a1.b1': '2', + a2: 3, + 'x-props.htmltype': true, + 'x-props.a': 2, + 'x-props': { + a: 1 + } } - } - var value1 = flatObj(obj1) - var result1 = { - 'a.b.c': '3', - 'a1.b1': '2', - a2: 3, - 'x-props.htmltype': true, - 'x-props.a': 2, - 'x-props': { - a: 2, - htmltype: true - }, - a: { - b: { - c: '3' + var value1 = flatObj(obj1) + var result1 = { + 'a.b.c': '3', + 'a1.b1': '2', + a2: 3, + 'x-props.htmltype': true, + 'x-props.a': 2, + 'x-props': { + a: 2, + htmltype: true + }, + a: { + b: { + c: '3' + } + }, + a1: { + b1: '2' } - }, - a1: { - b1: '2' } - } - expect(value1).toEqual(result1) -}) + expect(value1).toEqual(result1) + }) -test('checkRepeatId has repeated', () => { - var schema = { - type: 'object', - properties: { - aa: { - type: 'string' - }, - bb: { - type: 'string', - __id__: 'aa' + test('checkRepeatId has repeated', () => { + var schema = { + type: 'object', + properties: { + aa: { + type: 'string' + }, + bb: { + type: 'string', + __id__: 'aa' + } } } - } - var value = checkRepeatId(schema) - expect(value).not.toBeFalsy() -}) + var value = checkRepeatId(schema) + expect(value).not.toBeFalsy() + }) -test('checkRepeatId has not repeated', () => { - var schema = { - type: 'object', - properties: { - aa: { - type: 'string' - }, - bb: { - type: 'string' + test('checkRepeatId has not repeated', () => { + var schema = { + type: 'object', + properties: { + aa: { + type: 'string' + }, + bb: { + type: 'string' + } } } - } - var value = checkRepeatId(schema) - expect(value).toBeFalsy() + var value = checkRepeatId(schema) + expect(value).toBeFalsy() + }) }) diff --git a/packages/builder/src/actions/index.js b/packages/builder/src/actions/index.js index a2f4640bf88..67377209b00 100644 --- a/packages/builder/src/actions/index.js +++ b/packages/builder/src/actions/index.js @@ -1,12 +1,9 @@ import uuid from 'uuid' -export const changeLayoutId = layoutId => ({ - type: 'CHANGE_LAYOUTID', - data: { - id: layoutId - } -}) - +/** + * 添加组件 + * @param {Object} component + */ export const addComponent = (component, existId, id, type, containerId) => ({ type: 'ADD_COMPONENT', data: { @@ -18,32 +15,46 @@ export const addComponent = (component, existId, id, type, containerId) => ({ } }) +/** + * 添加组件并置于编辑状态 + * @param {Object} component + */ export const addComponentAndEdit = ( component, existId, type, - containerId + containerId = [] ) => dispatch => { - const id = uuid() - dispatch(addComponent(component, existId, id, type, containerId)) - dispatch(changeComponent(id)) - dispatch(showComponentProps(id, component, containerId)) - - if (component.__key__ === 'layout') { - dispatch(changeLayoutId(id)) - } + const id = component.id || uuid() + dispatch(addComponent(component, existId, id, type, containerId)) + dispatch(changeComponent(Array.isArray(id) ? id : [...containerId, id])) + dispatch( + showComponentProps(Array.isArray(id) ? id : [...containerId, id], component) + ) dispatch( - editComponent( - id, - { - active: true - }, - containerId - ) + editComponent(Array.isArray(id) ? id : [...containerId, id], { + active: true + }) ) } +/** + * 移动组件 + * @param {Array} sourceId 源ID + * @param {Array} targetId 目标ID + */ +export const moveComponent = (sourceId, targetId) => ({ + type: 'MOVE_COMOPNENT', + data: { + id: sourceId, + targetId + } +}) + +/** + * 改变组件顺序 + */ export const changeComponentOrder = (sourceId, targetId, containerId) => ({ type: 'CHANGE_COMPONENT_ORDER', data: { @@ -53,15 +64,20 @@ export const changeComponentOrder = (sourceId, targetId, containerId) => ({ } }) -export const editComponent = (id, propsData, containerId) => ({ +/** + * 修改组件数据 + */ +export const editComponent = (id, propsData) => ({ type: 'EDIT_COMPONENT', data: { id, - propsData, - containerId + propsData } }) +/** + * 删除组件 + */ export const deleteComponent = id => ({ type: 'DELETE_COMPONENT', data: { @@ -69,15 +85,20 @@ export const deleteComponent = id => ({ } }) -export const showComponentProps = (id, comp, containerId) => ({ +/** + * 展示组件属性 + */ +export const showComponentProps = (id, comp) => ({ type: 'SHOW_COMPONENT_PROPS', data: { id, - comp, - containerId + comp } }) +/** + * 编辑组件属性 + */ export const editComponentProps = (id, propsData) => ({ type: 'EDIT_COMPONENT_PROPS', data: { @@ -86,6 +107,9 @@ export const editComponentProps = (id, propsData) => ({ } }) +/** + * 改变预览状态 + */ export const changePreview = preview => ({ type: 'CHANGE_PREVIEW', data: { @@ -93,6 +117,9 @@ export const changePreview = preview => ({ } }) +/** + * 改变源码编辑状态 + */ export const changeCodeMode = codemode => ({ type: 'CHANGE_CODEMODE', data: { @@ -100,6 +127,9 @@ export const changeCodeMode = codemode => ({ } }) +/** + * 改变当前编辑的组件 + */ export const changeComponent = componentId => ({ type: 'CHANGE_COMPONENT', data: { @@ -107,11 +137,17 @@ export const changeComponent = componentId => ({ } }) +/** + * 修改全局配置 + */ export const changeGbConfig = data => ({ type: 'CHANGE_GB_CONFIG', data }) +/** + * 初始化schema + */ export const initSchema = data => ({ type: 'INIT_SCHEMA', data diff --git a/packages/builder/src/components/fields/field.js b/packages/builder/src/components/fields/field.js new file mode 100644 index 00000000000..09c804a8538 --- /dev/null +++ b/packages/builder/src/components/fields/field.js @@ -0,0 +1,103 @@ +import React from 'react' +import { DragSource } from 'react-dnd' +import ItemTypes from '../../constants/itemType' +import uuid from 'uuid' + +const DEFAULT_ICON_URL = + '//gw.alicdn.com/tfs/TB10xa4DbrpK1RjSZTEXXcWAVXa-116-60.png' + +const wrapFieldItem = fieldItem => + !!fieldItem && typeof fieldItem === 'object' + ? { + iconUrl: DEFAULT_ICON_URL, + ...fieldItem + } + : { + type: fieldItem, + icon: '', + iconUrl: DEFAULT_ICON_URL, + width: '58', + height: '30', + title: '自定义组件' + } + +const Box = ({ + addComponentAndEdit, + fieldItem, + isDragging, + connectDragSource +}) => { + const style = { + opacity: isDragging ? 0.4 : 1 + } + + if (isDragging) { + style.filter = 'blur(2px) brightness(.6)' + } + + const newFieldItem = wrapFieldItem(fieldItem) + const { key, iconUrl, width, height, title } = newFieldItem + + return connectDragSource( +
  • { + addComponentAndEdit(newFieldItem) + }} + style={style} + > + + {title} +
  • + ) +} + +export default DragSource( + ItemTypes.FIELD, + { + beginDrag: props => { + const { fieldItem } = props + const id = uuid() + return { fieldItem, id } + }, + endDrag(props, monitor) { + console.info('endDrag') + if (!monitor.didDrop()) { + return + } + console.info('endDrag success') + const item = monitor.getItem() + const dropResult = monitor.getDropResult() + + // @note: 不要直接拿fieldItem,避免下面删掉属性直接影响到原有的fieldItem + const fieldItem = { ...item.fieldItem } + const { addComponentAndEdit } = props + + // 删除多余的跟渲染无关的属性 + try { + ;['height', 'icon', 'iconUrl', 'width'].forEach(key => { + delete fieldItem[key] + }) + } catch (e) {} + + if (dropResult) { + if (dropResult.targetType === 'layout') { + addComponentAndEdit(fieldItem, '', 'layout', dropResult.targetId) + } else { + addComponentAndEdit(fieldItem) + } + } + } + }, + (connect, monitor) => ({ + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging() + }) +)(Box) diff --git a/packages/builder/src/components/fields/index.js b/packages/builder/src/components/fields/index.js index 75cba201334..dcaa027fadc 100644 --- a/packages/builder/src/components/fields/index.js +++ b/packages/builder/src/components/fields/index.js @@ -1,139 +1,58 @@ -import React, { Component } from 'react' +import React from 'react' import cls from 'classnames' -import PropTypes from 'prop-types' import defaultSupportFieldList from '../../configs/supportFieldList' -import { Header } from '../../utils/util' +import { Header, wrapComp2Class } from '../../utils/util' import { connect } from 'react-redux' -import { - addComponent, - editComponent, - showComponentProps, - changeComponent, - addComponentAndEdit -} from '../../actions' +import { addComponentAndEdit } from '../../actions' import uniqBy from 'lodash.uniqby' import { indexStyle as IndexStyle } from './style' - -class FieldList extends Component { - static propTypes = { - // addComponent: PropTypes.func, - // eslint-disable-next-line - supportFieldList: PropTypes.array, - includeFieldListKeyList: PropTypes.arrayOf(PropTypes.string) - } - - static defaultProps = { - supportFieldList: [], - includeFieldListKeyList: [] - } - - constructor(props) { - super(props) - const { supportFieldList, includeFieldListKeyList } = props - this.fieldList = defaultSupportFieldList - if (supportFieldList.length) { - this.fieldList = uniqBy( - [...supportFieldList, ...defaultSupportFieldList], - 'key' - ) - } - if (includeFieldListKeyList.length) { - this.fieldList = this.fieldList.filter( - fieldItem => includeFieldListKeyList.indexOf(fieldItem.key) > -1 - ) - } +import Field from './field' + +function FieldList(props) { + const { + addComponentAndEdit, + supportFieldList = [], + includeFieldListKeyList = [] + } = props + + let fieldList = defaultSupportFieldList + if (supportFieldList.length) { + fieldList = uniqBy([...supportFieldList, ...defaultSupportFieldList], 'key') } - - onDragStart = (ev, fieldItem) => { - ev.dataTransfer.setData('text/plain', JSON.stringify(fieldItem)) - // eslint-disable-next-line - ev.dataTransfer.dropEffect = 'copy' + if (includeFieldListKeyList.length) { + fieldList = fieldList.filter( + fieldItem => includeFieldListKeyList.indexOf(fieldItem.key) > -1 + ) } - wrapFieldItem = fieldItem => - typeof fieldItem === 'string' - ? { - type: fieldItem, - icon: '', - iconUrl: 'gw.alicdn.com/tfs/TB10xa4DbrpK1RjSZTEXXcWAVXa-116-60.png', - width: '58', - height: '30', - title: '自定义组件' - } - : fieldItem - - renderFieldList() { - const _addComponentAndEdit = this.props.addComponentAndEdit - - return ( + return ( + +
    +

    组件

    +

    可将选项拖动到主面板进行编辑

    +
      - {this.fieldList.map((fieldItem, i) => { - const newFieldItem = this.wrapFieldItem(fieldItem) - const { - key, - iconUrl = '//gw.alicdn.com/tfs/TB10xa4DbrpK1RjSZTEXXcWAVXa-116-60.png', - width, - height - } = newFieldItem + {fieldList.map((fieldItem, i) => { return ( -
    • this.onDragStart(ev, newFieldItem)} - onClick={() => { - _addComponentAndEdit && _addComponentAndEdit(newFieldItem) - }} - > - - {newFieldItem.title} -
    • + ) })}
    - ) - } - - render() { - return ( - -
    -

    组件

    -

    可将选项拖动到主面板进行编辑

    -
    - {this.renderFieldList()} -
    - ) - } +
    + ) } const mapStateToProps = state => state const mapDispatchToProps = dispatch => ({ - addComponent: component => dispatch(addComponent(component)), - editComponent: (id, propsData, containerId) => - dispatch(editComponent(id, propsData, containerId)), - showComponentProps: (id, comp) => dispatch(showComponentProps(id, comp)), - changeComponent: componentId => dispatch(changeComponent(componentId)), - addComponentAndEdit: (component, existId, type, containerId) => - dispatch(addComponentAndEdit(component, existId, type, containerId)) + addComponentAndEdit: (...args) => dispatch(addComponentAndEdit(...args)) }) -class StyledFieldListComp extends React.Component { - render() { - return - } -} - export default connect( mapStateToProps, mapDispatchToProps -)(StyledFieldListComp) +)(wrapComp2Class(FieldList)) diff --git a/packages/builder/src/components/fields/layout.js b/packages/builder/src/components/fields/layout.js index 65f4f816247..d129f5f36f1 100644 --- a/packages/builder/src/components/fields/layout.js +++ b/packages/builder/src/components/fields/layout.js @@ -3,19 +3,13 @@ import React from 'react' import cls from 'classnames' import PropTypes from 'prop-types' import supportLayoutList from '../../configs/supportLayoutList' -import { Header } from '../../utils/util' +import { Header, wrapComp2Class } from '../../utils/util' import { connect } from 'react-redux' import { addComponentAndEdit } from '../../actions' - +import Field from './layoutField' import { layoutStyle as LayoutStyle } from './style' class Component extends React.Component { - static propTypes = { - addComponentAndEdit: PropTypes.func - } - - static defaultProps = {} - constructor(props) { super(props) @@ -23,24 +17,15 @@ class Component extends React.Component { } renderList() { - const { addComponentAndEdit: _addComponentAndEdit } = this.props return (
      {this.layoutList.map((item, i) => { - const { title } = item return ( -
    • { - _addComponentAndEdit && - _addComponentAndEdit( - item - ) - }} - > - {title} -
    • + addComponentAndEdit={this.props.addComponentAndEdit} + /> ) })}
    @@ -60,20 +45,19 @@ class Component extends React.Component { } } +Component.propTypes = { + addComponentAndEdit: PropTypes.func +} + +Component.defaultProps = {} + const mapStateToProps = state => state const mapDispatchToProps = dispatch => ({ - addComponentAndEdit: (component, existId, type, containerId) => - dispatch(addComponentAndEdit(component, existId, type, containerId)) + addComponentAndEdit: (...args) => dispatch(addComponentAndEdit(...args)) }) -class StyledLayoutListComp extends React.Component { - render() { - return - } -} - export default connect( mapStateToProps, mapDispatchToProps -)(StyledLayoutListComp) +)(wrapComp2Class(Component)) diff --git a/packages/builder/src/components/fields/layoutField.js b/packages/builder/src/components/fields/layoutField.js new file mode 100644 index 00000000000..9a3da8f71ca --- /dev/null +++ b/packages/builder/src/components/fields/layoutField.js @@ -0,0 +1,85 @@ +import React from 'react' +import { DragSource } from 'react-dnd' +import ItemTypes from '../../constants/itemType' +import uuid from 'uuid' + +const Box = ({ + addComponentAndEdit, + fieldItem, + isDragging, + connectDragSource +}) => { + const opacity = isDragging ? 0.4 : 1 + const { key, title } = fieldItem + return ( +
  • { + const id = uuid() + const newFieldItem = { + type: 'object', + key: fieldItem.key, + id, + ...fieldItem.__key__data__, + properties: {}, + 'x-props': { + ...fieldItem.__key__data__['x-props'], + _extra: fieldItem + } + } + addComponentAndEdit(newFieldItem) + }} + style={Object.assign({}, { opacity })} + > + {title} +
  • + ) +} + +export default DragSource( + ItemTypes.LAYOUT, + { + beginDrag: props => { + const { fieldItem } = props + const id = uuid() + return { fieldItem, id } + }, + endDrag(props, monitor) { + if (!monitor.didDrop()) { + return + } + + const item = monitor.getItem() + const dropResult = monitor.getDropResult() + const { id } = item + const fieldItem = { ...item.fieldItem } + const { addComponentAndEdit } = props + try { + ;['height', 'icon', 'iconUrl', 'width'].forEach(key => { + delete fieldItem[key] + }) + } catch (e) {} + + if (dropResult) { + const newFieldItem = { + type: 'object', + key: fieldItem.key, + id, + ...fieldItem.__key__data__, + properties: {}, + 'x-props': { + ...fieldItem.__key__data__['x-props'], + _extra: fieldItem + } + } + + addComponentAndEdit(newFieldItem) + } + } + }, + (connect, monitor) => ({ + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging() + }) +)(Box) diff --git a/packages/builder/src/components/index.js b/packages/builder/src/components/index.js new file mode 100644 index 00000000000..e9ebbcdb17a --- /dev/null +++ b/packages/builder/src/components/index.js @@ -0,0 +1,13 @@ +import PropsSetting from './props/propsSetting' +import FieldList from './fields/index' +import Preview from './preview/index' +import GlobalBtnList from './globalBtnList/index' + +export { + PropsSetting, + FieldList, + Preview, + GlobalBtnList +} + +export default {} diff --git a/packages/builder/src/components/preview/card.js b/packages/builder/src/components/preview/card.js new file mode 100644 index 00000000000..4bda91fae92 --- /dev/null +++ b/packages/builder/src/components/preview/card.js @@ -0,0 +1,191 @@ +import React, { useImperativeHandle, useRef, forwardRef } from 'react' +import { DragSource, DropTarget } from 'react-dnd' +import cls from 'classnames' +import ItemTypes from '../../constants/itemType' +import { isLayoutWrapper } from '../../utils/util' +import flow from 'lodash.flow' +const style = { + cursor: 'move' +} +const Card = forwardRef( + ( + { + Field, + props, + canDrop, + isOver, + that, + isDragging, + connectDragSource, + connectDropTarget, + id + }, + ref + ) => { + const elementRef = useRef(null) + connectDragSource(elementRef) + connectDropTarget(elementRef) + useImperativeHandle(ref, () => ({ + getNode: () => elementRef.current + })) + + const opacity = isDragging ? 0.2 : 1 + const isActive = canDrop && isOver + let backgroundColor = '#fff' + if (isActive) { + backgroundColor = '#f5f5f5' + } + + const { active = false } = props.schema + const comp = { + ...props.schema + } + + return isLayoutWrapper(comp) ? ( + connectDropTarget( +
    + {!Object.keys(props.schema.properties).length ? ( +

    + 请从左边字段拖拽组件进来这里 +

    + ) : ( + React.createElement(Field, props) + )} +
    + ) + ) : ( +
    + {React.createElement(Field, { ...props })} +
    { + ev.preventDefault() + that.onMouseClick(id, comp) + }} + /> + +
    +
    + ) + } +) + +export default flow( + DragSource( + ItemTypes.CARD, + { + beginDrag: props => { + const { id } = props + return { + source: 'card', + id + } + }, + endDrag(props, monitor) { + if (!monitor.didDrop()) { + return + } + + const dropResult = monitor.getDropResult() + const { id: droppedId } = props + if (dropResult) { + const { targetId, targetType } = dropResult + + if (targetType === 'layout') { + // props.move(droppedId, targetId) + } else { + props.moveCard(droppedId, targetId, targetType) + } + } + } + }, + (connect, monitor) => ({ + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging() + }) + ), + DropTarget( + [ItemTypes.CARD, ItemTypes.FIELD], + { + drop(props, monitor) { + const comp = props.props.schema + + return { + name: 'card', + targetId: props.id, + targetType: isLayoutWrapper(comp) ? 'layout' : '' + } + }, + hover(props, monitor, component) { + const node = component.getNode() + if (!node) { + return null + } + const hoverBoundingRect = node.getBoundingClientRect() + // Get vertical middle + const hoverMiddleY = + (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2 + // Determine mouse position + const clientOffset = monitor.getClientOffset() + // Get pixels to the top + const hoverClientY = clientOffset.y - hoverBoundingRect.top + + const isOverHalf = hoverClientY > hoverMiddleY + monitor.getItem().isOverHalf = isOverHalf + } + }, + (connect, monitor) => ({ + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver(), + isOverCurrent: monitor.isOver({ shallow: true }), + canDrop: monitor.canDrop() + }) + ) +)(Card) diff --git a/packages/builder/src/components/preview/fieldMiddleware.js b/packages/builder/src/components/preview/fieldMiddleware.js index 07864b8bf43..279105adcd0 100644 --- a/packages/builder/src/components/preview/fieldMiddleware.js +++ b/packages/builder/src/components/preview/fieldMiddleware.js @@ -1,120 +1,44 @@ import React from 'react' -import cls from 'classnames' import { registerFieldMiddleware } from '../../utils/baseForm' +import Card from './card' +import { FormConsumer } from '../../constants/context' -export default (FormConsumer, that) => { +export default that => { + // 判断注册过则不再注册 const hasRegisted = window.__hasRegisted__ || false if (hasRegisted) { return false } - const { UI } = that.props window.__hasRegisted__ = true + + const moveCard = (dragIndex, hoverIndex) => { + that.props.changeComponentOrder(dragIndex, hoverIndex) + } + + const move = (sourceId, targetId) => { + that.props.moveComponent(sourceId, targetId) + } + registerFieldMiddleware(Field => props => React.createElement(FormConsumer, {}, (obj = {}) => { const { type } = obj + + // 根节点或者非预览直接返回 if (props.path.length === 0 || type !== 'preview') { return React.createElement(Field, props) } - const { title = '', active = false } = props.schema - const id = props.path[0] - const comp = { - id, - ...props.schema - } - const isLayoutWrapper = - comp['x-props'] && - comp['x-props']._extra && - comp['x-props']._extra.__key__ === 'layout' - const layoutDragProps = isLayoutWrapper - ? { - onDragOver: ev => that.onDragOver(ev, 'layout'), - onDragLeave: ev => that.onDragLeave(ev, 'layout'), - onDrop: ev => that.onDrop(ev, null, 'layout', id) - } - : {} - return isLayoutWrapper ? ( -
    - ) : ( -
    -
    - that.onDrop( - ev, - id, - props.schemaPath.length > 1 ? 'layout' : '', - props.schemaPath.length > 1 ? props.schemaPath[0] : '' - ) - } - /> -
    that.onDragStart(ev, id, 'move')} - > - {React.createElement(Field, { ...props })} -
    { - ev.preventDefault() - that.onMouseClick(id, comp) - }} - /> - { - that.props.changeComponent() - that.deleteComponent(id) - }} - > - 删除 - -
    -
    + return ( + ) }) ) diff --git a/packages/builder/src/components/preview/index.js b/packages/builder/src/components/preview/index.js index ea363f36704..bce0adaf8b0 100644 --- a/packages/builder/src/components/preview/index.js +++ b/packages/builder/src/components/preview/index.js @@ -1,205 +1,45 @@ import React, { Component } from 'react' import cls from 'classnames' -import PropTypes from 'prop-types' -import { SchemaForm } from '../../utils/baseForm' + import { connect } from 'react-redux' import { - addComponentAndEdit, changeComponentOrder, - editComponent, + moveComponent, deleteComponent, showComponentProps, changeComponent } from '../../actions' -import { normalizeSchema } from '../../utils/lang' -import { isEmptyObj, Header } from '../../utils/util' -import pick from 'lodash.pick' + +import { Header, wrapComp2Class } from '../../utils/util' import registerPreviewFieldMiddleware from './fieldMiddleware' +import MainBox from './mainBox' import PreviewStyle from './style' -const { Consumer: FormConsumer, Provider: FormProvider } = React.createContext() - class Preview extends Component { - static propTypes = { - changeComponentOrder: PropTypes.func, - addComponent: PropTypes.func, - preview: PropTypes.bool, - gbConfig: PropTypes.object, - onChange: PropTypes.func - } - componentDidMount() { - registerPreviewFieldMiddleware(FormConsumer, this) + registerPreviewFieldMiddleware(this) } - onMouseClick = (id, comp, type) => { - const { layoutId } = this.props + onMouseClick = (id, comp) => { this.props.changeComponent(id) this.props.showComponentProps(id, comp) - - this.props.editComponent( - id, - { - active: true - }, - type === 'layout' ? '' : layoutId - ) - } - - onDragStart = (ev, sourceId, dropType) => { - const plainData = { - dropType, - id: sourceId - } - ev.dataTransfer.setData('text/plain', JSON.stringify(plainData)) - ev.dataTransfer.dropEffect = dropType - } - - /** - * @param ev Object 事件event - * @param id String 已存在的id,将插入id后面 - * @param type String 拖拽类型 layout/field - * @param containerId String 已存在的容器id,将插入在containerId里面 - */ - onDrop = (ev, id, type, containerId) => { - ev.preventDefault() - ev.stopPropagation() - const { target } = ev - target.classList.remove('active') - let newFieldItem = {} - try { - newFieldItem = JSON.parse(ev.dataTransfer.getData('text')) - } catch (e) { - throw new Error('parse drop data error', e.message) - } - - if (newFieldItem.dropType === 'move') { - this.props.changeComponentOrder(newFieldItem.id, id, containerId) - } else { - this.props.addComponentAndEdit(newFieldItem, id, type, containerId) - } - } - - onDragLeave = ev => { - const { target } = ev - target.classList.remove('active') - } - - onDragOver = ev => { - ev.preventDefault() - const { target } = ev - target.classList.add('active') } deleteComponent = id => { this.props.deleteComponent && this.props.deleteComponent(id) } - onWrapperDrop = (ev, id) => { - ev.preventDefault() - let newFieldItem = {} - try { - newFieldItem = JSON.parse(ev.dataTransfer.getData('text')) - } catch (e) { - throw new Error('parse drop data error', e.message) - } - - if (newFieldItem.dropType === 'move') { - this.props.changeComponentOrder(newFieldItem.id, id) - } else { - this.props.addComponentAndEdit(newFieldItem, id) - } - } - - onWrapperDragOver = ev => { - ev.preventDefault() - } - - renderPreviewList() { - const { - preview, - gbConfig = {}, - schema = {}, - renderEngine, - onChange - } = this.props - const { properties = {} } = schema - - const { FormButtonGroup, Submit, Reset } = renderEngine - - if (isEmptyObj(properties)) { - return

    请从左边字段添加组件进来吧

    - } - - const children = - gbConfig.needFormButtonGroup === true ? ( - - 提交 - 重置 - - ) : ( - ' ' - ) - - const globalCfg = pick(gbConfig, [ - 'labelCol', - 'wrapperCol', - 'action', - 'labelAlign', - 'labelTextAlign', - 'autoAddColon', - 'inline', - 'size', - 'editable' - ]) - - return ( - - - {children} - -
    this.onDrop(ev)} - /> - - ) - } - render() { - const { initSchemaData = {} } = this.props - - // 一个组件都没有的时候把拖拽对象赋值到整个card - const dragProps = - isEmptyObj(initSchemaData) || isEmptyObj(initSchemaData.properties) - ? { - onDragOver: this.onWrapperDragOver, - onDrop: this.onWrapperDrop - } - : {} - return (

    预览区域

    组件过多时可下拉查看更多

    -
    {this.renderPreviewList()}
    +
    ) } @@ -208,24 +48,16 @@ class Preview extends Component { const mapStateToProps = state => state const mapDispatchToProps = dispatch => ({ - addComponentAndEdit: (component, existId, type, containerId) => - dispatch(addComponentAndEdit(component, existId, type, containerId)), changeComponentOrder: (sourceId, targetId, containerId) => dispatch(changeComponentOrder(sourceId, targetId, containerId)), - editComponent: (id, propsData, containerId) => - dispatch(editComponent(id, propsData, containerId)), + moveComponent: (sourceId, targetId) => + dispatch(moveComponent(sourceId, targetId)), deleteComponent: id => dispatch(deleteComponent(id)), showComponentProps: (id, comp) => dispatch(showComponentProps(id, comp)), changeComponent: componentId => dispatch(changeComponent(componentId)) }) -class StyledPreviewComp extends React.Component { - render() { - return - } -} - export default connect( mapStateToProps, mapDispatchToProps -)(StyledPreviewComp) +)(wrapComp2Class(Preview)) diff --git a/packages/builder/src/components/preview/mainBox.js b/packages/builder/src/components/preview/mainBox.js new file mode 100644 index 00000000000..9d55351e275 --- /dev/null +++ b/packages/builder/src/components/preview/mainBox.js @@ -0,0 +1,133 @@ +import React, { useRef, forwardRef } from 'react' +import { DropTarget } from 'react-dnd' +import ItemTypes from '../../constants/itemType' +import { SchemaForm } from '../../utils/baseForm' +import { isEmptyObj } from '../../utils/util' +import pick from 'lodash.pick' +import { normalizeSchema } from '../../utils/lang' +import { FormProvider } from '../../constants/context' + +const RenderPreviewList = ({ props }) => { + const { preview, gbConfig = {}, schema = {}, renderEngine, onChange } = props + const { properties = {} } = schema + const { FormButtonGroup, Submit, Reset } = renderEngine + const { needFormButtonGroup } = gbConfig + + if (isEmptyObj(properties)) { + return

    请从左边字段添加组件进来吧

    + } + + let children = ' ' + try { + children = + needFormButtonGroup === true || needFormButtonGroup === 'true' ? ( + + 提交 + 重置 + + ) : ( + ' ' + ) + } catch (e) { + if (window.location.href.indexOf('av_debug=true') > -1) { + console.error(`RenderPreviewList function error: ${e.message}`) + } + } + + // @see: https://alibaba.github.io/uform/#/aAUeUD/qAI7IVFnsJ + const globalCfg = pick(gbConfig, [ + 'labelCol', + 'wrapperCol', + 'action', + 'labelAlign', + 'labelTextAlign', + 'autoAddColon', + 'inline', + 'size', + 'editable', + 'defaultValue', + 'value', + 'locale', + 'schema', + 'effects', + 'actions', + 'editable', + 'onValidateFailed', + 'onReset', + 'onSubmit', + 'onChange' + ]) + + return ( + + + {children} + + + ) +} + +const MainBox = forwardRef( + ({ props, canDrop, isOver, connectDropTarget }, ref) => { + const elementRef = useRef(null) + connectDropTarget(elementRef) + + const isActive = canDrop && isOver + let backgroundColor = '#fff' + if (isActive) { + backgroundColor = '#f1f1f1' + } + + return ( +
    + +
    +
    + ) + } +) + +export default DropTarget( + [ItemTypes.FIELD, ItemTypes.LAYOUT], + { + drop: (props, monitor, component) => { + console.log('drop', component) + if (!component) { + return + } + const hasDroppedOnChild = monitor.didDrop() + if (hasDroppedOnChild) { + return + } + console.log('drop child') + component.onDrop(hasDroppedOnChild) + } + }, + (connect, monitor) => ({ + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver(), + isOverCurrent: monitor.isOver({ shallow: true }), + canDrop: monitor.canDrop() + }) +)(MainBox) diff --git a/packages/builder/src/components/preview/style.js b/packages/builder/src/components/preview/style.js index 14745b48e8c..ba5ea73264c 100644 --- a/packages/builder/src/components/preview/style.js +++ b/packages/builder/src/components/preview/style.js @@ -30,32 +30,15 @@ export default styled.div` border-radius: 2px; transition: all 0.1s ease; user-select: none; - &:hover { - .preview-line-del { - opacity: 1; - } - } .preview-line-del { cursor: pointer; - position: absolute; - right: 0px; - top: 50%; - transform: translate3d(0, -50%, 0); z-index: 101; opacity: 0; + width: 30px; + height: 16px; font-size: 12px; color: #333; - width: 64px; text-align: center; - &::after { - content: ''; - position: absolute; - left: 0; - top: 2px; - bottom: 2px; - width: 1px; - background: #eee; - } &::before { content: ''; display: block; @@ -66,11 +49,16 @@ export default styled.div` height: 16px; } } + &:hover { + > .comp-item-layout-tool .preview-line-del { + opacity: 1; + } + } .preview-line-layer { position: absolute; left: 0; top: 0; - right: 40px; + right: 0; bottom: 0; z-index: 100; cursor: move; @@ -106,21 +94,44 @@ export default styled.div` outline-color: #e6e7ff; } .preview-tips { - margin-top: 0; - padding-top: 72px; + position: absolute; + left: 0; + top: 72px; + width: 100%; text-align: center; color: #999; } .comp-item, .comp-item-layout { + position: relative; width: 100%; .next-row { width: 100%; } } + .comp-item { + z-index: 110; + &.is-over-half { + &::after { + content: ''; + display: block; + width: 100%; + height: 10px; + background: #222; + } + } + &.is-not-over-half { + &::before { + content: ''; + display: block; + width: 100%; + height: 10px; + background: #222; + } + } + } .comp-item-layout { - position: relative; margin: 10px 0; padding: 20px 10px; min-height: 200px; @@ -129,20 +140,22 @@ export default styled.div` &.active { border-color: #3f486b; } - .comp-item-layout-tool { - position: absolute; - top: 5px; - right: 5px; - > * { - float: right; - margin-left: 8px; - } - } - .comp-item-layout-empty { - margin-top: 0; - padding-top: 20px; - text-align: center; - color: #999; + } + .comp-item-layout-tool { + position: absolute; + top: 5px; + right: 5px; + z-index: 101; + > * { + float: right; + margin-left: 8px; } } + .comp-item-layout-empty { + margin-top: 0; + padding-top: 20px; + width: 100%; + text-align: center; + color: #999; + } ` diff --git a/packages/builder/src/components/props/editors/fieldAttrEditors/dataSourceEditor.js b/packages/builder/src/components/props/editors/fieldAttrEditors/dataSourceEditor.js index c4efee92f66..1c106b08853 100644 --- a/packages/builder/src/components/props/editors/fieldAttrEditors/dataSourceEditor.js +++ b/packages/builder/src/components/props/editors/fieldAttrEditors/dataSourceEditor.js @@ -26,12 +26,12 @@ class DataSourceEditor extends Component { } } - componentWillReceiveProps(nextProps) { + componentDidUpdate(prevProps, prevState) { const _dataSourceType = this.getDefaultDataSourceType( - nextProps, - this.state.dataSourceType + this.props, + prevState.dataSourceType ) - if (_dataSourceType !== this.state.dataSourceType) { + if (_dataSourceType !== prevState.dataSourceType) { this.setState({ dataSourceType: _dataSourceType }) diff --git a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/defaultValueGenerator.js b/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/defaultValueGenerator.js index 92ef7d39a71..ea39b1e1f7f 100644 --- a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/defaultValueGenerator.js +++ b/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/defaultValueGenerator.js @@ -15,13 +15,16 @@ class DefaultValueGenerator extends Component { } } - componentWillReceiveProps(nextProps) { - const value = nextProps.value || {} - this.setState({ - type: value.type || '', - value: value.value || '', - flag: value.flag || nextProps.flag - }) + componentDidUpdate(prevProps, prevState) { + const value = this.props.value || {} + const preValue = prevProps.value || {} + if (JSON.stringify(value) !== JSON.stringify(preValue)) { + this.setState({ + type: value.type || '', + value: value.value || '', + flag: value.flag || this.props.flag + }) + } } handleValueTypeChange = v => { diff --git a/packages/builder/src/components/props/fileSetting.js b/packages/builder/src/components/props/fileSetting.js index 26639c82634..8136182d1ce 100644 --- a/packages/builder/src/components/props/fileSetting.js +++ b/packages/builder/src/components/props/fileSetting.js @@ -24,10 +24,14 @@ class fileSetting extends Component { } } - componentWillReceiveProps(nextProps) { - this.setState({ - xprops: nextProps.xprops || {} - }) + componentDidUpdate(prevProps) { + const preXprops = prevProps.xprops || {} + const curXprops = this.props.xprops || {} + if (JSON.stringify(preXprops) !== JSON.stringify(curXprops)) { + this.setState({ + xprops: curXprops + }) + } } onChangeDefaultFile(newDefaultFileList) { diff --git a/packages/builder/src/components/props/propsSetting.js b/packages/builder/src/components/props/propsSetting.js index 7eb414e492f..c65eb7d8875 100644 --- a/packages/builder/src/components/props/propsSetting.js +++ b/packages/builder/src/components/props/propsSetting.js @@ -27,7 +27,7 @@ import pickBy from 'lodash.pickby' // 属性设置 class PropsSetting extends Component { static propTypes = { - componentId: PropTypes.string, + componentId: PropTypes.arrayOf(PropTypes.string), editComponent: PropTypes.func, editComponentProps: PropTypes.func, componentProps: PropTypes.object @@ -45,14 +45,13 @@ class PropsSetting extends Component { } onChangeHandler = formdata => { - const { componentId = '' } = this.props - if (!componentId) return false + const { componentId = [] } = this.props + if (!componentId.length) return false const propsData = pickBy(formdata, x => x !== undefined) // 是否隐藏属性 propsData['x-props'] = propsData['x-props'] || {} - propsData['x-item-props'] = propsData['x-item-props'] || {} propsData['x-props'].style = propsData['x-props'].style || {} if (propsData['x-props.htmltype'] === true) { @@ -78,30 +77,22 @@ class PropsSetting extends Component { } getSchemaValue() { - const { - componentId = '', - componentProps = {}, - initSchemaData = {} - } = this.props + const { componentId, componentProps = {}, initSchemaData = {} } = this.props - if (!componentId) return {} + if (!componentId.length) return {} - const curComponentProps = componentProps[componentId] || [] + const curComponentProps = componentProps[componentId.toString()] || [] const result = {} curComponentProps.forEach(compProp => { const { name, value } = compProp - if (name !== 'x-item-props') { - result[name] = value - } + result[name] = value }) - if ( - initSchemaData.properties && - initSchemaData.properties[componentId] && - initSchemaData.properties[componentId]['x-props'] - ) { - result['x-props'] = initSchemaData.properties[componentId]['x-props'] + const curComponentAttr = getCompDetailById(componentId, initSchemaData) + + if (curComponentAttr['x-props']) { + result['x-props'] = curComponentAttr['x-props'] Object.keys(result['x-props']).forEach(key => { if ( Object.hasOwnProperty.call(result, `x-props.${key}`) && @@ -116,27 +107,26 @@ class PropsSetting extends Component { } updateComponentPropsData = (componentId, propsData) => { - const { layoutId } = this.props - this.props.editComponent(componentId, propsData, layoutId) + this.props.editComponent(componentId, propsData) this.props.editComponentProps(componentId, propsData) } generatePropsSchema() { const { initSchemaData = {}, - componentId = '', + componentId, componentProps = {}, UI } = this.props - if (!componentId) { + if (!componentId.length) { return { type: 'object', properties: {} } } - const curComponentProps = componentProps[componentId] || [] + const curComponentProps = componentProps[componentId.toString()] || [] const curComponentAttr = getCompDetailById(componentId, initSchemaData) const finalSchema = {} @@ -170,9 +160,9 @@ class PropsSetting extends Component { } renderConfigList() { - const { componentId = '' } = this.props + const { componentId } = this.props - if (!componentId) { + if (!componentId.length) { return

    请选择待编辑的表单字段

    } @@ -219,9 +209,9 @@ class PropsSetting extends Component { } renderOptions() { - const { componentId = '', initSchemaData = {}, layoutId } = this.props + const { componentId, initSchemaData = {} } = this.props - if (!componentId) return null + if (!componentId.length) return null const curComponentAttr = getCompDetailById(componentId, initSchemaData) @@ -232,13 +222,9 @@ class PropsSetting extends Component { UI={this.props.UI} xprops={curComponentAttr['x-props'] || {}} onChange={xprops => { - this.props.editComponent( - componentId, - { - 'x-props': xprops - }, - layoutId - ) + this.props.editComponent(componentId, { + 'x-props': xprops + }) }} /> ) @@ -261,10 +247,8 @@ const mapStateToProps = state => state const mapDispatchToProps = dispatch => ({ showComponentProps: (id, comp) => dispatch(showComponentProps(id, comp)), - editComponentProps: (id, propsData) => - dispatch(editComponentProps(id, propsData)), - editComponent: (id, propsData, containerId) => - dispatch(editComponent(id, propsData, containerId)) + editComponentProps: (...args) => dispatch(editComponentProps(...args)), + editComponent: (...args) => dispatch(editComponent(...args)) }) class StyledPropsSettingComp extends React.Component { diff --git a/packages/builder/src/configs/supportConfigList.js b/packages/builder/src/configs/supportConfigList.js index dd1644b7140..452b16dbefa 100644 --- a/packages/builder/src/configs/supportConfigList.js +++ b/packages/builder/src/configs/supportConfigList.js @@ -2,9 +2,9 @@ const FIELDLIST = { ID: { name: '__id__', - title: '唯一标识', + title: '字段名称', type: 'string', - description: '唯一标识:发起请求时带上的参数id,必填,全局保证唯一。', + description: '字段名称:发起请求时带上的参数id,必填,全局保证唯一。', required: true }, PLACEHOLDER: { @@ -98,14 +98,14 @@ export const getPropsByKey = key => { return generateProps( [ 'ID', - 'DESCRIPTION', 'TITLE', 'DEFAULT', + 'DESCRIPTION', + 'PLACEHOLDER', 'REQUIRED', 'READONLY', 'DISABLED', - 'HIDDEN', - 'PLACEHOLDER' + 'HIDDEN' ], [ { @@ -123,14 +123,14 @@ export const getPropsByKey = key => { case 'number': return generateProps([ 'ID', - 'DESCRIPTION', 'TITLE', 'DEFAULT', + 'DESCRIPTION', + 'PLACEHOLDER', 'REQUIRED', 'READONLY', 'DISABLED', - 'HIDDEN', - 'PLACEHOLDER' + 'HIDDEN' ]) case 'date': case 'month': @@ -139,9 +139,9 @@ export const getPropsByKey = key => { return generateProps( [ 'ID', - 'DESCRIPTION', 'TITLE', 'DEFAULT', + 'DESCRIPTION', 'REQUIRED', 'READONLY', 'DISABLED', @@ -161,8 +161,8 @@ export const getPropsByKey = key => { return generateProps( [ 'ID', - 'DESCRIPTION', 'TITLE', + 'DESCRIPTION', 'REQUIRED', 'READONLY', 'DISABLED', @@ -240,7 +240,17 @@ export const getPropsByKey = key => { ] ) default: - return defaultProps + return generateProps([ + 'ID', + 'TITLE', + 'DEFAULT', + 'DESCRIPTION', + 'PLACEHOLDER', + 'REQUIRED', + 'READONLY', + 'DISABLED', + 'HIDDEN' + ]) } } diff --git a/packages/builder/src/constants/context.js b/packages/builder/src/constants/context.js new file mode 100644 index 00000000000..d4b4340f7e4 --- /dev/null +++ b/packages/builder/src/constants/context.js @@ -0,0 +1,2 @@ +import React from 'react' +export const { Consumer: FormConsumer, Provider: FormProvider } = React.createContext() diff --git a/packages/builder/src/constants/itemType.js b/packages/builder/src/constants/itemType.js new file mode 100644 index 00000000000..72ceaa9d424 --- /dev/null +++ b/packages/builder/src/constants/itemType.js @@ -0,0 +1,5 @@ +export default { + CARD: 'card', + FIELD: 'field', + LAYOUT: 'layout' +} diff --git a/packages/builder/src/demo/index-1-x.js b/packages/builder/src/demo/index-1-x.js index 56b53af8b1d..30d64dbb145 100644 --- a/packages/builder/src/demo/index-1-x.js +++ b/packages/builder/src/demo/index-1-x.js @@ -50,7 +50,7 @@ const props = { // 主题: dark/light,默认dark // themeStyle: 'light', // 是否展示布局组件,默认为false - showLayoutField: true, + showLayoutField: false, showPreviewBtn: true, showSourceCodeBtn: true, // 控制返回按钮点击事件 @@ -95,7 +95,8 @@ const props = { { name: 'editable', title: '表单是否可编辑', - description: '若设置为false,则可快速搭建出表单详情页,只需设置每个组件的默认值', + description: + '若设置为false,则可快速搭建出表单详情页,只需设置每个组件的默认值', type: 'boolean' } ], @@ -121,6 +122,7 @@ const props = { // console.info('index onChange data', data); // }, onSubmit: data => { + alert(`保存数据:${JSON.stringify(data)}`) console.info('index onSubmit data', data) } } diff --git a/packages/builder/src/index.js b/packages/builder/src/index.js index c6f589dee19..7cbad14a487 100644 --- a/packages/builder/src/index.js +++ b/packages/builder/src/index.js @@ -8,6 +8,8 @@ import rootReducer from './reducers' import App from './App' import ThemeList, { THEME_ENUM, DEFAULT_THEME } from './configs/theme' import { ThemeProvider } from 'styled-components' +import { DragDropContext } from 'react-dnd' +import HTML5Backend from 'react-dnd-html5-backend' const logger = createLogger({ collapsed: true @@ -20,8 +22,7 @@ const middleware = [ ].filter(Boolean) const initialState = { - componentId: '', - layoutId: '', + componentId: [], preview: false, codemode: false, componentProps: {}, @@ -48,7 +49,7 @@ const store = createStore( applyMiddleware(...middleware) ) -export default class Component extends React.Component { +class Component extends React.Component { static propTypes = { themeStyle: PropTypes.string } @@ -82,3 +83,5 @@ export default class Component extends React.Component { ) } } + +export default DragDropContext(HTML5Backend)(Component) diff --git a/packages/builder/src/reducers/componentId.js b/packages/builder/src/reducers/componentId.js index af0b17b0ef0..5e38e2550af 100644 --- a/packages/builder/src/reducers/componentId.js +++ b/packages/builder/src/reducers/componentId.js @@ -1,11 +1,10 @@ -export default (state = '', action) => { - let newState = state - const { data = {} } = action - const _componentId = data.componentId || '' +export default (state = [], action) => { + let newState = [...state] + const { data: { componentId = [] } = {}, type } = action - switch (action.type) { + switch (type) { case 'CHANGE_COMPONENT': - newState = _componentId + newState = Array.isArray(componentId) ? componentId : [componentId] return newState default: return state diff --git a/packages/builder/src/reducers/index.js b/packages/builder/src/reducers/index.js index d2e7dff5335..073d0d18560 100644 --- a/packages/builder/src/reducers/index.js +++ b/packages/builder/src/reducers/index.js @@ -3,7 +3,6 @@ import { combineReducers } from 'redux' import preview from './preview' import codemode from './codemode' import componentId from './componentId' -import layoutId from './layoutId' import componentProps from './componentProps' import gbConfig from './gbConfig' import initSchemaData from './initSchemaData' @@ -14,6 +13,5 @@ export default combineReducers({ codemode, componentProps, gbConfig, - initSchemaData, - layoutId + initSchemaData }) diff --git a/packages/builder/src/reducers/initSchemaData.js b/packages/builder/src/reducers/initSchemaData.js index 17b75c8f10a..f80f78c9dac 100644 --- a/packages/builder/src/reducers/initSchemaData.js +++ b/packages/builder/src/reducers/initSchemaData.js @@ -6,16 +6,49 @@ export default (state = {}, action) => { ...state } const { data = {} } = action - // eslint-disable-next-line const { component, id, + targetId, propsData = {}, existId = null, - addType, - containerId + containerId = [] } = data + const loop = (obj, idArr = []) => { + const _idArr = [...idArr] + const _id = _idArr.shift() + if (!_idArr.length) return obj.properties[_id] + return loop(obj.properties[_id], _idArr) + } + + const getProperties = (obj, idArr = []) => { + const _idArr = [...idArr] + const _id = _idArr.shift() + if (!_idArr.length) return obj.properties + return getProperties(obj.properties[_id], _idArr) + } + + const setProperties = (obj, idArr = [], prop) => { + const _idArr = [...idArr] + if (!_idArr.length) { + obj.properties = prop + } else { + const _id = _idArr.shift() + setProperties(obj.properties[_id], _idArr, prop) + } + } + + const deleteItem = (obj, idArr = []) => { + const _idArr = [...idArr] + const _id = _idArr.shift() + if (!_idArr.length) { + delete obj.properties[_id] + } else { + deleteItem(obj.properties[_id], _idArr) + } + } + switch (action.type) { case 'INIT_SCHEMA': // 自动生成z-index顺序 @@ -24,77 +57,49 @@ export default (state = {}, action) => { ...newState, ...newSchema } + return newState + case 'MOVE_COMOPNENT': + const sourceItem = loop(newState, [...id]) + const targetItem = loop(newState, [...targetId]) + + deleteItem(newState, [...id]) + + targetItem.properties[sourceItem.id] = sourceItem + return newState case 'CHANGE_COMPONENT_ORDER': - const { targetId } = data - let _tmpNewState = { - ...newState - } - if (containerId) { - _tmpNewState = newState.properties[containerId] + const _propertiesList = getOrderProperties(newState, [...containerId]) + const _sourceItem = loop(newState, [...id]) + const _targetItem = loop(newState, [...targetId]) + const targetIdx = _targetItem['x-index'] + const sourceIdx = _sourceItem['x-index'] + + if (id.length !== targetId.length) { + alert('目前只支持同级别组件的顺序替换') + return newState } - const propertiesList = getOrderProperties(_tmpNewState) - const targetItem = propertiesList.find(_item => _item.id === targetId) - let targetIdx = targetItem - ? targetItem['x-index'] - : propertiesList[propertiesList.length - 1]['x-index'] - const sourceItem = propertiesList.find(_item => _item.id === id) - const sourceIdx = sourceItem - ? sourceItem['x-index'] - : propertiesList[0]['x-index'] - const len = propertiesList.length - if (targetIdx < 0) { - targetIdx = 0 + + _propertiesList[targetIdx] = { + ..._sourceItem, + 'x-index': targetIdx } - propertiesList.splice(sourceIdx, 1) - for (let i = 0; i < targetIdx; i++) { - propertiesList[i] = { - ...propertiesList[i], - 'x-index': i - } + _propertiesList[sourceIdx] = { + ..._targetItem, + 'x-index': sourceIdx } - for (let i = len - 1; i > targetIdx; i--) { - propertiesList[i] = { - ...propertiesList[i - 1], - 'x-index': i + + const _properties11 = {} + _propertiesList.forEach(item => { + _properties11[item.id] = { + ...item } - } - propertiesList[targetIdx] = { - ...sourceItem, - 'x-index': targetIdx - } - const _properties = {} - propertiesList.forEach(item => { - _properties[item.id] = item }) - if (containerId) { - newState.properties[containerId].properties = _properties - } else { - newState.properties = _properties - } + setProperties(newState, containerId, _properties11) + return newState case 'ADD_COMPONENT': - const _component_ = - component.__key__ === 'layout' - ? { - type: 'object', - id, - ...component.__key__data__, - properties: {}, - 'x-props': { - ...component.__key__data__['x-props'], - _extra: component - } - } - : { - ...component - } - - const propertiesList1 = - addType === 'layout' - ? getOrderProperties(newState.properties[containerId]) - : getOrderProperties(newState) + const propertiesList1 = getOrderProperties(newState, [...containerId]) if (existId) { // 在特定的existId之前插入新的组件 @@ -108,14 +113,14 @@ export default (state = {}, action) => { } } propertiesList1[idx] = { - ..._component_, + ...component, id, 'x-index': idx } } else { // 在最后插入新的组件 propertiesList1[propertiesList1.length] = { - ..._component_, + ...component, id, 'x-index': propertiesList1.length } @@ -128,11 +133,7 @@ export default (state = {}, action) => { } }) - if (addType === 'layout') { - newState.properties[containerId].properties = _properties1 - } else { - newState.properties = _properties1 - } + setProperties(newState, containerId, _properties1) if (!newState.type) { newState.type = 'object' @@ -140,17 +141,17 @@ export default (state = {}, action) => { return newState case 'EDIT_COMPONENT': - const _data_ = - containerId && containerId !== id - ? state.properties[containerId].properties - : state.properties + const _data_ = getProperties(newState, id) + const lastId = [...id].pop() + Object.keys(_data_).forEach(compId => { if (compId) { _data_[compId] = merge( {}, _data_[compId], - id === null || compId === id + id === null || compId === lastId ? { + active: true, ...propsData } : { @@ -163,7 +164,7 @@ export default (state = {}, action) => { propsData['x-props'] && propsData['x-props'].enum && Array.isArray(propsData['x-props'].enum) && - (id === null || compId === id) + (id === null || compId === lastId) ) { _data_[compId]['x-props'] = _data_[compId]['x-props'] || {} _data_[compId]['x-props'].enum = propsData['x-props'].enum @@ -173,7 +174,7 @@ export default (state = {}, action) => { propsData['x-props'] && propsData['x-props'].requestOptions && propsData['x-props'].requestOptions.data && - (id === null || compId === id) + (id === null || compId === lastId) ) { _data_[compId]['x-props'].requestOptions = _data_[compId]['x-props'].requestOptions || {} @@ -184,39 +185,7 @@ export default (state = {}, action) => { }) return newState case 'DELETE_COMPONENT': - const newProp = { - ...newState.properties - } - - Object.keys(newState.properties).forEach(_key => { - if (_key === id) { - delete newProp[_key] - } - if ( - newState.properties[_key].type === 'object' && - newState.properties[_key].properties - ) { - Object.keys(newState.properties[_key].properties).forEach(__key => { - if (__key === id) { - delete newState.properties[_key].properties[__key] - } - }) - } - }) - - const _propertiesList_ = getOrderProperties({ - properties: newProp - }).filter(item => !!item) - const _properties_ = {} - _propertiesList_.forEach((item, idx) => { - if (item && item.id) { - _properties_[item.id] = { - ...item, - 'x-index': idx - } - } - }) - newState.properties = _properties_ + deleteItem(newState, [...id]) return newState default: return state diff --git a/packages/builder/src/reducers/layoutId.js b/packages/builder/src/reducers/layoutId.js deleted file mode 100644 index f5da407d8c3..00000000000 --- a/packages/builder/src/reducers/layoutId.js +++ /dev/null @@ -1,13 +0,0 @@ -export default (state = '', action) => { - const { data = {} } = action - const { id = '' } = data - let newState = state - - switch (action.type) { - case 'CHANGE_LAYOUTID': - newState = id - return newState - default: - return state - } -} diff --git a/packages/builder/src/style.js b/packages/builder/src/style.js index 2a9c4ebdb3f..d54e05c1817 100644 --- a/packages/builder/src/style.js +++ b/packages/builder/src/style.js @@ -87,7 +87,6 @@ export default styled.div` position: relative; overflow: hidden; padding: 0 340px 0 240px; - min-height: 700px; &::after { content: ""; diff --git a/packages/builder/src/utils/lang.js b/packages/builder/src/utils/lang.js index 1265165a7b4..ab2361af761 100644 --- a/packages/builder/src/utils/lang.js +++ b/packages/builder/src/utils/lang.js @@ -18,14 +18,22 @@ export const isNum = isType('Number') export const isIter = obj => isArr(obj) || isObj(obj) const replaceSingleDefault = v => { - if (!isFlagValue(v)) return '' + if (!isFlagValue(v)) return v const { type, flag, value } = v const now = moment(Date.now()) const params = Arg.all() - if (flag === 'date') { + if (flag === 'weekRange') { + if (type === 'pastStart') { + return now.subtract(value, 'weeks').format('YYYY-MM-DD') + } else if (type === 'future') { + return now.add(value, 'weeks').format('YYYY-MM-DD') + } else if (type === 'specify') { + return value + } + } else if (flag === 'date') { if (type === 'past') { return now.subtract(value, 'days').format('YYYY-MM-DD') } else if (type === 'future') { diff --git a/packages/builder/src/utils/util.js b/packages/builder/src/utils/util.js index 042ac79659d..9257959b268 100644 --- a/packages/builder/src/utils/util.js +++ b/packages/builder/src/utils/util.js @@ -1,3 +1,4 @@ +import React from 'react' import merge from 'lodash.merge' export * from './comp' @@ -38,37 +39,24 @@ export const wrapEnums = enums => /** * 根据组件id获取组件信息 - * @param {String} componentId 组件的id + * @param {Array} componentIdList 组件的id list * @param {Array} schema 组件schema */ -export const getCompDetailById = (componentId, schema = {}) => { +export const getCompDetailById = (componentIdList = [], schema = {}) => { + const _componentIdList = [...componentIdList] + const _componentId = _componentIdList.shift() const { properties = {} } = schema - if (properties[componentId]) { - return { - ...properties[componentId], - id: componentId - } - } - - if (!Object.keys(properties).length) { - return {} - } else { - for (const key in properties) { - if (Object.hasOwnProperty.call(properties, key)) { - const childProps = properties[key].properties - if (childProps && typeof childProps === 'object') { - if (childProps[componentId]) { - return { - ...childProps[componentId], - id: componentId - } - } - } + if (!_componentIdList.length) { + return properties[_componentId] + ? { + id: _componentId, + ...properties[_componentId] } - } - return {} + : {} } + + return getCompDetailById(_componentIdList, properties[_componentId]) } /** @@ -144,7 +132,12 @@ export const wrapSubmitSchema = (schema, keepAll = false) => { * @param {Object} schema * @param {String} containerId 相对容器id */ -export const getOrderProperties = (schema = {}) => { +export const getOrderProperties = (schema = {}, containerId = []) => { + if (containerId.length) { + const id = containerId.shift() + return getOrderProperties(schema.properties[id], containerId) + } + const { properties = {} } = schema if (isEmptyObj(properties)) return [] @@ -260,3 +253,15 @@ export const checkRepeatId = (schema = {}) => { loop(schema) return !!Object.keys(result).length } + +export const wrapComp2Class = Comp => + class extends React.Component { + render() { + return + } + } + +export const isLayoutWrapper = comp => + comp['x-props'] && + comp['x-props']._extra && + comp['x-props']._extra.__key__ === 'layout'