91 lines
3.6 KiB
Vue
91 lines
3.6 KiB
Vue
<script setup lang="ts">
|
||
// 迷你走势:归一化折线,SVG viewBox=1x1 + preserveAspectRatio=none 拉伸,
|
||
// 描边 vector-effect=non-scaling-stroke 保证粗细不缩放;
|
||
// 端点/悬停点用 HTML 圆点(非等比 viewBox 会把 SVG 圆拉成椭圆)。
|
||
// 从主页大盘总览卡片抽出,指数卡片两处共用。hover 出十字点与「日期 数值」提示。
|
||
import { computed, ref } from 'vue';
|
||
|
||
const props = defineProps<{
|
||
values: number[];
|
||
dates?: string[]; // 与 values 对齐的交易日(YYYYMMDD),hover 提示用
|
||
pct?: number | null; // 涨跌幅决定配色(正=涨色/负=跌色/缺=灰)
|
||
}>();
|
||
|
||
const PAD = 0.08; // 上下留白,避免贴边
|
||
|
||
const geom = computed(() => {
|
||
const vals = props.values;
|
||
const n = vals.length;
|
||
if (n < 2) return null;
|
||
const lo = Math.min(...vals);
|
||
const hi = Math.max(...vals);
|
||
const span = hi - lo || Math.abs(hi) || 1;
|
||
const pts = vals.map((v, i) => ({
|
||
x: i / (n - 1),
|
||
y: 1 - ((v - lo) / span) * (1 - 2 * PAD) - PAD,
|
||
}));
|
||
const line = pts.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x.toFixed(4)},${p.y.toFixed(4)}`).join(' ');
|
||
const area = `${line} L1,1 L0,1 Z`;
|
||
return { pts, line, area, last: pts[n - 1] };
|
||
});
|
||
|
||
const color = computed(() => {
|
||
const p = props.pct;
|
||
if (p == null || p === 0) return '#A8AFB8';
|
||
return p > 0 ? 'var(--color-up)' : 'var(--color-down)';
|
||
});
|
||
|
||
// hover:相对坐标记录点索引,tooltip 跟随
|
||
const hover = ref<{ i: number; x: number; y: number } | null>(null);
|
||
|
||
function onMove(e: MouseEvent) {
|
||
if (!geom.value) return;
|
||
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
||
const t = Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width));
|
||
const i = Math.round(t * (props.values.length - 1));
|
||
hover.value = { i, x: geom.value.pts[i].x, y: geom.value.pts[i].y };
|
||
}
|
||
|
||
const hoverText = computed(() => {
|
||
const h = hover.value;
|
||
if (!h) return '';
|
||
const d = props.dates?.[h.i] ?? '';
|
||
const iso = d ? `${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}` : '';
|
||
const v = props.values[h.i];
|
||
return `${iso} ${v != null ? v.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '--'}`;
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<div class="relative h-9" @mousemove="onMove" @mouseleave="hover = null">
|
||
<svg v-if="geom" class="h-full w-full" viewBox="0 0 1 1" preserveAspectRatio="none" aria-hidden="true">
|
||
<path :d="geom.area" :fill="color" fill-opacity="0.1" />
|
||
<path
|
||
:d="geom.line"
|
||
fill="none"
|
||
:stroke="color"
|
||
stroke-width="2"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
vector-effect="non-scaling-stroke"
|
||
/>
|
||
</svg>
|
||
<!-- 端点(带 2px 表面环)与悬停点 -->
|
||
<span
|
||
v-if="geom"
|
||
class="pointer-events-none absolute h-2.5 w-2.5 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-[#101014]"
|
||
:style="{ left: `${geom.last.x * 100}%`, top: `${geom.last.y * 100}%`, backgroundColor: color }"
|
||
/>
|
||
<template v-if="hover && geom">
|
||
<span
|
||
class="pointer-events-none absolute h-2.5 w-2.5 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-[#101014]"
|
||
:style="{ left: `${hover.x * 100}%`, top: `${hover.y * 100}%`, backgroundColor: color }"
|
||
/>
|
||
<span
|
||
class="pointer-events-none absolute -top-1 z-10 -translate-y-full whitespace-nowrap rounded border border-[#3A3D46] bg-[#1A1B21] px-1.5 py-0.5 font-mono text-[10px] tabular-nums text-[#E5E7EB]"
|
||
:style="{ left: `${Math.min(82, Math.max(18, hover.x * 100))}%` }"
|
||
>{{ hoverText }}</span>
|
||
</template>
|
||
</div>
|
||
</template>
|