- grades: 拆分 batch-grade-entry (457→374) + grade-record-list (523→231),新增 4 个子组件 - attendance: 迁移 stats-cards/filters 到 StatsGrid/FilterBar 底座 - error-book: 迁移 stats-cards/filters 到 StatsGrid/FilterBar 底座 - elective: 迁移 stats-cards/filters 到 StatsGrid/FilterBar 底座 - 删除 12 个重复组件,新增 app 层 filter 组件就近放置 - tsc 零错误,lint 无新增错误
293 lines
20 KiB
HTML
293 lines
20 KiB
HTML
<h2>画布式锚点布局 — 正文固定 + 节点散布 + 连线关联</h2>
|
||
<p class="subtitle">保留 React Flow 画布交互,正文为不可移动但可缩放的中央容器,节点通过连线关联正文锚点</p>
|
||
|
||
<div class="mockup">
|
||
<div class="mockup-header">备课编辑器 — 画布视图(默认状态)</div>
|
||
<div class="mockup-body" style="padding:0;background:#f1f5f9;overflow:hidden;">
|
||
<div style="font-family:monospace;font-size:12px;line-height:1.6;position:relative;height:560px;">
|
||
|
||
<!-- 顶部工具栏 -->
|
||
<div style="background:#1e293b;color:#e2e8f0;padding:8px 12px;display:flex;justify-content:space-between;align-items:center;z-index:10;position:relative;">
|
||
<div style="display:flex;align-items:center;gap:8px;">
|
||
<span>📖</span>
|
||
<span style="font-weight:bold;">秋天(第一课时)</span>
|
||
<span style="background:#334155;padding:2px 8px;border-radius:3px;font-size:10px;">语文 · 一年级上册</span>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:8px;">
|
||
<span style="font-size:10px;color:#94a3b8;">💾 已保存</span>
|
||
<span style="background:#334155;padding:4px 8px;border-radius:3px;font-size:10px;">📋 版本</span>
|
||
<span style="background:#3b82f6;padding:4px 12px;border-radius:3px;font-size:10px;">💾 保存</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 画布区域 -->
|
||
<div style="position:relative;width:100%;height:520px;background:#f1f5f9;background-image:radial-gradient(#cbd5e1 1px, transparent 1px);background-size:20px 20px;overflow:hidden;">
|
||
|
||
<!-- SVG 连线层(默认 10% 透明度) -->
|
||
<svg style="position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;opacity:0.1;" viewBox="0 0 800 520">
|
||
<!-- 节点1(导入) → 正文锚点1 -->
|
||
<path d="M 130 120 Q 200 140 280 180" stroke="#3b82f6" stroke-width="2" fill="none" stroke-dasharray="4 4"/>
|
||
<circle cx="280" cy="180" r="4" fill="#3b82f6"/>
|
||
<!-- 节点2(文本研习) → 正文锚点2 -->
|
||
<path d="M 130 220 Q 200 230 280 240" stroke="#f59e0b" stroke-width="2" fill="none" stroke-dasharray="4 4"/>
|
||
<circle cx="280" cy="240" r="4" fill="#f59e0b"/>
|
||
<!-- 节点3(新授) → 正文锚点3 -->
|
||
<path d="M 670 120 Q 600 150 520 200" stroke="#0ea5e9" stroke-width="2" fill="none" stroke-dasharray="4 4"/>
|
||
<circle cx="520" cy="200" r="4" fill="#0ea5e9"/>
|
||
<!-- 节点4(练习) → 正文锚点4 -->
|
||
<path d="M 670 220 Q 600 240 520 260" stroke="#ec4899" stroke-width="2" fill="none" stroke-dasharray="4 4"/>
|
||
<circle cx="520" cy="260" r="4" fill="#ec4899"/>
|
||
<!-- 节点5(小结) → 正文锚点5 -->
|
||
<path d="M 670 340 Q 600 320 520 300" stroke="#22c55e" stroke-width="2" fill="none" stroke-dasharray="4 4"/>
|
||
<circle cx="520" cy="300" r="4" fill="#22c55e"/>
|
||
<!-- 节点间连线(教学流程) -->
|
||
<path d="M 130 140 L 130 200" stroke="#64748b" stroke-width="1.5" fill="none"/>
|
||
<path d="M 670 140 L 670 200" stroke="#64748b" stroke-width="1.5" fill="none"/>
|
||
<path d="M 670 240 L 670 320" stroke="#64748b" stroke-width="1.5" fill="none"/>
|
||
</svg>
|
||
|
||
<!-- 中央:正文容器(不可移动,可缩放) -->
|
||
<div style="position:absolute;left:280px;top:80px;width:240px;background:#fffbeb;border:2px solid #f59e0b;border-radius:8px;padding:12px;box-shadow:0 4px 12px rgba(0,0,0,0.1);">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;border-bottom:1px solid #fde68a;padding-bottom:6px;">
|
||
<span style="font-size:11px;font-weight:bold;color:#92400e;">📜 课文正文</span>
|
||
<span style="font-size:9px;color:#94a3b8;background:#fef3c7;padding:1px 4px;border-radius:2px;">🔒 固定</span>
|
||
</div>
|
||
<div style="font-size:13px;color:#78350f;line-height:1.8;">
|
||
<div style="text-align:center;font-weight:bold;margin-bottom:6px;">秋天</div>
|
||
<p style="margin:0 0 4px 0;">
|
||
<span style="background:#dbeafe;border-bottom:2px solid #3b82f6;">天气凉了</span>,树叶黄了,
|
||
</p>
|
||
<p style="margin:0 0 4px 0;">
|
||
<span style="background:#fef3c7;border-bottom:2px solid #f59e0b;">天空那么蓝</span>,那么高。
|
||
</p>
|
||
<p style="margin:0 0 4px 0;">
|
||
一群大雁往南飞,
|
||
</p>
|
||
<p style="margin:0 0 4px 0;">
|
||
一会儿排成个<span style="background:#e0f2fe;border-bottom:2px solid #0ea5e9;">"人"字</span>,
|
||
</p>
|
||
<p style="margin:0 0 4px 0;">
|
||
一会儿排成个<span style="background:#fce7f3;border-bottom:2px solid #ec4899;">"一"字</span>。
|
||
</p>
|
||
<p style="margin:0;">
|
||
<span style="background:#dcfce7;border-bottom:2px solid #22c55e;">啊!秋天来了!</span>
|
||
</p>
|
||
</div>
|
||
<!-- 缩放控件 -->
|
||
<div style="position:absolute;bottom:-12px;right:-12px;background:#fff;border:1px solid #f59e0b;border-radius:50%;width:24px;height:24px;display:flex;align-items:center;justify-content:center;font-size:12px;cursor:pointer;box-shadow:0 2px 4px rgba(0,0,0,0.1);">🔍</div>
|
||
</div>
|
||
|
||
<!-- 左侧节点 -->
|
||
<!-- 节点1:导入 -->
|
||
<div style="position:absolute;left:30px;top:90px;width:140px;background:#fff;border:1px solid #3b82f6;border-left:3px solid #3b82f6;border-radius:6px;padding:8px;box-shadow:0 2px 6px rgba(0,0,0,0.08);cursor:move;">
|
||
<div style="display:flex;align-items:center;gap:4px;margin-bottom:4px;">
|
||
<span style="background:#3b82f6;color:#fff;border-radius:50%;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;font-size:8px;font-weight:bold;">1</span>
|
||
<span style="font-size:10px;font-weight:bold;color:#1e3a8a;">💡 导入</span>
|
||
</div>
|
||
<div style="font-size:9px;color:#64748b;">提问:你见过秋天的树叶吗?</div>
|
||
<!-- React Flow Handle 标记 -->
|
||
<div style="position:absolute;right:-4px;top:50%;width:8px;height:8px;background:#3b82f6;border-radius:50%;border:1px solid #fff;"></div>
|
||
</div>
|
||
|
||
<!-- 节点2:文本研习 -->
|
||
<div style="position:absolute;left:30px;top:200px;width:140px;background:#fff;border:1px solid #f59e0b;border-left:3px solid #f59e0b;border-radius:6px;padding:8px;box-shadow:0 2px 6px rgba(0,0,0,0.08);cursor:move;">
|
||
<div style="display:flex;align-items:center;gap:4px;margin-bottom:4px;">
|
||
<span style="background:#f59e0b;color:#fff;border-radius:50%;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;font-size:8px;font-weight:bold;">2</span>
|
||
<span style="font-size:10px;font-weight:bold;color:#92400e;">📝 文本研习</span>
|
||
</div>
|
||
<div style="font-size:9px;color:#64748b;">赏析:叠词的运用</div>
|
||
<div style="position:absolute;right:-4px;top:50%;width:8px;height:8px;background:#f59e0b;border-radius:50%;border:1px solid #fff;"></div>
|
||
</div>
|
||
|
||
<!-- 右侧节点 -->
|
||
<!-- 节点3:新授 -->
|
||
<div style="position:absolute;left:630px;top:90px;width:140px;background:#fff;border:1px solid #0ea5e9;border-left:3px solid #0ea5e9;border-radius:6px;padding:8px;box-shadow:0 2px 6px rgba(0,0,0,0.08);cursor:move;">
|
||
<div style="display:flex;align-items:center;gap:4px;margin-bottom:4px;">
|
||
<span style="background:#0ea5e9;color:#fff;border-radius:50%;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;font-size:8px;font-weight:bold;">3</span>
|
||
<span style="font-size:10px;font-weight:bold;color:#075985;">📚 新授</span>
|
||
</div>
|
||
<div style="font-size:9px;color:#64748b;">讲解:大雁南飞</div>
|
||
<div style="position:absolute;left:-4px;top:50%;width:8px;height:8px;background:#0ea5e9;border-radius:50%;border:1px solid #fff;"></div>
|
||
</div>
|
||
|
||
<!-- 节点4:练习 -->
|
||
<div style="position:absolute;left:630px;top:200px;width:140px;background:#fff;border:1px solid #ec4899;border-left:3px solid #ec4899;border-radius:6px;padding:8px;box-shadow:0 2px 6px rgba(0,0,0,0.08);cursor:move;">
|
||
<div style="display:flex;align-items:center;gap:4px;margin-bottom:4px;">
|
||
<span style="background:#ec4899;color:#fff;border-radius:50%;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;font-size:8px;font-weight:bold;">4</span>
|
||
<span style="font-size:10px;font-weight:bold;color:#9f1239;">✏️ 练习</span>
|
||
</div>
|
||
<div style="font-size:9px;color:#64748b;">3 道题</div>
|
||
<div style="position:absolute;left:-4px;top:50%;width:8px;height:8px;background:#ec4899;border-radius:50%;border:1px solid #fff;"></div>
|
||
</div>
|
||
|
||
<!-- 节点5:小结 -->
|
||
<div style="position:absolute;left:630px;top:320px;width:140px;background:#fff;border:1px solid #22c55e;border-left:3px solid #22c55e;border-radius:6px;padding:8px;box-shadow:0 2px 6px rgba(0,0,0,0.08);cursor:move;">
|
||
<div style="display:flex;align-items:center;gap:4px;margin-bottom:4px;">
|
||
<span style="background:#22c55e;color:#fff;border-radius:50%;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;font-size:8px;font-weight:bold;">5</span>
|
||
<span style="font-size:10px;font-weight:bold;color:#166534;">📌 小结</span>
|
||
</div>
|
||
<div style="font-size:9px;color:#64748b;">总结全文</div>
|
||
<div style="position:absolute;left:-4px;top:50%;width:8px;height:8px;background:#22c55e;border-radius:50%;border:1px solid #fff;"></div>
|
||
</div>
|
||
|
||
<!-- 顶部全局节点(未锚定) -->
|
||
<div style="position:absolute;left:30px;top:10px;width:100px;background:#fff;border:1px dashed #3b82f6;border-radius:6px;padding:6px;box-shadow:0 2px 4px rgba(0,0,0,0.05);opacity:0.8;">
|
||
<div style="font-size:9px;font-weight:bold;color:#1e3a8a;">🎯 教学目标</div>
|
||
</div>
|
||
<div style="position:absolute;left:140px;top:10px;width:100px;background:#fff;border:1px dashed #f59e0b;border-radius:6px;padding:6px;box-shadow:0 2px 4px rgba(0,0,0,0.05);opacity:0.8;">
|
||
<div style="font-size:9px;font-weight:bold;color:#92400e;">⭐ 重难点</div>
|
||
</div>
|
||
|
||
<!-- 底部全局节点(未锚定) -->
|
||
<div style="position:absolute;left:30px;top:440px;width:100px;background:#fff;border:1px dashed #a855f7;border-radius:6px;padding:6px;box-shadow:0 2px 4px rgba(0,0,0,0.05);opacity:0.8;">
|
||
<div style="font-size:9px;font-weight:bold;color:#9333ea;">🏠 作业</div>
|
||
</div>
|
||
<div style="position:absolute;left:140px;top:440px;width:100px;background:#fff;border:1px dashed #6366f1;border-radius:6px;padding:6px;box-shadow:0 2px 4px rgba(0,0,0,0.05);opacity:0.8;">
|
||
<div style="font-size:9px;font-weight:bold;color:#4f46e5;">📋 板书设计</div>
|
||
</div>
|
||
<div style="position:absolute;left:250px;top:440px;width:100px;background:#fff;border:1px dashed #64748b;border-radius:6px;padding:6px;box-shadow:0 2px 4px rgba(0,0,0,0.05);opacity:0.8;">
|
||
<div style="font-size:9px;font-weight:bold;color:#475569;">💭 教学反思</div>
|
||
</div>
|
||
|
||
<!-- React Flow Controls(右下角) -->
|
||
<div style="position:absolute;bottom:12px;right:12px;background:#fff;border:1px solid #e2e8f0;border-radius:4px;padding:4px;display:flex;flex-direction:column;gap:2px;box-shadow:0 2px 4px rgba(0,0,0,0.05);">
|
||
<div style="width:20px;height:20px;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:14px;border-radius:2px;">+</div>
|
||
<div style="width:20px;height:20px;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:14px;border-radius:2px;">−</div>
|
||
<div style="width:20px;height:20px;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:10px;border-radius:2px;">⌖</div>
|
||
</div>
|
||
|
||
<!-- 添加节点按钮(左下角) -->
|
||
<div style="position:absolute;bottom:12px;left:12px;background:#3b82f6;color:#fff;padding:6px 12px;border-radius:4px;font-size:10px;cursor:pointer;box-shadow:0 2px 4px rgba(59,130,246,0.3);">
|
||
+ 添加节点
|
||
</div>
|
||
|
||
<!-- 透明度提示 -->
|
||
<div style="position:absolute;top:12px;right:12px;background:#fff;border:1px solid #e2e8f0;border-radius:4px;padding:4px 8px;font-size:9px;color:#64748b;">
|
||
连线默认 10% 透明度 · 选中节点时完整显示
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 选中状态对比 -->
|
||
<div class="section" style="margin-top:24px;">
|
||
<h3>选中节点时的连线显示对比</h3>
|
||
<div class="split">
|
||
<div class="mockup">
|
||
<div class="mockup-header">默认状态 — 连线 10% 透明度</div>
|
||
<div class="mockup-body" style="padding:16px;background:#f1f5f9;">
|
||
<svg style="width:100%;height:120px;" viewBox="0 0 300 120">
|
||
<path d="M 30 60 Q 120 60 270 60" stroke="#3b82f6" stroke-width="2" fill="none" stroke-dasharray="4 4" opacity="0.1"/>
|
||
<rect x="10" y="45" width="40" height="30" fill="#fff" stroke="#3b82f6" rx="4"/>
|
||
<text x="30" y="64" text-anchor="middle" font-size="10" fill="#1e3a8a">导入</text>
|
||
<rect x="250" y="45" width="40" height="30" fill="#fffbeb" stroke="#f59e0b" stroke-width="2" rx="4"/>
|
||
<text x="270" y="64" text-anchor="middle" font-size="10" fill="#92400e">课文</text>
|
||
</svg>
|
||
<div style="text-align:center;font-size:10px;color:#94a3b8;">连线几乎不可见,画布干净</div>
|
||
</div>
|
||
</div>
|
||
<div class="mockup">
|
||
<div class="mockup-header">选中"导入"节点 — 连线 100% 显示</div>
|
||
<div class="mockup-body" style="padding:16px;background:#f1f5f9;">
|
||
<svg style="width:100%;height:120px;" viewBox="0 0 300 120">
|
||
<path d="M 30 60 Q 120 60 270 60" stroke="#3b82f6" stroke-width="2.5" fill="none" stroke-dasharray="4 4" opacity="1"/>
|
||
<circle cx="270" cy="60" r="5" fill="#3b82f6"/>
|
||
<rect x="10" y="45" width="40" height="30" fill="#fff" stroke="#3b82f6" stroke-width="3" rx="4"/>
|
||
<text x="30" y="64" text-anchor="middle" font-size="10" fill="#1e3a8a">导入</text>
|
||
<rect x="250" y="45" width="40" height="30" fill="#fffbeb" stroke="#f59e0b" stroke-width="2" rx="4"/>
|
||
<text x="270" y="64" text-anchor="middle" font-size="10" fill="#92400e">课文</text>
|
||
</svg>
|
||
<div style="text-align:center;font-size:10px;color:#3b82f6;">连线完整显示,高亮锚点位置</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 数据模型 -->
|
||
<div class="section">
|
||
<h3>数据模型设计</h3>
|
||
<div style="background:#1e293b;color:#e2e8f0;padding:16px;border-radius:6px;font-family:monospace;font-size:11px;line-height:1.6;">
|
||
<div style="color:#94a3b8;">// 正文容器节点(特殊节点类型,不可拖动,可缩放)</div>
|
||
<div><span style="color:#f59e0b;">interface</span> <span style="color:#3b82f6;">TextbookContentNode</span> <span style="color:#f59e0b;">extends</span> <span style="color:#3b82f6;">LessonPlanNode</span> {</div>
|
||
<div> type: <span style="color:#10b981;">"textbook_content"</span>; <span style="color:#64748b;">// 新增节点类型</span></div>
|
||
<div> data: {</div>
|
||
<div> chapterId: <span style="color:#10b981;">string</span>; <span style="color:#64748b;">// 关联教材章节</span></div>
|
||
<div> content: <span style="color:#10b981;">string</span>; <span style="color:#64748b;">// Markdown 正文(缓存)</span></div>
|
||
<div> zoom: <span style="color:#10b981;">number</span>; <span style="color:#64748b;">// 缩放比例 0.5-2.0</span></div>
|
||
<div> };</div>
|
||
<div> position: { x: <span style="color:#10b981;">number</span>; y: <span style="color:#10b981;">number</span> }; <span style="color:#64748b;">// 固定位置(不可拖动)</span></div>
|
||
<div> draggable: <span style="color:#10b981;">false</span>; <span style="color:#64748b;">// React Flow 节点锁定</span></div>
|
||
<div>}</div>
|
||
<br>
|
||
<div style="color:#94a3b8;">// 锚点连线(节点 → 正文位置)</div>
|
||
<div><span style="color:#f59e0b;">interface</span> <span style="color:#3b82f6;">AnchorEdge</span> <span style="color:#f59e0b;">extends</span> <span style="color:#3b82f6;">LessonPlanEdge</span> {</div>
|
||
<div> type: <span style="color:#10b981;">"anchor"</span>; <span style="color:#64748b;">// 锚点连线(vs "flow" 流程连线)</span></div>
|
||
<div> source: <span style="color:#10b981;">string</span>; <span style="color:#64748b;">// 节点 ID</span></div>
|
||
<div> target: <span style="color:#10b981;">string</span>; <span style="color:#64748b;">// 正文节点 ID</span></div>
|
||
<div> targetHandle: <span style="color:#10b981;">string</span>; <span style="color:#64748b;">// "anchor:123:145"(正文偏移量 start:end)</span></div>
|
||
<div>}</div>
|
||
<br>
|
||
<div style="color:#94a3b8;">// LessonPlanDocument v3</div>
|
||
<div><span style="color:#f59e0b;">interface</span> <span style="color:#3b82f6;">LessonPlanDocument</span> {</div>
|
||
<div> version: <span style="color:#10b981;">3</span>;</div>
|
||
<div> nodes: <span style="color:#3b82f6;">LessonPlanNode</span>[]; <span style="color:#64748b;">// 含 1 个 textbook_content + N 个教学节点</span></div>
|
||
<div> edges: <span style="color:#3b82f6;">AnchorEdge</span> | <span style="color:#3b82f6;">FlowEdge</span>[]; <span style="color:#64748b;">// 锚点连线 + 流程连线</span></div>
|
||
<div>}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 交互流程 -->
|
||
<div class="section">
|
||
<h3>核心交互流程</h3>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
||
<div style="background:#fff;border:1px solid #e2e8f0;border-radius:6px;padding:12px;">
|
||
<h4 style="margin:0 0 8px 0;color:#3b82f6;">🔗 锚定节点到正文</h4>
|
||
<ol style="margin:0;padding-left:16px;font-size:11px;color:#475569;line-height:1.8;">
|
||
<li>教师选中正文某段文字(或某个字)</li>
|
||
<li>选中后弹出浮动菜单:"关联节点 →"</li>
|
||
<li>从下拉列表选择已有节点,或"新建节点"</li>
|
||
<li>创建 AnchorEdge,source=节点, target=正文节点, targetHandle="anchor:start:end"</li>
|
||
<li>正文对应文字高亮显示(节点颜色)</li>
|
||
<li>连线默认 10% 透明,选中节点时 100%</li>
|
||
</ol>
|
||
</div>
|
||
<div style="background:#fff;border:1px solid #e2e8f0;border-radius:6px;padding:12px;">
|
||
<h4 style="margin:0 0 8px 0;color:#22c55e;">🖱️ 拖动节点到正文</h4>
|
||
<ol style="margin:0;padding-left:16px;font-size:11px;color:#475569;line-height:1.8;">
|
||
<li>教师从右侧节点列表拖动一个节点</li>
|
||
<li>拖动过程中,正文区域高亮可放置区域</li>
|
||
<li>拖到正文某个字前释放</li>
|
||
<li>创建点锚点(point anchor),targetHandle="anchor:pos"</li>
|
||
<li>节点自动定位到正文旁边(左或右空位)</li>
|
||
<li>连线默认 10% 透明,选中时完整显示</li>
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<h3>设计要点</h3>
|
||
<div class="pros-cons">
|
||
<div class="pros">
|
||
<h4>优势</h4>
|
||
<ul>
|
||
<li><strong>保留画布交互</strong>:缩放/平移/拖动节点,与当前备课模块一致</li>
|
||
<li><strong>正文固定居中</strong>:不可拖动,始终是视觉中心</li>
|
||
<li><strong>连线语义化</strong>:anchor 锚点连线 vs flow 流程连线</li>
|
||
<li><strong>透明度策略</strong>:默认 10%,选中时 100%,画布不杂乱</li>
|
||
<li><strong>正文可缩放</strong>:教师可放大正文便于阅读</li>
|
||
</ul>
|
||
</div>
|
||
<div class="cons">
|
||
<h4>技术挑战</h4>
|
||
<ul>
|
||
<li>正文偏移量需基于纯文本(Markdown 渲染后映射)</li>
|
||
<li>正文内容变更后锚点需重新定位</li>
|
||
<li>React Flow 自定义节点需处理正文渲染</li>
|
||
<li>数据结构升级 v2 → v3,需迁移</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|