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

13 KiB
Raw Blame History

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 是全站唯一数据源:侧边导航、首页卡片、路由表均由此自动生成。新增工具不要手动改路由文件、首页或侧边栏。

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.tstools 数组末尾追加一条配置,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 负责切换 + 持久化keytool-themeindex.html 内联脚本在挂载前预置属性防闪烁;main.ts 再同步一次。
  • 自定义样式一律使用 var(--td-*) 令牌(如 --td-bg-color-page--td-text-color-primary--td-brand-color),禁止硬编码颜色,否则暗黑模式会破版。

3.4 公共组件

组件 用途 关键 Props
CopyButton 复制结果 text(必填)、label
DownloadButton 下载文件 filenamegetBlob: () => Blob | null
ErrorAlert 错误提示 message(为空不渲染),@close
HistoryPanel 历史面板 items@select@remove@clear
UploadDrop 拖拽上传(全站唯一上传组件) filesv-modelacceptmultiplemaxtip

新建工具页必须复用这些组件,保持全站交互一致。

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-parserparseExpression 支持 5/6 段;interval.next().toDate() 迭代下次执行时间;可视化生成器用 parseExpression 做合法性校验
进制转换 views/radix-convert BigInt 手动逐位解析 禁止用 parseInt/Number大数会丢精度支持负号、0x/0o/0b 前缀、下划线分隔
哈希 views/hash spark-md5MD5+ Web Crypto crypto.subtle.digestSHA1/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.jsWASM 见第 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.gzeng.traineddata.gz 放入 public/traineddata/,把 LANG_PATH 改为 ${window.location.origin}/traineddata
  • worker 实例做了懒加载 + 复用 + onBeforeUnmountterminate(),新增 OCR 相关功能时请沿用该模式,避免重复初始化。

6. 编码规范约定

  • Vue 3 组合式 API <script setup lang="ts">;页面逻辑保持“状态 → 校验 → 处理 → 历史”结构。
  • TypeScript strict 模式(vue-tsc 参与构建),不要any 掩盖类型错误;pnpm build 必须通过。
  • 注释用中文,说明“为什么”而非复述代码;每个工具页顶部有用途说明。
  • 上传一律使用公共组件 UploadDropv-model:files 绑定 File[],直接拿 File 对象,不再用 t-uploadUploadFile[].raw)。涉及图片的页面已自动获得缩略图回显;文件变化用 watch(files, ...) 响应(含拖拽删除/清空场景)。
  • 所有对象 URLURL.createObjectURL)在用完后 revokeObjectURL,组件卸载时清理。
  • 样式:布局类用 UnoCSS 原子类或 unocss.config.ts 中已定义的快捷类(page-wrapform-rowcode-block);颜色类一律用 TDesign 令牌变量。
  • 移动端适配:栅格用 grid-template-columns: repeat(auto-fill, ...) + @media (max-width: 767px) 降为单列,参考 HomeView.vue

7. 已知坑位速查

  1. TDesign 图标名tdesign-icons-vue-next 不存在 CropIconNumbersIcon(本项目已用 FrameIconCalculatorIcon 替代)。新增图标前可用 Node 脚本验证导出名,避免构建失败:
    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 APIv4 使用 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. 构建、验证与提交

pnpm install        # 安装依赖
pnpm dev            # 开发调试
pnpm type-check     # 仅类型检查
pnpm build          # 类型检查 + 生产构建vue-tsc && vite build
pnpm preview        # 预览 dist
  • 任何改动提交前必须 pnpm build 通过;产物 dist/node_modules/ 已被 .gitignore 排除。
  • 提交信息使用 Conventional Commitsfeat: / 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等均可按现有模式快速复制。