PageHeader.goBack:上一页正好是声明的父页才 router.back()(与系统手势一致、 不留重复条目),否则一律 router.replace(父页) 落到逻辑上一级,杜绝来回跳。 闪卡复习可从首页/学习两处进入,去掉其固定 back-to,改回真正来源页。 |
||
|---|---|---|
| public | ||
| scripts | ||
| src | ||
| web | ||
| .dockerignore | ||
| .gitignore | ||
| AGENTS.md | ||
| docker-compose.yml | ||
| docker-entrypoint.sh | ||
| Dockerfile | ||
| Dockerfile.updater | ||
| package-lock.json | ||
| package.json | ||
| README.docker.md | ||
| README.md | ||
| server.js | ||
| WorkEnglish_前端风格与界面布局设计文档.md | ||
| WorkEnglish_轻社交功能设计文档.md | ||
📚 WorkEnglish —— 从工作中学习英语
把你日常的技术文档、英文邮件、中英混合会议纪要变成个人英语生词本 + 间隔重复(SRS)记忆系统。
核心理念:不背单词书。你学的每一个词都来自你真实工作场景的原文语境——技术术语(rollback、pipeline、stakeholder)、会议纪要里的地道表达,学完立刻能在明天的工作里用上。
✨ 功能
| 模块 | 说明 |
|---|---|
| 📥 素材导入 | 粘贴文本 / 上传文件(txt、md、docx、pdf)/ 扫描服务器目录,三种方式 |
| 🔍 智能提取 | 自动分词、词频统计、过滤基础词(ECDICT 词频 + 停用词),提取技术术语和缩写词(API、SLA) |
| 🀄 中英对照 | 中文会议纪要里嵌入的英文术语(如"需要做 rollback")自动配对原文中文句做语境 |
| 📖 语境卡片 | 单词 + 音标 + 释义 + 原文例句(英文句或中文对照句),未收录词自动标注 |
| 🧠 SRS 间隔重复 | SM-2 算法:忘记→明天再见,记得→1/6/14 天渐进拉长,符合遗忘曲线 |
| ✏️ 四种练习 | 英→中、中→英、原文填空(挖空工作原文!)、听音选词(浏览器 TTS) |
| 📈 学习统计 | 总词数/掌握量/连续打卡/近 30 天学习量/词汇等级分布 |
| 🔐 多用户 | 登录注册、数据隔离、首个用户自动成为管理员;适合团队/跨设备使用 |
| ✨ AI 增强(可选) | 填入 DeepSeek API Key 即启用「AI 语境讲解」;不填则纯本地离线运行 |
🚀 快速开始
要求:Node.js ≥ 18(推荐 20+),Python 3(仅安装词库时需要),磁盘剩余 ≥ 2GB。
# 1. 安装依赖
npm install --omit=dev
# 2. 安装离线词库(ECDICT,340 万词条,下载约 207MB)
npm run init-dict
# 3. 启动
npm start # 默认 http://localhost:3000
浏览器打开 http://localhost:3000,第一个注册的用户自动成为管理员。
端口被占用时:
PORT=8787 npm start数据目录:DATA_DIR=/data/workenglish npm start(默认./data)
🛠️ 环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
PORT |
3000 |
监听端口 |
HOST |
0.0.0.0 |
监听地址 |
DATA_DIR |
./data |
数据目录(app.db 用户数据 + stardict.db 词库) |
COOKIE_SECURE |
未设置 | 设为 1 表示启用 HTTPS 部署(Cookie 加 Secure 标记) |
📦 服务器部署(systemd + nginx)
1. 目录结构
mkdir -p /opt/workenglish/data /var/lib/workdocs
cd /opt/workenglish # 项目文件放这里
2. systemd 服务
/etc/systemd/system/workenglish.service:
[Unit]
Description=WorkEnglish - learn English from work documents
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/workenglish
Environment=NODE_ENV=production
Environment=PORT=3000
Environment=DATA_DIR=/opt/workenglish/data
Environment=COOKIE_SECURE=1
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now workenglish
⚠️ 若开启「目录扫描」,
www-data用户需要对扫描目录有读权限,例如:sudo chown -R www-data:www-data /var/lib/workdocs
3. nginx 反向代理(HTTPS 推荐)
/etc/nginx/sites-available/workenglish:
server {
listen 443 ssl http2;
server_name english.example.com;
ssl_certificate /etc/letsencrypt/live/english.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/english.example.com/privkey.pem;
client_max_body_size 30m; # 上传文件上限,与后端一致
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name english.example.com;
return 301 https://$host$request_uri;
}
sudo ln -s /etc/nginx/sites-available/workenglish /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
4. 目录扫描配置
- 管理员登录 → 「设置」→ 填写服务器上的绝对路径(如
/var/lib/workdocs)→ 保存 - 「素材」页点击「扫描目录中的文档」,支持 txt / md / docx / pdf
5. 数据备份
只需备份 data/app.db(用户数据)和 data/stardict.db(词库,可重新下载):
sqlite3 data/app.db ".backup /backup/app-$(date +%F).db"
✨ AI 增强(可选)
- 管理员 → 「我的」→「AI 设置」选择服务商并填入 API Key(模型默认见下方各服务商说明)
- 「素材导入」候选词可一键「AI 讲解」:AI 基于你的工作原文语境,给出贴合场景的释义和记忆法
- 词典兜底:配置后,添加生词时若离线词典未收录,会自动调用 AI 生成中文释义并缓存进生词本(生词本中标记为「AI补」);批量提取同样生效
- 不填 Key 则一切功能保持纯本地离线运行,文档数据不出服务器
支持的 AI 服务商(均为 OpenAI 兼容接口,可自由切换)
| 服务商 | 申请地址 | 默认模型 | 备注 |
|---|---|---|---|
| DeepSeek | platform.deepseek.com | deepseek-chat |
Key 形如 sk-... |
| 通义千问(阿里云) | bailian.console.aliyun.com(DashScope) | qwen-plus |
Key 形如 sk-...,兼容模式地址 |
| 讯飞星火 | console.xfyun.cn(开通星火大模型) | generalv3.5 |
使用「APIKey 认证」(OpenAI 兼容模式),非旧版签名鉴权 |
| Ollama 本地 | 本地/内网运行 ollama serve |
自定义(如 qwen2.5:7b) |
无需 Key;Docker 部署需让容器可访问 Ollama |
- 各服务商的 Key 独立保存,切换服务商无需重填;旧配置
ai_api_key自动视为 DeepSeek 的 Key - 「高级 → 接口地址」可填任意 OpenAI 兼容服务(Moonshot、vLLM 等)覆盖默认地址
- 每次请求使用当前选中的服务商;某个服务商不可用时切换其它家即可
🧪 测试
# 启动服务后(假设 8787 端口):
node scripts/smoke.js
# 或指定端口:
SMOKE_BASE=http://127.0.0.1:3000 node scripts/smoke.js
v2 冒烟:前端产物检查(SPA 挂载点 / 双主题 token / 入口资源)+ 全部 API 契约全流程(注册登录 → 素材导入候选提取 → 生词增删改查 → 复习评分 → 四种题型 → 统计 → 设置/AI 降级 → 登出)。
新版前端为 Vue 3 SPA(ES Module 构建),jsdom 无法执行,渲染级验证请用浏览器(可用 scripts/browser-smoke.js,需本机装 Playwright 且服务可访问时执行)。
💻 前端开发(web/ 目录)
前端为 Vue 3 + Vite + Vant 4,源码在 web/,构建产物输出到 public/(由 Express 托管,History 路由 fallback 后端已内置)。
# 开发模式(热更新):先起后端(npm start),再
cd web && npm install && npm run dev # http://localhost:5173,/api 自动代理到 3000
# 生产构建:产物写入 ../public
cd web && npm run build
注意:后端
src/与前端web/src/是两个独立目录;public/app.js、public/style.css为旧版前端(v1),构建不会删除,随时可回退(把git checkout -- public还原 index.html 即可)。
❓ 常见问题
- 词库没装/想重装:删掉
data/stardict.db后执行npm run init-dict(支持断点续传) - 想叠加多个词库:把任意含
stardict表的 sqlite 词库放入data/,命名为ecdict.db/stardict.db/dict.db之一即可。多个词库会按ecdict.db → stardict.db → dict.db顺序组成 fallback 链,查词时第一个命中的词库返回结果 - 单词在词典里查不到:添加时会自动标为「未收录」,生词本显示「未收录,点 ✏️ 补释义」。两种补法:① 在「设置」配置 DeepSeek Key,之后新添加的未收录词自动由 AI 补释义;② 在生词本点该词的「✏️ 编辑」按钮手动填写释义
- 改了释义想恢复:词典词条的释义存在生词本里,编辑后不会自动回写词典;点「✏️ 编辑」随时可改,只需留空即恢复为未收录状态(重新添加时会再次兜底)
- PDF 解析不出中文:系统内置的 pdf.js 对扫描版 PDF 无法提取文字(那是图片),请配合 OCR 工具先转文本
- 端口 3000 被占用:项目把端口做成环境变量
PORT=8787 npm start(本机部署建议 8787,避免和常见服务冲突) - 忘记管理员密码:
sqlite3 data/app.db "UPDATE users SET password_hash='' WHERE is_admin=1"后重新注册?——不可行,正确做法:直接删除该用户行后用新用户名注册成为新管理员(首个用户自动为管理员)。
📁 项目结构
server.js # Express 入口 + 全部 API 路由
src/
db.js # SQLite 初始化与表结构
auth.js # 注册/登录/会话(bcrypt + 签名 Cookie)
dictionary.js # 离线词库查询(支持多词库 fallback 链)
extract.js # 分词 / 语言识别 / 词频 / 语境例句提取
files.js # txt/md/docx/pdf 解析 + 目录扫描
srs.js # SM-2 间隔重复算法
ai.js # DeepSeek AI 讲解(预留接口,Key 可开关)
public/ # 前端(原生 JS,无构建,适配手机)
scripts/
fetch-dict.js # 词库下载安装
smoke.js # jsdom 浏览器冒烟测试
data/ # app.db(用户数据)+ stardict.db(词库)
📜 许可与技术说明
- 离线词库:ECDICT(MIT),包含音标/释义/词频/考试等级标签
- 间隔重复:SM-2(SuperMemo 2)算法
- 语音朗读:浏览器原生 SpeechSynthesis(无网络依赖)