This commit is contained in:
2026-09-16 09:09:10 +08:00
parent 71a0f6e404
commit a490fdc110
8 changed files with 487 additions and 196 deletions

View File

@@ -435,7 +435,7 @@ export interface EventBacktestResponse {
export interface IndexQuote {
code: string; // 000001.SH / HKTECH / DJI
name: string; // 上证指数 / 恒生科技 / 道琼斯
region: 'cn' | 'hk' | 'us';
region: 'cn' | 'hk' | 'us' | 'apac';
close: number | null;
change: number | null;
pct_chg: number | null;

View File

@@ -8,8 +8,9 @@ import type { MarketOverview as Overview } from '@/api/types';
import AmountHistoryChart from '@/components/AmountHistoryChart.vue';
import Sparkline from '@/components/Sparkline.vue';
// 主页大盘总览:中美指数最近收盘(收盘口径,标注交易日),沪深两市市值/成交统计。
// 港股与国际指数在 /indexes 国际指数页。数据为 EOD 口径,进页面拉一次 + 手动刷新即可。
// 主页大盘总览:沪深 + 海外(美/日/韩)指数最近收盘(收盘口径,标注交易日),两市市值/成交统计。
// 其余国际指数在 /indexes 国际指数页。数据为 EOD 口径,进页面拉一次 + 手动刷新即可。
// 卡片整体可点击,跳 /indexes/:code 指数详情K 线 / 基本信息 / 估值)。
const overview = ref<Overview | null>(null);
const loading = ref(false);
@@ -32,10 +33,21 @@ const groups = computed(() => {
const idx = overview.value?.indexes ?? [];
return [
{ label: '沪深主要指数', items: idx.filter((i) => i.region === 'cn') },
{ label: '美股市场', items: idx.filter((i) => i.region === 'us') },
// 美/日/韩合并一行,避免首页纵向空间被三组卡片撑爆
{ label: '海外市场', items: idx.filter((i) => i.region === 'us' || i.region === 'apac') },
].filter((g) => g.items.length > 0);
});
/** 每组卡片列数4 列(沪深)/ 5 列海外md 起 3 列防挤) */
function gridClass(n: number): string {
if (n === 4) return 'md:grid-cols-4';
return 'md:grid-cols-3 lg:grid-cols-5';
}
function cardTo(code: string): string {
return `/indexes/${encodeURIComponent(code)}`;
}
const updatedAt = computed(() => {
const s = overview.value?.updated_at;
return s ? new Date(s).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }) : '';
@@ -107,10 +119,10 @@ function fmtYi(v: number | null | undefined): string {
<!-- 加载骨架首载与卡片同构的占位 -->
<div v-if="!overview && loading" class="space-y-6">
<div class="grid grid-cols-2 gap-3 md:grid-cols-4">
<div v-for="i in 4" :key="i" class="h-[104px] animate-pulse rounded-lg border border-[#26272E] bg-[#101014]" />
<div v-for="i in 4" :key="i" class="h-[122px] animate-pulse rounded-lg border border-[#26272E] bg-[#101014]" />
</div>
<div class="grid grid-cols-2 gap-3 md:grid-cols-5">
<div v-for="i in 5" :key="i" class="h-[104px] animate-pulse rounded-lg border border-[#26272E] bg-[#101014]" />
<div class="grid grid-cols-2 gap-3 md:grid-cols-3 lg:grid-cols-5">
<div v-for="i in 5" :key="i" class="h-[122px] animate-pulse rounded-lg border border-[#26272E] bg-[#101014]" />
</div>
</div>
@@ -123,14 +135,13 @@ function fmtYi(v: number | null | undefined): string {
<template v-else-if="overview">
<div v-for="g in groups" :key="g.label" class="mb-4 last:mb-0">
<div class="mb-2 text-xs text-[#6B7280]">{{ g.label }}</div>
<div
class="grid grid-cols-2 gap-3"
:class="g.items.length === 4 ? 'md:grid-cols-4' : 'md:grid-cols-3 lg:grid-cols-5'"
>
<div
<div class="grid grid-cols-2 gap-3" :class="gridClass(g.items.length)">
<RouterLink
v-for="it in g.items"
:key="it.code"
class="relative overflow-visible rounded-lg border border-[#26272E] bg-[#101014] px-4 pb-3 pt-3"
:to="cardTo(it.code)"
class="group relative block rounded-lg border border-[#26272E] bg-[#101014] px-4 pb-2 pt-3 transition-all hover:-translate-y-0.5 hover:border-[#3A3D46] hover:shadow-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 focus-visible:ring-offset-black"
:title="`查看 ${it.name} 详情`"
>
<div class="flex items-baseline justify-between">
<span class="text-sm font-medium text-[#E5E7EB]">{{ it.name }}</span>
@@ -148,7 +159,11 @@ function fmtYi(v: number | null | undefined): string {
</div>
<!-- 迷你走势hover 出十字点与数值 -->
<Sparkline class="mt-2" :values="it.spark" :dates="it.spark_dates" :pct="it.pct_chg" />
</div>
<!-- 点击进入 /indexes/:code 指数详情 -->
<div class="mt-1 flex items-center justify-end text-[10px] text-[#6B7280] opacity-0 transition-opacity group-hover:opacity-100">
查看详情
</div>
</RouterLink>
</div>
</div>