Vue3中我是这样玩Echart的

前言

最近接到个业务需求,类似于数据大屏展示图表的功能,需要具备一键换肤和表格转换成为图表的功能,对于我这种老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库根据色阶生成颜色组来更新主题。最终实现了用户可以根据自定义颜色来展示图表数据的需求。

相关推荐
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端