Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6b5f1dc0b | ||
|
|
d863f0174c |
@@ -80,7 +80,7 @@ function getTools() {
|
|||||||
desc: '在线图片裁剪,标注,滤镜,画等操作',
|
desc: '在线图片裁剪,标注,滤镜,画等操作',
|
||||||
url: '/signimage',
|
url: '/signimage',
|
||||||
cateId: 5,
|
cateId: 5,
|
||||||
cate: '设计工具',
|
cate: '图片处理',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -142,8 +142,8 @@ function getTools() {
|
|||||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E8%B0%83%E8%89%B2%E6%9D%BF.png',
|
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/%E8%B0%83%E8%89%B2%E6%9D%BF.png',
|
||||||
desc: '包含纯色、渐变与阶梯色和常用色彩组合',
|
desc: '包含纯色、渐变与阶梯色和常用色彩组合',
|
||||||
url: '/palettes',
|
url: '/palettes',
|
||||||
cateId: 5,
|
cateId: 7,
|
||||||
cate: '设计工具'
|
cate: '其他工具'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -152,7 +152,7 @@ function getTools() {
|
|||||||
desc: '在线生成带logo、透明、艺术的二维码',
|
desc: '在线生成带logo、透明、艺术的二维码',
|
||||||
url: '/qrcode',
|
url: '/qrcode',
|
||||||
cateId: 5,
|
cateId: 5,
|
||||||
cate: '设计工具'
|
cate: '图片处理'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -160,8 +160,8 @@ function getTools() {
|
|||||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/choose.png',
|
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/choose.png',
|
||||||
desc: '选择困难,难以决定,今天吃什么,现在做什么,自定义选项都给你安排的明明白白',
|
desc: '选择困难,难以决定,今天吃什么,现在做什么,自定义选项都给你安排的明明白白',
|
||||||
url: '/decision',
|
url: '/decision',
|
||||||
cateId: 7,
|
cateId: 9,
|
||||||
cate: '其他'
|
cate: '选择随机'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -169,8 +169,8 @@ function getTools() {
|
|||||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/medium.png',
|
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/medium.png',
|
||||||
desc: '支持中文的摩斯电码编码解码',
|
desc: '支持中文的摩斯电码编码解码',
|
||||||
url: '/morse',
|
url: '/morse',
|
||||||
cateId: 3,
|
cateId: 4,
|
||||||
cate: '文本处理'
|
cate: '教育学术'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -178,8 +178,8 @@ function getTools() {
|
|||||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/randomnum.png',
|
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/randomnum.png',
|
||||||
desc: '可定制范围内进行随机数字,可用于抽奖、点名等用途',
|
desc: '可定制范围内进行随机数字,可用于抽奖、点名等用途',
|
||||||
url: '/random',
|
url: '/random',
|
||||||
cateId: 7,
|
cateId: 9,
|
||||||
cate: '其他'
|
cate: '选择随机'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -188,7 +188,7 @@ function getTools() {
|
|||||||
desc: '在线数字一键转换成人民币大写,中文大写转换数字',
|
desc: '在线数字一键转换成人民币大写,中文大写转换数字',
|
||||||
url: '/numberToChinese',
|
url: '/numberToChinese',
|
||||||
cateId: 7,
|
cateId: 7,
|
||||||
cate: '其他'
|
cate: '其他工具'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -215,7 +215,7 @@ function getTools() {
|
|||||||
desc: '把文本转换成图片,生成长图,具有超多个性文字排版',
|
desc: '把文本转换成图片,生成长图,具有超多个性文字排版',
|
||||||
url: '/textToImg',
|
url: '/textToImg',
|
||||||
cateId: 5,
|
cateId: 5,
|
||||||
cate: '设计工具'
|
cate: '图片处理'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -235,6 +235,69 @@ function getTools() {
|
|||||||
cateId: 8,
|
cateId: 8,
|
||||||
cate: '数据图表',
|
cate: '数据图表',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '折线图',
|
||||||
|
logo: '/images/logo/bar.png',
|
||||||
|
desc: '在线制作柱状图,像做表格一样制作可视化图表,支持导出静态或动态图表',
|
||||||
|
url: '/line',
|
||||||
|
cateId: 8,
|
||||||
|
cate: '数据图表',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '饼图',
|
||||||
|
logo: '/images/logo/bar.png',
|
||||||
|
desc: '在线制作柱状图,像做表格一样制作可视化图表,支持导出静态或动态图表',
|
||||||
|
url: '/pie',
|
||||||
|
cateId: 8,
|
||||||
|
cate: '数据图表',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '散点图',
|
||||||
|
logo: '/images/logo/bar.png',
|
||||||
|
desc: '在线制作柱状图,像做表格一样制作可视化图表,支持导出静态或动态图表',
|
||||||
|
url: '/scatter',
|
||||||
|
cateId: 8,
|
||||||
|
cate: '数据图表',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '抛硬币',
|
||||||
|
logo: '/images/logo/bar.png',
|
||||||
|
desc: '在线抛硬币,选择困难那么交给硬币来帮你选择吧',
|
||||||
|
url: '/coin',
|
||||||
|
cateId: 9,
|
||||||
|
cate: '选择随机',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '投骰子',
|
||||||
|
logo: '/images/logo/bar.png',
|
||||||
|
desc: '在线投骰子,可自定义骰子数量,简单好用的骰子工具',
|
||||||
|
url: '/dice',
|
||||||
|
cateId: 9,
|
||||||
|
cate: '选择随机',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '文本去重',
|
||||||
|
logo: '/images/logo/bar.png',
|
||||||
|
desc: '可以删除或去除文本或字符串中的重复行',
|
||||||
|
url: '/textRemoveDuplicate',
|
||||||
|
cateId: 3,
|
||||||
|
cate: '文本处理',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '图片分割',
|
||||||
|
logo: '/images/logo/bar.png',
|
||||||
|
desc: '将图片分割成四宫格、九宫格、十六宫格,支持自定义行与列',
|
||||||
|
url: '/imgCut',
|
||||||
|
cateId: 5,
|
||||||
|
cate: '图片处理',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,15 +406,6 @@ function getToolsCate() {
|
|||||||
cateId: 3,
|
cateId: 3,
|
||||||
cate: '文本处理'
|
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,
|
id: 1,
|
||||||
title: '字数统计',
|
title: '字数统计',
|
||||||
@@ -360,7 +414,16 @@ function getToolsCate() {
|
|||||||
url: '/wordCount',
|
url: '/wordCount',
|
||||||
cateId: 3,
|
cateId: 3,
|
||||||
cate: '文本处理',
|
cate: '文本处理',
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '文本去重',
|
||||||
|
logo: '/images/logo/bar.png',
|
||||||
|
desc: '可以删除或去除文本或字符串中的重复行',
|
||||||
|
url: '/textRemoveDuplicate',
|
||||||
|
cateId: 3,
|
||||||
|
cate: '文本处理',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -377,6 +440,15 @@ function getToolsCate() {
|
|||||||
cateId: 4,
|
cateId: 4,
|
||||||
cate: '教育学术'
|
cate: '教育学术'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '摩斯电码',
|
||||||
|
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/medium.png',
|
||||||
|
desc: '支持中文的摩斯电码编码解码',
|
||||||
|
url: '/morse',
|
||||||
|
cateId: 4,
|
||||||
|
cate: '教育学术'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: '常用进制转换',
|
title: '常用进制转换',
|
||||||
@@ -399,18 +471,9 @@ function getToolsCate() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 5,
|
id: 5,
|
||||||
title: '设计工具',
|
title: '图片处理',
|
||||||
icon: '',
|
icon: '',
|
||||||
list: [
|
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,
|
id: 1,
|
||||||
title: '二维码生成',
|
title: '二维码生成',
|
||||||
@@ -418,7 +481,7 @@ function getToolsCate() {
|
|||||||
desc: '在线生成带logo、透明、艺术的二维码',
|
desc: '在线生成带logo、透明、艺术的二维码',
|
||||||
url: '/qrcode',
|
url: '/qrcode',
|
||||||
cateId: 5,
|
cateId: 5,
|
||||||
cate: '设计工具'
|
cate: '图片处理'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -427,7 +490,7 @@ function getToolsCate() {
|
|||||||
desc: '在线图片裁剪,标注,滤镜,画等操作',
|
desc: '在线图片裁剪,标注,滤镜,画等操作',
|
||||||
url: '/signimage',
|
url: '/signimage',
|
||||||
cateId: 5,
|
cateId: 5,
|
||||||
cate: '设计工具',
|
cate: '图片处理',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -436,8 +499,17 @@ function getToolsCate() {
|
|||||||
desc: '把文本转换成图片,生成长图,具有超多个性文字排版',
|
desc: '把文本转换成图片,生成长图,具有超多个性文字排版',
|
||||||
url: '/textToImg',
|
url: '/textToImg',
|
||||||
cateId: 5,
|
cateId: 5,
|
||||||
cate: '设计工具'
|
cate: '图片处理'
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '图片分割',
|
||||||
|
logo: '/images/logo/bar.png',
|
||||||
|
desc: '将图片分割成四宫格、九宫格、十六宫格,支持自定义行与列',
|
||||||
|
url: '/imgCut',
|
||||||
|
cateId: 5,
|
||||||
|
cate: '图片处理',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -478,12 +550,38 @@ function getToolsCate() {
|
|||||||
url: '/bar',
|
url: '/bar',
|
||||||
cateId: 8,
|
cateId: 8,
|
||||||
cate: '数据图表',
|
cate: '数据图表',
|
||||||
|
},{
|
||||||
|
id: 1,
|
||||||
|
title: '折线图',
|
||||||
|
logo: '/images/logo/bar.png',
|
||||||
|
desc: '在线制作折线图,像做表格一样制作可视化图表,支持导出静态或动态图表',
|
||||||
|
url: '/line',
|
||||||
|
cateId: 8,
|
||||||
|
cate: '数据图表',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '饼图',
|
||||||
|
logo: '/images/logo/bar.png',
|
||||||
|
desc: '在线制作饼图,像做表格一样制作可视化图表,支持导出静态或动态图表',
|
||||||
|
url: '/pie',
|
||||||
|
cateId: 8,
|
||||||
|
cate: '数据图表',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '散点图',
|
||||||
|
logo: '/images/logo/bar.png',
|
||||||
|
desc: '在线制作散点图,像做表格一样制作可视化图表,支持导出静态或动态图表',
|
||||||
|
url: '/scatter',
|
||||||
|
cateId: 8,
|
||||||
|
cate: '数据图表',
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 7,
|
id: 9,
|
||||||
title: '其他',
|
title: '选择随机',
|
||||||
icon: '',
|
icon: '',
|
||||||
list: [
|
list: [
|
||||||
{
|
{
|
||||||
@@ -492,17 +590,8 @@ function getToolsCate() {
|
|||||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/randomnum.png',
|
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/randomnum.png',
|
||||||
desc: '可定制范围内进行随机数字,可用于抽奖、点名等用途',
|
desc: '可定制范围内进行随机数字,可用于抽奖、点名等用途',
|
||||||
url: '/random',
|
url: '/random',
|
||||||
cateId: 7,
|
cateId: 9,
|
||||||
cate: '其他'
|
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,
|
id: 1,
|
||||||
@@ -510,8 +599,42 @@ function getToolsCate() {
|
|||||||
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/choose.png',
|
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/choose.png',
|
||||||
desc: '选择困难,难以决定,今天吃什么,现在做什么,自定义选项都给你安排的明明白白',
|
desc: '选择困难,难以决定,今天吃什么,现在做什么,自定义选项都给你安排的明明白白',
|
||||||
url: '/decision',
|
url: '/decision',
|
||||||
|
cateId: 9,
|
||||||
|
cate: '选择随机'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '抛硬币',
|
||||||
|
logo: '/images/logo/bar.png',
|
||||||
|
desc: '在线抛硬币,选择困难那么交给硬币来帮你选择吧',
|
||||||
|
url: '/coin',
|
||||||
|
cateId: 9,
|
||||||
|
cate: '选择随机',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '投骰子',
|
||||||
|
logo: '/images/logo/bar.png',
|
||||||
|
desc: '在线投骰子,可自定义骰子数量,简单好用的骰子工具',
|
||||||
|
url: '/dice',
|
||||||
|
cateId: 9,
|
||||||
|
cate: '选择随机',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
title: '其他工具',
|
||||||
|
icon: '',
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '数字转金额大写',
|
||||||
|
logo: 'https://baseran2.oss-cn-shenzhen.aliyuncs.com/toools-web/icon/text%20recognition.png',
|
||||||
|
desc: '在线数字一键转换成人民币大写,中文大写转换数字',
|
||||||
|
url: '/numberToChinese',
|
||||||
cateId: 7,
|
cateId: 7,
|
||||||
cate: '其他'
|
cate: '其他工具'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -520,7 +643,16 @@ function getToolsCate() {
|
|||||||
desc: '手持滚动弹幕',
|
desc: '手持滚动弹幕',
|
||||||
url: '/barrage',
|
url: '/barrage',
|
||||||
cateId: 7,
|
cateId: 7,
|
||||||
cate: '其他',
|
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: 7,
|
||||||
|
cate: '其他工具'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
|
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 |
@@ -11,12 +11,12 @@ const componentStore = useComponentStore()
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- <el-container> -->
|
<el-container>
|
||||||
<!-- left -->
|
<!-- left -->
|
||||||
<!-- <el-aside class="fixed top-0 left-0 h-full z-10 c-md:block c-sm:hidden c-xs:hidden" width="240px" v-show="!componentStore.leftCom">
|
<el-aside class="fixed top-0 left-0 h-full z-10 c-md:block c-sm:hidden c-xs:hidden" width="240px" v-show="!componentStore.leftCom">
|
||||||
<Left></Left>
|
<Left></Left>
|
||||||
</el-aside> -->
|
</el-aside>
|
||||||
<!-- <el-drawer
|
<el-drawer
|
||||||
show-close
|
show-close
|
||||||
size="240px"
|
size="240px"
|
||||||
:with-header="false"
|
:with-header="false"
|
||||||
@@ -24,26 +24,26 @@ const componentStore = useComponentStore()
|
|||||||
direction="ltr"
|
direction="ltr"
|
||||||
>
|
>
|
||||||
<Left bgColor="#fff"></Left>
|
<Left bgColor="#fff"></Left>
|
||||||
</el-drawer> -->
|
</el-drawer>
|
||||||
|
|
||||||
<!-- right -->
|
<!-- right -->
|
||||||
<!-- <el-container> -->
|
<el-container :class="!componentStore.leftCom ? 'c-md:ml-[240px]' : ''">
|
||||||
<!-- <el-header>
|
<el-header>
|
||||||
<Header/>
|
<Header/>
|
||||||
</el-header> -->
|
</el-header>
|
||||||
<!-- <el-main> -->
|
<el-main>
|
||||||
<router-view v-slot="{ Component, route }">
|
<router-view v-slot="{ Component, route }">
|
||||||
<transition name="animation" mode="out-in">
|
<transition name="animation" mode="out-in">
|
||||||
<component :is="Component" :key="route.path"></component>
|
<component :is="Component" :key="route.path"></component>
|
||||||
</transition>
|
</transition>
|
||||||
</router-view>
|
</router-view>
|
||||||
<!-- </el-main> -->
|
</el-main>
|
||||||
<!-- <el-footer class="md:mb-6 mt-12 c-xs:mb-12">
|
<el-footer class="md:mb-6 mt-12 c-xs:mb-12">
|
||||||
<Floor />
|
<Floor />
|
||||||
</el-footer> -->
|
</el-footer>
|
||||||
<!-- </el-container> -->
|
</el-container>
|
||||||
|
|
||||||
<!-- </el-container> -->
|
</el-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -2,19 +2,4 @@
|
|||||||
export interface ResponseData {
|
export interface ResponseData {
|
||||||
code: number,
|
code: number,
|
||||||
message: string,
|
message: string,
|
||||||
}
|
|
||||||
|
|
||||||
//分页
|
|
||||||
export interface PageData {
|
|
||||||
current_page: number,
|
|
||||||
first_page_url: string,
|
|
||||||
from: number,
|
|
||||||
last_page: number,
|
|
||||||
last_page_url: string,
|
|
||||||
next_page_url: string,
|
|
||||||
path: string,
|
|
||||||
per_page: number,
|
|
||||||
prev_page_url: string,
|
|
||||||
to: number,
|
|
||||||
total: number,
|
|
||||||
}
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
//统一管理咱们项目用户相关的接口
|
|
||||||
import request from '@/utils/request'
|
|
||||||
|
|
||||||
import type {
|
|
||||||
ScanReqData,
|
|
||||||
UpdateTagReqData,
|
|
||||||
getScanResponseData,
|
|
||||||
getScanTagResponseData,
|
|
||||||
updateTagResponseData,
|
|
||||||
ScanInfoReqData,
|
|
||||||
ScanFileInfo
|
|
||||||
} from './type'
|
|
||||||
|
|
||||||
//项目用户相关的请求地址
|
|
||||||
|
|
||||||
enum API {
|
|
||||||
//获取scan列表
|
|
||||||
SCAN_FILE = '/scans',
|
|
||||||
//获取scan信息
|
|
||||||
SCAN_FILE_INFO = '/scans',
|
|
||||||
//获取标签
|
|
||||||
SCAN_TAGS = '/scan/tags',
|
|
||||||
//更新标签
|
|
||||||
UPDATE_TAG = '/scan/tag/updates',
|
|
||||||
}
|
|
||||||
//获取scan file
|
|
||||||
export const getList = (data: ScanReqData) => request.get<any, getScanResponseData>(API.SCAN_FILE, {
|
|
||||||
params: data
|
|
||||||
})
|
|
||||||
//获取scan file
|
|
||||||
export const getInfo = (data: ScanInfoReqData) => request.get<any, ScanFileInfo>(API.SCAN_FILE_INFO + '/' + data.fileId)
|
|
||||||
//获取scan tags
|
|
||||||
export const getTags = () => request.get<any, getScanTagResponseData>(API.SCAN_TAGS)
|
|
||||||
//更新标签
|
|
||||||
export const updateTag = (data: UpdateTagReqData) => request.post<any, updateTagResponseData>(API.UPDATE_TAG, data)
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import { ResponseData, PageData } from "../common"
|
|
||||||
|
|
||||||
export interface ScanReqData {
|
|
||||||
title: string,
|
|
||||||
tags: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ScanInfoReqData {
|
|
||||||
fileId: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateTagReqData {
|
|
||||||
fileId: number,
|
|
||||||
tagId: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ScanTagInfo {
|
|
||||||
id: number,
|
|
||||||
title: string,
|
|
||||||
pid: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
//file info
|
|
||||||
export interface ScanFileInfo {
|
|
||||||
id: number,
|
|
||||||
title: string,
|
|
||||||
path: string,
|
|
||||||
dir: string,
|
|
||||||
ext: string,
|
|
||||||
tags: number[],
|
|
||||||
scan_tag: ScanTagInfo[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ScanFilePage extends PageData {
|
|
||||||
data: ScanFileInfo[],
|
|
||||||
}
|
|
||||||
|
|
||||||
//tag info
|
|
||||||
export interface ScanTagInfo {
|
|
||||||
id: number,
|
|
||||||
title: string,
|
|
||||||
pid: number,
|
|
||||||
childTags: ScanTagInfo[]
|
|
||||||
}
|
|
||||||
|
|
||||||
//响应格式 - scan file
|
|
||||||
export interface getScanResponseData extends ResponseData {
|
|
||||||
data: ScanFileInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
//响应格式 - scan tag
|
|
||||||
export interface getScanTagResponseData extends ResponseData {
|
|
||||||
data: ScanTagInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
//响应格式 - update tag
|
|
||||||
export interface updateTagResponseData extends ResponseData {
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import {ref, reactive, onMounted } from 'vue'
|
import {ref, reactive, onMounted } from 'vue'
|
||||||
import Spreadsheet from 'x-data-spreadsheet'
|
import Spreadsheet from 'x-data-spreadsheet'
|
||||||
import 'x-data-spreadsheet/dist/locale/zh-cn';
|
import 'x-data-spreadsheet/dist/locale/zh-cn';
|
||||||
|
import { UploadProps,UploadRawFile,genFileId } from 'element-plus'
|
||||||
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
|
||||||
// import { copy } from '@/utils/string'
|
// import { copy } from '@/utils/string'
|
||||||
import { toEchartsData, toSpreadsheetData } from '@/utils/echarts'
|
import { toEchartsData, toSpreadsheetData } from '@/utils/echarts'
|
||||||
@@ -13,6 +14,7 @@ const info = reactive({
|
|||||||
|
|
||||||
const chartDom = ref<HTMLElement|null>()
|
const chartDom = ref<HTMLElement|null>()
|
||||||
const myChart = ref<echarts.ECharts>()
|
const myChart = ref<echarts.ECharts>()
|
||||||
|
const dataFileRef = ref()
|
||||||
|
|
||||||
const setOptionName = ref(1)
|
const setOptionName = ref(1)
|
||||||
//缩放比例
|
//缩放比例
|
||||||
@@ -296,7 +298,17 @@ const updateDataFile = async (params) => {
|
|||||||
console.log(e)
|
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
|
//copy
|
||||||
@@ -359,6 +371,7 @@ onMounted(() => {
|
|||||||
ref="dataFileRef"
|
ref="dataFileRef"
|
||||||
accept=".xls,.xlsx,.csv"
|
accept=".xls,.xlsx,.csv"
|
||||||
:http-request="updateDataFile"
|
:http-request="updateDataFile"
|
||||||
|
:on-exceed="handleExceed"
|
||||||
:limit="1"
|
:limit="1"
|
||||||
>
|
>
|
||||||
上传数据
|
上传数据
|
||||||
|
|||||||
@@ -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('然部落在线工具')
|
||||||
|
//副标题
|
||||||
|
const subTitle = ref('在线图表制作工具')
|
||||||
|
//显示标题 - 开关
|
||||||
|
const titleSwitch = ref(true)
|
||||||
|
//显示副标题 - 开关
|
||||||
|
const subTitleSwitch = ref(true)
|
||||||
|
|
||||||
|
/** 水印 */
|
||||||
|
const watermarkSwitch = ref(false) //开关
|
||||||
|
const waterMarkText = ref('然部落工具');
|
||||||
|
|
||||||
|
//创建水印
|
||||||
|
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>
|
||||||
@@ -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('然部落在线工具')
|
||||||
|
//副标题
|
||||||
|
const subTitle = ref('在线图表制作工具')
|
||||||
|
//显示标题 - 开关
|
||||||
|
const titleSwitch = ref(true)
|
||||||
|
//显示副标题 - 开关
|
||||||
|
const subTitleSwitch = ref(true)
|
||||||
|
|
||||||
|
/** 水印 */
|
||||||
|
const watermarkSwitch = ref(false) //开关
|
||||||
|
const waterMarkText = ref('然部落工具');
|
||||||
|
|
||||||
|
//创建水印
|
||||||
|
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('然部落在线工具')
|
||||||
|
//副标题
|
||||||
|
const subTitle = ref('在线图表制作工具')
|
||||||
|
//显示标题 - 开关
|
||||||
|
const titleSwitch = ref(true)
|
||||||
|
//显示副标题 - 开关
|
||||||
|
const subTitleSwitch = ref(true)
|
||||||
|
|
||||||
|
/** 水印 */
|
||||||
|
const watermarkSwitch = ref(false) //开关
|
||||||
|
const waterMarkText = ref('然部落工具');
|
||||||
|
|
||||||
|
//创建水印
|
||||||
|
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>
|
||||||
@@ -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>
|
||||||
@@ -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/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>
|
||||||
@@ -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>
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
// import { ref } from 'vue'
|
|
||||||
// import { copy } from '@/utils/string'
|
|
||||||
// const info = reactive({
|
|
||||||
// title: "关于本站",
|
|
||||||
// })
|
|
||||||
const feedBackUrl = import.meta.env.VITE_FEEDBACK_URL
|
|
||||||
//copy
|
|
||||||
// const copyRes = async (resStr: string) => {
|
|
||||||
// copy(resStr)
|
|
||||||
// }
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="flex flex-col mt-3 flex-1">
|
|
||||||
<div class="bg-white rounded-md p-5">
|
|
||||||
<br>
|
|
||||||
<p>本项目是一个开源工具站, 求星星</p>
|
|
||||||
<br>
|
|
||||||
<p>本项目开源地址:<el-link href="https://github.com/taoran1401/tools-web">https://github.com/taoran1401/tools-web</el-link></p>
|
|
||||||
<br>
|
|
||||||
<p class="font-bold">联系作者/问题反馈</p>
|
|
||||||
<br>
|
|
||||||
<p>反馈中心:<el-link :href="feedBackUrl">{{ feedBackUrl }}</el-link></p>
|
|
||||||
<br>
|
|
||||||
<p>邮箱:taoran0796@163.com</p>
|
|
||||||
<br>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,100 +1,31 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
// import { ref } from 'vue'
|
||||||
// import { useRouter } from "vue-router"
|
// import { copy } from '@/utils/string'
|
||||||
import { useScanStore } from '@/store/modules/scan'
|
// const info = reactive({
|
||||||
|
// title: "关于本站",
|
||||||
// const router = useRouter()
|
// })
|
||||||
const scanStore = useScanStore()
|
const feedBackUrl = import.meta.env.VITE_FEEDBACK_URL
|
||||||
|
//copy
|
||||||
//video html element
|
// const copyRes = async (resStr: string) => {
|
||||||
const videoPlayerRef = ref()
|
// copy(resStr)
|
||||||
|
|
||||||
//上一个
|
|
||||||
const playPreviousVideo = () => {
|
|
||||||
var data = scanStore.historyList[scanStore.historyList.length - 2];
|
|
||||||
videoPlayerRef.value.src = scanStore.sourceUrl + data.title;
|
|
||||||
// console.log(videoPlayerRef.value.src)
|
|
||||||
videoPlayerRef.value.load();
|
|
||||||
videoPlayerRef.value.play();
|
|
||||||
//记录当期播放
|
|
||||||
scanStore.setNowPlay(data)
|
|
||||||
// scanStore.setNowIndex(nextIndex)
|
|
||||||
//刷新页面
|
|
||||||
window.location.reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
//下一个
|
|
||||||
const playNextVideo = () => {
|
|
||||||
let nextIndex = scanStore.getNowIndex() + 1;
|
|
||||||
if (scanStore.isRandPlay === false) {
|
|
||||||
//随机播放,并且不能和当前相同TODO
|
|
||||||
nextIndex = Math.floor(Math.random() * scanStore.videoList.length);
|
|
||||||
}
|
|
||||||
if (nextIndex < scanStore.videoList.length) {
|
|
||||||
var data = scanStore.videoList[nextIndex];
|
|
||||||
videoPlayerRef.value.src = scanStore.sourceUrl + data.title;
|
|
||||||
videoPlayerRef.value.load();
|
|
||||||
videoPlayerRef.value.play();
|
|
||||||
//记录当期播放
|
|
||||||
scanStore.setNowPlay(data)
|
|
||||||
scanStore.setNowIndex(nextIndex)
|
|
||||||
//刷新页面
|
|
||||||
window.location.reload()
|
|
||||||
} else {
|
|
||||||
alert('没有下一个了')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//能播放时触发
|
|
||||||
const canplay = () => {
|
|
||||||
//更新历史播放
|
|
||||||
//过滤重复信息
|
|
||||||
let nowPlay = scanStore.getNowPlay()
|
|
||||||
console.log(nowPlay.id)
|
|
||||||
scanStore.historyList = scanStore.historyList.filter(item => item.id != nowPlay.id)
|
|
||||||
scanStore.historyList.push(scanStore.getNowPlay())
|
|
||||||
localStorage.setItem('historyList', JSON.stringify(scanStore.historyList))
|
|
||||||
}
|
|
||||||
|
|
||||||
//根据索引获取文件信息
|
|
||||||
// const getInfoByIndex = () => {
|
|
||||||
// return scanStore.videoList[Number(scanStore.nowIndex)];
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
//播放结束时触发
|
|
||||||
const ended = () => {
|
|
||||||
//自动播放下一个
|
|
||||||
playNextVideo()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
console.log(scanStore.getNowPlay())
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col mt-3 flex-1">
|
<div class="flex flex-col mt-3 flex-1">
|
||||||
<div class="ml-3">
|
<div class="bg-white rounded-md p-5">
|
||||||
<router-link to="/" class="mr-3">
|
<br>
|
||||||
<el-button>首页</el-button>
|
<p>本项目基于 <el-link class="" href="https://github.com/taoran1401/tools-web" target="_blank" type="primary">tools-web</el-link> 构建</p>
|
||||||
</router-link>
|
<br>
|
||||||
<el-button @click="playPreviousVideo">上一个</el-button>
|
<p class=""><span class="bg-gray-200 p-1">ctrl + D</span>收藏本站,以便以后使用</p>
|
||||||
<el-button @click="playNextVideo">下一个</el-button>
|
<br>
|
||||||
<div class="mt-3 mb-3">当前播放:{{ scanStore.getNowPlay().path }}</div>
|
<p class="font-bold">联系作者/问题反馈</p>
|
||||||
<video
|
<br>
|
||||||
ref="videoPlayerRef"
|
<p>反馈中心:<el-link :href="feedBackUrl">{{ feedBackUrl }}</el-link></p>
|
||||||
:src="scanStore.sourceUrl + scanStore.getNowPlay().title" class="w-[850px] h-[500px]"
|
<br>
|
||||||
:autoplay="false"
|
<p>邮箱:taoran0796@163.com</p>
|
||||||
:playbackRate="scanStore.playbackRate"
|
<br>
|
||||||
@canplay="canplay"
|
|
||||||
@ended="ended"
|
|
||||||
controls>
|
|
||||||
您的浏览器不支持video
|
|
||||||
</video>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { onMounted,reactive } from 'vue';
|
|
||||||
import { RouterLink } from "vue-router"
|
|
||||||
import { Star } from '@element-plus/icons-vue'
|
|
||||||
import { useToolsStore } from '@/store/modules/tools'
|
|
||||||
import { useUserStore } from '@/store/modules/user'
|
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
import { useRoute } from "vue-router"
|
|
||||||
//store
|
|
||||||
const toolsStore = useToolsStore()
|
|
||||||
const userStore = useUserStore()
|
|
||||||
const route = useRoute()
|
|
||||||
// const getToolsCate = async () => {
|
|
||||||
// try {
|
|
||||||
// await toolsStore.getToolCate()
|
|
||||||
// } catch (error: any) {
|
|
||||||
// ElMessage.error(error.message)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
//收藏
|
|
||||||
const toolCollect = reactive({
|
|
||||||
toolId: 0
|
|
||||||
})
|
|
||||||
const collect = async (toolId) => {
|
|
||||||
try {
|
|
||||||
if (!userStore.isLogin()) {
|
|
||||||
//未登录,弹出登录窗口
|
|
||||||
ElMessage.error('请登录')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
toolCollect.toolId = toolId
|
|
||||||
await toolsStore.toolCollect(toolCollect)
|
|
||||||
// ElMessage.success('收藏成功')
|
|
||||||
} catch (error: any) {
|
|
||||||
ElMessage.error(error.message)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// getToolsCate()
|
|
||||||
if (route.query && route.query.value) {//底部导航跳转过来的则定位到响应位置
|
|
||||||
document?.querySelector('#' + `${route.query.value}`)?.scrollIntoView();
|
|
||||||
} else {//其他位置跳转过来不需要定位的则定位到顶部
|
|
||||||
document?.querySelector('#collect')?.scrollIntoView()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="md:mr-6 c-xs:mr-0">
|
|
||||||
<!-- collect -->
|
|
||||||
<div id="collect">
|
|
||||||
<!-- cate title -->
|
|
||||||
<div class="md:mt-8 md:mb-8 c-xs:mt-1 c-xs:mb-4 text-xl font-bold text-[--base-black]">
|
|
||||||
我的收藏
|
|
||||||
</div>
|
|
||||||
<!-- card -->
|
|
||||||
<div class="flex justify-between flex-wrap self-card-div c-xs:ml-3" :gutter="10" v-if="toolsStore.collect.length > 0">
|
|
||||||
<router-link v-for="(item, index) in toolsStore.collect" :key="index" :to="item.url" class="flex flex-col mt-5 border-solid rounded-2xl border-gray w-[24%] p-2 bg-white hover:shadow-md c-xs:w-[99.5%] c-md:w-[24%] c-sm:w-[32%] p-5">
|
|
||||||
<div class="flex items-center border-b pb-2">
|
|
||||||
<el-image :src="item.logo" class="w-10 h-10 min-h-[2.5rem] min-w-[2.5rem] rounded-full"></el-image>
|
|
||||||
<div class="flex flex-col ml-2 w-full">
|
|
||||||
<div class="flex">
|
|
||||||
<div class="font-semibold text-lg line-clamp-1">{{ item.title }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<el-text size="small">{{ item.cate }}</el-text>
|
|
||||||
<el-button :icon="Star" circle size="small" @click.prevent="collect(item.id)" type="warning"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between mt-2">
|
|
||||||
<el-text line-clamp="2">{{ item.desc }}</el-text>
|
|
||||||
</div>
|
|
||||||
</router-link>
|
|
||||||
<!-- 占位 div -->
|
|
||||||
<div class="w-[24%] c-md:w-[24%] c-sm:w-[32%]"></div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="bg-white rounded-2xl">
|
|
||||||
<el-empty description="暂无收藏" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- list -->
|
|
||||||
<div v-for="(cate, index) in toolsStore.cates" :key="index">
|
|
||||||
<!-- cate title -->
|
|
||||||
<div class="mt-8 mb-3 text-xl font-bold text-[--base-black]" :id="'cate_' + cate.id">
|
|
||||||
{{ cate.title }}
|
|
||||||
</div>
|
|
||||||
<!-- card -->
|
|
||||||
<div class="flex justify-between flex-wrap self-card-div c-xs:ml-0" :gutter="10">
|
|
||||||
<router-link v-for="(item, index) in cate.list" :key="index" :to="item.url" class="flex flex-col mt-5 border-solid rounded-2xl border-gray w-[24%] p-2 bg-white hover:shadow-md c-xs:w-[99.5%] c-md:w-[24%] c-sm:w-[32%] p-5 hover:-translate-y-2 duration-300">
|
|
||||||
<div class="flex items-center border-b pb-2">
|
|
||||||
<el-image :src="item.logo" class="w-10 h-10 min-h-[2.5rem] min-w-[2.5rem] rounded-full"></el-image>
|
|
||||||
<div class="flex flex-col ml-2 w-full">
|
|
||||||
<div class="flex">
|
|
||||||
<div class="font-semibold text-lg line-clamp-1">{{ item.title }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<el-text size="small">{{ item.cate }}</el-text>
|
|
||||||
<el-button :icon="Star" circle size="small" @click.prevent="collect(item.id)" type="warning" :plain="!toolsStore.collectIds.includes(item.id)"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between mt-2">
|
|
||||||
<el-text line-clamp="2">{{ item.desc }}</el-text>
|
|
||||||
</div>
|
|
||||||
</router-link>
|
|
||||||
<!-- 占位 div -->
|
|
||||||
<div class="w-[24%] c-md:w-[24%] c-sm:w-[32%] "></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.self-card-div:after{
|
|
||||||
content: "";
|
|
||||||
width: 24%
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted,reactive, ref } from 'vue';
|
import { onMounted,reactive } from 'vue';
|
||||||
import { useScanStore } from '@/store/modules/scan'
|
import { RouterLink } from "vue-router"
|
||||||
// import { ElMessage } from 'element-plus'
|
import { Star } from '@element-plus/icons-vue'
|
||||||
import { Search } from '@element-plus/icons-vue';
|
import { useToolsStore } from '@/store/modules/tools'
|
||||||
import { useRouter } from "vue-router"
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { useRoute } from "vue-router"
|
||||||
//store
|
//store
|
||||||
const scanStore = useScanStore()
|
const toolsStore = useToolsStore()
|
||||||
const router = useRouter()
|
const userStore = useUserStore()
|
||||||
|
const route = useRoute()
|
||||||
// const getToolsCate = async () => {
|
// const getToolsCate = async () => {
|
||||||
// try {
|
// try {
|
||||||
// await toolsStore.getToolCate()
|
// await toolsStore.getToolCate()
|
||||||
@@ -15,226 +18,100 @@ const router = useRouter()
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const loading = ref(false)
|
//收藏
|
||||||
const setTagsDialog = ref(false)
|
const toolCollect = reactive({
|
||||||
// const checkedTags = reactive({})
|
toolId: 0
|
||||||
//----- config -----
|
|
||||||
//----- ^ -----
|
|
||||||
|
|
||||||
//获取分类
|
|
||||||
//查询参数
|
|
||||||
const searchParam = reactive({
|
|
||||||
title: '',
|
|
||||||
tags: '',
|
|
||||||
page: 1,
|
|
||||||
})
|
})
|
||||||
const getList = async (query) => {
|
const collect = async (toolId) => {
|
||||||
try {
|
try {
|
||||||
searchParam.title = query
|
if (!userStore.isLogin()) {
|
||||||
await scanStore.getList(searchParam)
|
//未登录,弹出登录窗口
|
||||||
} catch (error) {
|
ElMessage.error('请登录')
|
||||||
console.log(error)
|
return
|
||||||
|
}
|
||||||
|
toolCollect.toolId = toolId
|
||||||
|
await toolsStore.toolCollect(toolCollect)
|
||||||
|
// ElMessage.success('收藏成功')
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error(error.message)
|
||||||
}
|
}
|
||||||
}
|
return
|
||||||
|
|
||||||
//获取标签
|
|
||||||
const getTags = async () => {
|
|
||||||
try {
|
|
||||||
await scanStore.getTags()
|
|
||||||
//设置标签checked
|
|
||||||
// for (let index in scanStore.tags) {
|
|
||||||
// for (let childIndex in scanStore.tags[index].childTags) {
|
|
||||||
// let cheeckedTag = 'checkTag' + scanStore.tags[index].childTags[childIndex].id
|
|
||||||
// }
|
|
||||||
// console.log(scanStore.tags[index].childTags)
|
|
||||||
// }
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const optionClick = (path: string) => {
|
|
||||||
console.log(path)
|
|
||||||
// router.push(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
//获取文件信息
|
|
||||||
const getFileInfo = async (fileId: number) => {
|
|
||||||
try {
|
|
||||||
await scanStore.getInfo({
|
|
||||||
fileId: fileId,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//set tags
|
|
||||||
const setTagsDia = async (fileId: number) => {
|
|
||||||
//显示dialog
|
|
||||||
setTagsDialog.value = true
|
|
||||||
await getFileInfo(fileId)
|
|
||||||
}
|
|
||||||
|
|
||||||
//更新标签
|
|
||||||
const updateTag = async (fileId: number, tagId: number) => {
|
|
||||||
console.log(fileId, tagId)
|
|
||||||
try {
|
|
||||||
await scanStore.updateTag({
|
|
||||||
fileId: fileId,
|
|
||||||
tagId: tagId
|
|
||||||
})
|
|
||||||
//获取当前文件标签
|
|
||||||
await getFileInfo(fileId)
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//分页 - 页码改变时
|
|
||||||
const pageChange = async (nowPage: number) => {
|
|
||||||
searchParam.page = nowPage
|
|
||||||
console.log(nowPage)
|
|
||||||
await scanStore.getList(searchParam)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* open local app
|
|
||||||
*
|
|
||||||
* index: 索引
|
|
||||||
* data: 内容数据
|
|
||||||
*/
|
|
||||||
const open = (index: number, data: any) => {
|
|
||||||
//缓存当前播放和播放索引
|
|
||||||
scanStore.setNowPlay(data)
|
|
||||||
scanStore.setNowIndex(index)
|
|
||||||
router.push({
|
|
||||||
path: '/about',
|
|
||||||
})
|
|
||||||
|
|
||||||
// let addr = router.resolve({
|
|
||||||
// path: '/about',
|
|
||||||
// params: {
|
|
||||||
// url: url
|
|
||||||
// }
|
|
||||||
// }).href
|
|
||||||
// window.open(addr, '_blank')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList('');
|
// getToolsCate()
|
||||||
getTags();
|
if (route.query && route.query.value) {//底部导航跳转过来的则定位到响应位置
|
||||||
|
document?.querySelector('#' + `${route.query.value}`)?.scrollIntoView();
|
||||||
|
} else {//其他位置跳转过来不需要定位的则定位到顶部
|
||||||
|
document?.querySelector('#collect')?.scrollIntoView()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="md:mr-6 c-xs:mr-0">
|
||||||
<div class="mt-3 flex items-center">
|
<!-- collect -->
|
||||||
<el-select
|
<div id="collect">
|
||||||
v-model="searchParam.title"
|
<!-- cate title -->
|
||||||
filterable
|
<div class="md:mt-8 md:mb-8 c-xs:mt-1 c-xs:mb-4 text-xl font-bold text-[--base-black]">
|
||||||
remote
|
我的收藏
|
||||||
reserve-keyword
|
</div>
|
||||||
remote-show-suffix
|
<!-- card -->
|
||||||
:suffix-transition="false"
|
<div class="flex justify-between flex-wrap self-card-div c-xs:ml-3" :gutter="10" v-if="toolsStore.collect.length > 0">
|
||||||
:suffix-icon="Search"
|
<router-link v-for="(item, index) in toolsStore.collect" :key="index" :to="item.url" class="flex flex-col mt-5 border-solid rounded-2xl border-gray w-[24%] p-2 bg-white hover:shadow-md c-xs:w-[99.5%] c-md:w-[24%] c-sm:w-[32%] p-5">
|
||||||
placeholder="输入关键词搜索"
|
<div class="flex items-center border-b pb-2">
|
||||||
:remote-method="getList"
|
<el-image :src="item.logo" class="w-10 h-10 min-h-[2.5rem] min-w-[2.5rem] rounded-full"></el-image>
|
||||||
:loading="loading"
|
<div class="flex flex-col ml-2 w-full">
|
||||||
class="ml-3 mr-3 md:w-80 c-xs:w-60"
|
<div class="flex">
|
||||||
>
|
<div class="font-semibold text-lg line-clamp-1">{{ item.title }}</div>
|
||||||
<el-optiond
|
</div>
|
||||||
v-for="item in scanStore.list.data"
|
<div class="flex justify-between">
|
||||||
:key="item.id"
|
<el-text size="small">{{ item.cate }}</el-text>
|
||||||
:label="item.title"
|
<el-button :icon="Star" circle size="small" @click.prevent="collect(item.id)" type="warning"/>
|
||||||
:value="item.id"
|
</div>
|
||||||
@click="optionClick(item.path)"
|
</div>
|
||||||
>
|
</div>
|
||||||
</el-optiond>
|
<div class="flex items-center justify-between mt-2">
|
||||||
</el-select>
|
<el-text line-clamp="2">{{ item.desc }}</el-text>
|
||||||
<!-- <el-button>扫描目录</el-button> -->
|
</div>
|
||||||
<el-button>随机预览</el-button>
|
</router-link>
|
||||||
<div class='ml-3'>
|
<!-- 占位 div -->
|
||||||
<el-text>数量:111</el-text>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex w-full">
|
<!-- list -->
|
||||||
<!-- 左侧列表 -->
|
<div v-for="(cate, index) in toolsStore.cates" :key="index">
|
||||||
<div class="w-9/12">
|
<!-- cate title -->
|
||||||
<div class="flex items-center justify-between h-40 bg-white mt-3 ml-3 rounded-lg p-3" v-for="(item, index) in scanStore.list.data" :key="index">
|
<div class="mt-8 mb-3 text-xl font-bold text-[--base-black]" :id="'cate_' + cate.id">
|
||||||
<!-- left -->
|
{{ cate.title }}
|
||||||
<div class="flex items-center">
|
|
||||||
<!-- -->
|
|
||||||
<div>
|
|
||||||
<div v-if="scanStore.extVideo.includes(item.ext)">
|
|
||||||
<video :src="scanStore.sourceUrl + item.title" style="height:150px;width:270px;"></video>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<el-empty description="无预览" :image-size="100"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- -->
|
|
||||||
<div class="ml-3">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<el-button class="mr-2" type="warning" size="small">{{ item.ext }}</el-button>
|
|
||||||
<el-text>{{item.title}}</el-text>
|
|
||||||
</div>
|
|
||||||
<div class="mt-3">
|
|
||||||
<el-text class="mt-3">{{ item.path }}</el-text>
|
|
||||||
</div>
|
|
||||||
<div class="mt-3">
|
|
||||||
<el-tag type="danger" class="mt-2 mr-2" v-for="(childItem, childIndex) in item.scan_tag" :key="childIndex">{{ childItem.title }}</el-tag>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- right -->
|
|
||||||
<div class="flex flex-col justify-center">
|
|
||||||
<el-button size="small" type="primary" class="w-20" @click="open(index, item)">open</el-button>
|
|
||||||
<el-button size="small" type="primary" class="w-20 mt-2" style="margin-left: 0;" @click="setTagsDia(item.id)">set tags</el-button>
|
|
||||||
<!-- <el-button size="small" type="primary" class="w-20 mt-2" style="margin-left: 0;" >move</el-button> -->
|
|
||||||
<!-- <el-button size="small" type="primary" class="w-20 mt-2" style="margin-left: 0;">delete</el-button> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 分页 -->
|
|
||||||
<div class="mt-3 mb-3 ml-3 flex justify-end">
|
|
||||||
<el-pagination
|
|
||||||
background
|
|
||||||
layout="prev, pager, next, jumper"
|
|
||||||
:page-count="scanStore.list.last_page"
|
|
||||||
:total="scanStore.list.total"
|
|
||||||
:default-page-size="scanStore.list.per_page"
|
|
||||||
@current-change="pageChange"
|
|
||||||
@size-change="pageChange"
|
|
||||||
@prev-click="pageChange"
|
|
||||||
@next-click="pageChange"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 右侧栏目 -->
|
<!-- card -->
|
||||||
<div class="w-3/12 mt-3 ml-3 mr-3 rounded-lg">
|
<div class="flex justify-between flex-wrap self-card-div c-xs:ml-0" :gutter="10">
|
||||||
<!-- tags -->
|
<router-link v-for="(item, index) in cate.list" :key="index" :to="item.url" class="flex flex-col mt-5 border-solid rounded-2xl border-gray w-[24%] p-2 bg-white hover:shadow-md c-xs:w-[99.5%] c-md:w-[24%] c-sm:w-[32%] p-5 hover:-translate-y-2 duration-300">
|
||||||
<div class="p-3 bg-white" v-for="(item, index) in scanStore.tags" :key="index">
|
<div class="flex items-center border-b pb-2">
|
||||||
<div>{{ item.title }}</div>
|
<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-wrap">
|
<div class="flex flex-col ml-2 w-full">
|
||||||
<el-tag type="danger" class="mt-2 mr-2" v-for="(childItem, childIndex) in item.childTags" :key="childIndex" @click="">{{ childItem.title }}</el-tag>
|
<div class="flex">
|
||||||
</div>
|
<div class="font-semibold text-lg line-clamp-1">{{ item.title }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<el-text size="small">{{ item.cate }}</el-text>
|
||||||
|
<el-button :icon="Star" circle size="small" @click.prevent="collect(item.id)" type="warning" :plain="!toolsStore.collectIds.includes(item.id)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between mt-2">
|
||||||
|
<el-text line-clamp="2">{{ item.desc }}</el-text>
|
||||||
|
</div>
|
||||||
|
</router-link>
|
||||||
|
<!-- 占位 div -->
|
||||||
|
<div class="w-[24%] c-md:w-[24%] c-sm:w-[32%] "></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- set tags dialog -->
|
|
||||||
<el-dialog v-model="setTagsDialog" title="set tags" width="800" @close="getList('')">
|
|
||||||
<!-- tags -->
|
|
||||||
<div class="p-3 bg-white" v-for="(item, index) in scanStore.tags" :key="index">
|
|
||||||
<div>{{ item.title }}</div>
|
|
||||||
<div class="flex flex-wrap">
|
|
||||||
<el-check-tag type="danger" :checked="scanStore.fileInfoTags.includes(childItem.id)" @change="updateTag(scanStore.fileInfo.id, childItem.id)" class="mt-2 mr-2" v-for="(childItem, childIndex) in item.childTags" :key="childIndex">{{ childItem.title }}</el-check-tag>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- <el-button class="ml-3 mt-3" type="primary">保存</el-button> -->
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
//对外暴露配置路由(常量路由)
|
//对外暴露配置路由(常量路由)
|
||||||
export const constantRoute = [
|
export const constantRoute = [
|
||||||
|
//首页
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
component: () => import('@/components/Home/Home.vue'),
|
component: () => import('@/components/Home/Home.vue'),
|
||||||
@@ -9,6 +10,7 @@ export const constantRoute = [
|
|||||||
description: '然部落,在线工具,在线工具大全,开发人员工具,日常生活工具,办公助手,时间戳转换,加密,解密,md5,进制转换,二维码,正则表达式,json格式化,照片处理,字数统计',
|
description: '然部落,在线工具,在线工具大全,开发人员工具,日常生活工具,办公助手,时间戳转换,加密,解密,md5,进制转换,二维码,正则表达式,json格式化,照片处理,字数统计',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
//工具
|
||||||
{
|
{
|
||||||
path: '/timetran',
|
path: '/timetran',
|
||||||
component: () => import('@/components/TimeTran/TimeTran.vue'),
|
component: () => import('@/components/TimeTran/TimeTran.vue'),
|
||||||
@@ -269,6 +271,77 @@ export const constantRoute = [
|
|||||||
description: '在线制作柱状图,像做表格一样制作可视化图表,支持导出静态或动态图表',
|
description: '在线制作柱状图,像做表格一样制作可视化图表,支持导出静态或动态图表',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/line',
|
||||||
|
component: () => import('@/components/Chart/Line/Line.vue'),
|
||||||
|
name: 'line',
|
||||||
|
meta: {
|
||||||
|
title: "折线图",
|
||||||
|
keywords: '折线图',
|
||||||
|
description: '在线制作折线图,像做表格一样制作可视化图表,支持导出静态或动态图表',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/Pie',
|
||||||
|
component: () => import('@/components/Chart/Pie/Pie.vue'),
|
||||||
|
name: 'pie',
|
||||||
|
meta: {
|
||||||
|
title: "饼图",
|
||||||
|
keywords: '饼图',
|
||||||
|
description: '在线制作饼图,像做表格一样制作可视化图表,支持导出静态或动态图表',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/Scatter',
|
||||||
|
component: () => import('@/components/Chart/Scatter/Scatter.vue'),
|
||||||
|
name: 'scatter',
|
||||||
|
meta: {
|
||||||
|
title: "散点图",
|
||||||
|
keywords: '散点图',
|
||||||
|
description: '在线制作散点图,像做表格一样制作可视化图表,支持导出静态或动态图表',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/Coin',
|
||||||
|
component: () => import('@/components/Coin/Coin.vue'),
|
||||||
|
name: 'coin',
|
||||||
|
meta: {
|
||||||
|
title: "抛硬币",
|
||||||
|
keywords: '抛硬币,硬币',
|
||||||
|
description: '在线抛硬币,选择困难那么交给硬币来帮你选择吧',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/Dice',
|
||||||
|
component: () => import('@/components/Dice/Dice.vue'),
|
||||||
|
name: 'dice',
|
||||||
|
meta: {
|
||||||
|
title: "投骰子",
|
||||||
|
keywords: '投骰子,骰子,自定义骰子',
|
||||||
|
description: '在线投骰子,可自定义骰子数量,简单好用的骰子工具',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/TextRemoveDuplicate',
|
||||||
|
component: () => import('@/components/TextRemoveDuplicate/TextRemoveDuplicate.vue'),
|
||||||
|
name: 'textRemoveDuplicate',
|
||||||
|
meta: {
|
||||||
|
title: "文本去重",
|
||||||
|
keywords: '文本去重,文本排重,文本去除重复',
|
||||||
|
description: '可以删除或去除文本或字符串中的重复行',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/ImgCut',
|
||||||
|
component: () => import('@/components/ImgCut/ImgCut.vue'),
|
||||||
|
name: 'imgCut',
|
||||||
|
meta: {
|
||||||
|
title: "图片分割",
|
||||||
|
keywords: '图片分割,图片切割,四宫格,九宫格,十六宫格',
|
||||||
|
description: '将图片分割成四宫格、九宫格、十六宫格,支持自定义行与列',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 关于
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: '/about',
|
||||||
component: () => import('@/components/Home/About.vue'),
|
component: () => import('@/components/Home/About.vue'),
|
||||||
@@ -279,6 +352,7 @@ export const constantRoute = [
|
|||||||
description: '',
|
description: '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
//其他路由
|
||||||
{
|
{
|
||||||
path: '/404',
|
path: '/404',
|
||||||
component: () => import('@/components/404/404.vue'),
|
component: () => import('@/components/404/404.vue'),
|
||||||
|
|||||||
@@ -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))
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
//创建tools相关的小工具
|
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
import type { ScanReqData, ScanFilePage, ScanTagInfo, UpdateTagReqData, ScanInfoReqData, ScanFileInfo } from '@/api/scan/type'
|
|
||||||
import { getList, getTags, updateTag, getInfo } from '@/api/scan'
|
|
||||||
|
|
||||||
export const useScanStore = defineStore('scan', {
|
|
||||||
//用来存放变量
|
|
||||||
state: () => ({
|
|
||||||
list: {} as ScanFilePage,
|
|
||||||
fileInfo: {} as ScanFileInfo, //当前文件信息
|
|
||||||
fileInfoTags: [] as number[], //当期文件tags
|
|
||||||
tags: [] as ScanTagInfo[],
|
|
||||||
videoList: localStorage.getItem('videoList') !== null ? JSON.parse(localStorage.getItem('videoList') || '') : [], //播放列表,一般情况下同list
|
|
||||||
playbackRate: 1.5, //播放速度
|
|
||||||
isRandPlay: false, //是否随机播放
|
|
||||||
extVideo: ['ts', 'mp4', 'mkv', 'wmv', 'avi', 'flv', '3gp', 'dvd', 'mov', 'vob', 'webm'], //视频格式
|
|
||||||
sourceUrl: 'http://localhost:7881/', //资源服务地址
|
|
||||||
historyList: localStorage.getItem('historyList') !== null ? JSON.parse(localStorage.getItem('historyList') || '') : [], //历史播放
|
|
||||||
}),
|
|
||||||
//方法
|
|
||||||
actions: {
|
|
||||||
//获取scan file list
|
|
||||||
async getList(data: ScanReqData) {
|
|
||||||
//发送请求
|
|
||||||
const result: any = await getList(data)
|
|
||||||
if (result.code == 200) {
|
|
||||||
this.list = result.data
|
|
||||||
localStorage.setItem('videoList', JSON.stringify(result.data.data))
|
|
||||||
console.log(localStorage.getItem('videoList'))
|
|
||||||
return result.message
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error(result.message))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
//获取scan file list
|
|
||||||
async getInfo(data: ScanInfoReqData) {
|
|
||||||
//发送请求
|
|
||||||
const result: any = await getInfo(data)
|
|
||||||
if (result.code == 200) {
|
|
||||||
this.fileInfo = result.data
|
|
||||||
this.fileInfoTags = this.fileInfo.tags
|
|
||||||
return result.message
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error(result.message))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
//get tags
|
|
||||||
async getTags() {
|
|
||||||
//发送请求
|
|
||||||
const result: any = await getTags()
|
|
||||||
if (result.code == 200) {
|
|
||||||
this.tags = result.data
|
|
||||||
return result.message
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error(result.message))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
//收藏工具
|
|
||||||
async updateTag(data: UpdateTagReqData) {
|
|
||||||
const result: any = await updateTag(data)
|
|
||||||
if (result.code == 200) {
|
|
||||||
return result.message
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error(result.message))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
//设置播放列表
|
|
||||||
// setVideoList() {
|
|
||||||
|
|
||||||
// },
|
|
||||||
//设置播放速度
|
|
||||||
setPlaybackRate(number) {
|
|
||||||
this.playbackRate = number
|
|
||||||
},
|
|
||||||
//当期那播放信息、索引获取和设置
|
|
||||||
getNowPlay() {
|
|
||||||
return localStorage.getItem('nowPlay') !== null ? JSON.parse(localStorage.getItem('nowPlay') || '') : {};
|
|
||||||
},
|
|
||||||
getNowIndex() {
|
|
||||||
return localStorage.getItem('nowIndex') !== null ? JSON.parse(localStorage.getItem('nowIndex') || '') : 0;
|
|
||||||
},
|
|
||||||
setNowPlay(data) {
|
|
||||||
localStorage.setItem('nowPlay', JSON.stringify(data))
|
|
||||||
},
|
|
||||||
setNowIndex(index) {
|
|
||||||
localStorage.setItem('nowIndex', JSON.stringify(index))
|
|
||||||
},
|
|
||||||
//清除缓存
|
|
||||||
cleanCache() {
|
|
||||||
localStorage.removeItem('videoList');
|
|
||||||
localStorage.removeItem('historyList');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -22,11 +22,11 @@ export function toSpreadsheetData(data, type = 'bar') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换成echarts使用的数据
|
* 转换成echarts使用的数据 - 基础格式(适用于柱状图、折线图等)
|
||||||
*/
|
*/
|
||||||
export function toEchartsData(data) {
|
export function toEchartsData(data) {
|
||||||
let tmpColumn: Array<string> = [];
|
let tmpColumn: Array<any> = [];
|
||||||
let tmpValue: Array<string> = [];
|
let tmpValue: Array<any> = [];
|
||||||
for (let item in data.rows) {
|
for (let item in data.rows) {
|
||||||
if (item == 'len') {
|
if (item == 'len') {
|
||||||
continue
|
continue
|
||||||
@@ -37,9 +37,94 @@ export function toEchartsData(data) {
|
|||||||
return [tmpColumn, tmpValue];
|
return [tmpColumn, tmpValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成echarts使用的数据 - 适用于饼图
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function toEchartsPieData(data) {
|
||||||
|
let tmpArr: Array<Object> = []
|
||||||
|
for (let item in data.rows) {
|
||||||
|
if (item == 'len') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tmpArr.push({
|
||||||
|
name: data.rows[item]['cells'][0] ? data.rows[item]['cells'][0]['text'] : '',
|
||||||
|
value: data.rows[item]['cells'][1] ? data.rows[item]['cells'][1]['text'] : ''
|
||||||
|
})
|
||||||
|
// tmpColumn.push(data.rows[item]['cells'][0] ? data.rows[item]['cells'][0]['text'] : '')
|
||||||
|
// tmpValue.push(data.rows[item]['cells'][1] ? data.rows[item]['cells'][1]['text'] : '')
|
||||||
|
}
|
||||||
|
return tmpArr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据格式转换 - 根据type转换格式
|
||||||
|
* 对象格式{name: '', value: ''}; 列格式: nameArr = [], valueArr = []
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function tranObjAndColumn(data, type = 'toObj') {
|
||||||
|
let returnData = [] as any[];
|
||||||
|
if (type == 'toObj') {
|
||||||
|
if (data.len === 0 || data[0].len === 0) {
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
//转换成饼图使用的obj
|
||||||
|
let nameArr = data[0];
|
||||||
|
let valueArr = data[1];
|
||||||
|
let tmpArr: Array<Object> = []
|
||||||
|
|
||||||
|
for (let item in nameArr) {
|
||||||
|
if (item == 'len') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tmpArr.push({
|
||||||
|
name: nameArr[item] ? nameArr[item] : '',
|
||||||
|
value: valueArr[item] ? valueArr[item] : '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
returnData = tmpArr
|
||||||
|
} else if (type == 'toColumn') {
|
||||||
|
//饼图对象转换成列数组
|
||||||
|
let nameArr = [] as string[]
|
||||||
|
let valueArr = [] as number[]
|
||||||
|
for (let item in data) {
|
||||||
|
nameArr.push(data[item]?.name)
|
||||||
|
valueArr.push(data[item]?.value)
|
||||||
|
}
|
||||||
|
returnData = [nameArr, valueArr]
|
||||||
|
} else if (type == "toCoord") {
|
||||||
|
//转换成坐标(散点图用到)
|
||||||
|
if (data.len === 0 || data[0].len === 0) {
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
//转换成饼图使用的obj
|
||||||
|
let nameArr = data[0];
|
||||||
|
let valueArr = data[1];
|
||||||
|
let tmpArr: Array<any> = []
|
||||||
|
|
||||||
|
for (let item in nameArr) {
|
||||||
|
if (item == 'len') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tmpArr.push([
|
||||||
|
nameArr[item] ? nameArr[item] : 0,
|
||||||
|
valueArr[item] ? valueArr[item] : 0,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
returnData = tmpArr
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const EchartsUtils = {
|
const EchartsUtils = {
|
||||||
toSpreadsheetData,
|
toSpreadsheetData,
|
||||||
toEchartsData,
|
toEchartsData,
|
||||||
|
toEchartsPieData,
|
||||||
|
tranObjAndColumn,
|
||||||
}
|
}
|
||||||
export default EchartsUtils
|
export default EchartsUtils
|
||||||
|
|
||||||
|
|||||||