2 Commits
Author SHA1 Message Date
osiris 94dc723910 fix: 2024-05-18 19:14:55 +08:00
osiris b4205dac90 feat: 扫描文件 2024-04-07 09:54:46 +08:00
155 changed files with 8663 additions and 9194 deletions
-4
View File
@@ -1,4 +0,0 @@
node_modules
dist
.env.development
.env.production
+6 -4
View File
@@ -1,15 +1,17 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取 # 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development' NODE_ENV = 'development'
# 网站名称 # 网站名称
VITE_APP_TITLE = 'Tools-Web' VITE_APP_TITLE = '工具坊'
# 简短描述 # 网站域名
VITE_APP_DESC = '一个轻量的在线工具箱' VITE_APP_WEBSITE = 'xxx.xxx.com'
# 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 = ''
# 广告位开关 # 广告位开关
-2
View File
@@ -25,5 +25,3 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
public/sitemap.txt
-16
View File
@@ -1,16 +0,0 @@
# build stage
FROM node:18-alpine3.19 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;"]
+41 -118
View File
@@ -1,57 +1,50 @@
<div align="center"> # 基于Vite4.x + Vue3.x + ElementPlus + Typescript + Tailwind的工具站
<pre>
_______ _ __ __ _
|__ __| | | \ \ / / | |
| | ___ ___ | |___ _____\ \ /\ / /__| |__
| |/ _ \ / _ \| / __|______\ \/ \/ / _ \ '_ \
| | (_) | (_) | \__ \ \ /\ / __/ |_) |
|_|\___/ \___/|_|___/ \/ \/ \___|_.__/
</pre> [![license](https://img.shields.io/github/license/taoran1401/tools-web)](LICENSE)
<p> 只需简单几步,即可快速搭建属于自己的在线工具箱。</p>
[![node](https://img.shields.io/badge/any_text-18.14.2-red?label=node)](node) > 演示站点:https://tools.ranblogs.com
[![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> # 介绍
## 目录 免费开源的工具站,目前已开发数十种工具,对平板和手机适配进行了响应式设计,同时为了让seo更为友好做了ssr处理,搭建简单使用方便
- [功能展览](#功能展览) 已有工具:
- [开始使用](#开始使用) - 时间戳转换
- [Docker部署](#Docker部署) - MD5在线加密
- [手动部署](#手动部署) - Json在线转换
- [工具列表](#工具列表) - 正则测试工具
- [其他](#其他) - Unicode转中文
- IP查询
- 常用进制转换
- 在线图片处理
- 字数统计
- 随机密码生成
- URL编码/解码
- UUID生成器
- 手持弹幕
- ASCII码表
- 单位换算
- 色板
- 二维码生成
- 帮我决定
- 摩斯电码
- 生成随机数
- 数字转金额大写
- 文本对比
- markdown编辑器
- 文本转图片
- 网站favicon获取
## 功能展示 # 快速使用
在线站点:<a href="https://tools.ranblogs.com" target="_blank">Tools-Web</a> 安装`pnpm`, 为避免因为不同包管理器工具下载同一个依赖,可能版本不一样的问题,本项目强制使用`pnpm`
## 开始使用
### 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/naroat/tools-web.git git clone --depth=1 https://github.com/taoran1401/tools-web.git
``` ```
安装 安装
@@ -73,87 +66,17 @@ pnpm dev
打包 打包
``` ```
pnpm build pnpm build:dev
``` ```
## 工具列表 # 后端功能(非必须)
- 开发运维 主要包含登录后的收藏功能,若只用工具可以忽略该部分
- 随机密码生成
- URL编码/解码
- UUID生成器
- 时间戳转换
- MD5在线加密
- Json在线转换
- 正则测试工具
- Unicode转中文
- HTTP状态码
- JWT解析
- html实体转义
- js代码格式化/压缩
- Html代码格式化
- Css代码格式化/压缩
- 文本处理 项目地址:https://github.com/taoran1401/tools-web-api
- 文本对比
- markdown编辑器
- ASCII字形生成器
- 文本去重
- 在线文本编辑/HTML获取
- 字数统计
- 教育学术
- 单位换算
- 摩斯电码
- 常用进制转换
- ASCII码表
- 长度单位转换
- 面积单位转换
- 重量单位转换
- 时间单位转换
- 温度单位转换
- 压力单位转换
- 热量单位转换
- 功率单位转换
- 图片处理
- 二维码生成
- 在线图片处理
- 文本转图片
- 图片分割
- 数据图表
- 柱状图
- 折线图
- 饼图
- 散点图
- 选择随机
- 生成随机数
- 帮我决定
- 抛硬币
- 投骰子
- 其他工具
- 数字转金额大写
- 手持弹幕
- 色板
- Color选择器
## 其他
Q: 我应该如何添加新功能?
A:
-`components/Tools/tools.ts`文件中添加工具信息
-`router/router.ts`中添加路由
- 拷贝示例目录`components/Tools/Example`修改名称,在这个拷贝出来的目录中开发工具即可
<br/>
Q: 我可以参与开发 `Tools-Web` 吗?
A: 当然可以,随时欢迎提交 `PR`
使用方法见内`README`
# 其他
有问题或建议请提`issue`
+2 -4
View File
@@ -3,8 +3,7 @@
<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="/public/favicon.ico" /> <link rel="shortcut icon" type="image/svg+xml" href="/src/assets/logo.png" />
<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="">
@@ -28,8 +27,7 @@
})(); })();
</script> </script>
</head> </head>
<body class="bg-[--base-gray]"> <body style="background-color: var(--base-gray)">
<body>
<!--[if lt IE 9]> <!--[if lt IE 9]>
<div class="notice chromeframe">您的浏览器版本<strong>很旧很旧</strong>,为了正常地访问网站,请升级您的浏览器</div> <div class="notice chromeframe">您的浏览器版本<strong>很旧很旧</strong>,为了正常地访问网站,请升级您的浏览器</div>
<![endif]--> <![endif]-->
+586
View File
@@ -0,0 +1,586 @@
//获取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: 4,
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: 5,
cate: '设计工具'
},
{
id: 1,
title: '网站favicon获取',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/IP.png',
desc: '获取网站logo、icon、favicon、标题、关键词、描述等信息',
url: '/webInfo',
cateId: 6,
cate: '查询相关',
},
{
id: 1,
title: '柱状图',
logo: '/images/logo/bar.png',
desc: '在线制作柱状图,像做表格一样制作可视化图表,支持导出静态或动态图表',
url: '/bar',
cateId: 8,
cate: '数据图表',
},
]
}
//获取tools分类已经对应的工具
function getToolsCate() {
return [
{
id: 2,
title: '开发运维',
icon: '',
list: [
{
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/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: 3,
title: '文本处理',
icon: '',
list: [
{
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/medium.png',
desc: '支持中文的摩斯电码编码解码',
url: '/morse',
cateId: 3,
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: 4,
title: '教育学术',
icon: '',
list: [
{
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%BF%9B%E5%88%B6%E8%BD%AC%E6%8D%A2.png',
desc: '在线进制转换工具,可在2到64进制之间相互转换',
url: '/scaletran',
cateId: 4,
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: 4,
cate: '教育学术'
},
]
},
{
id: 5,
title: '设计工具',
icon: '',
list: [
{
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/img.png',
desc: '在线图片裁剪,标注,滤镜,画等操作',
url: '/signimage',
cateId: 5,
cate: '设计工具',
},
{
id: 1,
title: '文本转图片',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/text_to_img.png',
desc: '把文本转换成图片,生成长图,具有超多个性文字排版',
url: '/textToImg',
cateId: 5,
cate: '设计工具'
}
]
},
{
id: 6,
title: '查询相关',
icon: '',
list: [
{
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: '网站favicon获取',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/text_to_img.png',
desc: '获取网站logo、icon、favicon、标题、关键词、描述等信息',
url: '/webInfo',
cateId: 6,
cate: '查询相关',
}
]
},
{
id: 8,
title: '数据图表',
icon: '',
list: [
{
id: 1,
title: '柱状图',
logo: '/images/logo/bar.png',
desc: '在线制作柱状图,像做表格一样制作可视化图表,支持导出静态或动态图表',
url: '/bar',
cateId: 8,
cate: '数据图表',
}
]
},
{
id: 7,
title: '其他',
icon: '',
list: [
{
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/choose.png',
desc: '选择困难,难以决定,今天吃什么,现在做什么,自定义选项都给你安排的明明白白',
url: '/decision',
cateId: 7,
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: '其他',
}
]
}
]
}
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
@@ -1,10 +0,0 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
+18 -23
View File
@@ -5,50 +5,43 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc --noEmit && node --max_old_space_size=8192 node_modules/vite/bin/vite.js build --mode development", "build:dev": "vue-tsc && vite build --mode development",
"build:pro": "vue-tsc --noEmit && node --max_old_space_size=8192 node_modules/vite/bin/vite.js build --mode production", "build:pro": "vue-tsc && vite build --mode production",
"preview": "vite preview" "preview": "vite preview",
"preinstall": "node ./scripts/preinstall.js"
}, },
"dependencies": { "dependencies": {
"@codemirror/commands": "^6.3.2", "@codemirror/commands": "^6.3.0",
"@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.3", "@codemirror/language": "^6.9.2",
"@codemirror/lint": "^6.4.2", "@codemirror/lint": "^6.4.2",
"@codemirror/search": "^6.5.5", "@codemirror/search": "^6.5.4",
"@codemirror/state": "^6.4.1", "@codemirror/state": "^6.3.3",
"@codemirror/theme-one-dark": "^6.1.2", "@codemirror/theme-one-dark": "^6.1.2",
"@codemirror/view": "^6.22.1", "@codemirror/view": "^6.22.0",
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.1.0",
"@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",
"colord": "^2.9.3",
"csso": "^5.0.5",
"default-passive-events": "^2.0.0", "default-passive-events": "^2.0.0",
"echarts": "^5.5.0", "echarts": "^5.5.0",
"element-plus": "^2.4.3", "element-plus": "^2.4.2",
"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.6.8",
"sass": "^1.69.5", "sass": "^1.69.5",
"sass-loader": "^13.3.2", "sass-loader": "^13.3.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.10", "vue": "^3.3.4",
"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",
@@ -56,18 +49,20 @@
"xlsx": "^0.18.5" "xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^4.5.1", "@vitejs/plugin-vue": "^4.2.3",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"less": "^4.2.0", "less": "^4.2.0",
"mockjs": "^1.1.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.3.2", "typescript": "^5.0.2",
"vite": "^4.5.1", "vite": "^4.4.5",
"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.24" "vue-tsc": "^1.8.5"
} }
} }
+6312 -4430
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 795 KiB

+15 -15
View File
@@ -11,39 +11,39 @@ const componentStore = useComponentStore()
</script> </script>
<template> <template>
<el-container> <!-- <el-container> -->
<!-- left --> <!-- left -->
<el-aside class="fixed top-0 left-0 h-full z-10 c-md:block c-sm:hidden c-xs:hidden" width="240px" v-show="!componentStore.leftCom"> <!-- <el-aside class="fixed top-0 left-0 h-full z-10 c-md:block c-sm:hidden c-xs:hidden" width="240px" v-show="!componentStore.leftCom">
<Left></Left> <Left></Left>
</el-aside> </el-aside> -->
<el-drawer <!-- <el-drawer
show-close show-close
size="240px" size="240px"
:with-header="false" :with-header="false"
v-model="componentStore.leftComDrawer" v-model="componentStore.leftComDrawer"
direction="ltr" direction="ltr"
> >
<Left></Left> <Left bgColor="#fff"></Left>
</el-drawer> </el-drawer> -->
<!-- right --> <!-- right -->
<el-container :class="!componentStore.leftCom ? 'c-md:ml-[240px]' : ''"> <!-- <el-container> -->
<el-header> <!-- <el-header>
<Header/> <Header/>
</el-header> </el-header> -->
<el-main> <!-- <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-main> <!-- </el-main> -->
<el-footer class="md:mb-6 mt-12 c-xs:mb-12"> <!-- <el-footer class="md:mb-6 mt-12 c-xs:mb-12">
<Floor /> <Floor />
</el-footer> </el-footer> -->
</el-container> <!-- </el-container> -->
</el-container> <!-- </el-container> -->
</template> </template>
<style scoped> <style scoped>
+15
View File
@@ -3,3 +3,18 @@ export interface ResponseData {
code: number, code: number,
message: string, message: string,
} }
//分页
export interface PageData {
current_page: number,
first_page_url: string,
from: number,
last_page: number,
last_page_url: string,
next_page_url: string,
path: string,
per_page: number,
prev_page_url: string,
to: number,
total: number,
}
+35
View File
@@ -0,0 +1,35 @@
//统一管理咱们项目用户相关的接口
import request from '@/utils/request'
import type {
ScanReqData,
UpdateTagReqData,
getScanResponseData,
getScanTagResponseData,
updateTagResponseData,
ScanInfoReqData,
ScanFileInfo
} from './type'
//项目用户相关的请求地址
enum API {
//获取scan列表
SCAN_FILE = '/scans',
//获取scan信息
SCAN_FILE_INFO = '/scans',
//获取标签
SCAN_TAGS = '/scan/tags',
//更新标签
UPDATE_TAG = '/scan/tag/updates',
}
//获取scan file
export const getList = (data: ScanReqData) => request.get<any, getScanResponseData>(API.SCAN_FILE, {
params: data
})
//获取scan file
export const getInfo = (data: ScanInfoReqData) => request.get<any, ScanFileInfo>(API.SCAN_FILE_INFO + '/' + data.fileId)
//获取scan tags
export const getTags = () => request.get<any, getScanTagResponseData>(API.SCAN_TAGS)
//更新标签
export const updateTag = (data: UpdateTagReqData) => request.post<any, updateTagResponseData>(API.UPDATE_TAG, data)
+58
View File
@@ -0,0 +1,58 @@
import { ResponseData, PageData } from "../common"
export interface ScanReqData {
title: string,
tags: string
}
export interface ScanInfoReqData {
fileId: number,
}
export interface UpdateTagReqData {
fileId: number,
tagId: number
}
export interface ScanTagInfo {
id: number,
title: string,
pid: number,
}
//file info
export interface ScanFileInfo {
id: number,
title: string,
path: string,
dir: string,
ext: string,
tags: number[],
scan_tag: ScanTagInfo[]
}
export interface ScanFilePage extends PageData {
data: ScanFileInfo[],
}
//tag info
export interface ScanTagInfo {
id: number,
title: string,
pid: number,
childTags: ScanTagInfo[]
}
//响应格式 - scan file
export interface getScanResponseData extends ResponseData {
data: ScanFileInfo
}
//响应格式 - scan tag
export interface getScanTagResponseData extends ResponseData {
data: ScanTagInfo
}
//响应格式 - update tag
export interface updateTagResponseData extends ResponseData {
}
+38
View File
@@ -0,0 +1,38 @@
//统一管理咱们项目用户相关的接口
import request from '@/utils/request'
import type {
ToolsReqData,
ToolsResponseData,
ToolCateResponseData,
ToolRecommendResponseData,
ToolCollectResponseData,
CollectReqData,
GetToolCollectResponseData
} from './type'
//项目用户相关的请求地址
enum API {
//获取tools
GET_TOOLS = '/tools',
//获取tool cate
GET_TOOL_CATES = '/tool/cates',
//获取推荐
GET_TOOL_RECOMMEND = '/tool/recommends',
//收藏工具
COLLECT = '/tool/collects',
//我的收藏
GET_TOOL_COLLECTS = '/get/tool/collects',
}
//获取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)
//收藏工具
export const toolCollect = (data: CollectReqData) => request.post<any, ToolCollectResponseData>(API.COLLECT + '/' + data.toolId)
//我的收藏
export const getToolCollect = () => request.get<any, GetToolCollectResponseData>(API.GET_TOOL_COLLECTS)
@@ -1,3 +1,5 @@
import { ResponseData } from "../common"
export interface ToolsReqData { export interface ToolsReqData {
cateId: number, cateId: number,
title: string, title: string,
@@ -30,16 +32,26 @@ export interface CollectReqData {
} }
//tools list response //tools list response
export interface ToolsResponseData { export interface ToolsResponseData extends ResponseData {
data: ToolsInfo[] data: ToolsInfo[]
} }
//tools cate response //tools cate response
export interface ToolCateResponseData{ export interface ToolCateResponseData extends ResponseData {
data: ToolCate[] data: ToolCate[]
} }
//tools recommend response //tools recommend response
export interface ToolRecommendResponseData{ export interface ToolRecommendResponseData extends ResponseData {
data: ToolsInfo[] data: ToolsInfo[]
} }
//tools collect response
export interface ToolCollectResponseData extends ResponseData {
}
//get collect response
export interface GetToolCollectResponseData extends ResponseData {
}
+53
View File
@@ -0,0 +1,53 @@
import request from '@/utils/request'
import type {
LoginReqData,
LoginResponseData,
LogoutResponseData,
RegisterReqData,
RegisterResponseData,
SendCodeEmailResponseData,
sendCodeEmailReq,
UserInfoResponseData,
ForgetReqData,
ForgetResponseData
} from './type'
//项目用户相关的请求地址
enum API {
//login
LOGIN = '/login',
//logout
LOGOUT = '/logout',
//register
REGISTER = '/register',
//forget
FORGET = '/forget/password',
//注册 - 发送邮箱验证码
SEND_CODE_EMAIL = '/send/register/code',
//忘记密码 - 发送邮箱验证码
SEND_FORGET_CODE_EMAIL = '/send/forget/password/code',
//获取用户信息
USER_INFO = '/user/info'
}
//login
export const login = (data: LoginReqData) => request.post<any, LoginResponseData>(API.LOGIN, data)
//logout
export const logout = () => request.post<any, LogoutResponseData>(API.LOGOUT)
//register
export const register = (data: RegisterReqData) => request.post<any, RegisterResponseData>(API.REGISTER, data)
//forget
export const forget = (data: ForgetReqData) => request.post<any, ForgetResponseData>(API.FORGET, data)
//register - send code
export const sendCodeEmail = (data: sendCodeEmailReq) => request.post<any, SendCodeEmailResponseData>(API.SEND_CODE_EMAIL, data)
//forget - send code
export const sendForgetCodeEmail = (data: sendCodeEmailReq) => request.post<any, SendCodeEmailResponseData>(API.SEND_FORGET_CODE_EMAIL, data)
//获取用户信息
export const userInfo = () => request.get<any, UserInfoResponseData>(API.USER_INFO)
+49
View File
@@ -0,0 +1,49 @@
import { ResponseData } from "../common"
export interface LoginReqData {
email: string,
password: string
}
export interface RegisterReqData {
email: string,
code: string,
password: string,
password_confirmation: string
}
export interface ForgetReqData {
email: string,
code: string,
password: string,
password_confirmation: string
}
export interface sendCodeEmailReq {
email: string
}
export interface LoginResponseData extends ResponseData {
}
export interface LogoutResponseData extends ResponseData {
}
export interface RegisterResponseData extends ResponseData {
}
export interface ForgetResponseData extends ResponseData {
}
export interface SendCodeEmailResponseData extends ResponseData {
}
export interface UserInfoResponseData extends ResponseData {
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

@@ -1,7 +1,6 @@
<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码表",
@@ -312,14 +311,6 @@ 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>
@@ -1,7 +1,6 @@
<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({
@@ -115,14 +114,6 @@ 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>
@@ -2,9 +2,7 @@
import {ref, reactive, onMounted } from 'vue' import {ref, reactive, onMounted } from 'vue'
import Spreadsheet from 'x-data-spreadsheet' import Spreadsheet from 'x-data-spreadsheet'
import 'x-data-spreadsheet/dist/locale/zh-cn'; import 'x-data-spreadsheet/dist/locale/zh-cn';
import { UploadProps,UploadRawFile,genFileId } from 'element-plus'
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 { toEchartsData, toSpreadsheetData } from '@/utils/echarts' import { toEchartsData, toSpreadsheetData } from '@/utils/echarts'
import * as echarts from 'echarts' import * as echarts from 'echarts'
@@ -15,7 +13,6 @@ const info = reactive({
const chartDom = ref<HTMLElement|null>() const chartDom = ref<HTMLElement|null>()
const myChart = ref<echarts.ECharts>() const myChart = ref<echarts.ECharts>()
const dataFileRef = ref()
const setOptionName = ref(1) const setOptionName = ref(1)
// //
@@ -30,7 +27,7 @@ const attrColor = ref('#5470c6')
// //
const titlePos = ref('center') const titlePos = ref('center')
// //
const title = ref('Tools-Web') const title = ref('然部落在线工具')
// //
const subTitle = ref('在线图表制作工具') const subTitle = ref('在线图表制作工具')
// - // -
@@ -40,7 +37,7 @@ const subTitleSwitch = ref(true)
/** 水印 */ /** 水印 */
const watermarkSwitch = ref(false) // const watermarkSwitch = ref(false) //
const waterMarkText = ref('Tools-Web'); const waterMarkText = ref('然部落工具');
// //
const createWatermark = () => { const createWatermark = () => {
@@ -299,17 +296,7 @@ const updateDataFile = async (params) => {
console.log(e) console.log(e)
} }
} }
fileReader.readAsArrayBuffer(_file) fileReader.readAsBinaryString(_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 //copy
@@ -372,7 +359,6 @@ onMounted(() => {
ref="dataFileRef" ref="dataFileRef"
accept=".xls,.xlsx,.csv" accept=".xls,.xlsx,.csv"
:http-request="updateDataFile" :http-request="updateDataFile"
:on-exceed="handleExceed"
:limit="1" :limit="1"
> >
上传数据 上传数据
@@ -460,17 +446,6 @@ onMounted(() => {
<el-drawer id="x-spreadsheet" v-model="drawer" direction="btt" custom-class="sheet" style=""> <el-drawer id="x-spreadsheet" v-model="drawer" direction="btt" custom-class="sheet" style="">
</el-drawer> </el-drawer>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线图表制作工具在线制作柱状图<br>
在线编辑表格生成柱状图支持png和jpeg格式导出<br>
柱状图是采用垂直柱子,即矩形块的宽度一样,用高度来代表数值大小<br>
能让使用者更为直观地看到不同类别数据或不同时间阶段的比较情况及发展趋势通常利用于较小的数据集分析<br>
无需任何基础即可上手并且支持超全的自定义配置轻松实现你的个性化图表需求<br>
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -1,7 +1,6 @@
<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: "帮我决定",
@@ -87,14 +86,6 @@ 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>
@@ -1,7 +1,6 @@
<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({
@@ -32,14 +31,6 @@ const info = reactive({
theme="dark" theme="dark"
/> />
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线文本差异比对支持中文英文代码比对不限制字数轻松比对文本
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -1,7 +1,6 @@
<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",
@@ -20,14 +19,6 @@ const info = reactive({
<div class="p-4 rounded-2xl bg-white"> <div class="p-4 rounded-2xl bg-white">
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
示例...
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
+34
View File
@@ -0,0 +1,34 @@
<script setup lang="ts">
// import { ref } from 'vue'
// import { copy } from '@/utils/string'
// const info = reactive({
// title: "关于本站",
// })
const feedBackUrl = import.meta.env.VITE_FEEDBACK_URL
//copy
// const copyRes = async (resStr: string) => {
// copy(resStr)
// }
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<div class="bg-white rounded-md p-5">
<br>
<p>本项目是一个开源工具站, 求星星</p>
<br>
<p>本项目开源地址<el-link href="https://github.com/taoran1401/tools-web">https://github.com/taoran1401/tools-web</el-link></p>
<br>
<p class="font-bold">联系作者/问题反馈</p>
<br>
<p>反馈中心<el-link :href="feedBackUrl">{{ feedBackUrl }}</el-link></p>
<br>
<p>邮箱taoran0796@163.com</p>
<br>
</div>
</div>
</template>
<style scoped>
</style>
+92 -17
View File
@@ -1,28 +1,103 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue'
// import { useRouter } from "vue-router"
import { useScanStore } from '@/store/modules/scan'
// const router = useRouter()
const scanStore = useScanStore()
//video html element
const videoPlayerRef = ref()
//上一个
const playPreviousVideo = () => {
var data = scanStore.historyList[scanStore.historyList.length - 2];
videoPlayerRef.value.src = scanStore.sourceUrl + data.title;
// console.log(videoPlayerRef.value.src)
videoPlayerRef.value.load();
videoPlayerRef.value.play();
//记录当期播放
scanStore.setNowPlay(data)
// scanStore.setNowIndex(nextIndex)
//刷新页面
window.location.reload()
}
//下一个
const playNextVideo = () => {
let nextIndex = scanStore.getNowIndex() + 1;
if (scanStore.isRandPlay === false) {
//随机播放,并且不能和当前相同TODO
nextIndex = Math.floor(Math.random() * scanStore.videoList.length);
}
if (nextIndex < scanStore.videoList.length) {
var data = scanStore.videoList[nextIndex];
videoPlayerRef.value.src = scanStore.sourceUrl + data.title;
videoPlayerRef.value.load();
videoPlayerRef.value.play();
//记录当期播放
scanStore.setNowPlay(data)
scanStore.setNowIndex(nextIndex)
//刷新页面
window.location.reload()
} else {
alert('没有下一个了')
}
}
//能播放时触发
const canplay = () => {
//更新历史播放
//过滤重复信息
let nowPlay = scanStore.getNowPlay()
console.log(nowPlay.id)
scanStore.historyList = scanStore.historyList.filter(item => item.id != nowPlay.id)
scanStore.historyList.push(scanStore.getNowPlay())
localStorage.setItem('historyList', JSON.stringify(scanStore.historyList))
}
//根据索引获取文件信息
// const getInfoByIndex = () => {
// return scanStore.videoList[Number(scanStore.nowIndex)];
// }
//播放结束时触发
const ended = () => {
//自动播放下一个
playNextVideo()
}
onMounted(() => {
console.log(scanStore.getNowPlay())
})
</script> </script>
<template> <template>
<div class="flex flex-col mt-8 flex-1 items-center bg-white rounded-md p-10"> <div class="flex flex-col mt-3 flex-1">
<div class="p-5 w-2/3"> <div class="ml-3">
<h1 class="text-2xl font-bold">关于 Tools-Web</h1> <router-link to="/" class="mr-3">
<p class="mt-6"> <el-button>首页</el-button>
<el-text><el-link href="https://github.com/naroat/tools-web" target="_blank" type="primary">Tools-Web</el-link> 是一款免费开源的轻量在线工具箱包含开发文本媒体图表生活查询等实用工具如果对您有帮助请将其分享给您的朋友并且添加到收藏夹中顺便再点个</el-text> </router-link>
</p> <el-button @click="playPreviousVideo">上一个</el-button>
<el-button @click="playNextVideo">下一个</el-button>
<div class="mt-3 mb-3">当前播放{{ scanStore.getNowPlay().path }}</div>
<video
ref="videoPlayerRef"
:src="scanStore.sourceUrl + scanStore.getNowPlay().title" class="w-[850px] h-[500px]"
:autoplay="false"
:playbackRate="scanStore.playbackRate"
@canplay="canplay"
@ended="ended"
controls>
您的浏览器不支持video
</video>
<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>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.title{
/* rgb(51, 54, 57) */
}
</style> </style>
+123
View File
@@ -0,0 +1,123 @@
<script setup lang="ts">
import { onMounted,reactive } from 'vue';
import { RouterLink } from "vue-router"
import { Star } from '@element-plus/icons-vue'
import { useToolsStore } from '@/store/modules/tools'
import { useUserStore } from '@/store/modules/user'
import { ElMessage } from 'element-plus'
import { useRoute } from "vue-router"
//store
const toolsStore = useToolsStore()
const userStore = useUserStore()
const route = useRoute()
// const getToolsCate = async () => {
// try {
// await toolsStore.getToolCate()
// } catch (error: any) {
// ElMessage.error(error.message)
// }
// }
//收藏
const toolCollect = reactive({
toolId: 0
})
const collect = async (toolId) => {
try {
if (!userStore.isLogin()) {
//未登录,弹出登录窗口
ElMessage.error('请登录')
return
}
toolCollect.toolId = toolId
await toolsStore.toolCollect(toolCollect)
// ElMessage.success('收藏成功')
} catch (error: any) {
ElMessage.error(error.message)
}
return
}
onMounted(() => {
// getToolsCate()
if (route.query && route.query.value) {//底部导航跳转过来的则定位到响应位置
document?.querySelector('#' + `${route.query.value}`)?.scrollIntoView();
} else {//其他位置跳转过来不需要定位的则定位到顶部
document?.querySelector('#collect')?.scrollIntoView()
}
})
</script>
<template>
<div class="md:mr-6 c-xs:mr-0">
<!-- collect -->
<div id="collect">
<!-- cate title -->
<div class="md:mt-8 md:mb-8 c-xs:mt-1 c-xs:mb-4 text-xl font-bold text-[--base-black]">
我的收藏
</div>
<!-- card -->
<div class="flex justify-between flex-wrap self-card-div c-xs:ml-3" :gutter="10" v-if="toolsStore.collect.length > 0">
<router-link v-for="(item, index) in toolsStore.collect" :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">
<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">
<div class="font-semibold text-lg line-clamp-1">{{ item.title }}</div>
</div>
<div class="flex justify-between">
<el-text size="small">{{ item.cate }}</el-text>
<el-button :icon="Star" circle size="small" @click.prevent="collect(item.id)" type="warning"/>
</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="bg-white rounded-2xl">
<el-empty description="暂无收藏" />
</div>
</div>
<!-- list -->
<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]" :id="'cate_' + cate.id">
{{ cate.title }}
</div>
<!-- card -->
<div class="flex justify-between flex-wrap self-card-div c-xs:ml-0" :gutter="10">
<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">
<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">
<div class="font-semibold text-lg line-clamp-1">{{ item.title }}</div>
</div>
<div class="flex justify-between">
<el-text size="small">{{ item.cate }}</el-text>
<el-button :icon="Star" circle size="small" @click.prevent="collect(item.id)" type="warning" :plain="!toolsStore.collectIds.includes(item.id)"/>
</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>
</div>
</template>
<style scoped>
.self-card-div:after{
content: "";
width: 24%
}
</style>
+217 -43
View File
@@ -1,13 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted } from 'vue'; import { onMounted,reactive, ref } from 'vue';
import { RouterLink } from "vue-router" import { useScanStore } from '@/store/modules/scan'
// import { Star } from '@element-plus/icons-vue'
import { useToolsStore } from '@/store/modules/tools'
// import { ElMessage } from 'element-plus' // import { ElMessage } from 'element-plus'
import { useRoute } from "vue-router" import { Search } from '@element-plus/icons-vue';
import { useRouter } from "vue-router"
//store //store
const toolsStore = useToolsStore() const scanStore = useScanStore()
const route = useRoute() const router = useRouter()
// const getToolsCate = async () => { // const getToolsCate = async () => {
// try { // try {
// await toolsStore.getToolCate() // await toolsStore.getToolCate()
@@ -16,51 +15,226 @@ const route = useRoute()
// } // }
// } // }
const loading = ref(false)
const setTagsDialog = ref(false)
// const checkedTags = reactive({})
//----- config -----
//----- ^ -----
//获取分类
//查询参数
const searchParam = reactive({
title: '',
tags: '',
page: 1,
})
const getList = async (query) => {
try {
searchParam.title = query
await scanStore.getList(searchParam)
} catch (error) {
console.log(error)
}
}
//获取标签
const getTags = async () => {
try {
await scanStore.getTags()
//设置标签checked
// for (let index in scanStore.tags) {
// for (let childIndex in scanStore.tags[index].childTags) {
// let cheeckedTag = 'checkTag' + scanStore.tags[index].childTags[childIndex].id
// }
// console.log(scanStore.tags[index].childTags)
// }
} catch (error) {
console.log(error)
}
}
const optionClick = (path: string) => {
console.log(path)
// router.push(url)
}
//获取文件信息
const getFileInfo = async (fileId: number) => {
try {
await scanStore.getInfo({
fileId: fileId,
})
} catch (error) {
console.log(error)
}
}
//set tags
const setTagsDia = async (fileId: number) => {
//显示dialog
setTagsDialog.value = true
await getFileInfo(fileId)
}
//更新标签
const updateTag = async (fileId: number, tagId: number) => {
console.log(fileId, tagId)
try {
await scanStore.updateTag({
fileId: fileId,
tagId: tagId
})
//获取当前文件标签
await getFileInfo(fileId)
} catch (error) {
console.log(error)
}
}
//分页 - 页码改变时
const pageChange = async (nowPage: number) => {
searchParam.page = nowPage
console.log(nowPage)
await scanStore.getList(searchParam)
}
/**
* open local app
*
* index: 索引
* data: 内容数据
*/
const open = (index: number, data: any) => {
//缓存当前播放和播放索引
scanStore.setNowPlay(data)
scanStore.setNowIndex(index)
router.push({
path: '/about',
})
// let addr = router.resolve({
// path: '/about',
// params: {
// url: url
// }
// }).href
// window.open(addr, '_blank')
}
onMounted(() => { onMounted(() => {
// getToolsCate() getList('');
if (route.query && route.query.value) {//底部导航跳转过来的则定位到响应位置 getTags();
document?.querySelector('#' + `${route.query.value}`)?.scrollIntoView();
} else {//其他位置跳转过来不需要定位的则定位到顶部
document?.querySelector('#collect')?.scrollIntoView()
}
}) })
</script> </script>
<template> <template>
<div class="md:mr-6 c-xs:mr-0"> <div>
<!-- list --> <div class="mt-3 flex items-center">
<div v-for="(cate, index) in toolsStore.cates" :key="index"> <el-select
<!-- cate title --> v-model="searchParam.title"
<div class="mt-8 mb-3 text-xl font-bold text-[--base-black]" :id="'cate_' + cate.id"> filterable
{{ cate.title }} remote
</div> reserve-keyword
<!-- card --> remote-show-suffix
<div class="flex justify-between flex-wrap self-card-div c-xs:ml-0" :gutter="10"> :suffix-transition="false"
<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"> :suffix-icon="Search"
<div class="flex items-center border-b pb-2"> placeholder="输入关键词搜索"
<el-image :src="item.logo" class="w-10 h-10 min-h-[2.5rem] min-w-[2.5rem] rounded-full"></el-image> :remote-method="getList"
<div class="flex flex-col ml-2 w-full"> :loading="loading"
<div class="flex"> class="ml-3 mr-3 md:w-80 c-xs:w-60"
<div class="font-semibold text-lg line-clamp-1">{{ item.title }}</div> >
</div> <el-optiond
<div class="flex justify-between"> v-for="item in scanStore.list.data"
<el-text size="small">{{ item.cate }}</el-text> :key="item.id"
<!-- <el-button :icon="Star" circle size="small" @click.prevent="collect(item.id)" type="warning" :plain="!toolsStore.collectIds.includes(item.id)"/> --> :label="item.title"
</div> :value="item.id"
</div> @click="optionClick(item.path)"
</div> >
<div class="flex items-center justify-between mt-2"> </el-optiond>
<el-text line-clamp="2">{{ item.desc }}</el-text> </el-select>
</div> <!-- <el-button>扫描目录</el-button> -->
</router-link> <el-button>随机预览</el-button>
<!-- 占位 div --> <div class='ml-3'>
<div class="w-[24%] c-md:w-[24%] c-sm:w-[32%] "></div> <el-text>数量111</el-text>
</div> </div>
</div> </div>
<!-- 返回顶部 --> <div class="flex w-full">
<el-backtop :right="10" :bottom="50" /> <!-- 左侧列表 -->
<div class="w-9/12">
<div class="flex items-center justify-between h-40 bg-white mt-3 ml-3 rounded-lg p-3" v-for="(item, index) in scanStore.list.data" :key="index">
<!-- left -->
<div class="flex items-center">
<!-- -->
<div>
<div v-if="scanStore.extVideo.includes(item.ext)">
<video :src="scanStore.sourceUrl + item.title" style="height:150px;width:270px;"></video>
</div>
<div v-else>
<el-empty description="无预览" :image-size="100"/>
</div>
</div>
<!-- -->
<div class="ml-3">
<div class="flex items-center">
<el-button class="mr-2" type="warning" size="small">{{ item.ext }}</el-button>
<el-text>{{item.title}}</el-text>
</div>
<div class="mt-3">
<el-text class="mt-3">{{ item.path }}</el-text>
</div>
<div class="mt-3">
<el-tag type="danger" class="mt-2 mr-2" v-for="(childItem, childIndex) in item.scan_tag" :key="childIndex">{{ childItem.title }}</el-tag>
</div>
</div>
</div>
<!-- right -->
<div class="flex flex-col justify-center">
<el-button size="small" type="primary" class="w-20" @click="open(index, item)">open</el-button>
<el-button size="small" type="primary" class="w-20 mt-2" style="margin-left: 0;" @click="setTagsDia(item.id)">set tags</el-button>
<!-- <el-button size="small" type="primary" class="w-20 mt-2" style="margin-left: 0;" >move</el-button> -->
<!-- <el-button size="small" type="primary" class="w-20 mt-2" style="margin-left: 0;">delete</el-button> -->
</div>
</div>
<!-- 分页 -->
<div class="mt-3 mb-3 ml-3 flex justify-end">
<el-pagination
background
layout="prev, pager, next, jumper"
:page-count="scanStore.list.last_page"
:total="scanStore.list.total"
:default-page-size="scanStore.list.per_page"
@current-change="pageChange"
@size-change="pageChange"
@prev-click="pageChange"
@next-click="pageChange"/>
</div>
</div>
<!-- 右侧栏目 -->
<div class="w-3/12 mt-3 ml-3 mr-3 rounded-lg">
<!-- tags -->
<div class="p-3 bg-white" v-for="(item, index) in scanStore.tags" :key="index">
<div>{{ item.title }}</div>
<div class="flex flex-wrap">
<el-tag type="danger" class="mt-2 mr-2" v-for="(childItem, childIndex) in item.childTags" :key="childIndex" @click="">{{ childItem.title }}</el-tag>
</div>
</div>
</div>
</div>
<!-- set tags dialog -->
<el-dialog v-model="setTagsDialog" title="set tags" width="800" @close="getList('')">
<!-- tags -->
<div class="p-3 bg-white" v-for="(item, index) in scanStore.tags" :key="index">
<div>{{ item.title }}</div>
<div class="flex flex-wrap">
<el-check-tag type="danger" :checked="scanStore.fileInfoTags.includes(childItem.id)" @change="updateTag(scanStore.fileInfo.id, childItem.id)" class="mt-2 mr-2" v-for="(childItem, childIndex) in item.childTags" :key="childIndex">{{ childItem.title }}</el-check-tag>
</div>
</div>
<!-- <el-button class="ml-3 mt-3" type="primary">保存</el-button> -->
</el-dialog>
</div> </div>
</template> </template>
@@ -1,11 +1,11 @@
<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 +31,7 @@ import '@codemirror/commands';
const info = reactive({ const info = reactive({
title: "Json在线转换", title: "Json在线转换",
code: '', code: '',
extensions: [json()], //[json(), lineNumbers()], extensions: [json(), lineNumbers()],
isParseErr: false, isParseErr: false,
parseErr: '' parseErr: ''
}) })
@@ -123,14 +123,6 @@ 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>
@@ -3,7 +3,8 @@
import { onMounted, reactive } from 'vue'; import { onMounted, reactive } from 'vue';
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useToolsStore } from '@/store/modules/tools' import { useToolsStore } from '@/store/modules/tools'
// import { ElMessageBox } from 'element-plus' import { useUserStore } from '@/store/modules/user'
import { ElMessage } from 'element-plus'
import {rtrim} from '@/utils/string' import {rtrim} from '@/utils/string'
const props = defineProps({ const props = defineProps({
title: String, title: String,
@@ -18,6 +19,7 @@ const searchParam = reactive({
}) })
//store //store
const toolsStore = useToolsStore() const toolsStore = useToolsStore()
const userStore = useUserStore()
//根据路由查询tool id //根据路由查询tool id
const getToolInfo = async () => { const getToolInfo = async () => {
@@ -25,13 +27,26 @@ const getToolInfo = async () => {
searchParam.route = rtrim(routeStr, '/') searchParam.route = rtrim(routeStr, '/')
await toolsStore.getToolInfo(searchParam) await toolsStore.getToolInfo(searchParam)
} }
//收藏 //收藏
// const collect = () => { const toolCollect = reactive({
// ElMessageBox({ toolId: 0
// title: '提示', })
// message: '请使用快捷键`Ctrl+D`进行收藏' const collect = async (toolId) => {
// }) try {
// } if (!userStore.isLogin()) {
//未登录,弹出登录窗口
ElMessage.error('请登录')
return
}
toolCollect.toolId = toolId
await toolsStore.toolCollect(toolCollect)
// ElMessage.success('收藏成功')
} catch (error: any) {
ElMessage.error(error.message)
}
return
}
onMounted(() => { onMounted(() => {
getToolInfo() getToolInfo()
@@ -53,6 +68,10 @@ onMounted(() => {
<div class="text-xl"> <div class="text-xl">
{{ props.title }} {{ props.title }}
</div> </div>
<div>
<el-button type="warning" :plain="!toolsStore.collectIds.includes(toolsStore.toolInfo.id)" @click="collect(toolsStore.toolInfo.id)">收藏</el-button>
<!-- <el-button type="danger">取消收藏</el-button> -->
</div>
</div> </div>
</template> </template>
+5 -4
View File
@@ -1,11 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue'
const appDesc = ref(import.meta.env.VITE_APP_DESC || '') const beianNo = ref('粤ICP备19038804号-1')
const copyright = ref('ranblogs.com')
</script> </script>
<template> <template>
<div class="w-full rounded-2xl z-10 p-5 text-center"> <div class="bg-white w-full rounded-2xl z-10 p-5 text-center">
<el-text>{{ appDesc }} © 2024 <a href="https://github.com/naroat/tools-web" target="_blank" class="text-blue-700">Tools-Web</a></el-text> Copyright © 2023 · {{ copyright }} &nbsp;&nbsp; <a href="https://beian.miit.gov.cn/" target="_blank">{{ beianNo }}</a>
</div> </div>
</template> </template>
+81 -73
View File
@@ -3,16 +3,24 @@ import { ref, reactive, onMounted } from 'vue'
import { Search } 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 { useComponentStore } from '@/store/modules/component' import { useComponentStore } from '@/store/modules/component'
import { ElMessage } from 'element-plus'
import 'element-plus/theme-chalk/display.css' import 'element-plus/theme-chalk/display.css'
import { ToolsInfo } from '@/components/Tools/tools.type.ts'; import { ToolsInfo } from '@/api/tools/type';
import Login from '@/components/Layout/Login/Login.vue'
import { useUserStore } from '@/store/modules/user'
import defaultAvatar from '@/assets/default_avatar.png'
import router from '@/router'; import router from '@/router';
// const isNavDrawer = ref(false) // const isNavDrawer = ref(false)
const loading = ref(false) const loading = ref(false)
const options = ref<ToolsInfo[]>([]) const options = ref<ToolsInfo[]>([])
const loginRef = ref()
const userPopRef = ref()
const defAvatar = ref(defaultAvatar)
const feedBackUrl = import.meta.env.VITE_FEEDBACK_URL
//store //store
const toolsStore = useToolsStore() const toolsStore = useToolsStore()
const componentStore = useComponentStore() const componentStore = useComponentStore()
const userStore = useUserStore()
//查询参数 //查询参数
const searchParam = reactive({ const searchParam = reactive({
cateId: 0, cateId: 0,
@@ -42,40 +50,70 @@ const searchTools = async (query: string) => {
loading.value = true loading.value = true
if (query) { if (query) {
searchParam.title = query searchParam.title = query
options.value = await toolsStore.getTools(searchParam) await toolsStore.getTools(searchParam)
} else { } else {
options.value = [] options.value = []
} }
loading.value = false loading.value = false
} }
//保存到桌面
const createUrlShortcut = async () => {
try {
const blob = new Blob(
[`[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) => { const optionClick = (url: string) => {
router.push(url) router.push(url)
} }
//收藏
// const collect = () => {
// ElMessageBox({
// title: '提示',
// message: '请使用快捷键`Ctrl+D`进行收藏'
// })
// }
const openLogin = () => {
loginRef.value.show()
}
//获取用户信息
const userInfo = async () => {
try {
//提交
await userStore.userInfo()
} catch (error: any) {
ElMessage.error(error.message)
}
}
//退出登录
const logout = async () => {
try {
//提交
await userStore.logout()
location.reload()
} catch (error: any) {
ElMessage.error(error.message)
}
}
//获取收藏
const getToolCollect = async () => {
try {
await toolsStore.getCollect()
// ElMessage.success('收藏成功')
} catch (error: any) {
ElMessage.error(error.message)
}
return
}
onMounted(() => { onMounted(() => {
getToolCollect(),
userInfo()
}) })
</script> </script>
<template> <template>
<header class="h-24 w-full flex justify-between pt-2 pb-2 c-xs:h-16 c-xs:border-b-[1px] items-center"> <header class="h-20 w-full flex justify-between pt-2 pb-2 c-xs:h-16 c-xs:border-b-[1px] items-center">
<div class="flex items-center w-full"> <div class="flex items-center">
<Transition name="fold" class="hidden c-sm:block c-md:hidden c-xs:block"> <Transition name="fold" class="hidden c-sm:block c-md:hidden c-xs:block">
<svg v-if="!componentStore.leftComDrawer" @click="componentStore.setleftComDrawerStatus(true)" t="1702978210636" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7618" width="30" height="30"> <svg v-if="!componentStore.leftComDrawer" @click="componentStore.setleftComDrawerStatus(true)" t="1702978210636" class="icon" 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> <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>
@@ -95,16 +133,7 @@ onMounted(() => {
</svg> </svg>
</Transition> </Transition>
<div class="ml-3 mr-1"> <div class="md:w-80 c-xs:72">
<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>
</div>
<div class="c-xs:w-[85%] w-full mr-3">
<!-- <el-input <!-- <el-input
v-model="searchParam.title" v-model="searchParam.title"
placeholder="搜索工具" placeholder="搜索工具"
@@ -125,13 +154,12 @@ onMounted(() => {
placeholder="输入关键词搜索,如文本、json、图片等" placeholder="输入关键词搜索,如文本、json、图片等"
:remote-method="searchTools" :remote-method="searchTools"
:loading="loading" :loading="loading"
class="ml-3" class="ml-3 mr-3 md:w-80 c-xs:w-60"
size="large"
> >
<el-option <el-option
v-for="item in options" v-for="item in toolsStore.list"
:key="item.id" :key="item.id"
:label="item.title + ' - ' + item.desc" :label="item.title"
:value="item.id" :value="item.id"
@click="optionClick(item.url)" @click="optionClick(item.url)"
> >
@@ -142,38 +170,30 @@ onMounted(() => {
<div class=" w-full md:w-auto flex md:block c-xs:w-auto"> <div class=" w-full md:w-auto flex md:block c-xs:w-auto">
<ul class="flex mt-4 flex-col md:flex-row md:mt-0 justify-end items-center c-xs:mt-0"> <ul class="flex mt-4 flex-col md:flex-row md:mt-0 justify-end items-center c-xs:mt-0">
<!-- about --> <li class="hover:text-blue-500">
<li class="ml-3 c-xs:hidden"> <el-link :href="feedBackUrl" size="large" target="_blank">建议/问题</el-link>
<router-link to="/about">
<el-tooltip
class="box-item"
effect="dark"
content="关于tools-web"
placement="bottom"
>
<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>
<!-- github --> <li class="ml-3" v-if="userStore.isLogin()">
<li class="ml-3"> <!-- userStore.avatar -->
<el-tooltip <el-image class="w-10 h-10 cursor-pointer" fit="cover" :src="(userStore.avatar == '' || userStore.avatar == null || userStore.avatar == undefined) ? defAvatar : userStore.avatar" v-popover="userPopRef"></el-image>
class="box-item" <el-popover
effect="dark" ref="userPopRef"
content="GitHub仓库" trigger="hover"
placement="bottom" virtual-triggering
popper-style="padding-left: 0; padding-right: 0;"
> >
<a href="https://github.com/naroat/tools-web" target="_blank"> <div class="flex flex-col">
<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> <el-button link class="mb-3">{{ userStore.email }}</el-button>
</a> <el-button link type="danger" class="ml-0" style="margin-left: 0;" @click="logout">退出登录</el-button>
</el-tooltip> </div>
</el-popover>
</li> </li>
<li class="ml-3 hover:text-blue-500" v-else>
<li class="hover:text-blue-500 c-xs:hidden"> <el-button size="large" type="primary" @click="openLogin">登录</el-button>
<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>
<Login ref="loginRef"/>
</header> </header>
</template> </template>
@@ -187,16 +207,4 @@ onMounted(() => {
transform: translateX(20px); transform: translateX(20px);
opacity: 0; opacity: 0;
} }
:deep(.el-select__wrapper) {
box-shadow: 0 0 0 0px var(--el-input-border-color, var(--el-border-color)) inset;
cursor: default;
@apply md:w-full;
}
.el-select :deep(.el-select__wrapper){
background-color: rgba(46, 51, 56, 0.05);
background-color: rgb(255, 255, 255);
}
</style> </style>
+17 -7
View File
@@ -3,13 +3,16 @@
import { onMounted, ref, reactive } from 'vue'; import { onMounted, ref, reactive } from 'vue';
import { useToolsStore } from '@/store/modules/tools' import { useToolsStore } from '@/store/modules/tools'
import { useRouter, useRoute } from "vue-router" import { useRouter, useRoute } from "vue-router"
const props = defineProps({
bgColor: String,
})
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const appName = ref(import.meta.env.VITE_APP_TITLE || '工具坊') const appName = ref(import.meta.env.VITE_APP_TITLE || '工具坊')
const appNet = ref(import.meta.env.VITE_APP_DESC || '') const appNet = ref(import.meta.env.VITE_APP_WEBSITE || '')
//菜单选中 //菜单选中
const defaultActive = ref('') const defaultActive = ref('collect')
//默认展开的菜单 //默认展开的菜单
const defaultOpeneds = ['cate'] const defaultOpeneds = ['cate']
//store //store
@@ -53,9 +56,8 @@ const gotoAbout = () => {
router.push('about') router.push('about')
} }
onMounted(async () => { onMounted(() => {
await getToolCates() getToolCates()
}) })
</script> </script>
@@ -64,7 +66,7 @@ onMounted(async () => {
<el-scrollbar> <el-scrollbar>
<!-- logo --> <!-- logo -->
<div class="flex justify-center"> <div class="flex justify-center">
<router-link class="flex flex-row h-24" to="/"> <router-link class="flex flex-row h-20" to="/">
<img class="h-12 w-auto rounded-lg mr-2 mt-auto mb-auto" src="@/assets/logo.png" :alt="appNet"> <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="flex flex-col mt-auto mb-auto">
<div class="text-2xl">{{ appName }}</div> <div class="text-2xl">{{ appName }}</div>
@@ -79,10 +81,18 @@ onMounted(async () => {
class="w-[200px]" class="w-[200px]"
:default-active="defaultActive" :default-active="defaultActive"
:default-openeds="defaultOpeneds" :default-openeds="defaultOpeneds"
background-color="transparent" :background-color="props.bgColor ?? '#f5f5fb'"
@open="handleOpen" @open="handleOpen"
@close="handleClose" @close="handleClose"
> >
<el-menu-item index="collect" @click="gotoAnchor('collect')">
<template #title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" data-v-ea893728="" width="20" height="20">
<path fill="currentColor" d="m512 747.84 228.16 119.936a6.4 6.4 0 0 0 9.28-6.72l-43.52-254.08 184.512-179.904a6.4 6.4 0 0 0-3.52-10.88l-255.104-37.12L517.76 147.904a6.4 6.4 0 0 0-11.52 0L392.192 379.072l-255.104 37.12a6.4 6.4 0 0 0-3.52 10.88L318.08 606.976l-43.584 254.08a6.4 6.4 0 0 0 9.28 6.72zM313.6 924.48a70.4 70.4 0 0 1-102.144-74.24l37.888-220.928L88.96 472.96A70.4 70.4 0 0 1 128 352.896l221.76-32.256 99.2-200.96a70.4 70.4 0 0 1 126.208 0l99.2 200.96 221.824 32.256a70.4 70.4 0 0 1 39.04 120.064L774.72 629.376l37.888 220.928a70.4 70.4 0 0 1-102.144 74.24L512 820.096l-198.4 104.32z"></path>
</svg>
<span class="ml-2">我的收藏</span>
</template>
</el-menu-item>
<el-sub-menu index="cate"> <el-sub-menu index="cate">
<template #title> <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"> <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">
+406
View File
@@ -0,0 +1,406 @@
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ElMessage, FormInstance, FormRules } from 'element-plus'
import { useUserStore } from '@/store/modules/user'
import { Md5 } from 'ts-md5'
import 'element-plus/theme-chalk/display.css'
// import router from '@/router';
//visible
const visible = ref(false)
const reSendCount = ref(0)
const reForgetSendCount = ref(0)
//login select: 0 login; 1 reg; 2 forget
const loginSelect = ref(0)
//store
const userStore = useUserStore()
//interface
interface loginFormRule {
email: string,
password: string
}
interface registerFormRule {
email: string,
code: string,
password: string,
password_confirmation: string,
}
interface forgetFormRule {
email: string,
code: string,
password: string,
password_confirmation: string,
}
//ref
const ruleLoginFormRef = ref<FormInstance>()
const ruleRegisterFormRef = ref<FormInstance>()
const ruleForgetFormRef = ref<FormInstance>()
//login from data
const loginFrom = reactive<loginFormRule>({
email: '',
password: ''
})
//register from data
const registerFrom = reactive({
email: '',
code: '',
password: '',
password_confirmation: '',
})
//forget from data
const forgetFrom = reactive({
email: '',
code: '',
password: '',
password_confirmation: '',
})
//keep login check
const keepLogin = ref(true)
//显示弹窗
const show = () => {
visible.value = true
}
//关闭弹窗
// const close = () => {
// visible.value = false
// }
//发送验证码
const sendCode = async () => {
try {
//发送
await userStore.sendCodeEmail({
email: registerFrom.email
})
//设置倒计时
reSendCount.value = 180
const intervalId = setInterval(() => {
if (reSendCount.value == 0) {
clearInterval(intervalId)
} else {
reSendCount.value--
}
}, 1000)
ElMessage.success('发送成功')
} catch (error: any) {
ElMessage.error(error.message)
}
}
//忘记密码 - 发送验证码
const sendForgetCode = async () => {
try {
//发送
await userStore.sendForgetCodeEmail({
email: forgetFrom.email
})
//设置倒计时
reForgetSendCount.value = 180
const intervalId = setInterval(() => {
if (reForgetSendCount.value == 0) {
clearInterval(intervalId)
} else {
reForgetSendCount.value--
}
}, 1000)
ElMessage.success('发送成功')
} catch (error: any) {
ElMessage.error(error.message)
}
}
//登录 - 提交
const loginSubmit = async (formEl: FormInstance|undefined) => {
formEl?.validate(async (valid) => {
if (valid) {
loginSubmitCore()
} else {
return false
}
})
}
//登录core
const loginSubmitCore = async (isReg: boolean = false) => {
try {
//提交
await userStore.login({
email: loginFrom.email,
password: Md5.hashStr(loginFrom.password),
})
//登录成功(通过注册调用的登录不用提示文字)
if (isReg == false) {
ElMessage.success('登录成功')
}
//关闭弹窗
// close()
location.reload()
} catch (error: any) {
ElMessage.error(error.message)
}
}
//注册 - 提交
const registerSubmit = (formEl: FormInstance|undefined) => {
formEl?.validate(async (valid) => {
if (valid) {
try {
//提交注册
await userStore.register({
email: registerFrom.email,
code: registerFrom.code,
password: Md5.hashStr(registerFrom.password),
password_confirmation: Md5.hashStr(registerFrom.password_confirmation)
})
ElMessage.success('注册成功')
//模拟登录
loginFrom.email = registerFrom.email
loginFrom.password = registerFrom.password
loginSubmitCore(true)
} catch (error: any) {
ElMessage.error(error.message)
}
} else {
return false
}
})
}
//忘记密码 - 提交
const forgetSubmit = (formEl: FormInstance|undefined) => {
formEl?.validate(async (valid) => {
if (valid) {
try {
//提交注册
await userStore.forget({
email: forgetFrom.email,
code: forgetFrom.code,
password: Md5.hashStr(forgetFrom.password),
password_confirmation: Md5.hashStr(forgetFrom.password_confirmation)
})
ElMessage.success('操作成功')
//模拟登录
loginFrom.email = forgetFrom.email
loginFrom.password = forgetFrom.password
loginSubmitCore(true)
} catch (error: any) {
ElMessage.error(error.message)
}
} else {
return false
}
})
}
//登录 - 规则验证
const loginRules = reactive<FormRules<loginFormRule>>({
email: [
{required: true, message: '请输入邮箱', trigger: 'blur'},
{type: 'email', message: '邮箱格式错误', trigger: 'blur' },
],
password: [
{required: true, message: '请输入密码', trigger: 'blur'},
{min: 6, max: 20, message: '密码长度范围6~20', trigger: 'blur'},
]
})
//自定义验证 - 重复密码验证
const confirmPassword = (_rule: any, value: any, callback: any) => {
if (registerFrom.password !== value) {
callback(new Error('重复密码不一致'))
} else {
callback()
}
}
//忘记密码 - 自定义验证 - 重复密码验证
const confirmForgetPassword = (_rule: any, value: any, callback: any) => {
if (forgetFrom.password !== value) {
callback(new Error('重复密码不一致'))
} else {
callback()
}
}
//注册 - 规则验证
const registerRules = reactive<FormRules<registerFormRule>>({
email: [
{required: true, message: '请输入邮箱', trigger: 'blur'},
{type: 'email', message: '邮箱格式错误', trigger: 'blur' },
],
code: [
{required: true, message: '请输入验证码', trigger: 'blur'}
],
password: [
{required: true, message: '请输入密码', trigger: 'blur'},
{min: 6, max: 20, message: '密码长度范围6~20', trigger: 'blur'},
],
password_confirmation: [
{required: true, message: '请重复输入密码', trigger: 'blur'},
{validator: confirmPassword, trigger: 'blur'},
]
})
//忘记密码 - 规则验证
const forgetRules = reactive<FormRules<forgetFormRule>>({
email: [
{required: true, message: '请输入邮箱', trigger: 'blur'},
{type: 'email', message: '邮箱格式错误', trigger: 'blur' },
],
code: [
{required: true, message: '请输入验证码', trigger: 'blur'}
],
password: [
{required: true, message: '请输入密码', trigger: 'blur'},
{min: 6, max: 20, message: '密码长度范围6~20', trigger: 'blur'},
],
password_confirmation: [
{required: true, message: '请重复输入密码', trigger: 'blur'},
{validator: confirmForgetPassword, trigger: 'blur'},
]
})
defineExpose({
show
})
</script>
<template>
<!-- 登录弹窗 -->
<el-dialog v-model="visible" title="" width="450" center class="pl-6 pr-6 pb-6" style="border-radius: 1rem">
<!-- 登录 -->
<el-form
v-if="loginSelect == 0"
ref="ruleLoginFormRef"
:model="loginFrom"
:rules="loginRules"
>
<el-divider><div class="text-xl font-normal">邮箱登录</div></el-divider>
<div>
<el-form-item prop="email">
<el-input class="" v-model="loginFrom.email" size="large" placeholder="请输入邮箱"></el-input>
</el-form-item>
<el-form-item prop="password" style="margin-bottom: 0;">
<el-input class="" v-model="loginFrom.password" size="large" placeholder="请输入密码" type="password" show-password></el-input>
</el-form-item>
<div class="flex items-center mt-1">
<el-checkbox v-model="keepLogin" label="" size="large" />
<div class="">保持长时间登录</div>
</div>
<el-button class="w-full" type="primary" size="large" @click="loginSubmit(ruleLoginFormRef)">登录</el-button>
<el-divider border-style="dashed" />
<div class="flex justify-between">
<el-button link @click="loginSelect = 1">还没有账号去注册</el-button>
<!-- <el-button type="danger">忘记密码</el-button> -->
<el-button link @click="loginSelect = 2">忘记密码</el-button>
</div>
</div>
</el-form>
<!-- 注册 -->
<el-form
v-if="loginSelect == 1"
ref="ruleRegisterFormRef"
:model="registerFrom"
:rules="registerRules"
>
<el-divider><div class="text-xl font-normal">邮箱注册</div></el-divider>
<div>
<el-form-item prop="email">
<el-input class="" v-model="registerFrom.email" size="large" placeholder="请输入邮箱"></el-input>
</el-form-item>
<el-form-item prop="code">
<el-input
v-model="registerFrom.code"
placeholder="请输入验证码"
class=""
size="large"
>
<template #append>
<el-button v-if="reSendCount == 0" @click="sendCode">发送验证码</el-button>
<el-button v-else>{{reSendCount}}秒后重发</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input class="" v-model="registerFrom.password" size="large" placeholder="请输入密码" type="password" show-password></el-input>
</el-form-item>
<el-form-item prop="password_confirmation">
<el-input class="" v-model="registerFrom.password_confirmation" size="large" placeholder="请重复输入密码" type="password" show-password></el-input>
</el-form-item>
<el-button class="w-full" type="primary" size="large" @click="registerSubmit(ruleRegisterFormRef)">注册</el-button>
<el-divider border-style="dashed" />
<div class="flex justify-between">
<el-button link @click="loginSelect = 0">已经有账号去登录</el-button>
</div>
</div>
</el-form>
<!-- 忘记密码 -->
<el-form
v-if="loginSelect == 2"
ref="ruleForgetFormRef"
:model="forgetFrom"
:rules="forgetRules"
>
<el-divider><div class="text-xl font-normal">忘记密码</div></el-divider>
<div>
<el-form-item prop="email">
<el-input class="" v-model="forgetFrom.email" size="large" placeholder="请输入邮箱"></el-input>
</el-form-item>
<el-form-item prop="code">
<el-input
v-model="forgetFrom.code"
placeholder="请输入验证码"
class=""
size="large"
>
<template #append>
<el-button v-if="reForgetSendCount == 0" @click="sendForgetCode">发送验证码</el-button>
<el-button v-else>{{reForgetSendCount}}秒后重发</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input class="" v-model="forgetFrom.password" size="large" placeholder="请输入密码" type="password" show-password></el-input>
</el-form-item>
<el-form-item prop="password_confirmation">
<el-input class="" v-model="forgetFrom.password_confirmation" size="large" placeholder="请重复输入密码" type="password" show-password></el-input>
</el-form-item>
<el-button class="w-full" type="primary" size="large" @click="forgetSubmit(ruleForgetFormRef)">提交</el-button>
<el-divider border-style="dashed" />
<div class="flex justify-between">
<el-button link @click="loginSelect = 0">已经有账号去登录</el-button>
</div>
</div>
</el-form>
</el-dialog>
</template>
<style scoped>
.fold-enter-active {
transition: all 1s ease-out;
}
.fold-enter-from,
.fold-leave-to {
transform: translateX(20px);
opacity: 0;
}
</style>
+25
View File
@@ -1,5 +1,6 @@
<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({
@@ -20,18 +21,42 @@ 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
@@ -1,26 +0,0 @@
<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 bg-white p-4">
<el-divider content-position="left">{{ props.title }}</el-divider>
<div class="m-4">
<slot></slot>
</div>
</div>
</template>
<style scoped>
</style>
@@ -1,7 +1,6 @@
<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({
@@ -84,14 +83,6 @@ 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: '',
@@ -21,14 +21,6 @@ const info = reactive({
<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,7 +1,6 @@
<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({
@@ -50,14 +49,6 @@ 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,7 +1,6 @@
<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: "数字转金额大写",
@@ -43,13 +42,6 @@ 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>
@@ -1,7 +1,6 @@
<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: "色板",
@@ -71,14 +70,6 @@ const copyRes = async (resStr: string) => {
</div> </div>
<div></div> <div></div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线复制颜色好看的颜色组合色板
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -1,7 +1,6 @@
<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'
@@ -69,7 +68,7 @@ const gen = () => {
<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="size-select"> <el-select v-model="info.size" class="w-32">
<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" />
@@ -86,10 +85,8 @@ const gen = () => {
</el-input> </el-input>
</div> </div>
<div class="flex mb-3"> <div class="flex mb-3">
<div class="w-16"> <el-text class="w-16">纠错级别</el-text>
<el-text>纠错级别</el-text> <el-select v-model="info.errorCorrectionLevel" class="w-36">
</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" />
@@ -174,14 +171,6 @@ const gen = () => {
/> />
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
根据内容生成二维码自定义尺寸纠错级别颜色等带logo二维码
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -189,10 +178,4 @@ 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,7 +1,6 @@
<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: "生成随机数",
@@ -49,13 +48,6 @@ 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,7 +1,6 @@
<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({
@@ -149,14 +148,6 @@ 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>
@@ -1,13 +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 { 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
@@ -129,6 +128,7 @@ const execMatch = () => {
:autofocus="true" :autofocus="true"
:indent-with-tab="true" :indent-with-tab="true"
:tabSize="2" :tabSize="2"
:extensions="info.extensions"
/> />
</div> </div>
@@ -158,14 +158,6 @@ 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>
@@ -1,7 +1,6 @@
<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'
@@ -201,7 +200,7 @@ const copyRes = async (index: any) => {
<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="choosetranoptions-select"> <el-select v-model="info.chooseTranOptions" placeholder="Select" class="w-24">
<el-option <el-option
v-for="item in info.tranOptions" v-for="item in info.tranOptions"
:key="item.value" :key="item.value"
@@ -249,18 +248,8 @@ const copyRes = async (index: any) => {
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
进位制其实是一种记数的方式所以也称为进位记数法/位值计数法可以用有限的数字符号代表所有的数值 可使用数字符号的数目称为基数英文radix或底数基数为n即可称n进位制简称n进制 例如平常生活中我们经常用到的十进制就是使用10个阿拉伯数字0-9进行记数所以它的基数就是10称为十进制
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.choosetranoptions-select{
@apply w-24
}
</style> </style>
@@ -2,7 +2,6 @@
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'
@@ -81,14 +80,6 @@ const saveImg = () => {
@getNewImg="info.getNewImg" @getNewImg="info.getNewImg"
></SignImageCore> ></SignImageCore>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线图片裁剪图片标注图片滤镜图片画笔图片旋转图片文字图片尺寸调整等操作
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -1,7 +1,6 @@
<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' //
@@ -105,8 +104,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 class="flex"> <div>
<el-select v-model="info.downExt" class="downext-select" placeholder="Select"> <el-select v-model="info.downExt" class="" placeholder="Select">
<el-option <el-option
v-for="item in info.downExts" v-for="item in info.downExts"
:key="item.value" :key="item.value"
@@ -133,19 +132,11 @@ 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>
</div>
<!-- desc --> <!-- v-html="valueHtml" -->
<ToolDetail title="描述"> </div>
<el-text>
把文本转换成图片生成长图富文本自定义文字排版可导出pngjpeg格式可更换背景图设置宽度是好用的文本转图片工具
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.downext-select{
@apply w-24
}
</style> </style>
@@ -3,7 +3,6 @@ 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: "时间戳转换",
@@ -96,6 +95,16 @@ 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>
@@ -107,7 +116,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="trantimestamp-select"> <el-select v-model="info.chooseTranStampOption" placeholder="Select" class="w-24">
<el-option <el-option
v-for="item in info.tranOptions" v-for="item in info.tranOptions"
:key="item.value" :key="item.value"
@@ -120,18 +129,13 @@ const copyRes = async () => {
</div> </div>
</div> </div>
<!-- desc --> <div class="p-4 rounded-2xl bg-white mt-5">
<ToolDetail title="描述"> <div class="text-xl mt-5 mb-5">时间戳</div>
<el-text> <el-text>Unix 时间戳是从1970年1月1日UTC/GMT的午夜开始所经过的秒数不考虑闰秒</el-text>
时间戳是从1970年1月1日UTC/GMT的午夜开始所经过的秒数不考虑闰秒用于表示一个时间点然而这种格式对于人类阅读并不友好因此需要转换成可读的日期和时间格式这个工具能够将时间戳快速转换为人类可读的日期时间格式同时也支持反向转换即将日期时间转换为时间戳<br> </div>
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.trantimestamp-select{
@apply w-24
}
</style> </style>
@@ -1,183 +0,0 @@
<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>
-490
View File
@@ -1,490 +0,0 @@
<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
@@ -1,499 +0,0 @@
<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>
@@ -1,493 +0,0 @@
<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
@@ -1,142 +0,0 @@
<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>
@@ -1,263 +0,0 @@
<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>
@@ -1,97 +0,0 @@
<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>
-88
View File
@@ -1,88 +0,0 @@
<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
@@ -1,115 +0,0 @@
<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,68 +0,0 @@
<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>
@@ -1,82 +0,0 @@
<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>
@@ -1,145 +0,0 @@
<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>
-152
View File
@@ -1,152 +0,0 @@
<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>
-110
View File
@@ -1,110 +0,0 @@
<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
@@ -1,103 +0,0 @@
<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>
-109
View File
@@ -1,109 +0,0 @@
<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>
@@ -1,64 +0,0 @@
<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>
-569
View File
@@ -1,569 +0,0 @@
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: 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: 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: 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: 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: 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: '/palettes/',
cateId: 7,
cate: '其他工具'
},
{
id: 1,
title: 'Color选择器',
logo: '/images/logo/color_picker.png',
desc: '颜色选择器、在各种颜色空间如十六进制、rgb、hsl、css等等之间转换颜色',
url: '/colorpicker/',
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 } = 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;
});
}
return list
}
const ToolsExport = {
getTools,
getToolsCate,
toolsList,
};
export default ToolsExport;
@@ -1,7 +1,6 @@
<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'
@@ -63,14 +62,6 @@ 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,15 +1,14 @@
<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: '',
}) })
@@ -63,6 +62,7 @@ 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,14 +76,6 @@ 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,8 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, onMounted } 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 { 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'
@@ -16,42 +14,9 @@ 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>
@@ -91,14 +56,6 @@ onMounted(() => {
</el-tabs> </el-tabs>
</div> </div>
</div> </div>
<!-- desc -->
<ToolDetail title="描述">
<el-text>
在线单位换算长度单位换算面积单位换算时间单位换算温度单位换算压力单位换算热量单位换算功率单位换算
</el-text>
</ToolDetail>
</div> </div>
</template> </template>
@@ -1,7 +1,6 @@
<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({
@@ -55,14 +54,6 @@ 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>
@@ -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: '',
@@ -52,14 +52,6 @@ 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>
+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 + '-' + import.meta.env.VITE_APP_DESC document.title = import.meta.env.VITE_APP_TITLE
} }
//设置meta //设置meta
+36 -200
View File
@@ -1,19 +1,17 @@
//对外暴露配置路由(常量路由) //对外暴露配置路由(常量路由)
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: 'tools-web,在线工具,开发人员工具,时间戳转换,加密,解密,md5,进制转换,二维码,正则表达式,json格式化,照片处理,字数统计', keywords: '然部落,在线工具,开发人员工具,时间戳转换,加密,解密,md5,进制转换,二维码,正则表达式,json格式化,照片处理,字数统计',
description: 'tools-web,在线工具,在线工具大全,开发人员工具,日常生活工具,办公助手,时间戳转换,加密,解密,md5,进制转换,二维码,正则表达式,json格式化,照片处理,字数统计', description: '然部落,在线工具,在线工具大全,开发人员工具,日常生活工具,办公助手,时间戳转换,加密,解密,md5,进制转换,二维码,正则表达式,json格式化,照片处理,字数统计',
} }
}, },
//工具
{ {
path: '/timetran', path: '/timetran',
component: () => import('@/components/Tools/TimeTran/TimeTran.vue'), component: () => import('@/components/TimeTran/TimeTran.vue'),
name: 'timetran', name: 'timetran',
meta: { meta: {
title: "时间戳转换", title: "时间戳转换",
@@ -22,8 +20,8 @@ export const constantRoute = [
} }
}, },
{ {
path: '/md5', path: '/MD5',
component: () => import('@/components/Tools/MD5/MD5.vue'), component: () => import('@/components/MD5/MD5.vue'),
name: 'MD5', name: 'MD5',
meta: { meta: {
title: "MD5在线加密", title: "MD5在线加密",
@@ -33,17 +31,17 @@ export const constantRoute = [
}, },
{ {
path: '/json', path: '/json',
component: () => import('@/components/Tools/JsonTran/JsonTran.vue'), component: () => import('@/components/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 数据,语法高亮、校验、格式化、转义,去转义、压缩等功能,可以提高阅读修改的效率和准确性', description: 'json编辑器,json解析,json格式化,json压缩,json转义,json去转义',
} }
}, },
{ {
path: '/reg', path: '/reg',
component: () => import('@/components/Tools/RegTest/RegTest.vue'), component: () => import('@/components/RegTest/RegTest.vue'),
name: 'reg', name: 'reg',
meta: { meta: {
title: "正则表达式测试", title: "正则表达式测试",
@@ -53,7 +51,7 @@ export const constantRoute = [
}, },
{ {
path: '/unicode', path: '/unicode',
component: () => import('@/components/Tools/Unicode/Unicode.vue'), component: () => import('@/components/Unicode/Unicode.vue'),
name: 'unicode', name: 'unicode',
meta: { meta: {
title: "Unicode转中文", title: "Unicode转中文",
@@ -63,7 +61,7 @@ export const constantRoute = [
}, },
{ {
path: '/wordcount', path: '/wordcount',
component: () => import('@/components/Tools/WordCount/WordCount.vue'), component: () => import('@/components/WordCount/WordCount.vue'),
name: 'wordcount', name: 'wordcount',
meta: { meta: {
title: "在线字数统计", title: "在线字数统计",
@@ -73,7 +71,7 @@ export const constantRoute = [
}, },
{ {
path: '/ip', path: '/ip',
component: () => import('@/components/Tools/Ip/Ip.vue'), component: () => import('@/components/Ip/Ip.vue'),
name: 'ip', name: 'ip',
meta: { meta: {
title: "IP查询", title: "IP查询",
@@ -83,7 +81,7 @@ export const constantRoute = [
}, },
{ {
path: '/scaletran', path: '/scaletran',
component: () => import('@/components/Tools/ScaleTran/ScaleTran.vue'), component: () => import('@/components/ScaleTran/ScaleTran.vue'),
name: 'scaletran', name: 'scaletran',
meta: { meta: {
title: "常用进制转换", title: "常用进制转换",
@@ -93,17 +91,17 @@ export const constantRoute = [
}, },
{ {
path: '/signimage', path: '/signimage',
component: () => import('@/components/Tools/SignImage/SignImage.vue'), component: () => import('@/components/SignImage/SignImage.vue'),
name: 'signimage', name: 'signimage',
meta: { meta: {
title: "在线编辑图片", title: "在线编辑图片",
keywords: '在线图片裁剪,图片标注,图片滤镜,图片画笔、图片旋转图片文字,图片美,图片尺寸调整化', keywords: '图片裁剪,图片滤镜,图片旋转图片编辑,美化图片',
description: '在线图片裁剪,图片标注,图片滤镜,图片画笔、图片旋转图片文字,图片尺寸调整等操作', description: '图片裁剪,图片滤镜,图片旋转图片编辑,美化图片',
} }
}, },
{ {
path: '/randompassword', path: '/random_password',
component: () => import('@/components/Tools/RandomPassword/RandomPassword.vue'), component: () => import('@/components/RandomPassword/RandomPassword.vue'),
name: 'random_password', name: 'random_password',
meta: { meta: {
title: "随机密码生成", title: "随机密码生成",
@@ -113,7 +111,7 @@ export const constantRoute = [
}, },
{ {
path: '/urlencode', path: '/urlencode',
component: () => import('@/components/Tools/UrlEncode/UrlEncode.vue'), component: () => import('@/components/UrlEncode/UrlEncode.vue'),
name: 'urlencode', name: 'urlencode',
meta: { meta: {
title: "URL编码/解码", title: "URL编码/解码",
@@ -123,7 +121,7 @@ export const constantRoute = [
}, },
{ {
path: '/ascii', path: '/ascii',
component: () => import('@/components/Tools/ASCII/ASCII.vue'), component: () => import('@/components/ASCII/ASCII.vue'),
name: 'ascii', name: 'ascii',
meta: { meta: {
title: "ASCII码表", title: "ASCII码表",
@@ -133,7 +131,7 @@ export const constantRoute = [
}, },
{ {
path: '/uuid', path: '/uuid',
component: () => import('@/components/Tools/UUID/UUID.vue'), component: () => import('@/components/UUID/UUID.vue'),
name: 'uuid', name: 'uuid',
meta: { meta: {
title: "UUID生成器", title: "UUID生成器",
@@ -143,7 +141,7 @@ export const constantRoute = [
}, },
{ {
path: '/barrage', path: '/barrage',
component: () => import('@/components/Tools/Barrage/Barrage.vue'), component: () => import('@/components/Barrage/Barrage.vue'),
name: 'barrage', name: 'barrage',
meta: { meta: {
title: "手持弹幕", title: "手持弹幕",
@@ -153,7 +151,7 @@ export const constantRoute = [
}, },
{ {
path: '/palettes', path: '/palettes',
component: () => import('@/components/Tools/Palettes/Palettes.vue'), component: () => import('@/components/Palettes/Palettes.vue'),
name: 'palettes', name: 'palettes',
meta: { meta: {
title: "色板", title: "色板",
@@ -163,7 +161,7 @@ export const constantRoute = [
}, },
{ {
path: '/unit', path: '/unit',
component: () => import('@/components/Tools/Unit/Unit.vue'), component: () => import('@/components/Unit/Unit.vue'),
name: 'unit', name: 'unit',
meta: { meta: {
title: "单位转换", title: "单位转换",
@@ -173,7 +171,7 @@ export const constantRoute = [
}, },
{ {
path: '/qrcode', path: '/qrcode',
component: () => import('@/components/Tools/Qrcode/Qrcode.vue'), component: () => import('@/components/Qrcode/Qrcode.vue'),
name: 'qrcode', name: 'qrcode',
meta: { meta: {
title: "二维码生成", title: "二维码生成",
@@ -183,7 +181,7 @@ export const constantRoute = [
}, },
{ {
path: '/decision', path: '/decision',
component: () => import('@/components/Tools/Decision/Decision.vue'), component: () => import('@/components/Decision/Decision.vue'),
name: 'decision', name: 'decision',
meta: { meta: {
title: "帮我决定", title: "帮我决定",
@@ -193,7 +191,7 @@ export const constantRoute = [
}, },
{ {
path: '/morse', path: '/morse',
component: () => import('@/components/Tools/Morse/Morse.vue'), component: () => import('@/components/Morse/Morse.vue'),
name: 'morse', name: 'morse',
meta: { meta: {
title: "摩斯电码", title: "摩斯电码",
@@ -203,7 +201,7 @@ export const constantRoute = [
}, },
{ {
path: '/random', path: '/random',
component: () => import('@/components/Tools/Random/Random.vue'), component: () => import('@/components/Random/Random.vue'),
name: 'random', name: 'random',
meta: { meta: {
title: "生成随机数", title: "生成随机数",
@@ -212,8 +210,8 @@ export const constantRoute = [
} }
}, },
{ {
path: '/numbertochinese', path: '/numberToChinese',
component: () => import('@/components/Tools/NumberToChinese/NumberToChinese.vue'), component: () => import('@/components/NumberToChinese/NumberToChinese.vue'),
name: 'numberToChinese', name: 'numberToChinese',
meta: { meta: {
title: "数字转金额大写", title: "数字转金额大写",
@@ -223,7 +221,7 @@ export const constantRoute = [
}, },
{ {
path: '/diff', path: '/diff',
component: () => import('@/components/Tools/Diff/Diff.vue'), component: () => import('@/components/Diff/Diff.vue'),
name: 'diff', name: 'diff',
meta: { meta: {
title: "文本对比", title: "文本对比",
@@ -233,7 +231,7 @@ export const constantRoute = [
}, },
{ {
path: '/markdown', path: '/markdown',
component: () => import('@/components/Tools/Markdown/Markdown.vue'), component: () => import('@/components/Markdown/Markdown.vue'),
name: 'markdown', name: 'markdown',
meta: { meta: {
title: "markdown编辑器", title: "markdown编辑器",
@@ -242,8 +240,8 @@ export const constantRoute = [
} }
}, },
{ {
path: '/texttoimg', path: '/textToImg',
component: () => import('@/components/Tools/TextToImg/TextToImg.vue'), component: () => import('@/components/TextToImg/TextToImg.vue'),
name: 'textToImg', name: 'textToImg',
meta: { meta: {
title: "文本转图片", title: "文本转图片",
@@ -252,8 +250,8 @@ export const constantRoute = [
} }
}, },
{ {
path: '/webinfo', path: '/webInfo',
component: () => import('@/components/Tools/WebInfo/WebInfo.vue'), component: () => import('@/components/WebInfo/WebInfo.vue'),
name: 'webInfo', name: 'webInfo',
meta: { meta: {
title: "网站favicon获取", title: "网站favicon获取",
@@ -263,7 +261,7 @@ export const constantRoute = [
}, },
{ {
path: '/bar', path: '/bar',
component: () => import('@/components/Tools/Chart/Bar/Bar.vue'), component: () => import('@/components/Chart/Bar/Bar.vue'),
name: 'bar', name: 'bar',
meta: { meta: {
title: "柱状图", title: "柱状图",
@@ -271,167 +269,6 @@ export const constantRoute = [
description: '在线制作柱状图,像做表格一样制作可视化图表,支持导出静态或动态图表', 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: '/about', path: '/about',
component: () => import('@/components/Home/About.vue'), component: () => import('@/components/Home/About.vue'),
@@ -442,7 +279,6 @@ export const constantRoute = [
description: '', description: '',
} }
}, },
//其他路由
{ {
path: '/404', path: '/404',
component: () => import('@/components/404/404.vue'), component: () => import('@/components/404/404.vue'),
+94
View File
@@ -0,0 +1,94 @@
//创建tools相关的小工具
import { defineStore } from 'pinia'
import type { ScanReqData, ScanFilePage, ScanTagInfo, UpdateTagReqData, ScanInfoReqData, ScanFileInfo } from '@/api/scan/type'
import { getList, getTags, updateTag, getInfo } from '@/api/scan'
export const useScanStore = defineStore('scan', {
//用来存放变量
state: () => ({
list: {} as ScanFilePage,
fileInfo: {} as ScanFileInfo, //当前文件信息
fileInfoTags: [] as number[], //当期文件tags
tags: [] as ScanTagInfo[],
videoList: localStorage.getItem('videoList') !== null ? JSON.parse(localStorage.getItem('videoList') || '') : [], //播放列表,一般情况下同list
playbackRate: 1.5, //播放速度
isRandPlay: false, //是否随机播放
extVideo: ['ts', 'mp4', 'mkv', 'wmv', 'avi', 'flv', '3gp', 'dvd', 'mov', 'vob', 'webm'], //视频格式
sourceUrl: 'http://localhost:7881/', //资源服务地址
historyList: localStorage.getItem('historyList') !== null ? JSON.parse(localStorage.getItem('historyList') || '') : [], //历史播放
}),
//方法
actions: {
//获取scan file list
async getList(data: ScanReqData) {
//发送请求
const result: any = await getList(data)
if (result.code == 200) {
this.list = result.data
localStorage.setItem('videoList', JSON.stringify(result.data.data))
console.log(localStorage.getItem('videoList'))
return result.message
} else {
return Promise.reject(new Error(result.message))
}
},
//获取scan file list
async getInfo(data: ScanInfoReqData) {
//发送请求
const result: any = await getInfo(data)
if (result.code == 200) {
this.fileInfo = result.data
this.fileInfoTags = this.fileInfo.tags
return result.message
} else {
return Promise.reject(new Error(result.message))
}
},
//get tags
async getTags() {
//发送请求
const result: any = await getTags()
if (result.code == 200) {
this.tags = result.data
return result.message
} else {
return Promise.reject(new Error(result.message))
}
},
//收藏工具
async updateTag(data: UpdateTagReqData) {
const result: any = await updateTag(data)
if (result.code == 200) {
return result.message
} else {
return Promise.reject(new Error(result.message))
}
},
//设置播放列表
// setVideoList() {
// },
//设置播放速度
setPlaybackRate(number) {
this.playbackRate = number
},
//当期那播放信息、索引获取和设置
getNowPlay() {
return localStorage.getItem('nowPlay') !== null ? JSON.parse(localStorage.getItem('nowPlay') || '') : {};
},
getNowIndex() {
return localStorage.getItem('nowIndex') !== null ? JSON.parse(localStorage.getItem('nowIndex') || '') : 0;
},
setNowPlay(data) {
localStorage.setItem('nowPlay', JSON.stringify(data))
},
setNowIndex(index) {
localStorage.setItem('nowIndex', JSON.stringify(index))
},
//清除缓存
cleanCache() {
localStorage.removeItem('videoList');
localStorage.removeItem('historyList');
}
}
})
+64 -8
View File
@@ -1,10 +1,11 @@
//创建tools相关的小工具 //创建tools相关的小工具
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { getTools, getToolsCate } from '@/components/Tools/tools.ts' import { getTools, getToolCate, getToolRecommend, toolCollect, getToolCollect } from '@/api/tools'
import { getIp } from '@/api/ip' import { getIp } from '@/api/ip'
import type { ToolsReqData, ToolsInfo } from '@/components/Tools/tools.type.ts' import type { ToolsReqData, ToolsInfo, ToolCate, CollectReqData } from '@/api/tools/type'
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 type { WebInfo, WebInfoReqData } from '@/api/webinfo/type'
import { useUserStore } from '@/store/modules/user'
import { fetchWebInfo } from '@/api/webinfo' import { fetchWebInfo } from '@/api/webinfo'
export const useToolsStore = defineStore('tools', { export const useToolsStore = defineStore('tools', {
@@ -12,7 +13,7 @@ export const useToolsStore = defineStore('tools', {
state: () => ({ state: () => ({
list: [] as ToolsInfo[], list: [] as ToolsInfo[],
toolInfo: {} as ToolsInfo, toolInfo: {} as ToolsInfo,
cates: [] as any[], cates: [] as ToolCate[],
recommends: [] as ToolsInfo[], recommends: [] as ToolsInfo[],
ipData: {} as IpInfo, ipData: {} as IpInfo,
webInfo: {} as WebInfo, webInfo: {} as WebInfo,
@@ -25,20 +26,44 @@ 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)
this.list = result if (result.code == 200) {
return result this.list = result.data
return result.message
} else {
return Promise.reject(new Error(result.message))
}
}, },
//获取tool info //获取tool info
async getToolInfo(data: ToolsReqData) { async getToolInfo(data: ToolsReqData) {
//发送请求 //发送请求
const result: any = await getTools(data) const result: any = await getTools(data)
this.toolInfo = result if (result.code == 200) {
return result this.toolInfo = result.data[0]
return result.message
} else {
return Promise.reject(new Error(result.message))
}
}, },
//获取tools cate //获取tools cate
async getToolCate() { async getToolCate() {
//发送请求 //发送请求
this.cates = await getToolsCate() const result: any = await getToolCate()
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) {
@@ -50,6 +75,37 @@ export const useToolsStore = defineStore('tools', {
return Promise.reject(new Error(result.message)) return Promise.reject(new Error(result.message))
} }
}, },
//收藏工具
async toolCollect(data: CollectReqData) {
if (!useUserStore().isLogin()) {
//未登录
return
}
const result: any = await toolCollect(data)
if (result.code == 200) {
//收藏成功
this.getCollect();
return result.message
} else {
return Promise.reject(new Error(result.message))
}
},
//我的收藏
async getCollect() {
if (!useUserStore().isLogin()) {
//未登录
return
}
const result: any = await getToolCollect()
if (result.code == 200) {
this.collect = result.data
this.collectIds = this.collect.map(item => item.id)
console.log(this.collectIds)
return result.message
} else {
return Promise.reject(new Error(result.message))
}
},
//获取网站信息 //获取网站信息
async getWebInfo(data: WebInfoReqData) { async getWebInfo(data: WebInfoReqData) {
const result: any = await fetchWebInfo(data) const result: any = await fetchWebInfo(data)
+105
View File
@@ -0,0 +1,105 @@
import { defineStore } from 'pinia'
import { login, register, sendCodeEmail, userInfo, logout, sendForgetCodeEmail, forget} from '@/api/user'
import type { LoginReqData, RegisterReqData, sendCodeEmailReq, ForgetReqData } from '@/api/user/type'
export const useUserStore = defineStore('user', {
//用来存放变量
state: () => ({
token: import.meta.env.VITE_IS_MOCK == 'true' ? localStorage.setItem('TOKEN', '') : localStorage.getItem('TOKEN'),//token
username: '',
avatar: '',
email: '',
}),
//方法
actions: {
//获取登录状态
isLogin() {
let flag = false;
if (this.token != null && this.token != undefined && this.token != '') {
flag = true;
}
return flag;
},
//login
async login(data: LoginReqData) {
//发送请求
const result: any = await login(data)
if (result.code == 200) {
//仓库存储token
this.token = result.data.token
//本地存储token
localStorage.setItem('TOKEN', result.data.token)
return result.message
} else {
return Promise.reject(new Error(result.message))
}
},
//logout
async logout() {
//发送请求
const result: any = await logout()
if (result.code == 200) {
//本地存储token
localStorage.setItem('TOKEN', '')
return result.message
} else {
return Promise.reject(new Error(result.message))
}
},
//register
async register(data: RegisterReqData) {
//发送请求
const result: any = await register(data)
if (result.code == 200) {
return result.message
} else {
return Promise.reject(new Error(result.message))
}
},
//forget
async forget(data: ForgetReqData) {
//发送请求
const result: any = await forget(data)
if (result.code == 200) {
return result.message
} else {
return Promise.reject(new Error(result.message))
}
},
//register - send code email
async sendCodeEmail(data: sendCodeEmailReq) {
//发送请求
const result: any = await sendCodeEmail(data)
if (result.code == 200) {
return result.message
} else {
return Promise.reject(new Error(result.message))
}
},
//forget - send code email
async sendForgetCodeEmail(data: sendCodeEmailReq) {
//发送请求
const result: any = await sendForgetCodeEmail(data)
if (result.code == 200) {
return result.message
} else {
return Promise.reject(new Error(result.message))
}
},
//获取用户信息
async userInfo() {
if (!this.isLogin()) {
return
}
//发送请求
const result: any = await userInfo()
if (result.code == 200) {
this.username = result.data.nick_name
this.avatar = result.data.avatar
this.email = result.data.email
return result.message
} else {
return Promise.reject(new Error(result.message))
}
}
}
})
+3 -88
View File
@@ -22,11 +22,11 @@ export function toSpreadsheetData(data, type = 'bar') {
} }
/** /**
* 转换成echarts使用的数据 - 基础格式(适用于柱状图、折线图等) * 转换成echarts使用的数据
*/ */
export function toEchartsData(data) { export function toEchartsData(data) {
let tmpColumn: Array<any> = []; let tmpColumn: Array<string> = [];
let tmpValue: Array<any> = []; let tmpValue: Array<string> = [];
for (let item in data.rows) { for (let item in data.rows) {
if (item == 'len') { if (item == 'len') {
continue continue
@@ -37,94 +37,9 @@ export function toEchartsData(data) {
return [tmpColumn, tmpValue]; 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 = { const EchartsUtils = {
toSpreadsheetData, toSpreadsheetData,
toEchartsData, toEchartsData,
toEchartsPieData,
tranObjAndColumn,
} }
export default EchartsUtils export default EchartsUtils
+33 -2
View File
@@ -1,13 +1,44 @@
import axios from "axios"; import axios from "axios";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useUserStore } from '@/store/modules/user'
//store
import pinia from '../store' //这里需要引入pinia
const userStore = useUserStore(pinia)
//创建axios实例 //创建axios实例
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
}); })
//创建一个存储CancelToken源的映射,用于管理请求和取消操作
const cancelSources = new Map();
//mock环境下无法使用的接口
const mockBanUrl: string[] = [
'/login',
'/user/info',
'/get/tool/collects',
'/send/forget/password/code',
'/send/register/code',
'/forget/password',
'/register',
'/logout'
];
//请求拦截器 //请求拦截器
request.interceptors.request.use(config => { request.interceptors.request.use(config => {
if (import.meta.env.VITE_IS_MOCK == 'true' && config.url != undefined && mockBanUrl.includes(config.url)) {
//mock环境不能使用登录后相关功能
const source = axios.CancelToken.source()
//将source与请求的url或某种唯一标识符关联起来
cancelSources.set(config.url, source);
config.cancelToken = source.token;
//取消
source.cancel('Request canceled by the user.');
} else {
//获取token
if (userStore.token) {
// config.headers.token = userStore.token
config.headers.Authorization = 'Bearer ' + userStore.token
}
}
return config; return config;
}); });
//响应拦截器 //响应拦截器
+6
View File
@@ -1,7 +1,9 @@
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/
@@ -19,6 +21,10 @@ 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,
}),
seoperender() seoperender()
], ],
resolve: { resolve: {