This commit is contained in:
2026-09-07 18:07:31 +08:00
parent 359f9ae2e4
commit bc1c72d558
27 changed files with 4532 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
<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 对齐的交易日YYYYMMDDhover 提示用
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>