前言
最近接到个业务需求,类似于数据大屏展示图表的功能,需要具备一键换肤和表格转换成为图表的功能,对于我这种老Echarter来说已经是很轻车熟路的操作了,但是由于团队使用的是Vue3 +TS还是遇到了些坑点的。
Echart的基本操作
在vue3 中,通过npm install echarts可获取到对应的echart资源,在项目文件中建立对应的utils作为引用echart资源的工具库。
@/utils/utils
js
import * as echarts from "echarts/core"
import { BarChart, LineChart, PieChart, MapChart, PictorialBarChart, RadarChart } from "echarts/charts"
import {
TitleComponent,
TooltipComponent,
GridComponent,
PolarComponent,
AriaComponent,
ParallelComponent,
LegendComponent,
RadarComponent,
ToolboxComponent,
DataZoomComponent,
VisualMapComponent,
TimelineComponent,
CalendarComponent,
GraphicComponent
} from "echarts/components"
import { CanvasRenderer } from "echarts/renderers"
echarts.use([
LegendComponent,
TitleComponent,
TooltipComponent,
GridComponent,
PolarComponent,
AriaComponent,
ParallelComponent,
BarChart,
LineChart,
PieChart,
MapChart,
RadarChart,
CanvasRenderer,
PictorialBarChart,
RadarComponent,
ToolboxComponent,
DataZoomComponent,
VisualMapComponent,
TimelineComponent,
CalendarComponent,
GraphicComponent
])
export default echarts
然后再建立个Echart.vue文件
js
<template>
<div ref="echartsRef" style="height: 400px; width: 500px">gamePlay</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue"
import echarts from "@/utils/echarts"
const echartsRef = ref()
onMounted(() => {
const myChart = echarts.init(echartsRef.value)
// 指定图表的配置项和数据
const option = {
title: {
text: "ECharts 入门示例"
},
tooltip: {},
legend: {
data: ["销量"]
},
xAxis: {
data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"]
},
yAxis: {},
series: [
{
name: "销量",
type: "bar",
data: [5, 20, 36, 10, 10, 20]
}
]
}
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option)
})
</script>
就这么简单就可以渲染出一个echart图表了。
封装useEchartHook渲染图表
虽然我们已经能渲染出图表了,但是这个操作并不是最优解,因为我们的实际业务中是存在很多不同类型的图表,不可能每个都实例化Echart然后再手动调用setOptions方法,这显的很蠢。那么这个时候就需要引入useHookEchart的做法了。
js
import { Ref, shallowRef, onMounted, onDeactivated, onBeforeUnmount } from "vue"
import echarts from "@/utils/echarts"
export type EChartsCoreOption = echarts.EChartsCoreOption
const useEcharts = (elRef: Ref<HTMLDivElement>, options: EChartsCoreOption) => {
const charts = shallowRef<echarts.ECharts>()
const initCharts = () => {
charts.value = echarts.init(elRef.value)
setOptions(options)
}
const setOptions = (options: EChartsCoreOption) => {
charts.value && charts.value.setOption(options)
}
const echartsResize = () => {
charts.value && charts.value.resize()
}
onMounted(() => {
window.addEventListener("resize", echartsResize)
})
// 防止 echarts 页面 keepAlive 时,还在继续监听页面
onDeactivated(() => {
window.removeEventListener("resize", echartsResize)
})
onBeforeUnmount(() => {
window.removeEventListener("resize", echartsResize)
})
return {
initCharts,
setOptions,
echartsResize
}
}
export { useEcharts }
然后再echart.vue中引入
js
<template>
<div style="width: 100%; height: 100%" ref="elEcharts" />
</template>
<script setup lang="ts">
import { shallowRef, onMounted, watch } from "vue"
import { useEcharts, type EChartsCoreOption } from "@/hooks/useEcharts"
interface Props {
options: EChartsCoreOption
}
const props = defineProps<Props>()
const themeStore = useThemeStore()
const elEcharts = shallowRef()
const currentOptions = shallowRef(props.options)
const { setOptions, initCharts } = useEcharts(elEcharts, currentOptions.value)
watch(
() => props.options,
(nVal) => {
let targetOptions: EChartsCoreOption = {}
if (themeStore.currentColorArray && themeStore.currentColorArray.length > 0) {
targetOptions = { ...nVal }
targetOptions.color = themeStore.currentColorArray
} else {
targetOptions = { ...nVal }
}
setOptions(targetOptions)
}
)
onMounted(() => {
initCharts()
})
</script>
业务中使用
js
<template>
<BaseEcharts :options="options2" />
</template>
import BaseEcharts from "@/components/baseEcharts/index.vue"
const options2: EChartsCoreOption = {
tooltip: {
trigger: "axis"
},
grid: {
left: "3%",
right: "4%",
bottom: "0%",
top: "5%",
containLabel: true
},
xAxis: {
type: "category",
boundaryGap: false,
data: ["3-1", "3-2", "3-3", "3-4", "3-5", "3-6", "3-7"]
},
yAxis: {
axisLabel: {
formatter: function (val: number) {
return val
}
}
},
series: [
{
name: "QQ",
type: "line",
stack: "Total",
data: [200,201,202,203,204,205,206]
},
{
name: "微信",
type: "line",
stack: "Total",
data: [200,201,202,203,204,205,206]
}
]
}
关于Echart主题换肤
由于产品希望图表的主题是可以自由变换的,也就是图表换肤。那好说,因为在echart中可以通过options上的color字段进行自由换主题。这个时候需要改造useEcharts中的initCharts方法
js
...
const initCharts = (themeColor?: Array<string>) => {
charts.value = echarts.init(elRef.value)
if (themeColor) {
options.color = themeColor
}
setOptions(options)
}
...
然后再建立themeStore(由于是全局操作,这里使用的pinia) @/store/themeStore中建立updateCurrentColorByArray方法
js
import { defineStore } from "pinia"
import { ref } from "vue"
export const useThemeStore = defineStore("themeStore", () => {
const currentColorArray = ref<string[]>()
// 根据颜色组更新当前颜色组
const updateCurrentColorByArray = (color: string[]) => {
currentColorArray.value = color
}
return {
updateCurrentColorByArray,
currentColorArray
}
})
在echartsTheme.ts中预设以下几种主题
js
export const echartsThemeData = [
{
name: "vintage",
background: "#fef8ef",
theme: [
"#d87c7c",
"#919e8b",
"#d7ab82",
"#6e7074",
"#61a0a8",
"#efa18d",
"#787464",
"#cc7e63",
"#724e58",
"#4b565b"
]
},
{
name: "dark",
background: "#333",
theme: [
"#dd6b66",
"#759aa0",
"#e69d87",
"#8dc1a9",
"#ea7e53",
"#eedd78",
"#73a373",
"#73b9bc",
"#7289ab",
"#91ca8c",
"#f49f42"
]
},
{
name: "westeros",
background: "transparent",
theme: ["#516b91", "#59c4e6", "#edafda", "#93b7e3", "#a5e7f0", "#cbb0e3"]
},
{
name: "essos",
background: "rgba(242,234,191,0.15)",
theme: ["#893448", "#d95850", "#eb8146", "#ffb248", "#f2d643", "#ebdba4"]
},
{
name: "wonderland",
background: "transparent",
theme: ["#4ea397", "#22c3aa", "#7bd9a5", "#d0648a", "#f58db2", "#f2b3c9"]
},
{
name: "walden",
background: "rgba(252,252,252,0)",
theme: ["#3fb1e3", "#6be6c1", "#626c91", "#a0a7e6", "#c4ebad", "#96dee8"]
},
{
name: "chalk",
background: "#293441",
theme: ["#fc97af", "#87f7cf", "#f7f494", "#72ccff", "#f7c5a0", "#d4a4eb", "#d2f5a6", "#76f2f2"]
},
{
name: "infographic",
background: "transparent",
theme: [
"#C1232B",
"#27727B",
"#FCCE10",
"#E87C25",
"#B5C334",
"#FE8463",
"#9BCA63",
"#FAD860",
"#F3A43B",
"#60C0DD",
"#D7504B",
"#C6E579",
"#F4E001",
"#F0805A",
"#26C0C0"
]
},
{
name: "macarons",
background: "transparent",
theme: [
"#2ec7c9",
"#b6a2de",
"#5ab1ef",
"#ffb980",
"#d87a80",
"#8d98b3",
"#e5cf0d",
"#97b552",
"#95706d",
"#dc69aa",
"#07a2a4",
"#9a7fd1",
"#588dd5",
"#f5994e",
"#c05050",
"#59678c",
"#c9ab00",
"#7eb00a",
"#6f5553",
"#c14089"
]
},
{
name: "roma",
background: "transparent",
theme: [
"#E01F54",
"#001852",
"#f5e8c8",
"#b8d2c7",
"#c6b38e",
"#a4d8c2",
"#f3d999",
"#d3758f",
"#dcc392",
"#2e4783",
"#82b6e9",
"#ff6347",
"#a092f1",
"#0a915d",
"#eaf889",
"#6699FF",
"#ff6666",
"#3cb371",
"#d5b158",
"#38b6b6"
]
},
{
name: "shine",
background: "transparent",
theme: ["#c12e34", "#e6b600", "#0098d9", "#2b821d", "#005eaa", "#339ca8", "#cda819", "#32a487"]
},
{
name: "purple-passion",
background: "rgba(91,92,110,1)",
theme: ["#8a7ca8", "#e098c7", "#8fd3e8", "#71669e", "#cc70af", "#7cb4cc"]
}
]
在setting.vue中引入themeStore和echartTheme.ts
js
<template>
<div class="echarts-theme">
<baseTitle title="主题方案" toolContent="用于设置所有图表的主题" />
<el-row :gutter="20">
<el-col :span="12" v-for="item in echartsThemeData" :key="item.name">
<div class="echarts-theme-item-group" @click="handleClick(item.theme)">
<div
class="echarts-theme-item-color"
:style="{ backgroundColor: sItem }"
v-for="sItem in item.theme"
:key="sItem"
/>
</div>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import baseTitle from "./baseTitle.vue"
import { echartsThemeData } from "@/config/echartTheme"
import { useThemeStore } from "@/store/modules/themeStore"
const themeStore = useThemeStore()
const handleClick = (theme: Array<string>) => {
themeStore.updateCurrentColorByArray(theme)
}
</script>
<style lang="scss" scoped>
.echarts-theme-item-group {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
width: auto;
height: 32px;
padding: 5px;
margin-bottom: 8px;
overflow: hidden;
cursor: pointer;
border: 1px solid #eee;
border-radius: 4px;
}
.echarts-theme-item-color {
display: inline-block;
width: 20px;
height: 20px;
margin-right: 2px;
margin-bottom: 10px;
margin-left: 2px;
border-radius: 3px;
}
</style>
在echarts.vue中监听currentColorArray变化,重新去设置图表主题即可
js
...
watch(
() => themeStore.currentColorArray,
(nVal) => {
currentOptions.value.color = nVal
setOptions(currentOptions.value)
}
)
...
自定义颜色跟新图表主题
当我准备提交代码,关机下班的时候。产品突然说,用户再增加个根据颜色来生成图表的主题,而不是预设的主题。这个时候,我心里真的有1W只草泥马在奔腾。你一个图表目的不就是为了看数据展示吗?搞这么多花里胡哨的干嘛捏?
吐槽归吐槽,班还是要上的,先做个基础布局吧。
tvision-color
根据某个色阶来生成主题,我们可以通过 tvision-color进行操作。 在utils/color.ts
js
export const getColorArray = (hex: string) => {
const { colors: newPalette, primary: brandColorIndex } = Color.getColorGradations({
colors: [hex],
step: 10,
remainInput: false // 是否保留输入 不保留会矫正不合适的主题色
})[0]
return {
newPalette,
brandColorIndex
}
}
getColorArray方法会返回根据这个色阶返回一系列的相关颜色的数据,其中brandColorIndex为这个色阶的主色。 在themeStore中添加
js
...
// 根据颜色更新当前颜色组
const updateCurrentColorArray = (color: string) => {
const { newPalette, brandColorIndex } = getColorArray(color)
const firstColor = newPalette[0]
const primaryColor = newPalette[brandColorIndex]
newPalette[0] = primaryColor
newPalette[brandColorIndex] = firstColor
currentColorArray.value = newPalette
}
return {
updateCurrentColorArray,
updateCurrentColorByArray,
}
...
在业务中customColor.vue中调用
js
<template>
<div class="customColor">
<baseTitle title="主题色" />
<div class="customColor-list">
<div
class="customColor-item"
v-for="item in colorList"
:key="item"
:style="{ backgroundColor: item }"
@click="handleClick(item)"
>
<el-icon v-if="currentValue === item"><Check /></el-icon>
</div>
<el-color-picker v-model="color1" @change="customColor" />
</div>
</div>
</template>
<script setup lang="ts">
import baseTitle from "./baseTitle.vue"
import { ref } from "vue"
const colorList = ref(["#409EFF", "#007BA7", "#212121", "#11A983", "#13C2C2", "#6959CD", "#FF6B6B", "#87CEEB"])
const emits = defineEmits(["change"])
const currentValue = ref()
const color1 = ref()
const handleClick = (item: string) => {
currentValue.value = item
emits("change", item)
}
const customColor = (item: string | null) => {
if (item) {
handleClick(item)
}
}
</script>
<style scoped lang="scss">
@import "@/assets/mixins/box-center";
.customColor-list {
@include box-center(space-between);
.customColor-item {
width: 30px;
height: 30px;
font-size: 18px;
color: #fff;
cursor: pointer;
border-radius: 50%;
@include box-center;
}
}
</style>
总结
在这过程中,我们使用了Vue3和TS中使用Echart实现一键换肤和根据颜色生成图表主题的功能。通过封装useEchartHook来渲染图表,并使用tvision-color库根据色阶生成颜色组来更新主题。最终实现了用户可以根据自定义颜色来展示图表数据的需求。