king
2021-10-14 e41a64966b7832baffe96c21d1ea77ef6adb2905
2021-10-14
23个文件已修改
2个文件已添加
2770 ■■■■ 已修改文件
package-lock.json 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-bar/chartcompile/formconfig.jsx 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-dashboard/chartcompile/formconfig.jsx 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-pie/chartcompile/formconfig.jsx 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-scatter/chartcompile/formconfig.jsx 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/chart-custom/chartcompile/formconfig.jsx 434 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/chart-custom/chartcompile/index.jsx 623 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/chart-custom/chartcompile/index.scss 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/chart-custom/index.jsx 1123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/chart-custom/index.scss 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/group/groupcomponents/card.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/group/groupcomponents/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/normalform/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/tabs/tabcomponents/card.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/tabs/tabcomponents/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/menushell/card.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/menushell/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/modulesource/option.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/chart/custom-chart/index.jsx 382 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/chart/custom-chart/index.scss 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/group/normal-group/index.jsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/share/tabtransfer/index.jsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/index.jsx 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/menudesign/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json
@@ -4459,7 +4459,7 @@
      "dependencies": {
        "immutable": {
          "version": "3.7.6",
          "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz",
          "resolved": "http://registry.npm.taobao.org/immutable/download/immutable-3.7.6.tgz",
          "integrity": "sha1-E7TTyxK++hVIKib+Gy665kAHHks="
        }
      }
@@ -7541,11 +7541,19 @@
      }
    },
    "echarts": {
      "version": "4.5.0",
      "resolved": "https://registry.npmjs.org/echarts/-/echarts-4.5.0.tgz",
      "integrity": "sha512-q9M0errodeX/786uPifro76x0elbrUQkbSHh235QzbkaASuvP9AQoMErhGBno4iC/yq6kFDLqgmm3XCPWQGLzA==",
      "version": "5.2.1",
      "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.2.1.tgz",
      "integrity": "sha512-OJ79b22eqRfbSV8vYmDKmA+XWfNbr0Uk/OafWcFNIGDWti2Uw9A6eVCiJLmqPa9Sk+EWL+t5v26aak0z3gxiZw==",
      "requires": {
        "zrender": "4.1.2"
        "tslib": "2.3.0",
        "zrender": "5.2.1"
      },
      "dependencies": {
        "tslib": {
          "version": "2.3.0",
          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
          "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
        }
      }
    },
    "echarts-for-react": {
@@ -21347,9 +21355,19 @@
      }
    },
    "zrender": {
      "version": "4.1.2",
      "resolved": "https://registry.npmjs.org/zrender/-/zrender-4.1.2.tgz",
      "integrity": "sha512-MJYEo1ZOVesjxYsfcGtPXnUREmh4ACMV08QZLGZ3S7D1xOd96iz3O6nf6pv5PHb5NSHkbizr7ChSIgtAGwncvA=="
      "version": "5.2.1",
      "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.2.1.tgz",
      "integrity": "sha512-M3bPGZuyLTNBC6LiNKXJwSCtglMp8XUEqEBG+2MdICDI3d1s500Y4P0CzldQGsqpRVB7fkvf3BKQQRxsEaTlsw==",
      "requires": {
        "tslib": "2.3.0"
      },
      "dependencies": {
        "tslib": {
          "version": "2.3.0",
          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
          "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
        }
      }
    },
    "zscroller": {
      "version": "0.4.8",
package.json
@@ -31,7 +31,7 @@
    "dotenv": "6.2.0",
    "dotenv-expand": "4.2.0",
    "dragact": "^0.2.12",
    "echarts": "^4.5.0",
    "echarts": "^5.2.1",
    "echarts-for-react": "^2.0.15-beta.1",
    "eslint": "^6.1.0",
    "eslint-config-react-app": "^5.0.1",
src/menu/components/chart/antv-bar/chartcompile/formconfig.jsx
@@ -8,6 +8,7 @@
 * @param {object} card       // 图表对象
 */
export function getBaseForm (card) {
  let appType = sessionStorage.getItem('appType')
  let roleList = sessionStorage.getItem('sysRoles')
  if (roleList) {
    try {
@@ -63,7 +64,8 @@
      initVal: card.blacklist || [],
      multi: true,
      required: false,
      options: roleList
      options: roleList,
      forbid: !!appType
    },
    // {
    //   type: 'cascader',
src/menu/components/chart/antv-dashboard/chartcompile/formconfig.jsx
@@ -8,6 +8,7 @@
 * @param {object} card       // 图表对象
 */
export function getBaseForm (card) {
  let appType = sessionStorage.getItem('appType')
  let roleList = sessionStorage.getItem('sysRoles')
  if (roleList) {
    try {
@@ -63,7 +64,8 @@
      initVal: card.blacklist || [],
      multi: true,
      required: false,
      options: roleList
      options: roleList,
      forbid: !!appType
    }
  ]
}
src/menu/components/chart/antv-pie/chartcompile/formconfig.jsx
@@ -84,15 +84,6 @@
      required: true
    },
    {
      type: 'select',
      key: 'blacklist',
      label: '黑名单',
      initVal: card.blacklist || [],
      multi: true,
      required: false,
      options: roleList
    },
    {
      type: 'cascader',
      key: 'linkmenu',
      label: '关联菜单',
@@ -123,6 +114,16 @@
        { value: 'blank', text: '新窗口' },
        { value: 'self', text: '当前窗口' }
      ]
    },
    {
      type: 'select',
      key: 'blacklist',
      label: '黑名单',
      initVal: card.blacklist || [],
      multi: true,
      required: false,
      options: roleList,
      forbid: !!appType
    }
  ]
}
src/menu/components/chart/antv-scatter/chartcompile/formconfig.jsx
@@ -3,6 +3,7 @@
 * @param {object} card       // 图表对象
 */
export function getBaseForm (card) {
  let appType = sessionStorage.getItem('appType')
  let roleList = sessionStorage.getItem('sysRoles')
  if (roleList) {
    try {
@@ -58,7 +59,8 @@
      initVal: card.blacklist || [],
      multi: true,
      required: false,
      options: roleList
      options: roleList,
      forbid: !!appType
    }
  ]
}
src/menu/components/chart/chart-custom/chartcompile/formconfig.jsx
@@ -1,13 +1,9 @@
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
const Formdict = sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS
/**
 * @description 获取图表视图配置表单
 * @param {object} card       // 图表对象
 */
export function getBaseForm (card) {
  let appType = sessionStorage.getItem('appType')
  let roleList = sessionStorage.getItem('sysRoles')
  if (roleList) {
    try {
@@ -34,6 +30,17 @@
      initVal: card.name,
      tooltip: '用于组件间的区分。',
      required: true
    },
    {
      type: 'radio',
      key: 'chartType',
      label: '图表类型',
      initVal: card.chartType || 'antv',
      required: true,
      options: [
        { value: 'antv', text: 'antv' },
        { value: 'echarts', text: 'echarts' }
      ]
    },
    {
      type: 'number',
@@ -63,7 +70,8 @@
      initVal: card.blacklist || [],
      multi: true,
      required: false,
      options: roleList
      options: roleList,
      forbid: !!appType
    },
    // {
    //   type: 'cascader',
@@ -85,416 +93,6 @@
    //   forbid: appType !== 'pc',
    //   options: menulist
    // },
    // {
    //   type: 'radio',
    //   key: 'open',
    //   label: '打开方式',
    //   initVal: card.open || 'blank',
    //   required: false,
    //   forbid: appType !== 'pc',
    //   options: [
    //     { value: 'blank', text: '新窗口' },
    //     { value: 'self', text: '当前窗口' }
    //   ]
    // }
  ]
}
/**
 * @description 获取图表视图配置表单
 * @param {object} card       // 图表对象
 * @param {Array}  columns    // 显示列
 */
export function getOptionForm (card, columns) {
  let appType = sessionStorage.getItem('appType')
  let shapes = []
  if (card.chartType === 'line') {
    shapes = [
      { field: 'smooth', label: 'smooth' },
      { field: 'line', label: 'line' },
      { field: 'dot', label: 'dot' },
      { field: 'dash', label: 'dash' },
      { field: 'hv', label: 'hv' },
      { field: 'vh', label: 'vh' },
      { field: 'hvh', label: 'hvh' },
      { field: 'vhv', label: 'vhv' }
    ]
  } else if (card.chartType === 'bar') {
    shapes = [
      { field: 'rect', label: 'rect' },
      { field: 'hollow-rect', label: 'hollow-rect' },
      { field: 'line', label: 'line' },
      { field: 'tick', label: 'tick' },
      { field: 'funnel', label: 'funnel' },
      { field: 'pyramid', label: 'pyramid' }
    ]
  }
  let xfields = columns.filter(item => /^Nvarchar/ig.test(item.datatype))
  let yfields = columns.filter(item => /^(Int|Decimal)/ig.test(item.datatype))
  let labelOptions = [{
    value: 'false',
    text: '隐藏'
  }, {
    value: 'true',
    text: '显示'
  }]
  if (card.chartType === 'bar') {
    labelOptions[1].text = '外部'
    labelOptions.push(...[{
      value: 'top',
      text: '顶部'
    }, {
      value: 'middle',
      text: '中间'
    }, {
      value: 'bottom',
      text: '底部'
    }])
  }
  return [
    {
      type: 'radio',
      key: 'datatype',
      label: '数据类型',
      initVal: card.datatype || 'query',
      tooltip: '统计图表适用于展示数据类型为动态值。',
      required: false,
      options: [
        { value: 'query', text: Formdict['header.form.query'] },
        { value: 'statistics', text: Formdict['header.form.statistics'] }
      ]
    }, {
      type: 'select',
      key: 'Xaxis',
      label: 'X-轴',
      initVal: card.Xaxis || '',
      required: true,
      options: xfields
    }, {
      type: 'select',
      key: 'InfoType',
      label: '类型',
      initVal: card.InfoType || '',
      hidden: card.datatype !== 'statistics',
      required: true,
      options: xfields
    }, {
      type: 'select',
      key: 'InfoValue',
      label: '值',
      initVal: card.InfoValue || '',
      hidden: card.datatype !== 'statistics',
      required: true,
      options: yfields
    }, {
      type: 'select',
      key: 'legend',
      label: '图例位置',
      initVal: card.legend || 'bottom',
      required: false,
      options: [
        { field: 'bottom', label: '下' },
        { field: 'bottom-left', label: '下左' },
        { field: 'bottom-right', label: '下右' },
        { field: 'top', label: '上' },
        { field: 'top-left', label: '上左' },
        { field: 'top-right', label: '上右' },
        { field: 'right', label: '右' },
        { field: 'right-top', label: '右上' },
        { field: 'right-bottom', label: '右下' },
        { field: 'left', label: '左' },
        { field: 'left-top', label: '左上' },
        { field: 'left-bottom', label: '左下' },
        { field: 'hidden', label: '隐藏' }
      ]
    }, {
      type: 'select',
      key: 'Yaxis',
      label: 'Y-轴',
      initVal: card.Yaxis || [],
      multi: true, // 多选
      hidden: card.datatype === 'statistics',
      required: true,
      options: yfields
    }, {
      type: 'select',
      key: 'shape',
      label: '形状',
      initVal: card.shape || (shapes[0] && shapes[0].field),
      required: false,
      options: shapes
    }, {
      type: 'radio',
      key: 'tooltip',
      label: '悬浮提示',
      initVal: card.tooltip || 'true',
      required: false,
      options: [{
        value: 'true',
        text: '显示'
      }, {
        value: 'false',
        text: '隐藏'
      }]
    }, {
      type: 'radio',
      key: 'point',
      label: '点图',
      initVal: card.point || 'false',
      required: false,
      forbid: !['line'].includes(card.chartType),
      options: [{
        value: 'true',
        text: '显示'
      }, {
        value: 'false',
        text: '隐藏'
      }]
    }, {
      type: 'radio',
      key: 'transpose',
      label: '变换',
      initVal: card.transpose || 'false',
      tooltip: '横纵坐标轴交换',
      required: false,
      options: [{
        value: 'true',
        text: Formdict['model.true']
      }, {
        value: 'false',
        text: Formdict['model.false']
      }]
    }, {
      type: 'radio',
      key: 'show',
      label: '格式化',
      initVal: card.show || 'value',
      required: false,
      options: [{
        value: 'value',
        text: '无'
      }, {
        value: 'percent',
        text: '百分比'
      }]
    }, {
      type: labelOptions.length > 20 ? 'select' : 'radio',
      key: 'label',
      label: '标签',
      initVal: card.label || 'false',
      required: false,
      options: labelOptions
    }, {
      type: 'radio',
      key: 'labelColor',
      label: '标签颜色',
      initVal: card.labelColor || 'system',
      tooltip: '使用系统色时,使用色系选项设置的系统颜色,使用自定义为颜色设置中定义的图形颜色。',
      required: false,
      options: [{
        value: 'system',
        text: '系统'
      }, {
        value: 'custom',
        text: '自定义'
      }]
    // }, {
    //   type: 'radio',
    //   key: 'offset',
    //   label: '标注位置',
    //   initVal: card.offset || 'outer',
    //   required: false,
    //   options: [{
    //     value: 'outer',
    //     text: '外部'
    //   }, {
    //     value: 'inner',
    //     text: '内部'
    //   }],
    //   forbid: card.chartType !== 'bar'
    }, {
      type: 'radio',
      key: 'adjust',
      label: '多柱排列',
      initVal: card.adjust || 'dodge',
      required: false,
      forbid: !['bar'].includes(card.chartType),
      options: [{
        value: 'dodge',
        text: '分组'
      }, {
        value: 'stack',
        text: '堆叠'
      }]
    }, {
      type: 'radio',
      key: 'area',
      label: '面积图',
      initVal: card.area || 'false',
      // tooltip: '仅在形状为smooth时有效。',
      required: false,
      forbid: ['bar'].includes(card.chartType),
      options: [{
        value: 'true',
        text: '显示'
      }, {
        value: 'false',
        text: '不显示'
      }]
    }, {
      type: 'radio',
      key: 'repeat',
      label: '重复数据',
      initVal: card.repeat || 'unrepeat',
      required: false,
      options: [{
        value: 'unrepeat',
        text: '去重'
      }, {
        value: 'average',
        text: '平均'
      }, {
        value: 'cumsum',
        text: '累加'
      }]
    }, {
      type: 'radio',
      key: 'coordinate',
      label: '坐标',
      initVal: card.coordinate || 'angle',
      required: false,
      options: [{
        value: 'angle',
        text: '二维坐标'
      }, {
        value: 'polar',
        text: '极坐标'
      }]
    }, {
      type: 'radio',
      key: 'grid',
      label: '网格线',
      initVal: card.grid || 'show',
      required: false,
      options: [{
        value: 'show',
        text: '显示'
      }, {
        value: 'hidden',
        text: '隐藏'
      }]
    }, {
      type: 'radio',
      key: 'y_line',
      label: 'y轴边线',
      initVal: card.y_line || 'hidden',
      tooltip: '图形左侧或右侧的边线。',
      required: false,
      options: [{
        value: 'show',
        text: '显示'
      }, {
        value: 'hidden',
        text: '隐藏'
      }]
    }, {
      type: 'number',
      key: 'barSize',
      label: '柱形宽度',
      tooltip: '空值时,宽度自适应。',
      min: 5,
      max: 100,
      decimal: 0,
      initVal: card.barSize,
      forbid: !['bar'].includes(card.chartType),
      required: false
    }, {
      type: 'number',
      key: 'barRadius',
      label: '柱形圆角',
      tooltip: '柱形图上端圆角。',
      min: 0,
      max: 200,
      decimal: 0,
      initVal: card.barRadius || 0,
      forbid: !['bar'].includes(card.chartType),
      required: false
    }, {
      type: 'number',
      key: 'min',
      label: '最小值',
      tooltip: 'y轴最小值,为空时自适应。',
      initVal: card.min,
      required: false
    }, {
      type: 'number',
      key: 'max',
      label: '最大值',
      tooltip: 'y轴最大值,为空时自适应。',
      initVal: card.max,
      required: false
    }, {
      type: 'color',
      key: 'color',
      label: '色系',
      initVal: card.color || 'rgba(0, 0, 0, 0.65)',
      tooltip: '坐标轴提示文字及示例的颜色。',
      required: false
    }, {
      type: 'color',
      key: 'lineColor',
      label: '轴线颜色',
      initVal: card.lineColor,
      tooltip: '坐标轴线的颜色,包括x轴、y轴及网格线。',
      allowClear: true,
      required: false
    }, {
      type: 'color',
      key: 'selectColor',
      label: '选中颜色',
      initVal: card.selectColor || '',
      tooltip: '选中柱形图的颜色,在交互效果《元素选中(多选)》和《元素选中(单选)》中有效,自定义设置中无效。',
      forbid: !['bar'].includes(card.chartType),
      allowClear: true,
      required: false
    }, {
      type: 'number',
      key: 'rotate',
      label: '旋转',
      tooltip: '坐标轴标注文本的旋转角度。',
      min: 0,
      max: 360,
      decimal: 0,
      initVal: card.rotate,
      forbid: appType !== 'mob',
      required: false
    }, {
      type: 'select',
      key: 'interaction',
      label: '交互效果',
      initVal: card.interaction || [],
      multi: true,
      required: false,
      forbid: appType === 'mob',
      options: [
        { value: 'element-active', label: '元素聚焦' },
        { value: 'element-selected', label: '元素选中(多选)' },
        { value: 'element-single-selected', label: '元素选中(单选)' },
        { value: 'active-region', label: '背景框' },
        { value: 'view-zoom', label: '视图缩放' },
        { value: 'element-highlight', label: '元素高亮' },
        { value: 'element-highlight-by-color', label: '同色元素高亮' },
        { value: 'element-highlight-by-x', label: '同X轴元素高亮' },
        { value: 'legend-filter', label: '图例过滤' },
        { value: 'legend-active', label: '图例聚焦' },
        { value: 'legend-highlight', label: '图例高亮' },
        { value: 'brush', label: '选框过滤' },
      ]
    }
  ]
}
}
src/menu/components/chart/chart-custom/chartcompile/index.jsx
@@ -1,20 +1,17 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Modal, Form, Row, Col, Select, Icon, Radio, Tooltip, Input, InputNumber, Tabs, Button } from 'antd'
import { fromJS } from 'immutable'
import { Modal, Form, Icon, Tabs } from 'antd'
import Utils from '@/utils/utils.js'
import { chartColors } from '@/utils/option.js'
import { getBaseForm, getOptionForm } from './formconfig'
import { getBaseForm } from './formconfig'
import asyncComponent from '@/utils/asyncComponent'
import ColorSketch from '@/mob/colorsketch'
import './index.scss'
const { TabPane } = Tabs
const EditTable = asyncComponent(() => import('@/templates/zshare/editTable'))
const NormalForm = asyncComponent(() => import('@/menu/components/share/normalform'))
const CodeMirror = asyncComponent(() => import('@/templates/zshare/codemirror'))
class LineChartDrawerForm extends Component {
class CustomChartDrawerForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,
    plot: PropTypes.object,
@@ -23,464 +20,28 @@
  }
  state = {
    view: 'normal',
    ramp: 'false',
    view: 'base',
    visible: false,
    datatype: '',
    plot: null,
    formlist: null,
    baseFormlist: null,
    fieldName: null,
    colorColumns: [
      {
        title: '指标',
        dataIndex: 'label',
        editable: false,
        width: '40%'
      },
      {
        title: '颜色',
        dataIndex: 'color',
        inputType: 'color',
        editable: true,
        width: '40%',
        render: (text, record) => {
          return (<div style={{width: '80px', height: '23px', background: text}}></div>)
        }
      },
    ],
    rampColorColumns: [
      {
        title: '指标',
        dataIndex: 'label',
        editable: false,
        width: '20%'
      },
      {
        title: '颜色1',
        dataIndex: 'color',
        inputType: 'color',
        editable: true,
        width: '30%',
        render: (text, record) => {
          return (<div style={{width: '80px', height: '23px', background: text}}></div>)
        }
      },
      {
        title: '颜色2',
        dataIndex: 'color1',
        inputType: 'color',
        editable: true,
        width: '30%',
        render: (text, record) => {
          return (<div style={{width: '80px', height: '23px', background: text}}></div>)
        }
      },
    ],
    statColorColumns: [
      {
        title: '指标',
        dataIndex: 'type',
        inputType: 'input',
        editable: true,
        width: '40%'
      },
      {
        title: '颜色',
        dataIndex: 'color',
        inputType: 'color',
        editable: true,
        width: '40%',
        render: (text, record) => {
          return (<div style={{width: '80px', height: '23px', background: text}}></div>)
        }
      },
    ],
    rampStatColorColumns: [
      {
        title: '指标',
        dataIndex: 'type',
        inputType: 'input',
        editable: true,
        width: '20%'
      },
      {
        title: '颜色1',
        dataIndex: 'color',
        inputType: 'color',
        editable: true,
        width: '30%',
        render: (text, record) => {
          return (<div style={{width: '80px', height: '23px', background: text}}></div>)
        }
      },
      {
        title: '颜色2',
        dataIndex: 'color1',
        inputType: 'color',
        editable: true,
        width: '30%',
        render: (text, record) => {
          return (<div style={{width: '80px', height: '23px', background: text}}></div>)
        }
      },
    ],
    cusColumns: [
      {
        title: '指标',
        dataIndex: 'name',
        editable: false,
        width: '14%'
      },
      {
        title: '形状',
        dataIndex: 'shape',
        inputType: 'cascader',
        editable: true,
        width: '12%',
        render: (text, record) => {
          return text.join(' / ').replace('line', '折线').replace('bar', '柱形')
        },
        options: [
          {
            value: 'line',
            label: '折线',
            children: [
              { value: 'smooth', label: 'smooth' },
              { value: 'line', label: 'line' },
              { value: 'dot', label: 'dot' },
              { value: 'dash', label: 'dash' },
              { value: 'hv', label: 'hv' },
              { value: 'vh', label: 'vh' },
              { value: 'hvh', label: 'hvh' },
              { value: 'vhv', label: 'vhv' }
            ]
          },
          {
            value: 'bar',
            label: '柱形',
            children: [
              { value: 'rect', label: 'rect' },
              { value: 'hollow-rect', label: 'hollow-rect' },
              { value: 'line', label: 'line' },
              { value: 'tick', label: 'tick' },
              { value: 'funnel', label: 'funnel' },
              { value: 'pyramid', label: 'pyramid' }
            ],
          }
        ]
      },
      {
        title: '坐标轴',
        dataIndex: 'axis',
        inputType: 'select',
        editable: true,
        width: '12%',
        options: [
          { value: 'true', text: '显示'},
          { value: 'false', text: '隐藏'}
        ],
        render: (text, record) => {
          let trans = {'true': '显示', 'false': '隐藏'}
          return trans[text] || '隐藏'
        }
      },
      {
        title: '标注',
        dataIndex: 'label',
        inputType: 'select',
        editable: true,
        width: '12%',
        options: [
          { value: 'true', text: '显示'},
          { value: 'false', text: '隐藏'}
        ],
        render: (text, record) => {
          let trans = {'true': '显示', 'false': '隐藏'}
          return trans[text] || '隐藏'
        }
      },
      {
        title: '标题',
        dataIndex: 'title',
        inputType: 'select',
        editable: true,
        width: '12%',
        options: [
          { value: 'true', text: '显示'},
          { value: 'false', text: '隐藏'}
        ],
        render: (text, record) => {
          let trans = {'true': '显示', 'false': '隐藏'}
          return trans[text] || '显示'
        }
      },
      {
        title: '最小值',
        dataIndex: 'min',
        inputType: 'number',
        editable: true,
        required: false,
        width: '12%'
      },
      {
        title: '最大值',
        dataIndex: 'max',
        inputType: 'number',
        editable: true,
        required: false,
        width: '12%'
      },
    ]
  }
  showDrawer = () => {
    const { config } = this.props
    let fieldName = {}
    config.columns.forEach(col => {
      if (col.field) {
        fieldName[col.field] = col.label
      }
    })
    if (config.plot.correction) {
      delete config.plot.correction // 数据修正(已弃用)
      config.plot.barSize = 35
    }
    this.setState({
      visible: true,
      view: 'normal',
      ramp: config.plot.ramp || 'false',
      datatype: config.plot.datatype || 'query',
      fieldName: fieldName,
      view: 'base',
      plot: fromJS(config.plot).toJS(),
      baseFormlist: getBaseForm(config.plot),
      formlist: getOptionForm(config.plot, config.columns)
    })
  }
  radioChange = (e, key) => {
    const { formlist } = this.state
    let val = e.target.value
    if (key === 'datatype') {
      this.setState({
        datatype: val,
        formlist: formlist.map(item => {
          if (['Yaxis'].includes(item.key)) {
            item.hidden = val === 'statistics'
          } else if (['InfoType', 'InfoValue'].includes(item.key)) {
            item.hidden = val !== 'statistics'
          }
          return item
        })
      })
    }
  }
  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 style={{whiteSpace: 'nowrap'}} disabled={item.readonly} onChange={(e) => this.radioChange(e, item.key)}>
                  {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 allowClear={item.allowClear} />
              )}
            </Form.Item>
          </Col>
        )
      }
    })
    return fields
  }
  enabledChange = (e) => {
    const { plot } = this.state
    let val = e.target.value
    this.setState({plot: {...plot, enabled: val}})
  }
  mutilBarChange = (e) => {
    const { plot } = this.state
    let val = e.target.value
    this.setState({plot: {...plot, mutilBar: val}})
  }
  rampChange = (e) => {
    const { plot } = this.state
    let val = e.target.value
    let colors = plot.colors || []
    if (val === 'true') {
      colors = colors.map(item => {
        item.color1 = item.color1 || item.color
        return item
      })
    }
    this.setState({plot: {...plot, colors, ramp: val}, ramp: val})
  }
  rampDirectionChange = (e) => {
    const { plot } = this.state
    let val = e.target.value
    this.setState({plot: {...plot, rampDirection: val}})
  }
  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}
          if (values.datatype === 'statistics' || values.datatype !== plot.datatype) {
            _plot.enabled = 'false'
            _plot.customs = []
          } else if (!values.Yaxis || !plot.Yaxis || !is(fromJS(values.Yaxis), fromJS(plot.Yaxis))) {
            _plot.enabled = 'false'
            _plot.customs = []
            _plot.colors = null
          }
          if (values.datatype !== plot.datatype) {
            _plot.colors = null
          }
          this.setState({
            plot: _plot,
            visible: false
          })
          this.props.plotchange({...config, plot: _plot})
        }
      })
    } else if (view === 'base') {
    if (view === 'base') {
      this.baseRef.handleConfirm().then(res => {
        let _plot = {...plot, ...res}
@@ -500,66 +61,9 @@
  }
  changeTab = (tab) => {
    const { config } = this.props
    const { plot, view } = this.state
    if (view === 'normal') {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          let _plot = {...plot, ...values}
          if (values.datatype === 'statistics' || values.datatype !== plot.datatype) {
            _plot.enabled = 'false'
            _plot.customs = []
          } else if (!values.Yaxis || !plot.Yaxis || !is(fromJS(values.Yaxis), fromJS(plot.Yaxis))) {
            _plot.enabled = 'false'
            _plot.customs = []
            _plot.colors = null
          }
          let labels = {}
          config.columns.forEach(col => {
            labels[col.field] = col.label
          })
          if (values.datatype !== 'statistics' && (!_plot.customs || _plot.customs.length === 0)) {
            _plot.customs = _plot.Yaxis.map((item, i) => {
              return {
                uuid: Utils.getuuid(),
                type: item,
                name: labels[item] || item,
                axis: i === 0 ? 'true' : 'false',
                label: 'false',
                title: 'true',
                shape: _plot.chartType === 'bar' && i === 0 ? ['bar', 'rect'] : ['line', 'smooth']
              }
            })
          }
          if (values.datatype !== plot.datatype || !_plot.colors) {
            _plot.colors = []
            if (_plot.datatype === 'query') {
              let limit = chartColors.length
              _plot.colors = _plot.Yaxis.map((item, i) => {
                return {
                  uuid: Utils.getuuid(),
                  type: item,
                  label: labels[item] || item,
                  color: chartColors[i % limit],
                  color1: chartColors[i % limit]
                }
              })
            }
          }
          this.setState({
            datatype: _plot.datatype,
            plot: _plot,
            view: tab
          })
        }
      })
    } else if (view === 'base') {
    if (view === 'base') {
      this.baseRef.handleConfirm().then(res => {
        this.setState({
          plot: {...plot, ...res},
@@ -573,50 +77,22 @@
    }
  }
  addColor = () => {
    let plot = fromJS(this.state.plot).toJS()
    plot.colors = plot.colors || []
  onChange = (val) => {
    const { plot } = this.state
    plot.colors.push({
      uuid: Utils.getuuid(),
      type: `指标${plot.colors.length}`,
      color: 'rgb(91, 143, 249)',
      color1: 'rgb(91, 143, 249)'
    this.setState({
      plot: {...plot, script: val},
    })
    this.setState({plot})
  }
  changeColor = (colors) => {
    const { plot } = this.state
    this.setState({plot: {...plot, colors}})
  }
  changeCustom = (customs) => {
    const { plot } = this.state
    this.setState({plot: {...plot, customs}})
  }
  render() {
    const { view, visible, datatype, plot, ramp, colorColumns, rampColorColumns, statColorColumns, rampStatColorColumns, cusColumns, baseFormlist } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 6 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 18 }
      }
    }
    const { view, visible, baseFormlist, plot } = this.state
    return (
      <div className="line-chart-drawer-form">
        <Icon type="edit" title="编辑" onClick={this.showDrawer} />
      <>
        <Icon type="edit" style={{color: '#1890ff'}} title="编辑" onClick={this.showDrawer} />
        <Modal
          wrapClassName="popview-modal menu-chart-edit-modal"
          wrapClassName="popview-modal custom-chart-edit-modal"
          title="图表编辑"
          visible={visible}
          width={950}
@@ -625,72 +101,19 @@
          onCancel={() => { this.setState({ visible: false }) }}
          destroyOnClose
        >
          <Tabs activeKey={view} className="menu-chart-edit-box" onChange={this.changeTab}>
          <Tabs activeKey={view} 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>
            {plot ? <TabPane tab="颜色设置" key="color">
              <div>
                <Col span={8} style={{height: '40px', top: '-5px', zIndex: 1}}>
                  <Form {...formItemLayout}>
                    <Form.Item label="渐变色" style={{marginBottom: 10}}>
                      <Radio.Group value={plot.ramp || 'false'} onChange={this.rampChange}>
                        <Radio value="false">不使用</Radio>
                        <Radio value="true">使用</Radio>
                      </Radio.Group>
                    </Form.Item>
                  </Form>
                </Col>
                {plot.chartType === 'line' ? <Col span={8} style={{height: '40px', top: '-5px', zIndex: 1}}>
                  <Form {...formItemLayout}>
                    <Form.Item label="渐变方向" style={{marginBottom: 10}}>
                      <Radio.Group value={plot.rampDirection || 'horizontal'} onChange={this.rampDirectionChange}>
                        <Radio value="horizontal">水平</Radio>
                        <Radio value="vertical">垂直</Radio>
                      </Radio.Group>
                    </Form.Item>
                  </Form>
                </Col> : null}
                {datatype === 'statistics' ? <Button className="color-add mk-green" onClick={this.addColor}>{this.props.dict['model.add']}</Button> : null}
                {datatype === 'statistics' ? <EditTable actions={['edit', 'move', 'del']} data={plot.colors || []} columns={ramp ==='true' ? rampStatColorColumns : statColorColumns} onChange={this.changeColor}/> : null}
                {datatype !== 'statistics' ? <EditTable actions={['edit']} data={plot.colors || []} columns={ramp ==='true' ? rampColorColumns : colorColumns} onChange={this.changeColor}/> : null}
              </div>
            </TabPane> : null}
            {plot ? <TabPane tab="自定义设置" disabled={datatype === 'statistics'} key="custom">
              <Col span={12}>
                <Form {...formItemLayout}>
                  <Form.Item label="是否启用" style={{marginBottom: 10}}>
                    <Radio.Group value={plot.enabled || 'false'} onChange={this.enabledChange}>
                      <Radio value="true">是</Radio>
                      <Radio value="false">否</Radio>
                    </Radio.Group>
                  </Form.Item>
                </Form>
              </Col>
              <Col span={12}>
                <Form {...formItemLayout}>
                  <Form.Item label="多柱排列" style={{marginBottom: 10}}>
                    <Radio.Group value={plot.mutilBar || 'dodge'} onChange={this.mutilBarChange}>
                      <Radio value="dodge">分组</Radio>
                      <Radio value="stack">堆叠</Radio>
                      <Radio value="overlap">重叠</Radio>
                    </Radio.Group>
                  </Form.Item>
                </Form>
              </Col>
              <Col style={{fontSize: '12px', color: '#757575', paddingLeft: '10px'}} span={24}>注:使用自定义设置时,显示的坐标轴第一个在左侧,第二个在右侧,多余的不生效。</Col>
              <EditTable actions={['edit', 'move']} data={plot.customs || []} columns={cusColumns} onChange={this.changeCustom}/>
            {plot ? <TabPane tab="JS" key="JS">
              {plot.chartType === 'antv' ? <div>入参:Chart、 DataSet、 wrap(dom节点)、data、 config</div> : <div>入参:echarts、 DataSet、 wrap(dom节点)、 data、 config</div>}
              <CodeMirror mode="text/javascript" theme="cobalt" value={plot.script} onChange={this.onChange} />
            </TabPane> : null}
          </Tabs>
        </Modal>
      </div>
      </>
    );
  }
}
export default Form.create()(LineChartDrawerForm)
export default Form.create()(CustomChartDrawerForm)
src/menu/components/chart/chart-custom/chartcompile/index.scss
@@ -1,43 +1,10 @@
.line-chart-drawer-form {
  display: inline-block;
  > .anticon-edit {
    color: #1890ff;
  }
}
.menu-chart-edit-modal {
.custom-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-radio-wrapper {
          margin-right: 5px;
        }
        .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;
        }
      }
    }
  }
}
src/menu/components/chart/chart-custom/index.jsx
@@ -1,16 +1,16 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Icon, Popover } from 'antd'
import { Icon, Popover, notification } from 'antd'
import { Chart } from '@antv/g2'
import DataSet from '@antv/data-set'
import * as echarts from 'echarts'
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 { chartColors } from '@/utils/option.js'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import './index.scss'
@@ -18,10 +18,7 @@
const SettingComponent = asyncIconComponent(() => import('@/menu/datasource'))
const ChartCompileForm = asyncIconComponent(() => import('./chartcompile'))
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 ChartCustom extends Component {
@@ -42,17 +39,12 @@
    if (card.isNew) {
      let _plot = {
        chartType: card.type, // 图表类型
        enabled: 'false',     // 是否使用自定义设置
        datatype: 'query',    // 数据类型查询或统计
        customs: [],
        chartType: 'antv', // 图表类型
        width: card.width || 24,
        height: 400,
        barSize: 35,
        color: 'rgba(0, 0, 0, 0.65)',
        name: card.name
        name: card.name,
        script: ''
      }
      let _card = {
        uuid: card.uuid,
@@ -89,10 +81,6 @@
        _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
@@ -139,6 +127,8 @@
      let _element = document.getElementById(card.uuid + 'canvas')
      if (_element) {
        _element.innerHTML = ''
        _element.removeAttribute('_echarts_instance_')
        _element.removeAttribute('style')
      }
      this.$timer && clearTimeout(this.$timer)
@@ -146,1050 +136,39 @@
    }
  }
  getdata = (X_axis, Y_axis) => {
    let data = []
    let xdata = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
    let point = 7
    for (let i = 0; i < point; i++) {
      let item = {}
      item[X_axis] = xdata[i]
      if (typeof(Y_axis) === 'string') {
        item[Y_axis] = Math.floor(Math.random() * 5 * (i + 1)) + i
      } else {
        Y_axis.forEach(y => {
          item[y] = Math.floor(Math.random() * 5 * (i + 1)) + i
        })
      }
      data.push(item)
    }
    return data
  }
  viewrender = () => {
    const { card } = this.state
    if (card.plot.chartType === 'line') {
      this.linerender()
    } else if (card.plot.chartType === 'bar') {
      this.barrender()
    }
  }
    if (!card.plot.script) return
  /**
   * @description 折线图
   */
  linerender = () => {
    const { card } = this.state
    const plot = card.plot
    let color = plot.color || 'rgba(0, 0, 0, 0.65)'
    let X_axis = plot.Xaxis || 'x'
    let Y_axis = plot.Yaxis || ['y']
    let data = this.getdata(X_axis, Y_axis)
    if (plot.enabled !== 'true') {
      const ds = new DataSet()
      const dv = ds.createView().source(data)
      let transfield = {}
      card.columns.forEach(col => {
        if (col.field) {
          transfield[col.field] = col.label
        }
      })
      dv.transform({
        type: 'fold',
        fields: [...Y_axis],
        key: 'key',
        value: 'value'
      })
      if (plot.Xaxis) {
        dv.transform({
          type: 'map',
          callback(row) {
            row.key = transfield[row.key] || row.key
            return row
          },
    if (card.plot.chartType === 'antv') {
      try {
        // eslint-disable-next-line
        let func = new Function('Chart', 'DataSet', 'wrap', 'data', 'config', card.plot.script)
        func(Chart, DataSet, this.wrap, [], card)
      } catch (e) {
        console.warn(e)
        notification.warning({
          top: 92,
          message: '图表渲染失败!',
          duration: 5
        })
      }
      const chart = new Chart({
        container: card.uuid + 'canvas',
        autoFit: true,
        height: this.wrap.offsetHeight - 25
      })
    } else if (card.plot.chartType === 'echarts') {
      try {
        // eslint-disable-next-line
        let func = new Function('echarts', 'DataSet', 'wrap', 'data', 'config', card.plot.script)
        func(echarts, DataSet, this.wrap, [], card)
      } catch (e) {
        console.warn(e)
  
      chart.data(dv.rows)
      // 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 } } })
      let xc = {label: { style: { fill: color } }}
      let yc = {label: { style: { fill: color } }}
      if (plot.grid === 'hidden') {
        yc.grid = null
      }
      if (plot.y_line === 'show') {
        yc.line = {style: { stroke: '#D1D2CE' }}
      }
      if (plot.lineColor) {
        xc.tickLine = {style: { stroke: plot.lineColor }}
        xc.line = { style: { stroke: plot.lineColor } }
        if (yc.grid !== null) {
          yc.grid = { line: { style: { stroke: plot.lineColor } }}
        }
        if (yc.line) {
          yc.line = { style: { stroke: plot.lineColor } }
        }
      }
      chart.axis(X_axis, xc)
      chart.axis('value', yc)
      if (plot.coordinate !== 'polar') {
        chart.scale(X_axis, {
          range: [0, 1]
        notification.warning({
          top: 92,
          message: '图表渲染失败!',
          duration: 5
        })
      }
      chart.scale('value', {
        nice: true,
        range: [0, 0.9]
      })
      if (!plot.legend || plot.legend === 'hidden') {
        chart.legend(false)
      } else {
        chart.legend({
          position: plot.legend,
          itemName: { style: { fill: color } }
        })
      }
      if (plot.tooltip !== 'true') {
        chart.tooltip(false)
      } else {
        chart.tooltip({
          shared: true
        })
      }
      if (plot.transpose === 'true') {
        chart.coordinate().transpose()
      }
      if (plot.coordinate === 'polar') {
        chart.coordinate('polar', {
          innerRadius: 0.1,
          radius: 0.8
        })
      }
      let colors = new Map()
      let colorIndex = 0
      if (plot.colors && plot.colors.length > 0) {
        if (plot.ramp === 'true') {
          let _s = 'l(0) '
          if (plot.rampDirection === 'vertical') {
            _s = 'l(90) '
          }
          plot.colors.forEach(item => {
            if (!colors.has(transfield[item.type])) {
              colors.set(transfield[item.type], `${_s}0:${item.color} 1:${item.color1}` )
            }
          })
        } else {
          plot.colors.forEach(item => {
            if (!colors.has(transfield[item.type])) {
              colors.set(transfield[item.type], item.color)
            }
          })
        }
      }
      let _chart = chart
        .line()
        .position(`${X_axis}*value`)
        .shape(plot.shape || 'smooth')
        .tooltip(`${X_axis}*value*key`, (name, value, type) => {
          if (plot.show === 'percent') {
            value = value + '%'
          }
          return {
            name: type,
            value: value
          }
        })
      if (plot.colors && plot.colors.length > 0) {
        let limit = chartColors.length
        _chart.color('key', (key) => {
          if (colors.has(key)) {
            if (plot.area === 'true' && plot.rampDirection === 'vertical') {
              return colors.get(key).replace(/l\(9?0\) 0:|\s1:.*/ig, '')
            }
            return colors.get(key)
          } else {
            colorIndex++
            return chartColors[(colorIndex - 1) % limit]
          }
        })
      } else {
        _chart.color('key')
      }
      if (plot.label !== 'false') {
        _chart.label('value*key', (value, key) => {
          if (plot.show === 'percent') {
            value = value + '%'
          }
          let _color = color
          if (plot.labelColor === 'custom' && colors.has(key)) {
            _color = colors.get(key)
          }
          return {
            content: value,
            style: {
              fill: _color
            }
          }
        })
      }
      if (plot.point === 'true') {
        chart
          .point()
          .position(`${X_axis}*value`)
          .color('key')
          .size(3)
          .shape('circle')
      }
      if (plot.area === 'true') {
        let area = chart.area().position(`${X_axis}*value`).tooltip(false)
        if (plot.shape === 'smooth') {
          area.shape('smooth')
        }
        if (plot.colors && plot.colors.length > 0) {
          let limit = chartColors.length
          area.color('key', (key) => {
            if (colors.has(key)) {
              return colors.get(key)
            } else {
              colorIndex++
              return chartColors[(colorIndex - 1) % limit]
            }
          })
        } else {
          area.color('key')
        }
      }
      if (plot.interaction && plot.interaction.length) {
        plot.interaction.forEach(t => {
          chart.interaction(t)
        })
      }
      chart.render()
    } else {
      this.customrender(data)
    }
  }
  /**
   * @description 自定义图
   */
  customrender = (data) => {
    let card = fromJS(this.state.card).toJS()
    let plot = card.plot
    let color = plot.color || 'rgba(0, 0, 0, 0.65)'
    let fields = []
    let legends = []
    let transfield = {}
    let Bar_axis = []
    card.columns.forEach(col => {
      if (col.field) {
        transfield[col.field] = col.label
      }
    })
    let colors = new Map()
    let colorIndex = 0
    let limit = chartColors.length
    if (plot.colors && plot.colors.length > 0) {
      if (plot.ramp === 'true') {
        let bars = {}
        plot.customs.forEach(item => {
          if (!item.shape || !item.shape[0] || item.shape[0] === 'bar') {
            bars[item.type] = true
          }
        })
        plot.colors.forEach(item => {
          if (!colors.has(transfield[item.type])) {
            if (bars[item.type]) {
              colors.set(transfield[item.type], `l(90) 0:${item.color} 1:${item.color1}` )
            } else {
              colors.set(transfield[item.type], `l(0) 0:${item.color} 1:${item.color1}` )
            }
          }
        })
      } else {
        plot.colors.forEach(item => {
          if (!colors.has(transfield[item.type])) {
            colors.set(transfield[item.type], item.color)
          }
        })
      }
    }
    let axisIndex = 0
    let hasBar = false
    plot.$paddingLeft = 30
    plot.$paddingRight = 30
    plot.customs.forEach(item => {
      item.name = transfield[item.type] || item.type
      item.chartType = item.shape ? (item.shape[0] || 'bar') : 'bar'
      item.shape = item.shape ? (item.shape[1] || '') : ''
      if (colors.has(item.name)) {
        item.color = colors.get(item.name)
      } else {
        item.color = chartColors[colorIndex % limit]
        colorIndex++
      }
      if (item.chartType === 'bar') {
        Bar_axis.push(item.type)
        hasBar = true
      }
      if (item.axis === 'true' && axisIndex < 2) {
        if (axisIndex === 0) {
          // item.axis = { grid: {line: { style: { stroke: color } }}, title: { style: { fill: color } }, label: {style: { fill: color }} }
          item.axis = { label: {style: { fill: color }} }
          if (item.title !== 'false') {
            item.axis.title = { style: { fill: color } }
            plot.$paddingLeft = 50
          }
          if (plot.grid === 'hidden') {
            item.axis.grid = null
          }
          if (plot.y_line === 'show') {
            item.axis.line = {style: { stroke: '#D1D2CE' }}
          }
          if (plot.lineColor) {
            if (item.axis.grid !== null) {
              item.axis.grid = { line: { style: { stroke: plot.lineColor } }}
            }
            if (item.axis.line) {
              item.axis.line = { style: { stroke: plot.lineColor } }
            }
          }
          fields.unshift(item)
        } else {
          item.axis = { grid: null, label: {style: { fill: color }} }
          if (item.title !== 'false') {
            item.axis.title = { style: { fill: color } }
            plot.$paddingRight = 60
          }
          if (plot.y_line === 'show') {
            item.axis.line = {style: { stroke: '#D1D2CE' }}
          }
          if (plot.lineColor && item.axis.line) {
            item.axis.line = { style: { stroke: plot.lineColor } }
          }
          fields.splice(1, 0, item)
        }
        axisIndex++
      } else {
        item.axis = { grid: null, title: null, label: null }
        fields.push(item)
      }
      legends.push({
        value: item.name,
        name: item.name,
        marker: { symbol: item.chartType === 'bar' ? 'square' : 'hyphen', style: { stroke: item.color,fill: item.color, r: 5, lineWidth: 2 } }
      })
    })
    const ds = new DataSet()
    const dv = ds.createView().source(data)
    dv.transform({
      type: 'map',
      callback(row) {
        fields.forEach(line => {
          row[line.name] = row[line.type]
        })
        return row
      }
    })
    let padding = [10, 30, 30, 30]
    if (plot.mutilBar === 'overlap') {
      Bar_axis = []
    }
    if (!Bar_axis.length) {
      padding = [10, plot.$paddingRight, 30, plot.$paddingLeft]
    }
    const chart = new Chart({
      container: card.uuid + 'canvas',
      autoFit: true,
      height: this.wrap.offsetHeight - 25,
    })
    // chart.axis(plot.Xaxis, { label: { style: { fill: color } }, tickLine: {style: { stroke: color }}, line: { style: { stroke: color } } })
    let xc = {label: { style: { fill: color } }}
    if (plot.lineColor) {
      xc.tickLine = {style: { stroke: plot.lineColor }}
      xc.line = { style: { stroke: plot.lineColor } }
    }
    chart.axis(plot.Xaxis, xc)
    if (!hasBar) {
      chart.scale(plot.Xaxis, {
        range: [0, 1]
      })
    }
    if (!plot.legend || plot.legend === 'hidden') {
      chart.legend(false)
    } else {
      chart.legend({
        custom: true,
        position: plot.legend,
        items: legends,
        itemName: { style: { fill: color } }
      })
    }
    if (plot.tooltip !== 'true') {
      chart.tooltip(false)
    } else {
      chart.tooltip({
        shared: true,
      })
    }
    chart.scale({
      nice: true
    })
    let lablecfg = {
      position: 'top',
      offset: 2,
      style: {
        fill: '#fff'
      }
    }
    if (plot.label === 'top') {
      lablecfg.offset = -5
      lablecfg.style.textBaseline = 'top'
    } else if (plot.label === 'middle') {
      lablecfg.position = 'middle'
      lablecfg.offset = 0
    } else if (plot.label === 'bottom') {
      lablecfg.position = 'bottom'
      lablecfg.offset = 0
    } else if (plot.label === 'true') {
      lablecfg.style.fill = color
    }
    if (Bar_axis.length) {
      const view1 = chart.createView({
        region: {
          start: { x: 0, y: 0 },
          end: { x: 1, y: 1 }
        },
        padding
      })
      const dst = new DataSet()
      const dvt = dst.createView().source(data)
      dvt.transform({
        type: 'fold',
        fields: [...Bar_axis],
        key: 'key',
        value: 'value'
      })
      dvt.transform({
        type: 'map',
        callback(row) {
          row.key = transfield[row.key] || row.key
          return row
        },
      })
      view1.data(dvt.rows)
      view1.scale('value', {
        nice: true,
        range: [0, 0.9]
      })
      let yc = {label: { style: { fill: color } }}
      if (plot.grid === 'hidden') {
        yc.grid = null
      }
      if (plot.y_line === 'show') {
        yc.line = {style: { stroke: '#D1D2CE' }}
      }
      if (plot.lineColor) {
        if (yc.grid !== null) {
          yc.grid = { line: { style: { stroke: plot.lineColor } }}
        }
        if (yc.line) {
          yc.line = { style: { stroke: plot.lineColor } }
        }
      }
      view1.axis('value', yc)
      view1.legend(false)
      if (plot.mutilBar !== 'stack') {
        let _chart = view1
          .interval()
          .position(`${plot.Xaxis}*value`)
          .adjust([
            {
              type: 'dodge',
              marginRatio: 0
            }
          ])
          .shape(plot.shape || 'rect')
          .tooltip(`${plot.Xaxis}*value*key`, (name, value, key) => {
            if (plot.show === 'percent') {
              value = value + '%'
            }
            return {
              name: key,
              value: value
            }
          })
        if (plot.colors && plot.colors.length > 0) {
          let limit = chartColors.length
          _chart.color('key', (key) => {
            if (colors.has(key)) {
              return colors.get(key)
            } else {
              colorIndex++
              return chartColors[(colorIndex - 1) % limit]
            }
          })
        } else {
          _chart.color('key')
        }
        if (plot.label !== 'false') {
          _chart.label('value*key', (value, key) => {
            if (plot.show === 'percent') {
              value = value + '%'
            }
            if (plot.label === 'true' && plot.labelColor === 'custom' && colors.has(key)) {
              lablecfg.style.fill = colors.get(key)
            }
            return {
              content: value,
              ...lablecfg
            }
          })
        }
        if (plot.barSize || plot.correction) {
          _chart.size(plot.barSize || 35)
        }
        if (plot.barRadius) {
          _chart.style({ radius: [plot.barRadius, plot.barRadius, 0, 0] })
        }
      } else if (plot.mutilBar === 'stack') {
        let _chart = view1
          .interval()
          .position(`${plot.Xaxis}*value`)
          .adjust('stack')
          .shape(plot.shape || 'rect')
          .tooltip(`${plot.Xaxis}*value*key`, (name, value, type) => {
            if (plot.show === 'percent') {
              value = value + '%'
            }
            return {
              name: type,
              value: value
            }
          })
        if (plot.colors && plot.colors.length > 0) {
          let limit = chartColors.length
          _chart.color('key', (key) => {
            if (colors.has(key)) {
              return colors.get(key)
            } else {
              colorIndex++
              return chartColors[(colorIndex - 1) % limit]
            }
          })
        } else {
          _chart.color('key')
        }
        if (plot.label !== 'false') {
          _chart.label('value*key', (value, key) => {
            if (plot.show === 'percent') {
              value = value + '%'
            }
            if (plot.label === 'true' && plot.labelColor === 'custom' && colors.has(key)) {
              lablecfg.style.fill = colors.get(key)
            }
            return {
              content: value,
              ...lablecfg
            }
          })
        }
        if (plot.barSize || plot.correction) {
          _chart.size(plot.barSize || 35)
        }
        if (plot.barRadius) {
          _chart.style({ radius: [plot.barRadius, plot.barRadius, 0, 0] })
        }
      }
    }
    const view2 = chart.createView({
      region: {
        start: { x: 0, y: 0 },
        end: { x: 1, y: 1 }
      },
      padding
    })
    view2.data(dv.rows)
    view2.legend(false)
    fields.forEach(item => {
      if (item.chartType === 'bar' && !Bar_axis.length) {
        view2.axis(item.name, item.axis)
        view2.scale(item.name, {
          nice: true,
          range: [0, 0.9]
        })
        let _chart = view2
          .interval()
          .position(`${plot.Xaxis}*${item.name}`)
          .color(item.color)
          .shape(item.shape)
          .tooltip(`${item.name}`, (value) => {
            if (plot.show === 'percent') {
              value = value + '%'
            }
            return {
              name: item.name,
              value: value
            }
          })
        if (plot.barSize) {
          _chart.size(plot.barSize || 35)
        }
        if (item.label !== 'false') {
          _chart.label(item.name, (value) => {
            if (plot.show === 'percent') {
              value = value + '%'
            }
            if (plot.label === 'true' && plot.labelColor === 'custom') {
              lablecfg.style.fill = item.color
            }
            return {
              content: value,
              ...lablecfg
            }
          })
        }
        if (plot.barRadius) {
          _chart.style({ radius: [plot.barRadius, plot.barRadius, 0, 0] })
        }
      } else if (item.chartType === 'line') {
        if (!Bar_axis.length) {
          view2.axis(item.name, item.axis)
        } else {
          view2.axis(item.name, { grid: null, title: null, label: null })
        }
        view2.scale(item.name, {
          nice: true,
          range: [0, 0.9]
        })
        let _chart = view2
          .line()
          .position(`${plot.Xaxis}*${item.name}`)
          .color(item.color)
          .shape(item.shape)
          .tooltip(`${item.name}`, (value) => {
            if (plot.show === 'percent') {
              value = value + '%'
            }
            return {
              name: item.name,
              value: value
            }
          })
        if (item.label !== 'false') {
          _chart.label(item.name, (value) => {
            if (plot.show === 'percent') {
              value = value + '%'
            }
            let _color = color
            if (plot.labelColor === 'custom') {
              _color = item.color
            }
            return {
              content: value,
              style: {
                fill: _color
              }
            }
          })
        }
        if (plot.point === 'true') {
          chart
            .point()
            .position(`${plot.Xaxis}*${item.name}`)
            .color(item.color)
            .size(3)
            .shape('circle')
        }
      }
    })
    if (plot.interaction && plot.interaction.length) {
      plot.interaction.forEach(t => {
        if (t === 'element-active' || t === 'element-highlight') {
          chart.interaction(t)
        }
      })
    }
    chart.render()
  }
  /**
   * @description 柱形图
   */
  barrender = () => {
    const { card } = this.state
    const plot = card.plot
    let color = plot.color || 'rgba(0, 0, 0, 0.65)'
    let X_axis = plot.Xaxis || 'x'
    let Y_axis = plot.Yaxis || ['y']
    let data = this.getdata(X_axis, Y_axis)
    if (plot.enabled !== 'true') {
      const ds = new DataSet()
      const dv = ds.createView().source(data)
      let transfield = {}
      card.columns.forEach(col => {
        if (col.field) {
          transfield[col.field] = col.label
        }
      })
      dv.transform({
        type: 'fold',
        fields: [...Y_axis],
        key: 'key',
        value: 'value'
      })
      if (plot.Xaxis) {
        dv.transform({
          type: 'map',
          callback(row) {
            row.key = transfield[row.key] || row.key
            return row
          },
        })
      }
      const chart = new Chart({
        container: card.uuid + 'canvas',
        autoFit: true,
        height: this.wrap.offsetHeight - 25
      })
      chart.data(dv.rows)
      // 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 } } })
      let xc = {label: { style: { fill: color } }}
      let yc = {label: { style: { fill: color } }}
      if (plot.grid === 'hidden') {
        yc.grid = null
      }
      if (plot.y_line === 'show') {
        yc.line = {style: { stroke: '#D1D2CE' }}
      }
      if (plot.lineColor) {
        xc.tickLine = {style: { stroke: plot.lineColor }}
        xc.line = { style: { stroke: plot.lineColor } }
        if (yc.grid !== null) {
          yc.grid = { line: { style: { stroke: plot.lineColor } }}
        }
        if (yc.line) {
          yc.line = { style: { stroke: plot.lineColor } }
        }
      }
      chart.axis(X_axis, xc)
      chart.axis('value', yc)
      chart.scale('value', {
        nice: true,
        range: [0, 0.9]
      })
      if (!plot.legend || plot.legend === 'hidden') {
        chart.legend(false)
      } else {
        chart.legend({
          position: plot.legend,
          itemName: { style: { fill: color } }
        })
      }
      if (plot.tooltip !== 'true') {
        chart.tooltip(false)
      } else {
        chart.tooltip({
          shared: true
        })
      }
      if (plot.coordinate === 'polar') {
        chart.coordinate('polar', {
          innerRadius: 0.1,
          radius: 0.8
        })
      }
      let colors = new Map()
      let colorIndex = 0
      let lablecfg = {
        position: 'top',
        offset: 2,
        style: {
          fill: '#fff'
        }
      }
      if (plot.label === 'top') {
        lablecfg.offset = -5
        lablecfg.style.textBaseline = 'top'
      } else if (plot.label === 'middle') {
        lablecfg.position = 'middle'
        lablecfg.offset = 0
      } else if (plot.label === 'bottom') {
        lablecfg.position = 'bottom'
        lablecfg.offset = 0
      } else if (plot.label === 'true') {
        lablecfg.style.fill = color
      }
      if (plot.transpose === 'true') {
        chart.coordinate().transpose()
        if (plot.label === 'top') {
          delete lablecfg.style.textBaseline
          lablecfg.position = 'right'
          lablecfg.offset = -3
          lablecfg.style.textAlign = 'end'
        } else if (plot.label === 'middle') {
          lablecfg.position = 'middle'
          lablecfg.offset = 0
        } else if (plot.label === 'bottom') {
          lablecfg.position = 'left'
          lablecfg.offset = 2
        } else if (plot.label === 'true') {
          lablecfg.position = 'right'
          lablecfg.offset = 2
        }
      }
      if (plot.colors && plot.colors.length > 0) {
        if (plot.ramp === 'true') {
          plot.colors.forEach(item => {
            if (!colors.has(transfield[item.type])) {
              colors.set(transfield[item.type], `l(90) 0:${item.color} 1:${item.color1}` )
            }
          })
        } else {
          plot.colors.forEach(item => {
            if (!colors.has(transfield[item.type])) {
              colors.set(transfield[item.type], item.color)
            }
          })
        }
      }
      if (plot.adjust !== 'stack') {
        let _chart = chart
          .interval()
          .position(`${X_axis}*value`)
          .adjust([
            {
              type: 'dodge',
              marginRatio: 0
            }
          ])
          .shape(plot.shape || 'rect')
          .tooltip(`${X_axis}*value*key`, (name, value, key) => {
            if (plot.show === 'percent') {
              value = value + '%'
            }
            return {
              name: key,
              value: value
            }
          })
        if (plot.colors && plot.colors.length > 0) {
          let limit = chartColors.length
          _chart.color('key', (key) => {
            if (colors.has(key)) {
              return colors.get(key)
            } else {
              colorIndex++
              return chartColors[(colorIndex - 1) % limit]
            }
          })
        } else {
          _chart.color('key')
        }
        if (plot.label !== 'false') {
          _chart.label('value*key', (value, key) => {
            if (plot.show === 'percent') {
              value = value + '%'
            }
            if (plot.label === 'true' && plot.labelColor === 'custom' && colors.has(key)) {
              lablecfg.style.fill = colors.get(key)
            }
            return {
              content: value,
              ...lablecfg
            }
          })
        }
        if (plot.barSize || plot.correction) {
          _chart.size(plot.barSize || 35)
        }
        if (plot.selectColor) {
          _chart.state({
            selected: {
              style: {
                fill: plot.selectColor,
              }
            }
          })
        }
        if (plot.barRadius) {
          _chart.style({ radius: [plot.barRadius, plot.barRadius, 0, 0] })
        }
      } else if (plot.adjust === 'stack') {
        let _chart = chart
          .interval()
          .position(`${X_axis}*value`)
          .adjust('stack')
          .shape(plot.shape || 'rect')
          .tooltip(`${X_axis}*value*key`, (name, value, type) => {
            if (plot.show === 'percent') {
              value = value + '%'
            }
            return {
              name: type,
              value: value
            }
          })
        if (plot.colors && plot.colors.length > 0) {
          let limit = chartColors.length
          _chart.color('key', (key) => {
            if (colors.has(key)) {
              return colors.get(key)
            } else {
              colorIndex++
              return chartColors[(colorIndex - 1) % limit]
            }
          })
        } else {
          _chart.color('key')
        }
        if (plot.label !== 'false') {
          _chart.label('value*key', (value, key) => {
            if (plot.show === 'percent') {
              value = value + '%'
            }
            if (plot.label === 'true' && plot.labelColor === 'custom' && colors.has(key)) {
              lablecfg.style.fill = colors.get(key)
            }
            return {
              content: value,
              ...lablecfg
            }
          })
        }
        if (plot.barSize || plot.correction) {
          _chart.size(plot.barSize || 35)
        }
        if (plot.selectColor) {
          _chart.state({
            selected: {
              style: {
                fill: plot.selectColor,
              }
            }
          })
        }
        if (plot.barRadius) {
          _chart.style({ radius: [plot.barRadius, plot.barRadius, 0, 0] })
        }
      }
      if (plot.interaction && plot.interaction.length) {
        plot.interaction.forEach(t => {
          chart.interaction(t)
        })
      }
      chart.render()
    } else {
      this.customrender(data)
    }
  }
@@ -1199,6 +178,8 @@
      let _element = document.getElementById(card.uuid + 'canvas')
      if (_element) {
        _element.innerHTML = ''
        _element.removeAttribute('_echarts_instance_')
        _element.removeAttribute('style')
      }
      this.$timer && clearTimeout(this.$timer)
      this.$timer = setTimeout(() => {
@@ -1236,34 +217,6 @@
    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.verify = null
    newcard.show = 'icon'
    // 注册事件-添加按钮
    MKEmitter.emit('addButton', card.uuid, newcard)
  }
  changeStyle = () => {
    const { card } = this.state
@@ -1292,17 +245,14 @@
    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}>
      <div className="menu-custom-chart-edit-box" style={{..._style, height: card.plot.height || 400}} onClick={this.clickComponent} id={card.uuid}>
        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
          <div className="mk-popover-control">
            {appType !== 'mob' ? <Icon className="plus" title="添加搜索" onClick={this.addSearch} type="plus-circle" /> : null}
            {appType !== 'mob' ? <Icon className="plus" title="添加按钮" onClick={this.addButton} type="plus-square" /> : null}
            <ChartCompileForm config={card} dict={this.state.dict} plotchange={this.updateComponent}/>
            <CopyComponent type="line" card={card}/>
            <PasteComponent config={card} options={['action']} updateConfig={this.updateComponent} />
            <Icon className="style" title="调整样式" onClick={this.changeStyle} type="font-colors" />
            <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>
@@ -1311,11 +261,6 @@
        </Popover>
        {card.plot.title || card.search.length > 0 ? <NormalHeader config={card} updateComponent={this.updateComponent}/> : null}
        <div className="canvas" id={card.uuid + 'canvas'} ref={ref => this.wrap = ref}></div>
        {appType !== 'mob' ? <ActionComponent
          type="chart"
          config={card}
          updateaction={this.updateComponent}
        /> : null}
      </div>
    )
  }
src/menu/components/chart/chart-custom/index.scss
@@ -1,4 +1,4 @@
.menu-line-chart-edit-box {
.menu-custom-chart-edit-box {
  position: relative;
  box-sizing: border-box;
  background: #ffffff;
@@ -10,7 +10,7 @@
  .canvas {
    margin: 0px;
    padding: 15px 10px 10px;
    padding: 0px;
    letter-spacing: 0px;
    flex: 1;
  }
@@ -65,7 +65,7 @@
    top: 45px;
  }
}
.menu-line-chart-edit-box:hover {
.menu-custom-chart-edit-box:hover {
  z-index: 1;
  box-shadow: 0px 0px 4px #1890ff;
}
src/menu/components/group/groupcomponents/card.jsx
@@ -21,6 +21,7 @@
const CarouselPropCard = asyncComponent(() => import('@/menu/components/carousel/prop-card'))
const Balcony = asyncComponent(() => import('@/menu/components/card/balcony'))
const CodeSandbox = asyncComponent(() => import('@/menu/components/code/sandbox'))
const CustomChart = asyncComponent(() => import('@/menu/components/chart/chart-custom'))
const Card = ({ id, card, moveCard, findCard, delCard, updateConfig }) => {
  const originalIndex = findCard(id).index
@@ -90,6 +91,8 @@
      return (<CodeSandbox card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'balcony') {
      return (<Balcony card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'chart') {
      return (<CustomChart card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    }
  }
src/menu/components/group/groupcomponents/index.jsx
@@ -68,6 +68,7 @@
      let name = ''
      let names = {
        bar: '柱状图',
        chart: '图表',
        line: '折线图',
        tabs: '标签组',
        pie: '饼图',
src/menu/components/share/normalform/index.jsx
@@ -131,7 +131,7 @@
                  }
                ]
              })(
                <Radio.Group disabled={item.readonly} onChange={(e) => this.radioChange(e, item.key)}>
                <Radio.Group disabled={item.readonly}>
                  {item.options.map(option => {
                    return (
                      <Radio key={option.value} value={option.value}>{option.text}</Radio>
src/menu/components/tabs/tabcomponents/card.jsx
@@ -24,6 +24,7 @@
const NormalGroup = asyncComponent(() => import('@/menu/components/group/normal-group'))
const BraftEditor = asyncComponent(() => import('@/menu/components/editor/braft-editor'))
const CodeSandbox = asyncComponent(() => import('@/menu/components/code/sandbox'))
const CustomChart = asyncComponent(() => import('@/menu/components/chart/chart-custom'))
const Card = ({ id, card, moveCard, findCard, delCard, updateConfig }) => {
  const originalIndex = findCard(id).index
@@ -99,6 +100,8 @@
      return (<CodeSandbox card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'balcony') {
      return (<Balcony card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'chart') {
      return (<CustomChart card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    }
  }
src/menu/components/tabs/tabcomponents/index.jsx
@@ -96,6 +96,7 @@
      let name = ''
      let names = {
        bar: '柱状图',
        chart: '图表',
        line: '折线图',
        tabs: '标签组',
        pie: '饼图',
src/menu/menushell/card.jsx
@@ -24,6 +24,7 @@
const NormalGroup = asyncComponent(() => import('@/menu/components/group/normal-group'))
const BraftEditor = asyncComponent(() => import('@/menu/components/editor/braft-editor'))
const CodeSandbox = asyncComponent(() => import('@/menu/components/code/sandbox'))
const CustomChart = asyncComponent(() => import('@/menu/components/chart/chart-custom'))
const Card = ({ id, card, moveCard, findCard, delCard, updateConfig }) => {
  const originalIndex = findCard(id).index
@@ -99,6 +100,8 @@
      return (<CodeSandbox card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'balcony') {
      return (<Balcony card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'chart') {
      return (<CustomChart card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    }
  }
  return (
src/menu/menushell/index.jsx
@@ -90,6 +90,7 @@
      let name = ''
      let names = {
        bar: '柱状图',
        chart: '图表',
        line: '折线图',
        tabs: '标签组',
        pie: '饼图',
src/menu/modulesource/option.jsx
@@ -22,7 +22,7 @@
import dashboard from '@/assets/mobimg/dashboard.png'
import ratioboard from '@/assets/mobimg/ratioboard.png'
import scatter from '@/assets/mobimg/scatter.png'
// import chart from '@/assets/mobimg/chart.png'
import chart from '@/assets/mobimg/chart.png'
import tree from '@/assets/mobimg/tree.png'
// 组件配置信息
@@ -51,7 +51,7 @@
  { type: 'menu', url: dashboard, component: 'dashboard', subtype: 'dashboard', title: '仪表盘', width: 12 },
  { type: 'menu', url: ratioboard, component: 'dashboard', subtype: 'ratioboard', title: '占比图', width: 12 },
  { type: 'menu', url: scatter, component: 'scatter', subtype: 'scatter', title: '散点图', width: 24 },
  // { type: 'menu', url: chart, component: 'chart', subtype: 'custom', title: '自定义图表', width: 24 },
  { type: 'menu', url: chart, component: 'chart', subtype: 'custom', title: '自定义图表', 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'] },
src/tabviews/custom/components/chart/custom-chart/index.jsx
New file
@@ -0,0 +1,382 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Chart } from '@antv/g2'
import DataSet from '@antv/data-set'
import * as echarts from 'echarts'
import { Spin, Empty, notification } 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 NormalHeader from '@/tabviews/custom/components/share/normalheader'
import './index.scss'
class CustomChart 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,            // 数据加载状态
    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
    }
    _config.style.height = config.plot.height || 400
    this.setState({
      config: _config,
      data: _data,
      empty: !_data || _data.length === 0,
      BID: BID || '',
      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.length === 0}, () => {
        this.handleData()
      })
    } else if (config.setting.syncRefresh && nextProps.mainSearch && !is(fromJS(this.props.mainSearch), fromJS(nextProps.mainSearch))) {
      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('queryModuleParam', this.queryModuleParam)
    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('queryModuleParam', this.queryModuleParam)
    MKEmitter.removeListener('refreshByButtonResult', this.refreshByButtonResult)
  }
  handleTimer = () => {
    const { config } = this.state
    if (!config.timer) return
    const _change = { '5s': 5000, '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)                   // 关键字符替换,base64加密
    _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'))
    } else if (position === 'popclose') {                                      // 标签关闭刷新
      config.setting.supModule && MKEmitter.emit('reloadData', config.setting.supModule, (BID || 'empty'))
      btn.$tabId && MKEmitter.emit('refreshPopButton', btn.$tabId)
    }
  }
  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时,获取页面搜索排序等参数
   */
  queryModuleParam = (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 (config.setting.useMSearch && 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('returnModuleParam', 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: []
      }, () => {
        this.handleData()
      })
      return
    }
    let searches = search ? fromJS(search).toJS() : []
    if (config.setting.useMSearch && 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 === '')
    if (requireFields.length > 0) {
      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,
        empty: result.data.length === 0
      }, () => {
        this.handleData()
      })
    } else {
      this.setState({
        loading: false,
        timer: null
      })
      notification.error({
        top: 92,
        message: result.message,
        duration: 10
      })
    }
  }
  /**
   * @description 数据预处理,统计数据需要重置
   */
  handleData = () => {
    if (!this.wrap) return
    this.wrap.innerHTML = ''
    this.wrap.removeAttribute('_echarts_instance_')
    this.wrap.removeAttribute('style')
    setTimeout(() => {
      this.viewrender()
    }, 150)
  }
  /**
   * @description 图表渲染分组
   */
  viewrender = () => {
    const { config, data } = this.state
    if (!config.plot.script) return
    if (config.plot.chartType === 'antv') {
      try {
        // eslint-disable-next-line
        let func = new Function('Chart', 'DataSet', 'wrap', 'data', 'config', config.plot.script)
        func(Chart, DataSet, this.wrap, data, config)
      } catch (e) {
        console.warn(e)
        notification.warning({
          top: 92,
          message: '图表渲染失败!',
          duration: 5
        })
      }
    } else if (config.plot.chartType === 'echarts') {
      try {
        // eslint-disable-next-line
        let func = new Function('echarts', 'DataSet', 'wrap', 'data', 'config', config.plot.script)
        func(echarts, DataSet, this.wrap, data, config)
      } catch (e) {
        console.warn(e)
        notification.warning({
          top: 92,
          message: '图表渲染失败!',
          duration: 5
        })
      }
    }
  }
  refreshSearch = (list) => {
    this.setState({search: list}, () => {
      this.loadData()
    })
  }
  render() {
    const { config, loading, empty, BID } = this.state
    return (
      <div className="custom-chart-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' + (empty ? ' empty' : '')} ref={ref => this.wrap = ref}></div>
        {empty ? <Empty description={false}/> : null}
      </div>
    )
  }
}
export default CustomChart
src/tabviews/custom/components/chart/custom-chart/index.scss
New file
@@ -0,0 +1,57 @@
.custom-chart-plot-box {
  position: relative;
  background: #ffffff;
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  min-height: 100px;
  display: flex;
  flex-flow: column;
  .canvas {
    margin: 0;
    padding: 0px;
    letter-spacing: 0px;
    flex: 1;
  }
  .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;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: justify;
    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;
  }
}
src/tabviews/custom/components/group/normal-group/index.jsx
@@ -31,6 +31,7 @@
const CarouselDataCard = asyncComponent(() => import('@/tabviews/custom/components/carousel/data-card'))
const CarouselPropCard = asyncComponent(() => import('@/tabviews/custom/components/carousel/prop-card'))
const Balcony = asyncComponent(() => import('@/tabviews/custom/components/card/balcony'))
const CustomChart = asyncComponent(() => import('@/tabviews/custom/components/chart/custom-chart'))
class TabTransfer extends Component {
  static propTpyes = {
@@ -244,6 +245,12 @@
            <Balcony config={item} data={data} BID={_bid} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'chart') {
        return (
          <Col span={item.width} key={item.uuid}>
            <CustomChart config={item} data={data} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else {
        return null
      }
src/tabviews/custom/components/share/tabtransfer/index.jsx
@@ -34,6 +34,7 @@
const CarouselDataCard = asyncComponent(() => import('@/tabviews/custom/components/carousel/data-card'))
const CarouselPropCard = asyncComponent(() => import('@/tabviews/custom/components/carousel/prop-card'))
const Balcony = asyncComponent(() => import('@/tabviews/custom/components/card/balcony'))
const CustomChart = asyncComponent(() => import('@/tabviews/custom/components/chart/custom-chart'))
class TabTransfer extends Component {
  static propTpyes = {
@@ -284,6 +285,12 @@
            <Balcony config={item} data={data} BID={BID} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'chart') {
        return (
          <Col span={item.width} key={item.uuid}>
            <CustomChart config={item} data={data} BID={BID} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else {
        return null
      }
src/tabviews/custom/index.jsx
@@ -38,6 +38,7 @@
const Balcony = asyncComponent(() => import('./components/card/balcony'))
const SettingComponent = asyncComponent(() => import('@/tabviews/zshare/settingcomponent'))
const PagemsgComponent = asyncComponent(() => import('@/tabviews/zshare/pageMessage'))
const CustomChart = asyncComponent(() => import('./components/chart/custom-chart'))
class CustomPage extends Component {
  static propTpyes = {
@@ -502,7 +503,7 @@
        }
        item.components = this.filterComponent(item.components, roleId, permAction, permMenus, balMap, skip)
      } else if (['pie', 'bar', 'line', 'dashboard', 'scatter'].includes(item.type)) {
      } else if (['pie', 'bar', 'line', 'dashboard', 'scatter', 'chart'].includes(item.type)) {
        if (
          item.plot.blacklist && item.plot.blacklist.length > 0 &&
          item.plot.blacklist.filter(v => roleId.indexOf(v) > -1).length > 0
@@ -1144,6 +1145,12 @@
            <SandBox config={item} data={data} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'chart') {
        return (
          <Col span={item.width} key={item.uuid}>
            <CustomChart config={item} data={data} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else {
        return null
      }
src/views/menudesign/index.jsx
@@ -427,7 +427,7 @@
            buttons.push(`select '${cell.uuid}' as menuid, '${item.name + '-' + cell.label}' as menuname, '${_sort * 10}' as Sort`)
            _sort++
          })
        } else if (item.type === 'line' || item.type === 'bar') {
        } else if (item.type === 'line' || item.type === 'bar' || item.type === 'chart') {
          item.action && item.action.forEach(btn => {
            this.checkBtn(btn)
            buttons.push(`select '${btn.uuid}' as menuid, '${item.name + '-' + btn.label}' as menuname, '${_sort * 10}' as Sort`)