From fc55bf4131e3056a84ac7bae16540a4e714214b8 Mon Sep 17 00:00:00 2001
From: king <18310653075@163.com>
Date: 星期四, 16 二月 2023 18:13:00 +0800
Subject: [PATCH] 2023-02-16

---
 src/menu/components/card/double-data-card/options.jsx    |  253 ++++++++
 src/menu/components/card/doublecardcomponent/options.jsx |  142 ++++
 src/tabviews/custom/components/module/voucher/index.jsx  |    4 
 src/menu/datasource/verifycard/index.scss                |   17 
 src/templates/modalconfig/settingform/index.jsx          |   24 
 src/menu/datasource/verifycard/settingform/index.jsx     |    2 
 src/menu/components/card/double-data-card/index.jsx      |  643 ++++++++++++++++++++
 src/menu/components/card/doublecardcomponent/index.scss  |   64 ++
 src/menu/components/card/doublecardcomponent/index.jsx   |  308 +++++++++
 src/menu/components/card/cardcomponent/index.jsx         |    2 
 src/tabviews/zshare/actionList/printbutton/index.jsx     |   46 +
 src/menu/datasource/index.jsx                            |    4 
 src/menu/menushell/card.jsx                              |    3 
 src/tabviews/zshare/actionList/normalbutton/index.jsx    |   47 +
 src/menu/components/card/cardcomponent/options.jsx       |    5 
 src/menu/components/card/double-data-card/index.scss     |  141 ++++
 src/menu/datasource/verifycard/index.jsx                 |  110 +++
 src/menu/modulesource/option.jsx                         |    1 
 src/assets/css/main.scss                                 |    6 
 19 files changed, 1,793 insertions(+), 29 deletions(-)

diff --git a/src/assets/css/main.scss b/src/assets/css/main.scss
index e5ada0e..6f5c18a 100644
--- a/src/assets/css/main.scss
+++ b/src/assets/css/main.scss
@@ -326,6 +326,12 @@
     background: rgba(0, 0, 0, 0);
   }
 }
+.moveable-modal {
+  overflow-x: hidden!important;
+  .ant-modal-header {
+    cursor: move;
+  }
+}
 
 .ant-drawer {
   z-index: 1080!important;
diff --git a/src/menu/components/card/cardcomponent/index.jsx b/src/menu/components/card/cardcomponent/index.jsx
index 6bd283e..8509386 100644
--- a/src/menu/components/card/cardcomponent/index.jsx
+++ b/src/menu/components/card/cardcomponent/index.jsx
@@ -13,7 +13,7 @@
 import './index.scss'
 
 const NormalForm = asyncIconComponent(() => import('@/components/normalform'))
-const CardCellComponent = asyncComponent(() => import('../cardcellcomponent'))
+const CardCellComponent = asyncComponent(() => import('@/menu/components/card/cardcellcomponent'))
 const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
 const PasteController = asyncIconComponent(() => import('@/components/paste'))
 
diff --git a/src/menu/components/card/cardcomponent/options.jsx b/src/menu/components/card/cardcomponent/options.jsx
index dbcca49..02d46f2 100644
--- a/src/menu/components/card/cardcomponent/options.jsx
+++ b/src/menu/components/card/cardcomponent/options.jsx
@@ -73,7 +73,7 @@
       controlFields: [
         {field: 'transform', values: ['multi']},
       ],
-      forbid: appType === 'mob'
+      forbid: appType === 'mob' || subtype === 'dualdatacard'
     },
     {
       type: 'select',
@@ -91,7 +91,8 @@
         {value: 'opacity', label: '閫忔槑搴�'},
         {value: 'rotateX', label: '绾靛悜灞曞紑'},
         {value: 'rotateY', label: '妯悜灞曞紑'},
-      ]
+      ],
+      forbid: appType === 'mob' || subtype === 'dualdatacard'
     },
     {
       type: 'text',
diff --git a/src/menu/components/card/double-data-card/index.jsx b/src/menu/components/card/double-data-card/index.jsx
new file mode 100644
index 0000000..451e497
--- /dev/null
+++ b/src/menu/components/card/double-data-card/index.jsx
@@ -0,0 +1,643 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import { is, fromJS } from 'immutable'
+import { Popover, Modal, Pagination, message } from 'antd'
+import { PlusOutlined, PlusCircleOutlined, PlusSquareOutlined, EditOutlined, ToolOutlined, DeleteOutlined, FontColorsOutlined, DownOutlined } from '@ant-design/icons'
+
+import asyncComponent from '@/utils/asyncComponent'
+import asyncIconComponent from '@/utils/asyncIconComponent'
+import { resetStyle, getTables } from '@/utils/utils-custom.js'
+import MKEmitter from '@/utils/events.js'
+import Utils from '@/utils/utils.js'
+import getWrapForm from './options'
+import './index.scss'
+
+const SettingComponent = asyncIconComponent(() => import('@/menu/datasource'))
+const NormalForm = asyncIconComponent(() => import('@/components/normalform'))
+const CardComponent = asyncComponent(() => import('../cardcomponent'))
+const DoubleCardComponent = asyncComponent(() => import('../doublecardcomponent'))
+const MobPagination = asyncIconComponent(() => import('@/menu/components/share/mobPagination'))
+const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
+const UserComponent = asyncIconComponent(() => import('@/menu/components/share/usercomponent'))
+const PasteComponent = asyncIconComponent(() => import('@/components/paste'))
+const NormalHeader = asyncComponent(() => import('@/menu/components/share/normalheader'))
+const ClockComponent = asyncIconComponent(() => import('@/menu/components/share/clockcomponent'))
+const ActionComponent = asyncComponent(() => import('@/menu/components/share/actioncomponent'))
+
+const { confirm } = Modal
+
+class DoubleDataCardEditComponent extends Component {
+  static propTpyes = {
+    card: PropTypes.object,
+    deletecomponent: PropTypes.func,
+    updateConfig: PropTypes.func,
+  }
+
+  state = {
+    card: null,
+    appType: sessionStorage.getItem('appType'),
+    back: false
+  }
+
+  UNSAFE_componentWillMount () {
+    const { card } = this.props
+
+    if (card.isNew) {
+      let _card = {
+        uuid: card.uuid,
+        type: card.type,
+        format: 'array',   // 缁勪欢灞炴�� - 鏁版嵁鏍煎紡
+        pageable: true,    // 缁勪欢灞炴�� - 鏄惁鍙垎椤�
+        switchable: true,  // 缁勪欢灞炴�� - 鏁版嵁鏄惁鍙垏鎹�
+        width: card.width || 24,
+        name: card.name,
+        subtype: card.subtype,
+        setting: { interType: 'system' },
+        wrap: { name: card.name, width: card.width || 24, title: '', pagestyle: 'page', cardType: '' },
+        style: { marginLeft: '0px', marginRight: '0px', marginTop: '8px', marginBottom: '8px' },
+        headerStyle: { fontSize: '16px', borderBottomWidth: '1px', borderBottomColor: '#e8e8e8' },
+        columns: [],
+        subColumns: [],
+        scripts: [],
+        action: [],
+        search: [],
+        subcards: [{
+          uuid: Utils.getuuid(),
+          setting: { width: 24},
+          style: {
+            borderWidth: '1px', borderColor: '#e8e8e8',
+            paddingTop: '15px', paddingBottom: '15px', paddingLeft: '15px', paddingRight: '15px',
+            marginLeft: '8px', marginRight: '8px', marginTop: '8px', marginBottom: '8px'
+          },
+          elements: [{
+            uuid: Utils.getuuid(),
+            eleType: 'text',
+            datatype: 'static',
+            value: '寰幆鍖哄煙'
+          }],
+          backSetting: {},
+          backStyle: {
+            borderLeftWidth: '1px', borderLeftColor: '#e8e8e8',
+            borderRightWidth: '1px', borderRightColor: '#e8e8e8',
+            borderBottomWidth: '1px', borderBottomColor: '#e8e8e8',
+            paddingTop: '15px', paddingBottom: '15px', paddingLeft: '15px', paddingRight: '15px'
+          },
+          backElements: [{
+            uuid: Utils.getuuid(),
+            eleType: 'text',
+            datatype: 'static',
+            value: '瀛愯〃鍖哄煙'
+          }]
+        }]
+      }
+
+      if (card.config) {
+        let config = fromJS(card.config).toJS()
+
+        _card.wrap = config.wrap
+        _card.wrap.name = card.name
+        _card.style = config.style
+        _card.headerStyle = config.headerStyle
+
+        _card.setting = config.setting
+        _card.columns = config.columns
+        _card.scripts = config.scripts
+
+        _card.subcards = config.subcards.map(scard => {
+          scard.uuid = Utils.getuuid()
+          scard.elements = scard.elements.map(elem => {
+            elem.uuid = Utils.getuuid()
+            return elem
+          })
+          scard.backElements = scard.backElements.map(elem => {
+            elem.uuid = Utils.getuuid()
+            return elem
+          })
+          return scard
+        })
+        _card.action = config.action.map(col => {
+          col.uuid = Utils.getuuid()
+          return col
+        })
+        _card.search = config.search.map(col => {
+          col.uuid = Utils.getuuid()
+          return col
+        })
+      }
+
+      this.updateComponent(_card)
+    } else {
+      let _card = fromJS(card).toJS()
+
+      this.setState({
+        card: _card
+      })
+    }
+  }
+
+  shouldComponentUpdate (nextProps, nextState) {
+    return !is(fromJS(this.state), fromJS(nextState))
+  }
+
+  /**
+   * @description 缁勪欢閿�姣侊紝娓呴櫎state鏇存柊锛屾竻闄ゅ揩鎹烽敭璁剧疆
+   */
+  componentWillUnmount () {
+    this.setState = () => {
+      return
+    }
+  }
+
+  /**
+   * @description 鍗$墖琛屽灞備俊鎭洿鏂帮紙鏁版嵁婧愶紝鏍峰紡绛夛級
+   */
+  updateComponent = (card) => {
+    card.width = card.wrap.width
+    card.name = card.wrap.name
+
+    if (window.GLOB.styling && card.errors) { // 鏍峰紡淇敼鏃朵笉鍋氱瓫鏌�
+      this.setState({
+        card: card
+      })
+  
+      this.props.updateConfig(card)
+      return
+    }
+
+    card.errors = []
+    let columns = card.columns.map(c => c.field)
+
+    if (card.setting.interType === 'system' && card.setting.execute !== 'false' && !card.setting.dataresource) {
+      card.errors.push({ level: 0, detail: '鏈缃暟鎹簮锛�'})
+    } else if (card.setting.interType === 'system' && card.setting.execute === 'false' && card.scripts.filter(script => script.status !== 'false').length === 0) {
+      card.errors.push({ level: 0, detail: '鏁版嵁婧愪腑鏃犲彲鐢ㄨ剼鏈紒'})
+    } else if (!card.setting.primaryKey) {
+      card.errors.push({ level: 0, detail: '鏈缃富閿紒'})
+    } else if (!columns.includes(card.setting.primaryKey)) {
+      card.errors.push({ level: 0, detail: '涓婚敭宸插け鏁堬紒'})
+    } else if (!card.setting.supModule) {
+      card.errors.push({ level: 0, detail: '鏈缃笂绾х粍浠讹紒'})
+    }
+    
+    if (card.errors.length === 0) {
+      card.$tables = getTables(card)
+    }
+
+    card.action.forEach(cell => {
+      if (cell.OpenType === 'pop' || (cell.OpenType === 'funcbutton' && cell.execMode === 'pop')) {
+        if (!cell.modal || cell.modal.fields.length === 0) {
+          card.errors.push({ level: 1, detail: `鎸夐挳鈥�${cell.label}鈥濅腑琛ㄥ崟灏氭湭娣诲姞`})
+        }
+      }
+    })
+
+    card.subcards.forEach((item, i) => {
+      let linkbtn = item.setting.linkbtn || ''
+      item.elements.forEach(cell => {
+        if (cell.eleType === 'button') {
+          if (cell.OpenType === 'pop' || (cell.OpenType === 'funcbutton' && cell.execMode === 'pop')) {
+            if (!cell.modal || cell.modal.fields.length === 0) {
+              card.errors.push({ level: 1, detail: `鎸夐挳鈥�${cell.label}鈥濅腑琛ㄥ崟灏氭湭娣诲姞`})
+            }
+          }
+          if (linkbtn && linkbtn === cell.uuid) {
+            linkbtn = ''
+          }
+        } else if (cell.datatype === 'dynamic' && cell.field && !columns.includes(cell.field)) {
+          card.errors.push({ level: 1, detail: `鍗$墖涓姩鎬佸瓧娈碘��${cell.field}鈥濇棤鏁坄})
+        }
+      })
+
+      item.backElements.forEach(cell => {
+        if (cell.eleType === 'button') {
+          if (cell.OpenType === 'pop' || (cell.OpenType === 'funcbutton' && cell.execMode === 'pop')) {
+            if (!cell.modal || cell.modal.fields.length === 0) {
+              card.errors.push({ level: 1, detail: `鎸夐挳鈥�${cell.label}鈥濅腑琛ㄥ崟灏氭湭娣诲姞`})
+            }
+          }
+          if (linkbtn && linkbtn === cell.uuid) {
+            linkbtn = ''
+          }
+        } else if (cell.datatype === 'dynamic' && cell.field && !columns.includes(cell.field)) {
+          card.errors.push({ level: 1, detail: `鍗$墖涓姩鎬佸瓧娈碘��${cell.field}鈥濇棤鏁坄})
+        }
+      })
+
+      if (linkbtn) {
+        card.errors.push({ level: 1, detail: `绗�${i + 1}寮犲崱鐗囦腑缁戝畾鎸夐挳宸插垹闄})
+      }
+    })
+
+    this.setState({
+      card: card
+    })
+
+    this.props.updateConfig(card)
+  }
+
+  /**
+   * @description 鍗曚釜鍗$墖淇℃伅鏇存柊
+   */
+  updateCard = (cell, btn) => {
+    let card = fromJS(this.state.card).toJS()
+
+    card.subcards = card.subcards.map(item => {
+      if (item.uuid === cell.uuid) return cell
+      return item
+    })
+
+    if (btn) {
+      card.action = card.action.filter(item => item.uuid !== btn.uuid)
+    }
+
+    this.updateComponent(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)
+
+        _this.updateComponent(card)
+      },
+      onCancel() {}
+    })
+  }
+
+  changeStyle = () => {
+    const { card } = this.state
+
+    MKEmitter.emit('changeStyle', ['background', 'height', 'border', 'padding', 'margin', 'shadow', 'clear', 'minHeight'], card.style, this.getStyle)
+  }
+
+  getStyle = (style) => {
+    let _card = {...this.state.card, style}
+    
+    this.updateComponent(_card)
+  }
+
+  addSearch = (copy) => {
+    const { card } = this.state
+
+    let newcard = {}
+
+    if (copy) {
+      newcard = copy
+      newcard.focus = true
+    } else {
+      newcard.uuid = Utils.getuuid()
+      newcard.focus = true
+  
+      newcard.label = 'label'
+      newcard.type = 'select'
+      newcard.resourceType = '0'
+      newcard.options = []
+      newcard.orderType = 'asc'
+      newcard.match = '='
+    }
+
+    // 娉ㄥ唽浜嬩欢-娣诲姞鎼滅储
+    MKEmitter.emit('addSearch', card.uuid, newcard)
+  }
+
+  addButton = (copy) => {
+    const { card } = this.state
+
+    let newcard = {}
+
+    if (copy) {
+      newcard = copy
+      newcard.focus = true
+    } else {
+      newcard.uuid = Utils.getuuid()
+      newcard.focus = true
+      
+      newcard.label = 'label'
+      newcard.Ot = 'requiredSgl'
+      newcard.OpenType = 'pop'
+      newcard.icon = ''
+      newcard.class = 'green'
+      newcard.intertype = card.setting.interType || 'system'
+      newcard.innerFunc = card.setting.innerFunc || ''
+      newcard.sysInterface = card.setting.sysInterface || ''
+      newcard.outerFunc = card.setting.outerFunc || ''
+      newcard.interface = card.setting.interface || ''
+      newcard.execSuccess = 'grid'
+      newcard.execError = 'never'
+      newcard.verify = null
+      newcard.show = 'button'
+      newcard.style = {marginRight: '15px'}
+    }
+
+    // 娉ㄥ唽浜嬩欢-娣诲姞鎸夐挳
+    MKEmitter.emit('addButton', card.uuid, newcard)
+  }
+
+  setSubConfig = (item) => {
+    const { card, appType } = this.state
+    let btn = fromJS(item).toJS()
+
+    if (btn.OpenType === 'pop' || btn.execMode === 'pop') {
+      if (!btn.modal) {
+        btn.modal = {
+          setting: { title: btn.label, width: appType === 'mob' ? 100 : 60, cols: '2', container: 'view', focus: '', finish: 'close', clickouter: 'unclose', display: 'modal' },
+          tables: [],
+          groups: [],
+          fields: []
+        }
+      }
+      MKEmitter.emit('changeModal', card, btn)
+    } else if (btn.OpenType === 'popview' && appType !== 'mob') {
+      MKEmitter.emit('changePopview', card, btn)
+    }
+  }
+
+  addCard = (copy) => {
+    let card = fromJS(this.state.card).toJS()
+    let newcard = {}
+
+    if (copy) { // 绮樿创
+      newcard = copy
+    } else {
+      let height = card.subcards[0].style.height
+      if (height === 'auto') {
+        height = '100px'
+      }
+  
+      newcard = {
+        uuid: Utils.getuuid(),
+        $cardType: 'extendCard',
+        setting: { width: 24, click: ''},
+        style: {
+          height,
+          borderWidth: '1px', borderColor: '#e8e8e8',
+          paddingTop: '15px', paddingBottom: '15px', paddingLeft: '15px', paddingRight: '15px',
+          marginLeft: '8px', marginRight: '8px', marginTop: '8px', marginBottom: '8px'
+        },
+        backStyle: {},
+        elements: [],
+        backElements: []
+      }
+    }
+
+    card.subcards.push(newcard)
+
+    this.updateComponent(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.updateComponent(card)
+  }
+
+  getWrapForms = () => {
+    const { card } = this.state
+    
+    return getWrapForm(card.wrap, card.columns, card.setting)
+  }
+
+  updateWrap = (res) => {
+    const { card } = this.state
+
+    let _card = {...card, wrap: res}
+
+    if (res.supNodes) {
+      _card.supNodes = res.supNodes
+      _card.supNodes = _card.supNodes.map(item => {
+        item.componentId = item.nodes[item.nodes.length - 1]
+        return item
+      })
+
+      delete res.supNodes
+    } else {
+      delete _card.supNodes
+    }
+
+    
+    if (res.layout === 'flex') {
+      _card.wrap.pagestyle = 'page'
+    }
+    
+    if (res.selStyle === 'tabs' && card.wrap.selStyle !== 'tabs') {
+      Object.keys(_card.style).forEach(key => {
+        if (/^border/.test(key)) {
+          delete _card.style[key]
+        }
+      })
+      _card.style.borderBottomColor = '#eeeeee'
+      _card.style.borderBottomWidth = '1px'
+      _card.style.paddingBottom = '0px'
+
+      _card.subcards.forEach(item => {
+        delete item.style.marginBottom
+      })
+      
+      this.setState({card: {..._card, subcards: []}}, () => {
+        this.updateComponent(_card)
+      })
+    } else {
+      this.updateComponent(_card)
+    }
+  }
+
+  pasteComponent = (res, resolve) => {
+    const { card, appType } = this.state
+
+    let type = res.copyType
+    delete res.copyType
+
+    if (type === 'cardcell') {
+      res.uuid = Utils.getuuid()
+      res.setting = res.setting || {}
+      res.$cardType = 'extendCard'
+      res.setting.width = res.setting.width || 6
+
+      let mobtypes = ['pop', 'prompt', 'exec', 'innerpage', 'funcbutton']
+
+      let elements = []
+      res.elements && res.elements.forEach(cell => {
+        // if (cell.datatype === 'dynamic') {
+        //   cell.datatype = 'static'
+        // }
+
+        if (cell.eleType !== 'button') {
+          cell.uuid = Utils.getuuid()
+          elements.push(cell)
+        } else if (appType === 'mob' && !mobtypes.includes(cell.OpenType)) {
+          return
+        } else {
+          cell.uuid = Utils.getuuid()
+          elements.push(cell)
+        }
+      })
+
+      res.elements = elements
+
+      let backElements = []
+
+      if (appType !== 'mob') {
+        res.backElements && res.backElements.forEach(cell => {
+          // if (cell.datatype === 'dynamic') {
+          //   cell.datatype = 'static'
+          // }
+
+          if (cell.eleType !== 'button') {
+            cell.uuid = Utils.getuuid()
+            backElements.push(cell)
+          } else if (appType === 'mob' && !mobtypes.includes(cell.OpenType)) {
+            return
+          } else {
+            cell.uuid = Utils.getuuid()
+            backElements.push(cell)
+          }
+        })
+      }
+
+      res.backElements = backElements
+
+      resolve({status: true})
+
+      this.addCard(res)
+    } else if (type === 'search' || type === 'form') {
+      if (appType === 'mob') {
+        resolve({status: false, message: '绉诲姩绔暟鎹崱涓嶆敮鎸佹坊鍔犳悳绱㈡潯浠躲��'})
+      } else {
+        res.uuid = Utils.getuuid()
+        let keys = card.search.map(item => item.field.toLowerCase())
+  
+        if (type === 'form') {
+          if (['number', 'switch', 'textarea', 'fileupload', 'hint', 'color', 'funcvar'].includes(res.type)) {
+            res.type = 'text'
+          } else if (res.type === 'radio') {
+            res.type = 'select'
+          } else if (res.type === 'checkbox') {
+            res.type = 'multiselect'
+          } else if (res.type === 'datetime') {
+            res.type = 'date'
+          }
+        }
+  
+        if (res.field && keys.includes(res.field.toLowerCase())) {
+          resolve({status: false, message: '鎼滅储瀛楁宸插瓨鍦紒'})
+          return
+        }
+
+        resolve({status: true})
+
+        this.addSearch(res)
+      }
+    } else if (type === 'action') {
+      if (appType === 'mob' && !['pop', 'prompt', 'exec', 'innerpage'].includes(res.OpenType)) {
+        resolve({status: false, message: '绉诲姩绔笉鏀寔姝ょ被鍨嬬殑鎸夐挳銆�'})
+      } else {
+        resolve({status: true})
+    
+        res.uuid = Utils.getuuid()
+        this.addButton(res)
+      }
+    }
+  }
+
+  clickComponent = (e) => {
+    if (sessionStorage.getItem('style-control') === 'true' || sessionStorage.getItem('style-control') === 'component') {
+      e.stopPropagation()
+      MKEmitter.emit('clickComponent', this.state.card.uuid, null, (style) => {
+        let _card = {...this.state.card}
+        _card.style = {..._card.style, ...style}
+
+        this.setState({ card: _card })
+        this.props.updateConfig(_card)
+      })
+    }
+  }
+
+  render() {
+    const { card, appType } = this.state
+
+    let _style = resetStyle(card.style)
+
+    return (
+      <div className={'menu-double-data-card-edit-box ' + appType} style={_style} onClick={this.clickComponent} id={card.uuid}>
+        <NormalHeader config={card} updateComponent={this.updateComponent}/>
+        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
+          <div className="mk-popover-control">
+            <PlusOutlined className="plus" title="娣诲姞鍗$墖" onClick={() => this.addCard()}/>
+            {appType !== 'mob' ? <PlusCircleOutlined className="plus" title="娣诲姞鎼滅储" onClick={() => this.addSearch()}/> : null}
+            <PlusSquareOutlined className="plus" title="娣诲姞鎸夐挳" onClick={() => this.addButton()}/>
+            <NormalForm title="鏁版嵁鍗¤缃�" width={800} update={this.updateWrap} getForms={this.getWrapForms}>
+              <EditOutlined style={{color: '#1890ff'}} title="缂栬緫"/>
+            </NormalForm>
+            <CopyComponent type="datacard" card={card}/>
+            <PasteComponent options={['action', 'search', 'form', 'cardcell']} updateConfig={this.pasteComponent} />
+            <FontColorsOutlined className="style" title="璋冩暣鏍峰紡" onClick={this.changeStyle}/>
+            <ClockComponent config={card} updateConfig={this.updateComponent}/>
+            <UserComponent config={card}/>
+            <DeleteOutlined className="close" title="鍒犻櫎缁勪欢" onClick={() => this.props.deletecomponent(card.uuid)} />
+            <SettingComponent config={card} updateConfig={this.updateComponent} />
+          </div>
+        } trigger="hover">
+          <ToolOutlined />
+        </Popover>
+        <ActionComponent config={card} setSubConfig={this.setSubConfig} updateaction={this.updateComponent}/>
+        <div className={'float-' + (card.wrap.cardFloat || 'left') + ' select-' + card.wrap.selStyle}>
+          {card.subcards.map(subcard => {
+            if (subcard.$cardType === 'extendCard') {
+              return (<CardComponent key={subcard.uuid} cards={card} card={subcard} move={this.move} updateElement={this.updateCard} deleteElement={this.deleteCard}/>)
+            } else {
+              return (<DoubleCardComponent key={subcard.uuid} cards={card} card={subcard} updateElement={this.updateCard}/>)
+            }
+          })}
+        </div>
+        <div style={{clear: 'both'}}></div>
+        {card.wrap.pagestyle === 'page' && card.setting.laypage === 'true' && appType !== 'mob' ? <Pagination total={85} size="small" showTotal={total => `鍏� ${total} 鏉} pageSize={20} defaultCurrent={1}/> : null}
+        {card.wrap.pagestyle === 'page' && card.setting.laypage === 'true' && appType === 'mob' ? <MobPagination /> : null}
+        {card.wrap.pagestyle === 'more' && card.setting.laypage === 'true' ? <div className="mk-more">鏌ョ湅鏇村<DownOutlined/></div> : null}
+        <div className="component-name">
+          <div className="center">
+            <div className="title" onDoubleClick={() => {
+              let oInput = document.createElement('input')
+              oInput.value = card.uuid
+              document.body.appendChild(oInput)
+              oInput.select()
+              document.execCommand('Copy')
+              document.body.removeChild(oInput)
+              message.success('澶嶅埗鎴愬姛銆�')
+            }}>{card.name}</div>
+            <div className="content">
+              {card.errors && card.errors.map((err, index) => {
+                if (err.level === 0) {
+                  return <span key={index} className="error">{err.detail}</span>
+                } else {
+                  return <span key={index} className="waring">{err.detail}锛�</span>
+                }
+              })}
+            </div>
+          </div>
+        </div>
+      </div>
+    )
+  }
+}
+
+export default DoubleDataCardEditComponent
\ No newline at end of file
diff --git a/src/menu/components/card/double-data-card/index.scss b/src/menu/components/card/double-data-card/index.scss
new file mode 100644
index 0000000..9a5313d
--- /dev/null
+++ b/src/menu/components/card/double-data-card/index.scss
@@ -0,0 +1,141 @@
+.menu-double-data-card-edit-box {
+  position: relative;
+  box-sizing: border-box;
+  background: #ffffff;
+  background-position: center center;
+  background-repeat: no-repeat;
+  background-size: cover;
+  min-height: 20px;
+  overflow-y: auto;
+  
+  >.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-color: #ffffff;
+    background-position: center center;
+    background-repeat: no-repeat;
+    background-size: cover;
+    min-height: 20px;
+  }
+  .card-item.hover:not(:hover) {
+    button {
+      opacity: 0;
+      transition: opacity 0.3s;
+    }
+  }
+  
+  .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;
+    }
+  }
+  .ant-pagination {
+    float: right;
+    margin: 10px;
+  }
+
+  .model-menu-action-list {
+    .page-card {
+      line-height: 55px;
+    }
+  }
+  .normal-pagination {
+    .am-button::before {
+      display: none;
+    }
+    .am-button {
+      border: none;
+      font-size: 16px;
+    }
+  }
+  .float-center {
+    text-align: center;
+    >.ant-col {
+      display: inline-block;
+      float: none;
+      text-align: left;
+      vertical-align: top;
+    }
+  }
+  .float-right {
+    text-align: right;
+    >.ant-col {
+      display: inline-block;
+      float: none;
+      text-align: left;
+      vertical-align: top;
+    }
+  }
+  .select-tabs {
+    .card-item {
+      border-top: none!important;
+      border-left: none!important;
+      border-right: none!important;
+      border-radius: 0px!important;
+      border-bottom: 2px solid transparent!important;
+    }
+  }
+  .mk-more {
+    text-align: center;
+    line-height: 40px;
+    .anticon-down {
+      margin-left: 2px;
+    }
+  }
+}
+
+.menu-double-data-card-edit-box::-webkit-scrollbar {
+  width: 7px;
+  height: 7px;
+}
+.menu-double-data-card-edit-box::-webkit-scrollbar-thumb {
+  border-radius: 5px;
+  box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.13);
+  background: rgba(0, 0, 0, 0.13);
+}
+.menu-double-data-card-edit-box::-webkit-scrollbar-track {
+  box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05);
+  border-radius: 3px;
+  border: 1px solid rgba(0, 0, 0, 0.07);
+  background: rgba(0, 0, 0, 0);
+}
+.menu-double-data-card-edit-box::after {
+  display: block;
+  content: ' ';
+  clear: both;
+}
+.menu-double-data-card-edit-box.mob {
+  .model-menu-action-list {
+    .page-card {
+      line-height: 40px;
+      margin-top: 5px;
+    }
+  }
+}
+.menu-double-data-card-edit-box:hover {
+  z-index: 1;
+  box-shadow: 0px 0px 4px #1890ff;
+}
diff --git a/src/menu/components/card/double-data-card/options.jsx b/src/menu/components/card/double-data-card/options.jsx
new file mode 100644
index 0000000..2a7fabe
--- /dev/null
+++ b/src/menu/components/card/double-data-card/options.jsx
@@ -0,0 +1,253 @@
+/**
+ * @description Wrap琛ㄥ崟閰嶇疆淇℃伅
+ */
+export default function (wrap, columns = [], setting) {
+  let appType = sessionStorage.getItem('appType')
+  let MenuType = ''
+  let menu = window.GLOB.customMenu
+  let laypage = setting && setting.laypage !== 'false'
+
+  if (menu.parentId === 'BillPrintTemp') {
+    MenuType = 'billPrint'
+  }
+
+  let roleList = sessionStorage.getItem('sysRoles')
+
+  if (roleList) {
+    try {
+      roleList = JSON.parse(roleList)
+    } catch (e) {
+      roleList = []
+    }
+  } else {
+    roleList = []
+  }
+
+  let menulist = []
+
+  if (appType === 'mob') {
+    menulist = sessionStorage.getItem('appMenus')
+    if (menulist) {
+      try {
+        menulist = JSON.parse(menulist)
+      } catch (e) {
+        menulist = []
+      }
+    } else {
+      menulist = []
+    }
+  }
+
+  const cardWrapForm = [
+    {
+      type: 'text',
+      field: 'title',
+      label: '鏍囬',
+      initval: wrap.title || '',
+      required: false
+    },
+    {
+      type: 'text',
+      field: 'name',
+      label: '缁勪欢鍚嶇О',
+      initval: wrap.name || '',
+      tooltip: '鐢ㄤ簬缁勪欢闂寸殑鍖哄垎銆�',
+      required: true
+    },
+    {
+      type: 'number',
+      field: 'width',
+      label: '瀹藉害',
+      initval: wrap.width || 24,
+      tooltip: '鏍呮牸甯冨眬锛屾瘡琛岀瓑鍒嗕负24鍒椼��',
+      min: 1,
+      max: 24,
+      precision: 0,
+      required: true
+    },
+    {
+      type: 'radio',
+      field: 'layout',
+      label: '鍗$墖甯冨眬',
+      initval: wrap.layout || 'grid',
+      tooltip: appType === 'mob' ? '寮规�у竷灞�鏃讹紝婊戝姩鍔犺浇鏃犳晥' : '',
+      required: false,
+      options: [
+        {value: 'grid', label: '鏍呮牸甯冨眬'},
+        {value: 'flex', label: '寮规�у竷灞�'},
+      ],
+      controlFields: [
+        {field: 'printHeight', values: ['flex']},
+        {field: 'cardFloat', values: ['grid']},
+      ]
+    },
+    {
+      type: 'radio',
+      field: 'pagestyle',
+      label: '鍒嗛〉椋庢牸',
+      initval: wrap.pagestyle || 'page',
+      tooltip: '鏁版嵁婧愰�夋嫨鍒嗛〉鏃舵湁鏁堛�傛敞锛氬脊鎬у竷灞�鏃跺浐瀹氫负椤电爜銆�',
+      required: false,
+      disabled: !laypage,
+      options: [
+        {value: 'page', label: '椤电爜'},
+        {value: 'slide', label: '婊戝姩鍔犺浇', forbid: appType !== 'mob' || sessionStorage.getItem('editMenuType') === 'popview'},
+        {value: 'more', label: '鏌ョ湅鏇村'},
+      ],
+      controlFields: [
+        {field: 'slidetip', values: ['slide']},
+      ],
+    },
+    {
+      type: 'radio',
+      field: 'cardType',
+      label: '鏁版嵁閫夋嫨',
+      initval: wrap.cardType || '',
+      required: false,
+      options: [
+        {value: '', label: '涓嶅彲閫�'},
+        {value: 'radio', label: '鍗曢��'},
+        {value: 'checkbox', label: '澶氶��'},
+      ],
+      controlFields: [
+        {field: 'selected', values: ['radio', 'checkbox']},
+        {field: 'selStyle', values: ['radio', 'checkbox']},
+      ],
+    },
+    {
+      type: 'select',
+      field: 'selected',
+      label: '鏁版嵁閫変腑',
+      initval: wrap.selected || 'false',
+      tooltip: '鍒濆鍖栵細鏁版嵁鍔犺浇鏃堕�変腑棣栬鏁版嵁锛屼粎鎵ц涓�娆°�傛暟鎹姞杞斤細姣忔鏁版嵁鍔犺浇鏃跺潎閫変腑棣栬锛堝綋鎸夐挳鎵ц瀹屾垚骞惰繑鍥炰富閿�兼椂锛岄粯璁ら�変腑涓婚敭鍊煎搴旇锛夈�傞�変腑鏍囪锛氳繑鍥炴暟鎹腑瀛樺湪 selected 瀛楁锛屼笖鍊间负 true 鐨勬暟鎹閫変腑銆�',
+      required: false,
+      options: [
+        {value: 'false', label: '鏃�'},
+        {value: 'init', label: '鍒濆鍖�'},
+        {value: 'always', label: '鏁版嵁鍔犺浇'},
+        {value: 'sign', label: '閫変腑鏍囪'}
+      ],
+    },
+    {
+      type: 'select',
+      field: 'selStyle',
+      label: '閫変腑椋庢牸',
+      initval: wrap.selStyle || 'active',
+      tooltip: '瀛樺湪杈规鏃讹紝杈规浼氫娇鐢ㄧ郴缁熻壊銆�',
+      required: false,
+      options: [
+        {value: 'none', label: '鏃�'},
+        {value: 'active', label: '澶栭槾褰�'},
+        {value: 'backFont', label: '鑳屾櫙+鏂囧瓧'},
+        {value: 'font', label: '鏂囧瓧'},
+        {value: 'check', label: '鍕鹃�夛紙鍦嗘锛�'},
+        {value: 'check square', label: '鍕鹃�夛紙鏂规锛�'}
+      ]
+    },
+    {
+      type: 'radio',
+      field: 'cardFloat',
+      label: '瀵归綈鏂瑰紡',
+      initval: wrap.cardFloat || 'left',
+      tooltip: '璁剧疆鍗$墖鐨勫榻愭柟寮忋��',
+      required: false,
+      options: [
+        {value: 'left', label: '宸﹀榻�'},
+        {value: 'center', label: '灞呬腑'},
+        {value: 'right', label: '鍙冲榻�'},
+      ],
+    },
+    {
+      type: 'radio',
+      field: 'parity',
+      label: '濂囧伓鑳屾櫙',
+      initval: wrap.parity || 'false',
+      tooltip: '鍋舵暟琛屼細娣诲姞鑳屾櫙鑹层��',
+      required: false,
+      options: [
+        {value: 'false', label: '鏃�'},
+        {value: 'true', label: '鏈�'},
+      ],
+    },
+    {
+      type: 'number',
+      field: 'printHeight',
+      label: '鎹㈢畻楂樺害',
+      initval: wrap.printHeight || '',
+      tooltip: '褰撳墠鏁版嵁鍗¢珮搴︾浉褰撲簬鍑犳潯鏁版嵁銆�',
+      required: false,
+      forbid: MenuType !== 'billPrint'
+    },
+    {
+      type: 'radio',
+      field: 'empty',
+      label: '绌哄�奸殣钘�',
+      initval: wrap.empty || 'show',
+      tooltip: '褰撴煡璇㈡暟鎹负绌烘椂锛岄殣钘忚缁勪欢銆�',
+      required: false,
+      options: [
+        {value: 'show', label: '鍚�'},
+        {value: 'hidden', label: '鏄�'},
+      ],
+    },
+    {
+      type: 'select',
+      field: 'controlField',
+      label: '绂佺敤瀛楁',
+      initval: wrap.controlField || '',
+      tooltip: '鐢ㄤ簬鎺у埗琛屾暟鎹槸鍚﹀彲閫夋嫨銆�',
+      required: false,
+      allowClear: true,
+      options: columns,
+      controlFields: [
+        {field: 'controlVal', notNull: true},
+      ],
+    },
+    {
+      type: 'text',
+      field: 'controlVal',
+      label: '绂佺敤鍊�',
+      initval: wrap.controlVal || '',
+      tooltip: '褰撳瓧娈靛�间笌绂佺敤鍊肩浉绛夋椂锛岃鏁版嵁浼氱鐢紝澶氫釜鍊肩敤閫楀彿鍒嗛殧銆�',
+      required: false
+    },
+    {
+      type: 'radio',
+      field: 'permission',
+      label: '鏉冮檺楠岃瘉',
+      initval: wrap.permission || 'false',
+      required: false,
+      options: [
+        {value: 'true', label: '鍚敤'},
+        {value: 'false', label: '绂佺敤'},
+      ],
+      forbid: !appType || sessionStorage.getItem('editMenuType') === 'popview'
+    },
+    {
+      type: 'multiselect',
+      field: 'blacklist',
+      label: '榛戝悕鍗�',
+      initval: wrap.blacklist || [],
+      required: false,
+      options: roleList,
+      forbid: !!appType
+    },
+    {
+      type: 'text',
+      field: 'slidetip',
+      label: '搴曢儴鎻愮ず',
+      initval: wrap.slidetip || wrap.slidetip === '' ? wrap.slidetip : '娌℃湁鏇村浜�',
+      tooltip: '婊戝姩鍔犺浇鑷冲簳閮ㄦ椂鐨勬彁绀轰俊鎭��',
+      required: false,
+      forbid: !laypage || appType !== 'mob'
+    },
+  ]
+
+  return cardWrapForm.filter(item => {
+    if (['pagestyle'].includes(item.field)) {
+      item.options = item.options.filter(option => !option.forbid)
+    }
+
+    return !item.forbid
+  })
+} 
\ No newline at end of file
diff --git a/src/menu/components/card/doublecardcomponent/index.jsx b/src/menu/components/card/doublecardcomponent/index.jsx
new file mode 100644
index 0000000..6f1d02c
--- /dev/null
+++ b/src/menu/components/card/doublecardcomponent/index.jsx
@@ -0,0 +1,308 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import { is, fromJS } from 'immutable'
+import { Popover, Col } from 'antd'
+import { PlusOutlined, PlusSquareOutlined, EditOutlined, ToolOutlined, FontColorsOutlined } from '@ant-design/icons'
+
+import asyncComponent from '@/utils/asyncComponent'
+import asyncIconComponent from '@/utils/asyncIconComponent'
+import { resetStyle } from '@/utils/utils-custom.js'
+import getSettingForm from './options'
+import Utils from '@/utils/utils.js'
+import MKEmitter from '@/utils/events.js'
+import './index.scss'
+
+const NormalForm = asyncIconComponent(() => import('@/components/normalform'))
+const CardCellComponent = asyncComponent(() => import('@/menu/components/card/cardcellcomponent'))
+const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
+const PasteController = asyncIconComponent(() => import('@/components/paste'))
+
+class DoubleCardBoxComponent extends Component {
+  static propTpyes = {
+    cards: PropTypes.object,         // 鍗$墖琛岄厤缃俊鎭�
+    card: PropTypes.object,          // 鍗$墖閰嶇疆淇℃伅
+    updateElement: PropTypes.func    // 鑿滃崟閰嶇疆鏇存柊
+  }
+
+  state = {
+    card: null,            // 鍗$墖淇℃伅锛屽寘鎷鍙嶉潰
+    appType: sessionStorage.getItem('appType'),
+    visible: false
+  }
+
+  /**
+   * @description 鎼滅储鏉′欢鍒濆鍖�
+   */
+  UNSAFE_componentWillMount () {
+    const { card } = this.props
+
+    this.setState({
+      card: fromJS(card).toJS()
+    })
+  }
+
+  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
+    }
+  }
+
+  getStyle = (style) => {
+    const { card, side } = this.state
+
+    let _card = fromJS(card).toJS()
+    if (side === 'back') {
+      _card.backStyle = style
+    } else {
+      _card.style = style
+    }
+
+    this.setState({
+      card: _card
+    })
+
+    this.props.updateElement(_card)
+  }
+
+  updateCard = (elements, btn) => {
+    const { card, side } = this.state
+
+    let _card = {}
+
+    if (side === 'back') {
+      _card = {...card, backElements: elements}
+    } else {
+      _card = {...card, elements: elements}
+    }
+
+    this.setState({
+      card: _card
+    })
+
+    this.props.updateElement(_card, btn)
+  }
+
+  changeSide = () => {
+    const { card } = this.props
+    const { side } = this.state
+
+    let _side = ''
+    let _elements = null
+    if (side === 'front') {
+      _side = 'back'
+    } else {
+      _side = 'front'
+    }
+
+    if (_side === 'front') {
+      _elements = fromJS(card.elements).toJS()
+    } else {
+      _elements = fromJS(card.backElements).toJS()
+    }
+
+    this.setState({side: _side, elements: _elements})
+  }
+  
+  addElement = () => {
+    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', card.uuid, newcard)
+  }
+
+  addButton = () => {
+    const { card } = this.state
+
+    let newcard = {eleType: 'button', label: 'button', verify: null, show: 'link', sqlType: '', Ot: 'requiredSgl', OpenType: 'prompt', icon: '', class: 'primary', intertype: 'system', execSuccess: 'grid', execError: 'never', popClose: 'never'}
+    newcard.uuid = Utils.getuuid()
+    newcard.focus = true
+
+    // 娉ㄥ唽浜嬩欢-娣诲姞鍏冪礌
+    MKEmitter.emit('cardAddElement', card.uuid, newcard)
+  }
+
+  changeStyle = () => {
+    const { card, side } = this.state
+
+    let _style = null
+    let options = ['height', 'background', 'border', 'padding', 'margin', 'shadow']
+    if (side === 'front') {
+      _style = card.style ? fromJS(card.style).toJS() : {}
+    } else if (side === 'back') {
+      _style = card.backStyle ? fromJS(card.backStyle).toJS() : {}
+      options = ['background', 'padding']
+    }
+
+    MKEmitter.emit('changeStyle', options, _style, this.getStyle)
+  }
+
+  getSettingForms = () => {
+    const { cards } = this.props
+    const { card, appType } = this.state
+
+    let buttons = []
+    card.elements && card.elements.forEach(item => {
+      if (item.eleType === 'button') {
+        buttons.push({
+          value: item.uuid,
+          label: item.label
+        })
+      }
+    })
+    if (appType !== 'mob' && card.backElements) {
+      card.backElements.forEach(item => {
+        if (item.eleType === 'button') {
+          buttons.push({
+            value: item.uuid,
+            label: item.label
+          })
+        }
+      })
+    }
+    return getSettingForm(card.setting, cards.subtype, buttons, card.$cardType, cards.columns)
+  }
+
+  updateSetting = (res) => {
+    const { card, side, appType } = this.state
+
+    if (appType === '' && res.menu) {
+      let list = null
+      try {
+        list = JSON.parse(sessionStorage.getItem('thdMenuList')) || []
+      } catch (e) {
+        list = []
+      }
+
+      let id = res.menu[res.menu.length - 1]
+
+      list.forEach(item => {
+        if (item.MenuID === id) {
+          res.MenuID = id
+          res.MenuName = item.MenuName
+          res.MenuNo = item.MenuNo
+          res.tabType = item.type
+        }
+      })
+    }
+
+    let _card = {...card, setting: res}
+
+    this.setState({
+      card: _card
+    })
+
+    if (side === 'back' && res.type === 'simple') {
+      this.setState({
+        side: 'front',
+        elements: fromJS(_card.elements).toJS()
+      })
+    }
+
+    this.props.updateElement(_card)
+  }
+
+  paste = (element, resolve) => {
+    const { card } = this.state
+
+    let _uuid = Utils.getuuid()
+    
+    if (element.copyType === 'action') {
+      element.eleType = 'button'
+    }
+
+    element.uuid = _uuid
+    element.focus = true
+
+    resolve({status: true})
+
+    // 娉ㄥ唽浜嬩欢-娣诲姞鍏冪礌
+    MKEmitter.emit('cardAddElement', card.uuid, element)
+  }
+
+  doubleClickCard = () => {
+    const { card } = this.state
+
+    if (card.setting.click === 'menu' && card.setting.menu) {
+      MKEmitter.emit('changeEditMenu', {MenuID: card.setting.menu})
+    }
+  }
+
+  render() {
+    const { cards } = this.props
+    const { card } = this.state
+
+    let _style = {...card.style}
+    let _backStyle = {...card.backStyle}
+
+    _backStyle.marginLeft = _style.marginLeft
+    _backStyle.marginRight = _style.marginRight
+    _backStyle.marginBottom = _style.marginBottom
+    
+    delete _style.marginBottom
+
+    _style = resetStyle(_style)
+    _backStyle = resetStyle(_backStyle)
+
+    return (
+      <Col span={card.setting.width || 24}>
+        <div className={'card-item ' + (card.setting.btnControl || '')} style={_style} onDoubleClick={(e) => {e.stopPropagation(); this.doubleClickCard()}} id={card.uuid}>
+          <div className="card-control" onDoubleClick={(e) => e.stopPropagation()}>
+            <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
+              <div className="mk-popover-control">
+                <PlusOutlined className="plus" title="娣诲姞鍏冪礌" onClick={this.addElement} />
+                <PlusSquareOutlined className="plus" title="娣诲姞鎸夐挳" onClick={this.addButton} />
+                <NormalForm title={'寰幆鍗$墖璁剧疆'} width={950} update={this.updateSetting} getForms={this.getSettingForms}>
+                  <EditOutlined className="edit" title="缂栬緫"/>
+                </NormalForm>
+                <CopyComponent type="cardcell" card={card}/>
+                <PasteController options={['action', 'customCardElement']} updateConfig={this.paste} />
+                <FontColorsOutlined className="style" title="璋冩暣鏍峰紡" onClick={this.changeStyle} />
+              </div>
+            } trigger="hover">
+              <ToolOutlined />
+            </Popover>
+          </div>
+          <CardCellComponent cards={cards} cardCell={card} side="" elements={card.elements} updateElement={this.updateCard}/>
+        </div>
+        <div className={'card-item ' + (card.setting.btnControl || '')} style={_backStyle} onDoubleClick={(e) => {e.stopPropagation(); this.doubleClickCard()}} id={card.uuid}>
+          <div className="card-control" onDoubleClick={(e) => e.stopPropagation()}>
+            <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
+              <div className="mk-popover-control">
+                <PlusOutlined className="plus" title="娣诲姞鍏冪礌" onClick={this.addElement} />
+                <PlusSquareOutlined className="plus" title="娣诲姞鎸夐挳" onClick={this.addButton} />
+                <NormalForm title={'寰幆瀛愬崱鐗囪缃�'} width={950} update={this.updateSetting} getForms={this.getSettingForms}>
+                  <EditOutlined className="edit" title="缂栬緫"/>
+                </NormalForm>
+                <CopyComponent type="cardcell" card={card}/>
+                <PasteController options={['action', 'customCardElement']} updateConfig={this.paste} />
+                <FontColorsOutlined className="style" title="璋冩暣鏍峰紡" onClick={this.changeStyle} />
+              </div>
+            } trigger="hover">
+              <ToolOutlined />
+            </Popover>
+          </div>
+          <CardCellComponent cards={cards} cardCell={card} side="" elements={card.backElements} updateElement={this.updateCard}/>
+        </div>
+      </Col>
+    )
+  }
+}
+
+export default DoubleCardBoxComponent
\ No newline at end of file
diff --git a/src/menu/components/card/doublecardcomponent/index.scss b/src/menu/components/card/doublecardcomponent/index.scss
new file mode 100644
index 0000000..a32dfaa
--- /dev/null
+++ b/src/menu/components/card/doublecardcomponent/index.scss
@@ -0,0 +1,64 @@
+.menus-detail-modal {
+  .ant-modal {
+    top: 70px;
+  }
+  .ant-modal-body {
+    min-height: 200px;
+    .menu-line {
+      display: flex;
+      div {
+        padding: 16px 16px;
+        border-top: 1px solid #e8e8e8;
+        border-left: 1px solid #e8e8e8;
+        word-break: break-all;
+      }
+      div:last-child {
+        border-right: 1px solid #e8e8e8;
+      }
+      .sort {
+        width: 10%;
+        text-align: center;
+      }
+      .sign {
+        width: 35%;
+      }
+      .name {
+        width: 35%;
+      }
+      .action {
+        width: 20%;
+        text-align: center;
+        span {
+          display: inline-block;
+          padding: 0 10px;
+          cursor: pointer;
+          color: #1890ff;
+        }
+      }
+    }
+    .menu-line:first-child {
+      background-color: #fafafa;
+    }
+    .menu-line:last-child {
+      div {
+        border-bottom: 1px solid #e8e8e8;
+      }
+    }
+  }
+}
+
+.card-control {
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  .anticon-tool {
+    position: absolute;
+    left: 1px;
+    top: 1px;
+    padding: 1px;
+    z-index: 2;
+    font-size: 16px;
+    cursor: pointer;
+    background: rgba(255, 255, 255, 0.55);
+  }
+}
diff --git a/src/menu/components/card/doublecardcomponent/options.jsx b/src/menu/components/card/doublecardcomponent/options.jsx
new file mode 100644
index 0000000..be415f4
--- /dev/null
+++ b/src/menu/components/card/doublecardcomponent/options.jsx
@@ -0,0 +1,142 @@
+/**
+ * @description Setting琛ㄥ崟閰嶇疆淇℃伅
+ */
+export default function (setting, subtype, buttons = [], cardType, columns) {
+  let appType = sessionStorage.getItem('appType')
+  let menulist = []
+
+  if (appType) {
+    menulist = sessionStorage.getItem('appMenus')
+    if (menulist) {
+      try {
+        menulist = JSON.parse(menulist)
+      } catch (e) {
+        menulist = []
+      }
+    } else {
+      menulist = []
+    }
+  } else {
+    menulist = sessionStorage.getItem('fstMenuList')
+    if (menulist) {
+      try {
+        menulist = JSON.parse(menulist)
+      } catch (e) {
+        menulist = []
+      }
+    } else {
+      menulist = []
+    }
+  }
+
+  const cardSettingForm = [
+    {
+      type: 'number',
+      field: 'width',
+      label: '鍗$墖瀹藉害',
+      initval: setting.width || 24,
+      tooltip: '鏍呮牸甯冨眬锛屾瘡琛岀瓑鍒嗕负24鍒椼��',
+      min: 1,
+      max: 24,
+      precision: 0,
+      required: true
+    },
+    {
+      type: 'radio',
+      field: 'click',
+      label: '鐐瑰嚮浜嬩欢',
+      initval: setting.click || '',
+      required: false,
+      options: [
+        {value: '', label: '鏃�'},
+        {value: 'menu', label: '鑿滃崟'},
+        {value: 'link', label: '閾炬帴'},
+        {value: 'button', label: '鎸夐挳'}
+      ],
+      controlFields: [
+        {field: 'menu', values: ['menu']},
+        {field: 'linkurl', values: ['link']},
+        {field: 'open', values: ['menu', 'link']},
+        {field: 'joint', values: ['menu', 'link']},
+        {field: 'linkbtn', values: ['button']},
+        {field: 'clickType', values: ['button']},
+      ]
+    },
+    {
+      type: appType ? 'select' : 'cascader',
+      field: 'menu',
+      label: '鍏宠仈鑿滃崟',
+      initval: setting.menu || (appType ? '' : []),
+      required: true,
+      extendName: 'MenuNo',
+      options: menulist,
+    },
+    {
+      type: 'textarea',
+      field: 'linkurl',
+      label: '閾炬帴',
+      initval: setting.linkurl || '',
+      required: true,
+      options: [],
+      span: 24
+    },
+    {
+      type: 'radio',
+      field: 'open',
+      label: '鎵撳紑鏂瑰紡',
+      initval: setting.open || 'blank',
+      required: false,
+      options: [
+        {value: 'blank', label: appType !== 'mob' ? '鏂扮獥鍙�' : '鏂伴〉闈�'},
+        {value: 'self', label: appType !== 'mob' ? '褰撳墠绐楀彛' : '褰撳墠椤甸潰'},
+      ],
+      forbid: appType !== 'pc' && appType !== 'mob'
+    },
+    {
+      type: 'radio',
+      field: 'joint',
+      label: '鍙傛暟鎷兼帴',
+      initval: setting.joint || 'true',
+      required: false,
+      options: [
+        {value: 'true', label: '鏄�'},
+        {value: 'false', label: '鍚�'},
+      ],
+    },
+    {
+      type: 'select',
+      field: 'linkbtn',
+      label: '鍏宠仈鎸夐挳',
+      initval: setting.linkbtn || '',
+      required: true,
+      options: buttons
+    },
+    {
+      type: 'radio',
+      field: 'clickType',
+      label: '瑙﹀彂鏂瑰紡',
+      initval: setting.clickType || 'normal',
+      required: false,
+      options: [
+        {value: 'normal', label: '鍗曞嚮'},
+        {value: 'multi', label: '鍙屽嚮'},
+      ],
+      forbid: appType === 'mob'
+    },
+    {
+      type: 'radio',
+      field: 'btnControl',
+      label: '鎸夐挳鎺у埗',
+      initval: setting.btnControl || 'show',
+      tooltip: '鍙缃寜閽樉绀鸿鍒欙紝涓�鐩存樉绀烘垨榧犳爣鎮诞鏃舵樉绀恒��',
+      required: false,
+      options: [
+        {value: 'show', label: '姝e父鏄剧ず'},
+        {value: 'hover', label: '鎮诞鏄剧ず'},
+      ],
+      forbid: appType === 'mob'
+    }
+  ]
+
+  return cardSettingForm
+} 
\ No newline at end of file
diff --git a/src/menu/datasource/index.jsx b/src/menu/datasource/index.jsx
index 52a1544..c40ff35 100644
--- a/src/menu/datasource/index.jsx
+++ b/src/menu/datasource/index.jsx
@@ -198,6 +198,10 @@
 
       res.setting.maxScript = maxScript
 
+      if (config.subtype !== 'dualdatacard') {
+        delete res.subColumns
+      }
+
       this.setState({loading: false, visible: false})
       this.props.updateConfig({...config, ...res})
     }, () => {
diff --git a/src/menu/datasource/verifycard/index.jsx b/src/menu/datasource/verifycard/index.jsx
index 4962810..0bf8108 100644
--- a/src/menu/datasource/verifycard/index.jsx
+++ b/src/menu/datasource/verifycard/index.jsx
@@ -32,6 +32,7 @@
 
   state = {
     columns: [],
+    subColumns: [],
     activeKey: 'setting',
     loading: false,
     initsql: '',          // sql楠岃瘉鏃跺彉閲忓0鏄庡強璧嬪��
@@ -197,6 +198,7 @@
     this.setState({
       scripts,
       columns: config.columns ? fromJS(config.columns).toJS() : [],
+      subColumns: config.subColumns ? fromJS(config.subColumns).toJS() : [],
       setting: _setting,
       median: _setting,
       searches: search,
@@ -272,6 +274,26 @@
     values.uuid = Utils.getuuid()
 
     this.setState({ columns: [...columns, values] })
+  }
+
+  subColumnChange = (values, resolve) => {
+    const { subColumns } = this.state
+
+    let fields = subColumns.map(item => item.field.toLowerCase())
+    if (fields.includes(values.field.toLowerCase())) {
+      notification.warning({
+        top: 92,
+        message: '瀛楁宸插瓨鍦紒',
+        duration: 5
+      })
+      return
+    }
+
+    resolve()
+
+    values.uuid = Utils.getuuid()
+
+    this.setState({ subColumns: [...subColumns, values] })
   }
 
   deleteScript = (record) => {
@@ -422,6 +444,23 @@
         loading: false
       })
       this.getdefaultSql()
+    } else if (activeKey === 'subcolumns') {
+      if (this.subdatasource && this.subdatasource.state.editingKey) {
+        notification.warning({
+          top: 92,
+          message: '瀛楁鏈繚瀛橈紝璇蜂繚瀛樺悗鍒囨崲锛�',
+          duration: 5
+        })
+        this.setState({
+          loading: false
+        })
+        return
+      }
+
+      this.setState({
+        activeKey: val,
+        loading: false
+      })
     } else if (activeKey === 'scripts') {
       let _loading = false
       if (this.scriptsForm && this.scriptsForm.state.editItem) {
@@ -514,7 +553,7 @@
 
   submitDataSource = () => {
     const { config, mainSearch } = this.props
-    const { activeKey, setting, columns, scripts } = this.state
+    const { activeKey, setting, columns, subColumns, scripts } = this.state
 
     return new Promise((resolve, reject) => {
       if (activeKey === 'setting') {
@@ -536,7 +575,7 @@
             defaultSearch: _search,
             setting: res
           }, () => {
-            this.sqlverify(() => { resolve({setting: res, columns, scripts }) }, reject, false)
+            this.sqlverify(() => { resolve({setting: res, columns, subColumns, scripts }) }, reject, false)
           })
         }, () => {
           reject()
@@ -551,7 +590,18 @@
           reject()
           return
         }
-        this.sqlverify(() => { resolve({setting, columns, scripts }) }, reject, false)
+        this.sqlverify(() => { resolve({setting, columns, subColumns, scripts }) }, reject, false)
+      } else if (activeKey === 'subcolumns') {
+        if (this.subdatasource && this.subdatasource.state.editingKey) {
+          notification.warning({
+            top: 92,
+            message: '瀛楁鏈繚瀛橈紝璇蜂繚瀛樺悗鎻愪氦锛�',
+            duration: 5
+          })
+          reject()
+          return
+        }
+        this.sqlverify(() => { resolve({setting, columns, subColumns, scripts }) }, reject, false)
       } else if (activeKey === 'scripts') {
         let _loading = false
         if (this.scriptsForm && this.scriptsForm.state.editItem) {
@@ -570,7 +620,7 @@
           return
         }
 
-        this.sqlverify(() => { resolve({setting, columns, scripts }) }, reject, false)
+        this.sqlverify(() => { resolve({setting, columns, subColumns, scripts }) }, reject, false)
       }
     })
   }
@@ -701,6 +751,12 @@
     })
   }
 
+  updateSubfields = (columns) => {
+    this.setState({
+      subColumns: columns
+    })
+  }
+
   copyDatasource = () => {
     const { config } = this.props
     const { columns, scripts } = this.state
@@ -823,6 +879,27 @@
     message.success('澶嶅埗鎴愬姛銆�')
   }
 
+  copySubColumns = () => {
+    const { subColumns } = this.state
+    let m = []
+    let n = []
+
+    subColumns.forEach(col => {
+      m.push(`${col.field}(${col.label})`)
+      n.push(col.field)
+    })
+
+    let oInput = document.createElement('input')
+    oInput.value = `/*${m.join(',')}*/
+      ${n.join(',')}`
+    document.body.appendChild(oInput)
+    oInput.select()
+    document.execCommand('Copy')
+    document.body.removeChild(oInput)
+
+    message.success('澶嶅埗鎴愬姛銆�')
+  }
+
   /**
    * @description 缁勪欢閿�姣侊紝娓呴櫎state鏇存柊
    */
@@ -834,7 +911,7 @@
 
   render() {
     const { config } = this.props
-    const { columns, median, setting, scripts, colColumns, scriptsColumns, activeKey, loading, searches, defaultsql, visible, pvisible, reload, script, scriptValue } = this.state
+    const { columns, subColumns, median, setting, scripts, colColumns, scriptsColumns, activeKey, loading, searches, defaultsql, visible, pvisible, reload, script, scriptValue } = this.state
 
     return (
       <div className="model-data-source-wrap">
@@ -844,8 +921,6 @@
           <TabPane tab={
             <span>
               鏁版嵁婧�
-              {config.type !== 'interface' ? <CopyOutlined title="澶嶅埗鏁版嵁婧�" className="mk-copy-datasource" onClick={this.copyDatasource}/> : null}
-              {config.type !== 'interface' ? <SnippetsOutlined title="瀵煎叆鏁版嵁婧�" className="mk-paste-datasource" onClick={() => this.setState({pvisible: true})}/> : null}
             </span>
           } key="setting">
             {!reload ? <SettingForm
@@ -861,21 +936,38 @@
             <span>
               瀛楁闆�
               {columns.length ? <span className="count-tip">{columns.length}</span> : null}
-              <CopyOutlined title="浠ラ�楀彿鎷兼帴褰㈠紡澶嶅埗瀛楁" className="mk-copy-fields" onClick={this.copyColumns}/>
             </span>
           } key="columns">
             <ColForm columnChange={this.columnChange}/>
             <FieldsComponent
-              config={{...config, columns}}
+              config={{columns}}
               type="fields"
               updatefield={this.updatefields}
             />
             <EditTable actions={['edit', 'move', 'copy', 'del', 'clear']} type="datasourcefield" wrappedComponentRef={(inst) => this.datasource = inst} data={columns} columns={colColumns} onChange={(columns) => this.setState({columns})}/>
           </TabPane> : null}
+          {config.subtype === 'dualdatacard' ? <TabPane tab={
+            <span>
+              瀛愯〃瀛楁闆�
+              {subColumns.length ? <span className="count-tip">{subColumns.length}</span> : null}
+            </span>
+          } key="subcolumns">
+            <ColForm columnChange={this.subColumnChange}/>
+            <FieldsComponent
+              config={{columns: subColumns}}
+              type="fields"
+              updatefield={this.updateSubfields}
+            />
+            <EditTable actions={['edit', 'move', 'copy', 'del', 'clear']} type="datasourcefield" wrappedComponentRef={(inst) => this.subdatasource = inst} data={subColumns} columns={colColumns} onChange={(subColumns) => this.setState({subColumns})}/>
+          </TabPane> : null}
           <TabPane tab={
             <span>
               鑷畾涔夎剼鏈�
               {scripts.length ? <span className="count-tip">{scripts.length}</span> : null}
+              {config.type !== 'interface' && activeKey === 'setting' ? <CopyOutlined title="澶嶅埗鏁版嵁婧�" className="mk-copy-datasource" onClick={(e) => {e.stopPropagation();this.copyDatasource()}}/> : null}
+              {config.type !== 'interface' && activeKey === 'setting' ? <SnippetsOutlined title="瀵煎叆鏁版嵁婧�" className="mk-paste-datasource" onClick={(e) => {e.stopPropagation();this.setState({pvisible: true})}}/> : null}
+              {activeKey === 'columns' ? <CopyOutlined title="浠ラ�楀彿鎷兼帴褰㈠紡澶嶅埗瀛楁" className="mk-copy-fields" onClick={(e) => {e.stopPropagation();this.copyColumns()}}/> : null}
+              {activeKey === 'subcolumns' ? <CopyOutlined title="浠ラ�楀彿鎷兼帴褰㈠紡澶嶅埗瀛楁" className="mk-copy-fields" onClick={(e) => {e.stopPropagation();this.copySubColumns()}}/> : null}
             </span>
           } key="scripts" disabled={median.interType !== 'system'} id="mk-scripts-tabpane">
             {scripts.length ? <BorderOutlined className="full-scripts" onClick={() => {
diff --git a/src/menu/datasource/verifycard/index.scss b/src/menu/datasource/verifycard/index.scss
index 7fcbe7e..b9bc0fe 100644
--- a/src/menu/datasource/verifycard/index.scss
+++ b/src/menu/datasource/verifycard/index.scss
@@ -14,9 +14,9 @@
       cursor: pointer;
       z-index: 1;
       top: 19px;
-      right: -210px;
+      right: -50px;
       color: rgb(24, 144, 255);
-      display: none;
+      display: inline-block;
       opacity: 0.4;
       transition: opacity 0.2s;
     }
@@ -25,9 +25,9 @@
       cursor: pointer;
       z-index: 1;
       top: 19px;
-      right: -319px;
+      right: -50px;
       color: rgb(24, 144, 255);
-      display: none;
+      display: inline-block;
       opacity: 0.4;
       transition: opacity 0.2s;
     }
@@ -36,19 +36,14 @@
       cursor: pointer;
       z-index: 1;
       top: 19px;
-      right: -355px;
+      right: -90px;
       color:purple;
-      display: none;
+      display: inline-block;
       opacity: 0.4;
       transition: opacity 0.2s;
     }
     .mk-copy-fields:hover, .mk-copy-datasource:hover, .mk-paste-datasource:hover {
       opacity: 1;
-    }
-    .ant-tabs-tab-active {
-      .mk-copy-fields, .mk-copy-datasource, .mk-paste-datasource {
-        display: inline-block;
-      }
     }
     .count-tip {
       position: absolute;
diff --git a/src/menu/datasource/verifycard/settingform/index.jsx b/src/menu/datasource/verifycard/settingform/index.jsx
index ad9a2bd..d9be1cc 100644
--- a/src/menu/datasource/verifycard/settingform/index.jsx
+++ b/src/menu/datasource/verifycard/settingform/index.jsx
@@ -460,7 +460,7 @@
               </Form.Item>
             </Col> : null}
             {/* 1銆佷笉鍒嗛〉涓斾笉瀛樺湪涓婄骇妯″潡 */}
-            {!['navbar', 'interface'].includes(config.type) && !['editable', 'basetable'].includes(config.subtype) && (!config.pageable || (config.pageable && setting.laypage === 'false')) && (setting.supModule.length === 0 || setting.supModule[0] === 'empty') && setting.interType === 'system' ? <Col span={8}>
+            {!['navbar', 'interface'].includes(config.type) && !['editable', 'basetable', 'dualdatacard'].includes(config.subtype) && (!config.pageable || (config.pageable && setting.laypage === 'false')) && (setting.supModule.length === 0 || setting.supModule[0] === 'empty') && setting.interType === 'system' ? <Col span={8}>
               <Form.Item label={
                 <Tooltip placement="topLeft" title={'鍒濆鍖栧姞杞芥椂锛屾槸鍚︿笌鍏朵粬缁勪欢涓�鍚屽姞杞芥暟鎹紝娉細浠呭湪浣跨敤绯荤粺鍑芥暟锛屼笖鍒濆鍖栧姞杞芥暟鎹椂鏈夋晥锛屽垎椤佃姹傛椂鏃犳晥銆�'}>
                   <QuestionCircleOutlined className="mk-form-tip" />
diff --git a/src/menu/menushell/card.jsx b/src/menu/menushell/card.jsx
index 5d9b196..467795d 100644
--- a/src/menu/menushell/card.jsx
+++ b/src/menu/menushell/card.jsx
@@ -11,6 +11,7 @@
 const AntvScatter = asyncComponent(() => import('@/menu/components/chart/antv-scatter'))
 const AntvTabs = asyncComponent(() => import('@/menu/components/tabs/antv-tabs'))
 const DataCard = asyncComponent(() => import('@/menu/components/card/data-card'))
+const DoubleDataCard = asyncComponent(() => import('@/menu/components/card/double-data-card'))
 const PropCard = asyncComponent(() => import('@/menu/components/card/prop-card'))
 const Balcony = asyncComponent(() => import('@/menu/components/card/balcony'))
 const NormalTree = asyncComponent(() => import('@/menu/components/tree/antd-tree'))
@@ -78,6 +79,8 @@
       return (<AntvBar card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
     } else if (card.type === 'search') {
       return (<MainSearch card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
+    } else if (card.type === 'card' && card.subtype === 'dualdatacard') {
+      return (<DoubleDataCard card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
     } else if (card.type === 'pie') {
       return (<AntvPie card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
     } else if (card.type === 'dashboard') {
diff --git a/src/menu/modulesource/option.jsx b/src/menu/modulesource/option.jsx
index 8273421..92437f0 100644
--- a/src/menu/modulesource/option.jsx
+++ b/src/menu/modulesource/option.jsx
@@ -40,6 +40,7 @@
   { 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: card2, component: 'balcony', subtype: 'balcony', title: '娴姩鍗�', width: 24},
+  // { type: 'menu', url: card1, component: 'card', subtype: 'dualdatacard', title: '鍙岄噸鏁版嵁鍗�', width: 24 },
   { type: 'menu', url: simpleform, component: 'form', subtype: 'simpleform', title: '琛ㄥ崟', width: 24, forbid: ['billPrint'] },
   { type: 'menu', url: form, component: 'form', subtype: 'stepform', title: '琛ㄥ崟锛堝垎姝ワ級', width: 24, forbid: ['billPrint'] },
   { type: 'menu', url: tabForm, component: 'form', subtype: 'tabform', title: '琛ㄥ崟锛坱ab椤碉級', width: 24, forbid: ['billPrint'] },
diff --git a/src/tabviews/custom/components/module/voucher/index.jsx b/src/tabviews/custom/components/module/voucher/index.jsx
index a670376..aae840b 100644
--- a/src/tabviews/custom/components/module/voucher/index.jsx
+++ b/src/tabviews/custom/components/module/voucher/index.jsx
@@ -82,8 +82,8 @@
       }
     }
 
-    config.wrap.type = 'checkVoucher'
-    BID = '20230214130744811P0K95RQ155KG0QIQOFV'
+    // config.wrap.type = 'checkVoucher'
+    // BID = '20230214130744811P0K95RQ155KG0QIQOFV'
 
     // config.wrap.type = 'checkTemp'
     // BID = '20230214174458780MFR8IA576ON4VKNOLVH'
diff --git a/src/tabviews/zshare/actionList/normalbutton/index.jsx b/src/tabviews/zshare/actionList/normalbutton/index.jsx
index 3a5c539..55028c5 100644
--- a/src/tabviews/zshare/actionList/normalbutton/index.jsx
+++ b/src/tabviews/zshare/actionList/normalbutton/index.jsx
@@ -2424,6 +2424,12 @@
         this.setState({
           visible: true
         })
+
+        if (btnconfig.setting.display === 'modal' && btnconfig.setting.moveable === 'true') {
+          setTimeout(() => {
+            this.setMove()
+          }, 100)
+        }
       }
     } else {
       Api.getCacheConfig({
@@ -2677,15 +2683,17 @@
         width = btnconfig.setting.width > 100 ? btnconfig.setting.width : btnconfig.setting.width + '%'
         container = () => document.getElementById(btn.ContainerId)
       }
+
       return (
         <Modal
           title={title}
           maskClosable={clickouter}
           getContainer={container}
-          wrapClassName='action-modal'
+          wrapClassName={'action-modal' + (btnconfig.setting.moveable === 'true' ? ' moveable-modal modal-' + btn.uuid : '')}
           visible={visible}
           width={width}
           onOk={this.handleOk}
+          maskStyle={btnconfig.setting.moveable === 'true' ?  {backgroundColor: 'rgba(0, 0, 0, 0.15)'} : null}
           confirmLoading={this.state.confirmLoading}
           onCancel={this.handleCancel}
           destroyOnClose
@@ -2704,6 +2712,43 @@
     }
   }
 
+  setMove = () => {
+    const { btn } = this.props
+    let wrap = document.getElementsByClassName('modal-' + btn.uuid)[0]
+
+    if (!wrap) return
+
+    let node = wrap.getElementsByClassName('ant-modal-header')[0]
+
+    if (!node) return
+
+    node.onmousedown = function(e) {
+      let orileft = 0
+      let oritop = 0
+      let oleft = e.clientX
+      let otop = e.clientY
+
+      if (node.parentNode.style.left) {
+        orileft = parseFloat(node.parentNode.style.left)
+      }
+      if (node.parentNode.style.top) {
+        oritop = parseFloat(node.parentNode.style.top)
+      }
+
+      document.onmousemove = function(e) {
+        let left = e.clientX - oleft
+        let top = e.clientY - otop
+
+        node.parentNode.style.left = (orileft + left) + 'px'
+        node.parentNode.style.top = (oritop + top) + 'px'
+      }
+    }
+
+    document.onmouseup = function() {
+      document.onmousemove = null
+    }
+  }
+
   render() {
     const { btn } = this.props
     const { loadingNumber, loadingTotal, loading, disabled, hidden, check } = this.state
diff --git a/src/tabviews/zshare/actionList/printbutton/index.jsx b/src/tabviews/zshare/actionList/printbutton/index.jsx
index 9fa8f51..e98c0f6 100644
--- a/src/tabviews/zshare/actionList/printbutton/index.jsx
+++ b/src/tabviews/zshare/actionList/printbutton/index.jsx
@@ -2013,6 +2013,12 @@
         this.setState({
           visible: true
         })
+
+        if (btnconfig.setting.display === 'modal' && btnconfig.setting.moveable === 'true') {
+          setTimeout(() => {
+            this.setMove()
+          }, 100)
+        }
       }
     } else {
       Api.getCacheConfig({
@@ -2208,10 +2214,11 @@
         title={title}
         maskClosable={clickouter}
         getContainer={container}
-        wrapClassName='action-modal'
+        wrapClassName={'action-modal' + (btnconfig.setting.moveable === 'true' ? ' moveable-modal modal-' + btn.uuid : '')}
         visible={this.state.visible}
         confirmLoading={this.state.confirmLoading}
         width={width}
+        maskStyle={btnconfig.setting.moveable === 'true' ?  {backgroundColor: 'rgba(0, 0, 0, 0.15)'} : null}
         onOk={this.handleOk}
         onCancel={this.handleCancel}
         destroyOnClose
@@ -2229,6 +2236,43 @@
     )
   }
 
+  setMove = () => {
+    const { btn } = this.props
+    let wrap = document.getElementsByClassName('modal-' + btn.uuid)[0]
+
+    if (!wrap) return
+
+    let node = wrap.getElementsByClassName('ant-modal-header')[0]
+
+    if (!node) return
+
+    node.onmousedown = function(e) {
+      let orileft = 0
+      let oritop = 0
+      let oleft = e.clientX
+      let otop = e.clientY
+
+      if (node.parentNode.style.left) {
+        orileft = parseFloat(node.parentNode.style.left)
+      }
+      if (node.parentNode.style.top) {
+        oritop = parseFloat(node.parentNode.style.top)
+      }
+
+      document.onmousemove = function(e) {
+        let left = e.clientX - oleft
+        let top = e.clientY - otop
+
+        node.parentNode.style.left = (orileft + left) + 'px'
+        node.parentNode.style.top = (oritop + top) + 'px'
+      }
+    }
+
+    document.onmouseup = function() {
+      document.onmousemove = null
+    }
+  }
+
   render() {
     const { btn } = this.props
     const { loading, disabled, hidden } = this.state
diff --git a/src/templates/modalconfig/settingform/index.jsx b/src/templates/modalconfig/settingform/index.jsx
index e3b7b22..ab2adf7 100644
--- a/src/templates/modalconfig/settingform/index.jsx
+++ b/src/templates/modalconfig/settingform/index.jsx
@@ -111,7 +111,12 @@
             </Form.Item>
           </Col> */}
           <Col span={24}>
-            <Form.Item label="鏄剧ず鏂瑰紡">
+            <Form.Item label={
+              <Tooltip placement="topLeft" title="鍙�夋嫨琛ㄥ崟鐨勬樉绀哄舰寮忥紝娉細鏍囩鎵撳嵃鎸夐挳锛屾殏涓嶆敮鎸佷互鎶藉眽鏄剧ず琛ㄥ崟銆�">
+                <QuestionCircleOutlined className="mk-form-tip" />
+                鏄剧ず鏂瑰紡
+              </Tooltip>
+            }>
               {getFieldDecorator('display', {
                 initialValue: display || 'modal'
               })(
@@ -335,6 +340,23 @@
               )}
             </Form.Item>
           </Col> : null}
+          {!appType && display === 'modal' ? <Col span={12}>
+            <Form.Item label={
+              <Tooltip placement="topLeft" title="鏄惁鍙嫋鎷界Щ鍔ㄦā鎬佹銆�">
+                <QuestionCircleOutlined className="mk-form-tip" />
+                鍙Щ鍔�
+              </Tooltip>
+            }>
+              {getFieldDecorator('moveable', {
+                initialValue: config.setting.moveable || 'false'
+              })(
+                <Radio.Group>
+                  <Radio value="false">鍚�</Radio>
+                  <Radio value="true">鏄�</Radio>
+                </Radio.Group>
+              )}
+            </Form.Item>
+          </Col> : null}
         </Row>
       </Form>
     )

--
Gitblit v1.8.0