All checks were successful
Build and Deploy (tool.xpcool.com) / build-and-deploy (push) Successful in 1m1s
- 添加 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 变更日志文档和工作日志记录
197 lines
20 KiB
Markdown
197 lines
20 KiB
Markdown
# 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 + 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` 是全站唯一数据源:侧边导航、首页卡片、路由表均由此自动生成。**新增工具不要手动改路由文件、首页或侧边栏。**
|
||
|
||
```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→ja(errorCode 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 节。
|