89 Commits
Author SHA1 Message Date
taoran 3106e47ad1 fix 2026-03-02 21:37:53 +08:00
taoran 4b955d7e18 fix 2026-03-02 21:36:14 +08:00
陶然 c23f06df50 fix 2026-03-02 20:51:27 +08:00
陶然 94d3bee845 fix 2026-03-02 19:09:17 +08:00
陶然 e861236a9a fix 2026-03-02 18:51:04 +08:00
陶然 226997fb25 feat:页面美化,添加广告位 2026-03-02 18:49:02 +08:00
陶然 f492194fab fix:修复工具详情页中跳转关于页面404问题 2026-03-02 16:43:57 +08:00
陶然 5bbe38f6fe feat: 收藏工具 2026-03-02 16:29:28 +08:00
陶然 68b493c725 fix 2026-03-02 15:52:59 +08:00
陶然 5187935888 feat: 调整工具分类 2026-03-02 15:40:41 +08:00
陶然 0701b6a286 feat: 新增工具 2026-03-02 15:07:32 +08:00
TaoRan 071d7fb5e1 Merge pull request #7 from anyanf/master
fix:URL编解码按钮换行错位
2024-08-30 17:54:43 +08:00
ankang 71e3e59856 fix:URL编解码按钮换行错位 2024-08-29 10:40:25 +08:00
taoran 3d9380c575 Merge branch 'feature/06' 2024-08-23 14:42:29 +08:00
taoran 8b450b91cf docs 2024-08-23 14:42:23 +08:00
taoran ce3df800d3 Merge branch 'feature/06' 2024-08-21 18:09:02 +08:00
taoran 6d471fef84 fix: 搜索工具异常 2024-08-21 18:08:54 +08:00
taoran b45580dfdc Merge branch 'feature/06' 2024-08-21 12:02:41 +08:00
taoran dd38597204 docs 2024-08-21 12:02:33 +08:00
taoran 4fa19c1aed Merge branch 'feature/06' 2024-07-27 00:24:45 +08:00
taoran d984af3ebe fix: 修复多次搜索后无结果问题,修复骰子工具骰子图片丢失问题 2024-07-27 00:24:12 +08:00
taoran f289facdba Merge branch 'feature/06' 2024-07-09 18:25:43 +08:00
taoran 3604407dd7 fix: 路由名称seo优化 2024-07-09 18:25:17 +08:00
taoran a5233c55c6 Merge branch 'feature/06' 2024-07-05 18:29:43 +08:00
taoran ae62d59fa3 docs 2024-07-05 18:29:18 +08:00
taoran ba71938f01 fix 2024-07-04 10:38:48 +08:00
taoran 84f42c6825 Merge branch 'feature/06' 2024-06-28 12:12:57 +08:00
taoran c90524bac1 feat: add favicon 2024-06-28 12:12:50 +08:00
taoran c53bda5706 Merge branch 'feature/06' 2024-06-21 11:47:55 +08:00
taoran 4e1354e5d2 fix: 首页小屏错位样式修复 2024-06-21 11:47:48 +08:00
taoran f822d17e6e fix: 清除百度统计 2024-06-21 11:22:52 +08:00
taoran 9e9176fb68 fix 2024-06-21 11:16:48 +08:00
taoran 6082f1d789 feat: 工具详情页添加描述 2024-06-20 17:12:02 +08:00
taoran 95961dc05a docs 2024-06-20 15:01:17 +08:00
taoran c4bff93b44 feat: 新增工具:在线文本编辑/HTML获取; 回到顶部 2024-06-20 14:29:26 +08:00
taoran 6e8787a2d0 fix: debug 2024-06-18 18:25:21 +08:00
taoran 527b7f2f5d fix: add logo 2024-06-18 17:45:18 +08:00
taoran 95b7fec3b5 feat: 新增3个工具,样式修复,修改logo,保存桌面
新增功能:
 - js代码格式化/压缩
 - html代码格式化
 - css代码格式化/压缩
 - 保存到桌面

其他修改:
 - 修改logo
 - 修复一些样式问题
 - 修复一些已知的问题
2024-06-18 17:40:32 +08:00
osiris f05ee53440 Merge branch 'feature/06' 2024-06-05 09:30:36 +08:00
osiris 50a6e0be2a docs 2024-06-05 09:30:29 +08:00
osiris dd0022e219 Merge branch 'feature/06' 2024-06-05 09:29:07 +08:00
osiris 8ca5623b6b docs 2024-06-05 09:29:00 +08:00
osiris 18f9196212 Merge branch 'feature/06' 2024-06-05 09:24:42 +08:00
osiris 81145a70cd docs 2024-06-05 09:24:34 +08:00
osiris 2d9fb23cea Merge branch 'feature/06' 2024-06-05 09:12:35 +08:00
osiris 2090d61140 docs 2024-06-05 09:12:28 +08:00
osiris 4952fb94d6 Merge branch 'feature/06' 2024-06-05 09:11:38 +08:00
osiris 5ad691258b docs 2024-06-05 09:11:31 +08:00
osiris 90136ccbd2 feat: 单位换算 2024-05-31 11:11:42 +08:00
osiris 5a06a7aef3 fix: debug 2024-05-30 18:01:23 +08:00
osiris 144c22eb56 feat: 新增5个工具
- WT解析器
- Http状态码
- html实体转义(#)
- 颜色选择、转换器
- ASCII字形生成器
2024-05-30 17:54:06 +08:00
osiris a10c36cf73 style 2024-05-16 10:40:02 +08:00
osiris 6ef16b1da0 doc 2024-05-15 18:04:36 +08:00
osiris a4dc95ba38 doc 2024-05-15 17:39:15 +08:00
osiris a15c293322 doc 2024-05-15 17:36:39 +08:00
osiris 2c914205d7 doc 2024-05-15 17:31:59 +08:00
osiris 34c601c400 fix: debug 2024-05-15 17:28:59 +08:00
osiris 097d8fc0b8 feat: docker部署方案 2024-05-15 17:21:54 +08:00
osiris ad8a1179cd fix: debug 2024-05-14 15:18:57 +08:00
osiris c860a01416 feat: 搜索时允许根据描述搜索和一些样式调整 2024-05-14 15:05:57 +08:00
osiris efc1cb52eb style 2024-05-14 11:00:42 +08:00
osiris c0488db4e8 feat: 0514版本更新
feat:整理出一套纯前端
style: 关于本站内容调整,头部样式修改
fix: 简化mock数据格式
2024-05-14 10:37:21 +08:00
osiris a882cfeac9 feat: floor edit 2024-05-13 16:47:24 +08:00
osiris 9a55817794 feat: 只保留前端部分 2024-05-13 16:23:33 +08:00
osiris 68951056b1 fix: 添加隐私权政策,工具组件目录调整 2024-05-11 18:25:00 +08:00
osiris 4d72590f6f fix: dbeug 2024-04-30 11:07:28 +08:00
osiris 4bb328e5f6 docs 2024-04-11 16:24:48 +08:00
osiris de2ae48d58 feat: logo存储到本地 2024-04-11 15:55:30 +08:00
osiris b6b5f1dc0b fix: 修复数据图表上传数据文件只有第一次有效问题 2024-04-11 12:16:44 +08:00
osiris d863f0174c feat: 新增7个工具,修复部分问题
新增工具:
数据图表:折线图(#)
数据图表:饼图(#)
数据图表:散点图(#)
选择随机: 投骰子(#)
选择随机: 抛硬币(#)
文本处理:删除重复行(#)
图片工具:图片切割(#)

修复:
数据图表上传数据文件只有第一次有效问题
2024-04-11 12:09:34 +08:00
osiris df8ed7f9c8 fix: 修复bug:柱状图导入数据问题; 直接访问工具收藏栏报错问题 2024-03-26 18:47:51 +08:00
osiris 4631227f9a feat: 工具:柱状图 2024-03-26 15:54:10 +08:00
osiris dd77da705b docs 2024-03-08 18:20:07 +08:00
osiris 92def0bd57 fix: debug 2024-03-08 17:55:32 +08:00
osiris 7f9ee12172 feat: 头部添加建议/问题入口 2024-03-08 17:54:07 +08:00
osiris 551343dc0e feat: 网站favicon工具添加下载功能, debgu 2024-03-08 17:41:36 +08:00
osiris 101ee6dff2 feat: 网站favicon获取 2024-03-08 16:16:49 +08:00
osiris 6ecafa8bc2 fix: mock下午无法使用登录及相关功能 2024-03-07 15:26:36 +08:00
osiris d5865d4c8e fix: debug 2024-03-06 18:46:58 +08:00
osiris b8448cd1e9 fix: debug 2024-03-06 17:12:58 +08:00
osiris 653bca5da6 fix: debug 2024-03-06 17:12:07 +08:00
osiris 9c8d1e2f86 fix: debug 2024-03-06 16:55:48 +08:00
osiris 3a0eaa24ac docs 2024-03-01 16:51:22 +08:00
osiris f9d2c07ba5 docs 2024-03-01 16:32:21 +08:00
osiris 108948aa2c fix: debug 2024-03-01 15:48:50 +08:00
osiris e2676f9ec8 feat: 用户登录注册, 工具收藏 2024-02-29 18:07:26 +08:00
osiris 7456b9a35f feat: 平板和小屏幕适配 2024-02-19 15:03:16 +08:00
osiris 3f387b0c7e fix: debug 2024-02-19 11:35:47 +08:00
osiris 81146ad040 feat: 首页改版 2024-02-19 11:19:26 +08:00
201 changed files with 20506 additions and 8516 deletions
+4
View File
@@ -0,0 +1,4 @@
node_modules
dist
.env.development
.env.production
+7 -7
View File
@@ -1,18 +1,18 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取 # 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development' NODE_ENV = 'development'
# 网站名称 # 网站名称
VITE_APP_TITLE = '工具坊' VITE_APP_TITLE = 'Tools-Web'
# 网站域名 # 简短描述
VITE_APP_WEBSITE = 'xxx.xxx.com' VITE_APP_DESC = '一个轻量的在线工具箱'
# base api # base api
VITE_APP_BASE_API = '/v1' VITE_APP_BASE_API = '/v1'
# 服务端地址: https://xxx.xxx.com # 服务端地址: https://xxx.xxx.com;
VITE_SERVE = '' VITE_SERVE = ''
# 自定义启动host # 自定义启动host
VITE_HOST = '127.0.0.1' VITE_HOST = '127.0.0.1'
# 是否使用mock: bool
VITE_IS_MOCK = 'true'
# 第三方反馈互动地址 # 第三方反馈互动地址
VITE_FEEDBACK_URL = '' VITE_FEEDBACK_URL = ''
# 广告位开关 # 广告位开关(废弃)
VITE_ADV_SHOW = 'false' VITE_ADV_SHOW = 'false'
# 左侧底部广告位
VITE_LEFT_ADV = ''
+3
View File
@@ -13,6 +13,7 @@ dist-ssr
*.local *.local
.env.development .env.development
.env.production .env.production
.env
.vscode .vscode
# Editor directories and files # Editor directories and files
@@ -25,3 +26,5 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
public/sitemap.txt
+16
View File
@@ -0,0 +1,16 @@
# build stage
FROM node:22-alpine3.22 AS build-stage
# Set environment variables for non-interactive npm installs
ENV NPM_CONFIG_LOGLEVEL warn
WORKDIR /app
COPY . .
COPY .env.example .env.development
RUN npm install -g pnpm && pnpm i
RUN pnpm build
# production stage
FROM nginx:stable-alpine AS production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+150 -15
View File
@@ -1,19 +1,57 @@
# 基于Vite4.x + Vue3.x + ElementPlus + Typescript + Tailwind的工具站 <div align="center">
<pre>
_______ _ __ __ _
|__ __| | | \ \ / / | |
| | ___ ___ | |___ _____\ \ /\ / /__| |__
| |/ _ \ / _ \| / __|______\ \/ \/ / _ \ '_ \
| | (_) | (_) | \__ \ \ /\ / __/ |_) |
|_|\___/ \___/|_|___/ \/ \/ \___|_.__/
[![license](https://img.shields.io/github/license/taoran1401/tools-web)](LICENSE) </pre>
<p> 只需简单几步,即可快速搭建属于自己的在线工具箱。</p>
> 演示站点:https://tools.ranblogs.com [![node](https://img.shields.io/badge/any_text-22.21.1-red?label=node)](node)
[![vue](https://img.shields.io/badge/any_text-3.3.10-origin?label=vue)](vue)
[![tailwindcss](https://img.shields.io/badge/any_text-3.3.5-yellow?label=tailwindcss)](tailwindcss)
[![elementplus](https://img.shields.io/badge/any_text-2.7-blue?label=element-plus)](elementplus)
[![license](https://img.shields.io/github/license/naroat/tools-web)](LICENSE)
# 快速使用 </div>
安装`pnpm`, 为避免因为不同包管理器工具下载同一个依赖,可能版本不一样的问题,本项目强制使用`pnpm` ## 目录
- [功能展览](#功能展览)
- [开始使用](#开始使用)
- [Docker部署](#Docker部署)
- [手动部署](#手动部署)
- [工具列表](#工具列表)
- [其他](#其他)
## 功能展示
在线站点:<a href="https://tools.ranblogs.com" target="_blank">Tools-Web</a>
## 开始使用
### Docker部署
```
docker run -d --name tools-web --restart unless-stopped -p 8080:80 docker0796/tools-web:latest
```
访问:`http://127.0.0.1:8080`
### 手动部署
安装`pnpm`
``` ```
npm install pnpm -g npm install pnpm -g
``` ```
克隆 克隆
``` ```
git clone --depth=1 https://github.com/taoran1401/tools-web.git git clone --depth=1 https://github.com/naroat/tools-web.git
``` ```
安装 安装
@@ -35,15 +73,112 @@ pnpm dev
打包 打包
``` ```
pnpm build:dev pnpm build
``` ```
# 技术栈 打包seo静态页面:复制`.env.development`文件,并将文件名修改为`.env.production`,将里面的`NODE_ENV`的值改为`productio`,然后运行下面打包命令
```
pnpm build:pro
```
## 工具列表
- 开发运维
- 随机密码生成
- URL编码/解码
- UUID生成器
- 时间戳转换
- MD5在线加密
- Json在线转换
- 正则测试工具
- Unicode转中文
- HTTP状态码
- JWT解析
- html实体转义
- js代码格式化/压缩
- Html代码格式化
- Css代码格式化/压缩
- Base64加解密
- 进制转换计算器
- 数据存储单位换算
- Hash计算器
- XML格式化
- SQL格式化
- 文本处理
- 文本对比
- markdown编辑器
- ASCII字形生成器
- 文本去重
- 在线文本编辑/HTML获取
- 字数统计
- 文本替换
- 文本处理工作流
- 词频统计
- 教育学术
- 单位换算
- 摩斯电码
- 常用进制转换
- ASCII码表
- 长度单位转换
- 面积单位转换
- 重量单位转换
- 时间单位转换
- 温度单位转换
- 压力单位转换
- 热量单位转换
- 功率单位转换
- 图片处理
- 二维码生成
- 在线图片处理
- 文本转图片
- 图片分割
- 传图取色
- 图片水印添加
- 数据图表
- 柱状图
- 折线图
- 饼图
- 散点图
- 词云生成
- 选择随机
- 生成随机数
- 帮我决定
- 抛硬币
- 投骰子
- 抽签工具
- 转盘工具
- 其他工具
- 数字转金额大写
- 手持弹幕
- 色板
- Color选择器
- 番茄时钟
- 反应速度测试
- m3u8在线播放
- 剪刀石头布
- Emoji表情大全
- 简易计算器
## 其他
Q: 我应该如何添加新功能?
A:
-`components/Tools/tools.ts`文件中添加工具信息
-`router/router.ts`中添加路由
- 目录`components/Tools`中添加新的工具组件
<br/>
Q: 我可以参与开发 `Tools-Web` 吗?
A: 当然可以,随时欢迎提交 `PR`
- 编程语言: TypeScript + JavaScript
- 构件工具: Vite4.x
- 前端框架: Vue3.x
- 路由工具: Vue Router
- 状态管理: Pinia
- Ui框架: ElementPlus + Tailwindcss
- Http工具: Axios
+5 -13
View File
@@ -3,7 +3,8 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/assets/logo.png" /> <link rel="icon" type="image/svg+xml" href="/src/assets/logo.png" />
<link rel="shortcut icon" type="image/svg+xml" href="/src/assets/logo.png" /> <link rel="shortcut icon" type="image/svg+xml" href="/public/favicon.ico" />
<link rel="canonical" href="https://tools.ranblogs.com/">
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="renderer" content="webkit"> <meta name="renderer" content="webkit">
<meta name="keywords" content=""> <meta name="keywords" content="">
@@ -13,20 +14,11 @@
<meta property="og:site_name" content=""> <meta property="og:site_name" content="">
<meta property="og:description" content=""> <meta property="og:description" content="">
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:image" content="https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/logo.png"> <meta property="og:image" content="/src/assets/logo.png">
<meta property="og:url" content="https://tools.ranblogs.com/"> <meta property="og:url" content=""><!-- 可选:这里填写自己的访问地址 -->
<title></title> <title></title>
<!-- 百度统计 -->
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?a94ed52bb5ed2bd9fdbad5b7468a9338";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</head> </head>
<body class="bg-[--base-gray]">
<body> <body>
<!--[if lt IE 9]> <!--[if lt IE 9]>
<div class="notice chromeframe">您的浏览器版本<strong>很旧很旧</strong>,为了正常地访问网站,请升级您的浏览器</div> <div class="notice chromeframe">您的浏览器版本<strong>很旧很旧</strong>,为了正常地访问网站,请升级您的浏览器</div>
-309
View File
@@ -1,309 +0,0 @@
//获取tools
function getTools() {
return [
{
id: 1,
title: '时间戳转换',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/Time.png',
desc: '在线时间戳转换工具以及获取当前时间戳',
url: '/timetran',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'MD5在线加密',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/Password%20fishing.png',
desc: 'MD5在线加密,长度包含32位、16位',
url: '/MD5',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'Json在线转换',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/json.png',
desc: '在线编辑json, 语法检查',
url: '/json',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: '正则测试工具',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/reg.png',
desc: '正则表达式测试工具, 常用正则表达式',
url: '/reg',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'Unicode转中文',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/union.png',
desc: 'Unicode和中文的相互转换',
url: '/unicode',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: '字数统计',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/ico_%E6%95%B0%E6%8D%AE%E7%BB%9F%E8%AE%A1_%E5%B7%A5%E4%BD%9C%E9%87%8F%E7%BB%9F%E8%AE%A1.png',
desc: '在线统计字符串的字数、段落、标点符号数量',
url: '/wordCount',
cateId: 3,
cate: '文本处理',
},
{
id: 1,
title: 'IP查询',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/IP.png',
desc: '在线查询ip地址、ip归属地',
url: '/ip',
cateId: 6,
cate: '查询相关',
},
{
id: 1,
title: '常用进制转换',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E8%BF%9B%E5%88%B6%E8%BD%AC%E6%8D%A2.png',
desc: '在线进制转换工具,可在2到64进制之间相互转换',
url: '/scaletran',
cateId: 4,
cate: '教育学术',
},
{
id: 1,
title: '在线图片处理',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/img.png',
desc: '在线图片裁剪,标注,滤镜,画等操作',
url: '/signimage',
cateId: 5,
cate: '设计工具',
},
{
id: 1,
title: '随机密码生成',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/keywords.png',
desc: '密码生成器、随机字符串生成,批量生成',
url: '/random_password',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'URL编码/解码',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/url.png',
desc: 'URL在线编码解码工具(UrlEncode编码 和 UrlDecode解码)',
url: '/urlencode',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'UUID生成器',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E7%BA%BF%E6%80%A7-%E7%94%9F%E6%88%90.png',
desc: '批量生成UUID',
url: '/uuid',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: '手持弹幕',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E5%BC%B9%E5%B9%95.png',
desc: '手持滚动弹幕',
url: '/barrage',
cateId: 7,
cate: '其他',
},
{
id: 1,
title: 'ASCII码表',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/icon_%E7%BC%96%E7%A0%81%E5%AF%B9%E7%85%A7.png',
desc: 'ASCII码表,控制代码、标准ASCII字符和非标准ASCII字符对照表',
url: '/ascii',
cateId: 2,
cate: '开发运维'
},
{
id: 1,
title: '单位换算',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E5%8D%95%E4%BD%8D%E6%8D%A2%E7%AE%97.png',
desc: '在线重量、长度、面积、时间、角度、速度、温度、压力、热量、功率等换算',
url: '/unit',
cateId: 4,
cate: '教育学术'
},
{
id: 1,
title: '色板',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E8%B0%83%E8%89%B2%E6%9D%BF.png',
desc: '包含纯色、渐变与阶梯色和常用色彩组合',
url: '/palettes',
cateId: 5,
cate: '设计工具'
},
{
id: 1,
title: '二维码生成',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E4%BA%8C%E7%BB%B4%E7%A0%81.png',
desc: '在线生成带logo、透明、艺术的二维码',
url: '/qrcode',
cateId: 5,
cate: '设计工具'
},
{
id: 1,
title: '帮我决定',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/choose.png',
desc: '选择困难,难以决定,今天吃什么,现在做什么,自定义选项都给你安排的明明白白',
url: '/decision',
cateId: 7,
cate: '其他'
},
{
id: 1,
title: '摩斯电码',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/medium.png',
desc: '支持中文的摩斯电码编码解码',
url: '/morse',
cateId: 3,
cate: '文本处理'
},
{
id: 1,
title: '生成随机数',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/randomnum.png',
desc: '可定制范围内进行随机数字,可用于抽奖、点名等用途',
url: '/random',
cateId: 7,
cate: '其他'
},
{
id: 1,
title: '数字转金额大写',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/text%20recognition.png',
desc: '在线数字一键转换成人民币大写,中文大写转换数字',
url: '/numberToChinese',
cateId: 7,
cate: '其他'
},
{
id: 1,
title: '文本对比',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/diff.png',
desc: '文本差异比对支持中文、英文、代码比对',
url: '/diff',
cateId: 3,
cate: '文本处理'
},
{
id: 1,
title: 'markdown编辑器',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/file-markdown-fill.png',
desc: '在线创建或编辑markdown, 实时预览,导出markdown',
url: '/markdown',
cateId: 3,
cate: '文本处理'
},
{
id: 1,
title: '文本转图片',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/text_to_img.png',
desc: '把文本转换成图片,生成长图,具有超多个性文字排版',
url: '/textToImg',
cateId: 3,
cate: '文本处理'
}
]
}
//获取tools分类
function getToolsCate() {
return [
{
id: 2,
title: '开发运维'
},
{
id: 3,
title: '文本处理'
},
{
id: 4,
title: '教育学术'
},
{
id: 5,
title: '设计工具'
},
{
id: 6,
title: '查询相关'
},
{
id: 7,
title: '其他'
}
]
}
export default [
{
url: '/v1/tools',
method: 'get',//请求方式
response: (request) => {
//接收参数
const { cateId, title } = request.query
//调用获取用户信息函数,用于判断是否有此用户
let list = getTools()
//标题筛选
if (title != '') {
list = list.filter(item => {
let tmpValue = item.title.toLowerCase()
return tmpValue.indexOf(title.toLowerCase()) !== -1;
});
}
//分类筛选
if (cateId > 0) {
list = list.filter(item => {
return item.cateId == cateId;
});
}
return { code: 200, message: 'ok', data: list }
},
},
{
url: '/v1/tool/cates',
method: 'get',//请求方式
response: ({ body }) => {
const list = getToolsCate()
return { code: 200, message: 'ok', data: list }
},
},
{
url: '/v1/tool/recommends',
method: 'get',//请求方式
response: () => {
let list = [] as any
//获取推荐数量
let num = 10
const toolList = getTools()
//使用Set数据结构来存储已经生成的随机数
const set = new Set();
while (set.size < num) {
// 生成随机数
const randomNum = Math.floor(Math.random() * toolList.length);
// 如果随机数不在Set中,则添加到Set中并赋值给randomNumber变量
if (!set.has(randomNum)) {
set.add(randomNum);
list.push(toolList[randomNum])
}
}
return { code: 200, message: 'ok', data: list }
},
}
]
+10
View File
@@ -0,0 +1,10 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
+36 -21
View File
@@ -1,63 +1,78 @@
{ {
"name": "rbl-tools-web", "name": "tools-web",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build:dev": "vue-tsc && vite build --mode development", "build": "vue-tsc --noEmit && node --max_old_space_size=8192 node_modules/vite/bin/vite.js build --mode development",
"build:pro": "vue-tsc && vite build --mode production", "build:pro": "vue-tsc --noEmit && node --max_old_space_size=8192 node_modules/vite/bin/vite.js build --mode production",
"preview": "vite preview", "preview": "vite preview"
"preinstall": "node ./scripts/preinstall.js"
}, },
"dependencies": { "dependencies": {
"@codemirror/commands": "^6.3.0", "@codemirror/commands": "^6.3.2",
"@codemirror/lang-javascript": "^6.2.1", "@codemirror/lang-javascript": "^6.2.1",
"@codemirror/lang-json": "^6.0.1", "@codemirror/lang-json": "^6.0.1",
"@codemirror/language": "^6.9.2", "@codemirror/language": "^6.9.3",
"@codemirror/lint": "^6.4.2", "@codemirror/lint": "^6.4.2",
"@codemirror/search": "^6.5.4", "@codemirror/search": "^6.5.5",
"@codemirror/state": "^6.3.3", "@codemirror/state": "^6.4.1",
"@codemirror/theme-one-dark": "^6.1.2", "@codemirror/theme-one-dark": "^6.1.2",
"@codemirror/view": "^6.22.0", "@codemirror/view": "^6.22.1",
"@element-plus/icons-vue": "^2.1.0", "@element-plus/icons-vue": "^2.3.1",
"@kangc/v-md-editor": "^2.3.18", "@kangc/v-md-editor": "^2.3.18",
"@toast-ui/editor": "^3.2.2", "@toast-ui/editor": "^3.2.2",
"@vueuse/core": "^10.9.0",
"@wangeditor/editor": "^5.1.23", "@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12", "@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.2", "axios": "^1.6.2",
"element-plus": "^2.4.2", "codemirror": "^6.0.2",
"colord": "^2.9.3",
"crypto-js": "^4.2.0",
"csso": "^5.0.5",
"default-passive-events": "^2.0.0",
"echarts": "^5.5.0",
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.4.3",
"figlet": "^1.7.0",
"highlight.js": "^11.9.0", "highlight.js": "^11.9.0",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"imorse": "^1.0.0", "imorse": "^1.0.0",
"jwt-decode": "^4.0.0",
"lodash": "^4.17.21",
"prettier": "^3.3.2",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
"qrcode-vue3": "^1.6.8", "qrcode-vue3": "^1.7.1",
"sass": "^1.69.5", "sass": "^1.69.5",
"sass-loader": "^13.3.2", "sass-loader": "^13.3.2",
"sql-formatter": "^15.7.2",
"terser": "^5.31.1",
"ts-md5": "^1.3.1", "ts-md5": "^1.3.1",
"tui-image-editor": "^3.15.3", "tui-image-editor": "^3.15.3",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"v-code-diff": "^1.8.0", "v-code-diff": "^1.8.0",
"vite-plugin-seo-prerender": "^0.5.0", "vite-plugin-seo-prerender": "^0.5.0",
"vue": "^3.3.4", "vue": "^3.3.10",
"vue-clipboard3": "^2.0.0", "vue-clipboard3": "^2.0.0",
"vue-codemirror": "^6.1.1", "vue-codemirror": "^6.1.1",
"vue3-danmaku": "^1.6.0" "vue3-danmaku": "^1.6.0",
"x-data-spreadsheet": "^1.1.9",
"xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^4.2.3", "@types/crypto-js": "^4.2.2",
"@vitejs/plugin-vue": "^4.5.1",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"mockjs": "^1.1.0", "less": "^4.2.0",
"path": "^0.12.7", "path": "^0.12.7",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"tailwindcss": "^3.3.5", "tailwindcss": "^3.3.5",
"typescript": "^5.0.2", "typescript": "^5.9.3",
"vite": "^4.4.5", "vite": "^4.5.1",
"vite-plugin-html": "^3.2.0", "vite-plugin-html": "^3.2.0",
"vite-plugin-mock": "2.9.6",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vue-router": "^4.2.5", "vue-router": "^4.2.5",
"vue-tsc": "^1.8.5" "vue-tsc": "^3.2.5"
} }
} }
+7553 -7370
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772429681651" class="icon" viewBox="0 0 1303 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5445" xmlns:xlink="http://www.w3.org/1999/xlink" width="254.4921875" height="200"><path d="M1282.786896 270.896107L1000.908905 18.527799a73.076031 73.076031 0 0 0-49.337958-18.525006H105.750792S0 5.960584 0 95.141269v833.066759c1.117086 53.433939 47.9416 96.162472 105.006068 95.79011h1093.254667S1303.266803 1019.995247 1303.266803 928.114938V316.510445a64.232435 64.232435 0 0 0-21.13154-45.614338h0.651633z m-277.688919-162.535989l182.550443 166.445789h-148.013873s-35.188204 0-35.188204-32.302398v-132.18849l0.651634-1.954901z m229.095686 819.84791c0 8.657415-3.723619 16.942468-10.426135 22.90026a35.095113 35.095113 0 0 1-24.855159 8.843596H105.192249a34.257299 34.257299 0 0 1-34.629661-31.743856V95.141269c0-29.044232 34.53657-29.044232 34.536571-29.044231h830.367134v176.406471c0 89.180686 104.261344 96.441743 104.261345 96.441743h194.559115v589.262776z" fill="#1296db" p-id="5446"></path><path d="M344.993341 287.373123c68.235326 0 91.88031 22.527898 91.88031 61.25354a53.71321 53.71321 0 0 1-39.191095 53.71321c27.089331 3.258167 47.476148 26.344608 47.196876 53.71321 0 45.148886-29.509684 65.535702-90.763223 65.535703H255.254112V287.373123h89.739229z m-45.614338 95.603929h41.890719c30.62677 0 53.71321-9.681411 53.71321-30.068227 0-20.479907-17.687192-29.044232-53.71321-29.044232h-41.890719v59.112459z m0 98.862096h45.614338c37.050013 0 53.71321-9.122868 53.71321-31.650765 0-22.620988-23.086441-30.71986-53.71321-30.719861h-45.614338v62.370626z m294.910659-74.65857V401.222787c0-12.846487-9.681411-24.668979-36.584561-24.668979a42.449262 42.449262 0 0 0-41.332175 21.410812l-39.191095-5.306158a83.315985 83.315985 0 0 1 82.198899-48.872505c53.71321 0 77.265103 22.527898 77.265103 58.553916v82.198899c0 9.681411 5.957791 11.822492 18.897369 9.122868v25.786064a90.297771 90.297771 0 0 1-26.90315 3.72362 37.608556 37.608556 0 0 1-29.509684-12.287944 78.47528 78.47528 0 0 1 0-10.798497 96.162472 96.162472 0 0 1-64.511707 23.086441c-27.927146 0-59.577911-13.963573-59.577911-44.590343-2.792715-41.890719 39.098004-63.30153 119.155822-71.400403z m-53.71321 83.222895a92.438853 92.438853 0 0 0 53.71321-17.687193V439.389886c-40.30818 3.723619-79.033823 12.939578-79.033822 34.44348 2.792715 11.729401 9.681411 16.570106 26.344607 16.570107h-1.023995z m171.286494-19.828274a44.0318 44.0318 0 0 0 43.007805 21.969355c19.362821 0 31.650765-9.122868 31.650765-20.386817s-18.152645-18.804278-41.332176-25.786064c-45.614338-12.381035-67.11824-26.81006-67.11824-53.713211s29.509684-49.337958 74.658569-49.337957a75.775656 75.775656 0 0 1 75.775656 42.821623l-39.749638 5.957792a38.167099 38.167099 0 0 0-36.026018-17.687193c-19.828274 0-32.209308 8.564325-32.209308 19.362821 0 10.705406 19.828274 19.269731 36.491471 23.551893 49.989591 11.357039 74.75166 25.320612 74.75166 53.806301 0 28.392598-31.185313 53.71321-78.47528 53.713211a85.922519 85.922519 0 0 1-81.640356-45.614339l40.308181-8.657415z m235.891291-126.789242a81.081813 81.081813 0 0 1 85.922519 87.039605v17.128649H902.139899a42.449262 42.449262 0 0 0 47.196877 42.449262 49.337958 49.337958 0 0 0 44.683433-21.410812l39.098004 5.864701a84.33998 84.33998 0 0 1-83.781437 48.407052 83.781437 83.781437 0 0 1-90.297772-89.739228 83.781437 83.781437 0 0 1 88.715233-89.739229z m44.0318 73.541484a41.890719 41.890719 0 0 0-44.0318-40.773633 42.449262 42.449262 0 0 0-45.148885 40.773633h89.180685zM536.759742 560.407518a82.198899 82.198899 0 0 1 78.47528 47.755419l-43.007805 6.516334a39.191095 39.191095 0 0 0-36.49147-21.503902c-35.467475 0-49.431048 38.632552-53.713211 76.334199a67.11824 67.11824 0 0 1 56.878287-26.996241 71.493493 71.493493 0 0 1 77.358194 74.193117c0 48.407053-30.068227 78.382189-87.505057 78.382189-57.52992 0-86.015609-33.791846-86.015609-100.910087 0-82.850532 30.71986-133.771028 94.021391-133.771028zM531.546675 763.344778a41.332176 41.332176 0 0 0 43.473257-44.0318 41.890719 41.890719 0 0 0-43.473257-44.0318 44.0318 44.0318 0 1 0 0 88.0636zM752.171126 564.59659h62.370626v144.476434h29.509684v34.44348H814.541752v47.196876h-43.007804v-47.289966H650.143954v-26.81006L752.171126 564.59659z m19.362822 144.476434v-104.261344l-69.259322 104.261344h69.259322z" fill="#1296db" p-id="5447"></path></svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772432797191" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="31778" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M753.999941 483.999962c148.879988 0 269.999979 121.119991 269.999979 269.999979S902.879929 1023.99992 753.999941 1023.99992a268.703979 268.703979 0 0 1-169.495987-59.999995H89.999993C40.375997 963.999925 0 923.623928 0 873.999932v-783.999939C0 40.375997 40.375997 0 89.999993 0h543.999957C683.623947 0 723.999943 40.375997 723.999943 89.999993v395.679969a271.743979 271.743979 0 0 1 29.999998-1.68zM529.607959 903.999929A268.431979 268.431979 0 0 1 483.999962 753.999941a268.431979 268.431979 0 0 1 45.607997-149.999988H513.99996a29.999998 29.999998 0 0 1 0-59.999995h59.999995c3.08 0 6.056 0.464 8.855999 1.327999A269.967979 269.967979 0 0 1 663.999948 499.439961V89.999993a30.031998 30.031998 0 0 0-29.999998-29.999998h-543.999957a30.031998 30.031998 0 0 0-29.999998 29.999998v783.999939a30.031998 30.031998 0 0 0 29.999998 29.999997h439.607966z m344.391973-179.999986a29.999998 29.999998 0 0 1 0 59.999996h-119.999991a29.999998 29.999998 0 0 1-29.999998-29.999998v-119.999991a29.999998 29.999998 0 0 1 59.999996 0V723.999943h89.999993z m-119.999991 239.999982c115.791991 0 209.999984-94.207993 209.999984-209.999984S869.791932 543.999958 753.999941 543.999958 543.999958 638.20795 543.999958 753.999941s94.207993 209.999984 209.999983 209.999984zM573.999955 119.999991a29.999998 29.999998 0 0 1 29.999998 29.999997v183.999986a29.999998 29.999998 0 0 1-29.999998 29.999998h-423.999967A29.999998 29.999998 0 0 1 119.999991 333.999974v-183.999986A29.999998 29.999998 0 0 1 149.999988 119.999991h423.999967zM543.999958 303.999976V179.999986H179.999986V303.999976H543.999958zM393.999969 423.999967a29.999998 29.999998 0 0 1 0 59.999995h-243.999981a29.999998 29.999998 0 0 1 0-59.999995h243.999981z m-183.999985 119.999991a29.999998 29.999998 0 0 1 0 59.999995h-59.999996a29.999998 29.999998 0 0 1 0-59.999995h59.999996z m0 119.99999a29.999998 29.999998 0 0 1 0 59.999995h-59.999996a29.999998 29.999998 0 0 1 0-59.999995h59.999996z m0 119.999991a29.999998 29.999998 0 0 1 0 59.999995h-59.999996a29.999998 29.999998 0 0 1 0-59.999995h59.999996z m183.999985-239.999981a29.999998 29.999998 0 0 1 0 59.999995h-63.999995a29.999998 29.999998 0 0 1 0-59.999995h63.999995z m0 119.99999a29.999998 29.999998 0 0 1 0 59.999995h-63.999995a29.999998 29.999998 0 0 1 0-59.999995h63.999995z m0 119.999991a29.999998 29.999998 0 0 1 0 59.999995h-63.999995a29.999998 29.999998 0 0 1 0-59.999995h63.999995z m179.999986-359.999972a29.999998 29.999998 0 0 1 0 59.999995h-59.999995a29.999998 29.999998 0 0 1 0-59.999995h59.999995z" fill="#20A0FF" p-id="31779"></path></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772431477669" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19654" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M1024 512C1024 229.230208 794.769792 0 512 0 229.230208 0 0 229.230208 0 512 0 794.769792 229.230208 1024 512 1024 629.410831 1024 740.826187 984.331046 830.768465 912.686662 841.557579 904.092491 843.33693 888.379234 834.742758 877.590121 826.148587 866.801009 810.43533 865.021658 799.646219 873.615827 718.470035 938.277495 618.001779 974.048781 512 974.048781 256.817504 974.048781 49.951219 767.182496 49.951219 512 49.951219 256.817504 256.817504 49.951219 512 49.951219 767.182496 49.951219 974.048781 256.817504 974.048781 512 974.048781 599.492834 949.714859 683.336764 904.470807 755.960693 897.177109 767.668243 900.755245 783.071797 912.462793 790.365493 924.170342 797.659191 939.573897 794.081058 946.867595 782.373508 997.013826 701.880796 1024 608.898379 1024 512ZM337.170731 499.512194C371.654852 499.512194 399.609756 471.557291 399.609756 437.073171 399.609756 402.58905 371.654852 374.634146 337.170731 374.634146 302.686611 374.634146 274.731708 402.58905 274.731708 437.073171 274.731708 471.557291 302.686611 499.512194 337.170731 499.512194ZM711.804879 499.512194C746.288998 499.512194 774.243902 471.557291 774.243902 437.073171 774.243902 402.58905 746.288998 374.634146 711.804879 374.634146 677.320757 374.634146 649.365854 402.58905 649.365854 437.073171 649.365854 471.557291 677.320757 499.512194 711.804879 499.512194ZM352.788105 768.770967C396.165222 803.472661 453.151987 824.195121 524.487806 824.195121 595.823622 824.195121 652.810387 803.472661 696.187505 768.770967 722.700531 747.560546 738.882517 725.984565 746.631548 710.486505 752.800254 698.149092 747.799529 683.146916 735.462114 676.978208 723.124702 670.809502 708.122526 675.810227 701.953818 688.147642 701.03616 689.982957 698.492224 694.172969 694.165854 699.941463 686.602473 710.025971 676.927317 720.210345 664.983226 729.765617 630.311565 757.502948 584.273939 774.243902 524.487806 774.243902 464.70167 774.243902 418.664045 757.502948 383.992384 729.765617 372.048292 720.210345 362.373137 710.025971 354.809756 699.941463 350.483386 694.172969 347.93945 689.982957 347.021792 688.147642 340.853084 675.810227 325.850908 670.809502 313.513495 676.978208 301.176081 683.146916 296.175356 698.149092 302.344062 710.486505 310.093092 725.984565 326.275078 747.560546 352.788105 768.770967Z" fill="#f4ea29" p-id="19655"></path></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772430030253" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7514" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M217.6 768l-25.472 92.586667a61.696 61.696 0 0 0 119.04 32.725333L345.6 768h170.666667l-25.472 92.586667a61.696 61.696 0 0 0 119.04 32.725333L644.266667 768h187.733333a64 64 0 0 0 0-128h-152.533333l70.4-256H874.666667a64 64 0 0 0 0-128h-89.6l25.472-92.586667a61.696 61.696 0 1 0-119.04-32.725333L657.066667 256h-170.666667l25.472-92.586667a61.696 61.696 0 1 0-119.04-32.725333L358.4 256H192a64 64 0 1 0 0 128h131.2l-70.4 256H149.333333a64 64 0 0 0 0 128h68.266667z m163.2-128l70.4-256h170.666667l-70.4 256h-170.666667z" fill="#000000" p-id="7515"></path></svg>

After

Width:  |  Height:  |  Size: 893 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772432585106" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="29440" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M222.976 910.208l-109.248-109.184 458.496-459.584 110.336 110.336m324.736-302.08L874.24 16.64a56.64 56.64 0 0 0-80.192 0L616.576 194.176 506.816 85.568 426.624 165.76 507.392 246.4 0 753.792V1024h270.208l507.328-507.392 80.768 80.768 80.192-80.192-109.184-109.184 177.472-177.472a56.832 56.832 0 0 0 0.512-80.768z" fill="#173E67" p-id="29441"></path></svg>

After

Width:  |  Height:  |  Size: 690 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772435612733" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="40056" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M735.78125 579.9374999999999c-141.46875 0-256.21875-114.65625-256.21875-256.03125S594.3125 67.78125 735.78125 67.78125 992 182.4375 992 323.8125 877.25 579.9374999999999 735.78125 579.9374999999999z m0-486.5625c-127.31250000000001 0-230.625 103.21875-230.625 230.4375 0 127.31250000000001 103.21875 230.4375 230.625 230.4375 127.31250000000001 0 230.625-103.21875 230.625-230.4375 0-127.21874999999999-103.21875-230.4375-230.625-230.4375z m85.78125 358.5v-46.59375h22.6875V242.34375H798.875v209.53125h-45.28125V195.84375h135.9375v256.03125h-67.96875zM594.875 195.84375h136.03125v46.59375h-90.65625v58.21875h90.65625v46.59375h-90.65625v58.21875h90.65625v46.59375H594.875V195.84375zM273.59375 388.3125c0-37.59375 30.375-68.25 67.78125-68.25s67.78125 30.656250000000004 67.78125 68.25c0 37.59375-30.375 68.25-67.78125 68.25s-67.78125-30.5625-67.78125-68.25zM493.71875 532.3125c15.28125 17.71875 32.53125 33.84375 51.375 47.8125L421.8125 756.1874999999999l-80.625-113.34375-81 111h-78.9375l159.84375-231.65625 80.4375 113.15625 72.1875-103.03125z m206.0625 108.84375l59.34375 112.59375h-71.53125l-69.46875-132.9375c26.15625 10.40625 53.53125 17.25 81.65625 20.34375zM117.21875 207.65625v623.625h711.09375V629.6249999999999c30.281249999999996-9.1875 58.875-22.6875 85.21875-40.3125v272.90625c0 29.8125-24.09375 54-53.71875 54H85.71875C56.09375 916.21875 32 891.9375 32 862.21875V176.71875C32 146.90625 56.09375 122.625 85.71875 122.625H487.625c-20.90625 25.6875-37.5 54.1875-49.5 84.9375H117.21875z" p-id="40057" fill="#1296db"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772431789265" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22192" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M1007.19 263.95c0 5.07-1.61 9.41-4.96 12.63L825.28 453.65c-3.34 3.34-7.55 4.95-12.63 4.95-4.7 0-8.91-1.73-12.38-5.2-3.46-3.47-5.2-7.68-5.2-12.38V334.9H653.41c-17.7 0-33.68 2.85-48.17 8.29-14.36 5.57-27.12 13.87-38.13 24.89-11.02 11.02-20.44 22.41-28.11 33.93-7.81 11.64-16.1 25.88-24.89 42.84-11.76 22.91-26.13 54.36-43.09 94.48-10.65 24.27-19.81 44.83-27.37 61.42-7.55 16.6-17.58 35.91-29.84 58.07-12.26 22.17-24.14 40.62-35.29 55.23-11.27 14.74-24.89 29.97-40.99 45.95-16.1 15.85-32.69 28.48-49.78 37.88-17.09 9.41-36.78 17.09-58.82 23.28-22.04 6.06-45.69 9.04-70.83 9.04H34.28c-5.08 0-9.41-1.61-12.63-4.95-3.34-3.22-4.95-7.43-4.95-12.51V706.63c0-5.08 1.61-9.41 4.95-12.63 3.34-3.34 7.55-4.95 12.63-4.95H158.1c17.71 0 33.68-2.85 48.17-8.3 14.37-5.57 27.12-13.86 38.14-24.89 11.02-11.02 20.43-22.41 28.11-33.93 7.8-11.64 16.1-25.88 24.89-42.84 11.77-22.78 26.13-54.36 43.09-94.48 10.65-24.27 19.81-44.83 27.37-61.42 7.56-16.59 17.58-35.91 29.84-58.07 12.38-22.05 24.15-40.62 35.29-55.23 11.26-14.74 24.89-29.96 40.99-45.94 16.1-15.85 32.69-28.48 49.78-37.89 17.09-9.41 36.77-17.09 58.82-23.28 22.04-6.07 45.69-9.04 70.82-9.04h141.54V87.61c0-5.08 1.61-9.41 4.95-12.63 3.34-3.34 7.55-4.95 12.63-4.95 4.46 0 8.79 1.86 13.25 5.58l176.45 175.96c3.34 2.96 4.96 7.3 4.96 12.38z m-622.48 53.61c-22.04 33.93-47.31 84.2-75.66 150.94-8.17-16.6-14.86-29.84-20.43-40.12-5.58-10.15-13-21.92-22.41-35.17s-18.82-23.77-28.11-31.33c-9.41-7.55-21.05-13.99-34.92-19.32-13.87-5.32-28.85-8.05-45.07-8.05H34.4c-5.08 0-9.41-1.61-12.63-4.95-3.34-3.34-4.96-7.55-4.96-12.63V210.82c0-5.08 1.61-9.41 4.96-12.63 3.34-3.34 7.55-4.95 12.63-4.95h123.83c91.88-0.12 167.53 41.36 226.48 124.32z m622.48 441.69c0 5.07-1.61 9.41-4.96 12.63L825.28 948.95c-3.34 3.34-7.55 4.95-12.63 4.95-4.7 0-8.91-1.73-12.38-5.2-3.46-3.47-5.2-7.68-5.2-12.38V830.2c-11.76 0-27.49 0.12-46.93 0.25-19.56 0.25-34.42 0.37-44.82 0.62-10.41 0.25-23.78 0-40.37-0.62-16.59-0.62-29.6-1.49-39.25-2.85-9.53-1.24-21.42-3.22-35.29-5.82-13.99-2.6-25.63-5.94-34.92-10.28-9.17-4.21-19.93-9.53-32.08-15.85-12.26-6.32-23.03-13.62-32.69-22.04a548.445 548.445 0 0 1-30.46-29.47c-10.65-11.27-21.05-24.02-30.96-38.39 21.79-34.3 46.8-84.58 75.29-150.94 8.17 16.6 14.86 29.97 20.43 40.12 5.57 10.15 13 21.92 22.41 35.17s18.82 23.77 28.11 31.33c9.41 7.56 21.05 14.12 34.92 19.32 13.87 5.33 28.85 8.05 45.07 8.05h141.53V582.67c0-5.07 1.61-9.41 4.95-12.63 3.34-3.34 7.56-4.95 12.63-4.95 4.46 0 8.8 1.86 13.25 5.57l176.33 176.33c3.35 2.85 4.97 7.18 4.97 12.26z m0 0" p-id="22193" fill="#13227a"></path></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772435561727" class="icon" viewBox="0 0 1026 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="36421" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.390625" height="200"><path d="M47.104 258.048h501.76c26.624 0 47.104-20.48 47.104-47.104s-20.48-47.104-47.104-47.104h-501.76C20.48 162.816 0 184.32 0 209.92s20.48 48.128 47.104 48.128z m0 239.616h501.76c26.624 0 47.104-20.48 47.104-47.104s-20.48-47.104-47.104-47.104h-501.76C20.48 403.456 0 423.936 0 450.56s20.48 47.104 47.104 47.104z m240.64 131.072H47.104C20.48 628.736 0 649.216 0 675.84c0 26.624 20.48 47.104 47.104 47.104h242.688c26.624 0 47.104-20.48 47.104-47.104-2.048-26.624-22.528-47.104-49.152-47.104z m588.8-384c-33.792-17.408-80.896-44.032-98.304-80.896 0-18.432-17.408-35.84-35.84-35.84S706.56 145.408 706.56 163.84v468.992c-37.888-18.432-106.496-13.312-160.768 17.408-82.944 45.056-118.784 132.096-80.896 193.536 35.84 60.416 134.144 71.68 216.064 26.624 58.368-31.744 96.256-87.04 98.304-138.24V325.632c118.784 0 189.44 47.104 164.864 164.864-6.144 20.48-11.264 44.032-20.48 62.464-4.096 7.168-4.096 15.36 2.048 20.48 6.144 6.144 17.408 6.144 26.624-4.096 45.056-44.032 71.68-102.4 73.728-162.816-2.048-80.896-81.92-130.048-149.504-161.792z" fill="#CC66FF" p-id="36422"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772432655407" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="30609" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 0c282.7776 0 512 229.2224 512 512s-229.2224 512-512 512S0 794.7776 0 512 229.2224 0 512 0z m0 837.8368a46.592 46.592 0 0 0-46.2336 41.1136l-0.3072 5.4272v46.5408a46.592 46.592 0 0 0 92.7744 5.4272l0.3072-5.4272v-46.592a46.592 46.592 0 0 0-46.5408-46.4896z m418.9184-372.3776h-46.592a46.592 46.592 0 0 0-5.376 92.7744l5.4272 0.3072h46.5408a46.592 46.592 0 0 0 0-93.0816z m-791.296 0h-46.5408a46.592 46.592 0 0 0-5.4272 92.7744l5.4272 0.3072h46.592a46.592 46.592 0 0 0 0-93.0816zM512 139.6224a46.592 46.592 0 0 0-46.592 46.592V512c0 25.7024 20.8896 46.592 46.592 46.592h232.704a46.592 46.592 0 1 0 0-93.184H558.592V186.1632A46.592 46.592 0 0 0 512 139.6224z" fill="#d81e06" p-id="30610"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772432906138" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="33999" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M617.890909 258.327273c56.180364 0 103.726545 47.546182 103.726546 103.749818v216.087273c0 17.291636-12.962909 30.254545-30.254546 30.254545s-30.254545-12.986182-30.254545-25.949091v-185.832727c0-8.634182-8.657455-17.291636-17.291637-17.291636-12.962909 0-17.291636 8.657455-17.291636 17.291636v514.327273c0 25.925818-21.597091 43.217455-43.217455 43.217454-21.597091 0-43.217455-21.620364-43.217454-43.217454V595.456c0-8.634182-8.657455-17.291636-17.291637-17.291636h-21.620363c-8.634182 0-17.268364 8.657455-17.268364 17.291636v315.508364c0 25.925818-21.620364 43.217455-43.217454 43.217454-25.949091 0-43.240727-21.620364-43.240728-43.217454 4.328727 0 4.328727-513.000727 4.328728-514.327273 0-8.634182-4.328727-17.291636-17.268364-17.291636-12.986182 0-17.314909 8.657455-17.314909 17.291636v181.527273c0 17.291636-12.962909 25.925818-30.254546 25.925818h-4.305454a29.556364 29.556364 0 0 1-30.254546-30.254546v-216.110545c0-56.180364 47.522909-99.397818 99.374546-99.397818h216.133818zM67.653818 493.847273h185.716364v36.305454h-185.716364V581.818182L0 512l67.653818-69.818182v51.665455zM1024 512l-67.653818 69.818182v-51.665455h-185.693091v-36.305454h185.693091V442.181818L1024 512zM522.798545 46.545455c51.851636 0 95.092364 43.217455 95.092364 95.092363 0 51.851636-43.240727 99.397818-95.092364 99.397818-51.874909 0-99.421091-43.217455-99.42109-95.092363 0-51.851636 43.240727-99.374545 99.42109-99.397818z" fill="#1296db" p-id="34000"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772431887131" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="23324" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M431.7 323s13.9 29 21 62.2c15.1 71.2-3.7 100.5-26.9 99.1-24-1.4-37.1-37.8-37.1-37.8S377 404.2 374.2 390c-2.6-12.9-7.2-52.1-7.2-52.1s5.6-14.8 4.9-28.8c-0.6-11.9-3.2-29.1-3.2-29.1s19.4-8.1 26.6-10.4c30-9.8 58.8-24.8 70-38.6 10.1-12.3 20.9-39.9 20.9-39.9s7.1 11.1 9 15.6c2.3 5.5 8 24.3 8 24.3s55.6 35.1 79.7 57c46.7 42.4 40.6 73 23.9 87.3-20.6 17.6-49.2 2.4-78.7-16-22.4-14-54.6-51.7-54.6-51.7s-23.8-33.2-38.8-27.6c-18.1 6.9-3 43-3 43zM285.2 341.2s-19.3 17.6-40.3-7.8c-12-14.5-11.1-31.2-8-41.2 5.4-17.5 23-47.8 52.3-51.2 13.2-1.5 18.1 9.5 18.1 9.5s-18.4 19-24.1 35.3c-6.1 17.8 2 55.4 2 55.4z" fill="#d81e06" p-id="23325"></path><path d="M292.5 338c3.9 11.7 37 24.9 62.1 4.4 20.5-16.7 8.1-59.8 8.1-59.8s-16.9 25.7-41.1 13.2c-15.7-8.1-9.6-41.3-9.6-41.3s-10.9 4.2-21.3 34.1c-8.6 25.1-1 41 1.8 49.4z" fill="#d81e06" p-id="23326"></path><path d="M317.5 258.8s-3 31.5 10.7 32.9c16 1.7 32.5-15.1 32.5-15.1s4.2-3.1 20.9-10.8c24.8-11.4 57.2-13.1 79-40.2 16.3-20.3 18.2-45.1 18.2-45.1s-21.9-32.9-30.9-41.3c-9.9-9.4-44.6-29.9-44.6-29.9s-22.8 2.9-30 4.2c-8.3 1.5-32 7.2-32 7.2s-4.5 0-5.3-4.3c-0.8-4.4 5.7-5.9 5.7-5.9s28-5 37.4-6.2c5.7-0.8 22.9-1.8 22.9-1.8s4.4-25.9-42.4-38.5c-39-10.6-78.9 21.4-78.9 21.4s-26 20.3-37.2 41.4c-14.3 26.9 3.6 47.6 3.6 47.6s13.8-15.4 19.3-19.9c6.2-5.1 26.5-19.3 26.5-19.3s3.8-1.1 4-0.7c1.7 3.4-3.5 7.7-3.5 7.7s-14.6 10.4-18.5 13.8c-7.1 6-22.4 15.3-25.8 23.3-0.2 0.5 0.2 1.7 0 2.2-3.1 9.5-12.1 12.2-12 38 0 11.5 6.9 47.8 6.9 47.8s23.3-31.1 40.5-33.4c25.6-3.4 25.3 12 26.6 11 1.5-1.1 14-22.7 29.2-29.5 15.3-6.8 24.4-5.8 24.4-5.8s-18.3-14.4-24-21.2c-4.6-5.5-9.2-22.7-9.2-22.7s0.8-3.9 5.2-4.3c4.2-0.4 5.9 3.8 5.9 3.8s-0.6 16 9.3 25.3c9.4 8.8 32.1 15.7 33.7 19.1 1.8 3.8-40.5 8.4-49 15-15.2 12.2-19.1 34.2-19.1 34.2z" fill="#d81e06" p-id="23327"></path><path d="M342.7 863.7c31.5-15.2 111.2-9.5 97.7-55.6-8.1-27.7-92.1 1.8-95.7-5.3-6.4-12.6 59.2-36.4 79.4-47.7 37.1-20.7 39.1-45.1 32.7-58.2C431 644.3 300.6 754.2 296 751c-16.1-11.3 152.1-109.3 92.6-157.9-43.2-35.4-129.4 117.3-141.4 110.7-17.2-9.4 78.4-145.2 17.2-169.8-49-19.7-97 188.8-102.9 180.8-15.2-20.7-53.5-79.3-92.8-47.5-21 17.2 23.3 50.7 28.6 68.4 3.3 10.9-2 36.5-1.4 45.9 0.7 11.4 0.2 28.8 3.2 37.7 0.2 0.4 6 14.2 15.5 26.3 9.6 12.2 18.8 18.1 18.2 20.1-0.4 1.4-12.2-8.6-25.8-23.5-12.3-13.5-9.1-21.1-13.8-20.5-4 0.5-34.6 37.9 29.8 94.5 51.7 45.4 101.4 9.4 94.7 7-0.6-0.2-7.4-0.9-14.3-4.7-11.2-6.3-26.4-14.1-26.2-14 26.8 10.9 58.3 23.5 94.5 5.7 21.3-10.4 49.5-36.2 71-46.5z" fill="#d81e06" p-id="23328"></path><path d="M713.2 867.6s26.5-12.6 37.2-29.9c12.6-20.5-0.2-47.4-0.2-47.4s16.4-10.7 21.6-23.4c6.6-16.3 0.4-34 0.4-34s4.2 0.4 10.2-1.5c5.7-1.8 9.9-5.3 9.9-5.3s10.1 23.2 14.7 28.2c9.1 9.8 23.2 10.7 24.6 8.5 4.2-6.8-13.6-6.1-23.6-22.9-3.8-6.4-7.2-12.3-5.6-16.3 2.6-6.5 25.6-20.2 31.7-27.8 9.4-11.9 13.5-18.7 15.2-27.8 0.3-1.8 0.1-5.8-5.1-5.3-1 0.1-3 3-3 3s-13.2 24-22.8 34.6c-13.7 15.2-37.3 22.4-37.3 22.4s-12.7 4.6-26.7-7.8c-10.4-9.2-13.9-22-14.2-30.1-0.6-13.9 7.2-37.7 12.9-51.2 6.2-14.7 22.5-40.2 32.3-56.9 4.1-6.9 23.4-9.5 31.4-9 26.1 1.6 63.1 33.4 63.1 33.4s38.9 26.6 48.3 89.5c7.2 48.3-20.8 81.2-33 102.2-0.8 1.5-0.1 4 1.8 4.4 3.7 0.7 13.3-10.7 23.5-25.8 13.9-20.7 14.4-53.2 14.4-53.2s59.2 47.9-6.7 129.1c-48.5 59.8-94.9 32.3-94.9 32.3s13.8-16.4 18.3-22.5c6.7-9.2 10.5-16.7 10.1-23-0.1-1-1.3-5.2-2.3-3.3-2.2 4-7.7 13.7-7.7 13.7s-7 10.7-9.2 14c-5.9 9-15.1 16.3-15.1 16.3S807.3 889.2 773 890c-37.6 0.7-59.8-22.4-59.8-22.4z" fill="#d81e06" p-id="23329"></path><path d="M621.6 773.7s-12.8 8-17.3 17.1c-2.9 5.7-3.7 20.2-0.8 26 5.3 10.7 26.7 23.9 37.8 29.3 13.6 6.6 47.3 18.5 63 16.2 11.6-1.7 24-10.1 35.6-23.8 12.8-15.1 5.7-45.2 5.7-45.2s-21.7 14.4-57.2 5.1c-22.8-5.9-66.8-24.7-66.8-24.7z" fill="#d81e06" p-id="23330"></path><path d="M625 689.7s-17 12.4-22.8 28.7c-6.3 17.9 13.6 36.6 13.6 36.6s18.8 16.7 49.2 29c26.7 10.8 58.4 8.5 58.4 8.5s15.9 0.3 32.7-17c12.3-12.7 5.6-34.7 5.6-34.7s-26.9 5.6-68.9-13.6c-17.7-8-67.8-37.5-67.8-37.5z" fill="#d81e06" p-id="23331"></path><path d="M649.6 612.6c-6.8-0.6-22.8 3.9-31.1 22.8-8.7 19.7 6.1 37.6 6.1 37.6s21.7 21.4 57.8 42c50.1 28.6 84.9 18.8 84.9 18.8s-25.9-12-34.2-35.8c-7.7-22.1 3.8-45.9 3.8-45.9s-22.2-15.9-30.8-19.9c-13.3-5.8-41.6-18.4-56.5-19.6zM740.9 645.5s11.1-28.1 15.4-37c3.9-8 18.4-32.9 18.4-32.9s-39.8-28.9-66.1-27.9c-15.3 0.6-29.6 6-37 12.4-5.3 4.5-10.5 12.9-14.9 23.7-2.1 5.1-3.3 21.7-3.3 21.7s35.8 11.5 46.1 16.2c11.5 5.2 41.4 23.8 41.4 23.8z" fill="#d81e06" p-id="23332"></path></svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772430169211" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8596" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M858.938 121.599H653.556C632.92 64.605 578.877 23.333 515 23.333c-63.875 0-117.92 41.272-138.556 98.266H171.065c-54.046 0-98.266 44.22-98.266 98.266v687.872c0 54.043 44.22 98.263 98.266 98.263h687.872c54.043 0 98.263-44.22 98.263-98.263V219.865c0-54.046-44.22-98.266-98.262-98.266z m-343.938 0c27.027 0 49.138 22.11 49.138 49.134 0 27.021-22.11 49.131-49.138 49.131-27.024 0-49.134-22.11-49.134-49.131 0-27.024 22.11-49.134 49.134-49.134z m98.263 687.864H269.334V711.2h343.929v98.263z m147.4-196.527H269.334v-98.274h491.329v98.274z m0-196.537H269.334v-98.266h491.329v98.266z" p-id="8597" fill="#13227a"></path></svg>

After

Width:  |  Height:  |  Size: 949 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772430285877" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11595" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M879 113c17.673 0 32 14.327 32 32v734c0 17.673-14.327 32-32 32H145c-17.673 0-32-14.327-32-32V145c0-17.673 14.327-32 32-32h734z m-40 72H185v654h654V185zM651 644v56H287v-56h364z m123.699-256v152h-124.14v61.278l-89-89 89-89V484h68.139v-96h56zM527 484v56H287v-56h240z m124-168v56H287v-56h364z" fill="#d81e06" p-id="11596"></path></svg>

After

Width:  |  Height:  |  Size: 665 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772429959597" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6511" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M96 228.064c0-70.72 93.12-124.128 208-124.128S512 157.376 512 228.064v181.44c0 70.688-93.12 123.136-208 123.136S96 480.192 96 409.504v-181.44z m416 384.96c0-70.656 93.12-124.096 208-124.096s208 53.44 208 124.128v177.856c0 70.72-93.12 123.136-208 123.136S512 861.6 512 790.912v-177.856z" fill="#FFFFFF" p-id="6512"></path><path d="M240 700.352v122.24C277.888 871.552 306.144 896 320 896v32c-28.416 0-62.912-30.176-108.736-90.304l-3.264-4.288v-133.056L151.104 752l-17.6-16L224 653.824 314.496 736l-17.6 16L240 700.352z m544-376.704v-122.24C746.112 152.448 717.856 128 704 128V96c28.416 0 62.912 30.176 108.736 90.304l3.264 4.288v133.056L872.896 272l17.6 16L800 370.176 709.504 288l17.6-16 56.896 51.648z" fill="#27A2DF" p-id="6513"></path><path d="M480 344.096V279.968C443.168 313.632 378.112 336 304 336S164.832 313.632 128 280v64.096c18.624 32.512 90.336 62.816 176 62.816s157.376-30.304 176-62.816z m-176 197.632c-114.88 0-208-53.696-208-119.936V216C96 149.728 189.12 96 304 96S512 149.728 512 216v205.76c0 66.24-93.12 119.968-208 119.968z m176-152.864c-37.248 30.144-103.68 49.984-176 49.984s-138.752-19.84-176-49.984v45.28c13.504 37.024 85.504 73.28 176 73.28s162.496-36.256 176-73.28v-45.28zM304 301.76c100.576 0 176-44.864 176-85.728s-75.424-85.728-176-85.728S128 175.136 128 216s75.424 85.728 176 85.728zM896 728.096v-64.128c-36.832 33.664-101.888 56.032-176 56.032s-139.168-22.368-176-56v64.096c18.624 32.512 90.336 62.816 176 62.816s157.376-30.304 176-62.816z m-176 197.632c-114.88 0-208-53.696-208-119.936V600c0-66.272 93.12-120 208-120s208 53.728 208 120v205.76c0 66.24-93.12 119.968-208 119.968z m176-152.864c-37.248 30.144-103.68 49.984-176 49.984s-138.752-19.84-176-49.984v45.28c13.504 37.024 85.504 73.28 176 73.28s162.496-36.256 176-73.28v-45.28z m-176-87.136c100.576 0 176-44.864 176-85.728s-75.424-85.728-176-85.728S544 559.136 544 600s75.424 85.728 176 85.728z" fill="#5D6D7E" p-id="6514"></path></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772430510391" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13643" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M414.427429 445.878857h81.334857L344.795429 39.789714H241.810286l-152.137143 406.089143h78.994286l38.619428-107.666286h169.691429l37.449143 107.666286z m-184.32-172.032l59.099428-165.595428h7.021714l57.929143 165.595428H230.107429z m645.266285 673.645714h81.334857l-150.966857-406.089142H702.756571l-152.137142 406.089142h78.994285l38.619429-107.666285h169.691428l37.449143 107.666285z m-184.32-172.032l59.099429-165.595428h7.021714l57.929143 165.595428H691.053714z" p-id="13644" fill="#d4237a"></path><path d="M599.771429 118.272c119.222857 5.705143 218.185143 72.045714 244.955428 193.389714l-68.754286 3.657143 119.515429 159.012572L997.668571 303.542857l-76.507428 4.022857C892.342857 143.798857 761.782857 43.227429 603.428571 36.278857c-21.942857-0.658286-41.179429 19.090286-39.936 42.934857 2.56 20.406857 17.554286 38.253714 36.425143 39.058286zM439.588571 900.096c-133.339429-0.292571-242.249143-79.579429-259.657142-225.060571l73.142857-6.436572-124.854857-154.770286-96.182858 174.08 73.142858-6.363428c19.529143 187.977143 162.523429 298.496 333.824 300.617143 21.942857-0.219429 40.521143-20.626286 38.473142-44.397715a40.740571 40.740571 0 0 0-37.888-37.668571z" p-id="13645" fill="#d4237a"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772430578915" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18392" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M73.138103 0a36.569052 36.569052 0 0 0-36.569051 36.569052v219.414309A36.569052 36.569052 0 0 0 73.138103 292.552413h219.41431a36.569052 36.569052 0 0 0 36.569051-36.569052v-54.853577h365.690516v50.465291L251.595075 694.81198H73.138103a36.569052 36.569052 0 0 0-36.569051 36.569052v219.414309a36.569052 36.569052 0 0 0 36.569051 36.569052h219.41431a36.569052 36.569052 0 0 0 36.569051-36.569052v-54.853577h438.828619v91.422629a36.569052 36.569052 0 0 0 62.45994 25.890888l146.276207-146.276206a36.569052 36.569052 0 0 0 0-51.781777l-146.276207-146.276206a36.569052 36.569052 0 0 0-62.45994 25.890888v91.422629h-438.828619v-13.89624L808.907421 292.552413H950.795341a36.569052 36.569052 0 0 0 36.569052-36.569052v-219.414309A36.569052 36.569052 0 0 0 950.795341 0h-219.414309a36.569052 36.569052 0 0 0-36.569052 36.569052v54.853577h-365.690516V36.569052A36.569052 36.569052 0 0 0 292.552413 0H73.138103z" fill="#000000" p-id="18393"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772431978344" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="24906" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M539 374l-27-131.6-25 131.2c-27.2 4.9-53.4 17.9-74.4 38.9-54.9 54.9-54.9 143.9 0 198.8 54.9 54.9 143.9 54.9 198.8 0 54.9-54.9 54.9-143.9 0-198.8C590.8 392 565.5 379.2 539 374z" p-id="24907" fill="#ea986c"></path><path d="M512 0C229.2 0 0 229.2 0 512s229.2 512 512 512 512-229.2 512-512S794.8 0 512 0zM828.8 828.8 629.5 629.4c-21.4 21.3-47.2 35.7-74.4 43l73 272.5c-37.6 10.1-76.4 15.1-116 15.1s-78.4-5.1-116-15.1l73-272.5c-27.2-7.2-53-21.6-74.4-43L195.2 828.8c0 0 0 0 0 0-41.2-41.2-73.5-89.1-96-142.4-8.1-19.1-14.8-38.6-20.1-58.4L351.5 555c-7.5-28.2-7.5-57.9 0-86L79.1 396c5.3-19.8 12-39.3 20.1-58.4 22.6-53.3 54.9-101.3 96-142.4 0 0 0 0 0 0l199.3 199.3c21.4-21.3 47.2-35.7 74.4-43L396 79.1C433.6 69.1 472.4 64 512 64s78.4 5.1 116 15.1l-73 272.4c27.2 7.3 53 21.6 74.4 43l199.3-199.4c0 0 0 0 0 0 41.2 41.2 73.5 89.1 96 142.4 8.1 19.1 14.8 38.6 20.1 58.4l-272.5 73c7.5 28.1 7.5 57.8 0 86l272.5 73c-5.3 19.8-12 39.3-20.1 58.4C902.3 739.7 869.9 787.6 828.8 828.8 828.8 828.8 828.8 828.8 828.8 828.8z" p-id="24908" fill="#ea986c"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772432300485" class="icon" viewBox="0 0 1562 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="28422" xmlns:xlink="http://www.w3.org/1999/xlink" width="305.078125" height="200"><path d="M41.714526 700.631579H150.366316c105.256421 0 178.014316-56.751158 178.014316-182.37979S255.622737 339.752421 145.515789 339.752421H41.714526V700.631579z m86.824421-69.847579V408.629895h11.641264c58.206316 0 99.435789 23.767579 99.435789 109.621894 0 85.854316-41.229474 112.532211-99.435789 112.532211h-11.641264z m379.796211 76.638316c69.847579 0 134.844632-53.355789 134.844631-144.545684 0-91.674947-64.997053-145.030737-134.844631-145.030737-70.332632 0-135.329684 53.355789-135.329684 145.030737 0 91.189895 64.997053 144.545684 135.329684 144.545684z m0-69.847579c-32.498526 0-47.050105-29.103158-47.050105-74.698105 0-46.08 14.551579-75.183158 47.050105-75.183158 32.013474 0 46.565053 29.103158 46.565053 75.183158 0 45.594947-14.551579 74.698105-46.565053 74.698105z m267.749053 69.847579c18.917053 0 33.468632-3.395368 42.684631-7.27579l-10.186105-63.541894c-4.850526 0.970105-6.790737 0.970105-9.701053 0.970105-6.790737 0-15.521684-5.335579-15.521684-23.282526V313.074526h-85.854316v297.822316c0 57.721263 19.402105 96.525474 78.578527 96.525474z m202.266947 0c69.847579 0 134.844632-53.355789 134.844631-144.545684 0-91.674947-64.997053-145.030737-134.844631-145.030737-70.332632 0-135.329684 53.355789-135.329684 145.030737 0 91.189895 64.997053 144.545684 135.329684 144.545684z m0-69.847579c-32.498526 0-47.050105-29.103158-47.050105-74.698105 0-46.08 14.551579-75.183158 47.050105-75.183158 32.013474 0 46.565053 29.103158 46.565053 75.183158 0 45.594947-14.551579 74.698105-46.565053 74.698105z m189.170526 63.056842h85.854316v-157.642105c14.551579-36.378947 39.289263-49.475368 59.661474-49.475369 12.126316 0 20.372211 1.940211 30.073263 4.365474l14.551579-74.213053c-7.760842-3.395368-16.976842-5.820632-33.468632-5.820631-27.648 0-58.206316 17.946947-78.578526 54.810947h-1.940211l-5.820631-48.02021h-70.332632V700.631579z" fill="#5182E4" p-id="28423"></path><path d="M243.388632 269.473684h156.186947v-48.505263H301.271579v-51.09221h80.518737v-48.505264H301.271579v-44.624842h94.746947V28.887579H243.388632V269.473684z m260.958315 4.527158c18.755368 0 32.013474-3.880421 41.714527-6.790737l-9.054316-41.391158c-4.527158 1.293474-10.994526 3.233684-16.815158 3.233685-13.581474 0-23.282526-8.084211-23.282526-28.456421V130.425263h43.331368v-44.94821H496.909474v-48.505264h-47.858527l-6.467368 48.505264-28.133053 2.263579v42.684631h24.899369v70.817684c0 43.654737 18.755368 72.757895 64.997052 72.757895z m144.222316 0c46.565053 0 89.896421-35.570526 89.896421-96.363789 0-61.116632-43.331368-96.687158-89.896421-96.687158-46.888421 0-90.219789 35.570526-90.219789 96.687158 0 60.793263 43.331368 96.363789 90.219789 96.363789z m0-46.565053c-21.665684 0-31.366737-19.402105-31.366737-49.798736 0-30.72 9.701053-50.122105 31.366737-50.122106 21.342316 0 31.043368 19.402105 31.043369 50.122106 0 30.396632-9.701053 49.798737-31.043369 49.798736z m209.219369 46.565053c21.665684 0 46.888421-7.437474 65.967157-21.018947l-19.078736-34.600421a74.913684 74.913684 0 0 1-38.804211 11.641263c-23.282526 0-40.744421-10.347789-45.918316-35.570526h108.005053c1.832421-8.084211 2.694737-16.330105 2.586947-24.576 0-50.445474-25.869474-88.926316-78.901894-88.926316-44.624842 0-87.309474 36.864-87.309474 96.687158 0 60.793263 40.744421 96.363789 93.453474 96.363789z m-38.804211-117.059368c3.880421-21.989053 17.785263-32.013474 33.630316-32.013474 21.018947 0 29.103158 13.581474 29.103158 32.013474h-62.733474z" fill="#5182E4" fill-opacity=".3" p-id="28424"></path><path d="M914.324211 973.877895c54.703158 0 91.890526-39.343158 91.890526-105.094737 0-65.482105-37.187368-102.938947-91.890526-102.938947-54.703158 0-91.890526 37.187368-91.890527 102.938947s37.187368 105.094737 91.890527 105.094737z m0-41.498948c-26.408421 0-42.576842-24.522105-42.576843-63.595789 0-38.804211 16.168421-61.709474 42.576843-61.709474 26.408421 0 42.846316 22.905263 42.846315 61.709474 0 39.073684-16.437895 63.595789-42.846315 63.595789z m180.277894 41.498948c14.551579 0 29.372632-7.814737 40.151579-18.593684h1.077895l3.503158 14.821052h39.073684v-215.309474h-48.235789v51.2l1.886315 22.905264c-9.970526-9.701053-19.941053-15.898947-36.917894-15.898948-31.797895 0-63.326316 30.181053-63.326316 80.572632 0 49.852632 24.522105 80.303158 62.787368 80.303158z m12.665263-39.343158c-16.437895 0-26.138947-12.395789-26.138947-41.768421 0-28.294737 12.126316-40.690526 25.330526-40.690527a33.953684 33.953684 0 0 1 23.713685 9.162106v60.362105c-6.467368 9.970526-13.743158 12.934737-22.905264 12.934737z m109.67579 35.570526h47.696842v-153.330526h-47.696842V970.105263z m23.983158-174.618947c15.898947 0 27.216842-10.24 27.216842-24.522105 0-14.551579-11.317895-24.791579-27.216842-24.791579-16.168421 0-27.486316 10.24-27.486316 24.791579 0 14.282105 11.317895 24.522105 27.486316 24.522105z m129.077895 178.391579c38.804211 0 74.913684-29.642105 74.913684-80.303158 0-50.930526-36.109474-80.572632-74.913684-80.572632-39.073684 0-75.183158 29.642105-75.183158 80.572632 0 50.661053 36.109474 80.303158 75.183158 80.303158z m0-38.804211c-18.054737 0-26.138947-16.168421-26.138948-41.498947 0-25.6 8.084211-41.768421 26.138948-41.768421 17.785263 0 25.869474 16.168421 25.869473 41.768421 0 25.330526-8.084211 41.498947-25.869473 41.498947z" fill="#5182E4" fill-opacity=".504" p-id="28425"></path><path d="M238.753684 970.105263h45.810527v-66.56c0-24.252632-4.042105-51.738947-6.197895-74.105263h1.347368l19.671579 43.924211 49.583158 96.741052h49.044211v-200.488421h-45.810527v66.290526c0 23.983158 3.772632 52.816842 6.467369 74.105264H357.322105l-19.671579-44.463158-49.852631-95.932632h-49.044211V970.105263z m268.665263 3.772632c38.804211 0 74.913684-29.642105 74.913685-80.303158 0-50.930526-36.109474-80.572632-74.913685-80.572632-39.073684 0-75.183158 29.642105-75.183158 80.572632 0 50.661053 36.109474 80.303158 75.183158 80.303158z m0-38.804211c-18.054737 0-26.138947-16.168421-26.138947-41.498947 0-25.6 8.084211-41.768421 26.138947-41.768421 17.785263 0 25.869474 16.168421 25.869474 41.768421 0 25.330526-8.084211 41.498947-25.869474 41.498947z m105.094737 35.031579h47.696842v-102.4c9.431579-8.892632 15.898947-13.743158 26.677895-13.743158 12.126316 0 17.785263 6.197895 17.785263 27.216842V970.105263h47.966316v-94.854737c0-38.265263-14.282105-62.248421-47.696842-62.248421-20.749474 0-36.378947 10.509474-49.044211 22.905263h-1.077894l-3.233685-19.132631h-39.073684V970.105263zM1047.174737 269.473684h45.810526V202.913684c0-24.252632-4.042105-51.738947-6.197895-74.105263h1.347369l19.671579 43.924211 49.583158 96.741052h49.04421V68.985263h-45.810526v66.290526c0 23.983158 3.772632 52.816842 6.467368 74.105264h-1.347368l-19.671579-44.463158-49.852632-95.932632h-49.04421V269.473684z m202.105263 0h47.696842V116.143158h-47.696842V269.473684z m23.983158-174.618947c15.898947 0 27.216842-10.24 27.216842-24.522105 0-14.551579-11.317895-24.791579-27.216842-24.791579-16.168421 0-27.486316 10.24-27.486316 24.791579 0 14.282105 11.317895 24.522105 27.486316 24.522105z m108.328421 178.391579c41.768421 0 63.865263-21.827368 63.865263-50.122105 0-27.755789-21.288421-38.804211-40.421053-46.08-15.898947-5.658947-29.911579-9.162105-29.911578-18.324211 0-7.275789 5.389474-11.048421 16.168421-11.048421 10.24 0 21.018947 5.389474 31.797894 12.934737l21.557895-28.564211c-13.473684-9.970526-30.72-19.671579-54.972632-19.671579-35.570526 0-59.284211 19.671579-59.28421 48.774737 0 25.330526 21.557895 37.726316 39.882105 45.002105 15.629474 6.197895 30.450526 10.509474 30.450527 19.671579 0 7.545263-5.389474 12.126316-18.054737 12.126316-12.126316 0-24.791579-5.389474-38.804211-16.168421l-21.288421 29.911579c15.36 12.934737 39.073684 21.557895 59.014737 21.557895z" fill="#5182E4" p-id="28426"></path><path d="M1482.428632 636.928l-13.581474-3.449263c-15.952842-4.096-34.708211-8.407579-51.523369-12.288v-0.862316c16.384-4.311579 35.570526-8.407579 51.523369-12.719158l13.581474-3.449263v32.768z m66.613894-49.367579v-40.528842l-160.390737 49.798737v46.133895l160.390737 49.798736v-39.235368l-36.864-9.269895v-47.427368l36.864-9.269895z m0-54.325895v-38.157473h-70.063158c-16.168421-6.467368-21.989053-17.461895-21.989052-26.516211 0-5.389474 0.862316-9.054316 1.94021-13.365895l-32.983579-6.467368a34.654316 34.654316 0 0 0-2.586947 14.874947c0 12.288 7.976421 25.869474 24.360421 34.92379v0.862316l-21.342316 2.586947v31.258947h122.664421z m3.018106-151.552c0-12.934737-3.880421-28.456421-14.44379-40.313263l-25.222737 14.659369c4.958316 6.251789 8.623158 13.581474 8.623158 21.342315 0 15.090526-12.934737 26.085053-33.199158 26.085053-20.48 0-33.414737-10.563368-33.414737-27.378526 0-5.389474 1.940211-10.132211 6.682948-15.737263l-24.576-17.893053a53.086316 53.086316 0 0 0-13.150316 35.786105c0 34.277053 23.713684 64.458105 64.458105 64.458105 40.528842 0 64.242526-26.516211 64.242527-61.008842z" fill="#5182E4" fill-opacity=".504" p-id="28427"></path></svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772431544365" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21079" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M68.557 512.41a450.816 450.816 0 1 0 901.632 0 450.816 450.816 0 1 0-901.632 0z" fill="#E9ECFF" p-id="21080"></path><path d="M748.34 771.686H322.866c-40.653 0-73.728-33.075-73.728-73.728V273.357c0-13.466 10.906-24.371 24.371-24.371s24.372 10.905 24.372 24.37v424.551c0 13.773 11.212 24.986 24.985 24.986H748.34c13.466 0 24.371 10.905 24.371 24.371a24.371 24.371 0 0 1-24.37 24.422z" fill="#6C6CEA" p-id="21081"></path><path d="M433.766 693.3h-77.004a16.691 16.691 0 0 1-16.692-16.692V483.021c0-9.216 7.476-16.691 16.692-16.691h77.056c9.216 0 16.69 7.475 16.69 16.69v193.588a16.773 16.773 0 0 1-16.742 16.691z m154.42 0H511.13a16.691 16.691 0 0 1-16.692-16.692V276.122c0-9.216 7.476-16.692 16.692-16.692h77.056c9.216 0 16.69 7.476 16.69 16.692v400.486a16.727 16.727 0 0 1-16.69 16.691z" fill="#757BF2" p-id="21082"></path><path d="M742.554 693.3h-77.056a16.691 16.691 0 0 1-16.692-16.692V330.445c0-9.216 7.476-16.691 16.692-16.691h77.056c9.216 0 16.69 7.475 16.69 16.69v346.164c0 9.216-7.474 16.691-16.69 16.691z" fill="#6C6CEA" p-id="21083"></path><path d="M634.214 722.893H322.918c-13.772 0-24.985-11.213-24.985-24.986v-424.55c0-13.466-10.906-24.371-24.371-24.371s-24.372 10.905-24.372 24.37v424.551c0 40.653 33.076 73.728 73.728 73.728h205.62a445.798 445.798 0 0 0 105.676-48.742z m125.03-392.448c0-9.216-7.474-16.691-16.69-16.691h-77.056a16.691 16.691 0 0 0-16.692 16.69v346.164c0 9.216 7.476 16.691 16.692 16.691h10.342a454.129 454.129 0 0 0 83.405-87.347V330.445z" fill="#757BF2" p-id="21084"></path><path d="M271.053 631.86c9.011 0 17.971-0.308 26.88-0.82V273.357c0-13.466-10.906-24.371-24.371-24.371s-24.372 10.905-24.372 24.37v357.94c7.22 0.41 14.541 0.563 21.863 0.563z m162.713-165.53h-77.004a16.691 16.691 0 0 0-16.692 16.69v143.566a446.413 446.413 0 0 0 110.388-31.898V483.021c0-9.216-7.476-16.691-16.692-16.691z m171.06 17.766V276.122c0-9.216-7.476-16.692-16.692-16.692H511.13a16.691 16.691 0 0 0-16.692 16.692v296.55a450.995 450.995 0 0 0 110.388-88.576z m97.177-170.342h-36.505a16.691 16.691 0 0 0-16.692 16.69v96.666a446.095 446.095 0 0 0 53.197-113.356z" fill="#8486F8" p-id="21085"></path><path d="M297.933 450.048V273.357c0-13.466-10.906-24.371-24.371-24.371s-24.372 10.905-24.372 24.37v189.646a431.748 431.748 0 0 0 48.743-12.954z m214.374-150.22a452.463 452.463 0 0 0 28.314-40.346H511.13a16.691 16.691 0 0 0-16.692 16.69v45.006a419.386 419.386 0 0 0 17.87-21.35" fill="#8D92F8" p-id="21086"></path></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772430213579" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9712" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M145.6 0C100.8 0 64 35.2 64 80v862.4C64 987.2 100.8 1024 145.6 1024h732.8c44.8 0 81.6-36.8 81.6-81.6V324.8L657.6 0h-512z" fill="#FC7B24" p-id="9713"></path><path d="M960 326.4v16H755.2s-100.8-20.8-99.2-108.8c0 0 4.8 92.8 97.6 92.8H960z" fill="#FB5C1B" p-id="9714"></path><path d="M657.6 0v233.6c0 25.6 17.6 92.8 97.6 92.8H960L657.6 0z" fill="#FFFFFF" p-id="9715"></path><path d="M374.4 860.8c-3.2 0-6.4 0-9.6-3.2l-59.2-80-59.2 80c-3.2 3.2-6.4 3.2-9.6 3.2-6.4 0-11.2-4.8-11.2-11.2 0-1.6 0-4.8 1.6-6.4l62.4-81.6-57.6-78.4c-1.6-1.6-1.6-3.2-1.6-6.4 0-4.8 4.8-11.2 11.2-11.2 3.2 0 6.4 1.6 9.6 4.8l54.4 73.6 54.4-73.6c3.2-3.2 6.4-4.8 9.6-4.8 6.4 0 11.2 4.8 11.2 11.2 0 3.2 0 4.8-1.6 6.4l-57.6 76.8 62.4 83.2c1.6 1.6 1.6 4.8 1.6 6.4 0 6.4-4.8 11.2-11.2 11.2z m230.4 0c-6.4 0-11.2-4.8-11.2-11.2v-148.8l-64 156.8c-1.6 1.6-3.2 3.2-6.4 3.2s-4.8-1.6-6.4-3.2l-64-156.8v148.8c0 6.4-4.8 11.2-11.2 11.2-8 0-12.8-4.8-12.8-11.2V684.8c0-9.6 9.6-19.2 20.8-19.2 8 0 16 4.8 19.2 12.8l54.4 134.4 54.4-134.4c3.2-8 11.2-12.8 19.2-12.8 11.2 0 20.8 9.6 20.8 19.2v164.8c0 6.4-4.8 11.2-12.8 11.2z m169.6-1.6h-88c-9.6 0-17.6-8-17.6-17.6V676.8c0-6.4 6.4-11.2 12.8-11.2s11.2 4.8 11.2 11.2v161.6h81.6c4.8 0 9.6 4.8 9.6 9.6 0 6.4-4.8 11.2-9.6 11.2z" fill="#FFFFFF" p-id="9716"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 795 KiB

+94 -19
View File
@@ -1,33 +1,51 @@
<script setup lang="ts"> <script setup lang="ts">
import Header from '@/components/Layout/Header/Header.vue' import Header from '@/components/Layout/Header/Header.vue'
import Left from '@/components/Layout/Left/Left.vue'
import Floor from '@/components/Layout/Floor/Floor.vue' import Floor from '@/components/Layout/Floor/Floor.vue'
import Right from '@/components/Layout/Right/Right.vue' // import Right from '@/components/Layout/Right/Right.vue'
import { useComponentStore } from '@/store/modules/component'
//store
const componentStore = useComponentStore()
</script> </script>
<template> <template>
<Header></Header> <el-container>
<div class="w-full flex flex-col justify-between mb-20"> <!-- left -->
<el-row> <transition name="left-sidebar">
<el-col :xl="4" :lg="3" :sm="1" class=""></el-col> <el-aside v-if="!componentStore.leftCom" class="fixed top-0 left-0 h-full z-10 c-md:block c-sm:hidden c-xs:hidden" width="240px">
<el-col :xl="16" :lg="18" :sm="22" class="mt-4"> <Left></Left>
<el-row class="flex-1"> </el-aside>
<el-col :lg="19" :sm="19" class=""> </transition>
<el-drawer
show-close
size="240px"
:with-header="false"
v-model="componentStore.leftComDrawer"
direction="ltr"
>
<Left></Left>
</el-drawer>
<!-- right -->
<el-container :class="!componentStore.leftCom ? 'c-md:ml-[240px]' : ''" class="main-container">
<el-header>
<Header/>
</el-header>
<el-main>
<router-view v-slot="{ Component, route }"> <router-view v-slot="{ Component, route }">
<transition name="animation" mode="out-in"> <transition name="animation" mode="out-in">
<component :is="Component" :key="route.path"></component> <component :is="Component" :key="route.path"></component>
</transition> </transition>
</router-view> </router-view>
</el-col> </el-main>
<el-col :lg="5" :sm="5" class=""> <el-footer class="md:mb-6 mt-12 c-xs:mb-12">
<Right/> <Floor />
</el-col> </el-footer>
</el-row> </el-container>
</el-col>
<!-- <el-col :lg="4" :sm="1" class="flex bg-gray-200"></el-col> --> </el-container>
<el-col :xl="4" :lg="3" :sm="1"></el-col>
</el-row>
</div>
<Floor></Floor>
</template> </template>
<style scoped> <style scoped>
@@ -47,4 +65,61 @@ import Right from '@/components/Layout/Right/Right.vue'
.animation-leave-active { .animation-leave-active {
transition: all 0.3s cubic-bezier(1, 0.6, 0.6, 1); transition: all 0.3s cubic-bezier(1, 0.6, 0.6, 1);
} }
/* 左侧菜单折叠动画 */
.left-sidebar-enter-from,
.left-sidebar-leave-to {
transform: translateX(-100%);
opacity: 0;
}
.left-sidebar-enter-to,
.left-sidebar-leave-from {
transform: translateX(0);
opacity: 1;
}
.left-sidebar-enter-active,
.left-sidebar-leave-active {
transition: all 0.3s ease;
}
/* 主内容区域动画 */
.main-container {
transition: margin-left 0.3s ease;
}
</style>
<style>
/* 全局科技感样式 */
body {
background-color: #f0f2f5;
background-image:
radial-gradient(circle at 10% 20%, rgba(64, 158, 255, 0.05) 0%, transparent 20%),
radial-gradient(circle at 80% 30%, rgba(118, 75, 162, 0.05) 0%, transparent 25%),
radial-gradient(circle at 40% 70%, rgba(102, 126, 234, 0.05) 0%, transparent 20%),
radial-gradient(circle at 90% 90%, rgba(64, 158, 255, 0.05) 0%, transparent 15%);
background-attachment: fixed;
min-height: 100vh;
}
/* 科技感滚动条 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #409EFF, #667eea);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #667eea, #764ba2);
}
</style> </style>
-28
View File
@@ -1,28 +0,0 @@
//统一管理咱们项目用户相关的接口
import request from '@/utils/request'
import type {
ToolsReqData,
ToolsResponseData,
ToolCateResponseData,
ToolRecommendResponseData
} from './type'
//项目用户相关的请求地址
enum API {
//获取tools
GET_TOOLS = '/tools',
//获取tool cate
GET_TOOL_CATES = '/tool/cates',
//获取推荐
GET_TOOL_RECOMMEND = '/tool/recommends',
}
//获取tools
export const getTools = (data: ToolsReqData) => request.get<any, ToolsResponseData>(API.GET_TOOLS, {
params: data
})
//获取tool cate
export const getToolCate = () => request.get<any, ToolCateResponseData>(API.GET_TOOL_CATES)
//获取推荐
export const getToolRecommend = () => request.get<any, ToolRecommendResponseData>(API.GET_TOOL_RECOMMEND)
+16
View File
@@ -0,0 +1,16 @@
//统一管理咱们项目用户相关的接口
import request from '@/utils/request'
import type {
WebInfoReqData,
getWebInfoResponseData
} from './type'
//项目用户相关的请求地址
enum API {
//获取网站信息
WEBINFO = '/webinfo'
}
//获取ip
export const fetchWebInfo = (data: WebInfoReqData) => request.get<any, getWebInfoResponseData>(API.WEBINFO + '/' + data.link)
+18
View File
@@ -0,0 +1,18 @@
import { ResponseData } from "../common"
export interface WebInfoReqData {
link: string
}
export interface WebInfo {
link: string,
logo: string,
title: string,
keywords: string,
desc: string
}
//响应格式
export interface getWebInfoResponseData extends ResponseData {
data: WebInfo
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+28
View File
@@ -0,0 +1,28 @@
<script setup lang="ts">
</script>
<template>
<div class="flex flex-col mt-8 flex-1 items-center bg-white rounded-md p-10">
<div class="p-5 w-2/3">
<h1 class="text-2xl font-bold">关于 Tools-Web</h1>
<p class="mt-6">
<el-text><el-link href="https://github.com/naroat/tools-web" target="_blank" type="primary">Tools-Web</el-link> 是一款免费开源的轻量在线工具箱包含开发文本媒体图表生活查询等实用工具如果对您有帮助请将其分享给您的朋友并且添加到收藏夹中顺便再点个</el-text>
</p>
<h1 class="text-2xl font-bold mt-6 mb-6">技术</h1>
<p>
<el-text>主要基于Vite + Vue + ElementPlus + Typescript + TailwindCss开发某些工具使用了第三方开源库您可以在仓库的 <el-link href="https://github.com/naroat/tools-web/blob/master/package.json" target="_blank" type="primary">package.json</el-link> 文件中找到完整的列表</el-text>
</p>
<h1 class="text-2xl font-bold mt-6 mb-6">发现了 Bug</h1>
<p>
<el-text>如果您发现了 Bug或者某些功能未能按预期工作请在 GitHub 仓库的 <el-link type="primary" target="_blank" href="https://github.com/naroat/tools-web/issues/new" class="">issues</el-link> 中提交错误报告</el-text>
</p>
</div>
</div>
</template>
<style scoped>
.title{
/* rgb(51, 54, 57) */
}
</style>
+129 -20
View File
@@ -1,34 +1,84 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, onMounted } from 'vue'; import { onMounted } from 'vue';
import { RouterLink } from "vue-router" import { RouterLink } from "vue-router"
import { Star } from '@element-plus/icons-vue' import { StarFilled } from '@element-plus/icons-vue'
import { useToolsStore } from '@/store/modules/tools' import { useToolsStore } from '@/store/modules/tools'
import { ElMessage } from 'element-plus'
import { useRoute } from "vue-router"
//store //store
const toolsStore = useToolsStore() const toolsStore = useToolsStore()
//查询参数 const route = useRoute()
const searchParam = reactive({
cateId: 0, // 取消收藏
title: '' const removeCollect = (url: string) => {
}) const success = toolsStore.removeCollect(url)
const getToolsList = async () => { if (success) {
try { ElMessage.success('已取消收藏');
await toolsStore.getTools(searchParam) } else {
} catch (error) { ElMessage.error('取消收藏失败');
console.log(error)
} }
} }
onMounted(() => { onMounted(() => {
getToolsList() toolsStore.loadCollectedTools()
if (route.query && route.query.value) {//底部导航跳转过来的则定位到响应位置
document?.querySelector('#' + `${route.query.value}`)?.scrollIntoView();
} else {//其他位置跳转过来不需要定位的则定位到顶部
document?.querySelector('#collect')?.scrollIntoView()
}
}) })
</script> </script>
<template> <template>
<div> <div class="md:mr-6 c-xs:mr-0">
<!-- 收藏工具 -->
<div id="collect">
<!-- 收藏标题 -->
<div class="mt-8 mb-3 text-xl font-bold text-[--base-black] tech-title">
收藏工具
</div>
<!-- 收藏卡片 -->
<div v-if="toolsStore.collectedTools.length > 0" class="flex justify-between flex-wrap self-card-div c-xs:ml-0" :gutter="10">
<router-link v-for="(item, index) in toolsStore.collectedTools" :key="index" :to="item.url" class="flex flex-col mt-5 border-solid rounded-2xl border-gray w-[24%] p-2 bg-white hover:shadow-md c-xs:w-[99.5%] c-md:w-[24%] c-sm:w-[32%] p-5 hover:-translate-y-2 duration-300 tech-card">
<div class="flex items-center border-b pb-2">
<el-image :src="item.logo" class="w-10 h-10 min-h-[2.5rem] min-w-[2.5rem] rounded-full"></el-image>
<div class="flex flex-col ml-2 w-full">
<div class="flex justify-between">
<div class="font-semibold text-lg line-clamp-1">{{ item.title }}</div>
<el-button
:icon="StarFilled"
circle
size="small"
@click.prevent="removeCollect(item.url)"
type="warning"
/>
</div>
<div class="flex justify-between">
<el-text size="small">{{ item.cate }}</el-text>
</div>
</div>
</div>
<div class="flex items-center justify-between mt-2">
<el-text line-clamp="2">{{ item.desc }}</el-text>
</div>
</router-link>
<!-- 占位 div -->
<div class="w-[24%] c-md:w-[24%] c-sm:w-[32%] "></div>
</div>
<div v-else class="mt-5 p-5 bg-white rounded-2xl">
<el-empty description="暂无收藏工具" />
</div>
</div>
<!-- 工具分类列表 -->
<div v-for="(cate, index) in toolsStore.cates" :key="index">
<!-- cate title -->
<div class="mt-8 mb-3 text-xl font-bold text-[--base-black] tech-title" :id="'cate_' + cate.id">
{{ cate.title }}
</div>
<!-- card --> <!-- card -->
<div class="mr-3 flex justify-between flex-wrap self-card-div c-xs:ml-3" :gutter="10"> <div class="flex justify-between flex-wrap self-card-div c-xs:ml-0" :gutter="10">
<router-link v-for="(item, index) in toolsStore.list" :key="index" :to="item.url" class="flex flex-col mt-3 border-solid rounded border-gray w-[32.5%] p-2 bg-gray-100 hover:shadow-md c-xs:w-[99.5%]"> <router-link v-for="(item, index) in cate.list" :key="index" :to="item.url" class="flex flex-col mt-5 border-solid rounded-2xl border-gray w-[24%] p-2 bg-white hover:shadow-md c-xs:w-[99.5%] c-md:w-[24%] c-sm:w-[32%] p-5 hover:-translate-y-2 duration-300 tech-card">
<div class="flex items-center border-b pb-2"> <div class="flex items-center border-b pb-2">
<el-image :src="item.logo" class="w-10 h-10 min-h-[2.5rem] min-w-[2.5rem] rounded-full"></el-image> <el-image :src="item.logo" class="w-10 h-10 min-h-[2.5rem] min-w-[2.5rem] rounded-full"></el-image>
<div class="flex flex-col ml-2 w-full"> <div class="flex flex-col ml-2 w-full">
@@ -37,22 +87,81 @@ onMounted(() => {
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<el-text size="small">{{ item.cate }}</el-text> <el-text size="small">{{ item.cate }}</el-text>
<el-button :icon="Star" circle size="small"/>
</div> </div>
</div> </div>
</div> </div>
<div class="flex items-center justify-between mt-2"> <div class="flex items-center justify-between mt-2">
<el-text line-clamp="1">{{ item.desc }}</el-text> <el-text line-clamp="2">{{ item.desc }}</el-text>
</div> </div>
</router-link> </router-link>
<!-- 占位 div -->
<div class="w-[24%] c-md:w-[24%] c-sm:w-[32%] "></div>
</div>
</div> </div>
<!-- 返回顶部 -->
<el-backtop :right="10" :bottom="50" />
</div> </div>
</template> </template>
<style scoped> <style scoped>
.self-card-div:after{ .self-card-div:after{
content: ""; content: "";
width: 32.5% width: 24%
}
/* 科技感卡片样式 */
.router-link-active .tech-card {
border-color: #409EFF;
box-shadow: 0 0 20px rgba(64, 158, 255, 0.3);
}
.tech-card {
position: relative;
overflow: hidden;
transition: all 0.3s ease;
border: 1px solid transparent;
background: linear-gradient(135deg, #ffffff 0%, #f8f9ff 100%);
}
.tech-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(64, 158, 255, 0.2);
border-color: #409EFF;
}
.tech-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 2px;
background: linear-gradient(90deg, #409EFF, #667eea, #764ba2);
transform: scaleX(0);
transform-origin: left;
transition: transform 0.3s ease;
}
.tech-card:hover::before {
transform: scaleX(1);
}
/* 科技感标题样式 */
.tech-title {
position: relative;
display: inline-block;
margin-bottom: 1rem;
}
.tech-title::after {
content: '';
position: absolute;
bottom: -5px;
left: 0;
width: 60px;
height: 3px;
background: linear-gradient(90deg, #409EFF, #667eea);
border-radius: 3px;
} }
</style> </style>
@@ -1,27 +1,150 @@
<script setup lang="ts"> <script setup lang="ts">
import { Back } from '@element-plus/icons-vue' import { Star, StarFilled } from '@element-plus/icons-vue'
const props = defineProps({ import { onMounted, reactive, ref } from 'vue';
title: String import { useRoute } from 'vue-router'
import { useToolsStore } from '@/store/modules/tools'
import { ElMessage } from 'element-plus'
import {rtrim} from '@/utils/string'
const props = defineProps({ title: String, id: Number})
const route = useRoute()
//查询参数
const searchParam = reactive({
cateId: 0,
title: '',
route: ''
}) })
//store
const toolsStore = useToolsStore()
const isCollected = ref(false)
//根据路由查询tool id
const getToolInfo = async () => {
let routeStr = route.path
searchParam.route = rtrim(routeStr, '/')
await toolsStore.getToolInfo(searchParam)
// 检查收藏状态
isCollected.value = toolsStore.isCollected(searchParam.route)
}
// 收藏/取消收藏
const toggleCollect = () => {
const toolInfo = toolsStore.currentTool
if (!toolInfo) {
ElMessage.warning('获取工具信息失败');
return;
}
if (isCollected.value) {
// 取消收藏
const success = toolsStore.removeCollect(toolInfo.url)
if (success) {
isCollected.value = false
ElMessage.success('已取消收藏');
} else {
ElMessage.error('取消收藏失败');
}
} else {
// 添加收藏
const success = toolsStore.addCollect(toolInfo)
if (success) {
isCollected.value = true
ElMessage.success('收藏成功');
} else {
ElMessage.error('收藏失败,可能已经收藏过了');
}
}
}
onMounted(() => {
toolsStore.loadCollectedTools()
getToolInfo()
})
</script> </script>
<template> <template>
<div> <div class="flex justify-between items-center rounded-2xl p-4 mt-5 mb-5 tech-detail-header">
<div class="text-gray-600"> <div class="text-xl tech-title">
<div class="flex">
<RouterLink to="/" class="flex items-center">
<el-icon color="blue"><Back /></el-icon>
<div>返回首页</div>
</RouterLink>
</div>
</div>
<div class="text-xl mt-5 mb-5">
{{ props.title }} {{ props.title }}
</div> </div>
<div>
<el-button
:icon="isCollected ? StarFilled : Star"
:type="isCollected ? 'warning' : 'default'"
@click="toggleCollect"
class="tech-button"
size="default"
>
{{ isCollected ? '已收藏' : '收藏' }}
</el-button>
</div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
/* 科技感详情页头部 */
.tech-detail-header {
background: linear-gradient(135deg, #ffffff 0%, #f8f9ff 100%);
border: 1px solid rgba(64, 158, 255, 0.2);
position: relative;
overflow: hidden;
}
.tech-detail-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 2px;
background: linear-gradient(90deg, #409EFF, #667eea, #764ba2);
}
/* 科技感标题 */
.tech-title {
position: relative;
font-weight: 600;
color: #333;
}
/* 科技感按钮 */
.tech-button {
position: relative;
border-radius: 8px;
transition: all 0.3s ease;
overflow: hidden;
}
.tech-button:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
}
.tech-button:active {
transform: translateY(0);
}
/* 收藏状态按钮样式 */
.tech-button.is-warning {
background: linear-gradient(135deg, #ffc107, #ff9800);
border-color: #ffc107;
color: #fff;
}
.tech-button.is-warning:hover {
background: linear-gradient(135deg, #ffca28, #ff9800);
box-shadow: 0 4px 12px rgba(255, 193, 7, 0.4);
}
/* 未收藏状态按钮样式 */
.tech-button.is-default {
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
border-color: #dee2e6;
color: #495057;
}
.tech-button.is-default:hover {
background: linear-gradient(135deg, #ffffff, #f8f9fa);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
</style> </style>
+20 -5
View File
@@ -1,15 +1,30 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue';
const beianNo = ref('粤ICP备19038804号-1') const appDesc = ref(import.meta.env.VITE_APP_DESC || '')
const copyright = ref('ranblogs.com') const currentYear: number = new Date().getFullYear();
</script> </script>
<template> <template>
<div class="bg-black w-full fixed left-0 bottom-0 text-white text-center h-8 leading-8 z-10"> <div class="w-full rounded-2xl z-10 p-5 text-center tech-footer">
Copyright © 2023 · {{ copyright }} &nbsp;&nbsp; <a href="https://beian.miit.gov.cn/" target="_blank">{{ beianNo }}</a> <el-text>{{ appDesc }} © {{ currentYear }} <a href="https://github.com/naroat/tools-web" target="_blank" class="text-blue-700 tech-link">Tools-Web</a></el-text>
</div> </div>
</template> </template>
<style scoped> <style scoped>
/* 简约页脚 */
.tech-footer {
background: transparent;
border: none;
position: relative;
}
/* 简约链接 */
.tech-link {
transition: all 0.2s ease;
text-decoration: none;
}
.tech-link:hover {
color: #409EFF;
}
</style> </style>
+189 -128
View File
@@ -1,174 +1,235 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, onMounted } from 'vue'
import { Search,MoreFilled } from '@element-plus/icons-vue'; import { Search } from '@element-plus/icons-vue';
import { useToolsStore } from '@/store/modules/tools' import { useToolsStore } from '@/store/modules/tools'
import { ElMessageBox } from 'element-plus' import { useComponentStore } from '@/store/modules/component'
import 'element-plus/theme-chalk/display.css' import 'element-plus/theme-chalk/display.css'
import { ToolsInfo } from '@/components/Tools/tools.type.ts';
const appName = ref(import.meta.env.VITE_APP_TITLE || '工具坊') import router from '@/router';
const appNet = ref(import.meta.env.VITE_APP_WEBSITE || '') // const isNavDrawer = ref(false)
const isNavDrawer = ref(false) const loading = ref(false)
const options = ref<ToolsInfo[]>([])
//store //store
const toolsStore = useToolsStore() const toolsStore = useToolsStore()
const componentStore = useComponentStore()
//查询参数 //查询参数
const searchParam = reactive({ const searchParam = reactive({
cateId: 0, cateId: 0,
title: '' title: '',
route: '',
}) })
//获取分类
const getToolCates = async () => {
try {
await toolsStore.getToolCate()
} catch (error) {
console.log(error)
}
}
//search //search
const search = async () => { // const search = async () => {
try { // try {
await toolsStore.getTools(searchParam) // await toolsStore.getTools(searchParam)
//关闭抽屉 // //关闭抽屉
isNavDrawer.value = false // isNavDrawer.value = false
} catch (error) { // } catch (error) {
console.log(error) // console.log(error)
} // }
} // }
//选择分类 //选择分类
const chooseCate = (cateId: number) => { // const chooseCate = (cateId: number) => {
searchParam.cateId = cateId // searchParam.cateId = cateId
search() // search()
// }
//搜索工具
const searchTools = async (query: string) => {
loading.value = true
options.value = []
if (query) {
searchParam.title = query
options.value = await toolsStore.getTools(searchParam)
}
loading.value = false
} }
//收藏 //保存到桌面
const collect = () => { const createUrlShortcut = async () => {
ElMessageBox({ try {
title: '提示', const blob = new Blob(
message: '请使用快捷键`Ctrl+D`进行收藏' [`[InternetShortcut]\nURL=${encodeURI(window.location.href)}`],
}) { type: 'text/plain' }
);
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'Tools-Web.url';
a.click();
} catch (error) {
console.error('创建URL快捷方式失败:', error);
}
}
const optionClick = (url: string) => {
router.push(url)
} }
onMounted(() => { onMounted(() => {
getToolCates()
}) })
</script> </script>
<template> <template>
<header class=""> <header class="h-24 w-full flex justify-between pt-2 pb-2 c-xs:h-16 c-xs:border-b-[1px] items-center">
<nav class="h-28 w-full leading-10 flex flex-col justify-between pt-2 pb-2 c-xs:h-16 c-xs:border-b-[1px]"> <div class="flex items-center w-full">
<el-row> <div class="hidden c-sm:block c-md:hidden c-xs:block">
<el-col :xl="4" :lg="3" :sm="1"></el-col> <svg @click="componentStore.setleftComDrawerStatus(!componentStore.leftComDrawer)" :class="['icon', { 'rotate': componentStore.leftComDrawer }]" t="1702978210636" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7618" width="30" height="30">
<el-col :xl="16" :lg="18" :sm="22"> <path fill="#444" fill-opacity=".9" d="M895.936 256l-768-0.896 0.128-64L896 192l-0.064 64zM179.2 689.152l202.688-152a32 32 0 0 0 0-51.2L179.2 333.952a32 32 0 0 0-51.2 25.6v304a32 32 0 0 0 51.2 25.6z m12.8-89.6v-176l117.312 88L192 599.552zM896 544H480v-64H896v64z m-0.064 288l-768-0.896 0.128-64L896 768l-0.064 64z" p-id="7619"></path>
<div class="flex items-center justify-between"> </svg>
<!-- left -->
<router-link class="ml-4 flex flex-row" to="/">
<img class="h-10 w-auto rounded-lg lg:h-16 mr-2 mt-auto mb-auto" src="@/assets/logo.png" :alt="appNet">
<div class="flex flex-col mt-auto mb-auto">
<div class="text-xl">{{ appName }}</div>
<div class="text-sm text-gray-500">{{ appNet }}</div>
</div> </div>
<!-- c-md:block -->
<div class="hidden c-md:block">
<svg @click="componentStore.setLeftComStatus(!componentStore.leftCom)" :class="['icon', { 'rotate': componentStore.leftCom }]" t="1702978210636" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7618" width="30" height="30">
<path fill="#444" fill-opacity=".9" d="M895.936 256l-768-0.896 0.128-64L896 192l-0.064 64zM179.2 689.152l202.688-152a32 32 0 0 0 0-51.2L179.2 333.952a32 32 0 0 0-51.2 25.6v304a32 32 0 0 0 51.2 25.6z m12.8-89.6v-176l117.312 88L192 599.552zM896 544H480v-64H896v64z m-0.064 288l-768-0.896 0.128-64L896 768l-0.064 64z" p-id="7619"></path>
</svg>
</div>
<div class="ml-3 mr-1">
<router-link to="/">
<svg t="1715590310537" class="icon" viewBox="0 0 1053 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4287" width="25" height="25">
<path d="M526.63454 58.067422a21.503527 21.503527 0 0 1-27.501109 0.175539l474.979265 381.50475a31.538506 31.538506 0 1 0 39.496274-49.150919L538.629705 9.004273a41.602742 41.602742 0 0 0-51.988799 0.234052L11.573871 398.671586a31.538506 31.538506 0 1 0 39.993635 48.79984L526.63454 58.067422z" fill="#2c2c2c" p-id="4288"></path><path d="M1013.60897 564.087832L538.629705 182.583083a41.602742 41.602742 0 0 0-51.988799 0.204795L11.573871 572.191883a31.538506 31.538506 0 1 0 39.993635 48.79984l66.763331-54.709653v361.61033A94.644775 94.644775 0 0 0 213.004869 1022.537175H441.732179v-247.275931a15.79851 15.79851 0 0 1 15.739997-15.79851h110.472541c8.68918 0 15.769253 7.080073 15.769253 15.79851v247.275931H812.441281a94.615518 94.615518 0 0 0 94.644775-94.615518V559.465305l66.997383 53.831959a31.538506 31.538506 0 1 0 39.525531-49.209432z m-169.629183 363.804568c0 17.436874-14.101633 31.538506-31.567763 31.538506h-165.591785v-184.198919a78.875522 78.875522 0 0 0-78.875522-78.904778h-110.472541a78.875522 78.875522 0 0 0-78.846265 78.904778v184.198919h-165.591786c-17.46613 0-31.597019-14.130889-31.597019-31.538506V514.527323L512.883986 242.851471l331.095801 265.941578v419.128608z" fill="#2c2c2c" p-id="4289" stroke="black" stroke-width="10"></path>
</svg>
</router-link> </router-link>
<!-- right -->
<div class=" w-full md:w-auto flex c-xs:hidden md:block">
<ul class="flex mt-4 flex-col md:flex-row md:mt-0 justify-end items-center">
<li class="hover:text-blue-500">
<el-button link size="large" @click="collect">收藏本站</el-button>
</li>
<li class="ml-3 hover:text-blue-500">
<!-- <el-button round>登录/注册</el-button> -->
</li>
</ul>
</div> </div>
<!-- h5: 搜索导航 -->
<div class="c-lg:hidden c-sm:hidden"> <div class="c-xs:w-[85%] w-full mr-3">
<div class="mr-3 leading-3"> <!-- <el-input
<!-- <el-icon size="25"><Search /></el-icon> -->
<el-icon size="25" class="ml-2" style="transform:rotate(90deg)" @click="isNavDrawer == false ? isNavDrawer = true : isNavDrawer = false"><MoreFilled /></el-icon>
</div>
<!-- 抽屉 -->
<el-drawer v-model="isNavDrawer" direction="rtl" class="" size="70%">
<div>
<!-- search -->
<div>
<el-input
v-model="searchParam.title" v-model="searchParam.title"
placeholder="搜索工具" placeholder="搜索工具"
class="w-full max-w-xl h-10" class="h-10 ml-3"
@keyup.enter.native="search"> @keyup.enter.native="search">
<template #append> <template #append>
<el-button :icon="Search" @click="search"/> <el-button :icon="Search" @click="search"/>
</template> </template>
</el-input> </el-input> -->
</div> <el-select
<!-- cates --> v-model="searchParam.title"
<div class="mt-3 text-center"> filterable
<ul class="flex flex-col rounded items-center"> remote
<li class="flex items-center w-full text-center mb-2"> reserve-keyword
<div remote-show-suffix
class="hover:text-blue-400 text-xl w-full hover:bg-gray-200 hover:rounded-md h-10 leading-10" :class="searchParam.cateId == 0 ? 'text-blue-400 bg-gray-200 rounded-md' : ''" :suffix-transition="false"
@click="chooseCate(0)" :suffix-icon="Search"
>全部工具</div> placeholder="输入关键词搜索,如文本、json、图片等"
</li> :remote-method="searchTools"
<li v-for="(item, index) in toolsStore.cates" :key="index" class="flex items-center mb-2 w-full"> :loading="loading"
<!-- <el-text>{{ index != 0 ? '&nbsp;&nbsp;&nbsp;&nbsp;' : '' }} </el-text> --> class="ml-3"
<div size="large"
class="hover:text-blue-400 text-xl w-full hover:bg-gray-200 hover:rounded-md h-10 leading-10" :class="searchParam.cateId == item.id ? 'text-blue-400 bg-gray-200 rounded-md' : ''" >
@click="chooseCate(item.id)" <el-option
>{{ item.title }}</div> v-for="item in options"
</li> :key="item.id"
</ul> :label="item.title + ' - ' + item.desc"
</div> :value="item.id"
</div> @click="optionClick(item.url)"
</el-drawer> >
</el-option>
</el-select>
</div> </div>
</div> </div>
<!-- pc/pad:搜索导航 --> <div class=" w-full md:w-auto flex md:block c-xs:w-auto">
<el-row class="mt-3 hidden-xs-only"> <ul class="flex mt-4 flex-col md:flex-row md:mt-0 justify-end items-center c-xs:mt-0">
<el-col :lg="19" :sm="19"> <!-- about -->
<!-- cates --> <li class="ml-3 c-xs:hidden">
<div class="mr-3"> <router-link to="/about">
<ul class="flex bg-gray-100 h-10 rounded leading-10 pl-3"> <el-tooltip
<li class="flex items-center mr-3"> class="box-item"
<div effect="dark"
class="hover:text-blue-400 hover:underline hover:decoration-2 hover:underline-offset-[6px] text-sm" :class="searchParam.cateId == 0 ? 'text-blue-400 underline decoration-2 underline-offset-[6px]' : ''" content="关于tools-web"
@click="chooseCate(0)" placement="bottom"
>全部工具</div> >
<svg t="1715594765484" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5578" width="33" height="33"><path d="M511.899716 948.506609c-241.310951 0-437.636339-196.318224-437.636339-437.636339 0-241.323231 196.325387-437.639408 437.636339-437.639408s437.636339 196.316178 437.636339 437.639408C949.536055 752.188384 753.210667 948.506609 511.899716 948.506609zM511.899716 113.944122c-218.866776 0-396.926148 178.064488-396.926148 396.926148 0 218.856543 178.059372 396.926148 396.926148 396.926148 218.868823 0 396.926148-178.069605 396.926148-396.926148C908.825864 292.00861 730.768539 113.944122 511.899716 113.944122zM561.15656 335.324138c-29.853935 0-54.03773-24.189935-54.03773-54.047963 0-29.855982 24.184819-54.047963 54.03773-54.047963 29.838585 0 54.0408 24.191982 54.0408 54.047963C615.19736 311.134203 590.995145 335.324138 561.15656 335.324138zM424.962691 430.321746c0-4.394077 0-8.806573 0-13.19758 42.878576-17.016559 108.943224-10.793834 153.201218-26.418696 1.75804 0 3.510964 0 5.27719 0-21.329794 108.134813-66.391083 206.496028-76.599585 316.955792 2.425236 1.850138 2.086521 1.473561 5.287423 2.623757 33.247218 11.155061 52.320623-66.110697 73.948199-60.727083 21.644973 5.38873-13.548574 43.733037-18.47784 50.193169-19.400862 25.362644-56.465013 68.439742-100.376105 68.657706-31.008224 0.181125-63.159482-19.378349-58.101279-71.301929 5.056156-51.981908 34.219359-124.319423 50.172703-182.263114C472.184179 468.050022 488.801648 429.049776 424.962691 430.321746z" fill="#515151" p-id="5579"></path></svg>
</el-tooltip>
</router-link>
</li> </li>
<li v-for="(item, index) in toolsStore.cates" :key="index" class="flex items-center"> <!-- github -->
<el-text>{{ index != 0 ? '&nbsp;&nbsp;&nbsp;&nbsp;' : '' }} </el-text> <li class="ml-3">
<div <el-tooltip
class="hover:text-blue-400 hover:underline hover:decoration-2 hover:underline-offset-[6px] text-sm" :class="searchParam.cateId == item.id ? 'text-blue-400 underline decoration-2 underline-offset-[6px]' : ''" class="box-item"
@click="chooseCate(item.id)" effect="dark"
>{{ item.title }}</div> content="GitHub仓库"
placement="bottom"
>
<a href="https://github.com/naroat/tools-web" target="_blank">
<svg t="1715594665374" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4334" width="33" height="33"><path d="M512 85.333333C276.266667 85.333333 85.333333 276.266667 85.333333 512a426.410667 426.410667 0 0 0 291.754667 404.821333c21.333333 3.712 29.312-9.088 29.312-20.309333 0-10.112-0.554667-43.690667-0.554667-79.445333-107.178667 19.754667-134.912-26.112-143.445333-50.133334-4.821333-12.288-25.6-50.133333-43.733333-60.288-14.933333-7.978667-36.266667-27.733333-0.554667-28.245333 33.621333-0.554667 57.6 30.933333 65.621333 43.733333 38.4 64.512 99.754667 46.378667 124.245334 35.2 3.754667-27.733333 14.933333-46.378667 27.221333-57.045333-94.933333-10.666667-194.133333-47.488-194.133333-210.688 0-46.421333 16.512-84.778667 43.733333-114.688-4.266667-10.666667-19.2-54.4 4.266667-113.066667 0 0 35.712-11.178667 117.333333 43.776a395.946667 395.946667 0 0 1 106.666667-14.421333c36.266667 0 72.533333 4.778667 106.666666 14.378667 81.578667-55.466667 117.333333-43.690667 117.333334-43.690667 23.466667 58.666667 8.533333 102.4 4.266666 113.066667 27.178667 29.866667 43.733333 67.712 43.733334 114.645333 0 163.754667-99.712 200.021333-194.645334 210.688 15.445333 13.312 28.8 38.912 28.8 78.933333 0 57.045333-0.554667 102.912-0.554666 117.333334 0 11.178667 8.021333 24.490667 29.354666 20.224A427.349333 427.349333 0 0 0 938.666667 512c0-235.733333-190.933333-426.666667-426.666667-426.666667z" fill="#000000" p-id="4335"></path></svg>
</a>
</el-tooltip>
</li>
<li class="hover:text-blue-500 c-xs:hidden">
<el-button type="primary" class="ml-3 bg-gradient-to-r from-cyan-500 to-blue-500" @click="createUrlShortcut">保存到桌面</el-button>
</li> </li>
</ul> </ul>
</div> </div>
</el-col>
<el-col :lg="5" :sm="5">
<!-- search -->
<div>
<el-input
v-model="searchParam.title"
placeholder="搜索工具"
class="w-full max-w-xl h-10"
@keyup.enter.native="search">
<template #append>
<el-button :icon="Search" @click="search"/>
</template>
</el-input>
</div>
</el-col>
</el-row>
</el-col>
<el-col :xl="4" :lg="3" :sm="1" class="flex"></el-col>
</el-row>
</nav>
</header> </header>
</template> </template>
<style scoped> <style scoped>
/* 科技感搜索框 */
.el-select :deep(.el-select__wrapper){
background: linear-gradient(135deg, #ffffff 0%, #f8f9ff 100%);
border: 1px solid rgba(64, 158, 255, 0.2);
border-radius: 8px;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
}
/* 搜索框容器 */
.el-select {
transition: width 0.3s ease;
}
.el-select :deep(.el-select__wrapper):hover {
border-color: #409EFF;
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
}
.el-select :deep(.el-select__wrapper.is-focus) {
border-color: #409EFF;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
/* 科技感按钮 */
.el-button {
position: relative;
overflow: hidden;
transition: all 0.3s ease;
border-radius: 8px;
}
.el-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
}
/* 科技感图标 */
.icon {
transition: all 0.3s ease;
transform-origin: center;
}
.icon:hover {
transform: scale(1.1);
filter: drop-shadow(0 0 8px rgba(64, 158, 255, 0.5));
}
/* 旋转动画 */
.icon.rotate {
transform: rotate(180deg);
}
/* 简约Header */
header {
background: transparent;
position: relative;
}
</style> </style>
+239
View File
@@ -0,0 +1,239 @@
<script setup lang="ts">
// import { Tools } from '@element-plus/icons-vue'
import { onMounted, ref, reactive } from 'vue';
import { useToolsStore } from '@/store/modules/tools'
import { useRouter, useRoute } from "vue-router"
const router = useRouter()
const route = useRoute()
const appName = ref(import.meta.env.VITE_APP_TITLE || '工具坊')
const appNet = ref(import.meta.env.VITE_APP_DESC || '')
const leftAdv = ref(import.meta.env.VITE_LEFT_ADV || '')
//菜单选中
const defaultActive = ref('')
//默认展开的菜单
const defaultOpeneds = ['cate']
//store
const toolsStore = useToolsStore()
//获取分类
const getToolCates = async () => {
try {
await toolsStore.getToolCate()
} catch (error) {
console.log(error)
}
}
const handleOpen = () => {
}
const handleClose = () => {
}
//跳转锚点
const query = reactive({ value: '' })
const gotoAnchor = (anchor: string) => {
console.log(route.path)
if (route.path === '/') {
document?.getElementById(anchor)?.scrollIntoView({
behavior: "smooth", //smooth:平滑,auto:直接定位
block: "start",
inline: "start",
});
} else {
query.value = anchor
router.push({
path: '/',
query: query
})
}
}
const gotoAbout = () => {
router.push('/about')
}
onMounted(async () => {
await getToolCates()
})
</script>
<template>
<!-- -->
<el-scrollbar>
<!-- logo -->
<div class="flex justify-center">
<router-link class="flex flex-row h-24 logo-container p-4" to="/">
<img class="h-12 w-auto rounded-lg mr-2 mt-auto mb-auto" src="@/assets/logo.png" :alt="appNet">
<div class="flex flex-col mt-auto mb-auto">
<div class="text-2xl">{{ appName }}</div>
<div class="text-sm text-gray-500">{{ appNet }}</div>
</div>
</router-link>
</div>
<!-- menu -->
<!-- #f5f5fb -->
<div class="flex justify-center pl-8 pr-8">
<el-menu
class="w-[200px]"
:default-active="defaultActive"
:default-openeds="defaultOpeneds"
background-color="transparent"
@open="handleOpen"
@close="handleClose"
>
<el-sub-menu index="cate">
<template #title>
<svg t="1702896224109" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4239" width="20" height="20">
<path fill="currentColor" d="M382.083 103.466c-63.464-1.533-127.026-1.561-190.487 0.056-53.88 1.372-85.947 33.283-87.526 87.481-1.798 61.735-1.627 123.592-0.105 185.34 1.405 57.042 33.666 88.862 89.987 91.055 30.847 1.2 61.778 0.214 92.673 0.214v0.084c29.177 0 58.354 0.006 87.532-0.01 3.43 0 6.875 0.043 10.292-0.216 48.928-3.716 81.464-31.602 83.804-80.677 3.181-66.767 3.15-133.886 0.34-200.68-2.194-52.18-34.273-81.385-86.51-82.647z m0.727 223.135c-1.853 39.629-13.76 53.399-51.558 55.395-30.725 1.622-61.703 1.765-92.399-0.15-32.22-2.01-46.292-15.941-48.33-48.552-2.025-32.392-2.108-65.126 0.084-97.498 2.219-32.758 16.121-45.288 49.214-46.935 29.033-1.446 58.217-1.13 87.29-0.204 42.456 1.353 53.884 13.188 55.727 55.78 0.592 13.67 0.092 27.387 0.092 41.083-0.02 13.695 0.518 27.415-0.12 41.081zM468.883 657.431c-4.914-60.1-41.873-98.957-102.147-102.763-52.991-3.346-106.533-3.413-159.516-0.013-61.945 3.977-100.908 45.842-103.178 107.37-1.834 49.716-1.806 99.605 0 149.324 2.232 61.528 42.13 104.63 102.68 108.128 53.034 3.063 106.506 2.894 159.565 0.06 58.998-3.15 97.39-42.913 102.53-102.565 2.2-25.551 0.367-51.451 0.367-77.191l0.08-0.001c0-27.46 1.85-55.071-0.381-82.349zM382.676 782.81c-2.191 34.758-12.473 47.623-45.629 49.797-34.02 2.23-68.343 1.291-102.466-0.024-25.612-0.987-42.22-15.758-43.434-41.638-1.677-35.784-2.509-71.85 0.088-107.519 2.16-29.666 15.98-40.487 47.82-41.924 30.705-1.386 61.557-1.324 92.274-0.118 37.12 1.456 48.88 13.204 51.308 49.153 1.033 15.313 0.173 30.753 0.173 46.135 0.027 15.382 0.832 30.817-0.134 46.138zM638.575 466.817c66.85 2.183 133.883 2.232 200.728-0.029 48.107-1.626 78.502-34.315 80.95-82.898 1.64-32.522 0.314-65.193 0.314-97.798h0.18c0-29.173 0.002-58.345-0.006-87.518 0-3.433 0.093-6.874-0.12-10.295-3.237-51.777-32.66-83.203-84.46-84.65-65.155-1.824-130.44-1.78-195.6 0.026-49.255 1.363-81.717 31.976-83.38 80.049-2.312 66.843-2.187 133.87-0.181 200.732 1.448 48.3 33.485 80.811 81.575 82.38zM741.2 188.405c93.184 0.015 94.452 1.293 94.464 95.22 0.013 96.831-1.987 98.873-96.789 98.868-94.393-0.003-95.62-3.049-97.83-98.688-2.4-103.803 20.564-97.124 100.155-95.4zM920.572 648.267c-1.48-60.79-30.968-91.401-92.039-94.662-29.17-1.558-58.49-0.269-87.743-0.269v0.082c-30.974 0-61.945-0.023-92.918 0.034-3.43 0.005-6.883 0.302-10.287 0.754-49.282 6.534-79.299 35.872-80.674 84.647a3477.609 3477.609 0 0 0 0.02 196.134c1.362 48.254 32.605 80.758 80.595 84.46 35.921 2.77 72.291 2.164 108.32 0.313 28.821-1.48 47.916-22.114 46.583-45.146-1.247-21.556-18.569-37.536-46.166-40.082-18.794-1.733-37.942 0.3-56.714-1.548-33.033-3.254-45.768-15.985-47.505-49.427a897.595 897.595 0 0 1 0.012-92.884c1.76-33.962 14.537-47.378 47.042-49.022 32.61-1.65 65.425-1.604 98.043-0.06 33.31 1.575 45.544 14.444 48.07 47.957 1.42 18.832-0.68 37.952 1.06 56.734 2.577 27.82 18.189 42.528 42.153 42.564 24.261 0.037 40.096-14.295 41.679-42.527 1.828-32.595 1.266-65.38 0.47-98.052z" p-id="4240"></path>
</svg>
<!-- <el-icon size="22px"><Menu /></el-icon> -->
<span class="ml-2">分类</span>
</template>
<el-menu-item-group>
<el-menu-item
@click="gotoAnchor('cate_' + item.id)"
:index="item.id.toString()"
v-for="(item,index) in toolsStore.cates"
:key="index"
>
{{ item.title }}
</el-menu-item>
</el-menu-item-group>
</el-sub-menu>
<el-menu-item index="about" @click="gotoAbout">
<template #title>
<svg t="1702896616706" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5262" width="20" height="20">
<path fill="currentColor" d="M514.734921 246.750649C505.162698 257.386453 500.249969 270.276034 500.249969 285.16616 500.249969 297.397334 504.175087 308.083785 511.92403 316.364518 519.824913 324.771867 530.03022 329.102159 541.678957 329.102159 551.073917 329.102159 565.077726 326.189975 577.91666 311.780993 587.944704 300.891955 592.984049 287.774464 592.984049 273.162895 592.984049 261.210278 588.881668 250.726414 581.107402 242.622944 564.064792 224.896605 533.043698 226.795855 514.734921 246.750649ZM967.769518 511.708782C967.769518 765.87917 763.334174 972.618938 511.949353 972.618938 260.615179 972.618938 56.179835 765.87917 56.179835 511.708782 56.179835 257.411776 260.615179 50.722655 511.949353 50.722655 763.334174 50.722655 967.769518 257.411776 967.769518 511.708782ZM978.734525 312.084873C953.259243 251.080941 916.540396 196.230581 870.122709 149.382397 823.629053 102.331627 769.4371 65.38487 709.294161 39.529738 584.272819-13.851868 439.853797-13.851868 314.781809 39.529738 254.613547 65.38487 200.497564 102.35695 153.927937 149.382397 107.43428 196.331874 70.740757 251.258204 45.366768 312.084873 19.005169 375.418552 5.53315 442.37347 5.53315 511.607488 5.53315 580.79086 18.929199 647.771101 45.265475 711.02881 70.690111 771.906126 107.383634 826.705839 153.877291 873.857902 200.294977 920.832703 254.436284 957.855429 314.731162 983.634592 377.330465 1010.426688 443.626975 1023.84806 511.92403 1023.84806 580.322378 1023.84806 646.593565 1010.426688 709.218191 983.583945 769.487746 957.804783 823.629053 920.832703 870.072063 873.857902 916.540396 826.781809 953.183273 771.906126 978.633232 711.003487 1005.070801 647.771101 1018.416203 580.79086 1018.416203 511.607488 1018.46685 442.37347 1005.121448 375.418552 978.734525 312.084873ZM553.226402 633.311472C538.462893 647.568514 528.080322 656.457007 521.16705 662.104113 524.560378 643.618073 533.372901 607.000519 554.568539 534.904963 575.71353 463.493137 577.511487 451.00873 577.511487 444.880481 577.511487 434.19403 573.105225 424.7231 565.432253 418.164354 548.161733 403.780696 517.723075 406.61691 480.978905 427.635285 460.492321 439.258699 438.714247 458.200559 414.403838 485.144595L401.742167 499.401637 443.804239 532.144719 454.541336 521.407622C466.266043 509.936148 474.19225 502.440438 479.510152 497.45174 447.400153 604.164305 431.775651 670.511462 431.775651 700.19042 431.775651 713.662438 435.624799 724.855355 443.449712 733.313352 451.325271 742.024581 462.391572 746.582783 475.053243 746.582783 487.461681 746.582783 501.668076 741.746025 518.938596 731.844598 534.512451 722.905458 557.860573 703.406484 590.527685 672.334743L603.82244 659.521132 565.584193 621.890645 553.226402 633.311472Z" p-id="5263"></path>
</svg>
<span class="ml-2">关于本站</span>
</template>
</el-menu-item>
</el-menu>
</div>
<!-- 广告位 -->
<div class="ad-container" v-if="leftAdv != ''" v-html="leftAdv"></div>
</el-scrollbar>
<!-- </div> -->
</template>
<style scoped>
.el-menu{
border: none !important;
list-style: none !important;
padding-left: 0 !important;
margin-top: 1rem;
background-color: transparent !important;
}
.el-menu:hover{
background-color: transparent !important;
}
.el-sub-menu{
border-radius: 10rem !important;
list-style: none !important;
margin: 0 !important;
padding-left: 0 !important;
background-color: transparent !important;
}
/* 深选择器获取menu__title */
.el-sub-menu :deep(.el-sub-menu__title){
/* padding-left: 0 !important; */
font-size: 1rem;
border-radius: 1rem;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.el-sub-menu :deep(.el-sub-menu__title:hover){
/* padding-left: 0 !important; */
color: #fff;
background: linear-gradient(135deg, #409EFF, #667eea);
border-radius: 1rem;
box-shadow: 0 4px 15px rgba(64, 158, 255, 0.3);
}
.el-menu-item{
background-color: transparent;
line-height: 38px!important;
height: 38px!important;
margin: 10px 0;
border-radius: 1rem !important;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.el-menu-item::before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 3px;
height: 100%;
background: linear-gradient(180deg, #409EFF, #667eea);
transform: scaleY(0);
transform-origin: center;
transition: transform 0.3s ease;
border-radius: 0 1rem 1rem 0;
}
.el-menu-item:hover::before,
.el-menu-item.is-active::before {
transform: scaleY(1);
}
.el-menu-item.is-active{
color: #fff;
background: linear-gradient(135deg, #409EFF, #667eea);
border-radius: 1rem;
box-shadow: 0 4px 15px rgba(64, 158, 255, 0.3);
}
.el-menu-item:hover{
color: #fff;
background: linear-gradient(135deg, #409EFF, #667eea);
border-radius: 1rem;
box-shadow: 0 4px 15px rgba(64, 158, 255, 0.3);
}
/* 简约Logo区域 */
.logo-container {
position: relative;
border-radius: 12px;
}
/* 广告位样式 */
.ad-container {
position: fixed;
bottom: 20px;
left: 20px;
width: 200px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.ad-container:hover {
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.ad-link {
display: block;
width: 100%;
height: 100%;
}
.ad-image {
width: 100%;
height: auto;
display: block;
border-radius: 8px;
}
</style>
-25
View File
@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { Tools } from '@element-plus/icons-vue' import { Tools } from '@element-plus/icons-vue'
import { Message } from '@element-plus/icons-vue'
import { reactive, onMounted } from 'vue'; import { reactive, onMounted } from 'vue';
import { useToolsStore } from '@/store/modules/tools' import { useToolsStore } from '@/store/modules/tools'
const info = reactive({ const info = reactive({
@@ -21,42 +20,18 @@ const info = reactive({
//store //store
const toolsStore = useToolsStore() const toolsStore = useToolsStore()
//获取随机推荐
const getToolRecommend = async () => {
try {
await toolsStore.getToolRecommend()
} catch (e) {
console.log(e)
}
}
//点击走马灯 //点击走马灯
const clickCarousel = (url: string) => { const clickCarousel = (url: string) => {
let w = window.open('') let w = window.open('')
w?.document.write(`<!DOCTYPE html><html><body ><img src='${url}'/></body></html>`) w?.document.write(`<!DOCTYPE html><html><body ><img src='${url}'/></body></html>`)
} }
//反馈
const feedback = () => {
window.open(info.feedbackUrl)
}
onMounted(() => { onMounted(() => {
getToolRecommend()
}) })
</script> </script>
<template> <template>
<div> <div>
<!-- feedback -->
<div class="flex flex-col text-center mt-3 border-solid rounded border-gray border p-3 c-xs:mr-3 c-xs:ml-3">
<div @click="feedback()" class="text-2xl flex items-center justify-center cursor-pointer">
<el-icon><Message /></el-icon>
<div class="ml-2">
建议反馈
</div>
</div>
</div>
<!-- adv --> <!-- adv -->
<div class="mt-3" v-if="info.advShow == 'true'"> <div class="mt-3" v-if="info.advShow == 'true'">
<el-carousel <el-carousel
@@ -0,0 +1,53 @@
<script setup lang="ts">
import { onMounted } from 'vue';
const props = defineProps({
title: {
type: String,
default: '描述'
}
})
onMounted(() => {
})
</script>
<template>
<!-- desc -->
<div class="mt-3 rounded-2xl p-4 tech-detail-section">
<el-divider content-position="left" class="tech-divider">{{ props.title }}</el-divider>
<div class="m-4">
<slot></slot>
</div>
</div>
</template>
<style scoped>
/* 科技感详情区块 */
.tech-detail-section {
background: linear-gradient(135deg, #ffffff 0%, #f8f9ff 100%);
position: relative;
overflow: hidden;
}
/* 科技感分割线 */
.tech-divider {
position: relative;
}
.tech-divider .el-divider__text {
background: linear-gradient(135deg, #ffffff 0%, #f8f9ff 100%);
padding: 0 16px;
font-weight: 600;
color: #333;
}
.tech-divider::before {
background: linear-gradient(90deg, #409EFF, transparent);
}
.tech-divider::after {
background: linear-gradient(90deg, transparent, #667eea);
}
</style>
-81
View File
@@ -1,81 +0,0 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import { copy } from '@/utils/string'
const info = reactive({
title: "色板",
palettes: [
{
theme: 'last summer day', //
item: ['#A7C5C5', '#DEE0D5', '#E2AC48', '#B96028', '#983C2D'],
},
{
theme: 'Hazy Summer Afternoon', //
item: ['#26C4A5', '#54D99F', '#ACE08C', '#E8E284', '#F2B652'],
},
{
theme: 'Summering', //
item: ['#FFBE4C', '#B33127', '#93EDFF', '#648FAD', '#FFFFFF'],
},
{
theme: 'Bare August — Footcare Essentials', //
item: ['#F2636F', '#F2858E', '#F2AEB4', '#F27999', '#F2D9D0'],
},
{
theme: 'Big Vilnius Summer Loto', //
item: ['#D90416', '#BF2A45', '#528C49', '#BF7B54', '#F28D8D'],
},
{
theme: 'Illustration Design', //
item: ['#D9D2D6', '#010326', '#4C5D73', '#808C65', '#F2E750'],
},
{
theme: 'Beautiful tropical beach with blue sky and white clouds abstract texture background', //
item: ['#04B2D9', '#CEECF2', '#04C4D9', '#15BFBF', '#D9D0C7'],
},
]
})
//copy
const copyRes = async (resStr: string) => {
copy(resStr)
}
</script>
<template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10">
<DetailHeader :title="info.title"></DetailHeader>
<div class="">
<div class="flex flex-col">
<div v-for="(item, index) in info.palettes" :key="index" class="flex flex-col mb-3">
<div class="flex h-28 rounded-lg overflow-hidden items-center">
<span
v-for="(color, index) in item.item"
:key="index"
class="w-1/5 h-full color-palette"
:style="'background-color:' + color + ';'"
@click="copyRes(color)"
>
<div class="color-text hidden flex items-center justify-center h-full w-full" :style="'color:' + color + ';'" >{{ color }}</div>
</span>
<!-- @change="(val: any) => (changeCheckBox(val, 3)) -->
</div>
<!-- <div class="">
<el-text>主题:{{ item.theme }}</el-text>
</div> -->
<el-divider v-if="index != item.item.length + 1"/>
</div>
</div>
<div></div>
</div>
</div>
</template>
<style scoped>
.color-palette:hover .color-text{
display: flex;
filter: grayscale(1) contrast(999) invert(1)
}
</style>
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
// import { copy } from '@/utils/string' // import { copy } from '@/utils/string'
const info = reactive({ const info = reactive({
title: "ASCII码表", title: "ASCII码表",
@@ -277,10 +278,10 @@ const tableDataNonStandard = reactive([
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div class=""> <div class="p-4 rounded-2xl bg-white">
<el-table :data="tableDataControlCodes" class="w-full"> <el-table :data="tableDataControlCodes" class="w-full">
<el-table-column label="ASCII 控制字符 (字符编码: 0-31)" align="center"> <el-table-column label="ASCII 控制字符 (字符编码: 0-31)" align="center">
<el-table-column prop="dec" label="十进制DEC" width="120" /> <el-table-column prop="dec" label="十进制DEC" width="120" />
@@ -311,6 +312,14 @@ const tableDataNonStandard = reactive([
</el-table-column> </el-table-column>
</el-table> </el-table>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
ASCIIAmerican Standard Code for Information Interchange美国信息交换标准代码是一种字符编码标准它使用一个字节8位二进制数来表示128个不同的字符这些字符包括33个控制字符具有特殊功能但不可直接显示的字符和95个可打印字符ASCII码表中的字符从0到127编号其中0代表空字符NULL1代表开始头SOH2代表正文开始STX依此类推ASCII码表的最小值是0代表空字符
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -0,0 +1,183 @@
<script setup lang="ts">
import { reactive,ref,onMounted } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { copy } from '@/utils/string'
import figlet from 'figlet';
const info = reactive({
title: "ASCII字形生成器",
})
figlet.defaults({ fontPath: '//unpkg.com/[email protected]/fonts/' });
const content = ref('Tools-Web')
const contentRes = ref('')
const fontStyle = ref('Big')
const options = ref([
{
value: 'Alpha',
label: 'Alpha',
},
{
value: 'Avatar',
label: 'Avatar',
},
{
value: 'Banner',
label: 'Banner',
},
{
value: 'Banner3-D',
label: 'Banner3-D',
},
{
value: 'Basic',
label: 'Basic',
},
{
value: 'Bear',
label: 'Bear',
},
{
value: 'Big Money-ne',
label: 'Big Money-ne',
},
{
value: 'Big',
label: 'Big',
},
{
value: 'Block',
label: 'Block',
},
{
value: 'Epic',
label: 'Epic',
},
{
value: 'Ghost',
label: 'Ghost',
},
{
value: 'Knob',
label: 'Knob',
},
{
value: 'Linux',
label: 'Linux',
},
{
value: 'Mini',
label: 'Mini',
},
{
value: 'Mirror',
label: 'Mirror',
},
{
value: 'Peaks',
label: 'Peaks',
},
{
value: 'Slant',
label: 'Slant',
},
{
value: 'Small',
label: 'Small',
},
{
value: 'Stellar',
label: 'Stellar',
},
{
value: 'Thin',
label: 'Thin',
},
{
value: 'Wow',
label: 'Wow',
},
])
const gen = () => {
figlet(content.value,
{
font: fontStyle.value,
width: 120,
horizontalLayout: "default",
verticalLayout: "default",
whitespaceBreak: true,
},
function (err, data) {
if (err) {
console.log(';----', err)
return;
}
contentRes.value = data ?? ''
})
}
//清空输入框
const clear = () => {
content.value = ''
contentRes.value = ''
}
onMounted(() => {
gen()
})
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white ">
<div class="mb-6">
<el-input v-model="content" :rows="4" type="textarea" placeholder="请输入内容"></el-input>
<div class="mt-3 flex items-center">
<div class="flex items-center mr-3 w-36">
<div class="w-10">
<el-text class="">风格</el-text>
</div>
<el-select
v-model="fontStyle"
size="default"
class="ml-2"
@change="gen"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<el-button type="primary" @click="gen()">生成</el-button>
<el-button type="primary" @click="copy(contentRes)">复制结果</el-button>
<el-button type="primary" @click="clear">清除</el-button>
</div>
</div>
<div>
<pre tabindex="0"><code>{{ contentRes }}</code></pre>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
好用的ASCII字形生成器输入框中输入需要生成的字母提供多种风格选择
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeMount, reactive,ref } from 'vue' import { onBeforeMount, reactive,ref } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
// import { ElMessage } from 'element-plus' // import { ElMessage } from 'element-plus'
import vueDanmaku from 'vue3-danmaku' //https://www.npmjs.com/package/vue3-danmaku import vueDanmaku from 'vue3-danmaku' //https://www.npmjs.com/package/vue3-danmaku
const info = reactive({ const info = reactive({
@@ -57,7 +58,7 @@ onBeforeMount(() => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<!-- 全屏 --> <!-- 全屏 -->
@@ -83,10 +84,10 @@ onBeforeMount(() => {
</template> </template>
</vue-danmaku> </vue-danmaku>
<div> <div class="p-4 rounded-2xl bg-white">
<div class="flex mb-2"> <div class="flex mb-2">
<el-text class="w-20">弹幕内容:</el-text> <el-text class="w-20">弹幕内容:</el-text>
<div class="w-72"><el-input v-model="info.content" type="textarea" rows="3"></el-input></div> <div class="w-72"><el-input v-model="info.content" type="textarea" :rows="3" placeholder="可在此输入弹幕内容"></el-input></div>
</div> </div>
<div class="flex mb-2"> <div class="flex mb-2">
@@ -114,6 +115,14 @@ onBeforeMount(() => {
<el-text>双击可退出弹幕</el-text> <el-text>双击可退出弹幕</el-text>
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
手持弹幕是一种新型的互动沟通工具可以方便地为各种户外活动演出嘉年华等活动增加趣味性和互动性手持弹幕具有轻便易携带易操作等优点可以让每个参与者都变成活动的一部分同时手持弹幕还可以通过预先编写的文本表情等形式表达参与者的情感和想法实现沟通互动在社交媒体时代手持弹幕的使用也带来了更广泛的社交效应增加了活动的互动性和传播度无论是举办方还是参与者手持弹幕都是一个非常有价值的互动工具
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
+144
View File
@@ -0,0 +1,144 @@
<script setup lang="ts">
import { ref } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { ElMessage } from 'element-plus'
import { copy } from '@/utils/string'
// 状态管理
const title = "Base64加解密工具"
const activeTab = ref('encode')
const encodeInput = ref('')
const encodeOutput = ref('')
const decodeInput = ref('')
const decodeOutput = ref('')
// 加密处理
const encodeText = () => {
if (!encodeInput.value) {
ElMessage.warning('请输入要加密的文本')
return
}
try {
encodeOutput.value = btoa(unescape(encodeURIComponent(encodeInput.value)))
ElMessage.success('加密成功')
} catch (error) {
ElMessage.error('加密失败:' + error)
}
}
// 解密处理
const decodeText = () => {
if (!decodeInput.value) {
ElMessage.warning('请输入要解密的Base64字符串')
return
}
try {
decodeOutput.value = decodeURIComponent(escape(atob(decodeInput.value)))
ElMessage.success('解密成功')
} catch (error) {
ElMessage.error('解密失败:请输入有效的Base64字符串')
}
}
// 复制结果
const copyEncodeResult = () => {
if (!encodeOutput.value) {
ElMessage.warning('没有可复制的内容')
return
}
copy(encodeOutput.value)
}
const copyDecodeResult = () => {
if (!decodeOutput.value) {
ElMessage.warning('没有可复制的内容')
return
}
copy(decodeOutput.value)
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<el-tabs v-model="activeTab">
<el-tab-pane label="加密" name="encode">
<div class="mb-4">
<el-input
v-model="encodeInput"
type="textarea"
:rows="6"
placeholder="请输入要加密的文本"
/>
</div>
<div class="mb-4">
<el-button type="primary" @click="encodeText">加密</el-button>
</div>
<div>
<el-input
v-model="encodeOutput"
type="textarea"
:rows="6"
placeholder="加密结果"
readonly
>
<template #append>
<el-button @click="copyEncodeResult">复制</el-button>
</template>
</el-input>
</div>
</el-tab-pane>
<el-tab-pane label="解密" name="decode">
<div class="mb-4">
<el-input
v-model="decodeInput"
type="textarea"
:rows="6"
placeholder="请输入要解密的Base64字符串"
/>
</div>
<div class="mb-4">
<el-button type="primary" @click="decodeText">解密</el-button>
</div>
<div>
<el-input
v-model="decodeOutput"
type="textarea"
:rows="6"
placeholder="解密结果"
readonly
>
<template #append>
<el-button @click="copyDecodeResult">复制</el-button>
</template>
</el-input>
</div>
</el-tab-pane>
</el-tabs>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线Base64加解密工具支持文本的Base64编码和解码可用于URL编码数据传输等场景
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,132 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { ElMessage } from 'element-plus'
import { copy } from '@/utils/string'
const title = "进制转换计算器"
// 状态管理
const inputValue = ref('')
const fromBase = ref(10)
const toBase = ref(2)
// 进制选项
const baseOptions = [
{ label: '2 (二进制)', value: 2 },
{ label: '8 (八进制)', value: 8 },
{ label: '10 (十进制)', value: 10 },
{ label: '16 (十六进制)', value: 16 },
]
// 转换结果
const convertedValue = computed(() => {
if (!inputValue.value) return ''
try {
// 先转换为十进制
const decimalValue = parseInt(inputValue.value, fromBase.value)
if (isNaN(decimalValue)) return '无效输入'
// 再转换为目标进制
if (toBase.value === 10) {
return decimalValue.toString()
} else if (toBase.value === 16) {
return decimalValue.toString(16).toUpperCase()
} else {
return decimalValue.toString(toBase.value)
}
} catch (error) {
return '转换失败'
}
})
// 复制结果
const copyResult = () => {
if (!convertedValue.value || convertedValue.value === '无效输入' || convertedValue.value === '转换失败') {
ElMessage.warning('没有可复制的内容')
return
}
copy(convertedValue.value)
}
// 清空输入
const clearInput = () => {
inputValue.value = ''
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<!-- 输入部分 -->
<div class="mb-4">
<el-input
v-model="inputValue"
placeholder="请输入要转换的数值"
class="mb-2"
/>
<div class="flex gap-4 items-center">
<div class="flex items-center gap-2">
<el-text class="">从进制</el-text>
<el-select v-model="fromBase" style="width: 140px;">
<el-option
v-for="option in baseOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</div>
<div class="flex items-center gap-2">
<el-text>到进制</el-text>
<el-select v-model="toBase" style="width: 140px;">
<el-option
v-for="option in baseOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="mb-4">
<el-button type="primary" @click="clearInput">清空</el-button>
</div>
<!-- 结果部分 -->
<div>
<el-input
:value="convertedValue"
placeholder="转换结果"
readonly
>
<template #append>
<el-button @click="copyResult">复制</el-button>
</template>
</el-input>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线进制转换计算器支持二进制八进制十进制十六进制等多种进制之间的相互转换
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,268 @@
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div class="tool-content">
<div class="calculator">
<div class="calculator-display">
<div class="display-text">{{ display }}</div>
<div class="display-history">{{ history }}</div>
</div>
<div class="calculator-buttons">
<div class="button-row">
<el-button type="info" @click="clear">C</el-button>
<el-button type="info" @click="backspace"><</el-button>
<el-button type="info" @click="append('%')">%</el-button>
<el-button type="info" @click="append('/')">÷</el-button>
</div>
<div class="button-row">
<el-button @click="append('7')">7</el-button>
<el-button @click="append('8')">8</el-button>
<el-button @click="append('9')">9</el-button>
<el-button type="info" @click="append('*')">×</el-button>
</div>
<div class="button-row">
<el-button @click="append('4')">4</el-button>
<el-button @click="append('5')">5</el-button>
<el-button @click="append('6')">6</el-button>
<el-button type="info" @click="append('-')">-</el-button>
</div>
<div class="button-row">
<el-button @click="append('1')">1</el-button>
<el-button @click="append('2')">2</el-button>
<el-button @click="append('3')">3</el-button>
<el-button type="info" @click="append('+')">+</el-button>
</div>
<div class="button-row">
<el-button @click="append('0')">0</el-button>
<el-button @click="append('.')">.</el-button>
<el-button type="success" @click="calculate">=</el-button>
</div>
</div>
</div>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线简易计算器支持基本的加减乘除和取模运算具有历史记录显示功能可用于日常简单计算
</el-text>
</ToolDetail>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
const title = "简易计算器"
const display = ref('0');
const history = ref('');
const firstOperand = ref<number | null>(null);
const operator = ref<string | null>(null);
const waitingForSecondOperand = ref(false);
// 清空
const clear = () => {
display.value = '0';
history.value = '';
firstOperand.value = null;
operator.value = null;
waitingForSecondOperand.value = false;
};
// 退格
const backspace = () => {
if (waitingForSecondOperand.value) {
return;
}
if (display.value.length > 1) {
display.value = display.value.slice(0, -1);
} else {
display.value = '0';
}
};
// 添加数字或操作符
const append = (value: string) => {
if (['+', '-', '*', '/', '%'].includes(value)) {
setOperator(value);
return;
}
if (waitingForSecondOperand.value) {
display.value = value;
waitingForSecondOperand.value = false;
return;
}
if (value === '.') {
if (!display.value.includes('.')) {
display.value += value;
}
return;
}
if (display.value === '0' && value !== '.') {
display.value = value;
} else {
display.value += value;
}
};
// 计算
const calculate = () => {
if (operator.value === null || firstOperand.value === null) {
return;
}
const secondOperand = parseFloat(display.value);
let result: number;
switch (operator.value) {
case '+':
result = firstOperand.value + secondOperand;
break;
case '-':
result = firstOperand.value - secondOperand;
break;
case '*':
result = firstOperand.value * secondOperand;
break;
case '/':
if (secondOperand === 0) {
display.value = 'Error';
history.value = '';
firstOperand.value = null;
operator.value = null;
waitingForSecondOperand.value = false;
return;
}
result = firstOperand.value / secondOperand;
break;
case '%':
result = firstOperand.value % secondOperand;
break;
default:
return;
}
// 处理结果显示
if (Number.isInteger(result)) {
display.value = result.toString();
} else {
display.value = result.toFixed(2);
}
history.value = `${firstOperand.value} ${operator.value} ${secondOperand} =`;
firstOperand.value = result;
operator.value = null;
waitingForSecondOperand.value = true;
};
// 监听操作符点击
const setOperator = (op: string) => {
if (operator.value !== null) {
calculate();
}
firstOperand.value = parseFloat(display.value);
operator.value = op;
waitingForSecondOperand.value = true;
history.value = `${firstOperand.value} ${op}`;
};
</script>
<style scoped>
.tool-content {
display: flex;
justify-content: center;
align-items: center;
padding: 20px 0;
}
.calculator {
width: 100%;
max-width: 300px;
border: 1px solid #e4e7ed;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
}
.calculator-display {
background-color: #f5f7fa;
padding: 20px;
text-align: right;
border-bottom: 1px solid #e4e7ed;
}
.display-text {
font-size: 24px;
font-weight: bold;
color: #333;
margin-bottom: 5px;
}
.display-history {
font-size: 14px;
color: #999;
}
.calculator-buttons {
display: flex;
flex-direction: column;
}
.button-row {
display: flex;
}
.button-row .el-button {
flex: 1;
height: 60px;
font-size: 18px;
margin: 0;
border-radius: 0;
border-right: 1px solid #e4e7ed;
border-bottom: 1px solid #e4e7ed;
}
.button-row .el-button:last-child {
border-right: none;
}
.button-row:last-child .el-button {
border-bottom: none;
}
.button-row .el-button:nth-child(1) {
border-left: none;
}
.button-row .el-button.success {
flex: 2;
}
@media (max-width: 768px) {
.calculator {
max-width: 100%;
}
.button-row .el-button {
height: 50px;
font-size: 16px;
}
}
</style>
+491
View File
@@ -0,0 +1,491 @@
<script setup lang="ts">
import {ref, reactive, onMounted } from 'vue'
import Spreadsheet from 'x-data-spreadsheet'
import 'x-data-spreadsheet/dist/locale/zh-cn';
import { UploadProps,UploadRawFile,genFileId } from 'element-plus'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
// import { copy } from '@/utils/string'
import { toEchartsData, toSpreadsheetData } from '@/utils/echarts'
import * as echarts from 'echarts'
import * as XLSX from 'xlsx'
const info = reactive({
title: "柱状图",
})
const chartDom = ref<HTMLElement|null>()
const myChart = ref<echarts.ECharts>()
const dataFileRef = ref()
const setOptionName = ref(1)
//缩放比例
const sacleSize = ref(100)
//画布宽高
const widthCanvas = ref(720)
const heightCanvas = ref(400)
//下载文件类型
const downType = ref('1')
//图形属性颜色
const attrColor = ref('#5470c6')
//标题位置
const titlePos = ref('center')
//标题
const title = ref('Tools-Web')
//副标题
const subTitle = ref('在线图表制作工具')
//显示标题 - 开关
const titleSwitch = ref(true)
//显示副标题 - 开关
const subTitleSwitch = ref(true)
/** 水印 */
const watermarkSwitch = ref(false) //开关
const waterMarkText = ref('Tools-Web');
//创建水印
const createWatermark = () => {
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
canvas.width = canvas.height = 100;
ctx!.textAlign = 'center';
ctx!.textBaseline = 'middle';
ctx!.globalAlpha = 0.08;
ctx!.font = '20px Microsoft Yahei';
ctx!.translate(50, 50);
ctx!.rotate(-Math.PI / 4);
ctx!.fillText(waterMarkText.value, 0, 0);
return canvas
}
//操作图表
const canvasHandle = (type) => {
// let element = document.getElementById('main')
let element = chartDom.value
switch(type) {
case "scale":
//缩放画布
let scale = sacleSize.value / 100
element!.style.transform = `scale(${scale})`
break
case "size":
//图表尺寸
element!.style.width = widthCanvas.value + 'px';
element!.style.height = heightCanvas.value + 'px';
reloadCanvas()
break
case "title":
let fakerTitle = ''
let fakerSubTitle = ''
//修改标题相关配置
if (titleSwitch.value === true) {
fakerTitle = title.value
}
if (subTitleSwitch.value === true) {
fakerSubTitle = subTitle.value
}
myChart.value?.setOption({
title: {
text: fakerTitle,
subtext: fakerSubTitle,
left: titlePos.value
}
})
break
case "color":
//图表属性颜色
myChart.value?.setOption({
series: [
{
itemStyle: {
color: attrColor.value
}
}
]
})
break;
case "watermark":
//水印
if (watermarkSwitch.value === true) {
myChart.value?.setOption({
backgroundColor: {
image: createWatermark(),
}
})
} else {
myChart.value?.setOption({
backgroundColor: '#fff'
})
}
break;
case "data":
//更新数据
myChart.value?.setOption({
xAxis: {
data: colunmData.value,
},
series: [
{
data: valueData.value,
}
]
})
break;
}
}
const handleChange = () => {
}
//数据
const colunmData = ref(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']);
const valueData = ref(['23', '24', '18', '25', '27', '28', '25']);
//选项
const option = {
backgroundColor: '#fff',
// backgroundColor: {
// // type: 'pattern',
// // image: canvas,
// // repeat: 'repeat'
// },
title: {
text: title.value,
subtext: subTitle.value,
left: titlePos.value
},
xAxis: {
data: colunmData.value,
},
yAxis: {},
series: [
{
type: 'bar',
data: valueData.value,
itemStyle: {
color: attrColor.value
}
}
],
tooltip: {}
};
//重新加载画布
const reloadCanvas = () => {
myChart.value = echarts.init(chartDom.value)
myChart.value.clear();
myChart.value.resize({
width: widthCanvas.value,
height: heightCanvas.value
})
myChart.value.setOption(option)
}
//下载echarts图表图片
const downEchartsImg = () => {
let ext = downType.value == '1' ? 'png' : 'jpeg'
let imgUrl = myChart.value?.getDataURL({
type: ext as "png" | 'jpeg',
pixelRatio: 2,
})
if (imgUrl != undefined) {
var downloadLink = document.createElement('a');
downloadLink.href = imgUrl;
downloadLink.download = 'echart.' + ext;
downloadLink.click();
}
}
/** 上传、编辑数据 */
//抽屉开关
const drawer = ref(false)
const rowsData = ref({})
const editData = () => {
if (drawer.value == true) {
drawer.value = false
} else {
drawer.value = true
/** 在线excel */
Spreadsheet.locale('zh-cn', (window.x_spreadsheet as any).$messages['zh-cn']);
//const sheet = new Spreadsheet("#x-spreadsheet", {
new Spreadsheet("#x-spreadsheet", {
showToolbar: false, //隐藏顶部工具栏
showBottomBar: false,//隐藏底部工具栏,
view: {
height: () => document.documentElement.clientHeight / 2,
width: () => document.documentElement.clientWidth,
}
})
.loadData({
//样式
styles: [
{
bgcolor: '#f4f5f8',
textwrap: true,
color: '#900b09',
border: {
top: ['thin', '#0366d6'],
bottom: ['thin', '#0366d6'],
right: ['thin', '#0366d6'],
left: ['thin', '#0366d6'],
},
},
],
//数据
rows: rowsData.value
}) // load data
.change(data => {
//表格数据改变后触发
//规则: 获取第一列和第二列的数据
let tmp = toEchartsData(data)
colunmData.value = tmp[0]
valueData.value = tmp[1]
canvasHandle('data')
});
// sheet.validate()
}
}
//上传数据文件
const fileList = ref()
const updateDataFile = async (params) => {
const _file = params.file;
const fileReader = new FileReader();
fileReader.onload = (ev) => {
try {
if (!ev.target) {
return
}
const data = ev.target.result;
const workbook = XLSX.read(data, {
type: 'binary'
});
let useCount = 0;
let tmpColumnData:any[] = [];
let tmpValueData:any[] = [];
for (let sheet in workbook.Sheets) {
//这里只需要第一个sheet
if (useCount > 0) {
continue;
}
//循环读取每个文件
const sheetArray = XLSX.utils.sheet_to_json(workbook.Sheets[sheet], {header: ['0', '1']});
//若当前sheet没有数据,则continue
if(sheetArray.length == 0){
continue;
}
for(let item in sheetArray){
if (sheetArray[item] != undefined) {
let tmp1 = sheetArray as []
tmpColumnData.push(tmp1[item][0])
tmpValueData.push(tmp1[item][1])
}
}
useCount++
}
//更新数据
colunmData.value = tmpColumnData
valueData.value = tmpValueData
//更新图表
canvasHandle('data')
//更新表格
rowsData.value = toSpreadsheetData([
colunmData, valueData
])
} catch (e) {
console.log('error')
console.log(e)
}
}
fileReader.readAsArrayBuffer(_file)
}
//当超出限制时,执行的钩子函数
//这里覆盖前一个文件
const handleExceed: UploadProps['onExceed'] = (files) => {
dataFileRef.value!.clearFiles()
const file = files[0] as UploadRawFile
file.uid = genFileId()
dataFileRef.value!.handleStart(file)
dataFileRef.value!.submit()
}
//copy
// const copyRes = async (resStr: string) => {
// copy(resStr)
// }
onMounted(() => {
//init echart dom
chartDom.value = document.getElementById('main')
//设置画布宽高
canvasHandle('size')
//reload canvas
reloadCanvas()
//数据格式转换成
rowsData.value = toSpreadsheetData([
colunmData, valueData
])
// console.log(rowsData.value)
})
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl flex">
<div class="w-4/6">
<div class="flex flex-row-reverse mb-4">
<el-input-number v-model="sacleSize" :min="1" :max="100" :step="5" @change="canvasHandle('scale')" step-strictly/>
<el-text>缩放</el-text>
</div>
<!-- <div class="flex justify-center items-center"> -->
<div class="flex justify-center items-center max-h-[500px] max-w-[1000px] overflow-auto">
<div id="main" class="bg-white"></div>
</div>
</div>
<div class="w-2/6 bg-white ml-3 rounded-lg p-3">
<div class="mb-3">
<el-button class="w-full mb-3" type="primary" size="large" @click="downEchartsImg">下载图表</el-button>
<div class="flex items-center ml-1">
<el-text>文件类型</el-text>
<el-radio-group v-model="downType" class="ml-4">
<el-radio size="large" label="1">PNG</el-radio>
<el-radio size="large" label="2">JPEG</el-radio>
</el-radio-group>
</div>
<div class="flex w-full">
<el-tooltip
class="box-item"
effect="dark"
content="支持上传:xls, xlsx, csv文件"
placement="top-start"
>
<el-button class="w-1/2">
<el-upload
v-model:file-list="fileList"
class="dataFileRef flex"
ref="dataFileRef"
accept=".xls,.xlsx,.csv"
:http-request="updateDataFile"
:on-exceed="handleExceed"
:limit="1"
>
上传数据
</el-upload>
</el-button>
</el-tooltip>
<el-button class="w-1/2" @click="editData">编辑数据</el-button>
</div>
</div>
<el-collapse v-model="setOptionName" @change="handleChange" accordion>
<el-collapse-item title="画布设置" name="1">
<div class="flex">
<el-text class="w-16 text-right">宽度</el-text>
<el-input-number
v-model="widthCanvas"
:min="1"
:max="4000"
:step="10"
controls-position="right"
class="h-8 w-60 max-w-[60%] ml-3"
@change="canvasHandle('size')"
/>
</div>
<div class="flex mt-3">
<el-text class="w-16 text-right">高度</el-text>
<el-input-number
v-model="heightCanvas"
:min="1"
:max="4000"
:step="10"
controls-position="right"
class="h-8 w-60 max-w-[60%] ml-3"
@change="canvasHandle('size')"
/>
</div>
</el-collapse-item>
<el-collapse-item title="标题设置" name="2">
<div class="flex">
<el-text class="w-16 text-right">位置</el-text>
<el-radio-group v-model="titlePos" class="ml-3" @change="canvasHandle('title')">
<el-radio-button label="left" value="left"></el-radio-button>
<el-radio-button label="center" value="center"></el-radio-button>
<el-radio-button label="right" value="right"></el-radio-button>
</el-radio-group>
</div>
<el-divider content-position="left" class="pl-10">标题</el-divider>
<div class="flex">
<el-text class="w-16 text-right">显示</el-text>
<el-switch v-model="titleSwitch" class="ml-3" @change="canvasHandle('title')"/>
</div>
<div class="flex">
<el-text class="w-16 text-right">标题</el-text>
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="title" @blur="canvasHandle('title')"></el-input>
</div>
<el-divider content-position="left">副标题</el-divider>
<div class="flex">
<el-text class="w-16 text-right">显示</el-text>
<el-switch v-model="subTitleSwitch" class="ml-3" @change="canvasHandle('title')"/>
</div>
<div class="flex">
<el-text class="w-16 text-right">副标题</el-text>
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="subTitle" @blur="canvasHandle('title')"></el-input>
</div>
</el-collapse-item>
<el-collapse-item title="图形属性" name="3">
<div class="flex">
<el-text class="w-16 text-right">颜色</el-text>
<el-color-picker v-model="attrColor" class="ml-3" @change="canvasHandle('color')"/>
</div>
</el-collapse-item>
<el-collapse-item title="水印设置" name="4">
<div class="flex">
<el-text class="w-16 text-right">显示</el-text>
<el-switch v-model="watermarkSwitch" class="ml-3" @change="canvasHandle('watermark')"/>
</div>
<div class="flex">
<el-text class="w-16 text-right">水印内容</el-text>
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="waterMarkText" @change="canvasHandle('watermark')"></el-input>
</div>
</el-collapse-item>
</el-collapse>
</div>
</div>
<el-drawer id="x-spreadsheet" v-model="drawer" direction="btt" custom-class="sheet" style="">
</el-drawer>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线图表制作工具在线制作柱状图<br>
在线编辑表格生成柱状图支持png和jpeg格式导出<br>
柱状图是采用垂直柱子,即矩形块的宽度一样,用高度来代表数值大小<br>
能让使用者更为直观地看到不同类别数据或不同时间阶段的比较情况及发展趋势通常利用于较小的数据集分析<br>
无需任何基础即可上手并且支持超全的自定义配置轻松实现你的个性化图表需求<br>
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
:deep(.el-drawer__body){
display: none !important;
}
:deep(.el-drawer__header){
display: none !important;
margin-bottom: 0 !important;
}
:deep(.el-drawer){
height: 50% !important;
}
:deep(.el-upload-list){
display: none !important;
}
</style>
+490
View File
@@ -0,0 +1,490 @@
<script setup lang="ts">
import {ref, reactive, onMounted } from 'vue'
import Spreadsheet from 'x-data-spreadsheet'
import 'x-data-spreadsheet/dist/locale/zh-cn';
import { UploadProps,UploadRawFile,genFileId } from 'element-plus'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
// import { copy } from '@/utils/string'
import { toEchartsData, toSpreadsheetData } from '@/utils/echarts'
import * as echarts from 'echarts'
import * as XLSX from 'xlsx'
const info = reactive({
title: "折线图",
})
const chartDom = ref<HTMLElement|null>()
const myChart = ref<echarts.ECharts>()
const dataFileRef = ref()
const setOptionName = ref(1)
//缩放比例
const sacleSize = ref(100)
//画布宽高
const widthCanvas = ref(720)
const heightCanvas = ref(400)
//下载文件类型
const downType = ref('1')
//图形属性颜色
const attrColor = ref('#5470c6')
//标题位置
const titlePos = ref('center')
//标题
const title = ref('Tools-Web')
//副标题
const subTitle = ref('在线图表制作工具')
//显示标题 - 开关
const titleSwitch = ref(true)
//显示副标题 - 开关
const subTitleSwitch = ref(true)
/** 水印 */
const watermarkSwitch = ref(false) //开关
const waterMarkText = ref('Tools-Web');
//创建水印
const createWatermark = () => {
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
canvas.width = canvas.height = 100;
ctx!.textAlign = 'center';
ctx!.textBaseline = 'middle';
ctx!.globalAlpha = 0.08;
ctx!.font = '20px Microsoft Yahei';
ctx!.translate(50, 50);
ctx!.rotate(-Math.PI / 4);
ctx!.fillText(waterMarkText.value, 0, 0);
return canvas
}
//操作图表
const canvasHandle = (type) => {
// let element = document.getElementById('main')
let element = chartDom.value
switch(type) {
case "scale":
//缩放画布
let scale = sacleSize.value / 100
element!.style.transform = `scale(${scale})`
break
case "size":
//图表尺寸
element!.style.width = widthCanvas.value + 'px';
element!.style.height = heightCanvas.value + 'px';
reloadCanvas()
break
case "title":
let fakerTitle = ''
let fakerSubTitle = ''
//修改标题相关配置
if (titleSwitch.value === true) {
fakerTitle = title.value
}
if (subTitleSwitch.value === true) {
fakerSubTitle = subTitle.value
}
myChart.value?.setOption({
title: {
text: fakerTitle,
subtext: fakerSubTitle,
left: titlePos.value
}
})
break
case "color":
//图表属性颜色
myChart.value?.setOption({
series: [
{
itemStyle: {
color: attrColor.value
}
}
]
})
break;
case "watermark":
//水印
if (watermarkSwitch.value === true) {
myChart.value?.setOption({
backgroundColor: {
image: createWatermark(),
}
})
} else {
myChart.value?.setOption({
backgroundColor: '#fff'
})
}
break;
case "data":
//更新数据
myChart.value?.setOption({
xAxis: {
data: colunmData.value,
},
series: [
{
data: valueData.value,
}
]
})
break;
}
}
const handleChange = () => {
}
//数据
const colunmData = ref(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']);
const valueData = ref(['23', '24', '18', '25', '27', '28', '25']);
//选项
const option = {
backgroundColor: '#fff',
// backgroundColor: {
// // type: 'pattern',
// // image: canvas,
// // repeat: 'repeat'
// },
title: {
text: title.value,
subtext: subTitle.value,
left: titlePos.value
},
xAxis: {
data: colunmData.value,
},
yAxis: {},
series: [
{
type: 'line',
data: valueData.value,
itemStyle: {
color: attrColor.value
}
}
],
tooltip: {}
};
//重新加载画布
const reloadCanvas = () => {
myChart.value = echarts.init(chartDom.value)
myChart.value.clear();
myChart.value.resize({
width: widthCanvas.value,
height: heightCanvas.value
})
myChart.value.setOption(option)
}
//下载echarts图表图片
const downEchartsImg = () => {
let ext = downType.value == '1' ? 'png' : 'jpeg'
let imgUrl = myChart.value?.getDataURL({
type: ext as "png" | 'jpeg',
pixelRatio: 2,
})
if (imgUrl != undefined) {
var downloadLink = document.createElement('a');
downloadLink.href = imgUrl;
downloadLink.download = 'echart.' + ext;
downloadLink.click();
}
}
/** 上传、编辑数据 */
//抽屉开关
const drawer = ref(false)
const rowsData = ref({})
const editData = () => {
if (drawer.value == true) {
drawer.value = false
} else {
drawer.value = true
/** 在线excel */
Spreadsheet.locale('zh-cn', (window.x_spreadsheet as any).$messages['zh-cn']);
//const sheet = new Spreadsheet("#x-spreadsheet", {
new Spreadsheet("#x-spreadsheet", {
showToolbar: false, //隐藏顶部工具栏
showBottomBar: false,//隐藏底部工具栏,
view: {
height: () => document.documentElement.clientHeight / 2,
width: () => document.documentElement.clientWidth,
}
})
.loadData({
//样式
styles: [
{
bgcolor: '#f4f5f8',
textwrap: true,
color: '#900b09',
border: {
top: ['thin', '#0366d6'],
bottom: ['thin', '#0366d6'],
right: ['thin', '#0366d6'],
left: ['thin', '#0366d6'],
},
},
],
//数据
rows: rowsData.value
}) // load data
.change(data => {
//表格数据改变后触发
//规则: 获取第一列和第二列的数据
let tmp = toEchartsData(data)
colunmData.value = tmp[0]
valueData.value = tmp[1]
canvasHandle('data')
});
// sheet.validate()
}
}
//上传数据文件
const fileList = ref()
const updateDataFile = async (params) => {
const _file = params.file;
const fileReader = new FileReader();
fileReader.onload = (ev) => {
try {
if (!ev.target) {
return
}
const data = ev.target.result;
const workbook = XLSX.read(data, {
type: 'binary'
});
let useCount = 0;
let tmpColumnData:any[] = [];
let tmpValueData:any[] = [];
for (let sheet in workbook.Sheets) {
//这里只需要第一个sheet
if (useCount > 0) {
continue;
}
//循环读取每个文件
const sheetArray = XLSX.utils.sheet_to_json(workbook.Sheets[sheet], {header: ['0', '1']});
//若当前sheet没有数据,则continue
if(sheetArray.length == 0){
continue;
}
for(let item in sheetArray){
if (sheetArray[item] != undefined) {
let tmp1 = sheetArray as []
tmpColumnData.push(tmp1[item][0])
tmpValueData.push(tmp1[item][1])
}
}
useCount++
}
//更新数据
colunmData.value = tmpColumnData
valueData.value = tmpValueData
//更新图表
canvasHandle('data')
//更新表格
rowsData.value = toSpreadsheetData([
colunmData, valueData
])
} catch (e) {
console.log('error')
console.log(e)
}
}
fileReader.readAsArrayBuffer(_file)
}
//当超出限制时,执行的钩子函数
//这里覆盖前一个文件
const handleExceed: UploadProps['onExceed'] = (files) => {
dataFileRef.value!.clearFiles()
const file = files[0] as UploadRawFile
file.uid = genFileId()
dataFileRef.value!.handleStart(file)
dataFileRef.value!.submit()
}
//copy
// const copyRes = async (resStr: string) => {
// copy(resStr)
// }
onMounted(() => {
//init echart dom
chartDom.value = document.getElementById('main')
//设置画布宽高
canvasHandle('size')
//reload canvas
reloadCanvas()
//数据格式转换成
rowsData.value = toSpreadsheetData([
colunmData, valueData
])
// console.log(rowsData.value)
})
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl flex">
<div class="w-4/6">
<div class="flex flex-row-reverse mb-4">
<el-input-number v-model="sacleSize" :min="1" :max="100" :step="5" @change="canvasHandle('scale')" step-strictly/>
<el-text>缩放</el-text>
</div>
<!-- <div class="flex justify-center items-center"> -->
<div class="flex justify-center items-center max-h-[500px] max-w-[1000px] overflow-auto">
<div id="main" class="bg-white"></div>
</div>
</div>
<div class="w-2/6 bg-white ml-3 rounded-lg p-3">
<div class="mb-3">
<el-button class="w-full mb-3" type="primary" size="large" @click="downEchartsImg">下载图表</el-button>
<div class="flex items-center ml-1">
<el-text>文件类型</el-text>
<el-radio-group v-model="downType" class="ml-4">
<el-radio size="large" label="1">PNG</el-radio>
<el-radio size="large" label="2">JPEG</el-radio>
</el-radio-group>
</div>
<div class="flex w-full">
<el-tooltip
class="box-item"
effect="dark"
content="支持上传:xls, xlsx, csv文件"
placement="top-start"
>
<el-button class="w-1/2">
<el-upload
v-model:file-list="fileList"
class="dataFileRef flex"
ref="dataFileRef"
accept=".xls,.xlsx,.csv"
:http-request="updateDataFile"
:on-exceed="handleExceed"
:limit="1"
>
上传数据
</el-upload>
</el-button>
</el-tooltip>
<el-button class="w-1/2" @click="editData">编辑数据</el-button>
</div>
</div>
<el-collapse v-model="setOptionName" @change="handleChange" accordion>
<el-collapse-item title="画布设置" name="1">
<div class="flex">
<el-text class="w-16 text-right">宽度</el-text>
<el-input-number
v-model="widthCanvas"
:min="1"
:max="4000"
:step="10"
controls-position="right"
class="h-8 w-60 max-w-[60%] ml-3"
@change="canvasHandle('size')"
/>
</div>
<div class="flex mt-3">
<el-text class="w-16 text-right">高度</el-text>
<el-input-number
v-model="heightCanvas"
:min="1"
:max="4000"
:step="10"
controls-position="right"
class="h-8 w-60 max-w-[60%] ml-3"
@change="canvasHandle('size')"
/>
</div>
</el-collapse-item>
<el-collapse-item title="标题设置" name="2">
<div class="flex">
<el-text class="w-16 text-right">位置</el-text>
<el-radio-group v-model="titlePos" class="ml-3" @change="canvasHandle('title')">
<el-radio-button label="left" value="left"></el-radio-button>
<el-radio-button label="center" value="center"></el-radio-button>
<el-radio-button label="right" value="right"></el-radio-button>
</el-radio-group>
</div>
<el-divider content-position="left" class="pl-10">标题</el-divider>
<div class="flex">
<el-text class="w-16 text-right">显示</el-text>
<el-switch v-model="titleSwitch" class="ml-3" @change="canvasHandle('title')"/>
</div>
<div class="flex">
<el-text class="w-16 text-right">标题</el-text>
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="title" @blur="canvasHandle('title')"></el-input>
</div>
<el-divider content-position="left">副标题</el-divider>
<div class="flex">
<el-text class="w-16 text-right">显示</el-text>
<el-switch v-model="subTitleSwitch" class="ml-3" @change="canvasHandle('title')"/>
</div>
<div class="flex">
<el-text class="w-16 text-right">副标题</el-text>
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="subTitle" @blur="canvasHandle('title')"></el-input>
</div>
</el-collapse-item>
<el-collapse-item title="图形属性" name="3">
<div class="flex">
<el-text class="w-16 text-right">颜色</el-text>
<el-color-picker v-model="attrColor" class="ml-3" @change="canvasHandle('color')"/>
</div>
</el-collapse-item>
<el-collapse-item title="水印设置" name="4">
<div class="flex">
<el-text class="w-16 text-right">显示</el-text>
<el-switch v-model="watermarkSwitch" class="ml-3" @change="canvasHandle('watermark')"/>
</div>
<div class="flex">
<el-text class="w-16 text-right">水印内容</el-text>
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="waterMarkText" @change="canvasHandle('watermark')"></el-input>
</div>
</el-collapse-item>
</el-collapse>
</div>
</div>
<el-drawer id="x-spreadsheet" v-model="drawer" direction="btt" class="sheet" style="">
</el-drawer>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
折线图为日常最常用的图表之一当必须呈现与时间相关的数据时它是首选它最适合分析趋势<br>
在线图表制作工具在线制作折线图<br>
支持导入表格并在线编辑表格生成折线图支持png和jpeg格式导出<br>
支持超全的自定义配置轻松实现你的个性化图表需求<br>
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
:deep(.el-drawer__body){
display: none !important;
}
:deep(.el-drawer__header){
display: none !important;
margin-bottom: 0 !important;
}
:deep(.el-drawer){
height: 50% !important;
}
:deep(.el-upload-list){
display: none !important;
}
</style>
+499
View File
@@ -0,0 +1,499 @@
<script setup lang="ts">
import {ref, reactive, onMounted } from 'vue'
import Spreadsheet from 'x-data-spreadsheet'
import 'x-data-spreadsheet/dist/locale/zh-cn';
import { UploadProps,UploadRawFile,genFileId } from 'element-plus'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
// import { copy } from '@/utils/string'
import { toEchartsPieData, toSpreadsheetData, tranObjAndColumn } from '@/utils/echarts'
import * as echarts from 'echarts'
import * as XLSX from 'xlsx'
const info = reactive({
title: "饼图",
})
const chartDom = ref<HTMLElement|null>()
const myChart = ref<echarts.ECharts>()
const dataFileRef = ref()
const setOptionName = ref(1)
//缩放比例
const sacleSize = ref(100)
//画布宽高
const widthCanvas = ref(720)
const heightCanvas = ref(400)
//下载文件类型
const downType = ref('1')
//图形属性颜色
const attrColor = ref('#5470c6')
//标题位置
const titlePos = ref('center')
//标题
const title = ref('Tools-Web')
//副标题
const subTitle = ref('在线图表制作工具')
//显示标题 - 开关
const titleSwitch = ref(true)
//显示副标题 - 开关
const subTitleSwitch = ref(true)
/** 水印 */
const watermarkSwitch = ref(false) //开关
const waterMarkText = ref('Tools-Web');
//创建水印
const createWatermark = () => {
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
canvas.width = canvas.height = 100;
ctx!.textAlign = 'center';
ctx!.textBaseline = 'middle';
ctx!.globalAlpha = 0.08;
ctx!.font = '20px Microsoft Yahei';
ctx!.translate(50, 50);
ctx!.rotate(-Math.PI / 4);
ctx!.fillText(waterMarkText.value, 0, 0);
return canvas
}
//操作图表
const canvasHandle = (type) => {
// let element = document.getElementById('main')
let element = chartDom.value
switch(type) {
case "scale":
//缩放画布
let scale = sacleSize.value / 100
element!.style.transform = `scale(${scale})`
break
case "size":
//图表尺寸
element!.style.width = widthCanvas.value + 'px';
element!.style.height = heightCanvas.value + 'px';
reloadCanvas()
break
case "title":
let fakerTitle = ''
let fakerSubTitle = ''
//修改标题相关配置
if (titleSwitch.value === true) {
fakerTitle = title.value
}
if (subTitleSwitch.value === true) {
fakerSubTitle = subTitle.value
}
myChart.value?.setOption({
title: {
text: fakerTitle,
subtext: fakerSubTitle,
left: titlePos.value
}
})
break
case "color":
//图表属性颜色
myChart.value?.setOption({
series: [
{
itemStyle: {
color: attrColor.value
}
}
]
})
break;
case "watermark":
//水印
if (watermarkSwitch.value === true) {
myChart.value?.setOption({
backgroundColor: {
image: createWatermark(),
}
})
} else {
myChart.value?.setOption({
backgroundColor: '#fff'
})
}
break;
case "data":
//更新数据
myChart.value?.setOption({
series: [
{
data: seriesData.value,
}
]
})
break;
}
}
const handleChange = () => {
}
//数据
const colunmData = ref(['Search Engine', 'Direct', 'Email', 'Union Ads', 'Video Ads'] as string[]);
const valueData = ref([1048, 735, 580, 484, 300] as number[]);
const seriesData = ref([] as Object[])
//选项
const option = {
backgroundColor: '#fff',
// backgroundColor: {
// // type: 'pattern',
// // image: canvas,
// // repeat: 'repeat'
// },
title: {
text: title.value,
subtext: subTitle.value,
left: titlePos.value
},
series: [
{
type: 'pie',
radius: '50%',
data: seriesData.value,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
},
],
tooltip: {
// trigger: 'item'
},
};
//重新加载画布
const reloadCanvas = () => {
myChart.value = echarts.init(chartDom.value)
myChart.value.clear();
myChart.value.resize({
width: widthCanvas.value,
height: heightCanvas.value
})
myChart.value.setOption(option)
//加载数据
canvasHandle('data')
}
//下载echarts图表图片
const downEchartsImg = () => {
let ext = downType.value == '1' ? 'png' : 'jpeg'
let imgUrl = myChart.value?.getDataURL({
type: ext as "png" | 'jpeg',
pixelRatio: 2,
})
if (imgUrl != undefined) {
var downloadLink = document.createElement('a');
downloadLink.href = imgUrl;
downloadLink.download = 'echart.' + ext;
downloadLink.click();
}
}
/** 上传、编辑数据 */
//抽屉开关
const drawer = ref(false)
const rowsData = ref({})
const editData = () => {
if (drawer.value == true) {
drawer.value = false
} else {
drawer.value = true
/** 在线excel */
Spreadsheet.locale('zh-cn', (window.x_spreadsheet as any).$messages['zh-cn']);
//const sheet = new Spreadsheet("#x-spreadsheet", {
new Spreadsheet("#x-spreadsheet", {
showToolbar: false, //隐藏顶部工具栏
showBottomBar: false,//隐藏底部工具栏,
view: {
height: () => document.documentElement.clientHeight / 2,
width: () => document.documentElement.clientWidth,
}
})
.loadData({
//样式
styles: [
{
bgcolor: '#f4f5f8',
textwrap: true,
color: '#900b09',
border: {
top: ['thin', '#0366d6'],
bottom: ['thin', '#0366d6'],
right: ['thin', '#0366d6'],
left: ['thin', '#0366d6'],
},
},
],
//数据
rows: rowsData.value
}) // load data
.change(data => {
//表格数据改变后触发
//规则: 获取第一列和第二列的数据
seriesData.value = toEchartsPieData(data)
canvasHandle('data')
});
// sheet.validate()
}
}
//上传数据文件
const fileList = ref()
const updateDataFile = async (params) => {
const _file = params.file;
const fileReader = new FileReader();
fileReader.onload = (ev) => {
try {
if (!ev.target) {
return
}
const data = ev.target.result;
const workbook = XLSX.read(data, {
type: 'binary'
});
let useCount = 0;
let tmpColumnData:any[] = [];
let tmpValueData:any[] = [];
for (let sheet in workbook.Sheets) {
//这里只需要第一个sheet
if (useCount > 0) {
continue;
}
//循环读取每个文件
const sheetArray = XLSX.utils.sheet_to_json(workbook.Sheets[sheet], {header: ['0', '1']});
//若当前sheet没有数据,则continue
if(sheetArray.length == 0){
continue;
}
for(let item in sheetArray){
if (sheetArray[item] != undefined) {
let tmp1 = sheetArray as []
tmpColumnData.push(tmp1[item][0])
tmpValueData.push(tmp1[item][1])
}
}
useCount++
}
//更新数据
colunmData.value = tmpColumnData
valueData.value = tmpValueData
seriesData.value = tranObjAndColumn([
colunmData.value, valueData.value
])
//更新图表
canvasHandle('data')
//更新表格
rowsData.value = toSpreadsheetData([
colunmData, valueData
])
} catch (e) {
console.log('error')
console.log(e)
}
}
fileReader.readAsArrayBuffer(_file)
}
//当超出限制时,执行的钩子函数
//这里覆盖前一个文件
const handleExceed: UploadProps['onExceed'] = (files) => {
dataFileRef.value!.clearFiles()
const file = files[0] as UploadRawFile
file.uid = genFileId()
dataFileRef.value!.handleStart(file)
dataFileRef.value!.submit()
}
//copy
// const copyRes = async (resStr: string) => {
// copy(resStr)
// }
onMounted(() => {
//初始化数据
seriesData.value = tranObjAndColumn([
colunmData.value, valueData.value
])
//init echart dom
chartDom.value = document.getElementById('main')
//设置画布宽高
canvasHandle('size')
//reload canvas
reloadCanvas()
//数据格式转换成
rowsData.value = toSpreadsheetData([
colunmData, valueData
])
// console.log(rowsData.value)
})
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl flex">
<div class="w-4/6">
<div class="flex flex-row-reverse mb-4">
<el-input-number v-model="sacleSize" :min="1" :max="100" :step="5" @change="canvasHandle('scale')" step-strictly/>
<el-text>缩放</el-text>
</div>
<!-- <div class="flex justify-center items-center"> -->
<div class="flex justify-center items-center max-h-[500px] max-w-[1000px] overflow-auto">
<div id="main" class="bg-white"></div>
</div>
</div>
<div class="w-2/6 bg-white ml-3 rounded-lg p-3">
<div class="mb-3">
<el-button class="w-full mb-3" type="primary" size="large" @click="downEchartsImg">下载图表</el-button>
<div class="flex items-center ml-1">
<el-text>文件类型</el-text>
<el-radio-group v-model="downType" class="ml-4">
<el-radio size="large" label="1">PNG</el-radio>
<el-radio size="large" label="2">JPEG</el-radio>
</el-radio-group>
</div>
<div class="flex w-full">
<el-tooltip
class="box-item"
effect="dark"
content="支持上传:xls, xlsx, csv文件"
placement="top-start"
>
<el-button class="w-1/2">
<el-upload
v-model:file-list="fileList"
class="dataFileRef flex"
ref="dataFileRef"
accept=".xls,.xlsx,.csv"
:http-request="updateDataFile"
:on-exceed="handleExceed"
:limit="1"
>
上传数据
</el-upload>
</el-button>
</el-tooltip>
<el-button class="w-1/2" @click="editData">编辑数据</el-button>
</div>
</div>
<el-collapse v-model="setOptionName" @change="handleChange" accordion>
<el-collapse-item title="画布设置" name="1">
<div class="flex">
<el-text class="w-16 text-right">宽度</el-text>
<el-input-number
v-model="widthCanvas"
:min="1"
:max="4000"
:step="10"
controls-position="right"
class="h-8 w-60 max-w-[60%] ml-3"
@change="canvasHandle('size')"
/>
</div>
<div class="flex mt-3">
<el-text class="w-16 text-right">高度</el-text>
<el-input-number
v-model="heightCanvas"
:min="1"
:max="4000"
:step="10"
controls-position="right"
class="h-8 w-60 max-w-[60%] ml-3"
@change="canvasHandle('size')"
/>
</div>
</el-collapse-item>
<el-collapse-item title="标题设置" name="2">
<div class="flex">
<el-text class="w-16 text-right">位置</el-text>
<el-radio-group v-model="titlePos" class="ml-3" @change="canvasHandle('title')">
<el-radio-button label="left" value="left"></el-radio-button>
<el-radio-button label="center" value="center"></el-radio-button>
<el-radio-button label="right" value="right"></el-radio-button>
</el-radio-group>
</div>
<el-divider content-position="left" class="pl-10">标题</el-divider>
<div class="flex">
<el-text class="w-16 text-right">显示</el-text>
<el-switch v-model="titleSwitch" class="ml-3" @change="canvasHandle('title')"/>
</div>
<div class="flex">
<el-text class="w-16 text-right">标题</el-text>
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="title" @blur="canvasHandle('title')"></el-input>
</div>
<el-divider content-position="left">副标题</el-divider>
<div class="flex">
<el-text class="w-16 text-right">显示</el-text>
<el-switch v-model="subTitleSwitch" class="ml-3" @change="canvasHandle('title')"/>
</div>
<div class="flex">
<el-text class="w-16 text-right">副标题</el-text>
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="subTitle" @blur="canvasHandle('title')"></el-input>
</div>
</el-collapse-item>
<!-- <el-collapse-item title="图形属性" name="3">
<div class="flex">
<el-text class="w-16 text-right">颜色</el-text>
<el-color-picker v-model="attrColor" class="ml-3" @change="canvasHandle('color')"/>
</div>
</el-collapse-item> -->
<el-collapse-item title="水印设置" name="4">
<div class="flex">
<el-text class="w-16 text-right">显示</el-text>
<el-switch v-model="watermarkSwitch" class="ml-3" @change="canvasHandle('watermark')"/>
</div>
<div class="flex">
<el-text class="w-16 text-right">水印内容</el-text>
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="waterMarkText" @change="canvasHandle('watermark')"></el-input>
</div>
</el-collapse-item>
</el-collapse>
</div>
</div>
<el-drawer id="x-spreadsheet" v-model="drawer" direction="btt" custom-class="sheet" style="">
</el-drawer>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
饼图是通过一个圆来展示数据用圆内各扇形的角度来表示数据之间的占比关系<br>
能够很好地展示不同分类的变量之间或单个分类变量与整体之间的占比情况常用来强调某个突出的分类变量或表示表示占比关系<br>
在线图表制作工具在线制作饼图<br>
支持导入表格并在线编辑表格生成饼图支持png和jpeg格式导出<br>
支持超全的自定义配置轻松实现你的个性化图表需求<br>
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
:deep(.el-drawer__body){
display: none !important;
}
:deep(.el-drawer__header){
display: none !important;
margin-bottom: 0 !important;
}
:deep(.el-drawer){
height: 50% !important;
}
:deep(.el-upload-list){
display: none !important;
}
</style>
@@ -0,0 +1,493 @@
<script setup lang="ts">
import {ref, reactive, onMounted } from 'vue'
import Spreadsheet from 'x-data-spreadsheet'
import 'x-data-spreadsheet/dist/locale/zh-cn';
import { UploadProps,UploadRawFile,genFileId } from 'element-plus'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
// import { copy } from '@/utils/string'
import { toEchartsData, toSpreadsheetData, tranObjAndColumn } from '@/utils/echarts'
import * as echarts from 'echarts'
import * as XLSX from 'xlsx'
const info = reactive({
title: "散点图",
})
const chartDom = ref<HTMLElement|null>()
const myChart = ref<echarts.ECharts>()
const dataFileRef = ref()
const setOptionName = ref(1)
//缩放比例
const sacleSize = ref(100)
//画布宽高
const widthCanvas = ref(720)
const heightCanvas = ref(400)
//下载文件类型
const downType = ref('1')
//图形属性颜色
const attrColor = ref('#5470c6')
//标题位置
const titlePos = ref('center')
//标题
const title = ref('Tools-Web')
//副标题
const subTitle = ref('在线图表制作工具')
//显示标题 - 开关
const titleSwitch = ref(true)
//显示副标题 - 开关
const subTitleSwitch = ref(true)
/** 水印 */
const watermarkSwitch = ref(false) //开关
const waterMarkText = ref('Tools-Web');
//创建水印
const createWatermark = () => {
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
canvas.width = canvas.height = 100;
ctx!.textAlign = 'center';
ctx!.textBaseline = 'middle';
ctx!.globalAlpha = 0.08;
ctx!.font = '20px Microsoft Yahei';
ctx!.translate(50, 50);
ctx!.rotate(-Math.PI / 4);
ctx!.fillText(waterMarkText.value, 0, 0);
return canvas
}
//操作图表
const canvasHandle = (type) => {
// let element = document.getElementById('main')
let element = chartDom.value
switch(type) {
case "scale":
//缩放画布
let scale = sacleSize.value / 100
element!.style.transform = `scale(${scale})`
break
case "size":
//图表尺寸
element!.style.width = widthCanvas.value + 'px';
element!.style.height = heightCanvas.value + 'px';
reloadCanvas()
break
case "title":
let fakerTitle = ''
let fakerSubTitle = ''
//修改标题相关配置
if (titleSwitch.value === true) {
fakerTitle = title.value
}
if (subTitleSwitch.value === true) {
fakerSubTitle = subTitle.value
}
myChart.value?.setOption({
title: {
text: fakerTitle,
subtext: fakerSubTitle,
left: titlePos.value
}
})
break
case "color":
//图表属性颜色
myChart.value?.setOption({
series: [
{
itemStyle: {
color: attrColor.value
}
}
]
})
break;
case "watermark":
//水印
if (watermarkSwitch.value === true) {
myChart.value?.setOption({
backgroundColor: {
image: createWatermark(),
}
})
} else {
myChart.value?.setOption({
backgroundColor: '#fff'
})
}
break;
case "data":
//更新数据
myChart.value?.setOption({
series: [
{
data: seriesData.value,
}
]
})
break;
}
}
const handleChange = () => {
}
//数据
const colunmData = ref([10.0, 8.07, 13.0, 9.05, 11.0, 14.0, 13.4, 10.0, 14.0, 12.5, 9.15, 11.5, 3.03, 12.2, 2.02, 1.05, 4.05, 6.03, 12.0, 7.08, 5.02, 6.03]);
const valueData = ref([8.04, 6.95, 7.58, 8.81, 8.33, 7.66, 6.81, 6.33, 8.96, 6.82, 7.2, 7.2, 4.23, 7.83, 4.47, 3.33, 4.96, 7.24, 6.26, 8.84, 5.82, 5.68, 7.05]);
const seriesData = ref([] as Array<any>[])
//选项
const option = {
backgroundColor: '#fff',
// backgroundColor: {
// // type: 'pattern',
// // image: canvas,
// // repeat: 'repeat'
// },
title: {
text: title.value,
subtext: subTitle.value,
left: titlePos.value
},
xAxis: {},
yAxis: {},
series: [
{
type: 'scatter',
symbolSize: 30,
data: seriesData.value,
}
],
tooltip: {}
};
//重新加载画布
const reloadCanvas = () => {
myChart.value = echarts.init(chartDom.value)
myChart.value.clear();
myChart.value.resize({
width: widthCanvas.value,
height: heightCanvas.value
})
myChart.value.setOption(option)
//加载数据
canvasHandle('data')
}
//下载echarts图表图片
const downEchartsImg = () => {
let ext = downType.value == '1' ? 'png' : 'jpeg'
let imgUrl = myChart.value?.getDataURL({
type: ext as "png" | 'jpeg',
pixelRatio: 2,
})
if (imgUrl != undefined) {
var downloadLink = document.createElement('a');
downloadLink.href = imgUrl;
downloadLink.download = 'echart.' + ext;
downloadLink.click();
}
}
/** 上传、编辑数据 */
//抽屉开关
const drawer = ref(false)
const rowsData = ref({})
const editData = () => {
if (drawer.value == true) {
drawer.value = false
} else {
drawer.value = true
/** 在线excel */
Spreadsheet.locale('zh-cn', (window.x_spreadsheet as any).$messages['zh-cn']);
//const sheet = new Spreadsheet("#x-spreadsheet", {
new Spreadsheet("#x-spreadsheet", {
showToolbar: false, //隐藏顶部工具栏
showBottomBar: false,//隐藏底部工具栏,
view: {
height: () => document.documentElement.clientHeight / 2,
width: () => document.documentElement.clientWidth,
}
})
.loadData({
//样式
styles: [
{
bgcolor: '#f4f5f8',
textwrap: true,
color: '#900b09',
border: {
top: ['thin', '#0366d6'],
bottom: ['thin', '#0366d6'],
right: ['thin', '#0366d6'],
left: ['thin', '#0366d6'],
},
},
],
//数据
rows: rowsData.value
}) // load data
.change(data => {
//表格数据改变后触发
//规则: 获取第一列和第二列的数据
let tmp = toEchartsData(data)
colunmData.value = tmp[0]
valueData.value = tmp[1]
canvasHandle('data')
});
// sheet.validate()
}
}
//上传数据文件
const fileList = ref()
const updateDataFile = async (params) => {
const _file = params.file;
const fileReader = new FileReader();
fileReader.onload = (ev) => {
try {
if (!ev.target) {
return
}
const data = ev.target.result;
const workbook = XLSX.read(data, {
type: 'binary'
});
let useCount = 0;
let tmpColumnData:any[] = [];
let tmpValueData:any[] = [];
for (let sheet in workbook.Sheets) {
//这里只需要第一个sheet
if (useCount > 0) {
continue;
}
//循环读取每个文件
const sheetArray = XLSX.utils.sheet_to_json(workbook.Sheets[sheet], {header: ['0', '1']});
//若当前sheet没有数据,则continue
if(sheetArray.length == 0){
continue;
}
for(let item in sheetArray){
if (sheetArray[item] != undefined) {
let tmp1 = sheetArray as []
tmpColumnData.push(tmp1[item][0])
tmpValueData.push(tmp1[item][1])
}
}
useCount++
}
//更新数据
colunmData.value = tmpColumnData
valueData.value = tmpValueData
seriesData.value = tranObjAndColumn([
colunmData.value, valueData.value
])
//更新图表
canvasHandle('data')
//更新表格
rowsData.value = toSpreadsheetData([
colunmData, valueData
])
} catch (e) {
console.log('error')
console.log(e)
}
}
fileReader.readAsArrayBuffer(_file)
}
//当超出限制时,执行的钩子函数
//这里覆盖前一个文件
const handleExceed: UploadProps['onExceed'] = (files) => {
dataFileRef.value!.clearFiles()
const file = files[0] as UploadRawFile
file.uid = genFileId()
dataFileRef.value!.handleStart(file)
dataFileRef.value!.submit()
}
//copy
// const copyRes = async (resStr: string) => {
// copy(resStr)
// }
onMounted(() => {
//初始化数据
seriesData.value = tranObjAndColumn([
colunmData.value, valueData.value
], 'toCoord')
//init echart dom
chartDom.value = document.getElementById('main')
//设置画布宽高
canvasHandle('size')
//reload canvas
reloadCanvas()
//数据格式转换成
rowsData.value = toSpreadsheetData([
colunmData, valueData
])
// console.log(rowsData.value)
})
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl flex">
<div class="w-4/6">
<div class="flex flex-row-reverse mb-4">
<el-input-number v-model="sacleSize" :min="1" :max="100" :step="5" @change="canvasHandle('scale')" step-strictly/>
<el-text>缩放</el-text>
</div>
<!-- <div class="flex justify-center items-center"> -->
<div class="flex justify-center items-center max-h-[500px] max-w-[1000px] overflow-auto">
<div id="main" class="bg-white"></div>
</div>
</div>
<div class="w-2/6 bg-white ml-3 rounded-lg p-3">
<div class="mb-3">
<el-button class="w-full mb-3" type="primary" size="large" @click="downEchartsImg">下载图表</el-button>
<div class="flex items-center ml-1">
<el-text>文件类型</el-text>
<el-radio-group v-model="downType" class="ml-4">
<el-radio size="large" label="1">PNG</el-radio>
<el-radio size="large" label="2">JPEG</el-radio>
</el-radio-group>
</div>
<div class="flex w-full">
<el-tooltip
class="box-item"
effect="dark"
content="支持上传:xls, xlsx, csv文件"
placement="top-start"
>
<el-button class="w-1/2">
<el-upload
v-model:file-list="fileList"
class="dataFileRef flex"
ref="dataFileRef"
accept=".xls,.xlsx,.csv"
:http-request="updateDataFile"
:on-exceed="handleExceed"
:limit="1"
>
上传数据
</el-upload>
</el-button>
</el-tooltip>
<el-button class="w-1/2" @click="editData">编辑数据</el-button>
</div>
</div>
<el-collapse v-model="setOptionName" @change="handleChange" accordion>
<el-collapse-item title="画布设置" name="1">
<div class="flex">
<el-text class="w-16 text-right">宽度</el-text>
<el-input-number
v-model="widthCanvas"
:min="1"
:max="4000"
:step="10"
controls-position="right"
class="h-8 w-60 max-w-[60%] ml-3"
@change="canvasHandle('size')"
/>
</div>
<div class="flex mt-3">
<el-text class="w-16 text-right">高度</el-text>
<el-input-number
v-model="heightCanvas"
:min="1"
:max="4000"
:step="10"
controls-position="right"
class="h-8 w-60 max-w-[60%] ml-3"
@change="canvasHandle('size')"
/>
</div>
</el-collapse-item>
<el-collapse-item title="标题设置" name="2">
<div class="flex">
<el-text class="w-16 text-right">位置</el-text>
<el-radio-group v-model="titlePos" class="ml-3" @change="canvasHandle('title')">
<el-radio-button label="left" value="left"></el-radio-button>
<el-radio-button label="center" value="center"></el-radio-button>
<el-radio-button label="right" value="right"></el-radio-button>
</el-radio-group>
</div>
<el-divider content-position="left" class="pl-10">标题</el-divider>
<div class="flex">
<el-text class="w-16 text-right">显示</el-text>
<el-switch v-model="titleSwitch" class="ml-3" @change="canvasHandle('title')"/>
</div>
<div class="flex">
<el-text class="w-16 text-right">标题</el-text>
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="title" @blur="canvasHandle('title')"></el-input>
</div>
<el-divider content-position="left">副标题</el-divider>
<div class="flex">
<el-text class="w-16 text-right">显示</el-text>
<el-switch v-model="subTitleSwitch" class="ml-3" @change="canvasHandle('title')"/>
</div>
<div class="flex">
<el-text class="w-16 text-right">副标题</el-text>
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="subTitle" @blur="canvasHandle('title')"></el-input>
</div>
</el-collapse-item>
<el-collapse-item title="图形属性" name="3">
<div class="flex">
<el-text class="w-16 text-right">颜色</el-text>
<el-color-picker v-model="attrColor" class="ml-3" @change="canvasHandle('color')"/>
</div>
</el-collapse-item>
<el-collapse-item title="水印设置" name="4">
<div class="flex">
<el-text class="w-16 text-right">显示</el-text>
<el-switch v-model="watermarkSwitch" class="ml-3" @change="canvasHandle('watermark')"/>
</div>
<div class="flex">
<el-text class="w-16 text-right">水印内容</el-text>
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="waterMarkText" @change="canvasHandle('watermark')"></el-input>
</div>
</el-collapse-item>
</el-collapse>
</div>
</div>
<el-drawer id="x-spreadsheet" v-model="drawer" direction="btt" custom-class="sheet" style="">
</el-drawer>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
散点图更偏向于研究型图表通过直观醒目的图形方式反映出变量随自变量而变化的大致趋势可以帮助我们推断出不同维度数据之间的相关性也经常用在地图的标注上<br>
在线图表制作工具在线制作散点图<br>
支持导入表格并在线编辑表格生成散点图支持png和jpeg格式导出<br>
支持超全的自定义配置轻松实现你的个性化图表需求<br>
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
:deep(.el-drawer__body){
display: none !important;
}
:deep(.el-drawer__header){
display: none !important;
margin-bottom: 0 !important;
}
:deep(.el-drawer){
height: 50% !important;
}
:deep(.el-upload-list){
display: none !important;
}
</style>
+142
View File
@@ -0,0 +1,142 @@
<script setup lang="ts">
import { ref,reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
// import { copy } from '@/utils/string'
const info = reactive({
title: "抛硬币",
})
const genStatus = ref(false)
const isHeads = ref(true)
const throwCoin = () => {
genStatus.value = true
isHeads.value = Math.random() < 0.5;
setTimeout(() => {
genStatus.value = false;
}, 2500); // 休眠5秒,5000毫秒
}
//copy
// const copyRes = async (resStr: string) => {
// copy(resStr)
// }
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div style="flex-grow: 3; display: flex; justify-content: center;">
<!-- coin -->
<div class="rotate-wrap" :class="[genStatus == true ? (isHeads == true ? 'rotate-wrap-ani-z' : 'rotate-wrap-ani-f') : '']">
<div class="front circle" :class="[isHeads == true ? 'is-head' : '']"></div>
<div class="reverse circle" :class="[isHeads == false ? 'is-head' : '']"></div>
</div>
</div>
<div class="flex justify-center">
<el-button type="primary" class="w-48" size="large" @click="throwCoin" v-if="!genStatus">抛硬币</el-button>
<el-button type="primary" class="w-48" size="large" disabled v-else>抛硬币...</el-button>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
面临艰难的选择 我们邀请您在线掷硬币
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
.rotate-wrap {
width: 100px;
height: 100px;
margin: 100px;
transform-style: preserve-3d;
position: relative;
}
.rotate-wrap-ani-z{
/* animation: rotate 5s; */
animation: rotateZ 2.5s linear infinite;
}
.rotate-wrap-ani-f{
/* animation: rotate 5s; */
animation: rotateF 2.5s linear infinite;
}
.rotate-wrap:hover {
animation-play-state: paused;
}
.rotate-wrap .front, .rotate-wrap .reverse{
width: 100px;
height: 100px;
background-size: cover;
}
.rotate-wrap .front{
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-image: url('/public/images/coin/dollar.png');
}
.rotate-wrap .reverse{
background-image: url('/public/images/coin/xingxing.png');
}
.circle{
border-radius: 50%;
}
.line{
height: 200px;
width: 1px;
position:absolute;
top: 0;
left: 50%;
background: red;
transform: translateZ(0px);
}
.is-head{
transform: translateZ(1px);
}
@keyframes rotateZ {
0%{
transform: rotateX(0deg);
}
20%{
transform: rotateX(720deg) scale(1.2);
}
50%{
transform: rotateX(1080deg) scale(1.5);
}
80%{
transform: rotateX(1440deg) scale(1.2);
}
100% {
transform: rotateX(2160deg);
}
}
@keyframes rotateF {
0%{
transform: rotateX(0deg);
}
20%{
transform: rotateX(720deg) scale(1.2);
}
50%{
transform: rotateX(1080deg) scale(1.5);
}
80%{
transform: rotateX(1440deg) scale(1.2);
}
100% {
transform: rotateX(1980deg);
}
}
</style>
@@ -0,0 +1,601 @@
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div class="tool-content">
<div class="input-section">
<el-tabs v-model="activeCategory">
<el-tab-pane label="基础颜色" name="basic">
<div class="color-grid">
<div
v-for="color in basicColors"
:key="color.hex"
class="color-item"
:style="{ backgroundColor: color.hex }"
@click="copyColor(color)"
>
<div class="color-info">
<span class="color-name">{{ color.name }}</span>
<span class="color-hex">{{ color.hex }}</span>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="渐变色" name="gradient">
<div class="gradient-grid">
<div
v-for="(gradient, index) in gradients"
:key="index"
class="gradient-item"
:style="{ background: gradient.value }"
@click="copyGradient(gradient)"
>
<div class="gradient-info">
<span class="gradient-name">{{ gradient.name }}</span>
<span class="gradient-value">{{ gradient.value }}</span>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="Material Design" name="material">
<div class="color-grid">
<div
v-for="color in materialColors"
:key="color.hex"
class="color-item"
:style="{ backgroundColor: color.hex }"
@click="copyColor(color)"
>
<div class="color-info">
<span class="color-name">{{ color.name }}</span>
<span class="color-hex">{{ color.hex }}</span>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="扁平化" name="flat">
<div class="color-grid">
<div
v-for="color in flatColors"
:key="color.hex"
class="color-item"
:style="{ backgroundColor: color.hex }"
@click="copyColor(color)"
>
<div class="color-info">
<span class="color-name">{{ color.name }}</span>
<span class="color-hex">{{ color.hex }}</span>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="霓虹色" name="neon">
<div class="color-grid">
<div
v-for="color in neonColors"
:key="color.hex"
class="color-item"
:style="{ backgroundColor: color.hex }"
@click="copyColor(color)"
>
<div class="color-info">
<span class="color-name">{{ color.name }}</span>
<span class="color-hex">{{ color.hex }}</span>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="自然色" name="nature">
<div class="color-grid">
<div
v-for="color in natureColors"
:key="color.hex"
class="color-item"
:style="{ backgroundColor: color.hex }"
@click="copyColor(color)"
>
<div class="color-info">
<span class="color-name">{{ color.name }}</span>
<span class="color-hex">{{ color.hex }}</span>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="金属色" name="metal">
<div class="color-grid">
<div
v-for="color in metalColors"
:key="color.hex"
class="color-item"
:style="{ backgroundColor: color.hex }"
@click="copyColor(color)"
>
<div class="color-info">
<span class="color-name">{{ color.name }}</span>
<span class="color-hex">{{ color.hex }}</span>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="复古色" name="vintage">
<div class="color-grid">
<div
v-for="color in vintageColors"
:key="color.hex"
class="color-item"
:style="{ backgroundColor: color.hex }"
@click="copyColor(color)"
>
<div class="color-info">
<span class="color-name">{{ color.name }}</span>
<span class="color-hex">{{ color.hex }}</span>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="季节色" name="seasonal">
<div class="color-grid">
<div
v-for="color in seasonalColors"
:key="color.hex"
class="color-item"
:style="{ backgroundColor: color.hex }"
@click="copyColor(color)"
>
<div class="color-info">
<span class="color-name">{{ color.name }}</span>
<span class="color-hex">{{ color.hex }}</span>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="莫兰迪色" name="morandi">
<div class="color-grid">
<div
v-for="color in morandiColors"
:key="color.hex"
class="color-item"
:style="{ backgroundColor: color.hex }"
@click="copyColor(color)"
>
<div class="color-info">
<span class="color-name">{{ color.name }}</span>
<span class="color-hex">{{ color.hex }}</span>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="马卡龙色" name="macaron">
<div class="color-grid">
<div
v-for="color in macaronColors"
:key="color.hex"
class="color-item"
:style="{ backgroundColor: color.hex }"
@click="copyColor(color)"
>
<div class="color-info">
<span class="color-name">{{ color.name }}</span>
<span class="color-hex">{{ color.hex }}</span>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="工业风" name="industrial">
<div class="color-grid">
<div
v-for="color in industrialColors"
:key="color.hex"
class="color-item"
:style="{ backgroundColor: color.hex }"
@click="copyColor(color)"
>
<div class="color-info">
<span class="color-name">{{ color.name }}</span>
<span class="color-hex">{{ color.hex }}</span>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="中国传统色" name="chinese">
<div class="color-grid">
<div
v-for="color in chineseColors"
:key="color.hex"
class="color-item"
:style="{ backgroundColor: color.hex }"
@click="copyColor(color)"
>
<div class="color-info">
<span class="color-name">{{ color.name }}</span>
<span class="color-hex">{{ color.hex }}</span>
</div>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线色板工具提供基础颜色渐变色Material Design 颜色扁平化颜色霓虹色自然色金属色复古色季节色莫兰迪色马卡龙色工业风和中国传统色点击颜色可复制颜色值方便设计和开发使用
</el-text>
</ToolDetail>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
const title = "色板工具"
const activeCategory = ref('basic');
// 基础颜色
const basicColors = [
{ name: '红色', hex: '#FF0000' },
{ name: '绿色', hex: '#00FF00' },
{ name: '蓝色', hex: '#0000FF' },
{ name: '黄色', hex: '#FFFF00' },
{ name: '紫色', hex: '#800080' },
{ name: '橙色', hex: '#FFA500' },
{ name: '黑色', hex: '#000000' },
{ name: '白色', hex: '#FFFFFF' },
{ name: '灰色', hex: '#808080' },
{ name: '棕色', hex: '#A52A2A' },
{ name: '粉色', hex: '#FFC0CB' },
{ name: '青色', hex: '#00FFFF' },
{ name: '品红', hex: '#FF00FF' },
{ name: '橄榄绿', hex: '#808000' },
{ name: '海军蓝', hex: '#000080' },
{ name: '银色', hex: '#C0C0C0' },
];
// 渐变色
const gradients = [
{ name: '蓝到紫', value: 'linear-gradient(90deg, #3B82F6, #8B5CF6)' },
{ name: '红到黄', value: 'linear-gradient(90deg, #EF4444, #F59E0B)' },
{ name: '绿到蓝', value: 'linear-gradient(90deg, #10B981, #3B82F6)' },
{ name: '紫到粉', value: 'linear-gradient(90deg, #8B5CF6, #EC4899)' },
{ name: '黄到绿', value: 'linear-gradient(90deg, #F59E0B, #10B981)' },
{ name: '红到橙', value: 'linear-gradient(90deg, #EF4444, #F97316)' },
{ name: '蓝到青', value: 'linear-gradient(90deg, #3B82F6, #06B6D4)' },
{ name: '紫到红', value: 'linear-gradient(90deg, #8B5CF6, #EF4444)' },
];
// Material Design颜色
const materialColors = [
{ name: 'Material Red', hex: '#F44336' },
{ name: 'Material Pink', hex: '#E91E63' },
{ name: 'Material Purple', hex: '#9C27B0' },
{ name: 'Material Deep Purple', hex: '#673AB7' },
{ name: 'Material Indigo', hex: '#3F51B5' },
{ name: 'Material Blue', hex: '#2196F3' },
{ name: 'Material Light Blue', hex: '#03A9F4' },
{ name: 'Material Cyan', hex: '#00BCD4' },
{ name: 'Material Teal', hex: '#009688' },
{ name: 'Material Green', hex: '#4CAF50' },
{ name: 'Material Light Green', hex: '#8BC34A' },
{ name: 'Material Lime', hex: '#CDDC39' },
{ name: 'Material Yellow', hex: '#FFEB3B' },
{ name: 'Material Amber', hex: '#FFC107' },
{ name: 'Material Orange', hex: '#FF9800' },
{ name: 'Material Deep Orange', hex: '#FF5722' },
];
// 扁平化颜色
const flatColors = [
{ name: 'Flat Turquoise', hex: '#1ABC9C' },
{ name: 'Flat Green Sea', hex: '#16A085' },
{ name: 'Flat Peter River', hex: '#3498DB' },
{ name: 'Flat Belize Hole', hex: '#2980B9' },
{ name: 'Flat Amethyst', hex: '#9B59B6' },
{ name: 'Flat Wisteria', hex: '#8E44AD' },
{ name: 'Flat Wet Asphalt', hex: '#34495E' },
{ name: 'Flat Midnight Blue', hex: '#2C3E50' },
{ name: 'Flat Sun Flower', hex: '#F1C40F' },
{ name: 'Flat Orange', hex: '#F39C12' },
{ name: 'Flat Carrot', hex: '#E67E22' },
{ name: 'Flat Alizarin', hex: '#E74C3C' },
{ name: 'Flat Clouds', hex: '#ECF0F1' },
{ name: 'Flat Silver', hex: '#BDC3C7' },
{ name: 'Flat Concrete', hex: '#95A5A6' },
{ name: 'Flat Asbestos', hex: '#7F8C8D' },
];
// 霓虹色
const neonColors = [
{ name: 'Neon Pink', hex: '#FF1493' },
{ name: 'Neon Blue', hex: '#00FFFF' },
{ name: 'Neon Green', hex: '#39FF14' },
{ name: 'Neon Yellow', hex: '#FFFF00' },
{ name: 'Neon Orange', hex: '#FF6600' },
{ name: 'Neon Purple', hex: '#9932CC' },
{ name: 'Neon Red', hex: '#FF073A' },
{ name: 'Neon Cyan', hex: '#00FFFF' },
{ name: 'Neon Magenta', hex: '#FF00FF' },
{ name: 'Neon Lime', hex: '#32CD32' },
{ name: 'Neon Turquoise', hex: '#40E0D0' },
{ name: 'Neon Lavender', hex: '#E6E6FA' },
];
// 自然色
const natureColors = [
{ name: 'Forest Green', hex: '#228B22' },
{ name: 'Earth Brown', hex: '#8B4513' },
{ name: 'Sky Blue', hex: '#87CEEB' },
{ name: 'Sunset Orange', hex: '#FF8C00' },
{ name: 'Ocean Blue', hex: '#1E90FF' },
{ name: 'Moss Green', hex: '#8A9A5B' },
{ name: 'Sandy Beige', hex: '#F5DEB3' },
{ name: 'Leaf Green', hex: '#008000' },
{ name: 'Clay Brown', hex: '#B22222' },
{ name: 'Dolphin Gray', hex: '#878787' },
{ name: 'Coral Pink', hex: '#FF7F50' },
{ name: 'Pine Green', hex: '#01796F' },
];
// 金属色
const metalColors = [
{ name: 'Gold', hex: '#FFD700' },
{ name: 'Silver', hex: '#C0C0C0' },
{ name: 'Bronze', hex: '#CD7F32' },
{ name: 'Copper', hex: '#B87333' },
{ name: 'Platinum', hex: '#E5E4E2' },
{ name: 'Brass', hex: '#B5A642' },
{ name: 'Steel Blue', hex: '#4682B4' },
{ name: 'Aluminum', hex: '#A9A9A9' },
{ name: 'Chrome', hex: '#E0E0E0' },
{ name: 'Titanium', hex: '#818589' },
{ name: 'Zinc', hex: '#E8E8E8' },
{ name: 'Lead', hex: '#696969' },
];
// 复古色
const vintageColors = [
{ name: 'Sepia', hex: '#704214' },
{ name: 'Mustard', hex: '#FFDB58' },
{ name: 'Burgundy', hex: '#800020' },
{ name: 'Teal', hex: '#008080' },
{ name: 'Cream', hex: '#FFFDD0' },
{ name: 'Olive', hex: '#808000' },
{ name: 'Mauve', hex: '#E0B0FF' },
{ name: 'Peach', hex: '#FFDAB9' },
{ name: 'Sage', hex: '#9CAF88' },
{ name: 'Navy', hex: '#000080' },
{ name: 'Coral', hex: '#FF7F50' },
{ name: 'Lavender', hex: '#E6E6FA' },
];
// 季节色
const seasonalColors = [
{ name: 'Spring Green', hex: '#00FF7F' },
{ name: 'Summer Yellow', hex: '#FFFF00' },
{ name: 'Autumn Orange', hex: '#FF8C00' },
{ name: 'Winter Blue', hex: '#1E90FF' },
{ name: 'Cherry Blossom', hex: '#FFB7C5' },
{ name: 'Beach Sand', hex: '#F4A460' },
{ name: 'Fall Leaf', hex: '#8B4513' },
{ name: 'Snow White', hex: '#FFFFFF' },
{ name: 'Tulip Red', hex: '#FF0000' },
{ name: 'Sunflower Yellow', hex: '#FFD700' },
{ name: 'Pumpkin Orange', hex: '#FF7F50' },
{ name: 'Icicle Blue', hex: '#B0E0E6' },
];
// 莫兰迪色
const morandiColors = [
{ name: '莫兰迪灰', hex: '#9B9B9B' },
{ name: '莫兰迪粉', hex: '#E8D8D1' },
{ name: '莫兰迪蓝', hex: '#A8B8C8' },
{ name: '莫兰迪绿', hex: '#A8B8A8' },
{ name: '莫兰迪黄', hex: '#E8D8A8' },
{ name: '莫兰迪紫', hex: '#B8A8C8' },
{ name: '莫兰迪棕', hex: '#B8A89B' },
{ name: '莫兰迪橙', hex: '#E8C8A8' },
{ name: '莫兰迪青', hex: '#A8C8C8' },
{ name: '莫兰迪红', hex: '#C8A8A8' },
{ name: '莫兰迪灰蓝', hex: '#9B9BA8' },
{ name: '莫兰迪灰绿', hex: '#9BA89B' },
];
// 马卡龙色
const macaronColors = [
{ name: '马卡龙粉', hex: '#FFB6C1' },
{ name: '马卡龙蓝', hex: '#87CEFA' },
{ name: '马卡龙绿', hex: '#98FB98' },
{ name: '马卡龙黄', hex: '#FFFACD' },
{ name: '马卡龙紫', hex: '#E6E6FA' },
{ name: '马卡龙橙', hex: '#FFA07A' },
{ name: '马卡龙青', hex: '#E0FFFF' },
{ name: '马卡龙红', hex: '#FFC0CB' },
{ name: '马卡龙灰', hex: '#F0F0F0' },
{ name: '马卡龙棕', hex: '#F5DEB3' },
{ name: '马卡龙天蓝', hex: '#87CEEB' },
{ name: '马卡龙薄荷', hex: '#98FB98' },
];
// 工业风
const industrialColors = [
{ name: '工业黑', hex: '#2C2C2C' },
{ name: '工业灰', hex: '#666666' },
{ name: '工业银', hex: '#999999' },
{ name: '工业红', hex: '#B22222' },
{ name: '工业蓝', hex: '#4682B4' },
{ name: '工业黄', hex: '#DAA520' },
{ name: '工业绿', hex: '#228B22' },
{ name: '工业橙', hex: '#FF8C00' },
{ name: '工业棕', hex: '#8B4513' },
{ name: '工业青', hex: '#008080' },
{ name: '工业紫', hex: '#8A2BE2' },
{ name: '工业白', hex: '#F5F5F5' },
];
// 中国传统色
const chineseColors = [
{ name: '胭脂红', hex: '#E44B4E' },
{ name: '黛青', hex: '#4A6C8C' },
{ name: '琉璃黄', hex: '#FFD700' },
{ name: '玉脂白', hex: '#F7F7F7' },
{ name: '水墨黑', hex: '#1A1A1A' },
{ name: '竹青', hex: '#9DB47E' },
{ name: '朱砂红', hex: '#C93756' },
{ name: '石青', hex: '#557799' },
{ name: '金黄', hex: '#E6B422' },
{ name: '藕荷粉', hex: '#E8B4B8' },
{ name: '松绿', hex: '#3A5F0B' },
{ name: '琥珀色', hex: '#E6C200' },
];
// 复制颜色
const copyColor = (color: { name: string; hex: string }) => {
navigator.clipboard.writeText(color.hex).then(() => {
ElMessage.success(`已复制 ${color.name} 的颜色值: ${color.hex}`);
}).catch(() => {
ElMessage.error('复制失败');
});
};
// 复制渐变色
const copyGradient = (gradient: { name: string; value: string }) => {
navigator.clipboard.writeText(gradient.value).then(() => {
ElMessage.success(`已复制 ${gradient.name} 的渐变值: ${gradient.value}`);
}).catch(() => {
ElMessage.error('复制失败');
});
};
</script>
<style scoped>
.tool-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.input-section {
width: 100%;
}
.color-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 15px;
margin-top: 20px;
}
.color-item {
height: 120px;
border-radius: 12px;
display: flex;
align-items: flex-end;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.color-item:hover {
transform: translateY(-5px);
}
.color-info {
background-color: rgba(255, 255, 255, 0.8);
padding: 8px;
width: 100%;
text-align: center;
transition: all 0.3s ease;
}
.color-name {
display: block;
font-size: 14px;
font-weight: bold;
margin-bottom: 4px;
}
.color-hex {
display: block;
font-size: 12px;
font-family: monospace;
}
.gradient-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 15px;
margin-top: 20px;
}
.gradient-item {
height: 120px;
border-radius: 12px;
display: flex;
align-items: flex-end;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.gradient-item:hover {
transform: translateY(-5px);
}
.gradient-info {
background-color: rgba(255, 255, 255, 0.8);
padding: 8px;
width: 100%;
text-align: center;
transition: all 0.3s ease;
}
.gradient-name {
display: block;
font-size: 14px;
font-weight: bold;
margin-bottom: 4px;
}
.gradient-value {
display: block;
font-size: 12px;
font-family: monospace;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 10px;
}
@media (max-width: 768px) {
.color-grid {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
}
.gradient-grid {
grid-template-columns: 1fr;
}
}
</style>
@@ -0,0 +1,263 @@
<script setup lang="ts">
import { reactive,ref,onMounted } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { copy } from '@/utils/string'
import { CopyDocument } from '@element-plus/icons-vue'
import { colord, extend } from 'colord';
import cmykPlugin from 'colord/plugins/cmyk';
import hwbPlugin from 'colord/plugins/hwb';
import namesPlugin from 'colord/plugins/names';
import lchPlugin from 'colord/plugins/lch';
import labPlugin from 'colord/plugins/lab';
extend([cmykPlugin, hwbPlugin, namesPlugin, lchPlugin, labPlugin]);
const info = reactive({
title: "Color选择器",
})
const color = ref('#409eff')
const hex = ref()
const rgb = ref()
const hsl = ref()
const hsv = ref()
const hwb = ref()
const lab = ref()
const lch = ref()
const cmyk = ref()
const name = ref()
const change = () => {
let colorObj = colord(color.value)
hex.value = colorObj.toHex()
rgb.value = colorObj.toRgbString()
hsl.value = colorObj.toHslString()
hsv.value = colorObj.toHsv()
hwb.value = colorObj.toHwbString()
lab.value = colorObj.toLab()
lch.value = colorObj.toLchString()
cmyk.value = colorObj.toCmykString()
name.value = colorObj.toName({ closest: true }) //{ closest: true } 接近的
console.log(colorObj.toHslString())
}
//copy
const copyRes = async (resStr: string) => {
copy(resStr)
}
onMounted(() => {
change()
})
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white text-center">
<ul class="w-3/4">
<li class='flex items-center mb-3'>
<div class="w-36 text-right pr-3">选择颜色:</div>
<div class="w-full text-left">
<el-color-picker v-model="color" size="large" @change="change"/>
</div>
</li>
<li class='flex items-center mb-3'>
<div class="w-36 text-right pr-3">Hex:</div>
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
<div>
{{ hex }}
</div>
<el-tooltip
class="box-item"
effect="dark"
content="复制到剪贴板"
placement="top"
>
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(hex)"> </el-button>
</el-tooltip>
</div>
</li>
<li class='flex items-center mb-3'>
<div class="w-36 text-right pr-3">RGB:</div>
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
<div>
{{ rgb }}
</div>
<el-tooltip
class="box-item"
effect="dark"
content="复制到剪贴板"
placement="top"
>
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(rgb)"> </el-button>
</el-tooltip>
</div>
</li>
<li class='flex items-center mb-3'>
<div class="w-36 text-right pr-3">HSL:</div>
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
<div>
{{ hsl }}
</div>
<el-tooltip
class="box-item"
effect="dark"
content="复制到剪贴板"
placement="top"
>
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(hsl)"> </el-button>
</el-tooltip>
</div>
</li>
<li class='flex items-center mb-3'>
<div class="w-36 text-right pr-3">HSV:</div>
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
<div>
{{ hsv }}
</div>
<el-tooltip
class="box-item"
effect="dark"
content="复制到剪贴板"
placement="top"
>
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(JSON.stringify(hsv))"> </el-button>
</el-tooltip>
</div>
</li>
<li class='flex items-center mb-3'>
<div class="w-36 text-right pr-3">HWB:</div>
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
<div>
{{ hwb }}
</div>
<el-tooltip
class="box-item"
effect="dark"
content="复制到剪贴板"
placement="top"
>
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(hwb)"> </el-button>
</el-tooltip>
</div>
</li>
<li class='flex items-center mb-3'>
<div class="w-36 text-right pr-3">LAB:</div>
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
<div>
{{ lab }}
</div>
<el-tooltip
class="box-item"
effect="dark"
content="复制到剪贴板"
placement="top"
>
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(JSON.stringify(lab))"> </el-button>
</el-tooltip>
</div>
</li>
<li class='flex items-center mb-3'>
<div class="w-36 text-right pr-3">LCH:</div>
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
<div>
{{ lch }}
</div>
<el-tooltip
class="box-item"
effect="dark"
content="复制到剪贴板"
placement="top"
>
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(lch)"> </el-button>
</el-tooltip>
</div>
</li>
<li class='flex items-center mb-3'>
<div class="w-36 text-right pr-3">CMYK:</div>
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
<div>
{{ cmyk }}
</div>
<el-tooltip
class="box-item"
effect="dark"
content="复制到剪贴板"
placement="top"
>
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(cmyk)"> </el-button>
</el-tooltip>
</div>
</li>
<li class='flex items-center mb-3'>
<div class="w-36 text-right pr-3">Name:</div>
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
<div>
{{ name }}
</div>
<el-tooltip
class="box-item"
effect="dark"
content="复制到剪贴板"
placement="top"
>
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(name)"> </el-button>
</el-tooltip>
</div>
</li>
</ul>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在不同格式十六进制rgbhsl和css名称之间转换颜色<br>
<br>
CSS 颜色无处不在它可以作为文字背景阴影表格边框链接等属性我们在 CSS 中使用的大多数颜色都是 Hex RGB其实表示颜色的方式有很多<br>
<div class="indent-8">
- RGB: RGB 值也是我们常用的颜色表示方式RGB 指的就是红-绿-这个顺序非常重要每种颜色使用 0 255 之间的数字指定最常见的 RGB 值黑色rgb(0,0,0) 和白色rgb(255,255,255)RGB表示法使我们以更易读的形式来访问与十六进制值相同的颜色范围
</div>
<div class="indent-8">
- Hex: 可以使用十六进制值来表示 CSS 中的颜色这也是我们用的最多的颜色表示方式十六进制使用 16 个符号表示使用 0 - 9 表示值 0 9A - F 表示值 10 15<br/>
</div>
<div class="indent-8">
- HSL: HSL 全称是 Hue-Saturation-Lightness分别表示色调饱和度和亮度它基于 RGB 色轮的每种颜色都有一个角度以及饱和度和亮度值的百分比值<br/>
</div>
<div class="indent-8">
- HSV: HSV色彩空间是对传统RGB模型的一种改进引入了三个主要组件来定义颜色色调H饱和度S和明度V与RGB模型不同后者基于颜色的主导波长饱和度和亮度提供了更直观的颜色描述方式
</div>
<div class="indent-8">
- HWB: HWB 全称为Hue-Whiteness-Blackness表示色调白度和黑度<br/>
</div>
<div class="indent-8">
- LAB: LAB 是一个可以在 Photoshop 等软件中访问的颜色空间它代表了人类可以看到的整个颜色范围它使用三个轴表示亮度a 轴和b <br/>
</div>
<div class="indent-8">
- LCH: LCH 代表亮度色度和色调它与 Lab 具有相同的 L 但不是使用坐标 a* b*而是使用 C色度 H色调色调可以是 0 360 之间的值色度代表颜色的量它类似于 HSL 中的饱和度但是色度值可以超过 100理论上它是无上限的<br/>
</div>
<div class="indent-8">
- CMYK: CMYK 代表 CyanMagentaYellow Key它们与打印机中的墨水颜色相匹配虽然屏幕通常以 RGB 来显示颜色而打印机通常使用青色品红色黄色和黑色的组合来表示颜色这些是最常见的墨水颜色<br/>
</div>
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,97 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { copy } from '@/utils/string';
import { Codemirror } from "vue-codemirror";
import '@codemirror/search';
import '@codemirror/state';
import '@codemirror/commands';
import * as prettier from "prettier/standalone";
import * as parserCss from 'prettier/parser-postcss';
import { ElMessage } from 'element-plus'
import { minify } from "csso"
const info = reactive({
title: "css代码格式化/压缩",
code: '',
isParseErr: false,
parseErr: '',
})
//格式化
const formatCode = async () => {
try {
info.code = await prettier.format(info.code, { parser: "css", plugins: [parserCss]})
} catch (error) {
console.log(error)
ElMessage({
showClose: true,
message: '请填入正确代码格式',
type: 'error',
})
}
}
//压缩
const compress = async () => {
try {
info.code = minify(info.code, {
restructure: true,
}).css
} catch (error) {
console.log(error)
}
}
//清空输入框
const clear = () => {
info.code = ''
}
const copyRes = async () => {
copy(info.code)
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white ">
<div>
<codemirror
v-model="info.code"
placeholder="这里是代码..."
:style="{ height: '400px' }"
:autofocus="true"
:indent-with-tab="true"
:tabSize="2"
/>
</div>
<div class="mt-4">
<el-button type="primary" @click="formatCode">格式化</el-button>
<el-button type="primary" @click="compress">压缩</el-button>
<el-button type="primary" @click="copyRes">复制</el-button>
<el-button type="primary" @click="clear">清空</el-button>
</div>
<div class="mt-3 min-h-md bg-red-100 p-3 mb-3" v-show="info.isParseErr">
<el-text type="danger">{{ info.parseErr }}</el-text>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线css格式化在线压缩css代码
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, onMounted,ref } from 'vue' import { reactive, onMounted,ref } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
// import { copy } from '@/utils/string' // import { copy } from '@/utils/string'
const info = reactive({ const info = reactive({
title: "帮我决定", title: "帮我决定",
@@ -60,10 +61,10 @@ onMounted(() => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div class=""> <div class="p-4 rounded-2xl bg-white">
<div> <div>
<el-radio-group v-model="info.checked" @change="changeCheckBox"> <el-radio-group v-model="info.checked" @change="changeCheckBox">
<el-radio label="0" size="large">吃什么</el-radio> <el-radio label="0" size="large">吃什么</el-radio>
@@ -86,6 +87,14 @@ onMounted(() => {
<el-button @click="clear">清空</el-button> <el-button @click="clear">清空</el-button>
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
选择困难症患者的福音由我来帮你决定可自定义决定内容随机选择决定
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
+88
View File
@@ -0,0 +1,88 @@
<script setup lang="ts">
import { ref,reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import DiceCore from "@/components/Tools/Dice/DiceCore.vue"
// import { copy } from '@/utils/string'
const info = reactive({
title: "投骰子",
})
//骰子数量
const diceNum = ref(1)
const genStatus = ref(false)
const diceRef = ref()
const resetDiceKey = ref(0)
const shake = async () => {
genStatus.value = true
let res = await new Promise((resolve) => {
for (let i = 0; i < diceNum.value; i++) {
diceRef.value[i].throwDice()
}
resolve(true)
})
//等待执行完成
setTimeout(() => {
console.log(res)
genStatus.value = false
}, 1200)
}
//重置骰子点数
const resetDice = () => {
resetDiceKey.value++
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<view class="grow-[1.5]"></view>
<div class="flex justify-center grow-[3]">
<div class="flex flex-wrap h-96 items-center">
<!-- 骰子组件 -->
<DiceCore ref="diceRef" v-for="i in diceNum" :key="resetDiceKey" :class="[diceNum == 1 ? 'dice_full' : '', diceNum == 3 && i == 1 ? 'dice_full' : 'dice_default', diceNum == 5 && i == 3 ? 'dice_full' : 'dice_default']"/>
</div>
</div>
<div class="">
<div class="ml-5 flex justify-center">
<el-text style="">骰子数量</el-text>
<div class="w-4/6">
<el-slider class="w-72 ml-3" v-model="diceNum" :min="1" :max="6" :step="1" blockSize="18" :showValue="true"></el-slider>
</div>
</div>
</div>
<div class="mb-5 flex justify-center">
<el-button type="primary" class="w-48" size="large" @click="shake" v-if="!genStatus">投骰子</el-button>
<el-button type="primary" class="w-48" size="large" disabled v-else>投骰子...</el-button>
<el-button type="primary" class="w-48" size="large" @click="resetDice">重置</el-button>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线投骰子骰子工具自定义骰子数量
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
.dice_default{
flex: 0 0 50%;
display: flex;
justify-content: center;
}
.dice_full{
flex: 0 0 100%;
display: flex;
justify-content: center;
}
</style>
+115
View File
@@ -0,0 +1,115 @@
<script setup lang="ts">
import { ref, onBeforeUnmount } from 'vue'
//定义emit
const emit = defineEmits(['throwEnd'])
//是否正在掷筛子
const isDicing = ref(false)
//当前显示的动画图片索引
const aniIndex = ref(0)
//掷筛子动画所用到的图片(4张骰子旋转过程中的图)
const diceAnimationImages = ref([
'/images/dice/dice_run1.png',
'/images/dice/dice_run2.png',
'/images/dice/dice_run3.png',
'/images/dice/dice_run4.png'
])
//骰子每一个点的对应的图片
const diceImages = ref({
//1点的图片
1: '/images/dice/dice1.png',
//2点的图片
2: '/images/dice/dice2.png',
//3点的图片
3: '/images/dice/dice3.png',
//4点的图片
4: '/images/dice/dice4.png',
//5点的图片
5: '/images/dice/dice5.png',
//6点的图片
6: '/images/dice/dice6.png'
})
//当前掷筛子掷到的点数(默认1点)
const currentPoint = ref(1)
//定时器
const timer = ref(0)
const getDicePoint = async () => {
let point = 1;
//从1~6随机一个数
point = Math.floor(Math.random() * 6 + 1);
return point;
}
//掷骰子
const throwDice = async () => {
//如果当前骰子正在滚动则不能掷骰子
if (isDicing.value) {
return
}
//从接口获取点数
currentPoint.value = await getDicePoint();
//开启骰子动画
await startAnimation();
//动画完毕之后可以通知父组件当前掷到的点数
emit('throwEnd', currentPoint.value);
}
//开启动画效果
const startAnimation = async () => {
return new Promise((resolve) => {
//设置筛子开始运动
isDicing.value = true;
//记录动画次数
let num = 0;
//每隔100毫秒来回切换一张“动”图形成掷骰子的动画
timer.value = setInterval(() => {
let index = aniIndex.value;
index++;
if (index >= diceAnimationImages.value.length) {
index = 0;
}
aniIndex.value = index;
num++;
//差不多执行1.2秒钟的时候可以停止了
if (num > 12) {
//关闭定时器
clearInterval(timer.value);
//设置骰子停止
isDicing.value = false;
//返回结果
resolve(true);
}
}, 100);
})
}
onBeforeUnmount(() => {
//组件销毁之前清除定时器
clearInterval(timer.value);
})
defineExpose({
throwDice
})
</script>
<template>
<!-- 骰子组件 -->
<div class="dice-wrap" @tap="throwDice">
<!-- 筛子运动时候的展示的图片 -->
<el-image v-if="isDicing" :src="diceAnimationImages[aniIndex]" class="dice-icon" loading="lazy"></el-image>
<!-- 筛子静止时候的显示的对应点数的图片 -->
<el-image v-else :src="diceImages[currentPoint]" class="dice-icon" loading="lazy"></el-image>
</div>
</template>
<style scoped>
.dice-wrap {
width: 100px;
height: 100px;
}
.dice-icon {
width: 100px;
height: 100px;
}
</style>
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { CodeDiff } from "v-code-diff"; import { CodeDiff } from "v-code-diff";
// import { copy } from '@/utils/string' // import { copy } from '@/utils/string'
const info = reactive({ const info = reactive({
@@ -16,7 +17,7 @@ const info = reactive({
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div class=""> <div class="">
@@ -31,6 +32,14 @@ const info = reactive({
theme="dark" theme="dark"
/> />
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线文本差异比对支持中文英文代码比对不限制字数轻松比对文本
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
+737
View File
@@ -0,0 +1,737 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { copy } from '@/utils/string'
const title = "Emoji表情大全"
// 状态管理
const searchKeyword = ref('')
const selectedCategory = ref('all')
// Emoji分类
const emojiCategories = [
{ id: 'all', name: '全部' },
{ id: 'smileys', name: '表情符号与情感' },
{ id: 'people', name: '人物与身体' },
{ id: 'animals', name: '动物与自然' },
{ id: 'food', name: '食物与饮料' },
{ id: 'travel', name: '旅行与地点' },
{ id: 'activities', name: '活动' },
{ id: 'objects', name: '物体' },
{ id: 'symbols', name: '符号' },
{ id: 'flags', name: '旗帜' }
]
// Emoji数据
const emojis = [
// 表情符号与情感
{ emoji: '😀', name: '笑脸', category: 'smileys' },
{ emoji: '😂', name: '笑哭', category: 'smileys' },
{ emoji: '😍', name: '爱心眼', category: 'smileys' },
{ emoji: '😎', name: '墨镜', category: 'smileys' },
{ emoji: '🤔', name: '思考', category: 'smileys' },
{ emoji: '😢', name: '哭泣', category: 'smileys' },
{ emoji: '😡', name: '愤怒', category: 'smileys' },
{ emoji: '😱', name: '惊讶', category: 'smileys' },
{ emoji: '😴', name: '睡觉', category: 'smileys' },
{ emoji: '😵', name: '晕', category: 'smileys' },
{ emoji: '🤢', name: '恶心', category: 'smileys' },
{ emoji: '🤠', name: '牛仔', category: 'smileys' },
{ emoji: '😈', name: '恶魔', category: 'smileys' },
{ emoji: '👼', name: '天使', category: 'smileys' },
{ emoji: '👻', name: '幽灵', category: 'smileys' },
{ emoji: '💩', name: '便便', category: 'smileys' },
{ emoji: '😊', name: '微笑', category: 'smileys' },
{ emoji: '😃', name: '大笑', category: 'smileys' },
{ emoji: '😄', name: '露齿笑', category: 'smileys' },
{ emoji: '😁', name: '开心', category: 'smileys' },
{ emoji: '😆', name: '眯眼笑', category: 'smileys' },
{ emoji: '😅', name: '汗笑', category: 'smileys' },
{ emoji: '🤣', name: '大笑', category: 'smileys' },
{ emoji: '😇', name: '天使笑', category: 'smileys' },
{ emoji: '🥰', name: '可爱', category: 'smileys' },
{ emoji: '😘', name: '飞吻', category: 'smileys' },
{ emoji: '😗', name: '亲吻', category: 'smileys' },
{ emoji: '😙', name: '亲亲', category: 'smileys' },
{ emoji: '😚', name: '闭眼亲吻', category: 'smileys' },
{ emoji: '😋', name: '美味', category: 'smileys' },
{ emoji: '😛', name: '调皮', category: 'smileys' },
{ emoji: '😝', name: '吐舌', category: 'smileys' },
{ emoji: '😜', name: '眨眼吐舌', category: 'smileys' },
{ emoji: '🤪', name: '疯狂', category: 'smileys' },
{ emoji: '😏', name: '得意', category: 'smileys' },
{ emoji: '😒', name: '无奈', category: 'smileys' },
{ emoji: '🙄', name: '白眼', category: 'smileys' },
{ emoji: '😬', name: '尴尬', category: 'smileys' },
{ emoji: '😌', name: '放松', category: 'smileys' },
{ emoji: '😔', name: '难过', category: 'smileys' },
{ emoji: '😪', name: '疲惫', category: 'smileys' },
{ emoji: '🤤', name: '流口水', category: 'smileys' },
{ emoji: '😷', name: '口罩', category: 'smileys' },
{ emoji: '🤒', name: '发烧', category: 'smileys' },
{ emoji: '🤕', name: '受伤', category: 'smileys' },
{ emoji: '🤢', name: '呕吐', category: 'smileys' },
{ emoji: '🤮', name: '恶心', category: 'smileys' },
{ emoji: '🤧', name: '打喷嚏', category: 'smileys' },
{ emoji: '🥵', name: '热', category: 'smileys' },
{ emoji: '🥶', name: '冷', category: 'smileys' },
{ emoji: '🥴', name: '醉', category: 'smileys' },
{ emoji: '😵‍💫', name: '头晕', category: 'smileys' },
{ emoji: '🤯', name: '震惊', category: 'smileys' },
{ emoji: '🤠', name: '牛仔', category: 'smileys' },
{ emoji: '🥳', name: '庆祝', category: 'smileys' },
{ emoji: '😎', name: '酷', category: 'smileys' },
{ emoji: '🤓', name: '书呆子', category: 'smileys' },
{ emoji: '😕', name: '困惑', category: 'smileys' },
{ emoji: '😟', name: '担忧', category: 'smileys' },
{ emoji: '🙁', name: '不高兴', category: 'smileys' },
{ emoji: '😮', name: '惊讶', category: 'smileys' },
{ emoji: '😯', name: '震惊', category: 'smileys' },
{ emoji: '😲', name: '惊讶', category: 'smileys' },
{ emoji: '😳', name: '害羞', category: 'smileys' },
{ emoji: '🥺', name: '恳求', category: 'smileys' },
{ emoji: '😦', name: '震惊', category: 'smileys' },
{ emoji: '😧', name: '担心', category: 'smileys' },
{ emoji: '😨', name: '害怕', category: 'smileys' },
{ emoji: '😰', name: '恐慌', category: 'smileys' },
{ emoji: '😥', name: '难过', category: 'smileys' },
{ emoji: '😢', name: '哭泣', category: 'smileys' },
{ emoji: '😭', name: '大哭', category: 'smileys' },
{ emoji: '😱', name: '恐惧', category: 'smileys' },
{ emoji: '😖', name: '痛苦', category: 'smileys' },
{ emoji: '😣', name: '压力', category: 'smileys' },
{ emoji: '😞', name: '失望', category: 'smileys' },
{ emoji: '😓', name: '汗', category: 'smileys' },
{ emoji: '😩', name: '疲惫', category: 'smileys' },
{ emoji: '😫', name: '无奈', category: 'smileys' },
{ emoji: '🥱', name: '打哈欠', category: 'smileys' },
{ emoji: '😤', name: '愤怒', category: 'smileys' },
{ emoji: '😡', name: '生气', category: 'smileys' },
{ emoji: '😠', name: '愤怒', category: 'smileys' },
{ emoji: '🤬', name: '暴怒', category: 'smileys' },
{ emoji: '😈', name: '恶魔', category: 'smileys' },
{ emoji: '👿', name: '魔鬼', category: 'smileys' },
{ emoji: '💀', name: '骷髅', category: 'smileys' },
{ emoji: '💩', name: '便便', category: 'smileys' },
{ emoji: '🤡', name: '小丑', category: 'smileys' },
{ emoji: '👹', name: '妖怪', category: 'smileys' },
{ emoji: '👺', name: '小鬼', category: 'smileys' },
{ emoji: '👻', name: '幽灵', category: 'smileys' },
{ emoji: '💋', name: '吻', category: 'smileys' },
{ emoji: '💌', name: '情书', category: 'smileys' },
{ emoji: '💘', name: '爱心箭', category: 'smileys' },
{ emoji: '💝', name: '礼物', category: 'smileys' },
{ emoji: '💖', name: '爱心', category: 'smileys' },
{ emoji: '💗', name: '爱心', category: 'smileys' },
{ emoji: '💓', name: '心跳', category: 'smileys' },
{ emoji: '💞', name: '双心', category: 'smileys' },
{ emoji: '💕', name: '双心', category: 'smileys' },
{ emoji: '💟', name: '心形', category: 'smileys' },
{ emoji: '💔', name: '破碎的心', category: 'smileys' },
{ emoji: '❤️', name: '红心', category: 'smileys' },
{ emoji: '🧡', name: '橙心', category: 'smileys' },
{ emoji: '💛', name: '黄心', category: 'smileys' },
{ emoji: '💚', name: '绿心', category: 'smileys' },
{ emoji: '💙', name: '蓝心', category: 'smileys' },
{ emoji: '💜', name: '紫心', category: 'smileys' },
{ emoji: '🖤', name: '黑心', category: 'smileys' },
// 人物与身体
{ emoji: '👍', name: '点赞', category: 'people' },
{ emoji: '👎', name: '踩', category: 'people' },
{ emoji: '👋', name: '挥手', category: 'people' },
{ emoji: '🤝', name: '握手', category: 'people' },
{ emoji: '🙏', name: '祈祷', category: 'people' },
{ emoji: '👌', name: 'OK', category: 'people' },
{ emoji: '✌️', name: '胜利', category: 'people' },
{ emoji: '🤘', name: '摇滚', category: 'people' },
{ emoji: '🤙', name: '打电话', category: 'people' },
{ emoji: '👊', name: '拳头', category: 'people' },
{ emoji: '✊', name: '举起拳头', category: 'people' },
{ emoji: '👆', name: '向上指', category: 'people' },
{ emoji: '👇', name: '向下指', category: 'people' },
{ emoji: '👈', name: '向左指', category: 'people' },
{ emoji: '👉', name: '向右指', category: 'people' },
// 动物与自然
{ emoji: '🐶', name: '狗', category: 'animals' },
{ emoji: '🐱', name: '猫', category: 'animals' },
{ emoji: '🐭', name: '老鼠', category: 'animals' },
{ emoji: '🐹', name: '仓鼠', category: 'animals' },
{ emoji: '🐰', name: '兔子', category: 'animals' },
{ emoji: '🦊', name: '狐狸', category: 'animals' },
{ emoji: '🐻', name: '熊', category: 'animals' },
{ emoji: '🐼', name: '熊猫', category: 'animals' },
{ emoji: '🐨', name: '考拉', category: 'animals' },
{ emoji: '🐯', name: '老虎', category: 'animals' },
{ emoji: '🦁', name: '狮子', category: 'animals' },
{ emoji: '🐮', name: '牛', category: 'animals' },
{ emoji: '🐷', name: '猪', category: 'animals' },
{ emoji: '🐸', name: '青蛙', category: 'animals' },
{ emoji: '🐵', name: '猴子', category: 'animals' },
{ emoji: '🐔', name: '鸡', category: 'animals' },
{ emoji: '🐧', name: '企鹅', category: 'animals' },
{ emoji: '🐦', name: '鸟', category: 'animals' },
{ emoji: '🐤', name: '小鸡', category: 'animals' },
{ emoji: '🐣', name: '破壳小鸡', category: 'animals' },
{ emoji: '🐥', name: '黄小鸡', category: 'animals' },
{ emoji: '🦆', name: '鸭子', category: 'animals' },
{ emoji: '🦅', name: '鹰', category: 'animals' },
{ emoji: '🦉', name: '猫头鹰', category: 'animals' },
{ emoji: '🦇', name: '蝙蝠', category: 'animals' },
{ emoji: '🐺', name: '狼', category: 'animals' },
{ emoji: '🐗', name: '野猪', category: 'animals' },
{ emoji: '🐴', name: '马', category: 'animals' },
{ emoji: '🦄', name: '独角兽', category: 'animals' },
{ emoji: '🐝', name: '蜜蜂', category: 'animals' },
{ emoji: '🐛', name: '毛毛虫', category: 'animals' },
{ emoji: '🦋', name: '蝴蝶', category: 'animals' },
{ emoji: '🐌', name: '蜗牛', category: 'animals' },
{ emoji: '🐞', name: '瓢虫', category: 'animals' },
{ emoji: '🐜', name: '蚂蚁', category: 'animals' },
{ emoji: '🐢', name: '乌龟', category: 'animals' },
{ emoji: '🐍', name: '蛇', category: 'animals' },
{ emoji: '🦎', name: '蜥蜴', category: 'animals' },
{ emoji: '🦖', name: '霸王龙', category: 'animals' },
{ emoji: '🦕', name: '雷龙', category: 'animals' },
{ emoji: '🐙', name: '章鱼', category: 'animals' },
{ emoji: '🐚', name: '贝壳', category: 'animals' },
{ emoji: '🐌', name: '蜗牛', category: 'animals' },
{ emoji: '🌱', name: '发芽', category: 'animals' },
{ emoji: '🌿', name: '草', category: 'animals' },
{ emoji: '🍃', name: '叶子', category: 'animals' },
{ emoji: '🍂', name: '落叶', category: 'animals' },
{ emoji: '🍁', name: '枫叶', category: 'animals' },
{ emoji: '🌾', name: '稻穗', category: 'animals' },
{ emoji: '🌴', name: '棕榈树', category: 'animals' },
{ emoji: '🌵', name: '仙人掌', category: 'animals' },
{ emoji: '🌷', name: '郁金香', category: 'animals' },
{ emoji: '🌺', name: '花朵', category: 'animals' },
{ emoji: '🌸', name: '樱花', category: 'animals' },
{ emoji: '🌹', name: '玫瑰', category: 'animals' },
{ emoji: '🌻', name: '向日葵', category: 'animals' },
{ emoji: '🌼', name: '雏菊', category: 'animals' },
// 食物与饮料
{ emoji: '🍎', name: '苹果', category: 'food' },
{ emoji: '🍏', name: '青苹果', category: 'food' },
{ emoji: '🍐', name: '梨', category: 'food' },
{ emoji: '🍊', name: '橘子', category: 'food' },
{ emoji: '🍋', name: '柠檬', category: 'food' },
{ emoji: '🍌', name: '香蕉', category: 'food' },
{ emoji: '🍉', name: '西瓜', category: 'food' },
{ emoji: '🍇', name: '葡萄', category: 'food' },
{ emoji: '🍓', name: '草莓', category: 'food' },
{ emoji: '🍈', name: '瓜', category: 'food' },
{ emoji: '🍒', name: '樱桃', category: 'food' },
{ emoji: '🍑', name: '桃子', category: 'food' },
{ emoji: '🍍', name: '菠萝', category: 'food' },
{ emoji: '🥥', name: '椰子', category: 'food' },
{ emoji: '🥝', name: '猕猴桃', category: 'food' },
{ emoji: '🍅', name: '番茄', category: 'food' },
{ emoji: '🥑', name: '牛油果', category: 'food' },
{ emoji: '🥦', name: '西兰花', category: 'food' },
{ emoji: '🥬', name: '生菜', category: 'food' },
{ emoji: '🥒', name: '黄瓜', category: 'food' },
{ emoji: '🥜', name: '花生', category: 'food' },
{ emoji: '🌰', name: '栗子', category: 'food' },
{ emoji: '🍞', name: '面包', category: 'food' },
{ emoji: '🥐', name: '羊角面包', category: 'food' },
{ emoji: '🥖', name: '法棍', category: 'food' },
{ emoji: '🥨', name: '椒盐卷饼', category: 'food' },
{ emoji: '🥯', name: '百吉饼', category: 'food' },
{ emoji: '🧀', name: '奶酪', category: 'food' },
{ emoji: '🍖', name: '肉', category: 'food' },
{ emoji: '🍗', name: '鸡肉', category: 'food' },
{ emoji: '🍔', name: '汉堡', category: 'food' },
{ emoji: '🍟', name: '薯条', category: 'food' },
{ emoji: '🍕', name: '披萨', category: 'food' },
{ emoji: '🌭', name: '热狗', category: 'food' },
{ emoji: '🥪', name: '三明治', category: 'food' },
{ emoji: '🌮', name: '墨西哥卷', category: 'food' },
{ emoji: '🌯', name: '墨西哥饼', category: 'food' },
{ emoji: '🥙', name: '皮塔饼', category: 'food' },
{ emoji: '🍱', name: '便当', category: 'food' },
{ emoji: '🍘', name: '米饼', category: 'food' },
{ emoji: '🍙', name: '饭团', category: 'food' },
{ emoji: '🍚', name: '米饭', category: 'food' },
{ emoji: '🍛', name: '咖喱饭', category: 'food' },
{ emoji: '🍜', name: '拉面', category: 'food' },
{ emoji: '🍝', name: '意大利面', category: 'food' },
{ emoji: '🍠', name: '红薯', category: 'food' },
{ emoji: '🍢', name: '烤串', category: 'food' },
{ emoji: '🍣', name: '寿司', category: 'food' },
{ emoji: '🍤', name: '虾', category: 'food' },
{ emoji: '🍥', name: '鱼板', category: 'food' },
{ emoji: '🥮', name: '月饼', category: 'food' },
{ emoji: '🍡', name: '丸子', category: 'food' },
{ emoji: '🍧', name: '刨冰', category: 'food' },
{ emoji: '🍨', name: '冰淇淋', category: 'food' },
{ emoji: '🍩', name: '甜甜圈', category: 'food' },
{ emoji: '🍪', name: '饼干', category: 'food' },
{ emoji: '🎂', name: '蛋糕', category: 'food' },
{ emoji: '🍰', name: '甜点', category: 'food' },
{ emoji: '🧁', name: '纸杯蛋糕', category: 'food' },
{ emoji: '🍫', name: '巧克力', category: 'food' },
{ emoji: '🍬', name: '糖果', category: 'food' },
{ emoji: '🍭', name: '棒棒糖', category: 'food' },
{ emoji: '🍮', name: '布丁', category: 'food' },
{ emoji: '🍯', name: '蜂蜜', category: 'food' },
{ emoji: '🥛', name: '牛奶', category: 'food' },
{ emoji: '🍼', name: '奶瓶', category: 'food' },
{ emoji: '☕', name: '咖啡', category: 'food' },
{ emoji: '🍵', name: '茶', category: 'food' },
{ emoji: '🍶', name: '清酒', category: 'food' },
{ emoji: '🍾', name: '香槟', category: 'food' },
{ emoji: '🍷', name: '红酒', category: 'food' },
{ emoji: '🍸', name: '鸡尾酒', category: 'food' },
{ emoji: '🍹', name: '果汁', category: 'food' },
{ emoji: '🍺', name: '啤酒', category: 'food' },
{ emoji: '🍻', name: '干杯', category: 'food' },
{ emoji: '🥂', name: '碰杯', category: 'food' },
// 旅行与地点
{ emoji: '🌍', name: '地球', category: 'travel' },
{ emoji: '🌎', name: '西半球', category: 'travel' },
{ emoji: '🌏', name: '东半球', category: 'travel' },
{ emoji: '🌐', name: '全球', category: 'travel' },
{ emoji: '🗺️', name: '地图', category: 'travel' },
{ emoji: '🗾', name: '日本地图', category: 'travel' },
{ emoji: '🧭', name: '指南针', category: 'travel' },
{ emoji: '🏔️', name: '雪山', category: 'travel' },
{ emoji: '🏕️', name: '露营', category: 'travel' },
{ emoji: '🏖️', name: '沙滩', category: 'travel' },
{ emoji: '🏜️', name: '沙漠', category: 'travel' },
{ emoji: '🏝️', name: '荒岛', category: 'travel' },
{ emoji: '🏞️', name: '风景', category: 'travel' },
{ emoji: '🌋', name: '火山', category: 'travel' },
{ emoji: '💧', name: '水滴', category: 'travel' },
{ emoji: '🌊', name: '海浪', category: 'travel' },
{ emoji: '🌅', name: '日出', category: 'travel' },
{ emoji: '🌄', name: '日落', category: 'travel' },
{ emoji: '🌠', name: '流星', category: 'travel' },
{ emoji: '⭐', name: '星星', category: 'travel' },
{ emoji: '🌟', name: '闪亮星星', category: 'travel' },
{ emoji: '🌙', name: '月亮', category: 'travel' },
{ emoji: '🌝', name: '满月', category: 'travel' },
{ emoji: '🌚', name: '新月', category: 'travel' },
{ emoji: '🌛', name: '弯月', category: 'travel' },
{ emoji: '🌜', name: '残月', category: 'travel' },
{ emoji: '🌞', name: '太阳', category: 'travel' },
{ emoji: '🌡️', name: '温度计', category: 'travel' },
{ emoji: '🌤️', name: '晴', category: 'travel' },
{ emoji: '⛅', name: '多云', category: 'travel' },
{ emoji: '🌥️', name: '阴天', category: 'travel' },
{ emoji: '☁️', name: '云', category: 'travel' },
{ emoji: '🌧️', name: '下雨', category: 'travel' },
{ emoji: '⛈️', name: '雷雨', category: 'travel' },
{ emoji: '🌩️', name: '闪电', category: 'travel' },
{ emoji: '❄️', name: '雪花', category: 'travel' },
{ emoji: '☃️', name: '雪人', category: 'travel' },
{ emoji: '⛄', name: '雪⼈', category: 'travel' },
{ emoji: '🌬️', name: '风', category: 'travel' },
{ emoji: '💨', name: '大风', category: 'travel' },
{ emoji: '🕐', name: '1点', category: 'travel' },
{ emoji: '🕑', name: '2点', category: 'travel' },
{ emoji: '🕒', name: '3点', category: 'travel' },
{ emoji: '🕓', name: '4点', category: 'travel' },
{ emoji: '🕔', name: '5点', category: 'travel' },
{ emoji: '🕕', name: '6点', category: 'travel' },
{ emoji: '🕖', name: '7点', category: 'travel' },
{ emoji: '🕗', name: '8点', category: 'travel' },
{ emoji: '🕘', name: '9点', category: 'travel' },
{ emoji: '🕙', name: '10点', category: 'travel' },
{ emoji: '🕚', name: '11点', category: 'travel' },
{ emoji: '🕛', name: '12点', category: 'travel' },
// 活动
{ emoji: '⚽', name: '足球', category: 'activities' },
{ emoji: '🏀', name: '篮球', category: 'activities' },
{ emoji: '🏈', name: '橄榄球', category: 'activities' },
{ emoji: '⚾', name: '棒球', category: 'activities' },
{ emoji: '🥎', name: '垒球', category: 'activities' },
{ emoji: '🏐', name: '排球', category: 'activities' },
{ emoji: '🏉', name: '橄榄球', category: 'activities' },
{ emoji: '🎾', name: '网球', category: 'activities' },
{ emoji: '🥏', name: '飞盘', category: 'activities' },
{ emoji: '🏐', name: '排球', category: 'activities' },
{ emoji: '🏏', name: '板球', category: 'activities' },
{ emoji: '🏑', name: '曲棍球', category: 'activities' },
{ emoji: '🥍', name: '长曲棍球', category: 'activities' },
{ emoji: '🏒', name: '冰球', category: 'activities' },
{ emoji: '🥅', name: '球门', category: 'activities' },
{ emoji: '⛳', name: '高尔夫', category: 'activities' },
{ emoji: '⛸️', name: '滑冰', category: 'activities' },
{ emoji: '🎿', name: '滑雪', category: 'activities' },
{ emoji: '🛷', name: '雪橇', category: 'activities' },
{ emoji: '🏂', name: '单板滑雪', category: 'activities' },
{ emoji: '🏋️', name: '举重', category: 'activities' },
{ emoji: '🏌️', name: '高尔夫', category: 'activities' },
{ emoji: '🚣', name: '划船', category: 'activities' },
{ emoji: '🏊', name: '游泳', category: 'activities' },
{ emoji: '🏄', name: '冲浪', category: 'activities' },
{ emoji: '🏇', name: '赛马', category: 'activities' },
{ emoji: '🚴', name: '骑行', category: 'activities' },
{ emoji: '🚵', name: '山地自行车', category: 'activities' },
{ emoji: '🏹', name: '射箭', category: 'activities' },
{ emoji: '🎣', name: '钓鱼', category: 'activities' },
{ emoji: '🤿', name: '潜水', category: 'activities' },
{ emoji: '🎿', name: '滑雪', category: 'activities' },
{ emoji: '🎽', name: '运动衫', category: 'activities' },
{ emoji: '🎯', name: '靶心', category: 'activities' },
{ emoji: '🎱', name: '台球', category: 'activities' },
{ emoji: '🎮', name: '游戏手柄', category: 'activities' },
{ emoji: '🎰', name: '老虎机', category: 'activities' },
{ emoji: '🎲', name: '骰子', category: 'activities' },
{ emoji: '🎯', name: '靶心', category: 'activities' },
{ emoji: '🎭', name: '面具', category: 'activities' },
{ emoji: '🎪', name: '马戏团', category: 'activities' },
{ emoji: '🎫', name: '票', category: 'activities' },
{ emoji: '🎟️', name: '门票', category: 'activities' },
{ emoji: '🎨', name: '调色板', category: 'activities' },
{ emoji: '🎬', name: '电影', category: 'activities' },
{ emoji: '🎤', name: '麦克风', category: 'activities' },
{ emoji: '🎧', name: '耳机', category: 'activities' },
{ emoji: '🎼', name: '乐谱', category: 'activities' },
{ emoji: '🎵', name: '音符', category: 'activities' },
{ emoji: '🎶', name: '音乐', category: 'activities' },
{ emoji: '🎹', name: '钢琴', category: 'activities' },
{ emoji: '🥁', name: '鼓', category: 'activities' },
{ emoji: '🎷', name: '萨克斯', category: 'activities' },
{ emoji: '🎺', name: '小号', category: 'activities' },
{ emoji: '🎸', name: '吉他', category: 'activities' },
{ emoji: '🎻', name: '小提琴', category: 'activities' },
{ emoji: '🎪', name: '马戏团', category: 'activities' },
{ emoji: '🎭', name: '戏剧', category: 'activities' },
{ emoji: '🎨', name: '艺术', category: 'activities' },
{ emoji: '🎯', name: '目标', category: 'activities' },
// 物体
{ emoji: '💻', name: '电脑', category: 'objects' },
{ emoji: '📱', name: '手机', category: 'objects' },
{ emoji: '📞', name: '电话', category: 'objects' },
{ emoji: '📟', name: '寻呼机', category: 'objects' },
{ emoji: '📠', name: '传真', category: 'objects' },
{ emoji: '💽', name: '光盘', category: 'objects' },
{ emoji: '💾', name: '软盘', category: 'objects' },
{ emoji: '💿', name: 'CD', category: 'objects' },
{ emoji: '📀', name: 'DVD', category: 'objects' },
{ emoji: '📼', name: '录像带', category: 'objects' },
{ emoji: '📷', name: '相机', category: 'objects' },
{ emoji: '📸', name: '拍照', category: 'objects' },
{ emoji: '📹', name: '摄像机', category: 'objects' },
{ emoji: '🎥', name: '电影摄像机', category: 'objects' },
{ emoji: '📺', name: '电视', category: 'objects' },
{ emoji: '📻', name: '收音机', category: 'objects' },
{ emoji: '📟', name: '寻呼机', category: 'objects' },
{ emoji: '🔋', name: '电池', category: 'objects' },
{ emoji: '🔌', name: '插头', category: 'objects' },
{ emoji: '💡', name: '灯泡', category: 'objects' },
{ emoji: '🔦', name: '手电筒', category: 'objects' },
{ emoji: '📔', name: '笔记本', category: 'objects' },
{ emoji: '📕', name: '书', category: 'objects' },
{ emoji: '📖', name: '打开的书', category: 'objects' },
{ emoji: '📗', name: '绿书', category: 'objects' },
{ emoji: '📘', name: '蓝书', category: 'objects' },
{ emoji: '📙', name: '黄书', category: 'objects' },
{ emoji: '📚', name: '书架', category: 'objects' },
{ emoji: '📓', name: '笔记本', category: 'objects' },
{ emoji: '📒', name: '笔记本', category: 'objects' },
{ emoji: '📃', name: '文件', category: 'objects' },
{ emoji: '📜', name: '卷轴', category: 'objects' },
{ emoji: '📄', name: '纸张', category: 'objects' },
{ emoji: '📰', name: '报纸', category: 'objects' },
{ emoji: '📞', name: '电话', category: 'objects' },
{ emoji: '☎️', name: '电话', category: 'objects' },
{ emoji: '📟', name: '寻呼机', category: 'objects' },
{ emoji: '📠', name: '传真', category: 'objects' },
{ emoji: '📡', name: '天线', category: 'objects' },
{ emoji: '💾', name: '软盘', category: 'objects' },
{ emoji: '💿', name: 'CD', category: 'objects' },
{ emoji: '📀', name: 'DVD', category: 'objects' },
{ emoji: '📼', name: '录像带', category: 'objects' },
{ emoji: '🔍', name: '放大镜', category: 'objects' },
{ emoji: '🔎', name: '搜索', category: 'objects' },
{ emoji: '🕯️', name: '蜡烛', category: 'objects' },
{ emoji: '💣', name: '炸弹', category: 'objects' },
{ emoji: '🔫', name: '枪', category: 'objects' },
{ emoji: '🔪', name: '刀', category: 'objects' },
{ emoji: '💊', name: '药丸', category: 'objects' },
{ emoji: '💉', name: '注射器', category: 'objects' },
{ emoji: '🔬', name: '显微镜', category: 'objects' },
{ emoji: '🔭', name: '望远镜', category: 'objects' },
{ emoji: '🧪', name: '试管', category: 'objects' },
{ emoji: '🧫', name: '培养皿', category: 'objects' },
{ emoji: '🧬', name: 'DNA', category: 'objects' },
// 符号
{ emoji: '❤️', name: '红心', category: 'symbols' },
{ emoji: '🧡', name: '橙心', category: 'symbols' },
{ emoji: '💛', name: '黄心', category: 'symbols' },
{ emoji: '💚', name: '绿心', category: 'symbols' },
{ emoji: '💙', name: '蓝心', category: 'symbols' },
{ emoji: '💜', name: '紫心', category: 'symbols' },
{ emoji: '🖤', name: '黑心', category: 'symbols' },
{ emoji: '💔', name: '破碎的心', category: 'symbols' },
{ emoji: '💓', name: '心跳', category: 'symbols' },
{ emoji: '💗', name: '爱心', category: 'symbols' },
{ emoji: '💖', name: '闪亮的心', category: 'symbols' },
{ emoji: '💘', name: '丘比特之箭', category: 'symbols' },
{ emoji: '💝', name: '礼物', category: 'symbols' },
{ emoji: '💞', name: '双心', category: 'symbols' },
{ emoji: '💟', name: '心形', category: 'symbols' },
{ emoji: '❣️', name: '感叹号', category: 'symbols' },
{ emoji: '💕', name: '双心', category: 'symbols' },
{ emoji: '💌', name: '情书', category: 'symbols' },
{ emoji: '💋', name: '吻', category: 'symbols' },
{ emoji: '💯', name: '满分', category: 'symbols' },
{ emoji: '💢', name: '生气', category: 'symbols' },
{ emoji: '💥', name: '爆炸', category: 'symbols' },
{ emoji: '💫', name: '星星', category: 'symbols' },
{ emoji: '💦', name: '水滴', category: 'symbols' },
{ emoji: '💨', name: '风', category: 'symbols' },
{ emoji: '🕳️', name: '洞', category: 'symbols' },
{ emoji: '💣', name: '炸弹', category: 'symbols' },
{ emoji: '💬', name: '对话', category: 'symbols' },
{ emoji: '💭', name: '思考', category: 'symbols' },
{ emoji: '💤', name: '打鼾', category: 'symbols' },
{ emoji: '💡', name: '灯泡', category: 'symbols' },
{ emoji: '💢', name: '生气', category: 'symbols' },
{ emoji: '🔔', name: '铃铛', category: 'symbols' },
{ emoji: '🔕', name: '静音', category: 'symbols' },
{ emoji: '📣', name: '喇叭', category: 'symbols' },
{ emoji: '📢', name: '扩音器', category: 'symbols' },
{ emoji: '🔊', name: '音量', category: 'symbols' },
{ emoji: '🔉', name: '音量', category: 'symbols' },
{ emoji: '🔈', name: '音量', category: 'symbols' },
{ emoji: '🔇', name: '静音', category: 'symbols' },
{ emoji: '✅', name: '对勾', category: 'symbols' },
{ emoji: '❌', name: '叉号', category: 'symbols' },
{ emoji: '❓', name: '问号', category: 'symbols' },
{ emoji: '❗', name: '感叹号', category: 'symbols' },
{ emoji: '❔', name: '问号', category: 'symbols' },
{ emoji: '❕', name: '感叹号', category: 'symbols' },
{ emoji: '💯', name: '100分', category: 'symbols' },
{ emoji: '🔟', name: '10', category: 'symbols' },
{ emoji: '🔢', name: '数字', category: 'symbols' },
{ emoji: '🔣', name: '符号', category: 'symbols' },
{ emoji: '🔤', name: '字母', category: 'symbols' },
{ emoji: '🔡', name: '小写', category: 'symbols' },
{ emoji: '🔠', name: '大写', category: 'symbols' },
{ emoji: '🔸', name: '菱形', category: 'symbols' },
{ emoji: '🔹', name: '蓝色菱形', category: 'symbols' },
{ emoji: '🔺', name: '三角形', category: 'symbols' },
{ emoji: '🔻', name: '倒三角形', category: 'symbols' },
{ emoji: '💠', name: '钻石', category: 'symbols' },
{ emoji: '🔘', name: '圆形', category: 'symbols' },
{ emoji: '🔴', name: '红圆', category: 'symbols' },
{ emoji: '🔵', name: '蓝圆', category: 'symbols' },
{ emoji: '🔺', name: '三角形', category: 'symbols' },
{ emoji: '🔻', name: '倒三角形', category: 'symbols' },
{ emoji: '🔸', name: '橙色菱形', category: 'symbols' },
{ emoji: '🔹', name: '蓝色菱形', category: 'symbols' },
{ emoji: '🔶', name: '橙色六边形', category: 'symbols' },
{ emoji: '🔷', name: '蓝色六边形', category: 'symbols' },
{ emoji: '🔳', name: '白色正方形', category: 'symbols' },
{ emoji: '🔲', name: '黑色正方形', category: 'symbols' },
// 旗帜
{ emoji: '🇨🇳', name: '中国国旗', category: 'flags' },
{ emoji: '🇺🇸', name: '美国国旗', category: 'flags' },
{ emoji: '🇯🇵', name: '日本国旗', category: 'flags' },
{ emoji: '🇰🇷', name: '韩国国旗', category: 'flags' },
{ emoji: '🇬🇧', name: '英国国旗', category: 'flags' },
{ emoji: '🇫🇷', name: '法国国旗', category: 'flags' },
{ emoji: '🇩🇪', name: '德国国旗', category: 'flags' },
{ emoji: '🇮🇹', name: '意大利国旗', category: 'flags' },
{ emoji: '🇷🇺', name: '俄罗斯国旗', category: 'flags' },
{ emoji: '🇺🇾', name: '乌拉圭国旗', category: 'flags' },
{ emoji: '🇧🇪', name: '比利时国旗', category: 'flags' },
{ emoji: '🇳🇱', name: '荷兰国旗', category: 'flags' },
{ emoji: '🇪🇸', name: '西班牙国旗', category: 'flags' },
{ emoji: '🇵🇹', name: '葡萄牙国旗', category: 'flags' },
{ emoji: '🇮🇪', name: '爱尔兰国旗', category: 'flags' },
{ emoji: '🇦🇺', name: '澳大利亚国旗', category: 'flags' },
{ emoji: '🇨🇦', name: '加拿大国旗', category: 'flags' },
{ emoji: '🇮🇱', name: '以色列国旗', category: 'flags' },
{ emoji: '🇵🇰', name: '巴基斯坦国旗', category: 'flags' },
{ emoji: '🇮🇳', name: '印度国旗', category: 'flags' },
{ emoji: '🇮🇩', name: '印度尼西亚国旗', category: 'flags' },
{ emoji: '🇹🇭', name: '泰国国旗', category: 'flags' },
{ emoji: '🇻🇳', name: '越南国旗', category: 'flags' },
{ emoji: '🇸🇦', name: '沙特阿拉伯国旗', category: 'flags' },
{ emoji: '🇦🇪', name: '阿联酋国旗', category: 'flags' },
{ emoji: '🇹🇷', name: '土耳其国旗', category: 'flags' },
{ emoji: '🇲🇾', name: '马来西亚国旗', category: 'flags' },
{ emoji: '🇸🇬', name: '新加坡国旗', category: 'flags' },
{ emoji: '🇭🇰', name: '香港特别行政区区旗', category: 'flags' },
{ emoji: '🇲🇴', name: '澳门特别行政区区旗', category: 'flags' },
{ emoji: '🇳🇴', name: '挪威国旗', category: 'flags' },
{ emoji: '🇸🇪', name: '瑞典国旗', category: 'flags' },
{ emoji: '🇫🇮', name: '芬兰国旗', category: 'flags' },
{ emoji: '🇩🇰', name: '丹麦国旗', category: 'flags' },
{ emoji: '🇮🇸', name: '冰岛国旗', category: 'flags' },
{ emoji: '🇨🇭', name: '瑞士国旗', category: 'flags' },
{ emoji: '🇦🇹', name: '奥地利国旗', category: 'flags' },
{ emoji: '🇭🇺', name: '匈牙利国旗', category: 'flags' },
{ emoji: '🇵🇱', name: '波兰国旗', category: 'flags' },
{ emoji: '🇨🇿', name: '捷克国旗', category: 'flags' },
{ emoji: '🇸🇰', name: '斯洛伐克国旗', category: 'flags' },
{ emoji: '🇭🇷', name: '克罗地亚国旗', category: 'flags' },
{ emoji: '🇧🇦', name: '波黑国旗', category: 'flags' },
{ emoji: '🇸🇮', name: '斯洛文尼亚国旗', category: 'flags' },
{ emoji: '🇲🇰', name: '北马其顿国旗', category: 'flags' },
{ emoji: '🇧🇬', name: '保加利亚国旗', category: 'flags' },
{ emoji: '🇷🇴', name: '罗马尼亚国旗', category: 'flags' },
{ emoji: '🇺🇦', name: '乌克兰国旗', category: 'flags' },
{ emoji: '🇧🇾', name: '白俄罗斯国旗', category: 'flags' },
{ emoji: '🇰🇿', name: '哈萨克斯坦国旗', category: 'flags' },
{ emoji: '🇺🇿', name: '乌兹别克斯坦国旗', category: 'flags' },
{ emoji: '🇰🇬', name: '吉尔吉斯斯坦国旗', category: 'flags' },
{ emoji: '🇹🇯', name: '塔吉克斯坦国旗', category: 'flags' },
{ emoji: '🇦🇲', name: '亚美尼亚国旗', category: 'flags' },
{ emoji: '🇦🇿', name: '阿塞拜疆国旗', category: 'flags' },
{ emoji: '🇮🇶', name: '伊拉克国旗', category: 'flags' },
{ emoji: '🇮🇷', name: '伊朗国旗', category: 'flags' },
{ emoji: '🇱🇧', name: '黎巴嫩国旗', category: 'flags' },
{ emoji: '🇸🇾', name: '叙利亚国旗', category: 'flags' },
{ emoji: '🇯🇴', name: '约旦国旗', category: 'flags' },
{ emoji: '🇵🇸', name: '巴勒斯坦国旗', category: 'flags' },
{ emoji: '🇰🇼', name: '科威特国旗', category: 'flags' },
{ emoji: '🇴🇲', name: '阿曼国旗', category: 'flags' },
{ emoji: '🇶🇦', name: '卡塔尔国旗', category: 'flags' },
{ emoji: '🇧🇭', name: '巴林国旗', category: 'flags' },
{ emoji: '🇾🇪', name: '也门国旗', category: 'flags' },
{ emoji: '🇲🇦', name: '摩洛哥国旗', category: 'flags' },
{ emoji: '🇩🇿', name: '阿尔及利亚国旗', category: 'flags' },
{ emoji: '🇹🇳', name: '突尼斯国旗', category: 'flags' },
{ emoji: '🇪🇬', name: '埃及国旗', category: 'flags' },
{ emoji: '🇸🇩', name: '苏丹国旗', category: 'flags' },
{ emoji: '🇪🇷', name: '厄立特里亚国旗', category: 'flags' },
{ emoji: '🇩🇯', name: '吉布提国旗', category: 'flags' },
{ emoji: '🇸🇴', name: '索马里国旗', category: 'flags' },
{ emoji: '🇰🇪', name: '肯尼亚国旗', category: 'flags' },
{ emoji: '🇺🇬', name: '乌干达国旗', category: 'flags' },
{ emoji: '🇹🇿', name: '坦桑尼亚国旗', category: 'flags' },
{ emoji: '🇷🇼', name: '卢旺达国旗', category: 'flags' },
{ emoji: '🇧🇮', name: '布隆迪国旗', category: 'flags' },
{ emoji: '🇨🇩', name: '刚果民主共和国国旗', category: 'flags' },
{ emoji: '🇨🇬', name: '刚果共和国国旗', category: 'flags' },
{ emoji: '🇦🇴', name: '安哥拉国旗', category: 'flags' },
{ emoji: '🇲🇿', name: '莫桑比克国旗', category: 'flags' },
{ emoji: '🇿🇦', name: '南非国旗', category: 'flags' },
{ emoji: '🇳🇦', name: '纳米比亚国旗', category: 'flags' },
{ emoji: '🇧🇼', name: '博茨瓦纳国旗', category: 'flags' },
{ emoji: '🇿🇲', name: '赞比亚国旗', category: 'flags' },
{ emoji: '🇲🇼', name: '马拉维国旗', category: 'flags' },
{ emoji: '🇲🇺', name: '毛里求斯国旗', category: 'flags' },
{ emoji: '🇸🇨', name: '塞舌尔国旗', category: 'flags' },
{ emoji: '🇨🇺', name: '古巴国旗', category: 'flags' },
{ emoji: '🇲🇽', name: '墨西哥国旗', category: 'flags' },
{ emoji: '🇧🇷', name: '巴西国旗', category: 'flags' },
{ emoji: '🇦🇷', name: '阿根廷国旗', category: 'flags' },
{ emoji: '🇨🇱', name: '智利国旗', category: 'flags' },
{ emoji: '🇵🇪', name: '秘鲁国旗', category: 'flags' },
{ emoji: '🇪🇨', name: '厄瓜多尔国旗', category: 'flags' },
{ emoji: '🇧🇴', name: '玻利维亚国旗', category: 'flags' },
{ emoji: '🇵🇾', name: '巴拉圭国旗', category: 'flags' },
{ emoji: '🇨🇺', name: '古巴国旗', category: 'flags' },
{ emoji: '🇩🇴', name: '多米尼加共和国国旗', category: 'flags' },
{ emoji: '🇵🇭', name: '菲律宾国旗', category: 'flags' },
{ emoji: '🇻🇺', name: '瓦努阿图国旗', category: 'flags' },
{ emoji: '🇫🇯', name: '斐济国旗', category: 'flags' },
{ emoji: '🇵🇳', name: '帕劳国旗', category: 'flags' },
{ emoji: '🇲🇵', name: '马绍尔群岛国旗', category: 'flags' },
{ emoji: '🇫🇲', name: '密克罗尼西亚联邦国旗', category: 'flags' },
{ emoji: '🇰🇮', name: '基里巴斯国旗', category: 'flags' },
{ emoji: '🇹🇻', name: '图瓦卢国旗', category: 'flags' },
{ emoji: '🇨🇰', name: '库克群岛国旗', category: 'flags' },
{ emoji: '🇳🇺', name: '纽埃国旗', category: 'flags' },
{ emoji: '🇲🇲', name: '缅甸国旗', category: 'flags' },
{ emoji: '🇱🇰', name: '斯里兰卡国旗', category: 'flags' },
{ emoji: '🇲🇻', name: '马尔代夫国旗', category: 'flags' },
{ emoji: '🇧🇩', name: '孟加拉国国旗', category: 'flags' },
{ emoji: '🇳🇵', name: '尼泊尔国旗', category: 'flags' },
{ emoji: '🇱🇦', name: '老挝国旗', category: 'flags' },
{ emoji: '🇰🇭', name: '柬埔寨国旗', category: 'flags' }
]
// 过滤后的Emoji
const filteredEmojis = computed(() => {
let result = emojis
// 按分类过滤
if (selectedCategory.value !== 'all') {
result = result.filter(emoji => emoji.category === selectedCategory.value)
}
// 按关键词搜索
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase()
result = result.filter(emoji => emoji.name.toLowerCase().includes(keyword))
}
return result
})
// 复制Emoji
const copyEmoji = (emoji: string) => {
copy(emoji)
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<!-- 搜索和分类 -->
<div class="mb-4">
<el-input
v-model="searchKeyword"
placeholder="搜索Emoji"
class="mb-4"
/>
<div class="flex flex-wrap gap-2 mb-4">
<el-button
v-for="category in emojiCategories"
:key="category.id"
:type="selectedCategory === category.id ? 'primary' : 'default'"
@click="selectedCategory = category.id"
size="small"
>
{{ category.name }}
</el-button>
</div>
</div>
<!-- Emoji列表 -->
<div class="grid grid-cols-6 sm:grid-cols-8 md:grid-cols-10 lg:grid-cols-12 gap-4">
<div
v-for="emoji in filteredEmojis"
:key="emoji.emoji + emoji.name"
class="flex flex-col items-center p-2 border rounded hover:bg-gray-50 cursor-pointer"
@click="copyEmoji(emoji.emoji)"
>
<div class="text-3xl mb-1">{{ emoji.emoji }}</div>
<div class="text-xs text-center">{{ emoji.name }}</div>
</div>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线Emoji表情大全提供各种分类的Emoji表情点击即可复制到剪贴板方便在聊天文档中使用
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
// import { copy } from '@/utils/string' // import { copy } from '@/utils/string'
const info = reactive({ const info = reactive({
title: "tool name", title: "tool name",
@@ -13,12 +14,20 @@ const info = reactive({
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div class=""> <div class="p-4 rounded-2xl bg-white">
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
示例...
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -0,0 +1,234 @@
<script setup lang="ts">
import { ref, watch, computed } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { ElMessage } from 'element-plus'
import { copy } from '@/utils/string'
import { Md5 } from 'ts-md5'
import CryptoJS from 'crypto-js'
const title = "Hash计算器"
// 状态管理
const inputText = ref('')
const algorithm = ref('MD5')
const key = ref('')
const hashResult = ref('')
// 计算属性:是否是Hmac算法
const isHmacAlgorithm = computed(() => {
return algorithm.value.startsWith('HMAC-')
})
// 哈希算法选项
const algorithmOptions = [
{ label: 'MD5', value: 'MD5' },
{ label: 'SHA-1', value: 'SHA-1' },
{ label: 'SHA-256', value: 'SHA-256' },
{ label: 'SHA-384', value: 'SHA-384' },
{ label: 'SHA-512', value: 'SHA-512' },
{ label: 'Hmac-MD5', value: 'HMAC-MD5' },
{ label: 'Hmac-SHA1', value: 'HMAC-SHA1' },
{ label: 'Hmac-SHA256', value: 'HMAC-SHA256' },
{ label: 'Hmac-SHA384', value: 'HMAC-SHA384' },
{ label: 'Hmac-SHA512', value: 'HMAC-SHA512' }
]
// 监听输入变化,自动计算哈希
watch([inputText, algorithm, key], async () => {
if (!inputText.value) {
hashResult.value = ''
return
}
try {
if (algorithm.value === 'MD5') {
// 计算MD5
hashResult.value = Md5.hashStr(inputText.value)
} else if (algorithm.value.startsWith('HMAC-')) {
// 处理Hmac算法
if (!key.value) {
hashResult.value = '请输入Hmac密钥'
return
}
// 提取Hmac算法中的哈希算法部分
const hashAlgorithm = algorithm.value.replace('HMAC-', '')
if (hashAlgorithm === 'MD5') {
// 使用crypto-js计算Hmac-MD5
try {
const hmac = CryptoJS.HmacMD5(inputText.value, key.value)
hashResult.value = hmac.toString()
} catch (error) {
console.error('Hmac-MD5计算失败:', error)
hashResult.value = '计算失败'
}
} else {
// 处理其他Hmac算法
// 转换为Web Crypto API支持的标准格式
let standardHashAlgorithm
switch (hashAlgorithm) {
case 'SHA1':
standardHashAlgorithm = 'SHA-1'
break
case 'SHA256':
standardHashAlgorithm = 'SHA-256'
break
case 'SHA384':
standardHashAlgorithm = 'SHA-384'
break
case 'SHA512':
standardHashAlgorithm = 'SHA-512'
break
default:
standardHashAlgorithm = hashAlgorithm
}
try {
const encoder = new TextEncoder()
const data = encoder.encode(inputText.value)
const keyData = encoder.encode(key.value)
// 创建密钥
const cryptoKey = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'HMAC', hash: standardHashAlgorithm },
false,
['sign']
)
// 计算Hmac
const hmacBuffer = await crypto.subtle.sign('HMAC', cryptoKey, data)
const hmacArray = Array.from(new Uint8Array(hmacBuffer))
const hmacHex = hmacArray.map(b => b.toString(16).padStart(2, '0')).join('')
hashResult.value = hmacHex
} catch (error) {
console.error('Hmac计算失败:', error)
hashResult.value = '计算失败'
}
}
} else {
// 使用Web Crypto API计算其他哈希
const encoder = new TextEncoder()
const data = encoder.encode(inputText.value)
const hashBuffer = await crypto.subtle.digest(algorithm.value, data)
const hashArray = Array.from(new Uint8Array(hashBuffer))
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
hashResult.value = hashHex
}
} catch (error) {
console.error('计算失败:', error)
hashResult.value = '计算失败'
}
}, { immediate: true })
// 计算哈希
const calculateHash = async () => {
// 由于我们已经在watch中自动计算了哈希,这里只需要显示成功消息
if (!inputText.value) {
ElMessage.warning('请输入要计算哈希的文本')
return
}
if (algorithm.value.startsWith('HMAC-') && !key.value) {
ElMessage.warning('请输入Hmac密钥')
return
}
ElMessage.success('计算成功')
}
// 复制结果
const copyResult = () => {
if (!hashResult.value || hashResult.value === '计算失败' || hashResult.value === '请输入Hmac密钥') {
ElMessage.warning('没有可复制的内容')
return
}
copy(hashResult.value)
}
// 清空输入
const clearInput = () => {
inputText.value = ''
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<!-- 输入部分 -->
<div class="mb-4">
<el-input
v-model="inputText"
type="textarea"
:rows="4"
placeholder="请输入要计算哈希的文本"
class="mb-2"
/>
<div class="flex items-center gap-2 mb-2">
<el-text>哈希算法</el-text>
<el-select v-model="algorithm" style="width: 150px;">
<el-option
v-for="option in algorithmOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</div>
<!-- 密钥输入仅Hmac算法需要 -->
<div class="flex items-center gap-2 mb-2" v-if="isHmacAlgorithm">
<el-text>密钥(HMAC算法必填)</el-text>
<el-input
v-model="key"
placeholder="请输入Hmac密钥"
style="width: 500px;"
/>
</div>
</div>
<!-- 操作按钮 -->
<div class="mb-4">
<el-button type="primary" @click="calculateHash">计算哈希</el-button>
<el-button @click="copyResult">复制结果</el-button>
<el-button @click="clearInput">清空</el-button>
</div>
<!-- 结果部分 -->
<div>
<el-input
:value="hashResult"
type="textarea"
:rows="2"
placeholder="哈希结果"
readonly
>
<template #append>
<el-button @click="copyResult">复制</el-button>
</template>
</el-input>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线Hash计算器支持MD5SHA-1SHA-256SHA-384SHA-512等多种哈希算法以及Hmac-MD5Hmac-SHA1Hmac-SHA256Hmac-SHA384Hmac-SHA512等Hmac算法可用于数据完整性验证密码加密等场景
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,68 @@
<script setup lang="ts">
import { reactive,ref,onMounted } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { escape, unescape } from 'lodash';
import { copy } from '@/utils/string'
const info = reactive({
title: "HTML实体转义",
})
const content = ref('')
const cleanContent = ref('')
const parser = (type: string) => {
if (type == 'toHTML') {
//转html
cleanContent.value = unescape(content.value)
} else {
//转实体
cleanContent.value = escape(content.value)
}
}
//清空输入框
const clear = () => {
content.value = ''
cleanContent.value = ''
}
onMounted(() => {
})
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white ">
<div class="mb-6">
<el-input v-model="content" :rows="10" type="textarea" placeholder="请输入内容" @change="parser"></el-input>
<div class="mt-3">
<el-button type="primary" @click="parser('toEntity')">HTML转实体</el-button>
<el-button type="primary" @click="parser('toHTML')">实体转HTML</el-button>
<el-button type="primary" @click="copy(cleanContent)">复制结果</el-button>
<el-button type="primary" @click="clear">清除</el-button>
</div>
</div>
<div>
<el-input v-html="cleanContent" v-model="cleanContent" :rows="10" type="textarea" placeholder="" @change="parser"></el-input>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
html实体转义实体转义成html
</el-text>
</ToolDetail>
</div>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,82 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { copy } from '@/utils/string';
import { Codemirror } from "vue-codemirror";
import '@codemirror/search';
import '@codemirror/state';
import '@codemirror/commands';
import * as prettier from "prettier/standalone";
import * as parserHtml from 'prettier/parser-html';
import { ElMessage } from 'element-plus';
const info = reactive({
title: "html格式化/压缩",
code: '',
isParseErr: false,
parseErr: '',
})
//格式化
const formatCode = async () => {
try {
info.code = await prettier.format(info.code, { parser: "html", plugins: [parserHtml]})
} catch (error) {
ElMessage({
showClose: true,
message: '请填入正确代码格式',
type: 'error',
})
}
}
//清空输入框
const clear = () => {
info.code = ''
}
const copyRes = async () => {
copy(info.code)
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white ">
<div>
<codemirror
v-model="info.code"
placeholder="这里是代码..."
:style="{ height: '400px' }"
:autofocus="true"
:indent-with-tab="true"
:tabSize="2"
/>
</div>
<div class="mt-4">
<el-button type="primary" @click="formatCode">格式化</el-button>
<el-button type="primary" @click="copyRes">复制</el-button>
<el-button type="primary" @click="clear">清空</el-button>
</div>
<div class="mt-3 min-h-md bg-red-100 p-3 mb-3" v-show="info.isParseErr">
<el-text type="danger">{{ info.parseErr }}</el-text>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
提供在线htmlxml格式化
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,145 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
// import { copy } from '@/utils/string'
const info = reactive({
title: "HTTP状态码",
})
// ascii: https://ascii.co.uk/table
const tableDataControlOne = reactive([
{code:"100 Continue",desc:"请求者应当继续提出请求。 服务器返回此代码表示已收到请求的第一部分,正在等待其余部分"},
{code:"101 Switching Protocols",desc:"请求者已要求服务器切换协议,服务器已确认并准备切换"},
{code:"102 Processing",desc:"由WebDAVRFC 2518)扩展的状态码,代表处理将被继续执行。"},
])
const tableDataControlTwo = reactive([
{code:"200 OK",desc:"服务器已成功处理了请求。 通常,这表示服务器提供了请求的网页"},
{code:"201 Created",desc:"请求成功并且服务器创建了新的资源"},
{code:"202 Accepted",desc:"服务器已接受请求,但尚未处理"},
{code:"203 Non-Authoritative Information",desc:"服务器已成功处理了请求,但返回的信息可能来自另一来源"},
{code:"204 No Content",desc:"服务器成功处理了请求,但没有返回任何内容"},
{code:"205 Reset Content",desc:"服务器成功处理了请求,且没有返回任何内容。但是与204响应不同,返回此状态码的响应要求请求者重置文档视图。该响应主要是被用于接受用户输入后,立即重置表单,以便用户能够轻松地开始另一次输入"},
{code:"206 Partial Content",desc:"服务器成功处理了部分 GET 请求"},
{code:"207 Multi-Status",desc:"由WebDAV(RFC 2518)扩展的状态码,代表之后的消息体将是一个XML消息,并且可能依照之前子请求数量的不同,包含一系列独立的响应代码"},
{code:"208 Already Reported",desc:"一个DAV的绑定成员被前一个请求枚举,并且没有被再一次包括"},
{code:"226 IM Used",desc:"服务器已经满足了请求所要的资源,并且响应是一个或者多个实例操作应用于当前实例的结果"},
])
const tableDataControlThree = reactive([
{code:"300 Multiple Choices",desc:"针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。"},
{code:"301 Moved Permanently",desc:"请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。"},
{code:"302 Move Temporarily",desc:"服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求"},
{code:"303 See Other",desc:"请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。"},
{code:"304 Not Modified",desc:"自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。"},
{code:"305 Use Proxy",desc:"请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理"},
{code:"306 Switch Proxy",desc:"在最新版的规范中,306状态码已经不再被使用"},
{code:"307 Temporary Redirect",desc:"服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。"},
{code:"308 Permanent Redirect",desc:"这个请求和以后的请求都应该被另一个URI地址重新发送。307、308和302、301有相同的表现,但是不允许HTTP方法改变。例如,请求表单到一个永久转移的资源将会继续顺利地执行。"},
])
const tableDataControlFour = reactive([
{code:"400 Bad Request",desc:"服务器不理解请求的语法"},
{code:"401 Unauthorized",desc:"请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应"},
{code:"402 Payment Required",desc:"该状态码是为了将来可能的需求而预留的"},
{code:"403 Forbidden",desc:"请求有效,但服务器拒绝操作。用户可能没有资源的必要权限。"},
{code:"404 Not Found",desc:"服务器找不到请求的网页"},
{code:"405 Method Not Allowed",desc:"禁用请求中指定的方法"},
{code:"406 Not Acceptable",desc:"无法使用请求的内容特性响应请求的网页"},
{code:"407 Proxy Authentication Required",desc:"此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理"},
{code:"408 Request Timeout",desc:"服务器等候请求时发生超时"},
{code:"409 Conflict",desc:"服务器在完成请求时发生冲突。 服务器必须在响应中包含有关冲突的信息"},
{code:"410 Gone",desc:"如果请求的资源已永久删除,服务器就会返回此响应"},
{code:"411 Length Required",desc:"服务器不接受不含有效内容长度标头字段的请求"},
{code:"412 Precondition Failed",desc:"服务器未满足请求者在请求中设置的其中一个前提条件"},
{code:"413 Request Entity Too Large",desc:"服务器无法处理请求,因为请求实体过大,超出服务器的处理能力"},
{code:"414 Request-URI Too Long",desc:"请求的 URI(通常为网址)过长,服务器无法处理"},
{code:"415 Unsupported Media Type",desc:"请求的格式不受请求页面的支持"},
{code:"416 Requested Range Not Satisfiable",desc:"如果页面无法提供请求的范围,则服务器会返回此状态代码"},
{code:"417 Expectation Failed",desc:"服务器未满足”期望“请求标头字段的要求"},
{code:"418 I'm a teapot",desc:"这个代码是在1998年作为传统的IETF April Fools jokes被定义的在RFC2324,超文本咖啡罐控制协议,但是并没有被实际的HTTP服务器实现。RFC指定了这个代码应该是由茶罐返回给速溶咖啡"},
{code:"421 Misdirected Request",desc:"请求被指向到无法生成响应的服务器(比如由于连接重复使用)"},
{code:"422 Unprocessable Entity",desc:"请求格式正确,但是由于含有语义错误,无法响应。(RFC 4918 WebDAV"},
{code:"423 Locked",desc:"当前资源被锁定"},
{code:"424 Failed Dependency",desc:"由于之前的某个请求发生的错误,导致当前请求失败,例如 PROPPATCH"},
{code:"425 Too Early",desc:"状态码 425 Too Early 代表服务器不愿意冒风险来处理该请求,原因是处理该请求可能会被“重放”,从而造成潜在的重放攻击。"},
{code:"426 Upgrade Required",desc:"客户端应当切换到TLS/1.0"},
{code:"429 Too Many Requests",desc:"用户在给定的时间内发送了太多请求"},
{code:"431 Request Header Fields Too Large",desc:"服务器不愿意处理请求,因为单个报头字段或所有报头字段都太大了。"},
{code:"449 Retry With",desc:"由微软扩展,代表请求应当在执行完适当的操作后进行重试。"},
{code:"451 Unavailable For Legal Reasons",desc:"该请求因法律原因不可用"},
])
const tableDataControlFive = reactive([
{code:"500 Internal Server Error",desc:"服务器遇到错误,无法完成请求;一般来说,这个问题都会在服务器端的源代码出现错误时出现。"},
{code:"501 Not Implemented",desc:"服务器不支持当前请求所需要的某个功能。当服务器无法识别请求的方法,并且无法支持其对任何资源的请求。"},
{code:"502 Bad Gateway",desc:"作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应"},
{code:"503 Service Unavailable",desc:"服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态"},
{code:"504 Gateway Timeout",desc:"服务器作为网关或代理,但是没有及时从上游服务器收到请求"},
{code:"505 HTTP Version Not Supported",desc:"服务器不支持请求中所用的 HTTP 协议版本"},
{code:"506 Variant Also Negotiates",desc:"由《透明内容协商协议》(RFC 2295)扩展,代表服务器存在内部配置错误:被请求的协商变元资源被配置为在透明内容协商中使用自己,因此在一个协商处理中不是一个合适的重点"},
{code:"507 Insufficient Storage",desc:"服务器无法存储完成请求所必须的内容。这个状况被认为是临时的"},
{code:"509 Bandwidth Limit Exceeded",desc:"服务器达到带宽限制。这不是一个官方的状态码,但是仍被广泛使用。"},
{code:"510 Not Extended",desc:"获取资源所需要的策略并没有被满足"},
{code:"511 Network Authentication Required",desc:"客户端需要进行身份验证才能获得网络访问权限。"},
])
//copy
// const copyRes = async (resStr: string) => {
// copy(resStr)
// }
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<el-table :data="tableDataControlOne" class="w-full mb-3">
<el-table-column label="1xx 信息,表示临时响应并需要请求者继续执行操作" align="center">
<el-table-column prop="code" label="状态码" width="270" />
<el-table-column prop="desc" label="含义解释" />
</el-table-column>
</el-table>
<el-table :data="tableDataControlTwo" class="w-full mb-3">
<el-table-column label="2xx 成功,操作被成功接收并处理" align="center">
<el-table-column prop="code" label="状态码" width="270" />
<el-table-column prop="desc" label="含义解释"/>
</el-table-column>
</el-table>
<el-table :data="tableDataControlThree" class="w-full mb-3">
<el-table-column label="3xx 表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向" align="center">
<el-table-column prop="code" label="状态码" width="270" />
<el-table-column prop="desc" label="含义解释"/>
</el-table-column>
</el-table>
<el-table :data="tableDataControlFour" class="w-full mb-3">
<el-table-column label="4xx 客户端错误,请求包含语法错误或无法完成请求" align="center">
<el-table-column prop="code" label="状态码" width="270" />
<el-table-column prop="desc" label="含义解释"/>
</el-table-column>
</el-table>
<el-table :data="tableDataControlFive" class="w-full mb-3">
<el-table-column label="5xx 这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错" align="center">
<el-table-column prop="code" label="状态码" width="270" />
<el-table-column prop="desc" label="含义解释"/>
</el-table-column>
</el-table>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
超全面http状态对应的名称和含义解释
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,338 @@
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div class="tool-content">
<div class="upload-section">
<el-form label-position="top">
<el-form-item label="上传图片">
<el-upload
class="upload-demo"
action="#"
:auto-upload="false"
:on-change="handleFileChange"
:show-file-list="false"
accept="image/*"
>
<el-button type="primary">点击上传</el-button>
<template #tip>
<div class="el-upload__tip">
支持 JPGPNGGIF 等格式的图片
</div>
</template>
</el-upload>
</el-form-item>
</el-form>
</div>
<div class="image-section">
<div v-if="imageUrl" class="image-container">
<img
ref="image"
:src="imageUrl"
@load="handleImageLoad"
@click="handleImageClick"
class="preview-image"
crossorigin="anonymous"
/>
<canvas
ref="canvas"
class="hidden-canvas"
></canvas>
</div>
<div v-else class="no-image">
请上传图片
</div>
</div>
<div class="color-section">
<h3>颜色信息</h3>
<div v-if="selectedColor" class="color-info">
<div class="color-preview" :style="{ backgroundColor: selectedColor.hex }"></div>
<div class="color-details">
<div class="color-item">
<span class="color-label">HEX:</span>
<span class="color-value">{{ selectedColor.hex }}</span>
<el-button type="text" size="small" @click="copyColor(selectedColor.hex)">复制</el-button>
</div>
<div class="color-item">
<span class="color-label">RGB:</span>
<span class="color-value">{{ selectedColor.rgb }}</span>
<el-button type="text" size="small" @click="copyColor(selectedColor.rgb)">复制</el-button>
</div>
<div class="color-item">
<span class="color-label">HSL:</span>
<span class="color-value">{{ selectedColor.hsl }}</span>
<el-button type="text" size="small" @click="copyColor(selectedColor.hsl)">复制</el-button>
</div>
</div>
</div>
<div v-else class="no-color">
点击图片获取颜色
</div>
</div>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线传图取色工具上传图片后点击图片任意位置获取颜色值支持 HEXRGBHSL 三种颜色格式可用于设计和开发中获取颜色参考
</el-text>
</ToolDetail>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
const title = "传图取色"
const imageUrl = ref('');
const image = ref<HTMLImageElement | null>(null);
const canvas = ref<HTMLCanvasElement | null>(null);
const selectedColor = ref<{
hex: string;
rgb: string;
hsl: string;
} | null>(null);
// 处理文件上传
const handleFileChange = (file: any) => {
const reader = new FileReader();
reader.onload = (e) => {
imageUrl.value = e.target?.result as string;
selectedColor.value = null;
};
reader.readAsDataURL(file.raw);
};
// 处理图片加载完成
const handleImageLoad = () => {
if (image.value && canvas.value) {
const ctx = canvas.value.getContext('2d');
if (ctx) {
// 设置canvas尺寸与图片一致
canvas.value.width = image.value.width;
canvas.value.height = image.value.height;
// 绘制图片到canvas
ctx.drawImage(image.value, 0, 0);
}
}
};
// 处理图片点击事件
const handleImageClick = (e: MouseEvent) => {
if (!image.value || !canvas.value) {
return;
}
try {
const rect = image.value.getBoundingClientRect();
const x = Math.floor((e.clientX - rect.left) * (image.value.width / rect.width));
const y = Math.floor((e.clientY - rect.top) * (image.value.height / rect.height));
const ctx = canvas.value.getContext('2d');
if (ctx) {
const pixel = ctx.getImageData(x, y, 1, 1).data;
const [r, g, b] = pixel;
// 转换为各种颜色格式
const hex = rgbToHex(r, g, b);
const rgb = `rgb(${r}, ${g}, ${b})`;
const hsl = rgbToHsl(r, g, b);
selectedColor.value = {
hex,
rgb,
hsl
};
}
} catch (error) {
console.error('取色失败:', error);
ElMessage.error('取色失败,可能是图片跨域问题');
}
};
// RGB转HEX
const rgbToHex = (r: number, g: number, b: number) => {
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase()}`;
};
// RGB转HSL
const rgbToHsl = (r: number, g: number, b: number) => {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h = 0;
let s = 0;
let l = (max + min) / 2;
if (max !== min) {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
h = Math.round(h * 360);
s = Math.round(s * 100);
l = Math.round(l * 100);
return `hsl(${h}, ${s}%, ${l}%)`;
};
// 复制颜色值
const copyColor = (color: string) => {
navigator.clipboard.writeText(color).then(() => {
ElMessage.success('已复制到剪贴板');
}).catch(() => {
ElMessage.error('复制失败');
});
};
</script>
<style scoped>
.tool-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.upload-section {
width: 100%;
}
.image-section {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
min-height: 300px;
border: 1px solid #e4e7ed;
border-radius: 4px;
overflow: hidden;
}
.image-container {
position: relative;
max-width: 100%;
max-height: 400px;
}
.preview-image {
max-width: 100%;
max-height: 400px;
cursor: crosshair;
}
.hidden-canvas {
position: absolute;
top: 0;
left: 0;
opacity: 0;
pointer-events: none;
}
.no-image {
text-align: center;
color: #999;
padding: 100px 0;
}
.color-section {
width: 100%;
margin-top: 20px;
}
.color-section h3 {
margin-bottom: 15px;
color: #333;
}
.color-info {
display: flex;
align-items: flex-start;
gap: 20px;
padding: 20px;
background-color: #f5f7fa;
border-radius: 4px;
}
.color-preview {
width: 80px;
height: 80px;
border-radius: 4px;
border: 1px solid #e4e7ed;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.color-details {
flex: 1;
display: flex;
flex-direction: column;
gap: 10px;
}
.color-item {
display: flex;
align-items: center;
gap: 10px;
}
.color-label {
width: 60px;
font-weight: bold;
color: #666;
}
.color-value {
flex: 1;
font-family: monospace;
background-color: #fff;
padding: 5px 10px;
border-radius: 4px;
border: 1px solid #e4e7ed;
}
.no-color {
text-align: center;
color: #999;
padding: 40px 0;
background-color: #f9f9f9;
border-radius: 4px;
}
@media (min-width: 768px) {
.tool-content {
flex-direction: row;
flex-wrap: wrap;
}
.upload-section {
width: 100%;
}
.image-section {
width: 60%;
}
.color-section {
width: 40%;
}
}
</style>
@@ -0,0 +1,322 @@
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div class="tool-content">
<div class="input-section">
<el-form label-position="top">
<el-form-item label="上传图片">
<el-upload
class="upload-demo"
action="#"
:auto-upload="false"
:on-change="handleFileChange"
:show-file-list="false"
accept="image/*"
>
<el-button type="primary">点击上传</el-button>
<template #tip>
<div class="el-upload__tip">
支持 JPGPNGGIF 等格式的图片
</div>
</template>
</el-upload>
</el-form-item>
<el-form-item label="水印文字">
<el-input
v-model="watermarkText"
placeholder="请输入水印文字"
/>
</el-form-item>
<el-form-item label="水印设置">
<div class="settings-grid">
<el-form-item label="位置" class="setting-item">
<el-select v-model="position" size="small">
<el-option label="左上角" value="top-left" />
<el-option label="右上角" value="top-right" />
<el-option label="左下角" value="bottom-left" />
<el-option label="右下角" value="bottom-right" />
<el-option label="居中" value="center" />
</el-select>
</el-form-item>
<el-form-item label="字体大小" class="setting-item">
<el-input-number v-model="fontSize" :min="12" :max="100" size="small" />
</el-form-item>
<el-form-item label="透明度" class="setting-item">
<el-slider v-model="opacity" :min="0" :max="1" :step="0.1" />
</el-form-item>
<el-form-item label="颜色" class="setting-item">
<el-color-picker v-model="textColor" size="small" />
</el-form-item>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="addWatermark">添加水印</el-button>
<el-button @click="clearImage">清空</el-button>
</el-form-item>
</el-form>
</div>
<div class="preview-section">
<h3>预览</h3>
<div class="preview-container">
<div v-if="imageUrl" class="image-wrapper">
<img :src="imageUrl" class="original-image" />
<canvas
ref="canvas"
class="watermark-canvas"
:width="imageWidth"
:height="imageHeight"
></canvas>
</div>
<div v-else class="no-image">
请上传图片
</div>
</div>
<div v-if="watermarkedUrl" class="download-section">
<el-button type="success" @click="downloadImage">下载图片</el-button>
</div>
</div>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线图片水印添加工具上传图片后添加自定义文字水印支持设置水印位置字体大小透明度和颜色可下载带水印的图片
</el-text>
</ToolDetail>
</div>
</template>
<script setup lang="ts">
import { ref, nextTick } from 'vue';
import { ElMessage } from 'element-plus';
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
const title = "图片水印添加"
const imageUrl = ref('');
const watermarkedUrl = ref('');
const watermarkText = ref('水印');
const position = ref('bottom-right');
const fontSize = ref(24);
const opacity = ref(0.5);
const textColor = ref('#ffffff');
const imageWidth = ref(0);
const imageHeight = ref(0);
const canvas = ref<HTMLCanvasElement | null>(null);
// 处理文件上传
const handleFileChange = (file: any) => {
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
imageWidth.value = img.width;
imageHeight.value = img.height;
imageUrl.value = e.target?.result as string;
watermarkedUrl.value = '';
};
img.src = e.target?.result as string;
};
reader.readAsDataURL(file.raw);
};
// 添加水印
const addWatermark = async () => {
if (!imageUrl.value || !watermarkText.value) {
ElMessage.warning('请上传图片并输入水印文字');
return;
}
await nextTick();
if (canvas.value) {
const ctx = canvas.value.getContext('2d');
if (ctx) {
// 清除画布
ctx.clearRect(0, 0, canvas.value.width, canvas.value.height);
// 绘制原始图片
const img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0);
// 设置水印样式
ctx.font = `${fontSize.value}px Arial`;
ctx.fillStyle = `${textColor.value}${Math.floor(opacity.value * 255).toString(16).padStart(2, '0')}`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// 计算水印位置
let x = canvas.value!.width / 2;
let y = canvas.value!.height / 2;
switch (position.value) {
case 'top-left':
x = fontSize.value * 1.5;
y = fontSize.value * 1.5;
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
break;
case 'top-right':
x = canvas.value!.width - fontSize.value * 1.5;
y = fontSize.value * 1.5;
ctx.textAlign = 'right';
ctx.textBaseline = 'top';
break;
case 'bottom-left':
x = fontSize.value * 1.5;
y = canvas.value!.height - fontSize.value * 1.5;
ctx.textAlign = 'left';
ctx.textBaseline = 'bottom';
break;
case 'bottom-right':
x = canvas.value!.width - fontSize.value * 1.5;
y = canvas.value!.height - fontSize.value * 1.5;
ctx.textAlign = 'right';
ctx.textBaseline = 'bottom';
break;
}
// 绘制水印文字
ctx.fillText(watermarkText.value, x, y);
// 生成带水印的图片URL
watermarkedUrl.value = canvas.value!.toDataURL('image/png');
};
img.src = imageUrl.value;
}
}
};
// 下载图片
const downloadImage = () => {
if (!watermarkedUrl.value) {
return;
}
const link = document.createElement('a');
link.href = watermarkedUrl.value;
link.download = `watermarked_${Date.now()}.png`;
link.click();
};
// 清空
const clearImage = () => {
imageUrl.value = '';
watermarkedUrl.value = '';
watermarkText.value = '水印';
position.value = 'bottom-right';
fontSize.value = 24;
opacity.value = 0.5;
textColor.value = '#ffffff';
imageWidth.value = 0;
imageHeight.value = 0;
if (canvas.value) {
const ctx = canvas.value.getContext('2d');
if (ctx) {
ctx.clearRect(0, 0, canvas.value.width, canvas.value.height);
}
}
};
</script>
<style scoped>
.tool-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.input-section {
width: 100%;
}
.settings-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 10px;
}
.setting-item {
margin-bottom: 0;
}
.preview-section {
width: 100%;
margin-top: 20px;
}
.preview-section h3 {
margin-bottom: 15px;
color: #333;
}
.preview-container {
border: 1px solid #e4e7ed;
border-radius: 8px;
min-height: 400px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.image-wrapper {
position: relative;
max-width: 100%;
max-height: 400px;
}
.original-image {
max-width: 100%;
max-height: 400px;
display: block;
}
.watermark-canvas {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
.no-image {
text-align: center;
color: #999;
padding: 100px 0;
}
.download-section {
margin-top: 20px;
text-align: center;
}
@media (min-width: 768px) {
.tool-content {
flex-direction: row;
}
.input-section {
width: 40%;
}
.preview-section {
width: 60%;
}
}
</style>
+152
View File
@@ -0,0 +1,152 @@
<script setup lang="ts">
import { onMounted, reactive, ref,computed, watch } from 'vue'
import { UploadProps,UploadRawFile,genFileId } from 'element-plus'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
const info = reactive({
title: "图片切割",
})
const fileList = ref()
const lineNum = ref(3)
const image = ref({} as any)
const cutImg = ref([] as string[])
const dataFileRef = ref()
//上传
const updateDataFile = async (params) => {
let reader = new FileReader();
reader.readAsDataURL(params.file);
reader.addEventListener(
'load',
async () => {
const imageTmp = new Image();
imageTmp.onload = () => {
image.value = imageTmp;
cut();
};
imageTmp.src = reader.result as string;
},
false
);
}
//当超出限制时,执行的钩子函数
//这里覆盖前一个文件
const handleExceed: UploadProps['onExceed'] = (files) => {
dataFileRef.value!.clearFiles()
const file = files[0] as UploadRawFile
file.uid = genFileId()
dataFileRef.value!.handleStart(file)
dataFileRef.value!.submit()
}
//切割
const cut = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = image.value;
const wpiece = Math.floor(img.naturalWidth / lineNum.value);
const hpiece = Math.floor(img.naturalHeight / lineNum.value);
const results = [] as string[];
canvas.width = wpiece;
canvas.height = hpiece;
for (let r = 0; r < lineNum.value; r++) {
for (let c = 0; c < lineNum.value; c++) {
ctx?.drawImage(
img,
c * wpiece,
r * hpiece,
wpiece,
hpiece,
0,
0,
wpiece,
hpiece
);
results.push(canvas.toDataURL());
}
}
cutImg.value = results;
}
//计算切割样式
const cutImgStyle = computed(() => {
const width = `width: ${image.value.width || 800}px;`;
const grid = `grid: repeat(${lineNum.value}, 1fr) / repeat(${
lineNum.value
}, 1fr);`;
return width + grid;
})
watch(cutImgStyle, () => {
if (fileList.value) cut();
})
onMounted(() => {
})
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<el-upload
v-model:file-list="fileList"
class="dataFileRef flex"
ref="dataFileRef"
accept="image/*"
:http-request="updateDataFile"
:on-exceed="handleExceed"
:limit="1"
>
<el-button type="primary">请上传需要切割的图片</el-button>
</el-upload>
<div class="flex mt-3">
<div class="flex">
<el-text>行数和列数</el-text>
<el-input-number v-model="lineNum" :min="1" :max="10" class="ml-3"/>
</div>
</div>
<div class="mt-3 min-h-md bg-gray-100 p-3 mb-3 flex flex-col" v-if="image.src">
<!-- 预览 -->
<div class="mb-3">
<el-text>预览: </el-text>
<div :style="cutImgStyle" class="grid gap-1 max-w-full">
<img v-for="(src,index) in cutImg" :key="index" :src="src" alt="结果" />
</div>
</div>
<!-- 原图 -->
<div>
<el-text>原图: </el-text>
<div>
<img :src="image.src" alt="原图" v-if="image.src"/>
</div>
</div>
</div>
<div v-else>
<el-empty :image-size="200" description="无预览"/>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
将图片分割成四宫格九宫格十六宫格支持自定义行与列<br>
比如九宫格切图广泛应用于微信朋友圈微博等社交媒体
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
@@ -42,10 +42,10 @@ onMounted(() => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div> <div class="p-4 rounded-2xl bg-white">
<div class="flex"> <div class="flex">
<div class="mr-2 w-60"> <div class="mr-2 w-60">
<el-input v-model="params.ip" ></el-input> <el-input v-model="params.ip" ></el-input>
+110
View File
@@ -0,0 +1,110 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { copy } from '@/utils/string';
import { Codemirror } from "vue-codemirror";
import '@codemirror/search';
import '@codemirror/state';
import '@codemirror/commands';
import * as prettier from "prettier/standalone";
import * as parserBabel from 'prettier/parser-babel';
import * as prettierPluginEstree from "prettier/plugins/estree";
import { ElMessage } from 'element-plus'
import { minify } from "terser"
const info = reactive({
title: "js代码格式化/压缩",
code: '',
isParseErr: false,
parseErr: '',
})
interface Error {
name: string;
message: string;
stack?: string;
}
//格式化
const formatCode = async () => {
try {
info.code = await prettier.format(info.code, { parser: "babel", plugins: [parserBabel, prettierPluginEstree]})
} catch (error) {
ElMessage({
showClose: true,
message: '请填入正确的js代码',
type: 'error',
})
}
}
//混淆压缩
const confuseCompress = async () => {
try {
let res = await minify(info.code, {
mangle: {
toplevel: true,
}
})
info.code = res.code != undefined ? res.code : info.code
} catch(error) {
ElMessage({
showClose: true,
message: '请填入正确的js代码: ' + (error as Error).message,
type: 'error',
})
}
}
//清空输入框
const clear = () => {
info.code = ''
}
const copyRes = async () => {
copy(info.code)
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white ">
<div>
<codemirror
v-model="info.code"
placeholder="这里是代码..."
:style="{ height: '400px' }"
:autofocus="true"
:indent-with-tab="true"
:tabSize="2"
/>
</div>
<div class="mt-4">
<el-button type="primary" @click="formatCode">格式化</el-button>
<el-button type="primary" @click="confuseCompress">混淆压缩</el-button>
<el-button type="primary" @click="copyRes">复制</el-button>
<el-button type="primary" @click="clear">清空</el-button>
</div>
<div class="mt-3 min-h-md bg-red-100 p-3 mb-3" v-show="info.isParseErr">
<el-text type="danger">{{ info.parseErr }}</el-text>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
JS格式化/压缩工具,提供在线JS格式化JS压缩JS混淆
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
+103
View File
@@ -0,0 +1,103 @@
<script setup lang="ts">
import { reactive,ref,onMounted } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import {jwtDecode} from 'jwt-decode'
const info = reactive({
title: "JWT解析",
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b29scy5yYW5ibG9ncy5jb20iLCJzdWIiOiJhcnlhIiwiYXVkIjoidGFvcmFuIiwiaWF0IjoxNzE3MDM2MzMzLCJleHAiOjE3MTcwMzY5MzN9.9v_eDdsPnvcY7qZatNnKvN9VEf7t7o72OgwhLS6gy6w',
})
const decodeHeader = ref()
const decodePayload = ref()
const invalidToken = ref(false)
//解析
const parser = () => {
try {
decodePayload.value = JSON.stringify(jwtDecode(info.token), null, '\t')
decodeHeader.value = JSON.stringify(jwtDecode(info.token, {header: true}), null, '\t')
} catch (e) {
console.log('Invalid token', e)
invalidToken.value = true
}
}
//清空输入框
const clear = () => {
info.token = ''
decodeHeader.value = ''
decodePayload.value = ''
}
onMounted(() => {
parser()
})
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white ">
<div class="mb-6">
<el-input v-model="info.token" :rows="5" type="textarea" placeholder="请输入Token" @change="parser"></el-input>
<el-text type="danger" v-if="invalidToken">Invalid token</el-text>
<div class="mt-3">
<el-button type="primary" @click="parser">解析</el-button>
<el-button type="primary" @click="clear">清除</el-button>
</div>
</div>
<!-- header -->
<div v-if="decodeHeader">
<div class="mb-3">Header(头部)</div>
<el-input
v-model="decodeHeader"
type="textarea"
:autosize="true"
class="w-full"
/>
</div>
<!-- payload -->
<div v-if="decodePayload">
<div class="mb-3 mt-3">Payload(载荷)</div>
<el-input
v-model="decodePayload"
type="textarea"
:autosize="true"
v-if="decodePayload"
class="w-full"
/>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
解析和解码JSON Web Tokenjwt并显示其内容<br>
<br>
1. JWT JSON Web Tokens 的缩写是目前最流行的跨域认证解决方案是一个开放式标准(RFC 7519)用于在各方之间以JSON对象安全传输信息<br/>
2. Header(头部)<br/>
&nbsp;&nbsp;- typ(type): 用来标识整个token字符串是一个JWT字符串<br/>
&nbsp;&nbsp;- alg(algorithm): 用来说明这个JWT签发的时候所使用的签名和摘要算法<br/>
3. Payload(载荷)<br/>
&nbsp;&nbsp;- iss (Issuer)签发者<br/>
&nbsp;&nbsp;- sub (Subject)主题<br/>
&nbsp;&nbsp;- aud (Audience)接收者<br/>
&nbsp;&nbsp;- exp (Expiration time)过期时间<br/>
&nbsp;&nbsp;- nbf (Not Before)生效时间<br/>
&nbsp;&nbsp;- iat (Issued At)签发时间<br/>
&nbsp;&nbsp;- jti (JWT ID)编号<br/>
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
@@ -1,11 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { transferred, copy } from '@/utils/string'; import { transferred, copy } from '@/utils/string';
import { Codemirror } from "vue-codemirror"; import { Codemirror } from "vue-codemirror";
import { json } from '@codemirror/lang-json'; import { json } from '@codemirror/lang-json';
import '@codemirror/search'; import '@codemirror/search';
import { lineNumbers } from '@codemirror/view';
import '@codemirror/state'; import '@codemirror/state';
import '@codemirror/commands'; import '@codemirror/commands';
// import { syntaxTree } from '@codemirror/language'; // import { syntaxTree } from '@codemirror/language';
@@ -31,7 +32,7 @@ import '@codemirror/commands';
const info = reactive({ const info = reactive({
title: "Json在线转换", title: "Json在线转换",
code: '', code: '',
extensions: [json(), lineNumbers()], extensions: [json()], //[json(), lineNumbers()],
isParseErr: false, isParseErr: false,
parseErr: '' parseErr: ''
}) })
@@ -79,10 +80,10 @@ const copyRes = async () => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div class=""> <div class="p-4 rounded-2xl bg-white ">
<div> <div>
<!-- <!--
@@ -123,6 +124,14 @@ const copyRes = async () => {
<el-text type="danger">{{ info.parseErr }}</el-text> <el-text type="danger">{{ info.parseErr }}</el-text>
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
JSONJavaScript Object Notation是一种轻量级的数据交换格式它基于JavaScript的一个子集但与语言无关因此在多种编程环境中广泛使用JSON格式易于人阅读和编写同时也易于机器解析和生成它通常用于网络应用程序中服务器与客户端之间的数据传输<br>
JSON 工具提供实时编辑和预览JSON 数据语法高亮校验格式化转义去转义压缩等功能可以提高阅读修改的效率和准确性
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
+206
View File
@@ -0,0 +1,206 @@
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div class="tool-content">
<div class="input-section">
<el-form label-position="top">
<el-form-item label="抽签选项(每行一个)">
<el-input
v-model="optionsText"
type="textarea"
:rows="8"
placeholder="请输入抽签选项,每行一个"
/>
</el-form-item>
<el-form-item label="抽取设置">
<div class="settings-grid">
<el-form-item label="抽取数量" class="setting-item">
<el-input-number v-model="drawCount" :min="1" :max="10" size="small" />
</el-form-item>
<el-form-item label="是否允许重复" class="setting-item">
<el-switch v-model="allowRepeat" size="small" />
</el-form-item>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="drawLottery">开始抽签</el-button>
<el-button @click="clearOptions">清空</el-button>
</el-form-item>
</el-form>
</div>
<div class="result-section">
<h3>抽签结果</h3>
<div v-if="results.length > 0" class="result-list">
<div v-for="(result, index) in results" :key="index" class="result-item">
<span class="result-number">{{ index + 1 }}</span>
<span class="result-text">{{ result }}</span>
</div>
</div>
<div v-else class="no-result">
点击开始抽签按钮开始抽取
</div>
</div>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线抽签工具输入多个选项后随机抽取一个或多个结果支持设置抽取数量和是否允许重复抽取可用于随机选择抽奖等场景
</el-text>
</ToolDetail>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
const title = "抽签工具"
const optionsText = ref('');
const drawCount = ref(1);
const allowRepeat = ref(false);
const results = ref<string[]>([]);
// 开始抽签
const drawLottery = () => {
// 解析输入的选项
const options = optionsText.value
.split('\n')
.map(option => option.trim())
.filter(option => option !== '');
if (options.length === 0) {
return;
}
// 计算实际抽取数量
const actualDrawCount = allowRepeat.value
? Math.min(drawCount.value, 10)
: Math.min(drawCount.value, options.length);
// 执行抽签
const newResults: string[] = [];
const availableOptions = [...options];
for (let i = 0; i < actualDrawCount; i++) {
if (availableOptions.length === 0) {
break;
}
const randomIndex = Math.floor(Math.random() * availableOptions.length);
const selectedOption = availableOptions[randomIndex];
newResults.push(selectedOption);
// 如果不允许重复,从可用选项中移除
if (!allowRepeat.value) {
availableOptions.splice(randomIndex, 1);
}
}
results.value = newResults;
};
// 清空选项
const clearOptions = () => {
optionsText.value = '';
results.value = [];
};
</script>
<style scoped>
.tool-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.input-section {
width: 100%;
}
.settings-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 10px;
}
.setting-item {
margin-bottom: 0;
}
.result-section {
width: 100%;
margin-top: 20px;
}
.result-section h3 {
margin-bottom: 15px;
color: #333;
}
.result-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.result-item {
display: flex;
align-items: center;
padding: 10px;
background-color: #f5f7fa;
border-radius: 4px;
border-left: 4px solid #409eff;
}
.result-number {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
background-color: #409eff;
color: white;
border-radius: 50%;
margin-right: 10px;
font-size: 12px;
}
.result-text {
font-size: 16px;
color: #333;
}
.no-result {
text-align: center;
color: #999;
padding: 40px 0;
background-color: #f9f9f9;
border-radius: 4px;
}
@media (min-width: 768px) {
.tool-content {
flex-direction: row;
}
.input-section {
width: 50%;
}
.result-section {
width: 50%;
}
}
</style>
@@ -0,0 +1,141 @@
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div class="tool-content">
<div class="input-section">
<el-form label-position="top">
<el-form-item label="m3u8地址">
<el-input
v-model="m3u8Url"
placeholder="请输入m3u8格式的视频地址"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadVideo">加载视频</el-button>
<el-button @click="clearUrl">清空</el-button>
</el-form-item>
</el-form>
</div>
<div class="player-section">
<div v-if="m3u8Url" class="player-container">
<video
ref="videoPlayer"
class="video-js vjs-default-skin vjs-big-play-centered"
controls
preload="auto"
width="100%"
height="400"
>
<source :src="m3u8Url" type="application/x-mpegURL">
您的浏览器不支持HTML5视频播放
</video>
</div>
<div v-else class="no-video">
请输入m3u8地址并点击加载视频
</div>
</div>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线m3u8播放器支持播放m3u8格式的视频流可输入自定义m3u8地址进行播放适用于直播流和视频点播场景
</el-text>
</ToolDetail>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
const title = "m3u8在线播放"
const m3u8Url = ref('');
const videoPlayer = ref<HTMLVideoElement | null>(null);
// 加载视频
const loadVideo = () => {
if (!m3u8Url.value) {
return;
}
// 重新加载视频元素
if (videoPlayer.value) {
videoPlayer.value.load();
}
};
// 清空地址
const clearUrl = () => {
m3u8Url.value = '';
};
// 示例地址
const exampleUrl = 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8';
onMounted(() => {
// 加载示例地址
m3u8Url.value = exampleUrl;
loadVideo();
});
</script>
<style scoped>
.tool-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.input-section {
width: 100%;
}
.player-section {
width: 100%;
margin-top: 20px;
}
.player-container {
width: 100%;
border: 1px solid #e4e7ed;
border-radius: 8px;
overflow: hidden;
}
.no-video {
text-align: center;
color: #999;
padding: 100px 0;
background-color: #f9f9f9;
border-radius: 8px;
}
/* 确保视频播放器占满容器 */
:deep(.video-js) {
width: 100% !important;
height: 100% !important;
}
@media (min-width: 768px) {
.tool-content {
flex-direction: column;
}
.input-section {
width: 100%;
}
.player-section {
width: 100%;
}
}
</style>
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { Md5 } from 'ts-md5' import { Md5 } from 'ts-md5'
import { copy } from '@/utils/string' import { copy } from '@/utils/string'
const info = reactive({ const info = reactive({
@@ -32,10 +33,10 @@ const copyRes = async (resStr: string) => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div class=""> <div class="p-4 rounded-2xl bg-white ">
<div> <div>
<el-input v-model="info.encryptStr" :rows="5" type="textarea" placeholder="请输入加密内容"></el-input> <el-input v-model="info.encryptStr" :rows="5" type="textarea" placeholder="请输入加密内容"></el-input>
</div> </div>
@@ -83,6 +84,14 @@ const copyRes = async (resStr: string) => {
</el-input> </el-input>
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线md5加密,md5加密算法,md5加密工具,免费MD5在线加密,可生成32位md5,16位md5
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
const info = reactive({ const info = reactive({
title: "在线markdown编辑器", title: "在线markdown编辑器",
content: '', content: '',
@@ -15,12 +15,20 @@ const info = reactive({
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div class=""> <div class="">
<v-md-editor v-model="info.content" height="500px"></v-md-editor> <v-md-editor v-model="info.content" height="500px"></v-md-editor>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线编辑markdown
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { copy } from '@/utils/string' import { copy } from '@/utils/string'
import { toMorse, toText } from '@/utils/morse' import { toMorse, toText } from '@/utils/morse'
const info = reactive({ const info = reactive({
@@ -30,10 +31,10 @@ const copyRes = async (resStr: string) => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div class=""> <div class="p-4 rounded-2xl bg-white ">
<div> <div>
<el-input type="textarea" :rows="8" v-model="info.content"></el-input> <el-input type="textarea" :rows="8" v-model="info.content"></el-input>
</div> </div>
@@ -49,6 +50,14 @@ const copyRes = async (resStr: string) => {
<el-input type="textarea" :rows="8" v-model="info.tranRes"></el-input> <el-input type="textarea" :rows="8" v-model="info.tranRes"></el-input>
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
摩尔斯电码或摩斯电码Morse code是一种编码系统通过不同顺序的信号表示英文字母数字和标点符号由美国人艾尔菲德·维尔在协助Samuel Morse发明摩尔斯电报机1835时创造今天国际摩尔斯电码仍在使用
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { copy, numberToChinese } from '@/utils/string' import { copy, numberToChinese } from '@/utils/string'
const info = reactive({ const info = reactive({
title: "数字转金额大写", title: "数字转金额大写",
@@ -24,10 +25,10 @@ const copyRes = async (resStr: string) => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div class=""> <div class="p-4 rounded-2xl bg-white">
<div> <div>
<el-input type="number" v-model="info.content" placeholder="请输入数字,最高13位数字"></el-input> <el-input type="number" v-model="info.content" placeholder="请输入数字,最高13位数字"></el-input>
</div> </div>
@@ -42,6 +43,13 @@ const copyRes = async (resStr: string) => {
<el-input type="textarea" :rows="8" v-model="info.tranRes"></el-input> <el-input type="textarea" :rows="8" v-model="info.tranRes"></el-input>
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
可以将阿拉伯数字金额转换为中文大写金额的在线工具;用户输入阿拉伯数字金额如100会立即转换成壹佰的中文
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
+391
View File
@@ -0,0 +1,391 @@
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div class="tool-content">
<div class="timer-section">
<div class="timer-display">
<div class="timer-circle">
<svg width="200" height="200" viewBox="0 0 200 200">
<!-- 背景圆环 -->
<circle
cx="100"
cy="100"
r="90"
fill="none"
stroke="#e4e7ed"
stroke-width="10"
/>
<!-- 进度圆环 -->
<circle
cx="100"
cy="100"
r="90"
fill="none"
stroke="#409eff"
stroke-width="10"
stroke-linecap="round"
:stroke-dasharray="circumference"
:stroke-dashoffset="progressOffset"
transform="rotate(-90 100 100)"
/>
</svg>
<div class="timer-text">
{{ formattedTime }}
</div>
</div>
</div>
<div class="timer-controls">
<el-button
type="primary"
@click="startTimer"
:disabled="isRunning"
>
开始
</el-button>
<el-button
type="warning"
@click="pauseTimer"
:disabled="!isRunning"
>
暂停
</el-button>
<el-button
type="danger"
@click="resetTimer"
>
重置
</el-button>
</div>
</div>
<div class="settings-section">
<h3>时间设置分钟</h3>
<el-form label-position="top" class="settings-form">
<el-form-item label="工作时间">
<el-input-number
v-model="workTime"
:min="1"
:max="60"
:step="5"
:disabled="isRunning"
/>
</el-form-item>
<el-form-item label="短休息">
<el-input-number
v-model="shortBreakTime"
:min="1"
:max="30"
:step="5"
:disabled="isRunning"
/>
</el-form-item>
<el-form-item label="长休息">
<el-input-number
v-model="longBreakTime"
:min="1"
:max="60"
:step="5"
:disabled="isRunning"
/>
</el-form-item>
<el-form-item label="工作轮数">
<el-input-number
v-model="workRounds"
:min="1"
:max="10"
:disabled="isRunning"
/>
</el-form-item>
</el-form>
</div>
<div class="status-section">
<h3>状态</h3>
<div class="status-info">
<div class="status-item">
<span class="status-label">当前模式:</span>
<span class="status-value">{{ currentModeText }}</span>
</div>
<div class="status-item">
<span class="status-label">已完成轮数:</span>
<span class="status-value">{{ completedRounds }}/{{ workRounds }}</span>
</div>
<div class="status-item">
<span class="status-label">今日完成:</span>
<span class="status-value">{{ todayCompleted }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线番茄时钟工具用于时间管理支持工作和休息模式切换可自定义工作时间休息时间和工作轮数帮助提高工作效率和专注力
</el-text>
</ToolDetail>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { ElMessage } from 'element-plus';
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
const title = "番茄时钟"
// 时间设置
const workTime = ref(25);
const shortBreakTime = ref(5);
const longBreakTime = ref(15);
const workRounds = ref(4);
// 状态
const isRunning = ref(false);
const currentTime = ref(workTime.value * 60);
const currentMode = ref<'work' | 'shortBreak' | 'longBreak'>('work');
const completedRounds = ref(0);
const todayCompleted = ref(0);
const timerInterval = ref<number | null>(null);
// 计算属性
const circumference = computed(() => 2 * Math.PI * 90);
const progressOffset = computed(() => {
const totalTime = currentMode.value === 'work' ? workTime.value * 60 :
currentMode.value === 'shortBreak' ? shortBreakTime.value * 60 :
longBreakTime.value * 60;
const progress = currentTime.value / totalTime;
return circumference.value * (1 - progress);
});
const formattedTime = computed(() => {
const minutes = Math.floor(currentTime.value / 60);
const seconds = currentTime.value % 60;
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
});
const currentModeText = computed(() => {
switch (currentMode.value) {
case 'work': return '工作';
case 'shortBreak': return '短休息';
case 'longBreak': return '长休息';
default: return '工作';
}
});
// 开始计时器
const startTimer = () => {
if (isRunning.value) return;
isRunning.value = true;
timerInterval.value = window.setInterval(() => {
if (currentTime.value > 0) {
currentTime.value--;
} else {
// 时间到
clearInterval(timerInterval.value!);
isRunning.value = false;
// 播放提示音
playNotification();
// 切换模式
switchMode();
}
}, 1000);
};
// 暂停计时器
const pauseTimer = () => {
if (!isRunning.value) return;
clearInterval(timerInterval.value!);
isRunning.value = false;
};
// 重置计时器
const resetTimer = () => {
clearInterval(timerInterval.value!);
isRunning.value = false;
currentTime.value = workTime.value * 60;
currentMode.value = 'work';
completedRounds.value = 0;
};
// 切换模式
const switchMode = () => {
if (currentMode.value === 'work') {
completedRounds.value++;
todayCompleted.value++;
if (completedRounds.value % workRounds.value === 0) {
// 长休息
currentMode.value = 'longBreak';
currentTime.value = longBreakTime.value * 60;
} else {
// 短休息
currentMode.value = 'shortBreak';
currentTime.value = shortBreakTime.value * 60;
}
} else {
// 回到工作模式
currentMode.value = 'work';
currentTime.value = workTime.value * 60;
}
ElMessage.success(`切换到${currentModeText.value}模式`);
};
// 播放提示音
const playNotification = () => {
try {
const audio = new Audio('data:audio/wav;base64,UklGRigAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQAAAAA');
audio.play();
} catch (e) {
console.log('无法播放提示音');
}
};
// 生命周期
onMounted(() => {
// 从本地存储加载今日完成数
const today = new Date().toDateString();
const storedData = localStorage.getItem('pomodoro');
if (storedData) {
const data = JSON.parse(storedData);
if (data.date === today) {
todayCompleted.value = data.completed;
}
}
});
onUnmounted(() => {
// 保存今日完成数到本地存储
const today = new Date().toDateString();
localStorage.setItem('pomodoro', JSON.stringify({
date: today,
completed: todayCompleted.value
}));
// 清除计时器
if (timerInterval.value) {
clearInterval(timerInterval.value);
}
});
</script>
<style scoped>
.tool-content {
display: flex;
flex-direction: column;
gap: 30px;
}
.timer-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.timer-display {
display: flex;
justify-content: center;
}
.timer-circle {
position: relative;
width: 200px;
height: 200px;
}
.timer-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 32px;
font-weight: bold;
color: #333;
}
.timer-controls {
display: flex;
gap: 10px;
}
.settings-section {
width: 100%;
}
.settings-section h3 {
margin-bottom: 15px;
color: #333;
}
.settings-form {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.status-section {
width: 100%;
}
.status-section h3 {
margin-bottom: 15px;
color: #333;
}
.status-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.status-item {
padding: 15px;
background-color: #f5f7fa;
border-radius: 4px;
}
.status-label {
display: block;
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
.status-value {
font-size: 18px;
font-weight: bold;
color: #333;
}
@media (min-width: 768px) {
.tool-content {
flex-direction: row;
flex-wrap: wrap;
}
.timer-section {
width: 100%;
}
.settings-section {
width: 50%;
}
.status-section {
width: 50%;
}
}
</style>
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive,ref } from 'vue' import { reactive,ref } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import QRCodeVue3 from 'qrcode-vue3' import QRCodeVue3 from 'qrcode-vue3'
import { Delete, Plus } from '@element-plus/icons-vue' import { Delete, Plus } from '@element-plus/icons-vue'
import { ElMessage, type UploadFile } from 'element-plus' import { ElMessage, type UploadFile } from 'element-plus'
@@ -58,17 +59,17 @@ const gen = () => {
<div class="flex flex-col mt-3 ml-4 flex-1 mr-3"> <div class="flex flex-col mt-3 ml-4 flex-1 mr-3">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div class="flex justify-between w-full"> <div class="flex justify-between w-full p-4 rounded-2xl bg-white">
<div class="w-4/6 "> <div class="w-4/6 ">
<div class="flex mb-3"> <div class="flex mb-3">
<div class="w-16 mr-2"><el-text>内容</el-text></div> <div class="w-16 mr-2"><el-text>内容</el-text></div>
<el-input v-model="info.content" type="textarea" rows="3" class="w-full" placeholder="可在此输入文字或网址"></el-input> <el-input v-model="info.content" type="textarea" :rows="Number(3)" class="w-full" placeholder="可在此输入文字或网址"></el-input>
</div> </div>
<div class="flex mb-3"> <div class="flex mb-3">
<div class="w-16 mr-2"><el-text>尺寸</el-text></div> <div class="w-16 mr-2"><el-text>尺寸</el-text></div>
<el-input v-model="info.size" class=""> <el-input v-model="info.size" class="">
<template #append> <template #append>
<el-select v-model="info.size" class="w-32"> <el-select v-model="info.size" class="size-select">
<el-option label="常规 200px" value="200" /> <el-option label="常规 200px" value="200" />
<el-option label="常规 300px" value="300" /> <el-option label="常规 300px" value="300" />
<el-option label="适中 350px" value="350" /> <el-option label="适中 350px" value="350" />
@@ -85,8 +86,10 @@ const gen = () => {
</el-input> </el-input>
</div> </div>
<div class="flex mb-3"> <div class="flex mb-3">
<el-text class="w-16">纠错级别</el-text> <div class="w-16">
<el-select v-model="info.errorCorrectionLevel" class="w-36"> <el-text>纠错级别</el-text>
</div>
<el-select v-model="info.errorCorrectionLevel" class="corr-select">
<el-option label="L 可遮挡 7%" value="L" /> <el-option label="L 可遮挡 7%" value="L" />
<el-option label="M 可遮挡 15%" value="M" /> <el-option label="M 可遮挡 15%" value="M" />
<el-option label="Q 可遮挡 25%" value="Q" /> <el-option label="Q 可遮挡 25%" value="Q" />
@@ -171,6 +174,14 @@ const gen = () => {
/> />
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
根据内容生成二维码自定义尺寸纠错级别颜色等带logo二维码
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -178,4 +189,10 @@ const gen = () => {
.my-button{ .my-button{
background-color:red; background-color:red;
} }
.size-select{
@apply w-36
}
.corr-select{
@apply w-36
}
</style> </style>
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
// import { copy } from '@/utils/string' // import { copy } from '@/utils/string'
const info = reactive({ const info = reactive({
title: "生成随机数", title: "生成随机数",
@@ -30,10 +31,10 @@ const random = () => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div class="flex flex-col items-center"> <div class="flex flex-col items-center p-4 rounded-2xl bg-white">
<div> <div>
<el-input-number v-model="info.startNum" :min="0" size="large"/> <el-input-number v-model="info.startNum" :min="0" size="large"/>
<el-input-number v-model="info.endNum" :min="0" size="large" class="ml-5"/> <el-input-number v-model="info.endNum" :min="0" size="large" class="ml-5"/>
@@ -48,6 +49,13 @@ const random = () => {
<el-button @click="random" type="primary" size="large" disabled v-else>生成...</el-button> <el-button @click="random" type="primary" size="large" disabled v-else>生成...</el-button>
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
可定制范围内进行随机数字可用于抽奖点名等用途
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, reactive } from 'vue' import { onMounted, reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { copy, genRandomStrByChars } from '@/utils/string' import { copy, genRandomStrByChars } from '@/utils/string'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
const info = reactive({ const info = reactive({
@@ -117,10 +118,10 @@ onMounted(() => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div> <div class="p-4 rounded-2xl bg-white">
<el-text>密码组合</el-text> <el-text>密码组合</el-text>
<div> <div>
<el-checkbox v-model="info.checkedNum" label="数字(0-9)" @change="(val: any) => (changeCheckBox(val, 0))"/> <el-checkbox v-model="info.checkedNum" label="数字(0-9)" @change="(val: any) => (changeCheckBox(val, 0))"/>
@@ -148,6 +149,14 @@ onMounted(() => {
<el-input type="textarea" :autosize="info.autosize" v-model="info.resStr"></el-input> <el-input type="textarea" :autosize="info.autosize" v-model="info.resStr"></el-input>
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线生成密码支持开启或关闭大小写 数字 特殊符号支持自定义字符长度批量生成密码
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -0,0 +1,276 @@
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div class="tool-content">
<div class="test-section">
<div
ref="testArea"
class="test-area"
:class="testStatus"
@click="handleTestClick"
>
<div v-if="testStatus === 'ready'" class="test-message">
点击开始测试
</div>
<div v-else-if="testStatus === 'waiting'" class="test-message">
准备就绪等待变色...
</div>
<div v-else-if="testStatus === 'active'" class="test-message">
点击
</div>
<div v-else-if="testStatus === 'completed'" class="test-message">
反应时间: {{ reactionTime }} ms
</div>
</div>
<div class="test-controls">
<el-button
type="primary"
@click="startTest"
:disabled="testStatus === 'waiting' || testStatus === 'active'"
>
开始测试
</el-button>
<el-button @click="resetTest">重置</el-button>
</div>
</div>
<div class="stats-section">
<h3>测试统计</h3>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-label">最佳反应时间</div>
<div class="stat-value">{{ bestTime }} ms</div>
</div>
<div class="stat-item">
<div class="stat-label">平均反应时间</div>
<div class="stat-value">{{ averageTime }} ms</div>
</div>
<div class="stat-item">
<div class="stat-label">测试次数</div>
<div class="stat-value">{{ testCount }}</div>
</div>
</div>
<div class="history-section">
<h4>测试历史</h4>
<div class="history-list">
<div
v-for="(time, index) in testHistory"
:key="index"
class="history-item"
>
测试 {{ index + 1 }}: {{ time }} ms
</div>
</div>
</div>
</div>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线反应速度测试工具通过点击变色的方块来测试你的反应速度记录测试历史和统计数据帮助你了解自己的反应能力
</el-text>
</ToolDetail>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
const title = "反应速度测试"
// 移除未使用的 testArea 引用
const testStatus = ref<'ready' | 'waiting' | 'active' | 'completed'>('ready');
const reactionTime = ref(0);
const testHistory = ref<number[]>([]);
const startTime = ref(0);
const waitingTimer = ref<number | null>(null);
// 计算属性
const testCount = computed(() => testHistory.value.length);
const bestTime = computed(() => {
if (testHistory.value.length === 0) return 0;
return Math.min(...testHistory.value);
});
const averageTime = computed(() => {
if (testHistory.value.length === 0) return 0;
const sum = testHistory.value.reduce((acc, time) => acc + time, 0);
return Math.round(sum / testHistory.value.length);
});
// 开始测试
const startTest = () => {
testStatus.value = 'waiting';
// 随机等待时间(1-5秒)
const waitTime = 1000 + Math.random() * 4000;
waitingTimer.value = window.setTimeout(() => {
testStatus.value = 'active';
startTime.value = performance.now();
}, waitTime);
};
// 处理测试区域点击
const handleTestClick = () => {
if (testStatus.value === 'ready') {
startTest();
} else if (testStatus.value === 'active') {
const endTime = performance.now();
reactionTime.value = Math.round(endTime - startTime.value);
testHistory.value.push(reactionTime.value);
testStatus.value = 'completed';
}
};
// 重置测试
const resetTest = () => {
if (waitingTimer.value) {
clearTimeout(waitingTimer.value);
}
testStatus.value = 'ready';
reactionTime.value = 0;
};
</script>
<style scoped>
.tool-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.test-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.test-area {
width: 300px;
height: 300px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid #e4e7ed;
}
.test-area.ready {
background-color: #f5f7fa;
}
.test-area.waiting {
background-color: #ecf5ff;
}
.test-area.active {
background-color: #f0f9eb;
border-color: #67c23a;
box-shadow: 0 0 20px rgba(103, 194, 58, 0.5);
}
.test-area.completed {
background-color: #fef0f0;
border-color: #f56c6c;
}
.test-message {
font-size: 24px;
font-weight: bold;
color: #333;
}
.test-controls {
display: flex;
gap: 10px;
}
.stats-section {
width: 100%;
}
.stats-section h3 {
margin-bottom: 15px;
color: #333;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.stat-item {
padding: 20px;
background-color: #f5f7fa;
border-radius: 8px;
text-align: center;
}
.stat-label {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #409eff;
}
.history-section {
margin-top: 20px;
}
.history-section h4 {
margin-bottom: 10px;
color: #333;
}
.history-list {
background-color: #f9f9f9;
padding: 20px;
border-radius: 8px;
max-height: 200px;
overflow-y: auto;
}
.history-item {
margin-bottom: 10px;
color: #666;
}
.history-item:last-child {
margin-bottom: 0;
}
@media (min-width: 768px) {
.tool-content {
flex-direction: row;
}
.test-section {
width: 50%;
}
.stats-section {
width: 50%;
}
}
</style>
@@ -1,12 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { Codemirror } from "vue-codemirror" import { Codemirror } from "vue-codemirror"
import { lineNumbers } from '@codemirror/view'; // import { lineNumbers } from '@codemirror/view';
const info = reactive({ const info = reactive({
title: "正则表达式测试", title: "正则表达式测试",
extensions: [lineNumbers()], // extensions: [lineNumbers()],
reg: '', reg: '',
content: `一些测试实例: content: `一些测试实例:
邮箱1demo@163.com 邮箱1demo@163.com
@@ -116,10 +117,10 @@ const execMatch = () => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div> <div class="p-4 rounded-2xl bg-white ">
<div> <div>
<codemirror <codemirror
v-model="info.content" v-model="info.content"
@@ -128,7 +129,6 @@ const execMatch = () => {
:autofocus="true" :autofocus="true"
:indent-with-tab="true" :indent-with-tab="true"
:tabSize="2" :tabSize="2"
:extensions="info.extensions"
/> />
</div> </div>
@@ -158,6 +158,14 @@ const execMatch = () => {
<el-input type="textarea" :rows="4" readonly v-model="info.matchRes"></el-input> <el-input type="textarea" :rows="4" readonly v-model="info.matchRes"></el-input>
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
正则表达式是对字符串包括普通字符例如a z 之间的字母和特殊字符称为元字符操作的一种逻辑公式就是用事先定义好的一些特定字符及这些特定字符的组合组成一个规则字符串这个规则字符串用来表达对字符串的一种过滤逻辑正则表达式是一种文本模式该模式描述在搜索文本时要匹配的一个或多个字符串
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -0,0 +1,360 @@
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div class="tool-content">
<div class="game-section">
<div class="player-section">
<h3>你的选择</h3>
<div class="choices">
<div
v-for="choice in choices"
:key="choice.value"
class="choice-item"
:class="{ active: selectedChoice === choice.value }"
@click="makeChoice(choice.value)"
>
<div class="choice-icon">{{ choice.icon }}</div>
<div class="choice-name">{{ choice.name }}</div>
</div>
</div>
</div>
<div class="result-section">
<h3>对战结果</h3>
<div v-if="gameResult" class="result-content">
<div class="result-message">{{ gameResult.message }}</div>
<div class="result-details">
<div class="player-result">: {{ getChoiceName(selectedChoice) }}</div>
<div class="computer-result">电脑: {{ getChoiceName(computerChoice) }}</div>
</div>
<el-button type="primary" @click="resetGame">再来一局</el-button>
</div>
<div v-else class="no-result">
请选择你的武器开始游戏
</div>
</div>
<div class="computer-section">
<h3>电脑选择</h3>
<div class="computer-choice">
<div v-if="computerChoice" class="choice-item computer">
<div class="choice-icon">{{ getChoiceIcon(computerChoice) }}</div>
<div class="choice-name">{{ getChoiceName(computerChoice) }}</div>
</div>
<div v-else class="waiting">
等待中...
</div>
</div>
</div>
</div>
<div class="stats-section">
<h3>游戏统计</h3>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-label">总游戏数</div>
<div class="stat-value">{{ stats.total }}</div>
</div>
<div class="stat-item win">
<div class="stat-label">胜利</div>
<div class="stat-value">{{ stats.wins }}</div>
</div>
<div class="stat-item lose">
<div class="stat-label">失败</div>
<div class="stat-value">{{ stats.losses }}</div>
</div>
<div class="stat-item draw">
<div class="stat-label">平局</div>
<div class="stat-value">{{ stats.draws }}</div>
</div>
</div>
<el-button @click="resetStats">重置统计</el-button>
</div>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线剪刀石头布游戏与电脑对战支持实时显示对战结果和游戏统计可用于休闲娱乐和决策参考
</el-text>
</ToolDetail>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
const title = "剪刀石头布"
const choices = [
{ name: '剪刀', value: 'scissors', icon: '✂️' },
{ name: '石头', value: 'rock', icon: '🪨' },
{ name: '布', value: 'paper', icon: '📄' }
];
const selectedChoice = ref<string>('');
const computerChoice = ref<string>('');
const gameResult = ref<{ message: string; result: 'win' | 'lose' | 'draw' } | null>(null);
const stats = ref({
total: 0,
wins: 0,
losses: 0,
draws: 0
});
// 生成电脑的选择
const generateComputerChoice = () => {
const randomIndex = Math.floor(Math.random() * choices.length);
return choices[randomIndex].value;
};
// 判断游戏结果
const determineWinner = (player: string, computer: string) => {
if (player === computer) {
return { message: '平局!', result: 'draw' as const };
}
if (
(player === 'rock' && computer === 'scissors') ||
(player === 'scissors' && computer === 'paper') ||
(player === 'paper' && computer === 'rock')
) {
return { message: '你赢了!', result: 'win' as const };
}
return { message: '你输了!', result: 'lose' as const };
};
// 玩家选择
const makeChoice = (choice: string) => {
if (gameResult.value) {
return;
}
selectedChoice.value = choice;
computerChoice.value = generateComputerChoice();
gameResult.value = determineWinner(choice, computerChoice.value);
// 更新统计
stats.value.total++;
if (gameResult.value.result === 'win') {
stats.value.wins++;
} else if (gameResult.value.result === 'lose') {
stats.value.losses++;
} else {
stats.value.draws++;
}
};
// 重置游戏
const resetGame = () => {
selectedChoice.value = '';
computerChoice.value = '';
gameResult.value = null;
};
// 重置统计
const resetStats = () => {
stats.value = {
total: 0,
wins: 0,
losses: 0,
draws: 0
};
resetGame();
};
// 获取选择的名称
const getChoiceName = (choice: string) => {
const found = choices.find(c => c.value === choice);
return found ? found.name : '';
};
// 获取选择的图标
const getChoiceIcon = (choice: string) => {
const found = choices.find(c => c.value === choice);
return found ? found.icon : '';
};
</script>
<style scoped>
.tool-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.game-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 30px;
}
.player-section, .computer-section {
width: 100%;
text-align: center;
}
.choices {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 10px;
}
.choice-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
border: 2px solid #e4e7ed;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
min-width: 100px;
}
.choice-item:hover {
border-color: #409eff;
box-shadow: 0 0 10px rgba(64, 158, 255, 0.3);
}
.choice-item.active {
border-color: #409eff;
background-color: #ecf5ff;
box-shadow: 0 0 10px rgba(64, 158, 255, 0.5);
}
.choice-icon {
font-size: 48px;
margin-bottom: 10px;
}
.choice-name {
font-size: 16px;
color: #333;
}
.computer-choice {
margin-top: 10px;
}
.computer-choice .choice-item {
cursor: default;
}
.waiting {
padding: 40px;
font-size: 16px;
color: #999;
border: 2px dashed #e4e7ed;
border-radius: 8px;
min-width: 200px;
}
.result-section {
text-align: center;
margin: 20px 0;
}
.result-content {
padding: 30px;
background-color: #f5f7fa;
border-radius: 8px;
margin-top: 10px;
}
.result-message {
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.result-details {
margin-bottom: 20px;
font-size: 16px;
}
.player-result, .computer-result {
margin: 10px 0;
}
.no-result {
padding: 40px;
font-size: 16px;
color: #999;
background-color: #f9f9f9;
border-radius: 8px;
margin-top: 10px;
}
.stats-section {
margin-top: 20px;
text-align: center;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
margin: 20px 0;
}
.stat-item {
padding: 20px;
background-color: #f5f7fa;
border-radius: 8px;
text-align: center;
}
.stat-item.win {
background-color: #f0f9eb;
border: 1px solid #c2e7b0;
}
.stat-item.lose {
background-color: #fef0f0;
border: 1px solid #fbc4c4;
}
.stat-item.draw {
background-color: #f0f0f0;
border: 1px solid #d9d9d9;
}
.stat-label {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #333;
}
@media (min-width: 768px) {
.game-section {
flex-direction: row;
justify-content: space-around;
}
.player-section, .computer-section {
width: 30%;
}
.result-section {
width: 40%;
}
}
</style>
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { copy } from '@/utils/string' import { copy } from '@/utils/string'
import { isBinary, isOctal, isDecimal, isHexadecimal } from '@/utils/verify' import { isBinary, isOctal, isDecimal, isHexadecimal } from '@/utils/verify'
@@ -193,14 +194,14 @@ const copyRes = async (index: any) => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1 ">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div class="flex"> <div class="flex">
<div class="mr-2 w-full"> <div class="mr-2 w-full">
<el-input v-model="info.content"> <el-input v-model="info.content">
<template #prepend> <template #prepend>
<el-select v-model="info.chooseTranOptions" placeholder="Select" class="w-24"> <el-select v-model="info.chooseTranOptions" placeholder="Select" class="choosetranoptions-select">
<el-option <el-option
v-for="item in info.tranOptions" v-for="item in info.tranOptions"
:key="item.value" :key="item.value"
@@ -247,7 +248,19 @@ const copyRes = async (index: any) => {
</el-table> </el-table>
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
进位制其实是一种记数的方式所以也称为进位记数法/位值计数法可以用有限的数字符号代表所有的数值 可使用数字符号的数目称为基数英文radix或底数基数为n即可称n进位制简称n进制 例如平常生活中我们经常用到的十进制就是使用10个阿拉伯数字0-9进行记数所以它的基数就是10称为十进制
</el-text>
</ToolDetail>
</div>
</template> </template>
<style scoped> <style scoped>
.choosetranoptions-select{
@apply w-24
}
</style> </style>
@@ -2,6 +2,7 @@
import { ref, reactive } from "vue" import { ref, reactive } from "vue"
import SignImageCore from './SignImageCore.vue' import SignImageCore from './SignImageCore.vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { UploadProps, UploadInstance, UploadRawFile, genFileId } from 'element-plus' import { UploadProps, UploadInstance, UploadRawFile, genFileId } from 'element-plus'
import { autoDown } from '@/utils/file' import { autoDown } from '@/utils/file'
import { Jh_getTimeStamp } from '@/utils/time' import { Jh_getTimeStamp } from '@/utils/time'
@@ -48,10 +49,10 @@ const saveImg = () => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div class="flex flex-col mb-3 h-[38rem]"> <div class="flex flex-col mb-3 h-[38rem] p-4 rounded-2xl bg-white">
<div class="mb-3 flex flex-row-reverse"> <div class="mb-3 flex flex-row-reverse">
<!-- save --> <!-- save -->
<el-button type="primary" class="ml-2" @click="saveImg">保存</el-button> <el-button type="primary" class="ml-2" @click="saveImg">保存</el-button>
@@ -80,6 +81,14 @@ const saveImg = () => {
@getNewImg="info.getNewImg" @getNewImg="info.getNewImg"
></SignImageCore> ></SignImageCore>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线图片裁剪图片标注图片滤镜图片画笔图片旋转图片文字图片尺寸调整等操作
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -0,0 +1,130 @@
<script setup lang="ts">
import { ref } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { ElMessage } from 'element-plus'
import { copy } from '@/utils/string'
import { format } from 'sql-formatter'
const title = "SQL格式化"
// 状态管理
const inputText = ref('')
const outputText = ref('')
const indentSize = ref(4)
// 格式化SQL
const formatSql = () => {
if (!inputText.value) {
ElMessage.warning('请输入要格式化的SQL')
return
}
try {
// 使用sql-formatter库格式化SQL
const formatted = format(inputText.value)
outputText.value = formatted
ElMessage.success('格式化成功')
} catch (error) {
outputText.value = '格式化失败'
ElMessage.error('格式化失败:' + error)
}
}
// 压缩SQL
const minifySql = () => {
if (!inputText.value) {
ElMessage.warning('请输入要压缩的SQL')
return
}
try {
// 压缩SQL(去除空格和换行)
const minified = inputText.value
.replace(/\s+/g, ' ') // 替换多个空白字符为单个空格
.trim()
outputText.value = minified
ElMessage.success('压缩成功')
} catch (error) {
outputText.value = '压缩失败'
ElMessage.error('压缩失败:' + error)
}
}
// 复制结果
const copyResult = () => {
if (!outputText.value || outputText.value === '格式化失败' || outputText.value === '压缩失败') {
ElMessage.warning('没有可复制的内容')
return
}
copy(outputText.value)
}
// 清空输入
const clearInput = () => {
inputText.value = ''
outputText.value = ''
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<!-- 输入部分 -->
<div class="mb-4">
<el-input
v-model="inputText"
type="textarea"
:rows="6"
placeholder="请输入要格式化的SQL"
class="mb-2"
/>
<div class="flex items-center gap-2 mb-2">
<el-text>缩进大小</el-text>
<el-input-number v-model="indentSize" :min="1" :max="8" size="small" />
</div>
</div>
<!-- 操作按钮 -->
<div class="mb-4">
<el-button type="primary" @click="formatSql">格式化</el-button>
<el-button type="success" @click="minifySql">压缩</el-button>
<el-button @click="clearInput">清空</el-button>
</div>
<!-- 结果部分 -->
<div>
<el-input
v-model="outputText"
type="textarea"
:rows="6"
placeholder="格式化结果"
readonly
>
<template #append>
<el-button @click="copyResult">复制</el-button>
</template>
</el-input>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线SQL格式化工具用于美化和压缩SQL代码支持自定义缩进大小使SQL代码更易于阅读和维护
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,205 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { ElMessage } from 'element-plus'
import { copy } from '@/utils/string'
const title = "数据存储单位换算"
// 状态管理
const inputValue = ref('')
const fromUnit = ref('B')
const toUnit = ref('KB')
// 存储单位选项
const unitOptions = [
{ label: 'bit (比特)', value: 'bit' },
{ label: 'B (字节)', value: 'B' },
{ label: 'KB (千字节)', value: 'KB' },
{ label: 'MB (兆字节)', value: 'MB' },
{ label: 'GB (吉字节)', value: 'GB' },
{ label: 'TB (太字节)', value: 'TB' },
{ label: 'PB (拍字节)', value: 'PB' },
{ label: 'EB (艾字节)', value: 'EB' }
]
// 单位转换系数
const unitFactors = {
'bit': 1,
'B': 8, // 1字节 = 8比特
'KB': 8 * 1024,
'MB': 8 * 1024 * 1024,
'GB': 8 * 1024 * 1024 * 1024,
'TB': 8 * 1024 * 1024 * 1024 * 1024,
'PB': 8 * 1024 * 1024 * 1024 * 1024 * 1024,
'EB': 8 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024
}
// 单个转换结果
const convertedValue = computed(() => {
if (!inputValue.value) return ''
const value = parseFloat(inputValue.value)
if (isNaN(value)) return '无效输入'
try {
// 先转换为字节
const bytes = value * unitFactors[fromUnit.value]
// 再转换为目标单位
const result = bytes / unitFactors[toUnit.value]
// 检查小数点后的位数
const resultStr = result.toString()
const decimalIndex = resultStr.indexOf('.')
if (decimalIndex !== -1 && resultStr.length - decimalIndex - 1 > 5) {
// 小数点超过5位,使用科学计数法
return result.toExponential(5)
} else {
// 否则使用固定小数位
return result.toFixed(6)
}
} catch (error) {
return '转换失败'
}
})
// 所有单位的转换结果
const allConvertedValues = computed(() => {
if (!inputValue.value) return []
const value = parseFloat(inputValue.value)
if (isNaN(value)) return []
try {
// 先转换为字节
const bytes = value * unitFactors[fromUnit.value]
// 计算所有单位的转换结果
return unitOptions.map(option => {
const result = bytes / unitFactors[option.value]
// 检查小数点后的位数
const resultStr = result.toString()
const decimalIndex = resultStr.indexOf('.')
let formattedResult
if (decimalIndex !== -1 && resultStr.length - decimalIndex - 1 > 5) {
// 小数点超过5位,使用科学计数法
formattedResult = result.toExponential(5)
} else {
// 否则使用固定小数位
formattedResult = result.toFixed(6)
}
return {
unit: option.label,
value: formattedResult
}
})
} catch (error) {
return []
}
})
// 复制结果
const copyResult = () => {
if (!convertedValue.value || convertedValue.value === '无效输入' || convertedValue.value === '转换失败') {
ElMessage.warning('没有可复制的内容')
return
}
copy(convertedValue.value)
}
// 清空输入
const clearInput = () => {
inputValue.value = ''
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<!-- 输入部分 -->
<div class="mb-4">
<el-input
v-model="inputValue"
type="number"
placeholder="请输入数值"
class="mb-2"
/>
<div class="flex gap-4 items-center">
<div class="flex items-center gap-2">
<el-text>从单位</el-text>
<el-select v-model="fromUnit" style="width: 140px;">
<el-option
v-for="option in unitOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</div>
<div class="flex items-center gap-2">
<el-text>到单位</el-text>
<el-select v-model="toUnit" style="width: 140px;">
<el-option
v-for="option in unitOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="mb-4">
<el-button type="primary" @click="clearInput">清空</el-button>
</div>
<!-- 结果部分 -->
<div class="mb-4">
<el-input
:value="convertedValue"
placeholder="转换结果"
readonly
>
<template #append>
<el-button @click="copyResult">复制</el-button>
</template>
</el-input>
</div>
<!-- 所有单位转换结果 -->
<div v-if="allConvertedValues.length > 0">
<el-text class="block mb-2">所有单位转换结果</el-text>
<div class="grid grid-cols-2 md:grid-cols-4 gap-2">
<div v-for="item in allConvertedValues" :key="item.unit" class="bg-gray-50 p-2 rounded">
<div class="text-sm text-gray-500">{{ item.unit }}</div>
<div class="font-medium">{{ item.value }}</div>
</div>
</div>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线数据存储单位换算工具支持比特(bit)字节(B)KBMBGBTBPBEB等存储单位之间的相互转换
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
+109
View File
@@ -0,0 +1,109 @@
<script setup lang="ts">
import { reactive, ref, shallowRef, onBeforeUnmount, onMounted } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import '@wangeditor/editor/dist/css/style.css' // 引入富文本 css
import { Editor, Toolbar } from '@wangeditor/editor-for-vue' //富文本组件
import { copy } from '@/utils/string'
const info = reactive({
title: "在线文本编辑/HTML获取",
mode: 'default',
})
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()
// 内容
const content = ref(``)
// html
const html = ref('')
// 工具栏配置
const toolbarConfig = {
excludeKeys: [
"uploadImage",
"group-video",
"insertLink",
]
}
//编辑器配置
const editorConfig = { placeholder: '请输入内容...' }
const handleCreated = (editor) => {
editorRef.value = editor // 记录 editor 实例,重要!
// html = editor.getHtml()
}
const handleChange = (editor) => {
html.value = editor.getHtml()
}
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
onMounted(() => {
})
//copy
const copyRes = async (resStr: string) => {
copy(resStr)
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white h-[510px]">
<div class="border z-10 mt-3 h-96">
<Toolbar
class=""
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="info.mode"
/>
<Editor
class="border"
v-model="content"
:defaultConfig="editorConfig"
:mode="info.mode"
@onCreated="handleCreated"
@onChange="handleChange"
/>
</div>
</div>
<div class="p-4 mt-3 rounded-2xl bg-white">
<el-button type="primary" @click="copyRes(html)">复制HTML</el-button>
<el-input
v-model="html"
disabled
class="mt-3"
type="textarea"
autosize
placeholder="html预览处"
/>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线富文本编辑, html实时预览在线编辑文本文本编辑获取html
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
.downext-select{
@apply w-24
}
</style>
@@ -0,0 +1,64 @@
<script setup lang="ts">
import { reactive, computed } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { copy } from '@/utils/string'
const info = reactive({
title: "文本去重",
content: '',
tranRes: '',
})
//clear
const clear = () => {
info.content = ''
}
const removeDuplicate = computed(() => {
let tmpArr = info.content.split('\n')
let resultSet = new Set(tmpArr)
let fromResArr = Array.from(resultSet)
info.tranRes = fromResArr.join("\n")
return info.tranRes
})
//copy
const copyRes = async () => {
copy(info.tranRes)
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div>
<el-input type="textarea" :rows="8" v-model="info.content"></el-input>
</div>
<div class="mt-4">
<el-button type="primary" @click="copyRes">复制结果</el-button>
<el-button type="danger" @click="clear">清空内容</el-button>
</div>
<div class="mt-3 min-h-md bg-gray-100 p-3 mb-3">
<el-input type="textarea" :rows="8" v-model="removeDuplicate"></el-input>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
可以删除或去除文本或字符串中的重复行
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,130 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { ElMessage } from 'element-plus'
import { copy } from '@/utils/string'
const title = "文本替换"
// 状态管理
const inputText = ref('')
const findText = ref('')
const replaceText = ref('')
const useRegex = ref(false)
const caseSensitive = ref(true)
// 替换结果
const replaceResult = computed(() => {
if (!inputText.value) return ''
if (!findText.value) return inputText.value
try {
if (useRegex.value) {
// 使用正则表达式替换
const regex = new RegExp(findText.value, caseSensitive.value ? 'g' : 'gi')
return inputText.value.replace(regex, replaceText.value)
} else {
// 使用普通文本替换
if (caseSensitive.value) {
return inputText.value.replace(new RegExp(findText.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), replaceText.value)
} else {
return inputText.value.replace(new RegExp(findText.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'), replaceText.value)
}
}
} catch (error) {
return '替换失败:正则表达式语法错误'
}
})
// 复制结果
const copyResult = () => {
if (!replaceResult.value || replaceResult.value === '替换失败:正则表达式语法错误') {
ElMessage.warning('没有可复制的内容')
return
}
copy(replaceResult.value)
}
// 清空输入
const clearInput = () => {
inputText.value = ''
findText.value = ''
replaceText.value = ''
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<!-- 输入部分 -->
<div class="mb-4">
<el-input
v-model="inputText"
type="textarea"
:rows="6"
placeholder="请输入要处理的文本"
class="mb-2"
/>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-2">
<div>
<el-input
v-model="findText"
placeholder="请输入要查找的文本"
class="mb-2"
/>
</div>
<div>
<el-input
v-model="replaceText"
placeholder="请输入要替换的文本"
class="mb-2"
/>
</div>
</div>
<div class="flex items-center gap-4 mb-2">
<el-checkbox v-model="useRegex">使用正则表达式</el-checkbox>
<el-checkbox v-model="caseSensitive">区分大小写</el-checkbox>
</div>
</div>
<!-- 操作按钮 -->
<div class="mb-4">
<el-button @click="copyResult">复制结果</el-button>
<el-button @click="clearInput">清空</el-button>
</div>
<!-- 结果部分 -->
<div>
<el-input
:value="replaceResult"
type="textarea"
:rows="6"
placeholder="替换结果"
readonly
>
<template #append>
<el-button @click="copyResult">复制</el-button>
</template>
</el-input>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线文本替换工具支持普通文本和正则表达式替换可用于批量修改文本内容支持区分大小写选项
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, shallowRef, onBeforeUnmount } from 'vue' import { reactive, ref, shallowRef, onBeforeUnmount } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import html2canvas from "html2canvas"; import html2canvas from "html2canvas";
import '@wangeditor/editor/dist/css/style.css' // css import '@wangeditor/editor/dist/css/style.css' // css
import { Editor, Toolbar } from '@wangeditor/editor-for-vue' // import { Editor, Toolbar } from '@wangeditor/editor-for-vue' //
@@ -87,10 +88,10 @@ onBeforeUnmount(() => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div class=""> <div class="p-4 rounded-2xl bg-white">
<div> <div>
<div class=""> <div class="">
@@ -104,8 +105,8 @@ onBeforeUnmount(() => {
<el-text>背景颜色</el-text> <el-text>背景颜色</el-text>
<el-color-picker v-model="info.convasBackgroundColor" size="large" /> <el-color-picker v-model="info.convasBackgroundColor" size="large" />
</div> </div>
<div> <div class="flex">
<el-select v-model="info.downExt" class="" placeholder="Select"> <el-select v-model="info.downExt" class="downext-select" placeholder="Select">
<el-option <el-option
v-for="item in info.downExts" v-for="item in info.downExts"
:key="item.value" :key="item.value"
@@ -132,11 +133,19 @@ onBeforeUnmount(() => {
/> />
<div ref="poster" class="absolute top-0 -z-10" v-html="valueHtml"></div> <div ref="poster" class="absolute top-0 -z-10" v-html="valueHtml"></div>
</div> </div>
<!-- v-html="valueHtml" -->
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
把文本转换成图片生成长图富文本自定义文字排版可导出pngjpeg格式可更换背景图设置宽度是好用的文本转图片工具
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.downext-select{
@apply w-24
}
</style> </style>
@@ -0,0 +1,232 @@
<script setup lang="ts">
import { ref } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { ElMessage } from 'element-plus'
import { ArrowDown, ArrowUp, Delete } from '@element-plus/icons-vue'
import { copy } from '@/utils/string'
const title = "文本处理工作流"
// 状态管理
const inputText = ref('')
const workflowSteps = ref([
{ id: 1, type: 'trim', name: '去除首尾空格' },
{ id: 2, type: 'lowercase', name: '转为小写' }
])
const outputText = ref('')
// 可用的处理步骤类型
const availableSteps = [
{ type: 'trim', name: '去除首尾空格' },
{ type: 'lowercase', name: '转为小写' },
{ type: 'uppercase', name: '转为大写' },
{ type: 'removeEmptyLines', name: '移除空行' },
{ type: 'trimLines', name: '去除每行首尾空格' },
{ type: 'sort', name: '按字母排序' }
]
// 执行工作流
const executeWorkflow = () => {
if (!inputText.value) {
ElMessage.warning('请输入要处理的文本')
return
}
try {
let result = inputText.value
// 按顺序执行每个步骤
workflowSteps.value.forEach(step => {
result = processText(result, step.type)
})
outputText.value = result
ElMessage.success('工作流执行成功')
} catch (error) {
outputText.value = '执行失败'
ElMessage.error('执行失败:' + error)
}
}
// 处理文本
const processText = (text: string, type: string): string => {
switch (type) {
case 'trim':
return text.trim()
case 'lowercase':
return text.toLowerCase()
case 'uppercase':
return text.toUpperCase()
case 'removeEmptyLines':
return text.split('\n').filter(line => line.trim() !== '').join('\n')
case 'trimLines':
return text.split('\n').map(line => line.trim()).join('\n')
case 'sort':
return text.split('\n').filter(line => line.trim() !== '').sort().join('\n')
default:
return text
}
}
// 添加步骤
const addStep = (type: string, name: string) => {
const newId = Math.max(...workflowSteps.value.map(step => step.id), 0) + 1
workflowSteps.value.push({ id: newId, type, name })
}
// 删除步骤
const removeStep = (id: number) => {
workflowSteps.value = workflowSteps.value.filter(step => step.id !== id)
}
// 上移步骤
const moveStepUp = (index: number) => {
if (index > 0) {
const temp = workflowSteps.value[index]
workflowSteps.value[index] = workflowSteps.value[index - 1]
workflowSteps.value[index - 1] = temp
}
}
// 下移步骤
const moveStepDown = (index: number) => {
if (index < workflowSteps.value.length - 1) {
const temp = workflowSteps.value[index]
workflowSteps.value[index] = workflowSteps.value[index + 1]
workflowSteps.value[index + 1] = temp
}
}
// 复制结果
const copyResult = () => {
if (!outputText.value || outputText.value === '执行失败') {
ElMessage.warning('没有可复制的内容')
return
}
copy(outputText.value)
}
// 清空输入
const clearInput = () => {
inputText.value = ''
outputText.value = ''
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<!-- 输入部分 -->
<div class="mb-4">
<el-input
v-model="inputText"
type="textarea"
:rows="4"
placeholder="请输入要处理的文本"
class="mb-2"
/>
</div>
<!-- 工作流步骤 -->
<div class="mb-4">
<el-card class="mb-4">
<template #header>
<div class="flex justify-between items-center">
<span>工作流步骤</span>
<el-dropdown>
<el-button type="primary" size="small">
添加步骤 <el-icon><ArrowDown /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="step in availableSteps"
:key="step.type"
@click="addStep(step.type, step.name)"
>
{{ step.name }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<div v-if="workflowSteps.length === 0" class="text-center text-gray-500 py-4">
暂无步骤请添加处理步骤
</div>
<div v-else class="space-y-2">
<div
v-for="(step, index) in workflowSteps"
:key="step.id"
class="flex items-center gap-2 p-2 border rounded"
>
<el-button
type="text"
size="small"
@click="moveStepUp(index)"
:disabled="index === 0"
>
<el-icon><ArrowUp /></el-icon>
</el-button>
<el-button
type="text"
size="small"
@click="moveStepDown(index)"
:disabled="index === workflowSteps.length - 1"
>
<el-icon><ArrowDown /></el-icon>
</el-button>
<div class="flex-1">{{ step.name }}</div>
<el-button
type="danger"
size="small"
@click="removeStep(step.id)"
>
<el-icon><Delete /></el-icon>
</el-button>
</div>
</div>
</el-card>
</div>
<!-- 操作按钮 -->
<div class="mb-4">
<el-button type="primary" @click="executeWorkflow">执行工作流</el-button>
<el-button @click="clearInput">清空</el-button>
</div>
<!-- 结果部分 -->
<div>
<el-input
v-model="outputText"
type="textarea"
:rows="4"
placeholder="处理结果"
readonly
>
<template #append>
<el-button @click="copyResult">复制</el-button>
</template>
</el-input>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线文本处理工作流工具允许用户定义一系列文本处理步骤并按顺序执行可用于复杂的文本处理任务提高工作效率
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
@@ -3,6 +3,7 @@ import { onMounted, onUnmounted, reactive } from 'vue'
import { VideoPause,VideoPlay,CopyDocument } from '@element-plus/icons-vue' import { VideoPause,VideoPlay,CopyDocument } from '@element-plus/icons-vue'
import { Jh_getTimeStamp,Jh_timeStampToTime,Jh_convertTimeStamp } from '@/utils/time' import { Jh_getTimeStamp,Jh_timeStampToTime,Jh_convertTimeStamp } from '@/utils/time'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { copy } from '@/utils/string' import { copy } from '@/utils/string'
const info = reactive({ const info = reactive({
title: "时间戳转换", title: "时间戳转换",
@@ -84,8 +85,7 @@ const copyRes = async () => {
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1"> <div class="flex flex-col mt-3 ml-4 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div class="flex flex-col p-4 rounded-2xl bg-white">
<div class="flex flex-col">
<div class="flex flex-direction"> <div class="flex flex-direction">
<el-text class="mr-2 w-12">现在</el-text> <el-text class="mr-2 w-12">现在</el-text>
<el-button class="mr-3" link @click="copyRes()">{{ info.nowTime }} <el-icon class="ml-1 mr-1"><CopyDocument /></el-icon></el-button> <el-button class="mr-3" link @click="copyRes()">{{ info.nowTime }} <el-icon class="ml-1 mr-1"><CopyDocument /></el-icon></el-button>
@@ -96,16 +96,6 @@ const copyRes = async () => {
<div class="flex flex-direction mt-4 justify-start"> <div class="flex flex-direction mt-4 justify-start">
<el-text class="mr-2 w-12">时间戳</el-text> <el-text class="mr-2 w-12">时间戳</el-text>
<el-input v-model="info.waitTimeStamp" class="h-8 mr-2 w-60 max-w-[30%]" placeholder="毫秒/秒"> <el-input v-model="info.waitTimeStamp" class="h-8 mr-2 w-60 max-w-[30%]" placeholder="毫秒/秒">
<!-- <template #append>
<el-select v-model="info.chooseTranDateOption" placeholder="Select" class="w-24">
<el-option
v-for="item in info.tranOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template> -->
</el-input> </el-input>
<el-button class="mr-2 max-w-[25%]" @click="timeTran('toDate')">转换</el-button> <el-button class="mr-2 max-w-[25%]" @click="timeTran('toDate')">转换</el-button>
<el-input v-model="info.tranDate" class="h-8 w-72 mr-2 max-w-[30%]" placeholder="北京时间"></el-input> <el-input v-model="info.tranDate" class="h-8 w-72 mr-2 max-w-[30%]" placeholder="北京时间"></el-input>
@@ -117,7 +107,7 @@ const copyRes = async () => {
<el-button class="mr-2 max-w-[25%]" @click="timeTran('toStamp')">转换</el-button> <el-button class="mr-2 max-w-[25%]" @click="timeTran('toStamp')">转换</el-button>
<el-input v-model="info.tranTimeStamp" :value="info.tranTimeStamp == 0 ? '' : info.tranTimeStamp" class="h-8 w-72 mr-2 max-w-[30%]" placeholder="时间戳"> <el-input v-model="info.tranTimeStamp" :value="info.tranTimeStamp == 0 ? '' : info.tranTimeStamp" class="h-8 w-72 mr-2 max-w-[30%]" placeholder="时间戳">
<template #prepend> <template #prepend>
<el-select v-model="info.chooseTranStampOption" placeholder="Select" class="w-24"> <el-select v-model="info.chooseTranStampOption" placeholder="Select" class="trantimestamp-select">
<el-option <el-option
v-for="item in info.tranOptions" v-for="item in info.tranOptions"
:key="item.value" :key="item.value"
@@ -130,13 +120,18 @@ const copyRes = async () => {
</div> </div>
</div> </div>
<div> <!-- desc -->
<div class="text-xl mt-5 mb-5">时间戳</div> <ToolDetail title="描述">
<el-text>Unix 时间戳是从1970年1月1日UTC/GMT的午夜开始所经过的秒数不考虑闰秒</el-text> <el-text>
</div> 时间戳是从1970年1月1日UTC/GMT的午夜开始所经过的秒数不考虑闰秒用于表示一个时间点然而这种格式对于人类阅读并不友好因此需要转换成可读的日期和时间格式这个工具能够将时间戳快速转换为人类可读的日期时间格式同时也支持反向转换即将日期时间转换为时间戳<br>
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.trantimestamp-select{
@apply w-24
}
</style> </style>
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { copy } from '@/utils/string' import { copy } from '@/utils/string'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
@@ -44,10 +45,10 @@ const copyRes = async (resStr: string) => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div> <div class="p-4 rounded-2xl bg-white">
<div> <div>
<el-input v-model="info.pwNum" placeholder="范围1~100" class="" max="100" type="number"> <el-input v-model="info.pwNum" placeholder="范围1~100" class="" max="100" type="number">
<template #prepend>生成数量:</template> <template #prepend>生成数量:</template>
@@ -62,6 +63,14 @@ const copyRes = async (resStr: string) => {
<el-input type="textarea" :autosize="info.autosize" v-model="info.resStr"></el-input> <el-input type="textarea" :autosize="info.autosize" v-model="info.resStr"></el-input>
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线UUID批量生成
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -1,14 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { Codemirror } from "vue-codemirror" import { Codemirror } from "vue-codemirror"
import { lineNumbers } from '@codemirror/view'; // import { lineNumbers } from '@codemirror/view';
import { copy } from '@/utils/string' import { copy } from '@/utils/string'
const info = reactive({ const info = reactive({
title: "Unicode转中文", title: "Unicode转中文",
extensions: [lineNumbers()], // extensions: [lineNumbers()],
content: '', content: '',
tranRes: '', tranRes: '',
}) })
@@ -50,10 +51,10 @@ const copyRes = async () => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div> <div class="p-4 rounded-2xl bg-white">
<div> <div>
<codemirror <codemirror
v-model="info.content" v-model="info.content"
@@ -62,7 +63,6 @@ const copyRes = async () => {
:autofocus="true" :autofocus="true"
:indent-with-tab="true" :indent-with-tab="true"
:tabSize="2" :tabSize="2"
:extensions="info.extensions"
/> />
</div> </div>
@@ -76,6 +76,14 @@ const copyRes = async () => {
<el-input type="textarea" :rows="8" v-model="info.tranRes"></el-input> <el-input type="textarea" :rows="8" v-model="info.tranRes"></el-input>
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
Unicode是计算机科学领域里的一项业界标准有时候我们需要对一段文本或者一段内容进行重新排版编译的时候就需要将获取的值进行转码
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive, onMounted } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { useRoute } from "vue-router"
import Length from './Length.vue' import Length from './Length.vue'
import Area from './Area.vue' import Area from './Area.vue'
import Heat from './Heat.vue' import Heat from './Heat.vue'
@@ -14,16 +16,49 @@ const info = reactive({
activeName: 'lengthTab', activeName: 'lengthTab',
}) })
const route = useRoute()
const handleClick = () => { const handleClick = () => {
} }
onMounted(() => {
if (route.query.active) {
switch (route.query.active) {
case 'area':
info.activeName = 'areaTab'
break;
case 'weight':
info.activeName = 'weightTab'
break;
case 'time':
info.activeName = 'timeTab'
break;
case 'temperature':
info.activeName = 'temperatureTab'
break;
case 'pressure':
info.activeName = 'pressureTab'
break;
case 'heat':
info.activeName = 'heatTab'
break;
case 'power':
info.activeName = 'powerTab'
break
default:
info.activeName = 'lengthTab'
break;
}
}
})
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div>
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div> <div>
<div> <div class="p-4 rounded-2xl bg-white">
<el-tabs v-model="info.activeName" class="demo-tabs" @tab-click="handleClick"> <el-tabs v-model="info.activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane label="长度" name="lengthTab"> <el-tab-pane label="长度" name="lengthTab">
<Length/> <Length/>
@@ -56,6 +91,14 @@ const handleClick = () => {
</el-tabs> </el-tabs>
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线单位换算长度单位换算面积单位换算时间单位换算温度单位换算压力单位换算热量单位换算功率单位换算
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { copy } from '@/utils/string' import { copy } from '@/utils/string'
const info = reactive({ const info = reactive({
@@ -35,15 +36,15 @@ const copyRes = async () => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div> <div class="p-4 rounded-2xl bg-white">
<div> <div>
<el-input type="textarea" :rows="8" v-model="info.content"></el-input> <el-input type="textarea" :rows="8" v-model="info.content"></el-input>
</div> </div>
<div class="mt-4"> <div class="mt-4 flex flex-wrap gap-2 button-container">
<el-button type="primary" @click="toEncode">UrlEncode编码</el-button> <el-button type="primary" @click="toEncode">UrlEncode编码</el-button>
<el-button type="primary" @click="toDecode">UrlDecode解码</el-button> <el-button type="primary" @click="toDecode">UrlDecode解码</el-button>
<el-button type="primary" @click="copyRes">复制结果</el-button> <el-button type="primary" @click="copyRes">复制结果</el-button>
@@ -54,8 +55,20 @@ const copyRes = async () => {
<el-input type="textarea" :rows="8" v-model="info.tranRes"></el-input> <el-input type="textarea" :rows="8" v-model="info.tranRes"></el-input>
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线url编码在线url解码工具
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.button-container .el-button {
margin-right: 12px;
margin-left: 0px;
}
</style> </style>
+104
View File
@@ -0,0 +1,104 @@
<script setup lang="ts">
import { ref, reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
// import { copy } from '@/utils/string'
import { Jh_getTimeStamp } from '@/utils/time'
import { isUrl } from '@/utils/url'
import { autoDown, getFileExtension } from '@/utils/file'
import { useToolsStore } from '@/store/modules/tools'
import { ElMessage } from 'element-plus'
const info = reactive({
title: "网站favicon获取",
})
//store
const toolsStore = useToolsStore()
const searchUrl = ref()
const getWebInfo = async () => {
try {
if (isUrl(searchUrl.value)) {
//有效的域名
if (!searchUrl.value.includes('http://') && !searchUrl.value.includes('https://')) {
searchUrl.value = 'http://' + searchUrl.value;
}
const urlObj = new URL(searchUrl.value)
// webInfo.link = urlObj.hostname
searchUrl.value = urlObj.hostname
await toolsStore.getWebInfo({
link: searchUrl.value
})
} else {
//无效的域名
ElMessage({
message: "无效的域名或地址",
type: "error",
duration: 1500
})
}
} catch (error) {
console.error('Error fetching site info:', error);
}
};
const down = () => {
autoDown(toolsStore.webInfo.logo, Jh_getTimeStamp() + '.' + getFileExtension(toolsStore.webInfo.logo))
}
//copy
// const copyRes = async (resStr: string) => {
// copy(resStr)
// }
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div>
<el-input
v-model="searchUrl"
style="max-width: 600px"
placeholder="请输入域名或地址"
class="input-with-select"
size="large"
>
<template #append>
<el-button type="primary" @click="getWebInfo">获取</el-button>
</template>
</el-input>
</div>
<div>
<ul>
<li class="mt-3">
<el-text><b>域名: </b></el-text>
<el-link :href="'https://' + searchUrl" type="primary" target="_blank">{{ searchUrl }}</el-link>
</li>
<li class="mt-3 flex">
<el-text><b>logo: </b></el-text>
<div v-if="toolsStore.webInfo.logo" class="ml-2 flex items-center">
<el-image :src="toolsStore.webInfo.logo" class="h-12"></el-image>
<el-button class="ml-3" link type="primary" @click="down">下载</el-button>
</div>
</li>
<li class="mt-3 flex">
<el-text><b>标题: </b>{{ toolsStore.webInfo.title }}</el-text>
</li>
<li class="mt-3 flex">
<el-text><b>关键词: </b> {{ toolsStore.webInfo.keywords }}</el-text>
</li>
<li class="mt-3 flex">
<el-text><b>描述: </b> {{ toolsStore.webInfo.desc }}</el-text>
</li>
</ul>
</div>
</div>
</div>
</template>
<style scoped>
</style>
+307
View File
@@ -0,0 +1,307 @@
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div class="tool-content">
<div class="input-section">
<el-form label-position="top">
<el-form-item label="转盘选项(每行一个)">
<el-input
v-model="optionsText"
type="textarea"
:rows="6"
placeholder="请输入转盘选项,每行一个"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="updateWheel">更新转盘</el-button>
<el-button @click="clearOptions">清空</el-button>
</el-form-item>
</el-form>
</div>
<div class="wheel-section">
<div class="wheel-wrapper">
<div
ref="wheel"
class="wheel"
:style="wheelStyle"
>
<div
v-for="(option, index) in options"
:key="index"
class="wheel-sector"
:style="getSectorStyle(index)"
>
<div class="sector-text">{{ option }}</div>
</div>
<div class="wheel-center"></div>
</div>
<div class="wheel-pointer"></div>
</div>
<div class="wheel-controls">
<el-button
type="primary"
:disabled="isSpinning || options.length < 2"
@click="spinWheel"
>
{{ isSpinning ? '旋转中...' : '开始旋转' }}
</el-button>
<div v-if="result" class="result">
结果: {{ result }}
</div>
</div>
</div>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线转盘工具自定义选项后旋转转盘随机选择结果支持多个选项和动画效果可用于随机选择抽奖等场景
</el-text>
</ToolDetail>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
const title = "转盘工具"
const optionsText = ref('');
const options = ref<string[]>([]);
const isSpinning = ref(false);
const result = ref<string>('');
// const wheel = ref<HTMLElement | null>(null);
const rotation = ref(0);
// 颜色列表
const colors = [
'#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57',
'#ff9ff3', '#54a0ff', '#5f27cd', '#00d2d3', '#ff9f43'
];
// 解析选项
const parseOptions = () => {
const newOptions = optionsText.value
.split('\n')
.map(option => option.trim())
.filter(option => option !== '');
options.value = newOptions;
};
// 更新转盘
const updateWheel = () => {
parseOptions();
result.value = '';
};
// 清空选项
const clearOptions = () => {
optionsText.value = '';
options.value = [];
result.value = '';
};
// 转盘样式
const wheelStyle = computed(() => {
return {
transform: `rotate(${rotation.value}deg)`
};
});
// 获取扇形样式
const getSectorStyle = (index: number) => {
const count = options.value.length;
if (count === 0) return {};
const angle = 360 / count;
const startAngle = index * angle;
const endAngle = (index + 1) * angle;
// 计算扇形路径
const radius = 150;
const centerX = radius;
const centerY = radius;
const startX = centerX + radius * Math.cos((startAngle - 90) * Math.PI / 180);
const startY = centerY + radius * Math.sin((startAngle - 90) * Math.PI / 180);
const endX = centerX + radius * Math.cos((endAngle - 90) * Math.PI / 180);
const endY = centerY + radius * Math.sin((endAngle - 90) * Math.PI / 180);
// const largeArcFlag = endAngle - startAngle <= 180 ? '0' : '1';
// const path = `M ${centerX} ${centerY} L ${startX} ${startY} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${endX} ${endY} Z`;
return {
backgroundColor: colors[index % colors.length],
clipPath: `polygon(50% 50%, ${startX}px ${startY}px, ${endX}px ${endY}px)`
};
};
// 旋转转盘
const spinWheel = () => {
if (isSpinning.value || options.value.length < 2) {
return;
}
isSpinning.value = true;
result.value = '';
// 随机旋转角度(3-5圈)
const randomRotation = 360 * (3 + Math.random() * 2);
const finalRotation = rotation.value + randomRotation;
// 计算最终指向的选项
const anglePerOption = 360 / options.value.length;
const normalizedRotation = finalRotation % 360;
const selectedIndex = Math.floor((360 - normalizedRotation) / anglePerOption) % options.value.length;
// 执行旋转动画
const duration = 3000 + Math.random() * 2000; // 3-5秒
const startTime = performance.now();
const animate = (currentTime: number) => {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// 使用缓动函数
const easeOut = 1 - Math.pow(1 - progress, 3);
rotation.value = rotation.value + randomRotation * easeOut;
if (progress < 1) {
requestAnimationFrame(animate);
} else {
// 旋转结束
isSpinning.value = false;
result.value = options.value[selectedIndex];
}
};
requestAnimationFrame(animate);
};
// 初始示例
optionsText.value = '选项1\n选项2\n选项3\n选项4\n选项5';
parseOptions();
</script>
<style scoped>
.tool-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.input-section {
width: 100%;
}
.wheel-section {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.wheel-wrapper {
position: relative;
width: 300px;
height: 300px;
margin-bottom: 20px;
}
.wheel {
width: 100%;
height: 100%;
position: relative;
border-radius: 50%;
overflow: hidden;
transition: transform 0.3s ease;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
}
.wheel-sector {
position: absolute;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 14px;
text-align: center;
padding: 20px;
box-sizing: border-box;
}
.sector-text {
max-width: 80px;
word-break: break-word;
}
.wheel-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 60px;
height: 60px;
background-color: white;
border-radius: 50%;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
z-index: 10;
}
.wheel-pointer {
position: absolute;
top: -10px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 15px solid transparent;
border-right: 15px solid transparent;
border-bottom: 25px solid #ff6b6b;
z-index: 20;
}
.wheel-controls {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
}
.result {
font-size: 18px;
font-weight: bold;
color: #333;
margin-top: 10px;
padding: 10px 20px;
background-color: #f5f7fa;
border-radius: 4px;
}
@media (min-width: 768px) {
.tool-content {
flex-direction: row;
}
.input-section {
width: 40%;
}
.wheel-section {
width: 60%;
}
}
</style>
@@ -0,0 +1,282 @@
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div class="tool-content">
<div class="input-section">
<el-form label-position="top">
<el-form-item label="输入文本">
<el-input
v-model="inputText"
type="textarea"
:rows="6"
placeholder="请输入要生成词云的文本"
/>
</el-form-item>
<el-form-item label="词云设置">
<div class="settings-grid">
<el-form-item label="形状" class="setting-item">
<el-select v-model="shape" size="small">
<el-option label="圆形" value="circle" />
<el-option label="矩形" value="rect" />
<el-option label="三角形" value="triangle" />
<el-option label="心形" value="heart" />
</el-select>
</el-form-item>
<el-form-item label="颜色方案" class="setting-item">
<el-select v-model="colorScheme" size="small">
<el-option label="默认" value="default" />
<el-option label="暖色" value="warm" />
<el-option label="冷色" value="cool" />
<el-option label="多彩" value="multi" />
</el-select>
</el-form-item>
<el-form-item label="字体大小范围" class="setting-item">
<div class="size-range">
<el-input-number v-model="minFontSize" :min="10" :max="50" size="small" />
<span class="range-separator">-</span>
<el-input-number v-model="maxFontSize" :min="20" :max="100" size="small" />
</div>
</el-form-item>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="generateWordCloud">生成词云</el-button>
<el-button @click="downloadWordCloud">下载图片</el-button>
<el-button @click="clearText">清空</el-button>
</el-form-item>
</el-form>
</div>
<div class="result-section">
<div ref="chartContainer" class="chart-container"></div>
</div>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线词云生成工具根据输入文本生成词云图支持自定义词云形状颜色方案和字体大小范围可用于文本分析和可视化
</el-text>
</ToolDetail>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue';
import * as echarts from 'echarts';
import 'echarts-wordcloud';
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
const title = "词云生成"
const inputText = ref('');
const shape = ref('circle');
const colorScheme = ref('default');
const minFontSize = ref(12);
const maxFontSize = ref(60);
const chartContainer = ref<HTMLElement | null>(null);
let chart: echarts.ECharts | null = null;
// 颜色方案
const colorSchemes = {
default: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'],
warm: ['#ff7f50', '#87ceeb', '#da70d6', '#32cd32', '#6495ed', '#ff69b4', '#ba55d3', '#cd5c5c', '#ffa500'],
cool: ['#00bfff', '#9370db', '#3cb371', '#ff6347', '#4682b4', '#ff69b4', '#32cd32', '#ff4500', '#9400d3'],
multi: ['#ff0000', '#ff7f00', '#ffff00', '#00ff00', '#00ffff', '#0000ff', '#8b00ff', '#ff00ff', '#1e90ff']
};
// 词频统计函数
const getWordFrequency = (text: string) => {
// 简单的词频统计,实际应用中可能需要更复杂的分词
const words = text
.toLowerCase()
.replace(/[.,?!;:()\[\]{}]/g, ' ')
.split(/\s+/)
.filter(word => word.length > 1);
const frequency: Record<string, number> = {};
words.forEach(word => {
frequency[word] = (frequency[word] || 0) + 1;
});
return Object.entries(frequency)
.map(([name, value]) => ({ name, value }))
.sort((a, b) => b.value - a.value)
.slice(0, 100); // 只取前100个高频词
};
// 生成词云
const generateWordCloud = () => {
if (!inputText.value.trim()) {
return;
}
const words = getWordFrequency(inputText.value);
if (words.length === 0) {
return;
}
nextTick(() => {
if (chartContainer.value) {
if (chart) {
chart.dispose();
}
chart = echarts.init(chartContainer.value);
const option = {
tooltip: {},
series: [
{
type: 'wordCloud',
shape: shape.value,
left: 'center',
top: 'center',
width: '80%',
height: '80%',
right: null,
bottom: null,
sizeRange: [minFontSize.value, maxFontSize.value],
rotationRange: [-45, 45],
rotationStep: 45,
gridSize: 8,
drawOutOfBound: false,
textStyle: {
fontFamily: 'sans-serif',
fontWeight: 'bold',
color: function () {
return colorSchemes[colorScheme.value as keyof typeof colorSchemes][Math.floor(Math.random() * colorSchemes[colorScheme.value as keyof typeof colorSchemes].length)];
}
},
emphasis: {
focus: 'self',
textStyle: {
shadowBlur: 10,
shadowColor: '#333'
}
},
data: words
}
]
};
chart.setOption(option);
}
});
};
// 清空文本
const clearText = () => {
inputText.value = '';
if (chart) {
chart.clear();
}
};
// 下载词云图片
const downloadWordCloud = () => {
if (!chart) {
return;
}
try {
// 获取图片数据 URL
const dataURL = chart.getDataURL({
type: 'png',
pixelRatio: 2, // 提高图片质量
backgroundColor: '#fff'
});
// 创建下载链接
const link = document.createElement('a');
link.download = `wordcloud-${Date.now()}.png`;
link.href = dataURL;
link.click();
} catch (error) {
console.error('下载词云图片失败:', error);
}
};
// 响应式调整
const handleResize = () => {
if (chart) {
chart.resize();
}
};
onMounted(() => {
window.addEventListener('resize', handleResize);
// 示例文本
inputText.value = '词云 生成 工具 在线 文本 分析 可视化 数据 图表 形状 颜色 字体 大小 频率 统计 单词 词组 自定义 圆形 矩形 三角形 心形 暖色 冷色 多彩';
generateWordCloud();
});
</script>
<style scoped>
.tool-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.input-section {
width: 100%;
}
.settings-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 10px;
}
.setting-item {
margin-bottom: 0;
}
.size-range {
display: flex;
align-items: center;
gap: 10px;
}
.range-separator {
margin: 0 5px;
}
.result-section {
width: 100%;
height: 500px;
margin-top: 20px;
}
.chart-container {
width: 100%;
height: 100%;
border: 1px solid #e4e7ed;
border-radius: 4px;
}
@media (min-width: 768px) {
.tool-content {
flex-direction: row;
}
.input-section {
width: 40%;
}
.result-section {
width: 60%;
}
}
</style>
@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from 'vue' import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue' import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
const info = reactive({ const info = reactive({
title: "在线字数统计", title: "在线字数统计",
content: '', content: '',
@@ -35,10 +35,10 @@ const changeContent = () => {
</script> </script>
<template> <template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-10"> <div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader> <DetailHeader :title="info.title"></DetailHeader>
<div> <div class="p-4 rounded-2xl bg-white">
<div> <div>
<el-input type="textarea" :rows="16" v-model="info.content" @input="changeContent"></el-input> <el-input type="textarea" :rows="16" v-model="info.content" @input="changeContent"></el-input>
</div> </div>
@@ -52,6 +52,14 @@ const changeContent = () => {
<div class="ml-3"><el-text>空白字符{{ info.emptyNum }}</el-text></div> <div class="ml-3"><el-text>空白字符{{ info.emptyNum }}</el-text></div>
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线统计字数汉字标点数字字母字符
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -0,0 +1,142 @@
<script setup lang="ts">
import { ref } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { ElMessage } from 'element-plus'
import { copy } from '@/utils/string'
const title = "词频统计"
// 状态管理
const inputText = ref('')
const minWordLength = ref(2)
const frequencyResult = ref([] as { word: string; count: number }[])
// 统计词频
const calculateFrequency = () => {
if (!inputText.value) {
ElMessage.warning('请输入要统计词频的文本')
return
}
try {
const frequency = countWordFrequency(inputText.value, minWordLength.value)
frequencyResult.value = frequency
ElMessage.success('统计完成')
} catch (error) {
ElMessage.error('统计失败:' + error)
}
}
// 计算词频
const countWordFrequency = (text: string, minLength: number) => {
// 分词
const words = tokenize(text)
// 过滤长度
const filteredWords = words.filter(word => word.length >= minLength)
// 统计频率
const frequencyMap = new Map<string, number>()
filteredWords.forEach(word => {
frequencyMap.set(word, (frequencyMap.get(word) || 0) + 1)
})
// 转换为数组并排序
const frequencyArray = Array.from(frequencyMap.entries())
.map(([word, count]) => ({ word, count }))
.sort((a, b) => b.count - a.count)
return frequencyArray
}
// 文本分词
const tokenize = (text: string): string[] => {
// 简单的分词实现,去除标点符号,转为小写
return text
.toLowerCase()
.replace(/[.,?!;:"'()\[\]{}]/g, ' ')
.split(/\s+/)
.filter(word => word.length > 0)
}
// 复制结果
const copyResult = () => {
if (frequencyResult.value.length === 0) {
ElMessage.warning('没有可复制的内容')
return
}
const resultText = frequencyResult.value
.map(item => `${item.word}: ${item.count}`)
.join('\n')
copy(resultText)
ElMessage.success('复制成功')
}
// 清空输入
const clearInput = () => {
inputText.value = ''
frequencyResult.value = []
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<!-- 输入部分 -->
<div class="mb-4">
<el-input
v-model="inputText"
type="textarea"
:rows="6"
placeholder="请输入要统计词频的文本"
class="mb-2"
/>
<div class="flex items-center gap-2 mb-2">
<el-text>最小单词长度</el-text>
<el-input-number v-model="minWordLength" :min="1" :max="10" size="small" />
</div>
</div>
<!-- 操作按钮 -->
<div class="mb-4">
<el-button type="primary" @click="calculateFrequency">统计词频</el-button>
<el-button @click="clearInput">清空</el-button>
</div>
<!-- 结果部分 -->
<div v-if="frequencyResult.length > 0" class="mb-4">
<el-card shadow="never">
<template #header>
<div class="flex justify-between items-center">
<div class="font-bold">统计结果</div>
<el-button size="small" @click="copyResult">复制结果</el-button>
</div>
</template>
<el-table :data="frequencyResult" style="width: 100%">
<el-table-column prop="word" label="单词" width="200" />
<el-table-column prop="count" label="出现次数" />
</el-table>
</el-card>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线词频统计工具用于统计文本中单词出现的频率可用于文本分析关键词提取等场景支持设置最小单词长度
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,187 @@
<script setup lang="ts">
import { ref } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
import { ElMessage } from 'element-plus'
import { copy } from '@/utils/string'
const title = "XML格式化"
// 状态管理
const inputText = ref('')
const outputText = ref('')
const indentSize = ref(4)
// 格式化XML
const formatXml = () => {
if (!inputText.value) {
ElMessage.warning('请输入要格式化的XML文本')
return
}
try {
// 使用DOMParser解析XML
const parser = new DOMParser()
const xmlDoc = parser.parseFromString(inputText.value, 'text/xml')
// 检查解析是否成功
const errorNode = xmlDoc.querySelector('parsererror')
if (errorNode) {
throw new Error('XML解析错误: ' + errorNode.textContent)
}
// 格式化XML
const formatted = formatXmlNode(xmlDoc.documentElement, 0)
outputText.value = formatted
ElMessage.success('格式化成功')
} catch (error) {
outputText.value = '格式化失败'
ElMessage.error('格式化失败:' + error)
}
}
// 递归格式化XML节点
const formatXmlNode = (node: Node, indent: number): string => {
const indentStr = ' '.repeat(indent * indentSize.value)
let result = ''
if (node.nodeType === Node.ELEMENT_NODE) {
result += `${indentStr}<${node.nodeName}`
// 处理属性
if ((node as Element).attributes && (node as Element).attributes.length > 0) {
for (let i = 0; i < (node as Element).attributes.length; i++) {
const attr = (node as Element).attributes[i]
result += ` ${attr.name}="${attr.value}"`
}
}
if (node.childNodes.length === 0) {
// 空标签
result += '/>\n'
} else {
result += '>\n'
// 处理子节点
for (let i = 0; i < node.childNodes.length; i++) {
const childNode = node.childNodes[i]
if (childNode.nodeType === Node.TEXT_NODE && childNode.textContent?.trim() === '') {
// 跳过空白文本节点
continue
}
result += formatXmlNode(childNode, indent + 1)
}
result += `${indentStr}</${node.nodeName}>\n`
}
} else if (node.nodeType === Node.TEXT_NODE) {
const text = node.textContent?.trim()
if (text) {
result += `${indentStr}${text}\n`
}
} else if (node.nodeType === Node.CDATA_SECTION_NODE) {
result += `${indentStr}<![CDATA[${node.textContent}]]>\n`
} else if (node.nodeType === Node.COMMENT_NODE) {
result += `${indentStr}<!--${node.textContent}-->\n`
}
return result
}
// 压缩XML
const minifyXml = () => {
if (!inputText.value) {
ElMessage.warning('请输入要压缩的XML文本')
return
}
try {
// 压缩XML(去除空格和换行)
const minified = inputText.value
.replace(/\s+/g, ' ') // 替换多个空白字符为单个空格
.replace(/\s*<\s*/g, '<') // 去除标签周围的空格
.replace(/\s*>\s*/g, '>') // 去除标签周围的空格
.trim()
outputText.value = minified
ElMessage.success('压缩成功')
} catch (error) {
outputText.value = '压缩失败'
ElMessage.error('压缩失败:' + error)
}
}
// 复制结果
const copyResult = () => {
if (!outputText.value || outputText.value === '格式化失败' || outputText.value === '压缩失败') {
ElMessage.warning('没有可复制的内容')
return
}
copy(outputText.value)
}
// 清空输入
const clearInput = () => {
inputText.value = ''
outputText.value = ''
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<!-- 输入部分 -->
<div class="mb-4">
<el-input
v-model="inputText"
type="textarea"
:rows="6"
placeholder="请输入要格式化的XML文本"
class="mb-2"
/>
<div class="flex items-center gap-2 mb-2">
<el-text>缩进大小</el-text>
<el-input-number v-model="indentSize" :min="1" :max="8" size="small" />
</div>
</div>
<!-- 操作按钮 -->
<div class="mb-4">
<el-button type="primary" @click="formatXml">格式化</el-button>
<el-button type="success" @click="minifyXml">压缩</el-button>
<el-button @click="clearInput">清空</el-button>
</div>
<!-- 结果部分 -->
<div>
<el-input
v-model="outputText"
type="textarea"
:rows="6"
placeholder="格式化结果"
readonly
>
<template #append>
<el-button @click="copyResult">复制</el-button>
</template>
</el-input>
</div>
</div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线XML格式化工具用于美化和压缩XML代码支持自定义缩进大小使XML代码更易于阅读和维护
</el-text>
</ToolDetail>
</div>
</template>
<style scoped>
</style>
+759
View File
@@ -0,0 +1,759 @@
import type { ToolsReqData } from '@/components/Tools/tools.type.ts'
//获取tools分类与对应的工具
export function getToolsCate() {
return [
{
id: 2,
title: '开发运维',
icon: '',
list: [
{
id: 1,
title: '随机密码生成',
logo: '/images/logo/keywords.png',
desc: '密码生成器、随机字符串生成,批量生成',
url: '/randompassword/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'URL编码/解码',
logo: '/images/logo/url.png',
desc: 'URL在线编码解码工具(UrlEncode编码 和 UrlDecode解码)',
url: '/urlencode/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'UUID生成器',
logo: '/images/logo/uuid.png',
desc: '批量生成UUID',
url: '/uuid/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: '时间戳转换',
logo: '/images/logo/Time.png',
desc: '在线时间戳转换工具以及获取当前时间戳',
url: '/timetran/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'MD5在线加密',
logo: '/images/logo/md5.png',
desc: 'MD5在线加密,长度包含32位、16位',
url: '/md5/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'Json在线转换',
logo: '/images/logo/json.png',
desc: '提供实时编辑和预览JSON 数据,语法高亮、校验、格式化、转义,去转义、压缩等功能,可以提高阅读修改的效率和准确性',
url: '/json/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: '正则测试工具',
logo: '/images/logo/reg.png',
desc: '正则表达式测试工具, 常用正则表达式',
url: '/reg/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'Unicode转中文',
logo: '/images/logo/union.png',
desc: 'Unicode和中文的相互转换',
url: '/unicode/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'HTTP状态码',
logo: '/images/logo/http_code.png',
desc: 'http状态对应的名称和含义解释',
url: '/httpstatuscode/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'JWT解析',
logo: '/images/logo/jwt_parse.png',
desc: '解析和解码JSON Web Tokenjwt',
url: '/jwt/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'html实体转义',
logo: '/images/logo/HtmlEntity.png',
desc: 'html实体转义,实体转义成html',
url: '/htmlentity/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'js代码格式化/压缩',
logo: '/images/logo/JSForamt.png',
desc: 'JS格式化/压缩工具,提供在线JS格式化、JS压缩、JS混淆、JS解密',
url: '/jsforamt/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'Html代码格式化',
logo: '/images/logo/HtmlFormat.png',
desc: '提供在线html、xml格式化',
url: '/htmlformat/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'Css代码格式化/压缩',
logo: '/images/logo/CssFormat.png',
desc: 'css格式化/压缩工具,提供在线css格式化、css压缩',
url: '/cssformat/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'Base64加解密',
logo: '/images/logo/Base64.svg',
desc: '在线Base64加解密工具,支持文本的Base64编码和解码',
url: '/base64/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: '进制转换计算器',
logo: '/images/logo/scaletran.png',
desc: '在线进制转换计算器,支持二进制、八进制、十进制、十六进制等多种进制之间的相互转换',
url: '/baseconverter/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: '数据存储单位换算',
logo: '/images/logo/storageconverter.svg',
desc: '在线数据存储单位换算工具,支持字节、KB、MB、GB、TB、PB、EB等存储单位之间的相互转换',
url: '/storageconverter/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'Hash计算器',
logo: '/images/logo/hashcalculator.svg',
desc: '在线Hash计算器,支持MD5、SHA-1、SHA-256、SHA-384、SHA-512等多种哈希算法',
url: '/hashcalculator/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'XML格式化',
logo: '/images/logo/xmlformat.svg',
desc: '在线XML格式化工具,用于美化和压缩XML代码,支持自定义缩进大小',
url: '/xmlformat/',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'SQL格式化',
logo: '/images/logo/sqlformat.svg',
desc: '在线SQL格式化工具,用于美化和压缩SQL代码,支持自定义缩进大小',
url: '/sqlformat/',
cateId: 2,
cate: '开发运维'
}
]
},
{
id: 3,
title: '文本处理',
icon: '',
list: [
{
id: 1,
title: '文本对比',
logo: '/images/logo/diff.png',
desc: '文本差异比对支持中文、英文、代码比对',
url: '/diff/',
cateId: 3,
cate: '文本处理'
},
{
id: 1,
title: 'markdown编辑器',
logo: '/images/logo/file-markdown-fill.png',
desc: '在线创建或编辑markdown, 实时预览,导出markdown',
url: '/markdown/',
cateId: 3,
cate: '文本处理'
},
{
id: 1,
title: '字数统计',
logo: '/images/logo/wordCount.png',
desc: '在线统计字符串的字数、段落、标点符号数量',
url: '/wordcount/',
cateId: 3,
cate: '文本处理',
},
{
id: 1,
title: '文本去重',
logo: '/images/logo/textRemoveDuplicate.png',
desc: '可以删除或去除文本或字符串中的重复行',
url: '/textremoveduplicate/',
cateId: 3,
cate: '文本处理',
},
{
id: 1,
title: 'ASCII字形生成器',
logo: '/images/logo/ascii_word_pic.png',
desc: '在线生成字形ASCII画',
url: '/asciiwordpic/',
cateId: 3,
cate: '文本处理',
},
{
id: 1,
title: '在线文本编辑/HTML获取',
logo: '/images/logo/richtextEditor.png',
desc: '在线富文本编辑, html实时预览,在线编辑文本,文本编辑获取html',
url: '/textedit/',
cateId: 3,
cate: '文本处理'
},
{
id: 1,
title: '文本替换',
logo: '/images/logo/textreplace.svg',
desc: '在线文本替换工具,支持普通文本和正则表达式替换,可用于批量修改文本内容',
url: '/textreplace/',
cateId: 3,
cate: '文本处理',
},
{
id: 1,
title: '文本处理工作流',
logo: '/images/logo/textworkflow.svg',
desc: '在线文本处理工作流工具,允许用户定义一系列文本处理步骤并按顺序执行',
url: '/textworkflow/',
cateId: 3,
cate: '文本处理',
},
{
id: 1,
title: '词频统计',
logo: '/images/logo/wordfrequency.svg',
desc: '在线词频统计工具,用于统计文本中单词出现的频率,可用于文本分析、关键词提取等场景',
url: '/wordfrequency/',
cateId: 3,
cate: '文本处理',
},
]
},
{
id: 4,
title: '教育学术',
icon: '',
list: [
{
id: 1,
title: '单位换算',
logo: '/images/logo/unit.png',
desc: '在线重量、长度、面积、时间、角度、速度、温度、压力、热量、功率等换算',
url: '/unit/',
cateId: 4,
cate: '教育学术'
},
{
id: 1,
title: '摩斯电码',
logo: '/images/logo/medium.png',
desc: '支持中文的摩斯电码编码解码',
url: '/morse/',
cateId: 4,
cate: '教育学术'
},
{
id: 1,
title: '常用进制转换',
logo: '/images/logo/scaletran.png',
desc: '在线进制转换工具,可在2到64进制之间相互转换',
url: '/scaletran/',
cateId: 4,
cate: '教育学术',
},
{
id: 1,
title: 'ASCII码表',
logo: '/images/logo/ascii.png',
desc: 'ASCII码表,控制代码、标准ASCII字符和非标准ASCII字符对照表',
url: '/ascii/',
cateId: 4,
cate: '教育学术'
},
{
id: 1,
title: '长度单位转换',
logo: '/images/logo/length.png',
desc: '长度转换工具-支持国际长度单位,中国传统长度单位,英制长度单位',
url: '/unit/?active=length',
cateId: 4,
cate: '教育学术'
},
{
id: 1,
title: '面积单位转换',
logo: '/images/logo/area.png',
desc: '面积转换工具-支持国际面积单位,中国传统面积单位,英制面积单位',
url: '/unit/?active=area',
cateId: 4,
cate: '教育学术'
},
{
id: 1,
title: '重量单位转换',
logo: '/images/logo/weight.png',
desc: '重量转换工具-支持国际重量单位,中国传统重量单位,英制重量单位(常衡制和金衡制)',
url: '/unit/?active=weight',
cateId: 4,
cate: '教育学术'
},
{
id: 1,
title: '时间单位转换',
logo: '/images/logo/time_unit.png',
desc: '时间单位转换工具-支持国际时间单位',
url: '/unit/?active=time',
cateId: 4,
cate: '教育学术'
},
{
id: 1,
title: '温度单位转换',
logo: '/images/logo/temperature.png',
desc: '温度单位转换工具-支持国际温度单位',
url: '/unit/?active=temperature',
cateId: 4,
cate: '教育学术'
},
{
id: 1,
title: '压力单位转换',
logo: '/images/logo/pressure.png',
desc: '压力单位转换工具-Pa/kPa/hPa/MPa/bar/torr/psi等',
url: '/unit/?active=pressure',
cateId: 4,
cate: '教育学术'
},
{
id: 1,
title: '热量单位转换',
logo: '/images/logo/heat.png',
desc: '热量单位转换工具-Wh/mWh/kWh/MWh/J/kJ等',
url: '/unit/?active=heat',
cateId: 4,
cate: '教育学术'
},
{
id: 1,
title: '功率单位转换',
logo: '/images/logo/power.png',
desc: '功率单位转换工具-W/mW/kW/MW/GW等',
url: '/unit/?active=power',
cateId: 4,
cate: '教育学术'
},
]
},
{
id: 5,
title: '图片处理',
icon: '',
list: [
{
id: 1,
title: '二维码生成',
logo: '/images/logo/qrcode.png',
desc: '在线生成带logo、透明、艺术的二维码',
url: '/qrcode/',
cateId: 5,
cate: '图片处理'
},
{
id: 1,
title: '在线图片处理',
logo: '/images/logo/img.png',
desc: '在线图片裁剪,图片标注,图片滤镜,图片画笔、图片旋转、图片文字等操作',
url: '/signimage/',
cateId: 5,
cate: '图片处理',
},
{
id: 1,
title: '文本转图片',
logo: '/images/logo/text_to_img.png',
desc: '把文本转换成图片,生成长图,具有超多个性文字排版',
url: '/texttoimg/',
cateId: 5,
cate: '图片处理'
},
{
id: 1,
title: '图片分割',
logo: '/images/logo/imgCut.png',
desc: '将图片分割成四宫格、九宫格、十六宫格,支持自定义行与列',
url: '/imgcut/',
cateId: 5,
cate: '图片处理',
},
{
id: 1,
title: '传图取色',
logo: '/images/logo/imagecolorpicker.svg',
desc: '上传图片并点击图片获取颜色值',
url: '/imagecolorpicker/',
cateId: 5,
cate: '图片处理',
},
{
id: 1,
title: '图片水印添加',
logo: '/images/logo/imagewatermark.svg',
desc: '上传图片并添加文字水印',
url: '/imagewatermark/',
cateId: 5,
cate: '图片处理',
}
]
},
{
id: 8,
title: '数据图表',
icon: '',
list: [
{
id: 1,
title: '柱状图',
logo: '/images/logo/bar.png',
desc: '在线制作柱状图,像做表格一样制作可视化图表,支持导出静态或动态图表',
url: '/bar/',
cateId: 8,
cate: '数据图表',
},{
id: 1,
title: '折线图',
logo: '/images/logo/line.png',
desc: '在线制作折线图,像做表格一样制作可视化图表,支持导出静态或动态图表',
url: '/line/',
cateId: 8,
cate: '数据图表',
},
{
id: 1,
title: '饼图',
logo: '/images/logo/pie.png',
desc: '在线制作饼图,像做表格一样制作可视化图表,支持导出静态或动态图表',
url: '/pie/',
cateId: 8,
cate: '数据图表',
},
{
id: 1,
title: '散点图',
logo: '/images/logo/scatter.png',
desc: '在线制作散点图,像做表格一样制作可视化图表,支持导出静态或动态图表',
url: '/scatter/',
cateId: 8,
cate: '数据图表',
},
{
id: 1,
title: '词云生成',
logo: '/images/logo/wordcloud.svg',
desc: '根据输入文本生成词云图,支持自定义词云形状和颜色',
url: '/wordcloud/',
cateId: 8,
cate: '数据图表',
},
]
},
{
id: 9,
title: '选择随机',
icon: '',
list: [
{
id: 1,
title: '生成随机数',
logo: '/images/logo/random.png',
desc: '可定制范围内进行随机数字,可用于抽奖、点名等用途',
url: '/random/',
cateId: 9,
cate: '选择随机'
},
{
id: 1,
title: '帮我决定',
logo: '/images/logo/choose.png',
desc: '选择困难,难以决定,今天吃什么,现在做什么,自定义选项都给你安排的明明白白',
url: '/decision/',
cateId: 9,
cate: '选择随机'
},
{
id: 1,
title: '抛硬币',
logo: '/images/logo/coin.png',
desc: '在线抛硬币,选择困难那么交给硬币来帮你选择吧',
url: '/coin/',
cateId: 9,
cate: '选择随机',
},
{
id: 1,
title: '投骰子',
logo: '/images/logo/dice.png',
desc: '在线投骰子,可自定义骰子数量,简单好用的骰子工具',
url: '/dice/',
cateId: 9,
cate: '选择随机',
},
{
id: 1,
title: '抽签工具',
logo: '/images/logo/lottery.svg',
desc: '输入多个选项,随机抽取一个或多个结果',
url: '/lottery/',
cateId: 9,
cate: '选择随机',
},
{
id: 1,
title: '转盘工具',
logo: '/images/logo/wheel.svg',
desc: '自定义选项,旋转转盘随机选择结果',
url: '/wheel/',
cateId: 9,
cate: '选择随机',
},
]
},
// {
// id: 6,
// title: '查询相关',
// icon: '',
// list: [
// {
// id: 1,
// title: 'IP查询',
// logo: '/images/logo/IP.png',
// desc: '在线查询ip地址、ip归属地',
// url: '/ip',
// cateId: 6,
// cate: '查询相关',
// },
// {
// id: 1,
// title: '网站favicon获取',
// logo: '/images/logo/text_to_img.png',
// desc: '获取网站logo、icon、favicon、标题、关键词、描述等信息',
// url: '/webInfo',
// cateId: 6,
// cate: '查询相关',
// }
// ]
// },
{
id: 7,
title: '其他工具',
icon: '',
list: [
{
id: 1,
title: '数字转金额大写',
logo: '/images/logo/numberToChinese.png',
desc: '在线数字一键转换成人民币大写,中文大写转换数字',
url: '/numbertochinese/',
cateId: 7,
cate: '其他工具'
},
{
id: 1,
title: '手持弹幕',
logo: '/images/logo/dm.png',
desc: '手持滚动弹幕',
url: '/barrage/',
cateId: 7,
cate: '其他工具',
},
{
id: 1,
title: '色板',
logo: '/images/logo/palettes.png',
desc: '提供各种颜色的色板,点击颜色可复制颜色值',
url: '/colorpalette/',
cateId: 7,
cate: '其他工具',
},
{
id: 1,
title: 'Color选择器',
logo: '/images/logo/color_picker.png',
desc: '颜色选择器、在各种颜色空间如十六进制、rgb、hsl、css等等之间转换颜色',
url: '/colorpicker/',
cateId: 7,
cate: '其他工具'
},
{
id: 1,
title: '番茄时钟',
logo: '/images/logo/pomodoro.svg',
desc: '专注工作和休息的时间管理工具',
url: '/pomodoro/',
cateId: 7,
cate: '其他工具',
},
{
id: 1,
title: '反应速度测试',
logo: '/images/logo/reactiontest.svg',
desc: '测试你的反应速度,点击变色的方块',
url: '/reactiontest/',
cateId: 7,
cate: '其他工具',
},
{
id: 1,
title: 'm3u8在线播放',
logo: '/images/logo/m3u8player.svg',
desc: '播放m3u8格式的视频流',
url: '/m3u8player/',
cateId: 7,
cate: '其他工具',
},
{
id: 1,
title: '剪刀石头布',
logo: '/images/logo/rockpaperscissors.svg',
desc: '与电脑对战的剪刀石头布游戏',
url: '/rockpaperscissors/',
cateId: 7,
cate: '其他工具',
},
{
id: 1,
title: 'Emoji表情大全',
logo: '/images/logo/emoji.svg',
desc: '在线Emoji表情大全,提供各种分类的Emoji表情,点击即可复制到剪贴板',
url: '/emoji/',
cateId: 7,
cate: '其他工具',
},
{
id: 1,
title: '简易计算器',
logo: '/images/logo/calculator.svg',
desc: '基本的加减乘除计算工具',
url: '/calculator/',
cateId: 7,
cate: '其他工具',
},
]
}
]
}
//工具list
export function toolsList() {
let list = [] as any[]
let toolsCate = getToolsCate()
for (let item in toolsCate) {
for (let _item in toolsCate[item].list) {
list.push(toolsCate[item].list[_item])
}
}
return list
}
/**
* url为键名的工具list map
* @returns
*/
export function urlKeyMap() {
// let toolsMapByUrlKey = new Map()
// let list = toolsList()
// for (let item in list) {
// toolsMapByUrlKey.set(list[item].url, list[item])
// }
// return toolsMapByUrlKey
}
//获取工具
export function getTools(data: ToolsReqData) {
//接收参数
const { cateId, title, route } = data
//获取工具list
let list = toolsList()
//标题筛选
if (title != '') {
list = list.filter(item => {
let tmpValue = item.title.toLowerCase()
let tmpDesc = item.desc.toLowerCase()
// console.log(tmpValue.indexOf(title.toLowerCase()))
return tmpValue.indexOf(title.toLowerCase()) !== -1 || tmpDesc.indexOf(title.toLowerCase()) !== -1;
});
}
//分类筛选
if (cateId > 0) {
list = list.filter(item => {
return item.cateId == cateId;
});
}
//路由筛选
if (route) {
list = list.filter(item => {
// 移除末尾的斜杠后比较
const itemUrl = item.url.replace(/\/$/, '');
const routeUrl = route.replace(/\/$/, '');
return itemUrl === routeUrl;
});
}
return list
}
const ToolsExport = {
getTools,
getToolsCate,
toolsList,
};
export default ToolsExport;
@@ -1,8 +1,7 @@
import { ResponseData } from "../common"
export interface ToolsReqData { export interface ToolsReqData {
cateId: number, cateId: number,
title: string title: string,
route: string,
} }
//tools //tools
@@ -22,22 +21,25 @@ export interface ToolCate {
img: string, img: string,
desc: string, desc: string,
url: string, url: string,
cate: string cate: string,
list: ToolsInfo[]
}
export interface CollectReqData {
toolId: number
} }
//tools list response //tools list response
export interface ToolsResponseData extends ResponseData { export interface ToolsResponseData {
data: ToolsInfo[] data: ToolsInfo[]
} }
//tools cate response //tools cate response
export interface ToolCateResponseData extends ResponseData { export interface ToolCateResponseData{
data: ToolCate[] data: ToolCate[]
} }
//tools recommend response //tools recommend response
export interface ToolRecommendResponseData extends ResponseData { export interface ToolRecommendResponseData{
data: ToolsInfo[] data: ToolsInfo[]
} }
+2
View File
@@ -16,6 +16,8 @@ import 'element-plus/dist/index.css'
import pinia from './store' import pinia from './store'
//v-md-editor //v-md-editor
import { setupMdEditor } from './plugins/v-md-editor' import { setupMdEditor } from './plugins/v-md-editor'
//default-passive-events
import 'default-passive-events'
const app = createApp(App) const app = createApp(App)
+1 -1
View File
@@ -33,7 +33,7 @@ router.afterEach((to) => {
if (detailTitle) { if (detailTitle) {
document.title = detailTitle + '-' + import.meta.env.VITE_APP_TITLE document.title = detailTitle + '-' + import.meta.env.VITE_APP_TITLE
} else { } else {
document.title = import.meta.env.VITE_APP_TITLE document.title = import.meta.env.VITE_APP_TITLE + '-' + import.meta.env.VITE_APP_DESC
} }
//设置meta //设置meta
+447 -51
View File
@@ -1,17 +1,19 @@
//对外暴露配置路由(常量路由) //对外暴露配置路由(常量路由)
export const constantRoute = [ export const constantRoute = [
//首页
{ {
path: '/', path: '/',
component: () => import('@/components/Home/Home.vue'), component: () => import('@/components/Home/Home.vue'),
name: 'home', name: 'home',
meta: { meta: {
keywords: '然部落工具坊,在线工具,开发人员工具,时间戳转换,加密,解密,md5,进制转换,二维码,正则表达式,json格式化,照片处理,字数统计', keywords: 'tools-web,在线工具,开发人员工具,时间戳转换,加密,解密,md5,进制转换,二维码,正则表达式,json格式化,照片处理,字数统计',
description: '然部落工具,在线工具大全,开发人员工具,日常生活工具,办公助手,时间戳转换,加密,解密,md5,进制转换,二维码,正则表达式,json格式化,照片处理,字数统计', description: 'tools-web,在线工具,在线工具大全,开发人员工具,日常生活工具,办公助手,时间戳转换,加密,解密,md5,进制转换,二维码,正则表达式,json格式化,照片处理,字数统计',
} }
}, },
//工具
{ {
path: '/timetran', path: '/timetran',
component: () => import('@/components/TimeTran/TimeTran.vue'), component: () => import('@/components/Tools/TimeTran/TimeTran.vue'),
name: 'timetran', name: 'timetran',
meta: { meta: {
title: "时间戳转换", title: "时间戳转换",
@@ -20,8 +22,8 @@ export const constantRoute = [
} }
}, },
{ {
path: '/MD5', path: '/md5',
component: () => import('@/components/MD5/MD5.vue'), component: () => import('@/components/Tools/MD5/MD5.vue'),
name: 'MD5', name: 'MD5',
meta: { meta: {
title: "MD5在线加密", title: "MD5在线加密",
@@ -31,17 +33,17 @@ export const constantRoute = [
}, },
{ {
path: '/json', path: '/json',
component: () => import('@/components/JsonTran/JsonTran.vue'), component: () => import('@/components/Tools/JsonTran/JsonTran.vue'),
name: 'json', name: 'json',
meta: { meta: {
title: "Json在线转换", title: "Json在线转换",
keywords: 'json编辑器,json格式化,json压缩,json转义,json去转义', keywords: 'json编辑器,json格式化,json压缩,json转义,json去转义',
description: 'json编辑器,json解析,json格式化,json压缩,json转义,json去转义', description: '提供实时编辑和预览JSON 数据,语法高亮、校验、格式化、转义,去转义、压缩等功能,可以提高阅读修改的效率和准确性',
} }
}, },
{ {
path: '/reg', path: '/reg',
component: () => import('@/components/RegTest/RegTest.vue'), component: () => import('@/components/Tools/RegTest/RegTest.vue'),
name: 'reg', name: 'reg',
meta: { meta: {
title: "正则表达式测试", title: "正则表达式测试",
@@ -51,7 +53,7 @@ export const constantRoute = [
}, },
{ {
path: '/unicode', path: '/unicode',
component: () => import('@/components/Unicode/Unicode.vue'), component: () => import('@/components/Tools/Unicode/Unicode.vue'),
name: 'unicode', name: 'unicode',
meta: { meta: {
title: "Unicode转中文", title: "Unicode转中文",
@@ -61,7 +63,7 @@ export const constantRoute = [
}, },
{ {
path: '/wordcount', path: '/wordcount',
component: () => import('@/components/WordCount/WordCount.vue'), component: () => import('@/components/Tools/WordCount/WordCount.vue'),
name: 'wordcount', name: 'wordcount',
meta: { meta: {
title: "在线字数统计", title: "在线字数统计",
@@ -69,19 +71,19 @@ export const constantRoute = [
description: '在线字数计数器,主要是方便计算字数,以控制文字数量的小工具(小说作者、论文、文案文档、文字编辑必备工具)。', description: '在线字数计数器,主要是方便计算字数,以控制文字数量的小工具(小说作者、论文、文案文档、文字编辑必备工具)。',
} }
}, },
{ // {
path: '/ip', // path: '/ip',
component: () => import('@/components/Ip/Ip.vue'), // component: () => import('@/components/Tools/Ip/Ip.vue'),
name: 'ip', // name: 'ip',
meta: { // meta: {
title: "IP查询", // title: "IP查询",
keywords: 'ip,ip查询,ip地址查询,ip138,查ip,我的ip,公网ip,ip归属地查询', // keywords: 'ip,ip查询,ip地址查询,ip138,查ip,我的ip,公网ip,ip归属地查询',
description: '公网ip地址归属地查询', // description: '公网ip地址归属地查询',
} // }
}, // },
{ {
path: '/scaletran', path: '/scaletran',
component: () => import('@/components/ScaleTran/ScaleTran.vue'), component: () => import('@/components/Tools/ScaleTran/ScaleTran.vue'),
name: 'scaletran', name: 'scaletran',
meta: { meta: {
title: "常用进制转换", title: "常用进制转换",
@@ -91,17 +93,17 @@ export const constantRoute = [
}, },
{ {
path: '/signimage', path: '/signimage',
component: () => import('@/components/SignImage/SignImage.vue'), component: () => import('@/components/Tools/SignImage/SignImage.vue'),
name: 'signimage', name: 'signimage',
meta: { meta: {
title: "在线编辑图片", title: "在线编辑图片",
keywords: '图片裁剪,图片滤镜,图片旋转,图片编辑,美化图片', keywords: '在线图片裁剪,图片标注,图片滤镜,图片画笔、图片旋转、图片文字,图片美,图片尺寸调整化',
description: '图片裁剪,图片滤镜,图片旋转图片编辑,美化图片', description: '在线图片裁剪,图片标注,图片滤镜,图片画笔、图片旋转图片文字,图片尺寸调整等操作',
} }
}, },
{ {
path: '/random_password', path: '/randompassword',
component: () => import('@/components/RandomPassword/RandomPassword.vue'), component: () => import('@/components/Tools/RandomPassword/RandomPassword.vue'),
name: 'random_password', name: 'random_password',
meta: { meta: {
title: "随机密码生成", title: "随机密码生成",
@@ -111,7 +113,7 @@ export const constantRoute = [
}, },
{ {
path: '/urlencode', path: '/urlencode',
component: () => import('@/components/UrlEncode/UrlEncode.vue'), component: () => import('@/components/Tools/UrlEncode/UrlEncode.vue'),
name: 'urlencode', name: 'urlencode',
meta: { meta: {
title: "URL编码/解码", title: "URL编码/解码",
@@ -121,7 +123,7 @@ export const constantRoute = [
}, },
{ {
path: '/ascii', path: '/ascii',
component: () => import('@/components/ASCII/ASCII.vue'), component: () => import('@/components/Tools/ASCII/ASCII.vue'),
name: 'ascii', name: 'ascii',
meta: { meta: {
title: "ASCII码表", title: "ASCII码表",
@@ -131,7 +133,7 @@ export const constantRoute = [
}, },
{ {
path: '/uuid', path: '/uuid',
component: () => import('@/components/UUID/UUID.vue'), component: () => import('@/components/Tools/UUID/UUID.vue'),
name: 'uuid', name: 'uuid',
meta: { meta: {
title: "UUID生成器", title: "UUID生成器",
@@ -141,7 +143,7 @@ export const constantRoute = [
}, },
{ {
path: '/barrage', path: '/barrage',
component: () => import('@/components/Barrage/Barrage.vue'), component: () => import('@/components/Tools/Barrage/Barrage.vue'),
name: 'barrage', name: 'barrage',
meta: { meta: {
title: "手持弹幕", title: "手持弹幕",
@@ -149,19 +151,9 @@ export const constantRoute = [
description: '手持弹幕是一种新型的互动沟通工具,可以方便地为各种户外活动、演出嘉年华等活动增加趣味性和互动性。手持弹幕具有轻便、易携带、易操作等优点,可以让每个参与者都变成活动的一部分。同时,手持弹幕还可以通过预先编写的文本、表情等形式,表达参与者的情感和想法,实现沟通互动。在社交媒体时代,手持弹幕的使用也带来了更广泛的社交效应,增加了活动的互动性和传播度。无论是举办方还是参与者,手持弹幕都是一个非常有价值的互动工具。', description: '手持弹幕是一种新型的互动沟通工具,可以方便地为各种户外活动、演出嘉年华等活动增加趣味性和互动性。手持弹幕具有轻便、易携带、易操作等优点,可以让每个参与者都变成活动的一部分。同时,手持弹幕还可以通过预先编写的文本、表情等形式,表达参与者的情感和想法,实现沟通互动。在社交媒体时代,手持弹幕的使用也带来了更广泛的社交效应,增加了活动的互动性和传播度。无论是举办方还是参与者,手持弹幕都是一个非常有价值的互动工具。',
} }
}, },
{
path: '/palettes',
component: () => import('@/components/Palettes/Palettes.vue'),
name: 'palettes',
meta: {
title: "色板",
keywords: '色板,颜色板',
description: '在线复制颜色,好看的颜色组合色板',
}
},
{ {
path: '/unit', path: '/unit',
component: () => import('@/components/Unit/Unit.vue'), component: () => import('@/components/Tools/Unit/Unit.vue'),
name: 'unit', name: 'unit',
meta: { meta: {
title: "单位转换", title: "单位转换",
@@ -171,7 +163,7 @@ export const constantRoute = [
}, },
{ {
path: '/qrcode', path: '/qrcode',
component: () => import('@/components/Qrcode/Qrcode.vue'), component: () => import('@/components/Tools/Qrcode/Qrcode.vue'),
name: 'qrcode', name: 'qrcode',
meta: { meta: {
title: "二维码生成", title: "二维码生成",
@@ -181,7 +173,7 @@ export const constantRoute = [
}, },
{ {
path: '/decision', path: '/decision',
component: () => import('@/components/Decision/Decision.vue'), component: () => import('@/components/Tools/Decision/Decision.vue'),
name: 'decision', name: 'decision',
meta: { meta: {
title: "帮我决定", title: "帮我决定",
@@ -191,7 +183,7 @@ export const constantRoute = [
}, },
{ {
path: '/morse', path: '/morse',
component: () => import('@/components/Morse/Morse.vue'), component: () => import('@/components/Tools/Morse/Morse.vue'),
name: 'morse', name: 'morse',
meta: { meta: {
title: "摩斯电码", title: "摩斯电码",
@@ -201,7 +193,7 @@ export const constantRoute = [
}, },
{ {
path: '/random', path: '/random',
component: () => import('@/components/Random/Random.vue'), component: () => import('@/components/Tools/Random/Random.vue'),
name: 'random', name: 'random',
meta: { meta: {
title: "生成随机数", title: "生成随机数",
@@ -210,8 +202,8 @@ export const constantRoute = [
} }
}, },
{ {
path: '/numberToChinese', path: '/numbertochinese',
component: () => import('@/components/NumberToChinese/NumberToChinese.vue'), component: () => import('@/components/Tools/NumberToChinese/NumberToChinese.vue'),
name: 'numberToChinese', name: 'numberToChinese',
meta: { meta: {
title: "数字转金额大写", title: "数字转金额大写",
@@ -221,7 +213,7 @@ export const constantRoute = [
}, },
{ {
path: '/diff', path: '/diff',
component: () => import('@/components/Diff/Diff.vue'), component: () => import('@/components/Tools/Diff/Diff.vue'),
name: 'diff', name: 'diff',
meta: { meta: {
title: "文本对比", title: "文本对比",
@@ -231,7 +223,7 @@ export const constantRoute = [
}, },
{ {
path: '/markdown', path: '/markdown',
component: () => import('@/components/Markdown/Markdown.vue'), component: () => import('@/components/Tools/Markdown/Markdown.vue'),
name: 'markdown', name: 'markdown',
meta: { meta: {
title: "markdown编辑器", title: "markdown编辑器",
@@ -240,8 +232,8 @@ export const constantRoute = [
} }
}, },
{ {
path: '/textToImg', path: '/texttoimg',
component: () => import('@/components/TextToImg/TextToImg.vue'), component: () => import('@/components/Tools/TextToImg/TextToImg.vue'),
name: 'textToImg', name: 'textToImg',
meta: { meta: {
title: "文本转图片", title: "文本转图片",
@@ -249,6 +241,410 @@ export const constantRoute = [
description: '把文本转换成图片,生成长图,具有超多个性文字排版', description: '把文本转换成图片,生成长图,具有超多个性文字排版',
} }
}, },
{
path: '/webinfo',
component: () => import('@/components/Tools/WebInfo/WebInfo.vue'),
name: 'webInfo',
meta: {
title: "网站favicon获取",
keywords: '获取网站logo,获取网站icon,获取网站favicon,获取网站标题,获取网站关键词,获取网站描述',
description: '获取网站logo、icon、favicon、标题、关键词、描述等信息',
}
},
{
path: '/bar',
component: () => import('@/components/Tools/Chart/Bar/Bar.vue'),
name: 'bar',
meta: {
title: "柱状图",
keywords: '柱状图',
description: '在线制作柱状图,像做表格一样制作可视化图表,支持导出静态或动态图表',
}
},
{
path: '/line',
component: () => import('@/components/Tools/Chart/Line/Line.vue'),
name: 'line',
meta: {
title: "折线图",
keywords: '折线图',
description: '在线制作折线图,像做表格一样制作可视化图表,支持导出静态或动态图表',
}
},
{
path: '/pie',
component: () => import('@/components/Tools/Chart/Pie/Pie.vue'),
name: 'pie',
meta: {
title: "饼图",
keywords: '饼图',
description: '在线制作饼图,像做表格一样制作可视化图表,支持导出静态或动态图表',
}
},
{
path: '/scatter',
component: () => import('@/components/Tools/Chart/Scatter/Scatter.vue'),
name: 'scatter',
meta: {
title: "散点图",
keywords: '散点图',
description: '在线制作散点图,像做表格一样制作可视化图表,支持导出静态或动态图表',
}
},
{
path: '/coin',
component: () => import('@/components/Tools/Coin/Coin.vue'),
name: 'coin',
meta: {
title: "抛硬币",
keywords: '抛硬币,硬币',
description: '在线抛硬币,选择困难那么交给硬币来帮你选择吧',
}
},
{
path: '/dice',
component: () => import('@/components/Tools/Dice/Dice.vue'),
name: 'dice',
meta: {
title: "投骰子",
keywords: '投骰子,骰子,自定义骰子',
description: '在线投骰子,可自定义骰子数量,简单好用的骰子工具',
}
},
{
path: '/textremoveduplicate',
component: () => import('@/components/Tools/TextRemoveDuplicate/TextRemoveDuplicate.vue'),
name: 'textRemoveDuplicate',
meta: {
title: "文本去重",
keywords: '文本去重,文本排重,文本去除重复',
description: '可以删除或去除文本或字符串中的重复行',
}
},
{
path: '/imgcut',
component: () => import('@/components/Tools/ImgCut/ImgCut.vue'),
name: 'imgCut',
meta: {
title: "图片切割",
keywords: '图片分割,图片切割,四宫格,九宫格,十六宫格',
description: '将图片分割成四宫格、九宫格、十六宫格,支持自定义行与列',
}
},
{
path: '/httpstatuscode',
component: () => import('@/components/Tools/HttpStatusCode/HttpStatusCode.vue'),
name: 'HttpStatusCode',
meta: {
title: "HTTP状态码",
keywords: 'http状态码',
description: '所有http状态对应的名称和含义解释',
}
},
{
path: '/jwt',
component: () => import('@/components/Tools/JWT/JWT.vue'),
name: 'jwt',
meta: {
title: "JWT解析",
keywords: 'jwt解析,jwt解码,JSON Web Token解析',
description: '解析和解码JSON Web Tokenjwt',
}
},
{
path: '/htmlentity',
component: () => import('@/components/Tools/HtmlEntity/HtmlEntity.vue'),
name: 'HtmlEntity',
meta: {
title: "html实体转义",
keywords: 'html实体转义',
description: 'html实体转义,实体转义成html',
}
},
{
path: '/colorpicker',
component: () => import('@/components/Tools/ColorPicker/ColorPicker.vue'),
name: 'ColorPicker',
meta: {
title: "Color选择器",
keywords: '颜色选择器,Color选择器',
description: '颜色选择器、在各种颜色空间如十六进制、rgb、hsl、css等等之间转换颜色',
}
},
{
path: '/asciiwordpic',
component: () => import('@/components/Tools/ASCIIWordPic/ASCIIWordPic.vue'),
name: 'asciiWordPic',
meta: {
title: "ASCII字形生成器",
keywords: 'ASCII画,字形画,字形生成器',
description: '在线生成字形ASCII画',
}
},
{
path: '/jsforamt',
component: () => import('@/components/Tools/JSFormat/JSFormat.vue'),
name: 'JSForamt',
meta: {
title: "js代码格式化/压缩",
keywords: 'js代码格式化,js格式化,js压缩,javascript格式化',
description: 'JS格式化/压缩工具,提供在线JS格式化、JS压缩、JS混淆、JS解密',
}
},
{
path: '/htmlformat',
component: () => import('@/components/Tools/HtmlFormat/HtmlFormat.vue'),
name: 'HtmlFormat',
meta: {
title: "html代码格式化",
keywords: 'html格式化,xml格式化',
description: '提供在线html、xml格式化',
}
},
{
path: '/cssformat',
component: () => import('@/components/Tools/CssFormat/CssFormat.vue'),
name: 'CssFormat',
meta: {
title: "css代码格式化/压缩",
keywords: 'css格式化,css压缩',
description: '提供在线css格式化,在线css压缩工具',
}
},
{
path: '/textedit',
component: () => import('@/components/Tools/TextEdit/TextEdit.vue'),
name: 'TextEdit',
meta: {
title: "在线文本编辑/HTML获取",
keywords: '文本编辑,富文本预览,在线编辑文本,文本编辑获取html',
description: '在线富文本编辑, html实时预览,在线编辑文本,文本编辑获取html',
}
},
{
path: '/base64',
component: () => import('@/components/Tools/Base64/Base64.vue'),
name: 'Base64',
meta: {
title: "Base64加解密",
keywords: 'Base64,base64加密,base64解密,base64编码,base64解码',
description: '在线Base64加解密工具,支持文本的Base64编码和解码',
}
},
{
path: '/baseconverter',
component: () => import('@/components/Tools/BaseConverter/BaseConverter.vue'),
name: 'baseconverter',
meta: {
title: "进制转换计算器",
keywords: '进制转换,二进制,八进制,十进制,十六进制,进制计算器',
description: '在线进制转换计算器,支持二进制、八进制、十进制、十六进制等多种进制之间的相互转换',
}
},
{
path: '/storageconverter',
component: () => import('@/components/Tools/StorageConverter/StorageConverter.vue'),
name: 'storageconverter',
meta: {
title: "数据存储单位换算",
keywords: '存储单位换算,字节,KB,MB,GB,TB,PB,EB',
description: '在线数据存储单位换算工具,支持字节、KB、MB、GB、TB、PB、EB等存储单位之间的相互转换',
}
},
{
path: '/hashcalculator',
component: () => import('@/components/Tools/HashCalculator/HashCalculator.vue'),
name: 'hashcalculator',
meta: {
title: "Hash计算器",
keywords: 'Hash计算器,MD5,SHA-1,SHA-256,SHA-384,SHA-512',
description: '在线Hash计算器,支持MD5、SHA-1、SHA-256、SHA-384、SHA-512等多种哈希算法',
}
},
{
path: '/xmlformat',
component: () => import('@/components/Tools/XmlFormat/XmlFormat.vue'),
name: 'xmlformat',
meta: {
title: "XML格式化",
keywords: 'XML格式化,XML压缩,XML美化',
description: '在线XML格式化工具,用于美化和压缩XML代码,支持自定义缩进大小',
}
},
{
path: '/sqlformat',
component: () => import('@/components/Tools/SqlFormat/SqlFormat.vue'),
name: 'sqlformat',
meta: {
title: "SQL格式化",
keywords: 'SQL格式化,SQL压缩,SQL美化',
description: '在线SQL格式化工具,用于美化和压缩SQL代码,支持自定义缩进大小',
}
},
{
path: '/textreplace',
component: () => import('@/components/Tools/TextReplace/TextReplace.vue'),
name: 'textreplace',
meta: {
title: "文本替换",
keywords: '文本替换,正则表达式替换,批量替换',
description: '在线文本替换工具,支持普通文本和正则表达式替换,可用于批量修改文本内容',
}
},
{
path: '/textworkflow',
component: () => import('@/components/Tools/TextWorkflow/TextWorkflow.vue'),
name: 'textworkflow',
meta: {
title: "文本处理工作流",
keywords: '文本处理,工作流,批量处理',
description: '在线文本处理工作流工具,允许用户定义一系列文本处理步骤并按顺序执行',
}
},
{
path: '/emoji',
component: () => import('@/components/Tools/Emoji/Emoji.vue'),
name: 'emoji',
meta: {
title: "Emoji表情大全",
keywords: 'Emoji,表情符号,表情大全',
description: '在线Emoji表情大全,提供各种分类的Emoji表情,点击即可复制到剪贴板',
}
},
{
path: '/wordfrequency',
component: () => import('@/components/Tools/WordFrequency/WordFrequency.vue'),
name: 'wordfrequency',
meta: {
title: "词频统计",
keywords: '词频统计,文本分析,关键词提取',
description: '在线词频统计工具,用于统计文本中单词出现的频率,可用于文本分析、关键词提取等场景',
}
},
{
path: '/wordcloud',
component: () => import('@/components/Tools/WordCloud/WordCloud.vue'),
name: 'wordcloud',
meta: {
title: "词云生成",
keywords: '词云,文本可视化,词频分析',
description: '根据输入文本生成词云图,支持自定义词云形状和颜色',
}
},
{
path: '/lottery',
component: () => import('@/components/Tools/Lottery/Lottery.vue'),
name: 'lottery',
meta: {
title: "抽签工具",
keywords: '抽签,随机选择,抽奖',
description: '输入多个选项,随机抽取一个或多个结果',
}
},
{
path: '/rockpaperscissors',
component: () => import('@/components/Tools/RockPaperScissors/RockPaperScissors.vue'),
name: 'rockpaperscissors',
meta: {
title: "剪刀石头布",
keywords: '剪刀石头布,游戏,对战',
description: '与电脑对战的剪刀石头布游戏',
}
},
{
path: '/wheel',
component: () => import('@/components/Tools/Wheel/Wheel.vue'),
name: 'wheel',
meta: {
title: "转盘工具",
keywords: '转盘,随机选择,抽奖',
description: '自定义选项,旋转转盘随机选择结果',
}
},
{
path: '/imagecolorpicker',
component: () => import('@/components/Tools/ImageColorPicker/ImageColorPicker.vue'),
name: 'imagecolorpicker',
meta: {
title: "传图取色",
keywords: '图片取色,颜色提取,色彩分析',
description: '上传图片并点击图片获取颜色值',
}
},
{
path: '/pomodoro',
component: () => import('@/components/Tools/Pomodoro/Pomodoro.vue'),
name: 'pomodoro',
meta: {
title: "番茄时钟",
keywords: '番茄工作法,时间管理,专注',
description: '专注工作和休息的时间管理工具',
}
},
{
path: '/calculator',
component: () => import('@/components/Tools/Calculator/Calculator.vue'),
name: 'calculator',
meta: {
title: "简易计算器",
keywords: '计算器,加减乘除,数学计算',
description: '基本的加减乘除计算工具',
}
},
{
path: '/reactiontest',
component: () => import('@/components/Tools/ReactionTest/ReactionTest.vue'),
name: 'reactiontest',
meta: {
title: "反应速度测试",
keywords: '反应速度,测试,游戏',
description: '测试你的反应速度,点击变色的方块',
}
},
{
path: '/m3u8player',
component: () => import('@/components/Tools/M3U8Player/M3U8Player.vue'),
name: 'm3u8player',
meta: {
title: "m3u8在线播放",
keywords: 'm3u8,视频播放,流媒体',
description: '播放m3u8格式的视频流',
}
},
{
path: '/imagewatermark',
component: () => import('@/components/Tools/ImageWatermark/ImageWatermark.vue'),
name: 'imagewatermark',
meta: {
title: "图片水印添加",
keywords: '图片水印,水印添加,图片处理',
description: '上传图片并添加文字水印',
}
},
{
path: '/colorpalette',
component: () => import('@/components/Tools/ColorPalette/ColorPalette.vue'),
name: 'colorpalette',
meta: {
title: "色板工具",
keywords: '色板,颜色,渐变色,Material Design',
description: '提供各种颜色的色板,点击颜色可复制颜色值',
}
},
// 关于
{
path: '/about',
component: () => import('@/components/Home/About.vue'),
name: 'about',
meta: {
title: "关于",
keywords: '关于ranblogs,关于工具站',
description: '',
}
},
//其他路由
{ {
path: '/404', path: '/404',
component: () => import('@/components/404/404.vue'), component: () => import('@/components/404/404.vue'),
+22
View File
@@ -0,0 +1,22 @@
import { defineStore } from 'pinia'
export const useComponentStore = defineStore('component', {
//用来存放变量
state: () => ({
leftCom: false,
leftComDrawer: false,
}),
//方法
actions: {
//设置左侧组件状态
setLeftComStatus(status: boolean) {
console.log(1)
this.leftCom = status
},
//设置左侧组件状态(小屏)
setleftComDrawerStatus(status: boolean) {
console.log(2)
this.leftComDrawer = status
}
}
})
+80 -26
View File
@@ -1,17 +1,25 @@
//创建tools相关的小工具 //创建tools相关的小工具
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { getTools, getToolCate, getToolRecommend} from '@/api/tools' import { getTools, getToolsCate } from '@/components/Tools/tools.ts'
import { getIp } from '@/api/ip' import { getIp } from '@/api/ip'
import type { ToolsReqData, ToolsInfo, ToolCate } from '@/api/tools/type' import type { ToolsReqData, ToolsInfo } from '@/components/Tools/tools.type.ts'
import type { IpReqData, IpInfo } from '@/api/ip/type' import type { IpReqData, IpInfo } from '@/api/ip/type'
import type { WebInfo, WebInfoReqData } from '@/api/webinfo/type'
import { fetchWebInfo } from '@/api/webinfo'
// 收藏工具列表存储键名
const COLLECTED_TOOLS_KEY = 'collected_tools';
export const useToolsStore = defineStore('tools', { export const useToolsStore = defineStore('tools', {
//用来存放变量 //用来存放变量
state: () => ({ state: () => ({
list: [] as ToolsInfo[], list: [] as ToolsInfo[],
cates: [] as ToolCate[], currentTool: {} as ToolsInfo,
cates: [] as any[],
recommends: [] as ToolsInfo[], recommends: [] as ToolsInfo[],
ipData: {} as IpInfo, ipData: {} as IpInfo,
webInfo: {} as WebInfo,
collectedTools: [] as ToolsInfo[],
}), }),
//方法 //方法
actions: { actions: {
@@ -19,33 +27,20 @@ export const useToolsStore = defineStore('tools', {
async getTools(data: ToolsReqData) { async getTools(data: ToolsReqData) {
//发送请求 //发送请求
const result: any = await getTools(data) const result: any = await getTools(data)
if (result.code == 200) { this.list = result
this.list = result.data return result
return result.message },
} else { //获取tool info
return Promise.reject(new Error(result.message)) async getToolInfo(data: ToolsReqData) {
} //发送请求
const result: any = await getTools(data)
this.currentTool = result[0] || {}
return result
}, },
//获取tools cate //获取tools cate
async getToolCate() { async getToolCate() {
//发送请求 //发送请求
const result: any = await getToolCate() this.cates = await getToolsCate()
if (result.code == 200) {
this.cates = result.data
return result.message
} else {
return Promise.reject(new Error(result.message))
}
},
//获取推荐
async getToolRecommend() {
const result: any = await getToolRecommend()
if (result.code == 200) {
this.recommends = result.data
return result.message
} else {
return Promise.reject(new Error(result.message))
}
}, },
//获取ip //获取ip
async getIp(data: IpReqData) { async getIp(data: IpReqData) {
@@ -56,6 +51,65 @@ export const useToolsStore = defineStore('tools', {
} else { } else {
return Promise.reject(new Error(result.message)) return Promise.reject(new Error(result.message))
} }
},
//获取网站信息
async getWebInfo(data: WebInfoReqData) {
const result: any = await fetchWebInfo(data)
if (result.code == 200) {
this.webInfo = result.data
return result.message
} else {
return Promise.reject(new Error(result.message))
} }
},
// 加载收藏工具
loadCollectedTools() {
try {
const stored = localStorage.getItem(COLLECTED_TOOLS_KEY);
if (stored) {
this.collectedTools = JSON.parse(stored);
}
} catch (error) {
console.error('加载收藏工具失败:', error);
this.collectedTools = [];
}
},
// 保存收藏工具到本地存储
saveCollectedTools() {
try {
localStorage.setItem(COLLECTED_TOOLS_KEY, JSON.stringify(this.collectedTools));
} catch (error) {
console.error('保存收藏工具失败:', error);
}
},
// 添加收藏
addCollect(tool: ToolsInfo) {
// 检查是否已经收藏
const isCollected = this.collectedTools.some(t => t.url === tool.url);
if (!isCollected) {
this.collectedTools.push(tool);
this.saveCollectedTools();
return true;
}
return false;
},
// 移除收藏
removeCollect(url: string) {
const index = this.collectedTools.findIndex(tool => tool.url === url);
if (index > -1) {
this.collectedTools.splice(index, 1);
this.saveCollectedTools();
return true;
}
return false;
},
// 检查是否已收藏
isCollected(url: string) {
const normalizedUrl = url.replace(/\/$/, '');
return this.collectedTools.some(tool => {
const toolUrl = tool.url.replace(/\/$/, '');
return toolUrl === normalizedUrl;
});
},
} }
}) })
+5
View File
@@ -1,3 +1,8 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
:root{
--base-gray: #f5f5fb;
--base-black: #444;
}
+152
View File
@@ -0,0 +1,152 @@
/**
* 转换成Spreadsheet数据格式
*/
export function toSpreadsheetData(data, type = 'bar') {
let res = {}
let columnLen = data[0].value.length
switch(type) {
case 'bar':
for (let i = 0; i < columnLen; i++) {
res[i] = {
cells: {
0: {text: data[0].value[i] ?? ''},
1: {text: data[1].value[i] ?? ''}
}
}
}
break;
default:
break;
}
return res
}
/**
* 转换成echarts使用的数据 - 基础格式(适用于柱状图、折线图等)
*/
export function toEchartsData(data) {
let tmpColumn: Array<any> = [];
let tmpValue: Array<any> = [];
for (let item in data.rows) {
if (item == 'len') {
continue
}
tmpColumn.push(data.rows[item]['cells'][0] ? data.rows[item]['cells'][0]['text'] : '')
tmpValue.push(data.rows[item]['cells'][1] ? data.rows[item]['cells'][1]['text'] : '')
}
return [tmpColumn, tmpValue];
}
/**
* 转换成echarts使用的数据 - 适用于饼图
*
* @param data
* @returns
*/
export function toEchartsPieData(data) {
let tmpArr: Array<Object> = []
for (let item in data.rows) {
if (item == 'len') {
continue
}
tmpArr.push({
name: data.rows[item]['cells'][0] ? data.rows[item]['cells'][0]['text'] : '',
value: data.rows[item]['cells'][1] ? data.rows[item]['cells'][1]['text'] : ''
})
// tmpColumn.push(data.rows[item]['cells'][0] ? data.rows[item]['cells'][0]['text'] : '')
// tmpValue.push(data.rows[item]['cells'][1] ? data.rows[item]['cells'][1]['text'] : '')
}
return tmpArr;
}
/**
* 数据格式转换 - 根据type转换格式
* 对象格式{name: '', value: ''}; 列格式: nameArr = [], valueArr = []
* @param data
* @returns
*/
export function tranObjAndColumn(data, type = 'toObj') {
let returnData = [] as any[];
if (type == 'toObj') {
if (data.len === 0 || data[0].len === 0) {
return returnData;
}
//转换成饼图使用的obj
let nameArr = data[0];
let valueArr = data[1];
let tmpArr: Array<Object> = []
for (let item in nameArr) {
if (item == 'len') {
continue
}
tmpArr.push({
name: nameArr[item] ? nameArr[item] : '',
value: valueArr[item] ? valueArr[item] : '',
})
}
returnData = tmpArr
} else if (type == 'toColumn') {
//饼图对象转换成列数组
let nameArr = [] as string[]
let valueArr = [] as number[]
for (let item in data) {
nameArr.push(data[item]?.name)
valueArr.push(data[item]?.value)
}
returnData = [nameArr, valueArr]
} else if (type == "toCoord") {
//转换成坐标(散点图用到)
if (data.len === 0 || data[0].len === 0) {
return returnData;
}
//转换成饼图使用的obj
let nameArr = data[0];
let valueArr = data[1];
let tmpArr: Array<any> = []
for (let item in nameArr) {
if (item == 'len') {
continue
}
tmpArr.push([
nameArr[item] ? nameArr[item] : 0,
valueArr[item] ? valueArr[item] : 0,
])
}
returnData = tmpArr
}
return returnData;
}
const EchartsUtils = {
toSpreadsheetData,
toEchartsData,
toEchartsPieData,
tranObjAndColumn,
}
export default EchartsUtils
/**
1: {
cells: {
0: { text: 'testingtesttestetst' },
2: { text: 'testing' },
},
},
2: {
cells: {
0: { text: 'render', style: 0 },
1: { text: 'Hello' },
2: { text: 'haha', merge: [1, 1] },
}
},
8: {
cells: {
8: { text: 'border test', style: 0 },
}
}
*/
+14 -1
View File
@@ -26,6 +26,7 @@ export function base64ToBlod(base64String: string): string {
export function autoDown(url: string, filename: string) { export function autoDown(url: string, filename: string) {
const downloadLink = document.createElement('a'); const downloadLink = document.createElement('a');
downloadLink.href = url; downloadLink.href = url;
downloadLink.target = '_blank';
downloadLink.download = filename; // 指定下载的文件名 downloadLink.download = filename; // 指定下载的文件名
// 将下载链接添加到 DOM 中并模拟点击下载 // 将下载链接添加到 DOM 中并模拟点击下载
document.body.appendChild(downloadLink); document.body.appendChild(downloadLink);
@@ -33,8 +34,20 @@ export function autoDown(url: string, filename: string) {
document.body.removeChild(downloadLink); document.body.removeChild(downloadLink);
} }
/**
* 获取文件后缀
*/
export function getFileExtension(filename) {
const dotIndex = filename.lastIndexOf('.');
if (dotIndex === -1) {
return ''; // 没有找到'.',返回空字符串
}
return filename.substr(dotIndex + 1); // 返回'.'之后的部分作为后缀名
}
const FileUtils = { const FileUtils = {
base64ToBlod, base64ToBlod,
autoDown autoDown,
getFileExtension
} }
export default FileUtils export default FileUtils
+13 -2
View File
@@ -4,21 +4,32 @@ import { ElMessage } from "element-plus";
let request = axios.create({ let request = axios.create({
baseURL: import.meta.env.VITE_IS_MOCK == 'true' ? import.meta.env.VITE_APP_BASE_API : import.meta.env.VITE_SERVE + import.meta.env.VITE_APP_BASE_API, baseURL: import.meta.env.VITE_IS_MOCK == 'true' ? import.meta.env.VITE_APP_BASE_API : import.meta.env.VITE_SERVE + import.meta.env.VITE_APP_BASE_API,
timeout: 5000 timeout: 5000
}) });
//请求拦截器 //请求拦截器
request.interceptors.request.use(config => { request.interceptors.request.use(config => {
return config; return config;
}); });
//响应拦截器 //响应拦截器
request.interceptors.response.use((response) => { request.interceptors.response.use((response) => {
if (response.data.code == 401) {
//登录过期
localStorage.setItem('TOKEN', '')
location.reload()
}
return response.data; return response.data;
}, (error) => { }, (error) => {
if (error.code == 'ERR_CANCELED') {
//拒绝响应
return Promise.race([]);
}
//处理网络错误 //处理网络错误
let msg = ''; let msg = '';
let status = error.response.status; let status = error.response.status;
switch (status) { switch (status) {
case 401: case 401:
msg = "token过期"; //msg = "token过期";
msg = "登录过期";
break; break;
case 403: case 403:
msg = '无权访问'; msg = '无权访问';
+8
View File
@@ -0,0 +1,8 @@
/**
* sleep
* @param ms
* @returns
*/
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
+7 -1
View File
@@ -113,11 +113,17 @@ export function numberToChinese(num: number): string {
return chinese; return chinese;
} }
//rtrim: 删除右侧指定字符, 默认删除空格
export function rtrim(str, char = ' ') {
return str.replace(new RegExp('\\'+char+'+$', 'g'), '');
}
const StringUtils = { const StringUtils = {
transferred, transferred,
copy, copy,
genRandomStrByChars, genRandomStrByChars,
numberToChinese numberToChinese,
rtrim,
} }
export default StringUtils export default StringUtils
+20
View File
@@ -0,0 +1,20 @@
export function isUrl(str: string): boolean {
// const urlPattern = new RegExp(
// '^(https?:\\/\\/)?'+ // protocol
// '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name and extension
// '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
// '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
// '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
// '(\\#[-a-z\\d_]*)?$','i'); // fragment locator
// return urlPattern.test(str);
const urlPattern = new RegExp(
'^(https?:\\/\\/)?'+ // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,})','i'); // fragment locator
return urlPattern.test(str);
}
const UrlUtils = {
isUrl
}
export default UrlUtils
+72 -6
View File
@@ -1,10 +1,76 @@
import {constantRoute} from "./src/router/router" // import vitePluginSeoPrerender from "vite-plugin-seo-prerender";
import vitePluginSeoPrerender from "vite-plugin-seo-prerender";
import seoPrerender from "vite-plugin-seo-prerender";
export function seoperender(){ export function seoperender(){
const filterPath = ['/:pathMatch(.*)*', '/404']; // 排除的路径 return seoPrerender({
return vitePluginSeoPrerender({ routes: [
routes: constantRoute.map(routeConfig => routeConfig.path).filter(path => !filterPath.includes(path)), '/',
network: true, '/timetran',
'/md5',
'/json',
'/reg',
'/unicode',
'/wordcount',
'/scaletran',
'/signimage',
'/randompassword',
'/urlencode',
'/ascii',
'/uuid',
'/barrage',
'/unit',
'/qrcode',
'/decision',
'/morse',
'/random',
'/numbertochinese',
'/diff',
'/markdown',
'/texttoimg',
'/webinfo',
'/bar',
'/line',
'/pie',
'/scatter',
'/coin',
'/dice',
'/textremoveduplicate',
'/imgcut',
'/httpstatuscode',
'/jwt',
'/htmlentity',
'/colorpicker',
'/asciiwordpic',
'/jsforamt',
'/htmlformat',
'/cssformat',
'/textedit',
'/base64',
'/baseconverter',
'/storageconverter',
'/hashcalculator',
'/xmlformat',
'/sqlformat',
'/textreplace',
'/textworkflow',
'/emoji',
'/wordfrequency',
'/wordcloud',
'/lottery',
'/rockpaperscissors',
'/wheel',
'/imagecolorpicker',
'/pomodoro',
'/calculator',
'/reactiontest',
'/m3u8player',
'/imagewatermark',
'/colorpalette',
'/about'
],
// network: true,
// removeStyle: true // removeStyle: true
}) })
} }
+15 -1
View File
@@ -10,9 +10,23 @@ export default {
screens: { screens: {
// 自定义响应式尺寸 // 自定义响应式尺寸
'c-xs': {'max': '768px'}, 'c-xs': {'max': '768px'},
'c-sm': {'min': '768px'}, 'c-sm': {'min': '768px'}, //相当远默认的md
'c-md': {'min': '992px'},
'c-lg': {'min': '1200px'}, 'c-lg': {'min': '1200px'},
...defaultTheme.screens, ...defaultTheme.screens,
},
animation: {
fold: 'fold 1s infinite'
},
keyframes: {
fold: {
'0%, 100%': {
opacity: 0
},
'50%': {
opacity: 1
}
}
} }
}, },
plugins: [], plugins: [],
+2
View File
@@ -5,6 +5,8 @@
"module": "ESNext", "module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"], "lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
/* Bundler mode */ /* Bundler mode */
"moduleResolution": "bundler", "moduleResolution": "bundler",
-14
View File
@@ -1,9 +1,7 @@
import { defineConfig, loadEnv } from 'vite' import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import {viteMockServe} from 'vite-plugin-mock'
import path from 'path' import path from 'path'
import { createHtmlPlugin } from 'vite-plugin-html'
import {seoperender} from "./ssr.config"; import {seoperender} from "./ssr.config";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
@@ -21,18 +19,6 @@ export default defineConfig(({command, mode}) => {
// Specify symbolId format // Specify symbolId format
symbolId: 'icon-[dir]-[name]', symbolId: 'icon-[dir]-[name]',
}), }),
//mock
viteMockServe({
localEnabled: env.VITE_IS_MOCK == 'true' ? command === 'serve' : false,
}),
//修改html插件
// createHtmlPlugin({
// inject: {
// data: {
// title: env.VITE_APP_TITLE
// }
// }
// }),
seoperender() seoperender()
], ],
resolve: { resolve: {