vue3 + ts + echarts 移动端(1)

617 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

前言

上个月接触了一下移动端的图表,我在学习(Ctrl+C)的过程中,遇到了不少坑,因此我想记录下来,分享一下.

封装echarts

因为是初衷学习嘛.那肯定是自己造轮子来的香

<template>
    <div id="chart" ref="root" :style="{ height: height, width: width }"></div>
</template>
<script lang="ts">
import {
    defineComponent,
    PropType,
    ref,
    toRefs,
    nextTick,
    onMounted,
    onUnmounted,
    watch,
    shallowRef,
    watchEffect,
} from "vue";

import * as echarts from "echarts/core";
import { EChartsType, EChartsCoreOption } from "echarts/core";
// 引入 SVG 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import { SVGRenderer } from "echarts/renderers";
import { LabelLayout } from "echarts/features";
// 引入柱图表,图表后缀都为 Chart, 我这里选择在组件中引入所有的组件,这样就不需要在每个引用的文件里面再次import
import { PieChart, BarChart, LineChart } from "echarts/charts"; // 系列类型的定义后缀都为 SeriesOption
// 引入直角坐标系,标题,图例,提示框等组件,组件后缀都为 Component
import {
    GridComponent,
    TitleComponent,
    LegendComponent,
    TooltipComponent,
    DatasetComponent,
    DataZoomComponent,
} from "echarts/components";
// 注册必须的组件 经过测试, 一个地方注册, 全局都能使用
echarts.use([
    BarChart,
    PieChart,
    LineChart,
    LabelLayout,
    SVGRenderer,
    GridComponent,
    TitleComponent,
    LegendComponent,
    TooltipComponent,
    DatasetComponent,
    DataZoomComponent,
]);
export default defineComponent({
    name: "echarts",
    props: {
        width: {
            type: String,
            default: "100%",
            required: false,
        },
        height: {
            type: String,
            default: "250px",
            required: false,
        },
        option: {
            type: Object as PropType<EChartsCoreOption>,
            required: true,
        },
        autoResize: {
            type: Boolean,
            default: true,
            required: false,
        },
        loading: {
            type: Boolean,
            default: false,
            required: false,
        },
    },
    setup(props) {
        const root = ref<any>(null);
        // https://github.com/apache/echarts/issues/14339
        // 否则在动态改变bar高度时会报错
        const chart = shallowRef<EChartsType>();

        const { autoResize, loading } = toRefs(props);

        const init = () => {
            if (!root.value) return;
            chart.value = echarts.init(root.value, {}, { renderer: "svg" });

            function commit() {
                if (props.option && chart.value) {
                    chart.value.setOption(props.option || {});
                }
            }
            // 兼容option默认就有的场景,一般项目场景是异步获取,此时setOption通过watch执行
            nextTick(() => {
                resize();
                commit();
            });
        };

        // 更新/设置配置
        const setOption = () => {
            nextTick(() => {
                if (!chart.value) {
                    init();
                    if (!chart.value) return;
                }
                chart.value.clear();
                chart.value.setOption(props.option || {});
            });
        };

        const showLoading = () => {
            chart.value && chart.value.showLoading();
        };

        const hideLoading = () => {
            chart.value && chart.value.hideLoading();
        };

        watchEffect(() => {
            if (loading.value) {
                showLoading();
            } else {
                hideLoading();
            }
        })
        
        const resize = () => {
            if (chart.value && !chart.value.isDisposed()) {
                // 有时候需要动态更改chart高度,这时候需要手动resize一下
                if (props.height.endsWith("px")) {
                    let height = props.height.slice(0, props.height.length - 2);
                    chart.value.resize({
                        height: Number(height),
                    });
                } else {
                    chart.value.resize();
                }
            }
        };

        const cleanup = () => {
            if (chart.value) {
                chart.value.dispose();
                chart.value = undefined;
            }
        };

        watch(
            () => props.height,
            () => {
                nextTick(() => {
                    resize();
                    setOption();
                });
            }
        );

        watch(
            () => props.option,
            () => {
                if (!chart.value) {
                    init();
                } else {
                    setOption();
                }
            },
            { deep: true }
        );

        onMounted(() => {
            init();
            if (autoResize.value) {
                window.addEventListener("resize", resize);
            }
        });

        onUnmounted(() => {
            if (autoResize.value) {
                window.removeEventListener("resize", resize);
            }
            cleanup();
        });

        return {
            root,
            chart,
        };
    },
});
</script>

总结

由于项目的功能场景。我目前只支持以下几个简单功能。

  • [✔] resize (也就是支持pc和移动)
  • [✔] 异步请求 showloadinghideloading
  • [✔] 兼容初始值存在option和异步设置option两种。
  • 允许使用echarts中的dispatchAction触发图表行为,由于篇幅有限.这里不展开。

有几个小坑。其实文档都有写。真的要看文档,看文档,看文档。

  • 有时候图表会放在多个tab标签页里,那些初始隐藏的标签在初始化图表的时候因为获取不到容器的实际高宽,可能会绘制失败(我组件里面默认宽度是100%。就会显示100px),

    • 因此在切换到该标签页时需要手动调用 resize 方法获取正确的高宽并且刷新画布,
    • 或者在props中显示指定图表高宽。(通过js去获取宽高然后重新赋值)
  • 有时候想要重新设置chart图表高度。但是这并不会触发resize。(比方说legend在右侧。但是ui不同意使用scroll模式)

    • 要不就还是手动resize一下。
    • 我的做法是监听props里面高度的变化。重新触发resize。
  • 有时候echarts中的legend无法满足项目样式需求时。(UI给样式太过特别,echart中的rich难以满足)

    • 建议直接在外层写一个div。自己通过css样式去模拟