uark_front/src/components/home/TrendChart.vue

168 lines
3.9 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.

<template>
<!-- 左中圖表 -->
<section
class="row-span-4 bg-white/30 backdrop-blur-sm rounded-md shadow min-h-0 flex items-stretch"
>
<div
class="w-full h-full min-h-[200px] p-8 sm:min-h-[240px] md:min-h-[300px]"
>
<div ref="chartEl" class="w-full h-full"></div>
</div>
</section>
</template>
<script setup>
import { ref, watch, onMounted, onBeforeUnmount } from "vue";
import * as echarts from "echarts";
import { brand } from "@/styles/palette";
const props = defineProps({
activeKey: { type: String, default: "residents" },
// 結構同 currentFacility.charts
// { [key]: { legends: [string, string], a: number[], b: number[] }, ... }
charts: { type: Object, required: true },
});
const chartEl = ref(null);
let chart = null;
let ro = null; // ResizeObserver
// 工具:近 7 天標籤
function last7DaysLabels() {
const labels = [];
const now = new Date();
for (let i = 6; i >= 0; i--) {
const d = new Date(now);
d.setDate(now.getDate() - i);
labels.push(`${d.getMonth() + 1}/${d.getDate()}`);
}
return labels;
}
// 標題
function mkTitle(text, subtext = "", opts = {}) {
return {
text,
subtext,
left: "center",
top: 12,
itemGap: 8,
textStyle: {
color: brand.black,
fontSize: 20,
fontWeight: 600,
fontFamily: '"Noto Sans TC"',
},
subtextStyle: {
color: brand.gray,
fontSize: 14,
fontWeight: 400,
fontFamily: '"Noto Sans TC"',
lineHeight: 20,
},
...opts,
};
}
function applyChartOption() {
if (!chart) return;
const conf = props.charts?.[props.activeKey];
if (!conf) return;
const title = `${conf.legends?.[0] ?? ""} ${conf.legends?.[1] ?? ""}`;
const subTitle = "(近 7 天)";
chart.setOption({
color: [brand.green, brand.purple],
title: mkTitle(title, subTitle),
grid: { top: 84, left: 36, right: 16, bottom: 56, containLabel: true },
tooltip: { trigger: "axis", axisPointer: { type: "line" }, confine: true },
xAxis: {
type: "category",
boundaryGap: false,
data: last7DaysLabels(),
axisLine: { lineStyle: { color: brand.gray } },
axisTick: { show: false },
axisLabel: { color: brand.gray },
splitLine: { show: false },
},
legend: {
data: conf.legends || [],
bottom: 8,
icon: "circle",
itemWidth: 10,
itemHeight: 10,
itemGap: 24,
textStyle: { color: brand.gray },
},
yAxis: {
type: "value",
name: "數量",
nameLocation: "middle",
nameGap: 40,
nameTextStyle: { padding: [0, 8, 0, 8] },
axisLine: { show: true },
axisTick: { show: true },
axisLabel: { color: brand.gray },
splitLine: { show: true, lineStyle: { color: brand.grayLight } },
splitArea: {
show: true,
areaStyle: { color: [brand.white, brand.grayLighter] },
},
},
series: [
{
name: conf.legends?.[0] ?? "",
type: "line",
data: conf.a || [],
symbol: "circle",
symbolSize: 6,
lineStyle: { width: 2 },
},
{
name: conf.legends?.[1] ?? "",
type: "line",
data: conf.b || [],
symbol: "circle",
symbolSize: 6,
lineStyle: { width: 2 },
},
],
});
}
function initChart() {
if (!chartEl.value) return;
chart = echarts.init(chartEl.value);
applyChartOption();
}
function disposeChart() {
if (chart) {
chart.dispose();
chart = null;
}
}
function onWindowResize() {
chart?.resize?.();
}
onMounted(() => {
initChart();
window.addEventListener("resize", onWindowResize);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", onWindowResize);
disposeChart();
});
// 只要 activeKey 或 charts內容/參照)變化,就重繪
watch(
() => [props.activeKey, props.charts],
() => applyChartOption(),
{ deep: true }
);
</script>