- 新增 /translate 页面:源语言自动检测 + 中英韩日选择、一键互换、 双语对照输入输出、Ctrl+Enter 快捷翻译、复制译文、本地历史 - 调用免费第三方接口(有道演示接口 + 腾讯翻译君),均免 API Key、 支持 CORS;两个接口依次尝试自动降级,免费接口有频率限制已提示 - 自动检测用脚本特征兜底(中文/日文/韩文/英文),保证降级接口可用 - tools.ts 注册翻译工具项;README 与 agents.md 同步更新(工具数 15, 并注明翻译为唯一需要联网的工具)
14 KiB
14 KiB
agents.md — 项目维护与扩展指南
本文档面向后续接手维护/扩展本项目的 AI 编码代理(Agent)与开发者,说明架构约定、关键机制、开发流程与常见坑。动手改代码前请先读完本文。
1. 项目定位
- 名称:tool.xpcool.com(在线工具箱)
- 形态:纯前端 SPA,无后端、无 mock 接口。所有计算与文件处理在浏览器本地完成,用户文件不出浏览器(唯一例外:翻译工具直接调用第三方免费翻译接口,见第 4 节)。
- 技术栈:Vite 6 + Vue 3(
<script setup>)+ Pinia + TypeScript(strict)+ 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 # 本地历史(每个工具独立 key,localStorage 持久化)
│ ├── 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 是全站唯一数据源:侧边导航、首页卡片、路由表均由此自动生成。新增工具不要手动改路由文件、首页或侧边栏。
export interface ToolItem {
path: string // 路由路径(相对根路径,不含前导 /)
name: string // 工具名(菜单/卡片/页面标题)
desc: string // 一句话描述
icon: Component // TDesign 图标组件
component: () => Promise<Component> // 懒加载组件
}
新增一个工具的完整步骤:
- 新建
src/views/<tool-name>/<ToolName>View.vue,参考现有页面结构(标题 → 表单卡片 → 错误提示 → 结果卡片 → 历史面板)。 - 在
tools.ts的tools数组末尾追加一条配置,component使用() => import('@/views/<tool-name>/<ToolName>View.vue')。 - 完成——菜单、首页卡片、路由、文档标题自动生效。可运行
pnpm type-check验证。
注意: path 必须与页面内 useHistory('xxx') 的 key 一致(历史记录约定),并避免与现有 15 个 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 |
有道演示接口 + 腾讯翻译君(均免 key、支持 CORS) | 唯一需要联网的工具:浏览器直接 POST 第三方接口,不经过自建后端;语言码映射 zh-CN→zh-CHS(有道)/zh(腾讯);「自动检测」用脚本特征兜底判定(中文/日文/韩文/英文);两个接口依次尝试降级;免费接口有频率限制,页面已提示 |
| OCR | views/ocr |
tesseract.js(WASM) |
见第 5 节离线说明 |
5. OCR 特殊说明(重要)
views/ocr/OcrView.vue 中:
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. 已知坑位速查
- TDesign 图标名:
tdesign-icons-vue-next不存在CropIcon、NumbersIcon(本项目已用FrameIcon、CalculatorIcon替代)。新增图标前可用 Node 脚本验证导出名,避免构建失败:node --input-type=module -e "import('tdesign-icons-vue-next').then(m=>console.log('XxxIcon' in m))" - 暗黑模式:自定义样式忘用
--td-*变量会在暗黑下出现白块/黑字,是最常见的回归点。 - 时间显示:全站用户可见时间一律北京时间(
utils/formatBeijing),不要直接toLocaleString()。 - 大整数:进制转换用 BigInt;任何“数字输入”都要考虑超出
Number.MAX_SAFE_INTEGER的场景。 - cron-parser API:v4 使用
parseExpression(expr)返回可迭代表达式,异常即非法表达式;不支持秒的表达式是标准 5 段。 - 移动端复制:非 HTTPS 环境
navigator.clipboard不可用,useClipboard已内置execCommand回退,不要重写复制逻辑。 - JSON.parse 的输入:Vue
v-model绑定 textarea 的是字符串;解析前先trim()判空,错误信息取e.message展示给用户。 - t-slider 的 label:类型是
string | boolean | TNode,不能传函数 formatter(历史 bug);百分比文案用外部<span>显示。 - PNG 无损:canvas 的 PNG 编码忽略 quality 参数,
browser-image-compression对 PNG 只能靠迭代降质量(无效);「限制大小」模式对 PNG 基本不生效,UI 已提示改用 JPG/WebP。 - 透明图转 JPG 变黑:浏览器 canvas 把透明区编码为 JPG 时填黑色;必须先画白底再压缩(
flattenToWhite顺序不能反,先压缩透明区已被填黑不可恢复)。 - 历史文本解析:图片压缩/裁剪的历史按结构化文本保存(如
q=0.8 | type=jpeg | max=1920),用正则q=([\d.]+)等解析;不要用\| jpg这类含空分支的正则(/| jpg/恒为 true,是历史 bug)。 - 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. 构建、验证与提交
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)等均可按现有模式快速复制。