From 9353bc84d8c65da2130db48a2fe48dd714b0acb9 Mon Sep 17 00:00:00 2001
From: king <18310653075@163.com>
Date: 星期日, 23 五月 2021 00:05:18 +0800
Subject: [PATCH] 2021-05-23

---
 src/tabviews/zshare/imgScale/index.scss                               |   20 
 src/menu/components/tree/antd-tree/index.scss                         |   36 +
 src/menu/components/group/groupcomponents/index.jsx                   |    9 
 src/tabviews/custom/components/tree/antd-tree/index.jsx               |  483 ++++++++++++++++++++
 src/tabviews/zshare/normalTable/index.jsx                             |   42 
 src/menu/components/form/normal-form/groupform/index.jsx              |   19 
 src/menu/components/tree/antd-tree/wrapsetting/settingform/index.jsx  |  262 ++++++++++
 src/menu/components/form/normal-form/index.jsx                        |    6 
 src/pc/menushell/index.jsx                                            |    1 
 src/templates/treepageconfig/index.jsx                                |    3 
 src/menu/components/tree/antd-tree/wrapsetting/index.scss             |    7 
 src/menu/components/tabs/tabcomponents/index.jsx                      |    5 
 src/mob/components/formdragelement/index.scss                         |    3 
 src/menu/components/tree/antd-tree/index.jsx                          |  188 +++++++
 src/menu/menushell/card.jsx                                           |    3 
 src/tabviews/custom/components/tree/antd-tree/index.scss              |   93 +++
 src/assets/mobimg/tree.png                                            |    0 
 src/views/menudesign/index.jsx                                        |    2 
 src/menu/modulesource/option.jsx                                      |    2 
 src/tabviews/custom/components/card/data-card/index.jsx               |    2 
 src/menu/components/share/normalheader/index.scss                     |   11 
 src/menu/components/tree/antd-tree/wrapsetting/settingform/index.scss |   15 
 src/menu/components/tree/antd-tree/wrapsetting/index.jsx              |   83 +++
 src/views/pcdesign/index.jsx                                          |    2 
 src/menu/components/share/normalheader/index.jsx                      |    1 
 src/templates/zshare/formconfig.jsx                                   |   19 
 src/tabviews/custom/index.jsx                                         |    7 
 src/menu/menushell/index.jsx                                          |    1 
 src/tabviews/zshare/imgScale/index.jsx                                |   65 ++
 29 files changed, 1,357 insertions(+), 33 deletions(-)

diff --git a/src/assets/mobimg/tree.png b/src/assets/mobimg/tree.png
new file mode 100644
index 0000000..b26f993
--- /dev/null
+++ b/src/assets/mobimg/tree.png
Binary files differ
diff --git a/src/menu/components/form/normal-form/groupform/index.jsx b/src/menu/components/form/normal-form/groupform/index.jsx
index 71648f4..488cd6c 100644
--- a/src/menu/components/form/normal-form/groupform/index.jsx
+++ b/src/menu/components/form/normal-form/groupform/index.jsx
@@ -18,13 +18,22 @@
 
   UNSAFE_componentWillMount () {
     const { group } = this.props
+    const { appType } = this.state
     let fields = []
 
-    group.fields.forEach(f => {
-      if (f.field && ['select', 'link', 'text', 'number'].includes(f.type) && f.hidden !== 'true' && f.readonly !== 'true') {
-        fields.push(f)
-      }
-    })
+    if (appType === 'mob') {
+      group.fields.forEach(f => {
+        if (f.field && ['text', 'number'].includes(f.type) && f.hidden !== 'true' && f.readonly !== 'true') {
+          fields.push(f)
+        }
+      })
+    } else {
+      group.fields.forEach(f => {
+        if (f.field && ['select', 'link', 'text', 'number'].includes(f.type) && f.hidden !== 'true' && f.readonly !== 'true') {
+          fields.push(f)
+        }
+      })
+    }
 
     this.setState({
       fields: fields
diff --git a/src/menu/components/form/normal-form/index.jsx b/src/menu/components/form/normal-form/index.jsx
index c8b417c..5c7f303 100644
--- a/src/menu/components/form/normal-form/index.jsx
+++ b/src/menu/components/form/normal-form/index.jsx
@@ -463,7 +463,11 @@
     let standardform = null
 
     _inputfields = group.fields.filter(item => item.type === 'text' || item.type === 'number' || item.type === 'textarea' || item.type === 'color')
-    _tabfields = group.fields.filter(item => _form.field !== item.field && item.hidden !== 'true' && ['text', 'number', 'select', 'link'].includes(item.type))
+    if (appType === 'mob') {
+      _tabfields = group.fields.filter(item => _form.field !== item.field && item.hidden !== 'true' && ['text', 'number'].includes(item.type))
+    } else {
+      _tabfields = group.fields.filter(item => _form.field !== item.field && item.hidden !== 'true' && ['text', 'number', 'select', 'link'].includes(item.type))
+    }
     _tabfields.unshift({field: '', text: '鍘熻〃鍗�'})
 
     let uniq = new Map()
diff --git a/src/menu/components/group/groupcomponents/index.jsx b/src/menu/components/group/groupcomponents/index.jsx
index acffe01..b35112f 100644
--- a/src/menu/components/group/groupcomponents/index.jsx
+++ b/src/menu/components/group/groupcomponents/index.jsx
@@ -99,13 +99,20 @@
 
       let name = ''
       let names = {
-        bar: '鏌辩姸鍥�',
+        bbar: '鏌辩姸鍥�',
         line: '鎶樼嚎鍥�',
+        tabs: '鏍囩缁�',
         pie: '楗煎浘',
+        search: '鎼滅储',
         table: '琛ㄦ牸',
+        group: '鍒嗙粍',
         editor: '瀵屾枃鏈�',
+        code: '鑷畾涔�',
+        carousel: '杞挱',
+        form: '琛ㄥ崟',
         dashboard: '浠〃鐩�',
         scatter: '鏁g偣鍥�',
+        tree: '鏍戝舰鍒楄〃',
         card: '鍗$墖'
       }
       let i = 1
diff --git a/src/menu/components/share/normalheader/index.jsx b/src/menu/components/share/normalheader/index.jsx
index adac673..bfcc29b 100644
--- a/src/menu/components/share/normalheader/index.jsx
+++ b/src/menu/components/share/normalheader/index.jsx
@@ -73,6 +73,7 @@
         } trigger="hover">
           <span className="title">{title}</span>
         </Popover>
+        {config.wrap && config.wrap.searchable === 'true' ? <span className="ant-input-search ant-input-affix-wrapper"><span className="ant-input-suffix"><Icon type="search" /></span></span> : null}
         {hideSearch !== 'true' && config.search ? <SearchComponent config={config} updatesearch={this.props.updateComponent}/> : null}
       </div>
     )
diff --git a/src/menu/components/share/normalheader/index.scss b/src/menu/components/share/normalheader/index.scss
index ceac790..ca438ed 100644
--- a/src/menu/components/share/normalheader/index.scss
+++ b/src/menu/components/share/normalheader/index.scss
@@ -16,6 +16,17 @@
     min-height: 45px;
     min-width: 30px;
   }
+  .ant-input-search.ant-input-affix-wrapper {
+    width: calc(100% - 140px);
+    max-width: 130px;
+    margin-top: 8px;
+    margin-right: 25px;
+    float: right;
+    height: 28px;
+    border-radius: 20px;
+    border: 1px solid #d9d9d9;
+    opacity: 0.6;
+  }
 }
 .normal-header.hidden {
   display: none;
diff --git a/src/menu/components/tabs/tabcomponents/index.jsx b/src/menu/components/tabs/tabcomponents/index.jsx
index 816547b..f25da70 100644
--- a/src/menu/components/tabs/tabcomponents/index.jsx
+++ b/src/menu/components/tabs/tabcomponents/index.jsx
@@ -89,7 +89,7 @@
 
       let name = ''
       let names = {
-        bar: '鏌辩姸鍥�',
+        bbar: '鏌辩姸鍥�',
         line: '鎶樼嚎鍥�',
         tabs: '鏍囩缁�',
         pie: '楗煎浘',
@@ -97,9 +97,12 @@
         table: '琛ㄦ牸',
         group: '鍒嗙粍',
         editor: '瀵屾枃鏈�',
+        code: '鑷畾涔�',
         carousel: '杞挱',
+        form: '琛ㄥ崟',
         dashboard: '浠〃鐩�',
         scatter: '鏁g偣鍥�',
+        tree: '鏍戝舰鍒楄〃',
         card: '鍗$墖'
       }
       let i = 1
diff --git a/src/menu/components/tree/antd-tree/index.jsx b/src/menu/components/tree/antd-tree/index.jsx
new file mode 100644
index 0000000..18f83e7
--- /dev/null
+++ b/src/menu/components/tree/antd-tree/index.jsx
@@ -0,0 +1,188 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import { is, fromJS } from 'immutable'
+import { Icon, Popover, Tree } from 'antd'
+
+import asyncComponent from '@/utils/asyncComponent'
+import asyncIconComponent from '@/utils/asyncIconComponent'
+import { resetStyle } from '@/utils/utils-custom.js'
+import MKEmitter from '@/utils/events.js'
+import zhCN from '@/locales/zh-CN/model.js'
+import enUS from '@/locales/en-US/model.js'
+
+import './index.scss'
+
+const SettingComponent = asyncIconComponent(() => import('@/menu/datasource'))
+const NormalHeader = asyncComponent(() => import('@/menu/components/share/normalheader'))
+const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
+const UserComponent = asyncIconComponent(() => import('@/menu/components/share/usercomponent'))
+const ClockComponent = asyncIconComponent(() => import('@/menu/components/share/clockcomponent'))
+const WrapComponent = asyncIconComponent(() => import('./wrapsetting'))
+
+const { TreeNode } = Tree
+
+class AntdTree extends Component {
+  static propTpyes = {
+    card: PropTypes.object,
+    deletecomponent: PropTypes.func,
+    updateConfig: PropTypes.func,
+  }
+
+  state = {
+    dict: sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
+    card: null,
+    back: false
+  }
+
+  UNSAFE_componentWillMount () {
+    const { card } = this.props
+
+    if (card.isNew) {
+      let _card = {
+        uuid: card.uuid,
+        type: card.type,
+        floor: card.floor,
+        tabId: card.tabId || '',
+        parentId: card.parentId || '',
+        dataName: card.dataName || '',
+        format: 'array',    // 缁勪欢灞炴�� - 鏁版嵁鏍煎紡
+        pageable: false,    // 缁勪欢灞炴�� - 鏄惁鍙垎椤�
+        switchable: true,   // 缁勪欢灞炴�� - 鏁版嵁鏄惁鍙垏鎹�
+        width: card.width || 12,
+        name: card.name,
+        subtype: card.subtype,
+        setting: { interType: 'system' },
+        wrap: { name: card.name, title: '', width: card.width || 12, showIcon: 'false', showLine: 'false', searchable: 'false' },
+        style: { marginLeft: '8px', marginRight: '8px', marginTop: '8px', marginBottom: '8px' },
+        headerStyle: { fontSize: '16px', borderBottomWidth: '1px', borderBottomColor: '#e8e8e8' },
+        columns: [],
+        scripts: [],
+      }
+
+      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
+      }
+      
+      this.setState({
+        card: _card
+      })
+      this.props.updateConfig(_card)
+    } else {
+      this.setState({
+        card: fromJS(card).toJS()
+      })
+    }
+  }
+
+  componentDidMount () {
+    MKEmitter.addListener('submitStyle', this.getStyle)
+  }
+
+  shouldComponentUpdate (nextProps, nextState) {
+    return !is(fromJS(this.state), fromJS(nextState))
+  }
+
+  /**
+   * @description 缁勪欢閿�姣侊紝娓呴櫎state鏇存柊锛屾竻闄ゅ揩鎹烽敭璁剧疆
+   */
+  componentWillUnmount () {
+    this.setState = () => {
+      return
+    }
+    MKEmitter.removeListener('submitStyle', this.getStyle)
+  }
+
+  /**
+   * @description 鍗$墖琛屽灞備俊鎭洿鏂帮紙鏁版嵁婧愶紝鏍峰紡绛夛級
+   */
+  updateComponent = (component) => {
+    this.setState({
+      card: component
+    })
+
+    component.width = component.wrap.width
+    component.name = component.wrap.name
+
+    this.props.updateConfig(component)
+  }
+
+  changeStyle = () => {
+    const { card } = this.state
+
+    MKEmitter.emit('changeStyle', [card.uuid], ['height', 'background', 'border', 'padding', 'margin'], card.style)
+  }
+
+  getStyle = (comIds, style) => {
+    const { card } = this.state
+
+    if (comIds[0] !== card.uuid || comIds.length !== 1) return
+
+    let _card = {...card, style}
+
+    this.setState({
+      card: _card
+    })
+    
+    this.props.updateConfig(_card)
+  }
+
+  /**
+   * @description 鏇存柊鎼滅储鏉′欢閰嶇疆淇℃伅
+   */
+  updateconfig = (config) => {
+    this.setState({
+      card: config
+    })
+    this.props.updateConfig(config)
+  }
+
+  clickComponent = (e) => {
+    if (sessionStorage.getItem('style-control') === 'true' || sessionStorage.getItem('style-control') === 'component') {
+      e.stopPropagation()
+      MKEmitter.emit('clickComponent', this.state.card)
+    }
+  }
+
+  render() {
+    const { card } = this.state
+    let _style = resetStyle(card.style)
+
+    return (
+      <div className="menu-editor-sand-box" 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">
+            <WrapComponent config={card} updateConfig={this.updateComponent} />
+            <CopyComponent type="normaltable" card={card}/>
+            <Icon className="style" title="璋冩暣鏍峰紡" onClick={this.changeStyle} type="font-colors" />
+            <ClockComponent config={card} updateConfig={this.updateComponent}/>
+            <UserComponent config={card}/>
+            <Icon className="close" title="鍒犻櫎缁勪欢" type="delete" onClick={() => this.props.deletecomponent(card.uuid)} />
+            <SettingComponent config={card} updateConfig={this.updateComponent} />
+          </div>
+        } trigger="hover">
+          <Icon type="tool" />
+        </Popover>
+        <div className="tree-box">
+          <Tree defaultExpandAll={true} blockNode showIcon={card.wrap.showIcon === 'true'} showLine={card.wrap.showLine === 'true'} >
+            <TreeNode icon={<Icon type="folder-open" />} title="parent 0" key="0-0">
+              <TreeNode icon={<Icon type="file" />} title="leaf 0-0" key="0-0-0" isLeaf />
+              <TreeNode icon={<Icon type="file" />} title="leaf 0-1" key="0-0-1" isLeaf />
+            </TreeNode>
+            <TreeNode icon={<Icon type="folder-open" />} title="parent 1" key="0-1">
+              <TreeNode icon={<Icon type="file" />} title="leaf 1-0" key="0-1-0" isLeaf />
+              <TreeNode icon={<Icon type="file" />} title="leaf 1-1" key="0-1-1" isLeaf />
+            </TreeNode>
+          </Tree>
+        </div>
+      </div>
+    )
+  }
+}
+
+export default AntdTree
\ No newline at end of file
diff --git a/src/menu/components/tree/antd-tree/index.scss b/src/menu/components/tree/antd-tree/index.scss
new file mode 100644
index 0000000..c096ca8
--- /dev/null
+++ b/src/menu/components/tree/antd-tree/index.scss
@@ -0,0 +1,36 @@
+.menu-editor-sand-box {
+  position: relative;
+  box-sizing: border-box;
+  background: #ffffff;
+  background-position: center center;
+  background-repeat: no-repeat;
+  background-size: cover;
+  min-height: 30px;
+
+  .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);
+  }
+  .empty-content {
+    text-align: center;
+    font-size: 30px;
+    margin: 0;
+    line-height: 90px;
+    color: #bcbcbc;
+  }
+}
+.menu-editor-sand-box::after {
+  display: block;
+  content: ' ';
+  clear: both;
+}
+.menu-editor-sand-box:hover {
+  z-index: 1;
+  box-shadow: 0px 0px 4px #1890ff;
+}
diff --git a/src/menu/components/tree/antd-tree/wrapsetting/index.jsx b/src/menu/components/tree/antd-tree/wrapsetting/index.jsx
new file mode 100644
index 0000000..595cb9e
--- /dev/null
+++ b/src/menu/components/tree/antd-tree/wrapsetting/index.jsx
@@ -0,0 +1,83 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import { is, fromJS } from 'immutable'
+import { Icon, Modal } from 'antd'
+
+import zhCN from '@/locales/zh-CN/model.js'
+import enUS from '@/locales/en-US/model.js'
+import SettingForm from './settingform'
+import './index.scss'
+
+class DataSource extends Component {
+  static propTpyes = {
+    config: PropTypes.any,
+    updateConfig: PropTypes.func
+  }
+
+  state = {
+    dict: sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
+    visible: false,
+    wrap: null
+  }
+
+  UNSAFE_componentWillMount () {
+    const { config } = this.props
+
+    this.setState({wrap: fromJS(config.wrap).toJS()})
+  }
+
+  shouldComponentUpdate (nextProps, nextState) {
+    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
+  }
+
+  editDataSource = () => {
+    this.setState({
+      visible: true
+    })
+  }
+
+  verifySubmit = () => {
+    const { config } = this.props
+
+    this.verifyRef.handleConfirm().then(res => {
+
+      this.setState({
+        wrap: res,
+        visible: false
+      })
+      this.props.updateConfig({...config, wrap: res})
+    })
+  }
+
+  render () {
+    const { config } = this.props
+    const { visible, dict, wrap } = this.state
+
+    return (
+      <div className="model-menu-setting-wrap">
+        <Icon type="edit" 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}
+            wrap={wrap}
+            config={config}
+            inputSubmit={this.verifySubmit}
+            wrappedComponentRef={(inst) => this.verifyRef = inst}
+          />
+        </Modal>
+      </div>
+    )
+  }
+}
+
+export default DataSource
\ No newline at end of file
diff --git a/src/menu/components/tree/antd-tree/wrapsetting/index.scss b/src/menu/components/tree/antd-tree/wrapsetting/index.scss
new file mode 100644
index 0000000..04372e6
--- /dev/null
+++ b/src/menu/components/tree/antd-tree/wrapsetting/index.scss
@@ -0,0 +1,7 @@
+.model-menu-setting-wrap {
+  display: inline-block;
+
+  >.anticon-edit {
+    color: #1890ff;
+  }
+}
\ No newline at end of file
diff --git a/src/menu/components/tree/antd-tree/wrapsetting/settingform/index.jsx b/src/menu/components/tree/antd-tree/wrapsetting/settingform/index.jsx
new file mode 100644
index 0000000..d1a1479
--- /dev/null
+++ b/src/menu/components/tree/antd-tree/wrapsetting/settingform/index.jsx
@@ -0,0 +1,262 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import { Form, Row, Col, Input, Radio, Tooltip, Icon, InputNumber, Select } from 'antd'
+
+import './index.scss'
+
+class SettingForm extends Component {
+  static propTpyes = {
+    dict: PropTypes.object,      // 瀛楀吀椤�
+    config: PropTypes.object,    // 鍗$墖琛屼俊鎭�
+    wrap: PropTypes.object,      // 鏁版嵁婧愰厤缃�
+    inputSubmit: PropTypes.func  // 鍥炶溅浜嬩欢
+  }
+
+  state = {
+    roleList: [],
+  }
+
+  UNSAFE_componentWillMount () {
+    let roleList = sessionStorage.getItem('sysRoles')
+    if (roleList) {
+      try {
+        roleList = JSON.parse(roleList)
+      } catch {
+        roleList = []
+      }
+    } else {
+      roleList = []
+    }
+
+    this.setState({roleList})
+  }
+
+  handleConfirm = () => {
+    // 琛ㄥ崟鎻愪氦鏃舵鏌ヨ緭鍏ュ�兼槸鍚︽纭�
+    return new Promise((resolve, reject) => {
+      this.props.form.validateFieldsAndScroll((err, values) => {
+        if (!err) {
+          resolve(values)
+        } else {
+          reject(err)
+        }
+      })
+    })
+  }
+
+  handleSubmit = (e) => {
+    e.preventDefault()
+
+    if (this.props.inputSubmit) {
+      this.props.inputSubmit()
+    }
+  }
+
+  render() {
+    const { wrap, config } = this.props
+    const { getFieldDecorator } = this.props.form
+    const { roleList } = this.state
+
+    const formItemLayout = {
+      labelCol: {
+        xs: { span: 24 },
+        sm: { span: 8 }
+      },
+      wrapperCol: {
+        xs: { span: 24 },
+        sm: { span: 16 }
+      }
+    }
+
+    return (
+      <div className="model-menu-setting-form">
+        <Form {...formItemLayout}>
+          <Row gutter={24}>
+            <Col span={12}>
+              <Form.Item label="鏍囬">
+                {getFieldDecorator('title', {
+                  initialValue: wrap.title || ''
+                })(<Input placeholder={''} autoComplete="off" onPressEnter={this.handleSubmit} />)}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item label={
+                <Tooltip placement="topLeft" title="鐢ㄤ簬缁勪欢闂寸殑鍖哄垎銆�">
+                  <Icon type="question-circle" />
+                  缁勪欢鍚嶇О
+                </Tooltip>
+              }>
+                {getFieldDecorator('name', {
+                  initialValue: wrap.name,
+                  rules: [
+                    {
+                      required: true,
+                      message: this.props.dict['form.required.input'] + '缁勪欢鍚嶇О!'
+                    }
+                  ]
+                })(<Input placeholder={''} autoComplete="off" onPressEnter={this.handleSubmit} />)}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item label={
+                <Tooltip placement="topLeft" title="鏁版嵁鍊煎瓧娈点��">
+                  <Icon type="question-circle" />
+                  Value
+                </Tooltip>
+              }>
+                {getFieldDecorator('valueField', {
+                  initialValue: wrap.valueField || '',
+                  rules: [
+                    {
+                      required: true,
+                      message: this.props.dict['form.required.select'] + 'Value瀛楁!'
+                    }
+                  ]
+                })(
+                  <Select>
+                    {config.columns.map(option =>
+                      <Select.Option key={option.uuid} value={option.field}>{option.label}</Select.Option>
+                    )}
+                  </Select>
+                )}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item label={
+                <Tooltip placement="topLeft" title="鏄剧ず鏂囧瓧瀛楁銆�">
+                  <Icon type="question-circle" />
+                  Label
+                </Tooltip>
+              }>
+                {getFieldDecorator('labelField', {
+                  initialValue: wrap.labelField || '',
+                  rules: [
+                    {
+                      required: true,
+                      message: this.props.dict['form.required.select'] + 'Label瀛楁!'
+                    }
+                  ]
+                })(
+                  <Select>
+                    {config.columns.map(option =>
+                      <Select.Option key={option.uuid} value={option.field}>{option.label}</Select.Option>
+                    )}
+                  </Select>
+                )}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item label={
+                <Tooltip placement="topLeft" title="鐖剁骇瀛楁銆�">
+                  <Icon type="question-circle" />
+                  Parent
+                </Tooltip>
+              }>
+                {getFieldDecorator('parentField', {
+                  initialValue: wrap.parentField || '',
+                  rules: [
+                    {
+                      required: true,
+                      message: this.props.dict['form.required.select'] + 'Parent瀛楁!'
+                    }
+                  ]
+                })(
+                  <Select>
+                    {config.columns.map(option =>
+                      <Select.Option key={option.uuid} value={option.field}>{option.label}</Select.Option>
+                    )}
+                  </Select>
+                )}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item label={
+                <Tooltip placement="topLeft" title={'鐖剁骇瀛楁鍊间笌椤剁骇鏍囪瘑鐩稿悓鏃讹紝瑙嗕负椤剁骇鑺傜偣銆�'}>
+                  <Icon type="question-circle" />
+                  椤剁骇鏍囪瘑
+                </Tooltip>
+              }>
+                {getFieldDecorator('mark', {
+                  initialValue: wrap.mark || ''
+                })(<Input placeholder={''} autoComplete="off" onPressEnter={this.handleSubmit} />)}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item label={
+                <Tooltip placement="topLeft" title="鏍呮牸甯冨眬锛屾瘡琛岀瓑鍒嗕负24鍒椼��">
+                  <Icon type="question-circle" />
+                  瀹藉害
+                </Tooltip>
+              }>
+                {getFieldDecorator('width', {
+                  initialValue: wrap.width || 24,
+                  rules: [
+                    {
+                      required: true,
+                      message: this.props.dict['form.required.input'] + '瀹藉害!'
+                    }
+                  ]
+                })(<InputNumber min={1} max={24} precision={0} onPressEnter={this.handleSubmit} />)}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item label="鍥炬爣">
+                {getFieldDecorator('showIcon', {
+                  initialValue: wrap.showIcon || 'false'
+                })(
+                  <Radio.Group>
+                    <Radio value="true">鏄剧ず</Radio>
+                    <Radio value="false">闅愯棌</Radio>
+                  </Radio.Group>
+                )}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item label="鍒嗗壊绾�">
+                {getFieldDecorator('showLine', {
+                  initialValue: wrap.showLine || 'false'
+                })(
+                  <Radio.Group>
+                    <Radio value="true">鏄剧ず</Radio>
+                    <Radio value="false">闅愯棌</Radio>
+                  </Radio.Group>
+                )}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item label="杩囨护鏉′欢">
+                {getFieldDecorator('searchable', {
+                  initialValue: wrap.searchable || 'false'
+                })(
+                  <Radio.Group>
+                    <Radio value="true">鏄剧ず</Radio>
+                    <Radio value="false">闅愯棌</Radio>
+                  </Radio.Group>
+                )}
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item label="榛戝悕鍗�">
+                {getFieldDecorator('blacklist', {
+                  initialValue: wrap.blacklist || []
+                })(
+                  <Select
+                    showSearch
+                    mode="multiple"
+                    filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
+                  >
+                    {roleList.map(option =>
+                      <Select.Option key={option.uuid} value={option.value}>{option.text}</Select.Option>
+                    )}
+                  </Select>
+                )}
+              </Form.Item>
+            </Col>
+          </Row>
+        </Form>
+      </div>
+    )
+  }
+}
+
+export default Form.create()(SettingForm)
\ No newline at end of file
diff --git a/src/menu/components/tree/antd-tree/wrapsetting/settingform/index.scss b/src/menu/components/tree/antd-tree/wrapsetting/settingform/index.scss
new file mode 100644
index 0000000..c530b18
--- /dev/null
+++ b/src/menu/components/tree/antd-tree/wrapsetting/settingform/index.scss
@@ -0,0 +1,15 @@
+.model-menu-setting-form {
+  position: relative;
+
+  .anticon-question-circle {
+    color: #c49f47;
+    margin-right: 3px;
+  }
+  .ant-input-number {
+    width: 100%;
+  }
+  .color-sketch-block {
+    position: relative;
+    top: 7px;
+  }
+}
\ No newline at end of file
diff --git a/src/menu/menushell/card.jsx b/src/menu/menushell/card.jsx
index e086365..a482a9d 100644
--- a/src/menu/menushell/card.jsx
+++ b/src/menu/menushell/card.jsx
@@ -12,6 +12,7 @@
 const AntvTabs = asyncComponent(() => import('@/menu/components/tabs/antv-tabs'))
 const DataCard = asyncComponent(() => import('@/menu/components/card/data-card'))
 const PropCard = asyncComponent(() => import('@/menu/components/card/prop-card'))
+const NormalTree = asyncComponent(() => import('@/menu/components/tree/antd-tree'))
 const CarouselDataCard = asyncComponent(() => import('@/menu/components/carousel/data-card'))
 const CarouselPropCard = asyncComponent(() => import('@/menu/components/carousel/prop-card'))
 const TableCard = asyncComponent(() => import('@/menu/components/card/table-card'))
@@ -63,6 +64,8 @@
       return (<AntvPie card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
     } else if (card.type === 'dashboard') {
       return (<AntvDashboard card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
+    } else if (card.type === 'tree') {
+      return (<NormalTree card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
     } else if (card.type === 'scatter') {
       return (<AntvScatter card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
     } else if (card.type === 'form') {
diff --git a/src/menu/menushell/index.jsx b/src/menu/menushell/index.jsx
index 8a9fcfa..b1427ac 100644
--- a/src/menu/menushell/index.jsx
+++ b/src/menu/menushell/index.jsx
@@ -99,6 +99,7 @@
         form: '琛ㄥ崟',
         dashboard: '浠〃鐩�',
         scatter: '鏁g偣鍥�',
+        tree: '鏍戝舰鍒楄〃',
         card: '鍗$墖'
       }
       let i = 1
diff --git a/src/menu/modulesource/option.jsx b/src/menu/modulesource/option.jsx
index 9bd7d81..e4d50c4 100644
--- a/src/menu/modulesource/option.jsx
+++ b/src/menu/modulesource/option.jsx
@@ -19,6 +19,7 @@
 import form from '@/assets/mobimg/form.png'
 import dashboard from '@/assets/mobimg/dashboard.png'
 import scatter from '@/assets/mobimg/scatter.png'
+import tree from '@/assets/mobimg/tree.png'
 
 // 缁勪欢閰嶇疆淇℃伅
 export const menuOptions = [
@@ -31,6 +32,7 @@
   { type: 'menu', url: Carousel1, component: 'carousel', subtype: 'propcard', title: '杞挱-闈欐�佹暟鎹�', width: 24, forbid: ['billPrint'] },
   { type: 'menu', url: NormalTable, component: 'table', subtype: 'normaltable', title: '甯哥敤琛�', width: 24 },
   { type: 'menu', url: TableCard, component: 'table', subtype: 'tablecard', title: '琛ㄦ牸', width: 12 },
+  { type: 'menu', url: tree, component: 'tree', subtype: 'normaltree', title: '鏍戝舰鍒楄〃', width: 12 },
   { type: 'menu', url: line, component: 'line', subtype: 'line', title: '鎶樼嚎鍥�', width: 24 },
   { type: 'menu', url: line1, component: 'line', subtype: 'line1', title: '闃舵鎶樼嚎鍥�', width: 24 },
   { type: 'menu', url: bar, component: 'bar', subtype: 'bar', title: '鏌辩姸鍥�', width: 24 },
diff --git a/src/mob/components/formdragelement/index.scss b/src/mob/components/formdragelement/index.scss
index 188c60e..011e9e7 100644
--- a/src/mob/components/formdragelement/index.scss
+++ b/src/mob/components/formdragelement/index.scss
@@ -192,6 +192,9 @@
       opacity: 0;
       z-index: 1;
     }
+    .field-name {
+      margin-left: 10px;
+    }
   }
   .ant-form-item.required {
     .am-input-label::before {
diff --git a/src/pc/menushell/index.jsx b/src/pc/menushell/index.jsx
index 024490a..acc2b18 100644
--- a/src/pc/menushell/index.jsx
+++ b/src/pc/menushell/index.jsx
@@ -107,6 +107,7 @@
         navbar: '瀵艰埅鏍�',
         carousel: '杞挱',
         dashboard: '浠〃鐩�',
+        tree: '鏍戝舰鍒楄〃',
         card: '鍗$墖',
         login: '鐧诲綍'
       }
diff --git a/src/tabviews/custom/components/card/data-card/index.jsx b/src/tabviews/custom/components/card/data-card/index.jsx
index 1c56228..8269c1b 100644
--- a/src/tabviews/custom/components/card/data-card/index.jsx
+++ b/src/tabviews/custom/components/card/data-card/index.jsx
@@ -572,7 +572,7 @@
           {switchable ? <div className={'prev-page ' + (pageIndex === 1 ? 'disabled' : '')} onClick={this.prevPage}><div><div><img src={preImg} alt=""/></div></div></div> : null}
           {data && data.length > 0 ? <Row className="card-row-list">
             {data.map((item, index) => (
-              <Col className={(activeKey === index ? 'active' : (selectKeys.indexOf(index) > -1 ? 'selected' : '')) + (card.setting.click ? ' pointer' : '')} key={index} span={card.setting.width} offset={!index ? offset : 0} onClick={() => {this.changeCard(index, item)}}>
+              <Col className={(activeKey === index ? ' active' : (selectKeys.indexOf(index) > -1 ? ' selected' : '')) + (card.setting.click ? ' pointer' : '')} key={index} span={card.setting.width} offset={!index ? offset : 0} onClick={() => {this.changeCard(index, item)}}>
                 <CardItem card={card} cards={config} data={item}/>
               </Col>
             ))}
diff --git a/src/tabviews/custom/components/tree/antd-tree/index.jsx b/src/tabviews/custom/components/tree/antd-tree/index.jsx
new file mode 100644
index 0000000..1ab48b4
--- /dev/null
+++ b/src/tabviews/custom/components/tree/antd-tree/index.jsx
@@ -0,0 +1,483 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import { is, fromJS } from 'immutable'
+import { Spin, Empty, notification, Input, Tree, Icon } from 'antd'
+import moment from 'moment'
+
+import Api from '@/api'
+import Utils from '@/utils/utils.js'
+import UtilsDM from '@/utils/utils-datamanage.js'
+import MKEmitter from '@/utils/events.js'
+import './index.scss'
+
+const { TreeNode } = Tree
+const { Search } = Input
+
+class NormalTree extends Component {
+  static propTpyes = {
+    BID: PropTypes.any,              // 鐖剁骇Id
+    data: PropTypes.array,           // 缁熶竴鏌ヨ鏁版嵁
+    config: PropTypes.object,        // 缁勪欢閰嶇疆淇℃伅
+    mainSearch: PropTypes.any,       // 澶栧眰鎼滅储鏉′欢
+    menuType: PropTypes.any,         // 鑿滃崟绫诲瀷
+  }
+
+  state = {
+    BID: '',                   // 涓昏〃ID
+    config: null,              // 鍥捐〃閰嶇疆淇℃伅
+    loading: false,            // 鏁版嵁鍔犺浇鐘舵��
+    sync: false,               // 鏄惁缁熶竴璇锋眰鏁版嵁
+    data: null,                // 鏁版嵁
+    searchkey: null,           // 杩囨护鏉′欢
+    treedata: null,            // 鍒楄〃鏁版嵁闆�
+    treeNodes: null,           // 鍒楄〃鏁版嵁闆�
+    expandedKeys: [],          // 灞曞紑鐨勬爲鑺傜偣
+    selectedKeys: [],          // 閫変腑鐨勬爲鑺傜偣
+  }
+
+  UNSAFE_componentWillMount () {
+    const { config, data, initdata, BID } = this.props
+    let _config = fromJS(config).toJS()
+    let _data = null
+    let _sync = config.setting.sync === 'true'
+
+    if (config.setting.sync === 'true' && data) {
+      _data = data[config.dataName] || []
+      _sync = false
+    } else if (config.setting.sync === 'true' && initdata) {
+      _data = initdata || []
+      _sync = false
+    }
+
+    this.setState({
+      config: _config,
+      data: _data,
+      BID: BID || '',
+      arr_field: _config.columns.map(col => col.field).join(','),
+      sync: _sync
+    }, () => {
+      if (config.setting.sync !== 'true' && config.setting.onload === 'true') {
+        this.loadData()
+      } else if (config.setting.sync === 'true' && _data) {
+        this.handleData()
+      }
+    })
+  }
+
+  /**
+   * @description 鍥捐〃鏁版嵁鏇存柊锛屽埛鏂板唴瀹�
+   */
+  UNSAFE_componentWillReceiveProps (nextProps) {
+    const { sync, config } = this.state
+
+    if (sync && !is(fromJS(this.props.data), fromJS(nextProps.data))) {
+      let _data = []
+      if (nextProps.data && nextProps.data[config.dataName]) {
+        _data = nextProps.data[config.dataName] || []
+      }
+
+      // _data = [
+      //   {ID: 'dsdsagsgfs32ed2dqd61', nvarchar1: '1', nvarchar2: '1', nvarchar3: ''},
+      //   {ID: 'dsdsagsgfs32ed2dqd61', nvarchar1: '1-1', nvarchar2: '1-1', nvarchar3: '1'},
+      //   {ID: 'dsdsagsgfs32ed2dqd61', nvarchar1: '1-2', nvarchar2: '1-2', nvarchar3: '1'},
+      //   {ID: 'dsdsagsgfs32ed2dqd61', nvarchar1: '2', nvarchar2: '2', nvarchar3: ''},
+      //   {ID: 'dsdsagsgfs32ed2dqd61', nvarchar1: '2-1', nvarchar2: '2-1', nvarchar3: '2'},
+      //   {ID: 'dsdsagsgfs32ed2dqd61', nvarchar1: '2-2', nvarchar2: '2-2', nvarchar3: '2'},
+      // ]
+
+      this.setState({sync: false, data: _data}, () => {
+        this.handleData()
+      })
+    } else if (nextProps.mainSearch && !is(fromJS(this.props.mainSearch), fromJS(nextProps.mainSearch))) {
+      if (config.setting.syncRefresh === 'true') {
+        this.setState({}, () => {
+          this.loadData()
+        })
+      }
+    }
+  }
+
+  shouldComponentUpdate (nextProps, nextState) {
+    return !is(fromJS(this.state), fromJS(nextState))
+  }
+
+  componentDidMount () {
+    MKEmitter.addListener('reloadData', this.reloadData)
+    MKEmitter.addListener('resetSelectLine', this.resetParentParam)
+    this.handleTimer()
+  }
+
+  /**
+   * @description 缁勪欢閿�姣侊紝娓呴櫎state鏇存柊锛屾竻闄ゅ揩鎹烽敭璁剧疆
+   */
+  componentWillUnmount () {
+    clearTimeout(this.timer)
+    this.setState = () => {
+      return
+    }
+    MKEmitter.removeListener('reloadData', this.reloadData)
+    MKEmitter.removeListener('resetSelectLine', this.resetParentParam)
+  }
+
+  handleTimer = () => {
+    const { config } = this.state
+
+    if (!config.timer) return
+
+    const _change = {
+      '15s': 15000,
+      '30s': 30000,
+      '1min': 60000,
+      '5min': 300000,
+      '10min': 600000,
+      '15min': 900000,
+      '30min': 1800000,
+      '1hour': 3600000
+    }
+
+    let timer = _change[config.timer]
+
+    if (!timer) return
+
+    let _param = {
+      func: 's_get_timers_role',
+      LText: `select '${window.GLOB.appkey || ''}','${config.uuid}'`,
+      timer_type: config.timer,
+      component_id: config.uuid
+    }
+    
+    _param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')          // 鏃堕棿鎴�
+    _param.LText = Utils.formatOptions(_param.LText)                   // 鍏抽敭瀛楃鏇挎崲锛宐ase64鍔犲瘑
+    _param.secretkey = Utils.encrypt(_param.LText, _param.timestamp)   // md5瀵嗛挜
+
+    Api.getSystemConfig(_param).then(result => {
+      if (!result.status) {
+        notification.warning({
+          top: 92,
+          message: result.message,
+          duration: 5
+        })
+        return
+      } else if (result.run_type) {
+        this.setState({timer})
+        this.timer = setTimeout(() => {
+          this.timerTask()
+        }, timer)
+      }
+    })
+  }
+
+  timerTask = () => {
+    const { timer } = this.state
+    if (!timer) return
+    
+    this.loadData(true)
+    
+    this.timer = setTimeout(() => {
+      this.timerTask()
+    }, timer)
+  }
+
+  reloadData = (menuId) => {
+    const { config } = this.state
+
+    if (config.uuid !== menuId) return
+
+    this.loadData()
+  }
+
+  resetParentParam = (MenuID, id) => {
+    const { config } = this.state
+
+    if (!config.setting.supModule || config.setting.supModule !== MenuID) return
+    if (id !== this.state.BID) {
+      this.setState({ BID: id }, () => {
+        this.loadData()
+      })
+    }
+  }
+
+  /**
+   * @description 鏁版嵁鍔犺浇
+   */
+  async loadData (hastimer) {
+    const { mainSearch, menuType } = this.props
+    const { config, arr_field, BID } = this.state
+
+    if (config.setting.supModule && !BID) { // BID 涓嶅瓨鍦ㄦ椂锛屼笉鍋氭煡璇�
+      this.setState({
+        data: []
+      }, () => {
+        this.handleData()
+      })
+      return
+    }
+
+    let searches = []
+    if (mainSearch && mainSearch.length > 0) { // 涓昏〃鎼滅储鏉′欢
+      let keys = searches.map(item => item.key)
+      mainSearch.forEach(item => {
+        if (!keys.includes(item.key)) {
+          searches.push(item)
+        }
+      })
+    }
+
+    let requireFields = searches.filter(item => item.required && (!item.value || item.value.length === 0))
+    if (requireFields.length > 0) {
+      this.setState({
+        loading: false
+      })
+      return
+    }
+
+    if (!hastimer) {
+      this.setState({
+        loading: true
+      })
+    }
+
+    let _orderBy = config.setting.order || ''
+    let param = UtilsDM.getQueryDataParams(config.setting, arr_field, searches, _orderBy, '', '', BID, menuType)
+
+    let result = await Api.genericInterface(param)
+    if (result.status) {
+      this.setState({
+        data: result.data,
+        loading: false
+      }, () => {
+        this.handleData()
+      })
+    } else {
+      this.setState({
+        loading: false,
+        timer: null
+      })
+      notification.error({
+        top: 92,
+        message: result.message,
+        duration: 10
+      })
+    }
+  }
+
+  handleData = () => {
+    const { data, searchkey, config } = this.state
+    if (!data || data.length === 0) {
+      this.setState({
+        treedata: [],
+        treeNodes: [],
+      })
+      return
+    }
+    let parentNodes = []
+    let _options = []
+    let logMap = new Map()
+
+    data.forEach(item => {
+      let pval = item[config.wrap.parentField]
+      let val = item[config.wrap.valueField]
+
+      if (!val || logMap.has(val)) return
+
+      logMap.set(val, true)
+      if (pval === config.wrap.mark) {
+        parentNodes.push({
+          // ...item,
+          title: item[config.wrap.labelField] || '',
+          key: val,
+          parentId: ''
+        })
+      } else if (pval) {
+        _options.push({
+          // ...item,
+          title: item[config.wrap.labelField] || '',
+          key: val,
+          parentId: pval
+        })
+      }
+    })
+    let _treedata = this.getTree(parentNodes, _options)
+    
+    let _treeNodes = []
+
+    if (!searchkey) {
+      _treeNodes = fromJS(_treedata).toJS()
+    } else {
+      _treeNodes = this.getFilterTree(fromJS(_treedata).toJS(), searchkey.toLowerCase())
+    }
+
+    this.setState({
+      treedata: _treedata,
+      treeNodes: _treeNodes,
+    })
+  }
+
+  treeFilter = (value) => {
+    const { treedata } = this.state
+
+    let _treeNodes = []
+
+    if (!value) {
+      _treeNodes = fromJS(treedata).toJS()
+    } else {
+      _treeNodes = this.getFilterTree(fromJS(treedata).toJS(), value.toLowerCase())
+    }
+
+    this.setState({
+      searchkey: value,
+      treeNodes: _treeNodes
+    })
+  }
+
+  /**
+   * @description 鑾峰彇缁撴瀯鏍戜俊鎭�
+   */
+  getFilterTree = (parents, searchkey) => {
+    return parents.filter(node => {
+      if (!node.children) {
+        return (node.title.toLowerCase().indexOf(searchkey) >= 0 || node.key.toLowerCase().indexOf(searchkey) >= 0)
+      } else {
+        if (node.title.toLowerCase().indexOf(searchkey) >= 0 || node.key.toLowerCase().indexOf(searchkey) >= 0) {
+          return true
+        }
+        
+        node.children = this.getFilterTree(node.children, searchkey)
+        if (node.children.length === 0) {
+          return false
+        } else {
+          return true
+        }
+      }
+    })
+  }
+
+  /**
+   * @description 鑾峰彇缁撴瀯鏍戜俊鎭�
+   */
+  getTree = (parents, options) => {
+    parents.forEach(parent => {
+      parent.children = []
+      // 娣诲姞鑿滃崟鐨勫瓙鍏冪礌
+      options = options.filter(option => {
+        if (option.parentId === parent.key) {
+          parent.children.push(option)
+          return false
+        }
+        return true
+      })
+
+      if (parent.children.length === 0) {
+        parent.children = null
+      } else {
+        parent.children = this.getTree(parent.children, options)
+      }
+    })
+    return parents
+  }
+
+  /**
+   * @description 鑾峰彇鏍戣妭鐐�
+   */
+  renderTreeNodes = (nodes) => {
+    return nodes.map(item => {
+      if (item.children) {
+        return (
+          <TreeNode icon={<span><Icon type="folder-open" /><Icon type="folder" /></span>} title={item.title} key={item.key} dataRef={item}>
+            {this.renderTreeNodes(item.children)}
+          </TreeNode>
+        )
+      }
+      return <TreeNode icon={<Icon type="file" />} key={item.key} title={item.title} dataRef={item} isLeaf />
+    })
+  }
+
+  changeExpandedKeys = (expandedKeys) => {
+    this.setState({
+      expandedKeys: expandedKeys
+    })
+  }
+
+  // 鍙抽敭灞曞紑鑺傜偣涓嬬殑鍏ㄩ儴鍒嗘敮
+  changeExpandedAllKeys = ({event, node}) => {
+    const { expandedKeys } = this.state
+    let _node = node.props.dataRef
+    event.stopPropagation()
+
+    let keys = []
+    this.getExpandKeys(_node, keys)
+
+    this.setState({
+      expandedKeys: Array.from(new Set([...keys, ...expandedKeys])),
+    })
+  }
+
+  getExpandKeys = (node, keys) => {
+    if (node.children) {
+      keys.push(node.key)
+      node.children.forEach(_node => {
+        this.getExpandKeys(_node, keys)
+      })
+    }
+  }
+
+  selectTreeNode = (selectedKeys, {selected, node}) => {
+    const { config } = this.state
+    let _expandedKeys = fromJS(this.state.expandedKeys).toJS()
+    let _data = fromJS(node.props.dataRef).toJS()
+
+    if (_expandedKeys.indexOf(_data.key) >= 0) {
+      _expandedKeys = _expandedKeys.filter(key => key !== _data.key)
+    } else {
+      if (_data.children) {
+        _expandedKeys.push(_data.key)
+        _expandedKeys = Array.from(new Set(_expandedKeys))
+      }
+    }
+
+    if (selected) {
+      MKEmitter.emit('resetSelectLine', config.uuid, (_data ? _data.key : ''), _data)
+    }
+
+    this.setState({
+      expandedKeys: _expandedKeys,
+      selectedKeys: [_data.key]
+    })
+  }
+
+  render() {
+    const { config, loading, treeNodes, expandedKeys, selectedKeys } = this.state
+
+    return (
+      <div className="custom-tree-box" style={config.style}>
+        {loading ?
+          <div className="loading-mask">
+            <div className="ant-spin-blur"></div>
+            <Spin />
+          </div> : null
+        }
+        {config.wrap.title || config.wrap.searchable === 'true' ? <div className="tree-header" style={config.headerStyle}>
+          <span className="title">{config.wrap.title}</span>
+          {config.wrap.searchable === 'true' ? <Search allowClear onSearch={this.treeFilter} /> : null}
+        </div> : null}
+        {treeNodes && treeNodes.length > 0 ? <div className="tree-box">
+          <Tree
+            blockNode
+            onSelect={this.selectTreeNode}
+            expandedKeys={expandedKeys}
+            selectedKeys={selectedKeys}
+            onRightClick={this.changeExpandedAllKeys}
+            onExpand={this.changeExpandedKeys}
+            showIcon={config.wrap.showIcon === 'true'}
+            showLine={config.wrap.showLine === 'true'}
+          >
+            {this.renderTreeNodes(treeNodes)}
+          </Tree>
+        </div> : null}
+        {treeNodes && treeNodes.length === 0 ? <Empty description={false}/> : null}
+      </div>
+    )
+  }
+}
+
+export default NormalTree
\ No newline at end of file
diff --git a/src/tabviews/custom/components/tree/antd-tree/index.scss b/src/tabviews/custom/components/tree/antd-tree/index.scss
new file mode 100644
index 0000000..413cf1c
--- /dev/null
+++ b/src/tabviews/custom/components/tree/antd-tree/index.scss
@@ -0,0 +1,93 @@
+.custom-tree-box {
+  position: relative;
+  background: #ffffff;
+  background-position: center center;
+  background-repeat: no-repeat;
+  background-size: cover;
+  min-height: 100px;
+
+  .tree-header {
+    position: relative;
+    height: 45px;
+    padding-right: 8px;
+    border-bottom: 1px solid #e8e8e8;
+    overflow: hidden;
+    letter-spacing: 0px;
+  
+    .title {
+      text-decoration: inherit;
+      font-weight: inherit;
+      font-style: inherit;
+      float: left;
+      line-height: 45px;
+      margin-left: 10px;
+      position: relative;
+      z-index: 1;
+    }
+    .ant-input-search.ant-input-affix-wrapper {
+      width: calc(100% - 140px);
+      max-width: 130px;
+      margin-top: 6px;
+      float: right;
+      height: 30px;
+      border-radius: 20px;
+      border: 1px solid #d9d9d9;
+      opacity: 0.6;
+      input {
+        border: none;
+        border-radius: 20px;
+        height: 28px;
+      }
+    }
+  }
+  .tree-box {
+    overflow-x: auto;
+    padding-bottom: 10px;
+
+    .ant-tree-node-content-wrapper-close > span > span > .anticon-folder-open {
+      display: none;
+    }
+    .ant-tree-node-content-wrapper-open > span > span > .anticon-folder {
+      display: none;
+    }
+  }
+  .tree-box::-webkit-scrollbar {
+    height: 10px;
+  }
+  .tree-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);
+  }
+  .tree-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);
+  }
+  .ant-empty {
+    position: absolute;
+    top: calc(50% - 34px);
+    left: calc(50% - 92px);
+
+    .ant-empty-image {
+      height: 60px;
+    }
+  }
+  .loading-mask {
+    position: absolute;
+    left: 0px;
+    top: 0;
+    right: 0px;
+    bottom: 0px;
+    z-index: 1;
+
+    .ant-spin-blur {
+      position: absolute;
+      width: 100%;
+      height: 100%;
+      opacity: 0.5;
+      background: #ffffff;
+    }
+  }
+}
diff --git a/src/tabviews/custom/index.jsx b/src/tabviews/custom/index.jsx
index cbec0bc..d175a50 100644
--- a/src/tabviews/custom/index.jsx
+++ b/src/tabviews/custom/index.jsx
@@ -34,6 +34,7 @@
 const NormalGroup = asyncComponent(() => import('./components/group/normal-group'))
 const BraftEditor = asyncComponent(() => import('./components/editor/braft-editor'))
 const SandBox = asyncComponent(() => import('./components/code/sand-box'))
+const NormalTree = asyncComponent(() => import('./components/tree/antd-tree'))
 const SettingComponent = asyncComponent(() => import('@/tabviews/zshare/settingcomponent'))
 const PagemsgComponent = asyncComponent(() => import('@/tabviews/zshare/pageMessage'))
 
@@ -1132,6 +1133,12 @@
             <BraftEditor config={item} data={data} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
           </Col>
         )
+      } else if (item.type === 'tree') {
+        return (
+          <Col span={item.width} key={item.uuid}>
+            <NormalTree config={item} data={data} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
+          </Col>
+        )
       } else if (item.type === 'code') {
         return (
           <Col span={item.width} key={item.uuid}>
diff --git a/src/tabviews/zshare/imgScale/index.jsx b/src/tabviews/zshare/imgScale/index.jsx
new file mode 100644
index 0000000..dc4c542
--- /dev/null
+++ b/src/tabviews/zshare/imgScale/index.jsx
@@ -0,0 +1,65 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import { is, fromJS } from 'immutable'
+import { Icon } from 'antd'
+
+import './index.scss'
+
+class ImgScale extends Component {
+  static propTpyes = {
+    data: PropTypes.object
+  }
+
+  state = {
+    list: [],
+    index: 0
+  }
+
+  UNSAFE_componentWillMount() {
+    const { data } = this.props
+
+    this.setState({
+      list: data.list || [],
+      index: data.index || 0
+    })
+  }
+
+  shouldComponentUpdate (nextProps, nextState) {
+    return !is(fromJS(this.state), fromJS(nextState))
+  }
+
+  /**
+   * @description 缁勪欢閿�姣侊紝娓呴櫎state鏇存柊
+   */
+  componentWillUnmount () {
+    this.setState = () => {
+      return
+    }
+  }
+
+  reduce = () => {
+    const { index } = this.state
+
+    this.setState({index: index - 1})
+  }
+
+  plus = () => {
+    const { index } = this.state
+
+    this.setState({index: index + 1})
+  }
+
+  render() {
+    const { list, index } = this.state
+
+    return (
+      <div className="img-scale-wrap">
+        <img src={list[index]} alt="" />
+        {index > 0 ? <Icon type="left" onClick={this.reduce} /> : null}
+        {index < list.length -1 ? <Icon type="right" onClick={this.plus} /> : null}
+      </div>
+    )
+  }
+}
+
+export default ImgScale
\ No newline at end of file
diff --git a/src/tabviews/zshare/imgScale/index.scss b/src/tabviews/zshare/imgScale/index.scss
new file mode 100644
index 0000000..db4944e
--- /dev/null
+++ b/src/tabviews/zshare/imgScale/index.scss
@@ -0,0 +1,20 @@
+.img-scale-wrap {
+  position: relative;
+  img {
+    max-width: 100%;
+  }
+
+  .anticon {
+    position: absolute;
+    top: calc(50% - 25px);
+    font-size: 30px;
+    padding: 10px;
+  }
+
+  .anticon-left {
+    left: 20px;
+  }
+  .anticon-right {
+    right: 20px;
+  }
+}
diff --git a/src/tabviews/zshare/normalTable/index.jsx b/src/tabviews/zshare/normalTable/index.jsx
index ac7f9c5..aa989fb 100644
--- a/src/tabviews/zshare/normalTable/index.jsx
+++ b/src/tabviews/zshare/normalTable/index.jsx
@@ -17,6 +17,7 @@
 const PopupButton = asyncComponent(() => import('@/tabviews/zshare/actionList/popupbutton'))
 const TabButton = asyncComponent(() => import('@/tabviews/zshare/actionList/tabbutton'))
 const NewPageButton = asyncComponent(() => import('@/tabviews/zshare/actionList/newpagebutton'))
+const ImgScale = asyncComponent(() => import('@/tabviews/zshare/imgScale'))
 
 class NormalTable extends Component {
   static propTpyes = {
@@ -45,7 +46,7 @@
     pageSize: 10,         // 姣忛〉鏁版嵁鏉℃暟
     columns: null,        // 鏄剧ず鍒�
     imgShow: false,       // 鍥剧墖鏀惧ぇ妯℃�佹
-    imgSrc: '',           // 鍥剧墖璺緞
+    imgData: {},          // 鍥剧墖闆�
     lineMarks: null,      // 琛屾爣璁�
     activeIndex: null,    // 鏍囪褰撳墠閫変腑琛�
     rowspans: null        // 琛屽悎骞跺瓧娈典俊鎭�
@@ -547,7 +548,7 @@
       let photos = ''
       if (item.field && record.hasOwnProperty(item.field)) {
         photos = record[item.field] + ''
-        photos = photos.split(',')
+        photos = photos.split(',').filter(Boolean)
       } else {
         photos = ''
       }
@@ -557,7 +558,7 @@
         <div className="picture-col">
           {photos && photos.map((url, i) => {
             if (item.scale === 'true') {
-              return <img style={{maxHeight: maxHeight}} className="image-scale" onClick={this.imgScale} key={`${i}`} src={url} alt=""/>
+              return <img style={{maxHeight: maxHeight}} className="image-scale" onClick={() => this.imgScale(photos, i)} key={`${i}`} src={url} alt=""/>
             } else {
               return <img style={{maxHeight: maxHeight}} key={`${i}`} src={url} alt=""/>
             }
@@ -733,7 +734,7 @@
           let photos = []
           try {
             photos = record[col.field] + ''
-            photos = photos.split(',')
+            photos = photos.split(',').filter(Boolean)
           } catch {
             photos = []
           }
@@ -869,9 +870,8 @@
         <div className="content-fence">
           <div className="content-fence-top" style={images[0] ? {textAlign: images[0].align} : null}>
             {images.map((_img, index) => {
-              if (!_img.url) return ''
               if (_img.scale) {
-                return <img style={{maxHeight: _img.maxHeight}} className="image-scale" onClick={this.imgScale} key={`${index}`} src={_img.url} alt=""/>
+                return <img style={{maxHeight: _img.maxHeight}} className="image-scale" onClick={() => this.imgScale(images, index)} key={`${index}`} src={_img.url} alt=""/>
               } else {
                 return (<img style={{maxHeight: _img.maxHeight}} key={`${index}`} src={_img.url} alt=""/>)
               }
@@ -889,9 +889,8 @@
         <div className="content-fence">
           <div className="content-fence-left" style={images[0] ? {textAlign: images[0].align} : null}>
             {images.map((_img, index) => {
-              if (!_img.url) return ''
               if (_img.scale) {
-                return <img style={{maxHeight: _img.maxHeight}} className="image-scale" onClick={this.imgScale} key={`${index}`} src={_img.url} alt=""/>
+                return <img style={{maxHeight: _img.maxHeight}} className="image-scale" onClick={() => this.imgScale(images, index)} key={`${index}`} src={_img.url} alt=""/>
               } else {
                 return (<img style={{maxHeight: _img.maxHeight}} key={`${index}`} src={_img.url} alt=""/>)
               }
@@ -910,18 +909,19 @@
   /**
    * @description 鍥剧墖缂╂斁
    */
-  imgScaleClose = () => {
+  imgScale = (images, index) => {
     this.setState({
-     imgShow: false
+      imgShow: true,
+      imgData: {
+        list: images.map(item => {
+          if (typeof(item) === 'string') {
+            return item
+          }
+          return item.url
+        }),
+        index
+      }
     })
-  }
-  imgScale = (e) => {
-    if (e.target.nodeName === 'IMG') {
-      this.setState({
-        imgShow: true,
-        imgSrc: e.target.src
-      })
-    }
   }
 
   /**
@@ -1237,12 +1237,12 @@
           visible={this.state.imgShow}
           width="70vw"
           maskClosable={true}
-          onCancel={this.imgScaleClose}
+          onCancel={() => {this.setState({ imgShow: false })}}
           title={this.props.dict['main.form.picture.check']}
-          footer={[<span key="close" onClick={this.imgScaleClose}>{this.props.dict['main.close']}</span>]}
+          footer={[<span key="close" onClick={() => {this.setState({ imgShow: false })}}>{this.props.dict['main.close']}</span>]}
           destroyOnClose
         >
-         <img style={{maxWidth:'100%'}} src={this.state.imgSrc} alt="" />
+          <ImgScale data={this.state.imgData}/>
         </Modal>
       </div>
     )
diff --git a/src/templates/treepageconfig/index.jsx b/src/templates/treepageconfig/index.jsx
index 8a4b4dd..d1aa9bd 100644
--- a/src/templates/treepageconfig/index.jsx
+++ b/src/templates/treepageconfig/index.jsx
@@ -501,6 +501,7 @@
       }
 
       let submenu = menu.fstMenuList.filter(item => item.MenuID === config.fstMenuId)[0]
+
       let _Menu = {
         ...menu,
         LongParam: config,
@@ -509,7 +510,7 @@
         MenuNo: config.MenuNo,
         ParentId: config.ParentId,
         fstMenuId: config.fstMenuId,
-        supMenuList: submenu ? submenu.options : []
+        supMenuList: submenu ? submenu.children : []
       }
 
       _Menu.activeKey = activeKey       // 淇濆瓨褰撳墠鎵撳紑椤电
diff --git a/src/templates/zshare/formconfig.jsx b/src/templates/zshare/formconfig.jsx
index 95f77fc..1a91cb1 100644
--- a/src/templates/zshare/formconfig.jsx
+++ b/src/templates/zshare/formconfig.jsx
@@ -2453,14 +2453,29 @@
       type: 'radio',
       key: 'cursor',
       label: '鍏夋爣',
-      initVal: card.cursor || 'right',
+      initVal: card.cursor || 'left',
       options: [{
         value: 'right',
         text: '鍙冲榻�'
       }, {
         value: 'left',
         text: '宸﹀榻�'
-      }]
+      }],
+      forbid: appType !== 'mob'
+    },
+    {
+      type: 'radio',
+      key: 'scan',
+      label: '鎵爜',
+      initVal: card.scan || 'false',
+      options: [{
+        value: 'true',
+        text: '鍚敤'
+      }, {
+        value: 'false',
+        text: '涓嶅惎鐢�'
+      }],
+      forbid: appType !== 'mob'
     },
     {
       type: 'radio',
diff --git a/src/views/menudesign/index.jsx b/src/views/menudesign/index.jsx
index b01efb6..1297c91 100644
--- a/src/views/menudesign/index.jsx
+++ b/src/views/menudesign/index.jsx
@@ -863,6 +863,8 @@
           error = `缁勪欢銆�${item.name}銆嬫樉绀哄�煎皻鏈缃紒`
         } else if (item.type === 'scatter' && (!item.plot.Xaxis || !item.plot.Yaxis || !item.plot.gender)) {
           error = `缁勪欢銆�${item.name}銆嬪潗鏍囪酱灏氭湭璁剧疆锛乣
+        } else if (item.type === 'tree' && (!item.wrap.valueField || !item.wrap.labelField || !item.wrap.parentField)) {
+          error = `缁勪欢銆�${item.name}銆嬪熀鏈俊鎭皻鏈缃紒`
         }
       })
     }
diff --git a/src/views/pcdesign/index.jsx b/src/views/pcdesign/index.jsx
index 5a249a6..a03874f 100644
--- a/src/views/pcdesign/index.jsx
+++ b/src/views/pcdesign/index.jsx
@@ -1350,6 +1350,8 @@
           error = `缁勪欢銆�${item.name}銆嬫樉绀哄�煎皻鏈缃紒`
         } else if (item.type === 'scatter' && (!item.plot.Xaxis || !item.plot.Yaxis || !item.plot.gender)) {
           error = `缁勪欢銆�${item.name}銆嬪潗鏍囪酱灏氭湭璁剧疆锛乣
+        } else if (item.type === 'tree' && (!item.wrap.valueField || !item.wrap.labelField || !item.wrap.parentField)) {
+          error = `缁勪欢銆�${item.name}銆嬪熀鏈俊鎭皻鏈缃紒`
         }
       })
     }

--
Gitblit v1.8.0