Files
console-dev/app/components/ChartBox.vue
T

108 lines
3.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
/** 共用折线图容器:父级负责 option/数据,组件补充主题、系统动效偏好、可见尺寸和卸载清理。 */
import { computed, onMounted, onBeforeUnmount, ref } from "vue";
import { use } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import { LineChart } from "echarts/charts";
import {
GridComponent,
TooltipComponent,
LegendComponent,
} from "echarts/components";
import VChart from "vue-echarts";
import { t as useTheme } from "~/lib/useTheme.js";
import "~/assets/styles/ChartBox.CYP_mhat.css";
use([
// 仅注册实际使用的折线图、Canvas 和基础组件;新增图表类型时须同步显式注册。
CanvasRenderer,
LineChart,
GridComponent,
TooltipComponent,
LegendComponent,
]);
const props = defineProps({
option: { type: Object, required: true },
height: { default: 140 },
loading: Boolean,
});
const theme = useTheme();
const container = ref(null);
const visible = ref(false);
const reducedMotion = ref(false);
let observer;
let motionQuery;
const updateMotionPreference = (event) => {
reducedMotion.value = event.matches;
};
onMounted(() => {
// Canvas 不受 CSS 减动规则控制;先读取系统偏好,再允许图表创建,并跟随运行中的偏好变更。
motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
updateMotionPreference(motionQuery);
motionQuery.addEventListener("change", updateMotionPreference);
// 折叠面板初始尺寸可能为零;等容器可测量后创建图表,避免 ECharts 零宽初始化。
observer = new ResizeObserver(([entry]) => {
if (entry?.contentRect.width > 0 && entry.contentRect.height > 0)
visible.value = true;
});
if (container.value) observer.observe(container.value);
});
onBeforeUnmount(() => {
observer?.disconnect();
motionQuery?.removeEventListener("change", updateMotionPreference);
});
const themedOption = computed(() => {
// 在不修改父级 option 的前提下合成坐标轴主题,兼容单个轴与多轴数组。
const dark = theme.isDark.value;
const color = dark ? "#a1a1aa" : "#94a3b8";
const grid = dark ? "rgba(255,255,255,.08)" : "rgba(0,0,0,.06)";
const axis = {
axisLine: { lineStyle: { color: grid } },
axisLabel: { color, fontSize: 11 },
};
const apply = (value, y = false) => ({
...axis,
...value,
...(y ? { splitLine: { lineStyle: { color: grid } } } : {}),
});
return {
textStyle: { color },
...props.option,
// 减动时关闭绘制动画;恢复时显式还原父级设置/默认值,避免 ECharts 合并后残留 false。
animation: reducedMotion.value ? false : (props.option.animation ?? true),
xAxis: Array.isArray(props.option.xAxis)
? props.option.xAxis.map((value) => apply(value))
: apply(props.option.xAxis),
yAxis: Array.isArray(props.option.yAxis)
? props.option.yAxis.map((value) => apply(value, true))
: apply(props.option.yAxis, true),
};
});
</script>
<template>
<div ref="container" class="chart-box" :style="{ height: `${height}px` }">
<!-- 保留加载遮罩和文字,减动时只隐藏独立于数据动画的 Canvas 转圈。 -->
<VChart
v-if="visible"
:option="themedOption"
:loading="loading"
:loading-options="{ showSpinner: !reducedMotion }"
autoresize
class="chart"
/>
</div>
</template>
<style scoped>
.chart-box {
width: 100%;
min-width: 0;
overflow: hidden;
}
.chart {
width: 100%;
height: 100%;
}
</style>