| | |
| | | import PropTypes from 'prop-types' |
| | | import { is, fromJS } from 'immutable' |
| | | import { Chart } from '@antv/g2' |
| | | import { connect } from 'react-redux' |
| | | import DataSet from '@antv/data-set' |
| | | import { Spin, Empty, notification } from 'antd' |
| | | import moment from 'moment' |
| | | |
| | | // import searchLine from '../../share/searchLine' |
| | | import Api from '@/api' |
| | | import Utils from '@/utils/utils.js' |
| | | import { modifyTabview } from '@/store/action' |
| | | import { chartColors } from '@/utils/option.js' |
| | | import asyncComponent from '@/utils/asyncComponent' |
| | | import UtilsDM from '@/utils/utils-datamanage.js' |
| | | import zhCN from '@/locales/zh-CN/main.js' |
| | | import enUS from '@/locales/en-US/main.js' |
| | | import MKEmitter from '@/utils/events.js' |
| | | import './index.scss' |
| | | |
| | | class LineChart extends Component { |
| | | const NormalHeader = asyncComponent(() => import('@/tabviews/custom/components/share/normalheader')) |
| | | |
| | | class PieChart extends Component { |
| | | static propTpyes = { |
| | | BID: PropTypes.any, // 父级Id |
| | | data: PropTypes.array, // 统一查询数据 |
| | | config: PropTypes.object, // 组件配置信息 |
| | | mainSearch: PropTypes.any, // 全局搜索条件 |
| | | mainSearch: PropTypes.any, // 外层搜索条件 |
| | | menuType: PropTypes.any, // 菜单类型 |
| | | dataManager: PropTypes.any, // 数据权限 |
| | | } |
| | | |
| | | state = { |
| | | dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS, // 字典 |
| | | BID: '', // 上级ID |
| | | config: null, // 图表配置信息 |
| | | empty: true, // 图表数据为空 |
| | | loading: false, // 数据加载状态 |
| | |
| | | plot: null, // 图表设置 |
| | | data: null, // 数据 |
| | | search: null, // 搜索条件 |
| | | showHeader: false // 存在标题、搜索、或统计数据时显示 |
| | | } |
| | | |
| | | UNSAFE_componentWillMount () { |
| | | const { config, data } = this.props |
| | | const { config, data, initdata, BID } = this.props |
| | | let _config = fromJS(config).toJS() |
| | | |
| | | let _data = null |
| | |
| | | if (config.setting.sync === 'true' && data) { |
| | | _data = data[config.dataName] || [] |
| | | _sync = false |
| | | } else if (config.setting.sync === 'true' && initdata) { |
| | | _data = initdata || [] |
| | | _sync = false |
| | | } |
| | | |
| | | let showHeader = false |
| | | if (config.plot.title || config.search.length > 0) { |
| | | showHeader = true |
| | | _config.plot.height = _config.plot.height - 80 |
| | | } else { |
| | | _config.plot.height = _config.plot.height - 30 |
| | |
| | | this.setState({ |
| | | config: _config, |
| | | data: _data, |
| | | BID: BID || '', |
| | | arr_field: _config.columns.map(col => col.field).join(','), |
| | | plot: _config.plot, |
| | | sync: _sync, |
| | | title: config.plot.title, |
| | | search: Utils.initMainSearch(config.search), |
| | | showHeader |
| | | search: Utils.initMainSearch(config.search) |
| | | }, () => { |
| | | if (config.setting.sync !== 'true') { |
| | | if (config.setting.sync !== 'true' && config.setting.onload === 'true') { |
| | | this.loadData() |
| | | } else if (config.setting.sync === 'true') { |
| | | if (!_data) { |
| | | this.setState({ |
| | | loading: true |
| | | }) |
| | | } else { |
| | | this.handleData() |
| | | } |
| | | } else if (config.setting.sync === 'true' && _data) { |
| | | this.handleData() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | /** |
| | | * @description 校验图表的按钮组,如果为统计图表,计算图表字段 |
| | | */ |
| | | componentDidMount () { |
| | | |
| | | } |
| | | |
| | | /** |
| | |
| | | _data = nextProps.data[config.dataName] || [] |
| | | } |
| | | |
| | | this.setState({sync: false, loading: false, data: _data}, () => { |
| | | this.setState({sync: false, data: _data}, () => { |
| | | this.handleData() |
| | | }) |
| | | } else if (nextProps.mainSearch && !is(fromJS(this.props.mainSearch), fromJS(nextProps.mainSearch))) { |
| | | if (config.setting.syncRefresh === 'true') { |
| | | this.setState({}, () => { |
| | | this.loadData() |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | |
| | | shouldComponentUpdate (nextProps, nextState) { |
| | | return !is(fromJS(this.state), fromJS(nextState)) |
| | | } |
| | | |
| | | componentDidMount () { |
| | | MKEmitter.addListener('reloadData', this.reloadData) |
| | | MKEmitter.addListener('resetSelectLine', this.resetParentParam) |
| | | this.handleTimer() |
| | | } |
| | | |
| | | /** |
| | | * @description 组件销毁,清除state更新,清除快捷键设置 |
| | | */ |
| | | componentWillUnmount () { |
| | | clearTimeout(this.timer) |
| | | this.setState = () => { |
| | | return |
| | | } |
| | | MKEmitter.removeListener('reloadData', this.reloadData) |
| | | MKEmitter.removeListener('resetSelectLine', this.resetParentParam) |
| | | } |
| | | |
| | | handleTimer = () => { |
| | | const { config } = this.state |
| | | |
| | | if (!config.timer) return |
| | | |
| | | const _change = { |
| | | '15s': 15000, |
| | | '30s': 30000, |
| | | '1min': 60000, |
| | | '5min': 300000, |
| | | '10min': 600000, |
| | | '15min': 900000, |
| | | '30min': 1800000, |
| | | '1hour': 3600000 |
| | | } |
| | | |
| | | let timer = _change[config.timer] |
| | | |
| | | if (!timer) return |
| | | |
| | | let _param = { |
| | | func: 's_get_timers_role', |
| | | LText: `select '${window.GLOB.appkey || ''}','${config.uuid}'`, |
| | | timer_type: config.timer, |
| | | component_id: config.uuid |
| | | } |
| | | |
| | | _param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') // 时间戳 |
| | | _param.LText = Utils.formatOptions(_param.LText) // 关键字符替换,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) |
| | | } |
| | | |
| | | 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() |
| | | }) |
| | | } |
| | | } |
| | | |
| | | handleData = () => { |
| | |
| | | this.pierender() |
| | | } |
| | | |
| | | async loadData () { |
| | | const { mainSearch, BID, menuType, dataManager } = this.props |
| | | const { config, arr_field, search } = this.state |
| | | |
| | | let searches = fromJS(search).toJS() |
| | | if (mainSearch && mainSearch.length > 0) { // 主表搜索条件 |
| | | searches = [...mainSearch, ...searches] |
| | | async loadData (hastimer) { |
| | | const { mainSearch, menuType } = this.props |
| | | const { config, arr_field, search, BID } = this.state |
| | | |
| | | if (config.setting.supModule && !BID) { // BID 不存在时,不做查询 |
| | | this.setState({ |
| | | data: [] |
| | | }, () => { |
| | | this.handleData() |
| | | }) |
| | | return |
| | | } |
| | | |
| | | this.setState({ |
| | | loading: true |
| | | }) |
| | | let searches = fromJS(search).toJS() |
| | | if (mainSearch && mainSearch.length > 0) { // 主表搜索条件 |
| | | let keys = searches.map(item => item.key) |
| | | mainSearch.forEach(item => { |
| | | if (!keys.includes(item.key)) { |
| | | searches.push(item) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | if (!hastimer) { |
| | | this.setState({ |
| | | loading: true |
| | | }) |
| | | } |
| | | |
| | | let _orderBy = config.setting.order || '' |
| | | let param = UtilsDM.getQueryDataParams(config.setting, arr_field, searches, _orderBy, '', '', BID, menuType, dataManager) |
| | | let param = UtilsDM.getQueryDataParams(config.setting, arr_field, searches, _orderBy, '', '', BID, menuType) |
| | | |
| | | let result = await Api.genericInterface(param) |
| | | if (result.status) { |
| | |
| | | }) |
| | | } else { |
| | | this.setState({ |
| | | loading: false |
| | | loading: false, |
| | | timer: null |
| | | }) |
| | | notification.error({ |
| | | top: 92, |
| | |
| | | let _mdata = new Map() |
| | | _cdata.forEach(item => { |
| | | if (item[plot.Xaxis] && !_mdata.has(item[plot.Xaxis])) { |
| | | |
| | | if (typeof(item[plot.Yaxis]) !== 'number') { |
| | | item[plot.Yaxis] = parseFloat(item[plot.Yaxis]) |
| | | if (isNaN(item[plot.Yaxis])) { |
| | |
| | | |
| | | Y_axis = 'percent' // 显示百分比 |
| | | } |
| | | |
| | | chart.data(dv.rows) |
| | | |
| | | if (plot.shape === 'nightingale') { |
| | |
| | | chart.legend(X_axis, { |
| | | position: plot.legend, |
| | | itemName: { |
| | | style: { |
| | | fill: color, |
| | | } |
| | | style: { fill: color } |
| | | } |
| | | }) |
| | | } else { |
| | | chart.legend({ |
| | | position: plot.legend, |
| | | itemName: { |
| | | style: { |
| | | fill: color, |
| | | } |
| | | style: { fill: color } |
| | | } |
| | | }) |
| | | } |
| | |
| | | }) |
| | | } |
| | | |
| | | let colors = new Map() |
| | | let colorIndex = 0 |
| | | |
| | | if (plot.colors && plot.colors.length > 0) { |
| | | plot.colors.forEach(item => { |
| | | if (!colors.has(item.label)) { |
| | | colors.set(item.label, item.color) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | if (plot.shape !== 'nightingale') { |
| | | let _chart = chart |
| | | .interval() |
| | | .adjust('stack') |
| | | .position(Y_axis) |
| | | .color(X_axis) |
| | | .tooltip(`${X_axis}*${Y_axis}`, (name, value) => { |
| | | if (plot.show !== 'value') { |
| | | value = (value * 100).toFixed(2) + '%' |
| | |
| | | value: value |
| | | } |
| | | }) |
| | | if (plot.colors && plot.colors.length > 0) { |
| | | let limit = chartColors.length |
| | | _chart.color(X_axis, (type) => { |
| | | if (colors.get(type)) { |
| | | return colors.get(type) |
| | | } else { |
| | | colors.set(type, chartColors[colorIndex % limit]) |
| | | colorIndex++ |
| | | } |
| | | }) |
| | | } else { |
| | | _chart.color(X_axis) |
| | | } |
| | | if (plot.label !== 'false') { |
| | | if (plot.label === 'inner') { |
| | | _chart.label(Y_axis, { |
| | | offset: -30, |
| | | content: (data) => { |
| | | let _label = '' |
| | | let _val = '' |
| | | if (plot.show !== 'value') { |
| | | _val = `${(data[Y_axis] * 100).toFixed(2)}%` |
| | | } else { |
| | | _val = `${data[Y_axis]}` |
| | | } |
| | | if (plot.label === 'inner') { |
| | | _label = _val |
| | | } else { |
| | | _label = `${data[X_axis]}: ${_val}` |
| | | } |
| | | return _label |
| | | return _val |
| | | }, |
| | | style: { |
| | | textAlign: 'center', |
| | |
| | | layout: { type: 'pie-spider' }, |
| | | labelHeight: 20, |
| | | content: (data) => { |
| | | let _label = '' |
| | | let _val = '' |
| | | if (plot.show !== 'value') { |
| | | _val = `${(data[Y_axis] * 100).toFixed(2)}%` |
| | | } else { |
| | | _val = `${data[Y_axis]}` |
| | | } |
| | | if (plot.label === 'inner') { |
| | | _label = _val |
| | | } else { |
| | | _label = `${data[X_axis]}: ${_val}` |
| | | } |
| | | return _label |
| | | |
| | | return `${data[X_axis]}: ${_val}` |
| | | }, |
| | | labelLine: { |
| | | style: { |
| | |
| | | let _chart = chart |
| | | .interval() |
| | | .position(`${X_axis}*${Y_axis}`) |
| | | .color(X_axis) |
| | | |
| | | if (plot.label !== 'false') { |
| | | let _label = {} |
| | | if (plot.label === 'inner') { |
| | | _label.offset = -15 |
| | | if (plot.colors && plot.colors.length > 0) { |
| | | let limit = chartColors.length |
| | | _chart.color(X_axis, (type) => { |
| | | if (colors.get(type)) { |
| | | return colors.get(type) |
| | | } else { |
| | | _label.style = { |
| | | fill: color |
| | | } |
| | | colors.set(type, chartColors[colorIndex % limit]) |
| | | colorIndex++ |
| | | } |
| | | }) |
| | | } else { |
| | | _chart.color(X_axis) |
| | | } |
| | | if (plot.label !== 'false') { |
| | | let _label = {} |
| | | if (plot.label === 'inner') { |
| | | _label.offset = -15 |
| | | } else { |
| | | _label.style = { |
| | | fill: color |
| | | } |
| | | } |
| | | |
| | | _chart.label(X_axis, _label) |
| | | .style({ |
| | | lineWidth: 1, |
| | | stroke: '#fff', |
| | | }) |
| | | } |
| | | } |
| | | |
| | | if (plot.linkmenu && plot.linkmenu.length > 0) { |
| | | let menu_id = plot.linkmenu.slice(-1)[0] |
| | | let menu = this.props.permMenus.filter(m => m.MenuID === menu_id)[0] || '' |
| | | |
| | | chart.on('element:dblclick', (ev) => { |
| | | if (!menu) { |
| | | notification.warning({ |
| | | top: 92, |
| | | message: '菜单已删除或没有访问权限!', |
| | | duration: 5 |
| | | }) |
| | | return |
| | | } |
| | | |
| | | try { |
| | | let data = ev.data.data |
| | | let primaryId = '' |
| | | if (this.state.config.setting.primaryKey && data) { |
| | | primaryId = data[this.state.config.setting.primaryKey] || '' |
| | | } |
| | | |
| | | _chart.label(X_axis, _label) |
| | | .style({ |
| | | lineWidth: 1, |
| | | stroke: '#fff', |
| | | let newtab = { |
| | | ...menu, |
| | | selected: true, |
| | | param: { |
| | | BID: primaryId, |
| | | data: data |
| | | } |
| | | } |
| | | |
| | | let tabs = this.props.tabviews.filter(tab => { |
| | | tab.selected = false |
| | | return tab.MenuID !== newtab.MenuID |
| | | }) |
| | | |
| | | if (this.props.tabviews.length !== tabs.length) { |
| | | this.props.modifyTabview(fromJS(tabs).toJS()) |
| | | } |
| | | |
| | | this.setState({}, () => { |
| | | tabs.push(newtab) |
| | | this.props.modifyTabview(tabs) |
| | | }) |
| | | } catch { |
| | | console.warn('菜单打开失败!') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | chart.render() |
| | | } |
| | | |
| | | refreshSearch = (list) => { |
| | | this.setState({search: list}, () => { |
| | | this.loadData() |
| | | }) |
| | | } |
| | | |
| | | render() { |
| | | const { showHeader, config, loading, title, empty } = this.state |
| | | const { config, loading, empty, BID } = this.state |
| | | |
| | | return ( |
| | | <div className="custom-pie-chart-plot-box" style={config.style}> |
| | |
| | | <Spin /> |
| | | </div> : null |
| | | } |
| | | {showHeader ? <div className="chart-header"> |
| | | <span className="chart-title">{title}</span> |
| | | {/* <searchLine /> */} |
| | | </div> : null} |
| | | <NormalHeader config={config} BID={BID} menuType={this.props.menuType} refresh={this.refreshSearch} /> |
| | | <div className="canvas-wrap"> |
| | | <div className={'canvas' + (empty ? ' empty' : '')} id={this.state.chartId}></div> |
| | | </div> |
| | |
| | | } |
| | | } |
| | | |
| | | export default LineChart |
| | | const mapStateToProps = (state) => { |
| | | return { |
| | | tabviews: state.tabviews, |
| | | permMenus: state.permMenus, |
| | | } |
| | | } |
| | | |
| | | const mapDispatchToProps = (dispatch) => { |
| | | return { |
| | | modifyTabview: (tabviews) => dispatch(modifyTabview(tabviews)) |
| | | } |
| | | } |
| | | |
| | | export default connect(mapStateToProps, mapDispatchToProps)(PieChart) |