概念:
因为react-native需要使用到echart , 同时react-native需要转换成web端使用,所以才会有这两种方式的实现echart-native组件的编写
注意webView 需要添加androidLayerType={'software'}
不然有个别手机会出现闪退问题出现原因是原生的webView 嵌套react-native的webView
导致的,使用该参数关闭硬件加速就没问题
common/chart.tsx
import React, {Component} from 'react';
import {ActivityIndicator, Platform, View, ViewStyle} from 'react-native';
import {WebView} from 'react-native-webview';
import debounce from 'lodash/debounce';
const defaultH5HTML = `
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.5.0/echarts.min.js"></script>
</head>
<body>
</body>
<script>
var myChart = null
if (window.ReactNativeWebView && window.ReactNativeWebView.postMessage) {;
window.postMessage = function(data) {
var str = typeof data === 'object' ? JSON.stringify(data) : data;
window.ReactNativeWebView.postMessage(str)
};
window.parent.postMessage = window.postMessage
};
function EchartCommonInit(option, wrapStyle) {
var chartDom = document.createElement('div')
chartDom.style.width = '100%'
chartDom.style.height = '400px'
document.body.innerHTML = ''
document.body.append(chartDom)
if (echarts) {
myChart = echarts.init(chartDom, null, {renderer: 'canvas',useDirtyRect: true, useCoarsePointer: null, pointerSize: 10});
var options = {}
var wrapStyleObj = {}
if (option && typeof option === 'string') {
options = JSON.parse(option)
}
if (option && typeof option === 'object') {
options = option
}
if (wrapStyle && typeof wrapStyle === 'string') {
wrapStyleObj = JSON.parse(wrapStyle)
}
if (wrapStyle && typeof wrapStyle === 'object') {
wrapStyleObj = wrapStyle
}
for (var key in wrapStyleObj) {
var value = wrapStyleObj[key]
chartDom.style[key] = value
}
myChart.clear();
options && myChart.setOption(options);
var myChartParams = {
width: 320
}
if (wrapStyleObj.height) {
myChartParams.height = wrapStyleObj.height
}
if (chartDom.offsetWidth) {
myChartParams.width = chartDom.offsetWidth
}
myChart.resize(myChartParams);
myChart.off('click')
// 为饼图添加点击事件监听
myChart.on('click', function (params) {
// 控制台输出点击的数据的信息
console.log('click',params);
// console.log('JSON.stringify(params)', JSON.stringify(params.data))
// console.log('window.parent.postMessage', window.parent)
window.parent.postMessage({cmd:'rn-chart-click', clickcmd: window.clickcmd, res: JSON.stringify(params.data) },'*')
});
}
}
window.clickcmd = '';
window.EchartCommonInit = EchartCommonInit
window.onload=function(){EchartCommonInit(option,wrapStyle)}
window.receiveMessage = (data) => {
console.log('html---receiveMessage:', data);
var _data = data
if (typeof data === 'string') {
try {
_data = JSON.parse(data)
} catch (err){
}
}
if (_data.cmd === 'rn-chart-update-options-render') {
if (typeof _data.res === 'string') {
try {
var res = JSON.parse(_data.res)
console.log('res', res)
res && myChart.setOption(res);
myChart.off('click')
// 为饼图添加点击事件监听
myChart.on('click', function (params) {
// 控制台输出点击的数据的信息
console.log('click',params);
// 你可以在这里编写你的逻辑代码
// 例如:弹出一个提示框显示点击的数据的名称
// alert('你点击了' + params.name);
// console.log('JSON.stringify(params)', JSON.stringify(params.data))
// console.log('window.parent.postMessage', window.parent)
window.parent.postMessage({cmd:'rn-chart-click', clickcmd: window.clickcmd, res: JSON.stringify(params.data) },'*')
});
}catch {}
} else if (typeof _data.res === 'object') {
try {
var res = _data.res
console.log('res', res)
res && myChart.setOption(res);
myChart.off('click')
// 为饼图添加点击事件监听
myChart.on('click', function (params) {
// 控制台输出点击的数据的信息
console.log('click',params);
// 你可以在这里编写你的逻辑代码
// 例如:弹出一个提示框显示点击的数据的名称
// alert('你点击了' + params.name);
// console.log('JSON.stringify(params)', JSON.stringify(params.data))
// console.log('window.parent.postMessage', window.parent)
window.parent.postMessage({cmd:'rn-chart-click', clickcmd: window.clickcmd, res: JSON.stringify(params.data) },'*')
});
}catch {}
}
}
}
window && window.addEventListener(
'message',
(event) => {
console.log('html---Message received from Web Worker:', event);
var data = event.data
receiveMessage(data)
},
);
</script>
</html>
`;
interface SelfEChartPropsType {
options: objPropsType;
echartStyle?: ViewStyle;
echartClick?: () => {};
clickcmd?: string;
}
const replaceNoNumber = (str: string | number) => {
if (typeof str === 'string') {
return str.replace(/[^0-9.]+/g, '');
} else if (typeof str === 'number') {
return str;
}
return str;
};
export default class SelfEChart extends React.PureComponent<
SelfEChartPropsType,
any
> {
static defaultProps = {
echartStyle: {},
};
// 由于react-native-webView 的onMessage不识别window.postMessage, 需要改成window.ReactNativeWebView.postMessage, 同时injectedJavascript只支持一行压缩代码,不支持多行,请勿换行和格式化;
injectedJavascript = `if(window.ReactNativeWebView&&window.ReactNativeWebView.postMessage){;window.postMessage=function(data){var str=typeof data==='object'?JSON.stringify(data):data;window.ReactNativeWebView.postMessage(str)};window.parent.postMessage=window.postMessage};window.parent.postMessage({cmd:'rn-init',res:{'label':'这个只是测试rn能不能走通'}},'*');`;
isLoadEnd = 'hidden';
IframeRef: any = React.createRef();
webviewRef: any = React.createRef();
constructor(props: SelfEChartPropsType) {
super(props);
this.state = {
h5HTML: '',
height: 300,
};
this.onMessage = debounce(this.onMessage, 300, {
leading: true,
trailing: false,
});
}
componentDidMount(): void {
this.updateOptions();
}
componentDidUpdate(
prevProps: Readonly<SelfEChartPropsType>,
prevState: Readonly<any>,
snapshot?: any,
): void {
if (
JSON.stringify(prevProps.options) !== JSON.stringify(this.props.options)
) {
this.updateOptions();
}
}
updareRenderOptions = (options: objPropsType) => {
console.log(`options`, options);
Platform.select({
web: () => {
this.IframeRef?.current?.contentWindow?.postMessage(
{cmd: 'rn-chart-update-options-render', res: JSON.stringify(options)},
'*',
);
},
default: () => {
const initParams = JSON.stringify({
cmd: 'rn-chart-update-options-render',
res: options,
});
const initParamsJs = `window.receiveMessage(${initParams});true;`;
this.webviewRef?.current?.injectJavaScript(initParamsJs);
},
})();
};
updateOptions = () => {
const {options, echartStyle, clickcmd} = this.props;
const {height} = this.state;
const style = JSON.parse(JSON.stringify(echartStyle || {}));
let _height = height;
if (Boolean(style.height)) {
_height = +replaceNoNumber(style.height);
}
if (!style.height) {
style.height = height + 'px';
}
const _h5HTML = defaultH5HTML.replace(
'window.onload=function(){EchartCommonInit(option,wrapStyle)}',
`window.clickcmd='${clickcmd}';window.onload=function(){EchartCommonInit(${JSON.stringify(
options,
)},${JSON.stringify(
style,
)})};console.log("window", window);window.EchartCommonInit(${JSON.stringify(
options,
)},${JSON.stringify(style)})`,
);
this.setState({
h5HTML: _h5HTML,
height: _height,
});
};
onMessage = (e: objPropsType | string) => {
console.log('Message received from Web Worker:', e);
this.props.echartClick && this.props.echartClick(e.nativeEvent || {});
};
componentWillUnmount(): void {
this.setState({
h5HTML: '',
});
}
render() {
const {h5HTML, height} = this.state;
const {isLoadEnd, onMessage} = this;
const _height = height + 20;
return Boolean(h5HTML) ? (
Platform.select({
web: () => {
window &&
window.addEventListener(
'message',
(event: objPropsType | string) => {
onMessage({
nativeEvent: event,
});
},
);
return (
<iframe
ref={this.IframeRef}
srcDoc={h5HTML}
frameBorder={0}
style={{width: '100%', height: _height}}></iframe>
);
},
default: () => {
return (
<WebView
ref={this.webviewRef}
originWhitelist={['*']}
source={{html: h5HTML}} //加载的html资源
style={{width: '100%', height: _height, visibility: isLoadEnd}}
mixedContentMode={'compatibility'}
scalesPageToFit={true}
javaScriptEnabled={true}
startInLoadingState={true}
injectedJavaScript={this.injectedJavascript}
androidLayerType={'software'}
renderLoading={() => {
return isLoadEnd === 'hidden' ? (
<View style={{height: _height, backgroundColor: '#fff'}}>
<ActivityIndicator size="large" />
</View>
) : (
<></>
);
}}
onLoadEnd={() => {
// console.log('loadend');
this.isLoadEnd = 'visible';
}}
onMessage={onMessage}
/>
// <></>
);
},
})()
) : (
<></>
);
}
}
- 组件的使用
饼图的实现并使用在组件中的场景
import React from 'react';
import CommonChart from '../common/chart';
import moment from 'moment';
import {getMoneySymbolAdd, isJSON} from '@/libs/utils';
interface SelfEChartPropsType {
annularStatistic?: objPropsType;
echartClick?: Function;
clickcmd?: string;
}
export default class IssueChart extends React.PureComponent<
SelfEChartPropsType,
any
> {
static defaultProps = {
annularStatistic: {},
};
CommonChartRef = React.createRef();
constructor(props: SelfEChartPropsType) {
super(props);
this.state = {
options: {
grid: {
top: 10,
bottom: 10,
},
legend: {
show: true,
right: 10,
top: 'center',
align: 'left',
orient: 'vertical',
itemWidth: 8,
itemHeight: 8,
itemGap: 16,
},
color: ['#5B8FF9', '#F6BD16', '#65789B'],
graphic: [
{
type: 'text',
left: '20%',
top: '34%',
style: {
// text: '{a|2233}\n{b|¥3,234.00}\n{c|审批中}',
text: '',
width: 100,
height: 100,
textAlign: 'center',
rich: {
a: {
fill: '#B4B8BE',
fontSize: 12,
lineHeight: 16,
},
b: {
fill: '#353739',
fontSize: 18,
fontWeight: 'bold',
lineHeight: 24,
},
c: {
fill: '#3D3D3D',
fontSize: 14,
lineHeight: 28,
},
},
fill: '#333',
fontSize: 20,
},
},
],
series: [
{
name: 'Area Mode',
type: 'pie',
radius: [50, 84],
center: ['35%', '50%'],
itemStyle: {},
label: {
show: true,
normal: {
position: 'inside',
formatter: '{b}: {d}%',
fontSize: 10,
},
},
emphasis: {
label: {
show: false,
},
},
data: [
{value: 0, name: '审批中'},
{value: 0, name: '待发放'},
{value: 0, name: '已发放'},
],
},
],
},
};
}
componentDidMount(): void {
this.updateOptionsData(this.props.annularStatistic);
}
componentDidUpdate(
prevProps: Readonly<SelfEChartPropsType>,
prevState: Readonly<any>,
snapshot?: any,
): void {
if (
JSON.stringify(this.props.annularStatistic) !==
JSON.stringify(prevProps.annularStatistic)
) {
this.updateOptionsData(this.props.annularStatistic);
}
}
updateOptionsData = (annularStatistic: objPropsType = {}) => {
const options = this.state.options;
options.series[0].data[0].value = annularStatistic.ingApprovalAmount;
options.series[0].data[1].value = annularStatistic.noGrantAmount;
options.series[0].data[2].value = annularStatistic.successGrantAmount;
options.graphic[0].style.text = `{a|${moment().format(
'YYYY-MM',
)}}\n{b|¥${getMoneySymbolAdd(annularStatistic.ingApprovalAmount)}}\n{c|${
options.series[0].data[0].name
}}`;
if (annularStatistic.ingApprovalAmount > 1000) {
options.graphic[0].left = '20%'
} else {
options.graphic[0].left = '24%'
}
// options.series[1].data[0].value = annularStatistic.ingApprovalAmount;
// options.series[1].data[1].value = annularStatistic.noGrantAmount;
// options.series[1].data[2].value = annularStatistic.successGrantAmount;
// options.series[0].label.normal.formatter = [
// `{a|${moment().format('YYYY-MM')}}\n{b|¥${getMoneySymbolAdd(
// annularStatistic.ingApprovalAmount,
// )}}\n{c|${options.series[0].data[0].name}}`,
// ].join('');
this.setState(
{
options,
},
() => {
this.CommonChartRef.current?.updateOptions();
},
);
};
echartClick = (e: objPropsType) => {
let data = e.data;
if (typeof data === 'string' && isJSON(data)) {
data = JSON.parse(data);
}
if (
data.cmd === 'rn-chart-click' &&
data.clickcmd === this.props.clickcmd
) {
let res = data.res;
if (typeof res === 'string' && isJSON(res)) {
res = JSON.parse(res);
}
const options = this.state.options;
console.log('res', data, res);
if (res) {
options.graphic[0].style.text = `\{a|${moment().format(
'YYYY-MM',
)}\}\n\{b|¥${getMoneySymbolAdd(res.value)}\}\n\{c|${res.name}\}`
if (res.value > 1000) {
options.graphic[0].left = '20%'
} else {
options.graphic[0].left = '24%'
}
this.setState(
{
options,
},
() => {
this.CommonChartRef.current?.updareRenderOptions({
graphic: options.graphic,
});
},
);
}
this.props.echartClick && this.props.echartClick(res);
}
};
render() {
const {options} = this.state;
return (
<CommonChart
ref={this.CommonChartRef}
options={options}
echartStyle={{height: '194px'}}
clickcmd={this.props.clickcmd}
echartClick={this.echartClick}></CommonChart>
);
}
}