tool.xpcool.com/agents.md
夏犀麟 954b9e9d8a
All checks were successful
Build and Deploy (tool.xpcool.com) / build-and-deploy (push) Successful in 1m1s
feat(router): 新增多个工具分类和路由配置
- 添加 TerminalIcon, GitBranchIcon, ApiIcon, BrushIcon, SoundIcon,
Table1Icon, LogoGithubIcon, ContrastIcon, Edit2Icon, ChartBarIcon,
EarthIcon, GitMergeIcon, SettingIcon, CurrencyExchangeIcon, MobileIcon,
WifiIcon, RocketIcon 等图标导入
- 新增 dev, design, health, study, travel, fun, online 七个工具分类
- 添加 SQL格式化、文本对比、JSON转代码、渐变生成器、占位图生成、
ASCII艺术、摩斯电码、正则可视化、Markdown表格、API构建器、
Gitignore生成器、代码转图片、颜色命名器、缩进转换、代码统计、
Hello World大全、JSON对比、环境变量解析、汇率换算、快递查询、
手机号归属地、IP信息查询、网络测试、网络测速等多个工具路由配置
- 新增 .env.example 配置文件模板
- 创建 docs/api-contract.md 后端接口契约文档
- 新增 api-builder 和 ascii-art 工具页面组件
- 更新 agents.md 添加接手必读指引和维护约定
- 创建 CHANGELOG.md 变更日志文档和工作日志记录
2026-08-24 22:11:12 +08:00

197 lines
20 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与开发者说明架构约定、关键机制、开发流程与常见坑。动手改代码前请先读完本文。
> ## ⚡ 接手必读(任何 AI 账号 / 开发者接手时,按顺序先读这三份)
>
> 1. **`docs/PROJECT_STATE.md`** — 项目状态与交接文档单点真相当前全貌、9 分类 59 个工具清单、待办批次、技术坑、接手清单
> 2. **`docs/CHANGELOG.md`** — 变更日志:每次请求/变更都会追加记录(倒序),先看最近的改动
> 3. **`docs/api-contract.md`** — 后端接口契约:「在线服务」分类工具的后端契约,后端按此实现即可直接对接
>
> **维护约定**每次对本项目做实质变更必须同步更新这三份文档CHANGELOG 追加记录 / PROJECT_STATE 刷新状态 / 涉及后端则更新契约),并把 `docs/PROJECT_STATE.md` 第 7 节的接手清单走一遍。
## 1. 项目定位
- 名称tool.xpcool.com在线工具箱
- 形态:**纯前端 SPA**,无后端、无 mock 接口。所有计算与文件处理在浏览器本地完成,用户文件不出浏览器(唯一例外:翻译工具直接调用第三方免费翻译接口,见第 4 节)。
- 技术栈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 图标组件
category?: string // 分类 key对应 toolCategories缺省归入第一个分类
component: () => Promise<Component> // 懒加载组件
}
```
**分类机制**`toolCategories` 数组定义分类目前 `work` 工作效率、`life` 生活助手`groupedTools()` 按分类分组返回工具侧边导航`SideNav.vue`与首页分区`HomeView.vue`均消费该函数。**新增分类零改动** `toolCategories` 追加一条 `{ key/label/desc }`再把对应工具的 `category` 指向新 key 即可
**新增一个工具的完整步骤:**
1. 新建 `src/views/<tool-name>/<ToolName>View.vue`参考现有页面结构标题 表单卡片 错误提示 结果卡片 历史面板)。
2. `tools.ts` `tools` 数组对应分类分区末尾追加一条配置`component` 使用 `() => import('@/views/<tool-name>/<ToolName>View.vue')`并填写 `category`
3. 完成——菜单首页卡片路由文档标题自动生效可运行 `pnpm type-check` 验证
**注意:** `path` 必须与页面内 `useHistory('xxx')` key 一致历史记录约定并避免与现有工具的 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 打包WebWorker 核心库经 `?url` 打包并传 `libURL`(默认走 CDN 会失败),失败自动降级主线程;结果列表原图/压缩图可点击放大预览(`t-image-viewer`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` 自动编码 |
| 翻译 | `views/translate` | 有道演示接口 + 腾讯翻译君(翻译);`dict.youdao.com/dictvoice`发音Free Dictionary API → Datamuse词典`pinyin-pro`(拼音) | **唯一需要联网的工具**,界面参考有道翻译:语言对/场景下拉、双栏对照、字符计数、单词释义(拼音+词性释义+例句、发音朗读、逐句对照、反馈按钮、来源标注有道AI翻译标准模型/腾讯翻译君)。翻译接口:语言码映射 zh-CN→zh-CHS有道/zh腾讯「自动检测」用脚本特征兜底判定中文/日文/韩文/英文两个接口依次尝试降级免费接口有频率限制411页面已提示。**发音**`dict.youdao.com/dictvoice?audio=<文本>&type=<2=美音|0=中文/韩文>` 免签日文不支持500日文译文改用有道接口返回的签名 `tSpeakUrl`(仅有道成功时可用,否则隐藏按钮)。**词典**:英文单词先查 `api.dictionaryapi.dev`(含音标/例句,部分地区网络不稳),超时降级 `api.datamuse.com/words?sp=<词>&md=d`defs 为 "词性\t释义" 文本,需 split。中文源词拼音由 pinyin-pro 本地生成(`toneType: 'symbol'`)。**已知限制**:有道演示接口不支持 en→jaerrorCode 102自动降级腾讯「逐句对照」按句拆分后逐句翻译并发 3、最多 15 句「AI 润色」仅保留入口(需官方 Key点击展示说明 |
| OCR | `views/ocr` | `tesseract.js`WASM | 见第 5 节离线说明 |
| UUID 生成 | `views/uuid` | 原生 `crypto.getRandomValues` | RFC 4122 v4版本/变体位手工写入);支持数量 1-100、大写、去连字符历史存生成结果文本截断 300 字符),点击按行恢复 |
| 正则测试 | `views/regex-test` | 原生 `RegExp` | exec 循环收集匹配:**无 g 时只取第一处**、空匹配需手动推进 lastIndex 防死循环;高亮用 segments 数组 + span 渲染(禁止 v-html替换用 `text.replace(re, $1)`;历史存 JSON 快照pattern/flags/text解析失败静默忽略 |
| 命名转换 | `views/case-convert` | 纯 TS 分词 | 先按非字母数字切分(中文每汉字一词元),再用插空格法切 camelCase 边界(避免 lookbehind 兼容问题);输出 6 种风格 |
| 色值转换 | `views/color-convert` | 手写 RGB/HSL/HSV 公式 | 解析 #RGB/#RRGGBB/#RRGGBBAA、rgb(a)、hsl(a);原生 `<input type="color">` + 预设色板;预览文字色按相对亮度自动切换(暗黑模式可读) |
| 单位换算 | `views/unit-convert` | 纯 TS 单位表factor 基准换算) | 8 类单位;**温度特殊**C/F/K 三向公式不走 factor结果 `toPrecision(6)` 去尾零;切换类别自动重置为该类前两个单位 |
| YAML ↔ JSON | `views/yaml-json` | `js-yaml`(命名导入 `load`/`dump`,勿用 default import | YAML 未加引号的日期字符串会被隐式解析为日期,页面已提示;空输入/`load` 返回 undefined 需显式报错 |
| 字数统计 | `views/word-count` | 原生正则 + `TextEncoder` | 10 项实时统计;阅读时长 = 汉字/400 + 英文单词/200历史在输入失焦时记录无转换按钮避免打字刷屏 |
| 人民币大写 | `views/rmb-upper` | 纯 TS + BigInt 整数运算 | 金额按字符串处理并转「整数 + 分」的整数运算,**避免浮点精度**;每 4 位一组(个/万/亿/万亿组间补零规则已用测试样例覆盖0/10/1001/100000/负数/角分) |
| 简繁转换 | `views/zh-convert` | `opencc-js``import * as OpenCC`Converter 顶层创建复用) | cn↔tw 标准映射,不含台湾用语变体;**体积大**(懒加载 chunk 约 500KB gzip如需减小可改 CustomConverter 按需加载词典 |
| 日期计算 | `views/date-calc` | 原生 Date本地时间构造 | 三个 tab差值/推算/年龄生肖。`parseDate` 用 `new Date(y, m-1, d)` + 回读校验防时区偏移与 2 月 30 类非法日期;月/年推算先归 1 号再 setMonth/setFullYear溢出钳制到目标月最后一天生肖 `((year-4)%12+12)%12` 正模 |
| 密码生成 | `views/password-gen` | `crypto.getRandomValues` + 拒绝采样 | 每种选中字符集至少出现一次 + Fisher-Yates 洗牌t-slider 数值用外部 `<span>` 显示label 不能传函数);强度 = 长度×log2(池大小) 分四档 |
| HTTP 状态码 | `views/http-status` | 内置静态数据56 条1xx-5xx | 搜索code/短语/中文)+ 分类筛选;历史存搜索词 |
| BMI 计算器 | `views/bmi` | 纯 TS 计算 | 中国成人标准四级分类(<18.5/24/28健康体重范围按 18.5~23.9 反推历史存 `身高|体重` |
| 房贷计算器 | `views/mortgage` | TS 金融公式 | 等额本息/等额本金一次算出并对比等额本息 `M = P·r·(1+r)^n / ((1+r)^n 1)`等额本金总利息等差求和 `(n+1)·P·r/2`利率为 0 需特判历史存 JSON 快照 |
| 账单拆分 | `views/bill-split` | TS 贪心结算 | 每人已付 人均 净差额欠款方升序/收款方降序配对生成转账方案最多 20 历史存 `[[名字,金额],...]` |
| 随机决定 | `views/random-decision` | `crypto.getRandomValues` 拒绝采样 | 选项抽签 + 随机数两个 tab`secureRandInt` 拒绝采样避免取模偏斜不重复模式小范围洗牌/大范围 Set历史存 JSON 快照 |
| 番茄钟 | `views/pomodoro` | 原生定时器 + Web Audio | 基于目标时间戳计时`endTime` + 250ms tick后台节流不漂移阶段结束自动切换并计轮提示音用 AudioContext 880Hz 振荡器`onBeforeUnmount` 清理定时器历史存 `focus=25 | rest=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)。
12. **browser-image-compression 的 WebWorker 依赖 CDN**`useWebWorker: true` 时库会创建 Blob Worker `importScripts(libURL)`默认 `libURL` 指向 jsdelivr CDN——离线或网络受限时全部压缩报错(「压缩不成功最常见原因)。必须 `import ... from 'browser-image-compression/dist/browser-image-compression.js?url'` 打包本地资源并传 `libURL`同时 catch 后降级 `useWebWorker: false` 重试
## 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)。
- **更多工具**图片格式互转canvas)、Markdown 预览markdown-it)、SQL 格式化sql-formatter)、CSS 渐变生成器农历/节假日查询等均可按现有模式快速复制新增分类见 3.1