| | |
| | | import PropTypes from 'prop-types' |
| | | import { is, fromJS } from 'immutable' |
| | | import { Chart } from '@antv/g2' |
| | | import { Spin, Empty, notification } from 'antd' |
| | | import { Spin, Empty } from 'antd' |
| | | import { DownloadOutlined } from '@ant-design/icons' |
| | | import moment from 'moment' |
| | | |
| | | import Api from '@/api' |
| | | import Utils from '@/utils/utils.js' |
| | | import asyncComponent from './asyncButtonComponent' |
| | | import asyncBtnComponent from './asyncButtonComponent' |
| | | import UtilsDM from '@/utils/utils-datamanage.js' |
| | | import MKEmitter from '@/utils/events.js' |
| | | import TimerTask from '@/utils/timer-task.js' |
| | | import NormalHeader from '@/tabviews/custom/components/share/normalheader' |
| | | import './index.scss' |
| | | |
| | | const NormalHeader = asyncComponent(() => import('@/tabviews/custom/components/share/normalheader')) |
| | | const ExcelOutButton = asyncComponent(() => import('@/tabviews/zshare/actionList/exceloutbutton')) |
| | | const ExcelInButton = asyncComponent(() => import('@/tabviews/zshare/actionList/excelInbutton')) |
| | | const ExcelOutButton = asyncBtnComponent(() => import('@/tabviews/zshare/actionList/exceloutbutton')) |
| | | const ExcelInButton = asyncBtnComponent(() => import('@/tabviews/zshare/actionList/excelInbutton')) |
| | | |
| | | class ScatterChart extends Component { |
| | | static propTpyes = { |
| | | BID: PropTypes.any, // 父级Id |
| | | data: PropTypes.array, // 统一查询数据 |
| | | config: PropTypes.object, // 组件配置信息 |
| | | mainSearch: PropTypes.any, // 外层搜索条件 |
| | | menuType: PropTypes.any, // 菜单类型 |
| | | config: PropTypes.object |
| | | } |
| | | |
| | | state = { |
| | |
| | | empty: true, // 图表数据为空 |
| | | loading: false, // 数据加载状态 |
| | | chartId: Utils.getuuid(), // 图表Id |
| | | sync: false, // 是否统一请求数据 |
| | | plot: null, // 图表设置 |
| | | data: null, // 数据 |
| | | search: null, // 搜索条件 |
| | | chart: null |
| | | } |
| | | |
| | | data = [] |
| | | loaded = false |
| | | |
| | | UNSAFE_componentWillMount () { |
| | | const { config, data, initdata, BID } = this.props |
| | | const { config } = this.props |
| | | |
| | | let _config = fromJS(config).toJS() |
| | | let _data = null |
| | | let _sync = config.setting.sync === 'true' |
| | | let BID = '' |
| | | let BData = '' |
| | | |
| | | if (config.setting.sync === 'true' && data) { |
| | | _data = data[config.dataName] || [] |
| | | _sync = false |
| | | } else if (config.setting.sync === 'true' && initdata) { |
| | | _data = initdata || [] |
| | | _sync = false |
| | | } |
| | | |
| | | if (config.plot.title || config.search.length > 0) { |
| | | _config.plot.height = _config.plot.height - 80 |
| | | if (config.setting.supModule) { |
| | | BData = window.GLOB.CacheData.get(config.setting.supModule) |
| | | } else { |
| | | _config.plot.height = _config.plot.height - 30 |
| | | BData = window.GLOB.CacheData.get(config.$pageId) |
| | | } |
| | | if (BData) { |
| | | BID = BData.$BID || '' |
| | | } |
| | | |
| | | _config.style.height = config.plot.height |
| | | _config.plot.height = Utils.getHeight(_config.plot.height) |
| | | _config.style.height = 'auto' |
| | | _config.style.minHeight = _config.plot.height + 30 |
| | | |
| | | if (_config.plot.title) { |
| | | _config.style.minHeight = _config.style.minHeight + 45 |
| | | } |
| | | |
| | | if (_config.setting.sync === 'true') { |
| | | _config.setting.onload = 'false' |
| | | |
| | | if (window.GLOB.SyncData.has(_config.dataName)) { |
| | | this.data = window.GLOB.SyncData.get(_config.dataName) || [] |
| | | |
| | | if (_config.$cache) { |
| | | Api.writeCacheConfig(_config.uuid, fromJS(this.data).toJS(), BID) |
| | | } |
| | | |
| | | _config.setting.sync = 'false' |
| | | |
| | | this.loaded = true |
| | | |
| | | window.GLOB.SyncData.delete(_config.dataName) |
| | | } |
| | | } |
| | | |
| | | this.setState({ |
| | | config: _config, |
| | | data: _data, |
| | | BID: BID || '', |
| | | empty: !_data, |
| | | arr_field: _config.columns.map(col => col.field).join(','), |
| | | empty: this.data.length === 0, |
| | | 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() |
| | | } |
| | | search: _config.$searches |
| | | }) |
| | | } |
| | | |
| | | /** |
| | | * @description 图表数据更新,刷新内容 |
| | | */ |
| | | UNSAFE_componentWillReceiveProps (nextProps) { |
| | | const { sync, config } = this.state |
| | | |
| | | if (sync && !is(fromJS(this.props.data), fromJS(nextProps.data))) { |
| | | let _data = [] |
| | | if (nextProps.data && nextProps.data[config.dataName]) { |
| | | _data = nextProps.data[config.dataName] || [] |
| | | } |
| | | |
| | | this.setState({sync: false, data: _data, empty: !_data,}, () => { |
| | | this.handleData() |
| | | }) |
| | | } else if (nextProps.mainSearch && !is(fromJS(this.props.mainSearch), fromJS(nextProps.mainSearch))) { |
| | | if (config.setting.syncRefresh === 'true') { |
| | | this.setState({}, () => { |
| | | this.loadData() |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | |
| | | shouldComponentUpdate (nextProps, nextState) { |
| | |
| | | } |
| | | |
| | | componentDidMount () { |
| | | const { config } = this.state |
| | | |
| | | MKEmitter.addListener('reloadData', this.reloadData) |
| | | MKEmitter.addListener('resetSelectLine', this.resetParentParam) |
| | | MKEmitter.addListener('getexceloutparam', this.getexceloutparam) |
| | | MKEmitter.addListener('queryModuleParam', this.queryModuleParam) |
| | | MKEmitter.addListener('refreshByButtonResult', this.refreshByButtonResult) |
| | | this.handleTimer() |
| | | |
| | | if (config.setting.useMSearch) { |
| | | MKEmitter.addListener('searchRefresh', this.searchRefresh) |
| | | } |
| | | |
| | | if (config.setting.sync === 'true') { |
| | | MKEmitter.addListener('transferSyncData', this.transferSyncData) |
| | | } |
| | | |
| | | if (config.timer) { |
| | | this.timer = new TimerTask() |
| | | this.timer.init(config.uuid, config.timer, config.timerRepeats, () => { |
| | | this.loadData('timer') |
| | | }) |
| | | } |
| | | |
| | | this.initExec() |
| | | } |
| | | |
| | | /** |
| | | * @description 组件销毁,清除state更新,清除快捷键设置 |
| | | */ |
| | | componentWillUnmount () { |
| | | clearTimeout(this.timer) |
| | | this.setState = () => { |
| | | return |
| | | } |
| | | MKEmitter.removeListener('reloadData', this.reloadData) |
| | | MKEmitter.removeListener('searchRefresh', this.searchRefresh) |
| | | MKEmitter.removeListener('resetSelectLine', this.resetParentParam) |
| | | MKEmitter.removeListener('getexceloutparam', this.getexceloutparam) |
| | | MKEmitter.removeListener('queryModuleParam', this.queryModuleParam) |
| | | MKEmitter.removeListener('transferSyncData', this.transferSyncData) |
| | | MKEmitter.removeListener('refreshByButtonResult', this.refreshByButtonResult) |
| | | |
| | | this.timer && this.timer.stop() |
| | | } |
| | | |
| | | handleTimer = () => { |
| | | initExec = () => { |
| | | const { config, BID } = this.state |
| | | |
| | | if (config.$cache) { |
| | | if (config.$time) { |
| | | if (!this.loaded) { |
| | | Api.getLCacheConfig(config.uuid, config.$time, BID).then(res => { |
| | | if (!res.valid && config.setting.onload === 'true') { |
| | | setTimeout(() => { |
| | | this.loadData('init') |
| | | }, config.setting.delay || 0) |
| | | } |
| | | |
| | | if (!res.data || this.loaded) return |
| | | |
| | | this.data = res.data |
| | | this.handleData() |
| | | |
| | | this.setState({empty: res.data.length === 0}) |
| | | }) |
| | | } else { |
| | | this.handleData() |
| | | } |
| | | } else { |
| | | if (!this.loaded) { |
| | | Api.getLCacheConfig(config.uuid, 0, BID).then(res => { |
| | | if (!res.data || this.loaded) return |
| | | |
| | | this.data = res.data |
| | | this.handleData() |
| | | |
| | | this.setState({empty: res.data.length === 0}) |
| | | }) |
| | | } |
| | | |
| | | if (config.setting.onload === 'true') { |
| | | setTimeout(() => { |
| | | this.loadData('init') |
| | | }, config.setting.delay || 0) |
| | | } else if (this.loaded) { |
| | | this.handleData() |
| | | } |
| | | } |
| | | } else if (config.setting.onload === 'true') { |
| | | setTimeout(() => { |
| | | this.loadData() |
| | | }, config.setting.delay || 0) |
| | | } else if (this.loaded) { |
| | | this.handleData() |
| | | } |
| | | } |
| | | |
| | | transferSyncData = (syncId) => { |
| | | const { config, BID } = this.state |
| | | |
| | | if (config.$syncId !== syncId) return |
| | | |
| | | let _data = window.GLOB.SyncData.get(config.dataName) || [] |
| | | |
| | | if (config.$cache) { |
| | | Api.writeCacheConfig(config.uuid, fromJS(_data).toJS(), BID) |
| | | } |
| | | |
| | | this.data = _data |
| | | this.handleData() |
| | | |
| | | this.setState({empty: _data.length === 0}) |
| | | |
| | | this.loaded = true |
| | | |
| | | window.GLOB.SyncData.delete(config.dataName) |
| | | |
| | | MKEmitter.removeListener('transferSyncData', this.transferSyncData) |
| | | } |
| | | |
| | | searchRefresh = (searchId) => { |
| | | 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 |
| | | } |
| | | if (config.$searchId !== searchId) return |
| | | |
| | | _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) |
| | | } |
| | | this.setState({}, () => { |
| | | this.loadData() |
| | | }) |
| | | } |
| | | |
| | | timerTask = () => { |
| | | const { timer } = this.state |
| | | if (!timer) return |
| | | |
| | | this.loadData(true) |
| | | |
| | | this.timer = setTimeout(() => { |
| | | this.timerTask() |
| | | }, timer) |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | 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')) |
| | | if (['mainline', 'maingrid', 'popclose'].includes(position) && config.setting.supModule) { |
| | | MKEmitter.emit('reloadData', config.setting.supModule, position === 'maingrid' ? '' : BID) |
| | | } else { |
| | | this.loadData() |
| | | } |
| | | } |
| | | |
| | |
| | | const { config } = this.state |
| | | |
| | | if (!config.setting.supModule || config.setting.supModule !== MenuID) return |
| | | if (id !== this.state.BID) { |
| | | if (id !== this.state.BID || id !== '') { |
| | | this.setState({ BID: id }, () => { |
| | | this.loadData() |
| | | }) |
| | |
| | | /** |
| | | * @description 导出Excel时,获取页面搜索排序等参数 |
| | | */ |
| | | getexceloutparam = (menuId, btnId) => { |
| | | const { mainSearch } = this.props |
| | | const { arr_field, config, search } = this.state |
| | | queryModuleParam = (menuId, callback) => { |
| | | const { config, search } = this.state |
| | | |
| | | if (config.uuid !== menuId) return |
| | | |
| | | let searches = search ? fromJS(search).toJS() : [] |
| | | if (mainSearch && mainSearch.length > 0) { // 主表搜索条件 |
| | | let keys = searches.map(item => item.key.toLowerCase()) |
| | | if (config.setting.useMSearch) { // 主表搜索条件 |
| | | let mainSearch = window.GLOB.SearchBox.get(config.$searchId) || [] |
| | | let keys = config.$s_keys || [] |
| | | mainSearch.forEach(item => { |
| | | if (!keys.includes(item.key.toLowerCase())) { |
| | | searches.push(item) |
| | | } |
| | | if (keys.includes(item.key.toLowerCase())) return |
| | | |
| | | searches.push(item) |
| | | }) |
| | | } |
| | | |
| | | MKEmitter.emit('execExcelout', config.uuid, btnId, { |
| | | arr_field: arr_field, |
| | | callback({ |
| | | orderBy: config.setting.order || '', |
| | | search: searches, |
| | | menuName: config.name |
| | | search: searches |
| | | }) |
| | | } |
| | | |
| | | /** |
| | | * @description 数据加载 |
| | | */ |
| | | async loadData (hastimer) { |
| | | const { mainSearch, menuType } = this.props |
| | | const { config, arr_field, BID, search } = this.state |
| | | async loadData (type) { |
| | | const { config, BID, search } = this.state |
| | | |
| | | if (config.setting.supModule && !BID) { // BID 不存在时,不做查询 |
| | | this.setState({ |
| | | data: [], |
| | | empty: false, |
| | | }, () => { |
| | | this.setState({ empty: true }) |
| | | |
| | | if (!is(fromJS(this.data), fromJS([]))) { |
| | | this.data = [] |
| | | this.handleData() |
| | | }) |
| | | } |
| | | |
| | | this.loaded = true |
| | | return |
| | | } |
| | | |
| | | let searches = search ? fromJS(search).toJS() : [] |
| | | if (mainSearch && mainSearch.length > 0) { // 主表搜索条件 |
| | | let keys = searches.map(item => item.key) |
| | | if (config.setting.useMSearch) { // 主表搜索条件 |
| | | let mainSearch = window.GLOB.SearchBox.get(config.$searchId) || [] |
| | | let keys = config.$s_keys || [] |
| | | mainSearch.forEach(item => { |
| | | if (!keys.includes(item.key)) { |
| | | searches.push(item) |
| | | } |
| | | if (keys.includes(item.key.toLowerCase())) return |
| | | |
| | | searches.push(item) |
| | | }) |
| | | } |
| | | |
| | | let requireFields = searches.filter(item => item.required && (!item.value || item.value.length === 0)) |
| | | if (requireFields.length > 0) { |
| | | this.setState({ |
| | | loading: false |
| | | }) |
| | | if (config.$s_req && searches.filter(item => item.required && item.value === '').length > 0) { |
| | | return |
| | | } |
| | | |
| | | if (!hastimer) { |
| | | if (type !== 'timer') { |
| | | this.setState({ |
| | | loading: true |
| | | }) |
| | | } |
| | | |
| | | let _orderBy = config.setting.order || '' |
| | | let param = UtilsDM.getQueryDataParams(config.setting, arr_field, searches, _orderBy, '', '', BID, menuType) |
| | | let param = UtilsDM.getQueryDataParams(config.setting, searches, _orderBy, '', '', BID) |
| | | |
| | | let result = await Api.genericInterface(param) |
| | | if (result.status) { |
| | | if (config.$cache && type === 'init') { |
| | | Api.writeCacheConfig(config.uuid, result.data || [], BID) |
| | | } |
| | | this.loaded = true |
| | | |
| | | this.setState({ |
| | | data: result.data, |
| | | empty: false, |
| | | empty: !result.data || result.data.length === 0, |
| | | loading: false |
| | | }, () => { |
| | | this.handleData() |
| | | }) |
| | | |
| | | if (!is(fromJS(this.data), fromJS(result.data || []))) { |
| | | this.data = result.data || [] |
| | | this.handleData() |
| | | } |
| | | |
| | | if (config.timer && config.clearField && result.data && result.data[0]) { |
| | | let vals = (config.clearValue || '').split(',') |
| | | if (vals.includes(result.data[0][config.clearField])) { |
| | | this.timer && this.timer.stop() |
| | | } |
| | | } |
| | | |
| | | UtilsDM.querySuccess(result) |
| | | } else { |
| | | this.setState({ |
| | | loading: false, |
| | | timer: null |
| | | loading: false |
| | | }) |
| | | notification.error({ |
| | | top: 92, |
| | | message: result.message, |
| | | duration: 10 |
| | | }) |
| | | this.timer && this.timer.stop() |
| | | |
| | | UtilsDM.queryFail(result) |
| | | } |
| | | } |
| | | |
| | |
| | | if (_element) { |
| | | _element.innerHTML = '' |
| | | } |
| | | this.scatterrender() |
| | | |
| | | setTimeout(() => { |
| | | this.scatterrender() |
| | | }, 100) |
| | | } |
| | | |
| | | /** |
| | | * @description 折线图渲染 |
| | | * @description 散点图渲染 |
| | | */ |
| | | scatterrender = () => { |
| | | const { plot, data, chartId } = this.state |
| | | const { plot, chartId } = this.state |
| | | const chart = new Chart({ |
| | | container: chartId, |
| | | autoFit: true, |
| | | height: plot.height |
| | | }) |
| | | |
| | | chart.data(data); |
| | | chart.data(this.data); |
| | | chart.scale({ |
| | | [plot.Xaxis]: { nice: true }, |
| | | [plot.Yaxis]: { nice: true }, |
| | |
| | | itemName: { style: { fill: plot.color } } |
| | | }) |
| | | |
| | | chart.tooltip({ |
| | | showTitle: false, |
| | | showCrosshairs: true, |
| | | crosshairs: { |
| | | type: 'xy', |
| | | } |
| | | }) |
| | | if (plot.tooltip !== 'true') { |
| | | chart.tooltip(false) |
| | | } else { |
| | | chart.tooltip({ |
| | | showTitle: false, |
| | | showCrosshairs: true, |
| | | crosshairs: { |
| | | type: 'xy', |
| | | } |
| | | }) |
| | | } |
| | | |
| | | chart |
| | | .point() |
| | | .position(`${plot.Xaxis}*${plot.Yaxis}`) |
| | |
| | | .style({ |
| | | fillOpacity: 0.85 |
| | | }) |
| | | chart.interaction('legend-highlight'); |
| | | if (plot.interaction && plot.interaction.length) { |
| | | plot.interaction.forEach(t => { |
| | | chart.interaction(t) |
| | | }) |
| | | } |
| | | chart.render() |
| | | |
| | | this.setState({chart}) |
| | | } |
| | | |
| | | refreshSearch = (list) => { |
| | |
| | | }) |
| | | } |
| | | |
| | | downloadImage = () => { |
| | | const { chart, config } = this.state |
| | | const link = document.createElement('a'); |
| | | const filename = `${config.name}${moment().format('YYYY-MM-DD HH_mm_ss')}.png`; |
| | | const canvas = chart.getCanvas(); |
| | | canvas.get('timeline').stopAllAnimations(); |
| | | |
| | | setTimeout(() => { |
| | | const canvas = chart.getCanvas(); |
| | | const canvasDom = canvas.get('el'); |
| | | const dataURL = canvasDom.toDataURL('image/png'); |
| | | |
| | | if (window.Blob && window.URL) { |
| | | const arr = dataURL.split(','); |
| | | const mime = arr[0].match(/:(.*?);/)[1]; |
| | | const bstr = atob(arr[1]); |
| | | let n = bstr.length; |
| | | const u8arr = new Uint8Array(n); |
| | | while (n--) { |
| | | u8arr[n] = bstr.charCodeAt(n); |
| | | } |
| | | const blobObj = new Blob([u8arr], { type: mime }); |
| | | if (window.navigator.msSaveBlob) { |
| | | window.navigator.msSaveBlob(blobObj, filename); |
| | | } else { |
| | | link.addEventListener('click', () => { |
| | | link.download = filename; |
| | | link.href = window.URL.createObjectURL(blobObj); |
| | | }); |
| | | } |
| | | } |
| | | const e = document.createEvent('MouseEvents'); |
| | | e.initEvent('click', false, false); |
| | | link.dispatchEvent(e); |
| | | }, 16); |
| | | } |
| | | |
| | | render() { |
| | | const { config, loading, empty, BID } = this.state |
| | | |
| | | let style = {...config.style} |
| | | if (empty && config.plot.empty === 'hidden') { |
| | | style.opacity = 0 |
| | | style.position = 'absolute' |
| | | style.zIndex = -1 |
| | | style.width = '100%' |
| | | } |
| | | |
| | | return ( |
| | | <div className="custom-scatter-plot-box" style={config.style}> |
| | | <div className="custom-scatter-plot-box" id={'anchor' + config.uuid} style={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} /> |
| | | <NormalHeader config={config} BID={BID} refresh={this.refreshSearch} /> |
| | | <div className="canvas-wrap"> |
| | | <div className="chart-action"> |
| | | {config.plot.download === 'enable' && this.state.chart && !empty ? <DownloadOutlined onClick={this.downloadImage} className="system-color download"/> : null} |
| | | <div className={'chart-action' + (config.plot.download === 'enable' ? ' downable' : '')}> |
| | | {config.action.map(item => { |
| | | if (item.OpenType === 'excelOut') { |
| | | return ( |