# 苏科版八上物理 · 运维备忘录

> 最后更新：2026-08-04  
> 目的：记录已知问题和标准操作流程，避免团队踩坑

---

## 一、课件文件版本管理

### ⚠️ 问题：v1 和 v2 课件混淆

**现象**：用户打开课件发现只有文字没有配图，反馈"仍然没有图"。

**根因**：workspace 同时存在 `v1`（纯文字原版）和 `v2`（图文并茂修复版）两套课件文件。用户可能误打开 v1。

**命名规范**：
| 版本 | 文件名格式 | 说明 | 状态 |
|------|-----------|------|:---:|
| v1 | `XXXX_JB_xxx_课件_v1.pptx` | 原始纯文字版本 | 🗑️ 已废弃 |
| v2 | `XXXX_JB_xxx_课件_v2.pptx` | 图文并茂修复版 | ✅ 当前使用 |

**标准操作**：
1. ✅ 所有入口链接（index.html）**必须**指向 v2 版本
2. ✅ 对外发布前确认 v1 文件已清理或隔离
3. ✅ 新增/修复课件时直接更新 v2，不再创建 v1

---

## 二、Cloudflare Pages 部署

### ⚠️ 问题1：部署到 Preview 而非 Production

**现象**：`wrangler pages deploy` 成功，但自定义域名 `phy81.bugking.de5.net` 仍显示旧版本。

**根因**：CF Pages 项目的 production 分支是 `main`，而我们用的是 `master` 分支。wrangler 部署时只进了 Preview 环境，自定义域名绑定的是 Production 环境。

**解决方案**：
```bash
# 1. 将 CF Pages 项目的 production 分支改为 master（只需执行一次）
curl -s -X PATCH "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/physics-grade8-interactive" \
  -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"production_branch": "master"}'

# 2. 重新部署
npx wrangler pages deploy . --project-name=physics-grade8-interactive --branch=master
```

**验证方法**：
```bash
# 查看最近部署是否为 production 环境
curl -s "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/pages/projects/physics-grade8-interactive/deployments" \
  -H "Authorization: Bearer ${TOKEN}" | python3.11 -c "
import sys,json; [print(f'{d[\"environment\"]:12s} | {d[\"url\"]}') for d in json.load(sys.stdin)['result'][:3]]"
```

### ⚠️ 问题2：沙箱环境 CF API 不可达

**现象**：`wrangler pages deploy` 报 `fetch failed` 或 `SSL connection timeout`。

**根因**：沙箱 DNS 将 `api.cloudflare.com` 劫持到内部地址 `198.18.0.26`。

**解决方案**：
```bash
# 修改 /etc/hosts，将 api.cloudflare.com 指向正确 IP
cp /etc/hosts /tmp/hosts_bak
grep -v cloudflare /tmp/hosts_bak > /etc/hosts
echo "104.16.131.229 api.cloudflare.com" >> /etc/hosts

# 验证
python3.11 -c "import socket; print(socket.gethostbyname('api.cloudflare.com'))"
# 应输出: 104.16.131.229
```

> **注意**：沙箱重启后 /etc/hosts 会重置，每次部署前需重新添加。

### 标准部署流程（完整版）

```bash
# === 第1步：修复 CF API 网络（每次沙箱重启后必须执行）===
cp /etc/hosts /tmp/hosts_bak
grep -v cloudflare /tmp/hosts_bak > /etc/hosts
echo "104.16.131.229 api.cloudflare.com" >> /etc/hosts

# === 第2步：设置环境变量 ===
export CLOUDFLARE_API_TOKEN="cfut_9ddJCNAeOcMUqXZtd7ccNET0qeIcNGDpjgkLKRxd3cabd3be"
export CLOUDFLARE_ACCOUNT_ID="0a41f01aa097498d7aa4133b1cb4ac47"

# === 第3步：提交 Git ===
cd /workspace
git add -A
git commit -m "描述变更内容"
git push origin master

# === 第4步：部署到 CF Pages ===
npx wrangler pages deploy . --project-name=physics-grade8-interactive --branch=master

# === 第5步：验证 ===
# 检查 production 环境
curl -s "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/physics-grade8-interactive/deployments" \
  -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" | python3.11 -c "
import sys,json
for d in json.load(sys.stdin)['result'][:3]:
    print(f'{d[\"environment\"]:12s} | {d[\"url\"]}')"

# 验证自定义域名
curl -sI https://phy81.bugking.de5.net | head -3
```

### 关键凭证

| 项目 | 值 |
|------|-----|
| CF Account ID | `0a41f01aa097498d7aa4133b1cb4ac47` |
| CF API Token | `cfut_9ddJCNAeOcMUqXZtd7ccNET0qeIcNGDpjgkLKRxd3cabd3be` |
| CF Project | `physics-grade8-interactive` |
| CF Production Branch | `master`（已从 main 修改） |
| 自定义域名 | `phy81.bugking.de5.net` |
| Gitee 仓库 | `gitee.com/vrkaso/physics-grade8-interactive` |

---

## 三、课件配图质量标准

### 扫描标准

使用 XML 级别扫描（解压 PPTX 后统计 `<a:ln>` 等元素）：

| 等级 | 条件 | 说明 |
|:---:|------|------|
| 🌟 优秀 | 有意义密度 ≥ 10/页 且 线条 ≥ 页数×3 | 每页都有丰富配图 |
| ✅ 达标 | 有意义密度 ≥ 5/页 且 线条 ≥ 页数 | 基本图文并茂 |
| ❌ 需修复 | 不满足以上条件 | 需补充配图 |

> "有意义图形" = 线条（光路图/示意图）+ 椭圆（元件/分子）+ 三角形

### 扫描命令

```bash
cd /workspace
for f in *_课件_v2.pptx; do
  tmpdir="/tmp/scan_$$"
  mkdir -p "$tmpdir" && unzip -q "$f" -d "$tmpdir"
  slides=$(ls "$tmpdir/ppt/slides/slide"*.xml | wc -l)
  lines=$(grep -o '<a:ln[ >]' "$tmpdir"/ppt/slides/slide*.xml | wc -l)
  ellipses=$(grep -o '<a:prstGeom prst="ellipse"' "$tmpdir"/ppt/slides/slide*.xml | wc -l)
  meaningful=$((lines + ellipses))
  density=$((meaningful / slides))
  printf "%-45s | %2d页 | 线%4d 椭%3d | 密%3d/页\n" "$f" "$slides" "$lines" "$ellipses" "$density"
  rm -rf "$tmpdir"
done
```

---

## 四、PPTX 构建规范（pptxgenjs）

### 模板骨架

```javascript
const pptxgen = require("pptxgenjs");
const pptx = new pptxgen();
pptx.defineLayout({ name: "WIDE", width: 13.333, height: 7.5 });
pptx.layout = "WIDE";

// 形状 API（注意大小写）:
// pptx.shapes.LINE          - 线条
// pptx.shapes.RECTANGLE     - 矩形
// pptx.shapes.ROUNDED_RECTANGLE - 圆角矩形
// pptx.shapes.OVAL          - 椭圆/圆

// 颜色只能用6位 hex RGB（不能带透明度如 FFFFFFCC）

pptx.writeFile({ fileName: "/workspace/output.pptx" });
```

### 常见错误

| 错误 | 原因 | 修复 |
|------|------|------|
| `UNKNOWN-LAYOUT` | `pptx.layout = {width, height}` | 必须用 `pptx.defineLayout()` |
| `pptx.ShapeType not found` | API 错误 | 用 `pptx.shapes.LINE` 等 |
| 颜色警告 `"000000" used instead` | 用了8位 hex | 只用6位 hex RGB |
| 字符串引号冲突 | 中文引号 `"` 在 JS 字符串中 | 用 `「」` 替代 |

---

## 五、已确认的陷阱清单

- [x] CF Pages production 分支已设为 `master`（非 `main`）
- [x] 所有 index.html 链接指向 v2（非 v1）
- [x] 沙箱 /etc/hosts 需要手动添加 CF API IP
- [x] PPTX 扫描必须解压后数 XML 元素，不能看文件大小
- [x] pptxgenjs 形状名必须全大写（LINE/RECTANGLE/OVAL）
- [x] 0405 水循环课件中「液化成雨」已修正为冰晶熔化
- [x] 0302 透镜课件已重建含 193 条光路图线
- [x] **PPT无法打开 = cNvPr id 重复（pptxgenjs已知bug）**：同页内表格与文本框共用同一 id 会触发 Office 修复提示，需用 python-pptx 回合保存 + 重新编号 id 修复
- [x] **光线图缺箭头**：0301/0204/0304 的光线图 headEnd/tailEnd=0，需补方向箭头
- [x] **2026-08-04 光路图修复**：
  - 0204《光的反射》4页光路图缺入射光线 → 用 pptxgenjs 重建（build_0204_rays.js），补全入射光+反射光+法线+镜面+方向箭头
  - 全部7个光学课件批量添加 ~720 个方向箭头（headEnd 作为 a:ln 子元素插入）
  - ⚠️ headEnd 必须是 `<a:ln>` 的子元素，不是 `<p:spPr>` 的子元素！插错位置会破坏 XML
  - ⚠️ python-pptx save() 后文件可能被 lxml 解析但 pptx Package.open() 失败；用 git restore 恢复后重试
  - 安全做法：通过 python-pptx API 修改 lxml 树 + prs.save()，不手动操作 ZIP

---

## 六、后续部署 checklist

每次更新课件后，按以下顺序操作：

1. ☐ 扫描全部 v2 课件确认配图达标
2. ☐ 如有新增/修复课件，更新 index.html 入口链接
3. ☐ 修复 /etc/hosts（如沙箱已重启）
4. ☐ `git add -A && git commit -m "..." && git push origin master`
5. ☐ `npx wrangler pages deploy . --project-name=physics-grade8-interactive --branch=master`
6. ☐ 确认部署环境为 `production`
7. ☐ 验证 `https://phy81.bugking.de5.net/` 内容正确
