Compare commits
139 Commits
0e63c24ed9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
313bae87bf | ||
|
|
e3d132dc1b | ||
|
|
dbb124bf17 | ||
|
|
415dde9122 | ||
|
|
072c0d52b9 | ||
|
|
fb619139e5 | ||
|
|
031e8a8175 | ||
|
|
7f26bb8f9c | ||
|
|
164dcd4c84 | ||
|
|
692e8ef580 | ||
|
|
747344bfe3 | ||
|
|
3f68f3eb09 | ||
|
|
205b463900 | ||
|
|
5d9981fd7d | ||
|
|
7387d70289 | ||
|
|
fc150e1e14 | ||
|
|
ebaf03107d | ||
|
|
783b8f5484 | ||
|
|
524ecade19 | ||
|
|
2adf61faa8 | ||
|
|
d7017f0e30 | ||
|
|
b090a815ae | ||
|
|
224740ad98 | ||
|
|
b333cda8c5 | ||
|
|
4e6d397d8e | ||
|
|
7c1b764b59 | ||
|
|
4122175915 | ||
|
|
811ad11f9f | ||
|
|
12a766d3ee | ||
|
|
0a034945d4 | ||
|
|
fefc65702d | ||
|
|
a75fdcd60d | ||
|
|
2236eb36e7 | ||
|
|
c44f19aefd | ||
|
|
dce2561751 | ||
|
|
a208fcc601 | ||
|
|
3bd3ebc12d | ||
|
|
c2575960eb | ||
|
|
94f098b0f2 | ||
|
|
6104e6a685 | ||
|
|
e76c626779 | ||
|
|
ee10380462 | ||
|
|
fa68ec0b34 | ||
|
|
025d4de50d | ||
|
|
19a05091d3 | ||
|
|
11ddc8ccbe | ||
|
|
80d98e13e4 | ||
|
|
ef2040edf4 | ||
|
|
1a34d1f14e | ||
|
|
9ce8d6d3fd | ||
|
|
22c2e6459d | ||
|
|
8fff820e1d | ||
|
|
7cfd85d0f5 | ||
|
|
05e68a3dad | ||
|
|
0f33484bd2 | ||
|
|
841b130f4c | ||
|
|
27374d1b2c | ||
|
|
6dee6b6299 | ||
|
|
cb5b92160c | ||
|
|
99f15ee37a | ||
|
|
a3dc22cb9e | ||
|
|
9eb02807a4 | ||
|
|
80dd67780c | ||
|
|
220d702b44 | ||
|
|
dd7a49504f | ||
|
|
0f9d8825e7 | ||
|
|
d6227d6e6c | ||
|
|
92913a728f | ||
|
|
e510f191c9 | ||
|
|
2d49f1bad8 | ||
|
|
6baa60b2a1 | ||
|
|
13409e55f1 | ||
|
|
1756ac21a8 | ||
|
|
0058b0b311 | ||
|
|
4174cd61d7 | ||
|
|
44f997bad7 | ||
|
|
fcf89dfb2c | ||
|
|
bc03275262 | ||
|
|
16ffd44161 | ||
|
|
be31da6223 | ||
|
|
8afd7af6dc | ||
|
|
9ec1be1528 | ||
|
|
214ebec976 | ||
|
|
d1ad7a1f75 | ||
|
|
e962b67050 | ||
|
|
0780524c08 | ||
|
|
5ca2a76e3a | ||
|
|
0e4eccec21 | ||
|
|
e762490f19 | ||
|
|
5d306dc686 | ||
|
|
124ed97060 | ||
|
|
e29f30d278 | ||
|
|
6dd3b1fecd | ||
|
|
85121c49a3 | ||
|
|
8ff14fce1c | ||
|
|
24b8ae78c6 | ||
|
|
43f30f4013 | ||
|
|
40cba54ed4 | ||
|
|
a4c9eb02b4 | ||
|
|
05f9b4fd9d | ||
|
|
c7cbc86fd4 | ||
|
|
6fa712ad15 | ||
|
|
4db77b7d1e | ||
|
|
cbed4ef508 | ||
|
|
8583e3e387 | ||
|
|
9b0b7ef619 | ||
|
|
66a60213bf | ||
|
|
1b898fb6cf | ||
|
|
d7b15093f9 | ||
|
|
5c4db2fedc | ||
|
|
ccb2cf05df | ||
|
|
4f00e566df | ||
|
|
ef1be6d04f | ||
|
|
0f4e58d7e1 | ||
|
|
a856ab005d | ||
|
|
7b550d3b69 | ||
|
|
f7ea76cd60 | ||
|
|
8a7d0f69ca | ||
|
|
217b5b48e4 | ||
|
|
8fc798fbcf | ||
|
|
ccf1618b1c | ||
|
|
a1c283e10d | ||
|
|
872d5fb085 | ||
|
|
cbc6e259fa | ||
|
|
4b6cb5f11e | ||
|
|
d2c250a1b3 | ||
|
|
3569d83b8e | ||
|
|
98429e87eb | ||
|
|
b63d116b6c | ||
|
|
eee0145274 | ||
|
|
6ea8ba763b | ||
|
|
25dca843be | ||
|
|
41fe8d8903 | ||
|
|
e7a01eadef | ||
|
|
284c7939b8 | ||
|
|
6a22922ddd | ||
|
|
93eacccdbf | ||
|
|
2dd8c2197c | ||
|
|
56c3f32e2d |
14
.env.example
@@ -13,6 +13,16 @@ AI_API_KEY=""
|
|||||||
AI_BASE_URL=""
|
AI_BASE_URL=""
|
||||||
AI_MODEL=""
|
AI_MODEL=""
|
||||||
|
|
||||||
|
# ===== Redis / 缓存配置(可选) =====
|
||||||
|
# 缓存驱动: memory(默认,单实例 LRU) | redis(分布式,多实例共享)
|
||||||
|
CACHE_DRIVER=memory
|
||||||
|
# 速率限制驱动: memory(默认,单实例) | redis(分布式,多实例共享)
|
||||||
|
RATE_LIMIT_DRIVER=memory
|
||||||
|
# Upstash Redis REST 凭据(仅 CACHE_DRIVER=redis 或 RATE_LIMIT_DRIVER=redis 时必填)
|
||||||
|
# 获取方式: 注册 https://upstash.com → 创建数据库 → 复制 REST URL 和 TOKEN
|
||||||
|
UPSTASH_REDIS_REST_URL=
|
||||||
|
UPSTASH_REDIS_REST_TOKEN=
|
||||||
|
|
||||||
# ===== 灾备配置 =====
|
# ===== 灾备配置 =====
|
||||||
# 异地备份后端类型: s3|oss|nfs|none
|
# 异地备份后端类型: s3|oss|nfs|none
|
||||||
BACKUP_OFFSITE_BACKEND=none
|
BACKUP_OFFSITE_BACKEND=none
|
||||||
@@ -65,3 +75,7 @@ BACKUP_DIR=./backups
|
|||||||
RETENTION_DAYS=30
|
RETENTION_DAYS=30
|
||||||
# 备份校验最小文件大小(字节,默认 1024)
|
# 备份校验最小文件大小(字节,默认 1024)
|
||||||
BACKUP_VERIFY_MIN_SIZE=1024
|
BACKUP_VERIFY_MIN_SIZE=1024
|
||||||
|
|
||||||
|
# ===== 日志配置 =====
|
||||||
|
# 日志级别(debug/info/warn/error),默认 info
|
||||||
|
LOG_LEVEL=info
|
||||||
|
|||||||
@@ -67,6 +67,14 @@ jobs:
|
|||||||
- name: Typecheck
|
- name: Typecheck
|
||||||
run: npm run typecheck
|
run: npm run typecheck
|
||||||
|
|
||||||
|
- name: Unit tests
|
||||||
|
run: npm run test:unit
|
||||||
|
|
||||||
|
- name: Architecture scan
|
||||||
|
run: |
|
||||||
|
npm run arch:scan
|
||||||
|
npm run arch:query -- violations || true
|
||||||
|
|
||||||
- name: Install Playwright Chromium
|
- name: Install Playwright Chromium
|
||||||
run: npx playwright install chromium
|
run: npx playwright install chromium
|
||||||
|
|
||||||
|
|||||||
71
.gitea/workflows/lighthouse.yml
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
name: Lighthouse CI
|
||||||
|
|
||||||
|
# 性能预算回归门槛:每次 PR 与每日凌晨 3 点对关键路由采样断言。
|
||||||
|
# 失败时阻断合并,触发审计报告 docs/architecture/audit/performance-budget-audit-report.md 中基线复核。
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
schedule:
|
||||||
|
- cron: "0 3 * * *" # 每天凌晨 3 点性能采样
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lighthouse:
|
||||||
|
runs-on: CDCD
|
||||||
|
container: dockerreg.eazygame.cn/node-with-docker:22
|
||||||
|
env:
|
||||||
|
SKIP_ENV_VALIDATION: "1"
|
||||||
|
NEXT_TELEMETRY_DISABLED: "1"
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Cache npm dependencies
|
||||||
|
uses: actions/cache@v3
|
||||||
|
id: npm-cache
|
||||||
|
with:
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
|
- name: Configure npm proxy
|
||||||
|
run: |
|
||||||
|
GATEWAY_IP=$(ip route show | grep default | awk '{print $3}')
|
||||||
|
if [ -z "$GATEWAY_IP" ]; then
|
||||||
|
GATEWAY_IP="172.17.0.1"
|
||||||
|
fi
|
||||||
|
PROXY_URL="http://$GATEWAY_IP:7890"
|
||||||
|
npm config set proxy "$PROXY_URL"
|
||||||
|
npm config set https-proxy "$PROXY_URL"
|
||||||
|
echo "http_proxy=$PROXY_URL" >> $GITHUB_ENV
|
||||||
|
echo "https_proxy=$PROXY_URL" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Start production server
|
||||||
|
run: npm run start &
|
||||||
|
env:
|
||||||
|
PORT: "3000"
|
||||||
|
|
||||||
|
- name: Wait for server
|
||||||
|
run: |
|
||||||
|
for i in {1..30}; do
|
||||||
|
curl -s -o /dev/null -w "%{http_code}" http://localhost:3000 | grep -q "200\|307\|308" && break
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Install Lighthouse CI
|
||||||
|
run: npm install -g @lhci/cli@0.13.x
|
||||||
|
|
||||||
|
- name: Run Lighthouse CI
|
||||||
|
run: lhci autorun --config=./lighthouserc.json --collect.url=http://localhost:3000/login || true
|
||||||
|
|
||||||
|
- name: Assert performance budgets
|
||||||
|
run: lhci assert --config=./lighthouserc.json
|
||||||
1
.husky/commit-msg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
npx --no-install commitlint --edit $1
|
||||||
1
.husky/pre-commit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
npx lint-staged
|
||||||
@@ -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> nodeId: <span style="color:#10b981;">string</span>; <span style="color:#64748b;">// 关联的节点 ID</span></div>
|
||||||
|
<div> type: <span style="color:#10b981;">"point"</span> | <span style="color:#10b981;">"range"</span>; <span style="color:#64748b;">// 点锚点 or 范围锚点</span></div>
|
||||||
|
<div> start: <span style="color:#10b981;">number</span>; <span style="color:#64748b;">// 课文纯文本偏移量(字符)</span></div>
|
||||||
|
<div> end?: <span style="color:#10b981;">number</span>; <span style="color:#64748b;">// range 锚点的结束偏移</span></div>
|
||||||
|
<div> 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> version: <span style="color:#10b981;">3</span>; <span style="color:#64748b;">// 升级到 v3</span></div>
|
||||||
|
<div> nodes: <span style="color:#3b82f6;">LessonPlanNode</span>[];</div>
|
||||||
|
<div> edges: <span style="color:#3b82f6;">LessonPlanEdge</span>[]; <span style="color:#64748b;">// 保留:节点间连线</span></div>
|
||||||
|
<div> 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>
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
<h2>备课模块布局方案对比</h2>
|
||||||
|
<p class="subtitle">3 种布局方案 — 课文固定中央,教学节点围绕组织</p>
|
||||||
|
|
||||||
|
<div class="cards" data-multiselect>
|
||||||
|
<!-- 方案 A -->
|
||||||
|
<div class="card" data-choice="a" onclick="toggleSelect(this)">
|
||||||
|
<div class="card-image" style="padding:12px;background:#f8fafc;">
|
||||||
|
<div style="font-family:monospace;font-size:11px;line-height:1.4;">
|
||||||
|
<div style="border:1px solid #cbd5e1;background:#e2e8f0;padding:4px 8px;border-radius:4px 4px 0 0;display:flex;justify-content:space-between;">
|
||||||
|
<span>📖 秋天(第一课时)</span>
|
||||||
|
<span>💾 已保存</span>
|
||||||
|
</div>
|
||||||
|
<div style="display:grid;grid-template-columns:200px 1fr 200px;gap:4px;padding:8px;background:#fff;border:1px solid #cbd5e1;border-top:none;border-radius:0 0 4px 4px;min-height:280px;">
|
||||||
|
<!-- 左侧:课前 -->
|
||||||
|
<div style="display:flex;flex-direction:column;gap:4px;">
|
||||||
|
<div style="font-size:9px;color:#64748b;text-align:center;">课前</div>
|
||||||
|
<div style="background:#dbeafe;border:1px solid #3b82f6;padding:4px;border-radius:3px;font-size:10px;">🎯 教学目标</div>
|
||||||
|
<div style="background:#fef3c7;border:1px solid #f59e0b;padding:4px;border-radius:3px;font-size:10px;">⭐ 重难点</div>
|
||||||
|
<div style="background:#e0f2fe;border:1px solid #0ea5e9;padding:4px;border-radius:3px;font-size:10px;">💡 导入</div>
|
||||||
|
<div style="background:#fce7f3;border:1px solid #ec4899;padding:4px;border-radius:3px;font-size:10px;">📝 文本研习</div>
|
||||||
|
</div>
|
||||||
|
<!-- 中央:课文 -->
|
||||||
|
<div style="background:#fffbeb;border:2px solid #f59e0b;padding:8px;border-radius:4px;display:flex;flex-direction:column;">
|
||||||
|
<div style="font-size:10px;color:#92400e;font-weight:bold;margin-bottom:4px;">📜 课文正文</div>
|
||||||
|
<div style="font-size:9px;color:#78350f;line-height:1.5;flex:1;">
|
||||||
|
天气凉了,树叶黄了,<br>
|
||||||
|
一片片叶子从树上落下来。<br>
|
||||||
|
<span style="background:#fef08a;">天空那么蓝,那么高</span>。<br>
|
||||||
|
一群大雁往南飞,<br>
|
||||||
|
一会儿排成个"人"字,<br>
|
||||||
|
一会儿排成个"一"字。<br>
|
||||||
|
<span style="background:#bbf7d0;">啊!秋天来了!</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:8px;color:#92400e;margin-top:4px;">💡 选中文字可添加批注</div>
|
||||||
|
</div>
|
||||||
|
<!-- 右侧:课中/课后 -->
|
||||||
|
<div style="display:flex;flex-direction:column;gap:4px;">
|
||||||
|
<div style="font-size:9px;color:#64748b;text-align:center;">课中</div>
|
||||||
|
<div style="background:#dcfce7;border:1px solid #22c55e;padding:4px;border-radius:3px;font-size:10px;">📚 新授</div>
|
||||||
|
<div style="background:#ede9fe;border:1px solid #8b5cf6;padding:4px;border-radius:3px;font-size:10px;">✏️ 练习</div>
|
||||||
|
<div style="background:#fee2e2;border:1px solid #ef4444;padding:4px;border-radius:3px;font-size:10px;">📌 小结</div>
|
||||||
|
<div style="font-size:9px;color:#64748b;text-align:center;margin-top:4px;">课后</div>
|
||||||
|
<div style="background:#f3e8ff;border:1px solid #a855f7;padding:4px;border-radius:3px;font-size:10px;">🏠 作业</div>
|
||||||
|
<div style="background:#e0e7ff;border:1px solid #6366f1;padding:4px;border-radius:3px;font-size:10px;">📋 板书设计</div>
|
||||||
|
<div style="background:#f1f5f9;border:1px solid #64748b;padding:4px;border-radius:3px;font-size:10px;">💭 教学反思</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h3>A. 三栏布局(课前/课文/课后)</h3>
|
||||||
|
<p>课文固定中央(琥珀色边框),左侧"课前"节点(目标/重难点/导入/文本研习),右侧"课中+课后"节点(新授/练习/小结/作业/板书/反思)。节点按教学流程纵向排列。点击节点在右侧抽屉编辑。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 方案 B -->
|
||||||
|
<div class="card" data-choice="b" onclick="toggleSelect(this)">
|
||||||
|
<div class="card-image" style="padding:12px;background:#f8fafc;">
|
||||||
|
<div style="font-family:monospace;font-size:11px;line-height:1.4;">
|
||||||
|
<div style="border:1px solid #cbd5e1;background:#e2e8f0;padding:4px 8px;border-radius:4px 4px 0 0;display:flex;justify-content:space-between;">
|
||||||
|
<span>📖 秋天(第一课时)</span>
|
||||||
|
<span>💾 已保存</span>
|
||||||
|
</div>
|
||||||
|
<div style="padding:8px;background:#fff;border:1px solid #cbd5e1;border-top:none;border-radius:0 0 4px 4px;min-height:280px;">
|
||||||
|
<!-- 顶部:目标/重难点 -->
|
||||||
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-bottom:4px;">
|
||||||
|
<div style="background:#dbeafe;border:1px solid #3b82f6;padding:4px;border-radius:3px;font-size:10px;text-align:center;">🎯 教学目标</div>
|
||||||
|
<div style="background:#fef3c7;border:1px solid #f59e0b;padding:4px;border-radius:3px;font-size:10px;text-align:center;">⭐ 重难点</div>
|
||||||
|
</div>
|
||||||
|
<!-- 中央:课文 + 左右两侧节点 -->
|
||||||
|
<div style="display:grid;grid-template-columns:120px 1fr 120px;gap:4px;margin-bottom:4px;">
|
||||||
|
<div style="display:flex;flex-direction:column;gap:4px;">
|
||||||
|
<div style="background:#e0f2fe;border:1px solid #0ea5e9;padding:4px;border-radius:3px;font-size:9px;text-align:center;">💡 导入</div>
|
||||||
|
<div style="background:#fce7f3;border:1px solid #ec4899;padding:4px;border-radius:3px;font-size:9px;text-align:center;">📝 文本研习</div>
|
||||||
|
</div>
|
||||||
|
<div style="background:#fffbeb;border:2px solid #f59e0b;padding:8px;border-radius:4px;text-align:center;">
|
||||||
|
<div style="font-size:10px;color:#92400e;font-weight:bold;">📜 课文正文</div>
|
||||||
|
<div style="font-size:9px;color:#78350f;margin-top:4px;">天气凉了,树叶黄了...<br>天空那么蓝...<br>一群大雁往南飞...</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;flex-direction:column;gap:4px;">
|
||||||
|
<div style="background:#dcfce7;border:1px solid #22c55e;padding:4px;border-radius:3px;font-size:9px;text-align:center;">📚 新授</div>
|
||||||
|
<div style="background:#ede9fe;border:1px solid #8b5cf6;padding:4px;border-radius:3px;font-size:9px;text-align:center;">✏️ 练习</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 底部:小结/作业/板书/反思 -->
|
||||||
|
<div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:4px;">
|
||||||
|
<div style="background:#fee2e2;border:1px solid #ef4444;padding:4px;border-radius:3px;font-size:9px;text-align:center;">📌 小结</div>
|
||||||
|
<div style="background:#f3e8ff;border:1px solid #a855f7;padding:4px;border-radius:3px;font-size:9px;text-align:center;">🏠 作业</div>
|
||||||
|
<div style="background:#e0e7ff;border:1px solid #6366f1;padding:4px;border-radius:3px;font-size:9px;text-align:center;">📋 板书</div>
|
||||||
|
<div style="background:#f1f5f9;border:1px solid #64748b;padding:4px;border-radius:3px;font-size:9px;text-align:center;">💭 反思</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h3>B. 上下分区布局</h3>
|
||||||
|
<p>顶部目标/重难点横排,中央课文 + 左右导入/文本研习/新授/练习,底部小结/作业/板书/反思横排。按"目标→导入→课文→新授→小结"的阅读顺序自然流动。视觉层次更清晰。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 方案 C -->
|
||||||
|
<div class="card" data-choice="c" onclick="toggleSelect(this)">
|
||||||
|
<div class="card-image" style="padding:12px;background:#f8fafc;">
|
||||||
|
<div style="font-family:monospace;font-size:11px;line-height:1.4;">
|
||||||
|
<div style="border:1px solid #cbd5e1;background:#e2e8f0;padding:4px 8px;border-radius:4px 4px 0 0;display:flex;justify-content:space-between;">
|
||||||
|
<span>📖 秋天(第一课时)</span>
|
||||||
|
<span>💾 已保存</span>
|
||||||
|
</div>
|
||||||
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;padding:8px;background:#fff;border:1px solid #cbd5e1;border-top:none;border-radius:0 0 4px 4px;min-height:280px;">
|
||||||
|
<!-- 左侧:课文 -->
|
||||||
|
<div style="display:flex;flex-direction:column;gap:4px;">
|
||||||
|
<div style="background:#fffbeb;border:2px solid #f59e0b;padding:8px;border-radius:4px;flex:1;">
|
||||||
|
<div style="font-size:10px;color:#92400e;font-weight:bold;">📜 课文正文</div>
|
||||||
|
<div style="font-size:9px;color:#78350f;margin-top:4px;line-height:1.5;">
|
||||||
|
天气凉了,树叶黄了,<br>
|
||||||
|
一片片叶子从树上落下来。<br>
|
||||||
|
<span style="background:#fef08a;">天空那么蓝,那么高</span>。<br>
|
||||||
|
一群大雁往南飞...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="background:#fce7f3;border:1px solid #ec4899;padding:4px;border-radius:3px;font-size:10px;text-align:center;">📝 文本研习(批注)</div>
|
||||||
|
</div>
|
||||||
|
<!-- 右侧:教学流程时间线 -->
|
||||||
|
<div style="display:flex;flex-direction:column;gap:3px;">
|
||||||
|
<div style="font-size:9px;color:#64748b;text-align:center;">教学流程</div>
|
||||||
|
<div style="background:#dbeafe;border:1px solid #3b82f6;padding:3px 6px;border-radius:3px;font-size:9px;display:flex;align-items:center;gap: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;">1</span>
|
||||||
|
🎯 教学目标
|
||||||
|
</div>
|
||||||
|
<div style="background:#fef3c7;border:1px solid #f59e0b;padding:3px 6px;border-radius:3px;font-size:9px;display:flex;align-items:center;gap: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;">2</span>
|
||||||
|
⭐ 重难点
|
||||||
|
</div>
|
||||||
|
<div style="background:#e0f2fe;border:1px solid #0ea5e9;padding:3px 6px;border-radius:3px;font-size:9px;display:flex;align-items:center;gap: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;">3</span>
|
||||||
|
💡 导入
|
||||||
|
</div>
|
||||||
|
<div style="background:#dcfce7;border:1px solid #22c55e;padding:3px 6px;border-radius:3px;font-size:9px;display:flex;align-items:center;gap: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;">4</span>
|
||||||
|
📚 新授
|
||||||
|
</div>
|
||||||
|
<div style="background:#ede9fe;border:1px solid #8b5cf6;padding:3px 6px;border-radius:3px;font-size:9px;display:flex;align-items:center;gap:4px;">
|
||||||
|
<span style="background:#8b5cf6;color:#fff;border-radius:50%;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;font-size:8px;">5</span>
|
||||||
|
✏️ 练习
|
||||||
|
</div>
|
||||||
|
<div style="background:#fee2e2;border:1px solid #ef4444;padding:3px 6px;border-radius:3px;font-size:9px;display:flex;align-items:center;gap:4px;">
|
||||||
|
<span style="background:#ef4444;color:#fff;border-radius:50%;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;font-size:8px;">6</span>
|
||||||
|
📌 小结
|
||||||
|
</div>
|
||||||
|
<div style="background:#f3e8ff;border:1px solid #a855f7;padding:3px 6px;border-radius:3px;font-size:9px;display:flex;align-items:center;gap:4px;">
|
||||||
|
<span style="background:#a855f7;color:#fff;border-radius:50%;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;font-size:8px;">7</span>
|
||||||
|
🏠 作业
|
||||||
|
</div>
|
||||||
|
<div style="background:#e0e7ff;border:1px solid #6366f1;padding:3px 6px;border-radius:3px;font-size:9px;display:flex;align-items:center;gap:4px;">
|
||||||
|
<span style="background:#6366f1;color:#fff;border-radius:50%;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;font-size:8px;">8</span>
|
||||||
|
📋 板书
|
||||||
|
</div>
|
||||||
|
<div style="background:#f1f5f9;border:1px solid #64748b;padding:3px 6px;border-radius:3px;font-size:9px;display:flex;align-items:center;gap:4px;">
|
||||||
|
<span style="background:#64748b;color:#fff;border-radius:50%;width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;font-size:8px;">9</span>
|
||||||
|
💭 反思
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h3>C. 左课文 + 右时间线</h3>
|
||||||
|
<p>左侧课文正文(固定)+ 文本研习批注,右侧教学流程时间线(编号 1-9 按顺序)。点击时间线节点展开编辑抽屉。最贴近传统教案本格式,结构清晰。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section" style="margin-top:24px;">
|
||||||
|
<h3>三种方案的核心差异</h3>
|
||||||
|
<div class="pros-cons">
|
||||||
|
<div class="pros">
|
||||||
|
<h4>方案 A 三栏</h4>
|
||||||
|
<ul>
|
||||||
|
<li>课文始终居中可见</li>
|
||||||
|
<li>课前/课后分区直观</li>
|
||||||
|
<li>节点可拖动微调位置</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="cons">
|
||||||
|
<h4>方案 A 三栏</h4>
|
||||||
|
<ul>
|
||||||
|
<li>三栏可能拥挤(小屏)</li>
|
||||||
|
<li>教学流程顺序不够明显</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pros-cons">
|
||||||
|
<div class="pros">
|
||||||
|
<h4>方案 B 上下分区</h4>
|
||||||
|
<ul>
|
||||||
|
<li>视觉层次最清晰</li>
|
||||||
|
<li>阅读顺序自然(上→下)</li>
|
||||||
|
<li>课文居中突出</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="cons">
|
||||||
|
<h4>方案 B 上下分区</h4>
|
||||||
|
<ul>
|
||||||
|
<li>节点位置较固定</li>
|
||||||
|
<li>纵向空间需求大</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pros-cons">
|
||||||
|
<div class="pros">
|
||||||
|
<h4>方案 C 左课文+右时间线</h4>
|
||||||
|
<ul>
|
||||||
|
<li>最接近传统教案</li>
|
||||||
|
<li>教学流程顺序最明确</li>
|
||||||
|
<li>课文阅读体验最佳</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="cons">
|
||||||
|
<h4>方案 C 左课文+右时间线</h4>
|
||||||
|
<ul>
|
||||||
|
<li>节点画布感弱(更像列表)</li>
|
||||||
|
<li>失去节点图连线能力</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
<h2>正文占位符标记布局</h2>
|
||||||
|
<p class="subtitle">正文中嵌入占位符标记(特殊符号),默认接近透明,选中节点时完整显示</p>
|
||||||
|
|
||||||
|
<div class="mockup">
|
||||||
|
<div class="mockup-header">备课编辑器 — 默认状态(占位符 10% 透明度)</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:#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">
|
||||||
|
<path d="M 130 120 Q 200 140 290 175" stroke="#3b82f6" stroke-width="2" fill="none" stroke-dasharray="4 4"/>
|
||||||
|
<path d="M 130 220 Q 200 230 290 215" stroke="#f59e0b" stroke-width="2" fill="none" stroke-dasharray="4 4"/>
|
||||||
|
<path d="M 670 120 Q 600 150 510 195" stroke="#0ea5e9" stroke-width="2" fill="none" stroke-dasharray="4 4"/>
|
||||||
|
<path d="M 670 220 Q 600 240 510 235" stroke="#ec4899" stroke-width="2" fill="none" stroke-dasharray="4 4"/>
|
||||||
|
<path d="M 670 340 Q 600 320 510 275" stroke="#22c55e" stroke-width="2" fill="none" stroke-dasharray="4 4"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<!-- 中央:正文容器(不可移动) -->
|
||||||
|
<div style="position:absolute;left:280px;top:60px;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:2;">
|
||||||
|
<div style="text-align:center;font-weight:bold;margin-bottom:6px;">秋天</div>
|
||||||
|
<p style="margin:0 0 6px 0;">
|
||||||
|
<!-- 占位符 1(导入节点)- 默认 10% 透明度 -->
|
||||||
|
<span style="display:inline-block;background:#3b82f6;color:#fff;border-radius:3px;padding:0 4px;font-size:9px;font-weight:bold;opacity:0.1;margin-right:2px;">①</span>天气凉了,树叶黄了,
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 6px 0;">
|
||||||
|
<!-- 占位符 2(文本研习)- 默认 10% 透明度 -->
|
||||||
|
<span style="display:inline-block;background:#f59e0b;color:#fff;border-radius:3px;padding:0 4px;font-size:9px;font-weight:bold;opacity:0.1;margin-right:2px;">②</span>天空那么蓝,那么高。
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 6px 0;">
|
||||||
|
一群大雁往南飞,
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 6px 0;">
|
||||||
|
一会儿排成个"人"字<span style="display:inline-block;background:#0ea5e9;color:#fff;border-radius:3px;padding:0 4px;font-size:9px;font-weight:bold;opacity:0.1;margin-left:2px;">③</span>,
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 6px 0;">
|
||||||
|
一会儿排成个"一"字<span style="display:inline-block;background:#ec4899;color:#fff;border-radius:3px;padding:0 4px;font-size:9px;font-weight:bold;opacity:0.1;margin-left:2px;">④</span>。
|
||||||
|
</p>
|
||||||
|
<p style="margin:0;">
|
||||||
|
<span style="display:inline-block;background:#22c55e;color:#fff;border-radius:3px;padding:0 4px;font-size:9px;font-weight:bold;opacity:0.1;margin-right:2px;">⑤</span>啊!秋天来了!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 左侧节点 -->
|
||||||
|
<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);">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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);">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<!-- 右侧节点 -->
|
||||||
|
<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);">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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);">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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);">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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% · 选中节点时 100% 显示
|
||||||
|
</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:#fffbeb;">
|
||||||
|
<div style="font-size:14px;color:#78350f;line-height:2;font-family:serif;">
|
||||||
|
<span style="display:inline-block;background:#3b82f6;color:#fff;border-radius:3px;padding:0 5px;font-size:10px;font-weight:bold;opacity:0.1;margin-right:3px;">①</span>天气凉了,树叶黄了,
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:12px;font-size:10px;color:#94a3b8;text-align:center;">
|
||||||
|
占位符几乎不可见 · 画布干净
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mockup">
|
||||||
|
<div class="mockup-header">选中"导入"节点 — 占位符 ① 100% + 连线显示</div>
|
||||||
|
<div class="mockup-body" style="padding:16px;background:#fffbeb;">
|
||||||
|
<div style="font-size:14px;color:#78350f;line-height:2;font-family:serif;">
|
||||||
|
<span style="display:inline-block;background:#3b82f6;color:#fff;border-radius:3px;padding:0 5px;font-size:10px;font-weight:bold;opacity:1;margin-right:3px;box-shadow:0 0 0 2px #3b82f633;">①</span>天气凉了,树叶黄了,
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:12px;font-size:10px;color:#3b82f6;text-align:center;">
|
||||||
|
占位符完整显示 · 连线高亮 · 锚定位置清晰
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 占位符样式选项 -->
|
||||||
|
<div class="section">
|
||||||
|
<h3>占位符样式选项</h3>
|
||||||
|
<p class="subtitle">选择占位符在正文中的视觉呈现方式</p>
|
||||||
|
<div class="cards" data-multiselect>
|
||||||
|
<div class="card" data-choice="number" onclick="toggleSelect(this)">
|
||||||
|
<div class="card-image" style="padding:20px;background:#fffbeb;text-align:center;">
|
||||||
|
<div style="font-size:16px;color:#78350f;font-family:serif;line-height:2;">
|
||||||
|
<span style="display:inline-block;background:#3b82f6;color:#fff;border-radius:3px;padding:0 6px;font-size:11px;font-weight:bold;">①</span>天气凉了
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h3>数字圆圈</h3>
|
||||||
|
<p>①②③④⑤ — 与节点编号对应,简洁清晰</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" data-choice="icon" onclick="toggleSelect(this)">
|
||||||
|
<div class="card-image" style="padding:20px;background:#fffbeb;text-align:center;">
|
||||||
|
<div style="font-size:16px;color:#78350f;font-family:serif;line-height:2;">
|
||||||
|
<span style="display:inline-block;background:#3b82f6;color:#fff;border-radius:3px;padding:1px 5px;font-size:11px;">💡</span>天气凉了
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h3>节点图标</h3>
|
||||||
|
<p>💡📝📚✏️📌 — 与节点类型图标一致,直观</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" data-choice="dot" onclick="toggleSelect(this)">
|
||||||
|
<div class="card-image" style="padding:20px;background:#fffbeb;text-align:center;">
|
||||||
|
<div style="font-size:16px;color:#78350f;font-family:serif;line-height:2;">
|
||||||
|
<span style="display:inline-block;background:#3b82f6;color:#fff;border-radius:50%;width:10px;height:10px;font-size:8px;text-align:center;line-height:10px;">●</span>天气凉了
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h3>彩色圆点</h3>
|
||||||
|
<p>● — 极简,颜色对应节点,不干扰阅读</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card" data-choice="bracket" onclick="toggleSelect(this)">
|
||||||
|
<div class="card-image" style="padding:20px;background:#fffbeb;text-align:center;">
|
||||||
|
<div style="font-size:16px;color:#78350f;font-family:serif;line-height:2;">
|
||||||
|
<span style="color:#3b82f6;font-weight:bold;">【1】</span>天气凉了
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h3>方括号编号</h3>
|
||||||
|
<p>【1】【2】【3】— 类似脚注标记,学术感</p>
|
||||||
|
</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;">ContentPlaceholder</span> {</div>
|
||||||
|
<div> id: <span style="color:#10b981;">string</span>; <span style="color:#64748b;">// 占位符 ID</span></div>
|
||||||
|
<div> nodeId: <span style="color:#10b981;">string</span>; <span style="color:#64748b;">// 关联的节点 ID</span></div>
|
||||||
|
<div> offset: <span style="color:#10b981;">number</span>; <span style="color:#64748b;">// 在正文纯文本中的字符偏移量</span></div>
|
||||||
|
<div> label: <span style="color:#10b981;">string</span>; <span style="color:#64748b;">// 显示的标记("①" / "💡" / "●" / "【1】")</span></div>
|
||||||
|
<div> color: <span style="color:#10b981;">string</span>; <span style="color:#64748b;">// 节点颜色(用于占位符背景)</span></div>
|
||||||
|
<div>}</div>
|
||||||
|
<br>
|
||||||
|
<div style="color:#94a3b8;">// 正文渲染时注入占位符</div>
|
||||||
|
<div><span style="color:#f59e0b;">function</span> <span style="color:#3b82f6;">renderContentWithPlaceholders</span>(</div>
|
||||||
|
<div> content: <span style="color:#10b981;">string</span>, <span style="color:#64748b;">// Markdown 原文</span></div>
|
||||||
|
<div> placeholders: <span style="color:#3b82f6;">ContentPlaceholder</span>[]</div>
|
||||||
|
<div>): <span style="color:#10b981;">string</span> {</div>
|
||||||
|
<div> <span style="color:#64748b;">// 按 offset 排序,在对应位置插入占位符标记</span></div>
|
||||||
|
<div> <span style="color:#64748b;">// 渲染为 <span class="placeholder" data-node-id="xxx">①</span></span></div>
|
||||||
|
<div>}</div>
|
||||||
|
<br>
|
||||||
|
<div style="color:#94a3b8;">// CSS 透明度控制</div>
|
||||||
|
<div>.placeholder { <span style="color:#10b981;">opacity</span>: <span style="color:#f59e0b;">0.1</span>; <span style="color:#10b981;">transition</span>: <span style="color:#f59e0b;">opacity 0.2s</span>; }</div>
|
||||||
|
<div>.placeholder.active { <span style="color:#10b981;">opacity</span>: <span style="color:#f59e0b;">1</span>; }</div>
|
||||||
|
<div>.placeholder:hover { <span style="color:#10b981;">opacity</span>: <span style="color:#f59e0b;">0.6</span>; }</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>选择节点类型或已有节点</li>
|
||||||
|
<li>在正文对应位置插入占位符标记</li>
|
||||||
|
<li>创建 AnchorEdge 连线</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>该节点对应的占位符 opacity 从 0.1 → 1</li>
|
||||||
|
<li>连线从 10% → 100% 显示</li>
|
||||||
|
<li>占位符添加 active 样式(边框/阴影)</li>
|
||||||
|
<li>其他占位符保持 10% 透明度</li>
|
||||||
|
<li>点击空白处恢复默认状态</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,376 @@
|
|||||||
|
<h2>两种锚定方式的视觉规则</h2>
|
||||||
|
<p class="subtitle">范围锚定(选文本)vs 点锚定(插入占位符)— 默认/选中状态对比</p>
|
||||||
|
|
||||||
|
<!-- 规则总览 -->
|
||||||
|
<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.8;">
|
||||||
|
<div style="color:#94a3b8;">// 两种锚定方式</div>
|
||||||
|
<div><span style="color:#f59e0b;">type</span> <span style="color:#3b82f6;">AnchorType</span> = <span style="color:#10b981;">"range"</span> | <span style="color:#10b981;">"point"</span>;</div>
|
||||||
|
<br>
|
||||||
|
<div style="color:#94a3b8;">// 范围锚定(选一段文本关联节点)</div>
|
||||||
|
<div><span style="color:#94a3b8;">// 文本背景色 = 节点颜色</span></div>
|
||||||
|
<div>.range-anchor {</div>
|
||||||
|
<div> <span style="color:#10b981;">background-color</span>: <span style="color:#f59e0b;">var(--node-color)</span>; <span style="color:#64748b;">// 节点颜色</span></div>
|
||||||
|
<div> <span style="color:#10b981;">opacity</span>: <span style="color:#f59e0b;">0</span>; <span style="color:#64748b;">// 默认完全透明(和正常文本一样)</span></div>
|
||||||
|
<div>}</div>
|
||||||
|
<div>.range-anchor.active {</div>
|
||||||
|
<div> <span style="color:#10b981;">opacity</span>: <span style="color:#f59e0b;">0.3</span>; <span style="color:#64748b;">// 选中节点时显示背景色</span></div>
|
||||||
|
<div>}</div>
|
||||||
|
<br>
|
||||||
|
<div style="color:#94a3b8;">// 点锚定(在文本中插入占位符)</div>
|
||||||
|
<div>.point-anchor {</div>
|
||||||
|
<div> <span style="color:#10b981;">opacity</span>: <span style="color:#f59e0b;">0.3</span>; <span style="color:#64748b;">// 默认半透明</span></div>
|
||||||
|
<div>}</div>
|
||||||
|
<div>.point-anchor.active {</div>
|
||||||
|
<div> <span style="color:#10b981;">opacity</span>: <span style="color:#f59e0b;">1</span>; <span style="color:#64748b;">// 选中节点时不透明</span></div>
|
||||||
|
<div>}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 完整画布:默认状态 -->
|
||||||
|
<div class="section">
|
||||||
|
<h3>完整画布 — 默认状态</h3>
|
||||||
|
<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:480px;">
|
||||||
|
|
||||||
|
<!-- 画布 -->
|
||||||
|
<div style="position:relative;width:100%;height:480px;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 480">
|
||||||
|
<path d="M 130 100 Q 200 120 290 155" stroke="#3b82f6" stroke-width="2" fill="none" stroke-dasharray="4 4"/>
|
||||||
|
<path d="M 130 200 Q 200 210 290 195" stroke="#f59e0b" stroke-width="2" fill="none" stroke-dasharray="4 4"/>
|
||||||
|
<path d="M 670 100 Q 600 130 510 175" stroke="#0ea5e9" stroke-width="2" fill="none" stroke-dasharray="4 4"/>
|
||||||
|
<path d="M 670 200 Q 600 220 510 215" stroke="#ec4899" stroke-width="2" fill="none" stroke-dasharray="4 4"/>
|
||||||
|
<path d="M 670 320 Q 600 300 510 255" stroke="#22c55e" stroke-width="2" fill="none" stroke-dasharray="4 4"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<!-- 中央:正文容器 -->
|
||||||
|
<div style="position:absolute;left:280px;top:40px;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:2.2;">
|
||||||
|
<div style="text-align:center;font-weight:bold;margin-bottom:6px;">秋天</div>
|
||||||
|
<p style="margin:0 0 6px 0;">
|
||||||
|
<!-- 范围锚定:默认 opacity:0(完全透明,和正常文本一样) -->
|
||||||
|
<span style="background:#3b82f6;opacity:0;color:#78350f;">天气凉了</span>,树叶黄了,
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 6px 0;">
|
||||||
|
<!-- 点锚定:默认 opacity:0.3(半透明) -->
|
||||||
|
<span style="display:inline-block;background:#f59e0b;color:#fff;border-radius:3px;padding:0 4px;font-size:9px;font-weight:bold;opacity:0.3;margin-right:2px;">②</span>天空那么蓝,那么高。
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 6px 0;">
|
||||||
|
一群大雁往南飞,
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 6px 0;">
|
||||||
|
<!-- 范围锚定:默认 opacity:0 -->
|
||||||
|
一会儿排成个<span style="background:#0ea5e9;opacity:0;color:#78350f;">"人"字</span>
|
||||||
|
<!-- 点锚定:默认 opacity:0.3 -->
|
||||||
|
<span style="display:inline-block;background:#ec4899;color:#fff;border-radius:3px;padding:0 4px;font-size:9px;font-weight:bold;opacity:0.3;margin-left:2px;">④</span>,
|
||||||
|
</p>
|
||||||
|
<p style="margin:0;">
|
||||||
|
<!-- 点锚定:默认 opacity:0.3 -->
|
||||||
|
<span style="display:inline-block;background:#22c55e;color:#fff;border-radius:3px;padding:0 4px;font-size:9px;font-weight:bold;opacity:0.3;margin-right:2px;">⑤</span>啊!秋天来了!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 左侧节点 -->
|
||||||
|
<div style="position:absolute;left:30px;top:70px;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);">
|
||||||
|
<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>
|
||||||
|
<span style="margin-left:auto;font-size:8px;color:#94a3b8;background:#eff6ff;padding:1px 4px;border-radius:2px;">范围</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:9px;color:#64748b;">提问:你见过秋天的树叶吗?</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="position:absolute;left:30px;top:180px;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);">
|
||||||
|
<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>
|
||||||
|
<span style="margin-left:auto;font-size:8px;color:#94a3b8;background:#fffbeb;padding:1px 4px;border-radius:2px;">点</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:9px;color:#64748b;">赏析:叠词的运用</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧节点 -->
|
||||||
|
<div style="position:absolute;left:630px;top:70px;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);">
|
||||||
|
<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>
|
||||||
|
<span style="margin-left:auto;font-size:8px;color:#94a3b8;background:#f0f9ff;padding:1px 4px;border-radius:2px;">范围</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:9px;color:#64748b;">讲解:大雁南飞</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="position:absolute;left:630px;top:180px;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);">
|
||||||
|
<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>
|
||||||
|
<span style="margin-left:auto;font-size:8px;color:#94a3b8;background:#fdf2f8;padding:1px 4px;border-radius:2px;">点</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:9px;color:#64748b;">3 道题</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="position:absolute;left:630px;top:300px;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);">
|
||||||
|
<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>
|
||||||
|
<span style="margin-left:auto;font-size:8px;color:#94a3b8;background:#f0fdf4;padding:1px 4px;border-radius:2px;">点</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:9px;color:#64748b;">总结全文</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="position:absolute;top:12px;right:12px;background:#fff;border:1px solid #e2e8f0;border-radius:4px;padding:6px 10px;font-size:9px;color:#64748b;">
|
||||||
|
<div>🔵 范围锚定:默认 opacity:0</div>
|
||||||
|
<div>🔴 点锚定:默认 opacity:0.3</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 选中节点 1(范围锚定)的状态 -->
|
||||||
|
<div class="section">
|
||||||
|
<h3>选中"导入"节点(范围锚定)— 文本背景显示</h3>
|
||||||
|
<div class="mockup">
|
||||||
|
<div class="mockup-header">选中节点 1 — "天气凉了"背景色显示</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:320px;">
|
||||||
|
<div style="position:relative;width:100%;height:320px;background:#f1f5f9;background-image:radial-gradient(#cbd5e1 1px, transparent 1px);background-size:20px 20px;overflow:hidden;">
|
||||||
|
|
||||||
|
<!-- SVG 连线层(选中节点的连线 100% 显示) -->
|
||||||
|
<svg style="position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;" viewBox="0 0 800 320">
|
||||||
|
<!-- 选中节点的连线 100% 显示 -->
|
||||||
|
<path d="M 130 80 Q 200 100 290 135" stroke="#3b82f6" stroke-width="2.5" fill="none" stroke-dasharray="4 4" opacity="1"/>
|
||||||
|
<circle cx="290" cy="135" r="5" fill="#3b82f6"/>
|
||||||
|
<!-- 其他连线保持 10% -->
|
||||||
|
<path d="M 130 180 Q 200 190 290 175" stroke="#f59e0b" stroke-width="2" fill="none" stroke-dasharray="4 4" opacity="0.1"/>
|
||||||
|
<path d="M 670 80 Q 600 110 510 155" stroke="#0ea5e9" stroke-width="2" fill="none" stroke-dasharray="4 4" opacity="0.1"/>
|
||||||
|
<path d="M 670 180 Q 600 200 510 195" stroke="#ec4899" stroke-width="2" fill="none" stroke-dasharray="4 4" opacity="0.1"/>
|
||||||
|
<path d="M 670 280 Q 600 260 510 235" stroke="#22c55e" stroke-width="2" fill="none" stroke-dasharray="4 4" opacity="0.1"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<!-- 正文容器 -->
|
||||||
|
<div style="position:absolute;left:280px;top:20px;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="font-size:13px;color:#78350f;line-height:2.2;">
|
||||||
|
<div style="text-align:center;font-weight:bold;margin-bottom:6px;">秋天</div>
|
||||||
|
<p style="margin:0 0 6px 0;">
|
||||||
|
<!-- 范围锚定:选中时 opacity:0.3(背景色显示) -->
|
||||||
|
<span style="background:#3b82f6;opacity:0.3;color:#78350f;border-radius:2px;">天气凉了</span>,树叶黄了,
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 6px 0;">
|
||||||
|
<!-- 点锚定:未选中,保持 0.3 -->
|
||||||
|
<span style="display:inline-block;background:#f59e0b;color:#fff;border-radius:3px;padding:0 4px;font-size:9px;font-weight:bold;opacity:0.3;margin-right:2px;">②</span>天空那么蓝,那么高。
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 6px 0;">一群大雁往南飞,</p>
|
||||||
|
<p style="margin:0 0 6px 0;">
|
||||||
|
一会儿排成个<span style="background:#0ea5e9;opacity:0;color:#78350f;">"人"字</span>
|
||||||
|
<span style="display:inline-block;background:#ec4899;color:#fff;border-radius:3px;padding:0 4px;font-size:9px;font-weight:bold;opacity:0.3;margin-left:2px;">④</span>,
|
||||||
|
</p>
|
||||||
|
<p style="margin:0;">
|
||||||
|
<span style="display:inline-block;background:#22c55e;color:#fff;border-radius:3px;padding:0 4px;font-size:9px;font-weight:bold;opacity:0.3;margin-right:2px;">⑤</span>啊!秋天来了!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 选中的节点 1(高亮边框) -->
|
||||||
|
<div style="position:absolute;left:30px;top:50px;width:140px;background:#fff;border:2px solid #3b82f6;border-left:3px solid #3b82f6;border-radius:6px;padding:8px;box-shadow:0 0 0 3px #3b82f633,0 2px 6px rgba(0,0,0,0.08);">
|
||||||
|
<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>
|
||||||
|
<span style="margin-left:auto;font-size:8px;color:#fff;background:#3b82f6;padding:1px 4px;border-radius:2px;">范围·选中</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:9px;color:#64748b;">提问:你见过秋天的树叶吗?</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 其他节点(正常状态) -->
|
||||||
|
<div style="position:absolute;left:30px;top:160px;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);opacity:0.7;">
|
||||||
|
<div style="display:flex;align-items:center;gap: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>
|
||||||
|
|
||||||
|
<div style="position:absolute;left:630px;top:50px;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);opacity:0.7;">
|
||||||
|
<div style="display:flex;align-items:center;gap: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>
|
||||||
|
|
||||||
|
<div style="position:absolute;top:12px;right:12px;background:#fff;border:1px solid #3b82f6;border-radius:4px;padding:6px 10px;font-size:9px;color:#3b82f6;">
|
||||||
|
✅ 选中"导入"节点 → "天气凉了"背景显示
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 选中节点 2(点锚定)的状态 -->
|
||||||
|
<div class="section">
|
||||||
|
<h3>选中"文本研习"节点(点锚定)— 占位符不透明</h3>
|
||||||
|
<div class="mockup">
|
||||||
|
<div class="mockup-header">选中节点 2 — 占位符 ② 100% 显示</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:320px;">
|
||||||
|
<div style="position:relative;width:100%;height:320px;background:#f1f5f9;background-image:radial-gradient(#cbd5e1 1px, transparent 1px);background-size:20px 20px;overflow:hidden;">
|
||||||
|
|
||||||
|
<svg style="position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;" viewBox="0 0 800 320">
|
||||||
|
<path d="M 130 80 Q 200 100 290 135" stroke="#3b82f6" stroke-width="2" fill="none" stroke-dasharray="4 4" opacity="0.1"/>
|
||||||
|
<!-- 选中节点的连线 100% -->
|
||||||
|
<path d="M 130 180 Q 200 190 290 175" stroke="#f59e0b" stroke-width="2.5" fill="none" stroke-dasharray="4 4" opacity="1"/>
|
||||||
|
<circle cx="290" cy="175" r="5" fill="#f59e0b"/>
|
||||||
|
<path d="M 670 80 Q 600 110 510 155" stroke="#0ea5e9" stroke-width="2" fill="none" stroke-dasharray="4 4" opacity="0.1"/>
|
||||||
|
<path d="M 670 180 Q 600 200 510 195" stroke="#ec4899" stroke-width="2" fill="none" stroke-dasharray="4 4" opacity="0.1"/>
|
||||||
|
<path d="M 670 280 Q 600 260 510 235" stroke="#22c55e" stroke-width="2" fill="none" stroke-dasharray="4 4" opacity="0.1"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div style="position:absolute;left:280px;top:20px;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="font-size:13px;color:#78350f;line-height:2.2;">
|
||||||
|
<div style="text-align:center;font-weight:bold;margin-bottom:6px;">秋天</div>
|
||||||
|
<p style="margin:0 0 6px 0;">
|
||||||
|
<!-- 范围锚定:未选中,opacity:0 -->
|
||||||
|
<span style="background:#3b82f6;opacity:0;color:#78350f;">天气凉了</span>,树叶黄了,
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 6px 0;">
|
||||||
|
<!-- 点锚定:选中时 opacity:1(不透明) -->
|
||||||
|
<span style="display:inline-block;background:#f59e0b;color:#fff;border-radius:3px;padding:0 4px;font-size:9px;font-weight:bold;opacity:1;margin-right:2px;box-shadow:0 0 0 2px #f59e0b44;">②</span>天空那么蓝,那么高。
|
||||||
|
</p>
|
||||||
|
<p style="margin:0 0 6px 0;">一群大雁往南飞,</p>
|
||||||
|
<p style="margin:0 0 6px 0;">
|
||||||
|
一会儿排成个<span style="background:#0ea5e9;opacity:0;color:#78350f;">"人"字</span>
|
||||||
|
<span style="display:inline-block;background:#ec4899;color:#fff;border-radius:3px;padding:0 4px;font-size:9px;font-weight:bold;opacity:0.3;margin-left:2px;">④</span>,
|
||||||
|
</p>
|
||||||
|
<p style="margin:0;">
|
||||||
|
<span style="display:inline-block;background:#22c55e;color:#fff;border-radius:3px;padding:0 4px;font-size:9px;font-weight:bold;opacity:0.3;margin-right:2px;">⑤</span>啊!秋天来了!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="position:absolute;left:30px;top:50px;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);opacity:0.7;">
|
||||||
|
<div style="display:flex;align-items:center;gap: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>
|
||||||
|
|
||||||
|
<!-- 选中的节点 2(高亮边框) -->
|
||||||
|
<div style="position:absolute;left:30px;top:160px;width:140px;background:#fff;border:2px solid #f59e0b;border-left:3px solid #f59e0b;border-radius:6px;padding:8px;box-shadow:0 0 0 3px #f59e0b33,0 2px 6px rgba(0,0,0,0.08);">
|
||||||
|
<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>
|
||||||
|
<span style="margin-left:auto;font-size:8px;color:#fff;background:#f59e0b;padding:1px 4px;border-radius:2px;">点·选中</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:9px;color:#64748b;">赏析:叠词的运用</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="position:absolute;left:630px;top:50px;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);opacity:0.7;">
|
||||||
|
<div style="display:flex;align-items:center;gap: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>
|
||||||
|
|
||||||
|
<div style="position:absolute;top:12px;right:12px;background:#fff;border:1px solid #f59e0b;border-radius:4px;padding:6px 10px;font-size:9px;color:#f59e0b;">
|
||||||
|
✅ 选中"文本研习"节点 → 占位符 ② 不透明显示
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 两种锚定方式对比表 -->
|
||||||
|
<div class="section">
|
||||||
|
<h3>两种锚定方式对比</h3>
|
||||||
|
<div style="overflow-x:auto;">
|
||||||
|
<table style="width:100%;border-collapse:collapse;font-size:12px;">
|
||||||
|
<thead>
|
||||||
|
<tr style="background:#1e293b;color:#e2e8f0;">
|
||||||
|
<th style="padding:8px 12px;text-align:left;border:1px solid #334155;">特性</th>
|
||||||
|
<th style="padding:8px 12px;text-align:left;border:1px solid #334155;">范围锚定(选文本)</th>
|
||||||
|
<th style="padding:8px 12px;text-align:left;border:1px solid #334155;">点锚定(插入占位符)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr style="background:#fff;">
|
||||||
|
<td style="padding:8px 12px;border:1px solid #e2e8f0;font-weight:bold;">触发方式</td>
|
||||||
|
<td style="padding:8px 12px;border:1px solid #e2e8f0;">选中一段文字 → 关联节点</td>
|
||||||
|
<td style="padding:8px 12px;border:1px solid #e2e8f0;">点击文本某位置 → 插入占位符</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="background:#f8fafc;">
|
||||||
|
<td style="padding:8px 12px;border:1px solid #e2e8f0;font-weight:bold;">视觉表现</td>
|
||||||
|
<td style="padding:8px 12px;border:1px solid #e2e8f0;">文本背景色 = 节点颜色</td>
|
||||||
|
<td style="padding:8px 12px;border:1px solid #e2e8f0;">插入标记符号(①②③)</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="background:#fff;">
|
||||||
|
<td style="padding:8px 12px;border:1px solid #e2e8f0;font-weight:bold;">默认透明度</td>
|
||||||
|
<td style="padding:8px 12px;border:1px solid #e2e8f0;">
|
||||||
|
<span style="background:#3b82f6;color:#fff;padding:2px 6px;border-radius:3px;font-size:10px;">opacity: 0</span>
|
||||||
|
<span style="color:#64748b;font-size:10px;">(完全透明,和正常文本一样)</span>
|
||||||
|
</td>
|
||||||
|
<td style="padding:8px 12px;border:1px solid #e2e8f0;">
|
||||||
|
<span style="background:#f59e0b;color:#fff;padding:2px 6px;border-radius:3px;font-size:10px;">opacity: 0.3</span>
|
||||||
|
<span style="color:#64748b;font-size:10px;">(半透明,隐约可见)</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="background:#f8fafc;">
|
||||||
|
<td style="padding:8px 12px;border:1px solid #e2e8f0;font-weight:bold;">选中时透明度</td>
|
||||||
|
<td style="padding:8px 12px;border:1px solid #e2e8f0;">
|
||||||
|
<span style="background:#3b82f6;color:#fff;padding:2px 6px;border-radius:3px;font-size:10px;">opacity: 0.3</span>
|
||||||
|
<span style="color:#64748b;font-size:10px;">(背景色显示)</span>
|
||||||
|
</td>
|
||||||
|
<td style="padding:8px 12px;border:1px solid #e2e8f0;">
|
||||||
|
<span style="background:#f59e0b;color:#fff;padding:2px 6px;border-radius:3px;font-size:10px;">opacity: 1</span>
|
||||||
|
<span style="color:#64748b;font-size:10px;">(不透明,完整显示)</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="background:#fff;">
|
||||||
|
<td style="padding:8px 12px;border:1px solid #e2e8f0;font-weight:bold;">连线透明度</td>
|
||||||
|
<td colspan="2" style="padding:8px 12px;border:1px solid #e2e8f0;">
|
||||||
|
默认 <span style="background:#64748b;color:#fff;padding:2px 6px;border-radius:3px;font-size:10px;">opacity: 0.1</span> · 选中时 <span style="background:#3b82f6;color:#fff;padding:2px 6px;border-radius:3px;font-size:10px;">opacity: 1</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="background:#f8fafc;">
|
||||||
|
<td style="padding:8px 12px;border:1px solid #e2e8f0;font-weight:bold;">适用场景</td>
|
||||||
|
<td style="padding:8px 12px;border:1px solid #e2e8f0;">节点与具体文字内容相关(如赏析某词、讲解某句)</td>
|
||||||
|
<td style="padding:8px 12px;border:1px solid #e2e8f0;">节点对应文本某个位置(如在此处开始导入、在此处小结)</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</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;">// 锚点 — 统一接口,区分 type</div>
|
||||||
|
<div><span style="color:#f59e0b;">interface</span> <span style="color:#3b82f6;">NodeAnchor</span> {</div>
|
||||||
|
<div> id: <span style="color:#10b981;">string</span>;</div>
|
||||||
|
<div> nodeId: <span style="color:#10b981;">string</span>; <span style="color:#64748b;">// 关联的节点</span></div>
|
||||||
|
<div> type: <span style="color:#10b981;">"range"</span> | <span style="color:#10b981;">"point"</span>; <span style="color:#64748b;">// 两种锚定方式</span></div>
|
||||||
|
<div> start: <span style="color:#10b981;">number</span>; <span style="color:#64748b;">// 正文纯文本偏移量</span></div>
|
||||||
|
<div> end?: <span style="color:#10b981;">number</span>; <span style="color:#64748b;">// range 锚定的结束偏移(point 无)</span></div>
|
||||||
|
<div> textPreview?: <span style="color:#10b981;">string</span>; <span style="color:#64748b;">// range 锚定的文字预览</span></div>
|
||||||
|
<div>}</div>
|
||||||
|
<br>
|
||||||
|
<div style="color:#94a3b8;">// 渲染规则</div>
|
||||||
|
<div><span style="color:#f59e0b;">function</span> <span style="color:#3b82f6;">getAnchorStyle</span>(anchor: <span style="color:#3b82f6;">NodeAnchor</span>, isActive: <span style="color:#10b981;">boolean</span>) {</div>
|
||||||
|
<div> <span style="color:#f59e0b;">if</span> (anchor.type === <span style="color:#10b981;">"range"</span>) {</div>
|
||||||
|
<div> <span style="color:#f59e0b;">return</span> { backgroundColor: getNodeColor(anchor.nodeId), opacity: isActive ? <span style="color:#f59e0b;">0.3</span> : <span style="color:#f59e0b;">0</span> };</div>
|
||||||
|
<div> } <span style="color:#f59e0b;">else</span> { <span style="color:#64748b;">// point</span></div>
|
||||||
|
<div> <span style="color:#f59e0b;">return</span> { opacity: isActive ? <span style="color:#f59e0b;">1</span> : <span style="color:#f59e0b;">0.3</span> };</div>
|
||||||
|
<div> }</div>
|
||||||
|
<div>}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"reason":"idle timeout","timestamp":1782143663726}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
sessionDir=e:\Desktop\CICD\.superpowers\brainstorm\41500-1782168322.9344
|
||||||
|
stateDir=e:\Desktop\CICD\.superpowers\brainstorm\41500-1782168322.9344\state
|
||||||
|
contentDir=e:\Desktop\CICD\.superpowers\brainstorm\41500-1782168322.9344\content
|
||||||
@@ -4,19 +4,23 @@
|
|||||||
|
|
||||||
**任何任务开始前,必须先查阅架构影响地图,通过图定位代码和模块。**
|
**任何任务开始前,必须先查阅架构影响地图,通过图定位代码和模块。**
|
||||||
|
|
||||||
1. **先图后码**:执行任何分析、修改、搜索任务时,首先阅读 `docs/architecture/004_architecture_impact_map.md` 或 `docs/architecture/005_architecture_data.json`,从图中定位目标模块、函数、依赖关系,再按图索骥读取源码
|
1. **先图后码**:执行任何分析、修改、搜索任务时,首先运行 `npm run arch:scan` 更新 arch.db,再通过 `npm run arch:query` 查询目标模块、函数、依赖关系,结合阅读 `docs/architecture/004_architecture_impact_map.md` 定位架构设计意图,最后按图索骥读取源码
|
||||||
2. **图未覆盖则先补图**:如果发现项目中存在架构图未记录的模块、函数、表、路由等,**必须优先完善架构图信息**,然后再继续后续工作
|
2. **图未覆盖则先补图**:如果发现项目中存在 arch.db 未记录的模块、函数、表、路由等,**必须先运行 `npm run arch:scan` 重新扫描**,然后检查 004 是否需要补充
|
||||||
3. **改码必同步图**:对源码的任何修改完成后,必须同步更新 004 和 005 两个架构文档
|
3. **改码必同步图**:对源码的任何修改完成后,必须运行 `npm run arch:scan` 更新 arch.db;若架构设计意图有变化,同步更新 004
|
||||||
|
|
||||||
### 架构文档清单
|
### 架构文档清单
|
||||||
|
|
||||||
| 文档 | 用途 |
|
| 文档 | 用途 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `docs/architecture/004_architecture_impact_map.md` | 人类可读的架构影响地图 |
|
| `docs/architecture/004_architecture_impact_map.md` | 架构设计意图唯一源(人类可读) |
|
||||||
| `docs/architecture/005_architecture_data.json` | AI 友好格式的结构化数据 |
|
|
||||||
| `docs/architecture/006_k12_feature_checklist.md` | 标准功能模块清单 |
|
| `docs/architecture/006_k12_feature_checklist.md` | 标准功能模块清单 |
|
||||||
| `docs/architecture/007_gap_audit_report.md` | 差距审计报告 |
|
| `docs/architecture/007_gap_audit_report.md` | 差距审计报告 |
|
||||||
| `docs/architecture/audit/01_decoupling_roadmap.md` | 解耦路线图 |
|
| `docs/architecture/008_module_role_mapping.md` | 模块角色映射 |
|
||||||
|
| `docs/architecture/roadmap/` | 长远规划(tech-debt/decoupling/pending-features) |
|
||||||
|
| `docs/architecture/audit/` | 架构审查报告与归档(含已废弃的 005 JSON、004 V1) |
|
||||||
|
| `docs/troubleshooting/known-issues.md` | 已知问题速查(场景→技术映射 + 工作经验日志) |
|
||||||
|
|
||||||
|
> 注:005_architecture_data.json 已废弃归档至 `audit/archive/`,结构化数据查询统一通过 arch.db
|
||||||
|
|
||||||
### 需要同步图的场景
|
### 需要同步图的场景
|
||||||
|
|
||||||
@@ -30,9 +34,23 @@
|
|||||||
|
|
||||||
### 同步方式
|
### 同步方式
|
||||||
|
|
||||||
- 修改 Markdown 文档中对应的模块章节
|
- 修改源码后运行 `npm run arch:scan` 更新 arch.db(强制)
|
||||||
- 修改 JSON 文档中对应的节点(`modules.*.exports`、`permissions`、`dependencyMatrix`、`routes`、`dbTables` 等)
|
- 若架构设计意图变化,同步更新 `docs/architecture/004_architecture_impact_map.md`
|
||||||
- 确保两个文档内容一致
|
- 若发现新的"场景→技术"映射或工作经验,更新 `docs/troubleshooting/known-issues.md`
|
||||||
|
|
||||||
|
## 架构元数据库规则(arch.db)
|
||||||
|
|
||||||
|
**arch.db 是代码结构唯一源,AI 工作前必须运行 `npm run arch:scan` 更新。**
|
||||||
|
|
||||||
|
1. **arch.db 取代 005 JSON**:模块、函数、调用关系、依赖关系、技术标签查询 arch.db,不手动维护结构化数据文件
|
||||||
|
2. **查询命令**:
|
||||||
|
- `npm run arch:query -- sql "<SQL>"` 自定义 SQL 查询
|
||||||
|
- `npm run arch:query -- module-deps` 查模块依赖
|
||||||
|
- `npm run arch:query -- module-reverse-deps <module>` 查反向依赖
|
||||||
|
- `npm run arch:query -- symbol-refs <symbol>` 查符号引用链
|
||||||
|
- `npm run arch:query -- tech-usage <tag>` 查技术使用
|
||||||
|
- `npm run arch:query -- violations` 查架构违规
|
||||||
|
3. **arch.db 不替代 004**:arch.db 是"代码现状",004 是"设计意图",两者互补
|
||||||
|
|
||||||
## 编码规范
|
## 编码规范
|
||||||
|
|
||||||
@@ -107,7 +125,31 @@ src/modules/[module]/
|
|||||||
- 使用 `cn()` 工具函数管理条件类名
|
- 使用 `cn()` 工具函数管理条件类名
|
||||||
- **禁止**字符串拼接动态类名(`bg-${color}-500`)
|
- **禁止**字符串拼接动态类名(`bg-${color}-500`)
|
||||||
- **禁止**使用任意值(`w-[137px]`),除非有充分理由并注释
|
- **禁止**使用任意值(`w-[137px]`),除非有充分理由并注释
|
||||||
- 设计令牌在 `src/app/globals.css` 中使用 CSS 变量定义
|
- 设计令牌在 `src/app/styles/tokens/` 目录中分层定义,通过 `@theme inline` 暴露为 Tailwind 类
|
||||||
|
|
||||||
|
### 设计令牌规范(强制)
|
||||||
|
|
||||||
|
- **禁止硬编码颜色**: TSX/TS/CSS 中不得出现 `#hex` 颜色字面量,统一使用 `hsl(var(--*))` 或 Tailwind 类 `bg-*`
|
||||||
|
- **禁止硬编码字体**: 不得出现 `'Inter'`/`'Fraunces'`/`'JetBrains Mono'` 字面量,使用 `var(--font-family-sans/serif/mono)`
|
||||||
|
- **禁止硬编码字号**: 不得出现 `font-size: Npx`,使用 `var(--font-size-1~9)`
|
||||||
|
- **禁止 Tailwind 任意值**: 不得使用 `w-[Npx]`/`h-[Npx]`/`p-[Npx]` 等,映射到 `--space-*` 或 Tailwind 默认阶梯
|
||||||
|
- **豁免场景**(需 `// eslint-disable-next-line no-restricted-syntax -- <reason>` 注释):
|
||||||
|
- PWA manifest(`src/app/manifest.ts`)
|
||||||
|
- 邮件 HTML 内联样式(`src/modules/notifications/channels/email-channel.ts`)
|
||||||
|
- 图表 SVG 固定画布尺寸(recharts 选择器中的 `#ccc`/`#fff`)
|
||||||
|
- loading.tsx 占位骨架
|
||||||
|
- Dialog 固定宽度等无法令牌化的设计固定尺寸
|
||||||
|
- **令牌文件分布**: `src/app/styles/tokens/`(primitive/semantic-light/semantic-dark/lesson-preparation/tailwind-theme/index)
|
||||||
|
- **令牌分层**:
|
||||||
|
- Layer 1 Primitive(`primitive.css`):原始色板/字号/间距/阴影,业务代码不直接引用
|
||||||
|
- Layer 2 Semantic(`semantic-light.css` + `semantic-dark.css`):语义令牌,业务代码唯一引用入口
|
||||||
|
- 模块命名空间(`lesson-preparation.css`):`--lp-*` 令牌,明暗双份
|
||||||
|
- Tailwind 暴露(`tailwind-theme.css`):`@theme inline` 将 Semantic 令牌暴露为 `bg-*`/`text-*`/`font-*` 类
|
||||||
|
- **改令牌必同步图**: 修改令牌定义后,同步更新 `docs/architecture/004_architecture_impact_map.md` 与 arch.db(`npm run arch:scan`)
|
||||||
|
- **ESLint 强制约束**:
|
||||||
|
- `no-restricted-syntax`: 禁止 `#hex` 字面量
|
||||||
|
- `design-tokens/no-hardcoded-fonts`: 禁止 `'Inter'`/`'Fraunces'`/`'JetBrains Mono'` 字面量(单词边界匹配,不影响 `Interval`/`Interactive` 等标识符)
|
||||||
|
- 白名单:`primitive.css`(令牌定义)、`email-channel.ts`(邮件 HTML)、`manifest.ts`(PWA)
|
||||||
|
|
||||||
### 安全规范
|
### 安全规范
|
||||||
|
|
||||||
@@ -121,3 +163,64 @@ src/modules/[module]/
|
|||||||
- 使用 Conventional Commits 格式:`feat(scope): description`
|
- 使用 Conventional Commits 格式:`feat(scope): description`
|
||||||
- 类型:`feat`, `fix`, `chore`, `docs`, `style`, `refactor`, `test`, `perf`, `ci`
|
- 类型:`feat`, `fix`, `chore`, `docs`, `style`, `refactor`, `test`, `perf`, `ci`
|
||||||
- 提交前必须运行 `npm run lint` 和 `npx tsc --noEmit` 确保零错误
|
- 提交前必须运行 `npm run lint` 和 `npx tsc --noEmit` 确保零错误
|
||||||
|
|
||||||
|
## 问题记录规则
|
||||||
|
|
||||||
|
**所有工作完成后,必须将遇到的问题记录到 `docs/troubleshooting/known-issues.md`(索引式速查手册)。**
|
||||||
|
|
||||||
|
### 必须记录的场景
|
||||||
|
|
||||||
|
| 场景 | 记录要求 |
|
||||||
|
|------|---------|
|
||||||
|
| 构建报错(dev/build/lint/tsc) | 记录到"全局经验"对应主题分区 |
|
||||||
|
| 运行时异常(白屏/API 报错/数据加载失败) | 记录到"模块经验"对应模块分区 |
|
||||||
|
| 框架/库版本兼容问题 | 记录到"全局经验: Next.js 配置与运行时" |
|
||||||
|
| 依赖配置问题(serverExternalPackages/webpackIgnore 等) | 记录到"全局经验: Next.js 配置与运行时" |
|
||||||
|
| 架构约束违规 | 记录到"全局经验"对应主题分区 |
|
||||||
|
|
||||||
|
### 记录格式
|
||||||
|
|
||||||
|
索引式表格,指明"场景→技术/规则"映射,不写多行代码示例:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### X.X 主题分区
|
||||||
|
|
||||||
|
| 场景 | 技术/规则 |
|
||||||
|
|------|----------|
|
||||||
|
| 简述场景 | 正确做法(一句话) |
|
||||||
|
```
|
||||||
|
|
||||||
|
### 记录要求
|
||||||
|
|
||||||
|
- **索引式**:场景→技术/规则映射,不写代码示例和错误示范列
|
||||||
|
- **去重**:同类问题在原条目补充,不重复创建
|
||||||
|
- **引用架构规则**:架构分层、模块结构等规则引用 004 和 project_rules,不重复
|
||||||
|
- **工作经验日志**:在"工作经验日志"区按时间倒序追加(50 条上限),记录"做了什么/学到什么/下次注意"
|
||||||
|
|
||||||
|
## AI 工作强制流程
|
||||||
|
|
||||||
|
**所有 AI 工作必须遵循此流程,违反即违规。**
|
||||||
|
|
||||||
|
### 阶段 1: 上下文加载
|
||||||
|
|
||||||
|
1. `npm run arch:scan` 更新 arch.db
|
||||||
|
2. `npm run arch:query -- module-deps` 查目标模块依赖
|
||||||
|
3. `npm run arch:query -- symbol-refs <目标函数>` 查调用链
|
||||||
|
4. 阅读 `src/modules/[模块]/README.md` 读模块工作流程
|
||||||
|
5. 查 `docs/troubleshooting/known-issues.md` "模块经验" 分区读相关经验
|
||||||
|
|
||||||
|
### 阶段 2: 执行工作
|
||||||
|
|
||||||
|
1. 按规划执行
|
||||||
|
2. 修改代码后立即运行 `npm run arch:scan` 更新 arch.db
|
||||||
|
3. 运行 `npx tsc --noEmit` 和 `npm run lint` 确保零错误
|
||||||
|
|
||||||
|
### 阶段 3: 经验沉淀(强制,不可跳过)
|
||||||
|
|
||||||
|
1. 在 `docs/troubleshooting/known-issues.md` "工作经验日志" 区追加一条记录:
|
||||||
|
- 日期 + 时间
|
||||||
|
- 模块
|
||||||
|
- 做了什么 + 学到什么
|
||||||
|
2. 若发现新的"场景→技术"映射 → 提炼到对应模块分区
|
||||||
|
3. 若发现新的架构决策 → 更新 004
|
||||||
|
4. 若代码结构变化 → `npm run arch:scan` 确认 arch.db 已更新
|
||||||
|
|||||||
1
.tsc_out.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
src/app/(dashboard)/teacher/textbooks/error.tsx(3,10): error TS2305: Module '"@/shared/components/route-error"' has no exported member 'RouteError'.
|
||||||
43
CHANGELOG.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- arch:scan @public JSDoc 标记豁免机制,支持登录前/公开/内部工具 Server Action 豁免权限校验
|
||||||
|
- arch:scan 递归 CTE 违规检测,识别通过辅助函数间接调用 requirePermission 的调用链
|
||||||
|
- 大仓工程基建:LICENSE、CONTRIBUTING、SECURITY、.env.example 文档
|
||||||
|
- husky + lint-staged + commitlint 本地提交规范工具链
|
||||||
|
- /api/health 健康检查端点 + Dockerfile HEALTHCHECK
|
||||||
|
- @next/bundle-analyzer 构建体积分析工具
|
||||||
|
- CI 流水线新增 Unit test + coverage 阶段
|
||||||
|
- tsconfig 开启 noUncheckedIndexedAccess 严格模式
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- 重构 004 架构文档为完整架构设计文档(912 行,14 章节,13 个 mermaid 图)
|
||||||
|
- 重写 35 个模块 README,统一 8 章节模板(架构图/流程图/技术栈)
|
||||||
|
- 拆分 5 个超长文件:schema.ts (2245→29+27子文件)、invalidation-map.ts (1195→50+6子文件)、messaging/actions.ts (973→47+5子文件)、textbooks/data-access.ts (907→15+6子文件)、questions/data-access.ts (828→48+4子文件)
|
||||||
|
- 精简 known-issues.md 为索引式速查手册(场景→技术/规则映射)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- 修复 20 个 Server Action 权限违规(12 个 @public 豁免 + 8 个真违规修复)
|
||||||
|
- 修复 ai 模块 6 个 Action 权限误报(requireAiPermission 间接调用链识别)
|
||||||
|
- 修复 parent 模块 6 个 Action 权限缺失(requireAuth → requirePermission)
|
||||||
|
- 修复 settings 模块 updateProfileAction 权限校验(显式 requirePermission)
|
||||||
|
|
||||||
|
## [0.1.0] - 2026-06-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- 初始版本发布
|
||||||
|
- K12 智慧教学平台核心功能:备课、作业、考试、成绩、考勤、消息、家校互动
|
||||||
|
- 严格三层架构:app → modules → shared
|
||||||
|
- 5 层状态管理模型:URL(nuqs) · Server(TanStack Query) · Client(Zustand) · Global UI · Form
|
||||||
|
- 权限 3 道防线:proxy.ts → requirePermission → usePermission
|
||||||
|
- 设计令牌双层架构:Primitive + Semantic
|
||||||
|
- arch.db 架构元数据库(12 张表 + 7 个索引)
|
||||||
|
- cacheFn 请求级缓存层
|
||||||
|
- Gitea Actions CI/CD 流水线
|
||||||
129
CONTRIBUTING.md
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
# 贡献指南
|
||||||
|
|
||||||
|
感谢参与本项目!请遵循以下规范提交贡献。
|
||||||
|
|
||||||
|
## 开发环境准备
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 安装依赖
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 2. 准备环境变量
|
||||||
|
cp .env.example .env
|
||||||
|
# 编辑 .env 填入实际配置
|
||||||
|
|
||||||
|
# 3. 初始化数据库
|
||||||
|
npm run db:push
|
||||||
|
|
||||||
|
# 4. 启动开发服务器
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## 强制工作流程
|
||||||
|
|
||||||
|
**所有代码改动前必须先查阅架构文档:**
|
||||||
|
|
||||||
|
1. 阅读 `docs/architecture/004_architecture_impact_map.md` 了解架构设计意图
|
||||||
|
2. 运行 `npm run arch:scan` 更新 arch.db
|
||||||
|
3. 运行 `npm run arch:query -- module-deps` 查目标模块依赖
|
||||||
|
4. 阅读 `src/modules/[模块]/README.md` 了解模块工作流程
|
||||||
|
5. 查 `docs/troubleshooting/known-issues.md` 读相关经验
|
||||||
|
|
||||||
|
**代码改动后必须:**
|
||||||
|
|
||||||
|
1. 运行 `npx tsc --noEmit` 确保零错误
|
||||||
|
2. 运行 `npm run lint` 确保零错误
|
||||||
|
3. 运行 `npm run arch:scan` 更新 arch.db
|
||||||
|
4. 若架构设计意图变化,同步更新 004 文档
|
||||||
|
5. 若发现新场景→技术映射,更新 known-issues.md
|
||||||
|
|
||||||
|
## 提交规范
|
||||||
|
|
||||||
|
### Conventional Commits 格式
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>(<scope>): <description>
|
||||||
|
|
||||||
|
[optional body]
|
||||||
|
|
||||||
|
[optional footer]
|
||||||
|
```
|
||||||
|
|
||||||
|
**类型(type):**
|
||||||
|
- `feat`: 新功能
|
||||||
|
- `fix`: Bug 修复
|
||||||
|
- `docs`: 文档变更
|
||||||
|
- `style`: 代码格式(不影响功能)
|
||||||
|
- `refactor`: 重构(既不是新功能也不是修复)
|
||||||
|
- `test`: 测试相关
|
||||||
|
- `chore`: 构建/工具/依赖变更
|
||||||
|
- `perf`: 性能优化
|
||||||
|
- `ci`: CI/CD 变更
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
```
|
||||||
|
feat(arch-scan): add @public JSDoc tag exemption mechanism
|
||||||
|
fix(permissions): fix parent module 6 Action permission violations
|
||||||
|
refactor: split 5 oversized files into domain-specific subfiles
|
||||||
|
docs(architecture): rewrite 004 as architecture design document
|
||||||
|
```
|
||||||
|
|
||||||
|
### 提交前检查
|
||||||
|
|
||||||
|
husky + lint-staged 会在 `git commit` 时自动执行:
|
||||||
|
- ESLint 检查暂存文件
|
||||||
|
- Prettier 格式化暂存文件
|
||||||
|
- commitlint 校验 commit message 格式
|
||||||
|
|
||||||
|
如果检查失败,请修复后重新提交。
|
||||||
|
|
||||||
|
## 架构约束
|
||||||
|
|
||||||
|
### 严格三层架构
|
||||||
|
|
||||||
|
```
|
||||||
|
app → modules → shared
|
||||||
|
```
|
||||||
|
|
||||||
|
- `app/` 只能调用 `modules/` 的 Server Actions 和 data-access
|
||||||
|
- `modules/` 之间通过对方 data-access 通信,不直接查询对方 DB 表
|
||||||
|
- `shared/` 不得反向依赖 `modules/*` 或 `app/*`
|
||||||
|
|
||||||
|
### 代码质量规则
|
||||||
|
|
||||||
|
- 禁止 `any`,未知类型用 `unknown` + 类型守卫
|
||||||
|
- 禁止 `as` 断言(除非从 `unknown` 转换,需注释原因)
|
||||||
|
- 函数返回值必须显式标注,特别是 `Promise<T>`
|
||||||
|
- 仅用于类型的导入使用 `import type`
|
||||||
|
- Server Action 必须调用 `requirePermission()`(或加 `@public` 标记豁免)
|
||||||
|
- 前端权限检查使用 `usePermission().hasPermission()`,禁止 `role === "xxx"` 硬编码
|
||||||
|
- 单文件行数:组件 ≤500,actions/data-access ≤800,硬限 1000
|
||||||
|
|
||||||
|
### 设计令牌规范
|
||||||
|
|
||||||
|
- 禁止硬编码颜色(`#hex`),使用 `hsl(var(--*))` 或 Tailwind 类
|
||||||
|
- 禁止硬编码字体(`'Inter'`),使用 `var(--font-family-*)`
|
||||||
|
- 禁止 Tailwind 任意值(`w-[137px]`),映射到 `--space-*` 或默认阶梯
|
||||||
|
|
||||||
|
## 文档同步
|
||||||
|
|
||||||
|
### 需要同步架构图的场景
|
||||||
|
|
||||||
|
- 新增/删除/重命名导出函数、组件、Hook、类型
|
||||||
|
- 修改函数签名(参数、返回类型)
|
||||||
|
- 修改权限点或角色-权限映射
|
||||||
|
- 新增/删除数据库表、路由页面、API 路由
|
||||||
|
- 修改模块间依赖关系
|
||||||
|
- 新增模块
|
||||||
|
|
||||||
|
### 同步方式
|
||||||
|
|
||||||
|
- 修改源码后运行 `npm run arch:scan` 更新 arch.db(强制)
|
||||||
|
- 若架构设计意图变化,同步更新 `docs/architecture/004_architecture_impact_map.md`
|
||||||
|
- 若发现新"场景→技术"映射,更新 `docs/troubleshooting/known-issues.md`
|
||||||
|
|
||||||
|
## 问题报告
|
||||||
|
|
||||||
|
- 构建/lint/tsc 报错 → 记录到 `docs/troubleshooting/known-issues.md` "全局经验"分区
|
||||||
|
- 运行时异常 → 记录到"模块经验"分区
|
||||||
|
- 框架/库版本兼容问题 → 记录到"全局经验: Next.js 配置与运行时"
|
||||||
@@ -18,4 +18,7 @@ EXPOSE 3000
|
|||||||
ENV PORT 3000
|
ENV PORT 3000
|
||||||
ENV HOSTNAME "0.0.0.0"
|
ENV HOSTNAME "0.0.0.0"
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||||
|
CMD node -e "fetch('http://localhost:' + (process.env.PORT || 3000) + '/api/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"
|
||||||
|
|
||||||
CMD ["node", "server.js"]
|
CMD ["node", "server.js"]
|
||||||
|
|||||||
14
LICENSE
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
PROPRIETARY AND CONFIDENTIAL
|
||||||
|
|
||||||
|
Copyright (c) 2026 EazyGame. All rights reserved.
|
||||||
|
|
||||||
|
This source code and accompanying documentation (the "Software") is the
|
||||||
|
proprietary and confidential property of EazyGame. No part of the Software
|
||||||
|
may be reproduced, distributed, or transmitted in any form or by any means,
|
||||||
|
including photocopying, recording, or other electronic or mechanical methods,
|
||||||
|
without the prior written permission of EazyGame.
|
||||||
|
|
||||||
|
For licensing inquiries, contact: legal@eazygame.cn
|
||||||
|
|
||||||
|
Unauthorized use, reproduction, or distribution of this Software, via any
|
||||||
|
medium, is strictly prohibited and may result in civil and criminal penalties.
|
||||||
90
SECURITY.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# 安全策略
|
||||||
|
|
||||||
|
## 报告安全漏洞
|
||||||
|
|
||||||
|
**请不要通过 GitHub Issue 公开报告安全漏洞。**
|
||||||
|
|
||||||
|
发现安全漏洞请通过以下渠道私密报告:
|
||||||
|
|
||||||
|
- 邮件:security@eazygame.cn
|
||||||
|
- 内部工单系统:Security 项目 → New Issue
|
||||||
|
|
||||||
|
报告时请包含:
|
||||||
|
1. 漏洞描述和影响范围
|
||||||
|
2. 复现步骤(最小化示例)
|
||||||
|
3. 影响的版本号
|
||||||
|
4. 建议的修复方案(可选)
|
||||||
|
|
||||||
|
**响应时间:** 24 小时内确认收到,5 个工作日内给出评估结果。
|
||||||
|
|
||||||
|
## 安全架构
|
||||||
|
|
||||||
|
### 权限三道防线
|
||||||
|
|
||||||
|
```
|
||||||
|
proxy.ts (路由级 bitmap) → requirePermission (Server Action 级) → usePermission (客户端级)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **路由级**:`src/proxy.ts` 使用 bitmap 快速拦截未授权路由
|
||||||
|
- **Server Action 级**:每个 Action 必须调用 `requirePermission()`,或用 `@public` JSDoc 标记豁免
|
||||||
|
- **客户端级**:组件使用 `usePermission().hasPermission()` 控制元素显隐
|
||||||
|
|
||||||
|
### 认证与会话
|
||||||
|
|
||||||
|
- JWT/session ID 存储在 httpOnly + Secure + SameSite=Strict 的 Cookie 中
|
||||||
|
- 服务端环境变量不加 `NEXT_PUBLIC_` 前缀
|
||||||
|
- 环境变量使用 `@t3-oss/env-nextjs` + Zod 校验(`src/env.mjs`)
|
||||||
|
|
||||||
|
### 数据访问
|
||||||
|
|
||||||
|
- 前端禁止直接访问数据库,所有数据访问必须通过 `data-access.ts` 模块
|
||||||
|
- Server Action 必须使用 `requirePermission()` 进行权限校验
|
||||||
|
- 家长路由必须包含 `parentId` 和 `studentId` 双重权限校验,防止信息泄露
|
||||||
|
|
||||||
|
### 输入安全
|
||||||
|
|
||||||
|
- **禁止 `dangerouslySetInnerHTML`**(如必须使用,先用 DOMPurify 清洗)
|
||||||
|
- Server Action 输入使用 Zod 验证,验证失败返回结构化错误
|
||||||
|
- 注册/登录流程实施速率限制,防止暴力破解和邮箱枚举攻击
|
||||||
|
|
||||||
|
## 安全审计
|
||||||
|
|
||||||
|
### arch:scan 自动检测
|
||||||
|
|
||||||
|
`npm run arch:query -- violations` 会自动检测:
|
||||||
|
|
||||||
|
- **长文件**(>800 行):提示拆分,降低维护风险
|
||||||
|
- **Server Action 权限缺失**:识别未调用 `requirePermission` 的 Server Action(支持递归调用链识别)
|
||||||
|
|
||||||
|
### @public 豁免标记
|
||||||
|
|
||||||
|
登录前/公开/内部工具 Server Action 可用 `@public` JSDoc 标记豁免权限校验:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
/**
|
||||||
|
* 注册 Action,登录前公开调用。
|
||||||
|
*
|
||||||
|
* @public 登录前公开 Action,豁免 requirePermission 校验。
|
||||||
|
*/
|
||||||
|
export async function registerAction(formData: FormData) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**豁免场景:**
|
||||||
|
- 登录前 Action(注册、邮箱可用性检查、2FA 预检)
|
||||||
|
- 内部日志工具(audit-logger、change-logger、login-logger)
|
||||||
|
- 权限查询工具(isAdminRole、canConfigurePublicAiProvider)
|
||||||
|
- 登录后必经流程(onboarding 状态查询/完成)
|
||||||
|
|
||||||
|
## 依赖安全
|
||||||
|
|
||||||
|
- 定期运行 `npm audit` 检查已知漏洞
|
||||||
|
- CI 流水线包含 Trivy 安全扫描(`.trivyignore` 配置豁免项)
|
||||||
|
- 依赖升级通过 PR 审核,不允许直接推送 main 分支
|
||||||
|
|
||||||
|
## 数据保护
|
||||||
|
|
||||||
|
- 数据库备份:每日自动备份,每周 DR 演练
|
||||||
|
- 敏感数据(密码、2FA 密钥)使用 bcrypt/Argon2 哈希存储
|
||||||
|
- 日志不记录敏感信息(密码、token、个人身份信息)
|
||||||
BIN
bugs/screenshots/announcements.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
bugs/screenshots/login_failure.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
bugs/screenshots/messages.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
bugs/screenshots/teacher_attendance_error.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
bugs/screenshots/teacher_attendance_sheet_error.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
bugs/screenshots/teacher_attendance_stats_error.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
bugs/screenshots/teacher_classes.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
bugs/screenshots/teacher_classes_error.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
bugs/screenshots/teacher_classes_my_error.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
bugs/screenshots/teacher_classes_schedule_error.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
bugs/screenshots/teacher_classes_students_error.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
bugs/screenshots/teacher_course-plans_error.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
bugs/screenshots/teacher_dashboard_error.png
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
bugs/screenshots/teacher_diagnostic.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
bugs/screenshots/teacher_diagnostic_error.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
bugs/screenshots/teacher_elective_error.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
bugs/screenshots/teacher_error-book.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
bugs/screenshots/teacher_error-book_error.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
bugs/screenshots/teacher_exams_all_error.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
bugs/screenshots/teacher_exams_create_error.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
bugs/screenshots/teacher_exams_error.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
bugs/screenshots/teacher_grades.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
bugs/screenshots/teacher_grades_analytics_error.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
bugs/screenshots/teacher_grades_entry.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
bugs/screenshots/teacher_grades_stats.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
bugs/screenshots/teacher_homework_assignments_create_error.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
bugs/screenshots/teacher_homework_assignments_error.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
bugs/screenshots/teacher_homework_error.png
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
bugs/screenshots/teacher_homework_submissions.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
bugs/screenshots/teacher_homework_submissions_error.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
bugs/screenshots/teacher_lesson-plans_error.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
bugs/screenshots/teacher_lesson-plans_new_error.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
bugs/screenshots/teacher_questions_error.png
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
bugs/screenshots/teacher_schedule-changes.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
bugs/screenshots/teacher_schedule-changes_error.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
bugs/screenshots/teacher_textbooks_error.png
Normal file
|
After Width: | Height: | Size: 133 KiB |
1734
build-output.txt
Normal file
24
commitlint.config.mjs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export default {
|
||||||
|
extends: ["@commitlint/config-conventional"],
|
||||||
|
rules: {
|
||||||
|
"type-enum": [
|
||||||
|
2,
|
||||||
|
"always",
|
||||||
|
[
|
||||||
|
"feat",
|
||||||
|
"fix",
|
||||||
|
"docs",
|
||||||
|
"style",
|
||||||
|
"refactor",
|
||||||
|
"test",
|
||||||
|
"chore",
|
||||||
|
"perf",
|
||||||
|
"ci",
|
||||||
|
"build",
|
||||||
|
"revert",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
"subject-case": [0],
|
||||||
|
"header-max-length": [2, "always", 120],
|
||||||
|
},
|
||||||
|
}
|
||||||
4
cookies.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Netscape HTTP Cookie File
|
||||||
|
# https://curl.se/docs/http-cookies.html
|
||||||
|
# This file was generated by libcurl! Edit at your own risk.
|
||||||
|
|
||||||
BIN
debug-exams.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
@@ -69,7 +69,7 @@
|
|||||||
| 文档 | 归档原因 |
|
| 文档 | 归档原因 |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| [002 RBAC 重构方案](architecture/002_rbac_refactoring.md) | 描述修复前的安全隐患,当前所有 Server Action 已接入 `requirePermission()` |
|
| [002 RBAC 重构方案](architecture/002_rbac_refactoring.md) | 描述修复前的安全隐患,当前所有 Server Action 已接入 `requirePermission()` |
|
||||||
| [002 角色路由 RFC](architecture/002_role_based_routing.md) | 2025-12-23 提案,当前角色域路由已全部实现 |
|
| [002b 角色路由 RFC](architecture/002b_role_based_routing.md) | 2025-12-23 提案,当前角色域路由已全部实现 |
|
||||||
| [003 UI 重构计划](architecture/003_ui_refactoring_plan.md) | 2026-06-16 重构计划,当前已执行完毕 |
|
| [003 UI 重构计划](architecture/003_ui_refactoring_plan.md) | 2026-06-16 重构计划,当前已执行完毕 |
|
||||||
|
|
||||||
### 设计历史文档
|
### 设计历史文档
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ AI 模式: 选择 AI Provider → 粘贴试卷源文本 → AI 解析生成 →
|
|||||||
| API 路由 | 9 |
|
| API 路由 | 9 |
|
||||||
| Server Actions | 80+ |
|
| Server Actions | 80+ |
|
||||||
| 用户角色 | 6 (admin/teacher/student/parent/grade_head/teaching_head) |
|
| 用户角色 | 6 (admin/teacher/student/parent/grade_head/teaching_head) |
|
||||||
| 权限点 | 54 |
|
| 权限点 | 67 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -380,3 +380,4 @@ AI 模式: 选择 AI Provider → 粘贴试卷源文本 → AI 解析生成 →
|
|||||||
| [005 架构数据](./005_architecture_data.json) | AI 友好的结构化架构数据 |
|
| [005 架构数据](./005_architecture_data.json) | AI 友好的结构化架构数据 |
|
||||||
| [006 功能清单](./006_k12_feature_checklist.md) | 企业级 K12 标准功能模块清单 |
|
| [006 功能清单](./006_k12_feature_checklist.md) | 企业级 K12 标准功能模块清单 |
|
||||||
| [007 差距审计报告](./007_gap_audit_report.md) | 功能差距审计与补齐路线图 |
|
| [007 差距审计报告](./007_gap_audit_report.md) | 功能差距审计与补齐路线图 |
|
||||||
|
| [008 模块角色映射](./008_module_role_mapping.md) | 模块-角色-功能映射总览 |
|
||||||
|
|||||||
299
docs/architecture/008_module_role_mapping.md
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
# 模块-角色-功能映射总览
|
||||||
|
|
||||||
|
> 生成日期:2026-06-22
|
||||||
|
> 数据来源:`src/modules/` 全量扫描 + `src/app/(dashboard)/` 路由权限校验 + `src/shared/types/permissions.ts` 权限点定义
|
||||||
|
> 角色体系:admin / teacher / student / parent / grade_head / teaching_head(含 management 路由组)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、模块-角色速查表
|
||||||
|
|
||||||
|
| # | 模块 | 目录 | admin | teacher | student | parent | mgmt | 核心功能 |
|
||||||
|
|---|------|------|:-----:|:-------:|:-------:|:------:|:----:|----------|
|
||||||
|
| 1 | adaptive-practice | `adaptive-practice/` | | ✅ | ✅ | ✅ | ✅ | 专项练习:自适应出题、答题、练习历史、成绩分析 |
|
||||||
|
| 2 | ai | `ai/` | ✅ | ✅ | ✅ | ✅ | ✅ | AI 赋能:对话助手、出题辅助、批改辅助、学情分析、多模型配置 |
|
||||||
|
| 3 | announcements | `announcements/` | ✅ | ✅ | ✅ | ✅ | ✅ | 通知公告:学校/年级/班级三级公告发布与查看 |
|
||||||
|
| 4 | attendance | `attendance/` | ✅ | ✅ | ✅ | ✅ | | 考勤管理:学生/教师考勤登记、统计、规则配置 |
|
||||||
|
| 5 | audit | `audit/` | ✅ | | | | | 日志审计:操作日志、登录日志、数据变更日志、导出 |
|
||||||
|
| 6 | auth | `auth/` | ✅ | ✅ | ✅ | ✅ | ✅ | 认证系统:登录/注册/JWT/双因素认证 |
|
||||||
|
| 7 | classes | `classes/` | ✅ | ✅ | ✅ | | | 班级管理:班级 CRUD、课表、学生管理、邀请码 |
|
||||||
|
| 8 | course-plans | `course-plans/` | ✅ | ✅ | ✅ | ✅ | | 课程计划:教学计划创建、进度跟踪、日历视图 |
|
||||||
|
| 9 | dashboard | `dashboard/` | ✅ | ✅ | ✅ | ✅ | ✅ | 仪表盘:角色独立看板(统计卡片/图表/快捷操作) |
|
||||||
|
| 10 | diagnostic | `diagnostic/` | | ✅ | ✅ | ✅ | | 学情诊断:知识点掌握度雷达图、班级/个人诊断报告 |
|
||||||
|
| 11 | elective | `elective/` | ✅ | ✅ | ✅ | ✅ | | 选课管理:选修课创建/选课/退选/抽签 |
|
||||||
|
| 12 | error-book | `error-book/` | ✅ | ✅ | ✅ | ✅ | | 错题本:错题自动采集、SM-2 间隔复习、统计分析 |
|
||||||
|
| 13 | exams | `exams/` | | ✅ | | | | 考试管理:AI 出题/组卷/批改/监考/分析 |
|
||||||
|
| 14 | files | `files/` | ✅ | ✅ | ✅ | ✅ | ✅ | 文件管理:上传/预览/存储/权限控制 |
|
||||||
|
| 15 | grades | `grades/` | ✅ | ✅ | ✅ | ✅ | ✅ | 成绩管理:录入/查询/统计/趋势/导出/排名 |
|
||||||
|
| 16 | homework | `homework/` | | ✅ | ✅ | | | 作业管理:布置/提交/批改/评分/统计分析 |
|
||||||
|
| 17 | layout | `layout/` | ✅ | ✅ | ✅ | ✅ | ✅ | 布局框架:导航配置/侧边栏/顶部栏(所有角色共用) |
|
||||||
|
| 18 | lesson-preparation | `lesson-preparation/` | ✅ | ✅ | ✅ | ✅ | | 备课系统:教案创建/编辑/发布/查看 |
|
||||||
|
| 19 | messaging | `messaging/` | ✅ | ✅ | ✅ | ✅ | ✅ | 站内消息:私信/群发/草稿/已读状态 |
|
||||||
|
| 20 | notifications | `notifications/` | ✅ | ✅ | ✅ | ✅ | ✅ | 通知系统:站内/邮件/短信/微信多渠道推送、偏好管理 |
|
||||||
|
| 21 | onboarding | `onboarding/` | ✅ | ✅ | ✅ | ✅ | ✅ | 新手引导:首次登录角色选择、资料填写、班级加入 |
|
||||||
|
| 22 | parent | `parent/` | | | | ✅ | | 家长聚合:子女学习概览、成绩/作业/考勤详情 |
|
||||||
|
| 23 | proctoring | `proctoring/` | | ✅ | | | | 考试监考:实时提交进度、异常行为标记 |
|
||||||
|
| 24 | questions | `questions/` | ✅ | ✅ | | | | 题库管理:题目 CRUD、分类标签、批量导入导出 |
|
||||||
|
| 25 | rbac | `rbac/` | ✅ | | | | | 角色权限:动态角色管理、权限目录、角色-权限分配 |
|
||||||
|
| 26 | scheduling | `scheduling/` | ✅ | ✅ | ✅ | | | 排课系统:自动排课引擎、课表查看、调课/代课 |
|
||||||
|
| 27 | school | `school/` | ✅ | | | | | 学校管理:学校信息/学年学期/年级/班级/部门/学科 |
|
||||||
|
| 28 | settings | `settings/` | ✅ | ✅ | ✅ | ✅ | ✅ | 用户设置:个人信息/通知偏好/外观/安全/登出 |
|
||||||
|
| 29 | standards | `standards/` | (via) | (via) | | | | 课标库:国家/校标/自定义课标,通过 lesson-preparation 使用 |
|
||||||
|
| 30 | student | `student/` | | | ✅ | | | 学生聚合:课程查看/课表/学习路径 |
|
||||||
|
| 31 | textbooks | `textbooks/` | (via) | ✅ | ✅ | | | 教材资源:教材库/章节结构/知识点图谱/内容阅读 |
|
||||||
|
| 32 | users | `users/` | ✅ | | | | | 用户管理:用户 CRUD、批量导入、角色分配 |
|
||||||
|
|
||||||
|
> 注:`✅` = 有独立页面路由;`(via)` = 通过其他模块间接使用,无独立路由页面
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、按角色展开的模块清单
|
||||||
|
|
||||||
|
### 2.1 admin(系统管理员)
|
||||||
|
|
||||||
|
| 模块 | 页面路由 | 权限点 | 功能说明 |
|
||||||
|
|------|----------|--------|----------|
|
||||||
|
| **school** | `/admin/school` | `SCHOOL_MANAGE` | 学校基础信息配置 |
|
||||||
|
| | `/admin/school/schools` | `SCHOOL_MANAGE` | 多校区管理 |
|
||||||
|
| | `/admin/school/academic-year` | `SCHOOL_MANAGE` | 学年学期管理 |
|
||||||
|
| | `/admin/school/classes` | `SCHOOL_MANAGE` | 班级创建与管理 |
|
||||||
|
| | `/admin/school/departments` | `SCHOOL_MANAGE` | 部门管理 |
|
||||||
|
| | `/admin/school/grades` | `SCHOOL_MANAGE` | 年级管理与组长指派 |
|
||||||
|
| | `/admin/school/grades/insights` | `SCHOOL_MANAGE` | 年级洞察分析 |
|
||||||
|
| **users** | `/admin/users` | `USER_MANAGE` | 用户账号管理 |
|
||||||
|
| | `/admin/users/import` | `USER_MANAGE` | 批量导入用户 |
|
||||||
|
| **rbac** | `/admin/roles` | `ROLE_READ` | 角色列表查看 |
|
||||||
|
| | `/admin/roles/[id]` | `ROLE_READ` | 角色详情与权限编辑 |
|
||||||
|
| | `/admin/permissions` | `PERMISSION_READ` | 权限点目录查看 |
|
||||||
|
| **announcements** | `/admin/announcements` | `ANNOUNCEMENT_MANAGE` | 公告管理(创建/编辑/删除) |
|
||||||
|
| | `/admin/announcements/[id]` | `ANNOUNCEMENT_MANAGE` | 公告详情 |
|
||||||
|
| | `/admin/announcements/[id]/edit` | `ANNOUNCEMENT_MANAGE` | 编辑公告 |
|
||||||
|
| **audit** | `/admin/audit-logs` | `AUDIT_LOG_READ` | 操作日志查看 |
|
||||||
|
| | `/admin/audit-logs/overview` | `AUDIT_LOG_READ` | 审计概览统计 |
|
||||||
|
| | `/admin/audit-logs/data-changes` | `AUDIT_LOG_READ` | 数据变更日志 |
|
||||||
|
| | `/admin/audit-logs/login-logs` | `AUDIT_LOG_READ` | 登录日志 |
|
||||||
|
| **dashboard** | `/admin/dashboard` | `DASHBOARD_ADMIN_READ` | 管理员仪表盘 |
|
||||||
|
| **scheduling** | `/admin/scheduling/auto` | `SCHEDULE_AUTO` | 自动排课 |
|
||||||
|
| | `/admin/scheduling/changes` | `SCHEDULE_ADJUST` | 调课管理 |
|
||||||
|
| | `/admin/scheduling/rules` | `SCHEDULE_ADJUST` | 排课规则配置 |
|
||||||
|
| **course-plans** | `/admin/course-plans` | `COURSE_PLAN_READ` | 课程计划查看 |
|
||||||
|
| | `/admin/course-plans/create` | `COURSE_PLAN_MANAGE` | 创建课程计划 |
|
||||||
|
| | `/admin/course-plans/[id]` | `COURSE_PLAN_READ` | 课程计划详情 |
|
||||||
|
| | `/admin/course-plans/[id]/edit` | `COURSE_PLAN_MANAGE` | 编辑课程计划 |
|
||||||
|
| **elective** | `/admin/elective` | `ELECTIVE_READ` | 选课管理 |
|
||||||
|
| | `/admin/elective/create` | `ELECTIVE_MANAGE` | 创建选修课 |
|
||||||
|
| | `/admin/elective/[id]` | `ELECTIVE_READ` | 选修课详情 |
|
||||||
|
| | `/admin/elective/[id]/edit` | `ELECTIVE_MANAGE` | 编辑选修课 |
|
||||||
|
| **attendance** | `/admin/attendance` | `ATTENDANCE_READ` | 考勤数据查看 |
|
||||||
|
| **error-book** | `/admin/error-book` | `ERROR_BOOK_ANALYTICS_READ` | 全校错题统计分析 |
|
||||||
|
| **questions** | `/admin/questions` | `QUESTION_READ` | 题库管理 |
|
||||||
|
| **files** | `/admin/files` | `FILE_READ` | 文件管理 |
|
||||||
|
| **ai** | `/admin/ai-settings` | `AI_CHAT` | AI 多模型配置 |
|
||||||
|
| **lesson-plans** | `/admin/lesson-plans` | `LESSON_PLAN_READ` | 教案查看 |
|
||||||
|
| | `/admin/lesson-plans/[planId]/view` | `LESSON_PLAN_READ` | 教案详情 |
|
||||||
|
| **settings** | `/admin/settings` | `SETTINGS_ADMIN` | 管理员设置 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.2 teacher(教师)
|
||||||
|
|
||||||
|
| 模块 | 页面路由 | 权限点 | 功能说明 |
|
||||||
|
|------|----------|--------|----------|
|
||||||
|
| **dashboard** | `/teacher/dashboard` | `DASHBOARD_TEACHER_READ` | 教师仪表盘(待批改/今日课表/班级动态) |
|
||||||
|
| **classes** | `/teacher/classes/my` | `CLASS_READ` | 我的班级列表 |
|
||||||
|
| | `/teacher/classes/my/[id]` | `CLASS_READ` | 班级详情 |
|
||||||
|
| | `/teacher/classes/schedule` | `CLASS_READ` | 班级课表 |
|
||||||
|
| | `/teacher/classes/students` | `CLASS_READ` | 班级学生管理 |
|
||||||
|
| **exams** | `/teacher/exams` | `EXAM_READ` | 考试列表 |
|
||||||
|
| | `/teacher/exams/all` | `EXAM_READ` | 全部考试 |
|
||||||
|
| | `/teacher/exams/create` | `EXAM_CREATE` | 创建考试 |
|
||||||
|
| | `/teacher/exams/new` | `EXAM_CREATE` | AI 出题 |
|
||||||
|
| | `/teacher/exams/[id]/build` | `EXAM_READ` | 组卷 |
|
||||||
|
| | `/teacher/exams/[id]/edit-rich` | `EXAM_UPDATE` | 富文本编辑试卷 |
|
||||||
|
| | `/teacher/exams/[id]/analytics` | `EXAM_READ` | 考试分析 |
|
||||||
|
| | `/teacher/exams/[id]/proctoring` | `EXAM_PROCTOR` | 考试监考 |
|
||||||
|
| | `/teacher/exams/grading` | `HOMEWORK_GRADE` | 批改列表 |
|
||||||
|
| | `/teacher/exams/grading/[submissionId]` | `HOMEWORK_GRADE` | 批改详情 |
|
||||||
|
| **homework** | `/teacher/homework/assignments` | `HOMEWORK_CREATE` | 作业管理 |
|
||||||
|
| | `/teacher/homework/assignments/create` | `HOMEWORK_CREATE` | 布置作业 |
|
||||||
|
| | `/teacher/homework/assignments/[id]` | `HOMEWORK_CREATE` | 作业详情 |
|
||||||
|
| | `/teacher/homework/assignments/[id]/submissions` | `HOMEWORK_GRADE` | 提交列表 |
|
||||||
|
| | `/teacher/homework/submissions` | `HOMEWORK_GRADE` | 批改列表 |
|
||||||
|
| | `/teacher/homework/submissions/[submissionId]` | `HOMEWORK_GRADE` | 批改详情 |
|
||||||
|
| | `/teacher/homework/submissions/[submissionId]/scan-grading` | `HOMEWORK_GRADE` | 阅卷式批改 |
|
||||||
|
| **grades** | `/teacher/grades` | `GRADE_RECORD_READ` | 成绩查询 |
|
||||||
|
| | `/teacher/grades/analytics` | `GRADE_RECORD_READ` | 成绩分析 |
|
||||||
|
| | `/teacher/grades/entry` | `GRADE_RECORD_MANAGE` | 成绩录入 |
|
||||||
|
| | `/teacher/grades/stats` | `GRADE_RECORD_READ` | 成绩统计 |
|
||||||
|
| **questions** | `/teacher/questions` | `QUESTION_READ` | 题库管理 |
|
||||||
|
| **textbooks** | `/teacher/textbooks` | `TEXTBOOK_READ` | 教材查看 |
|
||||||
|
| | `/teacher/textbooks/[id]` | `TEXTBOOK_READ` | 教材内容 |
|
||||||
|
| **lesson-plans** | `/teacher/lesson-plans` | `LESSON_PLAN_READ` | 教案列表 |
|
||||||
|
| | `/teacher/lesson-plans/new` | `LESSON_PLAN_READ` | 创建教案 |
|
||||||
|
| | `/teacher/lesson-plans/[planId]/edit` | `LESSON_PLAN_READ` | 编辑教案 |
|
||||||
|
| **diagnostic** | `/teacher/diagnostic` | `DIAGNOSTIC_READ` | 学情诊断总览 |
|
||||||
|
| | `/teacher/diagnostic/class/[classId]` | `DIAGNOSTIC_READ` | 班级诊断 |
|
||||||
|
| | `/teacher/diagnostic/student/[studentId]` | `DIAGNOSTIC_READ` | 学生诊断 |
|
||||||
|
| **attendance** | `/teacher/attendance` | `ATTENDANCE_READ` | 考勤查看 |
|
||||||
|
| | `/teacher/attendance/sheet` | `ATTENDANCE_MANAGE` | 考勤登记 |
|
||||||
|
| | `/teacher/attendance/stats` | `ATTENDANCE_READ` | 考勤统计 |
|
||||||
|
| **course-plans** | `/teacher/course-plans` | `COURSE_PLAN_READ` | 课程计划 |
|
||||||
|
| | `/teacher/course-plans/[id]` | `COURSE_PLAN_READ` | 计划详情 |
|
||||||
|
| **elective** | `/teacher/elective` | `ELECTIVE_READ` | 选课管理 |
|
||||||
|
| | `/teacher/elective/create` | `ELECTIVE_MANAGE` | 创建选修课 |
|
||||||
|
| | `/teacher/elective/[id]/edit` | `ELECTIVE_MANAGE` | 编辑选修课 |
|
||||||
|
| **error-book** | `/teacher/error-book` | `ERROR_BOOK_ANALYTICS_READ` | 班级错题分析 |
|
||||||
|
| **practice** | `/teacher/practice` | `ADAPTIVE_PRACTICE_READ` | 专项练习统计 |
|
||||||
|
| **schedule** | `/teacher/schedule-changes` | `SCHEDULE_ADJUST` | 调课申请 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.3 student(学生)
|
||||||
|
|
||||||
|
| 模块 | 页面路由 | 权限点 | 功能说明 |
|
||||||
|
|------|----------|--------|----------|
|
||||||
|
| **dashboard** | `/student/dashboard` | `DASHBOARD_STUDENT_READ` | 学生仪表盘(作业/考试/成绩趋势) |
|
||||||
|
| **learning** | `/student/learning` | `CLASS_READ` | 学习中心 |
|
||||||
|
| | `/student/learning/assignments` | — | 作业列表 |
|
||||||
|
| | `/student/learning/assignments/[assignmentId]` | — | 作答 |
|
||||||
|
| | `/student/learning/assignments/[assignmentId]/result` | — | 作答结果 |
|
||||||
|
| | `/student/learning/courses` | `CLASS_READ` | 课程列表 |
|
||||||
|
| | `/student/learning/courses/[classId]` | `CLASS_READ` | 课程详情 |
|
||||||
|
| | `/student/learning/textbooks` | `TEXTBOOK_READ` | 教材 |
|
||||||
|
| | `/student/learning/textbooks/[id]` | `TEXTBOOK_READ` | 教材内容 |
|
||||||
|
| | `/student/learning/study-path` | `AI_CHAT` | AI 学习路径 |
|
||||||
|
| **grades** | `/student/grades` | `GRADE_RECORD_READ` | 成绩查询 |
|
||||||
|
| **error-book** | `/student/error-book` | `ERROR_BOOK_READ` | 我的错题本 |
|
||||||
|
| **practice** | `/student/practice` | `ADAPTIVE_PRACTICE_READ` | 专项练习 |
|
||||||
|
| | `/student/practice/[sessionId]` | `ADAPTIVE_PRACTICE_READ` | 练习对话 |
|
||||||
|
| **diagnostic** | `/student/diagnostic` | `DIAGNOSTIC_READ` | 学情诊断 |
|
||||||
|
| **elective** | `/student/elective` | `ELECTIVE_READ` | 选课 |
|
||||||
|
| | `/student/elective/[id]` | `ELECTIVE_READ` | 课程详情 |
|
||||||
|
| **schedule** | `/student/schedule` | `CLASS_READ` | 课表查看 |
|
||||||
|
| **attendance** | `/student/attendance` | `ATTENDANCE_READ` | 考勤查询 |
|
||||||
|
| **course-plans** | `/student/course-plans` | `COURSE_PLAN_READ` | 课程计划 |
|
||||||
|
| | `/student/course-plans/[id]` | `COURSE_PLAN_READ` | 计划详情 |
|
||||||
|
| **lesson-plans** | `/student/lesson-plans` | `LESSON_PLAN_READ` | 教案查看 |
|
||||||
|
| | `/student/lesson-plans/[planId]/view` | `LESSON_PLAN_READ` | 教案详情 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.4 parent(家长)
|
||||||
|
|
||||||
|
| 模块 | 页面路由 | 权限点 | 功能说明 |
|
||||||
|
|------|----------|--------|----------|
|
||||||
|
| **dashboard** | `/parent/dashboard` | `DASHBOARD_PARENT_READ` | 家长仪表盘(子女学习概况) |
|
||||||
|
| **children** | `/parent/children/[studentId]` | — | 子女详情聚合页 |
|
||||||
|
| **grades** | `/parent/grades` | `GRADE_RECORD_READ` | 子女成绩查询 |
|
||||||
|
| **error-book** | `/parent/error-book` | `ERROR_BOOK_READ` | 子女错题本 |
|
||||||
|
| **diagnostic** | `/parent/diagnostic` | `DIAGNOSTIC_READ` | 子女学情诊断 |
|
||||||
|
| **elective** | `/parent/elective` | `ELECTIVE_READ` | 子女选课查看 |
|
||||||
|
| **attendance** | `/parent/attendance` | `ATTENDANCE_READ` | 子女考勤查询 |
|
||||||
|
| **course-plans** | `/parent/course-plans` | `COURSE_PLAN_READ` | 课程计划 |
|
||||||
|
| | `/parent/course-plans/[id]` | `COURSE_PLAN_READ` | 计划详情 |
|
||||||
|
| **lesson-plans** | `/parent/lesson-plans` | `LESSON_PLAN_READ` | 教案查看 |
|
||||||
|
| | `/parent/lesson-plans/[planId]/view` | `LESSON_PLAN_READ` | 教案详情 |
|
||||||
|
| **practice** | `/parent/practice` | `ADAPTIVE_PRACTICE_READ` | 子女练习记录 |
|
||||||
|
| **leave** | `/parent/leave` | — | 请假申请 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.5 management(年级组长/教研组长)
|
||||||
|
|
||||||
|
| 模块 | 页面路由 | 权限点 | 功能说明 |
|
||||||
|
|------|----------|--------|----------|
|
||||||
|
| **grade** | `/management/grade` | `GRADE_MANAGE` | 年级管理首页 |
|
||||||
|
| | `/management/grade/dashboard` | `GRADE_RECORD_READ` | 年级仪表盘 |
|
||||||
|
| | `/management/grade/insights` | `GRADE_RECORD_READ` | 年级洞察分析 |
|
||||||
|
| | `/management/grade/classes` | `GRADE_MANAGE` | 年级班级管理 |
|
||||||
|
| | `/management/grade/practice` | `ADAPTIVE_PRACTICE_READ` | 年级练习统计 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、共享模块(所有角色可访问)
|
||||||
|
|
||||||
|
以下模块为所有已登录用户提供服务,不区分角色路由:
|
||||||
|
|
||||||
|
| 模块 | 路由 | 权限点 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| **auth** | `/login`, `/register` | 公开 | 认证(登录/注册) |
|
||||||
|
| **onboarding** | `/onboarding` | 已登录 | 新手引导(首次登录) |
|
||||||
|
| **announcements** | `/announcements`, `/announcements/[id]` | `ANNOUNCEMENT_READ` | 公告查看(所有角色) |
|
||||||
|
| **messages** | `/messages`, `/messages/[id]`, `/messages/compose` | `MESSAGE_READ` / `MESSAGE_SEND` | 站内消息 |
|
||||||
|
| **profile** | `/profile` | `USER_PROFILE_UPDATE` | 个人资料 |
|
||||||
|
| **settings** | `/settings` | `USER_PROFILE_UPDATE` | 用户设置(通知/外观/安全) |
|
||||||
|
| **dashboard** | `/dashboard` | 角色自动路由 | 仪表盘(自动重定向到角色对应仪表盘) |
|
||||||
|
| **layout** | 所有路由 | — | 全局布局框架(侧边栏/顶部栏/导航配置) |
|
||||||
|
| **notifications** | 全局组件 | — | 通知弹窗/偏好管理(内嵌于 layout) |
|
||||||
|
| **files** | 全局上传 | `FILE_UPLOAD` | 文件上传(所有角色可上传,admin 可管理) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、权限点汇总(67 个)
|
||||||
|
|
||||||
|
| 权限分类 | 权限点 | 适用角色 |
|
||||||
|
|----------|--------|----------|
|
||||||
|
| **考试** | `exam:create/create/read/update/delete/duplicate/publish/ai_generate/submit` | teacher |
|
||||||
|
| | `exam:proctor/read` | teacher |
|
||||||
|
| **作业** | `homework:create/grade` | teacher |
|
||||||
|
| | `homework:submit` | student |
|
||||||
|
| **题库** | `question:create/read/update/delete` | admin, teacher |
|
||||||
|
| **教材** | `textbook:create/read/update/delete` | admin, teacher |
|
||||||
|
| **班级** | `class:create/read/update/delete/enroll/schedule` | admin, teacher, student |
|
||||||
|
| **学校管理** | `school:manage` | admin |
|
||||||
|
| | `grade:manage` | admin, grade_head |
|
||||||
|
| | `user:manage` | admin |
|
||||||
|
| **成绩** | `grade_record:manage/read` | admin, teacher, student, parent |
|
||||||
|
| **考勤** | `attendance:manage/read` | admin, teacher, student, parent |
|
||||||
|
| **课程计划** | `course_plan:manage/read` | admin, teacher, student, parent |
|
||||||
|
| **公告** | `announcement:manage` | admin |
|
||||||
|
| | `announcement:read` | all |
|
||||||
|
| **消息** | `message:send/read/delete` | all |
|
||||||
|
| **排课** | `schedule:auto/adjust` | admin, teacher |
|
||||||
|
| **选课** | `elective:manage/read/select` | admin, teacher, student, parent |
|
||||||
|
| **诊断** | `diagnostic:manage/read` | admin, teacher, student, parent |
|
||||||
|
| **备课** | `lesson_plan:create/read/update/delete/publish` | admin, teacher, student, parent |
|
||||||
|
| **仪表盘** | `dashboard:admin_read/teacher_read/student_read/parent_read` | 各角色独立 |
|
||||||
|
| **错题本** | `error_book:read/manage` | student, parent |
|
||||||
|
| | `error_book:analytics_read` | admin, teacher |
|
||||||
|
| **专项练习** | `adaptive_practice:read/manage` | student, teacher, parent, grade_head |
|
||||||
|
| **AI** | `ai:chat` | all |
|
||||||
|
| | `ai:configure` | admin |
|
||||||
|
| **文件** | `file:upload/read/delete` | all(upload),admin(delete) |
|
||||||
|
| **审计** | `audit_log:read` | admin |
|
||||||
|
| **RBAC** | `role:create/read/update/delete/assign` | admin |
|
||||||
|
| | `permission:read` | admin |
|
||||||
|
| **设置** | `settings:admin` | admin |
|
||||||
|
| | `user:profile_update` | all |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、模块依赖关系速查
|
||||||
|
|
||||||
|
| 模块 | 依赖模块(通过 data-access) | 被依赖模块 |
|
||||||
|
|------|------------------------------|-----------|
|
||||||
|
| **exams** | questions, classes, school, homework | homework, dashboard, proctoring, diagnostic, grades |
|
||||||
|
| **homework** | exams, classes, school, users | grades, dashboard, diagnostic |
|
||||||
|
| **grades** | exams, classes, school, users | dashboard, parent |
|
||||||
|
| **classes** | homework, scheduling | exams, homework, grades, scheduling, attendance, dashboard |
|
||||||
|
| **dashboard** | users, classes, textbooks, questions, exams, homework | — |
|
||||||
|
| **parent** | classes, homework, grades | — |
|
||||||
|
| **diagnostic** | exams, questions, classes, users | — |
|
||||||
|
| **lesson-preparation** | textbooks, questions, exams, homework, classes, files | — |
|
||||||
|
| **messaging** | notifications | settings |
|
||||||
|
| **scheduling** | classes, users | — |
|
||||||
|
| **proctoring** | exams, users | — |
|
||||||
|
| **school** | — | exams, classes, grades, scheduling, attendance |
|
||||||
|
| **textbooks** | — | questions, exams, homework, lesson-preparation |
|
||||||
|
| **users** | — | all modules |
|
||||||
|
| **rbac** | — | admin |
|
||||||
|
| **audit** | — | all modules (via shared/audit-logger) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、维护说明
|
||||||
|
|
||||||
|
- 新增模块时,需同步更新本文档的模块-角色速查表(第一节)和对应角色的详细清单(第二节)
|
||||||
|
- 新增权限点时,需同步更新权限点汇总(第四节)
|
||||||
|
- 模块依赖关系变更时,需同步更新依赖关系速查(第五节)
|
||||||
|
- 路由变更时,需同步更新对应角色的路由表
|
||||||
4901
docs/architecture/audit/archive/004_architecture_impact_map_v1.md
Normal file
10
docs/architecture/audit/archive/README.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# 历史审查报告归档
|
||||||
|
|
||||||
|
> 本目录为只读归档,不再更新。
|
||||||
|
> 有价值的内容已提取到模块 README 和 known-issues.md。
|
||||||
|
|
||||||
|
## 归档文件
|
||||||
|
|
||||||
|
- 005_architecture_data.json(已废弃,由 arch.db 替代)
|
||||||
|
- 60+ 份模块审查报告(历史参考)
|
||||||
|
- data-access-audit-v1 系列文件(数据访问层审查)
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
# 数据库访问层重构专项 - 审计框架 v1
|
||||||
|
|
||||||
|
> 创建日期:2026-07-07
|
||||||
|
> 目标:对全项目 86 个 `data-access*.ts` + ~30 个 `actions.ts` 进行深度审计,输出可执行的分级治理路线图
|
||||||
|
> 推进路径:先审计后治理(用户已确认)
|
||||||
|
> 执行方案:纯深读(用户已确认方案 B)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、审计范围
|
||||||
|
|
||||||
|
### 1.1 文件范围
|
||||||
|
|
||||||
|
| 类型 | 路径模式 | 文件数(约) | 备注 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 数据访问层 | `src/modules/**/data-access*.ts` | 86 | 主审计对象 |
|
||||||
|
| Server Actions | `src/modules/**/actions.ts` | ~30 | 辅查(权限校验、业务逻辑归属) |
|
||||||
|
| 辅助文件 | `src/modules/**/schema.ts`、`types.ts` | 按需 | 仅当 data-access 引用时查看 |
|
||||||
|
|
||||||
|
### 1.2 排除范围
|
||||||
|
|
||||||
|
- `src/app/**`:仅在 A-05 规则(app 直访 DB)触发时反向查看
|
||||||
|
- `src/shared/**`:仅在 A-07 规则(shared 反向依赖)触发时查看
|
||||||
|
- 已有的模块级 audit 报告(`docs/architecture/audit/*-audit-report.md`):作为参考但不直接复用,因本次为横切关注点
|
||||||
|
|
||||||
|
### 1.3 模块分组(并行执行单元)
|
||||||
|
|
||||||
|
| 组 | 模块 | data-access 文件数 | sub-agent |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **G1 核心教学 A** | lesson-preparation(12)+ questions + textbooks | ~16 | agent-1 |
|
||||||
|
| **G2 核心教学 B** | exams + homework(7)+ grades(6)+ diagnostic + adaptive-practice(3) | ~21 | agent-2 |
|
||||||
|
| **G3 教学管理** | classes(6)+ school + scheduling + attendance(3)+ course-plans + proctoring | ~16 | agent-3 |
|
||||||
|
| **G4 用户与沟通** | users + messaging + notifications + parent + audit + auth + rbac(3) | ~12 | agent-4 |
|
||||||
|
| **G5 扩展与设置** | elective(5)+ settings(5)+ dashboard + files + search + onboarding + ai + announcements + error-book(3) | ~21 | agent-5 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、审计维度与检查规则
|
||||||
|
|
||||||
|
### 2.1 维度 1:模式标准化(Pattern Standardization)
|
||||||
|
|
||||||
|
| 规则 ID | 检查项 | 期望状态 | 检测方式 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| P-01 | `import "server-only"` 文件头 | 每个文件首行 | 静态 |
|
||||||
|
| P-02 | 类型导入使用 `import type` | 类型导入与值导入分离 | 静态 |
|
||||||
|
| P-03 | 读函数是否走 `cacheFn` 包装(Raw + Wrapper 配对) | 全部覆盖 | 深读 |
|
||||||
|
| P-04 | 函数返回类型显式标注 `Promise<T>` | 无隐式推断 | 深读 |
|
||||||
|
| P-05 | 错误处理一致 | data-access 层用 throw,actions 层用 ActionState | 深读 |
|
||||||
|
| P-06 | 分页参数命名统一 | `page`/`pageSize` 或 `limit`/`offset` 全局统一 | 深读 |
|
||||||
|
| P-07 | 日期序列化走 helper | `serializeDate`/`toISODateString` | 深读 |
|
||||||
|
| P-08 | 列表项映射走 `mapListItem` 模式 | 避免 inline mapping 重复 | 深读 |
|
||||||
|
| P-09 | `as` 断言出现次数 | 0(除 unknown 收窄) | 静态 |
|
||||||
|
| P-10 | `any` 出现次数 | 0 | 静态 |
|
||||||
|
|
||||||
|
### 2.2 维度 2:性能与查询优化(Performance)
|
||||||
|
|
||||||
|
| 规则 ID | 检查项 | 期望状态 | 检测方式 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| F-01 | 循环内 SQL 调用(N+1) | 改批量查询 + Map 解析 | 深读 |
|
||||||
|
| F-02 | `LIKE '%xxx%'` 全表扫描 | 改 FULLTEXT 或前缀匹配 | 深读 |
|
||||||
|
| F-03 | SELECT * 未指定列 | 显式列枚举 | 深读 |
|
||||||
|
| F-04 | JOIN 表数量 > 3 | 评估拆分或冗余字段 | 深读 |
|
||||||
|
| F-05 | 大表查询无 LIMIT | 添加默认 LIMIT | 深读 |
|
||||||
|
| F-06 | 重复查询同表/同条件 | 走 cacheFn 或合并查询 | 深读 |
|
||||||
|
| F-07 | 缺失索引(高频 WHERE 字段) | 提示加索引 | 深读 |
|
||||||
|
| F-08 | 跨模块多次调用 `getXxxNamesByIds` | 批量化 | 深读 |
|
||||||
|
| F-09 | 事务范围过大(含网络调用) | 收紧事务 | 深读 |
|
||||||
|
| F-10 | `count()` 全表统计无过滤 | 添加过滤条件 | 深读 |
|
||||||
|
|
||||||
|
### 2.3 维度 3:架构违规治理(Architecture)
|
||||||
|
|
||||||
|
| 规则 ID | 检查项 | 期望状态 | 检测方式 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| A-01 | data-access 含 `requirePermission` 调用 | 移至 actions | 静态 |
|
||||||
|
| A-02 | data-access 含业务逻辑(条件分支、状态机) | 移至 actions 或 lib | 深读 |
|
||||||
|
| A-03 | data-access 含 `"use server"` 标记 | 移至 actions | 静态 |
|
||||||
|
| A-04 | data-access 含 `revalidatePath` 调用 | 移至 actions | 静态 |
|
||||||
|
| A-05 | app/ 直接 import `@/shared/db` | 违规,改走 data-access | 静态 |
|
||||||
|
| A-06 | modules 间直接 import 对方 `@/shared/db/schema` 表 | 改走对方 data-access | 静态 |
|
||||||
|
| A-07 | shared/ 反向 import `@/auth`/`@/proxy`/`modules/*` | 违规 | 静态 |
|
||||||
|
| A-08 | actions.ts 漏调 `requirePermission` | 补齐 | 深读 |
|
||||||
|
| A-09 | actions.ts 含直接 DB 查询 | 移至 data-access | 深读 |
|
||||||
|
| A-10 | data-access 含 `console.log` 调试代码 | 删除 | 静态 |
|
||||||
|
|
||||||
|
### 2.4 维度 4:结构与可维护性(Structure)
|
||||||
|
|
||||||
|
| 规则 ID | 检查项 | 期望状态 | 检测方式 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| S-01 | 文件行数 > 800 行警告,> 1000 行必须拆分 | 拆分 | 静态 |
|
||||||
|
| S-02 | 单文件导出函数数 > 20 | 警告,考虑拆分 | 静态 |
|
||||||
|
| S-03 | 重复 helper(多模块各自实现 serializeDate/buildScopeFilter 等) | 提取到 shared/lib | 深读 |
|
||||||
|
| S-04 | 过细拆分(同模块 ≥ 5 个子文件且单文件 < 100 行) | 评估合并 | 静态 |
|
||||||
|
| S-05 | 未使用导出(dead code) | 删除 | 深读 |
|
||||||
|
| S-06 | 公共导出函数缺 JSDoc | 补齐 | 深读 |
|
||||||
|
| S-07 | 跨模块重复查询逻辑 | 提取共享 data-access | 深读 |
|
||||||
|
| S-08 | 模块内 data-access 与 actions 职责混淆 | 重新分层 | 深读 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、严重性分级
|
||||||
|
|
||||||
|
| 级别 | 含义 | 示例 | 治理窗口 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **P0 Critical** | 架构硬违规、安全漏洞、必定性能问题 | app 直访 DB、跨模块 schema 直查、actions 漏权限、N+1 循环 SQL | 立即 |
|
||||||
|
| **P1 High** | 显著性能/可维护性问题 | 超长文件(>1000 行)、缺 cacheFn 的热路径读函数、LIKE 全表扫描 | Phase 1 |
|
||||||
|
| **P2 Medium** | 模式偏差、可优化 | 错误处理不一致、缺 JSDoc、重复 helper、分页命名不统一 | Phase 2 |
|
||||||
|
| **P3 Low** | 风格问题、可选优化 | 单行格式、import 顺序、注释措辞 | Phase 3 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、执行流程
|
||||||
|
|
||||||
|
### 4.1 阶段 A:sub-agent 分组深读(并行)
|
||||||
|
|
||||||
|
每个 sub-agent 接收:
|
||||||
|
- 该组所有 `data-access*.ts` + 同模块 `actions.ts` 文件清单
|
||||||
|
- 完整规则表(4 维度 × 38 条规则)
|
||||||
|
- 统一输出格式(见 4.3)
|
||||||
|
|
||||||
|
每个 sub-agent 执行:
|
||||||
|
1. 完整读取每个文件(不使用 limit/offset)
|
||||||
|
2. 按规则表逐条检测
|
||||||
|
3. 命中即记录到问题清单
|
||||||
|
4. 对每个问题给出修复建议与预估工作量
|
||||||
|
|
||||||
|
### 4.2 阶段 B:主 agent 汇总
|
||||||
|
|
||||||
|
- 收集 5 个 sub-agent 的结构化输出
|
||||||
|
- 去重(同一问题被多 agent 命中时合并)
|
||||||
|
- 跨模块统计(如重复 helper 在多少模块出现)
|
||||||
|
- 生成优先级矩阵
|
||||||
|
- 编写治理路线图
|
||||||
|
|
||||||
|
### 4.3 sub-agent 输出格式
|
||||||
|
|
||||||
|
每个 sub-agent 产出 JSON 数组,每条问题:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "G1-001",
|
||||||
|
"file": "src/modules/lesson-preparation/data-access.ts",
|
||||||
|
"lines": "L123-L145",
|
||||||
|
"ruleId": "F-01",
|
||||||
|
"severity": "P0",
|
||||||
|
"dimension": "performance",
|
||||||
|
"title": "循环内调用 getClassNamesByIds",
|
||||||
|
"description": "在 for 循环内对每个 classId 单独查询 className,应改为批量查询后用 Map 解析",
|
||||||
|
"recommendation": "提取 classIds 数组,一次调用 getClassNamesByIds(classIds),循环内改为 map.get(classId)",
|
||||||
|
"effort": "S (≤30 分钟)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
工作量分级:
|
||||||
|
- **XS**:≤ 15 分钟(如删除 console.log、补 import type)
|
||||||
|
- **S**:≤ 30 分钟(如替换 as 断言为类型守卫)
|
||||||
|
- **M**:≤ 2 小时(如 N+1 改批量、提取 helper)
|
||||||
|
- **L**:≤ 1 天(如拆分超长文件、跨模块重构)
|
||||||
|
- **XL**:> 1 天(如架构层重构)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、报告输出
|
||||||
|
|
||||||
|
### 5.1 主报告
|
||||||
|
|
||||||
|
文件:`docs/architecture/audit/data-access-audit-v1.md`
|
||||||
|
|
||||||
|
结构:
|
||||||
|
1. **执行摘要**:总文件数、问题总数、P0/P1/P2/P3 分布、模块热度图
|
||||||
|
2. **量化指标仪表盘**:cacheFn 覆盖率、平均行数、`as` 断言数、违规 import 数等
|
||||||
|
3. **按维度分组的问题清单**:每条含 文件:行号、规则 ID、严重性、现状描述、修复建议、预估工作量
|
||||||
|
4. **按模块分组的问题清单**:每个模块的累计问题数与 Top 问题
|
||||||
|
5. **P0-P3 优先级矩阵**:四象限图(影响 × 紧迫度)
|
||||||
|
6. **分阶段治理路线图**:Phase 1 (P0) → Phase 2 (P1) → Phase 3 (P2) → Phase 4 (P3)
|
||||||
|
7. **附录**:完整规则表、sub-agent 原始输出索引
|
||||||
|
|
||||||
|
### 5.2 结构化数据
|
||||||
|
|
||||||
|
文件:`docs/architecture/audit/data-access-audit-v1-data.json`
|
||||||
|
|
||||||
|
字段:`issues[]`、`metrics{}`、`moduleSummary{}`、`roadmap{}`
|
||||||
|
|
||||||
|
### 5.3 速查手册同步
|
||||||
|
|
||||||
|
发现的新模式问题需追加到 `docs/troubleshooting/known-issues.md`(速查手册格式)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、质量约束
|
||||||
|
|
||||||
|
- **零误报**:每条问题必须给出文件:行号 + 代码证据,避免臆测
|
||||||
|
- **零遗漏**:86 个 data-access 文件必须全部深读,不得抽样
|
||||||
|
- **可执行**:每条修复建议必须具体到代码示例或操作步骤
|
||||||
|
- **不修改代码**:审计阶段只产出报告,不做任何源码修改
|
||||||
|
- **架构同步**:审计过程中发现的架构图遗漏(004/005 文档)记录到报告附录,治理阶段统一补图
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、后续衔接
|
||||||
|
|
||||||
|
审计报告 v1 完成后:
|
||||||
|
|
||||||
|
1. **用户审查报告**:确认问题清单与优先级
|
||||||
|
2. **制定治理路线图**:基于 P0-P3 分级,输出 `data-access-refactor-roadmap-v1.md`
|
||||||
|
3. **分阶段执行治理**:每阶段完成后运行 `npm run lint` + `npx tsc --noEmit` 验证
|
||||||
|
4. **同步架构文档**:每阶段完成后同步 004/005 文档与 known-issues.md
|
||||||
171
docs/architecture/audit/archive/data-access-audit-v1-data.json
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
{
|
||||||
|
"version": "v1",
|
||||||
|
"createdAt": "2026-07-07",
|
||||||
|
"scope": {
|
||||||
|
"dataAccessFiles": 86,
|
||||||
|
"actionsFilesAudited": 15,
|
||||||
|
"totalFilesAudited": 101
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"totalIssues": 230,
|
||||||
|
"bySeverity": {
|
||||||
|
"P0": 17,
|
||||||
|
"P1": 48,
|
||||||
|
"P2": 105,
|
||||||
|
"P3": 60
|
||||||
|
},
|
||||||
|
"byDimension": {
|
||||||
|
"pattern": 54,
|
||||||
|
"performance": 71,
|
||||||
|
"architecture": 58,
|
||||||
|
"structure": 47
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metrics": {
|
||||||
|
"serverOnlyMissing": 2,
|
||||||
|
"cacheFnMissingEstimated": 60,
|
||||||
|
"filesOver800Lines": 3,
|
||||||
|
"filesOver1000Lines": 1,
|
||||||
|
"filesOver20Exports": 5,
|
||||||
|
"asAssertionsNonExempt": 2,
|
||||||
|
"anyUsage": 0,
|
||||||
|
"consoleErrorCount": 25,
|
||||||
|
"nPlusOnePatterns": 11,
|
||||||
|
"likeFullScanPatterns": 7,
|
||||||
|
"selectStarCount": 35,
|
||||||
|
"noLimitQueries": 18,
|
||||||
|
"crossModuleSchemaAccess": 7,
|
||||||
|
"businessLogicInDataAccess": 18,
|
||||||
|
"unprotectedTransactions": 4,
|
||||||
|
"actionsPermissionIssues": 4,
|
||||||
|
"actionsDirectDB": 1
|
||||||
|
},
|
||||||
|
"moduleHeatmap": [
|
||||||
|
{ "module": "messaging", "p0": 1, "p1": 4, "total": 5, "risk": "critical" },
|
||||||
|
{ "module": "classes", "p0": 1, "p1": 4, "total": 14, "risk": "critical" },
|
||||||
|
{ "module": "school", "p0": 0, "p1": 5, "total": 8, "risk": "critical" },
|
||||||
|
{ "module": "lesson-preparation", "p0": 1, "p1": 3, "total": 28, "risk": "high" },
|
||||||
|
{ "module": "scheduling", "p0": 1, "p1": 2, "total": 6, "risk": "high" },
|
||||||
|
{ "module": "adaptive-practice", "p0": 1, "p1": 2, "total": 4, "risk": "high" },
|
||||||
|
{ "module": "elective", "p0": 0, "p1": 3, "total": 7, "risk": "high" },
|
||||||
|
{ "module": "textbooks", "p0": 2, "p1": 1, "total": 11, "risk": "high" },
|
||||||
|
{ "module": "onboarding", "p0": 2, "p1": 0, "total": 2, "risk": "medium" },
|
||||||
|
{ "module": "grades", "p0": 0, "p1": 2, "total": 4, "risk": "medium" },
|
||||||
|
{ "module": "questions", "p0": 1, "p1": 1, "total": 11, "risk": "medium" },
|
||||||
|
{ "module": "audit", "p0": 1, "p1": 1, "total": 3, "risk": "medium" },
|
||||||
|
{ "module": "parent", "p0": 1, "p1": 0, "total": 1, "risk": "medium" },
|
||||||
|
{ "module": "attendance", "p0": 0, "p1": 1, "total": 9, "risk": "medium" },
|
||||||
|
{ "module": "files", "p0": 0, "p1": 4, "total": 13, "risk": "medium" },
|
||||||
|
{ "module": "exams", "p0": 1, "p1": 0, "total": 1, "risk": "low" },
|
||||||
|
{ "module": "course-plans", "p0": 1, "p1": 0, "total": 5, "risk": "low" },
|
||||||
|
{ "module": "homework", "p0": 0, "p1": 1, "total": 2, "risk": "low" },
|
||||||
|
{ "module": "diagnostic", "p0": 0, "p1": 1, "total": 2, "risk": "low" }
|
||||||
|
],
|
||||||
|
"p0Issues": [
|
||||||
|
{ "id": "G4-003", "file": "src/modules/audit/actions.ts", "lines": "L192-225", "ruleId": "A-08", "title": "purgeAuditLogsAction 用读权限执行物理删除", "category": "security" },
|
||||||
|
{ "id": "G4-002", "file": "src/modules/parent/", "lines": "—", "ruleId": "A-08", "title": "parent 模块缺失 actions.ts,3 页面直访 data-access", "category": "security" },
|
||||||
|
{ "id": "G2-001", "file": "src/modules/exams/data-access.ts", "lines": "L1", "ruleId": "P-01", "title": "缺 import server-only", "category": "security" },
|
||||||
|
{ "id": "G5-001", "file": "src/modules/onboarding/data-access.ts", "lines": "L1", "ruleId": "P-01", "title": "缺 import server-only", "category": "security" },
|
||||||
|
{ "id": "G1-001", "file": "src/modules/textbooks/data-access-graph.ts", "lines": "L7-121", "ruleId": "A-06", "title": "直查 questions + diagnostic 模块表", "category": "architecture" },
|
||||||
|
{ "id": "G3-002", "file": "src/modules/scheduling/data-access.ts", "lines": "L8-17", "ruleId": "A-06", "title": "直查 classes/users/subjects 三模块表", "category": "architecture" },
|
||||||
|
{ "id": "G3-003", "file": "src/modules/scheduling/data-access-class-schedule.ts", "lines": "L28-158", "ruleId": "A-02", "title": "data-access 含校验+状态机业务逻辑", "category": "architecture" },
|
||||||
|
{ "id": "G5-002", "file": "src/modules/onboarding/actions.ts", "lines": "L15-76", "ruleId": "A-09", "title": "actions 直查 DB", "category": "architecture" },
|
||||||
|
{ "id": "G4-001", "file": "src/modules/messaging/data-access.ts", "lines": "L1-1089", "ruleId": "S-01", "title": "1089 行超 1000 硬限", "category": "structure" },
|
||||||
|
{ "id": "G1-002", "file": "src/modules/questions/data-access.ts", "lines": "L294-315", "ruleId": "F-01", "title": "deleteQuestionRecursive 递归 N+1", "category": "performance" },
|
||||||
|
{ "id": "G1-003", "file": "src/modules/questions/data-access.ts", "lines": "L350-378", "ruleId": "F-01", "title": "deleteQuestionsBatch 循环 N+1", "category": "performance" },
|
||||||
|
{ "id": "G1-004", "file": "src/modules/lesson-preparation/data-access-comments.ts", "lines": "L128-140", "ruleId": "F-01", "title": "deleteComment 递归 N+1", "category": "performance" },
|
||||||
|
{ "id": "G1-005", "file": "src/modules/textbooks/data-access.ts", "lines": "L426-458", "ruleId": "F-01", "title": "reorderChapters 循环 UPDATE", "category": "performance" },
|
||||||
|
{ "id": "G3-001", "file": "src/modules/classes/data-access.ts", "lines": "L17-313", "ruleId": "P-03", "title": "24+ 读函数未走 cacheFn", "category": "performance" },
|
||||||
|
{ "id": "G3-004", "file": "src/modules/classes/data-access-teacher.ts", "lines": "L92-116", "ruleId": "F-01", "title": "getTeacherClassesRaw 2N+1", "category": "performance" },
|
||||||
|
{ "id": "G3-005", "file": "src/modules/course-plans/data-access.ts", "lines": "L324-331", "ruleId": "F-01", "title": "reorderCoursePlanItems N+1 + 未包裹事务", "category": "performance" },
|
||||||
|
{ "id": "G2-003", "file": "src/modules/adaptive-practice/data-access-analytics.ts", "lines": "L311-384", "ruleId": "F-01", "title": "getTeacherClassPracticeOverviewsRaw 2N+1", "category": "performance" }
|
||||||
|
],
|
||||||
|
"roadmap": {
|
||||||
|
"phase0": {
|
||||||
|
"name": "紧急安全修复",
|
||||||
|
"priority": "immediate",
|
||||||
|
"tasks": [
|
||||||
|
{ "id": "G2-001", "effort": "XS", "action": "添加 import server-only 到 exams/data-access.ts" },
|
||||||
|
{ "id": "G5-001", "effort": "XS", "action": "添加 import server-only 到 onboarding/data-access.ts" },
|
||||||
|
{ "id": "G4-002", "effort": "M", "action": "新建 parent/actions.ts,3 页面改调 Action" },
|
||||||
|
{ "id": "G4-003", "effort": "S", "action": "audit purge 权限点新增 + 替换" },
|
||||||
|
{ "id": "G4-004", "effort": "S", "action": "audit retention 权限点替换" },
|
||||||
|
{ "id": "G5-002", "effort": "S", "action": "onboarding/actions.ts 移除直查 DB" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"phase1": {
|
||||||
|
"name": "P0 架构与性能修复",
|
||||||
|
"priority": "high",
|
||||||
|
"tasks": [
|
||||||
|
{ "batch": "1.1", "ids": ["G1-001", "G3-002", "G1-031", "G1-032", "G1-033"], "effort": "L", "action": "跨模块 schema 直查治理" },
|
||||||
|
{ "batch": "1.2", "ids": ["G4-001", "G4-005", "G4-006", "G4-007", "G4-008"], "effort": "L", "action": "messaging 拆分" },
|
||||||
|
{ "batch": "1.3", "ids": ["G1-002", "G1-003", "G1-004", "G1-005", "G3-001", "G3-004", "G3-005", "G2-003"], "effort": "L", "action": "N+1 热路径修复" },
|
||||||
|
{ "batch": "1.4", "ids": ["G3-003", "G3-024", "G3-025"], "effort": "M", "action": "scheduling 业务逻辑下移" },
|
||||||
|
{ "batch": "1.5", "ids": ["G5-003", "G5-004"], "effort": "L", "action": "elective 业务逻辑拆分" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"phase2": {
|
||||||
|
"name": "P1 性能与结构优化",
|
||||||
|
"priority": "medium",
|
||||||
|
"tasks": [
|
||||||
|
{ "batch": "2.1", "ids": ["G1-006", "G1-007", "G1-008", "G1-009", "G3-017", "G4-010"], "effort": "L", "action": "LIKE 全表扫描治理" },
|
||||||
|
{ "batch": "2.2", "ids": ["G3-007", "G2-005"], "effort": "M", "action": "超长文件拆分" },
|
||||||
|
{ "batch": "2.3", "ids": ["G3-007", "G3-008", "G3-009", "G3-010", "G3-011"], "effort": "L", "action": "school 模块重构" },
|
||||||
|
{ "batch": "2.4", "ids": ["G5-005", "G5-006", "G5-007"], "effort": "M", "action": "files 模块错误处理重构" },
|
||||||
|
{ "batch": "2.5", "ids": ["G3-006", "G3-012", "G3-025", "G4-009"], "effort": "S", "action": "事务包裹修复" },
|
||||||
|
{ "batch": "2.6", "ids": ["G1-011", "G1-012", "G1-013", "G1-050", "G1-051", "G1-052", "G1-053", "G3-031", "G3-032", "G3-041", "G3-044"], "effort": "M", "action": "无 LIMIT 查询保护" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"phase3": {
|
||||||
|
"name": "P2 模式标准化",
|
||||||
|
"priority": "low",
|
||||||
|
"tasks": [
|
||||||
|
{ "batch": "3.1", "ids": ["G1-021", "G1-022", "G1-023", "G1-024", "G3-001"], "effort": "M", "action": "cacheFn 全量补齐" },
|
||||||
|
{ "batch": "3.2", "ids": ["G1-039-049", "G3-009", "G3-026-028", "G5-007"], "effort": "M", "action": "SELECT * 改显式列" },
|
||||||
|
{ "batch": "3.3", "ids": ["G3-021", "G3-047", "G1-025"], "effort": "S", "action": "日期 helper 提取" },
|
||||||
|
{ "batch": "3.4", "ids": ["G1-026", "G1-027", "G3-020", "G3-036"], "effort": "M", "action": "重复 helper 提取" },
|
||||||
|
{ "batch": "3.5", "ids": ["G1-034-036", "G1-066", "G1-067", "G3-046"], "effort": "M", "action": "JSDoc 补齐" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"phase4": {
|
||||||
|
"name": "P3 风格优化",
|
||||||
|
"priority": "optional",
|
||||||
|
"tasks": [
|
||||||
|
{ "ids": ["G3-029", "G3-030"], "effort": "XS", "action": "as widening 断言改类型标注" },
|
||||||
|
{ "ids": ["G1-060-065"], "effort": "XS", "action": "非空断言 ! 改类型守卫" },
|
||||||
|
{ "ids": ["G3-048"], "effort": "S", "action": "export * 改显式 re-export" },
|
||||||
|
{ "ids": ["G3-018"], "effort": "XS", "action": "死代码删除" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"crossModuleRecommendations": {
|
||||||
|
"newSharedHelpers": [
|
||||||
|
{ "name": "toISODateString", "path": "src/shared/lib/date-utils.ts", "replaces": ["attendance/serializeDate", "scheduling/serializeDate", "school/toIso", "course-plans/toIso"] },
|
||||||
|
{ "name": "buildScopeFilter", "path": "src/shared/lib/scope-filter.ts", "replaces": ["attendance/buildScopeFilter", "grades/buildScopeFilter"] }
|
||||||
|
],
|
||||||
|
"newCrossModuleInterfaces": [
|
||||||
|
{ "name": "getActiveStudentIdsByClassIds", "module": "classes", "callers": ["adaptive-practice", "attendance"] },
|
||||||
|
{ "name": "getGradeNamesByIds", "module": "school", "callers": ["textbooks", "lesson-preparation"] },
|
||||||
|
{ "name": "getQuestionCountByKpIds", "module": "questions", "callers": ["textbooks"] },
|
||||||
|
{ "name": "getKpMasteryByTextbookId", "module": "diagnostic", "callers": ["textbooks"] }
|
||||||
|
],
|
||||||
|
"newPermissions": [
|
||||||
|
{ "name": "AUDIT_LOG_PURGE", "description": "审计日志物理删除", "roles": ["admin"] },
|
||||||
|
{ "name": "AUDIT_RETENTION_MANAGE", "description": "审计保留策略配置", "roles": ["admin"] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"architectureDocGaps": [
|
||||||
|
"parent 模块缺失 actions.ts - 004 文档模块清单未标注",
|
||||||
|
"onboarding/actions.ts 直查 DB - 005 文档 dependencyMatrix 需修正",
|
||||||
|
"messaging/data-access.ts 拆分后 - 005 文档 modules.messaging.exports 需更新",
|
||||||
|
"新增权限点 AUDIT_LOG_PURGE / AUDIT_RETENTION_MANAGE - 005 文档 permissions 需补记",
|
||||||
|
"新增 shared/lib/date-utils.ts - 004/005 shared 模块清单需补记"
|
||||||
|
],
|
||||||
|
"sourceOutputs": [
|
||||||
|
"docs/architecture/audit/g1-audit-output.json",
|
||||||
|
"docs/architecture/audit/g2-data-access-audit.json",
|
||||||
|
"docs/architecture/audit/g3-audit-output.json",
|
||||||
|
"docs/architecture/audit/g4-audit-output.json",
|
||||||
|
"docs/architecture/audit/g5-audit-output.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
525
docs/architecture/audit/archive/data-access-audit-v1.md
Normal file
@@ -0,0 +1,525 @@
|
|||||||
|
# 数据库访问层审计报告 v1
|
||||||
|
|
||||||
|
> 创建日期:2026-07-07
|
||||||
|
> 审计范围:86 个 `data-access*.ts` + ~30 个 `actions.ts`
|
||||||
|
> 审计方案:纯深读(5 个并行 sub-agent 全量扫描)
|
||||||
|
> 框架依据:[data-access-audit-framework-v1.md](./data-access-audit-framework-v1.md)
|
||||||
|
> 原始输出:[g1-audit-output.json](./g1-audit-output.json) · [g2-data-access-audit.json](./g2-data-access-audit.json) · [g3-audit-output.json](./g3-audit-output.json) · [g4-audit-output.json](./g4-audit-output.json) · [g5-audit-output.json](./g5-audit-output.json)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、执行摘要
|
||||||
|
|
||||||
|
| 指标 | 数值 |
|
||||||
|
|---|---|
|
||||||
|
| 审计文件总数 | 101(86 data-access + 15 actions 辅查) |
|
||||||
|
| 发现问题总数 | 230 |
|
||||||
|
| P0 Critical | 17(7.4%) |
|
||||||
|
| P1 High | 48(20.9%) |
|
||||||
|
| P2 Medium | 105(45.6%) |
|
||||||
|
| P3 Low | 60(26.1%) |
|
||||||
|
|
||||||
|
### 1.1 模块热度图(按 P0+P1 数量降序)
|
||||||
|
|
||||||
|
| 模块 | P0 | P1 | P0+P1 | 总计 | 风险等级 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| messaging | 1 | 4 | 5 | 5 | 🔴 极高 |
|
||||||
|
| classes | 1 | 4 | 5 | 14 | 🔴 极高 |
|
||||||
|
| school | 0 | 5 | 5 | 8 | 🔴 极高 |
|
||||||
|
| lesson-preparation | 1 | 3 | 4 | 28 | 🟠 高 |
|
||||||
|
| scheduling | 1 | 2 | 3 | 6 | 🟠 高 |
|
||||||
|
| adaptive-practice | 1 | 2 | 3 | 4 | 🟠 高 |
|
||||||
|
| elective | 0 | 3 | 3 | 7 | 🟠 高 |
|
||||||
|
| textbooks | 2 | 1 | 3 | 11 | 🟠 高 |
|
||||||
|
| onboarding | 2 | 0 | 2 | 2 | 🟡 中 |
|
||||||
|
| grades | 0 | 2 | 2 | 4 | 🟡 中 |
|
||||||
|
| questions | 1 | 1 | 2 | 11 | 🟡 中 |
|
||||||
|
| audit | 1 | 1 | 2 | 3 | 🟡 中 |
|
||||||
|
| parent | 1 | 0 | 1 | 1 | 🟡 中 |
|
||||||
|
| attendance | 0 | 1 | 1 | 9 | 🟡 中 |
|
||||||
|
| files | 0 | 4 | 4 | 13 | 🟡 中 |
|
||||||
|
| exams | 1 | 0 | 1 | 1 | 🟢 低 |
|
||||||
|
| course-plans | 1 | 0 | 1 | 5 | 🟢 低 |
|
||||||
|
| homework | 0 | 1 | 1 | 2 | 🟢 低 |
|
||||||
|
| diagnostic | 0 | 1 | 1 | 2 | 🟢 低 |
|
||||||
|
| 其他 (auth/rbac/notifications/dashboard/search/ai/announcements/error-book/proctoring/settings) | 0 | 0 | 0 | 0-3 | 🟢 低 |
|
||||||
|
|
||||||
|
### 1.2 维度分布
|
||||||
|
|
||||||
|
| 维度 | 问题数 | 占比 | P0 | P1 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 架构违规(A-*) | 58 | 25.2% | 6 | 18 |
|
||||||
|
| 性能优化(F-*) | 71 | 30.9% | 7 | 14 |
|
||||||
|
| 结构可维护性(S-*) | 47 | 20.4% | 2 | 11 |
|
||||||
|
| 模式标准化(P-*) | 54 | 23.5% | 2 | 5 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、量化指标仪表盘
|
||||||
|
|
||||||
|
| 指标 | 数值 | 备注 |
|
||||||
|
|---|---|---|
|
||||||
|
| `import "server-only"` 缺失文件 | 2 | exams/data-access.ts、onboarding/data-access.ts |
|
||||||
|
| cacheFn 未覆盖读函数(估算) | 60+ | 集中在 classes(24+)、questions(5)、textbooks(3)、lesson-preparation(4) |
|
||||||
|
| 超长文件(>800 行) | 3 | messaging(1089,超硬限)、school(938)、grades-analytics(831) |
|
||||||
|
| 单文件导出函数 > 20 | 4 | messaging(42+)、classes/data-access.ts(25+)、school(30+)、questions(28)、textbooks(35) |
|
||||||
|
| `as` 断言(非豁免) | 2 | classes/data-access-admin.ts、classes/data-access-teacher.ts(DEFAULT_CLASS_SUBJECTS widening) |
|
||||||
|
| `any` 使用 | 0 | 全部合规 |
|
||||||
|
| `console.error` 调试代码 | 25+ | school(12)、files(12)、classes(3)、course-plans(2)、audit(9) |
|
||||||
|
| N+1 循环 SQL(F-01) | 11 | 跨 4 组 |
|
||||||
|
| `LIKE '%xxx%'` 全表扫描 | 7 | lesson-preparation(4)、questions(1)、textbooks(1)、classes(1)、messaging(1) |
|
||||||
|
| SELECT * 未指定列 | 35+ | 跨 G1(16)、G3(11)、G5(8) |
|
||||||
|
| 无 LIMIT 大表查询 | 18 | 集中在 lesson-preparation |
|
||||||
|
| 跨模块直查 schema 表(A-06) | 7 | textbooks-graph(2)、lesson-preparation(2)、questions(1)、scheduling(1)、announcements(1) |
|
||||||
|
| data-access 含业务逻辑(A-02) | 18 | 集中在 scheduling、messaging、elective、classes |
|
||||||
|
| 未包裹事务的多步写(F-09) | 4 | course-plans、classes、auth、school |
|
||||||
|
| actions 漏/错权限校验(A-08) | 4 | parent(缺失全部)、audit(purge 用读权限)、audit(retention 用读权限) |
|
||||||
|
| actions 直查 DB(A-09) | 1 | onboarding |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、P0 Critical 问题清单(17 条,必须立即治理)
|
||||||
|
|
||||||
|
### 3.1 安全漏洞类(4 条)
|
||||||
|
|
||||||
|
| ID | 文件 | 问题 | 修复 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| G4-003 | audit/actions.ts L192-225 | `purgeAuditLogsAction` 用 `AUDIT_LOG_READ`(读权限)执行物理删除,权限提权漏洞 | 新增 `AUDIT_LOG_PURGE` 权限点 |
|
||||||
|
| G4-002 | parent/ | 模块缺失 actions.ts,3 个 app 页面直接 import data-access,完全绕过 `requirePermission` | 新建 parent/actions.ts,3 个页面改调 Action |
|
||||||
|
| G2-001 | exams/data-access.ts L1 | 缺 `import "server-only"`,DB 逻辑可能泄露到客户端 bundle | 首行添加 `import "server-only"` |
|
||||||
|
| G5-001 | onboarding/data-access.ts L1 | 缺 `import "server-only"` | 首行添加 `import "server-only"` |
|
||||||
|
|
||||||
|
### 3.2 架构硬违规类(5 条)
|
||||||
|
|
||||||
|
| ID | 文件 | 问题 | 修复 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| G1-001 | textbooks/data-access-graph.ts L7-121 | 直查 questions 模块 `questionsToKnowledgePoints` 表 + diagnostic 模块 `knowledgePointMastery` 表 | 改调对方 data-access 跨模块接口 |
|
||||||
|
| G3-002 | scheduling/data-access.ts L8-17 | 直查 classes/users/subjects 三模块的 schema 表 | 改调 `getClassNamesByIds`/`getUserNamesByIds` 等 |
|
||||||
|
| G3-003 | scheduling/data-access-class-schedule.ts | data-access 含时间校验、归属校验、状态机判断 | 校验逻辑移至 actions |
|
||||||
|
| G5-002 | onboarding/actions.ts L15-76 | actions.ts 直接 `import { db }` 并查 `users` 表,违反三层架构 | data-access 新增 `getUserOnboardedAt`,actions 改调 |
|
||||||
|
| G4-001 | messaging/data-access.ts L1-1089 | 单文件 1089 行超 1000 硬限,8 类职责混合 | 拆分为 7 个 data-access-*.ts |
|
||||||
|
|
||||||
|
### 3.3 必定性能问题类(8 条)
|
||||||
|
|
||||||
|
| ID | 文件 | 问题 | 修复 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| G1-002 | questions/data-access.ts L294-315 | `deleteQuestionRecursive` 递归 N+1,每子题单独查询+删除 | 收集后代 ID + `inArray` 批量删除 |
|
||||||
|
| G1-003 | questions/data-access.ts L350-378 | `deleteQuestionsBatch` 循环调用 `deleteQuestionRecursive` 产生 N×深度 查询 | 一次性收集所有后代 + 单次 `inArray` 删除 |
|
||||||
|
| G1-004 | lesson-preparation/data-access-comments.ts L128-140 | `deleteComment` 递归 N+1 | 单次查询构建 parent→children Map + 批量删除 |
|
||||||
|
| G1-005 | textbooks/data-access.ts L426-458 | `reorderChapters` 循环内逐条 UPDATE | `CASE WHEN` 批量更新 |
|
||||||
|
| G3-001 | classes/data-access.ts L17-313 | 24+ 读函数全部未走 cacheFn,跨模块高频调用直连 DB | 补齐 Raw + Wrapper 配对 |
|
||||||
|
| G3-004 | classes/data-access-teacher.ts L92-116 | `getTeacherClassesRaw` 循环内对每班发起 2 次子查询(2N+1) | 新增批量接口 |
|
||||||
|
| G3-005 | course-plans/data-access.ts L324-331 | `reorderCoursePlanItems` 循环内 N 次 UPDATE 且未包裹事务 | 事务 + `CASE WHEN` 批量更新 |
|
||||||
|
| G2-003 | adaptive-practice/data-access-analytics.ts L311-384 | `getTeacherClassPracticeOverviewsRaw` 对每班发起 2 条 SQL(2N+1) | 批量查询 + groupBy |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、P1 High 问题清单(48 条,Phase 1 治理)
|
||||||
|
|
||||||
|
### 4.1 性能类(14 条)
|
||||||
|
|
||||||
|
| ID | 文件 | 规则 | 概要 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| G1-006~009 | lesson-preparation/questions/textbooks | F-02 | 4 处 `LIKE '%xxx%'` 全表扫描(课案标题、JSON content、题目 content、教材 4 字段) |
|
||||||
|
| G1-010 | lesson-preparation/data-access.ts L247-277 | F-04 | `getLessonPlansRaw` 5 表 LEFT JOIN |
|
||||||
|
| G1-011~013 | lesson-preparation (3 处) | F-05 | 列表查询无 LIMIT(getLessonPlansRaw、getPendingReviewPlansRaw、getCalendarEventsRaw) |
|
||||||
|
| G1-014~015 | lesson-preparation (2 处) | F-10 | 全表拉取后内存聚合统计 |
|
||||||
|
| G1-016 | lesson-preparation/data-access-analytics.ts L168-184 | F-06 | 5 次串行 COUNT 查询同表 |
|
||||||
|
| G1-017~018 | lesson-preparation (2 处) | F-01 | 拉全表后内存 filter |
|
||||||
|
| G1-019 | textbooks/actions.ts L396-398 | F-08 | 循环调用 `getGradeNameById`(N 次 DB) |
|
||||||
|
| G2-002 | grades/data-access-appeals.ts L122-151 | F-01 | `getPendingAppealsForReviewRaw` JS 层 filter 班级范围(潜在数据泄露) |
|
||||||
|
| G2-004 | adaptive-practice/data-access-analytics.ts L320-325 | F-08 | 循环内跨模块调用 `getActiveStudentIdsByClassId` |
|
||||||
|
| G3-006 | course-plans/data-access.ts L309-332 | F-09 | `reorderCoursePlanItems` 多次 UPDATE 未包裹事务 |
|
||||||
|
| G3-012 | school/data-access.ts L803-822 | F-09 | `promoteGrades` 循环 UPDATE 未包裹事务 |
|
||||||
|
| G3-017 | classes/data-access-students.ts L281-285 | F-02 | `LIKE '%xxx%'` 全表扫描 users.name/email |
|
||||||
|
| G3-022 | attendance/data-access-correlation.ts L46-193 | F-01/A-02 | 148 行业务编排逻辑(含跨模块调用) |
|
||||||
|
|
||||||
|
### 4.2 架构类(11 条)
|
||||||
|
|
||||||
|
| ID | 文件 | 规则 | 概要 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| G4-004 | audit/actions.ts L163-190 | A-08 | `saveAuditRetentionConfigAction` 用读权限执行写操作 |
|
||||||
|
| G4-006~008 | messaging/data-access.ts (3 处) | A-02 | 状态机/防重复业务逻辑嵌入 data-access |
|
||||||
|
| G3-007~008 | school/data-access.ts | S-01/S-02 | 938 行 + 30+ 导出函数 |
|
||||||
|
| G3-010 | school/data-access.ts | A-10 | 12 处 `console.error` 吞异常 |
|
||||||
|
| G3-011 | school/data-access.ts L246-408 | A-02 | 角色判断业务逻辑嵌入 data-access |
|
||||||
|
| G3-024~025 | classes/data-access-teacher.ts L284-439 | A-02/F-09 | `enrollTeacherByInvitationCode` 155 行状态机 + 未包裹事务 |
|
||||||
|
| G5-003 | elective/data-access-operations.ts | A-02 | 业务逻辑混淆(抽签算法、冲突检测、i18n 通知) |
|
||||||
|
|
||||||
|
### 4.3 结构类(5 条)
|
||||||
|
|
||||||
|
| ID | 文件 | 规则 | 概要 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| G2-005 | grades/data-access-analytics.ts | S-01 | 831 行超 800 警告线 |
|
||||||
|
| G5-004 | elective/data-access-operations.ts L222-304 | S-08 | DB 写入与抽签算法混淆 |
|
||||||
|
| G5-005 | files/data-access.ts | A-10 | 12 处 `console.error` |
|
||||||
|
| G5-006 | files/data-access.ts | P-05 | try-catch 吞错误返回 null/[]/false |
|
||||||
|
| G4-009 | auth/data-access.ts | F-09 | `createUser` 两次 INSERT 无事务包裹 |
|
||||||
|
|
||||||
|
(完整 P1 清单详见各 sub-agent JSON 输出)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、按维度分组的问题清单
|
||||||
|
|
||||||
|
### 5.1 模式标准化(P-*,54 条)
|
||||||
|
|
||||||
|
#### P-01 `import "server-only"` 缺失(2 条 P0)
|
||||||
|
|
||||||
|
| ID | 文件 | 修复 |
|
||||||
|
|---|---|---|
|
||||||
|
| G2-001 | exams/data-access.ts L1 | 首行添加 `import "server-only"` |
|
||||||
|
| G5-001 | onboarding/data-access.ts L1 | 首行添加 `import "server-only"` |
|
||||||
|
|
||||||
|
#### P-03 cacheFn 未覆盖(30+ 条,P2)
|
||||||
|
|
||||||
|
集中模块:
|
||||||
|
- **classes/data-access.ts**(24+ 读函数,G3-001 P0)
|
||||||
|
- **lesson-preparation**(4 个,G1-021)
|
||||||
|
- **questions**(5 个,G1-023)
|
||||||
|
- **textbooks**(3 个,G1-024)
|
||||||
|
- **lesson-preparation-substitutes**(1 个,G1-022)
|
||||||
|
|
||||||
|
修复模式:
|
||||||
|
```ts
|
||||||
|
// Before
|
||||||
|
export const getClassNamesByIds = async (classIds: string[]) => { /* SQL */ }
|
||||||
|
|
||||||
|
// After
|
||||||
|
export const getClassNamesByIdsRaw = async (classIds: string[]) => { /* SQL */ }
|
||||||
|
export const getClassNamesByIds = cacheFn(getClassNamesByIdsRaw, {
|
||||||
|
tags: ["classes:names"],
|
||||||
|
ttl: 300,
|
||||||
|
keyParts: ["classes", "getClassNamesByIds"],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### P-05 错误处理不一致(13 条,P1-P2)
|
||||||
|
|
||||||
|
集中模块:files(9 处 try-catch 吞错误)、school(12 处 console.error + 吞异常)
|
||||||
|
|
||||||
|
#### P-07 日期序列化 helper 重复(5 处,P2-P3)
|
||||||
|
|
||||||
|
- attendance/data-access.ts `serializeDate`
|
||||||
|
- attendance/data-access-stats.ts `serializeDate`
|
||||||
|
- scheduling/data-access.ts `serializeDate`
|
||||||
|
- school/data-access.ts `toIso`
|
||||||
|
- course-plans/data-access.ts `toIso`/`toIsoRequired`
|
||||||
|
|
||||||
|
修复:提取到 `src/shared/lib/date-utils.ts`
|
||||||
|
|
||||||
|
#### P-09 `as` 断言(2 条 P3,非豁免)
|
||||||
|
|
||||||
|
- classes/data-access-admin.ts L36 `DEFAULT_CLASS_SUBJECTS as readonly string[]`
|
||||||
|
- classes/data-access-teacher.ts L41 同上
|
||||||
|
|
||||||
|
#### P-10 `any` 使用
|
||||||
|
|
||||||
|
零违规,全部合规。
|
||||||
|
|
||||||
|
### 5.2 性能优化(F-*,71 条)
|
||||||
|
|
||||||
|
#### F-01 N+1 循环 SQL(11 条,跨 P0/P1/P2)
|
||||||
|
|
||||||
|
| ID | 文件 | 模式 |
|
||||||
|
|---|---|---|
|
||||||
|
| G1-002 | questions deleteQuestionRecursive | 递归内单独查询+删除 |
|
||||||
|
| G1-003 | questions deleteQuestionsBatch | 循环调用递归删除 |
|
||||||
|
| G1-004 | lesson-preparation deleteComment | 递归内单独查询+删除 |
|
||||||
|
| G1-005 | textbooks reorderChapters | 循环内逐条 UPDATE |
|
||||||
|
| G1-017 | lesson-preparation getSchedulesByDateRangeRaw | 拉全表后内存 filter |
|
||||||
|
| G1-018 | lesson-preparation getResponsesByStudentIdRaw | 拉全量后内存 filter |
|
||||||
|
| G2-002 | grades getPendingAppealsForReviewRaw | JS 层 filter 班级范围 |
|
||||||
|
| G2-003 | adaptive-practice getTeacherClassPracticeOverviewsRaw | Promise.all 内 2N+1 |
|
||||||
|
| G3-004 | classes getTeacherClassesRaw | 循环内 2 次子查询 |
|
||||||
|
| G3-005 | course-plans reorderCoursePlanItems | 循环内 N 次 UPDATE |
|
||||||
|
| G3-042 | classes generateUniqueInvitationCode | 循环内重试查询 |
|
||||||
|
|
||||||
|
#### F-02 `LIKE '%xxx%'` 全表扫描(7 条 P1)
|
||||||
|
|
||||||
|
| ID | 文件 | 字段 |
|
||||||
|
|---|---|---|
|
||||||
|
| G1-006 | lesson-preparation | lessonPlans.title |
|
||||||
|
| G1-007 | lesson-preparation-knowledge | lessonPlans.content (JSON) |
|
||||||
|
| G1-008 | questions | questions.content (JSON, +LOWER+CAST) |
|
||||||
|
| G1-009 | textbooks | title/subject/grade/publisher 4 字段 |
|
||||||
|
| G3-017 | classes-students | users.name/email |
|
||||||
|
| G4-010 | messaging | messages.subject/content |
|
||||||
|
|
||||||
|
修复策略:
|
||||||
|
- 短期:前缀匹配 `LIKE 'xxx%'`(可走索引)
|
||||||
|
- 中期:FULLTEXT 索引 + `MATCH AGAINST IN BOOLEAN MODE`(questions 表已实施,参见架构图 1.1.4)
|
||||||
|
- 长期:关联表存储提取后的关系(如 lesson_plan_knowledge_point_refs)
|
||||||
|
|
||||||
|
#### F-03 SELECT * 未指定列(35+ 条 P2-P3)
|
||||||
|
|
||||||
|
集中模块:lesson-preparation(16 处)、school(4 处)、scheduling(3 处)、attendance(2 处)、course-plans(7 处)、files(8 处)
|
||||||
|
|
||||||
|
#### F-05 无 LIMIT 大表查询(18 条 P1-P2)
|
||||||
|
|
||||||
|
集中模块:lesson-preparation(7 处)、textbooks(2 处)、classes(3 处)、proctoring(1 处)
|
||||||
|
|
||||||
|
#### F-09 事务范围问题(4 条 P1)
|
||||||
|
|
||||||
|
| ID | 文件 | 问题 |
|
||||||
|
|---|---|---|
|
||||||
|
| G3-006 | course-plans reorderCoursePlanItems | 多次 UPDATE 未包裹事务 |
|
||||||
|
| G3-012 | school promoteGrades | 循环 UPDATE 未包裹事务 |
|
||||||
|
| G3-025 | classes enrollTeacherByInvitationCode | 多次写操作未包裹事务 |
|
||||||
|
| G4-009 | auth createUser | 两次 INSERT 无事务包裹 |
|
||||||
|
|
||||||
|
#### F-10 全表 COUNT 无过滤(4 条 P2)
|
||||||
|
|
||||||
|
集中模块:textbooks、questions、lesson-preparation、classes
|
||||||
|
|
||||||
|
### 5.3 架构违规(A-*,58 条)
|
||||||
|
|
||||||
|
#### A-02 data-access 含业务逻辑(18 条 P0-P2)
|
||||||
|
|
||||||
|
| 模块 | 文件 | 业务逻辑类型 |
|
||||||
|
|---|---|---|
|
||||||
|
| scheduling | data-access-class-schedule.ts | 时间校验 + 归属校验 + 状态机 |
|
||||||
|
| scheduling | data-access.ts | — |
|
||||||
|
| messaging | data-access.ts | 撤回状态机 + 防重复 + 页面编排 |
|
||||||
|
| elective | data-access-operations.ts | 抽签算法 + 冲突检测 + i18n 通知 |
|
||||||
|
| classes | data-access-teacher.ts | 邀请码状态机 + 角色校验 |
|
||||||
|
| classes | data-access-invitations.ts | 懒清理状态迁移 |
|
||||||
|
| school | data-access.ts | 角色判断 + 权限感知查询 |
|
||||||
|
| attendance | data-access-correlation.ts | 跨模块编排 + 成绩归一化 |
|
||||||
|
| attendance | data-access-stats.ts | 纯计算函数导出 |
|
||||||
|
| lesson-preparation | data-access-review.ts | 状态机迁移 |
|
||||||
|
| lesson-preparation | data-access-ai-evaluation.ts | 评分算法纯函数 |
|
||||||
|
| textbooks | data-access.ts | 重排序算法 |
|
||||||
|
| diagnostic | data-access.ts | 掌握度累积计算 |
|
||||||
|
| homework | data-access.ts | computeOverdueCount 闭包 |
|
||||||
|
|
||||||
|
#### A-06 跨模块直查 schema 表(7 条 P0-P2)
|
||||||
|
|
||||||
|
| ID | 文件 | 被查模块 |
|
||||||
|
|---|---|---|
|
||||||
|
| G1-001 | textbooks/data-access-graph.ts | questions + diagnostic |
|
||||||
|
| G1-031 | lesson-preparation/data-access.ts | textbooks (textbooks/chapters) |
|
||||||
|
| G1-032 | lesson-preparation/data-access-schedules.ts | classes |
|
||||||
|
| G1-033 | questions/data-access.ts | textbooks (knowledgePoints) |
|
||||||
|
| G3-002 | scheduling/data-access.ts | classes + users + subjects |
|
||||||
|
|
||||||
|
#### A-08 actions 权限校验问题(4 条 P0-P1)
|
||||||
|
|
||||||
|
| ID | 文件 | 问题 |
|
||||||
|
|---|---|---|
|
||||||
|
| G4-002 | parent/ | 模块缺失 actions.ts,3 页面直访 data-access |
|
||||||
|
| G4-003 | audit/actions.ts | purge 用读权限 |
|
||||||
|
| G4-004 | audit/actions.ts | retention 配置用读权限 |
|
||||||
|
| G4-047 | rbac/data-access-assignments.ts | 内存 post-fetch 过滤导致 total 错误(伴随 A-02) |
|
||||||
|
|
||||||
|
#### A-09 actions 直查 DB(1 条 P0)
|
||||||
|
|
||||||
|
| ID | 文件 | 问题 |
|
||||||
|
|---|---|---|
|
||||||
|
| G5-002 | onboarding/actions.ts L15-76 | 直接 `import { db }` 并查 `users` 表 |
|
||||||
|
|
||||||
|
#### A-10 `console.error` 调试代码(25+ 条 P1-P2)
|
||||||
|
|
||||||
|
| 模块 | 文件 | 数量 |
|
||||||
|
|---|---|---|
|
||||||
|
| school | data-access.ts | 12 |
|
||||||
|
| files | data-access.ts | 12 |
|
||||||
|
| classes | data-access-teacher/students/admin | 3 |
|
||||||
|
| course-plans | data-access.ts | 2 |
|
||||||
|
| audit | data-access.ts | 9 |
|
||||||
|
|
||||||
|
### 5.4 结构与可维护性(S-*,47 条)
|
||||||
|
|
||||||
|
#### S-01 超长文件(3 条 P0-P1)
|
||||||
|
|
||||||
|
| ID | 文件 | 行数 | 状态 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| G4-001 | messaging/data-access.ts | 1089 | 超 1000 硬限,必须拆分 |
|
||||||
|
| G3-007 | school/data-access.ts | 938 | 超 800 警告,接近硬限 |
|
||||||
|
| G2-005 | grades/data-access-analytics.ts | 831 | 超 800 警告 |
|
||||||
|
|
||||||
|
#### S-02 单文件导出函数过多(4 条 P2)
|
||||||
|
|
||||||
|
| 文件 | 导出数 |
|
||||||
|
|---|---|
|
||||||
|
| messaging/data-access.ts | 42+ |
|
||||||
|
| school/data-access.ts | 30+ |
|
||||||
|
| textbooks/data-access.ts | 35 |
|
||||||
|
| questions/data-access.ts | 28 |
|
||||||
|
| classes/data-access.ts | 25+ |
|
||||||
|
|
||||||
|
#### S-03 重复 helper(8 条 P2)
|
||||||
|
|
||||||
|
| helper | 出现模块 |
|
||||||
|
|---|---|
|
||||||
|
| serializeDate/toIso | attendance、scheduling、school、course-plans |
|
||||||
|
| toLessonPlanStatus | lesson-preparation(2 文件) |
|
||||||
|
| isStringArray | lesson-preparation(2 文件) |
|
||||||
|
| fetchClassesWithSubjects | classes(2 函数 145+124 行重复) |
|
||||||
|
| fetchGradesWithHeads | school(3 函数重复) |
|
||||||
|
|
||||||
|
#### S-06 缺 JSDoc(15+ 条 P2-P3)
|
||||||
|
|
||||||
|
集中模块:lesson-preparation(versions/templates)、questions、textbooks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、P0-P3 优先级矩阵
|
||||||
|
|
||||||
|
```
|
||||||
|
高影响
|
||||||
|
│
|
||||||
|
│ P0 立即治理 P1 Phase 1
|
||||||
|
│ ───────────────── ─────────────────
|
||||||
|
│ • parent 权限漏洞 • N+1 循环 SQL(非热路径)
|
||||||
|
│ • audit 权限提权 • LIKE 全表扫描
|
||||||
|
│ • server-only 缺失 • 超长文件(school/grades)
|
||||||
|
│ • 跨模块 schema 直查 • 业务逻辑嵌入 data-access
|
||||||
|
│ • N+1 循环 SQL(热路径) • 事务未包裹
|
||||||
|
│ • messaging 超硬限 • console.error 吞异常
|
||||||
|
│
|
||||||
|
├──────────────────────────────────────────────
|
||||||
|
│
|
||||||
|
│ P2 Phase 2 P3 Phase 3
|
||||||
|
│ ───────────────── ─────────────────
|
||||||
|
│ • cacheFn 未覆盖 • as 断言(widening)
|
||||||
|
│ • SELECT * 未指定列 • 非空断言 !
|
||||||
|
│ • 无 LIMIT 大表查询 • JSDoc 补齐
|
||||||
|
│ • 重复 helper • 动态 import 注释
|
||||||
|
│ • 单文件导出过多 • export * 改显式
|
||||||
|
│
|
||||||
|
低影响
|
||||||
|
高紧迫 ─────────────────── 低紧迫
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、分阶段治理路线图
|
||||||
|
|
||||||
|
### Phase 0:紧急安全修复(XS-S,立即执行)
|
||||||
|
|
||||||
|
| 任务 | ID | 工作量 | 验证 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 添加 `import "server-only"` 到 exams/data-access.ts | G2-001 | XS | tsc + lint |
|
||||||
|
| 添加 `import "server-only"` 到 onboarding/data-access.ts | G5-001 | XS | tsc + lint |
|
||||||
|
| 新建 parent/actions.ts,3 页面改调 Action | G4-002 | M | 手动测试 3 页面 |
|
||||||
|
| audit purge 权限点新增 + 替换 | G4-003 | S | 权限矩阵测试 |
|
||||||
|
| audit retention 权限点替换 | G4-004 | S | 权限矩阵测试 |
|
||||||
|
| onboarding/actions.ts 移除直查 DB | G5-002 | S | tsc + lint |
|
||||||
|
|
||||||
|
**Phase 0 完成标准**:所有 P0 安全漏洞修复,`npm run lint` + `npx tsc --noEmit` 零错误。
|
||||||
|
|
||||||
|
### Phase 1:P0 架构与性能修复(M-L,1-2 周)
|
||||||
|
|
||||||
|
| 任务批次 | 涉及 ID | 工作量 | 依赖 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **1.1 跨模块 schema 直查治理** | G1-001, G3-002, G1-031~033 | L | 需在 questions/diagnostic/textbooks/classes 模块新增跨模块接口 |
|
||||||
|
| **1.2 messaging 拆分** | G4-001, G4-005~008 | L | 拆分为 7 个子文件 + 业务逻辑移至 actions |
|
||||||
|
| **1.3 N+1 热路径修复** | G1-002~005, G3-001, G3-004, G3-005, G2-003 | L | classes 补齐 cacheFn 是基础 |
|
||||||
|
| **1.4 scheduling 业务逻辑下移** | G3-003, G3-024, G3-025 | M | data-access-class-schedule.ts 重写 |
|
||||||
|
| **1.5 elective 业务逻辑拆分** | G5-003, G5-004 | L | 提取 lib/lottery.ts + lib/schedule-conflict.ts |
|
||||||
|
|
||||||
|
**Phase 1 完成标准**:所有 P0 修复,关键路径性能提升,架构分层清晰。
|
||||||
|
|
||||||
|
### Phase 2:P1 性能与结构优化(M-L,2-3 周)
|
||||||
|
|
||||||
|
| 任务批次 | 涉及 ID | 工作量 |
|
||||||
|
|---|---|---|
|
||||||
|
| **2.1 LIKE 全表扫描治理** | G1-006~009, G3-017, G4-010 | L(FULLTEXT 索引 + 查询重写) |
|
||||||
|
| **2.2 超长文件拆分** | G3-007, G2-005 | M(school 按职责拆 8 文件、grades-analytics 按维度拆) |
|
||||||
|
| **2.3 school 模块重构** | G3-007~011 | L(拆分 + 角色判断移至 actions + 删除 console.error) |
|
||||||
|
| **2.4 files 模块错误处理重构** | G5-005, G5-006, G5-007 | M(删除 try-catch + console.error) |
|
||||||
|
| **2.5 事务包裹修复** | G3-006, G3-012, G3-025, G4-009 | S |
|
||||||
|
| **2.6 无 LIMIT 查询保护** | G1-011~013, G1-050~053, G3-031~032, G3-041, G3-044 | M |
|
||||||
|
|
||||||
|
**Phase 2 完成标准**:所有 P1 修复,无超长文件,无 LIKE 全表扫描,无未包裹事务。
|
||||||
|
|
||||||
|
### Phase 3:P2 模式标准化(S-M,1-2 周)
|
||||||
|
|
||||||
|
| 任务批次 | 涉及 ID | 工作量 |
|
||||||
|
|---|---|---|
|
||||||
|
| **3.1 cacheFn 全量补齐** | G1-021~024, G3-001(剩余) | M |
|
||||||
|
| **3.2 SELECT * 改显式列** | G1-039~049, G3-009, G3-026~028, G5-007 | M(机械替换) |
|
||||||
|
| **3.3 日期 helper 提取** | G3-021, G3-047, G1-025 | S(提取 shared/lib/date-utils.ts) |
|
||||||
|
| **3.4 重复 helper 提取** | G1-026~027, G3-020, G3-036 | M |
|
||||||
|
| **3.5 JSDoc 补齐** | G1-034~036, G1-066~067, G3-046 | M |
|
||||||
|
|
||||||
|
**Phase 3 完成标准**:所有 P2 修复,模式统一,helper 集中到 shared/lib。
|
||||||
|
|
||||||
|
### Phase 4:P3 风格优化(XS,按需)
|
||||||
|
|
||||||
|
| 任务 | 涉及 ID | 工作量 |
|
||||||
|
|---|---|---|
|
||||||
|
| `as` widening 断言改类型标注 | G3-029~030 | XS |
|
||||||
|
| 非空断言 `!` 改类型守卫 | G1-060~065 | XS |
|
||||||
|
| `export *` 改显式 re-export | G3-048 | S |
|
||||||
|
| 死代码删除 | G3-018 | XS |
|
||||||
|
|
||||||
|
**Phase 4 完成标准**:零 `as`(非豁免)、零 `!`、零死代码。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、跨模块治理建议
|
||||||
|
|
||||||
|
### 8.1 新增 shared/lib 公共 helper
|
||||||
|
|
||||||
|
| helper | 路径 | 用途 | 替代模块 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `toISODateString` | shared/lib/date-utils.ts | 日期序列化 | attendance/scheduling/school/course-plans |
|
||||||
|
| `buildScopeFilter` | shared/lib/scope-filter.ts | DataScope → SQL 过滤 | attendance/grades/homework 等重复实现 |
|
||||||
|
| `serializeDate` | (合并到 date-utils.ts) | 同 toISODateString | — |
|
||||||
|
|
||||||
|
### 8.2 新增跨模块批量接口
|
||||||
|
|
||||||
|
| 接口 | 模块 | 用途 | 调用方 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `getActiveStudentIdsByClassIds(classIds)` | classes | 批量获取多班学生 ID | adaptive-practice、attendance |
|
||||||
|
| `getGradeNamesByIds(gradeIds)` | school | 批量获取年级名称 | textbooks、lesson-preparation |
|
||||||
|
| `getQuestionCountByKpIds(kpIds)` | questions | 知识点关联题目数 | textbooks |
|
||||||
|
| `getKpMasteryByTextbookId(textbookId)` | diagnostic | 教材下知识点掌握度 | textbooks |
|
||||||
|
|
||||||
|
### 8.3 新增权限点
|
||||||
|
|
||||||
|
| 权限点 | 用途 | 角色映射 |
|
||||||
|
|---|---|---|
|
||||||
|
| `AUDIT_LOG_PURGE` | 审计日志物理删除 | admin 专属 |
|
||||||
|
| `AUDIT_RETENTION_MANAGE` | 审计保留策略配置 | admin 专属 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、附录
|
||||||
|
|
||||||
|
### 9.1 完整规则表
|
||||||
|
|
||||||
|
见 [data-access-audit-framework-v1.md](./data-access-audit-framework-v1.md) 第二节。
|
||||||
|
|
||||||
|
### 9.2 sub-agent 原始输出索引
|
||||||
|
|
||||||
|
| 组 | 文件 | 问题数 |
|
||||||
|
|---|---|---|
|
||||||
|
| G1 | [g1-audit-output.json](./g1-audit-output.json) | 67 |
|
||||||
|
| G2 | [g2-data-access-audit.json](./g2-data-access-audit.json) | 11 |
|
||||||
|
| G3 | [g3-audit-output.json](./g3-audit-output.json) | 50 |
|
||||||
|
| G4 | [g4-audit-output.json](./g4-audit-output.json) | 61 |
|
||||||
|
| G5 | [g5-audit-output.json](./g5-audit-output.json) | 41 |
|
||||||
|
|
||||||
|
### 9.3 架构图遗漏记录
|
||||||
|
|
||||||
|
审计过程中发现的架构图(004/005)需补记项(治理阶段统一补图):
|
||||||
|
|
||||||
|
1. **parent 模块缺失 actions.ts** —— 004 文档模块清单未标注此异常
|
||||||
|
2. **onboarding/actions.ts 直查 DB** —— 005 文档 dependencyMatrix 需修正
|
||||||
|
3. **messaging/data-access.ts 拆分后** —— 005 文档 modules.messaging.exports 需更新
|
||||||
|
4. **新增权限点 AUDIT_LOG_PURGE / AUDIT_RETENTION_MANAGE** —— 005 文档 permissions 节点需补记
|
||||||
|
5. **新增 shared/lib/date-utils.ts** —— 004/005 shared 模块清单需补记
|
||||||
|
|
||||||
|
### 9.4 治理验证检查清单
|
||||||
|
|
||||||
|
每个 Phase 完成后必须通过:
|
||||||
|
|
||||||
|
- [ ] `npm run lint` 零错误
|
||||||
|
- [ ] `npx tsc --noEmit` 零错误
|
||||||
|
- [ ] 架构文档 004/005 同步更新
|
||||||
|
- [ ] `docs/troubleshooting/known-issues.md` 追加新模式
|
||||||
|
- [ ] 受影响模块的功能测试通过
|
||||||
|
- [ ] P0/P1 问题在 issues JSON 中标记为 resolved
|
||||||