From 4a23b7a8f9c23d7903019e48869c8c3191fa0ffc Mon Sep 17 00:00:00 2001
From: king <18310653075@163.com>
Date: 星期三, 17 三月 2021 09:29:29 +0800
Subject: [PATCH] 2021-03-17

---
 src/menu/components/form/dragelement/source.jsx               |   13 
 src/menu/components/form/cardcomponent/settingform/index.scss |   11 
 src/menu/components/form/cardcomponent/index.jsx              |  229 ++++++++
 src/templates/formtabconfig/index.jsx                         |    4 
 src/menu/components/form/normal-form/index.jsx                |  301 +++++++++++
 src/assets/mobimg/form.png                                    |    0 
 src/templates/zshare/editcomponent/index.jsx                  |    6 
 src/menu/components/form/wrapsetting/index.scss               |    7 
 src/templates/sharecomponent/fieldscomponent/index.jsx        |    8 
 src/templates/modalconfig/index.jsx                           |    4 
 src/menu/components/form/cardcomponent/index.scss             |    0 
 src/menu/components/form/dragelement/index.scss               |   27 +
 src/menu/menushell/card.jsx                                   |    3 
 src/menu/components/form/normal-form/index.scss               |   92 +++
 src/menu/modulesource/option.jsx                              |    2 
 src/menu/components/form/dragelement/card.jsx                 |  167 ++++++
 src/menu/components/form/wrapsetting/settingform/index.jsx    |  253 +++++++++
 src/menu/components/form/cardcomponent/settingform/index.jsx  |  194 +++++++
 src/menu/components/form/dragelement/index.jsx                |  157 +++++
 src/menu/components/form/wrapsetting/settingform/index.scss   |   11 
 src/tabviews/zshare/mutilform/index.jsx                       |   16 
 src/menu/components/form/wrapsetting/index.jsx                |   83 +++
 src/menu/modalconfig/index.scss                               |    4 
 src/menu/modalconfig/index.jsx                                |    4 
 src/menu/menushell/index.jsx                                  |    1 
 25 files changed, 1,576 insertions(+), 21 deletions(-)

diff --git a/src/assets/mobimg/form.png b/src/assets/mobimg/form.png
new file mode 100644
index 0000000..6403b32
--- /dev/null
+++ b/src/assets/mobimg/form.png
Binary files differ
diff --git a/src/menu/components/form/cardcomponent/index.jsx b/src/menu/components/form/cardcomponent/index.jsx
new file mode 100644
index 0000000..5a3909e
--- /dev/null
+++ b/src/menu/components/form/cardcomponent/index.jsx
@@ -0,0 +1,229 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import { is, fromJS } from 'immutable'
+import { Modal, Popover, Icon } from 'antd'
+
+import asyncComponent from '@/utils/asyncComponent'
+import asyncIconComponent from '@/utils/asyncIconComponent'
+import zhCN from '@/locales/zh-CN/model.js'
+import enUS from '@/locales/en-US/model.js'
+import SettingForm from './settingform'
+
+import Utils from '@/utils/utils.js'
+import MKEmitter from '@/utils/events.js'
+import './index.scss'
+
+const CardCellComponent = asyncComponent(() => import('@/menu/components/card/cardcellcomponent'))
+const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
+
+class CardBoxComponent extends Component {
+  static propTpyes = {
+    cards: PropTypes.object,         // 鍗$墖琛岄厤缃俊鎭�
+    card: PropTypes.object,          // 鍗$墖閰嶇疆淇℃伅
+    move: PropTypes.func,            // 鍗$墖绉诲姩
+    deleteElement: PropTypes.func,   // 鍗$墖鍒犻櫎
+    updateElement: PropTypes.func    // 鑿滃崟閰嶇疆鏇存柊
+  }
+
+  state = {
+    dict: sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
+    card: null,            // 鍗$墖淇℃伅锛屽寘鎷鍙嶉潰
+    formlist: null,        // 璁剧疆琛ㄥ崟淇℃伅
+    elements: null,        // 缂栬緫缁�
+    visible: false,        // 妯℃�佹鎺у埗
+    settingVisible: false,
+  }
+
+  /**
+   * @description 鎼滅储鏉′欢鍒濆鍖�
+   */
+  UNSAFE_componentWillMount () {
+    const { card } = this.props
+
+    this.setState({
+      card: fromJS(card).toJS(),
+      elements: fromJS(card.elements).toJS(),
+    })
+  }
+
+  componentDidMount () {
+    MKEmitter.addListener('submitStyle', this.getStyle)
+  }
+
+  shouldComponentUpdate (nextProps, nextState) {
+    const { cards } = this.props
+    
+    return !is(fromJS(cards), fromJS(nextProps.cards)) || !is(fromJS(this.state), fromJS(nextState))
+  }
+
+  /**
+   * @description 缁勪欢閿�姣侊紝娓呴櫎state鏇存柊锛屾竻闄ゅ揩鎹烽敭璁剧疆
+   */
+  componentWillUnmount () {
+    this.setState = () => {
+      return
+    }
+    MKEmitter.removeListener('submitStyle', this.getStyle)
+  }
+
+  getStyle = (comIds, style) => {
+    const { cards } = this.props
+    const { card } = this.state
+
+    if (comIds.length !== 2 || comIds[0] !== cards.uuid || comIds[1] !== card.uuid) return
+
+    let _card = fromJS(card).toJS()
+    _card.style = style
+
+    this.setState({
+      card: _card
+    })
+
+    this.props.updateElement(_card)
+  }
+
+  updateCard = (elements) => {
+    const { card } = this.state
+
+    let _card = {...card, elements: elements}
+
+    this.setState({
+      card: _card
+    })
+
+    this.props.updateElement(_card)
+  }
+
+  addElement = () => {
+    const { cards } = this.props
+    const { card } = this.state
+
+    let newcard = {}
+    newcard.uuid = Utils.getuuid()
+    newcard.focus = true
+    
+    newcard.eleType = 'text'
+    newcard.datatype = 'dynamic'
+    newcard.height = 1
+
+    // 娉ㄥ唽浜嬩欢-娣诲姞鍏冪礌
+    MKEmitter.emit('cardAddElement', [cards.uuid, card.uuid], newcard)
+  }
+
+  addButton = () => {
+    const { cards } = this.props
+    const { card } = this.state
+
+    let newcard = {}
+    newcard.uuid = Utils.getuuid()
+    newcard.focus = true
+    
+    newcard.eleType = 'button'
+    newcard.label = 'button'
+    newcard.sqlType = ''
+    newcard.Ot = 'requiredSgl'
+    newcard.OpenType = 'prompt'
+    newcard.icon = ''
+    newcard.class = 'primary'
+    newcard.intertype = 'system'
+    newcard.execSuccess = 'grid'
+    newcard.execError = 'never'
+    newcard.popClose = 'never'
+    newcard.errorTime = 10
+    newcard.verify = null
+    newcard.show = 'link'
+
+    // 娉ㄥ唽浜嬩欢-娣诲姞鍏冪礌
+    MKEmitter.emit('cardAddElement', [cards.uuid, card.uuid], newcard)
+  }
+
+  changeStyle = () => {
+    const { cards } = this.props
+    const { card } = this.state
+
+    let options = ['background', 'border', 'padding', 'margin', 'shadow']
+
+    MKEmitter.emit('changeStyle', [cards.uuid, card.uuid], options, fromJS(card.style).toJS())
+  }
+
+  settingSubmit = () => {
+    const { card } = this.state
+
+    this.settingRef.handleConfirm().then(res => {
+      this.setState({
+        settingVisible: false,
+        card: {...card, setting: res}
+      })
+
+      this.props.updateElement({...card, setting: res})
+    })
+  }
+
+  clickComponent = (e) => {
+    if ((sessionStorage.getItem('style-control') === 'true' || sessionStorage.getItem('style-control') === 'propcard') && this.props.cards.subtype === 'propcard') {
+      e.stopPropagation()
+      MKEmitter.emit('clickComponent', this.state.card, this.props.cards, 'propcard')
+    }
+  }
+
+  render() {
+    const { cards } = this.props
+    const { card, elements, settingVisible, dict } = this.state
+
+    let _style = {...card.style}
+
+    if (_style.shadow) {
+      _style.boxShadow = '0 0 4px ' + _style.shadow
+    }
+    _style.height = cards.style.height
+
+    return (
+      <div className="card-item" style={_style} onClick={this.clickComponent} id={card.uuid}>
+        <CardCellComponent cards={cards} cardCell={card} side="front" elements={elements} updateElement={this.updateCard}/>
+        <div className="card-control">
+          <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
+            <div className="mk-popover-control">
+              <Icon className="plus" title="娣诲姞鍏冪礌" onClick={this.addElement} type="plus" />
+              <Icon className="plus" title="娣诲姞鎸夐挳" onClick={this.addButton} type="plus-square" />
+              <Icon className="edit" title="缂栬緫" type="edit" onClick={() => this.setState({settingVisible: true})} />
+              <CopyComponent type="cardcell" card={card}/>
+              <Icon className="style" title="璋冩暣鏍峰紡" onClick={this.changeStyle} type="font-colors" />
+              {cards.subtype === 'propcard' ? <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
+                <div className="mk-popover-control">
+                  <Icon className="plus" title="宸︾Щ" type="arrow-left" onClick={() => this.props.move(card, 'left')} />
+                  <Icon className="close" title="鍙崇Щ" type="arrow-right" onClick={() => this.props.move(card, 'right')} />
+                </div>
+              } trigger="hover" getPopupContainer={() => document.getElementById(card.uuid + 'swap')}>
+                <Icon type="swap" id={card.uuid + 'swap'}/>
+              </Popover> : null}
+              {cards.subtype === 'propcard' ? <Icon className="close" title="鍒犻櫎鍗$墖" type="delete" onClick={() => this.props.deleteElement(card)} /> : null}
+            </div>
+          } trigger="hover">
+            <Icon type="tool" />
+          </Popover>
+        </div>
+        <Modal
+          wrapClassName="popview-modal"
+          title={'鍗$墖璁剧疆'}
+          visible={settingVisible}
+          width={700}
+          maskClosable={false}
+          okText={dict['model.submit']}
+          onOk={this.settingSubmit}
+          onCancel={() => { this.setState({ settingVisible: false }) }}
+          destroyOnClose
+        >
+          <SettingForm
+            dict={dict}
+            cards={cards}
+            setting={card.setting}
+            inputSubmit={this.settingSubmit}
+            wrappedComponentRef={(inst) => this.settingRef = inst}
+          />
+        </Modal>
+      </div>
+    )
+  }
+}
+
+export default CardBoxComponent
\ No newline at end of file
diff --git a/src/menu/components/form/cardcomponent/index.scss b/src/menu/components/form/cardcomponent/index.scss
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/menu/components/form/cardcomponent/index.scss
diff --git a/src/menu/components/form/cardcomponent/settingform/index.jsx b/src/menu/components/form/cardcomponent/settingform/index.jsx
new file mode 100644
index 0000000..d8046cc
--- /dev/null
+++ b/src/menu/components/form/cardcomponent/settingform/index.jsx
@@ -0,0 +1,194 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import { Form, Row, Col, Radio, Tooltip, Icon, Input, Cascader, Select } from 'antd'
+
+import './index.scss'
+
+const { TextArea } = Input
+
+class SettingForm extends Component {
+  static propTpyes = {
+    dict: PropTypes.object,      // 瀛楀吀椤�
+    cards: PropTypes.object,     // 鍗$墖闆�
+    setting: PropTypes.object,   // 鏁版嵁婧愰厤缃�
+    inputSubmit: PropTypes.func  // 鍥炶溅浜嬩欢
+  }
+
+  state = {
+    type: this.props.setting.type || 'simple',
+    click: this.props.setting.click || '',
+    isApp: sessionStorage.getItem('appType') === 'pc',
+    menulist: []
+  }
+
+  UNSAFE_componentWillMount() {
+    const { isApp } = this.state
+    let menulist = null
+
+    if (isApp) {
+      menulist = sessionStorage.getItem('appMenus')
+    } else {
+      menulist = sessionStorage.getItem('fstMenuList')
+    }
+
+    if (menulist) {
+      try {
+        menulist = JSON.parse(menulist)
+      } catch {
+        menulist = []
+      }
+    } else {
+      menulist = []
+    }
+
+    this.setState({menulist})
+  }
+
+  handleConfirm = () => {
+    // 琛ㄥ崟鎻愪氦鏃舵鏌ヨ緭鍏ュ�兼槸鍚︽纭�
+    return new Promise((resolve, reject) => {
+      this.props.form.validateFieldsAndScroll((err, values) => {
+        if (!err) {
+          resolve(values)
+        } else {
+          reject(err)
+        }
+      })
+    })
+  }
+
+  handleSubmit = (e) => {
+    e.preventDefault()
+
+    if (this.props.inputSubmit) {
+      this.props.inputSubmit()
+    }
+  }
+
+  render() {
+    const { setting, cards } = this.props
+    const { getFieldDecorator } = this.props.form
+    const { click, menulist, isApp } = this.state
+
+    const formItemLayout = {
+      labelCol: {
+        xs: { span: 24 },
+        sm: { span: 8 }
+      },
+      wrapperCol: {
+        xs: { span: 24 },
+        sm: { span: 16 }
+      }
+    }
+
+    return (
+      <div className="model-menu-setting-form">
+        <Form {...formItemLayout}>
+          <Row gutter={24}>
+            {cards.subtype === 'propcard' ? <Col span={12}>
+              <Form.Item label={
+                <Tooltip placement="topLeft" title="鍗$墖鐐瑰嚮鏃讹紝鍚戝叾浠栫粍浠朵紶閫掔殑BID鍊笺��">
+                  <Icon type="question-circle" />
+                  涓婚敭鍊�
+                </Tooltip>
+              }>
+                {getFieldDecorator('primaryId', {
+                  initialValue: setting.primaryId || ''
+                })(<Input placeholder="" autoComplete="off" onPressEnter={this.handleSubmit}/>)}
+              </Form.Item>
+            </Col> : null}
+            <Col span={12}>
+              <Form.Item label="鐐瑰嚮浜嬩欢">
+                {getFieldDecorator('click', {
+                  initialValue: click
+                })(
+                  <Radio.Group onChange={(e) => this.setState({click: e.target.value})}>
+                    <Radio value="">鏃�</Radio>
+                    <Radio value="menu">鑿滃崟</Radio>
+                    <Radio value="link">閾炬帴</Radio>
+                  </Radio.Group>
+                )}
+              </Form.Item>
+            </Col>
+            {!isApp && click === 'menu' ? <Col span={12}>
+              <Form.Item label="鑿滃崟">
+                {getFieldDecorator('menu', {
+                  initialValue: setting.menu || [],
+                  rules: [
+                    {
+                      required: true,
+                      message: this.props.dict['form.required.select'] + '鑿滃崟!'
+                    }
+                  ]
+                })(
+                  <Cascader options={menulist} placeholder=""/>
+                )}
+              </Form.Item>
+            </Col> : null}
+            {isApp && click === 'menu' ? <Col span={12}>
+              <Form.Item label="鍏宠仈鑿滃崟">
+                {getFieldDecorator('menu', {
+                  initialValue: setting.menu || '',
+                  rules: [
+                    {
+                      required: true,
+                      message: this.props.dict['form.required.select'] + '鍏宠仈鑿滃崟!'
+                    }
+                  ]
+                })(
+                  <Select
+                    showSearch
+                    filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
+                  >
+                    {menulist.map(option =>
+                      <Select.Option key={option.MenuID} value={option.MenuID}>{option.MenuName}</Select.Option>
+                    )}
+                  </Select>
+                )}
+              </Form.Item>
+            </Col> : null}
+            {click === 'link' ? <Col span={24} className="textarea">
+              <Form.Item label="閾炬帴">
+                {getFieldDecorator('linkurl', {
+                  initialValue: setting.linkurl || '',
+                  rules: [
+                    {
+                      required: true,
+                      message: this.props.dict['form.required.input'] + '閾炬帴!'
+                    }
+                  ]
+                })( <TextArea rows={2}/> )}
+              </Form.Item>
+            </Col> : null}
+            {isApp ? <Col span={12}>
+              <Form.Item label="鎵撳紑鏂瑰紡">
+                {getFieldDecorator('open', {
+                  initialValue: setting.open || 'blank'
+                })(
+                  <Radio.Group>
+                    <Radio value="blank">鏂扮獥鍙�</Radio>
+                    <Radio value="self">褰撳墠绐楀彛</Radio>
+                  </Radio.Group>
+                )}
+              </Form.Item>
+            </Col> : null}
+            {click !== '' ? <Col span={12}>
+              <Form.Item label="鍙傛暟鎷兼帴">
+                {getFieldDecorator('joint', {
+                  initialValue: setting.joint || 'true'
+                })(
+                  <Radio.Group>
+                    <Radio value="true">鏄�</Radio>
+                    <Radio value="false">鍚�</Radio>
+                  </Radio.Group>
+                )}
+              </Form.Item>
+            </Col> : null}
+          </Row>
+        </Form>
+      </div>
+    )
+  }
+}
+
+export default Form.create()(SettingForm)
\ No newline at end of file
diff --git a/src/menu/components/form/cardcomponent/settingform/index.scss b/src/menu/components/form/cardcomponent/settingform/index.scss
new file mode 100644
index 0000000..159130b
--- /dev/null
+++ b/src/menu/components/form/cardcomponent/settingform/index.scss
@@ -0,0 +1,11 @@
+.model-menu-setting-form {
+  position: relative;
+
+  .anticon-question-circle {
+    color: #c49f47;
+    margin-right: 3px;
+  }
+  .ant-input-number {
+    width: 100%;
+  }
+}
\ No newline at end of file
diff --git a/src/menu/components/form/dragelement/card.jsx b/src/menu/components/form/dragelement/card.jsx
new file mode 100644
index 0000000..a70c65d
--- /dev/null
+++ b/src/menu/components/form/dragelement/card.jsx
@@ -0,0 +1,167 @@
+import React from 'react'
+import { useDrag, useDrop } from 'react-dnd'
+import { Icon, Select, DatePicker, Input, InputNumber, Button, Popover, Switch, Radio, Checkbox } from 'antd'
+import moment from 'moment'
+
+import asyncComponent from '@/utils/asyncComponent'
+import './index.scss'
+
+const { MonthPicker } = DatePicker
+const { TextArea } = Input
+
+const Editor = asyncComponent(() => import('@/components/editor'))
+const ColorSketch = asyncComponent(() => import('@/mob/colorsketch'))
+const CheckCard = asyncComponent(() => import('@/templates/modalconfig/checkCard'))
+
+const Card = ({ id, card, cols, moveCard, findCard, editCard, closeCard, copyCard, showField }) => {
+  const originalIndex = findCard(id).index
+  const [{ isDragging }, drag] = useDrag({
+    item: { type: 'form', id, originalIndex },
+    collect: monitor => ({
+      isDragging: monitor.isDragging(),
+    }),
+  })
+  const [, drop] = useDrop({
+    accept: 'form',
+    canDrop: () => true,
+    drop: (item) => {
+      const { id: draggedId, originalIndex } = item
+
+      if (originalIndex === undefined) {
+        item.dropTargetId = id
+      } else if (draggedId && draggedId !== id) {
+        const { index: overIndex } = findCard(id)
+        moveCard(draggedId, overIndex)
+      }
+    }
+  })
+  const opacity = isDragging ? 0 : 1
+
+  const edit = () => {
+    editCard(id)
+  }
+
+  const close = () => {
+    closeCard(id)
+  }
+
+  const copy = () => {
+    copyCard(id)
+  }
+
+  let selectval = ''
+  if (card.type === 'multiselect' || card.type === 'select' || card.type === 'link') {
+    if (card.initval) {
+      let _option = card.options.filter(option => option.Value === card.initval)[0]
+      if (_option) {
+        selectval = _option.Text || ''
+      } else {
+        selectval = ''
+      }
+    } else if (card.setAll === 'true') {
+      selectval = card.emptyText || '绌�'
+    }
+  }
+  let labelCol = 'ant-col-sm-8'
+  let wrapCol = 'ant-col-sm-16'
+  let isEntireLine = false
+
+  if (card.entireLine === 'true' || ['textarea', 'hint', 'checkcard', 'brafteditor'].includes(card.type)) {
+    isEntireLine = true
+  }
+
+  if (isEntireLine) {
+    if (cols === '2') {
+      labelCol = 'ant-col-sm-4'
+      wrapCol = 'ant-col-sm-20'
+    } else if (cols === '3') {
+      labelCol = 'ant-col-cuslabel'
+      wrapCol = 'ant-col-cuswrap'
+    } else if (cols === '4') {
+      labelCol = 'ant-col-sm-2'
+      wrapCol = 'ant-col-sm-22'
+    }
+    if (card.hidelabel === 'true') {
+      wrapCol = 'ant-col-sm-24'
+    }
+  }
+
+  let formItem = null
+  if (card.type === 'text') {
+    formItem = (<Input style={{marginTop: '4px'}} value={card.initval} />)
+  } else if (card.type === 'number') {
+    formItem = (<InputNumber value={card.initval} precision={card.decimal} />)
+  } else if (card.type === 'multiselect' || card.type === 'select' || card.type === 'link') {
+    formItem = (<Select value={selectval}></Select>)
+  } else if (card.type === 'color') {
+    formItem = (<ColorSketch value={card.initval || 'transparent'}/>)
+  } else if (card.type === 'date') {
+    formItem = (<DatePicker value={card.initval ? moment().subtract(card.initval, 'days') : null} />)
+  } else if (card.type === 'datemonth') {
+    formItem = (<MonthPicker value={card.initval ? moment().subtract(card.initval, 'month') : null} />)
+  } else if (card.type === 'datetime') {
+    formItem = (<DatePicker showTime value={card.initval ? moment().subtract(card.initval, 'days') : null} />)
+  } else if (card.type === 'textarea') {
+    formItem = (<TextArea value={card.initval} autoSize={{ minRows: 2, maxRows: 6 }} />)
+  } else if (card.type === 'brafteditor') {
+    formItem = (<Editor />)
+  } else if (card.type === 'fileupload') {
+    formItem = (<Button style={{marginTop: '3px'}}><Icon type="upload" /> 鐐瑰嚮涓婁紶 </Button>)
+  } else if (card.type === 'funcvar') {
+    formItem = (<Input style={{marginTop: '4px'}} value={card.linkfield} />)
+  } else if (card.type === 'linkMain') {
+    formItem = (<Input style={{marginTop: '4px'}} />)
+  } else if (card.type === 'switch') {
+    formItem = (<Switch checkedChildren={card.openText || ''} unCheckedChildren={card.closeText || ''} style={{marginTop: '8px'}} checked={card.initval}/>)
+  } else if (card.type === 'radio') {
+    formItem = card.options && card.options.length > 0 ? (<Radio.Group value={card.initval}>
+      {card.options.map(cell => <Radio key={cell.key} value={cell.Value}>{cell.Text}</Radio>)}
+    </Radio.Group>) : (<Radio.Group value={1}>
+      <Radio value={1}>A</Radio>
+      <Radio value={2}>B</Radio>
+      <Radio value={3}>C</Radio>
+      <Radio value={4}>D</Radio>
+    </Radio.Group>)
+  } else if (card.type === 'checkbox') {
+    let _val = card.initval ? card.initval.split(',') : []
+    formItem = card.options && card.options.length > 0 ? (<Checkbox.Group value={_val}>
+      {card.options.map(cell => <Checkbox key={cell.key} value={cell.Value}>{cell.Text}</Checkbox>)}
+    </Checkbox.Group>) : (<Checkbox.Group value={['A', 'C']}>
+      <Checkbox value="A">A</Checkbox>
+      <Checkbox value="B">B</Checkbox>
+      <Checkbox value="C">C</Checkbox>
+      <Checkbox value="D">D</Checkbox>
+    </Checkbox.Group>)
+  } else if (card.type === 'hint') {
+    formItem = <div style={{marginTop: '10px', color: 'rgba(0, 0, 0, 0.85)'}}>{card.message}</div>
+  } else if (card.type === 'checkcard') {
+    formItem = <CheckCard width={card.width} ratio={card.ratio} display={card.display} fields={card.fields} options={card.options} />
+  }
+
+  return (
+    <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
+      <div className="mk-popover-control">
+        <Icon className="edit" type="edit" onClick={edit} />
+        <Icon className="copy" type="copy" onClick={copy} />
+        <Icon className="close" type="close" onClick={close} />
+      </div>
+    } trigger="hover">
+      <div className="page-card" style={{ opacity: opacity}}>
+        <div ref={node => drag(drop(node))}>
+          {<div className="ant-row ant-form-item">
+            {card.hidelabel !== 'true' ? <div className={'ant-col ant-form-item-label ant-col-xs-24 ' + labelCol}>
+              {card.label ? <label className={card.required === 'true' ? 'required' : ''}>{card.tooltip ? 
+                <Icon type="question-circle" /> : null}
+                {card.label}</label> : null}
+            </div> : null}
+            <div className={'ant-col ant-form-item-control-wrapper ant-col-xs-24 ' + wrapCol}>
+              {formItem}
+              {showField ? card.field : ''}
+            </div>
+          </div>}
+        </div>
+      </div>
+    </Popover>
+  )
+}
+export default Card
diff --git a/src/menu/components/form/dragelement/index.jsx b/src/menu/components/form/dragelement/index.jsx
new file mode 100644
index 0000000..bcfe294
--- /dev/null
+++ b/src/menu/components/form/dragelement/index.jsx
@@ -0,0 +1,157 @@
+import React, { useState } from 'react'
+import { useDrop } from 'react-dnd'
+import { is, fromJS } from 'immutable'
+import update from 'immutability-helper'
+import { Col } from 'antd'
+import Utils from '@/utils/utils.js'
+import Card from './card'
+import './index.scss'
+
+const Container = ({list, group, setting, placeholder, handleList, handleForm, closeForm, showField }) => {
+  const [cards, setCards] = useState(list)
+  const moveCard = (id, atIndex) => {
+    const { card, index } = findCard(id)
+
+    if (!card) return
+
+    const _cards = update(cards, { $splice: [[index, 1], [atIndex, 0, card]] })
+
+    if (!group) {
+      handleList(_cards)
+    } else {
+      handleList(_cards, group)
+    }
+  }
+
+  if (!is(fromJS(cards), fromJS(list))) {
+    setCards(list)
+  }
+
+  const findCard = id => {
+    const card = cards.filter(c => `${c.uuid}` === id)[0]
+    return {
+      card,
+      index: cards.indexOf(card),
+    }
+  }
+
+  const editCard = id => {
+    const { card } = findCard(id)
+    handleForm(card)
+  }
+
+  const closeCard = id => {
+    const { card } = findCard(id)
+    closeForm(card)
+  }
+
+  const copyCard = id => {
+    const { card, index: overIndex } = findCard(id)
+
+    let _card = fromJS(card).toJS()
+    _card.uuid = Utils.getuuid()
+    _card.focus = true
+
+    // 澶嶅埗鍒板壀鍒囨澘
+    let oInput = document.createElement('input')
+    let val = JSON.parse(JSON.stringify(_card))
+    val.copyType = 'form'
+
+    oInput.value = window.btoa(window.encodeURIComponent(JSON.stringify(val)))
+    document.body.appendChild(oInput)
+    oInput.select()
+    document.execCommand('Copy')
+    oInput.className = 'oInput'
+    oInput.style.display = 'none'
+    document.body.removeChild(oInput)
+
+    const _cards = update(cards, { $splice: [[overIndex + 1, 0, _card]] })
+
+    setCards(_cards)
+
+    if (!group) {
+      handleList(_cards, null, null, _card)
+    } else {
+      handleList(_cards, group, null, _card)
+    }
+  }
+
+  const [, drop] = useDrop({
+    accept: 'form',
+    drop(item) {
+      if (item.hasOwnProperty('originalIndex') && group) {
+        const { card } = findCard(item.id)
+        if (!card) {
+          handleList(cards, group, item.id)
+        }
+      }
+      if (item.hasOwnProperty('originalIndex')) {
+        return
+      }
+
+      let newcard = {}
+      newcard.uuid = Utils.getuuid()
+      newcard.label = 'label'
+      newcard.type = item.subType
+      newcard.resourceType = '0'
+      newcard.options = []
+      newcard.readonly = 'false'
+      newcard.required = 'true'
+      newcard.focus = true
+
+      let targetId = ''
+
+      if (item.dropTargetId) {
+        targetId = item.dropTargetId
+        delete item.dropTargetId
+      } else if (cards.length > 0) {
+        targetId = cards[cards.length - 1].uuid
+      }
+
+      const { index: overIndex } = findCard(`${targetId}`) // cards涓虹┖鏃� overIndex 涓� -1
+      const _cards = update(cards, { $splice: [[overIndex + 1, 0, newcard]] })
+
+      setCards(_cards)
+
+      if (!group) {
+        handleList(_cards, null, null, newcard)
+      } else {
+        handleList(_cards, group, null, newcard)
+      }
+    }
+  })
+
+  let _cols = 24 / (setting.cols || 2)
+  
+  return (
+    <div ref={drop} className="ant-row modal-fields-row">
+      {cards.map(card => {
+        let isEntireLine = false
+
+        if (card.entireLine === 'true' || ['textarea', 'hint', 'checkcard', 'brafteditor'].includes(card.type)) {
+          isEntireLine = true
+        }
+        
+        return <Col key={card.uuid} className={isEntireLine ? 'textarea' + setting.cols : ''} span={isEntireLine ? 24 : _cols}>
+          <Card
+            id={card.uuid}
+            cols={setting.cols}
+            card={card}
+            showField={showField}
+            moveCard={moveCard}
+            editCard={editCard}
+            closeCard={closeCard}
+            copyCard={copyCard}
+            findCard={findCard}
+          />
+        </Col>
+      })}
+      {cards.length === 0 &&
+        <div className="modal-drawarea-placeholder">
+          {placeholder}
+        </div>
+      }
+    </div>
+  )
+}
+export default Container
diff --git a/src/menu/components/form/dragelement/index.scss b/src/menu/components/form/dragelement/index.scss
new file mode 100644
index 0000000..d68b01a
--- /dev/null
+++ b/src/menu/components/form/dragelement/index.scss
@@ -0,0 +1,27 @@
+.modal-source-item {
+  display: block;
+  box-shadow: 0px 0px 2px #bcbcbc;
+  padding: 0.4rem 0.7rem;
+  background-color: white;
+  margin: 0px 0px 10px;
+  cursor: move;
+  border-radius: 4px;
+}
+.modal-drawarea-placeholder {
+  width: 100%;
+  line-height: 65px;
+  text-align: center;
+  color: #bcbcbc;
+}
+.modal-fields-row {
+  padding-bottom: 35px;
+  .ant-col {
+    padding-left: 12px;
+    padding-right: 12px;
+  }
+  >.ant-col {
+    display: inline-block;
+    float: none;
+    vertical-align: top;
+  }
+}
\ No newline at end of file
diff --git a/src/menu/components/form/dragelement/source.jsx b/src/menu/components/form/dragelement/source.jsx
new file mode 100644
index 0000000..7d4af72
--- /dev/null
+++ b/src/menu/components/form/dragelement/source.jsx
@@ -0,0 +1,13 @@
+import React from 'react'
+import { useDrag } from 'react-dnd'
+import './index.scss'
+
+const SourceElement = ({content}) => {
+  const [, drag] = useDrag({ item: content })
+  return (
+    <div ref={drag} className="modal-source-item">
+      {content.label}
+    </div>
+  )
+}
+export default SourceElement
\ No newline at end of file
diff --git a/src/menu/components/form/normal-form/index.jsx b/src/menu/components/form/normal-form/index.jsx
new file mode 100644
index 0000000..14e5ec9
--- /dev/null
+++ b/src/menu/components/form/normal-form/index.jsx
@@ -0,0 +1,301 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import {connect} from 'react-redux'
+import { is, fromJS } from 'immutable'
+import { Icon, Popover, Modal, Carousel } from 'antd'
+
+import asyncComponent from '@/utils/asyncComponent'
+import asyncIconComponent from '@/utils/asyncIconComponent'
+
+import MKEmitter from '@/utils/events.js'
+import Utils from '@/utils/utils.js'
+import zhCN from '@/locales/zh-CN/model.js'
+import enUS from '@/locales/en-US/model.js'
+import './index.scss'
+
+const SettingComponent = asyncIconComponent(() => import('@/menu/datasource'))
+const WrapComponent = asyncIconComponent(() => import('@/menu/components/form/wrapsetting'))
+const CardComponent = asyncComponent(() => import('../dragelement'))
+const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
+const PasteComponent = asyncIconComponent(() => import('@/menu/components/share/pastecomponent'))
+const UserComponent = asyncIconComponent(() => import('@/menu/components/share/usercomponent'))
+
+const { confirm } = Modal
+
+class PropCardEditComponent extends Component {
+  static propTpyes = {
+    card: PropTypes.object,
+    deletecomponent: PropTypes.func,
+    updateConfig: PropTypes.func,
+  }
+
+  state = {
+    dict: sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
+    card: null,
+    back: false
+  }
+
+  UNSAFE_componentWillMount () {
+    const { card } = this.props
+
+    if (card.isNew) {
+      let _card = {
+        uuid: card.uuid,
+        type: card.type,
+        floor: card.floor,
+        tabId: card.tabId || '',
+        parentId: card.parentId || '',
+        format: 'object',   // 缁勪欢灞炴�� - 鏁版嵁鏍煎紡
+        pageable: false,    // 缁勪欢灞炴�� - 鏄惁鍙垎椤�
+        switchable: false,  // 缁勪欢灞炴�� - 鏁版嵁鏄惁鍙垏鎹�
+        dataName: card.dataName || '',
+        width: card.width || 24,
+        name: card.name,
+        subtype: card.subtype,
+        setting: { },
+        wrap: { name: card.name, width: card.width || 24, datatype: 'static' },
+        style: { marginLeft: '0px', marginRight: '0px', marginTop: '8px', marginBottom: '8px' },
+        columns: [],
+        scripts: [],
+        subcards: [{
+          uuid: Utils.getuuid(),
+          setting: {},
+          style: {},
+          fields: [],
+        }],
+      }
+
+      if (card.config) {
+        let config = fromJS(card.config).toJS()
+
+        _card.wrap = config.wrap
+        _card.wrap.name = card.name
+        _card.style = config.style
+
+        _card.subcards = config.subcards.map(scard => {
+          scard.uuid = Utils.getuuid()
+          scard.fields = scard.fields.map(elem => {
+            elem.uuid = Utils.getuuid()
+            return elem
+          })
+          return scard
+        })
+      }
+      this.setState({
+        card: _card
+      })
+      this.props.updateConfig(_card)
+    } else {
+      this.setState({
+        card: fromJS(card).toJS()
+      })
+    }
+  }
+
+  componentDidMount () {
+    MKEmitter.addListener('submitStyle', this.getStyle)
+    MKEmitter.addListener('submitComponentStyle', this.updateComponentStyle)
+  }
+
+  shouldComponentUpdate (nextProps, nextState) {
+    return !is(fromJS(this.state), fromJS(nextState)) || (!this.props.menu && nextProps.menu)
+  }
+
+  /**
+   * @description 缁勪欢閿�姣侊紝娓呴櫎state鏇存柊锛屾竻闄ゅ揩鎹烽敭璁剧疆
+   */
+  componentWillUnmount () {
+    this.setState = () => {
+      return
+    }
+    MKEmitter.removeListener('submitStyle', this.getStyle)
+    MKEmitter.removeListener('submitComponentStyle', this.updateComponentStyle)
+  }
+
+  updateComponentStyle = (parentId, keys, style) => {
+    const { card } = this.state
+
+    if (card.uuid !== parentId) return
+
+    let subcards = card.subcards.map(item => {
+      if (keys.includes(item.uuid)) {
+        item.style = {...item.style, ...style}
+      }
+      return item
+    })
+
+    this.setState({card: {...card, subcards: []}}, () => {
+      this.updateComponent({...card, subcards: subcards})
+    })
+  }
+
+  /**
+   * @description 鍗$墖琛屽灞備俊鎭洿鏂帮紙鏁版嵁婧愶紝鏍峰紡绛夛級
+   */
+  updateComponent = (component) => {
+    this.setState({
+      card: component
+    })
+
+    component.width = component.wrap.width
+    component.name = component.wrap.name
+
+    this.props.updateConfig(component)
+  }
+
+  /**
+   * @description 鍗曚釜鍗$墖淇℃伅鏇存柊
+   */
+  updateCard = (cell) => {
+    let card = fromJS(this.state.card).toJS()
+
+    card.subcards = card.subcards.map(item => {
+      if (item.uuid === cell.uuid) return cell
+      return item
+    })
+
+    this.setState({card})
+
+    this.props.updateConfig(card)
+  }
+
+  /**
+   * @description 鍗曚釜鍗$墖淇℃伅鏇存柊
+   */
+  deleteCard = (cell) => {
+    let card = fromJS(this.state.card).toJS()
+    let _this = this
+
+    confirm({
+      content: '纭畾鍒犻櫎琛ㄥ崟鍚楋紵',
+      onOk() {
+        card.subcards = card.subcards.filter(item => item.uuid !== cell.uuid)
+        
+        let uuids = []
+        cell.elements && cell.elements.forEach(c => {
+          if (c.eleType === 'button') {
+            uuids.push(c.uuid)
+          }
+        })
+        MKEmitter.emit('delButtons', uuids)
+
+        _this.setState({card})
+        _this.props.updateConfig(card)
+      },
+      onCancel() {}
+    })
+  }
+
+  changeStyle = () => {
+    const { card } = this.state
+
+    MKEmitter.emit('changeStyle', [card.uuid], ['height', 'background', 'border', 'padding', 'margin'], card.style)
+  }
+
+  getStyle = (comIds, style) => {
+    const { card } = this.state
+
+    if (comIds.length !== 1 || comIds[0] !== card.uuid) return
+
+    let _card = {...card, style}
+
+    this.setState({
+      card: _card
+    })
+    
+    this.props.updateConfig(_card)
+  }
+
+  addCard = () => {
+    let card = fromJS(this.state.card).toJS()
+
+    let newcard = {
+      uuid: Utils.getuuid(),
+      setting: {},
+      style: {},
+      elements: [],
+    }
+
+    if (card.subcards.length > 0) {
+      newcard = fromJS(card.subcards.slice(-1)[0]).toJS()
+      newcard.uuid = Utils.getuuid()
+      newcard.elements = newcard.elements.map(elem => {
+        elem.uuid = Utils.getuuid()
+        return elem
+      })
+    }
+
+    card.subcards.push(newcard)
+    
+    this.setState({card})
+    this.props.updateConfig(card)
+  }
+
+  move = (item, direction) => {
+    let card = fromJS(this.state.card).toJS()
+
+    let dragIndex = card.subcards.findIndex(c => c.uuid === item.uuid)
+    let hoverIndex = null
+
+    if (direction === 'left') {
+      hoverIndex = dragIndex - 1
+    } else {
+      hoverIndex = dragIndex + 1
+    }
+
+    if (hoverIndex === -1 || hoverIndex === card.subcards.length) return 
+
+    card.subcards.splice(hoverIndex, 0, ...card.subcards.splice(dragIndex, 1))
+
+    this.setState({card: {...card, subcards: []}}, () => {
+      this.setState({card})
+    })
+    this.props.updateConfig(card)
+  }
+
+  clickComponent = (e) => {
+    if (sessionStorage.getItem('style-control') === 'true' || sessionStorage.getItem('style-control') === 'component') {
+      e.stopPropagation()
+      MKEmitter.emit('clickComponent', this.state.card)
+    }
+  }
+
+  render() {
+    const { card } = this.state
+
+    return (
+      <div className="menu-normal-form-edit-box" style={{...card.style}} onClick={this.clickComponent} id={card.uuid}>
+        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
+          <div className="mk-popover-control">
+            <Icon className="plus" title="娣诲姞鍒嗙粍" onClick={this.addCard} type="plus" />
+            <WrapComponent config={card} updateConfig={this.updateComponent} />
+            <CopyComponent type="propcard" card={card}/>
+            <PasteComponent config={card} options={['cardcell']} updateConfig={this.updateComponent} />
+            <Icon className="style" title="璋冩暣鏍峰紡" onClick={this.changeStyle} type="font-colors" />
+            <UserComponent config={card}/>
+            <Icon className="close" title="鍒犻櫎缁勪欢" type="delete" onClick={() => this.props.deletecomponent(card.uuid)} />
+            {card.wrap.datatype !== 'static' ? <SettingComponent config={card} updateConfig={this.updateComponent} /> : null}
+            {card.wrap.datatype === 'static' ? <Icon style={{color: '#eeeeee', cursor: 'not-allowed'}} type="setting"/> : null}
+          </div>
+        } trigger="hover">
+          <Icon type="tool" />
+        </Popover>
+        {card.subcards.length > 0 ? <Carousel dotPosition={card.wrap.dotPosition || 'bottom'} effect={card.wrap.effect || 'scrollx'}>
+          {card.subcards.map((subcard) => (<CardComponent key={subcard.uuid} cards={card} card={subcard} move={this.move} updateElement={this.updateCard} deleteElement={this.deleteCard}/>))}
+        </Carousel> : null}
+      </div>
+    )
+  }
+}
+
+const mapStateToProps = (state) => {
+  return {
+    menu: state.customMenu
+  }
+}
+
+const mapDispatchToProps = () => {
+  return {}
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(PropCardEditComponent)
\ No newline at end of file
diff --git a/src/menu/components/form/normal-form/index.scss b/src/menu/components/form/normal-form/index.scss
new file mode 100644
index 0000000..f40fc16
--- /dev/null
+++ b/src/menu/components/form/normal-form/index.scss
@@ -0,0 +1,92 @@
+.menu-normal-form-edit-box {
+  position: relative;
+  box-sizing: border-box;
+  background: #ffffff;
+  background-position: center center;
+  background-repeat: no-repeat;
+  background-size: cover;
+  min-height: 30px;
+  
+  .card-control {
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    .anticon-tool {
+      right: auto;
+      left: 1px;
+      padding: 1px;
+    }
+  }
+  .anticon-tool {
+    position: absolute;
+    z-index: 2;
+    font-size: 16px;
+    right: 1px;
+    top: 1px;
+    cursor: pointer;
+    padding: 5px;
+    background: rgba(255, 255, 255, 0.55);
+  }
+
+  .card-item {
+    overflow: hidden;
+    position: relative;
+    background-position: center center;
+    background-repeat: no-repeat;
+    background-size: cover;
+    min-height: 20px;
+    height: 100%;
+  }
+  .ant-carousel:not(.ant-carousel-vertical) {
+    .slick-dots li button {
+      height: 8px;
+    }
+  }
+  .ant-carousel.ant-carousel-vertical {
+    .slick-dots li button {
+      width: 8px;
+    }
+  }
+  .ant-carousel {
+    .slick-dots li button {
+      background: #1890ff;
+    }
+  }
+  
+  .card-item:hover {
+    box-shadow: 0px 0px 2px #1890ff;
+  }
+
+  .model-menu-card-cell-list .card-detail-row > .anticon-plus {
+    position: absolute;
+    right: -30px;
+    font-size: 16px;
+  }
+  .model-menu-action-list {
+    line-height: 40px;
+    .ant-row > .anticon-plus {
+      position: absolute;
+      right: -30px;
+      font-size: 16px;
+    }
+  }
+  .card-add-button {
+    text-align: right;
+    clear: left;
+    .anticon-plus {
+      font-size: 20px;
+      color: #26C281;
+      padding: 5px;
+      margin-right: 10px;
+    }
+  }
+}
+.menu-normal-form-edit-box::after {
+  display: block;
+  content: ' ';
+  clear: both;
+}
+.menu-normal-form-edit-box:hover {
+  z-index: 1;
+  box-shadow: 0px 0px 4px #1890ff;
+}
diff --git a/src/menu/components/form/wrapsetting/index.jsx b/src/menu/components/form/wrapsetting/index.jsx
new file mode 100644
index 0000000..81346a6
--- /dev/null
+++ b/src/menu/components/form/wrapsetting/index.jsx
@@ -0,0 +1,83 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import { is, fromJS } from 'immutable'
+import { Icon, Modal } from 'antd'
+
+import zhCN from '@/locales/zh-CN/model.js'
+import enUS from '@/locales/en-US/model.js'
+import SettingForm from './settingform'
+import './index.scss'
+
+class DataSource extends Component {
+  static propTpyes = {
+    config: PropTypes.any,
+    updateConfig: PropTypes.func
+  }
+
+  state = {
+    dict: sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
+    visible: false,
+    wrap: null
+  }
+
+  UNSAFE_componentWillMount () {
+    const { config } = this.props
+
+    this.setState({wrap: fromJS(config.wrap).toJS()})
+  }
+
+  shouldComponentUpdate (nextProps, nextState) {
+    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
+  }
+
+  editDataSource = () => {
+    this.setState({
+      visible: true
+    })
+  }
+
+  verifySubmit = () => {
+    const { config } = this.props
+
+    this.verifyRef.handleConfirm().then(res => {
+
+      this.setState({
+        wrap: res,
+        visible: false
+      })
+      this.props.updateConfig({...config, wrap: res})
+    })
+  }
+
+  render () {
+    const { config } = this.props
+    const { visible, dict, wrap } = this.state
+
+    return (
+      <div className="model-menu-setting-wrap">
+        <Icon type="edit" title="缂栬緫" onClick={() => this.editDataSource()} />
+        <Modal
+          wrapClassName="popview-modal"
+          title={config.type === 'table' ? '琛ㄦ牸璁剧疆' : '鍗$墖璁剧疆'}
+          visible={visible}
+          width={800}
+          maskClosable={false}
+          okText={dict['model.submit']}
+          onOk={this.verifySubmit}
+          onCancel={() => { this.setState({ visible: false }) }}
+          destroyOnClose
+        >
+          <SettingForm
+            dict={dict}
+            wrap={wrap}
+            config={config}
+            inputSubmit={this.verifySubmit}
+            wrappedComponentRef={(inst) => this.verifyRef = inst}
+          />
+        </Modal>
+      </div>
+    )
+  }
+}
+
+export default DataSource
\ No newline at end of file
diff --git a/src/menu/components/form/wrapsetting/index.scss b/src/menu/components/form/wrapsetting/index.scss
new file mode 100644
index 0000000..04372e6
--- /dev/null
+++ b/src/menu/components/form/wrapsetting/index.scss
@@ -0,0 +1,7 @@
+.model-menu-setting-wrap {
+  display: inline-block;
+
+  >.anticon-edit {
+    color: #1890ff;
+  }
+}
\ No newline at end of file
diff --git a/src/menu/components/form/wrapsetting/settingform/index.jsx b/src/menu/components/form/wrapsetting/settingform/index.jsx
new file mode 100644
index 0000000..08b58e2
--- /dev/null
+++ b/src/menu/components/form/wrapsetting/settingform/index.jsx
@@ -0,0 +1,253 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import { Form, Row, Col, Input, Radio, Tooltip, Icon, InputNumber, Select } from 'antd'
+
+import './index.scss'
+
+class SettingForm extends Component {
+  static propTpyes = {
+    dict: PropTypes.object,      // 瀛楀吀椤�
+    config: PropTypes.object,    // 鍗$墖琛屼俊鎭�
+    wrap: PropTypes.object,      // 鏁版嵁婧愰厤缃�
+    inputSubmit: PropTypes.func  // 鍥炶溅浜嬩欢
+  }
+
+  state = {
+    roleList: []
+  }
+
+  UNSAFE_componentWillMount () {
+    let roleList = sessionStorage.getItem('sysRoles')
+    if (roleList) {
+      try {
+        roleList = JSON.parse(roleList)
+      } catch {
+        roleList = []
+      }
+    } else {
+      roleList = []
+    }
+
+    this.setState({roleList})
+  }
+
+  handleConfirm = () => {
+    // 琛ㄥ崟鎻愪氦鏃舵鏌ヨ緭鍏ュ�兼槸鍚︽纭�
+    return new Promise((resolve, reject) => {
+      this.props.form.validateFieldsAndScroll((err, values) => {
+        if (!err) {
+          resolve(values)
+        } else {
+          reject(err)
+        }
+      })
+    })
+  }
+
+  handleSubmit = (e) => {
+    e.preventDefault()
+
+    if (this.props.inputSubmit) {
+      this.props.inputSubmit()
+    }
+  }
+
+  render() {
+    const { wrap, config } = this.props
+    const { getFieldDecorator } = this.props.form
+    const { roleList } = this.state
+
+    const formItemLayout = {
+      labelCol: {
+        xs: { span: 24 },
+        sm: { span: 8 }
+      },
+      wrapperCol: {
+        xs: { span: 24 },
+        sm: { span: 16 }
+      }
+    }
+
+    return (
+      <div className="model-menu-setting-form">
+        <Form {...formItemLayout}>
+          <Row gutter={24}>
+            <Col span={12}>
+              <Form.Item label="鏍囬">
+                {getFieldDecorator('title', {
+                  initialValue: wrap.title || ''
+                })(<Input placeholder={''} autoComplete="off" onPressEnter={this.handleSubmit} />)}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item label={
+                <Tooltip placement="topLeft" title="鐢ㄤ簬缁勪欢闂寸殑鍖哄垎銆�">
+                  <Icon type="question-circle" />
+                  缁勪欢鍚嶇О
+                </Tooltip>
+              }>
+                {getFieldDecorator('name', {
+                  initialValue: wrap.name,
+                  rules: [
+                    {
+                      required: true,
+                      message: this.props.dict['form.required.input'] + '缁勪欢鍚嶇О!'
+                    }
+                  ]
+                })(<Input placeholder={''} autoComplete="off" onPressEnter={this.handleSubmit} />)}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item label={
+                <Tooltip placement="topLeft" title="鏍呮牸甯冨眬锛屾瘡琛岀瓑鍒嗕负24鍒椼��">
+                  <Icon type="question-circle" />
+                  瀹藉害
+                </Tooltip>
+              }>
+                {getFieldDecorator('width', {
+                  initialValue: wrap.width || 24,
+                  rules: [
+                    {
+                      required: true,
+                      message: this.props.dict['form.required.input'] + '瀹藉害!'
+                    }
+                  ]
+                })(<InputNumber min={1} max={24} precision={0} onPressEnter={this.handleSubmit} />)}
+              </Form.Item>
+            </Col>
+            {config.subtype === 'propcard' ? <Col span={12}>
+              <Form.Item label={
+                <Tooltip placement="topLeft" title="閫夋嫨闈欐�佸�硷紝鏃犻渶閰嶇疆鏁版嵁婧愩��">
+                  <Icon type="question-circle" />
+                  鏁版嵁鏉ユ簮
+                </Tooltip>
+              }>
+                {getFieldDecorator('datatype', {
+                  initialValue: wrap.datatype || 'dynamic'
+                })(
+                  <Radio.Group>
+                    <Radio value="dynamic">鍔ㄦ��</Radio>
+                    <Radio value="static">闈欐��</Radio>
+                  </Radio.Group>
+                )}
+              </Form.Item>
+            </Col> : null}
+            {config.subtype === 'datacard' ? <Col span={12}>
+              <Form.Item label={
+                <Tooltip placement="topLeft" title="鏁版嵁婧愪腑閫夋嫨鍒嗛〉鏃舵湁鏁堛��">
+                  <Icon type="question-circle" />
+                  鍒嗛〉椋庢牸
+                </Tooltip>
+              }>
+                {getFieldDecorator('pagestyle', {
+                  initialValue: wrap.pagestyle || 'page'
+                })(
+                  <Radio.Group>
+                    <Radio value="page">椤电爜</Radio>
+                    <Radio value="switch">宸﹀彸鍒囨崲</Radio>
+                  </Radio.Group>
+                )}
+              </Form.Item>
+            </Col> : null}
+            {config.subtype !== 'tablecard' ? <Col span={12}>
+              <Form.Item label="鍗$墖灞炴��">
+                {getFieldDecorator('cardType', {
+                  initialValue: wrap.cardType || ''
+                })(
+                  <Radio.Group style={{whiteSpace: 'nowrap'}}>
+                    <Radio key="" value=""> 涓嶅彲閫� </Radio>
+                    <Radio key="radio" value={'radio'}> 鍗曢�� </Radio>
+                    {config.subtype !== 'propcard' ? <Radio key="checkbox" value={'checkbox'}> 澶氶�� </Radio> : null}
+                  </Radio.Group>
+                )}
+              </Form.Item>
+            </Col> : null}
+            {config.subtype !== 'tablecard' ? <Col span={12}>
+              <Form.Item label={
+                <Tooltip placement="topLeft" title="璁剧疆涓哄眳涓榻愭垨鍙冲榻愶紝鍙湪鍗$墖涓�1琛屾椂鏈夋晥銆�">
+                  <Icon type="question-circle" />
+                  鍗$墖鎺掑垪
+                </Tooltip>
+              }>
+                {getFieldDecorator('cardFloat', {
+                  initialValue: wrap.cardFloat || 'left'
+                })(
+                  <Radio.Group style={{whiteSpace: 'nowrap'}}>
+                    <Radio key="left" value="left"> 宸﹀榻� </Radio>
+                    <Radio key="center" value="center"> 灞呬腑 </Radio>
+                    <Radio key="right" value="right"> 鍙冲榻� </Radio>
+                  </Radio.Group>
+                )}
+              </Form.Item>
+            </Col> : null}
+            {config.subtype !== 'tablecard' ? <Col span={12}>
+              <Form.Item label={
+                <Tooltip placement="topLeft" title="榧犳爣鎮诞浜庡崱鐗囦笂鏂规椂锛屽崱鐗囨斁澶�1.05鍊嶃��">
+                  <Icon type="question-circle" />
+                  鍗$墖鏀惧ぇ
+                </Tooltip>
+              }>
+                {getFieldDecorator('scale', {
+                  initialValue: wrap.scale || 'false'
+                })(
+                  <Radio.Group>
+                    <Radio key="false" value="false"> 鍚� </Radio>
+                    <Radio key="true" value="true"> 鏄� </Radio>
+                  </Radio.Group>
+                )}
+              </Form.Item>
+            </Col> : null}
+            {config.subtype === 'tablecard' ? <Col span={12}>
+              <Form.Item label={
+                <Tooltip placement="topLeft" title="琛ㄦ牸楂樺害锛岃秴鍑烘椂婊氬姩锛岄珮搴︿负绌烘椂鏍规嵁鍐呭鑷�傚簲銆�">
+                  <Icon type="question-circle" />
+                  楂樺害
+                </Tooltip>
+              }>
+                {getFieldDecorator('height', {
+                  initialValue: wrap.height
+                })(<InputNumber min={100} max={2000} precision={0} onPressEnter={this.handleSubmit} />)}
+              </Form.Item>
+            </Col> : null}
+            {config.subtype === 'propcard' ? <Col span={12}>
+              <Form.Item label={
+                <Tooltip placement="topLeft" title="閫夋嫨绫诲瀷涓恒�婇〉鐪�/椤佃剼銆嬫椂锛屾墦鍗扮殑姣忛〉閲岄兘浼氬甫鏈夎缁勪欢銆�">
+                  <Icon type="question-circle" />
+                  缁勪欢绫诲瀷
+                </Tooltip>
+              }>
+                {getFieldDecorator('printType', {
+                  initialValue: wrap.printType || 'content'
+                })(
+                  <Radio.Group>
+                    <Radio value="content">鍐呭</Radio>
+                    <Radio value="headerOrfooter">椤电湁/椤佃剼</Radio>
+                  </Radio.Group>
+                )}
+              </Form.Item>
+            </Col> : null}
+            <Col span={12}>
+              <Form.Item label="榛戝悕鍗�">
+                {getFieldDecorator('blacklist', {
+                  initialValue: wrap.blacklist || []
+                })(
+                  <Select
+                    showSearch
+                    mode="multiple"
+                    filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
+                  >
+                    {roleList.map(option =>
+                      <Select.Option key={option.uuid} value={option.value}>{option.text}</Select.Option>
+                    )}
+                  </Select>
+                )}
+              </Form.Item>
+            </Col>
+          </Row>
+        </Form>
+      </div>
+    )
+  }
+}
+
+export default Form.create()(SettingForm)
\ No newline at end of file
diff --git a/src/menu/components/form/wrapsetting/settingform/index.scss b/src/menu/components/form/wrapsetting/settingform/index.scss
new file mode 100644
index 0000000..159130b
--- /dev/null
+++ b/src/menu/components/form/wrapsetting/settingform/index.scss
@@ -0,0 +1,11 @@
+.model-menu-setting-form {
+  position: relative;
+
+  .anticon-question-circle {
+    color: #c49f47;
+    margin-right: 3px;
+  }
+  .ant-input-number {
+    width: 100%;
+  }
+}
\ No newline at end of file
diff --git a/src/menu/menushell/card.jsx b/src/menu/menushell/card.jsx
index 8150032..b89e539 100644
--- a/src/menu/menushell/card.jsx
+++ b/src/menu/menushell/card.jsx
@@ -14,6 +14,7 @@
 const CarouselPropCard = asyncComponent(() => import('@/menu/components/carousel/prop-card'))
 const TableCard = asyncComponent(() => import('@/menu/components/card/table-card'))
 const NormalTable = asyncComponent(() => import('@/menu/components/table/normal-table'))
+const NormalForm = asyncComponent(() => import('@/menu/components/form/normal-form'))
 const NormalGroup = asyncComponent(() => import('@/menu/components/group/normal-group'))
 const BraftEditor = asyncComponent(() => import('@/menu/components/editor/braft-editor'))
 const CodeSandbox = asyncComponent(() => import('@/menu/components/code/sandbox'))
@@ -58,6 +59,8 @@
       return (<MainSearch card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
     } else if (card.type === 'pie') {
       return (<AntvPie card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
+    } else if (card.type === 'form') {
+      return (<NormalForm card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
     } else if (card.type === 'tabs') {
       return (<AntvTabs tabs={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
     } else if (card.type === 'card' && card.subtype === 'datacard') {
diff --git a/src/menu/menushell/index.jsx b/src/menu/menushell/index.jsx
index 5f65129..05c87c4 100644
--- a/src/menu/menushell/index.jsx
+++ b/src/menu/menushell/index.jsx
@@ -92,6 +92,7 @@
         editor: '瀵屾枃鏈�',
         code: '鑷畾涔�',
         carousel: '杞挱',
+        form: '琛ㄥ崟',
         card: '鍗$墖'
       }
       let i = 1
diff --git a/src/menu/modalconfig/index.jsx b/src/menu/modalconfig/index.jsx
index 155a031..c6fa855 100644
--- a/src/menu/modalconfig/index.jsx
+++ b/src/menu/modalconfig/index.jsx
@@ -259,7 +259,7 @@
       if (_config.groups.length > 0) {
         _config.groups.forEach(group => {
           group.sublist = group.sublist.map(item => {
-            if (item.uuid !== res.uuid && item.field.toLowerCase() === res.field.toLowerCase()) {
+            if (item.uuid !== res.uuid && res.field && item.field && item.field.toLowerCase() === res.field.toLowerCase()) {
               fieldrepet = true
             } else if (item.uuid !== res.uuid && item.label === res.label) {
               labelrepet = true
@@ -274,7 +274,7 @@
         })
       } else {
         _config.fields = _config.fields.map(item => {
-          if (item.uuid !== res.uuid && item.field.toLowerCase() === res.field.toLowerCase()) {
+          if (item.uuid !== res.uuid && res.field && item.field && item.field.toLowerCase() === res.field.toLowerCase()) {
             fieldrepet = true
           } else if (item.uuid !== res.uuid && item.label === res.label) {
             labelrepet = true
diff --git a/src/menu/modalconfig/index.scss b/src/menu/modalconfig/index.scss
index 7dcf66e..be336b4 100644
--- a/src/menu/modalconfig/index.scss
+++ b/src/menu/modalconfig/index.scss
@@ -216,6 +216,10 @@
                 width: 100%;
                 margin-top: 4px;
               }
+              .normal-braft-editor {
+                border: 1px solid #d9d9d9;
+                border-radius: 4px;
+              }
             }
             .ant-form-item-control-wrapper::after {
               content: '';
diff --git a/src/menu/modulesource/option.jsx b/src/menu/modulesource/option.jsx
index 112abf9..957a360 100644
--- a/src/menu/modulesource/option.jsx
+++ b/src/menu/modulesource/option.jsx
@@ -16,6 +16,7 @@
 import Mainsearch from '@/assets/mobimg/mainsearch.png'
 import Carousel from '@/assets/mobimg/carousel.png'
 import Carousel1 from '@/assets/mobimg/carousel1.png'
+import form from '@/assets/mobimg/form.png'
 
 // 缁勪欢閰嶇疆淇℃伅
 export const menuOptions = [
@@ -23,6 +24,7 @@
   { type: 'menu', url: Mainsearch, component: 'search', subtype: 'mainsearch', title: '鎼滅储鏉′欢', width: 24, forbid: ['billPrint'] },
   { type: 'menu', url: card1, component: 'card', subtype: 'datacard', title: '鏁版嵁鍗�', width: 24 },
   { type: 'menu', url: card2, component: 'card', subtype: 'propcard', title: '灞炴�у崱', width: 24 },
+  { type: 'menu', url: form, component: 'form', subtype: 'form', title: '琛ㄥ崟', width: 24 },
   { type: 'menu', url: Carousel, component: 'carousel', subtype: 'datacard', title: '杞挱-鍔ㄦ�佹暟鎹�', width: 24, forbid: ['billPrint'] },
   { type: 'menu', url: Carousel1, component: 'carousel', subtype: 'propcard', title: '杞挱-闈欐�佹暟鎹�', width: 24, forbid: ['billPrint'] },
   { type: 'menu', url: NormalTable, component: 'table', subtype: 'normaltable', title: '甯哥敤琛�', width: 24 },
diff --git a/src/tabviews/zshare/mutilform/index.jsx b/src/tabviews/zshare/mutilform/index.jsx
index 8cb2174..7010528 100644
--- a/src/tabviews/zshare/mutilform/index.jsx
+++ b/src/tabviews/zshare/mutilform/index.jsx
@@ -1391,14 +1391,14 @@
   render() {
     const { cols } = this.state
     const formItemLayout = {
-      labelCol: {
-        xs: { span: 24 },
-        sm: { span: 8 }
-      },
-      wrapperCol: {
-        xs: { span: 24 },
-        sm: { span: 16 }
-      }
+      // labelCol: {
+      //   xs: { span: 24 },
+      //   sm: { span: 8 }
+      // },
+      // wrapperCol: {
+      //   xs: { span: 24 },
+      //   sm: { span: 16 }
+      // }
     }
 
     return (
diff --git a/src/templates/formtabconfig/index.jsx b/src/templates/formtabconfig/index.jsx
index 4493ddb..f07861f 100644
--- a/src/templates/formtabconfig/index.jsx
+++ b/src/templates/formtabconfig/index.jsx
@@ -905,7 +905,7 @@
         if (_ismutil && group.sublist.length === 0) {
           _config.enabled = false
         }
-        let arr = group.sublist.filter(item => item.field.toLowerCase() === _primary)
+        let arr = group.sublist.filter(item => item.field && item.field.toLowerCase() === _primary)
 
         if (arr.length > 0) {
           _config.enabled = false
@@ -1619,7 +1619,7 @@
     let primaryrepeat = false
 
     config.groups.forEach(group => {
-      let arr = group.sublist.filter(item => item.field.toLowerCase() === _primary)
+      let arr = group.sublist.filter(item => item.field && item.field.toLowerCase() === _primary)
 
       if (arr.length > 0) {
         primaryrepeat = true
diff --git a/src/templates/modalconfig/index.jsx b/src/templates/modalconfig/index.jsx
index 1db628e..d7e42bb 100644
--- a/src/templates/modalconfig/index.jsx
+++ b/src/templates/modalconfig/index.jsx
@@ -359,7 +359,7 @@
       if (_config.groups.length > 0) {
         _config.groups.forEach(group => {
           group.sublist = group.sublist.map(item => {
-            if (item.uuid !== res.uuid && item.field.toLowerCase() === res.field.toLowerCase()) {
+            if (item.uuid !== res.uuid && res.field && item.field && item.field.toLowerCase() === res.field.toLowerCase()) {
               fieldrepet = true
             } else if (item.uuid !== res.uuid && item.label === res.label) {
               labelrepet = true
@@ -374,7 +374,7 @@
         })
       } else {
         _config.fields = _config.fields.map(item => {
-          if (item.uuid !== res.uuid && item.field.toLowerCase() === res.field.toLowerCase()) {
+          if (item.uuid !== res.uuid && res.field && item.field && item.field.toLowerCase() === res.field.toLowerCase()) {
             fieldrepet = true
           } else if (item.uuid !== res.uuid && item.label === res.label) {
             labelrepet = true
diff --git a/src/templates/sharecomponent/fieldscomponent/index.jsx b/src/templates/sharecomponent/fieldscomponent/index.jsx
index a828bf7..43bb69b 100644
--- a/src/templates/sharecomponent/fieldscomponent/index.jsx
+++ b/src/templates/sharecomponent/fieldscomponent/index.jsx
@@ -70,7 +70,7 @@
       if (config.groups.length > 1) {
         config.groups.forEach(group => {
           group.sublist.forEach(item => {
-            if (columns.has(item.field.toLowerCase())) {
+            if (item.field && columns.has(item.field.toLowerCase())) {
               let _datatype = columns.get(item.field.toLowerCase()).datatype
               columns.set(item.field.toLowerCase(), {...item, selected: true, datatype: _datatype})
             }
@@ -78,7 +78,7 @@
         })
       } else {
         config.fields.forEach(item => {
-          if (columns.has(item.field.toLowerCase())) {
+          if (item.field && columns.has(item.field.toLowerCase())) {
             let _datatype = columns.get(item.field.toLowerCase()).datatype
             columns.set(item.field.toLowerCase(), {...item, selected: true, datatype: _datatype})
           }
@@ -243,7 +243,7 @@
         config.groups.forEach(group => {
           let _items = []
           group.sublist.forEach(item => {
-            if (columnsMap.has(item.field.toLowerCase())) {
+            if (item.field && columnsMap.has(item.field.toLowerCase())) {
               let cell = columnsMap.get(item.field.toLowerCase())
       
               if (cell.selected && cell.type === item.type) { // 鏁版嵁閫夋嫨鐘舵�佸強绫诲瀷鏈慨鏀规椂锛岀洿鎺ユ坊鍔�
@@ -283,7 +283,7 @@
         config.groups[config.groups.length - 1].sublist = [...config.groups.slice(-1)[0].sublist, ..._additems]
       } else {
         config.fields.forEach(item => {
-          if (columnsMap.has(item.field.toLowerCase())) {
+          if (item.field && columnsMap.has(item.field.toLowerCase())) {
             let cell = columnsMap.get(item.field.toLowerCase())
     
             if (cell.selected && cell.type === item.type) { // 鏁版嵁閫夋嫨鐘舵�佸強绫诲瀷鏈慨鏀规椂锛岀洿鎺ユ坊鍔�
diff --git a/src/templates/zshare/editcomponent/index.jsx b/src/templates/zshare/editcomponent/index.jsx
index d10b9a9..154eadb 100644
--- a/src/templates/zshare/editcomponent/index.jsx
+++ b/src/templates/zshare/editcomponent/index.jsx
@@ -224,7 +224,7 @@
         if (_config.groups.length > 0) {
           _config.groups.forEach(group => {
             group.sublist.forEach(item => {
-              fields.push(item.field.toLowerCase())
+              item.field && fields.push(item.field.toLowerCase())
               labels.push(item.label)
             })
             if (group.default) {
@@ -233,13 +233,13 @@
           })
         } else {
           _config.fields.forEach(item => {
-            fields.push(item.field.toLowerCase())
+            item.field && fields.push(item.field.toLowerCase())
             labels.push(item.label)
           })
           _config.fields.push(res)
         }
 
-        if (fields.includes(res.field.toLowerCase())) {
+        if (res.field && fields.includes(res.field.toLowerCase())) {
           notification.warning({
             top: 92,
             message: '瀛楁宸插瓨鍦紒',

--
Gitblit v1.8.0