Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fa19c1aed | ||
|
|
d984af3ebe | ||
|
|
f289facdba | ||
|
|
3604407dd7 | ||
|
|
a5233c55c6 | ||
|
|
ae62d59fa3 | ||
|
|
ba71938f01 | ||
|
|
84f42c6825 | ||
|
|
c90524bac1 | ||
|
|
c53bda5706 | ||
|
|
4e1354e5d2 | ||
|
|
f822d17e6e | ||
|
|
9e9176fb68 | ||
|
|
6082f1d789 | ||
|
|
95961dc05a | ||
|
|
c4bff93b44 | ||
|
|
6e8787a2d0 | ||
|
|
527b7f2f5d | ||
|
|
95b7fec3b5 | ||
|
|
f05ee53440 | ||
|
|
50a6e0be2a | ||
|
|
dd0022e219 | ||
|
|
8ca5623b6b | ||
|
|
18f9196212 | ||
|
|
81145a70cd | ||
|
|
2d9fb23cea | ||
|
|
2090d61140 | ||
|
|
4952fb94d6 | ||
|
|
5ad691258b | ||
|
|
90136ccbd2 | ||
|
|
5a06a7aef3 | ||
|
|
144c22eb56 | ||
|
|
a10c36cf73 | ||
|
|
6ef16b1da0 | ||
|
|
a4dc95ba38 | ||
|
|
a15c293322 | ||
|
|
2c914205d7 | ||
|
|
34c601c400 | ||
|
|
097d8fc0b8 | ||
|
|
ad8a1179cd | ||
|
|
c860a01416 | ||
|
|
efc1cb52eb | ||
|
|
c0488db4e8 | ||
|
|
a882cfeac9 | ||
|
|
9a55817794 | ||
|
|
68951056b1 | ||
|
|
4d72590f6f | ||
|
|
4bb328e5f6 | ||
|
|
de2ae48d58 | ||
|
|
b6b5f1dc0b | ||
|
|
d863f0174c | ||
|
|
df8ed7f9c8 | ||
|
|
4631227f9a | ||
|
|
dd77da705b | ||
|
|
92def0bd57 | ||
|
|
7f9ee12172 | ||
|
|
551343dc0e | ||
|
|
101ee6dff2 | ||
|
|
6ecafa8bc2 | ||
|
|
d5865d4c8e | ||
|
|
b8448cd1e9 | ||
|
|
653bca5da6 | ||
|
|
9c8d1e2f86 | ||
|
|
3a0eaa24ac | ||
|
|
f9d2c07ba5 | ||
|
|
108948aa2c | ||
|
|
e2676f9ec8 |
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
dist
|
||||
.env.development
|
||||
.env.production
|
||||
@@ -1,17 +1,15 @@
|
||||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||
NODE_ENV = 'development'
|
||||
# 网站名称
|
||||
VITE_APP_TITLE = '工具坊'
|
||||
# 网站域名
|
||||
VITE_APP_WEBSITE = 'xxx.xxx.com'
|
||||
VITE_APP_TITLE = 'Tools-Web'
|
||||
# 简短描述
|
||||
VITE_APP_DESC = '一个轻量的在线工具箱'
|
||||
# base api
|
||||
VITE_APP_BASE_API = '/v1'
|
||||
# 服务端地址: https://xxx.xxx.com
|
||||
# 服务端地址: https://xxx.xxx.com;
|
||||
VITE_SERVE = ''
|
||||
# 自定义启动host
|
||||
VITE_HOST = '127.0.0.1'
|
||||
# 是否使用mock: bool
|
||||
VITE_IS_MOCK = 'true'
|
||||
# 第三方反馈互动地址
|
||||
VITE_FEEDBACK_URL = ''
|
||||
# 广告位开关
|
||||
|
||||
@@ -25,3 +25,5 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
public/sitemap.txt
|
||||
@@ -0,0 +1,16 @@
|
||||
# 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;"]
|
||||
@@ -1,12 +1,50 @@
|
||||
# 基于Vite4.x + Vue3.x + ElementPlus + Typescript + Tailwind的工具站
|
||||
<div align="center">
|
||||
<pre>
|
||||
_______ _ __ __ _
|
||||
|__ __| | | \ \ / / | |
|
||||
| | ___ ___ | |___ _____\ \ /\ / /__| |__
|
||||
| |/ _ \ / _ \| / __|______\ \/ \/ / _ \ '_ \
|
||||
| | (_) | (_) | \__ \ \ /\ / __/ |_) |
|
||||
|_|\___/ \___/|_|___/ \/ \/ \___|_.__/
|
||||
|
||||
</pre>
|
||||
<p> 只需简单几步,即可快速搭建属于自己的在线工具箱。</p>
|
||||
|
||||
[](node)
|
||||
[](vue)
|
||||
[](tailwindcss)
|
||||
[](elementplus)
|
||||
[](LICENSE)
|
||||
|
||||
> 演示站点:https://tools.ranblogs.com
|
||||
</div>
|
||||
|
||||
# 快速使用
|
||||
## 目录
|
||||
|
||||
安装`pnpm`, 为避免因为不同包管理器工具下载同一个依赖,可能版本不一样的问题,本项目强制使用`pnpm`
|
||||
- [功能展览](#功能展览)
|
||||
- [开始使用](#开始使用)
|
||||
- [Docker部署](#Docker部署)
|
||||
- [手动部署](#手动部署)
|
||||
- [工具列表](#工具列表)
|
||||
- [其他](#其他)
|
||||
|
||||
## 功能展示
|
||||
|
||||
在线站点:<a href="https://tools.ranblogs.com" target="_blank">Tools-Web</a>
|
||||
|
||||
## 开始使用
|
||||
|
||||
### Docker部署
|
||||
|
||||
```
|
||||
docker run -d --name tools-web --restart unless-stopped -p 8080:80 docker0796/tools-web:latest
|
||||
```
|
||||
|
||||
访问:`http://127.0.0.1:8080`
|
||||
|
||||
|
||||
### 手动部署
|
||||
|
||||
安装`pnpm`
|
||||
```
|
||||
npm install pnpm -g
|
||||
```
|
||||
@@ -35,15 +73,87 @@ pnpm dev
|
||||
|
||||
打包
|
||||
```
|
||||
pnpm build:dev
|
||||
pnpm build
|
||||
```
|
||||
|
||||
# 技术栈
|
||||
## 工具列表
|
||||
|
||||
- 编程语言: TypeScript + JavaScript
|
||||
- 构件工具: Vite4.x
|
||||
- 前端框架: Vue3.x
|
||||
- 路由工具: Vue Router
|
||||
- 状态管理: Pinia
|
||||
- Ui框架: ElementPlus + Tailwindcss
|
||||
- Http工具: Axios
|
||||
- 开发运维
|
||||
- 随机密码生成
|
||||
- URL编码/解码
|
||||
- UUID生成器
|
||||
- 时间戳转换
|
||||
- MD5在线加密
|
||||
- Json在线转换
|
||||
- 正则测试工具
|
||||
- Unicode转中文
|
||||
- HTTP状态码
|
||||
- JWT解析
|
||||
- html实体转义
|
||||
- js代码格式化/压缩
|
||||
- Html代码格式化
|
||||
- Css代码格式化/压缩
|
||||
|
||||
- 文本处理
|
||||
- 文本对比
|
||||
- markdown编辑器
|
||||
- ASCII字形生成器
|
||||
- 文本去重
|
||||
- 在线文本编辑/HTML获取
|
||||
- 字数统计
|
||||
|
||||
- 教育学术
|
||||
- 单位换算
|
||||
- 摩斯电码
|
||||
- 常用进制转换
|
||||
- ASCII码表
|
||||
- 长度单位转换
|
||||
- 面积单位转换
|
||||
- 重量单位转换
|
||||
- 时间单位转换
|
||||
- 温度单位转换
|
||||
- 压力单位转换
|
||||
- 热量单位转换
|
||||
- 功率单位转换
|
||||
|
||||
- 图片处理
|
||||
- 二维码生成
|
||||
- 在线图片处理
|
||||
- 文本转图片
|
||||
- 图片分割
|
||||
|
||||
- 数据图表
|
||||
- 柱状图
|
||||
- 折线图
|
||||
- 饼图
|
||||
- 散点图
|
||||
|
||||
- 选择随机
|
||||
- 生成随机数
|
||||
- 帮我决定
|
||||
- 抛硬币
|
||||
- 投骰子
|
||||
|
||||
- 其他工具
|
||||
- 数字转金额大写
|
||||
- 手持弹幕
|
||||
- 色板
|
||||
- Color选择器
|
||||
|
||||
## 其他
|
||||
|
||||
Q: 我应该如何添加新功能?
|
||||
|
||||
A:
|
||||
- 在`components/Tools/tools.ts`文件中添加工具信息
|
||||
- 在`router/router.ts`中添加路由
|
||||
- 拷贝示例目录`components/Tools/Example`修改名称,在这个拷贝出来的目录中开发工具即可
|
||||
|
||||
<br/>
|
||||
|
||||
Q: 我可以参与开发 `Tools-Web` 吗?
|
||||
|
||||
A: 当然可以,随时欢迎提交 `PR`
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/assets/logo.png" />
|
||||
<link rel="shortcut icon" type="image/svg+xml" href="/src/assets/logo.png" />
|
||||
<link rel="shortcut icon" type="image/svg+xml" href="/public/favicon.ico" />
|
||||
<link rel="canonical" href="https://tools.ranblogs.com/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta name="keywords" content="">
|
||||
@@ -13,21 +14,12 @@
|
||||
<meta property="og:site_name" content="">
|
||||
<meta property="og:description" content="">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/logo.png">
|
||||
<meta property="og:url" content="https://tools.ranblogs.com/">
|
||||
<meta property="og:image" content="/src/assets/logo.png">
|
||||
<meta property="og:url" content=""><!-- 可选:这里填写自己的访问地址 -->
|
||||
<title></title>
|
||||
<!-- 百度统计 -->
|
||||
<script>
|
||||
var _hmt = _hmt || [];
|
||||
(function() {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?a94ed52bb5ed2bd9fdbad5b7468a9338";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body style="background-color: var(--base-gray)">
|
||||
<body class="bg-[--base-gray]">
|
||||
<body>
|
||||
<!--[if lt IE 9]>
|
||||
<div class="notice chromeframe">您的浏览器版本<strong>很旧很旧</strong>,为了正常地访问网站,请升级您的浏览器</div>
|
||||
<![endif]-->
|
||||
|
||||
@@ -1,544 +0,0 @@
|
||||
//获取tools
|
||||
function getTools() {
|
||||
return [
|
||||
{
|
||||
id: 1,
|
||||
title: '时间戳转换',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/Time.png',
|
||||
desc: '在线时间戳转换工具以及获取当前时间戳',
|
||||
url: '/timetran',
|
||||
cateId: 2,
|
||||
cate: '开发运维',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'MD5在线加密',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/Password%20fishing.png',
|
||||
desc: 'MD5在线加密,长度包含32位、16位',
|
||||
url: '/MD5',
|
||||
cateId: 2,
|
||||
cate: '开发运维',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'Json在线转换',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/json.png',
|
||||
desc: '在线编辑json, 语法检查',
|
||||
url: '/json',
|
||||
cateId: 2,
|
||||
cate: '开发运维',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: '正则测试工具',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/reg.png',
|
||||
desc: '正则表达式测试工具, 常用正则表达式',
|
||||
url: '/reg',
|
||||
cateId: 2,
|
||||
cate: '开发运维',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'Unicode转中文',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/union.png',
|
||||
desc: 'Unicode和中文的相互转换',
|
||||
url: '/unicode',
|
||||
cateId: 2,
|
||||
cate: '开发运维',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: '字数统计',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/ico_%E6%95%B0%E6%8D%AE%E7%BB%9F%E8%AE%A1_%E5%B7%A5%E4%BD%9C%E9%87%8F%E7%BB%9F%E8%AE%A1.png',
|
||||
desc: '在线统计字符串的字数、段落、标点符号数量',
|
||||
url: '/wordCount',
|
||||
cateId: 3,
|
||||
cate: '文本处理',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'IP查询',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/IP.png',
|
||||
desc: '在线查询ip地址、ip归属地',
|
||||
url: '/ip',
|
||||
cateId: 6,
|
||||
cate: '查询相关',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: '常用进制转换',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E8%BF%9B%E5%88%B6%E8%BD%AC%E6%8D%A2.png',
|
||||
desc: '在线进制转换工具,可在2到64进制之间相互转换',
|
||||
url: '/scaletran',
|
||||
cateId: 4,
|
||||
cate: '教育学术',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: '在线图片处理',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/img.png',
|
||||
desc: '在线图片裁剪,标注,滤镜,画等操作',
|
||||
url: '/signimage',
|
||||
cateId: 5,
|
||||
cate: '设计工具',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: '随机密码生成',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/keywords.png',
|
||||
desc: '密码生成器、随机字符串生成,批量生成',
|
||||
url: '/random_password',
|
||||
cateId: 2,
|
||||
cate: '开发运维',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'URL编码/解码',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/url.png',
|
||||
desc: 'URL在线编码解码工具(UrlEncode编码 和 UrlDecode解码)',
|
||||
url: '/urlencode',
|
||||
cateId: 2,
|
||||
cate: '开发运维',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'UUID生成器',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E7%BA%BF%E6%80%A7-%E7%94%9F%E6%88%90.png',
|
||||
desc: '批量生成UUID',
|
||||
url: '/uuid',
|
||||
cateId: 2,
|
||||
cate: '开发运维',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: '手持弹幕',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E5%BC%B9%E5%B9%95.png',
|
||||
desc: '手持滚动弹幕',
|
||||
url: '/barrage',
|
||||
cateId: 7,
|
||||
cate: '其他',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'ASCII码表',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/icon_%E7%BC%96%E7%A0%81%E5%AF%B9%E7%85%A7.png',
|
||||
desc: 'ASCII码表,控制代码、标准ASCII字符和非标准ASCII字符对照表',
|
||||
url: '/ascii',
|
||||
cateId: 2,
|
||||
cate: '开发运维'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: '单位换算',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E5%8D%95%E4%BD%8D%E6%8D%A2%E7%AE%97.png',
|
||||
desc: '在线重量、长度、面积、时间、角度、速度、温度、压力、热量、功率等换算',
|
||||
url: '/unit',
|
||||
cateId: 4,
|
||||
cate: '教育学术'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: '色板',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E8%B0%83%E8%89%B2%E6%9D%BF.png',
|
||||
desc: '包含纯色、渐变与阶梯色和常用色彩组合',
|
||||
url: '/palettes',
|
||||
cateId: 5,
|
||||
cate: '设计工具'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: '二维码生成',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E4%BA%8C%E7%BB%B4%E7%A0%81.png',
|
||||
desc: '在线生成带logo、透明、艺术的二维码',
|
||||
url: '/qrcode',
|
||||
cateId: 5,
|
||||
cate: '设计工具'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: '帮我决定',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/choose.png',
|
||||
desc: '选择困难,难以决定,今天吃什么,现在做什么,自定义选项都给你安排的明明白白',
|
||||
url: '/decision',
|
||||
cateId: 7,
|
||||
cate: '其他'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: '摩斯电码',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/medium.png',
|
||||
desc: '支持中文的摩斯电码编码解码',
|
||||
url: '/morse',
|
||||
cateId: 3,
|
||||
cate: '文本处理'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: '生成随机数',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/randomnum.png',
|
||||
desc: '可定制范围内进行随机数字,可用于抽奖、点名等用途',
|
||||
url: '/random',
|
||||
cateId: 7,
|
||||
cate: '其他'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: '数字转金额大写',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/text%20recognition.png',
|
||||
desc: '在线数字一键转换成人民币大写,中文大写转换数字',
|
||||
url: '/numberToChinese',
|
||||
cateId: 7,
|
||||
cate: '其他'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: '文本对比',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/diff.png',
|
||||
desc: '文本差异比对支持中文、英文、代码比对',
|
||||
url: '/diff',
|
||||
cateId: 3,
|
||||
cate: '文本处理'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'markdown编辑器',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/file-markdown-fill.png',
|
||||
desc: '在线创建或编辑markdown, 实时预览,导出markdown',
|
||||
url: '/markdown',
|
||||
cateId: 3,
|
||||
cate: '文本处理'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: '文本转图片',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/text_to_img.png',
|
||||
desc: '把文本转换成图片,生成长图,具有超多个性文字排版',
|
||||
url: '/textToImg',
|
||||
cateId: 3,
|
||||
cate: '文本处理'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
//获取tools分类已经对应的工具
|
||||
function getToolsCate() {
|
||||
return [
|
||||
{
|
||||
id: 2,
|
||||
title: '开发运维',
|
||||
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: 'ASCII码表',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/icon_%E7%BC%96%E7%A0%81%E5%AF%B9%E7%85%A7.png',
|
||||
desc: 'ASCII码表,控制代码、标准ASCII字符和非标准ASCII字符对照表',
|
||||
url: '/ascii',
|
||||
cateId: 2,
|
||||
cate: '开发运维'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: '时间戳转换',
|
||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/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/text_to_img.png',
|
||||
desc: '把文本转换成图片,生成长图,具有超多个性文字排版',
|
||||
url: '/textToImg',
|
||||
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: 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: 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: 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 }
|
||||
},
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
@@ -5,59 +5,69 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build:dev": "vue-tsc && vite build --mode development",
|
||||
"build:pro": "vue-tsc && vite build --mode production",
|
||||
"preview": "vite preview",
|
||||
"preinstall": "node ./scripts/preinstall.js"
|
||||
"build": "vue-tsc --noEmit && node --max_old_space_size=8192 node_modules/vite/bin/vite.js build --mode development",
|
||||
"build:pro": "vue-tsc --noEmit && node --max_old_space_size=8192 node_modules/vite/bin/vite.js build --mode production",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/commands": "^6.3.0",
|
||||
"@codemirror/commands": "^6.3.2",
|
||||
"@codemirror/lang-javascript": "^6.2.1",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/language": "^6.9.2",
|
||||
"@codemirror/language": "^6.9.3",
|
||||
"@codemirror/lint": "^6.4.2",
|
||||
"@codemirror/search": "^6.5.4",
|
||||
"@codemirror/state": "^6.3.3",
|
||||
"@codemirror/search": "^6.5.5",
|
||||
"@codemirror/state": "^6.4.1",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@codemirror/view": "^6.22.0",
|
||||
"@element-plus/icons-vue": "^2.1.0",
|
||||
"@codemirror/view": "^6.22.1",
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@kangc/v-md-editor": "^2.3.18",
|
||||
"@toast-ui/editor": "^3.2.2",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"axios": "^1.6.2",
|
||||
"element-plus": "^2.4.2",
|
||||
"colord": "^2.9.3",
|
||||
"csso": "^5.0.5",
|
||||
"default-passive-events": "^2.0.0",
|
||||
"echarts": "^5.5.0",
|
||||
"element-plus": "^2.4.3",
|
||||
"figlet": "^1.7.0",
|
||||
"highlight.js": "^11.9.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"imorse": "^1.0.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"prettier": "^3.3.2",
|
||||
"prismjs": "^1.29.0",
|
||||
"qrcode-vue3": "^1.6.8",
|
||||
"sass": "^1.69.5",
|
||||
"sass-loader": "^13.3.2",
|
||||
"terser": "^5.31.1",
|
||||
"ts-md5": "^1.3.1",
|
||||
"tui-image-editor": "^3.15.3",
|
||||
"uuid": "^9.0.1",
|
||||
"v-code-diff": "^1.8.0",
|
||||
"vite-plugin-seo-prerender": "^0.5.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue": "^3.3.10",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-codemirror": "^6.1.1",
|
||||
"vue3-danmaku": "^1.6.0"
|
||||
"vue3-danmaku": "^1.6.0",
|
||||
"x-data-spreadsheet": "^1.1.9",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue": "^4.5.1",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"fast-glob": "^3.3.2",
|
||||
"mockjs": "^1.1.0",
|
||||
"less": "^4.2.0",
|
||||
"path": "^0.12.7",
|
||||
"pinia": "^2.1.7",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.5",
|
||||
"typescript": "^5.3.2",
|
||||
"vite": "^4.5.1",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"vite-plugin-mock": "2.9.6",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vue-router": "^4.2.5",
|
||||
"vue-tsc": "^1.8.5"
|
||||
"vue-tsc": "^1.8.24"
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 795 KiB |
@@ -23,7 +23,7 @@ const componentStore = useComponentStore()
|
||||
v-model="componentStore.leftComDrawer"
|
||||
direction="ltr"
|
||||
>
|
||||
<Left bgColor="#fff"></Left>
|
||||
<Left></Left>
|
||||
</el-drawer>
|
||||
|
||||
<!-- right -->
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
//统一管理咱们项目用户相关的接口
|
||||
import request from '@/utils/request'
|
||||
|
||||
import type {
|
||||
ToolsReqData,
|
||||
ToolsResponseData,
|
||||
ToolCateResponseData,
|
||||
ToolRecommendResponseData
|
||||
} from './type'
|
||||
|
||||
//项目用户相关的请求地址
|
||||
|
||||
enum API {
|
||||
//获取tools
|
||||
GET_TOOLS = '/tools',
|
||||
//获取tool cate
|
||||
GET_TOOL_CATES = '/tool/cates',
|
||||
//获取推荐
|
||||
GET_TOOL_RECOMMEND = '/tool/recommends',
|
||||
}
|
||||
//获取tools
|
||||
export const getTools = (data: ToolsReqData) => request.get<any, ToolsResponseData>(API.GET_TOOLS, {
|
||||
params: data
|
||||
})
|
||||
//获取tool cate
|
||||
export const getToolCate = () => request.get<any, ToolCateResponseData>(API.GET_TOOL_CATES)
|
||||
//获取推荐
|
||||
export const getToolRecommend = () => request.get<any, ToolRecommendResponseData>(API.GET_TOOL_RECOMMEND)
|
||||
@@ -0,0 +1,16 @@
|
||||
//统一管理咱们项目用户相关的接口
|
||||
import request from '@/utils/request'
|
||||
|
||||
import type {
|
||||
WebInfoReqData,
|
||||
getWebInfoResponseData
|
||||
} from './type'
|
||||
|
||||
//项目用户相关的请求地址
|
||||
|
||||
enum API {
|
||||
//获取网站信息
|
||||
WEBINFO = '/webinfo'
|
||||
}
|
||||
//获取ip
|
||||
export const fetchWebInfo = (data: WebInfoReqData) => request.get<any, getWebInfoResponseData>(API.WEBINFO + '/' + data.link)
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ResponseData } from "../common"
|
||||
|
||||
export interface WebInfoReqData {
|
||||
link: string
|
||||
}
|
||||
|
||||
export interface WebInfo {
|
||||
link: string,
|
||||
logo: string,
|
||||
title: string,
|
||||
keywords: string,
|
||||
desc: string
|
||||
}
|
||||
|
||||
//响应格式
|
||||
export interface getWebInfoResponseData extends ResponseData {
|
||||
data: WebInfo
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -1,34 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
// import { reactive } from 'vue'
|
||||
// import { copy } from '@/utils/string'
|
||||
// const info = reactive({
|
||||
// title: "关于本站",
|
||||
// })
|
||||
|
||||
//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="https://txc.qq.com/products/619520">https://txc.qq.com/products/619520</el-link></p>
|
||||
<br>
|
||||
<p>邮箱:taoran0796@163.com</p>
|
||||
<br>
|
||||
<div class="flex flex-col mt-8 flex-1 items-center bg-white rounded-md p-10">
|
||||
<div class="p-5 w-2/3">
|
||||
<h1 class="text-2xl font-bold">关于 Tools-Web</h1>
|
||||
<p class="mt-6">
|
||||
<el-text><el-link href="https://github.com/taoran1401/tools-web" target="_blank" type="primary">Tools-Web</el-link> 是一款免费开源的轻量在线工具箱,包含开发、文本、媒体、图表、生活、查询等实用工具;如果对您有帮助,请将其分享给您的朋友,并且添加到收藏夹中!顺便再点个⭐️吧!</el-text>
|
||||
</p>
|
||||
|
||||
<h1 class="text-2xl font-bold mt-6 mb-6">技术</h1>
|
||||
<p>
|
||||
<el-text>主要基于Vite + Vue + ElementPlus + Typescript + TailwindCss开发,某些工具使用了第三方开源库,您可以在仓库的 <el-link href="https://github.com/taoran1401/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/taoran1401/tools-web/issues/new" class="">issues</el-link> 中提交错误报告。</el-text>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.title{
|
||||
/* rgb(51, 54, 57) */
|
||||
}
|
||||
</style>
|
||||
@@ -1,22 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { RouterLink } from "vue-router"
|
||||
import { Star } from '@element-plus/icons-vue'
|
||||
// import { Star } from '@element-plus/icons-vue'
|
||||
import { useToolsStore } from '@/store/modules/tools'
|
||||
// import { ElMessage } from 'element-plus'
|
||||
import { useRoute } from "vue-router"
|
||||
//store
|
||||
const toolsStore = useToolsStore()
|
||||
const route = useRoute()
|
||||
const getToolsCate = async () => {
|
||||
try {
|
||||
await toolsStore.getToolCate()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
// const getToolsCate = async () => {
|
||||
// try {
|
||||
// await toolsStore.getToolCate()
|
||||
// } catch (error: any) {
|
||||
// ElMessage.error(error.message)
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getToolsCate()
|
||||
// getToolsCate()
|
||||
if (route.query && route.query.value) {//底部导航跳转过来的则定位到响应位置
|
||||
document?.querySelector('#' + `${route.query.value}`)?.scrollIntoView();
|
||||
} else {//其他位置跳转过来不需要定位的则定位到顶部
|
||||
@@ -27,39 +29,6 @@ onMounted(() => {
|
||||
|
||||
<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.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">
|
||||
<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"/>
|
||||
</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 -->
|
||||
@@ -77,7 +46,7 @@ onMounted(() => {
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<el-text size="small">{{ item.cate }}</el-text>
|
||||
<el-button :icon="Star" circle size="small"/>
|
||||
<!-- <el-button :icon="Star" circle size="small" @click.prevent="collect(item.id)" type="warning" :plain="!toolsStore.collectIds.includes(item.id)"/> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -89,6 +58,9 @@ onMounted(() => {
|
||||
<div class="w-[24%] c-md:w-[24%] c-sm:w-[32%] "></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 返回顶部 -->
|
||||
<el-backtop :right="10" :bottom="50" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,8 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
// import { Star } from '@element-plus/icons-vue'
|
||||
import { onMounted, reactive } from 'vue';
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useToolsStore } from '@/store/modules/tools'
|
||||
// import { ElMessageBox } from 'element-plus'
|
||||
import {rtrim} from '@/utils/string'
|
||||
const props = defineProps({
|
||||
title: String
|
||||
title: String,
|
||||
id: Number
|
||||
})
|
||||
const route = useRoute()
|
||||
//查询参数
|
||||
const searchParam = reactive({
|
||||
cateId: 0,
|
||||
title: '',
|
||||
route: ''
|
||||
})
|
||||
//store
|
||||
const toolsStore = useToolsStore()
|
||||
|
||||
//根据路由查询tool id
|
||||
const getToolInfo = async () => {
|
||||
let routeStr = route.path
|
||||
searchParam.route = rtrim(routeStr, '/')
|
||||
await toolsStore.getToolInfo(searchParam)
|
||||
}
|
||||
//收藏
|
||||
// const collect = () => {
|
||||
// ElMessageBox({
|
||||
// title: '提示',
|
||||
// message: '请使用快捷键`Ctrl+D`进行收藏'
|
||||
// })
|
||||
// }
|
||||
|
||||
onMounted(() => {
|
||||
getToolInfo()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -19,10 +53,6 @@ const props = defineProps({
|
||||
<div class="text-xl">
|
||||
{{ props.title }}
|
||||
</div>
|
||||
<div>
|
||||
<el-button type="primary" plain>收藏</el-button>
|
||||
<!-- <el-button type="danger">取消收藏</el-button> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
const beianNo = ref('粤ICP备19038804号-1')
|
||||
const copyright = ref('ranblogs.com')
|
||||
import { ref } from 'vue';
|
||||
const appDesc = ref(import.meta.env.VITE_APP_DESC || '')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-white w-full rounded-2xl z-10 p-5 text-center">
|
||||
Copyright © 2023 · {{ copyright }} <a href="https://beian.miit.gov.cn/" target="_blank">{{ beianNo }}</a>
|
||||
<div class="w-full rounded-2xl z-10 p-5 text-center">
|
||||
<el-text>{{ appDesc }} © 2024 <a href="https://github.com/taoran1401/tools-web" target="_blank" class="text-blue-700">Tools-Web</a></el-text>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -3,11 +3,10 @@ import { ref, reactive, onMounted } from 'vue'
|
||||
import { Search } from '@element-plus/icons-vue';
|
||||
import { useToolsStore } from '@/store/modules/tools'
|
||||
import { useComponentStore } from '@/store/modules/component'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import 'element-plus/theme-chalk/display.css'
|
||||
import { ToolsInfo } from '@/api/tools/type';
|
||||
import router from '@/router';
|
||||
import { ToolsInfo } from '@/components/Tools/tools.type.ts';
|
||||
|
||||
import router from '@/router';
|
||||
// const isNavDrawer = ref(false)
|
||||
const loading = ref(false)
|
||||
const options = ref<ToolsInfo[]>([])
|
||||
@@ -17,16 +16,9 @@ const componentStore = useComponentStore()
|
||||
//查询参数
|
||||
const searchParam = reactive({
|
||||
cateId: 0,
|
||||
title: ''
|
||||
title: '',
|
||||
route: '',
|
||||
})
|
||||
//获取分类
|
||||
const getToolCates = async () => {
|
||||
try {
|
||||
await toolsStore.getToolCate()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
//search
|
||||
// const search = async () => {
|
||||
@@ -50,33 +42,40 @@ const searchTools = async (query: string) => {
|
||||
loading.value = true
|
||||
if (query) {
|
||||
searchParam.title = query
|
||||
await toolsStore.getTools(searchParam)
|
||||
options.value = await toolsStore.getTools(searchParam)
|
||||
} else {
|
||||
options.value = []
|
||||
}
|
||||
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) => {
|
||||
router.push(url)
|
||||
}
|
||||
|
||||
//收藏
|
||||
const collect = () => {
|
||||
ElMessageBox({
|
||||
title: '提示',
|
||||
message: '请使用快捷键`Ctrl+D`进行收藏'
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getToolCates()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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">
|
||||
<header class="h-24 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">
|
||||
<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">
|
||||
<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,8 +94,17 @@ onMounted(() => {
|
||||
<path fill="#444" fill-opacity=".9" d="M895.936 256l-768-0.896 0.128-64L896 192l-0.064 64zM179.2 689.152l202.688-152a32 32 0 0 0 0-51.2L179.2 333.952a32 32 0 0 0-51.2 25.6v304a32 32 0 0 0 51.2 25.6z m12.8-89.6v-176l117.312 88L192 599.552zM896 544H480v-64H896v64z m-0.064 288l-768-0.896 0.128-64L896 768l-0.064 64z" p-id="7619"></path>
|
||||
</svg>
|
||||
</Transition>
|
||||
|
||||
<div class="ml-3 mr-1">
|
||||
<router-link to="/">
|
||||
<svg t="1715590310537" class="icon" viewBox="0 0 1053 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4287" width="25" height="25">
|
||||
<path d="M526.63454 58.067422a21.503527 21.503527 0 0 1-27.501109 0.175539l474.979265 381.50475a31.538506 31.538506 0 1 0 39.496274-49.150919L538.629705 9.004273a41.602742 41.602742 0 0 0-51.988799 0.234052L11.573871 398.671586a31.538506 31.538506 0 1 0 39.993635 48.79984L526.63454 58.067422z" fill="#2c2c2c" p-id="4288"></path><path d="M1013.60897 564.087832L538.629705 182.583083a41.602742 41.602742 0 0 0-51.988799 0.204795L11.573871 572.191883a31.538506 31.538506 0 1 0 39.993635 48.79984l66.763331-54.709653v361.61033A94.644775 94.644775 0 0 0 213.004869 1022.537175H441.732179v-247.275931a15.79851 15.79851 0 0 1 15.739997-15.79851h110.472541c8.68918 0 15.769253 7.080073 15.769253 15.79851v247.275931H812.441281a94.615518 94.615518 0 0 0 94.644775-94.615518V559.465305l66.997383 53.831959a31.538506 31.538506 0 1 0 39.525531-49.209432z m-169.629183 363.804568c0 17.436874-14.101633 31.538506-31.567763 31.538506h-165.591785v-184.198919a78.875522 78.875522 0 0 0-78.875522-78.904778h-110.472541a78.875522 78.875522 0 0 0-78.846265 78.904778v184.198919h-165.591786c-17.46613 0-31.597019-14.130889-31.597019-31.538506V514.527323L512.883986 242.851471l331.095801 265.941578v419.128608z" fill="#2c2c2c" p-id="4289" stroke="black" stroke-width="10"></path>
|
||||
</svg>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="md:w-80 c-xs:72">
|
||||
<div class="c-xs:w-[85%] w-full mr-3">
|
||||
<!-- <el-input
|
||||
v-model="searchParam.title"
|
||||
placeholder="搜索工具"
|
||||
@@ -117,12 +125,13 @@ onMounted(() => {
|
||||
placeholder="输入关键词搜索,如文本、json、图片等"
|
||||
:remote-method="searchTools"
|
||||
:loading="loading"
|
||||
class="ml-3 mr-3 md:w-80 c-xs:w-60"
|
||||
class="ml-3"
|
||||
size="large"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in toolsStore.list"
|
||||
v-for="item in options"
|
||||
:key="item.id"
|
||||
:label="item.title"
|
||||
:label="item.title + ' - ' + item.desc"
|
||||
:value="item.id"
|
||||
@click="optionClick(item.url)"
|
||||
>
|
||||
@@ -133,12 +142,36 @@ onMounted(() => {
|
||||
|
||||
<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">
|
||||
<li class="hover:text-blue-500">
|
||||
<el-button link size="large" @click="collect">收藏本站</el-button>
|
||||
<!-- about -->
|
||||
<li class="ml-3 c-xs:hidden">
|
||||
<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>
|
||||
<!-- github -->
|
||||
<li class="ml-3">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="GitHub仓库"
|
||||
placement="bottom"
|
||||
>
|
||||
<a href="https://github.com/taoran1401/tools-web" target="_blank">
|
||||
<svg t="1715594665374" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4334" width="33" height="33"><path d="M512 85.333333C276.266667 85.333333 85.333333 276.266667 85.333333 512a426.410667 426.410667 0 0 0 291.754667 404.821333c21.333333 3.712 29.312-9.088 29.312-20.309333 0-10.112-0.554667-43.690667-0.554667-79.445333-107.178667 19.754667-134.912-26.112-143.445333-50.133334-4.821333-12.288-25.6-50.133333-43.733333-60.288-14.933333-7.978667-36.266667-27.733333-0.554667-28.245333 33.621333-0.554667 57.6 30.933333 65.621333 43.733333 38.4 64.512 99.754667 46.378667 124.245334 35.2 3.754667-27.733333 14.933333-46.378667 27.221333-57.045333-94.933333-10.666667-194.133333-47.488-194.133333-210.688 0-46.421333 16.512-84.778667 43.733333-114.688-4.266667-10.666667-19.2-54.4 4.266667-113.066667 0 0 35.712-11.178667 117.333333 43.776a395.946667 395.946667 0 0 1 106.666667-14.421333c36.266667 0 72.533333 4.778667 106.666666 14.378667 81.578667-55.466667 117.333333-43.690667 117.333334-43.690667 23.466667 58.666667 8.533333 102.4 4.266666 113.066667 27.178667 29.866667 43.733333 67.712 43.733334 114.645333 0 163.754667-99.712 200.021333-194.645334 210.688 15.445333 13.312 28.8 38.912 28.8 78.933333 0 57.045333-0.554667 102.912-0.554666 117.333334 0 11.178667 8.021333 24.490667 29.354666 20.224A427.349333 427.349333 0 0 0 938.666667 512c0-235.733333-190.933333-426.666667-426.666667-426.666667z" fill="#000000" p-id="4335"></path></svg>
|
||||
</a>
|
||||
</el-tooltip>
|
||||
</li>
|
||||
|
||||
<li class="hover:text-blue-500 c-xs:hidden">
|
||||
<el-button type="primary" class="ml-3 bg-gradient-to-r from-cyan-500 to-blue-500" @click="createUrlShortcut">保存到桌面</el-button>
|
||||
</li>
|
||||
<!-- <li class="ml-3 hover:text-blue-500"> -->
|
||||
<!-- <el-button round>登录/注册</el-button> -->
|
||||
<!-- </li> -->
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
@@ -153,5 +186,17 @@ onMounted(() => {
|
||||
.fold-leave-to {
|
||||
transform: translateX(20px);
|
||||
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>
|
||||
@@ -3,16 +3,13 @@
|
||||
import { onMounted, ref, reactive } from 'vue';
|
||||
import { useToolsStore } from '@/store/modules/tools'
|
||||
import { useRouter, useRoute } from "vue-router"
|
||||
const props = defineProps({
|
||||
bgColor: String,
|
||||
})
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const appName = ref(import.meta.env.VITE_APP_TITLE || '工具坊')
|
||||
const appNet = ref(import.meta.env.VITE_APP_WEBSITE || '')
|
||||
const appNet = ref(import.meta.env.VITE_APP_DESC || '')
|
||||
//菜单选中
|
||||
const defaultActive = ref('collect')
|
||||
const defaultActive = ref('')
|
||||
//默认展开的菜单
|
||||
const defaultOpeneds = ['cate']
|
||||
//store
|
||||
@@ -56,8 +53,9 @@ const gotoAbout = () => {
|
||||
router.push('about')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getToolCates()
|
||||
onMounted(async () => {
|
||||
await getToolCates()
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -66,7 +64,7 @@ onMounted(() => {
|
||||
<el-scrollbar>
|
||||
<!-- logo -->
|
||||
<div class="flex justify-center">
|
||||
<router-link class="flex flex-row h-20" to="/">
|
||||
<router-link class="flex flex-row h-24" to="/">
|
||||
<img class="h-12 w-auto rounded-lg mr-2 mt-auto mb-auto" src="@/assets/logo.png" :alt="appNet">
|
||||
<div class="flex flex-col mt-auto mb-auto">
|
||||
<div class="text-2xl">{{ appName }}</div>
|
||||
@@ -81,18 +79,10 @@ onMounted(() => {
|
||||
class="w-[200px]"
|
||||
:default-active="defaultActive"
|
||||
:default-openeds="defaultOpeneds"
|
||||
:background-color="props.bgColor ?? '#f5f5fb'"
|
||||
background-color="transparent"
|
||||
@open="handleOpen"
|
||||
@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">
|
||||
<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">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Tools } from '@element-plus/icons-vue'
|
||||
import { Message } from '@element-plus/icons-vue'
|
||||
import { reactive, onMounted } from 'vue';
|
||||
import { useToolsStore } from '@/store/modules/tools'
|
||||
const info = reactive({
|
||||
@@ -21,42 +20,18 @@ const info = reactive({
|
||||
//store
|
||||
const toolsStore = useToolsStore()
|
||||
|
||||
//获取随机推荐
|
||||
const getToolRecommend = async () => {
|
||||
try {
|
||||
await toolsStore.getToolRecommend()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
//点击走马灯
|
||||
const clickCarousel = (url: string) => {
|
||||
let w = window.open('')
|
||||
w?.document.write(`<!DOCTYPE html><html><body ><img src='${url}'/></body></html>`)
|
||||
}
|
||||
|
||||
//反馈
|
||||
const feedback = () => {
|
||||
window.open(info.feedbackUrl)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getToolRecommend()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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 -->
|
||||
<div class="mt-3" v-if="info.advShow == 'true'">
|
||||
<el-carousel
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<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,6 +1,7 @@
|
||||
<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: "ASCII码表",
|
||||
@@ -311,6 +312,14 @@ const tableDataNonStandard = reactive([
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是一种字符编码标准,它使用一个字节(8位二进制数)来表示128个不同的字符。这些字符包括33个控制字符(具有特殊功能但不可直接显示的字符)和95个可打印字符。ASCII码表中的字符从0到127编号,其中0代表空字符(NULL),1代表开始头(SOH),2代表正文开始(STX),依此类推。ASCII码表的最小值是0,代表空字符。
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive,ref,onMounted } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
import { copy } from '@/utils/string'
|
||||
import figlet from 'figlet';
|
||||
const info = reactive({
|
||||
title: "ASCII字形生成器",
|
||||
})
|
||||
figlet.defaults({ fontPath: '//unpkg.com/[email protected]/fonts/' });
|
||||
|
||||
const content = ref('Tools-Web')
|
||||
const contentRes = ref('')
|
||||
const fontStyle = ref('Big')
|
||||
const options = ref([
|
||||
{
|
||||
value: 'Alpha',
|
||||
label: 'Alpha',
|
||||
},
|
||||
{
|
||||
value: 'Avatar',
|
||||
label: 'Avatar',
|
||||
},
|
||||
{
|
||||
value: 'Banner',
|
||||
label: 'Banner',
|
||||
},
|
||||
{
|
||||
value: 'Banner3-D',
|
||||
label: 'Banner3-D',
|
||||
},
|
||||
{
|
||||
value: 'Basic',
|
||||
label: 'Basic',
|
||||
},
|
||||
{
|
||||
value: 'Bear',
|
||||
label: 'Bear',
|
||||
},
|
||||
{
|
||||
value: 'Big Money-ne',
|
||||
label: 'Big Money-ne',
|
||||
},
|
||||
{
|
||||
value: 'Big',
|
||||
label: 'Big',
|
||||
},
|
||||
{
|
||||
value: 'Block',
|
||||
label: 'Block',
|
||||
},
|
||||
{
|
||||
value: 'Epic',
|
||||
label: 'Epic',
|
||||
},
|
||||
{
|
||||
value: 'Ghost',
|
||||
label: 'Ghost',
|
||||
},
|
||||
{
|
||||
value: 'Knob',
|
||||
label: 'Knob',
|
||||
},
|
||||
{
|
||||
value: 'Linux',
|
||||
label: 'Linux',
|
||||
},
|
||||
{
|
||||
value: 'Mini',
|
||||
label: 'Mini',
|
||||
},
|
||||
{
|
||||
value: 'Mirror',
|
||||
label: 'Mirror',
|
||||
},
|
||||
{
|
||||
value: 'Peaks',
|
||||
label: 'Peaks',
|
||||
},
|
||||
{
|
||||
value: 'Slant',
|
||||
label: 'Slant',
|
||||
},
|
||||
{
|
||||
value: 'Small',
|
||||
label: 'Small',
|
||||
},
|
||||
{
|
||||
value: 'Stellar',
|
||||
label: 'Stellar',
|
||||
},
|
||||
{
|
||||
value: 'Thin',
|
||||
label: 'Thin',
|
||||
},
|
||||
{
|
||||
value: 'Wow',
|
||||
label: 'Wow',
|
||||
},
|
||||
])
|
||||
|
||||
const gen = () => {
|
||||
figlet(content.value,
|
||||
{
|
||||
font: fontStyle.value,
|
||||
width: 120,
|
||||
horizontalLayout: "default",
|
||||
verticalLayout: "default",
|
||||
whitespaceBreak: true,
|
||||
},
|
||||
function (err, data) {
|
||||
if (err) {
|
||||
console.log(';----', err)
|
||||
return;
|
||||
}
|
||||
contentRes.value = data
|
||||
})
|
||||
}
|
||||
|
||||
//清空输入框
|
||||
const clear = () => {
|
||||
content.value = ''
|
||||
contentRes.value = ''
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gen()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl bg-white ">
|
||||
<div class="mb-6">
|
||||
<el-input v-model="content" :rows="4" type="textarea" placeholder="请输入内容"></el-input>
|
||||
<div class="mt-3 flex items-center">
|
||||
<div class="flex items-center mr-3 w-36">
|
||||
<div class="w-10">
|
||||
<el-text class="">风格</el-text>
|
||||
</div>
|
||||
<el-select
|
||||
v-model="fontStyle"
|
||||
size="default"
|
||||
class="ml-2"
|
||||
@change="gen"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<el-button type="primary" @click="gen()">生成</el-button>
|
||||
<el-button type="primary" @click="copy(contentRes)">复制结果</el-button>
|
||||
<el-button type="primary" @click="clear">清除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<pre tabindex="0"><code>{{ contentRes }}</code></pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
好用的ASCII字形生成器,输入框中输入需要生成的字母;提供多种风格选择
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeMount, reactive,ref } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
// import { ElMessage } from 'element-plus'
|
||||
import vueDanmaku from 'vue3-danmaku' //https://www.npmjs.com/package/vue3-danmaku
|
||||
const info = reactive({
|
||||
@@ -114,6 +115,14 @@ onBeforeMount(() => {
|
||||
<el-text>双击可退出弹幕</el-text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
手持弹幕是一种新型的互动沟通工具,可以方便地为各种户外活动、演出嘉年华等活动增加趣味性和互动性。手持弹幕具有轻便、易携带、易操作等优点,可以让每个参与者都变成活动的一部分。同时,手持弹幕还可以通过预先编写的文本、表情等形式,表达参与者的情感和想法,实现沟通互动。在社交媒体时代,手持弹幕的使用也带来了更广泛的社交效应,增加了活动的互动性和传播度。无论是举办方还是参与者,手持弹幕都是一个非常有价值的互动工具。
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,491 @@
|
||||
<script setup lang="ts">
|
||||
import {ref, reactive, onMounted } from 'vue'
|
||||
import Spreadsheet from 'x-data-spreadsheet'
|
||||
import 'x-data-spreadsheet/dist/locale/zh-cn';
|
||||
import { UploadProps,UploadRawFile,genFileId } from 'element-plus'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
// import { copy } from '@/utils/string'
|
||||
import { toEchartsData, toSpreadsheetData } from '@/utils/echarts'
|
||||
import * as echarts from 'echarts'
|
||||
import * as XLSX from 'xlsx'
|
||||
const info = reactive({
|
||||
title: "柱状图",
|
||||
})
|
||||
|
||||
const chartDom = ref<HTMLElement|null>()
|
||||
const myChart = ref<echarts.ECharts>()
|
||||
const dataFileRef = ref()
|
||||
|
||||
const setOptionName = ref(1)
|
||||
//缩放比例
|
||||
const sacleSize = ref(100)
|
||||
//画布宽高
|
||||
const widthCanvas = ref(720)
|
||||
const heightCanvas = ref(400)
|
||||
//下载文件类型
|
||||
const downType = ref('1')
|
||||
//图形属性颜色
|
||||
const attrColor = ref('#5470c6')
|
||||
//标题位置
|
||||
const titlePos = ref('center')
|
||||
//标题
|
||||
const title = ref('Tools-Web')
|
||||
//副标题
|
||||
const subTitle = ref('在线图表制作工具')
|
||||
//显示标题 - 开关
|
||||
const titleSwitch = ref(true)
|
||||
//显示副标题 - 开关
|
||||
const subTitleSwitch = ref(true)
|
||||
|
||||
/** 水印 */
|
||||
const watermarkSwitch = ref(false) //开关
|
||||
const waterMarkText = ref('Tools-Web');
|
||||
|
||||
//创建水印
|
||||
const createWatermark = () => {
|
||||
let canvas = document.createElement('canvas');
|
||||
let ctx = canvas.getContext('2d');
|
||||
canvas.width = canvas.height = 100;
|
||||
ctx!.textAlign = 'center';
|
||||
ctx!.textBaseline = 'middle';
|
||||
ctx!.globalAlpha = 0.08;
|
||||
ctx!.font = '20px Microsoft Yahei';
|
||||
ctx!.translate(50, 50);
|
||||
ctx!.rotate(-Math.PI / 4);
|
||||
ctx!.fillText(waterMarkText.value, 0, 0);
|
||||
return canvas
|
||||
}
|
||||
|
||||
//操作图表
|
||||
const canvasHandle = (type) => {
|
||||
// let element = document.getElementById('main')
|
||||
let element = chartDom.value
|
||||
switch(type) {
|
||||
case "scale":
|
||||
//缩放画布
|
||||
let scale = sacleSize.value / 100
|
||||
element!.style.transform = `scale(${scale})`
|
||||
break
|
||||
case "size":
|
||||
//图表尺寸
|
||||
element!.style.width = widthCanvas.value + 'px';
|
||||
element!.style.height = heightCanvas.value + 'px';
|
||||
reloadCanvas()
|
||||
break
|
||||
case "title":
|
||||
let fakerTitle = ''
|
||||
let fakerSubTitle = ''
|
||||
//修改标题相关配置
|
||||
if (titleSwitch.value === true) {
|
||||
fakerTitle = title.value
|
||||
}
|
||||
if (subTitleSwitch.value === true) {
|
||||
fakerSubTitle = subTitle.value
|
||||
}
|
||||
myChart.value?.setOption({
|
||||
title: {
|
||||
text: fakerTitle,
|
||||
subtext: fakerSubTitle,
|
||||
left: titlePos.value
|
||||
}
|
||||
})
|
||||
break
|
||||
case "color":
|
||||
//图表属性颜色
|
||||
myChart.value?.setOption({
|
||||
series: [
|
||||
{
|
||||
itemStyle: {
|
||||
color: attrColor.value
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
break;
|
||||
case "watermark":
|
||||
//水印
|
||||
if (watermarkSwitch.value === true) {
|
||||
myChart.value?.setOption({
|
||||
backgroundColor: {
|
||||
image: createWatermark(),
|
||||
}
|
||||
})
|
||||
} else {
|
||||
myChart.value?.setOption({
|
||||
backgroundColor: '#fff'
|
||||
})
|
||||
}
|
||||
break;
|
||||
case "data":
|
||||
//更新数据
|
||||
myChart.value?.setOption({
|
||||
xAxis: {
|
||||
data: colunmData.value,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: valueData.value,
|
||||
}
|
||||
]
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
const handleChange = () => {
|
||||
|
||||
}
|
||||
|
||||
//数据
|
||||
const colunmData = ref(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']);
|
||||
const valueData = ref(['23', '24', '18', '25', '27', '28', '25']);
|
||||
//选项
|
||||
const option = {
|
||||
backgroundColor: '#fff',
|
||||
// backgroundColor: {
|
||||
// // type: 'pattern',
|
||||
// // image: canvas,
|
||||
// // repeat: 'repeat'
|
||||
// },
|
||||
title: {
|
||||
text: title.value,
|
||||
subtext: subTitle.value,
|
||||
left: titlePos.value
|
||||
},
|
||||
xAxis: {
|
||||
data: colunmData.value,
|
||||
},
|
||||
yAxis: {},
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
data: valueData.value,
|
||||
itemStyle: {
|
||||
color: attrColor.value
|
||||
}
|
||||
}
|
||||
],
|
||||
tooltip: {}
|
||||
};
|
||||
|
||||
//重新加载画布
|
||||
const reloadCanvas = () => {
|
||||
myChart.value = echarts.init(chartDom.value)
|
||||
myChart.value.clear();
|
||||
myChart.value.resize({
|
||||
width: widthCanvas.value,
|
||||
height: heightCanvas.value
|
||||
})
|
||||
myChart.value.setOption(option)
|
||||
}
|
||||
|
||||
//下载echarts图表图片
|
||||
const downEchartsImg = () => {
|
||||
let ext = downType.value == '1' ? 'png' : 'jpeg'
|
||||
let imgUrl = myChart.value?.getDataURL({
|
||||
type: ext as "png" | 'jpeg',
|
||||
pixelRatio: 2,
|
||||
})
|
||||
|
||||
if (imgUrl != undefined) {
|
||||
var downloadLink = document.createElement('a');
|
||||
downloadLink.href = imgUrl;
|
||||
downloadLink.download = 'echart.' + ext;
|
||||
downloadLink.click();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 上传、编辑数据 */
|
||||
//抽屉开关
|
||||
const drawer = ref(false)
|
||||
const rowsData = ref({})
|
||||
|
||||
const editData = () => {
|
||||
if (drawer.value == true) {
|
||||
drawer.value = false
|
||||
} else {
|
||||
drawer.value = true
|
||||
/** 在线excel */
|
||||
Spreadsheet.locale('zh-cn', (window.x_spreadsheet as any).$messages['zh-cn']);
|
||||
//const sheet = new Spreadsheet("#x-spreadsheet", {
|
||||
new Spreadsheet("#x-spreadsheet", {
|
||||
showToolbar: false, //隐藏顶部工具栏
|
||||
showBottomBar: false,//隐藏底部工具栏,
|
||||
view: {
|
||||
height: () => document.documentElement.clientHeight / 2,
|
||||
width: () => document.documentElement.clientWidth,
|
||||
}
|
||||
})
|
||||
.loadData({
|
||||
//样式
|
||||
styles: [
|
||||
{
|
||||
bgcolor: '#f4f5f8',
|
||||
textwrap: true,
|
||||
color: '#900b09',
|
||||
border: {
|
||||
top: ['thin', '#0366d6'],
|
||||
bottom: ['thin', '#0366d6'],
|
||||
right: ['thin', '#0366d6'],
|
||||
left: ['thin', '#0366d6'],
|
||||
},
|
||||
},
|
||||
],
|
||||
//数据
|
||||
rows: rowsData.value
|
||||
}) // load data
|
||||
.change(data => {
|
||||
//表格数据改变后触发
|
||||
//规则: 获取第一列和第二列的数据
|
||||
let tmp = toEchartsData(data)
|
||||
colunmData.value = tmp[0]
|
||||
valueData.value = tmp[1]
|
||||
canvasHandle('data')
|
||||
});
|
||||
// sheet.validate()
|
||||
}
|
||||
}
|
||||
|
||||
//上传数据文件
|
||||
const fileList = ref()
|
||||
const updateDataFile = async (params) => {
|
||||
const _file = params.file;
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = (ev) => {
|
||||
try {
|
||||
if (!ev.target) {
|
||||
return
|
||||
}
|
||||
const data = ev.target.result;
|
||||
const workbook = XLSX.read(data, {
|
||||
type: 'binary'
|
||||
});
|
||||
let useCount = 0;
|
||||
let tmpColumnData:any[] = [];
|
||||
let tmpValueData:any[] = [];
|
||||
for (let sheet in workbook.Sheets) {
|
||||
//这里只需要第一个sheet
|
||||
if (useCount > 0) {
|
||||
continue;
|
||||
}
|
||||
//循环读取每个文件
|
||||
const sheetArray = XLSX.utils.sheet_to_json(workbook.Sheets[sheet], {header: ['0', '1']});
|
||||
//若当前sheet没有数据,则continue
|
||||
if(sheetArray.length == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
for(let item in sheetArray){
|
||||
if (sheetArray[item] != undefined) {
|
||||
let tmp1 = sheetArray as []
|
||||
tmpColumnData.push(tmp1[item][0])
|
||||
tmpValueData.push(tmp1[item][1])
|
||||
}
|
||||
}
|
||||
useCount++
|
||||
}
|
||||
//更新数据
|
||||
colunmData.value = tmpColumnData
|
||||
valueData.value = tmpValueData
|
||||
//更新图表
|
||||
canvasHandle('data')
|
||||
//更新表格
|
||||
rowsData.value = toSpreadsheetData([
|
||||
colunmData, valueData
|
||||
])
|
||||
} catch (e) {
|
||||
console.log('error')
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
fileReader.readAsArrayBuffer(_file)
|
||||
}
|
||||
|
||||
//当超出限制时,执行的钩子函数
|
||||
//这里覆盖前一个文件
|
||||
const handleExceed: UploadProps['onExceed'] = (files) => {
|
||||
dataFileRef.value!.clearFiles()
|
||||
const file = files[0] as UploadRawFile
|
||||
file.uid = genFileId()
|
||||
dataFileRef.value!.handleStart(file)
|
||||
dataFileRef.value!.submit()
|
||||
}
|
||||
|
||||
//copy
|
||||
// const copyRes = async (resStr: string) => {
|
||||
// copy(resStr)
|
||||
// }
|
||||
|
||||
onMounted(() => {
|
||||
//init echart dom
|
||||
chartDom.value = document.getElementById('main')
|
||||
//设置画布宽高
|
||||
canvasHandle('size')
|
||||
//reload canvas
|
||||
reloadCanvas()
|
||||
//数据格式转换成
|
||||
rowsData.value = toSpreadsheetData([
|
||||
colunmData, valueData
|
||||
])
|
||||
// console.log(rowsData.value)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl flex">
|
||||
<div class="w-4/6">
|
||||
<div class="flex flex-row-reverse mb-4">
|
||||
<el-input-number v-model="sacleSize" :min="1" :max="100" :step="5" @change="canvasHandle('scale')" step-strictly/>
|
||||
<el-text>缩放:</el-text>
|
||||
</div>
|
||||
<!-- <div class="flex justify-center items-center"> -->
|
||||
<div class="flex justify-center items-center max-h-[500px] max-w-[1000px] overflow-auto">
|
||||
<div id="main" class="bg-white"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-2/6 bg-white ml-3 rounded-lg p-3">
|
||||
<div class="mb-3">
|
||||
<el-button class="w-full mb-3" type="primary" size="large" @click="downEchartsImg">下载图表</el-button>
|
||||
<div class="flex items-center ml-1">
|
||||
<el-text>文件类型</el-text>
|
||||
<el-radio-group v-model="downType" class="ml-4">
|
||||
<el-radio size="large" label="1">PNG</el-radio>
|
||||
<el-radio size="large" label="2">JPEG</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="支持上传:xls, xlsx, csv文件"
|
||||
placement="top-start"
|
||||
>
|
||||
<el-button class="w-1/2">
|
||||
<el-upload
|
||||
v-model:file-list="fileList"
|
||||
class="dataFileRef flex"
|
||||
ref="dataFileRef"
|
||||
accept=".xls,.xlsx,.csv"
|
||||
:http-request="updateDataFile"
|
||||
:on-exceed="handleExceed"
|
||||
:limit="1"
|
||||
>
|
||||
上传数据
|
||||
</el-upload>
|
||||
</el-button>
|
||||
|
||||
</el-tooltip>
|
||||
<el-button class="w-1/2" @click="editData">编辑数据</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-collapse v-model="setOptionName" @change="handleChange" accordion>
|
||||
<el-collapse-item title="画布设置" name="1">
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">宽度</el-text>
|
||||
<el-input-number
|
||||
v-model="widthCanvas"
|
||||
:min="1"
|
||||
:max="4000"
|
||||
:step="10"
|
||||
controls-position="right"
|
||||
class="h-8 w-60 max-w-[60%] ml-3"
|
||||
@change="canvasHandle('size')"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex mt-3">
|
||||
<el-text class="w-16 text-right">高度</el-text>
|
||||
<el-input-number
|
||||
v-model="heightCanvas"
|
||||
:min="1"
|
||||
:max="4000"
|
||||
:step="10"
|
||||
controls-position="right"
|
||||
class="h-8 w-60 max-w-[60%] ml-3"
|
||||
@change="canvasHandle('size')"
|
||||
/>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="标题设置" name="2">
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">位置</el-text>
|
||||
<el-radio-group v-model="titlePos" class="ml-3" @change="canvasHandle('title')">
|
||||
<el-radio-button label="left" value="left">左</el-radio-button>
|
||||
<el-radio-button label="center" value="center">中</el-radio-button>
|
||||
<el-radio-button label="right" value="right">右</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<el-divider content-position="left" class="pl-10">标题</el-divider>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">显示</el-text>
|
||||
<el-switch v-model="titleSwitch" class="ml-3" @change="canvasHandle('title')"/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">标题</el-text>
|
||||
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="title" @blur="canvasHandle('title')"></el-input>
|
||||
</div>
|
||||
<el-divider content-position="left">副标题</el-divider>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">显示</el-text>
|
||||
<el-switch v-model="subTitleSwitch" class="ml-3" @change="canvasHandle('title')"/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">副标题</el-text>
|
||||
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="subTitle" @blur="canvasHandle('title')"></el-input>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="图形属性" name="3">
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">颜色</el-text>
|
||||
<el-color-picker v-model="attrColor" class="ml-3" @change="canvasHandle('color')"/>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="水印设置" name="4">
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">显示</el-text>
|
||||
<el-switch v-model="watermarkSwitch" class="ml-3" @change="canvasHandle('watermark')"/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">水印内容</el-text>
|
||||
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="waterMarkText" @change="canvasHandle('watermark')"></el-input>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-drawer id="x-spreadsheet" v-model="drawer" direction="btt" custom-class="sheet" style="">
|
||||
</el-drawer>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
在线图表制作工具,在线制作柱状图<br>
|
||||
在线编辑表格生成柱状图,支持png和jpeg格式导出<br>
|
||||
柱状图是采用垂直柱子,即矩形块的宽度一样,用高度来代表数值大小。<br>
|
||||
能让使用者更为直观地看到不同类别数据或不同时间阶段的比较情况及发展趋势,通常利用于较小的数据集分析。<br>
|
||||
无需任何基础即可上手,并且支持超全的自定义配置,轻松实现你的个性化图表需求<br>
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-drawer__body){
|
||||
display: none !important;
|
||||
}
|
||||
:deep(.el-drawer__header){
|
||||
display: none !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
:deep(.el-drawer){
|
||||
height: 50% !important;
|
||||
}
|
||||
:deep(.el-upload-list){
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,490 @@
|
||||
<script setup lang="ts">
|
||||
import {ref, reactive, onMounted } from 'vue'
|
||||
import Spreadsheet from 'x-data-spreadsheet'
|
||||
import 'x-data-spreadsheet/dist/locale/zh-cn';
|
||||
import { UploadProps,UploadRawFile,genFileId } from 'element-plus'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
// import { copy } from '@/utils/string'
|
||||
import { toEchartsData, toSpreadsheetData } from '@/utils/echarts'
|
||||
import * as echarts from 'echarts'
|
||||
import * as XLSX from 'xlsx'
|
||||
const info = reactive({
|
||||
title: "折线图",
|
||||
})
|
||||
|
||||
const chartDom = ref<HTMLElement|null>()
|
||||
const myChart = ref<echarts.ECharts>()
|
||||
const dataFileRef = ref()
|
||||
|
||||
const setOptionName = ref(1)
|
||||
//缩放比例
|
||||
const sacleSize = ref(100)
|
||||
//画布宽高
|
||||
const widthCanvas = ref(720)
|
||||
const heightCanvas = ref(400)
|
||||
//下载文件类型
|
||||
const downType = ref('1')
|
||||
//图形属性颜色
|
||||
const attrColor = ref('#5470c6')
|
||||
//标题位置
|
||||
const titlePos = ref('center')
|
||||
//标题
|
||||
const title = ref('Tools-Web')
|
||||
//副标题
|
||||
const subTitle = ref('在线图表制作工具')
|
||||
//显示标题 - 开关
|
||||
const titleSwitch = ref(true)
|
||||
//显示副标题 - 开关
|
||||
const subTitleSwitch = ref(true)
|
||||
|
||||
/** 水印 */
|
||||
const watermarkSwitch = ref(false) //开关
|
||||
const waterMarkText = ref('Tools-Web');
|
||||
|
||||
//创建水印
|
||||
const createWatermark = () => {
|
||||
let canvas = document.createElement('canvas');
|
||||
let ctx = canvas.getContext('2d');
|
||||
canvas.width = canvas.height = 100;
|
||||
ctx!.textAlign = 'center';
|
||||
ctx!.textBaseline = 'middle';
|
||||
ctx!.globalAlpha = 0.08;
|
||||
ctx!.font = '20px Microsoft Yahei';
|
||||
ctx!.translate(50, 50);
|
||||
ctx!.rotate(-Math.PI / 4);
|
||||
ctx!.fillText(waterMarkText.value, 0, 0);
|
||||
return canvas
|
||||
}
|
||||
|
||||
//操作图表
|
||||
const canvasHandle = (type) => {
|
||||
// let element = document.getElementById('main')
|
||||
let element = chartDom.value
|
||||
switch(type) {
|
||||
case "scale":
|
||||
//缩放画布
|
||||
let scale = sacleSize.value / 100
|
||||
element!.style.transform = `scale(${scale})`
|
||||
break
|
||||
case "size":
|
||||
//图表尺寸
|
||||
element!.style.width = widthCanvas.value + 'px';
|
||||
element!.style.height = heightCanvas.value + 'px';
|
||||
reloadCanvas()
|
||||
break
|
||||
case "title":
|
||||
let fakerTitle = ''
|
||||
let fakerSubTitle = ''
|
||||
//修改标题相关配置
|
||||
if (titleSwitch.value === true) {
|
||||
fakerTitle = title.value
|
||||
}
|
||||
if (subTitleSwitch.value === true) {
|
||||
fakerSubTitle = subTitle.value
|
||||
}
|
||||
myChart.value?.setOption({
|
||||
title: {
|
||||
text: fakerTitle,
|
||||
subtext: fakerSubTitle,
|
||||
left: titlePos.value
|
||||
}
|
||||
})
|
||||
break
|
||||
case "color":
|
||||
//图表属性颜色
|
||||
myChart.value?.setOption({
|
||||
series: [
|
||||
{
|
||||
itemStyle: {
|
||||
color: attrColor.value
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
break;
|
||||
case "watermark":
|
||||
//水印
|
||||
if (watermarkSwitch.value === true) {
|
||||
myChart.value?.setOption({
|
||||
backgroundColor: {
|
||||
image: createWatermark(),
|
||||
}
|
||||
})
|
||||
} else {
|
||||
myChart.value?.setOption({
|
||||
backgroundColor: '#fff'
|
||||
})
|
||||
}
|
||||
break;
|
||||
case "data":
|
||||
//更新数据
|
||||
myChart.value?.setOption({
|
||||
xAxis: {
|
||||
data: colunmData.value,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: valueData.value,
|
||||
}
|
||||
]
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
const handleChange = () => {
|
||||
|
||||
}
|
||||
|
||||
//数据
|
||||
const colunmData = ref(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']);
|
||||
const valueData = ref(['23', '24', '18', '25', '27', '28', '25']);
|
||||
//选项
|
||||
const option = {
|
||||
backgroundColor: '#fff',
|
||||
// backgroundColor: {
|
||||
// // type: 'pattern',
|
||||
// // image: canvas,
|
||||
// // repeat: 'repeat'
|
||||
// },
|
||||
title: {
|
||||
text: title.value,
|
||||
subtext: subTitle.value,
|
||||
left: titlePos.value
|
||||
},
|
||||
xAxis: {
|
||||
data: colunmData.value,
|
||||
},
|
||||
yAxis: {},
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
data: valueData.value,
|
||||
itemStyle: {
|
||||
color: attrColor.value
|
||||
}
|
||||
}
|
||||
],
|
||||
tooltip: {}
|
||||
};
|
||||
|
||||
//重新加载画布
|
||||
const reloadCanvas = () => {
|
||||
myChart.value = echarts.init(chartDom.value)
|
||||
myChart.value.clear();
|
||||
myChart.value.resize({
|
||||
width: widthCanvas.value,
|
||||
height: heightCanvas.value
|
||||
})
|
||||
myChart.value.setOption(option)
|
||||
}
|
||||
|
||||
//下载echarts图表图片
|
||||
const downEchartsImg = () => {
|
||||
let ext = downType.value == '1' ? 'png' : 'jpeg'
|
||||
let imgUrl = myChart.value?.getDataURL({
|
||||
type: ext as "png" | 'jpeg',
|
||||
pixelRatio: 2,
|
||||
})
|
||||
|
||||
if (imgUrl != undefined) {
|
||||
var downloadLink = document.createElement('a');
|
||||
downloadLink.href = imgUrl;
|
||||
downloadLink.download = 'echart.' + ext;
|
||||
downloadLink.click();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 上传、编辑数据 */
|
||||
//抽屉开关
|
||||
const drawer = ref(false)
|
||||
const rowsData = ref({})
|
||||
|
||||
const editData = () => {
|
||||
if (drawer.value == true) {
|
||||
drawer.value = false
|
||||
} else {
|
||||
drawer.value = true
|
||||
/** 在线excel */
|
||||
Spreadsheet.locale('zh-cn', (window.x_spreadsheet as any).$messages['zh-cn']);
|
||||
//const sheet = new Spreadsheet("#x-spreadsheet", {
|
||||
new Spreadsheet("#x-spreadsheet", {
|
||||
showToolbar: false, //隐藏顶部工具栏
|
||||
showBottomBar: false,//隐藏底部工具栏,
|
||||
view: {
|
||||
height: () => document.documentElement.clientHeight / 2,
|
||||
width: () => document.documentElement.clientWidth,
|
||||
}
|
||||
})
|
||||
.loadData({
|
||||
//样式
|
||||
styles: [
|
||||
{
|
||||
bgcolor: '#f4f5f8',
|
||||
textwrap: true,
|
||||
color: '#900b09',
|
||||
border: {
|
||||
top: ['thin', '#0366d6'],
|
||||
bottom: ['thin', '#0366d6'],
|
||||
right: ['thin', '#0366d6'],
|
||||
left: ['thin', '#0366d6'],
|
||||
},
|
||||
},
|
||||
],
|
||||
//数据
|
||||
rows: rowsData.value
|
||||
}) // load data
|
||||
.change(data => {
|
||||
//表格数据改变后触发
|
||||
//规则: 获取第一列和第二列的数据
|
||||
let tmp = toEchartsData(data)
|
||||
colunmData.value = tmp[0]
|
||||
valueData.value = tmp[1]
|
||||
canvasHandle('data')
|
||||
});
|
||||
// sheet.validate()
|
||||
}
|
||||
}
|
||||
|
||||
//上传数据文件
|
||||
const fileList = ref()
|
||||
const updateDataFile = async (params) => {
|
||||
const _file = params.file;
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = (ev) => {
|
||||
try {
|
||||
if (!ev.target) {
|
||||
return
|
||||
}
|
||||
const data = ev.target.result;
|
||||
const workbook = XLSX.read(data, {
|
||||
type: 'binary'
|
||||
});
|
||||
let useCount = 0;
|
||||
let tmpColumnData:any[] = [];
|
||||
let tmpValueData:any[] = [];
|
||||
for (let sheet in workbook.Sheets) {
|
||||
//这里只需要第一个sheet
|
||||
if (useCount > 0) {
|
||||
continue;
|
||||
}
|
||||
//循环读取每个文件
|
||||
const sheetArray = XLSX.utils.sheet_to_json(workbook.Sheets[sheet], {header: ['0', '1']});
|
||||
//若当前sheet没有数据,则continue
|
||||
if(sheetArray.length == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
for(let item in sheetArray){
|
||||
if (sheetArray[item] != undefined) {
|
||||
let tmp1 = sheetArray as []
|
||||
tmpColumnData.push(tmp1[item][0])
|
||||
tmpValueData.push(tmp1[item][1])
|
||||
}
|
||||
}
|
||||
useCount++
|
||||
}
|
||||
//更新数据
|
||||
colunmData.value = tmpColumnData
|
||||
valueData.value = tmpValueData
|
||||
//更新图表
|
||||
canvasHandle('data')
|
||||
//更新表格
|
||||
rowsData.value = toSpreadsheetData([
|
||||
colunmData, valueData
|
||||
])
|
||||
} catch (e) {
|
||||
console.log('error')
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
fileReader.readAsArrayBuffer(_file)
|
||||
}
|
||||
|
||||
//当超出限制时,执行的钩子函数
|
||||
//这里覆盖前一个文件
|
||||
const handleExceed: UploadProps['onExceed'] = (files) => {
|
||||
dataFileRef.value!.clearFiles()
|
||||
const file = files[0] as UploadRawFile
|
||||
file.uid = genFileId()
|
||||
dataFileRef.value!.handleStart(file)
|
||||
dataFileRef.value!.submit()
|
||||
}
|
||||
|
||||
//copy
|
||||
// const copyRes = async (resStr: string) => {
|
||||
// copy(resStr)
|
||||
// }
|
||||
|
||||
onMounted(() => {
|
||||
//init echart dom
|
||||
chartDom.value = document.getElementById('main')
|
||||
//设置画布宽高
|
||||
canvasHandle('size')
|
||||
//reload canvas
|
||||
reloadCanvas()
|
||||
//数据格式转换成
|
||||
rowsData.value = toSpreadsheetData([
|
||||
colunmData, valueData
|
||||
])
|
||||
// console.log(rowsData.value)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl flex">
|
||||
<div class="w-4/6">
|
||||
<div class="flex flex-row-reverse mb-4">
|
||||
<el-input-number v-model="sacleSize" :min="1" :max="100" :step="5" @change="canvasHandle('scale')" step-strictly/>
|
||||
<el-text>缩放:</el-text>
|
||||
</div>
|
||||
<!-- <div class="flex justify-center items-center"> -->
|
||||
<div class="flex justify-center items-center max-h-[500px] max-w-[1000px] overflow-auto">
|
||||
<div id="main" class="bg-white"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-2/6 bg-white ml-3 rounded-lg p-3">
|
||||
<div class="mb-3">
|
||||
<el-button class="w-full mb-3" type="primary" size="large" @click="downEchartsImg">下载图表</el-button>
|
||||
<div class="flex items-center ml-1">
|
||||
<el-text>文件类型</el-text>
|
||||
<el-radio-group v-model="downType" class="ml-4">
|
||||
<el-radio size="large" label="1">PNG</el-radio>
|
||||
<el-radio size="large" label="2">JPEG</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="支持上传:xls, xlsx, csv文件"
|
||||
placement="top-start"
|
||||
>
|
||||
<el-button class="w-1/2">
|
||||
<el-upload
|
||||
v-model:file-list="fileList"
|
||||
class="dataFileRef flex"
|
||||
ref="dataFileRef"
|
||||
accept=".xls,.xlsx,.csv"
|
||||
:http-request="updateDataFile"
|
||||
:on-exceed="handleExceed"
|
||||
:limit="1"
|
||||
>
|
||||
上传数据
|
||||
</el-upload>
|
||||
</el-button>
|
||||
|
||||
</el-tooltip>
|
||||
<el-button class="w-1/2" @click="editData">编辑数据</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-collapse v-model="setOptionName" @change="handleChange" accordion>
|
||||
<el-collapse-item title="画布设置" name="1">
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">宽度</el-text>
|
||||
<el-input-number
|
||||
v-model="widthCanvas"
|
||||
:min="1"
|
||||
:max="4000"
|
||||
:step="10"
|
||||
controls-position="right"
|
||||
class="h-8 w-60 max-w-[60%] ml-3"
|
||||
@change="canvasHandle('size')"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex mt-3">
|
||||
<el-text class="w-16 text-right">高度</el-text>
|
||||
<el-input-number
|
||||
v-model="heightCanvas"
|
||||
:min="1"
|
||||
:max="4000"
|
||||
:step="10"
|
||||
controls-position="right"
|
||||
class="h-8 w-60 max-w-[60%] ml-3"
|
||||
@change="canvasHandle('size')"
|
||||
/>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="标题设置" name="2">
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">位置</el-text>
|
||||
<el-radio-group v-model="titlePos" class="ml-3" @change="canvasHandle('title')">
|
||||
<el-radio-button label="left" value="left">左</el-radio-button>
|
||||
<el-radio-button label="center" value="center">中</el-radio-button>
|
||||
<el-radio-button label="right" value="right">右</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<el-divider content-position="left" class="pl-10">标题</el-divider>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">显示</el-text>
|
||||
<el-switch v-model="titleSwitch" class="ml-3" @change="canvasHandle('title')"/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">标题</el-text>
|
||||
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="title" @blur="canvasHandle('title')"></el-input>
|
||||
</div>
|
||||
<el-divider content-position="left">副标题</el-divider>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">显示</el-text>
|
||||
<el-switch v-model="subTitleSwitch" class="ml-3" @change="canvasHandle('title')"/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">副标题</el-text>
|
||||
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="subTitle" @blur="canvasHandle('title')"></el-input>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="图形属性" name="3">
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">颜色</el-text>
|
||||
<el-color-picker v-model="attrColor" class="ml-3" @change="canvasHandle('color')"/>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="水印设置" name="4">
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">显示</el-text>
|
||||
<el-switch v-model="watermarkSwitch" class="ml-3" @change="canvasHandle('watermark')"/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">水印内容</el-text>
|
||||
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="waterMarkText" @change="canvasHandle('watermark')"></el-input>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-drawer id="x-spreadsheet" v-model="drawer" direction="btt" class="sheet" style="">
|
||||
</el-drawer>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
折线图为日常最常用的图表之一,当必须呈现与时间相关的数据时,它是首选它最适合分析趋势。<br>
|
||||
在线图表制作工具,在线制作折线图<br>
|
||||
支持导入表格并在线编辑表格生成折线图,支持png和jpeg格式导出<br>
|
||||
支持超全的自定义配置,轻松实现你的个性化图表需求<br>
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-drawer__body){
|
||||
display: none !important;
|
||||
}
|
||||
:deep(.el-drawer__header){
|
||||
display: none !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
:deep(.el-drawer){
|
||||
height: 50% !important;
|
||||
}
|
||||
:deep(.el-upload-list){
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,499 @@
|
||||
<script setup lang="ts">
|
||||
import {ref, reactive, onMounted } from 'vue'
|
||||
import Spreadsheet from 'x-data-spreadsheet'
|
||||
import 'x-data-spreadsheet/dist/locale/zh-cn';
|
||||
import { UploadProps,UploadRawFile,genFileId } from 'element-plus'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
// import { copy } from '@/utils/string'
|
||||
import { toEchartsPieData, toSpreadsheetData, tranObjAndColumn } from '@/utils/echarts'
|
||||
import * as echarts from 'echarts'
|
||||
import * as XLSX from 'xlsx'
|
||||
const info = reactive({
|
||||
title: "饼图",
|
||||
})
|
||||
|
||||
const chartDom = ref<HTMLElement|null>()
|
||||
const myChart = ref<echarts.ECharts>()
|
||||
const dataFileRef = ref()
|
||||
|
||||
const setOptionName = ref(1)
|
||||
//缩放比例
|
||||
const sacleSize = ref(100)
|
||||
//画布宽高
|
||||
const widthCanvas = ref(720)
|
||||
const heightCanvas = ref(400)
|
||||
//下载文件类型
|
||||
const downType = ref('1')
|
||||
//图形属性颜色
|
||||
const attrColor = ref('#5470c6')
|
||||
//标题位置
|
||||
const titlePos = ref('center')
|
||||
//标题
|
||||
const title = ref('Tools-Web')
|
||||
//副标题
|
||||
const subTitle = ref('在线图表制作工具')
|
||||
//显示标题 - 开关
|
||||
const titleSwitch = ref(true)
|
||||
//显示副标题 - 开关
|
||||
const subTitleSwitch = ref(true)
|
||||
|
||||
/** 水印 */
|
||||
const watermarkSwitch = ref(false) //开关
|
||||
const waterMarkText = ref('Tools-Web');
|
||||
|
||||
//创建水印
|
||||
const createWatermark = () => {
|
||||
let canvas = document.createElement('canvas');
|
||||
let ctx = canvas.getContext('2d');
|
||||
canvas.width = canvas.height = 100;
|
||||
ctx!.textAlign = 'center';
|
||||
ctx!.textBaseline = 'middle';
|
||||
ctx!.globalAlpha = 0.08;
|
||||
ctx!.font = '20px Microsoft Yahei';
|
||||
ctx!.translate(50, 50);
|
||||
ctx!.rotate(-Math.PI / 4);
|
||||
ctx!.fillText(waterMarkText.value, 0, 0);
|
||||
return canvas
|
||||
}
|
||||
|
||||
//操作图表
|
||||
const canvasHandle = (type) => {
|
||||
// let element = document.getElementById('main')
|
||||
let element = chartDom.value
|
||||
switch(type) {
|
||||
case "scale":
|
||||
//缩放画布
|
||||
let scale = sacleSize.value / 100
|
||||
element!.style.transform = `scale(${scale})`
|
||||
break
|
||||
case "size":
|
||||
//图表尺寸
|
||||
element!.style.width = widthCanvas.value + 'px';
|
||||
element!.style.height = heightCanvas.value + 'px';
|
||||
reloadCanvas()
|
||||
break
|
||||
case "title":
|
||||
let fakerTitle = ''
|
||||
let fakerSubTitle = ''
|
||||
//修改标题相关配置
|
||||
if (titleSwitch.value === true) {
|
||||
fakerTitle = title.value
|
||||
}
|
||||
if (subTitleSwitch.value === true) {
|
||||
fakerSubTitle = subTitle.value
|
||||
}
|
||||
myChart.value?.setOption({
|
||||
title: {
|
||||
text: fakerTitle,
|
||||
subtext: fakerSubTitle,
|
||||
left: titlePos.value
|
||||
}
|
||||
})
|
||||
break
|
||||
case "color":
|
||||
//图表属性颜色
|
||||
myChart.value?.setOption({
|
||||
series: [
|
||||
{
|
||||
itemStyle: {
|
||||
color: attrColor.value
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
break;
|
||||
case "watermark":
|
||||
//水印
|
||||
if (watermarkSwitch.value === true) {
|
||||
myChart.value?.setOption({
|
||||
backgroundColor: {
|
||||
image: createWatermark(),
|
||||
}
|
||||
})
|
||||
} else {
|
||||
myChart.value?.setOption({
|
||||
backgroundColor: '#fff'
|
||||
})
|
||||
}
|
||||
break;
|
||||
case "data":
|
||||
//更新数据
|
||||
myChart.value?.setOption({
|
||||
series: [
|
||||
{
|
||||
data: seriesData.value,
|
||||
}
|
||||
]
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
const handleChange = () => {
|
||||
|
||||
}
|
||||
|
||||
//数据
|
||||
const colunmData = ref(['Search Engine', 'Direct', 'Email', 'Union Ads', 'Video Ads'] as string[]);
|
||||
const valueData = ref([1048, 735, 580, 484, 300] as number[]);
|
||||
const seriesData = ref([] as Object[])
|
||||
//选项
|
||||
const option = {
|
||||
backgroundColor: '#fff',
|
||||
// backgroundColor: {
|
||||
// // type: 'pattern',
|
||||
// // image: canvas,
|
||||
// // repeat: 'repeat'
|
||||
// },
|
||||
title: {
|
||||
text: title.value,
|
||||
subtext: subTitle.value,
|
||||
left: titlePos.value
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: '50%',
|
||||
data: seriesData.value,
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
// trigger: 'item'
|
||||
},
|
||||
};
|
||||
|
||||
//重新加载画布
|
||||
const reloadCanvas = () => {
|
||||
myChart.value = echarts.init(chartDom.value)
|
||||
myChart.value.clear();
|
||||
myChart.value.resize({
|
||||
width: widthCanvas.value,
|
||||
height: heightCanvas.value
|
||||
})
|
||||
myChart.value.setOption(option)
|
||||
//加载数据
|
||||
canvasHandle('data')
|
||||
}
|
||||
|
||||
//下载echarts图表图片
|
||||
const downEchartsImg = () => {
|
||||
let ext = downType.value == '1' ? 'png' : 'jpeg'
|
||||
let imgUrl = myChart.value?.getDataURL({
|
||||
type: ext as "png" | 'jpeg',
|
||||
pixelRatio: 2,
|
||||
})
|
||||
|
||||
if (imgUrl != undefined) {
|
||||
var downloadLink = document.createElement('a');
|
||||
downloadLink.href = imgUrl;
|
||||
downloadLink.download = 'echart.' + ext;
|
||||
downloadLink.click();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 上传、编辑数据 */
|
||||
//抽屉开关
|
||||
const drawer = ref(false)
|
||||
const rowsData = ref({})
|
||||
|
||||
const editData = () => {
|
||||
if (drawer.value == true) {
|
||||
drawer.value = false
|
||||
} else {
|
||||
drawer.value = true
|
||||
/** 在线excel */
|
||||
Spreadsheet.locale('zh-cn', (window.x_spreadsheet as any).$messages['zh-cn']);
|
||||
//const sheet = new Spreadsheet("#x-spreadsheet", {
|
||||
new Spreadsheet("#x-spreadsheet", {
|
||||
showToolbar: false, //隐藏顶部工具栏
|
||||
showBottomBar: false,//隐藏底部工具栏,
|
||||
view: {
|
||||
height: () => document.documentElement.clientHeight / 2,
|
||||
width: () => document.documentElement.clientWidth,
|
||||
}
|
||||
})
|
||||
.loadData({
|
||||
//样式
|
||||
styles: [
|
||||
{
|
||||
bgcolor: '#f4f5f8',
|
||||
textwrap: true,
|
||||
color: '#900b09',
|
||||
border: {
|
||||
top: ['thin', '#0366d6'],
|
||||
bottom: ['thin', '#0366d6'],
|
||||
right: ['thin', '#0366d6'],
|
||||
left: ['thin', '#0366d6'],
|
||||
},
|
||||
},
|
||||
],
|
||||
//数据
|
||||
rows: rowsData.value
|
||||
}) // load data
|
||||
.change(data => {
|
||||
//表格数据改变后触发
|
||||
//规则: 获取第一列和第二列的数据
|
||||
seriesData.value = toEchartsPieData(data)
|
||||
canvasHandle('data')
|
||||
});
|
||||
// sheet.validate()
|
||||
}
|
||||
}
|
||||
|
||||
//上传数据文件
|
||||
const fileList = ref()
|
||||
const updateDataFile = async (params) => {
|
||||
const _file = params.file;
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = (ev) => {
|
||||
try {
|
||||
if (!ev.target) {
|
||||
return
|
||||
}
|
||||
const data = ev.target.result;
|
||||
const workbook = XLSX.read(data, {
|
||||
type: 'binary'
|
||||
});
|
||||
let useCount = 0;
|
||||
let tmpColumnData:any[] = [];
|
||||
let tmpValueData:any[] = [];
|
||||
for (let sheet in workbook.Sheets) {
|
||||
//这里只需要第一个sheet
|
||||
if (useCount > 0) {
|
||||
continue;
|
||||
}
|
||||
//循环读取每个文件
|
||||
const sheetArray = XLSX.utils.sheet_to_json(workbook.Sheets[sheet], {header: ['0', '1']});
|
||||
//若当前sheet没有数据,则continue
|
||||
if(sheetArray.length == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
for(let item in sheetArray){
|
||||
if (sheetArray[item] != undefined) {
|
||||
let tmp1 = sheetArray as []
|
||||
tmpColumnData.push(tmp1[item][0])
|
||||
tmpValueData.push(tmp1[item][1])
|
||||
}
|
||||
}
|
||||
useCount++
|
||||
}
|
||||
//更新数据
|
||||
colunmData.value = tmpColumnData
|
||||
valueData.value = tmpValueData
|
||||
seriesData.value = tranObjAndColumn([
|
||||
colunmData.value, valueData.value
|
||||
])
|
||||
//更新图表
|
||||
canvasHandle('data')
|
||||
//更新表格
|
||||
rowsData.value = toSpreadsheetData([
|
||||
colunmData, valueData
|
||||
])
|
||||
} catch (e) {
|
||||
console.log('error')
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
fileReader.readAsArrayBuffer(_file)
|
||||
}
|
||||
|
||||
//当超出限制时,执行的钩子函数
|
||||
//这里覆盖前一个文件
|
||||
const handleExceed: UploadProps['onExceed'] = (files) => {
|
||||
dataFileRef.value!.clearFiles()
|
||||
const file = files[0] as UploadRawFile
|
||||
file.uid = genFileId()
|
||||
dataFileRef.value!.handleStart(file)
|
||||
dataFileRef.value!.submit()
|
||||
}
|
||||
|
||||
//copy
|
||||
// const copyRes = async (resStr: string) => {
|
||||
// copy(resStr)
|
||||
// }
|
||||
|
||||
onMounted(() => {
|
||||
//初始化数据
|
||||
seriesData.value = tranObjAndColumn([
|
||||
colunmData.value, valueData.value
|
||||
])
|
||||
//init echart dom
|
||||
chartDom.value = document.getElementById('main')
|
||||
//设置画布宽高
|
||||
canvasHandle('size')
|
||||
//reload canvas
|
||||
reloadCanvas()
|
||||
//数据格式转换成
|
||||
rowsData.value = toSpreadsheetData([
|
||||
colunmData, valueData
|
||||
])
|
||||
// console.log(rowsData.value)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl flex">
|
||||
<div class="w-4/6">
|
||||
<div class="flex flex-row-reverse mb-4">
|
||||
<el-input-number v-model="sacleSize" :min="1" :max="100" :step="5" @change="canvasHandle('scale')" step-strictly/>
|
||||
<el-text>缩放:</el-text>
|
||||
</div>
|
||||
<!-- <div class="flex justify-center items-center"> -->
|
||||
<div class="flex justify-center items-center max-h-[500px] max-w-[1000px] overflow-auto">
|
||||
<div id="main" class="bg-white"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-2/6 bg-white ml-3 rounded-lg p-3">
|
||||
<div class="mb-3">
|
||||
<el-button class="w-full mb-3" type="primary" size="large" @click="downEchartsImg">下载图表</el-button>
|
||||
<div class="flex items-center ml-1">
|
||||
<el-text>文件类型</el-text>
|
||||
<el-radio-group v-model="downType" class="ml-4">
|
||||
<el-radio size="large" label="1">PNG</el-radio>
|
||||
<el-radio size="large" label="2">JPEG</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="支持上传:xls, xlsx, csv文件"
|
||||
placement="top-start"
|
||||
>
|
||||
<el-button class="w-1/2">
|
||||
<el-upload
|
||||
v-model:file-list="fileList"
|
||||
class="dataFileRef flex"
|
||||
ref="dataFileRef"
|
||||
accept=".xls,.xlsx,.csv"
|
||||
:http-request="updateDataFile"
|
||||
:on-exceed="handleExceed"
|
||||
:limit="1"
|
||||
>
|
||||
上传数据
|
||||
</el-upload>
|
||||
</el-button>
|
||||
|
||||
</el-tooltip>
|
||||
<el-button class="w-1/2" @click="editData">编辑数据</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-collapse v-model="setOptionName" @change="handleChange" accordion>
|
||||
<el-collapse-item title="画布设置" name="1">
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">宽度</el-text>
|
||||
<el-input-number
|
||||
v-model="widthCanvas"
|
||||
:min="1"
|
||||
:max="4000"
|
||||
:step="10"
|
||||
controls-position="right"
|
||||
class="h-8 w-60 max-w-[60%] ml-3"
|
||||
@change="canvasHandle('size')"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex mt-3">
|
||||
<el-text class="w-16 text-right">高度</el-text>
|
||||
<el-input-number
|
||||
v-model="heightCanvas"
|
||||
:min="1"
|
||||
:max="4000"
|
||||
:step="10"
|
||||
controls-position="right"
|
||||
class="h-8 w-60 max-w-[60%] ml-3"
|
||||
@change="canvasHandle('size')"
|
||||
/>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="标题设置" name="2">
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">位置</el-text>
|
||||
<el-radio-group v-model="titlePos" class="ml-3" @change="canvasHandle('title')">
|
||||
<el-radio-button label="left" value="left">左</el-radio-button>
|
||||
<el-radio-button label="center" value="center">中</el-radio-button>
|
||||
<el-radio-button label="right" value="right">右</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<el-divider content-position="left" class="pl-10">标题</el-divider>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">显示</el-text>
|
||||
<el-switch v-model="titleSwitch" class="ml-3" @change="canvasHandle('title')"/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">标题</el-text>
|
||||
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="title" @blur="canvasHandle('title')"></el-input>
|
||||
</div>
|
||||
<el-divider content-position="left">副标题</el-divider>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">显示</el-text>
|
||||
<el-switch v-model="subTitleSwitch" class="ml-3" @change="canvasHandle('title')"/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">副标题</el-text>
|
||||
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="subTitle" @blur="canvasHandle('title')"></el-input>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
<!-- <el-collapse-item title="图形属性" name="3">
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">颜色</el-text>
|
||||
<el-color-picker v-model="attrColor" class="ml-3" @change="canvasHandle('color')"/>
|
||||
</div>
|
||||
</el-collapse-item> -->
|
||||
<el-collapse-item title="水印设置" name="4">
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">显示</el-text>
|
||||
<el-switch v-model="watermarkSwitch" class="ml-3" @change="canvasHandle('watermark')"/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">水印内容</el-text>
|
||||
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="waterMarkText" @change="canvasHandle('watermark')"></el-input>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-drawer id="x-spreadsheet" v-model="drawer" direction="btt" custom-class="sheet" style="">
|
||||
</el-drawer>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
饼图是通过一个圆来展示数据,用圆内各扇形的角度来表示数据之间的占比关系。<br>
|
||||
能够很好地展示不同分类的变量之间或单个分类变量与整体之间的占比情况,常用来强调某个突出的分类变量或表示表示占比关系。<br>
|
||||
在线图表制作工具,在线制作饼图<br>
|
||||
支持导入表格并在线编辑表格生成饼图,支持png和jpeg格式导出<br>
|
||||
支持超全的自定义配置,轻松实现你的个性化图表需求<br>
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-drawer__body){
|
||||
display: none !important;
|
||||
}
|
||||
:deep(.el-drawer__header){
|
||||
display: none !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
:deep(.el-drawer){
|
||||
height: 50% !important;
|
||||
}
|
||||
:deep(.el-upload-list){
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,493 @@
|
||||
<script setup lang="ts">
|
||||
import {ref, reactive, onMounted } from 'vue'
|
||||
import Spreadsheet from 'x-data-spreadsheet'
|
||||
import 'x-data-spreadsheet/dist/locale/zh-cn';
|
||||
import { UploadProps,UploadRawFile,genFileId } from 'element-plus'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
// import { copy } from '@/utils/string'
|
||||
import { toEchartsData, toSpreadsheetData, tranObjAndColumn } from '@/utils/echarts'
|
||||
import * as echarts from 'echarts'
|
||||
import * as XLSX from 'xlsx'
|
||||
const info = reactive({
|
||||
title: "散点图",
|
||||
})
|
||||
|
||||
const chartDom = ref<HTMLElement|null>()
|
||||
const myChart = ref<echarts.ECharts>()
|
||||
const dataFileRef = ref()
|
||||
|
||||
const setOptionName = ref(1)
|
||||
//缩放比例
|
||||
const sacleSize = ref(100)
|
||||
//画布宽高
|
||||
const widthCanvas = ref(720)
|
||||
const heightCanvas = ref(400)
|
||||
//下载文件类型
|
||||
const downType = ref('1')
|
||||
//图形属性颜色
|
||||
const attrColor = ref('#5470c6')
|
||||
//标题位置
|
||||
const titlePos = ref('center')
|
||||
//标题
|
||||
const title = ref('Tools-Web')
|
||||
//副标题
|
||||
const subTitle = ref('在线图表制作工具')
|
||||
//显示标题 - 开关
|
||||
const titleSwitch = ref(true)
|
||||
//显示副标题 - 开关
|
||||
const subTitleSwitch = ref(true)
|
||||
|
||||
/** 水印 */
|
||||
const watermarkSwitch = ref(false) //开关
|
||||
const waterMarkText = ref('Tools-Web');
|
||||
|
||||
//创建水印
|
||||
const createWatermark = () => {
|
||||
let canvas = document.createElement('canvas');
|
||||
let ctx = canvas.getContext('2d');
|
||||
canvas.width = canvas.height = 100;
|
||||
ctx!.textAlign = 'center';
|
||||
ctx!.textBaseline = 'middle';
|
||||
ctx!.globalAlpha = 0.08;
|
||||
ctx!.font = '20px Microsoft Yahei';
|
||||
ctx!.translate(50, 50);
|
||||
ctx!.rotate(-Math.PI / 4);
|
||||
ctx!.fillText(waterMarkText.value, 0, 0);
|
||||
return canvas
|
||||
}
|
||||
|
||||
//操作图表
|
||||
const canvasHandle = (type) => {
|
||||
// let element = document.getElementById('main')
|
||||
let element = chartDom.value
|
||||
switch(type) {
|
||||
case "scale":
|
||||
//缩放画布
|
||||
let scale = sacleSize.value / 100
|
||||
element!.style.transform = `scale(${scale})`
|
||||
break
|
||||
case "size":
|
||||
//图表尺寸
|
||||
element!.style.width = widthCanvas.value + 'px';
|
||||
element!.style.height = heightCanvas.value + 'px';
|
||||
reloadCanvas()
|
||||
break
|
||||
case "title":
|
||||
let fakerTitle = ''
|
||||
let fakerSubTitle = ''
|
||||
//修改标题相关配置
|
||||
if (titleSwitch.value === true) {
|
||||
fakerTitle = title.value
|
||||
}
|
||||
if (subTitleSwitch.value === true) {
|
||||
fakerSubTitle = subTitle.value
|
||||
}
|
||||
myChart.value?.setOption({
|
||||
title: {
|
||||
text: fakerTitle,
|
||||
subtext: fakerSubTitle,
|
||||
left: titlePos.value
|
||||
}
|
||||
})
|
||||
break
|
||||
case "color":
|
||||
//图表属性颜色
|
||||
myChart.value?.setOption({
|
||||
series: [
|
||||
{
|
||||
itemStyle: {
|
||||
color: attrColor.value
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
break;
|
||||
case "watermark":
|
||||
//水印
|
||||
if (watermarkSwitch.value === true) {
|
||||
myChart.value?.setOption({
|
||||
backgroundColor: {
|
||||
image: createWatermark(),
|
||||
}
|
||||
})
|
||||
} else {
|
||||
myChart.value?.setOption({
|
||||
backgroundColor: '#fff'
|
||||
})
|
||||
}
|
||||
break;
|
||||
case "data":
|
||||
//更新数据
|
||||
myChart.value?.setOption({
|
||||
series: [
|
||||
{
|
||||
data: seriesData.value,
|
||||
}
|
||||
]
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
const handleChange = () => {
|
||||
|
||||
}
|
||||
|
||||
//数据
|
||||
const colunmData = ref([10.0, 8.07, 13.0, 9.05, 11.0, 14.0, 13.4, 10.0, 14.0, 12.5, 9.15, 11.5, 3.03, 12.2, 2.02, 1.05, 4.05, 6.03, 12.0, 7.08, 5.02, 6.03]);
|
||||
const valueData = ref([8.04, 6.95, 7.58, 8.81, 8.33, 7.66, 6.81, 6.33, 8.96, 6.82, 7.2, 7.2, 4.23, 7.83, 4.47, 3.33, 4.96, 7.24, 6.26, 8.84, 5.82, 5.68, 7.05]);
|
||||
const seriesData = ref([] as Array<any>[])
|
||||
//选项
|
||||
const option = {
|
||||
backgroundColor: '#fff',
|
||||
// backgroundColor: {
|
||||
// // type: 'pattern',
|
||||
// // image: canvas,
|
||||
// // repeat: 'repeat'
|
||||
// },
|
||||
title: {
|
||||
text: title.value,
|
||||
subtext: subTitle.value,
|
||||
left: titlePos.value
|
||||
},
|
||||
xAxis: {},
|
||||
yAxis: {},
|
||||
series: [
|
||||
{
|
||||
type: 'scatter',
|
||||
symbolSize: 30,
|
||||
data: seriesData.value,
|
||||
}
|
||||
],
|
||||
tooltip: {}
|
||||
};
|
||||
|
||||
//重新加载画布
|
||||
const reloadCanvas = () => {
|
||||
myChart.value = echarts.init(chartDom.value)
|
||||
myChart.value.clear();
|
||||
myChart.value.resize({
|
||||
width: widthCanvas.value,
|
||||
height: heightCanvas.value
|
||||
})
|
||||
myChart.value.setOption(option)
|
||||
//加载数据
|
||||
canvasHandle('data')
|
||||
}
|
||||
|
||||
//下载echarts图表图片
|
||||
const downEchartsImg = () => {
|
||||
let ext = downType.value == '1' ? 'png' : 'jpeg'
|
||||
let imgUrl = myChart.value?.getDataURL({
|
||||
type: ext as "png" | 'jpeg',
|
||||
pixelRatio: 2,
|
||||
})
|
||||
|
||||
if (imgUrl != undefined) {
|
||||
var downloadLink = document.createElement('a');
|
||||
downloadLink.href = imgUrl;
|
||||
downloadLink.download = 'echart.' + ext;
|
||||
downloadLink.click();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 上传、编辑数据 */
|
||||
//抽屉开关
|
||||
const drawer = ref(false)
|
||||
const rowsData = ref({})
|
||||
|
||||
const editData = () => {
|
||||
if (drawer.value == true) {
|
||||
drawer.value = false
|
||||
} else {
|
||||
drawer.value = true
|
||||
/** 在线excel */
|
||||
Spreadsheet.locale('zh-cn', (window.x_spreadsheet as any).$messages['zh-cn']);
|
||||
//const sheet = new Spreadsheet("#x-spreadsheet", {
|
||||
new Spreadsheet("#x-spreadsheet", {
|
||||
showToolbar: false, //隐藏顶部工具栏
|
||||
showBottomBar: false,//隐藏底部工具栏,
|
||||
view: {
|
||||
height: () => document.documentElement.clientHeight / 2,
|
||||
width: () => document.documentElement.clientWidth,
|
||||
}
|
||||
})
|
||||
.loadData({
|
||||
//样式
|
||||
styles: [
|
||||
{
|
||||
bgcolor: '#f4f5f8',
|
||||
textwrap: true,
|
||||
color: '#900b09',
|
||||
border: {
|
||||
top: ['thin', '#0366d6'],
|
||||
bottom: ['thin', '#0366d6'],
|
||||
right: ['thin', '#0366d6'],
|
||||
left: ['thin', '#0366d6'],
|
||||
},
|
||||
},
|
||||
],
|
||||
//数据
|
||||
rows: rowsData.value
|
||||
}) // load data
|
||||
.change(data => {
|
||||
//表格数据改变后触发
|
||||
//规则: 获取第一列和第二列的数据
|
||||
let tmp = toEchartsData(data)
|
||||
colunmData.value = tmp[0]
|
||||
valueData.value = tmp[1]
|
||||
canvasHandle('data')
|
||||
});
|
||||
// sheet.validate()
|
||||
}
|
||||
}
|
||||
|
||||
//上传数据文件
|
||||
const fileList = ref()
|
||||
const updateDataFile = async (params) => {
|
||||
const _file = params.file;
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = (ev) => {
|
||||
try {
|
||||
if (!ev.target) {
|
||||
return
|
||||
}
|
||||
const data = ev.target.result;
|
||||
const workbook = XLSX.read(data, {
|
||||
type: 'binary'
|
||||
});
|
||||
let useCount = 0;
|
||||
let tmpColumnData:any[] = [];
|
||||
let tmpValueData:any[] = [];
|
||||
for (let sheet in workbook.Sheets) {
|
||||
//这里只需要第一个sheet
|
||||
if (useCount > 0) {
|
||||
continue;
|
||||
}
|
||||
//循环读取每个文件
|
||||
const sheetArray = XLSX.utils.sheet_to_json(workbook.Sheets[sheet], {header: ['0', '1']});
|
||||
//若当前sheet没有数据,则continue
|
||||
if(sheetArray.length == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
for(let item in sheetArray){
|
||||
if (sheetArray[item] != undefined) {
|
||||
let tmp1 = sheetArray as []
|
||||
tmpColumnData.push(tmp1[item][0])
|
||||
tmpValueData.push(tmp1[item][1])
|
||||
}
|
||||
}
|
||||
useCount++
|
||||
}
|
||||
//更新数据
|
||||
colunmData.value = tmpColumnData
|
||||
valueData.value = tmpValueData
|
||||
seriesData.value = tranObjAndColumn([
|
||||
colunmData.value, valueData.value
|
||||
])
|
||||
//更新图表
|
||||
canvasHandle('data')
|
||||
//更新表格
|
||||
rowsData.value = toSpreadsheetData([
|
||||
colunmData, valueData
|
||||
])
|
||||
} catch (e) {
|
||||
console.log('error')
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
fileReader.readAsArrayBuffer(_file)
|
||||
}
|
||||
|
||||
//当超出限制时,执行的钩子函数
|
||||
//这里覆盖前一个文件
|
||||
const handleExceed: UploadProps['onExceed'] = (files) => {
|
||||
dataFileRef.value!.clearFiles()
|
||||
const file = files[0] as UploadRawFile
|
||||
file.uid = genFileId()
|
||||
dataFileRef.value!.handleStart(file)
|
||||
dataFileRef.value!.submit()
|
||||
}
|
||||
|
||||
//copy
|
||||
// const copyRes = async (resStr: string) => {
|
||||
// copy(resStr)
|
||||
// }
|
||||
|
||||
onMounted(() => {
|
||||
//初始化数据
|
||||
seriesData.value = tranObjAndColumn([
|
||||
colunmData.value, valueData.value
|
||||
], 'toCoord')
|
||||
//init echart dom
|
||||
chartDom.value = document.getElementById('main')
|
||||
//设置画布宽高
|
||||
canvasHandle('size')
|
||||
//reload canvas
|
||||
reloadCanvas()
|
||||
//数据格式转换成
|
||||
rowsData.value = toSpreadsheetData([
|
||||
colunmData, valueData
|
||||
])
|
||||
// console.log(rowsData.value)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl flex">
|
||||
<div class="w-4/6">
|
||||
<div class="flex flex-row-reverse mb-4">
|
||||
<el-input-number v-model="sacleSize" :min="1" :max="100" :step="5" @change="canvasHandle('scale')" step-strictly/>
|
||||
<el-text>缩放:</el-text>
|
||||
</div>
|
||||
<!-- <div class="flex justify-center items-center"> -->
|
||||
<div class="flex justify-center items-center max-h-[500px] max-w-[1000px] overflow-auto">
|
||||
<div id="main" class="bg-white"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-2/6 bg-white ml-3 rounded-lg p-3">
|
||||
<div class="mb-3">
|
||||
<el-button class="w-full mb-3" type="primary" size="large" @click="downEchartsImg">下载图表</el-button>
|
||||
<div class="flex items-center ml-1">
|
||||
<el-text>文件类型</el-text>
|
||||
<el-radio-group v-model="downType" class="ml-4">
|
||||
<el-radio size="large" label="1">PNG</el-radio>
|
||||
<el-radio size="large" label="2">JPEG</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="支持上传:xls, xlsx, csv文件"
|
||||
placement="top-start"
|
||||
>
|
||||
<el-button class="w-1/2">
|
||||
<el-upload
|
||||
v-model:file-list="fileList"
|
||||
class="dataFileRef flex"
|
||||
ref="dataFileRef"
|
||||
accept=".xls,.xlsx,.csv"
|
||||
:http-request="updateDataFile"
|
||||
:on-exceed="handleExceed"
|
||||
:limit="1"
|
||||
>
|
||||
上传数据
|
||||
</el-upload>
|
||||
</el-button>
|
||||
|
||||
</el-tooltip>
|
||||
<el-button class="w-1/2" @click="editData">编辑数据</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-collapse v-model="setOptionName" @change="handleChange" accordion>
|
||||
<el-collapse-item title="画布设置" name="1">
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">宽度</el-text>
|
||||
<el-input-number
|
||||
v-model="widthCanvas"
|
||||
:min="1"
|
||||
:max="4000"
|
||||
:step="10"
|
||||
controls-position="right"
|
||||
class="h-8 w-60 max-w-[60%] ml-3"
|
||||
@change="canvasHandle('size')"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex mt-3">
|
||||
<el-text class="w-16 text-right">高度</el-text>
|
||||
<el-input-number
|
||||
v-model="heightCanvas"
|
||||
:min="1"
|
||||
:max="4000"
|
||||
:step="10"
|
||||
controls-position="right"
|
||||
class="h-8 w-60 max-w-[60%] ml-3"
|
||||
@change="canvasHandle('size')"
|
||||
/>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="标题设置" name="2">
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">位置</el-text>
|
||||
<el-radio-group v-model="titlePos" class="ml-3" @change="canvasHandle('title')">
|
||||
<el-radio-button label="left" value="left">左</el-radio-button>
|
||||
<el-radio-button label="center" value="center">中</el-radio-button>
|
||||
<el-radio-button label="right" value="right">右</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<el-divider content-position="left" class="pl-10">标题</el-divider>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">显示</el-text>
|
||||
<el-switch v-model="titleSwitch" class="ml-3" @change="canvasHandle('title')"/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">标题</el-text>
|
||||
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="title" @blur="canvasHandle('title')"></el-input>
|
||||
</div>
|
||||
<el-divider content-position="left">副标题</el-divider>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">显示</el-text>
|
||||
<el-switch v-model="subTitleSwitch" class="ml-3" @change="canvasHandle('title')"/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">副标题</el-text>
|
||||
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="subTitle" @blur="canvasHandle('title')"></el-input>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="图形属性" name="3">
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">颜色</el-text>
|
||||
<el-color-picker v-model="attrColor" class="ml-3" @change="canvasHandle('color')"/>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="水印设置" name="4">
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">显示</el-text>
|
||||
<el-switch v-model="watermarkSwitch" class="ml-3" @change="canvasHandle('watermark')"/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<el-text class="w-16 text-right">水印内容</el-text>
|
||||
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="waterMarkText" @change="canvasHandle('watermark')"></el-input>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-drawer id="x-spreadsheet" v-model="drawer" direction="btt" custom-class="sheet" style="">
|
||||
</el-drawer>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
散点图更偏向于研究型图表,通过直观醒目的图形方式反映出变量随自变量而变化的大致趋势,可以帮助我们推断出不同维度数据之间的相关性,也经常用在地图的标注上。<br>
|
||||
在线图表制作工具,在线制作散点图<br>
|
||||
支持导入表格并在线编辑表格生成散点图,支持png和jpeg格式导出<br>
|
||||
支持超全的自定义配置,轻松实现你的个性化图表需求<br>
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-drawer__body){
|
||||
display: none !important;
|
||||
}
|
||||
:deep(.el-drawer__header){
|
||||
display: none !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
:deep(.el-drawer){
|
||||
height: 50% !important;
|
||||
}
|
||||
:deep(.el-upload-list){
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,142 @@
|
||||
<script setup lang="ts">
|
||||
import { ref,reactive } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
// import { copy } from '@/utils/string'
|
||||
const info = reactive({
|
||||
title: "抛硬币",
|
||||
})
|
||||
|
||||
const genStatus = ref(false)
|
||||
const isHeads = ref(true)
|
||||
|
||||
const throwCoin = () => {
|
||||
genStatus.value = true
|
||||
isHeads.value = Math.random() < 0.5;
|
||||
setTimeout(() => {
|
||||
genStatus.value = false;
|
||||
}, 2500); // 休眠5秒,5000毫秒
|
||||
}
|
||||
//copy
|
||||
// const copyRes = async (resStr: string) => {
|
||||
// copy(resStr)
|
||||
// }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl bg-white">
|
||||
<div style="flex-grow: 3; display: flex; justify-content: center;">
|
||||
<!-- coin -->
|
||||
<div class="rotate-wrap" :class="[genStatus == true ? (isHeads == true ? 'rotate-wrap-ani-z' : 'rotate-wrap-ani-f') : '']">
|
||||
<div class="front circle" :class="[isHeads == true ? 'is-head' : '']"></div>
|
||||
<div class="reverse circle" :class="[isHeads == false ? 'is-head' : '']"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<el-button type="primary" class="w-48" size="large" @click="throwCoin" v-if="!genStatus">抛硬币</el-button>
|
||||
<el-button type="primary" class="w-48" size="large" disabled v-else>抛硬币...</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
面临艰难的选择? 我们邀请您在线掷硬币!
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.rotate-wrap {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin: 100px;
|
||||
transform-style: preserve-3d;
|
||||
position: relative;
|
||||
}
|
||||
.rotate-wrap-ani-z{
|
||||
/* animation: rotate 5s; */
|
||||
animation: rotateZ 2.5s linear infinite;
|
||||
}
|
||||
.rotate-wrap-ani-f{
|
||||
/* animation: rotate 5s; */
|
||||
animation: rotateF 2.5s linear infinite;
|
||||
}
|
||||
.rotate-wrap:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
.rotate-wrap .front, .rotate-wrap .reverse{
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-size: cover;
|
||||
}
|
||||
.rotate-wrap .front{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-image: url('/public/images/coin/dollar.png');
|
||||
}
|
||||
.rotate-wrap .reverse{
|
||||
background-image: url('/public/images/coin/xingxing.png');
|
||||
}
|
||||
|
||||
.circle{
|
||||
border-radius: 50%;
|
||||
}
|
||||
.line{
|
||||
height: 200px;
|
||||
width: 1px;
|
||||
position:absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
background: red;
|
||||
transform: translateZ(0px);
|
||||
}
|
||||
.is-head{
|
||||
transform: translateZ(1px);
|
||||
}
|
||||
|
||||
@keyframes rotateZ {
|
||||
0%{
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
20%{
|
||||
transform: rotateX(720deg) scale(1.2);
|
||||
}
|
||||
50%{
|
||||
transform: rotateX(1080deg) scale(1.5);
|
||||
}
|
||||
80%{
|
||||
transform: rotateX(1440deg) scale(1.2);
|
||||
}
|
||||
100% {
|
||||
transform: rotateX(2160deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotateF {
|
||||
0%{
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
20%{
|
||||
transform: rotateX(720deg) scale(1.2);
|
||||
}
|
||||
50%{
|
||||
transform: rotateX(1080deg) scale(1.5);
|
||||
}
|
||||
80%{
|
||||
transform: rotateX(1440deg) scale(1.2);
|
||||
}
|
||||
100% {
|
||||
transform: rotateX(1980deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,263 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive,ref,onMounted } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
import { copy } from '@/utils/string'
|
||||
import { CopyDocument } from '@element-plus/icons-vue'
|
||||
import { colord, extend } from 'colord';
|
||||
import cmykPlugin from 'colord/plugins/cmyk';
|
||||
import hwbPlugin from 'colord/plugins/hwb';
|
||||
import namesPlugin from 'colord/plugins/names';
|
||||
import lchPlugin from 'colord/plugins/lch';
|
||||
import labPlugin from 'colord/plugins/lab';
|
||||
extend([cmykPlugin, hwbPlugin, namesPlugin, lchPlugin, labPlugin]);
|
||||
|
||||
const info = reactive({
|
||||
title: "Color选择器",
|
||||
})
|
||||
|
||||
const color = ref('#409eff')
|
||||
const hex = ref()
|
||||
const rgb = ref()
|
||||
const hsl = ref()
|
||||
const hsv = ref()
|
||||
const hwb = ref()
|
||||
const lab = ref()
|
||||
const lch = ref()
|
||||
const cmyk = ref()
|
||||
const name = ref()
|
||||
|
||||
|
||||
const change = () => {
|
||||
let colorObj = colord(color.value)
|
||||
hex.value = colorObj.toHex()
|
||||
rgb.value = colorObj.toRgbString()
|
||||
hsl.value = colorObj.toHslString()
|
||||
hsv.value = colorObj.toHsv()
|
||||
hwb.value = colorObj.toHwbString()
|
||||
lab.value = colorObj.toLab()
|
||||
lch.value = colorObj.toLchString()
|
||||
cmyk.value = colorObj.toCmykString()
|
||||
name.value = colorObj.toName({ closest: true }) //{ closest: true } 接近的
|
||||
console.log(colorObj.toHslString())
|
||||
}
|
||||
|
||||
//copy
|
||||
const copyRes = async (resStr: string) => {
|
||||
copy(resStr)
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
change()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl bg-white text-center">
|
||||
<ul class="w-3/4">
|
||||
<li class='flex items-center mb-3'>
|
||||
<div class="w-36 text-right pr-3">选择颜色:</div>
|
||||
<div class="w-full text-left">
|
||||
<el-color-picker v-model="color" size="large" @change="change"/>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class='flex items-center mb-3'>
|
||||
<div class="w-36 text-right pr-3">Hex:</div>
|
||||
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
|
||||
<div>
|
||||
{{ hex }}
|
||||
</div>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="复制到剪贴板"
|
||||
placement="top"
|
||||
>
|
||||
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(hex)"> </el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class='flex items-center mb-3'>
|
||||
<div class="w-36 text-right pr-3">RGB:</div>
|
||||
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
|
||||
<div>
|
||||
{{ rgb }}
|
||||
</div>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="复制到剪贴板"
|
||||
placement="top"
|
||||
>
|
||||
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(rgb)"> </el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class='flex items-center mb-3'>
|
||||
<div class="w-36 text-right pr-3">HSL:</div>
|
||||
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
|
||||
<div>
|
||||
{{ hsl }}
|
||||
</div>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="复制到剪贴板"
|
||||
placement="top"
|
||||
>
|
||||
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(hsl)"> </el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class='flex items-center mb-3'>
|
||||
<div class="w-36 text-right pr-3">HSV:</div>
|
||||
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
|
||||
<div>
|
||||
{{ hsv }}
|
||||
</div>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="复制到剪贴板"
|
||||
placement="top"
|
||||
>
|
||||
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(JSON.stringify(hsv))"> </el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class='flex items-center mb-3'>
|
||||
<div class="w-36 text-right pr-3">HWB:</div>
|
||||
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
|
||||
<div>
|
||||
{{ hwb }}
|
||||
</div>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="复制到剪贴板"
|
||||
placement="top"
|
||||
>
|
||||
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(hwb)"> </el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class='flex items-center mb-3'>
|
||||
<div class="w-36 text-right pr-3">LAB:</div>
|
||||
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
|
||||
<div>
|
||||
{{ lab }}
|
||||
</div>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="复制到剪贴板"
|
||||
placement="top"
|
||||
>
|
||||
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(JSON.stringify(lab))"> </el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class='flex items-center mb-3'>
|
||||
<div class="w-36 text-right pr-3">LCH:</div>
|
||||
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
|
||||
<div>
|
||||
{{ lch }}
|
||||
</div>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="复制到剪贴板"
|
||||
placement="top"
|
||||
>
|
||||
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(lch)"> </el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class='flex items-center mb-3'>
|
||||
<div class="w-36 text-right pr-3">CMYK:</div>
|
||||
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
|
||||
<div>
|
||||
{{ cmyk }}
|
||||
</div>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="复制到剪贴板"
|
||||
placement="top"
|
||||
>
|
||||
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(cmyk)"> </el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class='flex items-center mb-3'>
|
||||
<div class="w-36 text-right pr-3">Name:</div>
|
||||
<div class="w-full pl-3 p-1 text-left bg-[#f5f5fb] flex items-center justify-between">
|
||||
<div>
|
||||
{{ name }}
|
||||
</div>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="复制到剪贴板"
|
||||
placement="top"
|
||||
>
|
||||
<el-button :icon="CopyDocument" text bg type="primary" @click="copyRes(name)"> </el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
在不同格式(十六进制、rgb、hsl和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 到 9,A - 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 代表 Cyan、Magenta、Yellow 和 Key,它们与打印机中的墨水颜色相匹配。虽然屏幕通常以 RGB 来显示颜色,而打印机通常使用青色、品红色、黄色和黑色的组合来表示颜色,这些是最常见的墨水颜色。<br/>
|
||||
</div>
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,97 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
import { copy } from '@/utils/string';
|
||||
import { Codemirror } from "vue-codemirror";
|
||||
import '@codemirror/search';
|
||||
import '@codemirror/state';
|
||||
import '@codemirror/commands';
|
||||
import * as prettier from "prettier/standalone";
|
||||
import * as parserCss from 'prettier/parser-postcss';
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { minify } from "csso"
|
||||
|
||||
const info = reactive({
|
||||
title: "css代码格式化/压缩",
|
||||
code: '',
|
||||
isParseErr: false,
|
||||
parseErr: '',
|
||||
})
|
||||
|
||||
//格式化
|
||||
const formatCode = async () => {
|
||||
try {
|
||||
info.code = await prettier.format(info.code, { parser: "css", plugins: [parserCss]})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
ElMessage({
|
||||
showClose: true,
|
||||
message: '请填入正确代码格式',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//压缩
|
||||
const compress = async () => {
|
||||
try {
|
||||
info.code = minify(info.code, {
|
||||
restructure: true,
|
||||
}).css
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
//清空输入框
|
||||
const clear = () => {
|
||||
info.code = ''
|
||||
}
|
||||
|
||||
const copyRes = async () => {
|
||||
copy(info.code)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl bg-white ">
|
||||
|
||||
<div>
|
||||
<codemirror
|
||||
v-model="info.code"
|
||||
placeholder="这里是代码..."
|
||||
:style="{ height: '400px' }"
|
||||
:autofocus="true"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<el-button type="primary" @click="formatCode">格式化</el-button>
|
||||
<el-button type="primary" @click="compress">压缩</el-button>
|
||||
<el-button type="primary" @click="copyRes">复制</el-button>
|
||||
<el-button type="primary" @click="clear">清空</el-button>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 min-h-md bg-red-100 p-3 mb-3" v-show="info.isParseErr">
|
||||
<el-text type="danger">{{ info.parseErr }}</el-text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
在线css格式化,在线压缩css代码
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted,ref } 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: "帮我决定",
|
||||
@@ -86,6 +87,14 @@ onMounted(() => {
|
||||
<el-button @click="clear">清空</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
选择困难症患者的福音!由我来帮你决定,可自定义决定内容,随机选择决定
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<script setup lang="ts">
|
||||
import { ref,reactive } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
import DiceCore from "@/components/Tools/Dice/DiceCore.vue"
|
||||
// import { copy } from '@/utils/string'
|
||||
const info = reactive({
|
||||
title: "投骰子",
|
||||
})
|
||||
//骰子数量
|
||||
const diceNum = ref(1)
|
||||
const genStatus = ref(false)
|
||||
const diceRef = ref()
|
||||
const resetDiceKey = ref(0)
|
||||
|
||||
const shake = async () => {
|
||||
genStatus.value = true
|
||||
|
||||
let res = await new Promise((resolve) => {
|
||||
for (let i = 0; i < diceNum.value; i++) {
|
||||
diceRef.value[i].throwDice()
|
||||
}
|
||||
resolve(true)
|
||||
})
|
||||
|
||||
//等待执行完成
|
||||
setTimeout(() => {
|
||||
console.log(res)
|
||||
genStatus.value = false
|
||||
}, 1200)
|
||||
}
|
||||
|
||||
//重置骰子点数
|
||||
const resetDice = () => {
|
||||
resetDiceKey.value++
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl bg-white">
|
||||
<view class="grow-[1.5]"></view>
|
||||
<div class="flex justify-center grow-[3]">
|
||||
<div class="flex flex-wrap h-96 items-center">
|
||||
<!-- 骰子组件 -->
|
||||
<DiceCore ref="diceRef" v-for="i in diceNum" :key="resetDiceKey" :class="[diceNum == 1 ? 'dice_full' : '', diceNum == 3 && i == 1 ? 'dice_full' : 'dice_default', diceNum == 5 && i == 3 ? 'dice_full' : 'dice_default']"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="ml-5 flex justify-center">
|
||||
<el-text style="">骰子数量:</el-text>
|
||||
<div class="w-4/6">
|
||||
<el-slider class="w-72 ml-3" v-model="diceNum" :min="1" :max="6" :step="1" blockSize="18" :showValue="true"></el-slider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-5 flex justify-center">
|
||||
<el-button type="primary" class="w-48" size="large" @click="shake" v-if="!genStatus">投骰子</el-button>
|
||||
<el-button type="primary" class="w-48" size="large" disabled v-else>投骰子...</el-button>
|
||||
<el-button type="primary" class="w-48" size="large" @click="resetDice">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
在线投骰子,骰子工具,自定义骰子数量
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dice_default{
|
||||
flex: 0 0 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.dice_full{
|
||||
flex: 0 0 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,115 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onBeforeUnmount } from 'vue'
|
||||
|
||||
//定义emit
|
||||
const emit = defineEmits(['throwEnd'])
|
||||
//是否正在掷筛子
|
||||
const isDicing = ref(false)
|
||||
//当前显示的动画图片索引
|
||||
const aniIndex = ref(0)
|
||||
//掷筛子动画所用到的图片(4张骰子旋转过程中的图)
|
||||
const diceAnimationImages = ref([
|
||||
'/images/dice/dice_run1.png',
|
||||
'/images/dice/dice_run2.png',
|
||||
'/images/dice/dice_run3.png',
|
||||
'/images/dice/dice_run4.png'
|
||||
])
|
||||
//骰子每一个点的对应的图片
|
||||
const diceImages = ref({
|
||||
//1点的图片
|
||||
1: '/images/dice/dice1.png',
|
||||
//2点的图片
|
||||
2: '/images/dice/dice2.png',
|
||||
//3点的图片
|
||||
3: '/images/dice/dice3.png',
|
||||
//4点的图片
|
||||
4: '/images/dice/dice4.png',
|
||||
//5点的图片
|
||||
5: '/images/dice/dice5.png',
|
||||
//6点的图片
|
||||
6: '/images/dice/dice6.png'
|
||||
})
|
||||
//当前掷筛子掷到的点数(默认1点)
|
||||
const currentPoint = ref(1)
|
||||
//定时器
|
||||
const timer = ref(0)
|
||||
|
||||
const getDicePoint = async () => {
|
||||
let point = 1;
|
||||
//从1~6随机一个数
|
||||
point = Math.floor(Math.random() * 6 + 1);
|
||||
return point;
|
||||
}
|
||||
//掷骰子
|
||||
const throwDice = async () => {
|
||||
//如果当前骰子正在滚动则不能掷骰子
|
||||
if (isDicing.value) {
|
||||
return
|
||||
}
|
||||
//从接口获取点数
|
||||
currentPoint.value = await getDicePoint();
|
||||
//开启骰子动画
|
||||
await startAnimation();
|
||||
//动画完毕之后可以通知父组件当前掷到的点数
|
||||
emit('throwEnd', currentPoint.value);
|
||||
}
|
||||
//开启动画效果
|
||||
const startAnimation = async () => {
|
||||
return new Promise((resolve) => {
|
||||
//设置筛子开始运动
|
||||
isDicing.value = true;
|
||||
//记录动画次数
|
||||
let num = 0;
|
||||
//每隔100毫秒来回切换一张“动”图形成掷骰子的动画
|
||||
timer.value = setInterval(() => {
|
||||
let index = aniIndex.value;
|
||||
index++;
|
||||
if (index >= diceAnimationImages.value.length) {
|
||||
index = 0;
|
||||
}
|
||||
aniIndex.value = index;
|
||||
num++;
|
||||
//差不多执行1.2秒钟的时候可以停止了
|
||||
if (num > 12) {
|
||||
//关闭定时器
|
||||
clearInterval(timer.value);
|
||||
//设置骰子停止
|
||||
isDicing.value = false;
|
||||
//返回结果
|
||||
resolve(true);
|
||||
}
|
||||
}, 100);
|
||||
})
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
//组件销毁之前清除定时器
|
||||
clearInterval(timer.value);
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
throwDice
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 骰子组件 -->
|
||||
<div class="dice-wrap" @tap="throwDice">
|
||||
<!-- 筛子运动时候的展示的图片 -->
|
||||
<el-image v-if="isDicing" :src="diceAnimationImages[aniIndex]" class="dice-icon" loading="lazy"></el-image>
|
||||
<!-- 筛子静止时候的显示的对应点数的图片 -->
|
||||
<el-image v-else :src="diceImages[currentPoint]" class="dice-icon" loading="lazy"></el-image>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dice-wrap {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.dice-icon {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
import { CodeDiff } from "v-code-diff";
|
||||
// import { copy } from '@/utils/string'
|
||||
const info = reactive({
|
||||
@@ -31,6 +32,14 @@ const info = reactive({
|
||||
theme="dark"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
在线文本差异比对支持中文、英文、代码比对,不限制字数轻松比对文本。
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<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: "tool name",
|
||||
@@ -16,9 +17,17 @@ const info = reactive({
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="">
|
||||
<div class="p-4 rounded-2xl bg-white">
|
||||
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
示例...
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive,ref,onMounted } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
import { escape, unescape } from 'lodash';
|
||||
import { copy } from '@/utils/string'
|
||||
const info = reactive({
|
||||
title: "HTML实体转义",
|
||||
})
|
||||
|
||||
const content = ref('')
|
||||
const cleanContent = ref('')
|
||||
|
||||
const parser = (type: string) => {
|
||||
if (type == 'toHTML') {
|
||||
//转html
|
||||
cleanContent.value = unescape(content.value)
|
||||
} else {
|
||||
//转实体
|
||||
cleanContent.value = escape(content.value)
|
||||
}
|
||||
}
|
||||
|
||||
//清空输入框
|
||||
const clear = () => {
|
||||
content.value = ''
|
||||
cleanContent.value = ''
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl bg-white ">
|
||||
<div class="mb-6">
|
||||
<el-input v-model="content" :rows="10" type="textarea" placeholder="请输入内容" @change="parser"></el-input>
|
||||
<div class="mt-3">
|
||||
<el-button type="primary" @click="parser('toEntity')">HTML转实体</el-button>
|
||||
<el-button type="primary" @click="parser('toHTML')">实体转HTML</el-button>
|
||||
<el-button type="primary" @click="copy(cleanContent)">复制结果</el-button>
|
||||
<el-button type="primary" @click="clear">清除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<el-input v-html="cleanContent" v-model="cleanContent" :rows="10" type="textarea" placeholder="" @change="parser"></el-input>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
html实体转义,实体转义成html
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,82 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
import { copy } from '@/utils/string';
|
||||
import { Codemirror } from "vue-codemirror";
|
||||
import '@codemirror/search';
|
||||
import '@codemirror/state';
|
||||
import '@codemirror/commands';
|
||||
import * as prettier from "prettier/standalone";
|
||||
import * as parserHtml from 'prettier/parser-html';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const info = reactive({
|
||||
title: "html格式化/压缩",
|
||||
code: '',
|
||||
isParseErr: false,
|
||||
parseErr: '',
|
||||
})
|
||||
|
||||
//格式化
|
||||
const formatCode = async () => {
|
||||
try {
|
||||
info.code = await prettier.format(info.code, { parser: "html", plugins: [parserHtml]})
|
||||
} catch (error) {
|
||||
ElMessage({
|
||||
showClose: true,
|
||||
message: '请填入正确代码格式',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//清空输入框
|
||||
const clear = () => {
|
||||
info.code = ''
|
||||
}
|
||||
|
||||
const copyRes = async () => {
|
||||
copy(info.code)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl bg-white ">
|
||||
|
||||
<div>
|
||||
<codemirror
|
||||
v-model="info.code"
|
||||
placeholder="这里是代码..."
|
||||
:style="{ height: '400px' }"
|
||||
:autofocus="true"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<el-button type="primary" @click="formatCode">格式化</el-button>
|
||||
<el-button type="primary" @click="copyRes">复制</el-button>
|
||||
<el-button type="primary" @click="clear">清空</el-button>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 min-h-md bg-red-100 p-3 mb-3" v-show="info.isParseErr">
|
||||
<el-text type="danger">{{ info.parseErr }}</el-text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
提供在线html、xml格式化
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,145 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
// import { copy } from '@/utils/string'
|
||||
const info = reactive({
|
||||
title: "HTTP状态码",
|
||||
})
|
||||
// ascii: https://ascii.co.uk/table
|
||||
const tableDataControlOne = reactive([
|
||||
{code:"100 Continue",desc:"请求者应当继续提出请求。 服务器返回此代码表示已收到请求的第一部分,正在等待其余部分"},
|
||||
{code:"101 Switching Protocols",desc:"请求者已要求服务器切换协议,服务器已确认并准备切换"},
|
||||
{code:"102 Processing",desc:"由WebDAV(RFC 2518)扩展的状态码,代表处理将被继续执行。"},
|
||||
])
|
||||
|
||||
const tableDataControlTwo = reactive([
|
||||
{code:"200 OK",desc:"服务器已成功处理了请求。 通常,这表示服务器提供了请求的网页"},
|
||||
{code:"201 Created",desc:"请求成功并且服务器创建了新的资源"},
|
||||
{code:"202 Accepted",desc:"服务器已接受请求,但尚未处理"},
|
||||
{code:"203 Non-Authoritative Information",desc:"服务器已成功处理了请求,但返回的信息可能来自另一来源"},
|
||||
{code:"204 No Content",desc:"服务器成功处理了请求,但没有返回任何内容"},
|
||||
{code:"205 Reset Content",desc:"服务器成功处理了请求,且没有返回任何内容。但是与204响应不同,返回此状态码的响应要求请求者重置文档视图。该响应主要是被用于接受用户输入后,立即重置表单,以便用户能够轻松地开始另一次输入"},
|
||||
{code:"206 Partial Content",desc:"服务器成功处理了部分 GET 请求"},
|
||||
{code:"207 Multi-Status",desc:"由WebDAV(RFC 2518)扩展的状态码,代表之后的消息体将是一个XML消息,并且可能依照之前子请求数量的不同,包含一系列独立的响应代码"},
|
||||
{code:"208 Already Reported",desc:"一个DAV的绑定成员被前一个请求枚举,并且没有被再一次包括"},
|
||||
{code:"226 IM Used",desc:"服务器已经满足了请求所要的资源,并且响应是一个或者多个实例操作应用于当前实例的结果"},
|
||||
])
|
||||
|
||||
const tableDataControlThree = reactive([
|
||||
{code:"300 Multiple Choices",desc:"针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。"},
|
||||
{code:"301 Moved Permanently",desc:"请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。"},
|
||||
{code:"302 Move Temporarily",desc:"服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求"},
|
||||
{code:"303 See Other",desc:"请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。"},
|
||||
{code:"304 Not Modified",desc:"自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。"},
|
||||
{code:"305 Use Proxy",desc:"请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理"},
|
||||
{code:"306 Switch Proxy",desc:"在最新版的规范中,306状态码已经不再被使用"},
|
||||
{code:"307 Temporary Redirect",desc:"服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。"},
|
||||
{code:"308 Permanent Redirect",desc:"这个请求和以后的请求都应该被另一个URI地址重新发送。307、308和302、301有相同的表现,但是不允许HTTP方法改变。例如,请求表单到一个永久转移的资源将会继续顺利地执行。"},
|
||||
])
|
||||
|
||||
const tableDataControlFour = reactive([
|
||||
{code:"400 Bad Request",desc:"服务器不理解请求的语法"},
|
||||
{code:"401 Unauthorized",desc:"请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应"},
|
||||
{code:"402 Payment Required",desc:"该状态码是为了将来可能的需求而预留的"},
|
||||
{code:"403 Forbidden",desc:"请求有效,但服务器拒绝操作。用户可能没有资源的必要权限。"},
|
||||
{code:"404 Not Found",desc:"服务器找不到请求的网页"},
|
||||
{code:"405 Method Not Allowed",desc:"禁用请求中指定的方法"},
|
||||
{code:"406 Not Acceptable",desc:"无法使用请求的内容特性响应请求的网页"},
|
||||
{code:"407 Proxy Authentication Required",desc:"此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理"},
|
||||
{code:"408 Request Timeout",desc:"服务器等候请求时发生超时"},
|
||||
{code:"409 Conflict",desc:"服务器在完成请求时发生冲突。 服务器必须在响应中包含有关冲突的信息"},
|
||||
{code:"410 Gone",desc:"如果请求的资源已永久删除,服务器就会返回此响应"},
|
||||
{code:"411 Length Required",desc:"服务器不接受不含有效内容长度标头字段的请求"},
|
||||
{code:"412 Precondition Failed",desc:"服务器未满足请求者在请求中设置的其中一个前提条件"},
|
||||
{code:"413 Request Entity Too Large",desc:"服务器无法处理请求,因为请求实体过大,超出服务器的处理能力"},
|
||||
{code:"414 Request-URI Too Long",desc:"请求的 URI(通常为网址)过长,服务器无法处理"},
|
||||
{code:"415 Unsupported Media Type",desc:"请求的格式不受请求页面的支持"},
|
||||
{code:"416 Requested Range Not Satisfiable",desc:"如果页面无法提供请求的范围,则服务器会返回此状态代码"},
|
||||
{code:"417 Expectation Failed",desc:"服务器未满足”期望“请求标头字段的要求"},
|
||||
{code:"418 I'm a teapot",desc:"这个代码是在1998年作为传统的IETF April Fools‘ jokes被定义的在RFC2324,超文本咖啡罐控制协议,但是并没有被实际的HTTP服务器实现。RFC指定了这个代码应该是由茶罐返回给速溶咖啡"},
|
||||
{code:"421 Misdirected Request",desc:"请求被指向到无法生成响应的服务器(比如由于连接重复使用)"},
|
||||
{code:"422 Unprocessable Entity",desc:"请求格式正确,但是由于含有语义错误,无法响应。(RFC 4918 WebDAV)"},
|
||||
{code:"423 Locked",desc:"当前资源被锁定"},
|
||||
{code:"424 Failed Dependency",desc:"由于之前的某个请求发生的错误,导致当前请求失败,例如 PROPPATCH"},
|
||||
{code:"425 Too Early",desc:"状态码 425 Too Early 代表服务器不愿意冒风险来处理该请求,原因是处理该请求可能会被“重放”,从而造成潜在的重放攻击。"},
|
||||
{code:"426 Upgrade Required",desc:"客户端应当切换到TLS/1.0"},
|
||||
{code:"429 Too Many Requests",desc:"用户在给定的时间内发送了太多请求"},
|
||||
{code:"431 Request Header Fields Too Large",desc:"服务器不愿意处理请求,因为单个报头字段或所有报头字段都太大了。"},
|
||||
{code:"449 Retry With",desc:"由微软扩展,代表请求应当在执行完适当的操作后进行重试。"},
|
||||
{code:"451 Unavailable For Legal Reasons",desc:"该请求因法律原因不可用"},
|
||||
])
|
||||
|
||||
const tableDataControlFive = reactive([
|
||||
{code:"500 Internal Server Error",desc:"服务器遇到错误,无法完成请求;一般来说,这个问题都会在服务器端的源代码出现错误时出现。"},
|
||||
{code:"501 Not Implemented",desc:"服务器不支持当前请求所需要的某个功能。当服务器无法识别请求的方法,并且无法支持其对任何资源的请求。"},
|
||||
{code:"502 Bad Gateway",desc:"作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应"},
|
||||
{code:"503 Service Unavailable",desc:"服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态"},
|
||||
{code:"504 Gateway Timeout",desc:"服务器作为网关或代理,但是没有及时从上游服务器收到请求"},
|
||||
{code:"505 HTTP Version Not Supported",desc:"服务器不支持请求中所用的 HTTP 协议版本"},
|
||||
{code:"506 Variant Also Negotiates",desc:"由《透明内容协商协议》(RFC 2295)扩展,代表服务器存在内部配置错误:被请求的协商变元资源被配置为在透明内容协商中使用自己,因此在一个协商处理中不是一个合适的重点"},
|
||||
{code:"507 Insufficient Storage",desc:"服务器无法存储完成请求所必须的内容。这个状况被认为是临时的"},
|
||||
{code:"509 Bandwidth Limit Exceeded",desc:"服务器达到带宽限制。这不是一个官方的状态码,但是仍被广泛使用。"},
|
||||
{code:"510 Not Extended",desc:"获取资源所需要的策略并没有被满足"},
|
||||
{code:"511 Network Authentication Required",desc:"客户端需要进行身份验证才能获得网络访问权限。"},
|
||||
])
|
||||
|
||||
//copy
|
||||
// const copyRes = async (resStr: string) => {
|
||||
// copy(resStr)
|
||||
// }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl bg-white">
|
||||
<el-table :data="tableDataControlOne" class="w-full mb-3">
|
||||
<el-table-column label="1xx 信息,表示临时响应并需要请求者继续执行操作" align="center">
|
||||
<el-table-column prop="code" label="状态码" width="270" />
|
||||
<el-table-column prop="desc" label="含义解释" />
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-table :data="tableDataControlTwo" class="w-full mb-3">
|
||||
<el-table-column label="2xx 成功,操作被成功接收并处理" align="center">
|
||||
<el-table-column prop="code" label="状态码" width="270" />
|
||||
<el-table-column prop="desc" label="含义解释"/>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-table :data="tableDataControlThree" class="w-full mb-3">
|
||||
<el-table-column label="3xx 表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向" align="center">
|
||||
<el-table-column prop="code" label="状态码" width="270" />
|
||||
<el-table-column prop="desc" label="含义解释"/>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-table :data="tableDataControlFour" class="w-full mb-3">
|
||||
<el-table-column label="4xx 客户端错误,请求包含语法错误或无法完成请求" align="center">
|
||||
<el-table-column prop="code" label="状态码" width="270" />
|
||||
<el-table-column prop="desc" label="含义解释"/>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-table :data="tableDataControlFive" class="w-full mb-3">
|
||||
<el-table-column label="5xx 这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错" align="center">
|
||||
<el-table-column prop="code" label="状态码" width="270" />
|
||||
<el-table-column prop="desc" label="含义解释"/>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
超全面http状态对应的名称和含义解释
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,152 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref,computed, watch } from 'vue'
|
||||
import { UploadProps,UploadRawFile,genFileId } from 'element-plus'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
|
||||
const info = reactive({
|
||||
title: "图片切割",
|
||||
})
|
||||
|
||||
const fileList = ref()
|
||||
const lineNum = ref(3)
|
||||
const image = ref({} as any)
|
||||
const cutImg = ref([] as string[])
|
||||
const dataFileRef = ref()
|
||||
|
||||
//上传
|
||||
const updateDataFile = async (params) => {
|
||||
let reader = new FileReader();
|
||||
reader.readAsDataURL(params.file);
|
||||
reader.addEventListener(
|
||||
'load',
|
||||
async () => {
|
||||
const imageTmp = new Image();
|
||||
imageTmp.onload = () => {
|
||||
image.value = imageTmp;
|
||||
cut();
|
||||
};
|
||||
imageTmp.src = reader.result as string;
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
//当超出限制时,执行的钩子函数
|
||||
//这里覆盖前一个文件
|
||||
const handleExceed: UploadProps['onExceed'] = (files) => {
|
||||
dataFileRef.value!.clearFiles()
|
||||
const file = files[0] as UploadRawFile
|
||||
file.uid = genFileId()
|
||||
dataFileRef.value!.handleStart(file)
|
||||
dataFileRef.value!.submit()
|
||||
}
|
||||
|
||||
//切割
|
||||
const cut = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const img = image.value;
|
||||
|
||||
const wpiece = Math.floor(img.naturalWidth / lineNum.value);
|
||||
const hpiece = Math.floor(img.naturalHeight / lineNum.value);
|
||||
const results = [] as string[];
|
||||
|
||||
canvas.width = wpiece;
|
||||
canvas.height = hpiece;
|
||||
|
||||
for (let r = 0; r < lineNum.value; r++) {
|
||||
for (let c = 0; c < lineNum.value; c++) {
|
||||
ctx?.drawImage(
|
||||
img,
|
||||
c * wpiece,
|
||||
r * hpiece,
|
||||
wpiece,
|
||||
hpiece,
|
||||
0,
|
||||
0,
|
||||
wpiece,
|
||||
hpiece
|
||||
);
|
||||
results.push(canvas.toDataURL());
|
||||
}
|
||||
}
|
||||
cutImg.value = results;
|
||||
}
|
||||
|
||||
//计算切割样式
|
||||
const cutImgStyle = computed(() => {
|
||||
const width = `width: ${image.value.width || 800}px;`;
|
||||
const grid = `grid: repeat(${lineNum.value}, 1fr) / repeat(${
|
||||
lineNum.value
|
||||
}, 1fr);`;
|
||||
return width + grid;
|
||||
})
|
||||
|
||||
watch(cutImgStyle, () => {
|
||||
if (fileList.value) cut();
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl bg-white">
|
||||
<el-upload
|
||||
v-model:file-list="fileList"
|
||||
class="dataFileRef flex"
|
||||
ref="dataFileRef"
|
||||
accept="image/*"
|
||||
:http-request="updateDataFile"
|
||||
:on-exceed="handleExceed"
|
||||
:limit="1"
|
||||
>
|
||||
<el-button type="primary">请上传需要切割的图片</el-button>
|
||||
</el-upload>
|
||||
<div class="flex mt-3">
|
||||
<div class="flex">
|
||||
<el-text>行数和列数</el-text>
|
||||
<el-input-number v-model="lineNum" :min="1" :max="10" class="ml-3"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mt-3 min-h-md bg-gray-100 p-3 mb-3 flex flex-col" v-if="image.src">
|
||||
<!-- 预览 -->
|
||||
<div class="mb-3">
|
||||
<el-text>预览: </el-text>
|
||||
<div :style="cutImgStyle" class="grid gap-1 max-w-full">
|
||||
<img v-for="(src,index) in cutImg" :key="index" :src="src" alt="结果" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 原图 -->
|
||||
<div>
|
||||
<el-text>原图: </el-text>
|
||||
<div>
|
||||
<img :src="image.src" alt="原图" v-if="image.src"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-empty :image-size="200" description="无预览"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
将图片分割成四宫格、九宫格、十六宫格,支持自定义行与列;<br>
|
||||
比如:九宫格切图广泛应用于微信朋友圈,微博等社交媒体。
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,110 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
import { copy } from '@/utils/string';
|
||||
import { Codemirror } from "vue-codemirror";
|
||||
import '@codemirror/search';
|
||||
import '@codemirror/state';
|
||||
import '@codemirror/commands';
|
||||
import * as prettier from "prettier/standalone";
|
||||
import * as parserBabel from 'prettier/parser-babel';
|
||||
import * as prettierPluginEstree from "prettier/plugins/estree";
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { minify } from "terser"
|
||||
|
||||
const info = reactive({
|
||||
title: "js代码格式化/压缩",
|
||||
code: '',
|
||||
isParseErr: false,
|
||||
parseErr: '',
|
||||
})
|
||||
|
||||
interface Error {
|
||||
name: string;
|
||||
message: string;
|
||||
stack?: string;
|
||||
}
|
||||
|
||||
|
||||
//格式化
|
||||
const formatCode = async () => {
|
||||
try {
|
||||
info.code = await prettier.format(info.code, { parser: "babel", plugins: [parserBabel, prettierPluginEstree]})
|
||||
} catch (error) {
|
||||
ElMessage({
|
||||
showClose: true,
|
||||
message: '请填入正确的js代码',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//混淆压缩
|
||||
const confuseCompress = async () => {
|
||||
try {
|
||||
let res = await minify(info.code, {
|
||||
mangle: {
|
||||
toplevel: true,
|
||||
}
|
||||
})
|
||||
info.code = res.code != undefined ? res.code : info.code
|
||||
} catch(error) {
|
||||
ElMessage({
|
||||
showClose: true,
|
||||
message: '请填入正确的js代码: ' + (error as Error).message,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//清空输入框
|
||||
const clear = () => {
|
||||
info.code = ''
|
||||
}
|
||||
|
||||
const copyRes = async () => {
|
||||
copy(info.code)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl bg-white ">
|
||||
|
||||
<div>
|
||||
<codemirror
|
||||
v-model="info.code"
|
||||
placeholder="这里是代码..."
|
||||
:style="{ height: '400px' }"
|
||||
:autofocus="true"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<el-button type="primary" @click="formatCode">格式化</el-button>
|
||||
<el-button type="primary" @click="confuseCompress">混淆压缩</el-button>
|
||||
<el-button type="primary" @click="copyRes">复制</el-button>
|
||||
<el-button type="primary" @click="clear">清空</el-button>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 min-h-md bg-red-100 p-3 mb-3" v-show="info.isParseErr">
|
||||
<el-text type="danger">{{ info.parseErr }}</el-text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
JS格式化/压缩工具,提供在线JS格式化、JS压缩、JS混淆
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,103 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive,ref,onMounted } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
import {jwtDecode} from 'jwt-decode'
|
||||
const info = reactive({
|
||||
title: "JWT解析",
|
||||
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b29scy5yYW5ibG9ncy5jb20iLCJzdWIiOiJhcnlhIiwiYXVkIjoidGFvcmFuIiwiaWF0IjoxNzE3MDM2MzMzLCJleHAiOjE3MTcwMzY5MzN9.9v_eDdsPnvcY7qZatNnKvN9VEf7t7o72OgwhLS6gy6w',
|
||||
})
|
||||
|
||||
const decodeHeader = ref()
|
||||
const decodePayload = ref()
|
||||
const invalidToken = ref(false)
|
||||
|
||||
//解析
|
||||
const parser = () => {
|
||||
try {
|
||||
decodePayload.value = JSON.stringify(jwtDecode(info.token), null, '\t')
|
||||
decodeHeader.value = JSON.stringify(jwtDecode(info.token, {header: true}), null, '\t')
|
||||
} catch (e) {
|
||||
|
||||
console.log('Invalid token', e)
|
||||
invalidToken.value = true
|
||||
}
|
||||
}
|
||||
|
||||
//清空输入框
|
||||
const clear = () => {
|
||||
info.token = ''
|
||||
decodeHeader.value = ''
|
||||
decodePayload.value = ''
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
parser()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl bg-white ">
|
||||
<div class="mb-6">
|
||||
<el-input v-model="info.token" :rows="5" type="textarea" placeholder="请输入Token" @change="parser"></el-input>
|
||||
<el-text type="danger" v-if="invalidToken">Invalid token</el-text>
|
||||
<div class="mt-3">
|
||||
<el-button type="primary" @click="parser">解析</el-button>
|
||||
<el-button type="primary" @click="clear">清除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- header -->
|
||||
<div v-if="decodeHeader">
|
||||
<div class="mb-3">Header(头部)</div>
|
||||
<el-input
|
||||
v-model="decodeHeader"
|
||||
type="textarea"
|
||||
:autosize="true"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- payload -->
|
||||
<div v-if="decodePayload">
|
||||
<div class="mb-3 mt-3">Payload(载荷)</div>
|
||||
<el-input
|
||||
v-model="decodePayload"
|
||||
type="textarea"
|
||||
:autosize="true"
|
||||
v-if="decodePayload"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
解析和解码JSON Web Token(jwt)并显示其内容。<br>
|
||||
<br>
|
||||
1. JWT 是 JSON Web Tokens 的缩写,是目前最流行的跨域认证解决方案,是一个开放式标准(RFC 7519),用于在各方之间以JSON对象安全传输信息。<br/>
|
||||
2. Header(头部)<br/>
|
||||
- typ(type): 用来标识整个token字符串是一个JWT字符串<br/>
|
||||
- alg(algorithm): 用来说明这个JWT签发的时候所使用的签名和摘要算法<br/>
|
||||
3. Payload(载荷)<br/>
|
||||
- iss (Issuer):签发者<br/>
|
||||
- sub (Subject):主题<br/>
|
||||
- aud (Audience):接收者<br/>
|
||||
- exp (Expiration time):过期时间<br/>
|
||||
- nbf (Not Before):生效时间<br/>
|
||||
- iat (Issued At):签发时间<br/>
|
||||
- jti (JWT ID):编号<br/>
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,11 +1,11 @@
|
||||
<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 { transferred, copy } from '@/utils/string';
|
||||
import { Codemirror } from "vue-codemirror";
|
||||
import { json } from '@codemirror/lang-json';
|
||||
import '@codemirror/search';
|
||||
import { lineNumbers } from '@codemirror/view';
|
||||
import '@codemirror/state';
|
||||
import '@codemirror/commands';
|
||||
// import { syntaxTree } from '@codemirror/language';
|
||||
@@ -31,7 +31,7 @@ import '@codemirror/commands';
|
||||
const info = reactive({
|
||||
title: "Json在线转换",
|
||||
code: '',
|
||||
extensions: [json(), lineNumbers()],
|
||||
extensions: [json()], //[json(), lineNumbers()],
|
||||
isParseErr: false,
|
||||
parseErr: ''
|
||||
})
|
||||
@@ -123,6 +123,14 @@ const copyRes = async () => {
|
||||
<el-text type="danger">{{ info.parseErr }}</el-text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。它基于JavaScript的一个子集,但与语言无关,因此在多种编程环境中广泛使用。JSON格式易于人阅读和编写,同时也易于机器解析和生成。它通常用于网络应用程序中服务器与客户端之间的数据传输。<br>
|
||||
JSON 工具提供实时编辑和预览JSON 数据,语法高亮、校验、格式化、转义,去转义、压缩等功能,可以提高阅读修改的效率和准确性
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<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 { Md5 } from 'ts-md5'
|
||||
import { copy } from '@/utils/string'
|
||||
const info = reactive({
|
||||
@@ -83,6 +84,14 @@ const copyRes = async (resStr: string) => {
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
在线md5加密,md5加密算法,md5加密工具,免费MD5在线加密,可生成32位md5,16位md5
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
const info = reactive({
|
||||
title: "在线markdown编辑器",
|
||||
content: '',
|
||||
@@ -21,6 +21,14 @@ const info = reactive({
|
||||
<div class="">
|
||||
<v-md-editor v-model="info.content" height="500px"></v-md-editor>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
在线编辑markdown
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<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 { toMorse, toText } from '@/utils/morse'
|
||||
const info = reactive({
|
||||
@@ -49,6 +50,14 @@ const copyRes = async (resStr: string) => {
|
||||
<el-input type="textarea" :rows="8" v-model="info.tranRes"></el-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
摩尔斯电码(或摩斯电码,Morse code)是一种编码系统,通过不同顺序的信号表示英文字母、数字和标点符号。由美国人艾尔菲德·维尔在协助Samuel Morse发明摩尔斯电报机(1835年)时创造。今天,国际摩尔斯电码仍在使用。
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<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, numberToChinese } from '@/utils/string'
|
||||
const info = reactive({
|
||||
title: "数字转金额大写",
|
||||
@@ -42,6 +43,13 @@ const copyRes = async (resStr: string) => {
|
||||
<el-input type="textarea" :rows="8" v-model="info.tranRes"></el-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
可以将阿拉伯数字金额转换为中文大写金额的在线工具;用户输入阿拉伯数字金额,如100,会立即转换成“壹佰”的中文
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<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: "色板",
|
||||
@@ -70,6 +71,14 @@ const copyRes = async (resStr: string) => {
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
在线复制颜色,好看的颜色组合色板
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive,ref } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
import QRCodeVue3 from 'qrcode-vue3'
|
||||
import { Delete, Plus } from '@element-plus/icons-vue'
|
||||
import { ElMessage, type UploadFile } from 'element-plus'
|
||||
@@ -68,25 +69,27 @@ const gen = () => {
|
||||
<div class="w-16 mr-2"><el-text>尺寸</el-text></div>
|
||||
<el-input v-model="info.size" class="">
|
||||
<template #append>
|
||||
<el-select v-model="info.size" class="w-32">
|
||||
<el-option label="常规 200px" value="200" />
|
||||
<el-option label="常规 300px" value="300" />
|
||||
<el-option label="适中 350px" value="350" />
|
||||
<el-option label="较大 500px" value="500" />
|
||||
<!-- 分割线 start -->
|
||||
<div class="flex justify-center bg-gray-200 w-full">
|
||||
<div class="h-[1px] bg-gray-200 w-4/5"></div>
|
||||
</div>
|
||||
<!-- 分割线 end -->
|
||||
<el-option label="超大 1000px" value="1000" />
|
||||
<el-option label="超大 1200px" value="1200" />
|
||||
</el-select>
|
||||
<el-select v-model="info.size" class="size-select">
|
||||
<el-option label="常规 200px" value="200" />
|
||||
<el-option label="常规 300px" value="300" />
|
||||
<el-option label="适中 350px" value="350" />
|
||||
<el-option label="较大 500px" value="500" />
|
||||
<!-- 分割线 start -->
|
||||
<div class="flex justify-center bg-gray-200 w-full">
|
||||
<div class="h-[1px] bg-gray-200 w-4/5"></div>
|
||||
</div>
|
||||
<!-- 分割线 end -->
|
||||
<el-option label="超大 1000px" value="1000" />
|
||||
<el-option label="超大 1200px" value="1200" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="flex mb-3">
|
||||
<el-text class="w-16">纠错级别</el-text>
|
||||
<el-select v-model="info.errorCorrectionLevel" class="w-36">
|
||||
<div class="w-16">
|
||||
<el-text>纠错级别</el-text>
|
||||
</div>
|
||||
<el-select v-model="info.errorCorrectionLevel" class="corr-select">
|
||||
<el-option label="L 可遮挡 7%" value="L" />
|
||||
<el-option label="M 可遮挡 15%" value="M" />
|
||||
<el-option label="Q 可遮挡 25%" value="Q" />
|
||||
@@ -171,6 +174,14 @@ const gen = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
根据内容生成二维码,自定义尺寸,纠错级别,颜色等带logo二维码
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -178,4 +189,10 @@ const gen = () => {
|
||||
.my-button{
|
||||
background-color:red;
|
||||
}
|
||||
.size-select{
|
||||
@apply w-36
|
||||
}
|
||||
.corr-select{
|
||||
@apply w-36
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,7 @@
|
||||
<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: "生成随机数",
|
||||
@@ -48,6 +49,13 @@ const random = () => {
|
||||
<el-button @click="random" type="primary" size="large" disabled v-else>生成...</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
可定制范围内进行随机数字,可用于抽奖、点名等用途
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
import { copy, genRandomStrByChars } from '@/utils/string'
|
||||
import { ElMessage } from 'element-plus'
|
||||
const info = reactive({
|
||||
@@ -148,6 +149,14 @@ onMounted(() => {
|
||||
<el-input type="textarea" :autosize="info.autosize" v-model="info.resStr"></el-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
在线生成密码支持开启或关闭大小写 、数字 、特殊符号,支持自定义字符,长度;批量生成密码
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<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 { Codemirror } from "vue-codemirror"
|
||||
import { lineNumbers } from '@codemirror/view';
|
||||
// import { lineNumbers } from '@codemirror/view';
|
||||
|
||||
const info = reactive({
|
||||
title: "正则表达式测试",
|
||||
extensions: [lineNumbers()],
|
||||
// extensions: [lineNumbers()],
|
||||
reg: '',
|
||||
content: `一些测试实例:
|
||||
邮箱1:demo@163.com
|
||||
@@ -128,7 +129,6 @@ const execMatch = () => {
|
||||
:autofocus="true"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="2"
|
||||
:extensions="info.extensions"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -158,6 +158,14 @@ const execMatch = () => {
|
||||
<el-input type="textarea" :rows="4" readonly v-model="info.matchRes"></el-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
正则表达式是对字符串(包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为“元字符”))操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。正则表达式是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<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 { ElMessage } from 'element-plus'
|
||||
import { copy } from '@/utils/string'
|
||||
import { isBinary, isOctal, isDecimal, isHexadecimal } from '@/utils/verify'
|
||||
@@ -200,7 +201,7 @@ const copyRes = async (index: any) => {
|
||||
<div class="mr-2 w-full">
|
||||
<el-input v-model="info.content">
|
||||
<template #prepend>
|
||||
<el-select v-model="info.chooseTranOptions" placeholder="Select" class="w-24">
|
||||
<el-select v-model="info.chooseTranOptions" placeholder="Select" class="choosetranoptions-select">
|
||||
<el-option
|
||||
v-for="item in info.tranOptions"
|
||||
:key="item.value"
|
||||
@@ -248,8 +249,18 @@ const copyRes = async (index: any) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
进位制其实是一种记数的方式,所以也称为进位记数法/位值计数法,可以用有限的数字符号代表所有的数值。 可使用数字符号的数目称为基数(英文:radix)或底数,基数为n,即可称n进位制,简称n进制。 例如平常生活中我们经常用到的十进制,就是使用10个阿拉伯数字0-9进行记数,所以它的基数就是10,称为十进制。
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.choosetranoptions-select{
|
||||
@apply w-24
|
||||
}
|
||||
</style>
|
||||
@@ -2,6 +2,7 @@
|
||||
import { ref, reactive } from "vue"
|
||||
import SignImageCore from './SignImageCore.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 { autoDown } from '@/utils/file'
|
||||
import { Jh_getTimeStamp } from '@/utils/time'
|
||||
@@ -80,6 +81,14 @@ const saveImg = () => {
|
||||
@getNewImg="info.getNewImg"
|
||||
></SignImageCore>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
在线图片裁剪,图片标注,图片滤镜,图片画笔、图片旋转、图片文字、图片尺寸调整等操作
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, shallowRef, onBeforeUnmount, onMounted } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
import '@wangeditor/editor/dist/css/style.css' // 引入富文本 css
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue' //富文本组件
|
||||
import { copy } from '@/utils/string'
|
||||
const info = reactive({
|
||||
title: "在线文本编辑/HTML获取",
|
||||
mode: 'default',
|
||||
})
|
||||
// 编辑器实例,必须用 shallowRef
|
||||
const editorRef = shallowRef()
|
||||
|
||||
// 内容
|
||||
const content = ref(``)
|
||||
// html
|
||||
const html = ref('')
|
||||
|
||||
// 工具栏配置
|
||||
const toolbarConfig = {
|
||||
excludeKeys: [
|
||||
"uploadImage",
|
||||
"group-video",
|
||||
"insertLink",
|
||||
]
|
||||
}
|
||||
|
||||
//编辑器配置
|
||||
const editorConfig = { placeholder: '请输入内容...' }
|
||||
|
||||
const handleCreated = (editor) => {
|
||||
editorRef.value = editor // 记录 editor 实例,重要!
|
||||
// html = editor.getHtml()
|
||||
}
|
||||
|
||||
const handleChange = (editor) => {
|
||||
html.value = editor.getHtml()
|
||||
}
|
||||
|
||||
|
||||
// 组件销毁时,也及时销毁编辑器
|
||||
onBeforeUnmount(() => {
|
||||
const editor = editorRef.value
|
||||
if (editor == null) return
|
||||
editor.destroy()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
})
|
||||
|
||||
//copy
|
||||
const copyRes = async (resStr: string) => {
|
||||
copy(resStr)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl bg-white h-[510px]">
|
||||
<div class="border z-10 mt-3 h-96">
|
||||
<Toolbar
|
||||
class=""
|
||||
:editor="editorRef"
|
||||
:defaultConfig="toolbarConfig"
|
||||
:mode="info.mode"
|
||||
/>
|
||||
<Editor
|
||||
class="border"
|
||||
v-model="content"
|
||||
:defaultConfig="editorConfig"
|
||||
:mode="info.mode"
|
||||
@onCreated="handleCreated"
|
||||
@onChange="handleChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="p-4 mt-3 rounded-2xl bg-white">
|
||||
<el-button type="primary" @click="copyRes(html)">复制HTML</el-button>
|
||||
<el-input
|
||||
v-model="html"
|
||||
disabled
|
||||
class="mt-3"
|
||||
type="textarea"
|
||||
autosize
|
||||
placeholder="html预览处"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
在线富文本编辑, html实时预览,在线编辑文本,文本编辑获取html
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.downext-select{
|
||||
@apply w-24
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, computed } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
import { copy } from '@/utils/string'
|
||||
|
||||
const info = reactive({
|
||||
title: "文本去重",
|
||||
content: '',
|
||||
tranRes: '',
|
||||
})
|
||||
|
||||
|
||||
//clear
|
||||
const clear = () => {
|
||||
info.content = ''
|
||||
}
|
||||
|
||||
|
||||
const removeDuplicate = computed(() => {
|
||||
let tmpArr = info.content.split('\n')
|
||||
let resultSet = new Set(tmpArr)
|
||||
let fromResArr = Array.from(resultSet)
|
||||
info.tranRes = fromResArr.join("\n")
|
||||
return info.tranRes
|
||||
})
|
||||
|
||||
//copy
|
||||
const copyRes = async () => {
|
||||
copy(info.tranRes)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl bg-white">
|
||||
<div>
|
||||
<el-input type="textarea" :rows="8" v-model="info.content"></el-input>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<el-button type="primary" @click="copyRes">复制结果</el-button>
|
||||
<el-button type="danger" @click="clear">清空内容</el-button>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 min-h-md bg-gray-100 p-3 mb-3">
|
||||
<el-input type="textarea" :rows="8" v-model="removeDuplicate"></el-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
可以删除或去除文本或字符串中的重复行
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, shallowRef, onBeforeUnmount } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
import html2canvas from "html2canvas";
|
||||
import '@wangeditor/editor/dist/css/style.css' // 引入富文本 css
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue' //富文本组件
|
||||
@@ -104,8 +105,8 @@ onBeforeUnmount(() => {
|
||||
<el-text>背景颜色:</el-text>
|
||||
<el-color-picker v-model="info.convasBackgroundColor" size="large" />
|
||||
</div>
|
||||
<div>
|
||||
<el-select v-model="info.downExt" class="" placeholder="Select">
|
||||
<div class="flex">
|
||||
<el-select v-model="info.downExt" class="downext-select" placeholder="Select">
|
||||
<el-option
|
||||
v-for="item in info.downExts"
|
||||
:key="item.value"
|
||||
@@ -132,11 +133,19 @@ onBeforeUnmount(() => {
|
||||
/>
|
||||
<div ref="poster" class="absolute top-0 -z-10" v-html="valueHtml"></div>
|
||||
</div>
|
||||
|
||||
<!-- v-html="valueHtml" -->
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
把文本转换成图片,生成长图,富文本自定义文字排版,可导出png,jpeg格式,可更换背景图,设置宽度,是好用的文本转图片工具
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.downext-select{
|
||||
@apply w-24
|
||||
}
|
||||
</style>
|
||||
@@ -3,6 +3,7 @@ import { onMounted, onUnmounted, reactive } from 'vue'
|
||||
import { VideoPause,VideoPlay,CopyDocument } from '@element-plus/icons-vue'
|
||||
import { Jh_getTimeStamp,Jh_timeStampToTime,Jh_convertTimeStamp } from '@/utils/time'
|
||||
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: "时间戳转换",
|
||||
@@ -95,16 +96,6 @@ const copyRes = async () => {
|
||||
<div class="flex flex-direction mt-4 justify-start">
|
||||
<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="毫秒/秒">
|
||||
<!-- <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-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>
|
||||
@@ -116,7 +107,7 @@ const copyRes = async () => {
|
||||
<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="时间戳">
|
||||
<template #prepend>
|
||||
<el-select v-model="info.chooseTranStampOption" placeholder="Select" class="w-24">
|
||||
<el-select v-model="info.chooseTranStampOption" placeholder="Select" class="trantimestamp-select">
|
||||
<el-option
|
||||
v-for="item in info.tranOptions"
|
||||
:key="item.value"
|
||||
@@ -129,13 +120,18 @@ const copyRes = async () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 rounded-2xl bg-white mt-5">
|
||||
<div class="text-xl mt-5 mb-5">时间戳</div>
|
||||
<el-text>Unix 时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。</el-text>
|
||||
</div>
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
时间戳,是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数(不考虑闰秒),用于表示一个时间点。然而,这种格式对于人类阅读并不友好,因此需要转换成可读的日期和时间格式。这个工具能够将时间戳快速转换为人类可读的日期时间格式,同时也支持反向转换,即将日期时间转换为时间戳。<br>
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.trantimestamp-select{
|
||||
@apply w-24
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,7 @@
|
||||
<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 { ElMessage } from 'element-plus'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
@@ -62,6 +63,14 @@ const copyRes = async (resStr: string) => {
|
||||
<el-input type="textarea" :autosize="info.autosize" v-model="info.resStr"></el-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
在线UUID批量生成
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<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 { Codemirror } from "vue-codemirror"
|
||||
import { lineNumbers } from '@codemirror/view';
|
||||
// import { lineNumbers } from '@codemirror/view';
|
||||
import { copy } from '@/utils/string'
|
||||
|
||||
|
||||
const info = reactive({
|
||||
title: "Unicode转中文",
|
||||
extensions: [lineNumbers()],
|
||||
// extensions: [lineNumbers()],
|
||||
content: '',
|
||||
tranRes: '',
|
||||
})
|
||||
@@ -62,7 +63,6 @@ const copyRes = async () => {
|
||||
:autofocus="true"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="2"
|
||||
:extensions="info.extensions"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -76,6 +76,14 @@ const copyRes = async () => {
|
||||
<el-input type="textarea" :rows="8" v-model="info.tranRes"></el-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
Unicode是计算机科学领域里的一项业界标准,有时候我们需要对一段文本或者一段内容进行重新排版编译的时候就需要将获取的值进行转码。
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import { reactive, onMounted } from '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 Area from './Area.vue'
|
||||
import Heat from './Heat.vue'
|
||||
@@ -14,9 +16,42 @@ const info = reactive({
|
||||
activeName: 'lengthTab',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
@@ -56,6 +91,14 @@ const handleClick = () => {
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
在线单位换算、长度单位换算、面积单位换算、时间单位换算、温度单位换算、压力单位换算、热量单位换算、功率单位换算。
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<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({
|
||||
@@ -54,6 +55,14 @@ const copyRes = async () => {
|
||||
<el-input type="textarea" :rows="8" v-model="info.tranRes"></el-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
在线url编码,在线url解码工具
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
// import { copy } from '@/utils/string'
|
||||
import { Jh_getTimeStamp } from '@/utils/time'
|
||||
import { isUrl } from '@/utils/url'
|
||||
import { autoDown, getFileExtension } from '@/utils/file'
|
||||
import { useToolsStore } from '@/store/modules/tools'
|
||||
import { ElMessage } from 'element-plus'
|
||||
const info = reactive({
|
||||
title: "网站favicon获取",
|
||||
})
|
||||
|
||||
//store
|
||||
const toolsStore = useToolsStore()
|
||||
|
||||
const searchUrl = ref()
|
||||
|
||||
const getWebInfo = async () => {
|
||||
try {
|
||||
if (isUrl(searchUrl.value)) {
|
||||
//有效的域名
|
||||
if (!searchUrl.value.includes('http://') && !searchUrl.value.includes('https://')) {
|
||||
searchUrl.value = 'http://' + searchUrl.value;
|
||||
}
|
||||
const urlObj = new URL(searchUrl.value)
|
||||
// webInfo.link = urlObj.hostname
|
||||
searchUrl.value = urlObj.hostname
|
||||
|
||||
await toolsStore.getWebInfo({
|
||||
link: searchUrl.value
|
||||
})
|
||||
} else {
|
||||
//无效的域名
|
||||
ElMessage({
|
||||
message: "无效的域名或地址",
|
||||
type: "error",
|
||||
duration: 1500
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching site info:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const down = () => {
|
||||
autoDown(toolsStore.webInfo.logo, Jh_getTimeStamp() + '.' + getFileExtension(toolsStore.webInfo.logo))
|
||||
}
|
||||
|
||||
//copy
|
||||
// const copyRes = async (resStr: string) => {
|
||||
// copy(resStr)
|
||||
// }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mt-3 flex-1">
|
||||
<DetailHeader :title="info.title"></DetailHeader>
|
||||
|
||||
<div class="p-4 rounded-2xl bg-white">
|
||||
<div>
|
||||
<el-input
|
||||
v-model="searchUrl"
|
||||
style="max-width: 600px"
|
||||
placeholder="请输入域名或地址"
|
||||
class="input-with-select"
|
||||
size="large"
|
||||
>
|
||||
<template #append>
|
||||
<el-button type="primary" @click="getWebInfo">获取</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div>
|
||||
<ul>
|
||||
<li class="mt-3">
|
||||
<el-text><b>域名: </b></el-text>
|
||||
<el-link :href="'https://' + searchUrl" type="primary" target="_blank">{{ searchUrl }}</el-link>
|
||||
</li>
|
||||
<li class="mt-3 flex">
|
||||
<el-text><b>logo: </b></el-text>
|
||||
<div v-if="toolsStore.webInfo.logo" class="ml-2 flex items-center">
|
||||
<el-image :src="toolsStore.webInfo.logo" class="h-12"></el-image>
|
||||
<el-button class="ml-3" link type="primary" @click="down">下载</el-button>
|
||||
</div>
|
||||
</li>
|
||||
<li class="mt-3 flex">
|
||||
<el-text><b>标题: </b>{{ toolsStore.webInfo.title }}</el-text>
|
||||
</li>
|
||||
<li class="mt-3 flex">
|
||||
<el-text><b>关键词: </b> {{ toolsStore.webInfo.keywords }}</el-text>
|
||||
</li>
|
||||
<li class="mt-3 flex">
|
||||
<el-text><b>描述: </b> {{ toolsStore.webInfo.desc }}</el-text>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||
|
||||
import ToolDetail from '@/components/Layout/ToolDetail/ToolDetail.vue'
|
||||
const info = reactive({
|
||||
title: "在线字数统计",
|
||||
content: '',
|
||||
@@ -52,6 +52,14 @@ const changeContent = () => {
|
||||
<div class="ml-3"><el-text>空白字符:{{ info.emptyNum }}</el-text></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- desc -->
|
||||
<ToolDetail title="描述">
|
||||
<el-text>
|
||||
在线统计字数、汉字、标点、数字、字母、字符
|
||||
</el-text>
|
||||
</ToolDetail>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,569 @@
|
||||
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 Token(jwt)',
|
||||
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,8 +1,7 @@
|
||||
import { ResponseData } from "../common"
|
||||
|
||||
export interface ToolsReqData {
|
||||
cateId: number,
|
||||
title: string
|
||||
title: string,
|
||||
route: string,
|
||||
}
|
||||
|
||||
//tools
|
||||
@@ -26,19 +25,21 @@ export interface ToolCate {
|
||||
list: ToolsInfo[]
|
||||
}
|
||||
|
||||
export interface CollectReqData {
|
||||
toolId: number
|
||||
}
|
||||
|
||||
//tools list response
|
||||
export interface ToolsResponseData extends ResponseData {
|
||||
export interface ToolsResponseData {
|
||||
data: ToolsInfo[]
|
||||
}
|
||||
|
||||
//tools cate response
|
||||
export interface ToolCateResponseData extends ResponseData {
|
||||
export interface ToolCateResponseData{
|
||||
data: ToolCate[]
|
||||
}
|
||||
|
||||
//tools recommend response
|
||||
export interface ToolRecommendResponseData extends ResponseData {
|
||||
export interface ToolRecommendResponseData{
|
||||
data: ToolsInfo[]
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -16,6 +16,8 @@ import 'element-plus/dist/index.css'
|
||||
import pinia from './store'
|
||||
//v-md-editor
|
||||
import { setupMdEditor } from './plugins/v-md-editor'
|
||||
//default-passive-events
|
||||
import 'default-passive-events'
|
||||
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
@@ -19,7 +19,7 @@ const router = createRouter({
|
||||
router.beforeEach((to, _from, next) => {
|
||||
if (to.meta.title && to.meta.title != '') {
|
||||
let oldTitle = document.title
|
||||
document.title = <string>to.meta.title + ' - ' + oldTitle
|
||||
document.title = <string>to.meta.title + '-' + oldTitle
|
||||
}
|
||||
next()
|
||||
})
|
||||
@@ -31,9 +31,9 @@ router.afterEach((to) => {
|
||||
const detailTitle = title
|
||||
//设置title
|
||||
if (detailTitle) {
|
||||
document.title = detailTitle + ' - ' + import.meta.env.VITE_APP_TITLE
|
||||
document.title = detailTitle + '-' + import.meta.env.VITE_APP_TITLE
|
||||
} else {
|
||||
document.title = import.meta.env.VITE_APP_TITLE
|
||||
document.title = import.meta.env.VITE_APP_TITLE + '-' + import.meta.env.VITE_APP_DESC
|
||||
}
|
||||
|
||||
//设置meta
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
//对外暴露配置路由(常量路由)
|
||||
export const constantRoute = [
|
||||
//首页
|
||||
{
|
||||
path: '/',
|
||||
component: () => import('@/components/Home/Home.vue'),
|
||||
name: 'home',
|
||||
meta: {
|
||||
keywords: '然部落工具坊,在线工具,开发人员工具,时间戳转换,加密,解密,md5,进制转换,二维码,正则表达式,json格式化,照片处理,字数统计',
|
||||
description: '然部落工具坊,在线工具大全,开发人员工具,日常生活工具,办公助手,时间戳转换,加密,解密,md5,进制转换,二维码,正则表达式,json格式化,照片处理,字数统计',
|
||||
keywords: 'tools-web,在线工具,开发人员工具,时间戳转换,加密,解密,md5,进制转换,二维码,正则表达式,json格式化,照片处理,字数统计',
|
||||
description: 'tools-web,在线工具,在线工具大全,开发人员工具,日常生活工具,办公助手,时间戳转换,加密,解密,md5,进制转换,二维码,正则表达式,json格式化,照片处理,字数统计',
|
||||
}
|
||||
},
|
||||
//工具
|
||||
{
|
||||
path: '/timetran',
|
||||
component: () => import('@/components/TimeTran/TimeTran.vue'),
|
||||
component: () => import('@/components/Tools/TimeTran/TimeTran.vue'),
|
||||
name: 'timetran',
|
||||
meta: {
|
||||
title: "时间戳转换",
|
||||
@@ -20,8 +22,8 @@ export const constantRoute = [
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/MD5',
|
||||
component: () => import('@/components/MD5/MD5.vue'),
|
||||
path: '/md5',
|
||||
component: () => import('@/components/Tools/MD5/MD5.vue'),
|
||||
name: 'MD5',
|
||||
meta: {
|
||||
title: "MD5在线加密",
|
||||
@@ -31,17 +33,17 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/json',
|
||||
component: () => import('@/components/JsonTran/JsonTran.vue'),
|
||||
component: () => import('@/components/Tools/JsonTran/JsonTran.vue'),
|
||||
name: 'json',
|
||||
meta: {
|
||||
title: "Json在线转换",
|
||||
keywords: 'json编辑器,json格式化,json压缩,json转义,json去转义',
|
||||
description: 'json编辑器,json解析,json格式化,json压缩,json转义,json去转义',
|
||||
description: '提供实时编辑和预览JSON 数据,语法高亮、校验、格式化、转义,去转义、压缩等功能,可以提高阅读修改的效率和准确性',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/reg',
|
||||
component: () => import('@/components/RegTest/RegTest.vue'),
|
||||
component: () => import('@/components/Tools/RegTest/RegTest.vue'),
|
||||
name: 'reg',
|
||||
meta: {
|
||||
title: "正则表达式测试",
|
||||
@@ -51,7 +53,7 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/unicode',
|
||||
component: () => import('@/components/Unicode/Unicode.vue'),
|
||||
component: () => import('@/components/Tools/Unicode/Unicode.vue'),
|
||||
name: 'unicode',
|
||||
meta: {
|
||||
title: "Unicode转中文",
|
||||
@@ -61,7 +63,7 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/wordcount',
|
||||
component: () => import('@/components/WordCount/WordCount.vue'),
|
||||
component: () => import('@/components/Tools/WordCount/WordCount.vue'),
|
||||
name: 'wordcount',
|
||||
meta: {
|
||||
title: "在线字数统计",
|
||||
@@ -71,7 +73,7 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/ip',
|
||||
component: () => import('@/components/Ip/Ip.vue'),
|
||||
component: () => import('@/components/Tools/Ip/Ip.vue'),
|
||||
name: 'ip',
|
||||
meta: {
|
||||
title: "IP查询",
|
||||
@@ -81,7 +83,7 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/scaletran',
|
||||
component: () => import('@/components/ScaleTran/ScaleTran.vue'),
|
||||
component: () => import('@/components/Tools/ScaleTran/ScaleTran.vue'),
|
||||
name: 'scaletran',
|
||||
meta: {
|
||||
title: "常用进制转换",
|
||||
@@ -91,17 +93,17 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/signimage',
|
||||
component: () => import('@/components/SignImage/SignImage.vue'),
|
||||
component: () => import('@/components/Tools/SignImage/SignImage.vue'),
|
||||
name: 'signimage',
|
||||
meta: {
|
||||
title: "在线编辑图片",
|
||||
keywords: '图片裁剪,图片滤镜,图片旋转,图片编辑,美化图片',
|
||||
description: '图片裁剪,图片滤镜,图片旋转,图片编辑,美化图片',
|
||||
keywords: '在线图片裁剪,图片标注,图片滤镜,图片画笔、图片旋转、图片文字,图片美,图片尺寸调整化',
|
||||
description: '在线图片裁剪,图片标注,图片滤镜,图片画笔、图片旋转、图片文字,图片尺寸调整等操作',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/random_password',
|
||||
component: () => import('@/components/RandomPassword/RandomPassword.vue'),
|
||||
path: '/randompassword',
|
||||
component: () => import('@/components/Tools/RandomPassword/RandomPassword.vue'),
|
||||
name: 'random_password',
|
||||
meta: {
|
||||
title: "随机密码生成",
|
||||
@@ -111,7 +113,7 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/urlencode',
|
||||
component: () => import('@/components/UrlEncode/UrlEncode.vue'),
|
||||
component: () => import('@/components/Tools/UrlEncode/UrlEncode.vue'),
|
||||
name: 'urlencode',
|
||||
meta: {
|
||||
title: "URL编码/解码",
|
||||
@@ -121,7 +123,7 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/ascii',
|
||||
component: () => import('@/components/ASCII/ASCII.vue'),
|
||||
component: () => import('@/components/Tools/ASCII/ASCII.vue'),
|
||||
name: 'ascii',
|
||||
meta: {
|
||||
title: "ASCII码表",
|
||||
@@ -131,7 +133,7 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/uuid',
|
||||
component: () => import('@/components/UUID/UUID.vue'),
|
||||
component: () => import('@/components/Tools/UUID/UUID.vue'),
|
||||
name: 'uuid',
|
||||
meta: {
|
||||
title: "UUID生成器",
|
||||
@@ -141,7 +143,7 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/barrage',
|
||||
component: () => import('@/components/Barrage/Barrage.vue'),
|
||||
component: () => import('@/components/Tools/Barrage/Barrage.vue'),
|
||||
name: 'barrage',
|
||||
meta: {
|
||||
title: "手持弹幕",
|
||||
@@ -151,7 +153,7 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/palettes',
|
||||
component: () => import('@/components/Palettes/Palettes.vue'),
|
||||
component: () => import('@/components/Tools/Palettes/Palettes.vue'),
|
||||
name: 'palettes',
|
||||
meta: {
|
||||
title: "色板",
|
||||
@@ -161,7 +163,7 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/unit',
|
||||
component: () => import('@/components/Unit/Unit.vue'),
|
||||
component: () => import('@/components/Tools/Unit/Unit.vue'),
|
||||
name: 'unit',
|
||||
meta: {
|
||||
title: "单位转换",
|
||||
@@ -171,7 +173,7 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/qrcode',
|
||||
component: () => import('@/components/Qrcode/Qrcode.vue'),
|
||||
component: () => import('@/components/Tools/Qrcode/Qrcode.vue'),
|
||||
name: 'qrcode',
|
||||
meta: {
|
||||
title: "二维码生成",
|
||||
@@ -181,7 +183,7 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/decision',
|
||||
component: () => import('@/components/Decision/Decision.vue'),
|
||||
component: () => import('@/components/Tools/Decision/Decision.vue'),
|
||||
name: 'decision',
|
||||
meta: {
|
||||
title: "帮我决定",
|
||||
@@ -191,7 +193,7 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/morse',
|
||||
component: () => import('@/components/Morse/Morse.vue'),
|
||||
component: () => import('@/components/Tools/Morse/Morse.vue'),
|
||||
name: 'morse',
|
||||
meta: {
|
||||
title: "摩斯电码",
|
||||
@@ -201,7 +203,7 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/random',
|
||||
component: () => import('@/components/Random/Random.vue'),
|
||||
component: () => import('@/components/Tools/Random/Random.vue'),
|
||||
name: 'random',
|
||||
meta: {
|
||||
title: "生成随机数",
|
||||
@@ -210,8 +212,8 @@ export const constantRoute = [
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/numberToChinese',
|
||||
component: () => import('@/components/NumberToChinese/NumberToChinese.vue'),
|
||||
path: '/numbertochinese',
|
||||
component: () => import('@/components/Tools/NumberToChinese/NumberToChinese.vue'),
|
||||
name: 'numberToChinese',
|
||||
meta: {
|
||||
title: "数字转金额大写",
|
||||
@@ -221,7 +223,7 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/diff',
|
||||
component: () => import('@/components/Diff/Diff.vue'),
|
||||
component: () => import('@/components/Tools/Diff/Diff.vue'),
|
||||
name: 'diff',
|
||||
meta: {
|
||||
title: "文本对比",
|
||||
@@ -231,7 +233,7 @@ export const constantRoute = [
|
||||
},
|
||||
{
|
||||
path: '/markdown',
|
||||
component: () => import('@/components/Markdown/Markdown.vue'),
|
||||
component: () => import('@/components/Tools/Markdown/Markdown.vue'),
|
||||
name: 'markdown',
|
||||
meta: {
|
||||
title: "markdown编辑器",
|
||||
@@ -240,8 +242,8 @@ export const constantRoute = [
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/textToImg',
|
||||
component: () => import('@/components/TextToImg/TextToImg.vue'),
|
||||
path: '/texttoimg',
|
||||
component: () => import('@/components/Tools/TextToImg/TextToImg.vue'),
|
||||
name: 'textToImg',
|
||||
meta: {
|
||||
title: "文本转图片",
|
||||
@@ -249,6 +251,187 @@ export const constantRoute = [
|
||||
description: '把文本转换成图片,生成长图,具有超多个性文字排版',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/webinfo',
|
||||
component: () => import('@/components/Tools/WebInfo/WebInfo.vue'),
|
||||
name: 'webInfo',
|
||||
meta: {
|
||||
title: "网站favicon获取",
|
||||
keywords: '获取网站logo,获取网站icon,获取网站favicon,获取网站标题,获取网站关键词,获取网站描述',
|
||||
description: '获取网站logo、icon、favicon、标题、关键词、描述等信息',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/bar',
|
||||
component: () => import('@/components/Tools/Chart/Bar/Bar.vue'),
|
||||
name: 'bar',
|
||||
meta: {
|
||||
title: "柱状图",
|
||||
keywords: '柱状图',
|
||||
description: '在线制作柱状图,像做表格一样制作可视化图表,支持导出静态或动态图表',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/line',
|
||||
component: () => import('@/components/Tools/Chart/Line/Line.vue'),
|
||||
name: 'line',
|
||||
meta: {
|
||||
title: "折线图",
|
||||
keywords: '折线图',
|
||||
description: '在线制作折线图,像做表格一样制作可视化图表,支持导出静态或动态图表',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/pie',
|
||||
component: () => import('@/components/Tools/Chart/Pie/Pie.vue'),
|
||||
name: 'pie',
|
||||
meta: {
|
||||
title: "饼图",
|
||||
keywords: '饼图',
|
||||
description: '在线制作饼图,像做表格一样制作可视化图表,支持导出静态或动态图表',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/scatter',
|
||||
component: () => import('@/components/Tools/Chart/Scatter/Scatter.vue'),
|
||||
name: 'scatter',
|
||||
meta: {
|
||||
title: "散点图",
|
||||
keywords: '散点图',
|
||||
description: '在线制作散点图,像做表格一样制作可视化图表,支持导出静态或动态图表',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/coin',
|
||||
component: () => import('@/components/Tools/Coin/Coin.vue'),
|
||||
name: 'coin',
|
||||
meta: {
|
||||
title: "抛硬币",
|
||||
keywords: '抛硬币,硬币',
|
||||
description: '在线抛硬币,选择困难那么交给硬币来帮你选择吧',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/dice',
|
||||
component: () => import('@/components/Tools/Dice/Dice.vue'),
|
||||
name: 'dice',
|
||||
meta: {
|
||||
title: "投骰子",
|
||||
keywords: '投骰子,骰子,自定义骰子',
|
||||
description: '在线投骰子,可自定义骰子数量,简单好用的骰子工具',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/textremoveduplicate',
|
||||
component: () => import('@/components/Tools/TextRemoveDuplicate/TextRemoveDuplicate.vue'),
|
||||
name: 'textRemoveDuplicate',
|
||||
meta: {
|
||||
title: "文本去重",
|
||||
keywords: '文本去重,文本排重,文本去除重复',
|
||||
description: '可以删除或去除文本或字符串中的重复行',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/imgcut',
|
||||
component: () => import('@/components/Tools/ImgCut/ImgCut.vue'),
|
||||
name: 'imgCut',
|
||||
meta: {
|
||||
title: "图片切割",
|
||||
keywords: '图片分割,图片切割,四宫格,九宫格,十六宫格',
|
||||
description: '将图片分割成四宫格、九宫格、十六宫格,支持自定义行与列',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/httpstatuscode',
|
||||
component: () => import('@/components/Tools/HttpStatusCode/HttpStatusCode.vue'),
|
||||
name: 'HttpStatusCode',
|
||||
meta: {
|
||||
title: "HTTP状态码",
|
||||
keywords: 'http状态码',
|
||||
description: '所有http状态对应的名称和含义解释',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/jwt',
|
||||
component: () => import('@/components/Tools/JWT/JWT.vue'),
|
||||
name: 'jwt',
|
||||
meta: {
|
||||
title: "JWT解析",
|
||||
keywords: 'jwt解析,jwt解码,JSON Web Token解析',
|
||||
description: '解析和解码JSON Web Token(jwt)',
|
||||
}
|
||||
},
|
||||
{
|
||||
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',
|
||||
component: () => import('@/components/Home/About.vue'),
|
||||
@@ -259,6 +442,7 @@ export const constantRoute = [
|
||||
description: '',
|
||||
}
|
||||
},
|
||||
//其他路由
|
||||
{
|
||||
path: '/404',
|
||||
component: () => import('@/components/404/404.vue'),
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
//创建tools相关的小工具
|
||||
import { defineStore } from 'pinia'
|
||||
import { getTools, getToolCate, getToolRecommend} from '@/api/tools'
|
||||
import { getTools, getToolsCate } from '@/components/Tools/tools.ts'
|
||||
import { getIp } from '@/api/ip'
|
||||
import type { ToolsReqData, ToolsInfo, ToolCate } from '@/api/tools/type'
|
||||
import type { ToolsReqData, ToolsInfo } from '@/components/Tools/tools.type.ts'
|
||||
import type { IpReqData, IpInfo } from '@/api/ip/type'
|
||||
import type { WebInfo, WebInfoReqData } from '@/api/webinfo/type'
|
||||
import { fetchWebInfo } from '@/api/webinfo'
|
||||
|
||||
export const useToolsStore = defineStore('tools', {
|
||||
//用来存放变量
|
||||
state: () => ({
|
||||
list: [] as ToolsInfo[],
|
||||
cates: [] as ToolCate[],
|
||||
toolInfo: {} as ToolsInfo,
|
||||
cates: [] as any[],
|
||||
recommends: [] as ToolsInfo[],
|
||||
ipData: {} as IpInfo,
|
||||
collect: [],
|
||||
webInfo: {} as WebInfo,
|
||||
collect: [] as ToolsInfo[],
|
||||
collectIds: [] as number[],
|
||||
}),
|
||||
//方法
|
||||
actions: {
|
||||
@@ -20,33 +25,20 @@ export const useToolsStore = defineStore('tools', {
|
||||
async getTools(data: ToolsReqData) {
|
||||
//发送请求
|
||||
const result: any = await getTools(data)
|
||||
if (result.code == 200) {
|
||||
this.list = result.data
|
||||
return result.message
|
||||
} else {
|
||||
return Promise.reject(new Error(result.message))
|
||||
}
|
||||
this.list = result
|
||||
return result
|
||||
},
|
||||
//获取tool info
|
||||
async getToolInfo(data: ToolsReqData) {
|
||||
//发送请求
|
||||
const result: any = await getTools(data)
|
||||
this.toolInfo = result
|
||||
return result
|
||||
},
|
||||
//获取tools cate
|
||||
async getToolCate() {
|
||||
//发送请求
|
||||
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))
|
||||
}
|
||||
this.cates = await getToolsCate()
|
||||
},
|
||||
//获取ip
|
||||
async getIp(data: IpReqData) {
|
||||
@@ -57,6 +49,16 @@ export const useToolsStore = defineStore('tools', {
|
||||
} else {
|
||||
return Promise.reject(new Error(result.message))
|
||||
}
|
||||
}
|
||||
},
|
||||
//获取网站信息
|
||||
async getWebInfo(data: WebInfoReqData) {
|
||||
const result: any = await fetchWebInfo(data)
|
||||
if (result.code == 200) {
|
||||
this.webInfo = result.data
|
||||
return result.message
|
||||
} else {
|
||||
return Promise.reject(new Error(result.message))
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 转换成Spreadsheet数据格式
|
||||
*/
|
||||
export function toSpreadsheetData(data, type = 'bar') {
|
||||
let res = {}
|
||||
let columnLen = data[0].value.length
|
||||
switch(type) {
|
||||
case 'bar':
|
||||
for (let i = 0; i < columnLen; i++) {
|
||||
res[i] = {
|
||||
cells: {
|
||||
0: {text: data[0].value[i] ?? ''},
|
||||
1: {text: data[1].value[i] ?? ''}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换成echarts使用的数据 - 基础格式(适用于柱状图、折线图等)
|
||||
*/
|
||||
export function toEchartsData(data) {
|
||||
let tmpColumn: Array<any> = [];
|
||||
let tmpValue: Array<any> = [];
|
||||
for (let item in data.rows) {
|
||||
if (item == 'len') {
|
||||
continue
|
||||
}
|
||||
tmpColumn.push(data.rows[item]['cells'][0] ? data.rows[item]['cells'][0]['text'] : '')
|
||||
tmpValue.push(data.rows[item]['cells'][1] ? data.rows[item]['cells'][1]['text'] : '')
|
||||
}
|
||||
return [tmpColumn, tmpValue];
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换成echarts使用的数据 - 适用于饼图
|
||||
*
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export function toEchartsPieData(data) {
|
||||
let tmpArr: Array<Object> = []
|
||||
for (let item in data.rows) {
|
||||
if (item == 'len') {
|
||||
continue
|
||||
}
|
||||
tmpArr.push({
|
||||
name: data.rows[item]['cells'][0] ? data.rows[item]['cells'][0]['text'] : '',
|
||||
value: data.rows[item]['cells'][1] ? data.rows[item]['cells'][1]['text'] : ''
|
||||
})
|
||||
// tmpColumn.push(data.rows[item]['cells'][0] ? data.rows[item]['cells'][0]['text'] : '')
|
||||
// tmpValue.push(data.rows[item]['cells'][1] ? data.rows[item]['cells'][1]['text'] : '')
|
||||
}
|
||||
return tmpArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据格式转换 - 根据type转换格式
|
||||
* 对象格式{name: '', value: ''}; 列格式: nameArr = [], valueArr = []
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export function tranObjAndColumn(data, type = 'toObj') {
|
||||
let returnData = [] as any[];
|
||||
if (type == 'toObj') {
|
||||
if (data.len === 0 || data[0].len === 0) {
|
||||
return returnData;
|
||||
}
|
||||
//转换成饼图使用的obj
|
||||
let nameArr = data[0];
|
||||
let valueArr = data[1];
|
||||
let tmpArr: Array<Object> = []
|
||||
|
||||
for (let item in nameArr) {
|
||||
if (item == 'len') {
|
||||
continue
|
||||
}
|
||||
tmpArr.push({
|
||||
name: nameArr[item] ? nameArr[item] : '',
|
||||
value: valueArr[item] ? valueArr[item] : '',
|
||||
})
|
||||
}
|
||||
returnData = tmpArr
|
||||
} else if (type == 'toColumn') {
|
||||
//饼图对象转换成列数组
|
||||
let nameArr = [] as string[]
|
||||
let valueArr = [] as number[]
|
||||
for (let item in data) {
|
||||
nameArr.push(data[item]?.name)
|
||||
valueArr.push(data[item]?.value)
|
||||
}
|
||||
returnData = [nameArr, valueArr]
|
||||
} else if (type == "toCoord") {
|
||||
//转换成坐标(散点图用到)
|
||||
if (data.len === 0 || data[0].len === 0) {
|
||||
return returnData;
|
||||
}
|
||||
//转换成饼图使用的obj
|
||||
let nameArr = data[0];
|
||||
let valueArr = data[1];
|
||||
let tmpArr: Array<any> = []
|
||||
|
||||
for (let item in nameArr) {
|
||||
if (item == 'len') {
|
||||
continue
|
||||
}
|
||||
tmpArr.push([
|
||||
nameArr[item] ? nameArr[item] : 0,
|
||||
valueArr[item] ? valueArr[item] : 0,
|
||||
])
|
||||
}
|
||||
returnData = tmpArr
|
||||
}
|
||||
return returnData;
|
||||
}
|
||||
|
||||
|
||||
const EchartsUtils = {
|
||||
toSpreadsheetData,
|
||||
toEchartsData,
|
||||
toEchartsPieData,
|
||||
tranObjAndColumn,
|
||||
}
|
||||
export default EchartsUtils
|
||||
|
||||
/**
|
||||
|
||||
1: {
|
||||
cells: {
|
||||
0: { text: 'testingtesttestetst' },
|
||||
2: { text: 'testing' },
|
||||
},
|
||||
},
|
||||
2: {
|
||||
cells: {
|
||||
0: { text: 'render', style: 0 },
|
||||
1: { text: 'Hello' },
|
||||
2: { text: 'haha', merge: [1, 1] },
|
||||
}
|
||||
},
|
||||
8: {
|
||||
cells: {
|
||||
8: { text: 'border test', style: 0 },
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
@@ -24,17 +24,30 @@ export function base64ToBlod(base64String: string): string {
|
||||
* @param filename 文件名
|
||||
*/
|
||||
export function autoDown(url: string, filename: string) {
|
||||
const downloadLink = document.createElement('a');
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.href = url;
|
||||
downloadLink.target = '_blank';
|
||||
downloadLink.download = filename; // 指定下载的文件名
|
||||
// 将下载链接添加到 DOM 中并模拟点击下载
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
document.body.removeChild(downloadLink);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件后缀
|
||||
*/
|
||||
export function getFileExtension(filename) {
|
||||
const dotIndex = filename.lastIndexOf('.');
|
||||
if (dotIndex === -1) {
|
||||
return ''; // 没有找到'.',返回空字符串
|
||||
}
|
||||
return filename.substr(dotIndex + 1); // 返回'.'之后的部分作为后缀名
|
||||
}
|
||||
|
||||
const FileUtils = {
|
||||
base64ToBlod,
|
||||
autoDown
|
||||
autoDown,
|
||||
getFileExtension
|
||||
}
|
||||
export default FileUtils
|
||||
@@ -4,21 +4,32 @@ import { ElMessage } from "element-plus";
|
||||
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,
|
||||
timeout: 5000
|
||||
})
|
||||
});
|
||||
|
||||
//请求拦截器
|
||||
request.interceptors.request.use(config => {
|
||||
return config;
|
||||
});
|
||||
//响应拦截器
|
||||
request.interceptors.response.use((response) => {
|
||||
if (response.data.code == 401) {
|
||||
//登录过期
|
||||
localStorage.setItem('TOKEN', '')
|
||||
location.reload()
|
||||
}
|
||||
return response.data;
|
||||
}, (error) => {
|
||||
if (error.code == 'ERR_CANCELED') {
|
||||
//拒绝响应
|
||||
return Promise.race([]);
|
||||
}
|
||||
//处理网络错误
|
||||
let msg = '';
|
||||
let status = error.response.status;
|
||||
switch (status) {
|
||||
case 401:
|
||||
msg = "token过期";
|
||||
//msg = "token过期";
|
||||
msg = "登录过期";
|
||||
break;
|
||||
case 403:
|
||||
msg = '无权访问';
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* sleep
|
||||
* @param ms
|
||||
* @returns
|
||||
*/
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
@@ -113,11 +113,17 @@ export function numberToChinese(num: number): string {
|
||||
return chinese;
|
||||
}
|
||||
|
||||
//rtrim: 删除右侧指定字符, 默认删除空格
|
||||
export function rtrim(str, char = ' ') {
|
||||
return str.replace(new RegExp('\\'+char+'+$', 'g'), '');
|
||||
}
|
||||
|
||||
const StringUtils = {
|
||||
transferred,
|
||||
copy,
|
||||
genRandomStrByChars,
|
||||
numberToChinese
|
||||
numberToChinese,
|
||||
rtrim,
|
||||
}
|
||||
|
||||
export default StringUtils
|
||||
@@ -0,0 +1,20 @@
|
||||
export function isUrl(str: string): boolean {
|
||||
// const urlPattern = new RegExp(
|
||||
// '^(https?:\\/\\/)?'+ // protocol
|
||||
// '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name and extension
|
||||
// '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
|
||||
// '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
|
||||
// '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
|
||||
// '(\\#[-a-z\\d_]*)?$','i'); // fragment locator
|
||||
// return urlPattern.test(str);
|
||||
const urlPattern = new RegExp(
|
||||
'^(https?:\\/\\/)?'+ // protocol
|
||||
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,})','i'); // fragment locator
|
||||
return urlPattern.test(str);
|
||||
}
|
||||
|
||||
const UrlUtils = {
|
||||
isUrl
|
||||
}
|
||||
|
||||
export default UrlUtils
|
||||
@@ -1,9 +1,7 @@
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
import {viteMockServe} from 'vite-plugin-mock'
|
||||
import path from 'path'
|
||||
import { createHtmlPlugin } from 'vite-plugin-html'
|
||||
import {seoperender} from "./ssr.config";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
@@ -21,10 +19,6 @@ export default defineConfig(({command, mode}) => {
|
||||
// Specify symbolId format
|
||||
symbolId: 'icon-[dir]-[name]',
|
||||
}),
|
||||
//mock
|
||||
viteMockServe({
|
||||
localEnabled: env.VITE_IS_MOCK == 'true' ? command === 'serve' : false,
|
||||
}),
|
||||
seoperender()
|
||||
],
|
||||
resolve: {
|
||||
|
||||