From 2e5908bf05c200e12aa0fdfe5db5e21cbe7d014a Mon Sep 17 00:00:00 2001
From: king <18310653075@163.com>
Date: 星期三, 16 九月 2020 09:38:21 +0800
Subject: [PATCH] 2020-09-16

---
 src/menu/shellcomponent/index.jsx                          |  121 ++++++++++
 src/menu/shellcomponent/index.scss                         |   49 ++++
 src/views/login/loginform.jsx                              |   23 +
 src/menu/components/chart/antv-bar/chartcompile/index.scss |    3 
 src/menu/shellcomponent/card.jsx                           |   56 ++++
 src/menu/components/tabs/antv-tabs/index.scss              |   36 ++
 src/menu/components/chart/antv-bar/index.jsx               |   68 +----
 src/menu/components/tabs/tabcomponents/index.jsx           |  108 +++++++++
 src/menu/components/tabs/tabcomponents/index.scss          |   48 ++++
 src/menu/menushell/card.jsx                                |    6 
 src/menu/components/tabs/tabcomponents/card.jsx            |   56 ++++
 src/menu/components/tabs/antv-tabs/index.jsx               |   56 ++--
 src/menu/menushell/index.jsx                               |   35 ++
 src/views/menudesign/index.jsx                             |    7 
 14 files changed, 565 insertions(+), 107 deletions(-)

diff --git a/src/menu/components/chart/antv-bar/chartcompile/index.scss b/src/menu/components/chart/antv-bar/chartcompile/index.scss
index 0224784..231a936 100644
--- a/src/menu/components/chart/antv-bar/chartcompile/index.scss
+++ b/src/menu/components/chart/antv-bar/chartcompile/index.scss
@@ -25,6 +25,9 @@
         .ant-input-number {
           width: 100%;
         }
+        .ant-tabs-nav-wrap {
+          text-align: center;
+        }
       }
     }
   }
diff --git a/src/menu/components/chart/antv-bar/index.jsx b/src/menu/components/chart/antv-bar/index.jsx
index d6c61c0..0b0c8f3 100644
--- a/src/menu/components/chart/antv-bar/index.jsx
+++ b/src/menu/components/chart/antv-bar/index.jsx
@@ -18,7 +18,7 @@
 
 class antvBarLineChart extends Component {
   static propTpyes = {
-    config: PropTypes.object,
+    menu: PropTypes.object,
     card: PropTypes.object,
     updateConfig: PropTypes.func,
   }
@@ -29,7 +29,7 @@
   }
 
   UNSAFE_componentWillMount () {
-    const { card, config } = this.props
+    const { card, menu } = this.props
 
     if (card.isNew) {
       let _plot = {
@@ -51,27 +51,14 @@
         _plot.shape = 'hv'
       }
 
-      let name = ''
-      let names = {
-        bar: '鏌辩姸鍥�',
-        line: '鎶樼嚎鍥�',
-      }
-      let i = 1
-      
-      while (!name) {
-        let _name = names[card.type] + i
-        if (config.components.filter(com => com.setting && com.setting.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
+      if (card.floor === 1) {
+        while (!dataName) {
+          let _dataName = Utils.getdataName()
+          if (menu.components.filter(com => com.dataName === _dataName).length === 0) {
+            dataName = _dataName
+          }
         }
       }
 
@@ -84,7 +71,7 @@
         switchable: false, // 缁勪欢灞炴�� - 鏁版嵁鏄惁鍙垏鎹�
         dataName: dataName,
         subtype: card.subtype,
-        setting: {span: 12, height: 400, interType: 'system', name},
+        setting: {span: card.floor === 1 ? 12 : 24, height: 400, interType: 'system', name: card.name},
         columns: [],
         scripts: [],
         search: [],
@@ -510,33 +497,8 @@
     }
   }
 
-  plotChange = (_plot) => {
-    const { config } = this.props
-
-    if (_plot.datatype === 'statistics') {
-      _plot.Yaxis = [_plot.InfoValue]
-    }
-    
-    let _charts = fromJS(config.charts).toJS()
-
-    _charts = _charts.map(item => {
-      if (item.uuid === _plot.uuid) {
-        if (!is(fromJS(item), fromJS(_plot))) {
-          let _element = document.getElementById(_plot.uuid)
-          if (_element) {
-            _element.innerHTML = ''
-          }
-        }
-        return _plot
-      }
-      return item
-    })
-
-    this.props.plotchange({...config, charts: _charts})
-  }
-
   shouldComponentUpdate (nextProps, nextState) {
-    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
+    return !is(fromJS(this.state), fromJS(nextState))
   }
 
   updateComponent = (component) => {
@@ -564,28 +526,28 @@
 
   render() {
     const { card } = this.state
-    const { config } = this.props
+    const { menu } = this.props
 
     return (
       <div className="menu-line-chart-edit-box" style={{height: card.setting.height || 400}}>
         <SettingComponent
           config={card}
-          menu={config}
+          menu={menu}
           updateConfig={this.updateComponent}
         />
         <div className="chart-header">
           <span className="chart-title">{card.setting.title || ''}</span>
           <SearchComponent
-            menu={config}
+            menu={menu}
             config={card}
-            sysRoles={config.sysRoles}
+            sysRoles={menu.sysRoles}
             optionLibs={null}
             updatesearch={this.updateComponent}
           />
         </div>
         <ActionComponent
           type="chart"
-          menu={config}
+          menu={menu}
           config={card}
           tabs={[]}
           // setSubConfig={(_btn) => this.setSubConfig(_btn, 'button')}
diff --git a/src/menu/components/tabs/antv-tabs/index.jsx b/src/menu/components/tabs/antv-tabs/index.jsx
index 77d9ed6..d9c0ec0 100644
--- a/src/menu/components/tabs/antv-tabs/index.jsx
+++ b/src/menu/components/tabs/antv-tabs/index.jsx
@@ -12,13 +12,14 @@
 
 const SettingComponent = asyncComponent(() => import('../tabsetting'))
 const TabLabelComponent = asyncComponent(() => import('../tablabelform'))
+const TabComponents = asyncComponent(() => import('../tabcomponents'))
 
 const { TabPane } = Tabs
 const { confirm } = Modal
 
 class antvBarLineChart extends Component {
   static propTpyes = {
-    config: PropTypes.object,
+    menu: PropTypes.object,
     tabs: PropTypes.object,
     updateConfig: PropTypes.func,
   }
@@ -31,41 +32,30 @@
   }
 
   UNSAFE_componentWillMount () {
-    const { tabs, config } = this.props
+    const { tabs } = this.props
 
     if (tabs.isNew) {
-      let name = ''
-      let i = 1
-      
-      while (!name) {
-        let _name = '鏍囩椤�' + i
-        if (config.components.filter(com => com.setting && com.setting.name === _name).length === 0) {
-          name = _name
-        }
-        i++
-      }
-
       let _tabs = {
         uuid: tabs.uuid,
         type: tabs.type,
         floor: tabs.floor,
         subtype: tabs.subtype,
-        setting: {span: 12, position: 'top', tabStyle: 'line', name},
+        setting: {span: 12, position: 'top', tabStyle: 'line', name: tabs.name},
         subtabs: [{
           uuid: Utils.getuuid(),
           label: 'Tab 1',
           icon: '',
-          subcomponents: [],
+          components: [],
         }, {
           uuid: Utils.getuuid(),
           label: 'Tab 2',
           icon: '',
-          subcomponents: [],
+          components: [],
         }, {
           uuid: Utils.getuuid(),
           label: 'Tab 3',
           icon: '',
-          subcomponents: [],
+          components: [],
         }]
       }
       this.setState({
@@ -79,24 +69,30 @@
     }
   }
 
-  componentDidMount () { }
-
-  UNSAFE_componentWillReceiveProps (nextProps) {
-    if (!is(fromJS(this.props.plot), fromJS(nextProps.plot))) {
-
-    }
-  }
-
   shouldComponentUpdate (nextProps, nextState) {
-    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
+    return !is(fromJS(this.state), fromJS(nextState))
   }
 
   updateComponent = (component) => {
-    
     this.setState({
       tabs: component
     })
     this.props.updateConfig(component)
+  }
+
+  updateTabComponent = (tab) => {
+    let tabs = fromJS(this.state.tabs).toJS()
+
+    tabs.subtabs = tabs.subtabs.map(t => {
+      if (t.uuid === tab.uuid) {
+        return tab
+      } else {
+        return t
+      }
+    })
+
+    this.setState({tabs})
+    this.props.updateConfig(tabs)
   }
 
   tabAdd = (e) => {
@@ -106,7 +102,7 @@
         uuid: '',
         label: '',
         icon: '',
-        subcomponents: []
+        components: []
       },
       labelvisible: true
     })
@@ -175,6 +171,7 @@
   }
 
   render() {
+    const { menu } = this.props
     const { tabs, dict, labelvisible, editab } = this.state
 
     return (
@@ -194,7 +191,8 @@
                 <span>{tab.icon ? <Icon type={tab.icon} /> : null}{tab.label}</span>
               </Popover>
             } key={tab.uuid}>
-              Content of Tab Pane {tab.label}
+              {/* Content of Tab Pane {tab.label} */}
+              <TabComponents menu={menu} config={tab} handleList={this.updateTabComponent} deleteCard={this.deleteCard} />
             </TabPane>
           ))}
           <TabPane className="tab-add" disabled tab={<Icon onClick={this.tabAdd} type="plus" />} key="add"></TabPane>
diff --git a/src/menu/components/tabs/antv-tabs/index.scss b/src/menu/components/tabs/antv-tabs/index.scss
index 7f1b561..f6af7c1 100644
--- a/src/menu/components/tabs/antv-tabs/index.scss
+++ b/src/menu/components/tabs/antv-tabs/index.scss
@@ -46,12 +46,36 @@
     top: 0px;
   }
   .ant-tabs-card {
-    .ant-tabs-card-bar .ant-tabs-tab:last-child {
-      padding: 0px;
-      border: 0px;
-      background: transparent;
-      .anticon-plus {
-        padding: 12px 16px;
+    .ant-tabs-left-bar, .ant-tabs-right-bar {
+      .ant-tabs-tab {
+        > span {
+          padding: 0px 16px;
+        }
+        .anticon-plus {
+          padding: 0px 16px;
+        }
+      }
+      .ant-tabs-tab-active {
+        padding-left: 0px!important;
+        padding-right: 0px!important;
+      }
+    }
+    
+    .ant-tabs-card-bar {
+      .ant-tabs-tab {
+        padding: 0px;
+        > span {
+          display: inline-block;
+          padding: 0px 16px;
+        }
+      }
+      .ant-tabs-tab:last-child {
+        padding: 0px;
+        border: 0px;
+        background: transparent;
+        .anticon-plus {
+          padding: 12px 16px;
+        }
       }
     }
   }
diff --git a/src/menu/components/tabs/tabcomponents/card.jsx b/src/menu/components/tabs/tabcomponents/card.jsx
new file mode 100644
index 0000000..8bb48ec
--- /dev/null
+++ b/src/menu/components/tabs/tabcomponents/card.jsx
@@ -0,0 +1,56 @@
+import React from 'react'
+import { useDrag, useDrop } from 'react-dnd'
+import { Icon } from 'antd'
+
+import asyncComponent from '@/utils/asyncComponent'
+import './index.scss'
+
+const AntvBar = asyncComponent(() => import('@/menu/components/chart/antv-bar'))
+const AntvTabs = asyncComponent(() => import('@/menu/components/tabs/antv-tabs'))
+
+const Card = ({ id, menu, card, moveCard, findCard, delCard, hasDrop, doubleClickCard, updateConfig }) => {
+  const originalIndex = findCard(id).index
+  const [{ isDragging }, drag] = useDrag({
+    item: { type: 'menu', id, originalIndex },
+    collect: monitor => ({
+      isDragging: monitor.isDragging(),
+    }),
+  })
+  const [, drop] = useDrop({
+    accept: 'menu',
+    canDrop: () => true,
+    drop: (item) => {
+      if (!item.hasOwnProperty('originalIndex')) {
+        hasDrop(card)
+      }
+    },
+    hover({ id: draggedId }) {
+      if (!draggedId) return
+      if (draggedId !== id) {
+        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 menu={menu} card={card} updateConfig={updateConfig} />)
+    } else if (card.type === 'tabs') {
+      return (<AntvTabs menu={menu} tabs={card} updateConfig={updateConfig} />)
+    }
+  }
+
+  return (
+    <div className={'ant-col mk-component-card ant-col-' + (card.setting ? card.setting.span : 24)} ref={node => drag(drop(node))} style={style}>
+      {getCardComponent()}
+      <Icon className="remove-component" title="delete" type="delete" onClick={() => delCard(id)} />
+    </div>
+  )
+}
+export default Card
diff --git a/src/menu/components/tabs/tabcomponents/index.jsx b/src/menu/components/tabs/tabcomponents/index.jsx
new file mode 100644
index 0000000..93ab8f8
--- /dev/null
+++ b/src/menu/components/tabs/tabcomponents/index.jsx
@@ -0,0 +1,108 @@
+import React, { useState } from 'react'
+import { useDrop } from 'react-dnd'
+import { is, fromJS } from 'immutable'
+import update from 'immutability-helper'
+import { Empty, notification } from 'antd'
+
+import Utils from '@/utils/utils.js'
+import Card from './card'
+import './index.scss'
+
+const Container = ({menu, config, handleList, deleteCard, doubleClickCard }) => {
+  let target = null
+
+  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 hasDrop = (item) => {
+    target = item
+  }
+
+  const updateConfig = (element) => {
+    handleList({...config, components: cards.map(item => item.uuid === element.uuid ? element : item)})
+  }
+
+  const [, drop] = useDrop({
+    accept: 'menu',
+    drop(item) {
+      if (item.hasOwnProperty('originalIndex') || item.added) {
+        return
+      }
+      item.added = true
+
+      if (item.component === 'search') { // 鎼滅储缁勪欢涓嶅彲閲嶅娣诲姞
+        if (cards.filter(card => card.type === 'search').length > 0) {
+          notification.warning({
+            top: 92,
+            message: '鎼滅储鏉′欢涓嶅彲閲嶅娣诲姞锛�',
+            duration: 5
+          })
+          return
+        }
+      }
+      
+      let newcard = {
+        uuid: Utils.getuuid(),
+        type: item.component,
+        subtype: item.subtype,
+        floor: 2,   // 缁勪欢鐨勫眰绾�
+        isNew: true // 鏂版坊鍔犳爣蹇楋紝鐢ㄤ簬鍒濆鍖�
+      }
+      
+      let targetId = cards.length > 0 ? cards[cards.length - 1].uuid : 0
+      if (target) {
+        targetId = target.uuid
+      }
+
+      const { index: overIndex } = findCard(`${targetId}`)
+      let targetIndex = overIndex
+
+      targetIndex++
+
+      const _cards = update(cards, { $splice: [[targetIndex, 0, newcard]] })
+
+      handleList({...config, components: _cards})
+      target = null
+    }
+  })
+
+  return (
+    <div ref={drop} className="ant-row tab-shell-inner">
+      {cards.map(card => (
+        <Card
+          id={card.uuid}
+          key={card.uuid}
+          menu={menu}
+          config={config}
+          card={card}
+          moveCard={moveCard}
+          delCard={deleteCard}
+          findCard={findCard}
+          hasDrop={hasDrop}
+          updateConfig={updateConfig}
+          doubleClickCard={doubleClickCard}
+        />
+      ))}
+      {cards.length === 0 ?
+        <Empty description="璇锋坊鍔犵粍浠�" /> : null
+      }
+    </div>
+  )
+}
+export default Container
diff --git a/src/menu/components/tabs/tabcomponents/index.scss b/src/menu/components/tabs/tabcomponents/index.scss
new file mode 100644
index 0000000..05bb661
--- /dev/null
+++ b/src/menu/components/tabs/tabcomponents/index.scss
@@ -0,0 +1,48 @@
+.tab-shell-inner {
+  margin: -8px;
+
+  >.ant-col {
+    padding: 8px;
+  }
+  .anticon {
+    cursor: unset;
+  }
+
+  .mk-component-card {
+    position: relative;
+    .remove-component {
+      position: absolute;
+      right: 42px;
+      top: -16px;
+      color: #ff4d4f;
+      border-radius: 2px;
+      font-size: 16px;
+      padding: 5px;
+      cursor: pointer;
+      opacity: 0;
+      transition: opacity 0.2s;
+    }
+  }
+  .mk-component-card:hover {
+    .remove-component {
+      opacity: 1;
+    }
+    .model-datasource > .anticon-setting, .model-menu-tabs-setting > .anticon-setting {
+      opacity: 1;
+    }
+  }
+  >.ant-empty {
+    padding: 60px 0px 70px;
+  }
+
+  .model-datasource > .anticon-setting, .model-menu-tabs-setting > .anticon-setting {
+    font-size: 16px;
+    padding: 5px;
+    position: absolute;
+    right: 0px;
+    top: -30px;
+    opacity: 0;
+    cursor: pointer;
+    transition: opacity 0.2s;
+  }
+}
\ No newline at end of file
diff --git a/src/menu/menushell/card.jsx b/src/menu/menushell/card.jsx
index 3e7e882..1a4e8e7 100644
--- a/src/menu/menushell/card.jsx
+++ b/src/menu/menushell/card.jsx
@@ -8,7 +8,7 @@
 const AntvBar = asyncComponent(() => import('@/menu/components/chart/antv-bar'))
 const AntvTabs = asyncComponent(() => import('@/menu/components/tabs/antv-tabs'))
 
-const Card = ({ id, config, card, moveCard, findCard, delCard, hasDrop, doubleClickCard, updateConfig }) => {
+const Card = ({ id, menu, card, moveCard, findCard, delCard, hasDrop, doubleClickCard, updateConfig }) => {
   const originalIndex = findCard(id).index
   const [{ isDragging }, drag] = useDrag({
     item: { type: 'menu', id, originalIndex },
@@ -40,9 +40,9 @@
 
   const getCardComponent = () => {
     if (card.type === 'bar' || card.type === 'line') {
-      return (<AntvBar config={config} card={card} updateConfig={updateConfig} />)
+      return (<AntvBar menu={menu} card={card} updateConfig={updateConfig} />)
     } else if (card.type === 'tabs') {
-      return (<AntvTabs config={config} tabs={card} updateConfig={updateConfig} />)
+      return (<AntvTabs menu={menu} tabs={card} updateConfig={updateConfig} />)
     }
   }
 
diff --git a/src/menu/menushell/index.jsx b/src/menu/menushell/index.jsx
index 61edf52..81e2bbb 100644
--- a/src/menu/menushell/index.jsx
+++ b/src/menu/menushell/index.jsx
@@ -8,18 +8,18 @@
 import Card from './card'
 import './index.scss'
 
-const Container = ({config, handleList, deleteCard, doubleClickCard }) => {
+const Container = ({menu, handleList, deleteCard, doubleClickCard }) => {
   let target = null
 
-  const [cards, setCards] = useState(config.components)
+  const [cards, setCards] = useState(menu.components)
   const moveCard = (id, atIndex) => {
     const { card, index } = findCard(id)
     const _cards = update(cards, { $splice: [[index, 1], [atIndex, 0, card]] })
-    handleList({...config, components: _cards})
+    handleList({...menu, components: _cards})
   }
 
-  if (!is(fromJS(cards), fromJS(config.components))) {
-    setCards(config.components)
+  if (!is(fromJS(cards), fromJS(menu.components))) {
+    setCards(menu.components)
   }
   
   const findCard = id => {
@@ -35,16 +35,15 @@
   }
 
   const updateConfig = (element) => {
-    handleList({...config, components: cards.map(item => item.uuid === element.uuid ? element : item)})
+    handleList({...menu, components: cards.map(item => item.uuid === element.uuid ? element : item)})
   }
 
   const [, drop] = useDrop({
     accept: 'menu',
     drop(item) {
-      if (item.hasOwnProperty('originalIndex')) {
+      if (item.hasOwnProperty('originalIndex') || item.added) {
         return
       }
-
       if (item.component === 'search') { // 鎼滅储缁勪欢涓嶅彲閲嶅娣诲姞
         if (cards.filter(card => card.type === 'search').length > 0) {
           notification.warning({
@@ -54,6 +53,22 @@
           })
           return
         }
+      }
+
+      let name = ''
+      let names = {
+        bar: '鏌辩姸鍥�',
+        line: '鎶樼嚎鍥�',
+        tabs: '鏍囩缁�'
+      }
+      let i = 1
+      
+      while (!name && names[item.component]) {
+        let _name = names[item.component] + i
+        if (menu.components.filter(com => com.setting && com.setting.name === _name).length === 0) {
+          name = _name
+        }
+        i++
       }
 
       let newcard = {
@@ -76,7 +91,7 @@
 
       const _cards = update(cards, { $splice: [[targetIndex, 0, newcard]] })
 
-      handleList({...config, components: _cards})
+      handleList({...menu, components: _cards})
       target = null
     }
   })
@@ -87,7 +102,7 @@
         <Card
           id={card.uuid}
           key={card.uuid}
-          config={config}
+          menu={menu}
           card={card}
           moveCard={moveCard}
           delCard={deleteCard}
diff --git a/src/menu/shellcomponent/card.jsx b/src/menu/shellcomponent/card.jsx
new file mode 100644
index 0000000..1a4e8e7
--- /dev/null
+++ b/src/menu/shellcomponent/card.jsx
@@ -0,0 +1,56 @@
+import React from 'react'
+import { useDrag, useDrop } from 'react-dnd'
+import { Icon } from 'antd'
+
+import asyncComponent from '@/utils/asyncComponent'
+import './index.scss'
+
+const AntvBar = asyncComponent(() => import('@/menu/components/chart/antv-bar'))
+const AntvTabs = asyncComponent(() => import('@/menu/components/tabs/antv-tabs'))
+
+const Card = ({ id, menu, card, moveCard, findCard, delCard, hasDrop, doubleClickCard, updateConfig }) => {
+  const originalIndex = findCard(id).index
+  const [{ isDragging }, drag] = useDrag({
+    item: { type: 'menu', id, originalIndex },
+    collect: monitor => ({
+      isDragging: monitor.isDragging(),
+    }),
+  })
+  const [, drop] = useDrop({
+    accept: 'menu',
+    canDrop: () => true,
+    drop: (item) => {
+      if (!item.hasOwnProperty('originalIndex')) {
+        hasDrop(card)
+      }
+    },
+    hover({ id: draggedId }) {
+      if (!draggedId) return
+      if (draggedId !== id) {
+        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 menu={menu} card={card} updateConfig={updateConfig} />)
+    } else if (card.type === 'tabs') {
+      return (<AntvTabs menu={menu} tabs={card} updateConfig={updateConfig} />)
+    }
+  }
+
+  return (
+    <div className={'ant-col mk-component-card ant-col-' + (card.setting ? card.setting.span : 12)} ref={node => drag(drop(node))} style={style}>
+      {getCardComponent()}
+      <Icon className="remove-component" title="delete" type="delete" onClick={() => delCard(id)} />
+    </div>
+  )
+}
+export default Card
diff --git a/src/menu/shellcomponent/index.jsx b/src/menu/shellcomponent/index.jsx
new file mode 100644
index 0000000..81e2bbb
--- /dev/null
+++ b/src/menu/shellcomponent/index.jsx
@@ -0,0 +1,121 @@
+import React, { useState } from 'react'
+import { useDrop } from 'react-dnd'
+import { is, fromJS } from 'immutable'
+import update from 'immutability-helper'
+import { Empty, notification } from 'antd'
+
+import Utils from '@/utils/utils.js'
+import Card from './card'
+import './index.scss'
+
+const Container = ({menu, handleList, deleteCard, doubleClickCard }) => {
+  let target = null
+
+  const [cards, setCards] = useState(menu.components)
+  const moveCard = (id, atIndex) => {
+    const { card, index } = findCard(id)
+    const _cards = update(cards, { $splice: [[index, 1], [atIndex, 0, card]] })
+    handleList({...menu, components: _cards})
+  }
+
+  if (!is(fromJS(cards), fromJS(menu.components))) {
+    setCards(menu.components)
+  }
+  
+  const findCard = id => {
+    const card = cards.filter(c => `${c.uuid}` === id)[0]
+    return {
+      card,
+      index: cards.indexOf(card),
+    }
+  }
+
+  const hasDrop = (item) => {
+    target = item
+  }
+
+  const updateConfig = (element) => {
+    handleList({...menu, components: cards.map(item => item.uuid === element.uuid ? element : item)})
+  }
+
+  const [, drop] = useDrop({
+    accept: 'menu',
+    drop(item) {
+      if (item.hasOwnProperty('originalIndex') || item.added) {
+        return
+      }
+      if (item.component === 'search') { // 鎼滅储缁勪欢涓嶅彲閲嶅娣诲姞
+        if (cards.filter(card => card.type === 'search').length > 0) {
+          notification.warning({
+            top: 92,
+            message: '鎼滅储鏉′欢涓嶅彲閲嶅娣诲姞锛�',
+            duration: 5
+          })
+          return
+        }
+      }
+
+      let name = ''
+      let names = {
+        bar: '鏌辩姸鍥�',
+        line: '鎶樼嚎鍥�',
+        tabs: '鏍囩缁�'
+      }
+      let i = 1
+      
+      while (!name && names[item.component]) {
+        let _name = names[item.component] + i
+        if (menu.components.filter(com => com.setting && com.setting.name === _name).length === 0) {
+          name = _name
+        }
+        i++
+      }
+
+      let newcard = {
+        uuid: Utils.getuuid(),
+        type: item.component,
+        subtype: item.subtype,
+        floor: 1,   // 缁勪欢鐨勫眰绾�
+        isNew: true // 鏂版坊鍔犳爣蹇楋紝鐢ㄤ簬鍒濆鍖�
+      }
+      
+      let targetId = cards.length > 0 ? cards[cards.length - 1].uuid : 0
+      if (target) {
+        targetId = target.uuid
+      }
+
+      const { index: overIndex } = findCard(`${targetId}`)
+      let targetIndex = overIndex
+
+      targetIndex++
+
+      const _cards = update(cards, { $splice: [[targetIndex, 0, newcard]] })
+
+      handleList({...menu, components: _cards})
+      target = null
+    }
+  })
+
+  return (
+    <div ref={drop} className="ant-row menu-shell-inner">
+      {cards.map(card => (
+        <Card
+          id={card.uuid}
+          key={card.uuid}
+          menu={menu}
+          card={card}
+          moveCard={moveCard}
+          delCard={deleteCard}
+          findCard={findCard}
+          hasDrop={hasDrop}
+          updateConfig={updateConfig}
+          doubleClickCard={doubleClickCard}
+        />
+      ))}
+      {cards.length === 0 ?
+        <Empty description="璇锋坊鍔犵粍浠�" /> : null
+      }
+    </div>
+  )
+}
+export default Container
diff --git a/src/menu/shellcomponent/index.scss b/src/menu/shellcomponent/index.scss
new file mode 100644
index 0000000..1051e80
--- /dev/null
+++ b/src/menu/shellcomponent/index.scss
@@ -0,0 +1,49 @@
+.menu-shell-inner {
+  min-height: calc(100vh - 150px);
+  margin: -8px;
+
+  >.ant-col {
+    padding: 8px;
+  }
+  .anticon {
+    cursor: unset;
+  }
+
+  .mk-component-card {
+    position: relative;
+    .remove-component {
+      position: absolute;
+      right: 42px;
+      top: -16px;
+      color: #ff4d4f;
+      border-radius: 2px;
+      font-size: 16px;
+      padding: 5px;
+      cursor: pointer;
+      opacity: 0;
+      transition: opacity 0.2s;
+    }
+  }
+  .mk-component-card:hover {
+    .remove-component {
+      opacity: 1;
+    }
+    .model-datasource > .anticon-setting, .model-menu-tabs-setting > .anticon-setting {
+      opacity: 1;
+    }
+  }
+  >.ant-empty {
+    padding-top: 150px;
+  }
+
+  .model-datasource > .anticon-setting, .model-menu-tabs-setting > .anticon-setting {
+    font-size: 16px;
+    padding: 5px;
+    position: absolute;
+    right: 0px;
+    top: -30px;
+    opacity: 0;
+    cursor: pointer;
+    transition: opacity 0.2s;
+  }
+}
\ No newline at end of file
diff --git a/src/views/login/loginform.jsx b/src/views/login/loginform.jsx
index f7887f3..d781667 100644
--- a/src/views/login/loginform.jsx
+++ b/src/views/login/loginform.jsx
@@ -31,6 +31,7 @@
     activeKey: 'uname_pwd',
     username: '',
     password: '',
+    remember: true,
     delay: null,
     loginWays: [],
     smsId: '',
@@ -39,6 +40,12 @@
 
   UNSAFE_componentWillMount () {
     const { loginWays } = this.props
+    let remember = true
+    let _url = window.location.href.split('#')[0]
+    
+    if (localStorage.getItem(_url + 'remember') === 'false') {
+      remember = false
+    }
 
     let smsId = ''
     let _loginWays = []
@@ -54,7 +61,8 @@
     this.setState({
       smsId: smsId,
       loginWays: _loginWays,
-      activeKey: _loginWays[0].type
+      activeKey: _loginWays[0].type,
+      remember
     })
   }
 
@@ -246,6 +254,13 @@
     }
   }
 
+  rememberChange = (e) => {
+    let val = e.target.checked
+    let _url = window.location.href.split('#')[0]
+
+    localStorage.setItem(_url + 'remember', val)
+  }
+
   /**
    * @description 缁勪欢閿�姣侊紝娓呴櫎state鏇存柊
    */
@@ -257,7 +272,7 @@
 
   render() {
     const { getFieldDecorator } = this.props.form
-    const { activeKey, verdisabled, delay, loginWays } = this.state
+    const { activeKey, verdisabled, delay, loginWays, remember } = this.state
 
     return (
       <Form className={`login-form login-form-${loginWays.length}`} id="login-form" onSubmit={this.handleSubmit}>
@@ -323,8 +338,8 @@
           {activeKey === 'uname_pwd' ? <Form.Item className="minline">
             {getFieldDecorator('remember', {
               valuePropName: 'checked',
-              initialValue: true,
-            })(<Checkbox>{this.props.dict['login.remember']}</Checkbox>)}
+              initialValue: remember,
+            })(<Checkbox onChange={this.rememberChange}>{this.props.dict['login.remember']}</Checkbox>)}
           </Form.Item> : null}
           {this.props.langList && this.props.langList.length > 0 ? <Form.Item className="minline right">
             {getFieldDecorator('lang', {
diff --git a/src/views/menudesign/index.jsx b/src/views/menudesign/index.jsx
index e530f29..d3259c4 100644
--- a/src/views/menudesign/index.jsx
+++ b/src/views/menudesign/index.jsx
@@ -45,9 +45,12 @@
   }
 
   UNSAFE_componentWillMount() {
-    
     this.getMenuParam()
     // this.testFunc()
+  }
+
+  shouldComponentUpdate (nextProps, nextState) {
+    return !is(fromJS(this.state), fromJS(nextState))
   }
 
   /**
@@ -403,7 +406,7 @@
                     <Button type="primary" onClick={this.submitConfig} loading={this.state.menuloading}>{dict['mob.save']}</Button>
                   </div>
                 } style={{ width: '100%' }}>
-                  {config && config.components ? <MenuShell config={config} handleList={this.updateConfig} deleteCard={this.deleteCard} /> : null}
+                  {config && config.components ? <MenuShell menu={config} handleList={this.updateConfig} deleteCard={this.deleteCard} /> : null}
                 </Card>
               </div>
             </div>

--
Gitblit v1.8.0