mh_jy_safe_web/src/components/Chart/TableChart.js

272 lines
9.1 KiB
JavaScript
Raw Normal View History

2025-08-25 10:08:30 +08:00
// 核心库
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)