把highcharts封装成vue组件,任何项目用起来都很方便
<template>
<div class="highcharts-container" />
</template>
<script>
import * as _ from 'lodash';
import HighCharts from 'highcharts/highstock';
import HighchartsMore from 'highcharts/highcharts-more';
import HighchartsDrilldown from 'highcharts/modules/drilldown';
import Highcharts3D from 'highcharts/highcharts-3d';
import Exporting from 'highcharts/modules/exporting.js';
import HighchartsNoData from 'highcharts-no-data-to-display';
import defaultOptions from '_c/chart/default-options';
import Annotations from 'highcharts/modules/annotations.js';
import Oldie from 'highcharts/modules/oldie.js';
import { addResizeListener, removeResizeListener } from '@/utils/resize-event.js';
import debounce from '@/utils/debounce.js';
HighchartsMore(HighCharts);
HighchartsDrilldown(HighCharts);
Highcharts3D(HighCharts);
Exporting(HighCharts);
Annotations(HighCharts);
Oldie(HighCharts);
HighchartsNoData(HighCharts);
HighCharts.setOptions({
global: {
timezoneOffset: -8 * 60,
},
lang: {
noData: '数据未上报',
viewFullscreen: '全屏查看',
printChart: '打印图表',
downloadJPEG: '下载JPEG 图片',
downloadPDF: '下载PDF文档',
downloadPNG: '下载PNG 图片',
downloadSVG: '下载SVG 矢量图',
exportButtonTitle: '导出图片',
loading: '加载中...',
resetZoom: '重置视图',
},
});
export default {
name: 'high-chart',
props: {
options: {
type: Object,
default: () => {},
},
series: {
type: Array,
default: () => [],
},
loading: {
type: Boolean,
default: () => false,
},
},
data() {
return {
chart: null,
};
},
computed: {
finalOptions() {
return _.merge(_.cloneDeep(defaultOptions), this.options);
},
},
watch: {
options: {
handler() {
this.chart && this.chart.update(this.finalOptions);
},
deep: true,
},
loading(v) {
if (v && this.chart) {
this.chart.showLoading();
} else if (!v && this.chart) {
this.chart.hideLoading();
}
},
series: {
handler(newVal) {
if (newVal?.length) {
this.updateSeries();
} else {
this.initChart();
}
},
},
},
mounted() {
this.initChart();
addResizeListener(this.$el, this.onResize());
},
beforeDestroy() {
this.chart && this.chart.destroy();
this.chart = null;
},
destroyed() {
removeResizeListener(this.$el, this.onResize());
},
methods: {
onResize() {
return debounce(() => {
this.chart && this.chart.reflow();
}, 200);
},
initChart() {
this.chart = new HighCharts.chart(this.$el, this.finalOptions);
window.chart = this.chart;
if (this.loading) {
this.chart.showLoading();
}
this.updateSeries();
},
updateSeries() {
if (!this.chart) {
return;
}
this.series.forEach((series) => {
const exist = this.chart.get(series.id);
if (exist) {
exist.setData(series.data, false);
exist.setVisible(true, false);
exist.update(
{
diffDay: series.diffDay,
visible: series.visible,
showInLegend: series.showInLegend,
type: series.type,
},
false,
);
} else {
this.chart.addSeries(series, false);
}
});
this.chart.series.forEach((series) => {
if (!this.series.find((s) => s.name === series.name)) {
series.setVisible(false, false);
series.update({ showInLegend: false, type: series.type }, false);
}
});
this.chart.redraw();
},
},
};
</script>
<style lang="less" scoped>
@import '@/style/index';
.highcharts-container {
display: block;
& /deep/ .highcharts-container {
margin: 0 auto;
}
}
</style>
export default {
chart: {
spacingBottom: 5,
spacingLeft: 0,
height: 300,
},
colors: [
'#0000ff',
'#ff9a38',
'#3ecc36',
'#ff5620',
'#7953ff',
'#1ee6e6',
'#1be591',
'#db30ff',
'#ff3383',
'#33c4ff',
'#ffc500',
],
title: {
style: {
fontSize: '14px',
textOverflow: 'ellipsis',
wordBreak: 'break-all',
wordWrap: 'break-word',
// visibility: 'hidden',
},
text: '',
},
xAxis: {
gridLineWidth: 1,
tickPixelInterval: 40, // 刻度间距
type: 'datetime',
dateTimeLabelFormats: {
millisecond: '%H:%M:%S.%L',
second: '%H:%M:%S',
minute: '%H:%M',
hour: '%H',
day: '%m-%d',
week: '%m-%d',
month: '%Y-%m',
year: '%Y',
},
labels: {
rotation: -45,
},
plotBands: [
// {
// color: '#FFD4C5',
// from: 1635726000000,
// to: 1635736800000,
// label: {
// text: '异常',
// },
// },
],
},
yAxis: [
{
gridLineWidth: 1,
tickAmount: 7, // 规定坐标轴上的刻度总数
max: null,
endOnTick: true, // 是否强制将坐标轴结束于刻度线
ceiling: null, // 自动计算坐标轴极值的上限
// softMax: 1, // 坐标轴的柔和最大值
title: {
text: '',
style: {
fontWeight: 'bold',
fontSize: '12px',
},
align: 'high',
rotation: 0,
offset: 0,
y: -20,
x: 6,
},
labels: {
align: 'right',
padding: 0,
},
height: '90%',
},
{
gridLineWidth: 0,
// tickAmount: 7, // 规定坐标轴上的刻度总数
max: 100,
min: -100,
endOnTick: true, // 是否强制将坐标轴结束于刻度线
ceiling: null, // 自动计算坐标轴极值的上限
// softMax: 1, // 坐标轴的柔和最大值
title: {
text: '',
style: {
fontWeight: 'bold',
fontSize: '12px',
},
align: 'high',
rotation: 0,
offset: 0,
y: -20,
x: 6,
},
labels: {
align: 'left',
padding: 0,
},
opposite: true,
top: '90%',
height: '10%',
},
],
tooltip: {
enabled: true,
shared: true,
crosshairs: true,
dateTimeLabelFormats: {
millisecond: '%H:%M',
second: '%H:%M',
minute: '%H:%M',
hour: '%H:%M',
day: '%m-%d',
week: '%m-%d',
month: '%Y-%m',
},
useHTML: true,
},
credits: {
enabled: false,
},
legend: {
layout: 'horizontal',
align: 'center',
verticalAlign: 'bottom',
enabled: true,
margin: 0,
padding: 0,
itemWidth: 120,
symbolWidth: 10,
itemStyle: {
fontSize: '11px',
},
},
exporting: {
enabled: false,
buttons: {
contextButton: {
menuItems: [],
},
},
},
plotOptions: {
line: {
lineWidth: 1,
states: {
hover: {
lineWidth: 2,
},
},
marker: {
enabled: false,
symbol: 'diamond',
},
},
area: {
marker: {
enabled: false,
},
},
column: {
minPointLength: 10,
borderWidth: 0,
pointWidth: 10,
// pointPadding: 50,
// groupPadding: 50,
dataLabels: {
enabled: true,
},
},
series: {
states: {
inactive: {
enabled: false,
},
},
},
},
annotations: [],
series: [],
responsive: {
// 通过设定不同的响应规则来实现对图表在不同尺寸下的响应
rules: [
{
condition: {
maxWidth: 500,
},
chartOptions: {
legend: {
layout: 'horizontal',
align: 'center',
verticalAlign: 'bottom',
},
},
},
],
},
};
import ResizeObserver from 'resize-observer-polyfill';
/* istanbul ignore next */
// eslint-disable-next-line func-names
const resizeHandler = function (entries) {
// eslint-disable-next-line no-restricted-syntax
for (const entry of entries) {
const listeners = entry.target.__resizeListeners__ || [];
if (listeners.length) {
listeners.forEach((fn) => {
fn();
});
}
}
};
/* istanbul ignore next */
// eslint-disable-next-line func-names
export const addResizeListener = function (element, fn) {
if (!element.__resizeListeners__) {
// eslint-disable-next-line no-param-reassign
element.__resizeListeners__ = [];
// eslint-disable-next-line no-param-reassign
element.__ro__ = new ResizeObserver(resizeHandler);
element.__ro__.observe(element);
}
element.__resizeListeners__.push(fn);
};
/* istanbul ignore next */
// eslint-disable-next-line func-names
export const removeResizeListener = function (element, fn) {
if (!element || !element.__resizeListeners__) return;
element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
if (!element.__resizeListeners__.length) {
element.__ro__.disconnect();
}
};
// eslint-disable-next-line func-names
export const clearResizeListener = function (element) {
if (!element || !element.__resizeListeners__) return;
// eslint-disable-next-line no-param-reassign
element.__resizeListeners__ = [];
element.__ro__.disconnect();
};
// Some functions take a variable number of arguments, or a few expected
// arguments at the beginning and then a variable number of values to operate
// on. This helper accumulates all remaining arguments past the function’s
// argument length (or an explicit `startIndex`), into an array that becomes
// the last argument. Similar to ES6’s "rest parameter".
// eslint-disable-next-line func-names
const restArguments = function (func, startIndex) {
// eslint-disable-next-line no-param-reassign
startIndex = startIndex == null ? func.length - 1 : +startIndex;
// eslint-disable-next-line func-names
return function () {
const length = Math.max(arguments.length - startIndex, 0);
const rest = Array(length);
let index = 0;
for (; index < length; index++) {
// eslint-disable-next-line prefer-rest-params
rest[index] = arguments[index + startIndex];
}
// eslint-disable-next-line default-case
switch (startIndex) {
case 0:
return func.call(this, rest);
// eslint-disable-next-line prefer-rest-params
case 1:
return func.call(this, arguments[0], rest);
// eslint-disable-next-line prefer-rest-params
case 2:
return func.call(this, arguments[0], arguments[1], rest);
}
const args = Array(startIndex + 1);
for (index = 0; index < startIndex; index++) {
// eslint-disable-next-line prefer-rest-params
args[index] = arguments[index];
}
args[startIndex] = rest;
return func.apply(this, args);
};
};
// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
const delay = restArguments((func, wait, args) => {
return setTimeout(() => {
// eslint-disable-next-line prefer-spread
return func.apply(null, args);
}, wait);
});
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
// eslint-disable-next-line func-names
const debounce = function (func, wait, immediate) {
let timeout;
let result;
// eslint-disable-next-line func-names
const later = function (context, args) {
timeout = null;
if (args) result = func.apply(context, args);
};
// eslint-disable-next-line func-names
const debounced = restArguments(function (args) {
if (timeout) clearTimeout(timeout);
if (immediate) {
const callNow = !timeout;
timeout = setTimeout(later, wait);
if (callNow) result = func.apply(this, args);
} else {
timeout = delay(later, wait, this, args);
}
return result;
});
// eslint-disable-next-line func-names
debounced.cancel = function () {
clearTimeout(timeout);
timeout = null;
};
return debounced;
};
export default debounce;
{
"name": "tdesign-web-vue2-template",
"version": "0.0.1",
"scripts": {
"dev:mock": "vite --open --mode mock",
"dev": "vite --open --mode development",
"dev:linux": "vite --mode developmenet",
"build:test": "vite build --mode test",
"build": "vite build --mode release",
"serve": "vite preview",
"lint": "eslint --ext .vue,.js,.jsx,.ts,.tsx ./ --max-warnings 0",
"lint:fix": "eslint --ext .vue,.js,jsx,.ts,.tsx ./ --max-warnings 0 --fix",
"stylelint": "stylelint src/**/*.{html,vue,sass,less}",
"stylelint:fix": "stylelint --cache --fix src/**/*.{html,vue,vss,sass,less}",
"test": "jest --coverage"
},
"dependencies": {
"@types/lodash": "^4.14.176",
"@vitejs/plugin-legacy": "^1.5.3",
"dayjs": "^1.10.6",
"highcharts": "^9.3.3",
"highcharts-no-data-to-display": "^0.1.7",
"insert-css": "^2.0.0",
"lint-staged": "^10.5.4",
"lodash": "^4.17.21",
"nprogress": "^0.2.0",
"qrcode.vue": "^1.7.0",
"resize-observer-polyfill": "^1.5.1",
"tdesign-icons-vue": "^0.0.8",
"tdesign-vue": "^0.41.3",
"typescript": "^4.2.4",
"vite-plugin-vue2-svg": "^0.1.8",
"vue": "^2.6.11",
"vuedraggable": "^2.24.3",
"vuex": "^3.6.2"
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@babel/preset-env": "^7.16.4",
"@babel/preset-typescript": "^7.16.0",
"@commitlint/cli": "^12.0.1",
"@commitlint/config-conventional": "^12.0.1",
"@rollup/plugin-dynamic-import-vars": "^1.1.1",
"@types/jest": "^27.0.3",
"@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0",
"@vue/test-utils": "^1.3.0",
"axios": "^0.21.1",
"babel-jest": "^27.4.2",
"commitizen": "^4.2.3",
"eslint": "^7.22.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-vue": "^7.8.0",
"http-proxy-agent": "^4.0.1",
"husky": "^4.2.5",
"jest": "^27.4.3",
"less": "^4.1.0",
"less-loader": "^7.2.1",
"less-vars-to-js": "^1.3.0",
"mockjs": "^1.1.0",
"prettier": "^2.3.2",
"rollup-plugin-visualizer": "^5.5.4",
"stylelint": "^13.13.1",
"stylelint-config-airbnb": "0.0.0",
"stylelint-order": "^4.1.0",
"stylelint-scss": "^3.20.1",
"ts-jest": "^27.1.0",
"vite": "2.5.10",
"vite-plugin-environment": "^1.1.0",
"vite-plugin-mock": "^2.3.0",
"vite-plugin-theme": "^0.8.1",
"vite-plugin-vue2": "^1.2.2",
"vue-clipboard2": "^0.3.1",
"vue-jest": "^3.0.7",
"vue-router": "^3.5.1",
"vuex-router-sync": "^5.0.0"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"bit": {
"env": {},
"componentsDefaultDirectory": "components/{name}",
"packageManager": "npm"
}
}