diff --git a/README.md b/README.md index 0affd56..b5df6f4 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,16 @@ # 在线工具箱(tool.xpcool.com) -纯前端在线工具站:**Vite + Vue3 + Pinia + TypeScript + UnoCSS + TDesign**。15 个常用工具,所有文件处理与计算均在浏览器本地完成,**不上传任何用户数据到服务器**。 +纯前端在线工具站:**Vite + Vue3 + Pinia + TypeScript + UnoCSS + TDesign**。27 个常用工具,所有文件处理与计算均在浏览器本地完成,**不上传任何用户数据到服务器**(翻译工具除外,见隐私说明)。 ## 功能特性 -- 15 个实用工具:图片压缩/裁剪、二维码生成解析、JSON 格式化、时间戳转换、Base64 编解码、JWT 解析、Cron 表达式、进制转换、哈希计算、身份证解析、地址解析、URL 解析、翻译、图片 OCR 识别 +- 27 个实用工具:图片压缩/裁剪、二维码生成解析、JSON 格式化、时间戳转换、Base64 编解码、JWT 解析、Cron 表达式、进制转换、哈希计算、身份证解析、地址解析、URL 解析、翻译、图片 OCR 识别、UUID 生成、正则测试、命名转换、色值转换、单位换算、YAML ↔ JSON、字数统计、人民币大写、简繁转换、日期计算、密码生成、HTTP 状态码速查 - 左侧固定导航 + 中间内容区;PC Web 与移动端 H5 响应式适配(移动端为抽屉导航) - 暗黑 / 浅色主题切换,选择持久化,刷新不丢失 - 每个工具独立路由、独立本地历史记录(localStorage) - 所有文件上传支持点击选择 + 拖拽上传,图片自动缩略图回显;图片压缩支持批量、按质量/限制大小、等比缩放与 ZIP 批量下载 - 翻译工具参考有道翻译的交互:中/英/韩/日互译、双栏对照、单词释义(拼音 + 词性释义 + 例句)、真人发音朗读、逐句对照与来源标注 +- 开发常用工具全覆盖:UUID 批量生成、正则测试与替换预览、命名风格转换、色值转换、单位换算、YAML/JSON 互转、HTTP 状态码速查 - 通用组件:拖拽上传、复制按钮、下载按钮、错误提示、历史记录面板 - 全部能力由浏览器原生 API、WebAssembly 与 npm 前端库实现,无任何 mock 后端接口、无 CDN 脚本引入 @@ -25,7 +26,7 @@ | 样式 | UnoCSS 原子类 + SCSS(使用 TDesign 设计令牌变量) | | 路由 | Vue Router 4(History 模式) | -工具依赖:`qrcode`、`jsqr`、`cropperjs`、`browser-image-compression`、`tesseract.js`、`cron-parser`、`spark-md5`、`jszip`、`pinyin-pro`(全部通过 npm 安装)。 +工具依赖:`qrcode`、`jsqr`、`cropperjs`、`browser-image-compression`、`tesseract.js`、`cron-parser`、`spark-md5`、`jszip`、`pinyin-pro`、`js-yaml`、`opencc-js`(全部通过 npm 安装)。 ## 快速开始 @@ -68,6 +69,18 @@ pnpm preview | `/url-parse` | URL 解析 | | `/translate` | 翻译(中英韩日) | | `/ocr` | 图片 OCR 识别 | +| `/uuid` | UUID 生成 | +| `/regex-test` | 正则测试 | +| `/case-convert` | 命名风格转换 | +| `/color-convert` | 色值转换 | +| `/unit-convert` | 单位换算 | +| `/yaml-json` | YAML ↔ JSON 转换 | +| `/word-count` | 字数统计 | +| `/rmb-upper` | 人民币大写转换 | +| `/zh-convert` | 简繁转换 | +| `/date-calc` | 日期计算 | +| `/password-gen` | 密码生成器 | +| `/http-status` | HTTP 状态码速查 | ## 目录结构 diff --git a/agents.md b/agents.md index 01e3108..132c220 100644 --- a/agents.md +++ b/agents.md @@ -57,7 +57,7 @@ export interface ToolItem { 2. 在 `tools.ts` 的 `tools` 数组末尾追加一条配置,`component` 使用 `() => import('@/views//View.vue')`。 3. 完成——菜单、首页卡片、路由、文档标题自动生效。可运行 `pnpm type-check` 验证。 -**注意:** `path` 必须与页面内 `useHistory('xxx')` 的 key 一致(历史记录约定),并避免与现有 15 个 path 重复。 +**注意:** `path` 必须与页面内 `useHistory('xxx')` 的 key 一致(历史记录约定),并避免与现有 27 个 path 重复。 ### 3.2 历史记录约定 @@ -103,6 +103,18 @@ export interface ToolItem { | 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);原生 `` + 预设色板;预览文字色按相对亮度自动切换(暗黑模式可读) | +| 单位换算 | `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 数值用外部 `` 显示(label 不能传函数);强度 = 长度×log2(池大小) 分四档 | +| HTTP 状态码 | `views/http-status` | 内置静态数据(56 条,1xx-5xx) | 搜索(code/短语/中文)+ 分类筛选;历史存搜索词 | ## 5. OCR 特殊说明(重要) @@ -165,4 +177,4 @@ pnpm preview # 预览 dist - **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)等均可按现有模式快速复制。 +- **更多工具**:图片格式互转(canvas)、Markdown 预览(markdown-it)、SQL 格式化(sql-formatter)、CSS 渐变生成器、番茄钟、BMI/贷款计算器等均可按现有模式快速复制。 diff --git a/package.json b/package.json index b2ac503..c8b4751 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,14 @@ "type-check": "vue-tsc --noEmit -p tsconfig.json" }, "dependencies": { + "@types/js-yaml": "^4.0.9", "browser-image-compression": "^2.0.2", "cron-parser": "^4.9.0", "cropperjs": "^1.6.2", + "js-yaml": "^5.3.0", "jsqr": "^1.4.0", "jszip": "^3.10.1", + "opencc-js": "^1.4.1", "pinia": "^3.0.1", "pinyin-pro": "^3.29.3", "qrcode": "^1.5.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0778d3..dca9e07 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@types/js-yaml': + specifier: ^4.0.9 + version: 4.0.9 browser-image-compression: specifier: ^2.0.2 version: 2.0.2 @@ -17,12 +20,18 @@ importers: cropperjs: specifier: ^1.6.2 version: 1.6.2 + js-yaml: + specifier: ^5.3.0 + version: 5.3.0 jsqr: specifier: ^1.4.0 version: 1.4.0 jszip: specifier: ^3.10.1 version: 3.10.1 + opencc-js: + specifier: ^1.4.1 + version: 1.4.1 pinia: specifier: ^3.0.1 version: 3.0.4(typescript@5.7.3)(vue@3.5.41(typescript@5.7.3)) @@ -691,6 +700,9 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/lodash-es@4.17.12': resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} @@ -881,6 +893,9 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1121,6 +1136,10 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true + js-yaml@5.3.0: + resolution: {integrity: sha512-muutsYr+e2+d3rTgUGslq5rxbBlUy3cJ61IsHag2QNDQV+7zXWjkUpmALIajhrlLlrgRUiymj6U3zUr/TMK84Q==} + hasBin: true + jsqr@1.4.0: resolution: {integrity: sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==} @@ -1205,6 +1224,9 @@ packages: ofetch@1.5.1: resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + opencc-js@1.4.1: + resolution: {integrity: sha512-2lPLcrg7cnh1ATSduOxhnk/jq6KlR6w+5Ihl4wF7Z5e5Rva4vxTyugV5rZ7EiUCOZl+EDOLetIix+BhgYDCC+A==} + opencollective-postinstall@2.0.3: resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==} hasBin: true @@ -1912,6 +1934,8 @@ snapshots: '@types/estree@1.0.9': {} + '@types/js-yaml@4.0.9': {} + '@types/lodash-es@4.17.12': dependencies: '@types/lodash': 4.17.25 @@ -2210,6 +2234,8 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.2 + argparse@2.0.1: {} + balanced-match@1.0.2: {} binary-extensions@2.3.0: {} @@ -2452,6 +2478,10 @@ snapshots: jiti@2.7.0: {} + js-yaml@5.3.0: + dependencies: + argparse: 2.0.1 + jsqr@1.4.0: {} jszip@3.10.1: @@ -2527,6 +2557,8 @@ snapshots: node-fetch-native: 1.6.7 ufo: 1.6.4 + opencc-js@1.4.1: {} + opencollective-postinstall@2.0.3: {} p-limit@2.3.0: diff --git a/src/router/tools.ts b/src/router/tools.ts index 3ccea4e..e07dedf 100644 --- a/src/router/tools.ts +++ b/src/router/tools.ts @@ -15,6 +15,15 @@ import { LinkIcon, TranslateIcon, BrowseIcon, + RefreshIcon, + ControlPlatformIcon, + Palette1Icon, + SwapIcon, + FileCodeIcon, + ChartIcon, + MoneyIcon, + InternetIcon, + KeyIcon, } from 'tdesign-icons-vue-next' /** @@ -145,4 +154,88 @@ export const tools: ToolItem[] = [ icon: BrowseIcon, component: () => import('@/views/ocr/OcrView.vue'), }, + { + path: 'uuid', + name: 'UUID 生成', + desc: '批量生成 UUID v4,支持大小写与去连字符', + icon: RefreshIcon, + component: () => import('@/views/uuid/UuidView.vue'), + }, + { + path: 'regex-test', + name: '正则测试', + desc: '实时测试正则匹配、分组与替换,匹配高亮显示', + icon: ControlPlatformIcon, + component: () => import('@/views/regex-test/RegexTestView.vue'), + }, + { + path: 'case-convert', + name: '命名转换', + desc: '驼峰、蛇形、短横线等命名风格一键互转', + icon: CodeIcon, + component: () => import('@/views/case-convert/CaseConvertView.vue'), + }, + { + path: 'color-convert', + name: '色值转换', + desc: 'HEX、RGB、HSL、HSV 互转,取色器 + 预设色板', + icon: Palette1Icon, + component: () => import('@/views/color-convert/ColorConvertView.vue'), + }, + { + path: 'unit-convert', + name: '单位换算', + desc: '长度、质量、温度、数据存储等 8 类单位换算', + icon: SwapIcon, + component: () => import('@/views/unit-convert/UnitConvertView.vue'), + }, + { + path: 'yaml-json', + name: 'YAML ↔ JSON', + desc: 'YAML 与 JSON 双向转换,报错定位清晰', + icon: FileCodeIcon, + component: () => import('@/views/yaml-json/YamlJsonView.vue'), + }, + { + path: 'word-count', + name: '字数统计', + desc: '统计字符、汉字、单词、段落与阅读时长', + icon: ChartIcon, + component: () => import('@/views/word-count/WordCountView.vue'), + }, + { + path: 'rmb-upper', + name: '人民币大写', + desc: '数字金额转人民币大写,处理角分与负数', + icon: MoneyIcon, + component: () => import('@/views/rmb-upper/RmbUpperView.vue'), + }, + { + path: 'zh-convert', + name: '简繁转换', + desc: '简体与繁体中文互转(opencc 词库)', + icon: TranslateIcon, + component: () => import('@/views/zh-convert/ZhConvertView.vue'), + }, + { + path: 'date-calc', + name: '日期计算', + desc: '日期差值、推算与年龄生肖星座计算', + icon: CalendarIcon, + component: () => import('@/views/date-calc/DateCalcView.vue'), + }, + { + path: 'password-gen', + name: '密码生成', + desc: '随机强密码批量生成,含强度评估', + icon: KeyIcon, + component: () => import('@/views/password-gen/PasswordGenView.vue'), + }, + { + path: 'http-status', + name: 'HTTP 状态码', + desc: '常用 HTTP 状态码速查,支持搜索与分类', + icon: InternetIcon, + component: () => import('@/views/http-status/HttpStatusView.vue'), + }, ] diff --git a/src/views/case-convert/CaseConvertView.vue b/src/views/case-convert/CaseConvertView.vue new file mode 100644 index 0000000..ab3fcf6 --- /dev/null +++ b/src/views/case-convert/CaseConvertView.vue @@ -0,0 +1,157 @@ + + + + + + diff --git a/src/views/color-convert/ColorConvertView.vue b/src/views/color-convert/ColorConvertView.vue new file mode 100644 index 0000000..6e97f4b --- /dev/null +++ b/src/views/color-convert/ColorConvertView.vue @@ -0,0 +1,400 @@ + + + + + diff --git a/src/views/date-calc/DateCalcView.vue b/src/views/date-calc/DateCalcView.vue new file mode 100644 index 0000000..0acbec0 --- /dev/null +++ b/src/views/date-calc/DateCalcView.vue @@ -0,0 +1,409 @@ + + + + + + diff --git a/src/views/http-status/HttpStatusView.vue b/src/views/http-status/HttpStatusView.vue new file mode 100644 index 0000000..7ef327c --- /dev/null +++ b/src/views/http-status/HttpStatusView.vue @@ -0,0 +1,274 @@ + + + + + + diff --git a/src/views/password-gen/PasswordGenView.vue b/src/views/password-gen/PasswordGenView.vue new file mode 100644 index 0000000..2718b7d --- /dev/null +++ b/src/views/password-gen/PasswordGenView.vue @@ -0,0 +1,285 @@ + + + + + + diff --git a/src/views/regex-test/RegexTestView.vue b/src/views/regex-test/RegexTestView.vue new file mode 100644 index 0000000..6d68eb3 --- /dev/null +++ b/src/views/regex-test/RegexTestView.vue @@ -0,0 +1,284 @@ + + + + + diff --git a/src/views/rmb-upper/RmbUpperView.vue b/src/views/rmb-upper/RmbUpperView.vue new file mode 100644 index 0000000..79247ba --- /dev/null +++ b/src/views/rmb-upper/RmbUpperView.vue @@ -0,0 +1,195 @@ + + + + + + diff --git a/src/views/unit-convert/UnitConvertView.vue b/src/views/unit-convert/UnitConvertView.vue new file mode 100644 index 0000000..837629e --- /dev/null +++ b/src/views/unit-convert/UnitConvertView.vue @@ -0,0 +1,246 @@ + + + + + diff --git a/src/views/uuid/UuidView.vue b/src/views/uuid/UuidView.vue new file mode 100644 index 0000000..85feae3 --- /dev/null +++ b/src/views/uuid/UuidView.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/src/views/word-count/WordCountView.vue b/src/views/word-count/WordCountView.vue new file mode 100644 index 0000000..ffda957 --- /dev/null +++ b/src/views/word-count/WordCountView.vue @@ -0,0 +1,124 @@ + + + + + + diff --git a/src/views/yaml-json/YamlJsonView.vue b/src/views/yaml-json/YamlJsonView.vue new file mode 100644 index 0000000..7e87ef8 --- /dev/null +++ b/src/views/yaml-json/YamlJsonView.vue @@ -0,0 +1,120 @@ + + + + + + diff --git a/src/views/zh-convert/ZhConvertView.vue b/src/views/zh-convert/ZhConvertView.vue new file mode 100644 index 0000000..a7ca4b2 --- /dev/null +++ b/src/views/zh-convert/ZhConvertView.vue @@ -0,0 +1,102 @@ + + + + + +