refactor(grades,attendance,error-book,elective): 组件化重构第 1 批 - 低风险模块

- 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 无新增错误
This commit is contained in:
SpecialX
2026-07-06 19:01:57 +08:00
parent 11ddc8ccbe
commit 19a05091d3
96 changed files with 8641 additions and 1088 deletions

View File

@@ -0,0 +1,273 @@
<h2>课文锚点时间线布局</h2>
<p class="subtitle">课文作为主轴,节点锚定到课文位置,形成教学流程时间线</p>
<div class="mockup">
<div class="mockup-header">备课编辑器 — 课文锚点时间线</div>
<div class="mockup-body" style="padding:0;">
<div style="font-family:monospace;font-size:12px;line-height:1.6;">
<!-- 顶部工具栏 -->
<div style="background:#1e293b;color:#e2e8f0;padding:8px 12px;display:flex;justify-content:space-between;align-items:center;">
<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;">💾 已保存 · 2 分钟前</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="display:grid;grid-template-columns:1fr 320px;gap:0;background:#fff;min-height:480px;">
<!-- 左侧:课文正文区(带锚点 gutter -->
<div style="display:grid;grid-template-columns:32px 1fr;background:#fffbeb;border-right:1px solid #e2e8f0;">
<!-- 锚点 gutter显示锚点标记 -->
<div style="background:#fef3c7;border-right:1px solid #fde68a;position:relative;">
<!-- 锚点标记 -->
<div style="position:absolute;top:60px;left:4px;background:#3b82f6;color:#fff;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:bold;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,0.2);">1</div>
<div style="position:absolute;top:140px;left:4px;background:#f59e0b;color:#fff;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:bold;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,0.2);">2</div>
<div style="position:absolute;top:200px;left:4px;background:#0ea5e9;color:#fff;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:bold;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,0.2);">3</div>
<div style="position:absolute;top:280px;left:4px;background:#ec4899;color:#fff;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:bold;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,0.2);">4</div>
<div style="position:absolute;top:360px;left:4px;background:#22c55e;color:#fff;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:bold;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,0.2);">5</div>
</div>
<!-- 课文内容 -->
<div style="padding:16px 20px;font-size:13px;color:#78350f;line-height:2;position:relative;">
<div style="font-size:16px;font-weight:bold;color:#92400e;text-align:center;margin-bottom:16px;">秋天</div>
<p style="margin:0 0 12px 0;">
<span style="background:#dbeafe;border-bottom:2px solid #3b82f6;padding:1px 2px;">天气凉了,树叶黄了,</span>
一片片叶子从树上落下来。
</p>
<p style="margin:0 0 12px 0;">
<span style="background:#fef3c7;border-bottom:2px solid #f59e0b;padding:1px 2px;">天空那么蓝,那么高。</span>
一群大雁往南飞,
</p>
<p style="margin:0 0 12px 0;">
<span style="background:#e0f2fe;border-bottom:2px solid #0ea5e9;padding:1px 2px;">一会儿排成个"人"字,</span>
<span style="background:#fce7f3;border-bottom:2px solid #ec4899;padding:1px 2px;">一会儿排成个"一"字。</span>
</p>
<p style="margin:0 0 12px 0;">
<span style="background:#dcfce7;border-bottom:2px solid #22c55e;padding:1px 2px;">啊!秋天来了!</span>
</p>
<!-- 拖放提示 -->
<div style="margin-top:24px;padding:8px;border:1px dashed #cbd5e1;border-radius:4px;text-align:center;font-size:10px;color:#94a3b8;">
💡 选中文字可"关联节点",或从右侧拖动节点到课文某字前
</div>
</div>
</div>
<!-- 右侧:节点时间线 -->
<div style="background:#f8fafc;padding:12px;overflow-y:auto;">
<div style="font-size:10px;color:#64748b;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;">教学流程时间线</div>
<!-- 已锚定节点(按课文位置排序) -->
<div style="display:flex;flex-direction:column;gap:6px;">
<!-- 节点 1导入锚定到"天气凉了" -->
<div style="background:#fff;border:1px solid #3b82f6;border-left:3px solid #3b82f6;border-radius:4px;padding:8px;cursor:pointer;">
<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;">
<span style="background:#3b82f6;color:#fff;border-radius:50%;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:bold;">1</span>
<span style="font-size:11px;font-weight:bold;color:#1e3a8a;">💡 导入</span>
<span style="margin-left:auto;font-size:9px;color:#94a3b8;">📍 锚定</span>
</div>
<div style="font-size:10px;color:#64748b;background:#eff6ff;padding:4px 6px;border-radius:3px;">
"天气凉了,树叶黄了" → 提问:你见过秋天的树叶吗?
</div>
</div>
<!-- 节点 2文本研习锚定到"天空那么蓝" -->
<div style="background:#fff;border:1px solid #f59e0b;border-left:3px solid #f59e0b;border-radius:4px;padding:8px;cursor:pointer;">
<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;">
<span style="background:#f59e0b;color:#fff;border-radius:50%;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:bold;">2</span>
<span style="font-size:11px;font-weight:bold;color:#92400e;">📝 文本研习</span>
<span style="margin-left:auto;font-size:9px;color:#94a3b8;">📍 锚定</span>
</div>
<div style="font-size:10px;color:#64748b;background:#fffbeb;padding:4px 6px;border-radius:3px;">
"天空那么蓝,那么高" → 赏析:叠词的运用
</div>
</div>
<!-- 节点 3新授锚定到"人字" -->
<div style="background:#fff;border:1px solid #0ea5e9;border-left:3px solid #0ea5e9;border-radius:4px;padding:8px;cursor:pointer;">
<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;">
<span style="background:#0ea5e9;color:#fff;border-radius:50%;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:bold;">3</span>
<span style="font-size:11px;font-weight:bold;color:#075985;">📚 新授</span>
<span style="margin-left:auto;font-size:9px;color:#94a3b8;">📍 锚定</span>
</div>
<div style="font-size:10px;color:#64748b;background:#f0f9ff;padding:4px 6px;border-radius:3px;">
"一会儿排成个'人'字" → 讲解:大雁南飞
</div>
</div>
<!-- 节点 4练习锚定到"一字" -->
<div style="background:#fff;border:1px solid #ec4899;border-left:3px solid #ec4899;border-radius:4px;padding:8px;cursor:pointer;">
<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;">
<span style="background:#ec4899;color:#fff;border-radius:50%;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:bold;">4</span>
<span style="font-size:11px;font-weight:bold;color:#9f1239;">✏️ 练习</span>
<span style="margin-left:auto;font-size:9px;color:#94a3b8;">📍 锚定</span>
</div>
<div style="font-size:10px;color:#64748b;background:#fdf2f8;padding:4px 6px;border-radius:3px;">
"一会儿排成个'一'字" → 3 道题
</div>
</div>
<!-- 节点 5小结锚定到"秋天来了" -->
<div style="background:#fff;border:1px solid #22c55e;border-left:3px solid #22c55e;border-radius:4px;padding:8px;cursor:pointer;">
<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;">
<span style="background:#22c55e;color:#fff;border-radius:50%;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-weight:bold;">5</span>
<span style="font-size:11px;font-weight:bold;color:#166534;">📌 小结</span>
<span style="margin-left:auto;font-size:9px;color:#94a3b8;">📍 锚定</span>
</div>
<div style="font-size:10px;color:#64748b;background:#f0fdf4;padding:4px 6px;border-radius:3px;">
"啊!秋天来了!" → 总结全文
</div>
</div>
<!-- 分隔线 -->
<div style="border-top:1px dashed #cbd5e1;margin:8px 0;padding-top:8px;">
<div style="font-size:9px;color:#94a3b8;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px;">未锚定节点</div>
</div>
<!-- 未锚定节点 -->
<div style="background:#fff;border:1px dashed #cbd5e1;border-radius:4px;padding:8px;cursor:pointer;opacity:0.7;">
<div style="display:flex;align-items:center;gap:6px;">
<span style="font-size:11px;font-weight:bold;color:#1e3a8a;">🎯 教学目标</span>
<span style="margin-left:auto;font-size:9px;color:#94a3b8;">全局</span>
</div>
</div>
<div style="background:#fff;border:1px dashed #cbd5e1;border-radius:4px;padding:8px;cursor:pointer;opacity:0.7;">
<div style="display:flex;align-items:center;gap:6px;">
<span style="font-size:11px;font-weight:bold;color:#92400e;">⭐ 重难点</span>
<span style="margin-left:auto;font-size:9px;color:#94a3b8;">全局</span>
</div>
</div>
<div style="background:#fff;border:1px dashed #cbd5e1;border-radius:4px;padding:8px;cursor:pointer;opacity:0.7;">
<div style="display:flex;align-items:center;gap:6px;">
<span style="font-size:11px;font-weight:bold;color:#a855f7;">🏠 作业</span>
<span style="margin-left:auto;font-size:9px;color:#94a3b8;">课后</span>
</div>
</div>
<div style="background:#fff;border:1px dashed #cbd5e1;border-radius:4px;padding:8px;cursor:pointer;opacity:0.7;">
<div style="display:flex;align-items:center;gap:6px;">
<span style="font-size:11px;font-weight:bold;color:#6366f1;">📋 板书设计</span>
<span style="margin-left:auto;font-size:9px;color:#94a3b8;">全局</span>
</div>
</div>
<div style="background:#fff;border:1px dashed #cbd5e1;border-radius:4px;padding:8px;cursor:pointer;opacity:0.7;">
<div style="display:flex;align-items:center;gap:6px;">
<span style="font-size:11px;font-weight:bold;color:#64748b;">💭 教学反思</span>
<span style="margin-left:auto;font-size:9px;color:#94a3b8;">课后</span>
</div>
</div>
<!-- 添加节点按钮 -->
<div style="border:1px dashed #94a3b8;border-radius:4px;padding:8px;text-align:center;font-size:10px;color:#64748b;cursor:pointer;margin-top:4px;">
+ 添加节点
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="section" style="margin-top:24px;">
<h3>核心交互:两种锚定方式</h3>
<div class="split">
<div class="mockup">
<div class="mockup-header">方式 1拖动节点到课文某字前</div>
<div class="mockup-body" style="padding:16px;font-family:monospace;font-size:12px;">
<div style="display:flex;gap:12px;">
<div style="background:#f8fafc;padding:8px;border-radius:4px;">
<div style="font-size:9px;color:#94a3b8;margin-bottom:4px;">右侧节点</div>
<div style="background:#fff;border:1px solid #3b82f6;border-left:3px solid #3b82f6;padding:6px;border-radius:3px;cursor:grab;font-size:10px;">💡 导入</div>
</div>
<div style="font-size:18px;color:#94a3b8;align-self:center;"></div>
<div style="background:#fffbeb;padding:8px;border-radius:4px;flex:1;">
<div style="font-size:9px;color:#94a3b8;margin-bottom:4px;">课文</div>
<div style="font-size:11px;color:#78350f;line-height:1.8;">
天气凉了,<span style="background:#dbeafe;border:2px dashed #3b82f6;padding:1px 2px;border-radius:2px;">|</span>树叶黄了,<br>
一片片叶子从树上落下来。
</div>
<div style="font-size:9px;color:#3b82f6;margin-top:4px;">💡 节点锚定到此位置</div>
</div>
</div>
</div>
</div>
<div class="mockup">
<div class="mockup-header">方式 2选中文字 → 关联节点</div>
<div class="mockup-body" style="padding:16px;font-family:monospace;font-size:12px;">
<div style="background:#fffbeb;padding:8px;border-radius:4px;margin-bottom:8px;">
<div style="font-size:9px;color:#94a3b8;margin-bottom:4px;">1. 选中文字</div>
<div style="font-size:11px;color:#78350f;line-height:1.8;">
<span style="background:#fef08a;">天空那么蓝,那么高</span>
</div>
</div>
<div style="font-size:18px;color:#94a3b8;text-align:center;"></div>
<div style="background:#f8fafc;padding:8px;border-radius:4px;margin-top:8px;">
<div style="font-size:9px;color:#94a3b8;margin-bottom:4px;">2. 弹出菜单选择节点</div>
<div style="display:flex;gap:4px;flex-wrap:wrap;">
<span style="background:#fff;border:1px solid #3b82f6;padding:3px 6px;border-radius:3px;font-size:9px;cursor:pointer;">💡 导入</span>
<span style="background:#fff;border:1px solid #f59e0b;padding:3px 6px;border-radius:3px;font-size:9px;cursor:pointer;">📝 文本研习</span>
<span style="background:#fff;border:1px solid #0ea5e9;padding:3px 6px;border-radius:3px;font-size:9px;cursor:pointer;">📚 新授</span>
<span style="background:#fff;border:1px solid #ec4899;padding:3px 6px;border-radius:3px;font-size:9px;cursor:pointer;">✏️ 练习</span>
<span style="background:#fff;border:1px solid #22c55e;padding:3px 6px;border-radius:3px;font-size:9px;cursor:pointer;">📌 小结</span>
<span style="background:#fff;border:1px dashed #94a3b8;padding:3px 6px;border-radius:3px;font-size:9px;cursor:pointer;">+ 新建节点</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="section" style="margin-top:24px;">
<h3>数据模型锚点Anchor</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;">NodeAnchor</span> {</div>
<div>&nbsp;&nbsp;nodeId: <span style="color:#10b981;">string</span>; <span style="color:#64748b;">// 关联的节点 ID</span></div>
<div>&nbsp;&nbsp;type: <span style="color:#10b981;">"point"</span> | <span style="color:#10b981;">"range"</span>; <span style="color:#64748b;">// 点锚点 or 范围锚点</span></div>
<div>&nbsp;&nbsp;start: <span style="color:#10b981;">number</span>; <span style="color:#64748b;">// 课文纯文本偏移量(字符)</span></div>
<div>&nbsp;&nbsp;end?: <span style="color:#10b981;">number</span>; <span style="color:#64748b;">// range 锚点的结束偏移</span></div>
<div>&nbsp;&nbsp;textPreview?: <span style="color:#10b981;">string</span>; <span style="color:#64748b;">// 锚定文字预览(便于回显)</span></div>
<div>}</div>
<br>
<div style="color:#94a3b8;">// LessonPlanDocument 扩展</div>
<div><span style="color:#f59e0b;">interface</span> <span style="color:#3b82f6;">LessonPlanDocument</span> {</div>
<div>&nbsp;&nbsp;version: <span style="color:#10b981;">3</span>; <span style="color:#64748b;">// 升级到 v3</span></div>
<div>&nbsp;&nbsp;nodes: <span style="color:#3b82f6;">LessonPlanNode</span>[];</div>
<div>&nbsp;&nbsp;edges: <span style="color:#3b82f6;">LessonPlanEdge</span>[]; <span style="color:#64748b;">// 保留:节点间连线</span></div>
<div>&nbsp;&nbsp;anchors: <span style="color:#3b82f6;">NodeAnchor</span>[]; <span style="color:#64748b;">// 新增:节点与课文的锚点</span></div>
<div>}</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>:拖动(点锚点)+ 选文字(范围锚点)</li>
<li><strong>保留连线能力</strong>:节点间仍可连线(如"导入→新授"流程线)</li>
<li><strong>未锚定节点</strong>:目标/重难点/作业/板书/反思等全局节点不强制锚定</li>
</ul>
</div>
<div class="cons">
<h4>需要注意</h4>
<ul>
<li>课文偏移量需基于纯文本Markdown 渲染后需映射)</li>
<li>课文内容变更后锚点可能失效(需重新定位或提示)</li>
<li>数据结构升级到 v3需迁移现有 v2 数据</li>
</ul>
</div>
</div>
</div>