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库根据色阶生成颜色组来更新主题。最终实现了用户可以根据自定义颜色来展示图表数据的需求。

相关推荐
Мартин.3 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
一 乐4 小时前
学籍管理平台|在线学籍管理平台系统|基于Springboot+VUE的在线学籍管理平台系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
昨天;明天。今天。4 小时前
案例-表白墙简单实现
前端·javascript·css
数云界4 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd4 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常4 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer4 小时前
Vite:为什么选 Vite
前端
小御姐@stella4 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing4 小时前
【React】增量传输与渲染
前端·javascript·面试
GISer_Jing4 小时前
WebGL在低配置电脑的应用
javascript