From 772308eba58d2209e7cc108dc5567ef4ca7ee3b2 Mon Sep 17 00:00:00 2001
From: king <18310653075@163.com>
Date: 星期四, 15 四月 2021 16:17:20 +0800
Subject: [PATCH] 2021-04-15

---
 src/assets/mobimg/scatter.png                                              |    0 
 src/tabviews/custom/components/chart/antv-scatter/index.jsx                |  442 ++++++++++++++++
 src/tabviews/custom/components/chart/antv-bar-line/index.jsx               |   11 
 src/tabviews/custom/components/chart/antv-dashboard/index.jsx              |   26 
 src/tabviews/custom/components/chart/antv-scatter/index.scss               |   65 ++
 src/menu/components/group/groupcomponents/index.jsx                        |    1 
 src/menu/components/chart/antv-scatter/chartcompile/index.jsx              |  271 ++++++++++
 src/menu/components/chart/antv-scatter/chartcompile/formconfig.jsx         |  137 +++++
 src/menu/components/chart/antv-bar/index.jsx                               |   25 
 src/menu/components/chart/antv-scatter/index.jsx                           |  390 ++++++++++++++
 src/menu/components/tabs/tabcomponents/index.jsx                           |    1 
 src/views/billprint/index.jsx                                              |    1 
 src/menu/menushell/card.jsx                                                |    3 
 src/menu/components/chart/antv-scatter/chartcompile/index.scss             |   41 +
 src/views/mobdesign/index.jsx                                              |    2 
 src/views/menudesign/index.jsx                                             |    2 
 src/menu/modulesource/option.jsx                                           |    2 
 src/menu/components/chart/antv-bar/chartcompile/formconfig.jsx             |   13 
 src/menu/components/share/pastecomponent/index.jsx                         |   10 
 src/tabviews/custom/components/share/tabtransfer/index.jsx                 |    7 
 src/views/pcdesign/index.jsx                                               |    2 
 src/menu/components/chart/antv-scatter/index.scss                          |   65 ++
 src/tabviews/custom/components/group/normal-group/index.jsx                |    7 
 src/tabviews/custom/components/chart/antv-scatter/asyncButtonComponent.jsx |   34 +
 src/tabviews/custom/index.jsx                                              |    7 
 src/menu/menushell/index.jsx                                               |    1 
 26 files changed, 1,525 insertions(+), 41 deletions(-)

diff --git a/src/assets/mobimg/scatter.png b/src/assets/mobimg/scatter.png
new file mode 100644
index 0000000..dfeb28a
--- /dev/null
+++ b/src/assets/mobimg/scatter.png
Binary files differ
diff --git a/src/menu/components/chart/antv-bar/chartcompile/formconfig.jsx b/src/menu/components/chart/antv-bar/chartcompile/formconfig.jsx
index 29d0d30..8a0c978 100644
--- a/src/menu/components/chart/antv-bar/chartcompile/formconfig.jsx
+++ b/src/menu/components/chart/antv-bar/chartcompile/formconfig.jsx
@@ -378,16 +378,9 @@
       type: 'color',
       key: 'color',
       label: '鑹茬郴',
-      initVal: card.color || 'rgba(0, 0, 0, 0.85)',
-      tooltip: '鍧愭爣杞村強绀轰緥绛夋彁绀烘枃瀛椾娇鐢ㄧ殑棰滆壊銆�',
-      required: false,
-      options: [{
-        value: 'black',
-        text: '榛戣壊'
-      }, {
-        value: 'white',
-        text: '鐧借壊'
-      }]
+      initVal: card.color || 'rgba(0, 0, 0, 0.65)',
+      tooltip: '鍧愭爣杞存彁绀烘枃瀛楀強绀轰緥鐨勯鑹层��',
+      required: false
     }
   ]
 }
diff --git a/src/menu/components/chart/antv-bar/index.jsx b/src/menu/components/chart/antv-bar/index.jsx
index a80aa93..2c27c5f 100644
--- a/src/menu/components/chart/antv-bar/index.jsx
+++ b/src/menu/components/chart/antv-bar/index.jsx
@@ -51,6 +51,7 @@
         width: card.width || 24,
         height: 400,
         barSize: 35,
+        color: 'rgba(0, 0, 0, 0.65)',
         name: card.name
       }
 
@@ -195,7 +196,7 @@
   linerender = () => {
     const { card } = this.state
     let plot = {...card.plot, height: card.plot.height - 80} // 鍘婚櫎title鎵�鍗犵┖闂�
-    let color = plot.color || 'rgba(0, 0, 0, 0.85)'
+    let color = plot.color || 'rgba(0, 0, 0, 0.65)'
     let X_axis = plot.Xaxis || 'x'
     let Y_axis = plot.Yaxis || ['y']
 
@@ -236,8 +237,10 @@
   
       chart.data(dv.rows)
 
-      chart.axis(X_axis, { label: { style: { fill: color } }, line: { style: { fill: color } } })
-      chart.axis('value', { grid: { style: { fill: color } }, label: { style: { fill: color } } })
+      // chart.axis(X_axis, { label: { style: { fill: color } }, tickLine: {style: { stroke: color }}, line: { style: { stroke: color } } })
+      // chart.axis('value', { grid: { line: { style: { stroke: color } }}, label: { style: { fill: color } } })
+      chart.axis(X_axis, { label: { style: { fill: color } } })
+      chart.axis('value', { label: { style: { fill: color } } })
   
       if (plot.coordinate !== 'polar') {
         chart.scale(X_axis, {
@@ -349,7 +352,7 @@
   customrender = (data) => {
     let card = fromJS(this.state.card).toJS()
     let plot = {...card.plot, height: card.plot.height - 80} // 鍘婚櫎title鎵�鍗犵┖闂�
-    let color = plot.color || 'rgba(0, 0, 0, 0.85)'
+    let color = plot.color || 'rgba(0, 0, 0, 0.65)'
     let fields = []
     let legends = []
     let transfield = {}
@@ -396,7 +399,8 @@
 
       if (item.axis === 'true' && axisIndex < 2) {
         if (axisIndex === 0) {
-          item.axis = { grid: {style: { fill: color }}, title: { style: { fill: color } }, label: {style: { fill: color }} }
+          // item.axis = { grid: {line: { style: { stroke: color } }}, title: { style: { fill: color } }, label: {style: { fill: color }} }
+          item.axis = { title: { style: { fill: color } }, label: {style: { fill: color }} }
           fields.unshift(item)
         } else {
           item.axis = { grid: null, title: {style: { fill: color }}, label: {style: { fill: color }} }
@@ -435,7 +439,8 @@
 
     chart.data(dv.rows)
 
-    chart.axis(plot.Xaxis, { label: { style: { fill: color } }, line: { style: { fill: color } } })
+    // chart.axis(plot.Xaxis, { label: { style: { fill: color } }, tickLine: {style: { stroke: color }}, line: { style: { stroke: color } } })
+    chart.axis(plot.Xaxis, { label: { style: { fill: color } } })
 
     if (!hasBar) {
       chart.scale(plot.Xaxis, {
@@ -556,7 +561,7 @@
   barrender = () => {
     const { card } = this.state
     let plot = {...card.plot, height: card.plot.height - 80}
-    let color = plot.color || 'rgba(0, 0, 0, 0.85)'
+    let color = plot.color || 'rgba(0, 0, 0, 0.65)'
     let X_axis = plot.Xaxis || 'x'
     let Y_axis = plot.Yaxis || ['y']
 
@@ -598,8 +603,10 @@
   
       chart.data(dv.rows)
 
-      chart.axis(X_axis, { label: { style: { fill: color } }, line: { style: { fill: color } } })
-      chart.axis('value', { grid: { style: { fill: color } }, label: { style: { fill: color } } })
+      // chart.axis(X_axis, { label: { style: { fill: color } }, tickLine: {style: { stroke: color }}, line: { style: { stroke: color } } })
+      // chart.axis('value', { grid: { line: { style: { stroke: color } }}, label: { style: { fill: color } } })
+      chart.axis(X_axis, { label: { style: { fill: color } } })
+      chart.axis('value', { label: { style: { fill: color } } })
   
       chart.scale('value', {
         nice: true,
diff --git a/src/menu/components/chart/antv-scatter/chartcompile/formconfig.jsx b/src/menu/components/chart/antv-scatter/chartcompile/formconfig.jsx
new file mode 100644
index 0000000..e6a8a2f
--- /dev/null
+++ b/src/menu/components/chart/antv-scatter/chartcompile/formconfig.jsx
@@ -0,0 +1,137 @@
+/**
+ * @description 鑾峰彇鍥捐〃瑙嗗浘閰嶇疆琛ㄥ崟
+ * @param {object} card       // 鍥捐〃瀵硅薄
+ */
+export function getBaseForm (card) {
+  let roleList = sessionStorage.getItem('sysRoles')
+  if (roleList) {
+    try {
+      roleList = JSON.parse(roleList)
+    } catch {
+      roleList = []
+    }
+  } else {
+    roleList = []
+  }
+
+  return [
+    {
+      type: 'text',
+      key: 'title',
+      label: '鏍囬',
+      initVal: card.title,
+      required: false
+    },
+    {
+      type: 'text',
+      key: 'name',
+      label: '缁勪欢鍚嶇О',
+      initVal: card.name,
+      tooltip: '鐢ㄤ簬缁勪欢闂寸殑鍖哄垎銆�',
+      required: true
+    },
+    {
+      type: 'number',
+      key: 'width',
+      label: '瀹藉害',
+      initVal: card.width,
+      tooltip: '鏍呮牸甯冨眬锛屾瘡琛岀瓑鍒嗕负24鍒椼��',
+      min: 1,
+      max: 24,
+      decimal: 0,
+      required: true
+    },
+    {
+      type: 'number',
+      key: 'height',
+      label: '楂樺害',
+      initVal: card.height,
+      min: 100,
+      max: 1000,
+      decimal: 0,
+      required: true
+    },
+    {
+      type: 'select',
+      key: 'blacklist',
+      label: '榛戝悕鍗�',
+      initVal: card.blacklist || [],
+      multi: true,
+      required: false,
+      options: roleList
+    }
+  ]
+}
+
+/**
+ * @description 鑾峰彇鍥捐〃瑙嗗浘閰嶇疆琛ㄥ崟
+ * @param {object} card       // 鍥捐〃瀵硅薄
+ * @param {Array}  columns    // 鏄剧ず鍒�
+ */
+export function getOptionForm (card, columns) {
+  let xfields = columns.filter(item => /^Nvarchar/ig.test(item.datatype))
+  let yfields = columns.filter(item => /^(Int|Decimal)/ig.test(item.datatype))
+
+  return [
+    {
+      type: 'select',
+      key: 'gender',
+      label: '绫诲瀷',
+      initVal: card.gender || '',
+      required: true,
+      options: xfields
+    },
+    {
+      type: 'select',
+      key: 'Xaxis',
+      label: 'X-杞�',
+      initVal: card.Xaxis || '',
+      required: true,
+      options: columns
+    },
+    {
+      type: 'select',
+      key: 'Yaxis',
+      label: 'Y-杞�',
+      initVal: card.Yaxis || '',
+      required: true,
+      options: yfields
+    },
+    {
+      type: 'radio',
+      key: 'shape',
+      label: '褰㈢姸',
+      initVal: card.shape || 'circle',
+      required: false,
+      options: [{
+        value: 'circle',
+        text: 'circle'
+      }, {
+        value: 'square',
+        text: 'square'
+      }]
+    },
+    {
+      type: 'text',
+      key: 'Xunit',
+      label: 'X杞村崟浣�',
+      initVal: card.Xunit || '',
+      required: false
+    },
+    {
+      type: 'text',
+      key: 'Yunit',
+      label: 'Y杞村崟浣�',
+      initVal: card.Yunit || '',
+      required: false
+    },
+    {
+      type: 'color',
+      key: 'color',
+      label: '鑹茬郴',
+      initVal: card.color || 'rgba(0, 0, 0, 0.65)',
+      tooltip: '鍧愭爣杞存彁绀烘枃瀛楀強绀轰緥鐨勯鑹层��',
+      required: false
+    }
+  ]
+}
diff --git a/src/menu/components/chart/antv-scatter/chartcompile/index.jsx b/src/menu/components/chart/antv-scatter/chartcompile/index.jsx
new file mode 100644
index 0000000..0982251
--- /dev/null
+++ b/src/menu/components/chart/antv-scatter/chartcompile/index.jsx
@@ -0,0 +1,271 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import { fromJS } from 'immutable'
+import { Modal, Form, Row, Col, Select, Icon, Radio, Tooltip, Input, InputNumber, Tabs } from 'antd'
+
+import { getBaseForm, getOptionForm } from './formconfig'
+import asyncComponent from '@/utils/asyncComponent'
+import ColorSketch from '@/mob/colorsketch'
+import './index.scss'
+
+const { TabPane } = Tabs
+const NormalForm = asyncComponent(() => import('@/menu/components/share/normalform'))
+
+class LineChartDrawerForm extends Component {
+  static propTpyes = {
+    dict: PropTypes.object,
+    plot: PropTypes.object,
+    config: PropTypes.object,
+    plotchange: PropTypes.func
+  }
+
+  state = {
+    view: 'normal',
+    visible: false,
+    plot: null,
+    formlist: null,
+    baseFormlist: null
+  }
+
+  showDrawer = () => {
+    const { config } = this.props
+
+    this.setState({
+      visible: true,
+      view: 'normal',
+      plot: fromJS(config.plot).toJS(),
+      baseFormlist: getBaseForm(config.plot),
+      formlist: getOptionForm(config.plot, config.columns)
+    })
+  }
+
+  getFields() {
+    const { formlist } = this.state
+    const { getFieldDecorator } = this.props.form
+    const fields = []
+
+    if (!formlist) {
+      return fields
+    }
+
+    formlist.forEach((item, index) => {
+      if (item.hidden || item.forbid) return
+      
+      if (item.type === 'text') {
+        fields.push(
+          <Col span={12} key={index}>
+            <Form.Item label={item.tooltip ?
+              <Tooltip placement="topLeft" title={item.tooltip}>
+                <Icon type="question-circle" />
+                {item.label}
+              </Tooltip> : item.label
+            }>
+              {getFieldDecorator(item.key, {
+                initialValue: item.initVal,
+                rules: [
+                  {
+                    required: !!item.required,
+                    message: this.props.dict['form.required.input'] + item.label + '!'
+                  }
+                ]
+              })(<Input placeholder="" autoComplete="off" disabled={item.readonly} onPressEnter={this.onSubmit}/>)}
+            </Form.Item>
+          </Col>
+        )
+      } else if (item.type === 'number') {
+        fields.push(
+          <Col span={12} key={index}>
+            <Form.Item label={item.tooltip ?
+              <Tooltip placement="topLeft" title={item.tooltip}>
+                <Icon type="question-circle" />
+                {item.label}
+              </Tooltip> : item.label
+            }>
+              {getFieldDecorator(item.key, {
+                initialValue: item.initVal,
+                rules: [
+                  {
+                    required: !!item.required,
+                    message: this.props.dict['form.required.input'] + item.label + '!'
+                  }
+                ]
+              })(<InputNumber min={item.min} max={item.max} precision={item.decimal} onPressEnter={this.onSubmit}/>)}
+            </Form.Item>
+          </Col>
+        )
+      } else if (item.type === 'select') { // 涓嬫媺
+        fields.push(
+          <Col span={12} key={index}>
+            <Form.Item label={item.tooltip ?
+              <Tooltip placement="topLeft" title={item.tooltip}>
+                <Icon type="question-circle" />
+                {item.label}
+              </Tooltip> : item.label
+            }>
+              {getFieldDecorator(item.key, {
+                initialValue: item.initVal,
+                rules: [
+                  {
+                    required: !!item.required,
+                    message: this.props.dict['form.required.select'] + item.label + '!'
+                  }
+                ]
+              })(
+                <Select mode={item.multi ? 'multiple' : ''}>
+                  {item.options.map((option, index) =>
+                    <Select.Option key={index} value={option.field || option.value}>
+                      {option.label || option.text}
+                    </Select.Option>
+                  )}
+                </Select>
+              )}
+            </Form.Item>
+          </Col>
+        )
+      } else if (item.type === 'radio') {
+        fields.push(
+          <Col span={12} key={index}>
+            <Form.Item label={item.tooltip ?
+              <Tooltip placement="topLeft" title={item.tooltip}>
+                <Icon type="question-circle" />
+                {item.label}
+              </Tooltip> : item.label
+            }>
+              {getFieldDecorator(item.key, {
+                initialValue: item.initVal,
+                rules: [
+                  {
+                    required: !!item.required,
+                    message: this.props.dict['form.required.select'] + item.label + '!'
+                  }
+                ]
+              })(
+                <Radio.Group disabled={item.readonly}>
+                  {item.options.map(option => {
+                    return (
+                      <Radio key={option.value} value={option.value}>{option.text}</Radio>
+                    )
+                  })}
+                </Radio.Group>
+              )}
+            </Form.Item>
+          </Col>
+        )
+      } else if (item.type === 'color') {
+        fields.push(
+          <Col span={12} key={index}>
+            <Form.Item label={item.tooltip ?
+              <Tooltip placement="topLeft" title={item.tooltip}>
+                <Icon type="question-circle" />
+                {item.label}
+              </Tooltip> : item.label
+            }>
+              {getFieldDecorator(item.key, {
+                initialValue: item.initVal
+              })(
+                <ColorSketch />
+              )}
+            </Form.Item>
+          </Col>
+        )
+      }
+    })
+    return fields
+  }
+
+  onSubmit = () => {
+    const { config } = this.props
+    const { plot, view } = this.state
+
+    if (view === 'normal') {
+      this.props.form.validateFieldsAndScroll((err, values) => {
+        if (!err) {
+          let _plot = {...plot, ...values}
+
+          this.setState({
+            plot: _plot,
+            visible: false
+          })
+
+          this.props.plotchange({...config, plot: _plot})
+        }
+      })
+    } else if (view === 'base') {
+      this.baseRef.handleConfirm().then(res => {
+        let _plot = {...plot, ...res}
+
+        this.setState({
+          plot: _plot,
+          visible: false
+        })
+
+        this.props.plotchange({...config, plot: _plot})
+      })
+    }
+  }
+
+  changeTab = (tab) => {
+    const { plot, view } = this.state
+
+    if (view === 'normal') {
+      this.props.form.validateFieldsAndScroll((err, values) => {
+        if (!err) {
+          this.setState({
+            plot: {...plot, ...values},
+            view: tab
+          })
+        }
+      })
+    } else if (view === 'base') {
+      this.baseRef.handleConfirm().then(res => {
+        this.setState({
+          plot: {...plot, ...res},
+          view: tab
+        })
+      })
+    }
+  }
+
+  render() {
+    const { view, visible, baseFormlist } = this.state
+    const formItemLayout = {
+      labelCol: {
+        xs: { span: 24 },
+        sm: { span: 6 }
+      },
+      wrapperCol: {
+        xs: { span: 24 },
+        sm: { span: 18 }
+      }
+    }
+
+    return (
+      <div className="line-chart-drawer-form">
+        <Icon type="edit" title="缂栬緫" onClick={this.showDrawer} />
+        <Modal
+          wrapClassName="popview-modal menu-chart-edit-modal"
+          title="鍥捐〃缂栬緫"
+          visible={visible}
+          width={850}
+          maskClosable={false}
+          onOk={this.onSubmit}
+          onCancel={() => { this.setState({ visible: false }) }}
+          destroyOnClose
+        >
+          <Tabs activeKey={view} className="menu-chart-edit-box" onChange={this.changeTab}>
+            <TabPane tab="缁勪欢璁剧疆" key="base">
+              <NormalForm dict={this.props.dict} formlist={baseFormlist} inputSubmit={this.onSubmit} wrappedComponentRef={(inst) => this.baseRef = inst}/>
+            </TabPane>
+            <TabPane tab="鍥捐〃璁剧疆" key="normal">
+              <Form {...formItemLayout}>
+                <Row gutter={16}>{this.getFields()}</Row>
+              </Form>
+            </TabPane>
+          </Tabs>
+        </Modal>
+      </div>
+    );
+  }
+}
+
+export default Form.create()(LineChartDrawerForm)
\ No newline at end of file
diff --git a/src/menu/components/chart/antv-scatter/chartcompile/index.scss b/src/menu/components/chart/antv-scatter/chartcompile/index.scss
new file mode 100644
index 0000000..5928db8
--- /dev/null
+++ b/src/menu/components/chart/antv-scatter/chartcompile/index.scss
@@ -0,0 +1,41 @@
+.line-chart-drawer-form {
+  display: inline-block;
+  > .anticon-edit {
+    color: #1890ff;
+  }
+}
+.menu-chart-edit-modal {
+  .ant-modal {
+    top: 50px;
+    .ant-modal-body {
+      max-height: calc(100vh - 190px);
+      min-height: 50vh;
+      padding-top: 10px;
+      .menu-chart-edit-box {
+        .anticon-question-circle {
+          color: #c49f47;
+          position: relative;
+          left: -3px;
+        }
+        .ant-input-number {
+          width: 100%;
+        }
+        .ant-tabs-nav-wrap {
+          text-align: center;
+        }
+        .color-sketch-block {
+          position: relative;
+          top: 5px;
+          width: 240px;
+        }
+        .color-add {
+          float: right;
+          margin-bottom: 10px;
+          position: relative;
+          z-index: 1;
+        }
+      }
+    }
+  }
+}
+
diff --git a/src/menu/components/chart/antv-scatter/index.jsx b/src/menu/components/chart/antv-scatter/index.jsx
new file mode 100644
index 0000000..5151695
--- /dev/null
+++ b/src/menu/components/chart/antv-scatter/index.jsx
@@ -0,0 +1,390 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import { is, fromJS } from 'immutable'
+import { Icon, Popover, notification } from 'antd'
+import { Chart } from '@antv/g2'
+
+import MKEmitter from '@/utils/events.js'
+import asyncComponent from '@/utils/asyncComponent'
+import asyncIconComponent from '@/utils/asyncIconComponent'
+import { resetStyle } from '@/utils/utils-custom.js'
+import Utils from '@/utils/utils.js'
+import zhCN from '@/locales/zh-CN/model.js'
+import enUS from '@/locales/en-US/model.js'
+import './index.scss'
+
+const SettingComponent = asyncIconComponent(() => import('@/menu/datasource'))
+const ChartCompileForm = asyncIconComponent(() => import('./chartcompile'))
+const LogComponent = asyncIconComponent(() => import('@/menu/components/share/logcomponent'))
+const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
+const PasteComponent = asyncIconComponent(() => import('@/menu/components/share/pastecomponent'))
+const NormalHeader = asyncComponent(() => import('@/menu/components/share/normalheader'))
+const ActionComponent = asyncComponent(() => import('@/menu/components/share/actioncomponent'))
+const UserComponent = asyncIconComponent(() => import('@/menu/components/share/usercomponent'))
+const ClockComponent = asyncIconComponent(() => import('@/menu/components/share/clockcomponent'))
+
+class antvScatterChart extends Component {
+  static propTpyes = {
+    card: PropTypes.object,
+    updateConfig: PropTypes.func,
+    deletecomponent: PropTypes.func,
+  }
+
+  state = {
+    dict: sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
+    card: null,
+    ismob: sessionStorage.getItem('appType') === 'mob',
+    eventListener: null
+  }
+
+  UNSAFE_componentWillMount () {
+    const { card } = this.props
+
+    if (card.isNew) {
+      let _plot = {
+        width: card.width || 24,
+        height: 400,
+        gender: '',
+        Xaxis: '',
+        Xunit: '',
+        Yaxis: '',
+        Yunit: '',
+        shape: 'circle',
+        color: 'rgba(0, 0, 0, 0.65)',
+        name: card.name
+      }
+
+      let _card = {
+        uuid: card.uuid,
+        type: card.type,
+        floor: card.floor,
+        tabId: card.tabId || '',
+        parentId: card.parentId || '',
+        format: 'array',   // 缁勪欢灞炴�� - 鏁版嵁鏍煎紡
+        pageable: false,   // 缁勪欢灞炴�� - 鏄惁鍙垎椤�
+        switchable: false, // 缁勪欢灞炴�� - 鏁版嵁鏄惁鍙垏鎹�
+        dataName: card.dataName || '',
+        width: _plot.width,
+        name: _plot.name,
+        subtype: card.subtype,
+        setting: { interType: 'system' },
+        style: {
+          borderWidth: '1px', borderColor: 'rgb(217, 217, 217)',
+          marginLeft: '8px', marginRight: '8px', marginTop: '8px', marginBottom: '8px'
+        },
+        headerStyle: { fontSize: '16px', borderBottomWidth: '1px', borderBottomColor: '#e8e8e8' },
+        columns: [],
+        scripts: [],
+        search: [],
+        action: [],
+        plot: _plot,
+        btnlog: [],
+      }
+
+      if (card.config) {
+        let config = fromJS(card.config).toJS()
+
+        _card.plot = config.plot
+        _card.plot.name = card.name
+        _card.style = config.style
+        _card.headerStyle = config.headerStyle
+
+        _card.action = config.action.map(col => {
+          col.uuid = Utils.getuuid()
+          return col
+        })
+        _card.search = config.search.map(col => {
+          col.uuid = Utils.getuuid()
+          return col
+        })
+      }
+      this.setState({
+        card: _card
+      })
+      this.props.updateConfig(_card)
+    } else {
+      this.setState({
+        card: fromJS(card).toJS()
+      })
+    }
+  }
+
+  componentDidMount () {
+    this.ponitrender()
+    MKEmitter.addListener('submitStyle', this.getStyle)
+    MKEmitter.addListener('tabsChange', this.handleTabsChange)
+  }
+
+  shouldComponentUpdate (nextProps, nextState) {
+    return !is(fromJS(this.state), fromJS(nextState))
+  }
+
+  /**
+   * @description 缁勪欢閿�姣侊紝娓呴櫎state鏇存柊锛屾竻闄ゅ揩鎹烽敭璁剧疆
+   */
+  componentWillUnmount () {
+    this.setState = () => {
+      return
+    }
+    MKEmitter.removeListener('submitStyle', this.getStyle)
+    MKEmitter.removeListener('tabsChange', this.handleTabsChange)
+  }
+
+  handleTabsChange = (parentId) => {
+    const { card } = this.state
+
+    if (parentId === card.parentId) {
+      let _element = document.getElementById(card.uuid + 'canvas')
+      if (_element) {
+        _element.innerHTML = ''
+      }
+
+      setTimeout(this.ponitrender, 100)
+    }
+  }
+
+  getdata = () => {
+    let data = []
+
+    for (let i = 0; i < 500; i++) {
+      let item = {}
+      let n = Math.random()
+      let m = Math.random()
+      if (n > 0.7) {
+        n = n * n
+      } else if (n < 0.3) {
+        n = Math.sqrt(n)
+      }
+      if (m > 0.7) {
+        m = m * m
+      } else if (m < 0.3) {
+        m = Math.sqrt(m)
+      }
+
+      if (i % 2 === 0) {
+        item.gender = 'male'
+        item.height = 160 + Math.floor(n * 35)
+        item.weight = 50 + Math.floor(m * 55)
+      } else {
+        item.gender = 'female'
+        item.height = 140 + Math.floor(n * 40)
+        item.weight = 41 + Math.floor(m * 45)
+      }
+
+      data.push(item)
+    }
+
+    return data
+  }
+
+  /**
+   * @description 鏁g偣鍥�
+   */
+  ponitrender = () => {
+    const { card } = this.state
+    const plot = card.plot
+    const data = this.getdata()
+
+    const chart = new Chart({
+      container: card.uuid + 'canvas',
+      autoFit: true,
+      height: plot.height - 80
+    })
+
+    chart.data(data);
+    chart.scale({
+      height: { nice: true },
+      weight: { nice: true },
+    })
+    
+    // chart.axis('height', { label: { style: { fill: plot.color } }, tickLine: {style: { stroke: plot.color }}, line: { style: { stroke: plot.color } } })
+    // chart.axis('weight', { grid: { line: { style: { stroke: plot.color } }}, label: { style: { fill: plot.color } } })
+    chart.axis('height', { label: { style: { fill: plot.color } } })
+    chart.axis('weight', { label: { style: { fill: plot.color } } })
+    chart.legend({
+      position: plot.legend || 'bottom',
+      itemName: { style: { fill: plot.color } }
+    })
+
+    chart.tooltip({
+      showTitle: false,
+      showCrosshairs: true,
+      crosshairs: {
+        type: 'xy',
+      }
+    })
+    chart
+      .point()
+      .position('height*weight')
+      .color('gender')
+      .shape(plot.shape)
+      .tooltip('gender*height*weight', (gender, height, weight) => {
+        return {
+          name: gender,
+          value: height + (plot.Xunit ? `(${plot.Xunit}), ` : ', ') + weight + (plot.Yunit ? `(${plot.Yunit})` : '')
+        };
+      })
+      .style({
+        fillOpacity: 0.85
+      })
+    chart.interaction('legend-highlight');
+    chart.render()
+  }
+
+  updateComponent = (component) => {
+    const card = fromJS(this.state.card).toJS()
+    let refresh = false
+    if (!is(fromJS(component.plot), fromJS(card.plot)) || !is(fromJS(component.style), fromJS(card.style))) {
+      let _element = document.getElementById(card.uuid + 'canvas')
+      if (_element) {
+        _element.innerHTML = ''
+      }
+      refresh = true
+    }
+
+    component.width = component.plot.width
+    component.name = component.plot.name
+    
+    this.setState({
+      card: component
+    }, () => {
+      if (refresh) {
+        setTimeout(() => {
+          this.ponitrender()
+        }, 100)
+      }
+    })
+    this.props.updateConfig(component)
+  }
+
+  addSearch = () => {
+    const { card } = this.state
+
+    let newcard = {}
+    newcard.uuid = Utils.getuuid()
+    newcard.focus = true
+
+    newcard.label = 'label'
+    newcard.initval = ''
+    newcard.type = 'select'
+    newcard.resourceType = '0'
+    newcard.options = []
+    newcard.setAll = 'false'
+    newcard.orderType = 'asc'
+    newcard.display = 'dropdown'
+    newcard.match = '='
+
+    // 娉ㄥ唽浜嬩欢-娣诲姞鎼滅储
+    MKEmitter.emit('addSearch', card.uuid, newcard)
+  }
+
+  addButton = () => {
+    const { card } = this.state
+
+    let newcard = {}
+    newcard.uuid = Utils.getuuid()
+    newcard.focus = true
+    
+    newcard.label = '瀵煎嚭Excel'
+    newcard.sqlType = ''
+    newcard.Ot = 'requiredSgl'
+    newcard.OpenType = 'excelOut'
+    newcard.icon = 'download'
+    newcard.class = 'dgreen'
+    newcard.intertype = card.setting.interType
+    newcard.innerFunc = card.setting.innerFunc || ''
+    newcard.sysInterface = card.setting.sysInterface || ''
+    newcard.outerFunc = card.setting.outerFunc || ''
+    newcard.interface = card.setting.interface || ''
+    newcard.execSuccess = 'grid'
+    newcard.execError = 'never'
+    newcard.popClose = 'never'
+    newcard.errorTime = 10
+    newcard.verify = null
+    newcard.show = 'icon'
+
+    // 娉ㄥ唽浜嬩欢-娣诲姞鎸夐挳
+    MKEmitter.emit('addButton', card.uuid, newcard)
+  }
+
+  changeStyle = () => {
+    const { card } = this.state
+
+    MKEmitter.emit('changeStyle', [card.uuid], ['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.updateComponent(_card)
+  }
+
+  handleLog = (type, logs, item) => {
+    let card = fromJS(this.state.card).toJS()
+
+    if (type === 'revert') {
+      card.action = card.action ? [...card.action, item] : [item]
+      card.btnlog = logs
+
+      this.setState({ card })
+      this.props.updateConfig(card)
+      notification.success({
+        top: 92,
+        message: '鎭㈠鎴愬姛锛�',
+        duration: 2
+      })
+    } else {
+      card.btnlog = logs
+      this.setState({ card })
+      this.props.updateConfig(card)
+      notification.success({
+        top: 92,
+        message: '娓呴櫎鎴愬姛锛�',
+        duration: 2
+      })
+    }
+  }
+
+  clickComponent = (e) => {
+    if (sessionStorage.getItem('style-control') === 'true' || sessionStorage.getItem('style-control') === 'component') {
+      e.stopPropagation()
+      MKEmitter.emit('clickComponent', this.state.card)
+    }
+  }
+
+  render() {
+    const { card, ismob } = this.state
+    let _style = resetStyle(card.style)
+
+    return (
+      <div className="menu-line-chart-edit-box" style={{..._style, height: card.plot.height || 400}} 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">
+            {!ismob ? <Icon className="plus" title="娣诲姞鎼滅储" onClick={this.addSearch} type="plus-circle" /> : null}
+            <Icon className="plus" title="娣诲姞鎸夐挳" onClick={this.addButton} type="plus-square" />
+            <ChartCompileForm config={card} dict={this.state.dict} plotchange={this.updateComponent}/>
+            <CopyComponent type="line" card={card}/>
+            <PasteComponent config={card} options={['action', 'search', 'form']} updateConfig={this.updateComponent} />
+            <Icon className="style" title="璋冩暣鏍峰紡" onClick={this.changeStyle} type="font-colors" />
+            <LogComponent btnlog={card.btnlog || []} handlelog={this.handleLog} />
+            <ClockComponent config={card} updateConfig={this.updateComponent}/>
+            <UserComponent config={card}/>
+            <Icon className="close" title="delete" type="delete" onClick={() => this.props.deletecomponent(card.uuid)} />
+            <SettingComponent config={card} updateConfig={this.updateComponent}/>
+          </div>
+        } trigger="hover">
+          <Icon type="tool" />
+        </Popover>
+        <ActionComponent type="chart" config={card} updateaction={this.updateComponent} />
+        <div className="canvas" id={card.uuid + 'canvas'}></div>
+      </div>
+    )
+  }
+}
+
+export default antvScatterChart
\ No newline at end of file
diff --git a/src/menu/components/chart/antv-scatter/index.scss b/src/menu/components/chart/antv-scatter/index.scss
new file mode 100644
index 0000000..9754b3c
--- /dev/null
+++ b/src/menu/components/chart/antv-scatter/index.scss
@@ -0,0 +1,65 @@
+.menu-line-chart-edit-box {
+  position: relative;
+  box-sizing: border-box;
+  background: #ffffff;
+  background-position: center center;
+  background-repeat: no-repeat;
+  background-size: cover;
+  
+  .canvas {
+    margin: 0px;
+    // padding: 20px 15px 15px;
+    padding: 15px;
+    letter-spacing: 0px;
+  }
+
+  .chart-header {
+    position: relative;
+    height: 45px;
+    border-bottom: 1px solid #e8e8e8;
+    overflow: hidden;
+    padding-right: 35px;
+
+    .chart-title {
+      text-decoration: inherit;
+      font-weight: inherit;
+      font-style: inherit;
+      float: left;
+      line-height: 45px;
+      margin-left: 10px;
+      position: relative;
+      z-index: 1;
+    }
+  }
+
+  >.anticon-tool {
+    position: absolute;
+    right: 1px;
+    top: 1px;
+    z-index: 2;
+    font-size: 16px;
+    padding: 5px;
+    cursor: pointer;
+    color: rgba(0, 0, 0, 0.85);
+    background: rgba(255, 255, 255, 0.55);
+  }
+
+  .model-menu-action-list {
+    position: absolute;
+    right: 0px;
+    z-index: 4;
+    font-size: 16px;
+  
+    .ant-row .anticon-plus {
+      float: right;
+    }
+  
+    .page-card {
+      float: right;
+    }
+  }
+}
+.menu-line-chart-edit-box:hover {
+  z-index: 1;
+  box-shadow: 0px 0px 4px #1890ff;
+}
diff --git a/src/menu/components/group/groupcomponents/index.jsx b/src/menu/components/group/groupcomponents/index.jsx
index 0baa248..acffe01 100644
--- a/src/menu/components/group/groupcomponents/index.jsx
+++ b/src/menu/components/group/groupcomponents/index.jsx
@@ -105,6 +105,7 @@
         table: '琛ㄦ牸',
         editor: '瀵屾枃鏈�',
         dashboard: '浠〃鐩�',
+        scatter: '鏁g偣鍥�',
         card: '鍗$墖'
       }
       let i = 1
diff --git a/src/menu/components/share/pastecomponent/index.jsx b/src/menu/components/share/pastecomponent/index.jsx
index 2d12bb3..85be01c 100644
--- a/src/menu/components/share/pastecomponent/index.jsx
+++ b/src/menu/components/share/pastecomponent/index.jsx
@@ -106,11 +106,7 @@
     const { options } = this.props
     this.pasteFormRef.handleConfirm().then(res => {
       if (!options.includes(res.copyType)) {
-        notification.warning({
-          top: 92,
-          message: '閰嶇疆淇℃伅鏍煎紡閿欒锛�',
-          duration: 5
-        })
+        notification.warning({ top: 92, message: '閰嶇疆淇℃伅鏍煎紡閿欒锛�', duration: 5 })
         return
       }
 
@@ -131,6 +127,10 @@
         config.action = config.action || []
         config.action = config.action.filter(item => !item.origin)
 
+        if (['line', 'bar', 'scatter'].includes(config.type) && !['excelOut', 'excelIn'].includes(res.OpenType)) {
+          notification.warning({ top: 92, message: '鍥捐〃涓笉鏀寔姝ょ被鎸夐挳锛�', duration: 5 })
+          return
+        }
         MKEmitter.emit('addButton', config.uuid, res)
       } else if (type === 'search' || type === 'form') {
         config.search = config.search || []
diff --git a/src/menu/components/tabs/tabcomponents/index.jsx b/src/menu/components/tabs/tabcomponents/index.jsx
index 9b34445..816547b 100644
--- a/src/menu/components/tabs/tabcomponents/index.jsx
+++ b/src/menu/components/tabs/tabcomponents/index.jsx
@@ -99,6 +99,7 @@
         editor: '瀵屾枃鏈�',
         carousel: '杞挱',
         dashboard: '浠〃鐩�',
+        scatter: '鏁g偣鍥�',
         card: '鍗$墖'
       }
       let i = 1
diff --git a/src/menu/menushell/card.jsx b/src/menu/menushell/card.jsx
index 17310a5..e086365 100644
--- a/src/menu/menushell/card.jsx
+++ b/src/menu/menushell/card.jsx
@@ -8,6 +8,7 @@
 const MainSearch = asyncComponent(() => import('@/menu/components/search/main-search'))
 const AntvPie = asyncComponent(() => import('@/menu/components/chart/antv-pie'))
 const AntvDashboard = asyncComponent(() => import('@/menu/components/chart/antv-dashboard'))
+const AntvScatter = asyncComponent(() => import('@/menu/components/chart/antv-scatter'))
 const AntvTabs = asyncComponent(() => import('@/menu/components/tabs/antv-tabs'))
 const DataCard = asyncComponent(() => import('@/menu/components/card/data-card'))
 const PropCard = asyncComponent(() => import('@/menu/components/card/prop-card'))
@@ -62,6 +63,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 === 'scatter') {
+      return (<AntvScatter card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
     } else if (card.type === 'form') {
       return (<NormalForm card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
     } else if (card.type === 'tabs') {
diff --git a/src/menu/menushell/index.jsx b/src/menu/menushell/index.jsx
index 5121c17..8a9fcfa 100644
--- a/src/menu/menushell/index.jsx
+++ b/src/menu/menushell/index.jsx
@@ -98,6 +98,7 @@
         carousel: '杞挱',
         form: '琛ㄥ崟',
         dashboard: '浠〃鐩�',
+        scatter: '鏁g偣鍥�',
         card: '鍗$墖'
       }
       let i = 1
diff --git a/src/menu/modulesource/option.jsx b/src/menu/modulesource/option.jsx
index 0161c6f..9bd7d81 100644
--- a/src/menu/modulesource/option.jsx
+++ b/src/menu/modulesource/option.jsx
@@ -18,6 +18,7 @@
 import Carousel1 from '@/assets/mobimg/carousel1.png'
 import form from '@/assets/mobimg/form.png'
 import dashboard from '@/assets/mobimg/dashboard.png'
+import scatter from '@/assets/mobimg/scatter.png'
 
 // 缁勪欢閰嶇疆淇℃伅
 export const menuOptions = [
@@ -38,6 +39,7 @@
   { type: 'menu', url: Pie1, component: 'pie', subtype: 'ring', title: '鐜浘', width: 12 },
   { type: 'menu', url: Pie2, component: 'pie', subtype: 'nightingale', title: '鍗椾竵鏍煎皵鍥�', width: 12 },
   { type: 'menu', url: dashboard, component: 'dashboard', subtype: 'dashboard', title: '浠〃鐩�', width: 12 },
+  { type: 'menu', url: scatter, component: 'scatter', subtype: 'scatter', title: '鏁g偣鍥�', width: 24 },
   { type: 'menu', url: Editor, component: 'editor', subtype: 'brafteditor', title: '瀵屾枃鏈�', width: 24 },
   { type: 'menu', url: SandBox, component: 'code', subtype: 'sandbox', title: '鑷畾涔�', width: 24 },
   { type: 'menu', url: group, component: 'group', subtype: 'normalgroup', title: '鍒嗙粍', width: 24, forbid: ['billPrint'] },
diff --git a/src/tabviews/custom/components/chart/antv-bar-line/index.jsx b/src/tabviews/custom/components/chart/antv-bar-line/index.jsx
index 01878f0..73dc126 100644
--- a/src/tabviews/custom/components/chart/antv-bar-line/index.jsx
+++ b/src/tabviews/custom/components/chart/antv-bar-line/index.jsx
@@ -117,7 +117,7 @@
       }
     })
 
-    _config.plot.color = _config.plot.color || 'rgba(0, 0, 0, 0.85)'
+    _config.plot.color = _config.plot.color || 'rgba(0, 0, 0, 0.65)'
 
     if (_config.plot.enabled === 'true' && _config.plot.customs && _config.plot.customs.length > 0) {
       let colors = new Map()
@@ -158,7 +158,7 @@
   
         if (item.axis === 'true' && axisIndex < 2) {
           if (axisIndex === 0) {
-            item.axis = { grid: {style: { fill: _config.plot.color }}, title: { style: { fill: _config.plot.color } }, label: {style: { fill: _config.plot.color }} }
+            item.axis = { title: { style: { fill: _config.plot.color } }, label: {style: { fill: _config.plot.color }} }
             fields.unshift(item)
           } else {
             item.axis = { grid: null, title: {style: { fill: _config.plot.color }}, label: {style: { fill: _config.plot.color }} }
@@ -846,9 +846,8 @@
         },
         style: { fill: plot.color }
       },
-      line: { style: { fill: plot.color } }
     })
-    chart.axis(_valfield, { grid: { style: { fill: plot.color } }, label: { style: { fill: plot.color } } })
+    chart.axis(_valfield, { label: { style: { fill: plot.color } } })
 
     if (!plot.legend || plot.legend === 'hidden') {
       chart.legend(false)
@@ -998,7 +997,6 @@
         },
         style: { fill: plot.color }
       },
-      line: { style: { fill: plot.color } }
     })
 
     if (!plot.legend || plot.legend === 'hidden') {
@@ -1199,9 +1197,8 @@
         },
         style: { fill: plot.color }
       },
-      line: { style: { fill: plot.color } }
     })
-    chart.axis(_valfield, { grid: { style: { fill: plot.color } }, label: { style: { fill: plot.color } } })
+    chart.axis(_valfield, { label: { style: { fill: plot.color } } })
 
     if (!plot.legend || plot.legend === 'hidden') {
       chart.legend(false)
diff --git a/src/tabviews/custom/components/chart/antv-dashboard/index.jsx b/src/tabviews/custom/components/chart/antv-dashboard/index.jsx
index 46a23ab..2fe7eb4 100644
--- a/src/tabviews/custom/components/chart/antv-dashboard/index.jsx
+++ b/src/tabviews/custom/components/chart/antv-dashboard/index.jsx
@@ -304,27 +304,33 @@
   }
 
   /**
-   * @description 楗煎浘娓叉煋
+   * @description 浠〃鐩樻覆鏌�
    */
   dashboardrender = () => {
     const { plot, chartId, data } = this.state
+
+    let _data = fromJS(data).toJS()
+    if (_data.value && _data.value > plot.maxValue) {
+      _data.value = plot.maxValue
+    }
+
     const chart = new Chart({
       container: chartId,
       autoFit: true,
       height: plot.height ? (plot.height - 80) : 320,
       padding: [0, 0, 0, 0],
     })
-    chart.data([data]);
+    chart.data([_data]);
     chart.scale('value', {
       min: 0,
       max: plot.maxValue,
       tickInterval: plot.tickInterval,
-    });
+    })
     chart.coordinate('polar', {
       startAngle: (-9 / 8) * Math.PI,
       endAngle: (1 / 8) * Math.PI,
       radius: 0.75,
-    });
+    })
 
     chart.axis('1', false);
     chart.axis('value', {
@@ -345,9 +351,9 @@
         }
       },
       grid: null,
-    });
-    chart.legend(false);
-    chart.tooltip(false);
+    })
+    chart.legend(false)
+    chart.tooltip(false)
     chart
       .point()
       .position('value*1')
@@ -371,7 +377,7 @@
         appear: {
           animation: 'fade-in'
         }
-      });
+      })
 
     // 缁樺埗浠〃鐩樿儗鏅�
     chart.annotation().arc({
@@ -394,7 +400,7 @@
           lineWidth: 18,
           lineDash: null,
         },
-      });
+      })
     } else {
       let start = 0
       plot.colors.forEach(item => {
@@ -438,7 +444,7 @@
           textAlign: 'center',
         },
         offsetY: 15,
-      });
+      })
     }
 
     chart.render()
diff --git a/src/tabviews/custom/components/chart/antv-scatter/asyncButtonComponent.jsx b/src/tabviews/custom/components/chart/antv-scatter/asyncButtonComponent.jsx
new file mode 100644
index 0000000..5fb9c1a
--- /dev/null
+++ b/src/tabviews/custom/components/chart/antv-scatter/asyncButtonComponent.jsx
@@ -0,0 +1,34 @@
+import React, {Component} from 'react'
+import { Button } from 'antd'
+
+/**
+ * @description 寮傛鍔犺浇妯″潡
+ * @param {*} importComponent
+ */
+export default function asyncComponent(importComponent) {
+  return class extends Component {
+    constructor(props) {
+      super(props)
+
+      this.state = {
+        component: null
+      }
+    }
+
+    async componentDidMount() {
+      const {default: component} = await importComponent()
+
+      this.setState({component})
+    }
+
+    // <Button className="loading-skeleton" disabled={true}></Button> // 楠ㄦ灦鎸夐挳
+    render() {
+      const C = this.state.component
+      const btn = this.props.btn || {}
+
+      return C ?
+        <C {...this.props} /> :
+        <Button icon={btn.OpenType === 'excelOut' ? 'download' : 'upload'} disabled={true} title={btn.label} style={{border: 0, background: 'transparent'}}></Button>
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/tabviews/custom/components/chart/antv-scatter/index.jsx b/src/tabviews/custom/components/chart/antv-scatter/index.jsx
new file mode 100644
index 0000000..892847a
--- /dev/null
+++ b/src/tabviews/custom/components/chart/antv-scatter/index.jsx
@@ -0,0 +1,442 @@
+import React, {Component} from 'react'
+import PropTypes from 'prop-types'
+import { is, fromJS } from 'immutable'
+import { Chart } from '@antv/g2'
+import { connect } from 'react-redux'
+import { Spin, Empty, notification } from 'antd'
+import moment from 'moment'
+
+import Api from '@/api'
+import Utils from '@/utils/utils.js'
+import asyncComponent from './asyncButtonComponent'
+import UtilsDM from '@/utils/utils-datamanage.js'
+import { modifyTabview } from '@/store/action'
+import MKEmitter from '@/utils/events.js'
+import './index.scss'
+
+const NormalHeader = asyncComponent(() => import('@/tabviews/custom/components/share/normalheader'))
+const ExcelOutButton = asyncComponent(() => import('@/tabviews/zshare/actionList/exceloutbutton'))
+const ExcelInButton = asyncComponent(() => import('@/tabviews/zshare/actionList/excelInbutton'))
+
+class ScatterChart 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,              // 鍥捐〃閰嶇疆淇℃伅
+    empty: true,               // 鍥捐〃鏁版嵁涓虹┖
+    loading: false,            // 鏁版嵁鍔犺浇鐘舵��
+    chartId: Utils.getuuid(),  // 鍥捐〃Id
+    sync: false,               // 鏄惁缁熶竴璇锋眰鏁版嵁
+    plot: null,                // 鍥捐〃璁剧疆
+    data: null,                // 鏁版嵁
+    search: null,              // 鎼滅储鏉′欢
+  }
+
+  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
+    }
+
+    if (config.plot.title || config.search.length > 0) {
+      _config.plot.height = _config.plot.height - 80
+    } else {
+      _config.plot.height = _config.plot.height - 30
+    }
+
+    _config.style = {..._config.style, minHeight: config.plot.height}
+
+    this.setState({
+      config: _config,
+      data: _data,
+      BID: BID || '',
+      empty: !_data,
+      arr_field: _config.columns.map(col => col.field).join(','),
+      plot: _config.plot,
+      sync: _sync,
+      search: Utils.initMainSearch(config.search),
+    }, () => {
+      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] || []
+      }
+
+      this.setState({sync: false, data: _data, empty: !_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)
+    MKEmitter.addListener('getexceloutparam', this.getexceloutparam)
+    MKEmitter.addListener('refreshByButtonResult', this.refreshByButtonResult)
+    this.handleTimer()
+  }
+
+  /**
+   * @description 缁勪欢閿�姣侊紝娓呴櫎state鏇存柊锛屾竻闄ゅ揩鎹烽敭璁剧疆
+   */
+  componentWillUnmount () {
+    clearTimeout(this.timer)
+    this.setState = () => {
+      return
+    }
+    MKEmitter.removeListener('reloadData', this.reloadData)
+    MKEmitter.removeListener('resetSelectLine', this.resetParentParam)
+    MKEmitter.removeListener('getexceloutparam', this.getexceloutparam)
+    MKEmitter.removeListener('refreshByButtonResult', this.refreshByButtonResult)
+  }
+
+  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)
+  }
+
+  /**
+   * @description 鎸夐挳鎵ц瀹屾垚鍚庨〉闈㈠埛鏂�
+   * @param {*} menuId     // 鑿滃崟Id
+   * @param {*} position   // 鍒锋柊浣嶇疆
+   * @param {*} btn        // 鎵ц鐨勬寜閽�
+   */
+  refreshByButtonResult = (menuId, position, btn) => {
+    const { config, BID } = this.state
+
+    if (config.uuid !== menuId) return
+
+    this.loadData()                                                            // 鏁版嵁鍒锋柊
+
+    if (btn.syncComponentId && btn.syncComponentId !== config.uuid && btn.syncComponentId !== config.setting.supModule) {
+      MKEmitter.emit('reloadData', btn.syncComponentId)                        // 鍚岀骇鏍囩鍒锋柊
+    }
+
+    if (position === 'mainline' && config.setting.supModule) {                 // 涓昏〃琛屽埛鏂�
+      MKEmitter.emit('reloadData', config.setting.supModule, (BID || 'empty'))
+    }
+  }
+
+  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 瀵煎嚭Excel鏃讹紝鑾峰彇椤甸潰鎼滅储鎺掑簭绛夊弬鏁�
+   */
+  getexceloutparam = (menuId, btnId) => {
+    const { mainSearch } = this.props
+    const { arr_field, config, search } = this.state
+
+    if (config.uuid !== menuId) return
+
+    let searches = search ? fromJS(search).toJS() : []
+    if (mainSearch && mainSearch.length > 0) { // 涓昏〃鎼滅储鏉′欢
+      let keys = searches.map(item => item.key.toLowerCase())
+      mainSearch.forEach(item => {
+        if (!keys.includes(item.key.toLowerCase())) {
+          searches.push(item)
+        }
+      })
+    }
+
+    MKEmitter.emit('execExcelout', config.uuid, btnId, {
+      arr_field: arr_field,
+      orderBy: config.setting.order || '',
+      search: searches,
+      menuName: config.name
+    })
+  }
+
+  /**
+   * @description 鏁版嵁鍔犺浇
+   */
+  async loadData (hastimer) {
+    const { mainSearch, menuType } = this.props
+    const { config, arr_field, BID, search } = this.state
+
+    if (config.setting.supModule && !BID) { // BID 涓嶅瓨鍦ㄦ椂锛屼笉鍋氭煡璇�
+      this.setState({
+        data: [],
+        empty: false,
+      }, () => {
+        this.handleData()
+      })
+      return
+    }
+
+    let searches = search ? fromJS(search).toJS() : []
+    if (mainSearch && mainSearch.length > 0) { // 涓昏〃鎼滅储鏉′欢
+      let keys = searches.map(item => item.key)
+      mainSearch.forEach(item => {
+        if (!keys.includes(item.key)) {
+          searches.push(item)
+        }
+      })
+    }
+
+    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,
+        empty: false,
+        loading: false
+      }, () => {
+        this.handleData()
+      })
+    } else {
+      this.setState({
+        loading: false,
+        timer: null
+      })
+      notification.error({
+        top: 92,
+        message: result.message,
+        duration: 10
+      })
+    }
+  }
+
+  /**
+   * @description 鏁版嵁棰勫鐞嗭紝缁熻鏁版嵁闇�瑕侀噸缃�
+   */
+  handleData = () => {
+    let _element = document.getElementById(this.state.chartId)
+    if (_element) {
+      _element.innerHTML = ''
+    }
+    this.scatterrender()
+  }
+
+  /**
+   * @description 鎶樼嚎鍥炬覆鏌�
+   */
+  scatterrender = () => {
+    const { plot, data, chartId } = this.state
+    const chart = new Chart({
+      container: chartId,
+      autoFit: true,
+      height: plot.height
+    })
+
+    chart.data(data);
+    chart.scale({
+      [plot.Xaxis]: { nice: true },
+      [plot.Yaxis]: { nice: true },
+    })
+    
+    chart.axis(plot.Xaxis, { label: { style: { fill: plot.color } } })
+    chart.axis(plot.Yaxis, { label: { style: { fill: plot.color } } })
+    chart.legend({
+      position: 'bottom',
+      itemName: { style: { fill: plot.color } }
+    })
+
+    chart.tooltip({
+      showTitle: false,
+      showCrosshairs: true,
+      crosshairs: {
+        type: 'xy',
+      }
+    })
+    chart
+      .point()
+      .position(`${plot.Xaxis}*${plot.Yaxis}`)
+      .color(plot.gender)
+      .shape(plot.shape)
+      .tooltip(`${plot.gender}*${plot.Xaxis}*${plot.Yaxis}`, (gender, height, weight) => {
+        return {
+          name: gender,
+          value: height + (plot.Xunit ? `(${plot.Xunit}), ` : ', ') + weight + (plot.Yunit ? `(${plot.Yunit})` : '')
+        };
+      })
+      .style({
+        fillOpacity: 0.85
+      })
+    chart.interaction('legend-highlight');
+    chart.render()
+  }
+
+  refreshSearch = (list) => {
+    this.setState({search: list}, () => {
+      this.loadData()
+    })
+  }
+
+  render() {
+    const { config, loading, empty, BID } = this.state
+
+    return (
+      <div className="custom-scatter-plot-box" style={config.style}>
+        {loading ?
+          <div className="loading-mask">
+            <div className="ant-spin-blur"></div>
+            <Spin />
+          </div> : null
+        }
+        <NormalHeader config={config} BID={BID} menuType={this.props.menuType} refresh={this.refreshSearch} />
+        <div className="canvas-wrap">
+          <div className="chart-action">
+            {config.action.map(item => {
+              if (item.OpenType === 'excelOut') {
+                return (
+                  <ExcelOutButton
+                    key={item.uuid}
+                    BID={BID}
+                    btn={item}
+                    show="icon"
+                    setting={config.setting}
+                  />
+                )
+              } else {
+                return (
+                  <ExcelInButton
+                    key={item.uuid}
+                    BID={BID}
+                    btn={item}
+                    show="icon"
+                    setting={config.setting}
+                  />
+                )
+              }
+            })}
+          </div>
+          <div className={'canvas' + (empty ? ' empty' : '')} id={this.state.chartId}></div>
+        </div>
+        {empty ? <Empty description={false}/> : null}
+      </div>
+    )
+  }
+}
+
+const mapStateToProps = (state) => {
+  return {
+    tabviews: state.tabviews,
+    permMenus: state.permMenus,
+  }
+}
+
+const mapDispatchToProps = (dispatch) => {
+  return {
+    modifyTabview: (tabviews) => dispatch(modifyTabview(tabviews))
+  }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(ScatterChart)
\ No newline at end of file
diff --git a/src/tabviews/custom/components/chart/antv-scatter/index.scss b/src/tabviews/custom/components/chart/antv-scatter/index.scss
new file mode 100644
index 0000000..c8f3c8c
--- /dev/null
+++ b/src/tabviews/custom/components/chart/antv-scatter/index.scss
@@ -0,0 +1,65 @@
+.custom-scatter-plot-box {
+  position: relative;
+  background: #ffffff;
+  background-position: center center;
+  background-repeat: no-repeat;
+  background-size: cover;
+  min-height: 100px;
+
+  .canvas-wrap {
+    margin: 0 0px;
+    position: relative;
+    .chart-action {
+      position: absolute;
+      top: 0px;
+      right: 5px;
+      z-index: 1;
+      .ant-btn {
+        float: right;
+      }
+    }
+  }
+
+  .canvas {
+    margin: 0;
+    padding: 20px 15px 15px;
+    letter-spacing: 0px;
+  }
+  .canvas.empty {
+    div {
+      opacity: 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;
+    }
+  }
+  
+  .g2-tooltip-list{
+    display: none;
+  }
+  .g2-tooltip-title + .g2-tooltip-list{
+    display: block;
+  }
+}
diff --git a/src/tabviews/custom/components/group/normal-group/index.jsx b/src/tabviews/custom/components/group/normal-group/index.jsx
index 066f8c7..9de350a 100644
--- a/src/tabviews/custom/components/group/normal-group/index.jsx
+++ b/src/tabviews/custom/components/group/normal-group/index.jsx
@@ -16,6 +16,7 @@
 const AntvBarAndLine = asyncComponent(() => import('@/tabviews/custom/components/chart/antv-bar-line'))
 const AntvPie = asyncComponent(() => import('@/tabviews/custom/components/chart/antv-pie'))
 const AntvDashboard = asyncComponent(() => import('@/tabviews/custom/components/chart/antv-dashboard'))
+const AntvScatter = asyncComponent(() => import('@/tabviews/custom/components/chart/antv-scatter'))
 const DataCard = asyncComponent(() => import('@/tabviews/custom/components/card/data-card'))
 const TableCard = asyncComponent(() => import('@/tabviews/custom/components/card/table-card'))
 const NormalTable = asyncComponent(() => import('@/tabviews/custom/components/table/normal-table'))
@@ -260,6 +261,12 @@
             <AntvDashboard config={item} data={data} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
           </Col>
         )
+      } else if (item.type === 'scatter') {
+        return (
+          <Col span={item.width} key={item.uuid}>
+            <AntvScatter config={item} data={data} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
+          </Col>
+        )
       } else if (item.type === 'card' && item.subtype === 'datacard') {
         return (
           <Col span={item.width} key={item.uuid}>
diff --git a/src/tabviews/custom/components/share/tabtransfer/index.jsx b/src/tabviews/custom/components/share/tabtransfer/index.jsx
index 855a989..8b8a49b 100644
--- a/src/tabviews/custom/components/share/tabtransfer/index.jsx
+++ b/src/tabviews/custom/components/share/tabtransfer/index.jsx
@@ -19,6 +19,7 @@
 const AntvDashboard = asyncComponent(() => import('@/tabviews/custom/components/chart/antv-dashboard'))
 const AntvTabs = asyncComponent(() => import('@/tabviews/custom/components/tabs/antv-tabs'))
 const DataCard = asyncComponent(() => import('@/tabviews/custom/components/card/data-card'))
+const AntvScatter = asyncComponent(() => import('@/tabviews/custom/components/chart/antv-scatter'))
 const TableCard = asyncComponent(() => import('@/tabviews/custom/components/card/table-card'))
 const NormalTable = asyncComponent(() => import('@/tabviews/custom/components/table/normal-table'))
 const PropCard = asyncComponent(() => import('@/tabviews/custom/components/card/prop-card'))
@@ -279,6 +280,12 @@
             <AntvDashboard config={item} data={data} BID={BID} mainSearch={mainSearch} menuType={menuType} />
           </Col>
         )
+      } else if (item.type === 'scatter') {
+        return (
+          <Col span={item.width} key={item.uuid}>
+            <AntvScatter config={item} data={data} BID={BID} mainSearch={mainSearch} menuType={menuType} />
+          </Col>
+        )
       } else if (item.type === 'search') {
         return (
           <Col span={item.width} key={item.uuid}>
diff --git a/src/tabviews/custom/index.jsx b/src/tabviews/custom/index.jsx
index ed3932d..d3c9587 100644
--- a/src/tabviews/custom/index.jsx
+++ b/src/tabviews/custom/index.jsx
@@ -22,6 +22,7 @@
 const AntvPie = asyncComponent(() => import('./components/chart/antv-pie'))
 const AntvTabs = asyncComponent(() => import('./components/tabs/antv-tabs'))
 const AntvDashboard = asyncComponent(() => import('./components/chart/antv-dashboard'))
+const AntvScatter = asyncComponent(() => import('./components/chart/antv-scatter'))
 const DataCard = asyncComponent(() => import('./components/card/data-card'))
 const PropCard = asyncComponent(() => import('./components/card/prop-card'))
 const NormalForm = asyncComponent(() => import('./components/form/normal-form'))
@@ -1006,6 +1007,12 @@
             <AntvPie config={item} data={data} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
           </Col>
         )
+      } else if (item.type === 'scatter') {
+        return (
+          <Col span={item.width} key={item.uuid}>
+            <AntvScatter config={item} data={data} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
+          </Col>
+        )
       } else if (item.type === 'dashboard') {
         return (
           <Col span={item.width} key={item.uuid}>
diff --git a/src/views/billprint/index.jsx b/src/views/billprint/index.jsx
index 66e09e1..eaa9f8b 100644
--- a/src/views/billprint/index.jsx
+++ b/src/views/billprint/index.jsx
@@ -502,6 +502,7 @@
       let linkList = document.getElementsByTagName('link')     // 鑾峰彇鐖剁獥鍙ink鏍囩瀵硅薄鍒楄〃
       let styleList = document.getElementsByTagName('style')   // 鑾峰彇鐖剁獥鍙tyle鏍囩瀵硅薄鍒楄〃
 
+      iframe.style.marginTop = '600px'
       document.body.appendChild(iframe)
       let doc = iframe.contentWindow.document
       
diff --git a/src/views/menudesign/index.jsx b/src/views/menudesign/index.jsx
index 825a9ca..c4da4dd 100644
--- a/src/views/menudesign/index.jsx
+++ b/src/views/menudesign/index.jsx
@@ -859,6 +859,8 @@
           }
         } else if (item.type === 'dashboard' && !item.plot.valueField) {
           error = `缁勪欢銆�${item.name}銆嬫樉绀哄�煎皻鏈缃紒`
+        } else if (item.type === 'scatter' && (!item.plot.Xaxis || !item.plot.Yaxis || !item.plot.gender)) {
+          error = `缁勪欢銆�${item.name}銆嬪潗鏍囪酱灏氭湭璁剧疆锛乣
         }
       })
     }
diff --git a/src/views/mobdesign/index.jsx b/src/views/mobdesign/index.jsx
index 81fabf1..1db649d 100644
--- a/src/views/mobdesign/index.jsx
+++ b/src/views/mobdesign/index.jsx
@@ -1302,6 +1302,8 @@
           }
         } else if (item.type === 'dashboard' && !item.plot.valueField) {
           error = `缁勪欢銆�${item.name}銆嬫樉绀哄�煎皻鏈缃紒`
+        } else if (item.type === 'scatter' && (!item.plot.Xaxis || !item.plot.Yaxis || !item.plot.gender)) {
+          error = `缁勪欢銆�${item.name}銆嬪潗鏍囪酱灏氭湭璁剧疆锛乣
         }
       })
     }
diff --git a/src/views/pcdesign/index.jsx b/src/views/pcdesign/index.jsx
index 806e36d..8a56f67 100644
--- a/src/views/pcdesign/index.jsx
+++ b/src/views/pcdesign/index.jsx
@@ -1347,6 +1347,8 @@
           }
         } else if (item.type === 'dashboard' && !item.plot.valueField) {
           error = `缁勪欢銆�${item.name}銆嬫樉绀哄�煎皻鏈缃紒`
+        } else if (item.type === 'scatter' && (!item.plot.Xaxis || !item.plot.Yaxis || !item.plot.gender)) {
+          error = `缁勪欢銆�${item.name}銆嬪潗鏍囪酱灏氭湭璁剧疆锛乣
         }
       })
     }

--
Gitblit v1.8.0