tool.xpcool.com/agents.md
夏犀麟 6e25de5c21 feat: 全站拖拽上传 + 图片压缩批量与多选项优化
- 新增 UploadDrop 通用组件:点击选择 + 拖拽上传、图片缩略图回显、
  批量/单文件、删除,替代各页面的 t-upload 文件模式
- 图片压缩重写:批量(最多 20 张)、按质量/限制大小双模式、
  等比缩放、保持原格式、JPG 透明图白底填充、jszip 批量下载
- 修复压缩页历史解析正则 bug(/| jpg/ 恒为 true)与 slider label 类型错误
- 裁剪/二维码/Base64/哈希/OCR 页面统一接入 UploadDrop,文件变化改用 watch 响应
- 更新 agents.md 与 README:UploadDrop 约定、新坑位与依赖说明
2026-08-21 15:37:06 +08:00

167 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# agents.md — 项目维护与扩展指南
本文档面向后续接手维护/扩展本项目的 AI 编码代理Agent与开发者说明架构约定、关键机制、开发流程与常见坑。动手改代码前请先读完本文。
## 1. 项目定位
- 名称tool.xpcool.com在线工具箱
- 形态:**纯前端 SPA**,无后端、无 mock 接口。所有计算与文件处理在浏览器本地完成,用户文件不出浏览器。
- 技术栈Vite 6 + Vue 3`<script setup>`+ Pinia + TypeScriptstrict+ UnoCSS + TDesign Vue Next + pnpm。
- 客户端PC Web + 移动端 H5 响应式(`<768px` 时侧边栏变为抽屉导航)。
- 包管理**pnpm**存在 `pnpm-lock.yaml`禁止换成 npm/yarn 安装)。
## 2. 目录结构与职责
```
src/
├── main.ts # 入口:注册 Pinia/Router/TDesign 全量组件,初始化主题
├── App.vue # 仅承载 <router-view>
├── assets/styles/ # 全局 SCSS颜色只用 TDesign 令牌变量(--td-*
├── router/
│ ├── tools.ts # ★ 工具注册表:全站工具唯一数据源(菜单/首页/路由都从这里生成)
│ └── index.ts # 路由表MainLayout 作为父路由,子路由由 tools.ts 映射生成
├── stores/theme.ts # 暗黑/浅色主题Pinia + localStorage 持久化)
├── composables/
│ ├── useHistory.ts # 本地历史(每个工具独立 keylocalStorage 持久化)
│ ├── useClipboard.ts # 复制Clipboard API + execCommand 回退)
│ └── useDownload.ts # 下载Blob/文本/DataURL
├── components/common/ # CopyButton / DownloadButton / ErrorAlert / HistoryPanel
├── layout/
│ ├── MainLayout.vue # 顶栏 + 左侧固定导航 + 内容区 + 移动端抽屉 + 底部
│ └── SideNav.vue # 导航列表(含首页项 + 工具列表,数据来自 tools.ts
├── utils/index.ts # 通用函数(文件读取、字节格式化、北京时间格式化等)
└── views/
├── HomeView.vue # 首页:工具卡片栅格(数据来自 tools.ts
└── <tool-name>/XxxView.vue # 每个工具一个目录一个页面
```
## 3. 核心机制
### 3.1 工具注册表(最重要的扩展点)
`src/router/tools.ts` 是全站唯一数据源侧边导航首页卡片路由表均由此自动生成。**新增工具不要手动改路由文件首页或侧边栏。**
```ts
export interface ToolItem {
path: string // 路由路径(相对根路径,不含前导 /
name: string // 工具名(菜单/卡片/页面标题)
desc: string // 一句话描述
icon: Component // TDesign 图标组件
component: () => Promise<Component> // 懒加载组件
}
```
**新增一个工具的完整步骤:**
1. 新建 `src/views/<tool-name>/<ToolName>View.vue`参考现有页面结构标题 表单卡片 错误提示 结果卡片 历史面板)。
2. `tools.ts` `tools` 数组末尾追加一条配置`component` 使用 `() => import('@/views/<tool-name>/<ToolName>View.vue')`
3. 完成——菜单首页卡片路由文档标题自动生效可运行 `pnpm type-check` 验证
**注意:** `path` 必须与页面内 `useHistory('xxx')` key 一致历史记录约定并避免与现有 14 path 重复
### 3.2 历史记录约定
- 统一使用 `useHistory(key)`key 建议等于路由 path例如 `useHistory('json-format')`
- 数据结构 `HistoryItem { text: string; time: number }`上限 10 去重最新在前localStorage 持久化key 形如 `tool-history:<key>`)。
- 文本类工具`text` 存输入原文点击历史回填输入框
- 文件类工具图片压缩/裁剪等文件本体**不要**存入 localStorage会撑爆配额改为存文件名 | 关键参数形式的摘要点击历史只恢复参数
### 3.3 主题机制
- TDesign 通过 `document.documentElement.setAttribute('theme-mode', 'dark')` 切换暗黑模式
- `stores/theme.ts` 负责切换 + 持久化key`tool-theme``index.html` 内联脚本在挂载前预置属性防闪烁`main.ts` 再同步一次
- **自定义样式一律使用 `var(--td-*)` 令牌** `--td-bg-color-page`、`--td-text-color-primary`、`--td-brand-color`禁止硬编码颜色否则暗黑模式会破版
### 3.4 公共组件
| 组件 | 用途 | 关键 Props |
| --- | --- | --- |
| `CopyButton` | 复制结果 | `text`必填)、`label` |
| `DownloadButton` | 下载文件 | `filename`、`getBlob: () => Blob \| null` |
| `ErrorAlert` | 错误提示 | `message`(为空不渲染),`@close` |
| `HistoryPanel` | 历史面板 | `items`、`@select`、`@remove`、`@clear` |
| `UploadDrop` | 拖拽上传(全站唯一上传组件) | `files`v-model、`accept`、`multiple`、`max`、`tip` |
新建工具页必须复用这些组件,保持全站交互一致。
## 4. 各工具实现要点与依赖库
| 工具 | 目录 | 核心依赖 / 实现 | 备注 |
| --- | --- | --- | --- |
| 图片压缩 | `views/image-compress` | `browser-image-compression` + `jszip` | 支持批量(最多 20 张);「按质量 / 限制大小」双模式(大小模式传 `maxSizeMB` + 迭代);`maxWidthOrHeight` 等比缩放;输出格式可选保持原格式/JPG/PNG/WebP转 JPG 时透明图先垫白底canvas 重绘)避免变黑;批量下载用 jszip 打包WebP 编码部分 Safari 不支持,已做 try/catch 逐张降级提示 |
| 图片裁剪 | `views/image-cropper` | `cropperjs`(含 `cropperjs/dist/cropper.css` | 实例在 `onBeforeUnmount` 必须 `destroy()`;换图时先 destroy 再重建 |
| 二维码 | `views/qrcode` | `qrcode`(生成)+ `jsqr`(解析) | 解析需先把图片绘制到 canvas 取 `ImageData` |
| JSON 格式化 | `views/json-format` | 原生 `JSON.parse/stringify` | 缩进支持 2/4 空格/Tab |
| 时间戳 | `views/timestamp` | 原生 Date + `Intl.DateTimeFormat('Asia/Shanghai')` | 时间显示固定北京时间(与用户时区无关);日期→时间戳按 UTC+8 换算 |
| Base64 | `views/base64` | 原生 `btoa/atob` + `TextEncoder/TextDecoder` | 中文必须走 UTF-8 字节转换,直接 `btoa(中文)` 会抛异常 |
| JWT | `views/jwt-parse` | 原生 atob + Base64Url 补位 | **只解码不验签**页面已声明exp/iat/nbf 按秒转北京时间 |
| Cron | `views/cron` | `cron-parser``parseExpression` | 支持 5/6 段;`interval.next().toDate()` 迭代下次执行时间;可视化生成器用 `parseExpression` 做合法性校验 |
| 进制转换 | `views/radix-convert` | **BigInt 手动逐位解析** | 禁止用 `parseInt/Number`大数会丢精度支持负号、0x/0o/0b 前缀、下划线分隔 |
| 哈希 | `views/hash` | `spark-md5`MD5+ Web Crypto `crypto.subtle.digest`SHA1/256/512 | 大文件按 4MB 分片喂给 `SparkMD5.ArrayBuffer`,避免内存峰值 |
| 身份证 | `views/idcard` | 纯 TS 实现 GB 11643-1999 校验 | 内置全部省级行政区代码表;校验位权重表勿改动 |
| 地址解析 | `views/address-parse` | 内置省份表 + 正则规则 | 纯规则近似解析,非地理数据库;直辖市无地级市层级需特殊处理 |
| URL 解析 | `views/url-parse` | 原生 `URL` / `URLSearchParams` | 无协议输入自动补 `https://`;构造器用 `searchParams.append` 自动编码 |
| OCR | `views/ocr` | `tesseract.js`WASM | 见第 5 节离线说明 |
## 5. OCR 特殊说明(重要)
`views/ocr/OcrView.vue` 中:
```ts
import TesseractWorker from 'tesseract.js/dist/worker.min.js?url'
import TesseractCore from 'tesseract.js-core/tesseract-core.wasm.js?url'
```
- worker 与 wasm 核心已通过 Vite `?url` 资源导入打进构建产物,代码层面无 CDN 依赖。
- 语言数据是运行时资源,`LANG_PATH` 常量默认指向官方语言包源(首次识别下载约 20MB 后浏览器缓存)。**完全离线部署**:将 `chi_sim.traineddata.gz`、`eng.traineddata.gz` 放入 `public/traineddata/`,把 `LANG_PATH` 改为 `${window.location.origin}/traineddata`
- worker 实例做了懒加载 + 复用 + `onBeforeUnmount``terminate()`,新增 OCR 相关功能时请沿用该模式,避免重复初始化。
## 6. 编码规范约定
- Vue 3 组合式 API `<script setup lang="ts">`;页面逻辑保持“状态 → 校验 → 处理 → 历史”结构。
- TypeScript strict 模式(`vue-tsc` 参与构建),**不要**用 `any` 掩盖类型错误;`pnpm build` 必须通过。
- 注释用中文,说明“为什么”而非复述代码;每个工具页顶部有用途说明。
- 上传一律使用公共组件 `UploadDrop``v-model:files` 绑定 `File[]`,直接拿 `File` 对象,不再用 `t-upload``UploadFile[].raw`)。涉及图片的页面已自动获得缩略图回显;文件变化用 `watch(files, ...)` 响应(含拖拽删除/清空场景)。
- 所有对象 URL`URL.createObjectURL`)在用完后 `revokeObjectURL`,组件卸载时清理。
- 样式:布局类用 UnoCSS 原子类或 `unocss.config.ts` 中已定义的快捷类(`page-wrap`、`form-row`、`code-block`);颜色类一律用 TDesign 令牌变量。
- 移动端适配:栅格用 `grid-template-columns: repeat(auto-fill, ...)` + `@media (max-width: 767px)` 降为单列,参考 `HomeView.vue`
## 7. 已知坑位速查
1. **TDesign 图标名**`tdesign-icons-vue-next` 不存在 `CropIcon`、`NumbersIcon`(本项目已用 `FrameIcon`、`CalculatorIcon` 替代)。新增图标前可用 Node 脚本验证导出名,避免构建失败:
```bash
node --input-type=module -e "import('tdesign-icons-vue-next').then(m=>console.log('XxxIcon' in m))"
```
2. **暗黑模式**:自定义样式忘用 `--td-*` 变量会在暗黑下出现白块/黑字,是最常见的回归点。
3. **时间显示**:全站用户可见时间一律北京时间(`utils/formatBeijing`),不要直接 `toLocaleString()`
4. **大整数**:进制转换用 BigInt任何“数字输入”都要考虑超出 `Number.MAX_SAFE_INTEGER` 的场景。
5. **cron-parser API**v4 使用 `parseExpression(expr)` 返回可迭代表达式,异常即非法表达式;不支持秒的表达式是标准 5 段。
6. **移动端复制**:非 HTTPS 环境 `navigator.clipboard` 不可用,`useClipboard` 已内置 `execCommand` 回退,不要重写复制逻辑。
7. **JSON.parse 的输入**Vue `v-model` 绑定 textarea 的是字符串;解析前先 `trim()` 判空,错误信息取 `e.message` 展示给用户。
8. **t-slider 的 label**:类型是 `string | boolean | TNode`**不能传函数 formatter**(历史 bug百分比文案用外部 `<span>` 显示。
9. **PNG 无损**canvas 的 PNG 编码忽略 quality 参数,`browser-image-compression` 对 PNG 只能靠迭代降质量(无效);「限制大小」模式对 PNG 基本不生效UI 已提示改用 JPG/WebP。
10. **透明图转 JPG 变黑**:浏览器 canvas 把透明区编码为 JPG 时填黑色;必须先画白底再压缩(`flattenToWhite` 顺序不能反,先压缩透明区已被填黑不可恢复)。
11. **历史文本解析**:图片压缩/裁剪的历史按结构化文本保存(如 `q=0.8 | type=jpeg | max=1920`),用正则 `q=([\d.]+)` 等解析;不要用 `\| jpg` 这类含空分支的正则(`/| jpg/` 恒为 true是历史 bug
## 8. 构建、验证与提交
```bash
pnpm install # 安装依赖
pnpm dev # 开发调试
pnpm type-check # 仅类型检查
pnpm build # 类型检查 + 生产构建vue-tsc && vite build
pnpm preview # 预览 dist
```
- 任何改动提交前必须 `pnpm build` 通过;产物 `dist/``node_modules/` 已被 `.gitignore` 排除。
- 提交信息使用 Conventional Commits`feat:` / `fix:` / `docs:` / `refactor:`)。
- 冒烟验证:`pnpm dev` 后访问 `/` 与改动路由,确认 200 且页面正常渲染(纯前端站点可直接 curl 验证 HTML 骨架)。
## 9. 后续扩展建议
- **新增工具**:按 3.1 的步骤即可,零框架改动。
- **PWA / 离线**:可接入 `vite-plugin-pwa`,配合第 5 节 OCR 离线方案实现完全离线工具站。
- **国际化**:文案目前硬编码中文,若需 i18n 建议 vue-i18n工具注册表的 `name/desc` 改为 key。
- **体积优化**TDesign 目前全量注册(约 1.4MB gzip 373KB若追求极致可改按需引入unplugin-vue-components + TDesignResolver
- **更多工具**色值转换、UUID 生成、正则测试、YAML 转换js-yaml、图片格式互转canvas等均可按现有模式快速复制。