// Top.jsx — 顶部本子横条 + 时空轴(左栏竖向时间轴 / 展开:线路图 + 数字地图) const { useState: useStateTop } = React; // ============ 本子横条(书架式 · 统一深色书脊)============ function NotebookRow({ notebooks, counts, active, onPick, onAdd, onNewNote }) { const activeNb = notebooks.find(nb => nb.id === active); return (
本子
{notebooks.map(nb => (
))} {/* 选中一个本子后,在这里写新的一页 */} {activeNb && ( )}
); } // ============ 时空轴 · 左栏(时间 × 地点合一 · 以现在为主轴 · 可伸缩)============ function SpacetimeColumn({ onOpenNote, onExpand, selected, cityFilter, setCityFilter }) { const [scale, setScale] = useStateTop(1); // 时间疏密(伸缩) const asc = buildStations(); const all = [...asc].reverse(); // 以现在为主轴 → 最新在上,向下走入过去 const cities = [...new Set(asc.map(s => s.note.city))]; const stations = cityFilter ? all.filter(s => s.note.city === cityFilter) : all; const gap = Math.round(4 * scale); let lastYear = null; return (
时空轴
沿着时间与地点,回到那一刻
{cities.map(c => ( ))}
{/* 伸缩控制:把时间轴拉疏 / 收密 */}
时间疏密
{/* 现在 · 主轴锚点 */} {!cityFilter && ( )} {stations.map((st, i) => { const showYear = st.note.year !== lastYear; lastYear = st.note.year; const prevCity = i > 0 ? stations[i - 1].note.city : null; const transfer = prevCity && prevCity !== st.note.city; const place = st.note.city ? st.note.city + (st.note.scene ? ' · ' + st.note.scene : '') : '未记地点'; return ( {showYear &&
{st.note.year}
}
); })}
更早的时空 · 向下滑
); } // ============ 展开覆层:线路图 + 数字地图 ============ function MetroMap({ onClose, onOpenNote }) { const [view, setView] = useStateTop('route'); // route | map const stations = buildStations(); const cities = [...new Set(stations.map(s => s.note.city))]; return (
{ if (e.target === e.currentTarget) onClose(); }}>
时空地图
你的旅程 · {stations.length} 站 · 跨越 {cities.length} 城 · 2021 — 2026
{view === 'route' ? : }
{view === 'route' ? '时间自上而下,城市分列成线 — 点任意一站,回到那个时空的笔记' : '按经纬度落点的数字地图 — 虚线是你走过的顺序'}
); } // —— 线路图:时间(行) × 城市(列) 地铁式 —— // function RouteDiagram({ stations, cities, onOpenNote }) { const colX = {}; cities.forEach((c, i) => colX[c] = 150 + i * 150); const rowY = i => 70 + i * 74; const W = 150 + cities.length * 150 + 40; const H = rowY(stations.length - 1) + 60; const pos = stations.map((st, i) => ({ x: colX[st.note.city], y: rowY(i), st, i })); const segs = []; for (let i = 1; i < pos.length; i++) { const a = pos[i - 1], b = pos[i]; segs.push({ a, b, color: b.st.line, transfer: a.st.note.city !== b.st.note.city }); } let lastYear = null; return ( {/* 时间轴竖线 */} {/* 城市表头 */} {cities.map(c => ( {c} ))} {/* 连线 */} {segs.map((s, i) => ( ))} {/* 站点 + 时间刻度 */} {pos.map(p => { const showYear = p.st.note.year !== lastYear; lastYear = p.st.note.year; return ( {showYear && {p.st.note.year}} {p.st.note.date.slice(5)} {p.st.note.time} onOpenNote(p.st.note.id)} style={{ cursor: 'pointer' }}> {p.st.note.title} ); })} ); } // —— 数字地图:东亚经纬度落点 —— // function DigitalMap({ stations, cities, onOpenNote }) { const W = 760, H = 460; const lonMin = 110, lonMax = 140, latMin = 19, latMax = 38; const px = lon => 60 + (lon - lonMin) / (lonMax - lonMin) * (W - 120); const py = lat => 40 + (latMax - lat) / (latMax - latMin) * (H - 90); // 经纬网格 const lonLines = []; for (let lo = 110; lo <= 140; lo += 5) lonLines.push(lo); const latLines = []; for (let la = 20; la <= 35; la += 5) latLines.push(la); // 城市落点(去重) const cityPos = {}; cities.forEach(c => { cityPos[c] = { x: px(CITY_GEO[c].lon), y: py(CITY_GEO[c].lat) }; }); const counts = {}; stations.forEach(s => counts[s.note.city] = (counts[s.note.city] || 0) + 1); // 行程弧线(按时间顺序的城市变化) const path = []; let prev = null; stations.forEach(s => { if (s.note.city !== prev) { path.push(s.note.city); prev = s.note.city; } }); const arcs = []; for (let i = 1; i < path.length; i++) { const a = cityPos[path[i - 1]], b = cityPos[path[i]]; const mx = (a.x + b.x) / 2, my = (a.y + b.y) / 2 - Math.abs(b.x - a.x) * .18 - 14; arcs.push({ d: `M${a.x} ${a.y} Q${mx} ${my} ${b.x} ${b.y}`, color: CITY_LINE[path[i]] }); } return ( {/* 海面底 */} {/* 简化东亚陆块(示意) */} {/* 经纬网格 */} {lonLines.map(lo => ( {lo}°E ))} {latLines.map(la => ( {la}°N ))} {/* 行程弧线 */} {arcs.map((a, i) => ( ))} {/* 城市落点 */} {cities.map(c => { const p = cityPos[c]; return ( { const s = stations.find(s => s.note.city === c); onOpenNote(s.note.id); }} style={{ cursor: 'pointer' }}> {c} {counts[c]} 则 ); })} EAST ASIA · 数字时空图 ); } Object.assign(window, { NotebookRow, SpacetimeColumn, MetroMap });