108 lines
3.5 KiB
Vue
108 lines
3.5 KiB
Vue
<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>
|