什么是Echarts
是一个使用js实现的开源可视库,提供了多种图表,但是当我们在项目中进行使用的时候可能就是需要进行一系列的相关配置如: 标题,类型,x轴,y轴等,当我们使用较为频繁的时候就容易导致代码的冗余,并且整体将echarts进行安装引入也是比较大的,我们可以按照自己的需要进行对应组件的引入
Echarts的使用
安装Echarts
Echarts官网-安装echarst并实现按需引入Echarts图表和组件
基于Echarts相关配置进行组件的封装
// components/BarChart.vue
/* @/components/BarChart.vue */
<template>
<div ref="chartDom" :style="{ height: getHeight }"></div>
</template>
<script setup lang="ts">
import { echarts, type ECOption } from '@/utils/echarts'
import { ref, shallowRef, watch, computed, onMounted, onBeforeUnmount, type ShallowRef, type Ref } from 'vue'
import type { EChartsType } from 'echarts/types/dist/core'
import type {
XAXisOption, YAXisOption, LegendComponentOption, BarSeriesOption, DataZoomComponentOption
} from 'echarts/types/dist/shared'
import resize from '@/utils/resize'
import type { ChartSetting } from '@/types/ChartData'
//定义组件属性
const props = withDefaults(
defineProps<{
//数据
data?: Array<string | number>
//x轴数据
xAxisData?: Array<string>
//图表标题
title?: string
//系列配置
series?: Array<BarSeriesOption>
//x轴配置
xAxis?: Array<XAXisOption>
//y轴配置
yAxis?: Array<YAXisOption>
//图例配置
legend?: LegendComponentOption
//区域缩放配置
dataZoom?: Array<DataZoomComponentOption>
//图形高度
height?: number | string
//数据集
datasetSource?: Array<any>
//综合配置
options: ChartSetting
}>(),
{
data: () => [],
xAxisData: () => [],
title: 'ECharts柱状图',
}
)
//要渲染的Dom元素
const chartDom: Ref<HTMLDivElement | null> = ref(null)
//渲染的chart对象要用shallowRef
const chart: ShallowRef<EChartsType | null | undefined> = shallowRef(null)
//高度同时支持string和number
const getHeight = computed(() => {
return typeof props.height === 'number' ? props.height + 'px' : props.height
})
//监听数据变化,重新绘制
watch(
() => props,
() => {
drawChart()
},
{ deep: true }
)
//绘制
async function drawChart() {
let datasetSource: Array<any> | undefined = props.datasetSource,
series: Array<BarSeriesOption> = [],
xAxisData: Array<string> = props.xAxisData
let chartType = props.options.chartType || 'bar'; // 默认为柱状图,如果需要折线图则设置为 'line'
if (props.options) {
if (props.options.apiMethod) {
//获取接口数据作为数据集
let allx = await props.options.apiMethod()
datasetSource = allx.data
if (props.options.xProp) {
//根据配置的x轴属性名生成x轴数据
xAxisData = []
datasetSource?.forEach(data => {
xAxisData.push(data[props.options.xProp])
})
}
}
if (props.options.seriesOption) {
props.options.seriesOption.forEach((opt: any) => {
series.push({
name: '车牌',
barMaxWidth: 30,
emphasis: { focus: 'series' },
label: { show: true, position: 'top', color: 'inherit' },
...opt,
type: chartType, // 设置图表类型
})
})
}
}
// else {
// series = props.series ? props.series : [{
// name: '车牌',
// type: 'bar',
// barMaxWidth: 30,
// emphasis: { focus: 'self' },
// label: { show: true, position: 'inside', color: '#fff' },
// data: props.data
// }]
// }
let xAxis: Array<XAXisOption> = props.xAxis ? props.xAxis : [{
type: 'category',
axisTick: { show: false },
data: xAxisData
}]
let yAxis: Array<YAXisOption> = props.yAxis ? props.yAxis : [{ type: 'value', minInterval: 1 }]
let legend: LegendComponentOption = props.legend ? props.legend : {
show: true,
type: 'scroll',
orient: 'horizontal',
top: 25,
left: 'center'
}
let dataZoom: Array<DataZoomComponentOption> = props.dataZoom ? props.dataZoom : []
const options: ECOption = {
backgroundColor: '',
title: {
text: props.title
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
// appendToBody:true
},
legend: legend,
grid: {
left: 10,
right: 10,
bottom: props.dataZoom ? 40 : 10,
containLabel: true
},
toolbox: {
show: true,
feature: {
magicType: { type: ['line', 'bar'] },
dataView: { readOnly: false },
saveAsImage: {}
}
},
xAxis: xAxis,
yAxis: yAxis,
dataZoom: dataZoom,
dataset: {
source: datasetSource
},
series: series
}
//开启notMerge保证配置数据不会叠加
chart.value?.setOption(options, { notMerge: true });
}
const { chartObject, addResize, removeResize } = resize()
onMounted(() => {
chart.value = echarts.init(chartDom.value);
drawChart()
//添加窗口自适应
chartObject.value = chart.value
addResize()
})
onBeforeUnmount(() => {
removeResize()
chart.value?.dispose()
})
</script>
在父组件中使用封装好的组件
<template>
<el-row :gutter="16">
<el-col v-for="(item, index) in chartOptionList" :key="index" :lg="12" style="margin-bottom: 10px;">
<el-card>
<BarChart :title="item.title" :height="item.height || 300" :options="item.chartOption" />
</el-card>
</el-col>
</el-row>
</template>
<script setup lang="ts">
import BarChart from '@/components/BarChart.vue'
import type { ChartSetting } from '@/types/ChartData'
import { ref } from 'vue'
//axios api请求方法
import { getPayRecord, delicacies } from '@/services/http'
interface ChartCard {
//标题
title?: string,
//高度
height?: number,
//图表y轴配置
yAxis?: Array<any>
chartOption: ChartSetting
}
const chartOptionList = ref<ChartCard[]>([
{
title: '图1',
chartOption: {
apiMethod: () => getPayRecord(),
xProp: 'name',
chartType: 'bar', // 设置为 'line' 以生成折线图
seriesOption: [
{ name: '数量', encode: { x: 'name', y: 'count' } }
]
}
},
{
title: '图2',
chartOption: {
apiMethod: () => delicacies(),
xProp: 'name',
chartType: 'line', // 设置为 'line' 以生成折线图
seriesOption: [
{ name: '销量', encode: { x: 'name', y: 'saleNum' } },
{ name: '好评', encode: { x: 'name', y: 'positiveReviews' } },
]
}
},
])
</script>
效果展示
其他:
// types.ChartData.ts
export interface SeriesData {
name?: string
data?: number[]
color?: string
yAxisIndex?: number
radius?: string | string[]
itemStyle?: any
encode?: {
x?: string
y?: string
itemName?: string
value?: string
}
}
export interface ChartSetting {
//api接口方法
apiMethod: Function
// x轴属性名
xProp: string
chartType: string|undefined // 设置为 'line' 以生成折线图
//图例配置
seriesOption: SeriesData[]
}
// reqTypes.ts
import axiosInstance from '../utils/request'
export interface ApiResult<T> {
code: number
message: string
data: T
}
export async function get<T>(url: string, params?: any): Promise<ApiResult<T>> {
const response = await axiosInstance.get<ApiResult<T>>(url, { params })
return response.data
}
export async function post<T>(url: string, data?: any): Promise<ApiResult<T>> {
const response = await axiosInstance.post<ApiResult<T>>(url, data)
return response.data
}
export async function put<T>(url: string, data?: any): Promise<ApiResult<T>> {
const response = await axiosInstance.put<ApiResult<T>>(url, data)
return response.data
}
export async function del<T>(url: string, params?: any): Promise<ApiResult<T>> {
const response = await axiosInstance.delete<ApiResult<T>>(url, { params })
return response.data
}
// utils.echarts.ts
/* @/utils/echarts.ts */
/**在ts中实现按需引入echarts 图表和组件*/
import * as Echarts from 'echarts/core'
import { BarChart, PieChart, LineChart } from 'echarts/charts'
import {
// 标题组件
TitleComponent,
// 图例组件
LegendComponent,
// 提示框组件
TooltipComponent,
// 坐标系网格组件
GridComponent,
// 数据集组件
DatasetComponent,
// 内置数据转换器组件 (filter, sort)
TransformComponent,
// 工具栏组件
ToolboxComponent,
// 区域缩放组件
DataZoomComponent,
// 原生图形元素组件
} from 'echarts/components'
// 标签自动布局、全局过渡动画等特性
import { LabelLayout, UniversalTransition } from 'echarts/features'
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
// 使用canvas进行渲染 也可以使用 SVGRenderer 进行渲染
import { CanvasRenderer } from 'echarts/renderers'
import type {
// 系列类型的定义后缀都为 SeriesOption
BarSeriesOption, // 柱状图
PieSeriesOption, //饼图
LineSeriesOption, // 折线/面积图
} from 'echarts/charts'
import type {
// 组件类型的定义后缀都为 ComponentOption
TitleComponentOption,
TooltipComponentOption,
GridComponentOption,
DatasetComponentOption,
ToolboxComponentOption,
DataZoomComponentOption,
GraphicComponentOption,
} from 'echarts/components'
import type { ComposeOption } from 'echarts/core'
// 注册必须的组件
Echarts.use([
TitleComponent,
LegendComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
ToolboxComponent,
DataZoomComponent,
GridComponent,
LabelLayout,
UniversalTransition,
CanvasRenderer,
BarChart,
PieChart,
LineChart,
])
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
export type ECOption = ComposeOption<
| BarSeriesOption
| PieSeriesOption
| LineSeriesOption
| TitleComponentOption
| TooltipComponentOption
| GridComponentOption
| DatasetComponentOption
| ToolboxComponentOption
| DataZoomComponentOption
| GraphicComponentOption
>
export const echarts = Echarts
// request.ts 这里的baseUrl 是基于EasyMock进行模拟的数据
/**
* 使用 axios.create() 创建了一个 axios 实例,并设置了基本 URL 和请求超时时间。我们还添加了请求和响应拦截器
*
*/
import axios, {
AxiosInstance,
AxiosResponse,
InternalAxiosRequestConfig,
} from 'axios'
const axiosInstance: AxiosInstance = axios.create({
baseURL:
'https://mock.presstime.cn/mock/6686990ecb2f4f1158f2a7b8/screen-big-sys',
timeout: 5000,
})
// 添加请求拦截器
// 自定义请求头
axiosInstance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
// 在发送请求之前做些什么
const token = localStorage.getItem('ACCESS_TOKEN')
if (token) {
// 配置请求头
config.headers.Authorization = 'Bearer ' + token
}
return config
},
(error: any) => {
// 处理请求错误
return Promise.reject(error)
}
)
// 添加响应拦截器
axiosInstance.interceptors.response.use(
(response: AxiosResponse) => {
// 对响应数据做点什么
return response
},
(error: any) => {
// 处理响应错误
return Promise.reject(error)
}
)
export default axiosInstance
// resize.ts
/**
* 实现页面大小的自适应
* ECharts提供的API会发现,它提供了一个resize 方法 重新渲染图表结合window.addEventListener
* 功能:echarts图表自适应窗口变化封装方法
*/
//echarts图表自适应窗口变化封装方法
import { ref } from 'vue'
import { debounce } from 'lodash'
export default function () {
//echarts图的实例
const chartObject = ref()
//使用防抖debounce函数,减少resize的次数
const chartResizeHandler = debounce(() => {
if (chartObject.value) {
chartObject.value.resize()
}
}, 100)
const initResizeEvent = () => {
//添加窗口大小变化监听
window.addEventListener('resize', chartResizeHandler)
}
const destroyResizeEvent = () => {
//移除窗口大小变化监听
window.removeEventListener('resize', chartResizeHandler)
}
const addResize = () => {
initResizeEvent()
}
const removeResize = () => {
destroyResizeEvent()
}
return {
chartObject,
addResize,
removeResize,
}
}
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import { createPinia } from 'pinia'
import 'element-plus/dist/index.css'
// 实现持久化标记
import { createPersistedState } from 'pinia-plugin-persistedstate'
const app = createApp(App)
const pinia = createPinia()
// 使用pinia-plugin-persistedstate插件
pinia.use(createPersistedState())
app.use(ElementPlus)
app.mount('#app')