docs: 重写 README,补全135编辑器转换器说明文档

This commit is contained in:
2026-09-09 00:19:17 +08:00
parent fed9ec7905
commit 3fb2c84e62
+141 -54
View File
@@ -1,57 +1,86 @@
# 食谱翻译工具
# 食谱工具(recipe_tool)
一款基于 AI 的食谱文档翻译工具,支持解析 Word 文档、智能翻译、批量导出。
一款面向幼儿园食堂的食谱处理工具集,目前包含两大功能:
1. **食谱翻译工具** —— 基于 AI 的食谱文档翻译:解析 Word 文档、智能翻译菜名、批量导出双语对照 Word
2. **135编辑器转换器** —— 将幼儿园周食谱 `.docx` 一键转换可直接粘贴进 [135编辑器](https://www.135editor.com/) / 微信公众号排版器的 HTML
## 功能特性
- **Word 文档解析** - 解析 `.docx` 格式的食谱文档,提取菜品信息
- **AI 智能翻译** - 支持 OpenAI / GLM 等大模型 API,批量翻译菜品名称
- **翻译词库管理** - 管理常用词组翻译对照表
- **多格式导出** - 导出双语对照的 Word 文档
- **导出设置** - 自定义未翻译文本颜色、页面方向、第二页表头等
### 食谱翻译工具
- **Word 文档解析**:解析 `.docx` 格式的食谱文档,提取菜品信息
- **AI 智能翻译**:支持 OpenAI / 智谱 GLM 等大模型 API,批量翻译菜品名称
- **翻译词库管理**:管理常用词组翻译对照表,命中词库免调 API
- **多格式导出**:导出双语对照的 Word 文档
- **导出设置**:自定义未翻译文本颜色、页面方向、第二页表头等
### 135编辑器转换器(`/editor135`)
- **一键转换**:上传周食谱 `.docx` → 自动解析「星期 × 餐次(早餐/早点/午餐/体弱儿餐/午点)」中英菜名 → 填充进 135 模板生成 HTML
- **三视图展示**:左侧源代码(可复制/下载)与解析结果(按星期折叠面板),右侧 375px 移动端宽度实时预览(与公众号阅读宽度一致,内部滚动、吸顶)
- **缺失星期自动删除**:docx 里缺哪天(含周六/周日、间隔天)就整段移除该天内容,导出的 HTML 不包含;支持「周一/礼拜天」等别名归一化
- **逐条回读校验**:填充后重新解析输出 HTML,与原始数据逐条比对,`mismatch` 必须为空才可信
- **样式零污染**:填充只改文本节点,不改样式与结构;构建前后做样式/图片/section 数快照对比
- **微信图片代理**:预览时微信 CDN(`mmbiz.qpic.cn`)图片走本地代理转发,解决「此图片来自微信公众平台」防盗链占位图;复制/下载的 HTML 仍保持原始 URL,不影响公众号内使用
- **智能容错**:裸文本菜名自动套用模板样式块、条目数与模板不符时自动克隆补足/删除多余
## 技术栈
- **框架**: Next.js 16 (App Router)
- **语言**: TypeScript
- **样式**: Tailwind CSS + shadcn/ui
- **文档**: docx (Word 生成)
- **数据库**: MySQL 8.0
- **AI**: OpenAI API / 智谱 GLM
- **框架**:Next.js 16(App Router, Turbopack)
- **语言**:TypeScript(strict)
- **运行时**:Node.js ≥ 22(jsdom 30 依赖新 Node API)
- **包管理**:pnpm ≥ 11(配置全部位于 `pnpm-workspace.yaml`)
- **样式**:Tailwind CSS 4 + shadcn/ui + radix-ui
- **文档解析**:mammoth(docx → HTML)、cheerio、jsdom(模板填充)
- **文档生成**:docx
- **数据库**:MySQL 8.0(mysql2)
- **AI**:OpenAI SDK / 智谱 GLM
## 项目结构
```
├── app/ # Next.js App Router
│ ├── api/ # API 路由
│ │ ├── ai/ # AI 翻译接口
│ │ ├── config/ # 配置管理接口
│ │ ├── recipe/ # 食谱解析接口
│ │ ├── translate/ # 翻译接口
│ │ └── words/ # 词库管理接口
│ ├── settings/ # 设置页面
│ │ ├── ai/ # AI 配置
│ │ ├── database/ # 数据库配置
│ │ ├── export/ # 导出设置
│ │ └── translation/ # 翻译词库
│ └── page.tsx # 首页
├── components/ # React 组件
├── lib/ # 工具函数
│ ├── config.ts # 配置管理
│ ├── database.ts # 数据库连接
│ └── ai-translate.ts # AI 翻译
├── config.json # 配置文件
├── Dockerfile # Docker 构建
└── docker-compose.yml # Docker 编排
├── app/ # Next.js App Router
│ ├── api/ # API 路由
│ │ ├── ai/ # AI 翻译接口
│ │ ├── config/ # 配置管理接口
│ │ ├── editor135/
│ │ │ ├── render/ # 135转换:解析docx并填充模板
│ │ │ └── img/ # 微信CDN图片代理(仅允许 *.qpic.cn)
│ │ ├── recipe/ # 食谱解析接口
│ │ ├── translate/ # 翻译接口
│ │ ├── update/ # 应用自更新(check/execute)
│ │ └── words/ # 词库管理接口
│ ├── editor135/ # 135转换器页面
│ ├── settings/ # 设置页面
│ │ ├── ai/ # AI 配置
│ │ ├── export/ # 导出设置
│ │ ├── translation/ # 翻译词库
│ │ └── update/ # 应用更新
│ └── page.tsx # 首页(功能导航)
├── components/ # React 组件
├── lib/
│ ├── ai-translate.ts # AI 翻译
│ ├── config.ts # 配置管理
│ ├── database.ts # 数据库连接
│ ├── editor135-core.ts # 135转换核心库(解析/填充/缺失天删除/校验)
│ └── update.ts # 自更新逻辑
├── public/
│ └── template135.html # 135模板(运行时读取,替换此文件即可换模板)
├── scripts/
│ └── test-remove-days.ts # 135转换核心库回归测试(6场景)
├── config.json # 配置文件(首次启动由环境变量初始化)
├── Dockerfile # Docker 构建(node:22-alpine 双阶段)
└── docker-compose.yml # Docker 编排
```
## 快速开始
### 环境要求
- Node.js 20+
- MySQL 8.0
- pnpm
- Node.js **22+**(jsdom 30 要求)
- pnpm 11+(构建脚本批准配置在 `pnpm-workspace.yaml`)
- MySQL 8.0(翻译功能需要)
### 本地开发
@@ -60,21 +89,26 @@
pnpm install
# 2. 配置环境变量
cp .env.docker .env
# 编辑 .env 填入数据库和 AI API 配置
# 参考下方「环境变量」表格创建 .env,或从已有部署复制
# 3. 启动开发服务器
pnpm dev
```
访问 http://localhost:3000
访问 http://localhost:3000 ,翻译工具在首页,135转换器在 `/editor135` 或首页右上角「135编辑器转换」入口。
### 生产构建
```bash
pnpm build
pnpm start
```
### Docker 部署
```bash
# 1. 配置环境变量
cp .env.docker .env
# 编辑 .env 填入配置
cp .env.docker .env # 或手动创建,见下方表格
# 2. 构建并启动
docker-compose up -d
@@ -87,18 +121,16 @@ docker-compose logs -f app
### 配置文件
所有配置存储在 `config.json` 中,可通过设置页面修改:
所有配置存储在 `config.json` 中,可通过设置页面修改(首次启动由环境变量初始化,之后以 `config.json` 为准):
| 配置项 | 说明 |
|--------|------|
| `db` | 数据库连接配置 |
| `ai` | AI 翻译 API 配置 |
| `export` | 导出文档格式配置 |
| `db` | 数据库连接配置(host / port / user / password / database) |
| `ai` | AI 翻译 API 配置(apiKey / baseUrl / model) |
| `export` | 导出文档格式配置(untranslatedColor / orientation / secondPageHeader) |
### 环境变量
首次启动时,环境变量会初始化 `config.json`。之后配置以 `config.json` 为准。
| 变量名 | 说明 | 默认值 |
|--------|------|--------|
| `DB_HOST` | 数据库地址 | 127.0.0.1 |
@@ -109,9 +141,15 @@ docker-compose logs -f app
| `AI_API_KEY` | AI API Key | - |
| `AI_BASE_URL` | API 地址 | https://api.openai.com/v1 |
| `AI_MODEL` | 模型名称 | gpt-3.5-turbo |
| `EXPORT_UNTRANSLATED_COLOR` | 未翻译文本颜色 | #FF0000 |
| `EXPORT_ORIENTATION` | 页面方向 | landscape |
| `ONEDEV_ACCESS_TOKEN` | 自更新拉取镜像的令牌 | - |
| `REGISTRY_IMAGE` | 更新镜像仓库地址 | - |
## API 接口
### 翻译工具
| 接口 | 方法 | 说明 |
|------|------|------|
| `/api/config` | GET/POST | 获取/保存配置 |
@@ -121,13 +159,62 @@ docker-compose logs -f app
| `/api/words` | GET/POST | 词库列表/添加 |
| `/api/words/[id]` | PATCH/DELETE | 编辑/删除词条 |
## 导出设置
### 135编辑器转换器
| 选项 | 说明 |
|------|------|
| 未翻译文本颜色 | 未翻译菜品在文档中的显示颜色 |
| 页面方向 | 横向/纵向 |
| 第二页表头 | 多页时后续页显示表头行 |
| 接口 | 方法 | 说明 |
|------|------|------|
| `/api/editor135/render` | POST | 上传 `.docx`(multipart `file` 字段),返回 `{ fileName, summary, recipe, html, report }` |
| `/api/editor135/img` | GET | 微信图片代理:`?u=<URL编码的图片地址>`,仅允许 `*.qpic.cn` 域名 |
`render` 返回的 `report` 结构:
```jsonc
{
"filled": [ // 每个餐次的填充明细
{ "day": "星期一", "meal": "早餐", "replaced": 4, "added": 0, "removed": 0, "oldCount": 4, "newCount": 4 }
],
"warnings": [], // 非致命提示(如模板中无该餐次)
"repaired": [], // 裸文本菜名自动套样式的修复记录
"mismatch": [], // 回读校验差异,正常必须为空数组
"removedDays": ["星期五"], // docx 缺失、已从输出整段删除的星期
"verification": { // 构建前后快照对比
"stylesAdded": 0, "stylesRemoved": 0, // 样式变化数,正常为 0
"imgsUnchanged": true, // 图片是否未被改动
"sections": "570 -> 570", "elements": "1502 -> 1242"
}
}
```
## 135编辑器转换器工作原理
1. **解析**:mammoth 将 docx 转为 HTML,逐叶子节点扫描,识别星期标题(支持 周一/礼拜一 等别名归一化)、餐次标题、中文菜名与紧跟的英文菜名,按「星期 → 餐次 → 条目」收集
2. **删除缺失天**:对比 docx 与模板中出现的星期,缺失的天用「逐节点精确区间删除」从模板 DOM 移除(从该天标题节点删到下一现存天标题之前;最后一天以「三审三校/温馨提示」为尾界)——算法处理了相邻天共用容器、深层嵌套等结构,不会误删保留天
3. **填充**:就地替换文本节点(文本未变不触碰),条目数不符时以模板最后一条为锚点克隆补足或从后往前删除多余
4. **校验**:重新收集输出 DOM 与原始数据逐条比对(`mismatch`),并对比构建前后的样式/图片/元素快照(`verification`)
5. **输出**:序列化 HTML 返回,可直接粘贴进 135编辑器 / 公众号后台
### 更换模板
替换 `public/template135.html` 即可。模板需包含:星期标题(星期一~星期日文本)、餐次标题(早餐/早点/午餐/体弱儿餐/午点)、以及至少一组「中文菜名块 + 紧邻的英文菜名块」作为克隆锚点。
### 运行测试
```bash
# Node 22+ 直接跑 TS(无需编译)
node --experimental-strip-types scripts/test-remove-days.ts
```
测试覆盖 6 个场景:间隔天(一/三/五)、含周末、仅周末、中间连续缺、条数增删路径、完整周回归。
## Docker 构建注意事项
- 基础镜像为 `node:22-alpine`(jsdom 30 需 Node ≥ 22,`builder` 与 `runner` 两个 stage 都要用 22)
- pnpm 11 的构建脚本批准配置在 `pnpm-workspace.yaml` 的 `allowBuilds`,该文件必须在 `pnpm install` 之前 COPY 进镜像
- `pnpm-workspace.yaml` 中已放行 `msw` / `sharp` / `unrs-resolver` 的构建脚本(pnpm 11 在 CI 下未批准会直接报 `ERR_PNPM_IGNORED_BUILDS`)
## 自更新机制
部署后可通过设置页「应用更新」拉取镜像仓库新版本:`/api/update/check` 检查、`/api/update/execute` 执行 `docker pull` 并重启容器(需要挂载 docker.sock 及 `REGISTRY_IMAGE` / `ONEDEV_ACCESS_TOKEN` 配置)。
## License