From 82fe5225c6d678af65a9d7610b0ac5fb49a67f2c Mon Sep 17 00:00:00 2001
From: king <18310653075@163.com>
Date: 星期一, 04 一月 2021 11:08:19 +0800
Subject: [PATCH] 2021-01-04

---
 src/menu/components/group/tabcomponents/index.jsx           |  181 +++++++++++++
 src/menu/components/group/tabsetting/index.jsx              |   81 ++++++
 src/menu/components/group/tabsetting/settingform/index.jsx  |  134 ++++++++++
 src/menu/components/group/tabsetting/settingform/index.scss |   11 
 src/menu/components/group/tabsetting/index.scss             |    7 
 src/menu/components/tabs/tabcomponents/index.jsx            |    1 
 src/menu/modelsource/option.jsx                             |    2 
 src/menu/menushell/card.jsx                                 |   15 
 src/menu/components/group/tabcomponents/card.jsx            |   69 +++++
 src/menu/menushell/index.jsx                                |    2 
 src/menu/components/group/normal-group/index.jsx            |  172 +++++++++++++
 src/menu/components/group/normal-group/index.scss           |   32 ++
 src/menu/components/group/tabcomponents/index.scss          |   17 +
 13 files changed, 719 insertions(+), 5 deletions(-)

diff --git a/src/menu/components/group/normal-group/index.jsx b/src/menu/components/group/normal-group/index.jsx
new file mode 100644
index 0000000..0d19e48
--- /dev/null
+++ b/src/menu/components/group/normal-group/index.jsx
@@ -0,0 +1,172 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import { connect } from 'react-redux'
+import { is, fromJS } from 'immutable'
+import { Icon, Popover } from 'antd'
+
+import MKEmitter from '@/utils/events.js'
+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 './index.scss'
+
+const SettingComponent = asyncIconComponent(() => import('../tabsetting'))
+const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
+const PasteController = asyncIconComponent(() => import('@/menu/pastecontroller'))
+const TabComponents = asyncComponent(() => import('../tabcomponents'))
+
+class NormalGroup extends Component {
+  static propTpyes = {
+    group: PropTypes.object,
+    deletecomponent: PropTypes.func,
+    updateConfig: PropTypes.func,
+  }
+
+  state = {
+    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
+    group: null,
+    editab: null,
+  }
+
+  UNSAFE_componentWillMount () {
+    const { group } = this.props
+    console.log(group)
+    if (group.isNew) {
+      let _group = {
+        uuid: group.uuid,
+        type: group.type,
+        floor: group.floor,
+        tabId: group.tabId || '',
+        parentId: group.parentId || '',
+        subtype: group.subtype,
+        width: 24,
+        name: group.name,
+        setting: { width: 24, name: group.name },
+        style: { marginLeft: '8px', marginRight: '8px', marginTop: '8px', marginBottom: '8px' },
+        components: []
+      }
+      this.setState({
+        group: _group
+      })
+      this.props.updateConfig(_group)
+    } else {
+      this.setState({
+        group: fromJS(group).toJS()
+      })
+    }
+  }
+
+  shouldComponentUpdate (nextProps, nextState) {
+    return !is(fromJS(this.state), fromJS(nextState))
+  }
+
+  componentDidMount () {
+    MKEmitter.addListener('tabsChange', this.handleTabsChange)
+    MKEmitter.addListener('submitStyle', this.getStyle)
+  }
+
+  /**
+   * @description 缁勪欢閿�姣侊紝娓呴櫎state鏇存柊锛屾竻闄ゅ揩鎹烽敭璁剧疆
+   */
+  componentWillUnmount () {
+    this.setState = () => {
+      return
+    }
+    MKEmitter.removeListener('tabsChange', this.handleTabsChange)
+    MKEmitter.addListener('submitStyle', this.getStyle)
+  }
+
+  changeStyle = () => {
+    const { group } = this.state
+
+    MKEmitter.emit('changeStyle', [group.uuid], ['background', 'border', 'padding', 'margin'], group.style)
+  }
+
+  getStyle = (comIds, style) => {
+    const { group } = this.state
+
+    if (comIds.length !== 1 || comIds[0] !== group.uuid) return
+
+    let _card = {...group, style}
+
+    this.setState({
+      group: _card
+    })
+    
+    this.props.updateConfig(_card)
+  }
+
+  handleTabsChange = (parentId) => {
+    const { group } = this.state
+
+    if (parentId === group.parentId) {
+      MKEmitter.emit('tabsChange', group.uuid)
+    }
+  }
+
+  updateComponent = (component) => {
+    const { group } = this.state
+
+    if (!is(fromJS(group.setting), fromJS(component.setting))) {
+      // 娉ㄥ唽浜嬩欢-鏍囩鍙樺寲锛岄�氱煡鏍囩鍐呭厓绱�
+      MKEmitter.emit('tabsChange', group.uuid)
+    }
+
+    component.width = component.setting.width
+    component.name = component.setting.name
+
+    this.setState({
+      group: component
+    })
+    this.props.updateConfig(component)
+  }
+
+  insert = (item, cell) => {
+    let group = fromJS(this.state.group).toJS()
+
+    group.components.forEach(stab => {
+      if (stab.uuid === cell.uuid) {
+        stab.components.push(item)
+      }
+    })
+
+    this.setState({group})
+    this.props.updateConfig(group)
+  }
+
+  render() {
+    const { group } = this.state
+
+    return (
+      <div className="menu-group-edit-box" style={group.style}>
+        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
+          <div className="mk-popover-control">
+            <SettingComponent config={group} updateConfig={this.updateComponent} />
+            <CopyComponent type="tabs" card={group}/>
+            <PasteController type="tab" Tab={group} insert={this.insert} />
+            <Icon className="style" title="璋冩暣鏍峰紡" onClick={this.changeStyle} type="font-colors" />
+            <Icon className="close" title="delete" type="delete" onClick={() => this.props.deletecomponent(group.uuid)} />
+          </div>
+        } trigger="hover">
+          <Icon type="tool" />
+        </Popover>
+        <TabComponents config={group} handleList={this.updateComponent} deleteCard={this.deleteCard} />
+        
+      </div>
+    )
+  }
+}
+
+const mapStateToProps = (state) => {
+  return {
+    menu: state.customMenu
+  }
+}
+
+const mapDispatchToProps = () => {
+  return {}
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(NormalGroup)
\ No newline at end of file
diff --git a/src/menu/components/group/normal-group/index.scss b/src/menu/components/group/normal-group/index.scss
new file mode 100644
index 0000000..ff71992
--- /dev/null
+++ b/src/menu/components/group/normal-group/index.scss
@@ -0,0 +1,32 @@
+.menu-group-edit-box {
+  position: relative;
+  box-sizing: border-box;
+  background: #ffffff;
+  background-position: center center;
+  background-repeat: no-repeat;
+  background-size: cover;
+  min-height: 50px;
+  
+  .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);
+  }
+}
+.menu-group-edit-box::before {
+  content: ' ';
+  display: block;
+  float: right;
+  width: 20px;
+  height: 28px;
+  clear: right;
+}
+.menu-group-edit-box:hover {
+  z-index: 1;
+  box-shadow: 0px 0px 4px #1890ff;
+}
diff --git a/src/menu/components/group/tabcomponents/card.jsx b/src/menu/components/group/tabcomponents/card.jsx
new file mode 100644
index 0000000..ced290d
--- /dev/null
+++ b/src/menu/components/group/tabcomponents/card.jsx
@@ -0,0 +1,69 @@
+import React from 'react'
+import { useDrag, useDrop } from 'react-dnd'
+
+import asyncComponent from '@/utils/asyncComponent'
+import './index.scss'
+
+const AntvBar = asyncComponent(() => import('@/menu/components/chart/antv-bar'))
+const AntvPie = asyncComponent(() => import('@/menu/components/chart/antv-pie'))
+const DataCard = asyncComponent(() => import('@/menu/components/card/data-card'))
+const PropCard = asyncComponent(() => import('@/menu/components/card/prop-card'))
+const TableCard = asyncComponent(() => import('@/menu/components/card/table-card'))
+const NormalTable = asyncComponent(() => import('@/menu/components/table/normal-table'))
+
+const Card = ({ id, card, moveCard, findCard, delCard, updateConfig }) => {
+  const originalIndex = findCard(id).index
+  const [{ isDragging }, drag] = useDrag({
+    item: { type: 'menu', id, originalIndex, floor: card.floor },
+    collect: monitor => ({
+      isDragging: monitor.isDragging(),
+    }),
+  })
+  const [, drop] = useDrop({
+    accept: 'menu',
+    canDrop: () => true,
+    drop: (item) => {
+      const { id: draggedId, originalIndex, floor } = item
+      if (originalIndex === undefined) {
+        item.dropTargetId = id
+      } else if (draggedId && floor === card.floor) {
+        if (draggedId === id) return
+        const { index: originIndex } = findCard(draggedId)
+
+        if (originIndex === -1) return
+
+        const { index: overIndex } = findCard(id)
+
+        moveCard(draggedId, overIndex)
+      }
+    }
+  })
+
+  let style = { opacity: 1}
+  if (isDragging) {
+    style = { opacity: 0.3}
+  }
+
+  const getCardComponent = () => {
+    if (card.type === 'bar' || card.type === 'line') {
+      return (<AntvBar card={card} updateConfig={updateConfig} deletecomponent={delCard} />)
+    } else if (card.type === 'pie') {
+      return (<AntvPie card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
+    } else if (card.type === 'card' && card.subtype === 'datacard') {
+      return (<DataCard card={card} updateConfig={updateConfig} deletecomponent={delCard} />)
+    } else if (card.type === 'card' && card.subtype === 'propcard') {
+      return (<PropCard card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
+    } else if (card.type === 'table' && card.subtype === 'tablecard') {
+      return (<TableCard card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
+    } else if (card.type === 'table' && card.subtype === 'normaltable') {
+      return (<NormalTable card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
+    }
+  }
+
+  return (
+    <div className={'ant-col mk-component-card ant-col-' + (card.width || 24)} ref={node => drag(drop(node))} style={style}>
+      {getCardComponent()}
+    </div>
+  )
+}
+export default Card
diff --git a/src/menu/components/group/tabcomponents/index.jsx b/src/menu/components/group/tabcomponents/index.jsx
new file mode 100644
index 0000000..8b8c545
--- /dev/null
+++ b/src/menu/components/group/tabcomponents/index.jsx
@@ -0,0 +1,181 @@
+import React, { useState } from 'react'
+import { useDrop } from 'react-dnd'
+import { is, fromJS } from 'immutable'
+import update from 'immutability-helper'
+import { Empty, Modal } from 'antd'
+
+import Utils from '@/utils/utils.js'
+import MKEmitter from '@/utils/events.js'
+import Card from './card'
+import './index.scss'
+
+const { confirm } = Modal
+
+const Container = ({ config, handleList }) => {
+  const [cards, setCards] = useState(config.components)
+  const moveCard = (id, atIndex) => {
+    const { card, index } = findCard(id)
+    const _cards = update(cards, { $splice: [[index, 1], [atIndex, 0, card]] })
+    handleList({...config, components: _cards})
+  }
+
+  if (!is(fromJS(cards), fromJS(config.components))) {
+    setCards(config.components)
+  }
+  
+  const findCard = id => {
+    const card = cards.filter(c => `${c.uuid}` === id)[0]
+    return {
+      card,
+      index: cards.indexOf(card),
+    }
+  }
+
+  const updateConfig = (element) => {
+    handleList({...config, components: cards.map(item => item.uuid === element.uuid ? element : item)})
+  }
+
+  const deleteCard = (id) => {
+    const { card } = findCard(id)
+
+    let uuids = []
+    if (card.action && card.action.length) {
+      card.action.forEach(act => {
+        if (!act.origin) {
+          uuids.push(act.uuid)
+        }
+      })
+    }
+    if (card.type === 'card') {
+      card.subcards.forEach(_card => {
+        _card.elements && _card.elements.forEach(cell => {
+          if (cell.eleType === 'button') {
+            uuids.push(cell.uuid)
+          }
+        })
+        _card.backElements && _card.backElements.forEach(cell => {
+          if (cell.eleType === 'button') {
+            uuids.push(cell.uuid)
+          }
+        })
+      })
+    } else if (card.type === 'table' && card.subtype === 'tablecard') {
+      card.subcards.forEach(_card => {
+        _card.elements && _card.elements.forEach(cell => {
+          if (cell.eleType === 'button') {
+            uuids.push(cell.uuid)
+          }
+        })
+      })
+    } else if (card.type === 'table' && card.subtype === 'normaltable') {
+      card.cols && card.cols.forEach(col => {
+        if (col.type !== 'action') return
+        col.elements && col.elements.forEach(cell => {
+          uuids.push(cell.uuid)
+        })
+      })
+    }
+
+    confirm({
+      title: `纭畾鍒犻櫎銆�${card.name}銆嬪悧锛焋,
+      onOk() {
+        MKEmitter.emit('delButtons', uuids)
+        handleList({...config, components: cards.filter(item => item.uuid !== card.uuid)})
+      },
+      onCancel() {}
+    })
+  }
+
+  const [, drop] = useDrop({
+    accept: 'menu',
+    drop(item) {
+      if (item.hasOwnProperty('originalIndex') || item.added) {
+        return
+      } else if (item.component === 'tabs' || item.component === 'search' || item.component === 'group') { // 鍒嗙粍涓笉鍙坊鍔犳爣绛鹃〉鎴栨悳绱�
+        return
+      }
+
+      item.added = true
+
+      let name = ''
+      let names = {
+        bar: '鏌辩姸鍥�',
+        line: '鎶樼嚎鍥�',
+        pie: '楗煎浘',
+        table: '琛ㄦ牸',
+        card: '鍗$墖'
+      }
+      let i = 1
+      
+      while (!name && names[item.component]) {
+        let _name = names[item.component] + i
+        if (config.components.filter(com => com.name === _name).length === 0) {
+          name = _name
+        }
+        i++
+      }
+
+      let dataName = ''
+      while (!dataName) {
+        let _dataName = Utils.getdataName()
+        if (config.components.filter(com => com.dataName === _dataName).length === 0) {
+          dataName = _dataName
+        }
+      }
+      
+      let newcard = {
+        uuid: Utils.getuuid(),
+        tabId: config.uuid,
+        parentId: config.parentId,
+        type: item.component,
+        subtype: item.subtype,
+        config: item.config,
+        width: item.width || 24,
+        dataName: dataName,
+        name: name,
+        floor: config.floor || 1, // 缁勪欢鐨勫眰绾�
+        isNew: true               // 鏂版坊鍔犳爣蹇楋紝鐢ㄤ簬鍒濆鍖�
+      }
+      console.log(newcard)
+      let targetId = ''
+
+      if (item.dropTargetId) {
+        targetId = item.dropTargetId
+        delete item.dropTargetId
+      } else if (cards.length > 0) {
+        targetId = cards.slice(-1)[0].uuid
+      }
+
+      const { index: overIndex } = findCard(`${targetId}`)
+      const _cards = update(cards, { $splice: [[overIndex + 1, 0, newcard]] })
+
+      handleList({...config, components: _cards})
+    }
+  })
+
+  return (
+    <div ref={drop} className="ant-row group-shell-inner">
+      {cards.map(card => (
+        <Card
+          id={card.uuid}
+          key={card.uuid}
+          config={config}
+          card={card}
+          moveCard={moveCard}
+          delCard={deleteCard}
+          findCard={findCard}
+          updateConfig={updateConfig}
+        />
+      ))}
+      {cards.length === 0 ?
+        <Empty description={
+          <span>
+            璇锋坊鍔犵粍浠躲��
+            <span style={{display: 'block', fontSize: '12px', color: '#959595'}}>娉細鏍囩椤点�佸垎缁勩�佹悳绱笉鍙坊鍔�</span>
+          </span>
+        } /> : null
+      }
+    </div>
+  )
+}
+export default Container
diff --git a/src/menu/components/group/tabcomponents/index.scss b/src/menu/components/group/tabcomponents/index.scss
new file mode 100644
index 0000000..ad63183
--- /dev/null
+++ b/src/menu/components/group/tabcomponents/index.scss
@@ -0,0 +1,17 @@
+.group-shell-inner {
+  margin: -8px;
+
+  >.ant-col {
+    padding: 8px;
+  }
+  .anticon {
+    cursor: unset;
+  }
+
+  .mk-component-card {
+    position: relative;
+  }
+  >.ant-empty {
+    padding: 10px 0px;
+  }
+}
\ No newline at end of file
diff --git a/src/menu/components/group/tabsetting/index.jsx b/src/menu/components/group/tabsetting/index.jsx
new file mode 100644
index 0000000..b913fc8
--- /dev/null
+++ b/src/menu/components/group/tabsetting/index.jsx
@@ -0,0 +1,81 @@
+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: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
+    visible: false,
+    setting: null
+  }
+
+  UNSAFE_componentWillMount () {
+    const { config } = this.props
+
+    this.setState({setting: fromJS(config.setting).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({
+        setting: res,
+        visible: false
+      })
+      this.props.updateConfig({...config, setting: res})
+    })
+  }
+
+  render () {
+    const { visible, dict, setting } = this.state
+
+    return (
+      <div className="model-menu-setting-wrap">
+        <Icon type="edit" onClick={() => this.editDataSource()} />
+        <Modal
+          wrapClassName="popview-modal"
+          title={'鏍囩椤甸厤缃�'}
+          visible={visible}
+          width={700}
+          maskClosable={false}
+          okText={dict['model.submit']}
+          onOk={this.verifySubmit}
+          onCancel={() => { this.setState({ visible: false }) }}
+          destroyOnClose
+        >
+          <SettingForm
+            dict={dict}
+            setting={setting}
+            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/group/tabsetting/index.scss b/src/menu/components/group/tabsetting/index.scss
new file mode 100644
index 0000000..04372e6
--- /dev/null
+++ b/src/menu/components/group/tabsetting/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/group/tabsetting/settingform/index.jsx b/src/menu/components/group/tabsetting/settingform/index.jsx
new file mode 100644
index 0000000..73da976
--- /dev/null
+++ b/src/menu/components/group/tabsetting/settingform/index.jsx
@@ -0,0 +1,134 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import { Form, Row, Col, Input, Tooltip, Icon, InputNumber, Select } from 'antd'
+
+import './index.scss'
+
+class SettingForm extends Component {
+  static propTpyes = {
+    dict: PropTypes.object,       // 瀛楀吀椤�
+    setting: 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 { setting } = 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={
+                <Tooltip placement="topLeft" title="鐢ㄤ簬缁勪欢闂寸殑鍖哄垎銆�">
+                  <Icon type="question-circle" />
+                  缁勪欢鍚嶇О
+                </Tooltip>
+              }>
+                {getFieldDecorator('name', {
+                  initialValue: setting.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: setting.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>
+            <Col span={12}>
+              <Form.Item label="榛戝悕鍗�">
+                {getFieldDecorator('blacklist', {
+                  initialValue: setting.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/group/tabsetting/settingform/index.scss b/src/menu/components/group/tabsetting/settingform/index.scss
new file mode 100644
index 0000000..159130b
--- /dev/null
+++ b/src/menu/components/group/tabsetting/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/tabs/tabcomponents/index.jsx b/src/menu/components/tabs/tabcomponents/index.jsx
index 44ceaaf..4891671 100644
--- a/src/menu/components/tabs/tabcomponents/index.jsx
+++ b/src/menu/components/tabs/tabcomponents/index.jsx
@@ -142,6 +142,7 @@
         pie: '楗煎浘',
         search: '鎼滅储',
         table: '琛ㄦ牸',
+        group: '鍒嗙粍',
         card: '鍗$墖'
       }
       let i = 1
diff --git a/src/menu/menushell/card.jsx b/src/menu/menushell/card.jsx
index 15108b4..3d365e8 100644
--- a/src/menu/menushell/card.jsx
+++ b/src/menu/menushell/card.jsx
@@ -12,6 +12,7 @@
 const PropCard = asyncComponent(() => import('@/menu/components/card/prop-card'))
 const TableCard = asyncComponent(() => import('@/menu/components/card/table-card'))
 const NormalTable = asyncComponent(() => import('@/menu/components/table/normal-table'))
+const NormalGroup = asyncComponent(() => import('@/menu/components/group/normal-group'))
 
 const Card = ({ id, card, moveCard, findCard, delCard, updateConfig }) => {
   const originalIndex = findCard(id).index
@@ -29,10 +30,14 @@
       if (originalIndex === undefined) {
         item.dropTargetId = id
       } else if (draggedId && floor === card.floor) {
-        if (draggedId !== id) {
-          const { index: overIndex } = findCard(id)
-          moveCard(draggedId, overIndex)
-        }
+        if (draggedId === id) return
+        const { index: originIndex } = findCard(draggedId)
+
+        if (originIndex === -1) return
+
+        const { index: overIndex } = findCard(id)
+
+        moveCard(draggedId, overIndex)
       }
     }
   })
@@ -59,6 +64,8 @@
       return (<TableCard card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
     } else if (card.type === 'table' && card.subtype === 'normaltable') {
       return (<NormalTable card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
+    } else if (card.type === 'group' && card.subtype === 'normalgroup') {
+      return (<NormalGroup group={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
     }
   }
   return (
diff --git a/src/menu/menushell/index.jsx b/src/menu/menushell/index.jsx
index 36a5852..c572d6e 100644
--- a/src/menu/menushell/index.jsx
+++ b/src/menu/menushell/index.jsx
@@ -115,6 +115,7 @@
         delete item.added // 鍒犻櫎缁勪欢娣诲姞鏍囪
         return
       }
+      console.log(item)
       if (item.component === 'search') { // 鎼滅储缁勪欢涓嶅彲閲嶅娣诲姞
         if (cards.filter(card => card.type === 'search').length > 0) {
           notification.warning({
@@ -134,6 +135,7 @@
         pie: '楗煎浘',
         search: '鎼滅储',
         table: '琛ㄦ牸',
+        group: '鍒嗙粍',
         card: '鍗$墖'
       }
       let i = 1
diff --git a/src/menu/modelsource/option.jsx b/src/menu/modelsource/option.jsx
index 6a54b69..55940f9 100644
--- a/src/menu/modelsource/option.jsx
+++ b/src/menu/modelsource/option.jsx
@@ -16,7 +16,7 @@
 // 缁勪欢閰嶇疆淇℃伅
 export const menuOptions = [
   { type: 'menu', url: tabs, component: 'tabs', subtype: 'tabs', title: '鏍囩椤�', width: 24, forbid: ['billPrint'] },
-  { type: 'menu', url: group, component: 'group', subtype: 'group', title: '鍒嗙粍', width: 24, forbid: ['billPrint'] },
+  { type: 'menu', url: group, component: 'group', subtype: 'normalgroup', title: '鍒嗙粍', width: 24, forbid: ['billPrint'] },
   { type: 'menu', url: Mainsearch, component: 'search', subtype: 'mainsearch', title: '鎼滅储鏉′欢', width: 24, forbid: ['billPrint'] },
   { type: 'menu', url: card1, component: 'card', subtype: 'datacard', title: '鏁版嵁鍗�', config: `[{"uuid":"160135809128212dm7i29fim9ksto9od","setting":{"width":6},"style":{"paddingTop":"15px","marginTop":"4px","paddingRight":"15px","marginRight":"8px","marginLeft":"8px","backgroundColor":"rgba(255, 255, 255, 1)","borderColor":"#e8e8e8","paddingLeft":"15px","marginBottom":"4px","borderWidth":"1px","paddingBottom":"10px"},"backStyle":{},"elements":[{"datatype":"static","width":12,"marks":null,"height":1,"value":"鍏冲崟","style":{},"prefix":"","postfix":"","format":"","eleType":"text","uuid":"160231860159931untbea62sgokunc5s"},{"datatype":"static","width":12,"marks":null,"style":{"color":"rgba(250, 219, 20, 1)","textAlign":"right"},"btnstyle":{},"eleType":"icon","icon":"question-circle","field":"","uuid":"1602318768361nv8ql4t47sgcsn88b0u"},{"datatype":"static","width":24,"marks":null,"height":1,"innerHeight":36,"value":"100","style":{"fontSize":"24px","fontWeight":"500","color":"rgba(0, 0, 0, 1)"},"prefix":"","btnstyle":{},"postfix":"","format":"","eleType":"text","uuid":"1602318817884v70gtgb65ubnm8mbcvv"},{"color":"#1890ff","width":24,"marks":null,"maxValue":100,"style":{"color":"rgba(250, 140, 22, 1)","paddingTop":"20px","paddingBottom":"10px"},"btnstyle":{},"eleType":"slider","field":"int1","uuid":"16023188871233rkktuvpp1h077igrsu"},{"eleType":"splitline","width":24,"color":"#e8e8e8","uuid":"1602320017038n31bk9o831ggug0tu0b","marks":null,"style":{"marginTop":"10px","marginBottom":"10px"},"btnstyle":{}},{"datatype":"static","width":12,"marks":null,"height":1,"value":"100","style":{"marginTop":"6px"},"prefix":"鍏冲崟","btnstyle":{},"postfix":"","format":"","eleType":"text","uuid":"1602320061243drd7lf3agvn04kgr175"}],"backElements":[]}]` },
   { type: 'menu', url: card2, component: 'card', subtype: 'propcard', title: '灞炴�у崱', config: `[{"uuid":"1603681387259qaqf1127f72esmtchge","setting":{"width":6,"type":"simple"},"style":{"paddingTop":"15px","marginTop":"8px","paddingRight":"15px","marginRight":"8px","marginLeft":"8px","borderColor":"#e8e8e8","paddingLeft":"15px","marginBottom":"8px","borderWidth":"1px","paddingBottom":"15px"},"backStyle":{},"elements":[{"datatype":"static","width":12,"marks":null,"height":1,"value":"瓒呮椂宸ュ崟","style":{"color":"rgba(67, 67, 67, 0.51)"},"prefix":"","postfix":"","format":"","eleType":"text","uuid":"1603681402945qnkgm7q8cng65evn5ev"},{"eleType":"icon","datatype":"static","width":12,"icon":"question-circle","tooltip":"瓒呮椂宸ュ崟","uuid":"1603681473384i2crkbtofg4pu76k06a","marks":null,"style":{"textAlign":"right","color":"rgba(250, 219, 20, 1)"}},{"datatype":"static","width":24,"marks":null,"height":1,"innerHeight":36,"value":"100","style":{"fontSize":"24px","color":"rgba(0, 0, 0, 1)"},"prefix":"","postfix":"","format":"","eleType":"number","uuid":"1603681539870d704ufqf98kc6t7537t"},{"color":"rgba(250, 219, 20, 1)","datatype":"static","width":24,"marks":null,"maxValue":100,"value":50,"style":{"paddingTop":"10px","paddingBottom":"10px"},"eleType":"slider","uuid":"1603683067556mvupau0odvrtv45u7o8"},{"eleType":"splitline","width":24,"color":"#e8e8e8","uuid":"1603683117981t9k55k8an430fuppmci","marks":null,"style":{"paddingTop":"5px","paddingBottom":"5px"}},{"datatype":"static","width":12,"marks":null,"height":1,"value":"100","style":{"color":"rgba(0, 0, 0, 0.65)","marginTop":"10px"},"prefix":"瓒呮椂宸ュ崟  ","postfix":"","format":"","eleType":"text","uuid":"1603683136553uvsmkfohkft9idbfkhu"}],"backElements":[]}]` },

--
Gitblit v1.8.0