Initial commit: WpywMail 桌面客户端:Node 零依赖本地服务 + React 19 / shadcn-ui 界面,支持收发信、注册、找回密码、会话与账号管理

This commit is contained in:
WpyQwq
2026-09-19 11:20:43 +08:00
commit c7fb8f8f68
47 changed files with 11573 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
# WpywMail 客户端
给自建邮件服务器 **WpywMail**(`mail.example.com`)写的桌面客户端。
收信走 **IMAP4rev1(993 隐式 TLS)**,发信走 **SMTP 提交(587 STARTTLS + AUTH)**。
界面用 **shadcn/ui + Tailwind CSS v4 + Vite(React 19)** —— 结构与视觉以 shadcn 官方的
Mail 示例为基线,控件不自己发明。后端是**零依赖的 Node 服务**,负责讲 IMAP/SMTP 与 MIME。
## 怎么启动
双击 **`start.cmd`**。它会:
1. 若 `ui/dist` 尚未构建,自动 `npm install` + `npm run build`(首次约一两分钟);
2. 起一个只监听 `127.0.0.1:8788` 的本地服务(界面与邮件协议之间的桥);
3. 自动打开浏览器指向 `http://127.0.0.1:8788/`。
关闭那个黑窗口就等于退出客户端。需要 Node.js 20+(当前机器 v24)。
改界面时用热更新:`npm --prefix ui run dev`(已配好把 `/api` 代理到 8788)。
首次启动若 `data\account.json` 里没有账号,界面会要求登录;登录成功后凭据写回该文件(只在本机)。
## 现在能做什么
| 能力 | 说明 |
|---|---|
| 收信 | 6 个文件夹(收件箱/草稿/已发送/归档/垃圾邮件/废纸篓),带未读数 |
| 邮件列表 | 头像、发件人、主题、时间、大小、未读圆点、旗标、已回复/草稿标记 |
| 阅读区 | 中文主题与正文正确解码、附件下载、发件人与收件人信息、完整时间 |
| 撰写 / 回复 / 转发 | 弹窗式;中文头部自动 RFC 2047 编码;多附件;回复带 `In-Reply-To`/`References` |
| 快速回复 | 阅读区底部直接写,`Ctrl+Enter` 发送 |
| 草稿 | 「存草稿」写入 Drafts |
| 搜索 | 主题 / 发件人 / 收件人 / UID,**支持中文**(见「已知限制」) |
| 管理动作 | 已读未读、旗标、归档、删除(默认进废纸篓)、切换文件夹 |
| 主题 | 深色/浅色双主题,切换后记忆(默认深色) |
### 键盘
| 键 | 动作 |
|---|---|
| `J` / `K`(或 ↑↓) | 上下选邮件 |
| `Enter` | 打开 |
| `C` | 撰写 |
| `R` / `F` | 回复 / 转发 |
| `S` | 加/取消旗标 |
| `U` | 已读/未读切换 |
| `E` | 归档 |
| `#` | 删除 |
| `/` | 聚焦搜索框 |
| `Esc` | 关闭弹窗 / 取消焦点 |
## 架构
```
start.cmd
└─ node server/index.js 本地服务(仅 127.0.0.1:8788):托管界面 + JSON API
├─ server/imap.js IMAP4rev1 客户端(自己实现,含字面量感知解析器)
├─ server/smtp.js SMTP 提交客户端(STARTTLS + AUTH PLAIN,DATA 按字节写出)
├─ server/mime.js MIME 编解码(RFC 5322/2047/2231/2045)
├─ server/account.js 账户配置读写(data/account.json)
├─ ui/ 界面源码(shadcn/ui + Tailwind + Vite)→ 构建到 ui/dist
└─ web/ 旧版纯手写界面(兜底;ui/dist 不存在时才会被发出去)
```
**为什么后端零依赖**:IMAP/SMTP/MIME 是这块的硬骨头,自己实现能对症下药 —— 实测用它挖出了服务端
三个真实缺陷(见 `E:\deepseek\artifacts\wpywmail-server-findings.md`),并且不引入供应链风险。
**为什么界面用现成组件库**:控件观感不必自己发明。shadcn 是「把源码复制进项目」的组件(不是黑盒依赖),
随手可改,同时白拿 Radix 的可访问性底座与 Tailwind 的迭代速度。
## 设计系统
结构实例化 **shadcn/ui 官方 Mail 示例**(左:账号 + 文件夹导航;中:邮件列表;右:阅读与操作)。
配色、圆角、阴影、暗色全部用 **shadcn 默认(new-york / neutral)主题 token**,原样写在
`ui/src/styles.css`(`oklch()` 变量 + `.dark` 覆盖),所以 light/dark 都是"官方观感"。
换主题只改那一个文件的变量块。
字体走系统栈(Segoe UI / 微软雅黑 / PingFang)—— 邮件客户端要的是"读起来像系统原生",不是品牌表达。
## 验收记录
| 套件 | 结果 | 报告 |
|---|---|---|
| MIME 单元测试 `node tools/mime-test.js` | **25/25** | `E:\deepseek\artifacts\client-mime-test.txt` |
| 协议闭环自测 `node tools/smoke.js` | **20/20** | `E:\deepseek\artifacts\client-smoke.txt` |
| 后端 API 链路 `node tools/api-check.js` | **10/10** | `E:\deepseek\artifacts\client-api-check.txt` |
| 界面运行时(新 UI)`node tools/ui-check-v2.js` | **8/8** | `E:\deepseek\artifacts\client-ui-check-v2.txt` |
`smoke.js` 是真正的闭环:**用本项目的代码发一封中文信 → 服务器投递 → 再用本项目的 IMAP 代码
把它从收件箱取回并解析**,断言主题与正文(含全角标点)往返无损。
`ui-check-v2.js` 用 **Edge 无头 + CDP(Node 自带 WebSocket,零依赖)** 真开页面:收集 JS 异常与
控制台报错、断言文件夹/列表/阅读区/撰写弹窗都渲染、验证主题切换真的改变背景色、检查无横向溢出。
`api-check.js` 覆盖界面所用的那条链路:发送 → 到达 → 旗标 → 未读 → 移动(归档/移回)→ 删除 → 清理痕迹。
## 已知限制(都是真的,没藏)
1. **搜索是「服务端取回摘要 + 本地过滤」**,窗口上限 500 封,**不含正文全文**。
原因:服务端 `SEARCH` 没实现 `BODY`/`OR`,且命令行按 `Encoding.ASCII` 解码 → 中文检索词到服务端
会变成 `??`(详见服务端缺陷记录)。客户端绕过了它,所以中文搜索可用。
2. **不渲染 HTML 邮件正文**,只渲染解析出的纯文本(避免 XSS)。只有 HTML 的邮件会退化成去标签文本。
3. **附件只支持「添加并随信发出」与「下载」**,不做内联图片渲染。
4. **密码以明文存在 `data\account.json`**(本机、便携)。要更稳妥可以用 Windows DPAPI 包一层。
5. 未实现:IDLE 实时推送(目前 30 秒轮询文件夹计数)、会话视图、离线缓存、富文本撰写、多账号。
6. 服务端目前**没有 `SPECIAL-USE`**,文件夹靠名字映射。
7. `web/`(旧手写界面)已退役但保留作兜底;日常运行的是 `ui/dist`。
## 排障
- **界面显示"正在连接邮件服务器…"不动**:看黑窗口里的报错,多半是服务器 993 连不上或密码变了。
- **发送失败**:报错原样显示服务端返回(如 `550 Relay denied`)。家用 DNS 偶发瞬时解析失败
(`EAI_AGAIN`)已加一次自动重试。
- **改了界面没生效**:`npm --prefix ui run build` 重新构建(`ui/dist` 是静态产物,改了源码不会自动更新)。
- **端口被占**:设环境变量 `WPYWMAIL_PORT` 换端口。