tool.xpcool.com/src/router/tools.ts
夏犀麟 af233e1628
All checks were successful
Build and Deploy (tool.xpcool.com) / build-and-deploy (push) Successful in 1m6s
feat: 视觉重设计 + 中英日韩四语言 + 三态昼夜主题 + 服务端接口对接
视觉体系(核心是「令牌 → TDesign 桥接」)
- 新增 tokens.scss:--tk-* 设计令牌(青玉 Jade 单一强调色、冷调中性色阶、
  圆角/间距/字号/z-index/动效规范、按语言切换字体栈)
- 新增 tdesign-theme.scss:--tk-* → --td-* 桥接(含完整冷调灰阶 1-14)
  → 60+ 个未改动的存量工具页零成本继承新配色与圆角
- index.scss 重写:只过渡颜色、统一 focus-visible、reduced-motion 降级
- 新增 v-reveal 滚动进入指令

布局与交互
- MainLayout 重写:毛玻璃 sticky 顶栏 + 三区布局 + 页脚(含备案号)
- 新增 CommandPalette(⌘K/Ctrl+K)、LocaleSwitch(显示语言代码不用国旗)、
  ThemeSwitch(三态;图标显示生效 mode、菜单勾选用户偏好 preference)
- SideNav 重写:内联筛选、可折叠分组持久化、活动态指示条、需联网标记
- HomeView 重写:左对齐 Hero + 真实可用搜索 + 概览数字条 + 服务端工具专区

国际化与主题
- vue-i18n 11 + zh-CN/en-US/ja-JP/ko-KR 完整语言包(58 个工具名与描述全译)
- helpers 取词带中文兜底;index.html 防闪烁内联脚本与 store 三处同步
- 主题三态 light/dark/auto,preference 与 mode 分离

服务端对接(service.xpcool.com /api/service/open/tools/*)
- 新增 openTools.ts:接口元数据与调用函数共用同一份路径定义
- 改造/新建 6 页:ip-info、uuid、hash(服务端 MD5 两端口径对比)、
  timestamp(服务端时钟 + 时钟偏移)、random-string、api-status
- 均为「本机 / 服务端」双模式,默认本机、不自动打网络请求
- 后端未实现的 5 个接口用 BackendPending 如实标注,不伪装可用

修复
- 空分类渲染缺陷(health/study/travel 无工具却渲染空区块),分类 9 → 6
- UnoCSS panel 快捷类补内边距;IpInfoView 漏 onBeforeUnmount;
  SideNav 末尾 import;HomeView 错误导入 SUPPORTED_LOCALES

其他
- 新增 src/config.ts 集中站点常量,页脚展示备案号并链工信部核验
- docs/api-contract.md 按线上实际重写;PROJECT_STATE.md 全面更新
- 构建通过:vue-tsc --noEmit + vite build
2026-09-14 02:05:38 +08:00

642 lines
20 KiB
TypeScript
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.

import type { Component } from 'vue'
import {
ImageIcon,
FileImageIcon,
FrameIcon,
QrcodeIcon,
CodeIcon,
TimeIcon,
FileIcon,
LockOnIcon,
CalendarIcon,
CalculatorIcon,
FingerprintIcon,
CreditcardIcon,
LocationIcon,
LinkIcon,
TranslateIcon,
BrowseIcon,
RefreshIcon,
ControlPlatformIcon,
Palette1Icon,
SwapIcon,
FileCodeIcon,
ChartIcon,
MoneyIcon,
InternetIcon,
KeyIcon,
HeartIcon,
WalletIcon,
UsergroupIcon,
GiftIcon,
AlarmIcon,
TerminalIcon,
GitBranchIcon,
ApiIcon,
BrushIcon,
SoundIcon,
ViewModuleIcon,
SearchIcon,
Table1Icon,
LogoGithubIcon,
ContrastIcon,
Edit2Icon,
ChartBarIcon,
EarthIcon,
GitMergeIcon,
SettingIcon,
CurrencyExchangeIcon,
MobileIcon,
WifiIcon,
RocketIcon,
TextboxIcon,
ServerIcon,
} from 'tdesign-icons-vue-next'
/**
* 工具注册表:网站内所有工具的“唯一数据源”。
* 侧边导航、首页卡片、命令面板、路由表均由此文件生成。
*
* ⚙️ 新增工具只需三步:
* 1. 在 src/views/<工具名>/ 下创建页面组件(可参考现有工具页面);
* 2. 在本文件的 tools 数组中追加一条配置path/name/desc/icon/category/component
* 3. 在 src/i18n/locales/ 的 4 个语言包中补齐 tool.<path>.name / tool.<path>.desc。
* 无需修改路由文件、首页与侧边栏,它们会自动渲染新工具。
*
* 🌐 多语言约定:本文件里的 name / desc 是「简体中文原文」,同时作为翻译缺失时的兜底值;
* 其它语言由 i18n 语言包提供(见 src/i18n/helpers.ts 的 toolName / toolDesc
*
* 🗂️ 分类扩展:在 toolCategories 数组追加一条 { key/label/desc }
* 再把对应工具的 category 指向新 key 即可,导航与首页会自动出现新分组。
* 注意没有任何工具的分类不会渲染groupedTools 会过滤掉空分组)。
*/
/** 工具分类定义(数据驱动,顺序即展示顺序) */
export interface ToolCategory {
/** 分类唯一标识,与 ToolItem.category 对应 */
key: string
/** 分类名称(导航分组标题、首页分区标题) */
label: string
/** 一句话描述(首页分区副标题) */
desc: string
}
export const toolCategories: ToolCategory[] = [
{ key: 'work', label: '工作效率', desc: '开发编程、办公文本与文件处理' },
{ key: 'dev', label: '开发进阶', desc: '面向开发者的格式化、转换与对比工具' },
{ key: 'design', label: '创意设计', desc: '生成渐变、占位图与字符画,灵感随手可得' },
{ key: 'life', label: '生活助手', desc: '日常计算、查询与决策小工具' },
{ key: 'fun', label: '趣味娱乐', desc: '摩斯电码等好玩的小玩具' },
{ key: 'online', label: '服务端工具', desc: '调用自建后端开放接口,需要联网' },
]
export interface ToolItem {
/** 路由路径(相对根路径,不含前导斜杠),同时用作 i18n 的 key */
path: string
/** 工具名称(简体中文原文,翻译缺失时兜底) */
name: string
/** 一句话描述(简体中文原文,翻译缺失时兜底) */
desc: string
/** 菜单/卡片图标TDesign 图标组件) */
icon: Component
/** 所属分类 key必须在 toolCategories 中存在),缺省归入第一个分类 */
category?: string
/** 是否依赖后端接口(首页会打上「需联网」标记,并参与服务端工具分区) */
needsNetwork?: boolean
/** 页面组件(懒加载,减小首屏体积) */
component: () => Promise<Component>
}
export const tools: ToolItem[] = [
/* ==================== 工作效率 ==================== */
{
path: 'image-compress',
name: '图片压缩',
desc: '本地压缩 JPG/PNG/WebP可调质量压缩后直接下载',
icon: ImageIcon,
category: 'work',
component: () => import('@/views/image-compress/ImageCompressView.vue'),
},
{
path: 'image-to-ico',
name: '图片转 ICO',
desc: 'PNG/JPG/WebP/SVG 转图标,多尺寸合成并控制输出体积',
icon: FileImageIcon,
category: 'work',
component: () => import('@/views/image-to-ico/ImageToIcoView.vue'),
},
{
path: 'json-format',
name: 'JSON 格式化',
desc: 'JSON 格式化、压缩、校验,一键复制结果',
icon: CodeIcon,
category: 'work',
component: () => import('@/views/json-format/JsonFormatView.vue'),
},
{
path: 'timestamp',
name: '时间戳转换',
desc: '时间戳与时间互转,支持秒/毫秒、批量转换与服务端时间校准',
icon: TimeIcon,
category: 'work',
component: () => import('@/views/timestamp/TimestampView.vue'),
},
{
path: 'base64',
name: 'Base64 编解码',
desc: '文本 Base64 编解码,图片文件与 Base64 互转',
icon: FileIcon,
category: 'work',
component: () => import('@/views/base64/Base64View.vue'),
},
{
path: 'jwt-parse',
name: 'JWT 解析',
desc: '解析 JWT 的 Header 与 Payload展示过期时间',
icon: LockOnIcon,
category: 'work',
component: () => import('@/views/jwt-parse/JwtParseView.vue'),
},
{
path: 'cron',
name: 'Cron 表达式',
desc: '解析 Cron 下次执行时间,可视化生成表达式',
icon: CalendarIcon,
category: 'work',
component: () => import('@/views/cron/CronView.vue'),
},
{
path: 'radix-convert',
name: '进制转换',
desc: '二进制、八进制、十进制、十六进制互转',
icon: CalculatorIcon,
category: 'work',
component: () => import('@/views/radix-convert/RadixConvertView.vue'),
},
{
path: 'hash',
name: '哈希计算',
desc: '文本/文件计算 MD5、SHA1、SHA256、SHA512可对比服务端结果',
icon: FingerprintIcon,
category: 'work',
component: () => import('@/views/hash/HashView.vue'),
},
{
path: 'url-parse',
name: 'URL 解析',
desc: '解析协议/域名/路径/参数,也支持构造 URL',
icon: LinkIcon,
category: 'work',
component: () => import('@/views/url-parse/UrlParseView.vue'),
},
{
path: 'translate',
name: '翻译',
desc: '中文、英文、韩文、日文互译(免费在线接口)',
icon: TranslateIcon,
category: 'work',
component: () => import('@/views/translate/TranslateView.vue'),
},
{
path: 'ocr',
name: '图片 OCR 识别',
desc: '本地识别图片文字支持中英文WASM',
icon: BrowseIcon,
category: 'work',
component: () => import('@/views/ocr/OcrView.vue'),
},
{
path: 'uuid',
name: 'UUID 生成',
desc: '批量生成 UUID可选浏览器本地生成或调用服务端接口',
icon: RefreshIcon,
category: 'work',
component: () => import('@/views/uuid/UuidView.vue'),
},
{
path: 'regex-test',
name: '正则测试',
desc: '实时测试正则匹配、分组与替换,匹配高亮显示',
icon: ControlPlatformIcon,
category: 'work',
component: () => import('@/views/regex-test/RegexTestView.vue'),
},
{
path: 'case-convert',
name: '命名转换',
desc: '驼峰、蛇形、短横线等命名风格一键互转',
icon: CodeIcon,
category: 'work',
component: () => import('@/views/case-convert/CaseConvertView.vue'),
},
{
path: 'color-convert',
name: '色值转换',
desc: 'HEX、RGB、HSL、HSV 互转,取色器 + 预设色板',
icon: Palette1Icon,
category: 'work',
component: () => import('@/views/color-convert/ColorConvertView.vue'),
},
{
path: 'yaml-json',
name: 'YAML ↔ JSON',
desc: 'YAML 与 JSON 双向转换,报错定位清晰',
icon: FileCodeIcon,
category: 'work',
component: () => import('@/views/yaml-json/YamlJsonView.vue'),
},
{
path: 'word-count',
name: '字数统计',
desc: '统计字符、汉字、单词、段落与阅读时长',
icon: ChartIcon,
category: 'work',
component: () => import('@/views/word-count/WordCountView.vue'),
},
{
path: 'zh-convert',
name: '简繁转换',
desc: '简体与繁体中文互转opencc 词库)',
icon: TranslateIcon,
category: 'work',
component: () => import('@/views/zh-convert/ZhConvertView.vue'),
},
/* ==================== 生活助手 ==================== */
{
path: 'image-cropper',
name: '图片裁剪',
desc: '按比例裁剪图片,实时预览,导出 JPG/PNG',
icon: FrameIcon,
category: 'life',
component: () => import('@/views/image-cropper/ImageCropperView.vue'),
},
{
path: 'qrcode',
name: '二维码生成解析',
desc: '文本/链接生成二维码,上传图片解析二维码内容',
icon: QrcodeIcon,
category: 'life',
component: () => import('@/views/qrcode/QrcodeView.vue'),
},
{
path: 'idcard',
name: '身份证解析',
desc: '校验身份证合法性,解析生日、性别、年龄、地区',
icon: CreditcardIcon,
category: 'life',
component: () => import('@/views/idcard/IdcardView.vue'),
},
{
path: 'address-parse',
name: '地址解析',
desc: '拆分中文地址的省、市、区与详细地址',
icon: LocationIcon,
category: 'life',
component: () => import('@/views/address-parse/AddressParseView.vue'),
},
{
path: 'unit-convert',
name: '单位换算',
desc: '长度、质量、温度、数据存储等 8 类单位换算',
icon: SwapIcon,
category: 'life',
component: () => import('@/views/unit-convert/UnitConvertView.vue'),
},
{
path: 'rmb-upper',
name: '人民币大写',
desc: '数字金额转人民币大写,处理角分与负数',
icon: MoneyIcon,
category: 'life',
component: () => import('@/views/rmb-upper/RmbUpperView.vue'),
},
{
path: 'date-calc',
name: '日期计算',
desc: '日期差值、推算与年龄生肖星座计算',
icon: CalendarIcon,
category: 'life',
component: () => import('@/views/date-calc/DateCalcView.vue'),
},
{
path: 'password-gen',
name: '密码生成',
desc: '随机强密码批量生成,含强度评估',
icon: KeyIcon,
category: 'life',
component: () => import('@/views/password-gen/PasswordGenView.vue'),
},
{
path: 'bmi',
name: 'BMI 计算器',
desc: '计算身体质量指数,中国标准分级与健康体重范围',
icon: HeartIcon,
category: 'life',
component: () => import('@/views/bmi/BmiView.vue'),
},
{
path: 'mortgage',
name: '房贷计算器',
desc: '等额本息/等额本金对比,月供、总利息一目了然',
icon: WalletIcon,
category: 'life',
component: () => import('@/views/mortgage/MortgageView.vue'),
},
{
path: 'bill-split',
name: '账单拆分',
desc: '多人聚餐 AA 分账,自动生成转账方案',
icon: UsergroupIcon,
category: 'life',
component: () => import('@/views/bill-split/BillSplitView.vue'),
},
{
path: 'random-decision',
name: '随机决定',
desc: '选项抽签治选择困难,随机数/骰子一键生成',
icon: GiftIcon,
category: 'life',
component: () => import('@/views/random-decision/RandomDecisionView.vue'),
},
{
path: 'pomodoro',
name: '番茄钟',
desc: '专注与休息倒计时,记录完成轮数',
icon: AlarmIcon,
category: 'life',
component: () => import('@/views/pomodoro/PomodoroView.vue'),
},
/* ==================== 开发进阶 ==================== */
{
path: 'sql-format',
name: 'SQL 格式化',
desc: 'SQL 语句美化:关键词换行、子句缩进,实时预览',
icon: TerminalIcon,
category: 'dev',
component: () => import('@/views/sql-format/SqlFormatView.vue'),
},
{
path: 'text-diff',
name: '文本对比',
desc: '逐行对比两段文本,增删高亮一目了然',
icon: GitBranchIcon,
category: 'dev',
component: () => import('@/views/text-diff/TextDiffView.vue'),
},
{
path: 'json-to-code',
name: 'JSON 转代码',
desc: 'JSON 一键生成 TypeScript 接口、Go 结构体与 Python 类',
icon: ApiIcon,
category: 'dev',
component: () => import('@/views/json-to-code/JsonToCodeView.vue'),
},
/* ==================== 创意设计 ==================== */
{
path: 'gradient-generator',
name: '渐变生成器',
desc: '可视化调出 CSS 渐变,复制即用,内置流行配色预设',
icon: BrushIcon,
category: 'design',
component: () => import('@/views/gradient-generator/GradientGeneratorView.vue'),
},
{
path: 'placeholder-image',
name: '占位图生成',
desc: '自定义尺寸与配色的占位图PNG 下载 / SVG 代码复制',
icon: ImageIcon,
category: 'design',
component: () => import('@/views/placeholder-image/PlaceholderImageView.vue'),
},
{
path: 'ascii-art',
name: '图片转字符画',
desc: '把图片转成 ASCII 字符画,可调密度与反转,酷炫复古',
icon: ViewModuleIcon,
category: 'design',
component: () => import('@/views/ascii-art/AsciiArtView.vue'),
},
/* ==================== 趣味娱乐 ==================== */
{
path: 'morse-code',
name: '摩斯电码',
desc: '中英文与摩斯码互转,还能播放滴滴答答的电报声',
icon: SoundIcon,
category: 'fun',
component: () => import('@/views/morse-code/MorseCodeView.vue'),
},
/* ==================== 开发进阶(编程工具) ==================== */
{
path: 'regex-visual',
name: '正则可视化',
desc: '把正则解析成结构树,附匹配测试',
icon: SearchIcon,
category: 'dev',
component: () => import('@/views/regex-visual/RegexVisualView.vue'),
},
{
path: 'markdown-table',
name: 'Markdown 表格',
desc: '粘贴数据一键生成 Markdown / HTML 表格',
icon: Table1Icon,
category: 'dev',
component: () => import('@/views/markdown-table/MarkdownTableView.vue'),
},
{
path: 'api-builder',
name: 'API 请求构造器',
desc: '可视化拼接请求,生成 fetch 与 curl 代码',
icon: ApiIcon,
category: 'dev',
component: () => import('@/views/api-builder/ApiBuilderView.vue'),
},
{
path: 'gitignore-gen',
name: '.gitignore 生成器',
desc: '按技术栈勾选生成忽略规则',
icon: LogoGithubIcon,
category: 'dev',
component: () => import('@/views/gitignore-gen/GitignoreGenView.vue'),
},
{
path: 'code-to-image',
name: '代码转图片',
desc: '代码渲染成卡片图片,支持语法高亮',
icon: ImageIcon,
category: 'dev',
component: () => import('@/views/code-to-image/CodeToImageView.vue'),
},
{
path: 'color-namer',
name: '颜色命名器',
desc: '色值匹配最接近的 CSS 颜色名称',
icon: ContrastIcon,
category: 'dev',
component: () => import('@/views/color-namer/ColorNamerView.vue'),
},
{
path: 'indent-convert',
name: '缩进转换器',
desc: '空格与 Tab 缩进互转2/4/8 空格切换',
icon: Edit2Icon,
category: 'dev',
component: () => import('@/views/indent-convert/IndentConvertView.vue'),
},
{
path: 'code-stats',
name: '代码统计',
desc: '统计总行、代码行、注释行与空白行',
icon: ChartBarIcon,
category: 'dev',
component: () => import('@/views/code-stats/CodeStatsView.vue'),
},
{
path: 'hello-world',
name: 'Hello World 大全',
desc: '30+ 编程语言的入门代码速查',
icon: EarthIcon,
category: 'dev',
component: () => import('@/views/hello-world/HelloWorldView.vue'),
},
{
path: 'json-diff',
name: 'JSON 对比',
desc: '逐键对比两个 JSON 的新增、删除与修改',
icon: GitMergeIcon,
category: 'dev',
component: () => import('@/views/json-diff/JsonDiffView.vue'),
},
{
path: 'env-parse',
name: '环境变量解析',
desc: '.env 与 JSON / 键值对双向转换',
icon: SettingIcon,
category: 'dev',
component: () => import('@/views/env-parse/EnvParseView.vue'),
},
/* ==================== 服务端工具(调用自建后端开放接口) ====================
* 接口基地址由 VITE_API_BASE 配置,路径为 /api/service/open/tools/*(全部 POST
* needsNetwork: true 的工具在首页会打上「需联网」标记。 */
{
path: 'api-status',
name: '服务端接口状态',
desc: '探测后端开放接口的可用性与响应耗时,确认前后端连通情况',
icon: ServerIcon,
category: 'online',
needsNetwork: true,
component: () => import('@/views/api-status/ApiStatusView.vue'),
},
{
path: 'random-string',
name: '随机字符串',
desc: '按长度与字符类型生成随机串,可选服务端密码学随机源',
icon: TextboxIcon,
category: 'online',
needsNetwork: true,
component: () => import('@/views/random-string/RandomStringView.vue'),
},
{
path: 'ip-info',
name: 'IP 信息查询',
desc: '查询服务端识别的来源 IP、是否内网地址并展示本机环境信息',
icon: InternetIcon,
category: 'online',
needsNetwork: true,
component: () => import('@/views/ip-info/IpInfoView.vue'),
},
{
path: 'network-test',
name: '网络测试',
desc: '站点可达性检测HTTP / Ping / TCP / DNS',
icon: WifiIcon,
category: 'online',
needsNetwork: true,
component: () => import('@/views/network-test/NetworkTestView.vue'),
},
{
path: 'speed-test',
name: '网络测速',
desc: '宽带下载/上传速度与延迟测试',
icon: RocketIcon,
category: 'online',
needsNetwork: true,
component: () => import('@/views/speed-test/SpeedTestView.vue'),
},
{
path: 'exchange-rate',
name: '汇率换算',
desc: '主流货币实时汇率换算',
icon: CurrencyExchangeIcon,
category: 'online',
needsNetwork: true,
component: () => import('@/views/exchange-rate/ExchangeRateView.vue'),
},
{
path: 'express-query',
name: '快递查询',
desc: '物流轨迹一键查询',
icon: LocationIcon,
category: 'online',
needsNetwork: true,
component: () => import('@/views/express-query/ExpressQueryView.vue'),
},
{
path: 'phone-locate',
name: '手机号归属地',
desc: '归属地与运营商查询',
icon: MobileIcon,
category: 'online',
needsNetwork: true,
component: () => import('@/views/phone-locate/PhoneLocateView.vue'),
},
]
/** 工具默认分类:未填写 category 时归入第一个分类 */
export function toolCategoryKey(tool: ToolItem): string {
return tool.category ?? toolCategories[0].key
}
/**
* 按分类分组返回工具(按 toolCategories 声明顺序)。
* 侧边导航、首页分区、命令面板统一使用该函数,新增分类零改动。
*
* 注意:这里会过滤掉「没有任何工具的分类」。历史上 toolCategories 里
* 有 health / study / travel 三个只声明未使用的分类,导致首页渲染出
* 标题下方空白的分区、侧栏出现空分组标题。过滤后新增分类也不会再复现。
*/
export function groupedTools(): Array<{ category: ToolCategory; items: ToolItem[] }> {
return toolCategories
.map((category) => ({
category,
items: tools.filter((t) => toolCategoryKey(t) === category.key),
}))
.filter((group) => group.items.length > 0)
}
/** 依赖后端接口的工具(首页做区分展示,命令面板加联网标记) */
export function networkTools(): ToolItem[] {
return tools.filter((t) => t.needsNetwork)
}
/**
* 工具搜索:名称、描述、路由 path 三者任一命中即算匹配。
* 同时把 i18n 翻译结果一起纳入匹配,保证英文/日文/韩文界面下
* 用对应语言的关键词也能搜到(例如 "compress" / "압축")。
*/
export function searchTools(keyword: string, resolve: (tool: ToolItem) => string[]): ToolItem[] {
const kw = keyword.trim().toLowerCase()
if (!kw) return tools
return tools.filter((tool) => {
const haystack = [tool.path, tool.name, tool.desc, ...resolve(tool)]
.join(' ')
.toLowerCase()
return haystack.includes(kw)
})
}