272 lines
9.1 KiB
JavaScript
272 lines
9.1 KiB
JavaScript
|
|
// 核心库
|
|||
|
|
import React, { Component } from 'react'
|
|||
|
|
import { connect } from 'dva'
|
|||
|
|
import PropTypes from 'prop-types'
|
|||
|
|
// 组件库
|
|||
|
|
import ReactEcharts from "echarts-for-react"
|
|||
|
|
import { Popover, Table, Icon, Spin, Input, message } from 'antd'
|
|||
|
|
import { ExportToExcel, IFComponent } from '@woowalker/feui'
|
|||
|
|
// 工具库
|
|||
|
|
import { isEqual } from 'lodash'
|
|||
|
|
import { uuid, initFilter } from '../../utils/common'
|
|||
|
|
// 样式
|
|||
|
|
import classNames from 'classnames'
|
|||
|
|
import styles from './chart.css'
|
|||
|
|
|
|||
|
|
class TableChart extends Component {
|
|||
|
|
constructor (props) {
|
|||
|
|
super(props)
|
|||
|
|
this.state = {
|
|||
|
|
data: [],
|
|||
|
|
columns: [],
|
|||
|
|
toggleChart: false,
|
|||
|
|
// 用于列表导出的 id
|
|||
|
|
tableId: uuid(),
|
|||
|
|
// 设定的参考目标值
|
|||
|
|
targetVal: undefined
|
|||
|
|
}
|
|||
|
|
this.refOfChart = null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
componentDidMount () {
|
|||
|
|
const { option, type, chartId } = this.props
|
|||
|
|
if (option) {
|
|||
|
|
this.getTableData(option, type)
|
|||
|
|
chartId && this.getTargetVal()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
|||
|
|
if (!isEqual(this.props.option, nextProps.option)) {
|
|||
|
|
this.getTableData(nextProps.option, nextProps.type)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 生成 table 数据 */
|
|||
|
|
getTableData = (option, type) => {
|
|||
|
|
const data = []
|
|||
|
|
const columns = []
|
|||
|
|
const { radar, series = [], xAxis = [], yAxis = [] } = option
|
|||
|
|
switch (type) {
|
|||
|
|
// 饼图
|
|||
|
|
case 2:
|
|||
|
|
if (series[0] && Array.isArray(series[0].data) && series[0].data.length) {
|
|||
|
|
// 从 series 取 columns
|
|||
|
|
let total = 0
|
|||
|
|
series[0].data.forEach(({ name, value }) => {
|
|||
|
|
total += value
|
|||
|
|
columns.push({ title: name, dataIndex: name, key: name })
|
|||
|
|
})
|
|||
|
|
// 从 series 取 data
|
|||
|
|
const tempTableData = {}
|
|||
|
|
series[0].data.forEach(({ name, value }) => {
|
|||
|
|
tempTableData[name] = `${value}(${total ? (value / total * 100).toFixed(2) : 0}%)`
|
|||
|
|
})
|
|||
|
|
data.push(tempTableData)
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
// 雷达图
|
|||
|
|
case 3:
|
|||
|
|
if (radar[0] && Array.isArray(radar[0].indicator) && radar[0].indicator.length) {
|
|||
|
|
columns.push({ title: '类型', dataIndex: 'name', key: 'name' })
|
|||
|
|
// 从 radar 取 columns
|
|||
|
|
radar[0].indicator.forEach(item => columns.push({ title: `${item.text}(${item.max})`, dataIndex: item.text, key: item.text }))
|
|||
|
|
// 从 series 取 data
|
|||
|
|
series.forEach(serie => {
|
|||
|
|
const tempTableData = {}
|
|||
|
|
columns.forEach((item, index) => {
|
|||
|
|
const { name, data: serieData } = serie
|
|||
|
|
tempTableData[item.key] = item.title === '类型' ? name : serieData[0].value[index - 1]
|
|||
|
|
})
|
|||
|
|
data.push(tempTableData)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
// 折线、柱状图
|
|||
|
|
default:
|
|||
|
|
if (xAxis[0] && Array.isArray(xAxis[0].data) && xAxis[0].data.length) {
|
|||
|
|
columns.push({ title: '类型', dataIndex: 'name', key: 'name' })
|
|||
|
|
// 从 xAxis 取 columns
|
|||
|
|
xAxis[0].data.forEach(item => columns.push({ title: item, dataIndex: item, key: item }))
|
|||
|
|
// 从 series 取 data
|
|||
|
|
series.forEach(serie => {
|
|||
|
|
const tempTableData = {}
|
|||
|
|
columns.forEach((item, index) => {
|
|||
|
|
const { name, data: serieData, yAxisIndex } = serie
|
|||
|
|
const yName = (yAxis[yAxisIndex || 0] || {}).name
|
|||
|
|
tempTableData[item.key] = item.title === '类型' ? `${name}${yName ? '(' + yName + ')' : ''}` : (serieData[index - 1]?.TooltipValue || serieData[index - 1]?.value)
|
|||
|
|
})
|
|||
|
|
data.push(tempTableData)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
this.setState({ data, columns })
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 获取 chartId 对应的目标值 */
|
|||
|
|
getTargetVal = () => {
|
|||
|
|
const { chartId, login, dispatch } = this.props
|
|||
|
|
const json = initFilter(login.OrgId, chartId)
|
|||
|
|
dispatch({
|
|||
|
|
type: 'app/getRedisValue',
|
|||
|
|
payload: json
|
|||
|
|
}).then(res => {
|
|||
|
|
this.setState({ targetVal: isNaN(res) ? res : Number(res) })
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 设置 chartId 对应的目标值 */
|
|||
|
|
setTargetVal = (value) => {
|
|||
|
|
const { chartId, login, dispatch } = this.props
|
|||
|
|
const json = initFilter(login.OrgId, chartId)
|
|||
|
|
json.Parameter1 = value
|
|||
|
|
dispatch({
|
|||
|
|
type: 'app/setRedisValue',
|
|||
|
|
payload: json
|
|||
|
|
}).then(res => {
|
|||
|
|
if (res) {
|
|||
|
|
this.setState({ targetVal: isNaN(value) ? value : Number(value) })
|
|||
|
|
message.success('操作成功')
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 处理目标值 targetVal 的修改 */
|
|||
|
|
handleTVChange = (evt) => {
|
|||
|
|
const { value } = evt.target
|
|||
|
|
const targetVal = value === '' ? '' : isNaN(value) ? value : Number(value)
|
|||
|
|
this.setState({ targetVal })
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
getChartOption = (option) => {
|
|||
|
|
const { targetVal } = this.state
|
|||
|
|
if (Array.isArray(option.series) && option.series.length && targetVal) {
|
|||
|
|
if (Array.isArray(option.yAxis) && option.yAxis.length) {
|
|||
|
|
// 标记线默认对应的Y轴就是第一条Y轴,所以这里默认取第一条Y轴进行设置
|
|||
|
|
const { max, min } = option.yAxis[0]
|
|||
|
|
let maxSet = false
|
|||
|
|
let minSet = false
|
|||
|
|
// 监测对应 max 值是否小于 targetVal 值
|
|||
|
|
if (max) {
|
|||
|
|
maxSet = max < targetVal
|
|||
|
|
} else {
|
|||
|
|
// 取对应默认第一条Y轴的数据,取其最大值
|
|||
|
|
const targetSeries = option.series.filter(item => item.yAxisIndex === 0)
|
|||
|
|
const maxData = Math.max.apply(null, targetSeries.map(item => Math.max.apply(null, item.data)))
|
|||
|
|
maxSet = maxData < targetVal
|
|||
|
|
}
|
|||
|
|
// 监测对应 min 值是否大于 targetVal 值
|
|||
|
|
minSet = min > targetVal
|
|||
|
|
// 设置 targetVal 对应的 max 和 min 值
|
|||
|
|
maxSet && (option.yAxis[0].max = targetVal)
|
|||
|
|
minSet && (option.yAxis[0].min = targetVal)
|
|||
|
|
}
|
|||
|
|
return {
|
|||
|
|
...option,
|
|||
|
|
series: [
|
|||
|
|
{
|
|||
|
|
name: '标记线',
|
|||
|
|
type: 'line',
|
|||
|
|
markLine: {
|
|||
|
|
label: {
|
|||
|
|
position: 'insideEndTop',
|
|||
|
|
formatter: '{b}: {c}'
|
|||
|
|
},
|
|||
|
|
lineStyle: {
|
|||
|
|
color: '#096dd9'
|
|||
|
|
},
|
|||
|
|
data: [
|
|||
|
|
{
|
|||
|
|
name: '目标值',
|
|||
|
|
yAxis: targetVal,
|
|||
|
|
lineStyle: { width: 2 }
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
].concat(option.series)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return option
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
render () {
|
|||
|
|
const { chartId, option, onEvents, loading, style } = this.props
|
|||
|
|
|
|||
|
|
const { targetVal, data, columns, toggleChart } = this.state
|
|||
|
|
const loadingGet = this.props.dvaLoading.effects['app/getRedisValue']
|
|||
|
|
const loadingSet = this.props.dvaLoading.effects['app/setRedisValue']
|
|||
|
|
return (
|
|||
|
|
<Spin size='large' spinning={!!loading} wrapperClassName={styles.chartTable}>
|
|||
|
|
<div className={styles.chartTableSwap}>
|
|||
|
|
{/** 额外需要插入的工具集 */}
|
|||
|
|
{this.props.children}
|
|||
|
|
{/** 目标值设定 */}
|
|||
|
|
<IFComponent IF={!toggleChart && !!chartId}>
|
|||
|
|
<Popover
|
|||
|
|
content={
|
|||
|
|
<Input.Search
|
|||
|
|
autoFocus
|
|||
|
|
allowClear
|
|||
|
|
value={targetVal}
|
|||
|
|
loading={loadingGet || loadingSet}
|
|||
|
|
placeholder='请输入设定目标值'
|
|||
|
|
enterButton='保存'
|
|||
|
|
onChange={this.handleTVChange}
|
|||
|
|
onSearch={this.setTargetVal}
|
|||
|
|
/>
|
|||
|
|
}
|
|||
|
|
trigger='click'
|
|||
|
|
>
|
|||
|
|
<Icon type='setting' title='目标值设定' style={{ marginRight: 10 }} />
|
|||
|
|
</Popover>
|
|||
|
|
</IFComponent>
|
|||
|
|
{/** 图形列表切换按钮 */}
|
|||
|
|
<span onClick={() => { this.setState({ toggleChart: !toggleChart }) }}>
|
|||
|
|
<Icon type='swap' />
|
|||
|
|
<span>{`切换${toggleChart ? '图表' : '列表'}`}</span>
|
|||
|
|
</span>
|
|||
|
|
{/** 导出按钮 */}
|
|||
|
|
<IFComponent IF={toggleChart}>
|
|||
|
|
<ExportToExcel
|
|||
|
|
fileName='列表数据'
|
|||
|
|
tableId={this.state.tableId}
|
|||
|
|
render={({ onExport }) => <Icon type='download' title='导出' onClick={onExport} style={{ paddingLeft: 10 }} />}
|
|||
|
|
/>
|
|||
|
|
</IFComponent>
|
|||
|
|
</div>
|
|||
|
|
<ReactEcharts
|
|||
|
|
ref={ref => this.refOfChart = ref?.getEchartsInstance()}
|
|||
|
|
notMerge
|
|||
|
|
lazyUpdate
|
|||
|
|
option={this.getChartOption(option)}
|
|||
|
|
onEvents={onEvents}
|
|||
|
|
className={classNames(styles.chart, { [styles.show]: !toggleChart, [styles.hide]: toggleChart })}
|
|||
|
|
style={{ width: '100%', height: '100%', ...style }}
|
|||
|
|
/>
|
|||
|
|
<Table
|
|||
|
|
id={this.state.tableId}
|
|||
|
|
bordered
|
|||
|
|
rowKey='ID'
|
|||
|
|
size='small'
|
|||
|
|
dataSource={data}
|
|||
|
|
columns={columns}
|
|||
|
|
scroll={{ x: true }}
|
|||
|
|
pagination={false}
|
|||
|
|
className={classNames(styles.table, { [styles.show]: toggleChart, [styles.hide]: !toggleChart })}
|
|||
|
|
/>
|
|||
|
|
</Spin>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TableChart.propTypes = {
|
|||
|
|
option: PropTypes.object,
|
|||
|
|
type: PropTypes.string,
|
|||
|
|
chartId: PropTypes.string,
|
|||
|
|
onEvents: PropTypes.object,
|
|||
|
|
style: PropTypes.object
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default connect(({ login, loading }) => ({ login, dvaLoading: loading }))(TableChart)
|