使用概览 · 场景文档

本页由 2 个真实组件拼成;提示词与源码都可直接复制带走。

整页提示词

复现「使用概览」整页场景(/showcase/demo/dashboard):
- 页面级规则按 /design#pages 的对应小节执行,不在此复述
- 本页由这些真实组件拼成(手写等价物不算):
- 滑动删除(Swipe Actions):四段链:1:1 跟手 → 越界阻尼 30% → 阈值 96px / 速度 0.5px/ms 双判 → 吸附或飞出|/showcase/component/drag/swipe-to-dismiss
- 分段控件(Segmented Control (iOS)):APG radiogroup:方向键遍历 + roving tabindex|/showcase/component/segmented/glass-apple
- 骨架:视口高度固定,页头不滚,正文在容器内滚;分区页的页头与正文共用同一条内容列

本页用到的组件

清单是扫 src/pages/showcase/demo/dashboard.astro 的 import 闭包得出的, 手写等价物不会出现在这里。

一条链接装走整页

/r/scene--dashboard.json /llms/scenes/dashboard.md

dashboard.astro src/pages/showcase/demo/dashboard.astro

---
import BaseLayout from '../../../layouts/BaseLayout.astro';
import DemoChrome from '../../../components/DemoChrome.astro';
import PageHeader from '../../../components/PageHeader.astro';
import PageShell from '../../../components/PageShell.astro';
import SceneDocLink from '../../../components/SceneDocLink.astro';
import DashboardFilter from './DashboardFilter.tsx';
import ActivityFeed from './ActivityFeed.tsx';

import data from '../../../mocks/dashboard.json';
import type { DashboardData, Trend } from '../../../mocks/types';

const { metrics, chart, activity } = data as DashboardData;
const CHART_MAX = Math.max(...chart.values);
const CHART_MID = Math.round(CHART_MAX / 2);
const CHART_FIRST = chart.values[0];
const CHART_LAST = chart.values[chart.values.length - 1];
const CHART_TREND = CHART_LAST >= CHART_FIRST ? '整体上升' : '整体下降';

const TREND_COLOR: Record<Trend, string> = {
  up:   'text-[var(--color-success-fg)]',
  down: 'text-[var(--color-warning-fg)]',
};

// 白色内容面板:不透明、软阴影(玻璃留给悬浮层)
const panel = 'rounded-2xl bg-[var(--color-surface)] border border-[var(--glass-border)] shadow-[var(--shadow-sm)]';
---
<BaseLayout title="UI Echo · Dashboard">
  <DemoChrome />
  <PageShell demo col>

    <PageHeader
      slot="header"
      title="使用概览"
      description="核心指标、访问趋势与最近活动。"
    >
      <Fragment slot="actions">
        <DashboardFilter client:visible />
        <SceneDocLink id="dashboard" />
        <span class="text-xs text-[var(--color-text-subtle)] font-mono whitespace-nowrap">
          近 30 天
        </span>
      </Fragment>
    </PageHeader>

    <!-- 指标行:白色面板,数字是主角 -->
    <div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-6">
      {metrics.map((m) => (
        <dl class={`${panel} p-5`}>
          <dt class="text-xs text-[var(--color-text-muted)] mb-2">{m.label}</dt>
          <dd class="text-[length:var(--text-2xl)] leading-none font-semibold tracking-tight tabular-nums" style={{ fontWeight: 'var(--fw-semibold)' }}>
            {m.value}
          </dd>
          <dd class={`text-xs mt-2.5 font-mono tabular-nums ${TREND_COLOR[m.trend]}`}>
            {m.trend === 'up' ? '↑' : '↓'} {m.delta}
          </dd>
        </dl>
      ))}
    </div>

    <!-- 图表:刻度 + 渐变柱;悬停可读,数据表兜底(USWDS:不要求悬停才能读懂) -->
    <figure class={`${panel} p-6 mb-6`}>
      <figcaption class="flex items-center justify-between mb-5">
        <h3 class="text-sm font-medium">{chart.title}</h3>
        <div class="flex items-center gap-2 text-xs text-[var(--color-text-muted)]">
          <span class="size-2 rounded-sm bg-[var(--color-accent)]/80"></span>
          <span>{chart.unit}</span>
        </div>
      </figcaption>

      <div class="flex gap-3">
        <!-- 刻度:与柱基线同一套数值 -->
        <div class="flex flex-col justify-between h-40 pb-5 text-right text-[length:var(--text-2xs)] text-[var(--color-text-subtle)] font-mono tabular-nums" aria-hidden="true">
          <span>{CHART_MAX}k</span>
          <span>{CHART_MID}k</span>
          <span>0</span>
        </div>

        <div
          class="relative h-40 flex-1"
          role="img"
          aria-label={`1 至 12 月${chart.title}:从 ${CHART_FIRST} 千到 ${CHART_LAST} 千,${CHART_TREND}`}
        >
          <!-- 网格线 -->
          <div class="absolute inset-x-0 top-0 bottom-5 flex flex-col justify-between" aria-hidden="true">
            <div class="border-t border-dashed border-[var(--glass-border)]"></div>
            <div class="border-t border-dashed border-[var(--glass-border)]"></div>
            <div class="border-t border-[var(--glass-border)]"></div>
          </div>
          <!-- 柱 -->
          <div class="relative flex items-end gap-1.5 h-[calc(100%-20px)]">
            {chart.values.map((v, i) => (
              <div
                class="group flex-1 h-full flex items-end rounded-t-md bg-gradient-to-t from-[var(--color-accent)]/45 to-[var(--color-accent)]/85 hover:to-[var(--color-accent)] transition-colors relative"
                style={{ height: `${(v / CHART_MAX) * 100}%` }}
                title={`${i + 1}月 · ${v}k`}
              >
                <span class="
                  absolute -top-6 left-1/2 -translate-x-1/2
                  px-1.5 py-0.5 rounded text-[length:var(--text-2xs)] font-mono
                  bg-[var(--color-text)] text-[var(--color-bg)]
                  opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none
                ">{v}k</span>
              </div>
            ))}
          </div>
          <!-- 稀疏月份标签 -->
          <div class="flex gap-1.5 mt-1.5">
            {chart.values.map((_, i) => (
              <>
                <span class="sm:hidden flex-1 text-center text-[length:var(--text-2xs)] text-[var(--color-text-subtle)] font-mono whitespace-nowrap">
                  {i % 3 === 0 ? `${i + 1}月` : ''}
                </span>
                <span class="hidden sm:block flex-1 text-center text-[length:var(--text-2xs)] text-[var(--color-text-subtle)] font-mono whitespace-nowrap">
                  {i % 2 === 0 ? `${i + 1}月` : ''}
                </span>
              </>
            ))}
          </div>
        </div>
      </div>

      <details class="mt-4 border-t border-[var(--glass-border)] pt-3">
        <summary class="cursor-pointer text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text)] w-fit">
          查看数据表
        </summary>
        <table class="w-full mt-3 text-xs tabular-nums">
          <caption class="sr-only">{chart.title}按月数据,单位为{chart.unit}</caption>
          <thead>
            <tr class="border-b border-[var(--glass-border)]">
              <th scope="col" class="py-2 text-left font-medium text-[var(--color-text-muted)]">月份</th>
              <th scope="col" class="py-2 text-right font-medium text-[var(--color-text-muted)]">{chart.unit}</th>
            </tr>
          </thead>
          <tbody>
            {chart.values.map((v, i) => (
              <tr class="border-b border-[var(--glass-border)] last:border-b-0">
                <th scope="row" class="py-1.5 text-left font-normal text-[var(--color-text-subtle)]">{i + 1} 月</th>
                <td class="py-1.5 text-right font-mono">{v}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </details>
    </figure>

    <!-- 最近活动:真实组件驱动,可左滑 / Delete 清除 -->
    <ActivityFeed activities={activity} client:visible />

  </PageShell>
</BaseLayout>

DashboardFilter.tsx src/pages/showcase/demo/DashboardFilter.tsx

import { useState } from 'react';
import { GlassAppleSegmented } from '~/components/segmented/glass-apple/Segmented';

export default function DashboardFilter() {
  const [value, setValue] = useState('all');
  return (
    <GlassAppleSegmented
      value={value}
      onChange={setValue}
      options={[
        { value: 'all', label: '全部' },
        { value: 'users', label: '用户' },
        { value: 'traffic', label: '流量' },
        { value: 'revenue', label: '收入' },
      ]}
    />
  );
}

ActivityFeed.tsx src/pages/showcase/demo/ActivityFeed.tsx

import { useState } from 'react';
import { SwipeToDismiss } from '../../../components/drag/swipe-to-dismiss/SwipeToDismiss';
import type { Activity } from '../../../mocks/types';

export default function ActivityFeed({ activities }: { activities: Activity[] }) {
  const [items, setItems] = useState(activities);
  const [message, setMessage] = useState('');

  const dismiss = (user: string, action: string) => {
    setItems((prev) => prev.filter((i) => !(i.user === user && i.action === action)));
    setMessage(`已清除 @${user} 的一条动态,还剩 ${items.length - 1} 条`);
  };

  const panel = 'rounded-2xl bg-[var(--color-surface)] border border-[var(--glass-border)] shadow-[var(--shadow-sm)]';

  return (
    <div className={`${panel} p-6`}>
      <div className="flex items-center justify-between mb-4">
        <h3 className="text-sm font-medium">最近活动</h3>
        {items.length < activities.length && (
          <button
            type="button"
            onClick={() => {
              setItems(activities);
              setMessage('已恢复全部动态');
            }}
            className="press-feedback px-2.5 py-1 rounded-lg text-xs border border-[var(--glass-border)] bg-[var(--color-surface)] text-[var(--color-text-muted)] hover:text-[var(--color-text)] transition-colors cursor-pointer"
          >
            恢复
          </button>
        )}
      </div>

      <div className="divide-y divide-[var(--glass-border)]">
        {items.map((a) => (
          <SwipeToDismiss key={`${a.user}-${a.action}`} onDismiss={() => dismiss(a.user, a.action)}>
            <div className="flex items-center gap-3 py-3">
              <div
                className={`
                  size-8 rounded-full shrink-0
                  flex items-center justify-center text-xs font-mono
                  ${a.bot
                    ? 'bg-[var(--glass-recess)] text-[var(--color-text-muted)]'
                    : 'bg-[var(--color-accent)]/15 text-[var(--color-accent)]'}
                `}
              >
                {a.user[0].toUpperCase()}
              </div>
              <p className="flex-1 min-w-0 text-sm truncate">
                <span className="text-[var(--color-text-muted)]">@{a.user}</span>{' '}
                <span>{a.action}</span>
              </p>
              <span className="text-xs text-[var(--color-text-subtle)] font-mono shrink-0">{a.time}</span>
            </div>
          </SwipeToDismiss>
        ))}

        {items.length === 0 && (
          <p className="py-8 text-center text-xs text-[var(--color-text-muted)]">没有新的活动</p>
        )}
      </div>

      <p aria-live="polite" className="sr-only">
        {message}
      </p>
    </div>
  );
}

页面级规则(页头、工具栏、表格、设置、详情、流程、状态)只写在 页面模式 一处,这里不复述——复述就会漂移。