3fb2c84e625860c2ba8346a55842a3368f6deab0
食谱工具(recipe_tool)
一款面向幼儿园食堂的食谱处理工具集,目前包含两大功能:
- 食谱翻译工具 —— 基于 AI 的食谱文档翻译:解析 Word 文档、智能翻译菜名、批量导出双语对照 Word
- 135编辑器转换器 —— 将幼儿园周食谱
.docx一键转换可直接粘贴进 135编辑器 / 微信公众号排版器的 HTML
功能特性
食谱翻译工具
- 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, 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/ # 配置管理接口
│ │ ├── 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 22+(jsdom 30 要求)
- pnpm 11+(构建脚本批准配置在
pnpm-workspace.yaml) - MySQL 8.0(翻译功能需要)
本地开发
# 1. 安装依赖
pnpm install
# 2. 配置环境变量
# 参考下方「环境变量」表格创建 .env,或从已有部署复制
# 3. 启动开发服务器
pnpm dev
访问 http://localhost:3000 ,翻译工具在首页,135转换器在 /editor135 或首页右上角「135编辑器转换」入口。
生产构建
pnpm build
pnpm start
Docker 部署
# 1. 配置环境变量
cp .env.docker .env # 或手动创建,见下方表格
# 2. 构建并启动
docker-compose up -d
# 3. 查看日志
docker-compose logs -f app
配置说明
配置文件
所有配置存储在 config.json 中,可通过设置页面修改(首次启动由环境变量初始化,之后以 config.json 为准):
| 配置项 | 说明 |
|---|---|
db |
数据库连接配置(host / port / user / password / database) |
ai |
AI 翻译 API 配置(apiKey / baseUrl / model) |
export |
导出文档格式配置(untranslatedColor / orientation / secondPageHeader) |
环境变量
| 变量名 | 说明 | 默认值 |
|---|---|---|
DB_HOST |
数据库地址 | 127.0.0.1 |
DB_PORT |
数据库端口 | 3306 |
DB_USER |
数据库用户名 | root |
DB_PASSWORD |
数据库密码 | - |
DB_NAME |
数据库名 | recipe_tools |
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 | 获取/保存配置 |
/api/recipe |
POST | 解析食谱文档 |
/api/translate |
POST | 批量翻译 |
/api/ai/translate |
GET | 单词翻译测试 |
/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 结构:
{
"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编辑器转换器工作原理
- 解析:mammoth 将 docx 转为 HTML,逐叶子节点扫描,识别星期标题(支持 周一/礼拜一 等别名归一化)、餐次标题、中文菜名与紧跟的英文菜名,按「星期 → 餐次 → 条目」收集
- 删除缺失天:对比 docx 与模板中出现的星期,缺失的天用「逐节点精确区间删除」从模板 DOM 移除(从该天标题节点删到下一现存天标题之前;最后一天以「三审三校/温馨提示」为尾界)——算法处理了相邻天共用容器、深层嵌套等结构,不会误删保留天
- 填充:就地替换文本节点(文本未变不触碰),条目数不符时以模板最后一条为锚点克隆补足或从后往前删除多余
- 校验:重新收集输出 DOM 与原始数据逐条比对(
mismatch),并对比构建前后的样式/图片/元素快照(verification) - 输出:序列化 HTML 返回,可直接粘贴进 135编辑器 / 公众号后台
更换模板
替换 public/template135.html 即可。模板需包含:星期标题(星期一~星期日文本)、餐次标题(早餐/早点/午餐/体弱儿餐/午点)、以及至少一组「中文菜名块 + 紧邻的英文菜名块」作为克隆锚点。
运行测试
# 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
MIT
Description
Languages
HTML
53.6%
TypeScript
44.3%
CSS
1%
Shell
0.5%
Dockerfile
0.4%
Other
0.2%