refactor: RouteLine hover 跟随封装为组件自带能力——自动监听场景内 a[data-comp=course-card](章 id 取自 href),消费方零接线;演示场景从样例页移入组件页(两 app 同步);E2E 契约随迁 components 页(站点断言改场景内定位),27/27 绿

This commit is contained in:
cqh963852 committed 2026-10-08 16:07:56 +08:00
1 parent 3c9aaf5faf
commit 061b16cf79
12 files changed
+842 -87

No files matched your search

@@ -1,10 +1,18 @@
import { Page, Section } from '../../lib/ui'
import { ASSIGNMENT, CHAPTERS, PROFILES } from '../../lib/chapters'
import { ASSIGNMENT, CHAPTERS, EDGES, PROFILES } from '../../lib/chapters'
import { RouteLine } from '@stack/design-system'
import { MemphisCard } from '@stack/design-system'
import { AssignBlock, Ticket } from '@stack/design-system'
import { EntryGuide } from '@stack/design-system'
import { Mark } from '@stack/design-system'
/* RouteLine 演示坐标(节点=卡描边真实边缘) */
const nodes = {
1: { id: 1, borderLeft: 40, right: 290, y: 180 },
2: { id: 2, borderLeft: 380, right: 630, y: 108 },
5: { id: 5, borderLeft: 700, right: 840, y: 108 },
} as const
export function App() {
return (
<Page active="design-components" title="组件 · 入口载体与状态" desc="CourseCard(入口)· 票券(状态)· 指派块+脱靶标记(任务与警示)· 入口画像。线条的形式规格见 02 符号形状,与卡片的实际组合见 04 样例。">
@@ -21,6 +29,15 @@ export function App() {
</div>
</div>
</Section>
<Section label="RouteLine" note="实=先后 虚=相关 点=同族;hover 卡片,线与站点自动跟随(组件自带)">
<div className="relative h-[600px] overflow-hidden rounded-2xl bg-paper">
<RouteLine edges={EDGES} nodes={nodes} width={1160} height={600} />
<div className="absolute left-[40px] top-[110px] w-[250px]"><MemphisCard chapter={CHAPTERS[1]} /></div>
<div className="absolute left-[380px] top-[44px] w-[250px] -rotate-1"><MemphisCard chapter={CHAPTERS[2]} /></div>
<div className="absolute left-[700px] top-[44px] w-[140px]"><MemphisCard chapter={CHAPTERS[5]} compact /></div>
</div>
</Section>
<Section label="票券 & 指派学习块" note="票=完成状态;块=位置/预期/场景+脱靶标记">
<div className="flex flex-wrap items-start gap-8">
<Ticket chapter={CHAPTERS[1]} done />
@@ -1,17 +1,10 @@
/** 组合场景=大部分组件拿出来的真实使用,不做解释性标注:
* 入口画像 → 课程卡与关系线 → 进度票券 → 指派块;形状散落于卡片附近空当。 */
import { Page } from '../../lib/ui'
import { ASSIGNMENT, CHAPTERS, EDGES, PROFILES } from '../../lib/chapters'
import { AssignBlock, EntryGuide, MemphisCard, RouteLine, Scatter, Ticket } from '@stack/design-system'
import { ASSIGNMENT, CHAPTERS, PROFILES } from '../../lib/chapters'
import { AssignBlock, EntryGuide, MemphisCard, Scatter, Ticket } from '@stack/design-system'
import { Cross, Ring, Tremble, Tri, Wave } from '@stack/design-system'
/* 场景坐标(节点=卡描边真实边缘):卡1(40,110,250) 卡2(380,44,250,rot) 卡3(700,44,140) */
const nodes = {
1: { id: 1, borderLeft: 40, right: 290, y: 180 },
2: { id: 2, borderLeft: 380, right: 630, y: 108 },
5: { id: 5, borderLeft: 700, right: 840, y: 108 },
} as const
export function App() {
return (
<Page active="design-examples" title="样例 · 组合场景" desc="大部分组件同台真实使用:入口画像 → 课程卡与关系线 → 进度票券 → 指派块;形状散落于卡片附近空当。">
@@ -27,7 +20,6 @@ export function App() {
<Tremble size={20} key="tremble" />,
]}
/>
<RouteLine edges={EDGES} nodes={nodes} width={1160} height={600} />
<div className="absolute left-[40px] top-[110px] w-[250px]"><MemphisCard chapter={CHAPTERS[1]} entry={1} /></div>
<div className="absolute left-[380px] top-[44px] w-[250px] -rotate-1"><MemphisCard chapter={CHAPTERS[2]} entry={2} /></div>
<div className="absolute left-[700px] top-[44px] w-[140px]"><MemphisCard chapter={CHAPTERS[5]} compact /></div>
+3 -18
View File
@@ -1,6 +1,5 @@
/** 课程书架——教程首页:入口画像 → 六章卡片 → 关系与进度
* 主线场景:hover 卡片时线与站点跟随浮起(状态提升到场景层) */
import { useState } from 'react'
import { Page, Section } from '../../lib/ui'
import { MemphisCard, EntryGuide, RouteLine, Ticket } from '@stack/design-system'
import type { ChapterId } from '@stack/design-system'
@@ -23,7 +22,6 @@ const GRID: { id: ChapterId; entry?: 1 | 2 | 3 }[] = [
]
export function App() {
const [hovered, setHovered] = useState<ChapterId | null>(null)
return (
<Page
active="index"
@@ -51,27 +49,14 @@ export function App() {
nodes={nodes}
width={1160}
height={520}
hovered={hovered}
/>
<div
className="absolute left-[40px] top-[110px] w-[250px]"
onMouseEnter={() => setHovered(1)}
onMouseLeave={() => setHovered(null)}
>
<div className="absolute left-[40px] top-[110px] w-[250px]">
<MemphisCard chapter={CHAPTERS[1]} href="/ch1.html" />
</div>
<div
className="absolute left-[380px] top-[44px] w-[250px] -rotate-1"
onMouseEnter={() => setHovered(2)}
onMouseLeave={() => setHovered(null)}
>
<div className="absolute left-[380px] top-[44px] w-[250px] -rotate-1">
<MemphisCard chapter={CHAPTERS[2]} href="/ch2.html" />
</div>
<div
className="absolute left-[700px] top-[44px] w-[140px]"
onMouseEnter={() => setHovered(5)}
onMouseLeave={() => setHovered(null)}
>
<div className="absolute left-[700px] top-[44px] w-[140px]">
<MemphisCard chapter={CHAPTERS[5]} compact href="/ch5.html" />
</div>
<div className="absolute left-[60px] top-[380px] flex gap-4">