我在项目中需要用到很多的图标,比如折线、饼图、柱状、关系等各种图标,我又比较懒,所以就封了一个基本的组件库,只需要传递options和canvasId,基本就可以了,代码如下:
javascript"><template>
<div class="wrapper-charts">
<div class="text-title" v-if="isShowTitle">
<div class="title-h3">{{ title || '' }}</div>
<div v-if="isShowSelect">
<!-- <template #select> xxx </template> -->
<span class="title-tip" v-if="showInstitutionName">{{ institutionName || '' }}</span>
<slot name="select">
<a-select size="small" style="width: 113px" :value="modelValue" class="selectType" @select="onChange"
:fieldNames="labelProps">
<a-select-option value="全部">全部</a-select-option>
<a-select-option :value="item" v-for="(item, index) in selectOptions" :key="index">
{{ item }}
</a-select-option>
</a-select>
</slot>
</div>
</div>
<div class="wrapper-charts-content" :style="bodyStyle">
<div class="charts-styles" :class="className" v-if="chartOptOneSort">
<div v-size-direct="resizeChart" :ref="chartId" :id="chartId" :style="styles"></div>
</div>
<div class="no-data-available-chart" v-else>
<svg-icon name="noDataAvailable" width="121" height="130"></svg-icon>
<div>暂无数据</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, watch, nextTick, defineSlots, markRaw } from 'vue'
import * as echarts from 'echarts'
// Props
const props = defineProps({
modelValue: {
type: [String, Number, null, Array],
default: '全部'
},
isShowTitle: {
type: Boolean,
default: false
},
isShowSelect: {
type: Boolean,
default: true
},
loading: {
type: Boolean,
default: false
},
className: {
type: String,
default: ""
},
selectOptions: {
type: Array,
default: []
},
labelProps: {
type: Object,
default: {
label: 'name',
value: 'value',
options: 'options'
}
},
showInstitutionName: {
type: Boolean,
default: true
},
institutionName: {
type: String,
default: ''
},
title: {
type: String,
default: '高原病就珍'
},
// id 需要不一样,不然会覆盖
chartId: {
type: String,
required: true,
default: 'chartContainer'
},
option: {
type: Object,
required: true // option 是必须的
},
bodyStyle: {
type: Object,
default: {
width: '100%',
height: '100%'
}
},
styles: {
type: Object,
default: {
width: '100%',
height: '100%'
}
},
clickChart: {
type: Function,
required: false
}
})
let chartInstance = ref(null)
const emit = defineEmits(['update:modelValue', 'change', 'clickChart', 'finished']) // 用来触发事件
const $solt = defineSlots()
const onChange = (value) => {
emit('update:modelValue', value)
emit('change', value)
}
const chartOptOneSort = ref(false)
// 初始化图表
const initChart = () => {
if (chartInstance.value) {
chartInstance.value?.dispose() // 销毁已有实例,避免重复渲染
}
const dom = document.getElementById(props.chartId)
chartInstance.value = markRaw(echarts.init(dom))
if (props.loading) {
chartInstance.value?.showLoading()
}
chartInstance.value?.off('click') // 移除旧的点击事件
chartInstance.value?.setOption(props.option)
// 监听图表渲染完成事件
chartInstance.value?.on('finished', () => {
chartInstance.value?.hideLoading()
emit('finished')
})
// 监听图表点击事件
chartInstance.value?.on('click', (params) => {
// 通过 emit 触发 'click' 事件,传递参数
emit('clickChart', params)
})
}
// 监听 option 的变化并更新图表
watch(
() => props.option,
(newOption) => {
chartOptOneSort.value = newOption?.series?.length > 0;
let isShowView = newOption?.series?.map(item => item.data.some(value => value > 0)) || [];
chartOptOneSort.value = isShowView.some(item => item);
if (chartOptOneSort.value) {
nextTick(() => {
if (chartInstance.value) {
// chartInstance.value.clear();
chartInstance.value?.dispose() // 销毁已有实例,避免重复渲染
}
initChart()
})
}
},
{ deep: true, immediate: true } // 深度监听
)
// 处理窗口大小变化时,重新调整图表尺寸
const resizeChart = (contentRect) => {
if (chartInstance.value) {
chartInstance.value?.resize()
}
}
// 在组件挂载时初始化图表,并添加 resize 事件监听
onMounted(() => {
// initChart();
// window.addEventListener('resize', handleResize)
})
// 在组件卸载时销毁图表,并移除 resize 事件监听
onBeforeUnmount(() => {
if (chartInstance.value) {
chartInstance.value?.dispose()
}
// window.removeEventListener('resize', handleResize)
})
defineExpose({
chartInstance,
resizeChart,
initChart,
onChange,
chartOptOneSort
})
</script>
<style lang="less" scoped>
.wrapper-charts {
width: 100%;
height: 100%;
}
/* 可以根据需要设置图表的样式 */
.text-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
.title-h3 {
font-size: 16px;
font-style: normal;
font-weight: 600;
color: #333;
}
.title-tip {
font-size: 14px;
font-weight: 400;
color: #5e6580;
margin-right: 10px;
}
}
.wrapper-charts-content {
width: 100%;
height: 100%;
.charts-styles {
width: 100%;
height: calc(100% - 35px);
}
}
.no-data-available-chart {
width: 100%;
height: 100%;
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
color: #5e6580;
font-family: 'PingFang SC';
font-size: 14px;
font-style: normal;
font-weight: 400;
}
</style>
在使用的页面中引入或者在全局配置都是可以的,我是才页面中引入的,
javascript">import StatisticsCharts from '@/components/ECharts/lineTrend.vue'
配置文件 config.ts
javascript">import { ref } from 'vue'
import * as echarts from 'echarts'
// charts-config
// 折线图柱状数据配置
// https://echarts.apache.org/zh/option.html#tooltip.confine
export const colors = ['#405BC8', '#00CFBE', '#A96BFF']
export function convertToPercentage(decimal: number) {
return (decimal * 100).toFixed(2) + ' %'
}
export const chartOptions = {
color: colors,
tooltip: {
trigger: 'axis',
triggerOn: 'mousemove',
confine: true,
height: 'auto',
backgroundColor: 'rgba(40, 49, 67, 0.85);',
borderColor: 'rgba(40, 49, 67, 0.85);',
enterable: true,
appendToBody: true,
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
// crossStyle: {
// color: '#fff'
// }
},
// 自定义提示框内容
formatter: function (data) {
let tooltipHtml = `
<div style="color:#ffffff;font-size: 14px;font-weight: 400;margin-bottom: 8px;max-height: 200px; overflow-y: auto;">
<div style="color:#ffffff;font-size: 14px;font-weight: 400;margin-bottom: 8px;">
${data[0].axisValue}
</br>
</div>
`
data.forEach((item) => {
tooltipHtml += `
<div style="font-size: 12px;padding: 4px 0;width: auto;max-height: 200px; overflow-y: auto;">
<div style="display:inline-block;margin-right:4px;width:10px;height:10px;background-color: ${item.color};"></div>
<span>${item.seriesName} :</span>
<span style="color:#ffffff">${item.seriesType == 'line' ? convertToPercentage(Number(item.data)) : item.data + '元'}</span>
</div>
`
})
tooltipHtml += '</div>'
return tooltipHtml
},
textStyle: {
fontSize: 14
}
},
toolbox: {
top: '2%',
right: '3%',
show: false,
feature: {
// dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true }
// saveAsImage: { show: true }
}
},
legend: {
width: '80%',
itemHeight: 10,
itemWidth: 10,
top: '2%',
left: '3%',
itemGap: 15,
type: 'scroll', // 数据过多时,分页显示
icon: 'rect', // 设置图例的形状
selected: [] //这里默认显示数组中前十个,如果不设置,则所有的数据都会显示在图表上
},
xAxis: [
{
type: 'category',
boundaryGap: true, //坐标轴两边留白
axisTick: {
show: false
},
data: [],
axisPointer: {
type: 'shadow'
},
axisLabel: {
show: true, //下方日期显示与否
interval: 0, // 设置数据间隔
rotate: 28, // 标题倾斜
margin: 5, //刻度标签与轴线之间的距离
textStyle: {
fontSize: 12, //横轴字体大小
color: '#8F94A7' //颜色
}
}
}
],
yAxis: [
{
name: '',
// interval: 5,
position: 'left',
alignTicks: true,
type: 'value',
splitLine: {
show: false
},
axisLine: {
show: false,
// min: 0,
// max: 10,
lineStyle: { color: '#8F94A7' }
},
axisLabel: {
show: true,
fontSize: 14,
color: '#8F94A7',
formatter: function (value: string) {
return `${Number(value)} 元`
}
}
},
{
// name: '温度',
// interval: 5,
type: 'value',
position: 'right',
alignTicks: true,
axisLine: {
show: false
// lineStyle: {
// color: colors[2]
// }
},
axisLabel: {
show: true,
fontSize: 14,
formatter: function (value: string) {
return `${(Number(value) * 100).toFixed(0)} %`
},
color: '#8F94A7'
}
}
],
// X轴可滚动
dataZoom: [
{
type: 'slider', // 设置为滑动条型式
show: true, // 显示dataZoom组件
bottom: 0,
height: 10,
showDetail: false,
startValue: 0, //滚动条的起始位置
endValue: 9 //滚动条的截止位置(按比例分割你的柱状图x轴长度)
// xAxisIndex: [0] // 表示控制第一个x轴
// start: 0, // 默认显示的起始位置为0
// end: 20, // 默认显示的结束位置为100
// handleSize: 8, // 滑动条的手柄大小
// handleStyle: {
// color: '#DCE2E8' // 滑动条的手柄颜色
// },
// filterMode: 'filter' // 设置为filter模式,即数据超过范围时会被过滤掉
},
{
type: 'inside', //设置鼠标滚轮缩放
show: true,
xAxisIndex: [0],
startValue: 0,
endValue: 9
}
],
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
series: [
{
name: '药品费用总额',
type: 'bar',
barWidth: '20',
tooltip: {
valueFormatter: function (value: string) {
return `${Number(value)} 元`
}
},
itemStyle: {
normal: {
barBorderRadius: [2, 2, 0, 0]
}
},
data: []
},
{
name: '医疗总费用',
type: 'bar',
barWidth: '20',
itemStyle: {
//柱形图圆角,鼠标移上去效果,如果只是一个数字则说明四个参数全部设置为那么多
normal: {
//柱形图圆角,初始化效果
barBorderRadius: [2, 2, 0, 0]
}
},
tooltip: {
valueFormatter: function (value: string) {
return `${Number(value)} 元`
}
},
data: []
},
{
name: '药占比',
type: 'line',
yAxisIndex: 1,
smooth: true, //true 为平滑曲线,false为直线
symbol: 'circle', //将小圆点改成实心 不写symbol默认空心 none 不显示
symbolSize: 8, //小圆点的大小
label: {
show: true,
position: 'top',
formatter: function (params: any) {
return convertToPercentage(Number(params.value))
}
},
tooltip: {
valueFormatter: function (value: string) {
return convertToPercentage(Number(value))
}
},
data: []
}
]
}
// 折线配置
export const chartLineOptions = {
color: ['#209E85'],
// title: {
// text: 'Stacked Line'
// },
tooltip: {
trigger: 'axis',
triggerOn: 'mousemove',
confine: true,
height: 'auto',
backgroundColor: 'rgba(40, 49, 67, 0.85);',
borderColor: 'rgba(40, 49, 67, 0.85);',
enterable: true,
appendToBody: true,
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
// crossStyle: {
// color: '#fff'
// }
},
// 自定义提示框内容
formatter: function (data) {
let tooltipHtml = `
<div style="color:#ffffff;font-size: 14px;font-weight: 400;margin-bottom: 8px;max-height: 200px; overflow-y: auto;">
<div style="color:#ffffff;font-size: 14px;font-weight: 400;margin-bottom: 8px;">
${data[0].axisValue}
</br>
</div>
`
data.forEach((item) => {
tooltipHtml += `
<div style="font-size: 12px;padding: 4px 0;width: auto;max-height: 200px; overflow-y: auto;">
<div style="display:inline-block;margin-right:4px;width:10px;height:10px;background-color: ${item.color};"></div>
<span>${item.seriesName} :</span>
<span style="color:#ffffff">${item.seriesType == 'line' ? convertToPercentage(Number(item.data)) : item.data + '元'}</span>
</div>
`
})
tooltipHtml += '</div>'
return tooltipHtml
},
textStyle: {
fontSize: 14
}
},
legend: {
data: []
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: true,
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
name: '',
// interval: 5,
position: 'left',
alignTicks: true,
type: 'value',
splitLine: {
show: true // X 轴线
},
axisLine: {
// Y 轴线
show: false,
// min: 0,
// max: 10,
lineStyle: { color: '#939495' }
},
axisLabel: {
show: true,
fontSize: 14,
color: '#939495',
formatter: '{value} 元'
}
},
series: [
{
type: 'line',
// symbol: 'none', //去掉折线图中的节点
smooth: true, //true 为平滑曲线,false为直线
symbol: 'circle', //将小圆点改成实心 不写symbol默认空心
symbolSize: 8, //小圆点的大小
itemStyle: {
color: '#209E85' //小圆点和线的颜色
},
// areaStyle: {
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
// {
// offset: 0,
// color: 'rgba(213,72,120,0.8)' //靠上方的透明颜色
// },
// {
// offset: 1,
// color: 'rgba(213,72,120,0.3)' //靠下方的透明颜色
// }
// ])
// },
label: {
show: true,
position: 'top',
formatter: function (params: any) {
return convertToPercentage(Number(params.value))
}
},
tooltip: {
valueFormatter: function (value: string) {
return convertToPercentage(Number(value))
}
},
stack: 'Total',
data: [120, 132, 101, 134, 90, 230, 210]
}
]
}
// 柱状图配置
export const barChartOptions = {
title: {
textStyle: {
//文字颜色
color: '#07123C',
fontWeight: 'bold',
//字体系列
fontFamily: 'sans-serif',
//字体大小
fontSize: 18
}
},
toolbox: {
top: '2%',
right: '3%',
show: false,
feature: {
// dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true }
// saveAsImage: { show: true }
}
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(40, 49, 67, 0.85);',
borderColor: 'rgba(40, 49, 67, 0.85);',
enterable: true,
appendToBody: true,
textStyle: {
color: '#fff'
}
},
legend: {
// top: '2%',
left: '3%',
itemHeight: 10,
itemWidth: 10,
itemGap: 15,
type: 'scroll', // 数据过多时,分页显示
selected: [], //这里默认显示数组中前十个,如果不设置,则所有的数据都会显示在图表上
icon: 'rect', // 设置图例的形状
data: []
},
color: ['#13A89B'],
barWidth: 20,
calculable: true,
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['急诊科', '门诊科', '儿科', '妇科', '神经外科', '神经内科', '皮肤科', '放射科'],
axisTick: {
show: false
},
axisLabel: {
// 轴文字
show: true,
color: '#A6AAB2',
fontSize: 12,
interval: 0,
rotate: 20
},
axisLine: {
lineStyle: {
type: 'solid',
color: '#E6E6E8',
width: '1'
}
}
},
yAxis: {
type: 'value',
axisLabel: {
// 轴文字
show: true,
color: '#A6AAB2',
fontSize: 12
}
// max:99999//给y轴设置最大值
},
series: [
{
type: 'bar',
data: [120, 200, 150, 80, 70, 110, 130, 50],
// barGap:'80%',/*多个并排柱子设置柱子之间的间距*/
// barCategoryGap:'50%',/*多个并排柱子设置柱子之间的间距*/
itemStyle: {
//柱状图上方显示数值
normal: {
//柱形图圆角,初始化效果
barBorderRadius: [2, 2, 0, 0],
color: '#13A89B',
label: {
show: true, //开启显示
position: 'top', //在上方显示
textStyle: {
//数值样式
color: '#5E6580',
fontSize: 12
}
}
}
},
showBackground: true,
backgroundStyle: {
color: '#f2f8ff'
},
label: {
show: true,
position: 'top',
formatter: '{c}'
}
}
],
dataZoom: [
{
type: 'slider', //给x轴设置滚动条
show: true, //flase直接隐藏图形
xAxisIndex: [0],
bottom: 0,
height: 10,
showDetail: false,
startValue: 0, //滚动条的起始位置
endValue: 9 //滚动条的截止位置(按比例分割你的柱状图x轴长度)
},
{
type: 'inside', //设置鼠标滚轮缩放
show: true,
xAxisIndex: [0],
startValue: 0,
endValue: 9
}
]
}