SOURCE

<template>
  <div class="base-chart-warp">
    <!-- 暂无数据 begin -->
    <div
      v-if="isNoData"
      class="noData"
    >
      <div class="noData-img"></div>
      <p class="noData-txt">
        {{ noDataTxt }}
      </p>
    </div>
    <!-- 暂无数据 end -->
    <!-- 图表展示 begin -->
    <div class="base-echart">
      <div
        ref="echartDivRef"
        :style="{width, height}"
      ></div>
    </div>
    <!-- 图表展示 end -->
  </div>
</template>

<script lang="ts">
import {
  defineComponent,
  PropType,
  toRefs,
  onMounted,
  // onUnmounted,
  watchEffect,
  ref
} from "vue"
import * as echarts from "echarts"
import useEchart from "../hooks/useEchart"

export default defineComponent({
  props: {
    isNoData: { type: Boolean, default: false },
    noDataTxt: { type: String, default: "" },
    width: { type: String, default: "100%" },
    height: { type: String, default: "360px" },
    options: {
      type: Object as PropType<echarts.EChartsOption>
    }
  },
  setup(props) {
    const echartDivRef = ref<HTMLElement | undefined>()

    onMounted(() => {
      const { setOptions } = useEchart(echartDivRef.value as HTMLElement)
      watchEffect(() => {
        const { options } = toRefs(props)
        setOptions(options.value as echarts.EChartsOption)
      })
    })

    // onUnmounted(() => {
    //   echart.dispose
    // })
    return { echartDivRef }
  }
})
</script>

<style lang="scss" scoped>
// 暂无数据
.noData {
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  right: 0;
  z-index: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;

  .noData-img {
    width: 184px;
    height: 151px;
    // background: url('../../../assets/images/no-data.png') no-repeat center center;
    background-size: 100% auto;
  }

  .noData-txt {
    font-size: 14px;
    color: #7b8795;
    margin-top: 30px;
  }
}
</style>


// useEchart.ts
//hooks
/****************************************************/

import * as echarts from "echarts"

export default function (el: HTMLElement) {
  const echartInstance = echarts.init(el)
  const setOptions = (options: echarts.EChartsOption) => {
    echartInstance.setOption(options)
  }
  const updateSize = () => {
    echartInstance.resize()
  }

  window.addEventListener("resize", () => {
    echartInstance.resize()
  })

  return {
    echartInstance,
    setOptions,
    updateSize
  }
}


/****************************************************/

//  每段渐变的圆环

const originData = [
        { value: 1048, name: 'Search Engine', color: ['rgba(0,0,0,1)','rgba(0,0,0,0)'] },
        { value: 735, name: 'Direct', color: ['rgba(0,0,255,1)','rgba(0,0,255,0)'] },
        { value: 580, name: 'Email', color: ['rgba(0,255,0,1)','rgba(0,255,0,0)'] },
        { value: 484, name: 'Union Ads', color: ['rgba(255,0,0,1)','rgba(255,0,0,0)'] },
      ];
const total = originData.reduce((sum, it) => sum + Number(it.value || 0), 0);
let startAngle = -Math.PI / 2;
// 动态生成带有渐变方向的数据项
const data = originData.map((dItem) => {
  const angleSpan =  total ? (dItem.value / total) * Math.PI * 2 : 0; 
  // 计算扇区中心角度
  const midAngle = startAngle + angleSpan / 2;
  // 更新下一个扇区的起始角度
  startAngle += angleSpan;

  // 计算顺时针切线方向(midAngle + π/2)
  const tangentDir = midAngle + Math.PI / 2;
  // 渐变方向向量
  const dx = Math.cos(tangentDir);
  const dy = Math.sin(tangentDir);

  return {
    ...dItem,
    itemStyle: {
      color: Array.isArray(dItem.color)
        ? new echarts.graphic.LinearGradient(0.5 - dx * 0.5, 0.5 - dy * 0.5, 0.5 + dx * 0.5, 0.5 + dy * 0.5, [
            { offset: 0, color: dItem.color[0] },
            { offset: 1, color: dItem.color[1] },
          ])
        : dItem.color,
    },
  };
});
/****************************************************/
console 命令行工具 X clear

                    
>
console