11 Commits
Author SHA1 Message Date
osiris fe129d3f9f fix 2024-05-27 17:41:51 +08:00
osiris 1dba247410 fix 2024-05-27 12:04:16 +08:00
osiris b235935fd3 fix: debug 2024-05-14 15:20:18 +08:00
osiris 92347aa62d fix: 2024-05-14 15:13:56 +08:00
osiris 49be0ddcbc feat: 2024-05-14 10:52:13 +08:00
osiris 68951056b1 fix: 添加隐私权政策,工具组件目录调整 2024-05-11 18:25:00 +08:00
osiris 4d72590f6f fix: dbeug 2024-04-30 11:07:28 +08:00
osiris 4bb328e5f6 docs 2024-04-11 16:24:48 +08:00
osiris de2ae48d58 feat: logo存储到本地 2024-04-11 15:55:30 +08:00
osiris b6b5f1dc0b fix: 修复数据图表上传数据文件只有第一次有效问题 2024-04-11 12:16:44 +08:00
osiris d863f0174c feat: 新增7个工具,修复部分问题
新增工具:
数据图表:折线图(#)
数据图表:饼图(#)
数据图表:散点图(#)
选择随机: 投骰子(#)
选择随机: 抛硬币(#)
文本处理:删除重复行(#)
图片工具:图片切割(#)

修复:
数据图表上传数据文件只有第一次有效问题
2024-04-11 12:09:34 +08:00
103 changed files with 5626 additions and 6092 deletions
+4 -4
View File
@@ -1,12 +1,12 @@
# 变量必须以 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; 使用mock时不需要填写
VITE_SERVE = ''
# 自定义启动host
VITE_HOST = '127.0.0.1'
+1 -26
View File
@@ -8,32 +8,7 @@
免费开源的工具站,目前已开发数十种工具,对平板和手机适配进行了响应式设计,同时为了让seo更为友好做了ssr处理,搭建简单使用方便
已有工具:
- 时间戳转换
- MD5在线加密
- Json在线转换
- 正则测试工具
- Unicode转中文
- IP查询
- 常用进制转换
- 在线图片处理
- 字数统计
- 随机密码生成
- URL编码/解码
- UUID生成器
- 手持弹幕
- ASCII码表
- 单位换算
- 色板
- 二维码生成
- 帮我决定
- 摩斯电码
- 生成随机数
- 数字转金额大写
- 文本对比
- markdown编辑器
- 文本转图片
- 网站favicon获取
![](/public/index.png)
# 快速使用
+1 -1
View File
@@ -27,7 +27,7 @@
})();
</script>
</head>
<body style="background-color: var(--base-gray)">
<body class="bg-[--base-gray]">
<!--[if lt IE 9]>
<div class="notice chromeframe">您的浏览器版本<strong>很旧很旧</strong>,为了正常地访问网站,请升级您的浏览器</div>
<![endif]-->
+161 -319
View File
@@ -1,244 +1,16 @@
//获取tools
function getTools() {
return [
{
id: 1,
title: '时间戳转换',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/Time.png',
desc: '在线时间戳转换工具以及获取当前时间戳',
url: '/timetran',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'MD5在线加密',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/Password%20fishing.png',
desc: 'MD5在线加密,长度包含32位、16位',
url: '/MD5',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'Json在线转换',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/json.png',
desc: '在线编辑json, 语法检查',
url: '/json',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: '正则测试工具',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/reg.png',
desc: '正则表达式测试工具, 常用正则表达式',
url: '/reg',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'Unicode转中文',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/union.png',
desc: 'Unicode和中文的相互转换',
url: '/unicode',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: '字数统计',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/ico_%E6%95%B0%E6%8D%AE%E7%BB%9F%E8%AE%A1_%E5%B7%A5%E4%BD%9C%E9%87%8F%E7%BB%9F%E8%AE%A1.png',
desc: '在线统计字符串的字数、段落、标点符号数量',
url: '/wordCount',
cateId: 3,
cate: '文本处理',
},
{
id: 1,
title: 'IP查询',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/IP.png',
desc: '在线查询ip地址、ip归属地',
url: '/ip',
cateId: 6,
cate: '查询相关',
},
{
id: 1,
title: '常用进制转换',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E8%BF%9B%E5%88%B6%E8%BD%AC%E6%8D%A2.png',
desc: '在线进制转换工具,可在2到64进制之间相互转换',
url: '/scaletran',
cateId: 4,
cate: '教育学术',
},
{
id: 1,
title: '在线图片处理',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/img.png',
desc: '在线图片裁剪,标注,滤镜,画等操作',
url: '/signimage',
cateId: 5,
cate: '设计工具',
},
{
id: 1,
title: '随机密码生成',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/keywords.png',
desc: '密码生成器、随机字符串生成,批量生成',
url: '/random_password',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'URL编码/解码',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/url.png',
desc: 'URL在线编码解码工具(UrlEncode编码 和 UrlDecode解码)',
url: '/urlencode',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: 'UUID生成器',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E7%BA%BF%E6%80%A7-%E7%94%9F%E6%88%90.png',
desc: '批量生成UUID',
url: '/uuid',
cateId: 2,
cate: '开发运维',
},
{
id: 1,
title: '手持弹幕',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E5%BC%B9%E5%B9%95.png',
desc: '手持滚动弹幕',
url: '/barrage',
cateId: 7,
cate: '其他',
},
{
id: 1,
title: 'ASCII码表',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/icon_%E7%BC%96%E7%A0%81%E5%AF%B9%E7%85%A7.png',
desc: 'ASCII码表,控制代码、标准ASCII字符和非标准ASCII字符对照表',
url: '/ascii',
cateId: 4,
cate: '教育学术'
},
{
id: 1,
title: '单位换算',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E5%8D%95%E4%BD%8D%E6%8D%A2%E7%AE%97.png',
desc: '在线重量、长度、面积、时间、角度、速度、温度、压力、热量、功率等换算',
url: '/unit',
cateId: 4,
cate: '教育学术'
},
{
id: 1,
title: '色板',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E8%B0%83%E8%89%B2%E6%9D%BF.png',
desc: '包含纯色、渐变与阶梯色和常用色彩组合',
url: '/palettes',
cateId: 5,
cate: '设计工具'
},
{
id: 1,
title: '二维码生成',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E4%BA%8C%E7%BB%B4%E7%A0%81.png',
desc: '在线生成带logo、透明、艺术的二维码',
url: '/qrcode',
cateId: 5,
cate: '设计工具'
},
{
id: 1,
title: '帮我决定',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/choose.png',
desc: '选择困难,难以决定,今天吃什么,现在做什么,自定义选项都给你安排的明明白白',
url: '/decision',
cateId: 7,
cate: '其他'
},
{
id: 1,
title: '摩斯电码',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/medium.png',
desc: '支持中文的摩斯电码编码解码',
url: '/morse',
cateId: 3,
cate: '文本处理'
},
{
id: 1,
title: '生成随机数',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/randomnum.png',
desc: '可定制范围内进行随机数字,可用于抽奖、点名等用途',
url: '/random',
cateId: 7,
cate: '其他'
},
{
id: 1,
title: '数字转金额大写',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/text%20recognition.png',
desc: '在线数字一键转换成人民币大写,中文大写转换数字',
url: '/numberToChinese',
cateId: 7,
cate: '其他'
},
{
id: 1,
title: '文本对比',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/diff.png',
desc: '文本差异比对支持中文、英文、代码比对',
url: '/diff',
cateId: 3,
cate: '文本处理'
},
{
id: 1,
title: 'markdown编辑器',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/file-markdown-fill.png',
desc: '在线创建或编辑markdown, 实时预览,导出markdown',
url: '/markdown',
cateId: 3,
cate: '文本处理'
},
{
id: 1,
title: '文本转图片',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/text_to_img.png',
desc: '把文本转换成图片,生成长图,具有超多个性文字排版',
url: '/textToImg',
cateId: 5,
cate: '设计工具'
},
{
id: 1,
title: '网站favicon获取',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/IP.png',
desc: '获取网站logo、icon、favicon、标题、关键词、描述等信息',
url: '/webInfo',
cateId: 6,
cate: '查询相关',
},
{
id: 1,
title: '柱状图',
logo: '/images/logo/bar.png',
desc: '在线制作柱状图,像做表格一样制作可视化图表,支持导出静态或动态图表',
url: '/bar',
cateId: 8,
cate: '数据图表',
},
]
let tools = [] as any[]
let toolsCate = getToolsCate()
for (let item in toolsCate) {
for (let _item in toolsCate[item].list) {
tools.push(toolsCate[item].list[_item])
}
}
return tools
}
//获取tools分类已经对应的工具
//获取tools分类对应的工具
function getToolsCate() {
return [
{
@@ -249,7 +21,7 @@ function getToolsCate() {
{
id: 1,
title: '随机密码生成',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/keywords.png',
logo: '/images/logo/keywords.png',
desc: '密码生成器、随机字符串生成,批量生成',
url: '/random_password',
cateId: 2,
@@ -258,7 +30,7 @@ function getToolsCate() {
{
id: 1,
title: 'URL编码/解码',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/url.png',
logo: '/images/logo/url.png',
desc: 'URL在线编码解码工具(UrlEncode编码 和 UrlDecode解码)',
url: '/urlencode',
cateId: 2,
@@ -267,7 +39,7 @@ function getToolsCate() {
{
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',
logo: '/images/logo/uuid.png',
desc: '批量生成UUID',
url: '/uuid',
cateId: 2,
@@ -276,7 +48,7 @@ function getToolsCate() {
{
id: 1,
title: '时间戳转换',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/Time.png',
logo: '/images/logo/Time.png',
desc: '在线时间戳转换工具以及获取当前时间戳',
url: '/timetran',
cateId: 2,
@@ -285,7 +57,7 @@ function getToolsCate() {
{
id: 1,
title: 'MD5在线加密',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/Password%20fishing.png',
logo: '/images/logo/md5.png',
desc: 'MD5在线加密,长度包含32位、16位',
url: '/MD5',
cateId: 2,
@@ -294,7 +66,7 @@ function getToolsCate() {
{
id: 1,
title: 'Json在线转换',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/json.png',
logo: '/images/logo/json.png',
desc: '在线编辑json, 语法检查',
url: '/json',
cateId: 2,
@@ -303,7 +75,7 @@ function getToolsCate() {
{
id: 1,
title: '正则测试工具',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/reg.png',
logo: '/images/logo/reg.png',
desc: '正则表达式测试工具, 常用正则表达式',
url: '/reg',
cateId: 2,
@@ -312,7 +84,7 @@ function getToolsCate() {
{
id: 1,
title: 'Unicode转中文',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/union.png',
logo: '/images/logo/union.png',
desc: 'Unicode和中文的相互转换',
url: '/unicode',
cateId: 2,
@@ -328,7 +100,7 @@ function getToolsCate() {
{
id: 1,
title: '文本对比',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/diff.png',
logo: '/images/logo/diff.png',
desc: '文本差异比对支持中文、英文、代码比对',
url: '/diff',
cateId: 3,
@@ -337,30 +109,30 @@ function getToolsCate() {
{
id: 1,
title: 'markdown编辑器',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/file-markdown-fill.png',
logo: '/images/logo/file-markdown-fill.png',
desc: '在线创建或编辑markdown, 实时预览,导出markdown',
url: '/markdown',
cateId: 3,
cate: '文本处理'
},
{
id: 1,
title: '摩斯电码',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/medium.png',
desc: '支持中文的摩斯电码编码解码',
url: '/morse',
cateId: 3,
cate: '文本处理'
},
{
id: 1,
title: '字数统计',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/ico_%E6%95%B0%E6%8D%AE%E7%BB%9F%E8%AE%A1_%E5%B7%A5%E4%BD%9C%E9%87%8F%E7%BB%9F%E8%AE%A1.png',
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: '文本处理',
},
]
},
{
@@ -371,16 +143,25 @@ function getToolsCate() {
{
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',
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: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E8%BF%9B%E5%88%B6%E8%BD%AC%E6%8D%A2.png',
logo: '/images/logo/scaletran.png',
desc: '在线进制转换工具,可在2到64进制之间相互转换',
url: '/scaletran',
cateId: 4,
@@ -389,7 +170,7 @@ function getToolsCate() {
{
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',
logo: '/images/logo/ascii.png',
desc: 'ASCII码表,控制代码、标准ASCII字符和非标准ASCII字符对照表',
url: '/ascii',
cateId: 4,
@@ -399,70 +180,45 @@ function getToolsCate() {
},
{
id: 5,
title: '设计工具',
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',
logo: '/images/logo/qrcode.png',
desc: '在线生成带logo、透明、艺术的二维码',
url: '/qrcode',
cateId: 5,
cate: '设计工具'
cate: '图片处理'
},
{
id: 1,
title: '在线图片处理',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/img.png',
logo: '/images/logo/img.png',
desc: '在线图片裁剪,标注,滤镜,画等操作',
url: '/signimage',
cateId: 5,
cate: '设计工具',
cate: '图片处理',
},
{
id: 1,
title: '文本转图片',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/text_to_img.png',
logo: '/images/logo/text_to_img.png',
desc: '把文本转换成图片,生成长图,具有超多个性文字排版',
url: '/textToImg',
cateId: 5,
cate: '设计工具'
}
]
},
{
id: 6,
title: '查询相关',
icon: '',
list: [
{
id: 1,
title: 'IP查询',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/IP.png',
desc: '在线查询ip地址、ip归属地',
url: '/ip',
cateId: 6,
cate: '查询相关',
cate: '图片处理'
},
{
id: 1,
title: '网站favicon获取',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/text_to_img.png',
desc: '获取网站logo、icon、favicon、标题、关键词、描述等信息',
url: '/webInfo',
cateId: 6,
cate: '查询相关',
}
title: '图片分割',
logo: '/images/logo/imgCut.png',
desc: '将图片分割成四宫格、九宫格、十六宫格,支持自定义行与列',
url: '/imgCut',
cateId: 5,
cate: '图片处理',
},
]
},
{
@@ -478,49 +234,134 @@ function getToolsCate() {
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: 7,
title: '其他',
id: 9,
title: '选择随机',
icon: '',
list: [
{
id: 1,
title: '生成随机数',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/randomnum.png',
logo: '/images/logo/random.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: '其他'
cateId: 9,
cate: '选择随机'
},
{
id: 1,
title: '帮我决定',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/choose.png',
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: '其他'
cate: '其他工具'
},
{
id: 1,
title: '手持弹幕',
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E5%BC%B9%E5%B9%95.png',
logo: '/images/logo/dm.png',
desc: '手持滚动弹幕',
url: '/barrage',
cateId: 7,
cate: '其他',
cate: '其他工具',
},
{
id: 1,
title: '色板',
logo: '/images/logo/palettes.png',
desc: '包含纯色、渐变与阶梯色和常用色彩组合',
url: '/palettes',
cateId: 7,
cate: '其他工具'
}
]
}
@@ -540,7 +381,8 @@ export default [
if (title != '') {
list = list.filter(item => {
let tmpValue = item.title.toLowerCase()
return tmpValue.indexOf(title.toLowerCase()) !== -1;
let tmpDesc = item.desc.toLowerCase()
return tmpValue.indexOf(title.toLowerCase()) !== -1 || tmpDesc.indexOf(title.toLowerCase()) !== -1;
});
}
//分类筛选
+3177 -5664
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 987 KiB

+1 -1
View File
@@ -23,7 +23,7 @@ const componentStore = useComponentStore()
v-model="componentStore.leftComDrawer"
direction="ltr"
>
<Left bgColor="#fff"></Left>
<Left></Left>
</el-drawer>
<!-- right -->
+18 -24
View File
@@ -1,34 +1,28 @@
<script setup lang="ts">
// import { ref } from 'vue'
// import { copy } from '@/utils/string'
// const info = reactive({
// title: "关于本站",
// })
const feedBackUrl = import.meta.env.VITE_FEEDBACK_URL
//copy
// const copyRes = async (resStr: string) => {
// copy(resStr)
// }
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<div class="bg-white rounded-md p-5">
<br>
<p>本项目是一个开源工具站, 求星星</p>
<br>
<p>本项目开源地址<el-link href="https://github.com/taoran1401/tools-web">https://github.com/taoran1401/tools-web</el-link></p>
<br>
<p class="font-bold">联系作者/问题反馈</p>
<br>
<p>反馈中心<el-link :href="feedBackUrl">{{ feedBackUrl }}</el-link></p>
<br>
<p>邮箱taoran0796@163.com</p>
<br>
<div 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>
+3 -3
View File
@@ -1,12 +1,12 @@
<script setup lang="ts">
import { ref } from 'vue'
const beianNo = ref('粤ICP备19038804号-1')
const copyright = ref('ranblogs.com')
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 }} &nbsp;&nbsp; <a href="https://beian.miit.gov.cn/" target="_blank">{{ beianNo }}</a>
<div class="w-full rounded-2xl z-10 p-5 text-center">
{{ appDesc }} © 2024 <a href="https://github.com/taoran1401/tools-web" target="_blank" class="text-blue-700">Tools-Web</a> &nbsp;&nbsp; <a href="https://xy.ranblogs.com/tools_web.html" target="_blank">隐私权政策</a> &nbsp;&nbsp; <a href="https://beian.miit.gov.cn/" target="_blank">{{ beianNo }}</a>
</div>
</template>
+48 -8
View File
@@ -16,7 +16,7 @@ const options = ref<ToolsInfo[]>([])
const loginRef = ref()
const userPopRef = ref()
const defAvatar = ref(defaultAvatar)
const feedBackUrl = import.meta.env.VITE_FEEDBACK_URL
// const feedBackUrl = import.meta.env.VITE_FEEDBACK_URL
//store
const toolsStore = useToolsStore()
const componentStore = useComponentStore()
@@ -112,7 +112,7 @@ onMounted(() => {
</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">
<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">
<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">
@@ -132,8 +132,16 @@ 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="md:w-80 c-xs:72">
<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>
<!-- <el-input
v-model="searchParam.title"
placeholder="搜索工具"
@@ -154,12 +162,13 @@ onMounted(() => {
placeholder="输入关键词搜索,如文本、json、图片等"
:remote-method="searchTools"
:loading="loading"
class="ml-3 mr-3 md:w-80 c-xs:w-60"
class="ml-3 mr-3 md:w-[45rem] c-xs:w-60"
size="large"
>
<el-option
v-for="item in toolsStore.list"
:key="item.id"
:label="item.title"
:label="item.title + ' - ' + item.desc"
:value="item.id"
@click="optionClick(item.url)"
>
@@ -170,9 +179,27 @@ 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-link :href="feedBackUrl" size="large" target="_blank">建议/问题</el-link>
<li class="ml-3">
<router-link to="/about">
<el-tooltip
class="box-item"
effect="dark"
content="关于tools-web"
placement="bottom"
>
<svg t="1715594765484" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5578" width="33" height="33"><path d="M511.899716 948.506609c-241.310951 0-437.636339-196.318224-437.636339-437.636339 0-241.323231 196.325387-437.639408 437.636339-437.639408s437.636339 196.316178 437.636339 437.639408C949.536055 752.188384 753.210667 948.506609 511.899716 948.506609zM511.899716 113.944122c-218.866776 0-396.926148 178.064488-396.926148 396.926148 0 218.856543 178.059372 396.926148 396.926148 396.926148 218.868823 0 396.926148-178.069605 396.926148-396.926148C908.825864 292.00861 730.768539 113.944122 511.899716 113.944122zM561.15656 335.324138c-29.853935 0-54.03773-24.189935-54.03773-54.047963 0-29.855982 24.184819-54.047963 54.03773-54.047963 29.838585 0 54.0408 24.191982 54.0408 54.047963C615.19736 311.134203 590.995145 335.324138 561.15656 335.324138zM424.962691 430.321746c0-4.394077 0-8.806573 0-13.19758 42.878576-17.016559 108.943224-10.793834 153.201218-26.418696 1.75804 0 3.510964 0 5.27719 0-21.329794 108.134813-66.391083 206.496028-76.599585 316.955792 2.425236 1.850138 2.086521 1.473561 5.287423 2.623757 33.247218 11.155061 52.320623-66.110697 73.948199-60.727083 21.644973 5.38873-13.548574 43.733037-18.47784 50.193169-19.400862 25.362644-56.465013 68.439742-100.376105 68.657706-31.008224 0.181125-63.159482-19.378349-58.101279-71.301929 5.056156-51.981908 34.219359-124.319423 50.172703-182.263114C472.184179 468.050022 488.801648 429.049776 424.962691 430.321746z" fill="#515151" p-id="5579"></path></svg>
</el-tooltip>
</router-link>
</li>
<li class="ml-3">
<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>
</li>
<!-- <li class="hover:text-blue-500">
<el-link :href="feedBackUrl" size="large" target="_blank">建议/问题</el-link>
</li> -->
<li class="ml-3" v-if="userStore.isLogin()">
<!-- userStore.avatar -->
<el-image class="w-10 h-10 cursor-pointer" fit="cover" :src="(userStore.avatar == '' || userStore.avatar == null || userStore.avatar == undefined) ? defAvatar : userStore.avatar" v-popover="userPopRef"></el-image>
@@ -207,4 +234,17 @@ onMounted(() => {
transform: translateX(20px);
opacity: 0;
}
:deep(.el-input__wrapper) {
box-shadow: 0 0 0 0px var(--el-input-border-color, var(--el-border-color)) inset;
cursor: default;
}
:deep(.el-input__wrapper) {
box-shadow: 0 0 0 0px var(--el-input-border-color, var(--el-border-color)) inset;
cursor: default;
}
.el-select :deep(.el-input__wrapper){
background-color: rgba(46, 51, 56, 0.05);
background-color: rgb(255, 255, 255);
}
</style>
+5 -8
View File
@@ -3,14 +3,11 @@
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')
//默认展开的菜单
@@ -56,8 +53,8 @@ const gotoAbout = () => {
router.push('about')
}
onMounted(() => {
getToolCates()
onMounted(async () => {
await getToolCates()
})
</script>
@@ -66,7 +63,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,7 +78,7 @@ onMounted(() => {
class="w-[200px]"
:default-active="defaultActive"
:default-openeds="defaultOpeneds"
:background-color="props.bgColor ?? '#f5f5fb'"
background-color="transparent"
@open="handleOpen"
@close="handleClose"
>
@@ -2,6 +2,7 @@
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 { copy } from '@/utils/string'
import { toEchartsData, toSpreadsheetData } from '@/utils/echarts'
@@ -13,6 +14,7 @@ const info = reactive({
const chartDom = ref<HTMLElement|null>()
const myChart = ref<echarts.ECharts>()
const dataFileRef = ref()
const setOptionName = ref(1)
//
@@ -27,7 +29,7 @@ const attrColor = ref('#5470c6')
//
const titlePos = ref('center')
//
const title = ref('然部落在线工具')
const title = ref('Tools-Web')
//
const subTitle = ref('在线图表制作工具')
// -
@@ -37,7 +39,7 @@ const subTitleSwitch = ref(true)
/** 水印 */
const watermarkSwitch = ref(false) //
const waterMarkText = ref('然部落工具');
const waterMarkText = ref('Tools-Web');
//
const createWatermark = () => {
@@ -296,7 +298,17 @@ const updateDataFile = async (params) => {
console.log(e)
}
}
fileReader.readAsBinaryString(_file)
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
@@ -359,6 +371,7 @@ onMounted(() => {
ref="dataFileRef"
accept=".xls,.xlsx,.csv"
:http-request="updateDataFile"
:on-exceed="handleExceed"
:limit="1"
>
上传数据
+479
View File
@@ -0,0 +1,479 @@
<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 { 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>
</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>
+487
View File
@@ -0,0 +1,487 @@
<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 { 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>
</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,482 @@
<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 { 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>
</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>
+133
View File
@@ -0,0 +1,133 @@
<script setup lang="ts">
import { ref,reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.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>
</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>
+79
View File
@@ -0,0 +1,79 @@
<script setup lang="ts">
import { ref,reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.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>
</div>
</template>
<style scoped>
.dice_default{
flex: 0 0 50%;
display: flex;
justify-content: center;
}
.dice_full{
flex: 0 0 100%;
display: flex;
justify-content: center;
}
</style>
+115
View File
@@ -0,0 +1,115 @@
<script setup lang="ts">
import { ref, onBeforeUnmount } from 'vue'
//定义emit
const emit = defineEmits(['throwEnd'])
//是否正在掷筛子
const isDicing = ref(false)
//当前显示的动画图片索引
const aniIndex = ref(0)
//掷筛子动画所用到的图片(4张骰子旋转过程中的图)
const diceAnimationImages = ref([
'/public/images/dice/dice_run1.png',
'/public/images/dice/dice_run2.png',
'/public/images/dice/dice_run3.png',
'/public/images/dice/dice_run4.png'
])
//骰子每一个点的对应的图片
const diceImages = ref({
//1点的图片
1: '/public/images/dice/dice1.png',
//2点的图片
2: '/public/images/dice/dice2.png',
//3点的图片
3: '/public/images/dice/dice3.png',
//4点的图片
4: '/public/images/dice/dice4.png',
//5点的图片
5: '/public/images/dice/dice5.png',
//6点的图片
6: '/public/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>
+143
View File
@@ -0,0 +1,143 @@
<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'
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>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,55 @@
<script setup lang="ts">
import { reactive, computed } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.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>
</div>
</template>
<style scoped>
</style>
+7
View File
@@ -21,6 +21,13 @@ router.beforeEach((to, _from, next) => {
let oldTitle = document.title
document.title = <string>to.meta.title + '-' + oldTitle
}
// console.log(to,_from)
// if (window.location.hostname == 'ascii.ranblogs.com') {
// if (to.fullPath != '/ascii' && window.location.hostname == 'mshop.io') {
// next('/ascii')
// } else {
// next()
// }
next()
})
//路由后置卫士
+102 -28
View File
@@ -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: "时间戳转换",
@@ -21,7 +23,7 @@ export const constantRoute = [
},
{
path: '/MD5',
component: () => import('@/components/MD5/MD5.vue'),
component: () => import('@/components/Tools/MD5/MD5.vue'),
name: 'MD5',
meta: {
title: "MD5在线加密",
@@ -31,7 +33,7 @@ export const constantRoute = [
},
{
path: '/json',
component: () => import('@/components/JsonTran/JsonTran.vue'),
component: () => import('@/components/Tools/JsonTran/JsonTran.vue'),
name: 'json',
meta: {
title: "Json在线转换",
@@ -41,7 +43,7 @@ export const constantRoute = [
},
{
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,7 +93,7 @@ export const constantRoute = [
},
{
path: '/signimage',
component: () => import('@/components/SignImage/SignImage.vue'),
component: () => import('@/components/Tools/SignImage/SignImage.vue'),
name: 'signimage',
meta: {
title: "在线编辑图片",
@@ -101,7 +103,7 @@ export const constantRoute = [
},
{
path: '/random_password',
component: () => import('@/components/RandomPassword/RandomPassword.vue'),
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: "生成随机数",
@@ -211,7 +213,7 @@ export const constantRoute = [
},
{
path: '/numberToChinese',
component: () => import('@/components/NumberToChinese/NumberToChinese.vue'),
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编辑器",
@@ -241,7 +243,7 @@ export const constantRoute = [
},
{
path: '/textToImg',
component: () => import('@/components/TextToImg/TextToImg.vue'),
component: () => import('@/components/Tools/TextToImg/TextToImg.vue'),
name: 'textToImg',
meta: {
title: "文本转图片",
@@ -251,7 +253,7 @@ export const constantRoute = [
},
{
path: '/webInfo',
component: () => import('@/components/WebInfo/WebInfo.vue'),
component: () => import('@/components/Tools/WebInfo/WebInfo.vue'),
name: 'webInfo',
meta: {
title: "网站favicon获取",
@@ -261,7 +263,7 @@ export const constantRoute = [
},
{
path: '/bar',
component: () => import('@/components/Chart/Bar/Bar.vue'),
component: () => import('@/components/Tools/Chart/Bar/Bar.vue'),
name: 'bar',
meta: {
title: "柱状图",
@@ -269,6 +271,77 @@ export const constantRoute = [
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: '/about',
component: () => import('@/components/Home/About.vue'),
@@ -279,6 +352,7 @@ export const constantRoute = [
description: '',
}
},
//其他路由
{
path: '/404',
component: () => import('@/components/404/404.vue'),
+21
View File
@@ -0,0 +1,21 @@
import { defineStore } from 'pinia'
export const useUserStore = defineStore('linux', {
//用来存放变量
state: () => ({
}),
//方法
actions: {
//获取tools
async getCmds() {
//发送请求
// const result: any = await getTools(data)
// if (result.code == 200) {
// return result.message
// } else {
// return Promise.reject(new Error(result.message))
// }
},
}
})
+88 -3
View File
@@ -22,11 +22,11 @@ export function toSpreadsheetData(data, type = 'bar') {
}
/**
* 转换成echarts使用的数据
* 转换成echarts使用的数据 - 基础格式(适用于柱状图、折线图等)
*/
export function toEchartsData(data) {
let tmpColumn: Array<string> = [];
let tmpValue: Array<string> = [];
let tmpColumn: Array<any> = [];
let tmpValue: Array<any> = [];
for (let item in data.rows) {
if (item == 'len') {
continue
@@ -37,9 +37,94 @@ export function toEchartsData(data) {
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