fix: 添加隐私权政策,工具组件目录调整

This commit is contained in:
osiris
2024-05-11 18:25:00 +08:00
parent 4d72590f6f
commit 68951056b1
46 changed files with 35 additions and 35 deletions
+319
View File
@@ -0,0 +1,319 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
// import { copy } from '@/utils/string'
const info = reactive({
title: "ASCII码表",
})
// ascii: https://ascii.co.uk/table
const tableDataControlCodes = reactive([
{dec:"0",oct:"000",hex:"0",code:"NUL",desc:"空字符"},
{dec:"1",oct:"001",hex:"1",code:"SOH",desc:"标题开始"},
{dec:"2",oct:"002",hex:"2",code:"STX",desc:"正文开始"},
{dec:"3",oct:"003",hex:"3",code:"ETX",desc:"正文结束"},
{dec:"4",oct:"004",hex:"4",code:"EOT",desc:"传输结束"},
{dec:"5",oct:"005",hex:"5",code:"ENQ",desc:"询问"},
{dec:"6",oct:"006",hex:"6",code:"ACK",desc:"收到通知"},
{dec:"7",oct:"007",hex:"7",code:"BEL",desc:"铃"},
{dec:"8",oct:"010",hex:"8",code:"BS",desc:"退格"},
{dec:"9",oct:"011",hex:"9",code:"HT",desc:"水平制表符"},
{dec:"10",oct:"012",hex:"A",code:"LF",desc:"换行键"},
{dec:"11",oct:"013",hex:"B",code:"VT",desc:"垂直制表符"},
{dec:"12",oct:"014",hex:"C",code:"FF",desc:"换页键"},
{dec:"13",oct:"015",hex:"D",code:"CR",desc:"回车键"},
{dec:"14",oct:"016",hex:"E",code:"SO",desc:"移出"},
{dec:"15",oct:"017",hex:"F",code:"SI",desc:"移入"},
{dec:"16",oct:"020",hex:"10",code:"DLE",desc:"数据链路转义"},
{dec:"17",oct:"021",hex:"11",code:"DC1",desc:"设备控制 1"},
{dec:"18",oct:"022",hex:"12",code:"DC2",desc:"设备控制 2"},
{dec:"19",oct:"023",hex:"13",code:"DC3",desc:"设备控制 3"},
{dec:"20",oct:"024",hex:"14",code:"DC4",desc:"设备控制 4"},
{dec:"21",oct:"025",hex:"15",code:"NAK",desc:"拒绝接收"},
{dec:"22",oct:"026",hex:"16",code:"SYN",desc:"同步空闲"},
{dec:"23",oct:"027",hex:"17",code:"ETB",desc:"传输块结束"},
{dec:"24",oct:"030",hex:"18",code:"CAN",desc:"取消"},
{dec:"25",oct:"031",hex:"19",code:"EM",desc:"介质中断"},
{dec:"26",oct:"032",hex:"1A",code:"SUB",desc:"替换"},
{dec:"27",oct:"033",hex:"1B",code:"ESC",desc:"换码符"},
{dec:"28",oct:"034",hex:"1C",code:"FS",desc:"文件分隔符"},
{dec:"29",oct:"035",hex:"1D",code:"GS",desc:"组分隔符"},
{dec:"30",oct:"036",hex:"1E",code:"RS",desc:"记录分离符"},
{dec:"31",oct:"037",hex:"1F",code:"US",desc:"单元分隔符"}
])
const tableDataStandard = reactive([
{dec:"32",oct:"040",hex:"20",code:"Space",desc:"空格"},
{dec:"33",oct:"041",hex:"21",code:"!",desc:""},
{dec:"34",oct:"042",hex:"22",code:"\"",desc:""},
{dec:"35",oct:"043",hex:"23",code:"#",desc:""},
{dec:"36",oct:"044",hex:"24",code:"$",desc:""},
{dec:"37",oct:"045",hex:"25",code:"%",desc:""},
{dec:"38",oct:"046",hex:"26",code:"&",desc:""},
{dec:"40",oct:"050",hex:"28",code:"(",desc:""},
{dec:"41",oct:"051",hex:"29",code:")",desc:""},
{dec:"42",oct:"052",hex:"2A",code:"*",desc:""},
{dec:"43",oct:"053",hex:"2B",code:"+",desc:""},
{dec:"44",oct:"054",hex:"2C",code:",",desc:""},
{dec:"45",oct:"055",hex:"2D",code:"-",desc:""},
{dec:"46",oct:"056",hex:"2E",code:".",desc:""},
{dec:"47",oct:"057",hex:"2F",code:"\/",desc:""},
{dec:"48",oct:"060",hex:"30",code:"0",desc:""},
{dec:"49",oct:"061",hex:"31",code:"1",desc:""},
{dec:"50",oct:"062",hex:"32",code:"2",desc:""},
{dec:"51",oct:"063",hex:"33",code:"3",desc:""},
{dec:"52",oct:"064",hex:"34",code:"4",desc:""},
{dec:"53",oct:"065",hex:"35",code:"5",desc:""},
{dec:"54",oct:"066",hex:"36",code:"6",desc:""},
{dec:"55",oct:"067",hex:"37",code:"7",desc:""},
{dec:"56",oct:"070",hex:"38",code:"8",desc:""},
{dec:"57",oct:"071",hex:"39",code:"9",desc:""},
{dec:"58",oct:"072",hex:"3A",code:":",desc:""},
{dec:"59",oct:"073",hex:"3B",code:";",desc:""},
{dec:"60",oct:"074",hex:"3C",code:"<",desc:""},
{dec:"61",oct:"075",hex:"3D",code:"=",desc:""},
{dec:"62",oct:"076",hex:"3E",code:">",desc:""},
{dec:"63",oct:"077",hex:"3F",code:"?",desc:""},
{dec:"64",oct:"100",hex:"40",code:"@",desc:""},
{dec:"65",oct:"101",hex:"41",code:"A",desc:""},
{dec:"66",oct:"102",hex:"42",code:"B",desc:""},
{dec:"67",oct:"103",hex:"43",code:"C",desc:""},
{dec:"68",oct:"104",hex:"44",code:"D",desc:""},
{dec:"69",oct:"105",hex:"45",code:"E",desc:""},
{dec:"70",oct:"106",hex:"46",code:"F",desc:""},
{dec:"71",oct:"107",hex:"47",code:"G",desc:""},
{dec:"72",oct:"110",hex:"48",code:"H",desc:""},
{dec:"73",oct:"111",hex:"49",code:"I",desc:""},
{dec:"74",oct:"112",hex:"4A",code:"J",desc:""},
{dec:"75",oct:"113",hex:"4B",code:"K",desc:""},
{dec:"76",oct:"114",hex:"4C",code:"L",desc:""},
{dec:"77",oct:"115",hex:"4D",code:"M",desc:""},
{dec:"78",oct:"116",hex:"4E",code:"N",desc:""},
{dec:"79",oct:"117",hex:"4F",code:"O",desc:""},
{dec:"80",oct:"120",hex:"50",code:"P",desc:""},
{dec:"81",oct:"121",hex:"51",code:"Q",desc:""},
{dec:"82",oct:"122",hex:"52",code:"R",desc:""},
{dec:"83",oct:"123",hex:"53",code:"S",desc:""},
{dec:"84",oct:"124",hex:"54",code:"T",desc:""},
{dec:"85",oct:"125",hex:"55",code:"U",desc:""},
{dec:"86",oct:"126",hex:"56",code:"V",desc:""},
{dec:"87",oct:"127",hex:"57",code:"W",desc:""},
{dec:"88",oct:"130",hex:"58",code:"X",desc:""},
{dec:"89",oct:"131",hex:"59",code:"Y",desc:""},
{dec:"90",oct:"132",hex:"5A",code:"Z",desc:""},
{dec:"91",oct:"133",hex:"5B",code:"[",desc:""},
{dec:"92",oct:"134",hex:"5C",code:"\\",desc:""},
{dec:"93",oct:"135",hex:"5D",code:"]",desc:""},
{dec:"94",oct:"136",hex:"5E",code:"^",desc:""},
{dec:"95",oct:"137",hex:"5F",code:"_",desc:""},
{dec:"96",oct:"140",hex:"60",code:"`",desc:""},
{dec:"97",oct:"141",hex:"61",code:"a",desc:""},
{dec:"98",oct:"142",hex:"62",code:"b",desc:""},
{dec:"99",oct:"143",hex:"63",code:"c",desc:""},
{dec:"100",oct:"144",hex:"64",code:"d",desc:""},
{dec:"101",oct:"145",hex:"65",code:"e",desc:""},
{dec:"102",oct:"146",hex:"66",code:"f",desc:""},
{dec:"103",oct:"147",hex:"67",code:"g",desc:""},
{dec:"104",oct:"150",hex:"68",code:"h",desc:""},
{dec:"105",oct:"151",hex:"69",code:"i",desc:""},
{dec:"106",oct:"152",hex:"6A",code:"j",desc:""},
{dec:"107",oct:"153",hex:"6B",code:"k",desc:""},
{dec:"108",oct:"154",hex:"6C",code:"l",desc:""},
{dec:"109",oct:"155",hex:"6D",code:"m",desc:""},
{dec:"110",oct:"156",hex:"6E",code:"n",desc:""},
{dec:"111",oct:"157",hex:"6F",code:"o",desc:""},
{dec:"112",oct:"160",hex:"70",code:"p",desc:""},
{dec:"113",oct:"161",hex:"71",code:"q",desc:""},
{dec:"114",oct:"162",hex:"72",code:"r",desc:""},
{dec:"115",oct:"163",hex:"73",code:"s",desc:""},
{dec:"116",oct:"164",hex:"74",code:"t",desc:""},
{dec:"117",oct:"165",hex:"75",code:"u",desc:""},
{dec:"118",oct:"166",hex:"76",code:"v",desc:""},
{dec:"119",oct:"167",hex:"77",code:"w",desc:""},
{dec:"120",oct:"170",hex:"78",code:"x",desc:""},
{dec:"121",oct:"171",hex:"79",code:"y",desc:""},
{dec:"122",oct:"172",hex:"7A",code:"z",desc:""},
{dec:"123",oct:"173",hex:"7B",code:"{",desc:""},
{dec:"124",oct:"174",hex:"7C",code:"|",desc:""},
{dec:"125",oct:"175",hex:"7D",code:"},",desc:""},
{dec:"126",oct:"176",hex:"7E",code:"~",desc:""},
{dec:"127",oct:"177",hex:"7F",code:"DEL",desc:"删除"},
])
const tableDataNonStandard = reactive([
{dec:"128",oct:"200",hex:"80",code:"\u20ac",desc:""},
{dec:"129",oct:"201",hex:"81",code:"\u0081",desc:""},
{dec:"130",oct:"202",hex:"82",code:"\u201a",desc:""},
{dec:"131",oct:"203",hex:"83",code:"\u0192",desc:""},
{dec:"132",oct:"204",hex:"84",code:"\u201e",desc:""},
{dec:"133",oct:"205",hex:"85",code:"\u2026",desc:""},
{dec:"134",oct:"206",hex:"86",code:"\u2020",desc:""},
{dec:"135",oct:"207",hex:"87",code:"\u2021",desc:""},
{dec:"136",oct:"210",hex:"88",code:"\u02c6",desc:""},
{dec:"137",oct:"211",hex:"89",code:"\u2030",desc:""},
{dec:"138",oct:"212",hex:"8A",code:"\u0160",desc:""},
{dec:"139",oct:"213",hex:"8B",code:"\u2039",desc:""},
{dec:"140",oct:"214",hex:"8C",code:"\u0152",desc:""},
{dec:"141",oct:"215",hex:"8D",code:"\u008d",desc:""},
{dec:"142",oct:"216",hex:"8E",code:"\u017d",desc:""},
{dec:"143",oct:"217",hex:"8F",code:"\u008f",desc:""},
{dec:"144",oct:"220",hex:"90",code:"\u0090",desc:""},
{dec:"145",oct:"221",hex:"91",code:"\u2018",desc:""},
{dec:"146",oct:"222",hex:"92",code:"\u2019",desc:""},
{dec:"147",oct:"223",hex:"93",code:"\u201c",desc:""},
{dec:"148",oct:"224",hex:"94",code:"\u201d",desc:""},
{dec:"149",oct:"225",hex:"95",code:"\u2022",desc:""},
{dec:"150",oct:"226",hex:"96",code:"\u2013",desc:""},
{dec:"151",oct:"227",hex:"97",code:"\u2014",desc:""},
{dec:"152",oct:"230",hex:"98",code:"\u02dc",desc:""},
{dec:"153",oct:"231",hex:"99",code:"\u2122",desc:""},
{dec:"154",oct:"232",hex:"9A",code:"\u0161",desc:""},
{dec:"155",oct:"233",hex:"9B",code:"\u203a",desc:""},
{dec:"156",oct:"234",hex:"9C",code:"\u0153",desc:""},
{dec:"157",oct:"235",hex:"9D",code:"\u009d",desc:""},
{dec:"158",oct:"236",hex:"9E",code:"\u017e",desc:""},
{dec:"159",oct:"237",hex:"9F",code:"\u0178",desc:""},
{dec:"160",oct:"240",hex:"A0",code:" ",desc:"\u63d2\u5165\u7a7a\u683c"},
{dec:"161",oct:"241",hex:"A1",code:"\u00a1",desc:""},
{dec:"162",oct:"242",hex:"A2",code:"\u00a2",desc:""},
{dec:"163",oct:"243",hex:"A3",code:"\u00a3",desc:""},
{dec:"164",oct:"244",hex:"A4",code:"\u00a4",desc:""},
{dec:"165",oct:"245",hex:"A5",code:"\u00a5",desc:""},
{dec:"166",oct:"246",hex:"A6",code:"\u00a6",desc:""},
{dec:"167",oct:"247",hex:"A7",code:"\u00a7",desc:""},
{dec:"168",oct:"250",hex:"A8",code:"\u00a8",desc:""},
{dec:"169",oct:"251",hex:"A9",code:"\u00a9",desc:""},
{dec:"170",oct:"252",hex:"AA",code:"\u00aa",desc:""},
{dec:"171",oct:"253",hex:"AB",code:"\u00ab",desc:""},
{dec:"172",oct:"254",hex:"AC",code:"\u00ac",desc:""},
{dec:"173",oct:"255",hex:"AD",code:"\u00ad",desc:""},
{dec:"174",oct:"256",hex:"AE",code:"\u00ae",desc:""},
{dec:"175",oct:"257",hex:"AF",code:"\u00af",desc:""},
{dec:"176",oct:"260",hex:"B0",code:"\u00b0",desc:""},
{dec:"177",oct:"261",hex:"B1",code:"\u00b1",desc:""},
{dec:"178",oct:"262",hex:"B2",code:"\u00b2",desc:""},
{dec:"179",oct:"263",hex:"B3",code:"\u00b3",desc:""},
{dec:"180",oct:"264",hex:"B4",code:"\u00b4",desc:""},
{dec:"181",oct:"265",hex:"B5",code:"\u00b5",desc:""},
{dec:"182",oct:"266",hex:"B6",code:"\u00b6",desc:""},
{dec:"183",oct:"267",hex:"B7",code:"\u00b7",desc:""},
{dec:"184",oct:"270",hex:"B8",code:"\u00b8",desc:""},
{dec:"185",oct:"271",hex:"B9",code:"\u00b9",desc:""},
{dec:"186",oct:"272",hex:"BA",code:"\u00ba",desc:""},
{dec:"187",oct:"273",hex:"BB",code:"\u00bb",desc:""},
{dec:"188",oct:"274",hex:"BC",code:"\u00bc",desc:""},
{dec:"189",oct:"275",hex:"BD",code:"\u00bd",desc:""},
{dec:"190",oct:"276",hex:"BE",code:"\u00be",desc:""},
{dec:"191",oct:"277",hex:"BF",code:"\u00bf",desc:""},
{dec:"192",oct:"300",hex:"C0",code:"\u00c0",desc:""},
{dec:"193",oct:"301",hex:"C1",code:"\u00c1",desc:""},
{dec:"194",oct:"302",hex:"C2",code:"\u00c2",desc:""},
{dec:"195",oct:"303",hex:"C3",code:"\u00c3",desc:""},
{dec:"196",oct:"304",hex:"C4",code:"\u00c4",desc:""},
{dec:"197",oct:"305",hex:"C5",code:"\u00c5",desc:""},
{dec:"198",oct:"306",hex:"C6",code:"\u00c6",desc:""},
{dec:"199",oct:"307",hex:"C7",code:"\u00c7",desc:""},
{dec:"200",oct:"310",hex:"C8",code:"\u00c8",desc:""},
{dec:"201",oct:"311",hex:"C9",code:"\u00c9",desc:""},
{dec:"202",oct:"312",hex:"CA",code:"\u00ca",desc:""},
{dec:"203",oct:"313",hex:"CB",code:"\u00cb",desc:""},
{dec:"204",oct:"314",hex:"CC",code:"\u00cc",desc:""},
{dec:"205",oct:"315",hex:"CD",code:"\u00cd",desc:""},
{dec:"206",oct:"316",hex:"CE",code:"\u00ce",desc:""},
{dec:"207",oct:"317",hex:"CF",code:"\u00cf",desc:""},
{dec:"208",oct:"320",hex:"D0",code:"\u00d0",desc:""},
{dec:"209",oct:"321",hex:"D1",code:"\u00d1",desc:""},
{dec:"210",oct:"322",hex:"D2",code:"\u00d2",desc:""},
{dec:"211",oct:"323",hex:"D3",code:"\u00d3",desc:""},
{dec:"212",oct:"324",hex:"D4",code:"\u00d4",desc:""},
{dec:"213",oct:"325",hex:"D5",code:"\u00d5",desc:""},
{dec:"214",oct:"326",hex:"D6",code:"\u00d6",desc:""},
{dec:"215",oct:"327",hex:"D7",code:"\u00d7",desc:""},
{dec:"216",oct:"330",hex:"D8",code:"\u00d8",desc:""},
{dec:"217",oct:"331",hex:"D9",code:"\u00d9",desc:""},
{dec:"218",oct:"332",hex:"DA",code:"\u00da",desc:""},
{dec:"219",oct:"333",hex:"DB",code:"\u00db",desc:""},
{dec:"220",oct:"334",hex:"DC",code:"\u00dc",desc:""},
{dec:"221",oct:"335",hex:"DD",code:"\u00dd",desc:""},
{dec:"222",oct:"336",hex:"DE",code:"\u00de",desc:""},
{dec:"223",oct:"337",hex:"DF",code:"\u00df",desc:""},
{dec:"224",oct:"340",hex:"E0",code:"\u00e0",desc:""},
{dec:"225",oct:"341",hex:"E1",code:"\u00e1",desc:""},
{dec:"226",oct:"342",hex:"E2",code:"\u00e2",desc:""},
{dec:"227",oct:"343",hex:"E3",code:"\u00e3",desc:""},
{dec:"228",oct:"344",hex:"E4",code:"\u00e4",desc:""},
{dec:"229",oct:"345",hex:"E5",code:"\u00e5",desc:""},
{dec:"230",oct:"346",hex:"E6",code:"\u00e6",desc:""},
{dec:"231",oct:"347",hex:"E7",code:"\u00e7",desc:""},
{dec:"232",oct:"350",hex:"E8",code:"\u00e8",desc:""},
{dec:"233",oct:"351",hex:"E9",code:"\u00e9",desc:""},
{dec:"234",oct:"352",hex:"EA",code:"\u00ea",desc:""},
{dec:"235",oct:"353",hex:"EB",code:"\u00eb",desc:""},
{dec:"236",oct:"354",hex:"EC",code:"\u00ec",desc:""},
{dec:"237",oct:"355",hex:"ED",code:"\u00ed",desc:""},
{dec:"238",oct:"356",hex:"EE",code:"\u00ee",desc:""},
{dec:"239",oct:"357",hex:"EF",code:"\u00ef",desc:""},
{dec:"240",oct:"360",hex:"F0",code:"\u00f0",desc:""},
{dec:"241",oct:"361",hex:"F1",code:"\u00f1",desc:""},
{dec:"242",oct:"362",hex:"F2",code:"\u00f2",desc:""},
{dec:"243",oct:"363",hex:"F3",code:"\u00f3",desc:""},
{dec:"244",oct:"364",hex:"F4",code:"\u00f4",desc:""},
{dec:"245",oct:"365",hex:"F5",code:"\u00f5",desc:""},
{dec:"246",oct:"366",hex:"F6",code:"\u00f6",desc:""},
{dec:"247",oct:"367",hex:"F7",code:"\u00f7",desc:""},
{dec:"248",oct:"370",hex:"F8",code:"\u00f8",desc:""},
{dec:"249",oct:"371",hex:"F9",code:"\u00f9",desc:""},
{dec:"250",oct:"372",hex:"FA",code:"\u00fa",desc:""},
{dec:"251",oct:"373",hex:"FB",code:"\u00fb",desc:""},
{dec:"252",oct:"374",hex:"FC",code:"\u00fc",desc:""},
{dec:"253",oct:"375",hex:"FD",code:"\u00fd",desc:""},
{dec:"254",oct:"376",hex:"FE",code:"\u00fe",desc:""},
{dec:"255",oct:"377",hex:"FF",code:"\u00ff",desc:""},
])
//copy
// const copyRes = async (resStr: string) => {
// copy(resStr)
// }
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<el-table :data="tableDataControlCodes" class="w-full">
<el-table-column label="ASCII 控制字符 (字符编码: 0-31)" align="center">
<el-table-column prop="dec" label="十进制DEC" width="120" />
<el-table-column prop="oct" label="八进制OCT" width="120" />
<el-table-column prop="hex" label="十六进制HEX" width="120" />
<el-table-column prop="code" label="符号" width="120" />
<el-table-column prop="desc" label="描述" />
</el-table-column>
</el-table>
<el-table :data="tableDataStandard" class="w-full mt-3">
<el-table-column label="ASCII 打印字符 (字符编码: 32-127)" align="center">
<el-table-column prop="dec" label="十进制DEC" width="120" />
<el-table-column prop="oct" label="八进制OCT" width="120" />
<el-table-column prop="hex" label="十六进制HEX" width="120" />
<el-table-column prop="code" label="符号" width="120" />
<el-table-column prop="desc" label="描述" />
</el-table-column>
</el-table>
<el-table :data="tableDataNonStandard" class="w-full mt-3">
<el-table-column label="ASCII扩展码 (字符编码: 128-255)" align="center">
<el-table-column prop="dec" label="十进制DEC" width="120" />
<el-table-column prop="oct" label="八进制OCT" width="120" />
<el-table-column prop="hex" label="十六进制HEX" width="120" />
<el-table-column prop="code" label="符号" width="120" />
<el-table-column prop="desc" label="描述" />
</el-table-column>
</el-table>
</div>
</div>
</template>
<style scoped>
</style>
+124
View File
@@ -0,0 +1,124 @@
<script setup lang="ts">
import { onBeforeMount, reactive,ref } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
// import { ElMessage } from 'element-plus'
import vueDanmaku from 'vue3-danmaku' //https://www.npmjs.com/package/vue3-danmaku
const info = reactive({
title: "手持弹幕",
content: '工具坊',
barrage: [] as string[],
speed: 200,
textSize: 500,
textColor: '#FFFFFF',
bgColor: '#000000',
channels: 1,//轨道数量
extraStyle: '',
danmakuFullStyle: "",
isPlay: false,
danmakuFullHeight: '100%',
danmakuFullWidth: '100%',
})
const danmakuFullRef = ref(null) as any
//全屏播放/双击退出全屏并暂停
const fullScreenPlay = () => {
if (info.isPlay) {
setExtraStyle('visibility: hidden;')
//暂停
danmakuFullRef.value.resize()
danmakuFullRef.value.stop()
// danmakuFullRef.value.pause()
info.isPlay = false
} else {
setExtraStyle()
//播放
formatBarrage()
danmakuFullRef.value.resize()
danmakuFullRef.value.play()
info.isPlay = true
}
}
//设置额外样式
const setExtraStyle = (danmakuStyleExt: string = '') => {
info.extraStyle = "color: " + info.textColor + ";font-size: " + info.textSize + "px"
info.danmakuFullStyle = "z-index: 99; position: fixed; top: 0px; left: 0px; height:" + info.danmakuFullHeight + "; width:" + info.danmakuFullWidth + "; background-color:" + info.bgColor + ";" + danmakuStyleExt
}
//格式化弹幕
const formatBarrage = () => {
info.barrage[0] = info.content
}
onBeforeMount(() => {
setExtraStyle('visibility: hidden;')
})
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<!-- 全屏 -->
<vue-danmaku
ref="danmakuFullRef"
v-model:danmus="info.barrage"
loop
:autoplay="false"
:speeds="info.speed"
:channels="info.channels"
:extraStyle='info.extraStyle'
:style="info.danmakuFullStyle"
@dblclick="fullScreenPlay"
:useSlot="true"
>
<!-- 弹幕slot -->
<template v-slot:dm="{ danmu }">
<div class="" :style="info.extraStyle">
<!-- <img class="img" :src="danmu.avatar" /> -->
<!-- <span>{{ index }}{{ danmu.name }}</span> -->
<span>{{ danmu }}</span>
</div>
</template>
</vue-danmaku>
<div class="p-4 rounded-2xl bg-white">
<div class="flex mb-2">
<el-text class="w-20">弹幕内容:</el-text>
<div class="w-72"><el-input v-model="info.content" type="textarea" rows="3"></el-input></div>
</div>
<div class="flex mb-2">
<el-text class="w-20">播放速度:</el-text>
<div class="w-72 ml-2"><el-slider v-model="info.speed" :min="1" :max="500"/></div>
</div>
<div class="flex mb-2">
<el-text class="w-20">文字大小:</el-text>
<div class="w-72 ml-2"><el-slider v-model="info.textSize" :min="12" :max="1000"/></div>
</div>
<div class="flex mb-2">
<el-text class="w-20">文字颜色:</el-text>
<div><el-color-picker v-model="info.textColor" size="large" @change="setExtraStyle()"/></div>
</div>
<div class="flex mb-2">
<el-text class="w-20">背景颜色:</el-text>
<div><el-color-picker v-model="info.bgColor" size="large" @change="setExtraStyle()"/></div>
</div>
<div>
<el-button @click="fullScreenPlay" type="primary" class="mr-3">{{ info.isPlay == false ? '播放' : '暂停'}}</el-button>
<el-text>双击可退出弹幕</el-text>
</div>
</div>
</div>
</template>
<style scoped>
.vue-danmaku .danmus .dm{
top: 50% !important;
}
</style>
+479
View File
@@ -0,0 +1,479 @@
<script setup lang="ts">
import {ref, reactive, onMounted } from 'vue'
import Spreadsheet from 'x-data-spreadsheet'
import 'x-data-spreadsheet/dist/locale/zh-cn';
import { UploadProps,UploadRawFile,genFileId } from 'element-plus'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
// import { copy } from '@/utils/string'
import { toEchartsData, toSpreadsheetData } from '@/utils/echarts'
import * as echarts from 'echarts'
import * as XLSX from 'xlsx'
const info = reactive({
title: "柱状图",
})
const chartDom = ref<HTMLElement|null>()
const myChart = ref<echarts.ECharts>()
const dataFileRef = ref()
const setOptionName = ref(1)
//缩放比例
const sacleSize = ref(100)
//画布宽高
const widthCanvas = ref(720)
const heightCanvas = ref(400)
//下载文件类型
const downType = ref('1')
//图形属性颜色
const attrColor = ref('#5470c6')
//标题位置
const titlePos = ref('center')
//标题
const title = ref('然部落在线工具')
//副标题
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: 'bar',
data: valueData.value,
itemStyle: {
color: attrColor.value
}
}
],
tooltip: {}
};
//重新加载画布
const reloadCanvas = () => {
myChart.value = echarts.init(chartDom.value)
myChart.value.clear();
myChart.value.resize({
width: widthCanvas.value,
height: heightCanvas.value
})
myChart.value.setOption(option)
}
//下载echarts图表图片
const downEchartsImg = () => {
let ext = downType.value == '1' ? 'png' : 'jpeg'
let imgUrl = myChart.value?.getDataURL({
type: ext as "png" | 'jpeg',
pixelRatio: 2,
})
if (imgUrl != undefined) {
var downloadLink = document.createElement('a');
downloadLink.href = imgUrl;
downloadLink.download = 'echart.' + ext;
downloadLink.click();
}
}
/** 上传、编辑数据 */
//抽屉开关
const drawer = ref(false)
const rowsData = ref({})
const editData = () => {
if (drawer.value == true) {
drawer.value = false
} else {
drawer.value = true
/** 在线excel */
Spreadsheet.locale('zh-cn', (window.x_spreadsheet as any).$messages['zh-cn']);
//const sheet = new Spreadsheet("#x-spreadsheet", {
new Spreadsheet("#x-spreadsheet", {
showToolbar: false, //隐藏顶部工具栏
showBottomBar: false,//隐藏底部工具栏,
view: {
height: () => document.documentElement.clientHeight / 2,
width: () => document.documentElement.clientWidth,
}
})
.loadData({
//样式
styles: [
{
bgcolor: '#f4f5f8',
textwrap: true,
color: '#900b09',
border: {
top: ['thin', '#0366d6'],
bottom: ['thin', '#0366d6'],
right: ['thin', '#0366d6'],
left: ['thin', '#0366d6'],
},
},
],
//数据
rows: rowsData.value
}) // load data
.change(data => {
//表格数据改变后触发
//规则: 获取第一列和第二列的数据
let tmp = toEchartsData(data)
colunmData.value = tmp[0]
valueData.value = tmp[1]
canvasHandle('data')
});
// sheet.validate()
}
}
//上传数据文件
const fileList = ref()
const updateDataFile = async (params) => {
const _file = params.file;
const fileReader = new FileReader();
fileReader.onload = (ev) => {
try {
if (!ev.target) {
return
}
const data = ev.target.result;
const workbook = XLSX.read(data, {
type: 'binary'
});
let useCount = 0;
let tmpColumnData:any[] = [];
let tmpValueData:any[] = [];
for (let sheet in workbook.Sheets) {
//这里只需要第一个sheet
if (useCount > 0) {
continue;
}
//循环读取每个文件
const sheetArray = XLSX.utils.sheet_to_json(workbook.Sheets[sheet], {header: ['0', '1']});
//若当前sheet没有数据,则continue
if(sheetArray.length == 0){
continue;
}
for(let item in sheetArray){
if (sheetArray[item] != undefined) {
let tmp1 = sheetArray as []
tmpColumnData.push(tmp1[item][0])
tmpValueData.push(tmp1[item][1])
}
}
useCount++
}
//更新数据
colunmData.value = tmpColumnData
valueData.value = tmpValueData
//更新图表
canvasHandle('data')
//更新表格
rowsData.value = toSpreadsheetData([
colunmData, valueData
])
} catch (e) {
console.log('error')
console.log(e)
}
}
fileReader.readAsArrayBuffer(_file)
}
//当超出限制时,执行的钩子函数
//这里覆盖前一个文件
const handleExceed: UploadProps['onExceed'] = (files) => {
dataFileRef.value!.clearFiles()
const file = files[0] as UploadRawFile
file.uid = genFileId()
dataFileRef.value!.handleStart(file)
dataFileRef.value!.submit()
}
//copy
// const copyRes = async (resStr: string) => {
// copy(resStr)
// }
onMounted(() => {
//init echart dom
chartDom.value = document.getElementById('main')
//设置画布宽高
canvasHandle('size')
//reload canvas
reloadCanvas()
//数据格式转换成
rowsData.value = toSpreadsheetData([
colunmData, valueData
])
// console.log(rowsData.value)
})
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl flex">
<div class="w-4/6">
<div class="flex flex-row-reverse mb-4">
<el-input-number v-model="sacleSize" :min="1" :max="100" :step="5" @change="canvasHandle('scale')" step-strictly/>
<el-text>缩放</el-text>
</div>
<!-- <div class="flex justify-center items-center"> -->
<div class="flex justify-center items-center max-h-[500px] max-w-[1000px] overflow-auto">
<div id="main" class="bg-white"></div>
</div>
</div>
<div class="w-2/6 bg-white ml-3 rounded-lg p-3">
<div class="mb-3">
<el-button class="w-full mb-3" type="primary" size="large" @click="downEchartsImg">下载图表</el-button>
<div class="flex items-center ml-1">
<el-text>文件类型</el-text>
<el-radio-group v-model="downType" class="ml-4">
<el-radio size="large" label="1">PNG</el-radio>
<el-radio size="large" label="2">JPEG</el-radio>
</el-radio-group>
</div>
<div class="flex w-full">
<el-tooltip
class="box-item"
effect="dark"
content="支持上传:xls, xlsx, csv文件"
placement="top-start"
>
<el-button class="w-1/2">
<el-upload
v-model:file-list="fileList"
class="dataFileRef flex"
ref="dataFileRef"
accept=".xls,.xlsx,.csv"
:http-request="updateDataFile"
:on-exceed="handleExceed"
:limit="1"
>
上传数据
</el-upload>
</el-button>
</el-tooltip>
<el-button class="w-1/2" @click="editData">编辑数据</el-button>
</div>
</div>
<el-collapse v-model="setOptionName" @change="handleChange" accordion>
<el-collapse-item title="画布设置" name="1">
<div class="flex">
<el-text class="w-16 text-right">宽度</el-text>
<el-input-number
v-model="widthCanvas"
:min="1"
:max="4000"
:step="10"
controls-position="right"
class="h-8 w-60 max-w-[60%] ml-3"
@change="canvasHandle('size')"
/>
</div>
<div class="flex mt-3">
<el-text class="w-16 text-right">高度</el-text>
<el-input-number
v-model="heightCanvas"
:min="1"
:max="4000"
:step="10"
controls-position="right"
class="h-8 w-60 max-w-[60%] ml-3"
@change="canvasHandle('size')"
/>
</div>
</el-collapse-item>
<el-collapse-item title="标题设置" name="2">
<div class="flex">
<el-text class="w-16 text-right">位置</el-text>
<el-radio-group v-model="titlePos" class="ml-3" @change="canvasHandle('title')">
<el-radio-button label="left" value="left"></el-radio-button>
<el-radio-button label="center" value="center"></el-radio-button>
<el-radio-button label="right" value="right"></el-radio-button>
</el-radio-group>
</div>
<el-divider content-position="left" class="pl-10">标题</el-divider>
<div class="flex">
<el-text class="w-16 text-right">显示</el-text>
<el-switch v-model="titleSwitch" class="ml-3" @change="canvasHandle('title')"/>
</div>
<div class="flex">
<el-text class="w-16 text-right">标题</el-text>
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="title" @blur="canvasHandle('title')"></el-input>
</div>
<el-divider content-position="left">副标题</el-divider>
<div class="flex">
<el-text class="w-16 text-right">显示</el-text>
<el-switch v-model="subTitleSwitch" class="ml-3" @change="canvasHandle('title')"/>
</div>
<div class="flex">
<el-text class="w-16 text-right">副标题</el-text>
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="subTitle" @blur="canvasHandle('title')"></el-input>
</div>
</el-collapse-item>
<el-collapse-item title="图形属性" name="3">
<div class="flex">
<el-text class="w-16 text-right">颜色</el-text>
<el-color-picker v-model="attrColor" class="ml-3" @change="canvasHandle('color')"/>
</div>
</el-collapse-item>
<el-collapse-item title="水印设置" name="4">
<div class="flex">
<el-text class="w-16 text-right">显示</el-text>
<el-switch v-model="watermarkSwitch" class="ml-3" @change="canvasHandle('watermark')"/>
</div>
<div class="flex">
<el-text class="w-16 text-right">水印内容</el-text>
<el-input class="h-8 w-60 max-w-[60%] ml-3" v-model="waterMarkText" @change="canvasHandle('watermark')"></el-input>
</div>
</el-collapse-item>
</el-collapse>
</div>
</div>
<el-drawer id="x-spreadsheet" v-model="drawer" direction="btt" custom-class="sheet" style="">
</el-drawer>
</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>
+479
View File
@@ -0,0 +1,479 @@
<script setup lang="ts">
import {ref, reactive, onMounted } from 'vue'
import Spreadsheet from 'x-data-spreadsheet'
import 'x-data-spreadsheet/dist/locale/zh-cn';
import { UploadProps,UploadRawFile,genFileId } from 'element-plus'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
// import { copy } from '@/utils/string'
import { toEchartsData, toSpreadsheetData } from '@/utils/echarts'
import * as echarts from 'echarts'
import * as XLSX from 'xlsx'
const info = reactive({
title: "折线图",
})
const chartDom = ref<HTMLElement|null>()
const myChart = ref<echarts.ECharts>()
const dataFileRef = ref()
const setOptionName = ref(1)
//缩放比例
const sacleSize = ref(100)
//画布宽高
const widthCanvas = ref(720)
const heightCanvas = ref(400)
//下载文件类型
const downType = ref('1')
//图形属性颜色
const attrColor = ref('#5470c6')
//标题位置
const titlePos = ref('center')
//标题
const title = ref('然部落在线工具')
//副标题
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>
+487
View File
@@ -0,0 +1,487 @@
<script setup lang="ts">
import {ref, reactive, onMounted } from 'vue'
import Spreadsheet from 'x-data-spreadsheet'
import 'x-data-spreadsheet/dist/locale/zh-cn';
import { UploadProps,UploadRawFile,genFileId } from 'element-plus'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
// import { copy } from '@/utils/string'
import { toEchartsPieData, toSpreadsheetData, tranObjAndColumn } from '@/utils/echarts'
import * as echarts from 'echarts'
import * as XLSX from 'xlsx'
const info = reactive({
title: "饼图",
})
const chartDom = ref<HTMLElement|null>()
const myChart = ref<echarts.ECharts>()
const dataFileRef = ref()
const setOptionName = ref(1)
//缩放比例
const sacleSize = ref(100)
//画布宽高
const widthCanvas = ref(720)
const heightCanvas = ref(400)
//下载文件类型
const downType = ref('1')
//图形属性颜色
const attrColor = ref('#5470c6')
//标题位置
const titlePos = ref('center')
//标题
const title = ref('然部落在线工具')
//副标题
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>
+133
View File
@@ -0,0 +1,133 @@
<script setup lang="ts">
import { ref,reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
// import { copy } from '@/utils/string'
const info = reactive({
title: "抛硬币",
})
const genStatus = ref(false)
const isHeads = ref(true)
const throwCoin = () => {
genStatus.value = true
isHeads.value = Math.random() < 0.5;
setTimeout(() => {
genStatus.value = false;
}, 2500); // 休眠5秒,5000毫秒
}
//copy
// const copyRes = async (resStr: string) => {
// copy(resStr)
// }
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div style="flex-grow: 3; display: flex; justify-content: center;">
<!-- coin -->
<div class="rotate-wrap" :class="[genStatus == true ? (isHeads == true ? 'rotate-wrap-ani-z' : 'rotate-wrap-ani-f') : '']">
<div class="front circle" :class="[isHeads == true ? 'is-head' : '']"></div>
<div class="reverse circle" :class="[isHeads == false ? 'is-head' : '']"></div>
</div>
</div>
<div class="flex justify-center">
<el-button type="primary" class="w-48" size="large" @click="throwCoin" v-if="!genStatus">抛硬币</el-button>
<el-button type="primary" class="w-48" size="large" disabled v-else>抛硬币...</el-button>
</div>
</div>
</div>
</template>
<style scoped>
.rotate-wrap {
width: 100px;
height: 100px;
margin: 100px;
transform-style: preserve-3d;
position: relative;
}
.rotate-wrap-ani-z{
/* animation: rotate 5s; */
animation: rotateZ 2.5s linear infinite;
}
.rotate-wrap-ani-f{
/* animation: rotate 5s; */
animation: rotateF 2.5s linear infinite;
}
.rotate-wrap:hover {
animation-play-state: paused;
}
.rotate-wrap .front, .rotate-wrap .reverse{
width: 100px;
height: 100px;
background-size: cover;
}
.rotate-wrap .front{
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-image: url('/public/images/coin/dollar.png');
}
.rotate-wrap .reverse{
background-image: url('/public/images/coin/xingxing.png');
}
.circle{
border-radius: 50%;
}
.line{
height: 200px;
width: 1px;
position:absolute;
top: 0;
left: 50%;
background: red;
transform: translateZ(0px);
}
.is-head{
transform: translateZ(1px);
}
@keyframes rotateZ {
0%{
transform: rotateX(0deg);
}
20%{
transform: rotateX(720deg) scale(1.2);
}
50%{
transform: rotateX(1080deg) scale(1.5);
}
80%{
transform: rotateX(1440deg) scale(1.2);
}
100% {
transform: rotateX(2160deg);
}
}
@keyframes rotateF {
0%{
transform: rotateX(0deg);
}
20%{
transform: rotateX(720deg) scale(1.2);
}
50%{
transform: rotateX(1080deg) scale(1.5);
}
80%{
transform: rotateX(1440deg) scale(1.2);
}
100% {
transform: rotateX(1980deg);
}
}
</style>
@@ -0,0 +1,94 @@
<script setup lang="ts">
import { reactive, onMounted,ref } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
// import { copy } from '@/utils/string'
const info = reactive({
title: "帮我决定",
scope: '', //多个用`,`隔开
randomVal: '',
checked: '0',
food: '沙县,烧腊,小炒肉,兰州拉面,汤饭,黄焖鸡,卤肉饭,蛋炒饭,羊肉粉,螺蛳粉,猪脚饭,白切鸡,肯德基,火锅,烧烤,泡面,西北风,煎饼果子',
subject: '电影,听音乐,游戏,逛街,看电视,睡觉,吃饭,清洁,锻炼,学习,发呆,散步,购物,回家,唱歌',
})
const chooseStatus = ref(false)
const changeCheckBox = (val: any) => {
switch(val) {
case '0':
info.scope = info.food
break;
case '1':
info.scope = info.subject
break;
default:
info.scope = ''
break;
}
}
const init = () => {
changeCheckBox(info.checked)
}
//选择
const choose = () => {
chooseStatus.value = true
let count = 0
let scopeList = info.scope.split(',')
const chooseInterval = setInterval(() => {
const randomNum = Math.floor(Math.random() * scopeList.length);
info.randomVal = scopeList[randomNum]
count++
if (count > 10) {
clearTimeout(chooseInterval)
chooseStatus.value = false
}
}, 50)
}
//clear
const clear = () => {
info.scope = ''
info.checked = ''
info.randomVal = ''
}
onMounted(() => {
init()
})
</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-radio-group v-model="info.checked" @change="changeCheckBox">
<el-radio label="0" size="large">吃什么</el-radio>
<el-radio label="1" size="large">干什么</el-radio>
</el-radio-group>
</div>
<div>
<el-input type="textarea" v-model="info.scope" :rows="5"></el-input>
</div>
<div class="text-5xl h-24 leading-[6rem]" v-if="info.randomVal != ''">
{{ info.randomVal }}
<!-- <el-text class=""></el-text> -->
</div>
<div class="mt-3">
<el-button @click="choose" v-if="!chooseStatus" type="primary">帮我决定</el-button>
<el-button @click="choose" type="primary" disabled v-else>决定中...</el-button>
<el-button @click="clear">清空</el-button>
</div>
</div>
</div>
</template>
<style scoped>
</style>
+79
View File
@@ -0,0 +1,79 @@
<script setup lang="ts">
import { ref,reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import DiceCore from "@/components/Tools/Dice/DiceCore.vue"
// import { copy } from '@/utils/string'
const info = reactive({
title: "投骰子",
})
//骰子数量
const diceNum = ref(1)
const genStatus = ref(false)
const diceRef = ref()
const resetDiceKey = ref(0)
const shake = async () => {
genStatus.value = true
let res = await new Promise((resolve) => {
for (let i = 0; i < diceNum.value; i++) {
diceRef.value[i].throwDice()
}
resolve(true)
})
//等待执行完成
setTimeout(() => {
console.log(res)
genStatus.value = false
}, 1200)
}
//重置骰子点数
const resetDice = () => {
resetDiceKey.value++
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<view class="grow-[1.5]"></view>
<div class="flex justify-center grow-[3]">
<div class="flex flex-wrap h-96 items-center">
<!-- 骰子组件 -->
<DiceCore ref="diceRef" v-for="i in diceNum" :key="resetDiceKey" :class="[diceNum == 1 ? 'dice_full' : '', diceNum == 3 && i == 1 ? 'dice_full' : 'dice_default', diceNum == 5 && i == 3 ? 'dice_full' : 'dice_default']"/>
</div>
</div>
<div class="">
<div class="ml-5 flex justify-center">
<el-text style="">骰子数量</el-text>
<div class="w-4/6">
<el-slider class="w-72 ml-3" v-model="diceNum" :min="1" :max="6" :step="1" blockSize="18" :showValue="true"></el-slider>
</div>
</div>
</div>
<div class="mb-5 flex justify-center">
<el-button type="primary" class="w-48" size="large" @click="shake" v-if="!genStatus">投骰子</el-button>
<el-button type="primary" class="w-48" size="large" disabled v-else>投骰子...</el-button>
<el-button type="primary" class="w-48" size="large" @click="resetDice">重置</el-button>
</div>
</div>
</div>
</template>
<style scoped>
.dice_default{
flex: 0 0 50%;
display: flex;
justify-content: center;
}
.dice_full{
flex: 0 0 100%;
display: flex;
justify-content: center;
}
</style>
+115
View File
@@ -0,0 +1,115 @@
<script setup lang="ts">
import { ref, onBeforeUnmount } from 'vue'
//定义emit
const emit = defineEmits(['throwEnd'])
//是否正在掷筛子
const isDicing = ref(false)
//当前显示的动画图片索引
const aniIndex = ref(0)
//掷筛子动画所用到的图片(4张骰子旋转过程中的图)
const diceAnimationImages = ref([
'/public/images/dice/dice_run1.png',
'/public/images/dice/dice_run2.png',
'/public/images/dice/dice_run3.png',
'/public/images/dice/dice_run4.png'
])
//骰子每一个点的对应的图片
const diceImages = ref({
//1点的图片
1: '/public/images/dice/dice1.png',
//2点的图片
2: '/public/images/dice/dice2.png',
//3点的图片
3: '/public/images/dice/dice3.png',
//4点的图片
4: '/public/images/dice/dice4.png',
//5点的图片
5: '/public/images/dice/dice5.png',
//6点的图片
6: '/public/images/dice/dice6.png'
})
//当前掷筛子掷到的点数(默认1点)
const currentPoint = ref(1)
//定时器
const timer = ref(0)
const getDicePoint = async () => {
let point = 1;
//从1~6随机一个数
point = Math.floor(Math.random() * 6 + 1);
return point;
}
//掷骰子
const throwDice = async () => {
//如果当前骰子正在滚动则不能掷骰子
if (isDicing.value) {
return
}
//从接口获取点数
currentPoint.value = await getDicePoint();
//开启骰子动画
await startAnimation();
//动画完毕之后可以通知父组件当前掷到的点数
emit('throwEnd', currentPoint.value);
}
//开启动画效果
const startAnimation = async () => {
return new Promise((resolve) => {
//设置筛子开始运动
isDicing.value = true;
//记录动画次数
let num = 0;
//每隔100毫秒来回切换一张“动”图形成掷骰子的动画
timer.value = setInterval(() => {
let index = aniIndex.value;
index++;
if (index >= diceAnimationImages.value.length) {
index = 0;
}
aniIndex.value = index;
num++;
//差不多执行1.2秒钟的时候可以停止了
if (num > 12) {
//关闭定时器
clearInterval(timer.value);
//设置骰子停止
isDicing.value = false;
//返回结果
resolve(true);
}
}, 100);
})
}
onBeforeUnmount(() => {
//组件销毁之前清除定时器
clearInterval(timer.value);
})
defineExpose({
throwDice
})
</script>
<template>
<!-- 骰子组件 -->
<div class="dice-wrap" @tap="throwDice">
<!-- 筛子运动时候的展示的图片 -->
<el-image v-if="isDicing" :src="diceAnimationImages[aniIndex]" class="dice-icon" loading="lazy"></el-image>
<!-- 筛子静止时候的显示的对应点数的图片 -->
<el-image v-else :src="diceImages[currentPoint]" class="dice-icon" loading="lazy"></el-image>
</div>
</template>
<style scoped>
.dice-wrap {
width: 100px;
height: 100px;
}
.dice-icon {
width: 100px;
height: 100px;
}
</style>
+39
View File
@@ -0,0 +1,39 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import { CodeDiff } from "v-code-diff";
// import { copy } from '@/utils/string'
const info = reactive({
title: "文本对比",
leftTxt: '旧文本',
rightTxt: '新文本'
})
//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="">
<div class="flex justify-between">
<el-input type="textarea" :rows="16" v-model="info.leftTxt"></el-input>
<el-input type="textarea" :rows="16" v-model="info.rightTxt" class="ml-3"></el-input>
</div>
<code-diff
:old-string="info.leftTxt"
:new-string="info.rightTxt"
output-format="side-by-side"
theme="dark"
/>
</div>
</div>
</template>
<style scoped>
</style>
+27
View File
@@ -0,0 +1,27 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
// import { copy } from '@/utils/string'
const info = reactive({
title: "tool name",
})
//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>
</div>
</template>
<style scoped>
</style>
+143
View File
@@ -0,0 +1,143 @@
<script setup lang="ts">
import { onMounted, reactive, ref,computed, watch } from 'vue'
import { UploadProps,UploadRawFile,genFileId } from 'element-plus'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
const info = reactive({
title: "图片切割",
})
const fileList = ref()
const lineNum = ref(3)
const image = ref({} as any)
const cutImg = ref([] as string[])
const dataFileRef = ref()
//上传
const updateDataFile = async (params) => {
let reader = new FileReader();
reader.readAsDataURL(params.file);
reader.addEventListener(
'load',
async () => {
const imageTmp = new Image();
imageTmp.onload = () => {
image.value = imageTmp;
cut();
};
imageTmp.src = reader.result as string;
},
false
);
}
//当超出限制时,执行的钩子函数
//这里覆盖前一个文件
const handleExceed: UploadProps['onExceed'] = (files) => {
dataFileRef.value!.clearFiles()
const file = files[0] as UploadRawFile
file.uid = genFileId()
dataFileRef.value!.handleStart(file)
dataFileRef.value!.submit()
}
//切割
const cut = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = image.value;
const wpiece = Math.floor(img.naturalWidth / lineNum.value);
const hpiece = Math.floor(img.naturalHeight / lineNum.value);
const results = [] as string[];
canvas.width = wpiece;
canvas.height = hpiece;
for (let r = 0; r < lineNum.value; r++) {
for (let c = 0; c < lineNum.value; c++) {
ctx?.drawImage(
img,
c * wpiece,
r * hpiece,
wpiece,
hpiece,
0,
0,
wpiece,
hpiece
);
results.push(canvas.toDataURL());
}
}
cutImg.value = results;
}
//计算切割样式
const cutImgStyle = computed(() => {
const width = `width: ${image.value.width || 800}px;`;
const grid = `grid: repeat(${lineNum.value}, 1fr) / repeat(${
lineNum.value
}, 1fr);`;
return width + grid;
})
watch(cutImgStyle, () => {
if (fileList.value) cut();
})
onMounted(() => {
})
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<el-upload
v-model:file-list="fileList"
class="dataFileRef flex"
ref="dataFileRef"
accept="image/*"
:http-request="updateDataFile"
:on-exceed="handleExceed"
:limit="1"
>
<el-button type="primary">请上传需要切割的图片</el-button>
</el-upload>
<div class="flex mt-3">
<div class="flex">
<el-text>行数和列数</el-text>
<el-input-number v-model="lineNum" :min="1" :max="10" class="ml-3"/>
</div>
</div>
<div class="mt-3 min-h-md bg-gray-100 p-3 mb-3 flex flex-col" v-if="image.src">
<!-- 预览 -->
<div class="mb-3">
<el-text>预览: </el-text>
<div :style="cutImgStyle" class="grid gap-1 max-w-full">
<img v-for="(src,index) in cutImg" :key="index" :src="src" alt="结果" />
</div>
</div>
<!-- 原图 -->
<div>
<el-text>原图: </el-text>
<div>
<img :src="image.src" alt="原图" v-if="image.src"/>
</div>
</div>
</div>
<div v-else>
<el-empty :image-size="200" description="无预览"/>
</div>
</div>
</div>
</template>
<style scoped>
</style>
+77
View File
@@ -0,0 +1,77 @@
<script setup lang="ts">
import { onMounted, reactive } from 'vue'
import { useToolsStore } from '@/store/modules/tools'
import { isIp } from '@/utils/verify'
import { ElMessage } from 'element-plus'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
const info = reactive({
title: "IP查询",
})
//store
const toolsStore = useToolsStore()
const params = reactive({
ip: '',
})
//查询
const search = async (type: string) => {
try {
//通过点击获取ip信息
if (type == 'click') {
//验证是否ip地址
if (!isIp(params.ip)) {
ElMessage({
message: "无效的ip地址",
type: "warning",
duration: 1500
})
return
}
}
//获取信息
await toolsStore.getIp(params)
} catch (error) {
console.log(error)
}
}
onMounted(() => {
search('')
})
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div class="flex">
<div class="mr-2 w-60">
<el-input v-model="params.ip" ></el-input>
</div>
<el-button type="primary" @click="search('click')">查询</el-button>
</div>
<div class="mt-3 min-h-md bg-gray-100 p-3 mb-3 flex flex-col">
<div class="bg-white p-2 font-bold text-xl">您的IP信息</div>
<div class="bg-white p-2 flex">
<div class="mr-1">运营商: </div>
<div>{{ toolsStore.ipData.isp }}</div>
</div>
<div class="bg-white p-2 flex">
<div class="mr-1">域名解析地址: </div>
<div>{{ toolsStore.ipData.ip }}</div>
</div>
<div class="bg-white p-2 flex">
<div class="mr-1">所在地理位置: </div>
<div>{{ toolsStore.ipData.pos }}</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>
+130
View File
@@ -0,0 +1,130 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import { transferred, copy } from '@/utils/string';
import { Codemirror } from "vue-codemirror";
import { json } from '@codemirror/lang-json';
import '@codemirror/search';
import { lineNumbers } from '@codemirror/view';
import '@codemirror/state';
import '@codemirror/commands';
// import { syntaxTree } from '@codemirror/language';
// import { linter, Diagnostic } from '@codemirror/lint';
// const regexpLinter = linter(view => {
// let diagnostics: Diagnostic[] = []
// syntaxTree(view.state).cursor().iterate(node => {
// if (node.name == "number") diagnostics.push({
// from: node.from,
// to: node.to,
// severity: "warning",
// message: "Regular expressions are FORBIDDEN",
// actions: [{
// name: "Remove",
// apply(view, from, to) { view.dispatch({changes: {from, to}}) }
// }]
// })
// })
// return diagnostics
// })
const info = reactive({
title: "Json在线转换",
code: '',
extensions: [json(), lineNumbers()],
isParseErr: false,
parseErr: ''
})
//格式化json
const formatJson = () => {
try {
//初始化错误信息
info.isParseErr = false;
info.parseErr = ''
// 1、JSON.parse:把JSON字符串转换为JSON对象
// 2、JSON.stringify:把JSON对象 转换为 有缩进的 JSON字符串格式
info.code = JSON.stringify(JSON.parse(info.code), null, '\t');
} catch(error) {
info.isParseErr = true;
// info.parseErr = (error as Error).message
info.parseErr = '无效的JSON'
}
}
//压缩
const compress = () => {
info.code = info.code.replace(/[\r\n\t]/g, "")
}
//转义
const tran = () => {
info.code = transferred(info.code, "\"")
}
//去转义
const unTransferred = () => {
info.code = info.code.replace(/[\\]/g, ``)
}
//清空输入框
const clear = () => {
info.code = ''
}
const copyRes = async () => {
copy(info.code)
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white ">
<div>
<!--
tabSize: tab键前进的个数
style 自定义样式
autofocus 挂载后立即聚焦在编辑器
indent-with-tab绑定键盘tab事件
extensions 扩展传数组
@ready="console.log('ready', $event)"
@change="console.log('change', $event)"
@focus="console.log('focus', $event)"
@blur="console.log('blur', $event)"
-->
<codemirror
v-model="info.code"
placeholder="这里是代码..."
:style="{ height: '400px' }"
:autofocus="true"
:indent-with-tab="true"
:tabSize="2"
:extensions="info.extensions"
/>
</div>
<div class="mt-4">
<el-button type="primary" @click="formatJson">校验/格式化</el-button>
<el-button type="primary" @click="compress">压缩</el-button>
<el-button type="primary" @click="tran">转义</el-button>
<el-button type="primary" @click="unTransferred">去转义</el-button>
<!-- <el-button type="primary">unicode转中文</el-button> -->
<!-- <el-button type="primary">中文转unicode</el-button> -->
<el-button type="primary" @click="copyRes">复制</el-button>
<el-button type="primary" @click="clear">清空</el-button>
</div>
<div class="mt-3 min-h-md bg-red-100 p-3 mb-3" v-show="info.isParseErr">
<el-text type="danger">{{ info.parseErr }}</el-text>
</div>
</div>
</div>
</template>
<style scoped>
</style>
+91
View File
@@ -0,0 +1,91 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import { Md5 } from 'ts-md5'
import { copy } from '@/utils/string'
const info = reactive({
title: "MD5在线加密",
encryptStr: '',
encryptUpper32: '',
encryptLower32: '',
encryptUpper16: '',
encryptLower16: '',
})
//加密
const encrypt = () => {
info.encryptLower32 = Md5.hashStr(info.encryptStr)
info.encryptUpper32 = info.encryptLower32.toUpperCase()
info.encryptUpper16 = info.encryptUpper32.substring(8, 24)
info.encryptLower16 = info.encryptLower32.substring(8, 24)
}
//清空输入框
const clear = () => {
info.encryptStr = ''
}
//copy
const copyRes = async (resStr: string) => {
copy(resStr)
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white ">
<div>
<el-input v-model="info.encryptStr" :rows="5" type="textarea" placeholder="请输入加密内容"></el-input>
</div>
<div class="mt-4">
<el-button type="primary" @click="encrypt()" >加密</el-button>
<el-button type="primary" @click="clear()">清空输入框</el-button>
</div>
<div class="w-[30rem]">
<el-input :value="info.encryptUpper32" class="mt-4" readonly>
<template #prepend>
<el-text>32位大写</el-text>
</template>
<template #append>
<el-button @click="copyRes(info.encryptUpper32)">复制</el-button>
</template>
</el-input>
<el-input :value="info.encryptLower32" class="mt-2" readonly>
<template #prepend>
<el-text>32位小写</el-text>
</template>
<template #append>
<el-button @click="copyRes(info.encryptLower32)">复制</el-button>
</template>
</el-input>
<el-input :value="info.encryptUpper16" class="mt-2" readonly>
<template #prepend>
<el-text>16位大写</el-text>
</template>
<template #append>
<el-button @click="copyRes(info.encryptUpper16)">复制</el-button>
</template>
</el-input>
<el-input :value="info.encryptLower16" class="mt-2" readonly>
<template #prepend>
<el-text>16位小写</el-text>
</template>
<template #append>
<el-button @click="copyRes(info.encryptLower16)">复制</el-button>
</template>
</el-input>
</div>
</div>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,29 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
const info = reactive({
title: "在线markdown编辑器",
content: '',
})
//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="">
<v-md-editor v-model="info.content" height="500px"></v-md-editor>
</div>
</div>
</template>
<style scoped>
</style>
+57
View File
@@ -0,0 +1,57 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import { copy } from '@/utils/string'
import { toMorse, toText } from '@/utils/morse'
const info = reactive({
title: "摩斯电码",
content: '支持中文的摩斯电码编码(Encode)解码(Decode)',
tranRes: '',
})
const toEncode = () => {
info.tranRes = toMorse(info.content)
}
const toDecode = () => {
info. tranRes = toText(info.content)
}
const clear = () => {
info.content = ''
info.tranRes = ''
}
//copy
const copyRes = async (resStr: string) => {
copy(resStr)
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white ">
<div>
<el-input type="textarea" :rows="8" v-model="info.content"></el-input>
</div>
<div class="mt-4">
<el-button type="primary" @click="toEncode">摩斯编码</el-button>
<el-button type="primary" @click="toDecode">摩斯解码</el-button>
<el-button type="primary" @click="copyRes(info.tranRes)">复制结果</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="info.tranRes"></el-input>
</div>
</div>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,50 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import { copy, numberToChinese } from '@/utils/string'
const info = reactive({
title: "数字转金额大写",
content: '',
tranRes: '',
})
const tran = () => {
//数字转中文
info.tranRes = numberToChinese(parseInt(info.content))
}
const clear = () => {
info.content = ''
info.tranRes = ''
}
const copyRes = async (resStr: string) => {
copy(resStr)
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div>
<el-input type="number" v-model="info.content" placeholder="请输入数字,最高13位数字"></el-input>
</div>
<div class="mt-4">
<el-button type="primary" @click="tran">数字转中文大写</el-button>
<el-button type="primary" @click="copyRes(info.tranRes)">复制结果</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="info.tranRes"></el-input>
</div>
</div>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,81 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import { copy } from '@/utils/string'
const info = reactive({
title: "色板",
palettes: [
{
theme: 'last summer day', //
item: ['#A7C5C5', '#DEE0D5', '#E2AC48', '#B96028', '#983C2D'],
},
{
theme: 'Hazy Summer Afternoon', //
item: ['#26C4A5', '#54D99F', '#ACE08C', '#E8E284', '#F2B652'],
},
{
theme: 'Summering', //
item: ['#FFBE4C', '#B33127', '#93EDFF', '#648FAD', '#FFFFFF'],
},
{
theme: 'Bare August — Footcare Essentials', //
item: ['#F2636F', '#F2858E', '#F2AEB4', '#F27999', '#F2D9D0'],
},
{
theme: 'Big Vilnius Summer Loto', //
item: ['#D90416', '#BF2A45', '#528C49', '#BF7B54', '#F28D8D'],
},
{
theme: 'Illustration Design', //
item: ['#D9D2D6', '#010326', '#4C5D73', '#808C65', '#F2E750'],
},
{
theme: 'Beautiful tropical beach with blue sky and white clouds abstract texture background', //
item: ['#04B2D9', '#CEECF2', '#04C4D9', '#15BFBF', '#D9D0C7'],
},
]
})
//copy
const copyRes = async (resStr: string) => {
copy(resStr)
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="">
<div class="flex flex-col">
<div v-for="(item, index) in info.palettes" :key="index" class="flex flex-col mb-3">
<div class="flex h-28 rounded-lg overflow-hidden items-center">
<span
v-for="(color, index) in item.item"
:key="index"
class="w-1/5 h-full color-palette"
:style="'background-color:' + color + ';'"
@click="copyRes(color)"
>
<div class="color-text hidden flex items-center justify-center h-full w-full" :style="'color:' + color + ';'" >{{ color }}</div>
</span>
<!-- @change="(val: any) => (changeCheckBox(val, 3)) -->
</div>
<!-- <div class="">
<el-text>主题:{{ item.theme }}</el-text>
</div> -->
<el-divider v-if="index != item.item.length + 1"/>
</div>
</div>
<div></div>
</div>
</div>
</template>
<style scoped>
.color-palette:hover .color-text{
display: flex;
filter: grayscale(1) contrast(999) invert(1)
}
</style>
+181
View File
@@ -0,0 +1,181 @@
<script setup lang="ts">
import { reactive,ref } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import QRCodeVue3 from 'qrcode-vue3'
import { Delete, Plus } from '@element-plus/icons-vue'
import { ElMessage, type UploadFile } from 'element-plus'
const info = reactive({
title: "二维码生成",
content: '可在此输入文字或网址,右键图片另存为可保存图片',
width: 200,
height: 200,
size: '200',
sizeSelect: '',
margin: 1,
fileList: <string[]>[],
fileUrl: '',
preColor: '#000',
bgColor: '#fff',
qrKey: 1,
errorCorrectionLevel: 'Q',
})
const uploadLogo = ref()
//上传达到上限触发
const handleExceed = () => {
ElMessage({
message: '上传数量已达上限,请清除后重新上传',
type: 'warning',
})
}
//设置尺寸
const setQRSize = () => {
info.width = parseInt(info.size)
info.height = parseInt(info.size)
}
const handleChange = (file: UploadFile) => {
info.fileList.push(file.url as string)
info.fileUrl = info.fileList[0] ?? ''
}
const handleRemove = (file: UploadFile) => {
info.fileList = uploadLogo.value.handleRemove(file)
}
//生成二维码
const gen = () => {
//设置尺寸
setQRSize()
info.qrKey += 1
}
</script>
<template>
<div class="flex flex-col mt-3 ml-4 flex-1 mr-3">
<DetailHeader :title="info.title"></DetailHeader>
<div class="flex justify-between w-full p-4 rounded-2xl bg-white">
<div class="w-4/6 ">
<div class="flex mb-3">
<div class="w-16 mr-2"><el-text>内容</el-text></div>
<el-input v-model="info.content" type="textarea" rows="3" class="w-full" placeholder="可在此输入文字或网址"></el-input>
</div>
<div class="flex mb-3">
<div class="w-16 mr-2"><el-text>尺寸</el-text></div>
<el-input v-model="info.size" class="">
<template #append>
<el-select v-model="info.size" class="w-32">
<el-option label="常规 200px" value="200" />
<el-option label="常规 300px" value="300" />
<el-option label="适中 350px" value="350" />
<el-option label="较大 500px" value="500" />
<!-- 分割线 start -->
<div class="flex justify-center bg-gray-200 w-full">
<div class="h-[1px] bg-gray-200 w-4/5"></div>
</div>
<!-- 分割线 end -->
<el-option label="超大 1000px" value="1000" />
<el-option label="超大 1200px" value="1200" />
</el-select>
</template>
</el-input>
</div>
<div class="flex mb-3">
<el-text class="w-16">纠错级别</el-text>
<el-select v-model="info.errorCorrectionLevel" class="w-36">
<el-option label="L 可遮挡 7%" value="L" />
<el-option label="M 可遮挡 15%" value="M" />
<el-option label="Q 可遮挡 25%" value="Q" />
<el-option label="H 可遮挡 30%" value="H" />
</el-select>
</div>
<div class="flex mb-3">
<div class="w-16"><el-text>边距</el-text></div>
<el-input-number v-model="info.margin" :step="1" :min="0"/>
</div>
<div class="flex mb-3">
<div class="w-16"><el-text>颜色</el-text></div>
<!-- <div class="w-16 flex flex-col items-center bg-gray-100 p-1 rounded-md mr-2">
<el-text>前景色</el-text>
<el-color-picker v-model="info.preColor" />
</div> -->
<div class="w-16 flex flex-col items-center bg-gray-100 p-1 rounded-md">
<el-text>背景色</el-text>
<el-color-picker v-model="info.bgColor" />
</div>
</div>
<div class="flex mb-3">
<div class="w-16"><el-text>logo</el-text></div>
<el-upload
ref="uploadLogo"
action="#"
:auto-upload="false"
:limit="1"
list-type="picture-card"
accept=".png,.ico,.jpg,.jpeg"
:on-change="handleChange"
:on-exceed="handleExceed">
<el-icon><Plus /></el-icon>
<template #file="{ file }">
<div class="border-2 w-full border-blue-400">
<img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
<span class="el-upload-list__item-actions">
<span
class="el-upload-list__item-delete"
@click="handleRemove(file)"
>
<el-icon><Delete /></el-icon>
</span>
</span>
</div>
</template>
</el-upload>
</div>
<div class="flex mb-3">
<div class="w-16"></div>
<el-button type="primary" @click="gen">生成二维码</el-button>
</div>
</div>
<div class="w-[200px] h-[200px]">
<QRCodeVue3
:key="info.qrKey"
:width="info.width"
:height="info.height"
:value="info.content"
:margin="info.margin"
:qrOptions="{ typeNumber: 0, mode: 'Byte', errorCorrectionLevel: info.errorCorrectionLevel }"
:imageOptions="{ hideBackgroundDots: true, imageSize: 0.4, margin: 0 }"
:image="info.fileList[0]"
:dotsOptions="{
type: 'none',
color: '#26249a',
gradient: {
type: 'linear',
rotation: 0,
colorStops: [
{ offset: 0, color: '#26249a' },
{ offset: 1, color: '#26249a' },
],
},
}"
:backgroundOptions="{ color: info.bgColor }"
:cornersSquareOptions="{ type: 'square', color: '#000000' }"
:cornersDotOptions="{ type: undefined, color: '#000000' }"
fileExt="png"
myclass="my-qur"
imgclass="img-qr"
/>
</div>
</div>
</div>
</template>
<style scoped>
.my-button{
background-color:red;
}
</style>
+56
View File
@@ -0,0 +1,56 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
// import { copy } from '@/utils/string'
const info = reactive({
title: "生成随机数",
startNum: 1,
endNum: 100,
resNum: 0,
genStatus: false,
})
const random = () => {
info.genStatus = true
let count = 0
const genInterval = setInterval(() => {
info.resNum = Math.floor(Math.random() * (info.endNum - info.startNum + 1)) + info.startNum;
count++
if (count > 10) {
clearTimeout(genInterval)
info.genStatus = false
}
}, 50)
}
//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="flex flex-col items-center p-4 rounded-2xl bg-white">
<div>
<el-input-number v-model="info.startNum" :min="0" size="large"/>
<el-input-number v-model="info.endNum" :min="0" size="large" class="ml-5"/>
</div>
<div class="h-60 text-8xl leading-[15rem]">
{{ info.resNum }}
</div>
<div>
<el-button @click="random" type="primary" size="large" v-if="!info.genStatus">生成</el-button>
<el-button @click="random" type="primary" size="large" disabled v-else>生成...</el-button>
</div>
</div>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,156 @@
<script setup lang="ts">
import { onMounted, reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import { copy, genRandomStrByChars } from '@/utils/string'
import { ElMessage } from 'element-plus'
const info = reactive({
title: "随机密码生成",
char: '',
pwLen: 10,
pwNum: 5,
resStr: '',
autosize: {
minRows: 5
},
checkedUpper: true,
checkedLower: true,
checkedNum: true,
checkedSign: false,
charNum: '0123456789',
charUpper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
charLower: 'abcdefghijklmnopqrstuvwxyz',
cahrSign: '~!@#$%^&*()_+',
})
//生成密码
const gen = () => {
//包含字符验证
if (info.char == '') {
ElMessage({
message: "包含字符不能为空",
type: "warning",
duration: 1500
})
return
}
//生成长度验证
if (info.pwLen > 100) {
ElMessage({
message: "生成长度范围在1~100",
type: "warning",
duration: 1500
})
return
}
//生成数量验证
if (info.pwNum > 100) {
ElMessage({
message: "生成数量范围在1~100",
type: "warning",
duration: 1500
})
return
}
//清空旧数据
info.resStr = ''
//按数量生成密码
for (let i = 1; i <= info.pwNum; i++) {
let ext = '\n'
if (i == info.pwNum) {
ext = ''
}
info.resStr += genRandomStrByChars(info.char, info.pwLen) + ext
}
}
const changeCheckBox = (val: any, type: number) => {
switch(type) {
case 0:
//设置数字字符
setChar(val, /\d+/g, info.charNum)
break;
case 1:
//设置小写字母字符
setChar(val, /[a-z]/g, info.charLower)
break;
case 2:
//设置大写字母字符
setChar(val, /[A-Z]/g, info.charUpper)
break;
case 3:
//设置特殊符号字符
setChar(val, /[~!@#$%^&*()_+]/g, info.cahrSign)
break;
default:
info.char = ''
break;
}
}
//设置字符
const setChar = (val: boolean, reg: RegExp, charType: string) => {
if (val == true) {
//添加
info.char += charType
} else {
//清除
info.char = info.char.replace(reg, "")
}
}
//copy
const copyRes = async (resStr: string) => {
copy(resStr)
}
onMounted(() => {
//设置初始字符
changeCheckBox(info.checkedNum, 0)
changeCheckBox(info.checkedLower, 1)
changeCheckBox(info.checkedUpper, 2)
changeCheckBox(info.checkedSign, 3)
})
</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-text>密码组合</el-text>
<div>
<el-checkbox v-model="info.checkedNum" label="数字(0-9)" @change="(val: any) => (changeCheckBox(val, 0))"/>
<el-checkbox v-model="info.checkedLower" label="小写字母(a-z)" @change="(val: any) => (changeCheckBox(val, 1))"/>
<el-checkbox v-model="info.checkedUpper" label="大写字母(A-Z)" @change="(val: any) => (changeCheckBox(val, 2))"/>
<el-checkbox v-model="info.checkedSign" label="其他符号(~!@#$%^&*()-+_=,.)" @change="(val: any) => (changeCheckBox(val, 3))"/>
</div>
<div>
<el-input class="" v-model="info.char">
<template #prepend>包含字符:</template>
</el-input>
<el-input v-model="info.pwLen" placeholder="范围1~100" class="mt-3" max="100" type="number">
<template #prepend>生成长度:</template>
</el-input>
<el-input v-model="info.pwNum" placeholder="范围1~100" class="mt-3" max="100" type="number">
<template #prepend>生成数量:</template>
</el-input>
</div>
<div class="mt-3 mb-3">
<el-button type="primary" @click="gen">生成密码</el-button>
<el-button type="primary" @click="copyRes(info.resStr)">复制全部</el-button>
</div>
<!-- res -->
<div>
<el-input type="textarea" :autosize="info.autosize" v-model="info.resStr"></el-input>
</div>
</div>
</div>
</template>
<style scoped>
</style>
+165
View File
@@ -0,0 +1,165 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import { Codemirror } from "vue-codemirror"
import { lineNumbers } from '@codemirror/view';
const info = reactive({
title: "正则表达式测试",
extensions: [lineNumbers()],
reg: '',
content: `一些测试实例:
邮箱1[email protected]
邮箱2[email protected]
手机号:13012341234
url: http://tools.ranblogs.com、https://ranblogs.com
IP: 192.168.0.1
时间:2023-11-24
`,
matchRes: '',
matchNum: 0,
isAll: true,
isCase: false,
isParseErr: false,
parseErr: '',
regList: [
{
id: 1,
title: '匹配手机号',
reg: '(13\\d|14[579]|15[^4\\D]|17[^49\\D]|18\\d)\\d{8}'
},
{
id: 1,
title: '匹配网址URL',
reg: '(http|ftp|https):\\\/\\\/[\\w\\-_]+(\\.[\\w\\-_]+)+([\\w\\-\\.,@?^=%&:/~\\+#]*[\\w\\-\\@?^=%&/~\\+#])?'
},
{
id: 1,
title: '匹配Email',
reg: `\\w[-\\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\\.)+[A-Za-z]{2,14}`
},
{
id: 1,
title: '匹配中文字符',
reg: "[\u4e00-\u9fa5]"
},
{
id: 1,
title: '匹配双字节字符(包含汉字)',
reg: "[^\x00-\xff]+"
},
{
id: 1,
title: '匹配18位身份证号',
reg: "\\d{17}[0-9Xx]|\\d{15}"
},
{
id: 1,
title: '匹配整数',
reg: "[1-9]\\d*"
},
{
id: 1,
title: '匹配IP(IPV4)',
reg: "\\d{0,3}\\.\\d{0,3}\\.\\d{0,3}\\.\\d{0,3}"
},
{
id: 1,
title: '匹配日期(年-月-日)',
reg: "(([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29)"
},
]
})
//使用正则
const useReg = (reg: string) => {
info.reg = reg
}
const clearOldRes = () => {
info.matchNum = 0
info.matchRes = ''
}
//测试匹配(执行匹配)
const execMatch = () => {
//清除旧结果
clearOldRes()
//check
if (info.reg == '') {
return
}
let rule = ''
//是否全局
rule = info.isAll === true ? 'g' : ''
//是否区分大小写
rule += info.isCase === true ? 'i' : ''
let pattern = new RegExp(info.reg, rule);
let matchArr = info.content.match(pattern)
if (info.isAll === true) {
if (matchArr?.length) {
info.matchNum = matchArr?.length
for (let index in matchArr) {
info.matchRes += matchArr[index] + `\n`
}
}
} else {
if (matchArr?.[0]) {
info.matchRes = matchArr[0]
info.matchNum = 1
}
}
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white ">
<div>
<codemirror
v-model="info.content"
placeholder=""
:style="{ height: '300px' }"
:autofocus="true"
:indent-with-tab="true"
:tabSize="2"
:extensions="info.extensions"
/>
</div>
<div class="mt-4">
<el-text class="font-bold">常用正则</el-text>
<ul class="flex flex-wrap">
<li v-for="(item, index) in info.regList" :key="index">
<el-button link type="primary" @click="useReg(item.reg)" class="mr-2 mt-2">{{ item.title }}</el-button>
</li>
</ul>
<div class="flex items-center mt-4">
<el-input v-model="info.reg" class="mr-2" placeholder="正则表达式"></el-input>
<div class="mr-2">
<el-checkbox v-model="info.isAll" label="全局搜索" size="large"/>
</div>
<div class="mr-2">
<el-checkbox v-model="info.isCase" label="忽略大小写" size="large" />
</div>
<el-button type="primary" @click="execMatch">测试匹配</el-button>
</div>
</div>
<div class="mt-3 min-h-md bg-gray-100 p-3 mb-3">
<el-text>共找到 {{ info.matchNum }} 处匹配结果</el-text>
<el-input type="textarea" :rows="4" readonly v-model="info.matchRes"></el-input>
</div>
</div>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,255 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import { ElMessage } from 'element-plus'
import { copy } from '@/utils/string'
import { isBinary, isOctal, isDecimal, isHexadecimal } from '@/utils/verify'
import { InfoFilled } from '@element-plus/icons-vue'
import { scale58ToBase, scale62ToBase, scale64ToBase, baseToScale58, baseToScale62, baseToScale64 } from '@/utils/math'
const info = reactive({
title: "常用进制转换",
content: '',
chooseTranOptions: '10',
tranOptions: [
{
value: '2',
label: '2进制',
tranValue: '',
desc: '',
},
{
value: '8',
label: '8进制',
tranValue: '',
desc: '',
},
{
value: '10',
label: '10进制',
tranValue: '',
desc: '',
},
{
value: '16',
label: '16进制',
tranValue: '',
desc: '',
},
{
value: '32',
label: '32进制',
tranValue: '',
desc: '数字 + 大写字母,不包含 ILOU 字符',
},
{
value: '58',
label: '58进制',
tranValue: '',
desc: '数字 + 大小写字母,不包含 0OlI 字符',
},
{
value: '62',
label: '62进制',
tranValue: '',
desc: '数字 + 大小写字母',
},
{
value: '64',
label: '64进制',
tranValue: '',
desc: '数字 + 大小写字母以及两个特殊字符 +/',
},
]
})
//高进制转换10进制 - int
const tailScaleToBase = (content: string, scale: number): number => {
let res = 0
switch(scale) {
case 58:
res = scale58ToBase(content)
break;
case 62:
res = scale62ToBase(content)
break;
case 64:
res = scale64ToBase(content)
break;
default:
ElMessage({
message: "参数错误",
type: "warning",
duration: 1500
})
}
return res
}
//10进制转换高进制
const baseToTailScale = (num: number, scale: number): string => {
let res = ''
switch(scale) {
case 58:
res = baseToScale58(num)
break;
case 62:
res = baseToScale62(num)
break;
case 64:
res = baseToScale64(num)
break;
default:
ElMessage({
message: "参数错误",
type: "warning",
duration: 1500
})
}
return res
}
//转换前 - check
const tranCheck = () => {
if (!info.content) {
ElMessage({
message: "请输入转换数值",
type: "warning",
duration: 1500
})
return false
}
if (info.chooseTranOptions == '2' && isBinary(info.content) == false) {
ElMessage({
message: "不是二进制值",
type: "warning",
duration: 1500
})
return false
}
if (info.chooseTranOptions == '8' && isOctal(info.content) == false) {
ElMessage({
message: "不是八进制值",
type: "warning",
duration: 1500
})
return false
}
if (info.chooseTranOptions == '10' && isDecimal(info.content) == false) {
ElMessage({
message: "不是十进制值",
type: "warning",
duration: 1500
})
return false
}
if (info.chooseTranOptions == '16' && isHexadecimal(info.content) == false) {
ElMessage({
message: "不是十六进制值",
type: "warning",
duration: 1500
})
return false
}
return true
}
//转换
const tran = () => {
//转换前 - check
if (!tranCheck()) {
return
}
//当前输入的内容转换成10进制
let tranContent
let chooseTranInt = parseInt(info.chooseTranOptions, 10)
if (chooseTranInt <= 36) {
tranContent = parseInt(info.content, chooseTranInt)
} else {
tranContent = tailScaleToBase(info.content, chooseTranInt)
}
//用上面转换的10进制内容转换对应进制
for (let index in info.tranOptions) {
let valueInt = parseInt(info.tranOptions[index].value, 10)
if (valueInt <= 36) {
info.tranOptions[index].tranValue = tranContent.toString(valueInt)
} else {
info.tranOptions[index].tranValue = baseToTailScale(tranContent, valueInt)
}
}
}
//copy
const copyRes = async (index: any) => {
copy(info.tranOptions[index].tranValue)
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1 ">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div class="flex">
<div class="mr-2 w-full">
<el-input v-model="info.content">
<template #prepend>
<el-select v-model="info.chooseTranOptions" placeholder="Select" class="w-24">
<el-option
v-for="item in info.tranOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-input>
</div>
<el-button type="primary" @click="tran()">转换</el-button>
</div>
<div class="mt-3 min-h-md bg-gray-100 p-3 mb-3 flex flex-col">
<el-table :data="info.tranOptions" border style="width: 100%">
<el-table-column prop="label" label="进制" width="120">
<template #default="scope">
<div class="flex items-center">
<span class="mr-1">{{ scope.row.label }}</span>
<el-tooltip
v-if="scope.row.desc != ''"
class="box-item"
effect="dark"
:content="scope.row.desc"
placement="top-start"
><el-icon ><InfoFilled /></el-icon></el-tooltip>
</div>
</template>
</el-table-column>
<el-table-column prop="tranValue" label="结果"/>
<el-table-column prop="" label="操作" width="60">
<template #default="scope">
<el-button
link
type="primary"
size="small"
@click.prevent="copyRes(scope.$index)"
>
复制
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,88 @@
<script setup lang="ts">
import { ref, reactive } from "vue"
import SignImageCore from './SignImageCore.vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import { UploadProps, UploadInstance, UploadRawFile, genFileId } from 'element-plus'
import { autoDown } from '@/utils/file'
import { Jh_getTimeStamp } from '@/utils/time'
// 图片处理
const info = reactive({
title:"在线编辑图片",
//图片地址
previewsImgUrl:"https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg",
//获取处理完的图片
getNewImg:(url: string) => {
//下载
autoDown(url, Jh_getTimeStamp() + '.png')
},
//用于刷新组件视图
cKey: 1,
})
//使用 upload ref
const upload = ref<UploadInstance>()
//使用 SignImageCore ref
const refSignImageCore = ref<any>()
//上传文件发生变化
const uploadChange: UploadProps['onChange'] = (file) => {
//UploadFile转换url
info.previewsImgUrl = URL.createObjectURL(file.raw!)
// 更新组件
info.cKey++
}
//钩子 - 文件上传超出限制
const uploadExceed: UploadProps['onExceed'] = (files) => {
upload.value!.clearFiles()
const file = files[0] as UploadRawFile
file.uid = genFileId()
upload.value!.handleStart(file)
}
//保存图片
const saveImg = () => {
refSignImageCore.value!.save()
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="flex flex-col mb-3 h-[38rem] p-4 rounded-2xl bg-white">
<div class="mb-3 flex flex-row-reverse">
<!-- save -->
<el-button type="primary" class="ml-2" @click="saveImg">保存</el-button>
<!-- upload -->
<el-upload
ref="upload"
:limit="1"
@exceed="uploadExceed"
accept="image/*"
@change="uploadChange"
:auto-upload="false"
:show-file-list="false"
>
<template #trigger>
<el-button type="primary">选择图片</el-button>
</template>
</el-upload>
</div>
<!-- 图片处理框 -->
<SignImageCore
ref="refSignImageCore"
:key="info.cKey"
:dialogVisible.sync="true"
:title="info.title"
:imgUrl="info.previewsImgUrl"
@getNewImg="info.getNewImg"
></SignImageCore>
</div>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,234 @@
<script setup>
import { ref, onMounted, nextTick } from 'vue'
import 'tui-image-editor/dist/tui-image-editor.css'
// import 'tui-color-picker/dist/tui-color-picker.css'
import { base64ToBlod } from '@/utils/file'
import ImageEditor from 'tui-image-editor'
// 中文菜单
const locale_zh = {
ZoomIn: '放大',
ZoomOut: '缩小',
Hand: '手掌',
History: '历史',
Resize: '调整宽高',
Crop: '裁剪',
DeleteAll: '全部删除',
Delete: '删除',
Undo: '撤销',
Redo: '反撤销',
Reset: '重置',
Flip: '镜像',
Rotate: '旋转',
Draw: '画',
Shape: '形状标注',
Icon: '图标标注',
Text: '文字标注',
Mask: '遮罩',
Filter: '滤镜',
Bold: '加粗',
Italic: '斜体',
Underline: '下划线',
Left: '左对齐',
Center: '居中',
Right: '右对齐',
Color: '颜色',
'Text size': '字体大小',
Custom: '自定义',
Square: '正方形',
Apply: '应用',
Cancel: '取消',
'Flip X': 'X 轴',
'Flip Y': 'Y 轴',
Range: '区间',
Stroke: '描边',
Fill: '填充',
Circle: '圆',
Triangle: '三角',
Rectangle: '矩形',
Free: '曲线',
Straight: '直线',
Arrow: '箭头',
'Arrow-2': '箭头2',
'Arrow-3': '箭头3',
'Star-1': '星星1',
'Star-2': '星星2',
Polygon: '多边形',
Location: '定位',
Heart: '心形',
Bubble: '气泡',
'Custom icon': '自定义图标',
'Load Mask Image': '加载蒙层图片',
Grayscale: '灰度',
Blur: '模糊',
Sharpen: '锐化',
Emboss: '浮雕',
'Remove White': '除去白色',
Distance: '距离',
Brightness: '亮度',
Noise: '噪音',
'Color Filter': '彩色滤镜',
Sepia: '棕色',
Sepia2: '棕色2',
Invert: '负片',
Pixelate: '像素化',
Threshold: '阈值',
Tint: '色调',
Multiply: '正片叠底',
Blend: '混合色',
Width: '宽度',
Height: '高度',
'Lock Aspect Ratio': '锁定宽高比例'
}
// 画布组件自定义样式
const customTheme = {
"common.bi.image": "", // 左上角logo图片
"common.bisize.width": "0px",
"common.bisize.height": "0px",
"common.backgroundImage": "none",
"common.backgroundColor": "#f3f4f6",
"common.border": "1px solid #333",
// header
"header.backgroundImage": "none",
"header.backgroundColor": "#f3f4f6",
"header.border": "0px",
// load button
"loadButton.backgroundColor": "#fff",
"loadButton.border": "1px solid #ddd",
"loadButton.color": "#222",
"loadButton.fontFamily": "NotoSans, sans-serif",
"loadButton.fontSize": "12px",
"loadButton.display": "none", // 可以直接隐藏掉
// download button
"downloadButton.backgroundColor": "#fdba3b",
"downloadButton.border": "1px solid #fdba3b",
"downloadButton.color": "#fff",
"downloadButton.fontFamily": "NotoSans, sans-serif",
"downloadButton.fontSize": "12px",
"downloadButton.display": "none", // 可以直接隐藏掉
// icons default
"menu.normalIcon.color": "#8a8a8a",
"menu.activeIcon.color": "#555555",
"menu.disabledIcon.color": "#ccc",
"menu.hoverIcon.color": "#e9e9e9",
"submenu.normalIcon.color": "#8a8a8a",
"submenu.activeIcon.color": "#e9e9e9",
"menu.iconSize.width": "24px",
"menu.iconSize.height": "24px",
"submenu.iconSize.width": "32px",
"submenu.iconSize.height": "32px",
// submenu primary color
"submenu.backgroundColor": "#1e1e1e",
"submenu.partition.color": "#858585",
// submenu labels
"submenu.normalLabel.color": "#858585",
"submenu.normalLabel.fontWeight": "lighter",
"submenu.activeLabel.color": "#fff",
"submenu.activeLabel.fontWeight": "lighter",
// checkbox style
"checkbox.border": "1px solid #ccc",
"checkbox.backgroundColor": "#fff",
// rango style
"range.pointer.color": "#fff",
"range.bar.color": "#666",
"range.subbar.color": "#d1d1d1",
"range.disabledPointer.color": "#414141",
"range.disabledBar.color": "#282828",
"range.disabledSubbar.color": "#414141",
"range.value.color": "#fff",
"range.value.fontWeight": "lighter",
"range.value.fontSize": "11px",
"range.value.border": "1px solid #353535",
"range.value.backgroundColor": "#151515",
"range.title.color": "#fff",
"range.title.fontWeight": "lighter",
// colorpicker style
"colorpicker.button.border": "1px solid #1e1e1e",
"colorpicker.title.color": "#fff",
};
// props
const props = defineProps({
dialogVisible: {
type: Boolean,
default: () => {
return false;
},
},
title: {
type: String,
default: "",
},
imgUrl: {
type: String,
default: "",
},
});
const emit = defineEmits()
const instance = ref()
onMounted(() => {
nextTick(() => {
init() // 页面创建完成后调用
})
})
const init = ()=> {
instance.value = new ImageEditor(document.querySelector('#tui-image-editor'), {
includeUI: {
loadImage: {
path: props.imgUrl,
name: 'image'
},
menu: ['resize', 'crop', 'rotate', 'draw', 'shape', 'icon', 'text', 'filter'], // 底部菜单按钮列表 隐藏镜像flip和遮罩mask
initMenu: 'none', // 默认打开的菜单项
menuBarPosition: 'left', // 菜单所在的位置
locale: locale_zh, // 本地化语言为中文
theme: customTheme // 自定义样式
},
cssMaxWidth: 400, // canvas 最大宽度
cssMaxHeight: 500 // canvas 最大高度
})
document.getElementsByClassName('tui-image-editor-main')[0].style.top = '0' // 调整图片显示位置
document.getElementsByClassName(
'tie-btn-reset tui-image-editor-item help'
)[0].style.display = 'none' // 隐藏顶部重置按钮
}
// 保存图片,并上传
const save = () => {
// base64 文件
const base64String = instance.value.toDataURL()
//转换blod
const url = base64ToBlod(base64String)
//传递给父组件
emit('getNewImg', url);
}
defineExpose({
save
})
</script>
<template>
<div id="tui-image-editor"></div>
</template>
<style lang="scss" 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>
@@ -0,0 +1,142 @@
<script setup lang="ts">
import { reactive, ref, shallowRef, onBeforeUnmount } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import html2canvas from "html2canvas";
import '@wangeditor/editor/dist/css/style.css' // 引入富文本 css
import { Editor, Toolbar } from '@wangeditor/editor-for-vue' //富文本组件
// import { copy } from '@/utils/string'
const info = reactive({
title: "文本转图片",
mode: 'default',
convasWidth: 860,
convasBackgroundColor: '#fff',
downExt: '.png',
downExts: [
{
value: '.png',
label: '.png'
},
{
value: '.jpg',
label: '.jpg'
},
]
})
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()
// 内容 HTML
const valueHtml = ref(`文字转图片演示😀
拥有丰富的样式选择
可自由调整宽度背景色
支持一键导出为长图
`)
// 绑定 需要把那个内容生成图片
const poster = ref();
// 工具栏配置
const toolbarConfig = {
excludeKeys: [
"uploadImage",
"group-video",
"insertLink",
]
}
//编辑器配置
const editorConfig = { placeholder: '请输入内容...' }
const handleCreated = (editor) => {
editorRef.value = editor // 记录 editor 实例,重要!
}
const goDown =() => {
html2canvas(poster.value, {
backgroundColor: info.convasBackgroundColor,//海报的背景颜色
useCORS: true, // 允许跨域
width: info.convasWidth, //生成海报的w
}).then(canvas=>{
// canvas 其实就是我们所讲的res 的意思 返回报文的意思
let baseImg = canvas.toDataURL("image/png");
if (info.downExt == '.jpg') {
baseImg = canvas.toDataURL("image/jpg");
}
//创建a标签
let save = document.createElement('a');
// <a href=''></a>
save.href = baseImg;
// 下载的名字
save.download = 'yz'
// 直接回调a的点击事件
save.click()
})
}
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
//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>
<div class="">
<div class="flex items-center">
<el-text class="w-20">宽度</el-text>
<el-slider v-model="info.convasWidth" show-input size="large" :min="260" :max="1920"/>
</div>
</div>
<div class="mt-3 flex items-center justify-between">
<div class="">
<el-text>背景颜色</el-text>
<el-color-picker v-model="info.convasBackgroundColor" size="large" />
</div>
<div>
<el-select v-model="info.downExt" class="" placeholder="Select">
<el-option
v-for="item in info.downExts"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-button type="primary" class="ml-2" @click="goDown">下载</el-button>
</div>
</div>
</div>
<div class="border relative z-10 mt-3">
<Toolbar
class="border-b"
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="info.mode"
/>
<Editor
v-model="valueHtml"
:defaultConfig="editorConfig"
:mode="info.mode"
@onCreated="handleCreated"
/>
<div ref="poster" class="absolute top-0 -z-10" v-html="valueHtml"></div>
</div>
<!-- v-html="valueHtml" -->
</div>
</div>
</template>
<style scoped>
</style>
+141
View File
@@ -0,0 +1,141 @@
<script setup lang="ts">
import { onMounted, onUnmounted, reactive } from 'vue'
import { VideoPause,VideoPlay,CopyDocument } from '@element-plus/icons-vue'
import { Jh_getTimeStamp,Jh_timeStampToTime,Jh_convertTimeStamp } from '@/utils/time'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import { copy } from '@/utils/string'
const info = reactive({
title: "时间戳转换",
nowTime: Jh_getTimeStamp(),
isPlay: true,
waitTimeStamp: Jh_getTimeStamp(),//待转换的时间戳
tranTimeStamp: 0, //转换后的时间戳
waitDate: Jh_timeStampToTime(Jh_getTimeStamp(), '{y}-{m}-{d} {h}:{i}:{s}'), //待转换时间
tranDate: '', //转换后时间
chooseTranStampOption: '0',
chooseTranDateOption: '0',
tranOptions: [
{
value: '0',
label: '毫秒(ms)'
},
{
value: '1',
label: '秒(s)'
}
]
})
let timer: any
//开始定时任务
const start = () => {
if (!timer) {
timer = setInterval(() => {
info.nowTime = Jh_getTimeStamp()
}, 1000)
}
}
//停止定时任务
const stop = () => {
clearInterval(timer)
timer = null
}
//进入
onMounted(() => {
start()
})
//消亡
onUnmounted(() => {
stop()
})
const isPlayChange = () => {
if (info.isPlay == true) {
info.isPlay = false
stop()
} else {
info.isPlay = true
start()
}
}
//时间日期相互转换
const timeTran = (type: string) => {
if (type == 'toStamp') {
info.tranTimeStamp = info.chooseTranStampOption == '0' ? Jh_convertTimeStamp(info.waitDate) : Jh_convertTimeStamp(info.waitDate) / 1000
} else {
//转换number类型
if (typeof(info.waitTimeStamp) === 'string') info.waitTimeStamp = parseInt(info.waitTimeStamp as string)
info.tranDate = Jh_timeStampToTime(info.waitTimeStamp, '{y}-{m}-{d} {h}:{i}:{s}')
}
}
//复制时间戳
const copyRes = async () => {
copy('' + info.nowTime)
}
</script>
<template>
<div class="flex flex-col mt-3 ml-4 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="flex flex-col p-4 rounded-2xl bg-white">
<div class="flex flex-direction">
<el-text class="mr-2 w-12">现在</el-text>
<el-button class="mr-3" link @click="copyRes()">{{ info.nowTime }} <el-icon class="ml-1 mr-1"><CopyDocument /></el-icon></el-button>
<el-button v-if="info.isPlay" type="danger" link class="flex items-center" @click="isPlayChange()"><el-icon class="mr-1" size="16"><VideoPlay/></el-icon>停止</el-button>
<el-button v-else="info.isPlay" type="primary" link class="flex items-center" @click="isPlayChange()"><el-icon class="mr-1" size="16"><VideoPause /></el-icon>开始</el-button>
</div>
<div class="flex flex-direction mt-4 justify-start">
<el-text class="mr-2 w-12">时间戳</el-text>
<el-input v-model="info.waitTimeStamp" class="h-8 mr-2 w-60 max-w-[30%]" placeholder="毫秒/秒">
<!-- <template #append>
<el-select v-model="info.chooseTranDateOption" placeholder="Select" class="w-24">
<el-option
v-for="item in info.tranOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template> -->
</el-input>
<el-button class="mr-2 max-w-[25%]" @click="timeTran('toDate')">转换</el-button>
<el-input v-model="info.tranDate" class="h-8 w-72 mr-2 max-w-[30%]" placeholder="北京时间"></el-input>
</div>
<div class="flex flex-direction mt-4 justify-start">
<el-text class="mr-2 w-12">时间</el-text>
<el-input v-model="info.waitDate" class="h-8 mr-2 w-60 max-w-[30%]"></el-input>
<el-button class="mr-2 max-w-[25%]" @click="timeTran('toStamp')">转换</el-button>
<el-input v-model="info.tranTimeStamp" :value="info.tranTimeStamp == 0 ? '' : info.tranTimeStamp" class="h-8 w-72 mr-2 max-w-[30%]" placeholder="时间戳">
<template #prepend>
<el-select v-model="info.chooseTranStampOption" placeholder="Select" class="w-24">
<el-option
v-for="item in info.tranOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-input>
</div>
</div>
<div class="p-4 rounded-2xl bg-white mt-5">
<div class="text-xl mt-5 mb-5">时间戳</div>
<el-text>Unix 时间戳是从1970年1月1日UTC/GMT的午夜开始所经过的秒数不考虑闰秒</el-text>
</div>
</div>
</template>
<style scoped>
</style>
+70
View File
@@ -0,0 +1,70 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import { copy } from '@/utils/string'
import { ElMessage } from 'element-plus'
import { v4 as uuidv4 } from 'uuid'
const info = reactive({
title: "UUID生成器",
resStr: '',
pwNum: 1,
autosize: {
minRows: 5
},
})
//生成
const gen = () => {
//生成数量验证
if (info.pwNum > 100) {
ElMessage({
message: "生成数量范围在1~100",
type: "warning",
duration: 1500
})
return
}
//清空旧数据
info.resStr = ''
//按数量生成密码
for (let i = 1; i <= info.pwNum; i++) {
let ext = '\n'
if (i == info.pwNum) {
ext = ''
}
info.resStr += uuidv4() + ext
}
}
//copy
const copyRes = async (resStr: string) => {
copy(resStr)
}
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div>
<el-input v-model="info.pwNum" placeholder="范围1~100" class="" max="100" type="number">
<template #prepend>生成数量:</template>
</el-input>
</div>
<div class="mt-3 mb-3">
<el-button type="primary" @click="gen">生成UUID</el-button>
<el-button type="primary" @click="copyRes(info.resStr)">复制全部</el-button>
</div>
<!-- res -->
<div>
<el-input type="textarea" :autosize="info.autosize" v-model="info.resStr"></el-input>
</div>
</div>
</div>
</template>
<style scoped>
</style>
+83
View File
@@ -0,0 +1,83 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import { Codemirror } from "vue-codemirror"
import { lineNumbers } from '@codemirror/view';
import { copy } from '@/utils/string'
const info = reactive({
title: "Unicode转中文",
extensions: [lineNumbers()],
content: '',
tranRes: '',
})
const clearRes = () => {
info.tranRes = ''
}
//to zh
//值转换中文
const toZH = () => {
//clear
clearRes()
let str = info.content
str = str.replace(/\\/g, "%");
// 转换中文
str = unescape(str);
info.tranRes = str
}
//to unicode
const toUnicode = () => {
//clear
clearRes()
//只转换中文
for (let i = 0; i < info.content.length; i++) {
if (/^[\u4E00-\u9FA5\uF900-\uFA2D]+$/.test(info.content[i])) {
let code = info.content.charCodeAt(i).toString(16)
info.tranRes += '\\u' + code
} else {
info.tranRes += info.content[i]
}
}
}
//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>
<codemirror
v-model="info.content"
placeholder=""
:style="{ height: '200px' }"
:autofocus="true"
:indent-with-tab="true"
:tabSize="2"
:extensions="info.extensions"
/>
</div>
<div class="mt-4">
<el-button type="primary" @click="toZH">unicode转中文</el-button>
<el-button type="primary" @click="toUnicode">中文转unicode</el-button>
<el-button type="primary" @click="copyRes">复制结果</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="info.tranRes"></el-input>
</div>
</div>
</div>
</template>
<style scoped>
</style>
+415
View File
@@ -0,0 +1,415 @@
<script setup lang="ts">
import { reactive } from 'vue'
const lengthInfo: {
[key: string]: string|number
} = reactive({
km: '',
m: '',
dm: '',
cm: '',
mm: '',
ha: '',
tradition_mu: '',
tradition_fen: '',
tradition_mill: '',
tradition_hao: '',
eng_nmi: '',
eng_mi: '',
eng_acre: '',
eng_rd: '',
eng_yd: '',
eng_ft: '',
eng_in: '',
})
//clear
const clear = () => {
for (let item in lengthInfo) {
lengthInfo[item] = ''
}
}
/**
* 转换
* @param type
*/
const tran = (key: string) => {
//转换成平方米
switch (key) {
case 'km':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * (1000 * 1000)
break;
case 'm':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 1
break;
case 'dm':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.01
break;
case 'cm':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.0001
break;
case 'mm':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.000001
break;
case 'ha':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 10000
break;
case 'tradition_mu':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 666.6666667
lengthInfo.tradition_mu = parseFloat(lengthInfo[key] as string) * 1
break;
case 'tradition_fen':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 66.67
lengthInfo.tradition_mu = parseFloat(lengthInfo[key] as string) * 0.1
break;
case 'tradition_mill':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 6.67
lengthInfo.tradition_mu = parseFloat(lengthInfo[key] as string) * 0.01
break;
case 'tradition_hao':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.67
lengthInfo.tradition_mu = parseFloat(lengthInfo[key] as string) * 0.001
break;
case 'eng_nmi':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * (1852 * 1852)
break;
case 'eng_mi':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 2589988.110336
lengthInfo.eng_ft = parseFloat(lengthInfo[key] as string) * 27878400
break;
case 'eng_acre':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 4046.8564224
lengthInfo.eng_ft = parseFloat(lengthInfo[key] as string) * 43560
break;
case 'eng_rd':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 25.2928526
lengthInfo.eng_ft = parseFloat(lengthInfo[key] as string) * 272.25
break;
case 'eng_yd':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.8361274
lengthInfo.eng_ft = parseFloat(lengthInfo[key] as string) * 9
break;
case 'eng_ft':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.092903
lengthInfo.eng_ft = parseFloat(lengthInfo[key] as string) * 1
break;
case 'eng_in':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.0006452
lengthInfo.eng_ft = parseFloat(lengthInfo[key] as string) * 0.0069444
break;
default:
break;
}
//平方米转换成其他单位
let mVal = lengthInfo.m as number
//传统面积处理
let muVal = mVal * 0.0015
if (['tradition_mu', 'tradition_fen', 'tradition_mill', 'tradition_hao'].includes(key)) {
muVal = (lengthInfo.tradition_mu == '' ? muVal : lengthInfo.tradition_mu) as number
}
//英制
let engFtVal = mVal / 0.092903
if (['eng_mi', 'eng_acre', 'eng_rd', 'eng_yd', 'eng_ft', 'eng_in'].includes(key)) {
engFtVal = (lengthInfo.eng_ft == '' ? engFtVal : lengthInfo.eng_ft) as number
}
lengthInfo.km = mVal * 0.000001
lengthInfo.dm = mVal * 100
lengthInfo.cm = mVal * 10000
lengthInfo.mm = mVal * 1000000
lengthInfo.ha = mVal * 0.0001
lengthInfo.tradition_mu = muVal
lengthInfo.tradition_fen = muVal / 0.1
lengthInfo.tradition_mill = muVal / 0.01
lengthInfo.tradition_hao = muVal / 0.001
lengthInfo.eng_nmi = mVal / (1852 * 1852)
lengthInfo.eng_mi = engFtVal / 27878400
lengthInfo.eng_acre = engFtVal / 43560
lengthInfo.eng_rd = engFtVal / 272.25
lengthInfo.eng_yd = engFtVal / 9
lengthInfo.eng_ft = engFtVal
lengthInfo.eng_in = engFtVal / 0.0069444
}
</script>
<template>
<div>
<div>
<el-divider content-position="left">国际面积单位(公制)</el-divider>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">平方千米(km<sup>2</sup>)</el-text>
<el-input
v-model="lengthInfo.km"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('km')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">平方米(m<sup>2</sup>)</el-text>
<el-input
v-model="lengthInfo.m"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('m')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">平方分米(dm<sup>2</sup>)</el-text>
<el-input
v-model="lengthInfo.dm"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('dm')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">平方厘米(cm<sup>2</sup>)</el-text>
<el-input
v-model="lengthInfo.cm"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('cm')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">平方毫米(mm<sup>2</sup>)</el-text>
<el-input
v-model="lengthInfo.mm"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('mm')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">公顷(ha)</el-text>
<el-input
v-model="lengthInfo.ha"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('ha')">转换</el-button>
</template>
</el-input>
</div>
</div>
<el-divider content-position="left">中国传统面积单位(市制)</el-divider>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text"></el-text>
<el-input
v-model="lengthInfo.tradition_mu"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('tradition_mu')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text"></el-text>
<el-input
v-model="lengthInfo.tradition_fen"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('tradition_fen')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text"></el-text>
<el-input
v-model="lengthInfo.tradition_mill"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('tradition_mill')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text"></el-text>
<el-input
v-model="lengthInfo.tradition_hao"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('tradition_hao')">转换</el-button>
</template>
</el-input>
</div>
</div>
<el-divider content-position="left">英制面积单位</el-divider>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">平方海里(nmi<sup>2</sup>)</el-text>
<el-input
v-model="lengthInfo.eng_nmi"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('eng_nmi')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">平方英里(mi<sup>2</sup>)</el-text>
<el-input
v-model="lengthInfo.eng_mi"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('eng_mi')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">英亩(acre)</el-text>
<el-input
v-model="lengthInfo.eng_acre"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('eng_acre')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">平方竿(rd<sup>2</sup>)</el-text>
<el-input
v-model="lengthInfo.eng_rd"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('eng_rd')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">平方码(yd<sup>2</sup>)</el-text>
<el-input
v-model="lengthInfo.eng_yd"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('eng_yd')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">平方英尺(ft<sup>2</sup>)</el-text>
<el-input
v-model="lengthInfo.eng_ft"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('eng_ft')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">平方英寸(in<sup>2</sup>)</el-text>
<el-input
v-model="lengthInfo.eng_in"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('eng_in')">转换</el-button>
</template>
</el-input>
</div>
</div>
</div>
<div class="w-full text-center">
<el-button type="primary" @click="clear">清除全部</el-button>
</div>
</div>
</template>
<style scoped>
.custom-box{
display: flex;
justify-content: space-between;
margin-bottom: 0.75rem/* 12px */;
}
.custom-box-single{
display: flex;
width: 50%;
}
.custom-box-text{
text-align: center;
width: 10rem;
}
</style>
+172
View File
@@ -0,0 +1,172 @@
<script setup lang="ts">
import { reactive } from 'vue'
const info: {
[key: string]: string|number
} = reactive({
wh: '',
mmwh: '',
kwh: '',
mwh: '',
j: '',
kj: '',
})
//clear
const clear = () => {
for (let item in info) {
info[item] = ''
}
}
/**
* 转换
* @param type
*/
const tran = (key: string) => {
//转换成瓦
switch (key) {
case 'wh':
info.wh = parseFloat(info[key] as string) * 1
break;
case 'mmwh':
info.wh = parseFloat(info[key] as string) * 0.001
break;
case 'kwh':
info.wh = parseFloat(info[key] as string) * 1000
break;
case 'mwh':
info.wh = parseFloat(info[key] as string) * 1000000
break;
case 'j':
info.wh = parseFloat(info[key] as string) * 0.0002777777777777778
break;
case 'kj':
info.wh = parseFloat(info[key] as string) * 0.2777777777777778
break;
default:
break;
}
//转换成其他单位
let val = info.wh as number
info.mmwh = val / 0.001
info.kwh = val / 1000
info.mwh = val / 1000000
info.j = val / 0.0002777777777777778
info.kj = info.j / 1000
}
</script>
<template>
<div>
<div>
<el-divider content-position="left">热量单位</el-divider>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">(Wh)</el-text>
<el-input
v-model="info.wh"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('wh')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">毫瓦时(mWh)</el-text>
<el-input
v-model="info.mmwh"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('mmwh')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">千瓦时(kWh)</el-text>
<el-input
v-model="info.kwh"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('kwh')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">兆瓦时(MWh)</el-text>
<el-input
v-model="info.mwh"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('mwh')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">(J)</el-text>
<el-input
v-model="info.j"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('j')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">千焦(kJ)</el-text>
<el-input
v-model="info.kj"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('kj')">转换</el-button>
</template>
</el-input>
</div>
</div>
</div>
<div class="w-full text-center">
<el-button type="primary" @click="clear">清除全部</el-button>
</div>
</div>
</template>
<style scoped>
.custom-box{
display: flex;
justify-content: space-between;
margin-bottom: 0.75rem/* 12px */;
}
.custom-box-single{
display: flex;
width: 50%;
}
.custom-box-text{
text-align: center;
width: 10rem;
}
</style>
+512
View File
@@ -0,0 +1,512 @@
<script setup lang="ts">
import { reactive } from 'vue'
const lengthInfo: {
[key: string]: string|number
} = reactive({
km: '',
m: '',
dm: '',
cm: '',
mm: '',
dmm: '',
um: '',
nm: '',
tradition_li: '',
tradition_zhang: '',
tradition_chi: '',
tradition_cun: '',
tradition_fen: '',
tradition_mill: '',
tradition_hao: '',
eng_nmi: '',
eng_fm: '',
eng_mi: '',
eng_fur: '',
eng_yd: '',
eng_ft: '',
eng_in: '',
})
//clear
const clear = () => {
for (let item in lengthInfo) {
lengthInfo[item] = ''
}
}
/**
* 转换
* @param type
*/
const tran = (key: string) => {
//转换成米
switch (key) {
case 'km':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 1000
break;
case 'm':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 1
break;
case 'dm':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.1
break;
case 'cm':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.01
break;
case 'mm':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.001
break;
case 'dmm':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.0001
break;
case 'um':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.000001
break;
case 'nm':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.000000001
break;
case 'tradition_li':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 500
lengthInfo.tradition_chi = parseFloat(lengthInfo[key] as string) * 1500
break;
case 'tradition_zhang':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 3.3333333
lengthInfo.tradition_chi = parseFloat(lengthInfo[key] as string) * 10
break;
case 'tradition_chi':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.3
lengthInfo.tradition_chi = parseFloat(lengthInfo[key] as string) * 1
break;
case 'tradition_cun':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.03
lengthInfo.tradition_chi = parseFloat(lengthInfo[key] as string) * 0.1
break;
case 'tradition_fen':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.003
lengthInfo.tradition_chi = parseFloat(lengthInfo[key] as string) * 0.01
break;
case 'tradition_mill':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.0003
lengthInfo.tradition_chi = parseFloat(lengthInfo[key] as string) * 0.001
break;
case 'tradition_hao':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.00003
lengthInfo.tradition_chi = parseFloat(lengthInfo[key] as string) * 0.0001
break;
case 'eng_nmi':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 1852
break;
case 'eng_fm':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 1.852
lengthInfo.eng_ft = parseFloat(lengthInfo[key] as string) * 6
break;
case 'eng_mi':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 1609.34
lengthInfo.eng_ft = parseFloat(lengthInfo[key] as string) * 5280
break;
case 'eng_fur':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 201.168
lengthInfo.eng_ft = parseFloat(lengthInfo[key] as string) * 660
break;
case 'eng_yd':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.9144
lengthInfo.eng_ft = parseFloat(lengthInfo[key] as string) * 3
break;
case 'eng_ft':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.3048
lengthInfo.eng_ft = parseFloat(lengthInfo[key] as string) * 1
break;
case 'eng_in':
lengthInfo.m = parseFloat(lengthInfo[key] as string) * 0.0254
break;
default:
break;
}
//米转换成其他单位
let mVal = lengthInfo.m as number
//中国传统单位处理
let chiVal = mVal * 3
if (['tradition_li', 'tradition_zhang', 'tradition_chi', 'tradition_cun', 'tradition_fen', 'tradition_mill', 'tradition_hao'].includes(key)) {
chiVal = (lengthInfo.tradition_chi == '' ? chiVal : lengthInfo.tradition_chi) as number
}
//英制处理
let engFtVal = mVal / 0.3048
if (['eng_fm', 'eng_mi', 'eng_fur', 'eng_yd', 'eng_ft'].includes(key)) {
engFtVal = (lengthInfo.eng_ft == '' ? mVal / 0.3048 : lengthInfo.eng_ft) as number
}
lengthInfo.km = mVal / 1000
lengthInfo.dm = mVal * 10
lengthInfo.cm = mVal * 100
lengthInfo.mm = mVal * 1000
lengthInfo.dmm = mVal * 10000
lengthInfo.um = mVal * 1000000
lengthInfo.nm = mVal * 1000000000
lengthInfo.tradition_li = mVal / 500
lengthInfo.tradition_chi = chiVal
lengthInfo.tradition_zhang = chiVal / 10
lengthInfo.tradition_cun = chiVal / 0.1
lengthInfo.tradition_fen = chiVal / 0.01
lengthInfo.tradition_mill = chiVal / 0.001
lengthInfo.tradition_hao = chiVal / 0.0001
lengthInfo.eng_nmi = mVal / 1852
lengthInfo.eng_ft = engFtVal
lengthInfo.eng_fm = engFtVal / 6
lengthInfo.eng_mi = engFtVal / 5280
lengthInfo.eng_fur = engFtVal / 660
lengthInfo.eng_yd = engFtVal / 3
lengthInfo.eng_in = engFtVal * 12
}
</script>
<template>
<div>
<div>
<el-divider content-position="left">国际长度单位(公制)</el-divider>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">公里(km)</el-text>
<el-input
v-model="lengthInfo.km"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('km')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">(m)</el-text>
<el-input
v-model="lengthInfo.m"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('m')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">分米(dm)</el-text>
<el-input
v-model="lengthInfo.dm"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('dm')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">厘米(cm)</el-text>
<el-input
v-model="lengthInfo.cm"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('cm')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">毫米(mm)</el-text>
<el-input
v-model="lengthInfo.mm"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('mm')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">(dmm)</el-text>
<el-input
v-model="lengthInfo.dmm"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('dmm')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">微米(um)</el-text>
<el-input
v-model="lengthInfo.um"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('um')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">纳米(nm)</el-text>
<el-input
v-model="lengthInfo.nm"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('nm')">转换</el-button>
</template>
</el-input>
</div>
</div>
<el-divider content-position="left">中国传统长度单位(市制)</el-divider>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text"></el-text>
<el-input
v-model="lengthInfo.tradition_li"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('tradition_li')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text"></el-text>
<el-input
v-model="lengthInfo.tradition_zhang"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('tradition_zhang')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text"></el-text>
<el-input
v-model="lengthInfo.tradition_chi"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('tradition_chi')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text"></el-text>
<el-input
v-model="lengthInfo.tradition_cun"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('tradition_cun')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text"></el-text>
<el-input
v-model="lengthInfo.tradition_fen"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('tradition_fen')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text"></el-text>
<el-input
v-model="lengthInfo.tradition_mill"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('tradition_mill')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text"></el-text>
<el-input
v-model="lengthInfo.tradition_hao"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('tradition_hao')">转换</el-button>
</template>
</el-input>
</div>
</div>
<el-divider content-position="left">英制长度单位</el-divider>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">海里(nmi)</el-text>
<el-input
v-model="lengthInfo.eng_nmi"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('eng_nmi')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">英寻(fm)</el-text>
<el-input
v-model="lengthInfo.eng_fm"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('eng_fm')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">英里(mi)</el-text>
<el-input
v-model="lengthInfo.eng_mi"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('eng_mi')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">弗隆(fur)</el-text>
<el-input
v-model="lengthInfo.eng_fur"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('eng_fur')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">(yd)</el-text>
<el-input
v-model="lengthInfo.eng_yd"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('eng_yd')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">英尺(ft)</el-text>
<el-input
v-model="lengthInfo.eng_ft"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('eng_ft')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">英寸(in)</el-text>
<el-input
v-model="lengthInfo.eng_in"
placeholder=""
class="input-with-select"
type="number"
>
<template #append>
<el-button @click="tran('eng_in')">转换</el-button>
</template>
</el-input>
</div>
</div>
</div>
<div class="w-full text-center">
<el-button type="primary" @click="clear">清除全部</el-button>
</div>
</div>
</template>
<style scoped>
.custom-box{
display: flex;
justify-content: space-between;
margin-bottom: 0.75rem/* 12px */;
}
.custom-box-single{
display: flex;
width: 45%;
}
.custom-box-text{
text-align: center;
width: 5rem/* 80px */;
}
</style>
+155
View File
@@ -0,0 +1,155 @@
<script setup lang="ts">
import { reactive } from 'vue'
const info: {
[key: string]: string|number
} = reactive({
w: '',
mmw: '',
kw: '',
mw: '',
gw: '',
})
//clear
const clear = () => {
for (let item in info) {
info[item] = ''
}
}
/**
* 转换
* @param type
*/
const tran = (key: string) => {
//转换成瓦
switch (key) {
case 'w':
info.w = parseFloat(info[key] as string) * 1
break;
case 'mmw':
info.w = parseFloat(info[key] as string) * 0.001
break;
case 'kw':
info.w = parseFloat(info[key] as string) * 1000
break;
case 'mw':
info.w = parseFloat(info[key] as string) * 1000000
break;
case 'gw':
info.w = parseFloat(info[key] as string) * 1000000000
break;
default:
break;
}
//转换成其他单位
let val = info.w as number
info.mmw = val / 0.001
info.kw = val / 1000
info.mw = val / 1000000
info.gw = val / 1000000000
}
</script>
<template>
<div>
<div>
<el-divider content-position="left">功率单位</el-divider>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">(W)</el-text>
<el-input
v-model="info.w"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('w')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">毫瓦(mW)</el-text>
<el-input
v-model="info.mmw"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('mmw')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">千瓦(kW)</el-text>
<el-input
v-model="info.kw"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('kw')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">兆瓦(MW)</el-text>
<el-input
v-model="info.mw"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('mw')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">亿瓦(GW)</el-text>
<el-input
v-model="info.gw"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('gw')">转换</el-button>
</template>
</el-input>
</div>
</div>
</div>
<div class="w-full text-center">
<el-button type="primary" @click="clear">清除全部</el-button>
</div>
</div>
</template>
<style scoped>
.custom-box{
display: flex;
justify-content: space-between;
margin-bottom: 0.75rem/* 12px */;
}
.custom-box-single{
display: flex;
width: 50%;
}
.custom-box-text{
text-align: center;
width: 10rem;
}
</style>
+193
View File
@@ -0,0 +1,193 @@
<script setup lang="ts">
import { reactive } from 'vue'
const info: {
[key: string]: string|number
} = reactive({
pa: '',
hpa: '',
kpa: '',
mpa: '',
bar: '',
torr: '',
psi: '',
})
//clear
const clear = () => {
for (let item in info) {
info[item] = ''
}
}
/**
* 转换
* @param type
*/
const tran = (key: string) => {
//转换成帕
switch (key) {
case 'pa':
info.pa = parseFloat(info[key] as string) * 1
break;
case 'hpa':
info.pa = parseFloat(info[key] as string) * 100
break;
case 'kpa':
info.pa = parseFloat(info[key] as string) * 1000
break;
case 'mpa':
info.pa = parseFloat(info[key] as string) * 1000000
break;
case 'bar':
info.pa = parseFloat(info[key] as string) * 100000
break;
case 'torr':
info.pa = parseFloat(info[key] as string) * 133.322368
break;
case 'psi':
info.pa = parseFloat(info[key] as string) * 6894.76
break;
default:
break;
}
//转换成其他单位
let val = info.pa as number
info.hpa = val / 100
info.kpa = val / 1000
info.mpa = val / 1000000
info.bar = val / 100000
info.torr = val / 133.322368
info.psi = val / 6894.76
}
</script>
<template>
<div>
<div>
<el-divider content-position="left">压力单位</el-divider>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">(Pa)</el-text>
<el-input
v-model="info.pa"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('pa')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">百帕(hPa)</el-text>
<el-input
v-model="info.hpa"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('hpa')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">千帕(kPa)</el-text>
<el-input
v-model="info.kpa"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('kpa')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">兆帕(MPa)</el-text>
<el-input
v-model="info.mpa"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('mpa')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">(bar)</el-text>
<el-input
v-model="info.bar"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('bar')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">(torr)</el-text>
<el-input
v-model="info.torr"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('torr')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text" style="font-size: 0.8rem;;">磅力/平方英寸(psi)</el-text>
<el-input
v-model="info.psi"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('psi')">转换</el-button>
</template>
</el-input>
</div>
</div>
</div>
<div class="w-full text-center">
<el-button type="primary" @click="clear">清除全部</el-button>
</div>
</div>
</template>
<style scoped>
.custom-box{
display: flex;
justify-content: space-between;
margin-bottom: 0.75rem/* 12px */;
}
.custom-box-single{
display: flex;
width: 50%;
}
.custom-box-text{
text-align: center;
width: 10rem;
}
</style>
+155
View File
@@ -0,0 +1,155 @@
<script setup lang="ts">
import { reactive } from 'vue'
const info: {
[key: string]: string|number
} = reactive({
c: '',
f: '',
k: '',
re: '',
r: '',
})
//clear
const clear = () => {
for (let item in info) {
info[item] = ''
}
}
/**
* 转换
* @param type
*/
const tran = (key: string) => {
//转换成摄氏度
switch (key) {
case 'c':
info.c = parseFloat(info[key] as string) * 1
break;
case 'f':
info.c = parseFloat(info[key] as string) * -17.2222222
break;
case 'k':
info.c = parseFloat(info[key] as string) * -272.15
break;
case 're':
info.c = parseFloat(info[key] as string) * 1.25
break;
case 'r':
info.c = parseFloat(info[key] as string) * -272.5944444
break;
default:
break;
}
//转换成其他单位
let val = info.c as number
info.f = 32 + (val * 1.8)
info.k = 273.15 + val
info.re = val / 1.25
info.r = (val + 273.15) * 1.8
}
</script>
<template>
<div>
<div>
<el-divider content-position="left">国际温度单位</el-divider>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">摄氏度()</el-text>
<el-input
v-model="info.c"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('c')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">华氏度()</el-text>
<el-input
v-model="info.f"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('f')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">开氏度(K)</el-text>
<el-input
v-model="info.k"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('k')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">列氏度(°Re)</el-text>
<el-input
v-model="info.re"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('re')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">兰氏度(°R)</el-text>
<el-input
v-model="info.r"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('r')">转换</el-button>
</template>
</el-input>
</div>
</div>
</div>
<div class="w-full text-center">
<el-button type="primary" @click="clear">清除全部</el-button>
</div>
</div>
</template>
<style scoped>
.custom-box{
display: flex;
justify-content: space-between;
margin-bottom: 0.75rem/* 12px */;
}
.custom-box-single{
display: flex;
width: 50%;
}
.custom-box-text{
text-align: center;
width: 10rem;
}
</style>
+210
View File
@@ -0,0 +1,210 @@
<script setup lang="ts">
import { reactive } from 'vue'
const info: {
[key: string]: string|number
} = reactive({
year: '',
month: '',
d: '',
h: '',
min: '',
s: '',
ms: '',
week: ''
})
//clear
const clear = () => {
for (let item in info) {
info[item] = ''
}
}
/**
* 转换
* @param type
*/
const tran = (key: string) => {
//转换成秒
switch (key) {
case 'year':
info.s = parseFloat(info[key] as string) * 31536000
break;
case 'month':
info.s = parseFloat(info[key] as string) * 2629800
break;
case 'd':
info.s = parseFloat(info[key] as string) * 86400
break;
case 'h':
info.s = parseFloat(info[key] as string) * 3600
break;
case 'min':
info.s = parseFloat(info[key] as string) * 60
break;
case 's':
info.s = parseFloat(info[key] as string) * 1
break;
case 'ms':
info.s = parseFloat(info[key] as string) * 0.001
break;
case 'week':
info.s = parseFloat(info[key] as string) * 604800
break;
default:
break;
}
//转换成其他单位
let val = info.s as number
info.year = val / 31536000
info.month = info.year * 12
info.d = val / 86400
info.h = val / 3600
info.min = val / 60
info.ms = val / 0.001
info.week = val / 604800
}
</script>
<template>
<div>
<div>
<el-divider content-position="left">国际时间单位</el-divider>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">(year)</el-text>
<el-input
v-model="info.year"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('year')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">(month)</el-text>
<el-input
v-model="info.month"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('month')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">(d)</el-text>
<el-input
v-model="info.d"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('d')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">(h)</el-text>
<el-input
v-model="info.h"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('h')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">(min)</el-text>
<el-input
v-model="info.min"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('min')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">(s)</el-text>
<el-input
v-model="info.s"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('s')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">毫秒(ms)</el-text>
<el-input
v-model="info.ms"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('ms')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">(week)</el-text>
<el-input
v-model="info.week"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('week')">转换</el-button>
</template>
</el-input>
</div>
</div>
</div>
<div class="w-full text-center">
<el-button type="primary" @click="clear">清除全部</el-button>
</div>
</div>
</template>
<style scoped>
.custom-box{
display: flex;
justify-content: space-between;
margin-bottom: 0.75rem/* 12px */;
}
.custom-box-single{
display: flex;
width: 50%;
}
.custom-box-text{
text-align: center;
width: 10rem;
}
</style>
+76
View File
@@ -0,0 +1,76 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import Length from './Length.vue'
import Area from './Area.vue'
import Heat from './Heat.vue'
import Power from './Power.vue'
import Pressure from './Pressure.vue'
import Temperature from './Temperature.vue'
import Time from './Time.vue'
import Weight from './Weight.vue'
const info = reactive({
title: "单位转换",
activeName: 'lengthTab',
})
const handleClick = () => {
}
</script>
<template>
<div>
<DetailHeader :title="info.title"></DetailHeader>
<div>
<div class="p-4 rounded-2xl bg-white">
<el-tabs v-model="info.activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane label="长度" name="lengthTab">
<Length/>
</el-tab-pane>
<el-tab-pane label="面积" name="areaTab">
<Area/>
</el-tab-pane>
<!-- <el-tab-pane label="体积" name="lengthTab">体积</el-tab-pane> -->
<el-tab-pane label="重量" name="weightTab">
<Weight/>
</el-tab-pane>
<el-tab-pane label="时间" name="timeTab">
<Time/>
</el-tab-pane>
<!-- <el-tab-pane label="角度" name="fourth">角度</el-tab-pane> -->
<!-- <el-tab-pane label="速度" name="fourth">速度</el-tab-pane> -->
<el-tab-pane label="温度" name="temperatureTab">
<Temperature/>
</el-tab-pane>
<el-tab-pane label="压力" name="pressureTab">
<Pressure/>
</el-tab-pane>
<el-tab-pane label="热量" name="heatTab">
<Heat/>
</el-tab-pane>
<el-tab-pane label="功率" name="powerTab">
<Power/>
</el-tab-pane>
<!-- <el-tab-pane label="字节" name="fourth">字节</el-tab-pane> -->
</el-tabs>
</div>
</div>
</div>
</template>
<style scoped>
.custom-box{
display: flex;
justify-content: space-between;
margin-bottom: 0.75rem/* 12px */;
}
.custom-box-single{
display: flex;
width: 45%;
}
.custom-box-text{
text-align: center;
width: 5rem/* 80px */;
}
</style>
+447
View File
@@ -0,0 +1,447 @@
<script setup lang="ts">
import { reactive } from 'vue'
const info: {
[key: string]: string|number
} = reactive({
t: '',
kg: '',
g: '',
mg: '',
jin: '',
liang: '',
qian: '',
dan: '',
eng_lb: '',
eng_oz: '',
eng_st: '',
eng_cwt: '',
eng_dr: '',
eng_gr: '',
eng_lt: '',
eng_lbt: '',
eng_ozt: '',
eng_grain: '',
eng_dwt: '',
})
//clear
const clear = () => {
for (let item in info) {
info[item] = ''
}
}
/**
* 转换
* @param type
*/
const tran = (key: string) => {
switch (key) {
case 't':
info.g = parseFloat(info[key] as string) * 1000000
break;
case 'kg':
info.g = parseFloat(info[key] as string) * 1000
break;
case 'g':
info.g = parseFloat(info[key] as string) * 1
break;
case 'mg':
info.g = parseFloat(info[key] as string) * 0.001
break;
case 'jin':
info.g = parseFloat(info[key] as string) * 500
break;
case 'liang':
info.g = parseFloat(info[key] as string) * 50
break;
case 'qian':
info.g = parseFloat(info[key] as string) * 5
break;
case 'dan':
info.g = parseFloat(info[key] as string) * 50000
break;
case 'eng_lb':
info.g = parseFloat(info[key] as string) * 453.59237
info.eng_lb = parseFloat(info[key] as string) * 1
break;
case 'eng_oz':
info.g = parseFloat(info[key] as string) * 28.3495231
info.eng_lb = parseFloat(info[key] as string) * 0.0625
break;
case 'eng_st':
info.g = parseFloat(info[key] as string) * 6350.29318
info.eng_lb = parseFloat(info[key] as string) * 14
break;
case 'eng_cwt':
info.g = parseFloat(info[key] as string) * 50802.34544
info.eng_lb = parseFloat(info[key] as string) * 112
break;
case 'eng_dr':
info.g = parseFloat(info[key] as string) * 1.7718452
info.eng_lb = parseFloat(info[key] as string) * 0.0039063
break;
case 'eng_gr':
info.g = parseFloat(info[key] as string) * 0.0647989
info.eng_lb = parseFloat(info[key] as string) * 0.0001429
break;
case 'eng_lt':
info.g = parseFloat(info[key] as string) * 1016046.9088
info.eng_lb = parseFloat(info[key] as string) * 2240
break;
case 'eng_lbt':
info.g = parseFloat(info[key] as string) * 373.2417216
info.eng_lbt = parseFloat(info[key] as string) * 1
break;
case 'eng_ozt':
info.g = parseFloat(info[key] as string) * 31.1034768
info.eng_lbt = parseFloat(info[key] as string) * 0.0833333
break;
case 'eng_grain':
info.g = parseFloat(info[key] as string) * 0.0647989
info.eng_lbt = parseFloat(info[key] as string) * 0.0001736
break;
case 'eng_dwt':
info.g = parseFloat(info[key] as string) * 1.5551738
info.eng_lbt = parseFloat(info[key] as string) * 0.0041667
break;
default:
break;
}
//转换成其他单位
let val = info.g as number
//英制 - 常衡制
let engLbVal = val / 453.59237
if (['tradition_mu', 'tradition_fen', 'tradition_mill', 'tradition_hao'].includes(key)) {
engLbVal = (info.eng_lb == '' ? engLbVal : info.eng_lb) as number
}
//英制 - 金衡制
let engLbtVal = val / 373.2417216
if (['eng_lbt', 'eng_ozt', 'eng_grain', 'eng_dwt'].includes(key)) {
engLbtVal = (info.eng_lbt == '' ? engLbtVal : info.eng_lbt) as number
}
info.t = val * 0.000001
info.kg = val * 0.001
info.mg = val * 1000
info.jin = val * 0.002
info.liang = val * 0.02
info.qian = val * 0.2
info.dan = val * 0.00002
info.eng_lb = engLbVal * 1
info.eng_oz = engLbVal * 16
info.eng_st = engLbVal * 0.0714286
info.eng_cwt = engLbVal * 0.0089286
info.eng_dr = engLbVal * 256
info.eng_gr = engLbVal * 7000
info.eng_lt = engLbVal * 0.0004464
info.eng_lbt = engLbtVal * 1
info.eng_ozt = engLbtVal * 12
info.eng_grain = engLbtVal * 5760
info.eng_dwt = engLbtVal * 240
}
</script>
<template>
<div>
<div>
<el-divider content-position="left">国际重量单位(公制)</el-divider>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">(t)</el-text>
<el-input
v-model="info.t"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('t')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">千克/公斤(kg)</el-text>
<el-input
v-model="info.kg"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('kg')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">(g)</el-text>
<el-input
v-model="info.g"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('g')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">毫克(mg)</el-text>
<el-input
v-model="info.mg"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('mg')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- ----- -->
<el-divider content-position="left">中国传统重量单位(市制)</el-divider>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text"></el-text>
<el-input
v-model="info.jin"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('jin')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text"></el-text>
<el-input
v-model="info.liang"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('liang')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text"></el-text>
<el-input
v-model="info.qian"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('qian')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text"></el-text>
<el-input
v-model="info.dan"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('dan')">转换</el-button>
</template>
</el-input>
</div>
</div>
<el-divider content-position="left">英制重量单位(常衡制)</el-divider>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">(lb)</el-text>
<el-input
v-model="info.eng_lb"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('eng_lb')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">盎司(oz)</el-text>
<el-input
v-model="info.eng_oz"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('eng_oz')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">英石(st)</el-text>
<el-input
v-model="info.eng_st"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('eng_st')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">英担(cwt)</el-text>
<el-input
v-model="info.eng_cwt"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('eng_cwt')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">打兰(dr)</el-text>
<el-input
v-model="info.eng_dr"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('eng_dr')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">格令(gr)</el-text>
<el-input
v-model="info.eng_gr"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('eng_gr')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">长吨(lt)</el-text>
<el-input
v-model="info.eng_lt"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('eng_lt')">转换</el-button>
</template>
</el-input>
</div>
</div>
<el-divider content-position="left">英制重量单位(金衡制)</el-divider>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">(lbt)</el-text>
<el-input
v-model="info.eng_lbt"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('eng_lbt')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">盎司(ozt)</el-text>
<el-input
v-model="info.eng_ozt"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('eng_ozt')">转换</el-button>
</template>
</el-input>
</div>
</div>
<!-- group -->
<div class="custom-box">
<div class="custom-box-single">
<el-text class="custom-box-text">格令</el-text>
<el-input
v-model="info.eng_grain"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('eng_grain')">转换</el-button>
</template>
</el-input>
</div>
<div class="custom-box-single">
<el-text class="custom-box-text">英钱(dwt)</el-text>
<el-input
v-model="info.eng_dwt"
placeholder=""
class="input-with-select"
>
<template #append>
<el-button @click="tran('eng_dwt')">转换</el-button>
</template>
</el-input>
</div>
</div>
</div>
<div class="w-full text-center">
<el-button type="primary" @click="clear">清除全部</el-button>
</div>
</div>
</template>
<style scoped>
.custom-box{
display: flex;
justify-content: space-between;
margin-bottom: 0.75rem/* 12px */;
}
.custom-box-single{
display: flex;
width: 50%;
}
.custom-box-text{
text-align: center;
width: 10rem;
}
</style>
@@ -0,0 +1,61 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
import { copy } from '@/utils/string'
const info = reactive({
title: "URL编码/解码",
content: '',
tranRes: '',
})
//编码
const toEncode = () => {
info.tranRes = ''
info.tranRes = encodeURIComponent(info.content)
}
//解码
const toDecode = () => {
info.tranRes = ''
info.tranRes = decodeURIComponent(info.content)
}
//clear
const clear = () => {
info.content = ''
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="toEncode">UrlEncode编码</el-button>
<el-button type="primary" @click="toDecode">UrlDecode解码</el-button>
<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="info.tranRes"></el-input>
</div>
</div>
</div>
</template>
<style scoped>
</style>
+104
View File
@@ -0,0 +1,104 @@
<script setup lang="ts">
import { ref, reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
// import { copy } from '@/utils/string'
import { Jh_getTimeStamp } from '@/utils/time'
import { isUrl } from '@/utils/url'
import { autoDown, getFileExtension } from '@/utils/file'
import { useToolsStore } from '@/store/modules/tools'
import { ElMessage } from 'element-plus'
const info = reactive({
title: "网站favicon获取",
})
//store
const toolsStore = useToolsStore()
const searchUrl = ref()
const getWebInfo = async () => {
try {
if (isUrl(searchUrl.value)) {
//有效的域名
if (!searchUrl.value.includes('http://') && !searchUrl.value.includes('https://')) {
searchUrl.value = 'http://' + searchUrl.value;
}
const urlObj = new URL(searchUrl.value)
// webInfo.link = urlObj.hostname
searchUrl.value = urlObj.hostname
await toolsStore.getWebInfo({
link: searchUrl.value
})
} else {
//无效的域名
ElMessage({
message: "无效的域名或地址",
type: "error",
duration: 1500
})
}
} catch (error) {
console.error('Error fetching site info:', error);
}
};
const down = () => {
autoDown(toolsStore.webInfo.logo, Jh_getTimeStamp() + '.' + getFileExtension(toolsStore.webInfo.logo))
}
//copy
// const copyRes = async (resStr: string) => {
// copy(resStr)
// }
</script>
<template>
<div class="flex flex-col mt-3 flex-1">
<DetailHeader :title="info.title"></DetailHeader>
<div class="p-4 rounded-2xl bg-white">
<div>
<el-input
v-model="searchUrl"
style="max-width: 600px"
placeholder="请输入域名或地址"
class="input-with-select"
size="large"
>
<template #append>
<el-button type="primary" @click="getWebInfo">获取</el-button>
</template>
</el-input>
</div>
<div>
<ul>
<li class="mt-3">
<el-text><b>域名: </b></el-text>
<el-link :href="'https://' + searchUrl" type="primary" target="_blank">{{ searchUrl }}</el-link>
</li>
<li class="mt-3 flex">
<el-text><b>logo: </b></el-text>
<div v-if="toolsStore.webInfo.logo" class="ml-2 flex items-center">
<el-image :src="toolsStore.webInfo.logo" class="h-12"></el-image>
<el-button class="ml-3" link type="primary" @click="down">下载</el-button>
</div>
</li>
<li class="mt-3 flex">
<el-text><b>标题: </b>{{ toolsStore.webInfo.title }}</el-text>
</li>
<li class="mt-3 flex">
<el-text><b>关键词: </b> {{ toolsStore.webInfo.keywords }}</el-text>
</li>
<li class="mt-3 flex">
<el-text><b>描述: </b> {{ toolsStore.webInfo.desc }}</el-text>
</li>
</ul>
</div>
</div>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,59 @@
<script setup lang="ts">
import { reactive } from 'vue'
import DetailHeader from '@/components/Layout/DetailHeader/DetailHeader.vue'
const info = reactive({
title: "在线字数统计",
content: '',
tranRes: '',
chinaCharNum: 0,
letterNum: 0,
punctuationHalfNum: 0,
punctuationFullNum: 0,
numberNum: 0,
emptyNum: 0,
sum: 0,
})
//内容发生变化,重新统计
const changeContent = () => {
//汉字
info.chinaCharNum = (info.content.match(/[\u4e00-\u9fa5]/g) || []).length;
//字母
info.letterNum = (info.content.match(/[a-zA-Z]/g) || []).length;
//符号 - 半角
info.punctuationHalfNum = (info.content.match(/[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/g) || []).length;
//符号 - 全角
info.punctuationFullNum = (info.content.match(/[\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5]/g) || []).length;
//数字
info.numberNum = (info.content.match(/[\d+]/g) || []).length;
//空白字符
info.emptyNum = (info.content.match(/[\s*]/g) || []).length;
//合计
info.sum = info.chinaCharNum + info.letterNum + info.punctuationHalfNum + info.punctuationFullNum + info.numberNum + info.emptyNum;
}
</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="16" v-model="info.content" @input="changeContent"></el-input>
</div>
<div class="mt-3 min-h-md bg-gray-100 p-3 mb-3 flex">
<div><el-text>总字数{{ info.sum }}</el-text></div>
<div class="ml-3"><el-text>汉字{{ info.chinaCharNum }}</el-text></div>
<div class="ml-3"><el-text>字母{{ info.letterNum }}</el-text></div>
<div class="ml-3"><el-text>数字{{ info.numberNum }}</el-text></div>
<div class="ml-3"><el-text>标点符号半角{{ info.punctuationHalfNum }}</el-text></div>
<div class="ml-3"><el-text>标点符号全角{{ info.punctuationFullNum }}</el-text></div>
<div class="ml-3"><el-text>空白字符{{ info.emptyNum }}</el-text></div>
</div>
</div>
</div>
</template>
<style scoped>
</style>