项目初始化

This commit is contained in:
leilei
2025-09-19 17:24:46 +08:00
commit 293951a610
107 changed files with 10222 additions and 0 deletions

18
.env.development Normal file
View File

@@ -0,0 +1,18 @@
# 开发环境配置
VITE_APP_ENV = 'development'
VITE_BASE_PATH = '/'
VITE_APP_PORT = 80
# 应用模板管理后台/开发环境
VITE_APP_BASE_API = '/dev-api'
# 后端实际地址可选用于proxy.target
VITE_API_TARGET = 'https://xsynergy.gxtech.ltd'
# 公网为“ web ” 私有化为不跳转为“ private ” 私有化跳转为“ skip ”
VITE_APP_COOPERATION_TYPE = 'skip'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip

15
.env.production Normal file
View File

@@ -0,0 +1,15 @@
# 生产环境配置
VITE_APP_ENV = 'production'
VITE_APP_PORT = 80
VITE_BASE_PATH = '/'
# 应用模板管理后台/生产环境
VITE_APP_BASE_API = 'https://xsynergy.gxtech.ltd'
# 公网为“web” 私有化为不跳转为“private” 私有化跳转为“skip”
VITE_APP_COOPERATION_TYPE = 'skip'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip

30
.gitignore vendored Normal file
View File

@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

3
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

29
README.md Normal file
View File

@@ -0,0 +1,29 @@
# tabula-rase-project
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```

13
index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>互动白板</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

22
jsconfig.json Normal file
View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": false,
"jsx": "preserve",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"checkJs": true,
"lib": ["esnext", "dom"]
},
"include": ["src/**/*.js", "src/**/*.vue"],
"exclude": ["node_modules", "dist"]
}

3924
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

34
package.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "tabula-rase-project",
"version": "0.0.0",
"private": true,
"license": "MIT",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
"autoprefixer": "^10.4.21",
"axios": "^0.27.2",
"code-inspector-plugin": "^0.20.12",
"element-plus": "^2.2.27",
"js-cookie": "^3.0.1",
"mitt": "^3.0.0",
"mqtt": "^5.14.0",
"pinia": "^2.0.22",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.1",
"uuid": "^11.1.0",
"vue": "^3.5.12",
"vue-router": "^4.4.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.1.4",
"sass": "^1.56.1",
"unplugin-auto-import": "^20.1.0",
"unplugin-vue-components": "^29.0.0",
"vite": "^5.4.10"
}
}

15
postcss.config.js Normal file
View File

@@ -0,0 +1,15 @@
// postcss.config.js
module.exports = {
plugins: [
{
postcssPlugin: "internal:charset-removal",
AtRule: {
charset: (atRule) => {
if (atRule.name === "charset") atRule.remove();
}
}
},
require('tailwindcss'),
require('autoprefixer')
]
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

10
src/App.vue Normal file
View File

@@ -0,0 +1,10 @@
<script setup>
import { RouterLink, RouterView } from 'vue-router'
</script>
<template>
<RouterView />
</template>
<style lang="scss" scoped></style>

36
src/api/login.js Normal file
View File

@@ -0,0 +1,36 @@
import request from '@/utils/request'
// import request from '@/views/custom/Meter/public/request.js'
// 登录方法
export function login(username, password) {
const params = {
username,
password
}
return request({
url: '/api/v1/auth/login',
headers: {
isToken: false
},
method: 'post',
data: params,
})
}
// 获取用户详细信息
export function getInfo(userUid) {
return request({
url: `/api/v1/auth/user/${userUid}`,
method: 'get'
})
}
// 退出方法
export function logout() {
return request({
url: '/api/v1/auth/logout',
method: 'post'
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -0,0 +1,17 @@
<svg width="19" height="18" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d)">
<circle cx="9.99609" cy="9" r="5" fill="white"/>
<circle cx="9.99609" cy="9" r="4.75" stroke="black" stroke-opacity="0.3" stroke-width="0.5"/>
</g>
<defs>
<filter id="filter0_d" x="0.996094" y="0" width="18" height="18" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.137674 0 0 0 0 0.190937 0 0 0 0 0.270833 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 904 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,17 @@
<svg width="12" height="24" viewBox="0 0 12 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d)">
<rect x="4" y="4" width="4" height="16" rx="2" fill="white"/>
<rect x="4.25" y="4.25" width="3.5" height="15.5" rx="1.75" stroke="black" stroke-opacity="0.3" stroke-width="0.5"/>
</g>
<defs>
<filter id="filter0_d" x="0" y="0" width="12" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.137674 0 0 0 0 0.190937 0 0 0 0 0.270833 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 933 B

View File

@@ -0,0 +1,17 @@
<svg width="24" height="12" viewBox="0 0 24 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d)">
<rect x="20" y="4" width="4" height="16" rx="2" transform="rotate(90 20 4)" fill="white"/>
<rect x="19.75" y="4.25" width="3.5" height="15.5" rx="1.75" transform="rotate(90 19.75 4.25)" stroke="black" stroke-opacity="0.3" stroke-width="0.5"/>
</g>
<defs>
<filter id="filter0_d" x="0" y="0" width="24" height="12" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.137674 0 0 0 0 0.190937 0 0 0 0 0.270833 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,20 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d)">
<circle cx="9" cy="9" r="5" fill="white"/>
<circle cx="9" cy="9" r="4.75" stroke="black" stroke-opacity="0.3" stroke-width="0.5"/>
</g>
<path d="M10.8047 11.1242L9.49934 11.1242L9.49934 9.81885" stroke="black" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.94856 6.72607L8.25391 6.72607L8.25391 8.03142" stroke="black" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.69517 6.92267C10.007 7.03301 10.2858 7.22054 10.5055 7.46776C10.7252 7.71497 10.8787 8.01382 10.9517 8.33642C11.0247 8.65902 11.0148 8.99485 10.9229 9.31258C10.831 9.63031 10.6601 9.91958 10.4262 10.1534L9.49701 11.0421M8.25792 6.72607L7.30937 7.73554C7.07543 7.96936 6.90454 8.25863 6.81264 8.57636C6.72073 8.89408 6.71081 9.22992 6.78381 9.55251C6.8568 9.87511 7.01032 10.174 7.23005 10.4212C7.44978 10.6684 7.72855 10.8559 8.04036 10.9663" stroke="black" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
<defs>
<filter id="filter0_d" x="0" y="0" width="18" height="18" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.137674 0 0 0 0 0.190937 0 0 0 0 0.270833 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 60.1 (88133) - https://sketch.com -->
<title>shape-cursor</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M20,21.5 C20.2454599,21.5 20.4496084,21.6768752 20.4919443,21.9101244 L20.5,22 L20.5,27 C20.5,27.2761424 20.2761424,27.5 20,27.5 C19.7545401,27.5 19.5503916,27.3231248 19.5080557,27.0898756 L19.5,27 L19.5,22 C19.5,21.7238576 19.7238576,21.5 20,21.5 Z M27,19.5 C27.2761424,19.5 27.5,19.7238576 27.5,20 C27.5,20.2454599 27.3231248,20.4496084 27.0898756,20.4919443 L27,20.5 L22,20.5 C21.7238576,20.5 21.5,20.2761424 21.5,20 C21.5,19.7545401 21.6768752,19.5503916 21.9101244,19.5080557 L22,19.5 L27,19.5 Z M18,19.5 C18.2761424,19.5 18.5,19.7238576 18.5,20 C18.5,20.2454599 18.3231248,20.4496084 18.0898756,20.4919443 L18,20.5 L13,20.5 C12.7238576,20.5 12.5,20.2761424 12.5,20 C12.5,19.7545401 12.6768752,19.5503916 12.9101244,19.5080557 L13,19.5 L18,19.5 Z M20,12.5 C20.2454599,12.5 20.4496084,12.6768752 20.4919443,12.9101244 L20.5,13 L20.5,18 C20.5,18.2761424 20.2761424,18.5 20,18.5 C19.7545401,18.5 19.5503916,18.3231248 19.5080557,18.0898756 L19.5,18 L19.5,13 C19.5,12.7238576 19.7238576,12.5 20,12.5 Z" id="path-1"></path>
<filter x="-64.6%" y="-59.5%" width="229.3%" height="246.1%" filterUnits="objectBoundingBox" id="filter-2">
<feMorphology radius="1" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"></feMorphology>
<feOffset dx="0" dy="2" in="shadowSpreadOuter1" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="3" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"></feComposite>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="页面-4" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Whiteboard-Guidelines" transform="translate(-344.000000, -751.000000)">
<g id="shape-cursor" transform="translate(344.000000, 751.000000)">
<rect id="矩形备份-44" fill="#FFFFFF" opacity="0.01" x="0" y="0" width="40" height="40" rx="2"></rect>
<g id="形状结合" fill-rule="nonzero">
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
<path stroke="#FFFFFF" stroke-width="1" d="M20,21 C20.4854103,21 20.898085,21.3479993 20.9899479,21.8654877 L21,22 L21,27 C21,27.5522847 20.5522847,28 20,28 C19.5145897,28 19.101915,27.6520007 19.0100521,27.1345123 L19,27 L19,22 C19,21.4477153 19.4477153,21 20,21 Z M27,19 C27.5522847,19 28,19.4477153 28,20 C28,20.4854103 27.6520007,20.898085 27.1345123,20.9899479 L27,21 L22,21 C21.4477153,21 21,20.5522847 21,20 C21,19.5145897 21.3479993,19.101915 21.8654877,19.0100521 L22,19 L27,19 Z M18,19 C18.5522847,19 19,19.4477153 19,20 C19,20.4854103 18.6520007,20.898085 18.1345123,20.9899479 L18,21 L13,21 C12.4477153,21 12,20.5522847 12,20 C12,19.5145897 12.3479993,19.101915 12.8654877,19.0100521 L13,19 L18,19 Z M20,12 C20.4854103,12 20.898085,12.3479993 20.9899479,12.8654877 L21,13 L21,18 C21,18.5522847 20.5522847,19 20,19 C19.5145897,19 19.101915,18.6520007 19.0100521,18.1345123 L19,18 L19,13 C19,12.4477153 19.4477153,12 20,12 Z" fill="#212324" fill-rule="evenodd"></path>
</g>
<rect id="矩形" fill="#FFFFFF" x="18.5" y="17" width="3" height="6"></rect>
<rect id="矩形" fill="#FFFFFF" x="17" y="18.5" width="6" height="3"></rect>
<path d="M20,21.5 C20.2454599,21.5 20.4496084,21.6768752 20.4919443,21.9101244 L20.5,22 L20.5,27 C20.5,27.2761424 20.2761424,27.5 20,27.5 C19.7545401,27.5 19.5503916,27.3231248 19.5080557,27.0898756 L19.5,27 L19.5,22 C19.5,21.7238576 19.7238576,21.5 20,21.5 Z M27,19.5 C27.2761424,19.5 27.5,19.7238576 27.5,20 C27.5,20.2454599 27.3231248,20.4496084 27.0898756,20.4919443 L27,20.5 L22,20.5 C21.7238576,20.5 21.5,20.2761424 21.5,20 C21.5,19.7545401 21.6768752,19.5503916 21.9101244,19.5080557 L22,19.5 L27,19.5 Z M18,19.5 C18.2761424,19.5 18.5,19.7238576 18.5,20 C18.5,20.2454599 18.3231248,20.4496084 18.0898756,20.4919443 L18,20.5 L13,20.5 C12.7238576,20.5 12.5,20.2761424 12.5,20 C12.5,19.7545401 12.6768752,19.5503916 12.9101244,19.5080557 L13,19.5 L18,19.5 Z M20,12.5 C20.2454599,12.5 20.4496084,12.6768752 20.4919443,12.9101244 L20.5,13 L20.5,18 C20.5,18.2761424 20.2761424,18.5 20,18.5 C19.7545401,18.5 19.5503916,18.3231248 19.5080557,18.0898756 L19.5,18 L19.5,13 C19.5,12.7238576 19.7238576,12.5 20,12.5 Z" id="形状结合" fill="#212324" fill-rule="nonzero"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="47px" height="40px" viewBox="0 0 47 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 60.1 (88133) - https://sketch.com -->
<title>text-cursor</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M16,26.5 C15.7238576,26.5 15.5,26.2761424 15.5,26 C15.5,25.7545401 15.6768752,25.5503916 15.9101244,25.5080557 L16,25.5 L19.5,25.5 L19.5,14.5 L16,14.5 C15.7238576,14.5 15.5,14.2761424 15.5,14 C15.5,13.7545401 15.6768752,13.5503916 15.9101244,13.5080557 L16,13.5 L24,13.5 C24.2761424,13.5 24.5,13.7238576 24.5,14 C24.5,14.2454599 24.3231248,14.4496084 24.0898756,14.4919443 L24,14.5 L20.5,14.5 L20.5,25.5 L24,25.5 C24.2761424,25.5 24.5,25.7238576 24.5,26 C24.5,26.2454599 24.3231248,26.4496084 24.0898756,26.4919443 L24,26.5 L16,26.5 Z" id="path-1"></path>
<filter x="-284.0%" y="-81.5%" width="668.1%" height="293.9%" filterUnits="objectBoundingBox" id="filter-2">
<feMorphology radius="1" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"></feMorphology>
<feOffset dx="0" dy="2" in="shadowSpreadOuter1" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="3" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"></feComposite>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="页面-4" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Whiteboard-Guidelines" transform="translate(-388.000000, -672.000000)">
<g id="text-cursor" transform="translate(392.000000, 672.000000)">
<rect id="矩形备份-40" fill="#FFFFFF" opacity="0.01" x="0" y="0" width="40" height="40" rx="2"></rect>
<g id="形状结合" fill-rule="nonzero">
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
<path stroke="#FFFFFF" stroke-width="1" d="M19,25 L19,15 L16,15 C15.4477153,15 15,14.5522847 15,14 C15,13.5145897 15.3479993,13.101915 15.8654877,13.0100521 L16,13 L24,13 C24.5522847,13 25,13.4477153 25,14 C25,14.4854103 24.6520007,14.898085 24.1345123,14.9899479 L24,15 L21,15 L21,25 L24,25 C24.5522847,25 25,25.4477153 25,26 C25,26.4854103 24.6520007,26.898085 24.1345123,26.9899479 L24,27 L16,27 C15.4477153,27 15,26.5522847 15,26 C15,25.5145897 15.3479993,25.101915 15.8654877,25.0100521 L16,25 L19,25 Z" fill="#212324" fill-rule="evenodd"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

1
src/assets/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

View File

@@ -0,0 +1,99 @@
@import './variables.module.scss';
@mixin colorBtn($color) {
background: $color;
&:hover {
color: $color;
&:before,
&:after {
background: $color;
}
}
}
.blue-btn {
@include colorBtn($blue)
}
.light-blue-btn {
@include colorBtn($light-blue)
}
.red-btn {
@include colorBtn($red)
}
.pink-btn {
@include colorBtn($pink)
}
.green-btn {
@include colorBtn($green)
}
.tiffany-btn {
@include colorBtn($tiffany)
}
.yellow-btn {
@include colorBtn($yellow)
}
.pan-btn {
font-size: 14px;
color: #fff;
padding: 14px 36px;
border-radius: 8px;
border: none;
outline: none;
transition: 600ms ease all;
position: relative;
display: inline-block;
&:hover {
background: #fff;
&:before,
&:after {
width: 100%;
transition: 600ms ease all;
}
}
&:before,
&:after {
content: '';
position: absolute;
top: 0;
right: 0;
height: 2px;
width: 0;
transition: 400ms ease all;
}
&::after {
right: inherit;
top: inherit;
left: 0;
bottom: 0;
}
}
.custom-button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
color: #fff;
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: 0;
margin: 0;
padding: 10px 15px;
font-size: 14px;
border-radius: 4px;
}

View File

@@ -0,0 +1,154 @@
.el-collapse {
.collapse__title {
font-weight: 600;
padding: 0 8px;
font-size: 1.2em;
line-height: 1.1em;
}
.el-collapse-item__content {
padding: 0 8px;
}
}
.el-divider--horizontal {
margin-bottom: 10px;
margin-top: 10px;
}
.el-breadcrumb__inner,
.el-breadcrumb__inner a {
font-weight: 400 !important;
}
.el-upload {
input[type='file'] {
display: none !important;
}
}
.el-upload__input {
display: none;
}
.cell {
.el-tag {
margin-right: 0px;
}
}
.small-padding {
.cell {
padding-left: 5px;
padding-right: 5px;
}
}
.fixed-width {
.el-button--mini {
padding: 7px 10px;
width: 60px;
}
}
.status-col {
.cell {
padding: 0 10px;
text-align: center;
.el-tag {
margin-right: 0px;
}
}
}
/*-------------Dialog-------------**/
.el-overlay {
overflow: hidden;
.el-overlay-dialog {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
.el-dialog {
margin: 0 auto !important;
.el-dialog__body {
padding: 15px !important;
}
.el-dialog__header {
padding: 16px 16px 8px 16px;
box-sizing: border-box;
border-bottom: 1px solid var(--el-color-primary);
margin-right: 0;
}
}
}
}
.el-dialog__body {
max-height: calc(90vh - 111px) !important;
overflow-y: auto;
overflow-x: hidden;
}
// refine element ui upload
.upload-container {
.el-upload {
width: 100%;
.el-upload-dragger {
width: 100%;
height: 200px;
}
}
}
// dropdown
.el-dropdown-menu {
a {
display: block;
}
}
// fix date-picker ui bug in filter-item
.el-range-editor.el-input__inner {
display: inline-flex !important;
}
// to fix el-date-picker css style
.el-range-separator {
box-sizing: content-box;
}
.el-menu--collapse > div > .el-submenu > .el-submenu__title .el-submenu__icon-arrow {
display: none;
}
.el-dropdown .el-dropdown-link {
color: var(--el-color-primary) !important;
}
/* 当 el-form 的 inline 属性为 true 时 */
/* 设置 label 的宽度默认为 68px */
.el-form--inline .el-form-item__label {
width: 68px;
}
/* 设置 el-select 的宽度默认为 240px */
.el-form--inline .el-select {
width: 240px;
}
/* 设置 el-input 的宽度默认为 240px */
.el-form--inline .el-input {
width: 240px;
}
//.el-button:hover {
// background-color: red;
//}

View File

@@ -0,0 +1,434 @@
@import "./variables.module.scss";
@import "./mixin.scss";
@import "./transition.scss";
@import "./element-ui.scss";
@import "./sidebar.scss";
@import "./btn.scss";
@import "./ruoyi.scss";
@import 'element-plus/theme-chalk/index.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
height: 100%;
margin: 0;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
Microsoft YaHei, Arial, sans-serif;
}
label {
font-weight: 700;
}
html {
height: 100%;
box-sizing: border-box;
}
#app {
height: 100%;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
.no-padding {
padding: 0px !important;
}
.padding-content {
padding: 4px 0;
}
a:focus,
a:active {
outline: none;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
text-decoration: none;
}
div:focus {
outline: none;
}
.fr {
float: right;
}
.fl {
float: left;
}
.pr-5 {
padding-right: 5px;
}
.pl-5 {
padding-left: 5px;
}
.block {
display: block;
}
.pointer {
cursor: pointer;
}
.inlineBlock {
display: block;
}
.clearfix {
&:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
}
aside {
background: #eef1f6;
padding: 8px 24px;
margin-bottom: 20px;
border-radius: 2px;
display: block;
line-height: 32px;
font-size: 16px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
color: #2c3e50;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
a {
color: #337ab7;
cursor: pointer;
&:hover {
color: rgb(32, 160, 255);
}
}
}
//main-container全局样式
.app-container {
background-color: white;
padding: 30px;
position: relative;
box-shadow: var(--el-box-shadow-light);
}
.components-container {
margin: 30px 50px;
position: relative;
}
// .pagination-container {
// margin-top: 30px;
// }
.text-center {
text-align: center;
}
.sub-navbar {
height: 50px;
line-height: 50px;
position: relative;
width: 100%;
text-align: right;
padding-right: 20px;
transition: 600ms ease position;
background: linear-gradient(90deg,
rgba(32, 182, 249, 1) 0%,
rgba(32, 182, 249, 1) 0%,
rgba(33, 120, 241, 1) 100%,
rgba(33, 120, 241, 1) 100%);
.subtitle {
font-size: 20px;
color: #fff;
}
&.draft {
background: #d0d0d0;
}
&.deleted {
background: #d0d0d0;
}
}
.link-type,
.link-type:focus {
color: #337ab7;
cursor: pointer;
&:hover {
color: rgb(32, 160, 255);
}
}
.filter-container {
padding-bottom: 10px;
.filter-item {
display: inline-block;
vertical-align: middle;
margin-bottom: 10px;
}
}
// form样式修改
.el-form-item--default .el-form-item__label {
position: relative;
line-height: 1.4 !important;
text-align: left;
padding-left: 10px;
display: flex;
align-items: center;
justify-content: flex-start;
}
.el-form-item--default .el-form-item__label::before {
position: absolute;
left: 0;
}
// form-item全局配置
.form-100 {
width: 100%;
margin-right: 0 !important;
.el-select {
width: 100%;
}
.el-input-number {
width: 100%;
}
}
.form-50 {
width: 50%;
margin-right: 0 !important;
.el-select {
width: 100%;
}
.el-input-number {
width: 100%;
}
}
// 详情展示表格
.agency-detail-massage-cont {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
width: 100%;
// border-top: solid 1px rgb(235,238,245);
// border-right: solid 1px rgb(235,238,245);
}
.agency-detail-cont-title {
width: 100%;
font-size: 20px;
color: #333;
font-weight: 900;
margin-bottom: 10px;
}
// 如果item是单数需要手动加一个空item 暂时未有解决方法
.agency-detail-cont-item {
display: flex;
justify-content: space-between;
align-items: center;
width: 50%;
// border-bottom: solid 1px rgb(235,238,245);
}
.agency-detail-item-title {
flex: 3;
display: inline-flex;
justify-content: flex-start;
align-items: center;
width: 20%;
height: calc(100%);
padding: 15px 0;
padding-left: 23px;
flex-shrink: 0;
color: #333;
font-size: 16px;
font-weight: 600;
// border-left: solid 1px rgb(235,238,245);
// background-color: rgb(248,248,249);
}
.agency-detail-item-content {
flex: 7;
display: inline-flex;
justify-content: flex-start;
align-items: center;
width: 70%;
height: calc(100%);
padding: 8px 0 8px;
padding-left: 23px;
color: #333;
font-size: 16px;
font-weight: 400;
// border-left: solid 1px rgb(235,238,245);
}
.agency-detail-item-img {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-start;
}
// 单行文本超出隐藏
.text-out-of-hiding-1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// 两行文本超出隐藏
.text-out-of-hiding-2 {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.el-table .el-table__header-wrapper th,
.el-table .el-table__fixed-header-wrapper th {
background-color: #ededed !important;
}
// ======================================================================== app/components list class
.container-application {
box-sizing: border-box;
padding: 0 20px 8px;
.liation-checkbox {
display: flex;
position: absolute;
left: 20px;
top: 20px;
height: 34px !important;
z-index: 10;
}
.application-list {
line-height: 0;
margin-top: 20px;
height: auto;
box-shadow: 0 1px 10px -1px #999999;
border-radius: 6px;
overflow: hidden;
position: relative;
padding: 15px;
padding-bottom: 0;
.application-img {
position: relative;
width: 100%;
// overflow: hidden;
}
.application-img::before {
content: "";
display: block;
padding-top: 100%;
}
.application-img-value {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.application-icon {
position: absolute;
left: 16px;
top: 18px;
display: flex;
font-size: 30px;
z-index: 10;
cursor: pointer;
.application-icon-img {
display: flex;
width: 30px;
height: 30px;
margin-top: 2px;
margin-left: 4px;
}
}
.list-icon {
width: 34px;
height: 34px;
position: absolute;
right: 20px;
top: 20px;
z-index: 10;
cursor: pointer;
}
.list-text {
width: 100%;
height: 45px;
margin-top: 10px;
line-height: 45px;
font-size: 17px;
color: rgb(38, 38, 38);
font-weight: 700;
}
.list-content {
width: 100%;
height: 50px;
.list-content-text {
line-height: 1.2;
font-size: 14px;
color: rgb(96, 98, 102);
}
}
}
}
.application-list-selectitem-icon {
width: 14px;
height: 14px;
margin-right: 5px;
}

View File

@@ -0,0 +1,66 @@
@mixin clearfix {
&:after {
content: "";
display: table;
clear: both;
}
}
@mixin scrollBar {
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
@mixin relative {
position: relative;
width: 100%;
height: 100%;
}
@mixin pct($pct) {
width: #{$pct};
position: relative;
margin: 0 auto;
}
@mixin triangle($width, $height, $color, $direction) {
$width: $width/2;
$color-border-style: $height solid $color;
$transparent-border-style: $width solid transparent;
height: 0;
width: 0;
@if $direction==up {
border-bottom: $color-border-style;
border-left: $transparent-border-style;
border-right: $transparent-border-style;
}
@else if $direction==right {
border-left: $color-border-style;
border-top: $transparent-border-style;
border-bottom: $transparent-border-style;
}
@else if $direction==down {
border-top: $color-border-style;
border-left: $transparent-border-style;
border-right: $transparent-border-style;
}
@else if $direction==left {
border-right: $color-border-style;
border-top: $transparent-border-style;
border-bottom: $transparent-border-style;
}
}

View File

@@ -0,0 +1,309 @@
/**
* 通用css样式布局处理
* Copyright (c) 2019
*/
/** 基础通用 **/
.pt5 {
padding-top: 5px;
}
.pr5 {
padding-right: 5px;
}
.pb5 {
padding-bottom: 5px;
}
.mt5 {
margin-top: 5px;
}
.mr5 {
margin-right: 5px;
}
.mb5 {
margin-bottom: 5px;
}
.mb8 {
margin-bottom: 8px;
}
.ml5 {
margin-left: 5px;
}
.mt10 {
margin-top: 10px;
}
.mr10 {
margin-right: 10px;
}
.mb10 {
margin-bottom: 10px;
}
.ml10 {
margin-left: 10px;
}
.mt20 {
margin-top: 20px;
}
.mr20 {
margin-right: 20px;
}
.mb20 {
margin-bottom: 20px;
}
.ml20 {
margin-left: 20px;
}
.h1,
.h2,
.h3,
.h4,
.h5,
.h6,
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
}
.el-form .el-form-item__label {
font-weight: 700;
}
.el-dialog:not(.is-fullscreen) {
margin-top: 6vh !important;
}
.el-dialog.scrollbar .el-dialog__body {
overflow: auto;
overflow-x: hidden;
max-height: 70vh;
padding: 10px 20px 0;
}
.el-table {
.el-table__header-wrapper,
.el-table__fixed-header-wrapper {
th {
word-break: break-word;
background-color: #f8f8f9 !important;
color: #515a6e;
height: 40px !important;
font-size: 13px;
}
}
.el-table__body-wrapper {
.el-button [class*="el-icon-"]+span {
margin-left: 1px;
}
}
}
/** 表单布局 **/
.form-header {
font-size: 15px;
color: #6379bb;
border-bottom: 1px solid #ddd;
margin: 8px 10px 25px 10px;
padding-bottom: 5px;
}
/** 表格布局 **/
.pagination-container {
// position: relative;
height: 25px;
margin-bottom: 10px;
margin-top: 15px;
padding: 10px 20px !important;
}
/* tree border */
.tree-border {
margin-top: 5px;
border: 1px solid #e5e6e7;
background: #ffffff none;
border-radius: 4px;
width: 100%;
}
.pagination-container .el-pagination {
right: 0;
position: absolute;
}
@media (max-width: 768px) {
.pagination-container .el-pagination>.el-pagination__jump {
display: none !important;
}
.pagination-container .el-pagination>.el-pagination__sizes {
display: none !important;
}
}
.el-table .fixed-width .el-button--small {
padding-left: 0;
padding-right: 0;
width: inherit;
}
/** 表格更多操作下拉样式 */
.el-table .el-dropdown-link {
cursor: pointer;
color: #409eff;
margin-left: 10px;
}
.el-table .el-dropdown,
.el-icon-arrow-down {
font-size: 12px;
}
.el-tree-node__content>.el-checkbox {
margin-right: 8px;
}
.list-group-striped>.list-group-item {
border-left: 0;
border-right: 0;
border-radius: 0;
padding-left: 0;
padding-right: 0;
}
.list-group {
padding-left: 0px;
list-style: none;
}
.list-group-item {
border-bottom: 1px solid #e7eaec;
border-top: 1px solid #e7eaec;
margin-bottom: -1px;
padding: 11px 0px;
font-size: 13px;
}
.pull-right {
float: right !important;
}
.el-card__header {
padding: 14px 15px 7px !important;
min-height: 40px;
}
.el-card__body {
padding: 15px 20px 20px 20px !important;
}
.card-box {
padding-right: 15px;
padding-left: 15px;
margin-bottom: 10px;
}
/* button color */
.el-button--cyan.is-active,
.el-button--cyan:active {
background: #20b2aa;
border-color: #20b2aa;
color: #ffffff;
}
.el-button--cyan:focus,
.el-button--cyan:hover {
background: #48d1cc;
border-color: #48d1cc;
color: #ffffff;
}
.el-button--cyan {
background-color: #20b2aa;
border-color: #20b2aa;
color: #ffffff;
}
/* text color */
.text-navy {
color: #1ab394;
}
.text-primary {
color: inherit;
}
.text-success {
color: #1c84c6;
}
.text-info {
color: #23c6c8;
}
.text-warning {
color: #f8ac59;
}
.text-danger {
color: #ed5565;
}
.text-muted {
color: #888888;
}
/* image */
.img-circle {
border-radius: 50%;
}
.img-lg {
width: 120px;
height: 120px;
}
.avatar-upload-preview {
position: absolute;
top: 50%;
transform: translate(50%, -50%);
width: 200px;
height: 200px;
border-radius: 50%;
box-shadow: 0 0 4px #ccc;
overflow: hidden;
}
/* 拖拽列样式 */
.sortable-ghost {
opacity: 0.8;
color: #fff !important;
background: #42b983 !important;
}
/* 表格右侧工具栏样式 */
.top-right-btn {
margin-left: auto;
}

View File

@@ -0,0 +1,265 @@
$menu-bg-color: #8290f0;
#app {
.main-container {
min-height: 100%;
transition: margin-left .28s;
// margin-left: $base-sidebar-width;
position: relative;
}
.sidebarHide {
margin-left: 0 !important;
}
// @media screen and (max-width: 995px) {
// .sidebar-container {
// position: fixed !important;
// }
// }
.sidebar-container {
-webkit-transition: width .28s;
transition: width 0.28s;
width: $base-sidebar-width !important;
background-color: $base-menu-background;
height: 100%;
position: relative;
font-size: 0px;
top: 0;
bottom: 0;
left: 0;
z-index: 1000;
overflow: hidden;
// reset element-ui css
.horizontal-collapse-transition {
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
}
.scrollbar-wrapper {
overflow-x: hidden !important;
}
.el-scrollbar__bar.is-vertical {
right: 0px;
}
.el-scrollbar {
height: 100%;
}
&.has-logo {
.el-scrollbar {
height: calc(100% - 50px);
}
}
.is-horizontal {
display: none;
}
a {
display: inline-block;
width: 100%;
overflow: hidden;
}
.el-menu {
border: none;
height: 100%;
width: 100% !important;
}
.el-menu-item,
.menu-title {
overflow: hidden !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
}
.menu-title {
margin-left: 10px;
}
.el-menu-item .el-menu-tooltip__trigger {
display: flex;
align-items: center;
justify-content: center;
}
// menu hover
.sub-menu-title-noDropdown,
.el-sub-menu__title {
&:hover {
background-color: #c4cbf3 !important;
}
}
.el-menu-item:hover {
background-color: #c4cbf3 !important;
}
& .theme-dark .is-active>.el-sub-menu__title {
color: $base-menu-color-active !important;
}
& .nest-menu .el-sub-menu>.el-sub-menu__title,
& .el-sub-menu .el-menu-item {
min-width: $base-sidebar-width !important;
&:hover {
background-color: #c4cbf3 !important;
}
}
& .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title,
& .theme-dark .el-sub-menu .el-menu-item {
background-color: $base-sub-menu-background !important;
&:hover {
background-color: $base-sub-menu-hover !important;
}
}
& .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title,
& .theme-dark .el-sub-menu .el-menu-item.is-active {
color: #ffffff !important;
background-color: #{$menu-bg-color} !important; // 点击菜单的颜色
}
.el-menu-item.is-active {
color: #ffffff !important;
background-color: #{$menu-bg-color} !important; // 点击菜单的颜色
}
}
.hideSidebar {
.sidebar-container {
// width: 54px !important;
height: calc(100vh - 50px);
background-color: red;
}
.main-container {
margin-left: 0;
}
.sub-menu-title-noDropdown {
padding: 0 !important;
position: relative;
.el-tooltip {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
}
}
.el-sub-menu {
overflow: hidden;
&>.el-sub-menu__title {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
}
}
// .el-menu--collapse {
// .el-sub-menu {
// &>.el-sub-menu__title {
// &>span {
// height: 0;
// width: 0;
// overflow: hidden;
// visibility: hidden;
// display: inline-block;
// }
// &>i {
// height: 0;
// width: 0;
// overflow: hidden;
// visibility: hidden;
// display: inline-block;
// }
// }
// }
// }
}
.el-menu--collapse .el-menu .el-sub-menu {
min-width: $base-sidebar-width !important;
}
// mobile responsive
.mobile {
.main-container {
margin-left: 0px;
}
.sidebar-container {
transition: transform .28s;
width: $base-sidebar-width !important;
}
// &.hideSidebar {
// .sidebar-container {
// pointer-events: none;
// transition-duration: 0.3s;
// transform: translate3d(-$base-sidebar-width, 0, 0);
// }
// }
}
.withoutAnimation {
.main-container,
.sidebar-container {
transition: none;
}
}
}
// when menu collapsed
.el-menu--vertical {
&>.el-menu {
.svg-icon {
margin-right: 16px;
}
}
.nest-menu .el-sub-menu>.el-sub-menu__title,
.el-menu-item {
&:hover {
// you can use $sub-menuHover
background-color: FF7B5D !important;
}
}
// the scroll bar appears when the sub-menu is too long
>.el-menu--popup {
max-height: 100vh;
overflow-y: auto;
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
}

View File

@@ -0,0 +1,49 @@
// global transition css
/* fade */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.28s;
}
.fade-enter,
.fade-leave-active {
opacity: 0;
}
/* fade-transform */
.fade-transform--move,
.fade-transform-leave-active,
.fade-transform-enter-active {
transition: all .5s;
}
.fade-transform-enter {
opacity: 0;
transform: translateX(-30px);
}
.fade-transform-leave-to {
opacity: 0;
transform: translateX(30px);
}
/* breadcrumb transition */
.breadcrumb-enter-active,
.breadcrumb-leave-active {
transition: all .5s;
}
.breadcrumb-enter,
.breadcrumb-leave-active {
opacity: 0;
transform: translateX(20px);
}
.breadcrumb-move {
transition: all .5s;
}
.breadcrumb-leave-active {
position: absolute;
}

View File

@@ -0,0 +1,65 @@
// base color
$blue: #324157;
$light-blue: #3A71A8;
$red: #C03639;
$pink: #E65D6E;
$green: #30B08F;
$tiffany: #4AB7BD;
$yellow: #FEC171;
$panGreen: #30B08F;
// 默认菜单主题风格
$base-menu-color: #000;
$base-menu-color-active: #000;
$base-menu-background: #fff;
$base-logo-title-color: #ffffff;
$base-menu-light-color: rgba(0, 0, 0, 0.7);
$base-menu-light-background: #ffffff;
$base-logo-light-title-color: #001529;
$base-sub-menu-background: #fff;
$base-sub-menu-hover: #c4cbf3;
// 自定义暗色菜单风格
/**
$base-menu-color:hsla(0,0%,100%,.65);
$base-menu-color-active:#fff;
$base-menu-background:#001529;
$base-logo-title-color: #ffffff;
$base-menu-light-color:rgba(0,0,0,.70);
$base-menu-light-background:#ffffff;
$base-logo-light-title-color: #001529;
$base-sub-menu-background:#000c17;
$base-sub-menu-hover:#001528;
*/
$--color-primary: #409EFF;
$--color-success: #67C23A;
$--color-warning: #E6A23C;
$--color-danger: #F56C6C;
$--color-info: #909399;
$base-sidebar-width: 200px;
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
menuColor: $base-menu-color;
menuLightColor: $base-menu-light-color;
menuColorActive: $base-menu-color-active;
menuBackground: $base-menu-background;
menuLightBackground: $base-menu-light-background;
subMenuBackground: $base-sub-menu-background;
subMenuHover: $base-sub-menu-hover;
sideBarWidth: $base-sidebar-width;
logoTitleColor: $base-logo-title-color;
logoLightTitleColor: $base-logo-light-title-color;
primaryColor: $--color-primary;
successColor: $--color-success;
dangerColor: $--color-danger;
infoColor: $--color-info;
warningColor: $--color-warning;
}

View File

@@ -0,0 +1,206 @@
<template>
<div class="wrapper-content">
<div class="login-form">
<div class="selected-rectangle"></div>
<el-form ref="loginRef" class="form-info" :model="loginForm" :rules="loginRules">
<h3 class="title">欢迎登录</h3>
<el-form-item prop="username">
<el-input v-model="loginForm.username" auto-complete="off" placeholder="请输入您的账号" size="large"
type="text"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginForm.password" auto-complete="off" placeholder="请输入密码" size="large"
type="password" @keyup.enter="handleLogin"></el-input>
</el-form-item>
<el-form-item>
<el-button :loading="loading" size="large" class="button-login" type="primary"
@click.prevent="handleLogin">
<span v-if="!loading"> </span>
<span v-else>登录中...</span>
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup>
import useUserStore from '@/stores/modules/user'
import { watch, ref, getCurrentInstance } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const userStore = useUserStore()
const router = useRouter()
const { proxy } = getCurrentInstance()
const redirect = ref(undefined);
const emit = defineEmits(['loginSuccess'])
const loginForm = ref({
username: '',
password: '',
})
const loginRules = {
username: [{ required: true, trigger: 'blur', message: '请输入用户名' }],
password: [{ required: true, trigger: 'blur', message: '请输入密码' }],
}
const loading = ref(false)
function handleLogin() {
proxy.$refs.loginRef.validate((valid) => {
if (valid) {
loading.value = true
// 调用action的登录方法
userStore
.login({
password: loginForm.value.password,
username: loginForm.value.username,
})
.then(async (res) => {
const userInfo = JSON.parse(localStorage.getItem('userData'))
emit('loginSuccess', userInfo)
})
.catch((e) => {
console.log('登录失败', e)
loading.value = false
})
}
})
requestNotificationPermission()
}
/**
* @description 请求浏览器的通知权限
* @returns {*}
*/
function requestNotificationPermission() {
if ('Notification' in window) {
Notification.requestPermission().then(() => { })
} else {
console.log('浏览器不支持通知')
}
}
</script>
<style lang="scss" scoped>
.wrapper-content {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
padding: 0 40px;
background: linear-gradient(135deg, #f5f7fa 0%, #eef1f6 100%);
}
.login-form {
width: 100%;
max-width: 520px;
background: #fff;
border-radius: 20px;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
padding: 60px 50px;
display: flex;
flex-direction: column;
align-items: stretch;
position: relative;
.form-info {
width: 100%;
.title {
font-size: 28px;
font-weight: 600;
color: #051435;
margin-bottom: 40px;
}
.el-form-item {
margin-bottom: 28px;
}
:deep(.el-input__wrapper) {
border-radius: 40px;
background: #f8f9fb;
border: 1px solid #dbe1ec;
padding: 0 22px;
transition: border-color 0.25s ease;
input {
height: 48px;
font-size: 16px;
color: #051435;
}
&:hover,
&.is-focus {
border-color: #00c8c4;
box-shadow: 0 0 0 2px rgba(0, 200, 196, 0.15);
}
}
:deep(.el-form-item__error) {
font-size: 13px;
margin-top: 6px;
color: #f56c6c;
}
.button-login {
width: 100%;
height: 50px;
margin-top: 10px;
border-radius: 35px;
font-size: 18px;
font-weight: 500;
background: linear-gradient(135deg, #00c8c4, #00a5a1);
border: none;
color: #fff;
letter-spacing: 1px;
transition: background 0.3s ease, transform 0.2s ease;
&:hover {
background: linear-gradient(135deg, #00a5a1, #008f8b);
transform: translateY(-2px);
}
&:active {
transform: translateY(0);
}
}
}
}
/* 移动端优化 */
@media screen and (max-width: 768px) {
.wrapper-content {
padding: 20px;
}
.login-form {
padding: 40px 25px;
max-width: 100%;
.form-info {
.title {
font-size: 22px;
margin-bottom: 30px;
}
.button-login {
height: 44px;
font-size: 16px;
}
:deep(.el-input__wrapper) {
padding: 0 18px;
input {
height: 42px;
font-size: 15px;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 24</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="切图" transform="translate(-48.000000, -152.000000)">
<g id="编组-4备份" transform="translate(48.000000, 152.000000)">
<line x1="6" y1="18" x2="16.4593589" y2="7.54064109" id="路径-20" stroke="#3381FF" stroke-linecap="square" stroke-linejoin="round"></line>
<polygon id="矩形" fill="#3381FF" points="12 6 18 6 18 12"></polygon>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 746 B

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 24</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="切图" transform="translate(-8.000000, -152.000000)">
<g id="编组-4备份" transform="translate(8.000000, 152.000000)">
<line x1="6" y1="18" x2="16.4593589" y2="7.54064109" id="路径-20" stroke="#444E60" stroke-linecap="square" stroke-linejoin="round"></line>
<polygon id="矩形" fill="#444E60" points="12 6 18 6 18 12"></polygon>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 744 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="切图" transform="translate(-48.000000, -476.000000)" stroke="#3381FF">
<g id="编组-9备份" transform="translate(48.000000, 476.000000)">
<path d="M10.6666667,9.66666667 L10.6666667,6.16666667 C10.6666667,5.52233446 11.1890011,5 11.8333333,5 C12.4776655,5 13,5.52233446 13,6.16666667 L13,9.66666667 L13,9.66666667 C14.2886644,9.66666667 15.3333333,10.7113356 15.3333333,12 L15.3333333,12 L15.3333333,12 L8.33333333,12 C8.33333333,10.7113356 9.37800225,9.66666667 10.6666667,9.66666667 L10.6666667,9.66666667 L10.6666667,9.66666667 Z" id="路径-47"></path>
<path d="M15.3333333,12 L17.6666667,17.8333333 C13.8159078,18.6034851 9.85075886,18.6034851 6,17.8333333 L8.33333333,12" id="路径"></path>
<line x1="11.8333333" y1="17.8333333" x2="11.8333333" y2="15.5" id="路径-49"></line>
<line x1="9.5" y1="17.8333333" x2="9.5" y2="15.5" id="路径-51"></line>
<line x1="14.1666667" y1="17.8333333" x2="14.1666667" y2="15.5" id="路径-52"></line>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="小班课" transform="translate(-16.000000, -549.000000)" stroke="#444E60">
<g id="编组-35" transform="translate(8.000000, 285.000000)">
<g id="编组-9" transform="translate(8.000000, 264.000000)">
<path d="M10.6666667,9.66666667 L10.6666667,6.16666667 C10.6666667,5.52233446 11.1890011,5 11.8333333,5 C12.4776655,5 13,5.52233446 13,6.16666667 L13,9.66666667 L13,9.66666667 C14.2886644,9.66666667 15.3333333,10.7113356 15.3333333,12 L15.3333333,12 L15.3333333,12 L8.33333333,12 C8.33333333,10.7113356 9.37800225,9.66666667 10.6666667,9.66666667 L10.6666667,9.66666667 L10.6666667,9.66666667 Z" id="路径-47"></path>
<path d="M15.3333333,12 L17.6666667,17.8333333 C13.8159078,18.6034851 9.85075886,18.6034851 6,17.8333333 L8.33333333,12" id="路径"></path>
<line x1="11.8333333" y1="17.8333333" x2="11.8333333" y2="15.5" id="路径-49"></line>
<line x1="9.5" y1="17.8333333" x2="9.5" y2="15.5" id="路径-51"></line>
<line x1="14.1666667" y1="17.8333333" x2="14.1666667" y2="15.5" id="路径-52"></line>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>click_p@1x</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="编组-4备份-2">
<polygon id="路径-5" stroke="#444E60" points="7 5.07179677 8.66987298 17.9641016 11.9090909 13.5745916 17.330127 12.9641016"></polygon>
<line x1="12" y1="13.7320508" x2="15" y2="18.9282032" id="路径-7" stroke="#444E60"></line>
<polygon id="路径-5" stroke="#3381FF" points="7 5.07179677 8.66987298 17.9641016 11.9090909 13.5745916 17.330127 12.9641016"></polygon>
<line x1="12" y1="13.7320508" x2="15" y2="18.9282032" id="路径-7" stroke="#3381FF"></line>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 918 B

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>click@1x</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="编组-4" stroke="#444E60">
<polygon id="路径-5" points="7 5.07179677 8.66987298 17.9641016 11.9090909 13.5745916 17.330127 12.9641016"></polygon>
<line x1="12" y1="13.7320508" x2="15" y2="18.9282032" id="路径-7"></line>
<polygon id="路径-5" points="7 5.07179677 8.66987298 17.9641016 11.9090909 13.5745916 17.330127 12.9641016"></polygon>
<line x1="12" y1="13.7320508" x2="15" y2="18.9282032" id="路径-7"></line>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 857 B

View File

@@ -0,0 +1 @@
<svg t="1757644652588" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8843" width="18" height="18"><path d="M512 85.3248c235.5968 0 426.6752 169.728 426.6752 379.264a237.1328 237.1328 0 0 1-237.056 237.0304H617.728a71.0656 71.0656 0 0 0-71.1168 71.1168c0 17.9968 7.1168 34.6112 17.9968 46.9248a71.68 71.68 0 0 1 18.5088 47.872c0 39.3984-32.7168 71.1424-71.1168 71.1424-235.5968 0-426.6752-191.0784-426.6752-426.6752 0-235.5968 191.0784-426.6752 426.6752-426.6752z m-50.7392 687.4112a156.3136 156.3136 0 0 1 156.4672-156.4672h83.8912a151.808 151.808 0 0 0 151.7056-151.6288c0-160.0512-150.6816-293.9648-341.3248-293.9648a341.3504 341.3504 0 0 0-28.8512 681.472 155.648 155.648 0 0 1-21.888-79.36v-0.0512zM320 512a64 64 0 1 1 0-128 64 64 0 0 1 0 128z m384 0a64 64 0 1 1 0-128 64 64 0 0 1 0 128zM512 384a64 64 0 1 1 0-128 64 64 0 0 1 0 128z" p-id="8844" fill="#3381ff"></path></svg>

After

Width:  |  Height:  |  Size: 924 B

View File

@@ -0,0 +1 @@
<svg t="1757644652588" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8843" width="18" height="18"><path d="M512 85.3248c235.5968 0 426.6752 169.728 426.6752 379.264a237.1328 237.1328 0 0 1-237.056 237.0304H617.728a71.0656 71.0656 0 0 0-71.1168 71.1168c0 17.9968 7.1168 34.6112 17.9968 46.9248a71.68 71.68 0 0 1 18.5088 47.872c0 39.3984-32.7168 71.1424-71.1168 71.1424-235.5968 0-426.6752-191.0784-426.6752-426.6752 0-235.5968 191.0784-426.6752 426.6752-426.6752z m-50.7392 687.4112a156.3136 156.3136 0 0 1 156.4672-156.4672h83.8912a151.808 151.808 0 0 0 151.7056-151.6288c0-160.0512-150.6816-293.9648-341.3248-293.9648a341.3504 341.3504 0 0 0-28.8512 681.472 155.648 155.648 0 0 1-21.888-79.36v-0.0512zM320 512a64 64 0 1 1 0-128 64 64 0 0 1 0 128z m384 0a64 64 0 1 1 0-128 64 64 0 0 1 0 128zM512 384a64 64 0 1 1 0-128 64 64 0 0 1 0 128z" p-id="8844" fill="#444e60"></path></svg>

After

Width:  |  Height:  |  Size: 924 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>folder备份</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linejoin="round">
<g id="切图" transform="translate(-48.000000, -368.000000)" stroke="#3381FF">
<g id="folder备份" transform="translate(48.000000, 368.000000)">
<rect id="矩形" x="6" y="6" width="12" height="12" rx="6"></rect>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 621 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>folder备份</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linejoin="round">
<g id="切图" transform="translate(-8.000000, -368.000000)" stroke="#444E60">
<g id="folder备份" transform="translate(8.000000, 368.000000)">
<rect id="矩形" x="6" y="6" width="12" height="12" rx="6"></rect>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 619 B

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 25</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="切图" transform="translate(-48.000000, -116.000000)" stroke="#3381FF">
<g id="编组-3" transform="translate(48.000000, 116.000000)">
<line x1="11" y1="8" x2="16" y2="13" id="路径-26"></line>
<path d="M15.5,6.16666667 L17.8333333,8.5 C18.4776655,9.14433221 18.4776655,10.1890011 17.8333333,10.8333333 L12,16.6666667 C10.7113356,17.9553311 8.62199775,17.9553311 7.33333333,16.6666667 L6.16666667,15.5 C5.52233446,14.8556678 5.52233446,13.8109989 6.16666667,13.1666667 L13.1666667,6.16666667 C13.8109989,5.52233446 14.8556678,5.52233446 15.5,6.16666667 Z" id="矩形" stroke-linecap="round" stroke-linejoin="round"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 25</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="切图" transform="translate(-8.000000, -116.000000)" stroke="#444E60">
<g id="编组-3" transform="translate(8.000000, 116.000000)">
<line x1="11" y1="8" x2="16" y2="13" id="路径-26"></line>
<path d="M15.5,6.16666667 L17.8333333,8.5 C18.4776655,9.14433221 18.4776655,10.1890011 17.8333333,10.8333333 L12,16.6666667 C10.7113356,17.9553311 8.62199775,17.9553311 7.33333333,16.6666667 L6.16666667,15.5 C5.52233446,14.8556678 5.52233446,13.8109989 6.16666667,13.1666667 L13.1666667,6.16666667 C13.8109989,5.52233446 14.8556678,5.52233446 15.5,6.16666667 Z" id="矩形" stroke-linecap="round" stroke-linejoin="round"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 24</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="切图" transform="translate(-48.000000, -224.000000)" stroke="#3381FF">
<g id="编组备份" transform="translate(48.000000, 224.000000)">
<path d="M12.5,5 C13.0522847,5 13.5,5.44771525 13.5,6 L13.5,7 C13.5,6.44771525 13.9477153,6 14.5,6 C15.0522847,6 15.5,6.44771525 15.5,7 L15.5,7 L15.5,9 C15.5,8.44771525 15.9477153,8 16.5,8 C17.0522847,8 17.5,8.44771525 17.5,9 L17.5,9 L17.5,15 C17.5,17.209139 15.709139,19 13.5,19 L12.1375846,19 C10.5374722,19 9.09131968,18.0464126 8.46100451,16.5756772 L6.77854301,12.6499337 C6.61031263,12.2573961 6.69801722,11.8019828 7,11.5 C7.27614237,11.2238576 7.72385763,11.2238576 8,11.5 L9.5,13 L9.5,7 L9.50672773,6.88337887 C9.56449284,6.38604019 9.98716416,6 10.5,6 C11.0522847,6 11.5,6.44771525 11.5,7 L11.5,7 L11.5,6 C11.5,5.44771525 11.9477153,5 12.5,5 Z M11.5,6 L11.5,10 M13.5,6 L13.5,10 M15.5,8 L15.5,10" id="形状结合备份" transform="translate(12.000000, 12.000000) rotate(45.000000) translate(-12.000000, -12.000000) "></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 24</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="切图" transform="translate(-8.000000, -224.000000)" stroke="#444E60">
<g id="编组备份" transform="translate(8.000000, 224.000000)">
<path d="M12.5,5 C13.0522847,5 13.5,5.44771525 13.5,6 L13.5,7 C13.5,6.44771525 13.9477153,6 14.5,6 C15.0522847,6 15.5,6.44771525 15.5,7 L15.5,7 L15.5,9 C15.5,8.44771525 15.9477153,8 16.5,8 C17.0522847,8 17.5,8.44771525 17.5,9 L17.5,9 L17.5,15 C17.5,17.209139 15.709139,19 13.5,19 L12.1375846,19 C10.5374722,19 9.09131968,18.0464126 8.46100451,16.5756772 L6.77854301,12.6499337 C6.61031263,12.2573961 6.69801722,11.8019828 7,11.5 C7.27614237,11.2238576 7.72385763,11.2238576 8,11.5 L9.5,13 L9.5,7 L9.50672773,6.88337887 C9.56449284,6.38604019 9.98716416,6 10.5,6 C11.0522847,6 11.5,6.44771525 11.5,7 L11.5,7 L11.5,6 C11.5,5.44771525 11.9477153,5 12.5,5 Z M11.5,6 L11.5,10 M13.5,6 L13.5,10 M15.5,8 L15.5,10" id="形状结合备份" transform="translate(12.000000, 12.000000) rotate(45.000000) translate(-12.000000, -12.000000) "></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 25</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="切图" transform="translate(-48.000000, -188.000000)">
<g id="编组-3备份" transform="translate(48.000000, 188.000000)">
<circle id="椭圆形" fill="#3381FF" cx="12" cy="12" r="2"></circle>
<line x1="12" y1="4" x2="12" y2="6" id="路径-4" stroke="#3381FF" stroke-linecap="square" stroke-linejoin="round"></line>
<line x1="12" y1="18" x2="12" y2="20" id="路径-4备份" stroke="#3381FF" stroke-linecap="square" stroke-linejoin="round"></line>
<line x1="20" y1="12" x2="18" y2="12" id="路径-4" stroke="#3381FF" stroke-linecap="square" stroke-linejoin="round"></line>
<line x1="6" y1="12" x2="4" y2="12" id="路径-4备份" stroke="#3381FF" stroke-linecap="square" stroke-linejoin="round"></line>
<line x1="17.6568542" y1="17.6568542" x2="16.2426407" y2="16.2426407" id="路径-4" stroke="#3381FF" stroke-linecap="square" stroke-linejoin="round"></line>
<line x1="7.75735931" y1="7.75735931" x2="6.34314575" y2="6.34314575" id="路径-4备份" stroke="#3381FF" stroke-linecap="square" stroke-linejoin="round"></line>
<line x1="6.34314575" y1="17.6568542" x2="7.75735931" y2="16.2426407" id="路径-4" stroke="#3381FF" stroke-linecap="square" stroke-linejoin="round"></line>
<line x1="16.2426407" y1="7.75735931" x2="17.6568542" y2="6.34314575" id="路径-4备份" stroke="#3381FF" stroke-linecap="square" stroke-linejoin="round"></line>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 25</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="切图" transform="translate(-8.000000, -188.000000)">
<g id="编组-3备份" transform="translate(8.000000, 188.000000)">
<circle id="椭圆形" fill="#444E60" cx="12" cy="12" r="2"></circle>
<line x1="12" y1="4" x2="12" y2="6" id="路径-4" stroke="#444E60" stroke-linecap="square" stroke-linejoin="round"></line>
<line x1="12" y1="18" x2="12" y2="20" id="路径-4备份" stroke="#444E60" stroke-linecap="square" stroke-linejoin="round"></line>
<line x1="20" y1="12" x2="18" y2="12" id="路径-4" stroke="#444E60" stroke-linecap="square" stroke-linejoin="round"></line>
<line x1="6" y1="12" x2="4" y2="12" id="路径-4备份" stroke="#444E60" stroke-linecap="square" stroke-linejoin="round"></line>
<line x1="17.6568542" y1="17.6568542" x2="16.2426407" y2="16.2426407" id="路径-4" stroke="#444E60" stroke-linecap="square" stroke-linejoin="round"></line>
<line x1="7.75735931" y1="7.75735931" x2="6.34314575" y2="6.34314575" id="路径-4备份" stroke="#444E60" stroke-linecap="square" stroke-linejoin="round"></line>
<line x1="6.34314575" y1="17.6568542" x2="7.75735931" y2="16.2426407" id="路径-4" stroke="#444E60" stroke-linecap="square" stroke-linejoin="round"></line>
<line x1="16.2426407" y1="7.75735931" x2="17.6568542" y2="6.34314575" id="路径-4备份" stroke="#444E60" stroke-linecap="square" stroke-linejoin="round"></line>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="170px" height="29px" viewBox="0 0 170 29" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 55.1 (78136) - https://sketchapp.com -->
<title>mask (1)</title>
<desc>Created with Sketch.</desc>
<g id="页面1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="mask-(1)" transform="translate(0.000000, -0.177515)" fill="#FFFFFF" fill-rule="nonzero">
<path d="M170,0.177514793 L170,29.1775148 L0,29.1775148 L0,0.177514793 L170,0.177514793 Z M155.702875,7.70508717 L155.434123,7.71050163 L8.49907471,13.4220166 C7.82142679,13.4483583 7.28571429,14.0026739 7.28571429,14.6775148 C7.28571429,15.3523557 7.82142679,15.9066713 8.49907471,15.933013 L8.49907471,15.933013 L155.434123,21.644528 C155.525243,21.6480696 155.616424,21.649841 155.707613,21.649841 C159.577292,21.649841 162.714286,18.5282242 162.714286,14.6775148 C162.714286,14.5867726 162.712506,14.4960388 162.708947,14.4053655 C162.557902,10.5575911 159.300853,7.56019825 155.434123,7.71050163 L155.702875,7.70508717 Z" id="mask"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>follow备份 3</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="切图" transform="translate(-48.000000, -44.000000)" stroke="#3381FF">
<g id="folder备份" transform="translate(48.000000, 44.000000)">
<path d="M15.2700286,11.5822879 L9.44305056,17.4092659 C9.06480988,17.7875066 8.55180543,18 8.01689232,18 L6,18 L6,18 L6,15.9831077 C6,15.4481946 6.2124934,14.9351901 6.59073408,14.5569494 L14.5569494,6.59073408 C15.3445949,5.80308864 16.6216205,5.80308864 17.4092659,6.59073408 C18.1969114,7.37837953 18.1969114,8.65540512 17.4092659,9.44305056 L17.4092659,9.44305056 L17.4092659,9.44305056 C18.1969114,10.230696 18.1969114,11.5077216 17.4092659,12.295367 L14.5569494,15.1476835 L14.5569494,15.1476835" id="路径"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>follow备份 3</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="切图" transform="translate(-8.000000, -44.000000)" stroke="#444E60">
<g id="folder备份" transform="translate(8.000000, 44.000000)">
<path d="M15.2700286,11.5822879 L9.44305056,17.4092659 C9.06480988,17.7875066 8.55180543,18 8.01689232,18 L6,18 L6,18 L6,15.9831077 C6,15.4481946 6.2124934,14.9351901 6.59073408,14.5569494 L14.5569494,6.59073408 C15.3445949,5.80308864 16.6216205,5.80308864 17.4092659,6.59073408 C18.1969114,7.37837953 18.1969114,8.65540512 17.4092659,9.44305056 L17.4092659,9.44305056 L17.4092659,9.44305056 C18.1969114,10.230696 18.1969114,11.5077216 17.4092659,12.295367 L14.5569494,15.1476835 L14.5569494,15.1476835" id="路径"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 25</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="工具栏" transform="translate(-128.000000, -128.000000)" stroke="#3381FF">
<g id="编组-34备份" transform="translate(56.000000, 88.000000)">
<g id="follow备份-23" transform="translate(72.000000, 40.000000)">
<path d="M11.8220813,5.53069562 C12.3696773,5.62507662 12.4625734,5.72554557 12.5206245,5.84774318 L12.5206245,5.84774318 L14.2189736,9.42277042 L17.9920285,9.99234378 C18.1520889,10.0165062 18.2857395,10.1091286 18.3751462,10.2348609 C18.4719375,10.3709779 18.5180744,10.5461839 18.4934299,10.727512 C18.4738012,10.8719348 18.4101452,11.0060802 18.3101095,11.107382 L18.3101095,11.107382 L15.6017319,13.8500424 L16.2437398,17.738772 C16.273578,17.9195063 16.2322362,18.0963001 16.1392467,18.2355891 C16.0531778,18.3645118 15.9218276,18.4616905 15.7619923,18.4909999 C15.6401188,18.5133481 15.5148269,18.4923492 15.4051219,18.4324315 L15.4051219,18.4324315 L11.9999312,16.5726127 L8.59474046,18.4324315 C8.45624603,18.5080732 8.30173213,18.5180361 8.16377893,18.4719016 C8.01822109,18.4232239 7.89275102,18.3137981 7.81733557,18.160431 C7.75356476,18.0307448 7.73238084,17.8825791 7.75612261,17.738772 L7.75612261,17.738772 L8.39813049,13.8500424 L5.68975282,11.107382 C5.56536722,10.981422 5.50330824,10.813681 5.50012823,10.6457587 C5.49693852,10.4773242 5.55293545,10.306672 5.67278368,10.1752191 C5.76224575,10.0770947 5.87963871,10.0116959 6.00783388,9.99234378 L6.00783388,9.99234378 L9.78088874,9.42277042 L11.4792378,5.84774318 C11.5527358,5.69302981 11.6769476,5.58159072 11.8220813,5.53069562 Z" id="星形"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 25</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="工具栏" transform="translate(-128.000000, -48.000000)" stroke="#444E60">
<g id="编组-34" transform="translate(56.000000, 8.000000)">
<g id="follow备份-23" transform="translate(72.000000, 40.000000)">
<path d="M11.8220813,5.53069562 C12.3696773,5.62507662 12.4625734,5.72554557 12.5206245,5.84774318 L12.5206245,5.84774318 L14.2189736,9.42277042 L17.9920285,9.99234378 C18.1520889,10.0165062 18.2857395,10.1091286 18.3751462,10.2348609 C18.4719375,10.3709779 18.5180744,10.5461839 18.4934299,10.727512 C18.4738012,10.8719348 18.4101452,11.0060802 18.3101095,11.107382 L18.3101095,11.107382 L15.6017319,13.8500424 L16.2437398,17.738772 C16.273578,17.9195063 16.2322362,18.0963001 16.1392467,18.2355891 C16.0531778,18.3645118 15.9218276,18.4616905 15.7619923,18.4909999 C15.6401188,18.5133481 15.5148269,18.4923492 15.4051219,18.4324315 L15.4051219,18.4324315 L11.9999312,16.5726127 L8.59474046,18.4324315 C8.45624603,18.5080732 8.30173213,18.5180361 8.16377893,18.4719016 C8.01822109,18.4232239 7.89275102,18.3137981 7.81733557,18.160431 C7.75356476,18.0307448 7.73238084,17.8825791 7.75612261,17.738772 L7.75612261,17.738772 L8.39813049,13.8500424 L5.68975282,11.107382 C5.56536722,10.981422 5.50330824,10.813681 5.50012823,10.6457587 C5.49693852,10.4773242 5.55293545,10.306672 5.67278368,10.1752191 C5.76224575,10.0770947 5.87963871,10.0116959 6.00783388,9.99234378 L6.00783388,9.99234378 L9.78088874,9.42277042 L11.4792378,5.84774318 C11.5527358,5.69302981 11.6769476,5.58159072 11.8220813,5.53069562 Z" id="星形"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>folder备份</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linejoin="round">
<g id="切图" transform="translate(-48.000000, -332.000000)" stroke="#3381FF">
<g id="folder备份" transform="translate(48.000000, 332.000000)">
<rect id="矩形" x="6" y="6" width="12" height="12" rx="2"></rect>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 621 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>folder备份</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linejoin="round">
<g id="切图" transform="translate(-8.000000, -332.000000)" stroke="#444E60">
<g id="folder备份" transform="translate(8.000000, 332.000000)">
<rect id="矩形" x="6" y="6" width="12" height="12" rx="2"></rect>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 619 B

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 25</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="工具栏" transform="translate(-64.000000, -128.000000)" stroke="#3381FF">
<g id="编组-34备份" transform="translate(56.000000, 88.000000)">
<g id="follow备份-21" transform="translate(8.000000, 40.000000)">
<path d="M12,5.5 C12.3399741,5.5 12.6799483,5.62969577 12.9393398,5.8890873 L12.9393398,5.8890873 L18.1109127,11.0606602 C18.3703042,11.3200517 18.5,11.6600259 18.5,12 C18.5,12.3399741 18.3703042,12.6799483 18.1109127,12.9393398 L18.1109127,12.9393398 L12.9393398,18.1109127 C12.6799483,18.3703042 12.3399741,18.5 12,18.5 C11.6600259,18.5 11.3200517,18.3703042 11.0606602,18.1109127 L11.0606602,18.1109127 L5.8890873,12.9393398 C5.62969577,12.6799483 5.5,12.3399741 5.5,12 C5.5,11.6600259 5.62969577,11.3200517 5.8890873,11.0606602 L5.8890873,11.0606602 L11.0606602,5.8890873 C11.3200517,5.62969577 11.6600259,5.5 12,5.5 Z" id="矩形"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 25</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="工具栏" transform="translate(-64.000000, -48.000000)" stroke="#444E60">
<g id="编组-34" transform="translate(56.000000, 8.000000)">
<g id="follow备份-21" transform="translate(8.000000, 40.000000)">
<path d="M12,5.5 C12.3399741,5.5 12.6799483,5.62969577 12.9393398,5.8890873 L12.9393398,5.8890873 L18.1109127,11.0606602 C18.3703042,11.3200517 18.5,11.6600259 18.5,12 C18.5,12.3399741 18.3703042,12.6799483 18.1109127,12.9393398 L18.1109127,12.9393398 L12.9393398,18.1109127 C12.6799483,18.3703042 12.3399741,18.5 12,18.5 C11.6600259,18.5 11.3200517,18.3703042 11.0606602,18.1109127 L11.0606602,18.1109127 L5.8890873,12.9393398 C5.62969577,12.6799483 5.5,12.3399741 5.5,12 C5.5,11.6600259 5.62969577,11.3200517 5.8890873,11.0606602 L5.8890873,11.0606602 L11.0606602,5.8890873 C11.3200517,5.62969577 11.6600259,5.5 12,5.5 Z" id="矩形"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 55.1 (78136) - https://sketchapp.com -->
<title>selector</title>
<desc>Created with Sketch.</desc>
<g id="页面1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="selector" transform="translate(1.000000, 1.000000)">
<path d="M7,14 L2,14 C0.8954305,14 0,13.1045695 0,12 L0,2 C0,0.8954305 0.8954305,0 2,0 L12,0 C13.1045695,0 14,0.8954305 14,2 L14,7 L14,7" id="路径" stroke="#3381FF" stroke-linecap="round" stroke-linejoin="round"></path>
<polygon id="路径-10" fill="#3381FF" fill-rule="nonzero" points="8 8 14 10 11.3333333 11.3333333 10 14"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 861 B

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>follow备份 14</title>
<g id="Flat" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="白板-关闭摄像头-大班课" transform="translate(-16.000000, -247.000000)">
<g id="编组-35" transform="translate(8.000000, 207.000000)">
<g id="folder备份" transform="translate(8.000000, 40.000000)">
<path d="M12,19 L7,19 C5.8954305,19 5,18.1045695 5,17 L5,7 C5,5.8954305 5.8954305,5 7,5 L17,5 C18.1045695,5 19,5.8954305 19,7 L19,12 L19,12" id="路径" stroke="#444E60" stroke-linecap="round" stroke-linejoin="round"></path>
<polygon id="路径-10" fill="#444E60" points="13 13 19 15 16.3333333 16.3333333 15 19"></polygon>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 983 B

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 25</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="工具栏" transform="translate(-160.000000, -128.000000)" stroke="#3381FF">
<g id="编组-34备份" transform="translate(56.000000, 88.000000)">
<g id="follow备份-24" transform="translate(104.000000, 40.000000)">
<path d="M16.5,6 C16.9142136,6 17.2892136,6.16789322 17.5606602,6.43933983 C17.8321068,6.71078644 18,7.08578644 18,7.5 L18,7.5 L18,13.7142857 C18,14.1284993 17.8321068,14.5034993 17.5606602,14.7749459 C17.2892136,15.0463925 16.9142136,15.2142857 16.5000418,15.2142857 L16.5000418,15.2142857 L12.862101,15.2139816 L10.4309693,17.8655011 L9.7122759,15.2139169 L7.5,15.2142857 C7.08578644,15.2142857 6.71078644,15.0463925 6.43933983,14.7749459 C6.16789322,14.5034993 6,14.1284993 6,13.7142857 L6,13.7142857 L6,7.5 C6,7.08578644 6.16789322,6.71078644 6.43933983,6.43933983 C6.71078644,6.16789322 7.08578644,6 7.5,6 L7.5,6 Z" id="形状结合"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 25</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="工具栏" transform="translate(-160.000000, -48.000000)" stroke="#444E60">
<g id="编组-34" transform="translate(56.000000, 8.000000)">
<g id="follow备份-24" transform="translate(104.000000, 40.000000)">
<path d="M16.5,6 C16.9142136,6 17.2892136,6.16789322 17.5606602,6.43933983 C17.8321068,6.71078644 18,7.08578644 18,7.5 L18,7.5 L18,13.7142857 C18,14.1284993 17.8321068,14.5034993 17.5606602,14.7749459 C17.2892136,15.0463925 16.9142136,15.2142857 16.5000418,15.2142857 L16.5000418,15.2142857 L12.862101,15.2139816 L10.4309693,17.8655011 L9.7122759,15.2139169 L7.5,15.2142857 C7.08578644,15.2142857 6.71078644,15.0463925 6.43933983,14.7749459 C6.16789322,14.5034993 6,14.1284993 6,13.7142857 L6,13.7142857 L6,7.5 C6,7.08578644 6.16789322,6.71078644 6.43933983,6.43933983 C6.71078644,6.16789322 7.08578644,6 7.5,6 L7.5,6 Z" id="形状结合"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>follow备份 7</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="切图" transform="translate(-48.000000, -404.000000)" stroke="#3381FF">
<g id="folder备份-2" transform="translate(48.000000, 404.000000)">
<line x1="6" y1="18" x2="18" y2="6" id="路径-2"></line>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 638 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>follow备份 5</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="切图" transform="translate(-8.000000, -404.000000)" stroke="#444E60">
<g id="folder备份-2" transform="translate(8.000000, 404.000000)">
<line x1="6" y1="18" x2="18" y2="6" id="路径-2"></line>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 636 B

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="4px" height="4px" viewBox="0 0 4 4" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 55.1 (78136) - https://sketchapp.com -->
<title>矩形</title>
<desc>Created with Sketch.</desc>
<g id="页面1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<polygon id="矩形" fill="#3381FF" fill-rule="nonzero" points="4 0 4 4 0 4"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 507 B

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="4px" height="4px" viewBox="0 0 4 4" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 55.1 (78136) - https://sketchapp.com -->
<title>矩形</title>
<desc>Created with Sketch.</desc>
<g id="页面1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<polygon id="矩形" fill="#444E60" fill-rule="nonzero" points="4 0 4 4 0 4"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 507 B

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 25</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="切图" transform="translate(-48.000000, -80.000000)" stroke="#3381FF">
<g id="编组-2" transform="translate(48.000000, 80.000000)">
<polyline id="路径-16" points="6 6 18 6 18 8"></polyline>
<line x1="12" y1="6" x2="12" y2="16" id="路径-17"></line>
<line x1="6" y1="6" x2="6" y2="8" id="路径-18"></line>
<line x1="10" y1="18" x2="14" y2="18" id="路径-19"></line>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 859 B

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 25</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="切图" transform="translate(-8.000000, -80.000000)" stroke="#444E60">
<g id="编组-2" transform="translate(8.000000, 80.000000)">
<polyline id="路径-16" points="6 6 18 6 18 8"></polyline>
<line x1="12" y1="6" x2="12" y2="16" id="路径-17"></line>
<line x1="6" y1="6" x2="6" y2="8" id="路径-18"></line>
<line x1="10" y1="18" x2="14" y2="18" id="路径-19"></line>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 857 B

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 25</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="工具栏" transform="translate(-96.000000, -128.000000)" stroke="#3381FF">
<g id="编组-34备份" transform="translate(56.000000, 88.000000)">
<g id="follow备份-22" transform="translate(40.000000, 40.000000)">
<path d="M11.8735432,6.51639519 L18.4433604,16.8729099 C18.5014646,16.9705991 18.513552,17.0816789 18.4849451,17.1824518 C18.4637477,17.2571235 18.4207741,17.3254323 18.3599622,17.3797162 L18.3599622,17.3797162 L5.9619473,17.5 C5.83202254,17.5 5.71357665,17.4508256 5.62815063,17.3687543 C5.5756394,17.3183052 5.53576798,17.2551406 5.51530514,17.1841993 L5.51530514,17.1841993 L11.5944453,6.72141053 C11.6575042,6.61539105 11.7604689,6.5460226 11.8735432,6.51639519 L11.8735432,6.51639519 Z" id="多边形"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>矩形备份 25</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="工具栏" transform="translate(-96.000000, -48.000000)" stroke="#444E60">
<g id="编组-34" transform="translate(56.000000, 8.000000)">
<g id="follow备份-22" transform="translate(40.000000, 40.000000)">
<path d="M11.8735432,6.51639519 L18.4433604,16.8729099 C18.5014646,16.9705991 18.513552,17.0816789 18.4849451,17.1824518 C18.4637477,17.2571235 18.4207741,17.3254323 18.3599622,17.3797162 L18.3599622,17.3797162 L5.9619473,17.5 C5.83202254,17.5 5.71357665,17.4508256 5.62815063,17.3687543 C5.5756394,17.3183052 5.53576798,17.2551406 5.51530514,17.1841993 L5.51530514,17.1841993 L11.5944453,6.72141053 C11.6575042,6.61539105 11.7604689,6.5460226 11.8735432,6.51639519 L11.8735432,6.51639519 Z" id="多边形"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 60.1 (88133) - https://sketch.com -->
<title>upload-pressed</title>
<desc>Created with Sketch.</desc>
<g id="页面-4" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Whiteboard-Guidelines" transform="translate(-736.000000, -295.000000)">
<g id="upload-pressed" transform="translate(736.000000, 295.000000)">
<rect id="矩形备份-18" fill="#FFFFFF" opacity="0.01" x="0" y="0" width="40" height="40" rx="2"></rect>
<polyline id="路径-11备份-5" stroke="#3381FF" stroke-linecap="round" stroke-linejoin="round" transform="translate(20.000000, 14.000000) rotate(-270.000000) translate(-20.000000, -14.000000) " points="22 18 20 16 18 14 20 12 22 10"></polyline>
<polyline id="Stroke-11备份-3" stroke="#3381FF" stroke-linecap="round" stroke-linejoin="round" points="28 24 28 28 12 28 12 24"></polyline>
<line x1="20" y1="24" x2="20" y2="12" id="Stroke-3备份-3" stroke="#3381FF" stroke-linecap="round" stroke-linejoin="round"></line>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 60.1 (88133) - https://sketch.com -->
<title>upload</title>
<desc>Created with Sketch.</desc>
<g id="页面-4" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Whiteboard-Guidelines" transform="translate(-696.000000, -295.000000)">
<g id="upload" transform="translate(696.000000, 295.000000)">
<rect id="矩形备份-18" fill="#FFFFFF" opacity="0.01" x="0" y="0" width="40" height="40" rx="2"></rect>
<polyline id="路径-11备份-5" stroke="#212324" stroke-linecap="round" stroke-linejoin="round" transform="translate(20.000000, 14.000000) rotate(-270.000000) translate(-20.000000, -14.000000) " points="22 18 20 16 18 14 20 12 22 10"></polyline>
<polyline id="Stroke-11备份-3" stroke="#212324" stroke-linecap="round" stroke-linejoin="round" points="28 24 28 28 12 28 12 24"></polyline>
<line x1="20" y1="24" x2="20" y2="12" id="Stroke-3备份-3" stroke="#212324" stroke-linecap="round" stroke-linejoin="round"></line>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,228 @@
<template>
<div class="tool-mid-box-left">
<!-- 工具循环 -->
<div class="tool-box-cell-box-left" v-for="item in tools" :key="item.shapeType">
<!-- <el-tooltip :content="item.name" placement="top-end">
<div class="tool-box-cell" @click="clickAppliance(item.shapeType)">
<img :src="item.shapeType === currentShapType ? item.iconActive : item.icon" :alt="item.name" />
</div>
</el-tooltip> -->
<el-tooltip :content="item.name" placement="top-end">
<div class="tool-box-cell" @click="clickAppliance(item.shapeType)">
<img
:src="(
// 当前是该工具
item.shapeType === currentShapType
// 当前在选颜色 / 粗细时保持上一个绘图工具高亮
|| (['colorSelector','brushSize'].includes(currentShapType) && item.shapeType === lastDrawingTool)
)
? item.iconActive
: item.icon"
:alt="item.name"
/>
</div>
</el-tooltip>
<!-- 颜色选择器 -->
<div v-if="item.shapeType === 'colorSelector' && currentShapType === 'colorSelector' && colorSelectorControl"
class="tool-popup">
<el-color-picker ref="colorPickerRef" v-model="selectedColor" v-model:visible="colorVisible" show-alpha
@change="handleColorChange" />
</div>
<!-- 画笔大小选择器 -->
<div v-if="item.shapeType === 'brushSize' && currentShapType === 'brushSize' && brushSizeControl"
class="tool-popup">
<el-select ref="brushSizeRef" v-model="selectedThickness" placeholder="画笔粗细" size="small"
@change="handleThicknessChange" v-model:visible="brushSizeVisible" style="width: 60px">
<el-option v-for="size in thicknessOptions" :key="size" :label="size + 'px'" :value="size" />
</el-select>
</div>
</div>
</div>
</template>
<script setup>
import { ref, nextTick, onMounted, onBeforeUnmount } from "vue";
const props = defineProps({
canvas: {
type: Object,
required: true,
validator: (value) => value && typeof value.setDrawingTool === "function",
},
});
// 工具图标导入
import pen from "./image/pencil.svg";
import penActive from "./image/pencil-active.svg";
import eraser from "./image/eraser.svg";
import eraserActive from "./image/eraser-active.svg";
import ellipse from "./image/ellipse.svg";
import ellipseActive from "./image/ellipse-active.svg";
import rectangle from "./image/rectangle.svg";
import rectangleActive from "./image/rectangle-active.svg";
import straight from "./image/straight.svg";
import straightActive from "./image/straight-active.svg";
import brushSize from "./image/brushSize.svg";
import brushSizeActive from "./image/brushSize-active.svg";
import colorSelector from "./image/colorSelector.svg";
import colorSelectorActive from "./image/colorSelector-active.svg";
const tools = ref([
{ name: "笔", icon: pen, iconActive: penActive, shapeType: "pencil" },
{ name: "圆形", icon: ellipse, iconActive: ellipseActive, shapeType: "circle" },
{ name: "矩形", icon: rectangle, iconActive: rectangleActive, shapeType: "rectangle" },
{ name: "直线", icon: straight, iconActive: straightActive, shapeType: "line" },
{ name: "选色器", icon: colorSelector, iconActive: colorSelectorActive, shapeType: "colorSelector" },
{ name: "画笔大小", icon: brushSize, iconActive: brushSizeActive, shapeType: "brushSize" },
{ name: "橡皮擦", icon: eraser, iconActive: eraserActive, shapeType: "eraser" },
]);
// 状态
const selectedColor = ref("#ffcc00");
const selectedThickness = ref(2);
const thicknessOptions = [1, 2, 4, 8, 16];
const colorSelectorControl = ref(false);
const brushSizeControl = ref(false);
const colorVisible = ref(false);
const brushSizeVisible = ref(false);
const currentShapType = ref("pencil"); // 当前点击的工具
const lastDrawingTool = ref("pencil"); // 记录最后一个绘图工具
// refs
const colorPickerRef = ref();
const brushSizeRef = ref();
// 颜色选择逻辑
const handleColorChange = (color) => {
props.canvas?.setColor(color);
colorSelectorControl.value = false;
colorVisible.value = false;
};
// 粗细选择逻辑
const handleThicknessChange = (size) => {
props.canvas?.setThickness(size);
brushSizeControl.value = false;
brushSizeVisible.value = false;
};
// 点击工具逻辑
function clickAppliance(type) {
// 点击同一个工具时切换关闭
if (currentShapType.value === type) {
if (type === "colorSelector") {
colorSelectorControl.value = !colorSelectorControl.value;
colorVisible.value = colorSelectorControl.value;
} else if (type === "brushSize") {
brushSizeControl.value = !brushSizeControl.value;
brushSizeVisible.value = brushSizeControl.value;
}
return;
}
// 切换到新工具
currentShapType.value = type;
lastDrawingTool.value = type; // 记录最新绘图工具
if (type === "colorSelector") {
colorSelectorControl.value = true;
brushSizeControl.value = false;
nextTick(() => {
colorVisible.value = true;
});
} else if (type === "brushSize") {
brushSizeControl.value = true;
colorSelectorControl.value = false;
nextTick(() => {
brushSizeVisible.value = true;
});
} else {
colorSelectorControl.value = false;
brushSizeControl.value = false;
colorVisible.value = false;
brushSizeVisible.value = false;
props.canvas?.setDrawingTool(type);
}
}
// 点击工具栏外部关闭弹窗
function handleClickOutside(event) {
const toolBox = document.querySelector(".tool-mid-box-left");
if (!toolBox.contains(event.target)) {
// colorSelectorControl.value = false;
brushSizeControl.value = false;
// colorVisible.value = false;
brushSizeVisible.value = false;
}
}
onMounted(() => {
if (!props.canvas || typeof props.canvas.setDrawingTool !== "function") {
console.error("Invalid canvas prop passed to ToolBox");
}
document.addEventListener("click", handleClickOutside);
});
onBeforeUnmount(() => {
document.removeEventListener("click", handleClickOutside);
});
</script>
<style lang="scss" scoped>
.tool-mid-box-left {
width: 40px;
display: flex;
border-radius: 4px;
background-color: white;
justify-content: flex-start;
align-items: center;
flex-direction: column;
padding: 4px 0;
box-shadow: 0 8px 24px 0 rgba(0, 0, 0, 0.1);
}
.tool-box-cell {
width: 24px;
height: 24px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
position: relative;
}
.tool-box-cell-box-left {
width: 32px;
height: 32px;
user-select: none;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
border-radius: 2px;
position: relative;
&:hover {
background: rgba(33, 35, 36, 0.1);
}
}
/* 弹出层样式:显示在图标右侧 */
.tool-popup {
position: absolute;
left: 40px;
/* 距离工具栏宽度 */
top: 50%;
transform: translateY(-50%);
background: white;
padding: 6px;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
}
</style>

View File

@@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>

View File

@@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>

View File

@@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>

View File

@@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>

View File

@@ -0,0 +1,19 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>

285
src/core/index.js Normal file
View File

@@ -0,0 +1,285 @@
import EventEmitter from "@/utils/emitter";
/**
* Canvas绘图类支持多种图形绘制和多人同步
* 使用百分比坐标系统确保跨设备一致性
*/
class Canvas extends EventEmitter {
constructor(canvasId) {
super();
this.canvas = document.getElementById(canvasId);
if (!this.canvas) {
throw new Error(`Canvas element with id ${canvasId} not found`);
}
this.ctx = this.canvas.getContext('2d');
this.shapes = []; // 所有已绘制形状
this.currentShape = null; // 当前正在绘制的形状
this.isDrawing = false;
this.drawingTool = 'pencil';
this.pathOptimizationEnabled = true;
this.optimizationThreshold = 0.005;
this.currentColor = '#ffcc00';
this.currentThickness = 2;
this.resize();
// 绑定事件
this.handleMouseDown = this.handleMouseDown.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.handleMouseUp = this.handleMouseUp.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.canvas.addEventListener('mousedown', this.handleMouseDown);
this.canvas.addEventListener('mousemove', this.handleMouseMove);
this.canvas.addEventListener('mouseup', this.handleMouseUp);
this.canvas.addEventListener('mouseleave', this.handleMouseLeave);
window.addEventListener('resize', () => this.resize());
}
resize() {
const parent = this.canvas.parentElement;
if (!parent) return;
const containerWidth = parent.offsetWidth;
const containerHeight = parent.offsetHeight;
let width = containerWidth;
let height = Math.floor((width * 9) / 16);
if (height > containerHeight) {
height = containerHeight;
width = Math.floor((height * 16) / 9);
}
if (this.canvas.width === width && this.canvas.height === height) return;
this.canvas.width = width;
this.canvas.height = height;
this.canvas.style.width = width + "px";
this.canvas.style.height = height + "px";
this.render();
}
setDrawingTool(tool) { this.drawingTool = tool; }
setColor(color) { this.currentColor = color; }
setThickness(size) { this.currentThickness = size; }
setPathOptimization(enabled) { this.pathOptimizationEnabled = enabled; }
setOptimizationThreshold(threshold) { this.optimizationThreshold = threshold; }
getShapes() { return this.shapes; }
setShapes(shapes) { this.shapes = shapes; this.render(); }
getMouseCoordinates(e) {
const rect = this.canvas.getBoundingClientRect();
return {
x: (e.clientX - rect.left) / this.canvas.width,
y: (e.clientY - rect.top) / this.canvas.height
};
}
handleMouseDown(e) {
this.isDrawing = true;
const coords = this.getMouseCoordinates(e);
if (this.drawingTool === 'pencil') {
this.currentShape = { type: 'pencil', data: { color: this.currentColor, path: [coords], thickness: this.currentThickness } };
} else if (this.drawingTool === 'line') {
this.currentShape = { type: 'line', data: { color: this.currentColor, start: coords, end: coords, thickness: this.currentThickness } };
} else if (this.drawingTool === 'rectangle') {
this.currentShape = { type: 'rectangle', data: { color: this.currentColor, start: coords, end: coords, thickness: this.currentThickness, fill: false } };
} else if (this.drawingTool === 'circle') {
this.currentShape = { type: 'circle', data: { color: this.currentColor, start: coords, end: coords, thickness: this.currentThickness, fill: false } };
} else if (this.drawingTool === 'eraser') {
this.currentShape = { type: 'eraser', data: { color: '#ffffff', start: coords, end: coords, thickness: 3 } };
}
this.emit('drawingStart', this.currentShape);
}
handleMouseMove(e) {
if (!this.isDrawing || !this.currentShape) return;
const coords = this.getMouseCoordinates(e);
if (this.drawingTool === 'pencil') {
this.currentShape.data.path.push(coords);
} else {
this.currentShape.data.end = coords;
}
this.render();
this.emit('drawingUpdate', this.currentShape);
}
handleMouseUp(e) {
if (!this.isDrawing || !this.currentShape) return;
this.isDrawing = false;
const coords = this.getMouseCoordinates(e);
if (this.drawingTool === 'pencil' && this.pathOptimizationEnabled && this.currentShape.data.path.length > 10) {
this.currentShape.data.path = this.optimizePath(this.currentShape.data.path);
} else {
this.currentShape.data.end = coords;
}
this.shapes.push({ ...this.currentShape });
this.emit('drawingEnd', this.currentShape);
this.currentShape = null;
this.render();
}
handleMouseLeave(e) {
if (this.isDrawing) this.handleMouseUp(e);
}
optimizePath(path) {
if (path.length < 3) return path;
const optimizedPath = [path[0]];
for (let i = 1; i < path.length - 1;) {
let a = 1;
while (i + a < path.length && this.calculateDistance(path[i], path[i + a]) < this.optimizationThreshold) a++;
optimizedPath.push(path[i]);
i += a;
}
optimizedPath.push(path[path.length - 1]);
return optimizedPath;
}
calculateDistance(p1, p2) {
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}
addShape(shapeData) {
if (Array.isArray(shapeData)) this.shapes.push(...shapeData);
else this.shapes.push(shapeData);
this.render();
}
clearCanvas() {
this.shapes = [];
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.emit('clear');
}
exportToDataURL(type = 'image/png', quality = 1) {
return this.canvas.toDataURL(type, quality);
}
render() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// 绘制历史形状
this.shapes.forEach(shape => this.drawShape(shape));
// 绘制当前正在绘制的形状
if (this.currentShape) this.drawShape(this.currentShape);
// 画画布边框
this.ctx.save();
this.ctx.strokeStyle = "#000";
this.ctx.lineWidth = 2;
this.ctx.strokeRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.restore();
}
drawShape(shape) {
switch (shape.type) {
case 'pencil': this.drawPencil(shape.data); break;
case 'line': this.drawLine(shape.data); break;
case 'rectangle': this.drawRectangle(shape.data); break;
case 'circle': this.drawCircle(shape.data); break;
case 'eraser': this.drawEraser(shape.data, shape); break;
}
}
drawPencil(p) {
if (p.path.length < 2) return;
const path = p.path.map(pt => ({ x: pt.x * this.canvas.width, y: pt.y * this.canvas.height }));
this.ctx.beginPath();
this.ctx.strokeStyle = p.color;
this.ctx.lineWidth = p.thickness;
this.ctx.lineCap = 'round';
this.ctx.lineJoin = 'round';
this.drawSmoothCurve(this.ctx, path);
this.ctx.stroke();
}
drawSmoothCurve(ctx, path) {
if (path.length < 3) { ctx.moveTo(path[0].x, path[0].y); for (let i = 1; i < path.length; i++) ctx.lineTo(path[i].x, path[i].y); return; }
ctx.moveTo(path[0].x, path[0].y);
const threshold = 5;
for (let i = 1; i < path.length - 2;) {
let a = 1;
while (i + a < path.length - 2 &&
Math.sqrt(Math.pow(path[i].x - path[i + a].x, 2) + Math.pow(path[i].y - path[i + a].y, 2)) < threshold) a++;
const xc = (path[i].x + path[i + a].x) / 2;
const yc = (path[i].y + path[i + a].y) / 2;
ctx.quadraticCurveTo(path[i].x, path[i].y, xc, yc);
i += a;
}
ctx.lineTo(path[path.length - 1].x, path[path.length - 1].y);
}
drawLine(l) {
const sx = l.start.x * this.canvas.width, sy = l.start.y * this.canvas.height;
const ex = l.end.x * this.canvas.width, ey = l.end.y * this.canvas.height;
this.ctx.beginPath();
this.ctx.moveTo(sx, sy);
this.ctx.lineTo(ex, ey);
this.ctx.strokeStyle = l.color;
this.ctx.lineWidth = l.thickness;
this.ctx.stroke();
}
drawRectangle(r) {
const sx = r.start.x * this.canvas.width, sy = r.start.y * this.canvas.height;
const ex = r.end.x * this.canvas.width, ey = r.end.y * this.canvas.height;
const w = ex - sx, h = ey - sy;
this.ctx.beginPath();
this.ctx.rect(sx, sy, w, h);
if (r.fill) { this.ctx.fillStyle = r.color; this.ctx.fill(); }
else { this.ctx.strokeStyle = r.color; this.ctx.lineWidth = r.thickness; this.ctx.stroke(); }
}
drawCircle(c) {
const sx = c.start.x * this.canvas.width, sy = c.start.y * this.canvas.height;
const ex = c.end.x * this.canvas.width, ey = c.end.y * this.canvas.height;
const cx = (sx + ex) / 2, cy = (sy + ey) / 2;
const rx = Math.abs(ex - sx) / 2, ry = Math.abs(ey - sy) / 2;
this.ctx.beginPath();
this.ctx.ellipse(cx, cy, rx, ry, 0, 0, 2 * Math.PI);
if (c.fill) { this.ctx.fillStyle = c.color; this.ctx.fill(); }
else { this.ctx.strokeStyle = c.color; this.ctx.lineWidth = c.thickness; this.ctx.stroke(); }
}
drawEraser(e, shapeObj) {
const sx = e.start.x * this.canvas.width, sy = e.start.y * this.canvas.height;
const ex = e.end.x * this.canvas.width, ey = e.end.y * this.canvas.height;
const w = Math.abs(ex - sx), h = Math.abs(ey - sy);
const x = Math.min(sx, ex), y = Math.min(sy, ey);
this.ctx.beginPath();
this.ctx.rect(x, y, w, h);
this.ctx.fillStyle = 'rgba(255,255,255)';
this.ctx.fill();
// 仅当前正在绘制的橡皮擦显示边框
if (this.currentShape && shapeObj === this.currentShape) {
this.ctx.save();
this.ctx.strokeStyle = '#000';
this.ctx.lineWidth = 1;
this.ctx.strokeRect(x, y, w, h);
this.ctx.restore();
}
}
destroy() {
this.canvas.removeEventListener('mousedown', this.handleMouseDown);
this.canvas.removeEventListener('mousemove', this.handleMouseMove);
this.canvas.removeEventListener('mouseup', this.handleMouseUp);
this.canvas.removeEventListener('mouseleave', this.handleMouseLeave);
window.removeEventListener('resize', this.resize);
}
}
export default Canvas;

28
src/main.js Normal file
View File

@@ -0,0 +1,28 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import "@/assets/styles/index.scss"; // global css
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import App from './App.vue'
import router from './router'
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(createPinia())
app.use(router)
app.use(ElementPlus, {
locale: zhCn,
})
app.mount('#app')

77
src/plugins/cache.js Normal file
View File

@@ -0,0 +1,77 @@
const sessionCache = {
set (key, value) {
if (!sessionStorage) {
return
}
if (key != null && value != null) {
sessionStorage.setItem(key, value)
}
},
get (key) {
if (!sessionStorage) {
return null
}
if (key == null) {
return null
}
return sessionStorage.getItem(key)
},
setJSON (key, jsonValue) {
if (jsonValue != null) {
this.set(key, JSON.stringify(jsonValue))
}
},
getJSON (key) {
const value = this.get(key)
if (value != null) {
return JSON.parse(value)
}
},
remove (key) {
sessionStorage.removeItem(key);
}
}
const localCache = {
set (key, value) {
if (!localStorage) {
return
}
if (key != null && value != null) {
localStorage.setItem(key, value)
}
},
get (key) {
if (!localStorage) {
return null
}
if (key == null) {
return null
}
return localStorage.getItem(key)
},
setJSON (key, jsonValue) {
if (jsonValue != null) {
this.set(key, JSON.stringify(jsonValue))
}
},
getJSON (key) {
const value = this.get(key)
if (value != null) {
return JSON.parse(value)
}
},
remove (key) {
localStorage.removeItem(key);
}
}
export default {
/**
* 会话级缓存
*/
session: sessionCache,
/**
* 本地缓存
*/
local: localCache
}

35
src/router/index.js Normal file
View File

@@ -0,0 +1,35 @@
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
// {
// path: '/',
// component: () => import("@/views/custom/tabulaRase/index.vue"),
// },
{
path: '/',
redirect: '/login', // 这里做重定向
},
{
path: '/whiteboard',
component: () => import('@/views/custom/tabulaRase/index.vue'),
},
{
path: "/login",
component: () => import("@/views/login.vue"),
},
// 错误页面路由
{
path: "/:pathMatch(.*)*",
component: () => import("@/views/error/404.vue"),
},
{
path: "/401",
component: () => import("@/views/error/401.vue"),
}
],
})
export default router

12
src/stores/counter.js Normal file
View File

@@ -0,0 +1,12 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})

0
src/stores/index.js Normal file
View File

View File

@@ -0,0 +1,33 @@
import { defineStore } from 'pinia'
import { generateUUID } from '@/utils/tools.js'
export const useMeterStore = defineStore('meter', {
state: () => ({
udid: ''
}),
actions: {
initUdid() {
var udid = window.localStorage.getItem('UDID')
if (!udid) {
udid = generateUUID();
window.localStorage.setItem("UDID", udid);
}
this.setUdid(udid)
},
setUdid(udid) {
this.udid = udid
},
getUdid() {
return this.udid
},
getSudid() {
var typedArray = new Uint8Array(this.udid.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16)
}));
const sudid = btoa(String.fromCharCode.apply(null, typedArray)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
return sudid
}
}
})

View File

@@ -0,0 +1,72 @@
import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { defineStore } from 'pinia'
const useUserStore = defineStore(
'user',
{
state: () => ({
token: getToken(),
name: '',
avatar: '',
roles: '',
}),
actions: {
// 登录
async login(userInfo) {
try {
const { username, password } = userInfo;
const trimmedUsername = username.trim();
const res = await login(trimmedUsername, password);
const { token, user } = res.data;
localStorage.setItem('userData', JSON.stringify(user));
setToken(token);
this.token = token;
} catch (error) {
console.error('登录失败:', error);
throw error;
}
},
// 获取用户信息
getInfo() {
return new Promise((resolve, reject) => {
try {
const userData = localStorage.getItem('userData');
if (!userData) {
return reject(new Error('未找到用户数据'));
}
const parsedData = JSON.parse(userData);
if (!parsedData) {
return reject(new Error('用户数据格式无效'));
}
resolve(parsedData);
} catch (error) {
console.error('获取用户信息失败:', error);
reject(error instanceof Error ? error : new Error('解析用户数据失败'));
}
});
},
// 退出系统
async logOut() {
try {
await logout();
this.token = '';
this.roles = '';
removeToken();
} catch (error) {
console.error('退出登录失败:', error);
throw error;
}
},
//判断是否登录
checkLogin() {
return !!getToken();
}
}
})
export default useUserStore

30
src/utils/auth.js Normal file
View File

@@ -0,0 +1,30 @@
import Cookies from "js-cookie";
const TokenKey = "token";
const ExpiresInKey = "Meta-Enterprise-Expires-In";
export function getToken() {
return Cookies.get(TokenKey);
}
export function setToken(token) {
return Cookies.set(TokenKey, token);
}
export function removeToken() {
return Cookies.remove(TokenKey);
}
export function getExpiresIn() {
return Cookies.get(ExpiresInKey) || -1;
}
export function setExpiresIn(time) {
return Cookies.set(ExpiresInKey, time);
}
export function removeExpiresIn() {
return Cookies.remove(ExpiresInKey);
}

583
src/utils/bigDecimal.js Normal file
View File

@@ -0,0 +1,583 @@
var bigDecimal = (function (e) {
var n = {}
function t(r) {
if (n[r]) return n[r].exports
var i = (n[r] = { i: r, l: !1, exports: {} })
return e[r].call(i.exports, i, i.exports, t), (i.l = !0), i.exports
}
return (
(t.m = e),
(t.c = n),
(t.d = function (e, n, r) {
t.o(e, n) || Object.defineProperty(e, n, { enumerable: !0, get: r })
}),
(t.r = function (e) {
'undefined' != typeof Symbol &&
Symbol.toStringTag &&
Object.defineProperty(e, Symbol.toStringTag, { value: 'Module' }),
Object.defineProperty(e, '__esModule', { value: !0 })
}),
(t.t = function (e, n) {
if ((1 & n && (e = t(e)), 8 & n)) return e
if (4 & n && 'object' == typeof e && e && e.__esModule) return e
var r = Object.create(null)
if (
(t.r(r),
Object.defineProperty(r, 'default', { enumerable: !0, value: e }),
2 & n && 'string' != typeof e)
)
for (var i in e)
t.d(
r,
i,
function (n) {
return e[n]
}.bind(null, i)
)
return r
}),
(t.n = function (e) {
var n =
e && e.__esModule
? function () {
return e.default
}
: function () {
return e
}
return t.d(n, 'a', n), n
}),
(t.o = function (e, n) {
return Object.prototype.hasOwnProperty.call(e, n)
}),
(t.p = ''),
t((t.s = 6))
)
})([
function (e, n, t) {
'use strict'
function r(e) {
for (
var n = '',
t = e.length,
r = e.split('.')[1],
i = r ? r.length : 0,
o = 0;
o < t;
o++
)
e[o] >= '0' && e[o] <= '9' ? (n += 9 - parseInt(e[o])) : (n += e[o])
return u(n, i > 0 ? '0.' + new Array(i).join('0') + '1' : '1')
}
function i(e) {
var n = e.split('.')
for (n[0] || (n[0] = '0'); '0' == n[0][0] && n[0].length > 1;)
n[0] = n[0].substring(1)
return n[0] + (n[1] ? '.' + n[1] : '')
}
function o(e, n) {
var t = e.split('.'),
r = n.split('.'),
i = t[0].length,
o = r[0].length
return (
i > o
? (r[0] =
new Array(Math.abs(i - o) + 1).join('0') + (r[0] ? r[0] : ''))
: (t[0] =
new Array(Math.abs(i - o) + 1).join('0') + (t[0] ? t[0] : '')),
(i = t[1] ? t[1].length : 0),
(o = r[1] ? r[1].length : 0),
(i || o) &&
(i > o
? (r[1] =
(r[1] ? r[1] : '') + new Array(Math.abs(i - o) + 1).join('0'))
: (t[1] =
(t[1] ? t[1] : '') + new Array(Math.abs(i - o) + 1).join('0'))),
[
(e = t[0] + (t[1] ? '.' + t[1] : '')),
(n = r[0] + (r[1] ? '.' + r[1] : '')),
]
)
}
function u(e, n) {
var t
; (e = (t = o(e, n))[0]), (n = t[1])
for (var r = '', i = 0, u = e.length - 1; u >= 0; u--)
if ('.' !== e[u]) {
var a = parseInt(e[u]) + parseInt(n[u]) + i
; (r = (a % 10) + r), (i = Math.floor(a / 10))
} else r = '.' + r
return i ? i.toString() + r : r
}
Object.defineProperty(n, '__esModule', { value: !0 }),
(n.pad = n.trim = n.add = void 0),
(n.add = function (e, n) {
var t
void 0 === n && (n = '0')
var a = 0,
s = -1
'-' == e[0] && (a++, (s = 1), (e = e.substring(1)).length),
'-' == n[0] && (a++, (s = 2), (n = n.substring(1)).length),
(e = i(e)),
(n = i(n)),
(e = (t = o(i(e), i(n)))[0]),
(n = t[1]),
1 == a && (1 == s ? (e = r(e)) : (n = r(n)))
var d = u(e, n)
return a
? 2 == a
? '-' + i(d)
: e.length < d.length
? i(d.substring(1))
: '-' + i(r(d))
: i(d)
}),
(n.trim = i),
(n.pad = o)
},
function (e, n, t) {
'use strict'
Object.defineProperty(n, '__esModule', { value: !0 }), (n.roundOff = void 0)
var r = t(2)
function i(e, n, t, i) {
if (!e || e === new Array(e.length + 1).join('0')) return !1
if (
i === r.RoundingModes.DOWN ||
(!t && i === r.RoundingModes.FLOOR) ||
(t && i === r.RoundingModes.CEILING)
)
return !1
if (
i === r.RoundingModes.UP ||
(t && i === r.RoundingModes.FLOOR) ||
(!t && i === r.RoundingModes.CEILING)
)
return !0
var o = '5' + new Array(e.length).join('0')
if (e > o) return !0
if (e < o) return !1
switch (i) {
case r.RoundingModes.HALF_DOWN:
return !1
case r.RoundingModes.HALF_UP:
return !0
case r.RoundingModes.HALF_EVEN:
default:
return parseInt(n[n.length - 1]) % 2 == 1
}
}
function o(e, n) {
void 0 === n && (n = 0),
n || (n = 1),
'number' == typeof e && e.toString()
for (var t = '', r = e.length - 1; r >= 0; r--) {
var i = parseInt(e[r]) + n
10 == i ? ((n = 1), (i = 0)) : (n = 0), (t += i)
}
return n && (t += n), t.split('').reverse().join('')
}
n.roundOff = function e(n, t, u) {
if (
(void 0 === t && (t = 0),
void 0 === u && (u = r.RoundingModes.HALF_EVEN),
u === r.RoundingModes.UNNECESSARY)
)
throw new Error(
'UNNECESSARY Rounding Mode has not yet been implemented'
)
'number' == typeof n && (n = n.toString())
var a = !1
'-' === n[0] && ((a = !0), (n = n.substring(1)))
var s = n.split('.'),
d = s[0],
l = s[1]
if (t < 0) {
if (((t = -t), d.length <= t)) return '0'
var f = d.substr(0, d.length - t)
return (
(a ? '-' : '') +
(f = e((n = f + '.' + d.substr(d.length - t) + l), 0, u)) +
new Array(t + 1).join('0')
)
}
if (0 == t) {
d.length
return i(s[1], d, a, u) ? (a ? '-' : '') + o(d) : (a ? '-' : '') + d
}
if (!s[1]) return (a ? '-' : '') + d + '.' + new Array(t + 1).join('0')
if (s[1].length < t)
return (
(a ? '-' : '') +
d +
'.' +
s[1] +
new Array(t - s[1].length + 1).join('0')
)
l = s[1].substring(0, t)
var g = s[1].substring(t)
return g && i(g, l, a, u) && (l = o(l)).length > t
? o(d, parseInt(l[0])) + '.' + l.substring(1)
: (a ? '-' : '') + d + '.' + l
}
},
function (e, n, t) {
'use strict'
Object.defineProperty(n, '__esModule', { value: !0 }),
(n.RoundingModes = void 0),
(function (e) {
; (e[(e.CEILING = 0)] = 'CEILING'),
(e[(e.DOWN = 1)] = 'DOWN'),
(e[(e.FLOOR = 2)] = 'FLOOR'),
(e[(e.HALF_DOWN = 3)] = 'HALF_DOWN'),
(e[(e.HALF_EVEN = 4)] = 'HALF_EVEN'),
(e[(e.HALF_UP = 5)] = 'HALF_UP'),
(e[(e.UNNECESSARY = 6)] = 'UNNECESSARY'),
(e[(e.UP = 7)] = 'UP')
})(n.RoundingModes || (n.RoundingModes = {}))
},
function (e, n, t) {
'use strict'
function r(e) {
for (; '0' == e[0];) e = e.substr(1)
if (-1 != e.indexOf('.'))
for (; '0' == e[e.length - 1];) e = e.substr(0, e.length - 1)
return (
'' == e || '.' == e
? (e = '0')
: '.' == e[e.length - 1] && (e = e.substr(0, e.length - 1)),
'.' == e[0] && (e = '0' + e),
e
)
}
Object.defineProperty(n, '__esModule', { value: !0 }),
(n.multiply = void 0),
(n.multiply = function (e, n) {
; (e = e.toString()), (n = n.toString())
var t = 0
'-' == e[0] && (t++, (e = e.substr(1))),
'-' == n[0] && (t++, (n = n.substr(1))),
(e = r(e)),
(n = r(n))
var i = 0,
o = 0
; -1 != e.indexOf('.') && (i = e.length - e.indexOf('.') - 1),
-1 != n.indexOf('.') && (o = n.length - n.indexOf('.') - 1)
var u = i + o
if (
((e = r(e.replace('.', ''))),
(n = r(n.replace('.', ''))),
e.length < n.length)
) {
var a = e
; (e = n), (n = a)
}
if ('0' == n) return '0'
for (
var s, d, l = n.length, f = 0, g = [], c = l - 1, v = '', p = 0;
p < l;
p++
)
g[p] = e.length - 1
for (p = 0; p < 2 * e.length; p++) {
for (var h = 0, b = n.length - 1; b >= c && b >= 0; b--)
g[b] > -1 &&
g[b] < e.length &&
(h += parseInt(e[g[b]--]) * parseInt(n[b]))
; (h += f), (f = Math.floor(h / 10)), (v = (h % 10) + v), c--
}
return (
(v = r(
((s = v),
0 == (d = u)
? s
: (s =
d >= s.length
? new Array(d - s.length + 1).join('0') + s
: s).substr(0, s.length - d) +
'.' +
s.substr(s.length - d, d))
)),
1 == t && (v = '-' + v),
v
)
})
},
function (e, n, t) {
'use strict'
Object.defineProperty(n, '__esModule', { value: !0 }), (n.divide = void 0)
var r = t(0),
i = t(1)
n.divide = function (e, n, t) {
if ((void 0 === t && (t = 8), 0 == n))
throw new Error('Cannot divide by 0')
if (
((e = e.toString()),
(n = n.toString()),
(e = e.replace(/(\.\d*?[1-9])0+$/g, '$1').replace(/\.0+$/, '')),
(n = n.replace(/(\.\d*?[1-9])0+$/g, '$1').replace(/\.0+$/, '')),
0 == e)
)
return '0'
var o = 0
'-' == n[0] && ((n = n.substring(1)), o++),
'-' == e[0] && ((e = e.substring(1)), o++)
var u = n.indexOf('.') > 0 ? n.length - n.indexOf('.') - 1 : -1
if (((n = r.trim(n.replace('.', ''))), u >= 0)) {
var a = e.indexOf('.') > 0 ? e.length - e.indexOf('.') - 1 : -1
if (-1 == a) e = r.trim(e + new Array(u + 1).join('0'))
else if (u > a)
(e = e.replace('.', '')),
(e = r.trim(e + new Array(u - a + 1).join('0')))
else if (u < a) {
var s = (e = e.replace('.', '')).length - a + u
e = r.trim(e.substring(0, s) + '.' + e.substring(s))
} else u == a && (e = r.trim(e.replace('.', '')))
}
var d = 0,
l = n.length,
f = '',
g =
e.indexOf('.') > -1 && e.indexOf('.') < l
? e.substring(0, l + 1)
: e.substring(0, l)
if (
((e =
e.indexOf('.') > -1 && e.indexOf('.') < l
? e.substring(l + 1)
: e.substring(l)),
g.indexOf('.') > -1)
) {
var c = g.length - g.indexOf('.') - 1
l > (g = g.replace('.', '')).length &&
((c += l - g.length), (g += new Array(l - g.length + 1).join('0'))),
(d = c),
(f = '0.' + new Array(c).join('0'))
}
for (t += 2; d <= t;) {
for (var v = 0; parseInt(g) >= parseInt(n);)
(g = r.add(g, '-' + n)), v++
; (f += v),
e
? ('.' == e[0] && ((f += '.'), d++, (e = e.substring(1))),
(g += e.substring(0, 1)),
(e = e.substring(1)))
: (d || (f += '.'), d++, (g += '0'))
}
return (1 == o ? '-' : '') + r.trim(i.roundOff(f, t - 2))
}
},
function (e, n, t) {
'use strict'
Object.defineProperty(n, '__esModule', { value: !0 }),
(n.negate = n.subtract = void 0)
var r = t(0)
function i(e) {
return (e = '-' == e[0] ? e.substr(1) : '-' + e)
}
; (n.subtract = function (e, n) {
return (e = e.toString()), (n = i((n = n.toString()))), r.add(e, n)
}),
(n.negate = i)
},
function (e, n, t) {
'use strict'
var r = t(0),
i = t(1),
o = t(3),
u = t(4),
a = t(7),
s = t(8),
d = t(5),
l = t(2),
f = (function () {
function e(n) {
void 0 === n && (n = '0'), (this.value = e.validate(n))
}
return (
(e.validate = function (e) {
if (e) {
if (((e = e.toString()), isNaN(e)))
throw Error('Parameter is not a number: ' + e)
'+' == e[0] && (e = e.substring(1))
} else e = '0'
if (/e/i.test(e)) {
var n = e.split(/[eE]/),
t = n[0],
i = n[1]
; (t = r.trim(t)),
(i = parseInt(i) + t.indexOf('.')),
(e =
(t = t.replace('.', '')).length < i
? t + new Array(i - t.length + 1).join('0')
: t.length >= i && i > 0
? r.trim(t.substring(0, i)) +
(t.length > i ? '.' + t.substring(i) : '')
: '0.' + new Array(1 - i).join('0') + t)
}
return e
}),
(e.prototype.getValue = function () {
return this.value
}),
(e.getPrettyValue = function (n, t, r) {
if (t || r) {
if (!t || !r)
throw Error(
'Illegal Arguments. Should pass both digits and separator or pass none'
)
} else (t = 3), (r = ',')
var i = '-' == (n = e.validate(n)).charAt(0)
i && (n = n.substring(1))
for (
var o = n.indexOf('.'), u = '', a = (o = o > 0 ? o : n.length);
a > 0;
)
a < t ? ((t = a), (a = 0)) : (a -= t),
(u = n.substring(a, a + t) + (a < o - t && a >= 0 ? r : '') + u)
return (i ? '-' : '') + u + n.substring(o)
}),
(e.prototype.getPrettyValue = function (n, t) {
return e.getPrettyValue(this.value, n, t)
}),
(e.round = function (n, t, r) {
if (
(void 0 === t && (t = 0),
void 0 === r && (r = l.RoundingModes.HALF_EVEN),
(n = e.validate(n)),
isNaN(t))
)
throw Error('Precision is not a number: ' + t)
return i.roundOff(n, t, r)
}),
(e.prototype.round = function (n, t) {
if (
(void 0 === n && (n = 0),
void 0 === t && (t = l.RoundingModes.HALF_EVEN),
isNaN(n))
)
throw Error('Precision is not a number: ' + n)
return new e(i.roundOff(this.value, n, t))
}),
(e.floor = function (n) {
return -1 === (n = e.validate(n)).indexOf('.')
? n
: e.round(n, 0, l.RoundingModes.FLOOR)
}),
(e.prototype.floor = function () {
return -1 === this.value.indexOf('.')
? new e(this.value)
: new e(this.value).round(0, l.RoundingModes.FLOOR)
}),
(e.ceil = function (n) {
return -1 === (n = e.validate(n)).indexOf('.')
? n
: e.round(n, 0, l.RoundingModes.CEILING)
}),
(e.prototype.ceil = function () {
return -1 === this.value.indexOf('.')
? new e(this.value)
: new e(this.value).round(0, l.RoundingModes.CEILING)
}),
(e.add = function (n, t) {
return (n = e.validate(n)), (t = e.validate(t)), r.add(n, t)
}),
(e.prototype.add = function (n) {
return new e(r.add(this.value, n.getValue()))
}),
(e.subtract = function (n, t) {
return (n = e.validate(n)), (t = e.validate(t)), d.subtract(n, t)
}),
(e.prototype.subtract = function (n) {
return new e(d.subtract(this.value, n.getValue()))
}),
(e.multiply = function (n, t) {
return (n = e.validate(n)), (t = e.validate(t)), o.multiply(n, t)
}),
(e.prototype.multiply = function (n) {
return new e(o.multiply(this.value, n.getValue()))
}),
(e.divide = function (n, t, r) {
return (n = e.validate(n)), (t = e.validate(t)), u.divide(n, t, r)
}),
(e.prototype.divide = function (n, t) {
return new e(u.divide(this.value, n.getValue(), t))
}),
(e.modulus = function (n, t) {
return (n = e.validate(n)), (t = e.validate(t)), a.modulus(n, t)
}),
(e.prototype.modulus = function (n) {
return new e(a.modulus(this.value, n.getValue()))
}),
(e.compareTo = function (n, t) {
return (n = e.validate(n)), (t = e.validate(t)), s.compareTo(n, t)
}),
(e.prototype.compareTo = function (e) {
return s.compareTo(this.value, e.getValue())
}),
(e.negate = function (n) {
return (n = e.validate(n)), d.negate(n)
}),
(e.prototype.negate = function () {
return new e(d.negate(this.value))
}),
(e.RoundingModes = l.RoundingModes),
e
)
})()
e.exports = f
},
function (e, n, t) {
'use strict'
Object.defineProperty(n, '__esModule', { value: !0 }), (n.modulus = void 0)
var r = t(4),
i = t(1),
o = t(3),
u = t(5),
a = t(2)
function s(e) {
if (-1 != e.indexOf('.'))
throw new Error('Modulus of non-integers not supported')
}
n.modulus = function (e, n) {
if (0 == n) throw new Error('Cannot divide by 0')
; (e = e.toString()), (n = n.toString()), s(e), s(n)
var t = ''
return (
'-' == e[0] && ((t = '-'), (e = e.substr(1))),
'-' == n[0] && (n = n.substr(1)),
t +
u.subtract(
e,
o.multiply(n, i.roundOff(r.divide(e, n), 0, a.RoundingModes.FLOOR))
)
)
}
},
function (e, n, t) {
'use strict'
Object.defineProperty(n, '__esModule', { value: !0 }),
(n.compareTo = void 0)
var r = t(0)
n.compareTo = function (e, n) {
var t,
i = !1
if ('-' == e[0] && '-' != n[0]) return -1
if ('-' != e[0] && '-' == n[0]) return 1
if (
('-' == e[0] &&
'-' == n[0] &&
((e = e.substr(1)), (n = n.substr(1)), (i = !0)),
(e = (t = r.pad(e, n))[0]),
(n = t[1]),
0 == e.localeCompare(n))
)
return 0
for (var o = 0; o < e.length; o++)
if (e[o] != n[o]) return e[o] > n[o] ? (i ? -1 : 1) : i ? 1 : -1
return 0
}
},
])
export default bigDecimal

25
src/utils/emitter.js Normal file
View File

@@ -0,0 +1,25 @@
import mitt from 'mitt';
class EventEmitter {
constructor() {
this.emitter = mitt();
}
on(eventName, handler) {
this.emitter.on(eventName, handler);
}
off(eventName, handler) {
this.emitter.off(eventName, handler);
}
emit(eventName, event) {
this.emitter.emit(eventName, event);
}
removeAllListeners() {
this.emitter.all.clear();
}
}
export default EventEmitter;

6
src/utils/errorCode.js Normal file
View File

@@ -0,0 +1,6 @@
export default {
'401': '认证失败,无法访问系统资源',
'403': '当前操作没有权限',
'404': '访问资源不存在',
'default': '系统未知错误,请反馈给管理员'
}

127
src/utils/mqtt.js Normal file
View File

@@ -0,0 +1,127 @@
import mqtt from "mqtt"
// MQTT 连接选项
const options = {
clean: true,
connectTimeout: 4000,
reconnectPeriod: 1000,
qos: 1, // 默认 QoS 1
}
const brokerUrl = "wss://push.cnsdt.com:443/mqtt" // 你的 MQTT 服务地址
class MQTTClient {
constructor() {
this.client = null
this.subscriptions = new Set()
this.messageHandlers = new Map()
}
// 连接
connect(clientId) {
if (this.client && this.client.connected) {
return Promise.resolve()
}
return new Promise((resolve, reject) => {
this.client = mqtt.connect(brokerUrl, {
...options,
clientId: clientId || `client_${Math.random().toString(16).substr(2, 8)}`,
})
this.client.on("connect", () => resolve())
this.client.on("error", (error) => reject(error))
// 消息分发
this.client.on("message", (topic, payload) => {
try {
const message = JSON.parse(payload.toString())
// 遍历所有订阅主题,执行通配符匹配
this.messageHandlers.forEach((handlers, subTopic) => {
if (this.topicMatch(subTopic, topic)) {
handlers.forEach((handler) => handler(message, topic))
}
})
} catch (err) {
console.error("MQTT message parse error:", err)
}
})
})
}
// 断开
disconnect() {
if (this.client) {
this.client.end()
this.client = null
this.subscriptions.clear()
this.messageHandlers.clear()
}
}
// 订阅
subscribe(topic, handler) {
if (!this.client || !this.client.connected) {
console.error("MQTT client not connected")
return
}
if (!this.subscriptions.has(topic)) {
this.client.subscribe(topic, { qos: 1 }, (err) => {
if (err) {
console.error("Subscription error:", err)
} else {
this.subscriptions.add(topic)
}
})
}
const handlers = this.messageHandlers.get(topic) || []
handlers.push(handler)
this.messageHandlers.set(topic, handlers)
}
// 取消订阅
unsubscribe(topic, handler) {
if (!this.subscriptions.has(topic)) return
if (handler) {
const handlers = this.messageHandlers.get(topic) || []
const index = handlers.indexOf(handler)
if (index !== -1) {
handlers.splice(index, 1)
this.messageHandlers.set(topic, handlers)
}
} else {
this.client.unsubscribe(topic)
this.subscriptions.delete(topic)
this.messageHandlers.delete(topic)
}
}
// 发布
publish(topic, message) {
if (!this.client || !this.client.connected) {
console.error("MQTT client not connected")
return
}
this.client.publish(topic, JSON.stringify(message), { qos: 1 })
}
// 通配符匹配
topicMatch(subTopic, msgTopic) {
const subLevels = subTopic.split("/")
const msgLevels = msgTopic.split("/")
for (let i = 0; i < subLevels.length; i++) {
if (subLevels[i] === "#") return true
if (subLevels[i] === "+") continue
if (msgLevels[i] === undefined) return false
if (subLevels[i] !== msgLevels[i]) return false
}
return subLevels.length === msgLevels.length
}
}
export const mqttClient = new MQTTClient()

194
src/utils/request.js Normal file
View File

@@ -0,0 +1,194 @@
import axios from "axios";
import {
ElNotification,
ElMessageBox,
ElMessage,
} from "element-plus";
import { tansParams } from "@/utils/ruoyi";
import cache from "@/plugins/cache";
import { getToken, removeToken } from "@/utils/auth";
import router from '@/router';
import { useMeterStore } from '@/stores/modules/meter'
axios.defaults.headers["Content-Type"] = "application/json;charset=utf-8";
const meterStore = useMeterStore()
meterStore.initUdid()
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时
timeout: 10000,
});
// request拦截器
service.interceptors.request.use(
(config) => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false;
if (getToken() && !isToken) {
config.headers["Authorization"] = "Bearer " + getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
}
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false;
if (meterStore.getSudid()) {
config.headers["X-User-Agent"] = `gxtech/web 1.0.0: c=GxTech, udid=${meterStore.getSudid()}, sv=15.4.1, app=stt`;
}
// get请求映射params参数
if (config.method === "get" && config.params) {
let url = config.url + "?" + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
if (
!isRepeatSubmit &&
(config.method === "post" || config.method === "put")
) {
const requestObj = {
url: config.url,
data:
typeof config.data === "object"
? JSON.stringify(config.data)
: config.data,
time: new Date().getTime(),
};
const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小
const limitSize = 5 * 1024 * 1024; // 限制存放数据5M
if (requestSize >= limitSize) {
console.warn(
`[${config.url}]: ` +
"请求数据大小超出允许的5M限制无法进行防重复提交验证。"
);
return config;
}
const sessionObj = cache.session.getJSON("sessionObj");
if (
sessionObj === undefined ||
sessionObj === null ||
sessionObj === ""
) {
cache.session.setJSON("sessionObj", requestObj);
} else {
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (
s_data === requestObj.data &&
requestObj.time - s_time < interval &&
s_url === requestObj.url
) {
const message = "数据正在处理,请勿重复提交";
console.warn(`[${s_url}]: ` + message);
return Promise.reject(new Error(message));
} else {
cache.session.setJSON("sessionObj", requestObj);
}
}
}
return config;
},
(error) => {
Promise.reject(error);
}
);
service.interceptors.response.use(
(response) => {
// 1. 检查响应是否存在
if (!response) {
ElMessage.error('无响应数据');
return Promise.reject(new Error('无响应数据'));
}
// 2. 安全获取响应数据和状态码
const responseData = response.data || {};
const statusCode = response.status;
const businessCode = responseData.meta?.code || statusCode;
// 3. 二进制数据直接返回
if (
response.request.responseType === 'blob' ||
response.request.responseType === 'arraybuffer'
) {
return responseData;
}
// 4. 根据业务码处理不同情况
switch (businessCode) {
case 200:
case 201:
return Promise.resolve(responseData);
case 401:
return handleUnauthorized().then(() => {
return Promise.reject({ code: 401, message: '未授权' });
});
case 500:
const serverErrorMsg = responseData.meta?.message || '服务器内部错误';
ElMessage({ message: serverErrorMsg, type: 'error' });
return Promise.reject({ code: 500, message: serverErrorMsg });
default:
const errorMsg = responseData.meta?.message || `业务错误 (${businessCode})`;
ElNotification.error({ title: errorMsg });
return Promise.reject({ code: businessCode, message: errorMsg });
}
},
(error) => {
console.error('请求错误:', error);
let { message } = error;
let code = error?.response?.status || -1;
if (message == 'Network Error') {
message = '后端接口连接异常';
ElMessage({ message, type: 'error', duration: 5 * 1000 });
} else if (message.includes('timeout')) {
message = '系统接口请求超时';
ElMessage({ message, type: 'error', duration: 5 * 1000 });
} else if (message.includes('Request failed with status code')) {
// message = '系统接口' + message.substr(message.length - 3) + '异常';
}
// 返回结构化错误
return Promise.reject({
code,
message,
raw: error // 保留原始 error
});
}
);
// 单独处理401未授权
function handleUnauthorized() {
return ElMessageBox.confirm(
'认证信息已失效,您可以继续留在该页面,或者重新登录',
'系统提示',
{
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
removeToken()
if (router.currentRoute.path !== '/login') {
router.push({
path: '/login',
query: { redirect: router.currentRoute.fullPath }
});
} else {
// 如果在登录页,强制刷新以清除残留状态
window.location.reload();
}
})
.catch(() => {
return Promise.reject('用户取消操作');
});
}
export default service;

718
src/utils/ruoyi.js Normal file
View File

@@ -0,0 +1,718 @@
import bigDecimal from "@/utils/bigDecimal";
/**
* @description 分钟转时间
* @author
* @date
* @param
* @returns
*/
export function formatMinutes(minutes) {
let tiemInfo = Number(minutes);
var time = [];
var day = parseInt(tiemInfo / 60 / 24);
var hour = parseInt((tiemInfo / 60) % 24);
var min = parseInt(tiemInfo % 60);
time[0] = day > 0 ? day : 0;
time[1] = hour > 0 ? hour : 0;
time[2] = min > 0 ? parseFloat(min) : 0;
return time;
}
/**
* @description 时间转分钟
* @author
* @date
* @param
* @returns
*/
export function totalMinutes(day, hour, minute) {
return day * 1440 + hour * 60 + minute;
}
/**
* @description 数组指定必填项判断校验
* @author // arr要判断的数组 data要判断里面那些不能为空的字段
* @date
* @param
* @returns
*/
export function everyI(arr, data, text) {
let flag = arr.flatMap((item) => {
let flag1 = data.flatMap((key) => {
if (text == "不校验零") {
if (item[key] || item[key] == 0) {
return true;
} else {
false;
}
} else {
if (item[key]) {
return true;
} else {
false;
}
}
});
return flag1;
});
// 如果不只条的时候全部都要填写 every只要有一个不满足就不让走 smoe只要有一个满足就可以走
let info = flag.every((val) => val);
return info;
}
/**
* @description 找下对应的字段显示上去 arr1枚举表 arr2对比数组 text取出的字段
* @author cl
* @date 2022-2-17
* @param
* @returns
*/
export function getArr(arr1, arr2, text) {
let arrInfo = [];
if (!arr2) return;
arr1.map((item) => {
arr2.split(",").map((n) => {
if (n == item.value) {
arrInfo.push(item[text]);
}
});
});
return arrInfo.join(",");
}
/**
* @description
* @author cl
* @date 2022-2-18
* @param arr要计算的数组数据 Array
* @param num2要计算的价格字段 string
* @param arr2要合并计算总价的数据 string
* @param currency优惠券的币种字段 Array
* @param currency要减去的优惠券币种字段 string
* @param amount要减去的优惠券价格字段 string
* @returns
*/
export function getPriceI(arr, num1, num2, arr2, currency, amount) {
let arrInfo = arr2 ? arr2 : []; // 总计
let currencyInfo = currency; // 优惠券币种
let amountInfo = amount ? amount : 0; // 优惠价钱
let price = {
USD: 0,
CNY: 0,
};
arr.concat(arrInfo).flatMap((i) => {
if (i[num1] == "USD") {
price.USD = Number(bigDecimal.add(price.USD, i[num2]));
} else {
price.CNY = Number(bigDecimal.add(price.CNY, i[num2]));
}
});
// 有优惠卷的话减去优惠间
if (currencyInfo === "CNY") {
price.CNY = Number(bigDecimal.subtract(price.CNY, amountInfo));
} else {
price.USD = Number(bigDecimal.subtract(price.USD, amountInfo));
}
let arrI = [
price.USD > 0 ? "USD" + format_with_array(price.USD) : "USD0.00",
price.CNY > 0 ? "CNY" + format_with_array(price.CNY) : "CNY0.00",
]
.filter((current) => {
return current !== "USD0.00" && current !== "CNY0.00";
})
.join(" + ");
return arrI;
}
/**
* @description 数组字段拼接回调法
* @author cl
* @date 2022-12-9
* @param
* @returns
*/
export function _getArr(arr, name, id, child) {
arr.forEach((item, index) => {
// 作为键值对
item.label = item[name];
item.value = item[id];
if (item.children) {
_getArr(item.children, name, id);
}
});
return arr;
}
/**
* 防抖原理一定时间内只有最后一次操作再过wait毫秒后才执行函数
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
let timeout = null;
export function debounce(func, wait = 300, immediate = false) {
// 清除定时器
if (timeout !== null) clearTimeout(timeout);
// 立即执行,此类情况一般用不到
if (immediate) {
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait);
if (callNow) typeof func === "function" && func();
} else {
// 设置定时器当最后一次操作后timeout不会再被清除所以在延时wait毫秒后执行func回调方法
timeout = setTimeout(() => {
typeof func === "function" && func();
}, wait);
}
}
/**
* @description 去重对比后去除已存在的替换 arrA已经选择的 arrB数据源 text去重的标识字段 type类型
* @author cl
* @date 2022-12-9
* @param
* @returns
*/
export function processI(arrA, arrB, text) {
// 缓存用于记录
const cache = [];
for (const t of arrB) {
// 检查缓存中是否已经存在
if (arrA.find((c) => c[text] === t[text])) {
// 已经存在说明以前记录过,现在这个就是多余的,直接忽略
continue;
}
// 不存在就说明以前没遇到过,把它记录下来
cache.push(t);
}
return cache;
}
/**
* @description 去重去除已存在的 typeName类型标记
* @author cl
* @date 2022-2-17
* @param
* @returns
*/
export function duplicate(arr1, arr2, text, typeName, type) {
arr1.map((item) => {
// 检查缓存中是否已经存在
if (arr2.find((c) => c[text] == item[text])) {
// 已经存在说明以前记录过,现在这个就是多余的,直接忽略
return;
}
// 不存在就说明以前没遇到过,把它记录下来
if (typeName) {
item[typeName] = type;
arr2.push(item);
} else {
arr2.push(item);
}
});
return arr2;
}
/**
* 手机号格式化
* @param {string | number} mobile 手机号
* @returns '188 8888 8888'格式的手机号
*/
export function formatMobile(mobile) {
const regMobile = /^1\d{10}/;
if (!regMobile.test(mobile)) {
return "";
}
return String(mobile).replace(/(^\d{3}|\d{4}\B)/g, "$1 ");
}
/**
* @description 复制
* @param {*} id DOM ID
*/
export function copyDomText(id) {
const node = document.getElementById(id);
if (node) {
let createRange = document.createRange();
createRange.selectNodeContents(document.getElementById(id));
const selection = document.getSelection();
selection.removeAllRanges();
selection.addRange(createRange);
document.execCommand("Copy");
selection.removeAllRanges();
}
}
/**
* 金额 千分位
* @param { number} mobile 金额
* @returns '12,300,000' 金额格式
*/
export function format_with_array(
number,
decimals,
dec_point,
thousands_sep,
round_tag
) {
number = (number + "").replace(/[^0-9+-Ee.]/g, "");
decimals = decimals || 2; //默认保留2位
dec_point = dec_point || "."; //默认是'.';
thousands_sep = thousands_sep || ","; //默认是',';
round_tag = round_tag || "round"; //默认是四舍五入
var n = !isFinite(+number) ? 0 : +number,
prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
sep = typeof thousands_sep === "undefined" ? "," : thousands_sep,
dec = typeof dec_point === "undefined" ? "." : dec_point,
s = "",
toFixedFix = function (n, prec) {
var k = Math.pow(10, prec);
return (
"" +
parseFloat(
Math[round_tag](parseFloat((n * k).toFixed(prec * 2))).toFixed(
prec * 2
)
) /
k
);
};
s = (prec ? toFixedFix(n, prec) : "" + Math.round(n)).split(".");
var re = /(-?\d+)(\d{3})/;
while (re.test(s[0])) {
s[0] = s[0].replace(re, "$1" + sep + "$2");
}
if ((s[1] || "").length < prec) {
s[1] = s[1] || "";
s[1] += new Array(prec - s[1].length + 1).join("0");
}
return "¥" + s.join(dec);
}
// 获取两个时间段内的所有日期/月份 日 传入 YYYY-MM , YYYY-MM (2020-09) (2020-12) 返回 YYYY-MM 数组
export function getAllDay(start, end) {
let result = [];
let min = new Date(start);
let max = new Date(end);
let curr = min;
do {
let month = new Date(curr).getMonth() + 1;
let t = "";
if (month < 10) {
t = "0" + month;
} else t = month;
let str = curr.getFullYear() + "-" + t;
result.push(str);
if (month == 12) {
curr.setFullYear(new Date(curr).getFullYear() + 1);
curr.setMonth(0);
} else curr.setMonth(month);
} while (curr <= max);
return result;
}
// 日期格式化
export function parseTime(time, pattern) {
if (arguments.length === 0 || !time) {
return null;
}
const format = pattern || "{y}-{m}-{d} {h}:{i}:{s}";
let date;
if (typeof time === "object") {
date = time;
} else {
if (typeof time === "string" && /^[0-9]+$/.test(time)) {
time = parseInt(time);
} else if (typeof time === "string") {
time = time
.replace(new RegExp(/-/gm), "/")
.replace("T", " ")
.replace(new RegExp(/\.[\d]{3}/gm), "");
}
if (typeof time === "number" && time.toString().length === 10) {
time = time * 1000;
}
date = new Date(time);
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay(),
};
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key];
// Note: getDay() returns 0 on Sunday
if (key === "a") {
return ["日", "一", "二", "三", "四", "五", "六"][value];
}
if (result.length > 0 && value < 10) {
value = "0" + value;
}
return value || 0;
});
return time_str;
}
// 表单重置
export function resetForm(refName) {
if (this.$refs[refName]) {
this.$refs[refName].resetFields();
}
}
// 添加日期范围
export function addDateRange(params, dateRange, propName) {
let search = params;
search.params =
typeof search.params === "object" &&
search.params !== null &&
!Array.isArray(search.params)
? search.params
: {};
dateRange = Array.isArray(dateRange) ? dateRange : [];
if (typeof propName === "undefined") {
search.params["beginTime"] = dateRange[0];
search.params["endTime"] = dateRange[1];
} else {
search.params["begin" + propName] = dateRange[0];
search.params["end" + propName] = dateRange[1];
}
return search;
}
// 回显数据字典
export function selectDictLabel(datas, value) {
if (value === undefined) {
return "";
}
var actions = [];
Object.keys(datas).some((key) => {
if (datas[key].value == "" + value) {
actions.push(datas[key].label);
return true;
}
});
if (actions.length === 0) {
actions.push(value);
}
return actions.join("");
}
// 回显数据字典(字符串数组)
export function selectDictLabels(datas, value, separator) {
if (value === undefined || value.length === 0) {
return "";
}
if (Array.isArray(value)) {
value = value.join(",");
}
var actions = [];
var currentSeparator = undefined === separator ? "," : separator;
var temp = value.split(currentSeparator);
Object.keys(value.split(currentSeparator)).some((val) => {
var match = false;
Object.keys(datas).some((key) => {
if (datas[key].value == "" + temp[val]) {
actions.push(datas[key].label + currentSeparator);
match = true;
}
});
if (!match) {
actions.push(temp[val] + currentSeparator);
}
});
return actions.join("").substring(0, actions.join("").length - 1);
}
// 字符串格式化(%s )
export function sprintf(str) {
var args = arguments,
flag = true,
i = 1;
str = str.replace(/%s/g, function () {
var arg = args[i++];
if (typeof arg === "undefined") {
flag = false;
return "";
}
return arg;
});
return flag ? str : "";
}
// 转换字符串undefined,null等转化为""
export function parseStrEmpty(str) {
if (!str || str == "undefined" || str == "null") {
return "";
}
return str;
}
// 数据合并
export function mergeRecursive(source, target) {
for (var p in target) {
try {
if (target[p].constructor == Object) {
source[p] = mergeRecursive(source[p], target[p]);
} else {
source[p] = target[p];
}
} catch (e) {
source[p] = target[p];
}
}
return source;
}
/**
* 构造树型结构数据
* @param {*} data 数据源
* @param {*} id id字段 默认 'id'
* @param {*} parentId 父节点字段 默认 'parentId'
* @param {*} children 孩子节点字段 默认 'children'
*/
export function handleTree(data, id, parentId, children) {
let config = {
id: id || "id",
parentId: parentId || "parentId",
childrenList: children || "children",
};
var childrenListMap = {};
var nodeIds = {};
var tree = [];
for (let d of data) {
let parentId = d[config.parentId];
if (childrenListMap[parentId] == null) {
childrenListMap[parentId] = [];
}
nodeIds[d[config.id]] = d;
childrenListMap[parentId].push(d);
}
for (let d of data) {
let parentId = d[config.parentId];
if (nodeIds[parentId] == null) {
tree.push(d);
}
}
for (let t of tree) {
adaptToChildrenList(t);
}
function adaptToChildrenList(o) {
if (childrenListMap[o[config.id]] !== null) {
o[config.childrenList] = childrenListMap[o[config.id]];
}
if (o[config.childrenList]) {
for (let c of o[config.childrenList]) {
adaptToChildrenList(c);
}
}
}
return tree;
}
/**
* 参数处理
* @param {*} params 参数
*/
export function tansParams(params) {
let result = "";
for (const propName of Object.keys(params)) {
const value = params[propName];
var part = encodeURIComponent(propName) + "=";
if (value !== null && value !== "" && typeof value !== "undefined") {
if (typeof value === "object") {
for (const key of Object.keys(value)) {
if (
value[key] !== null &&
value[key] !== "" &&
typeof value[key] !== "undefined"
) {
let params = propName + "[" + key + "]";
var subPart = encodeURIComponent(params) + "=";
result += subPart + encodeURIComponent(value[key]) + "&";
}
}
} else {
result += part + encodeURIComponent(value) + "&";
}
}
}
return result;
}
// 返回项目路径
export function getNormalPath(p) {
if (p.length === 0 || !p || p == "undefined") {
return p;
}
let res = p.replace("//", "/");
if (res[res.length - 1] === "/") {
return res.slice(0, res.length - 1);
}
return res;
}
// 验证是否为blob格式
export function blobValidate(data) {
return data.type !== "application/json";
}
// 处理图片格式
export function disposeImage(primary, type = false) {
if (primary && (primary.length || !Array.isArray(primary))) {
let imgValue = null;
if (Array.isArray(primary)) {
imgValue = [];
primary.forEach((item) => {
if (
(typeof item == "string" && type) ||
(item.url && item.uuid && !type)
) {
imgValue.push(item);
} else {
if (type) {
if (item.prefix && item.path) {
imgValue.push(item.prefix + item.path);
} else {
imgValue.push(item.url);
}
} else {
imgValue.push({
name: item.name,
url: item.prefix + item.path,
uuid: item.fileId,
});
}
}
});
} else {
if (
(typeof primary == "string" && type) ||
(primary.url && primary.uuid && !type)
) {
imgValue = primary;
} else {
if (type) {
if (primary.prefix && primary.path) {
imgValue = primary.prefix + primary.path;
} else {
imgValue = primary.url;
}
} else {
imgValue = {
name: primary.name,
url: primary.prefix + primary.path,
uuid: primary.fileId,
};
}
}
}
return imgValue;
} else {
return [];
}
}
// 处理图片格式
export function getImage(primary) {
if (primary && primary.length) {
let imgArr = [];
primary.forEach((item) => {
imgArr.push(item.uuid);
});
return imgArr.toString();
} else {
return "";
}
}
export function getUrl(url) {
return new Promise((resolve, reject) => {
getFileMessage({ fileId: url })
.then((res) => {
resolve(disposeImage(res.data));
})
.catch((row) => {
ElMessage({
message: "图片获取错误" + row,
type: "error",
duration: 5 * 1000,
});
reject(false);
});
});
}
// 获取图片详情
export async function getImageMessage(url, type = false) {
if (!url || (Array.isArray(url) && !url.length)) {
} else {
let uuidList = null;
if (typeof url == "string" || typeof url == "number") {
uuidList = url.toString().split(",");
} else if (Array.isArray(url)) {
uuidList = url;
}
let imgurl = [];
for (const item in uuidList) {
const url = await getUrl(uuidList[item]);
if (url) {
if (type) {
imgurl.push(url);
} else {
imgurl.push(url.url);
}
} else {
imgurl.push(img);
}
}
return imgurl;
}
}
// 克隆
export function deepClone(obj) {
if (obj === null || typeof obj !== "object") {
// 如果 obj 是 null 或不是对象,则直接返回 obj
return obj;
}
if (Array.isArray(obj)) {
// 如果 obj 是数组
const clonedArray = [];
for (let i = 0; i < obj.length; i++) {
// 递归克隆数组中的每个元素
clonedArray[i] = deepClone(obj[i]);
}
return clonedArray;
}
// 如果 obj 是普通对象
const clonedObj = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 递归克隆对象的每个属性
clonedObj[key] = deepClone(obj[key]);
}
}
return clonedObj;
}
// 数组去重
export function removeDuplicate(arr) {
const newArr = [];
arr.forEach((item) => {
if (newArr.indexOf(item) === -1) {
newArr.push(item);
}
});
return newArr; // 返回一个新数组
}

160
src/utils/tools.js Normal file
View File

@@ -0,0 +1,160 @@
// 获取uuid
export function generateUUID() {
var d = new Date().getTime();
var d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now() * 1000)) || 0;
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16;
if (d > 0) {
r = (d + r) % 16 | 0;
d = Math.floor(d / 16);
} else {
r = (d2 + r) % 16 | 0;
d2 = Math.floor(d2 / 16);
}
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
}
/**
* 过滤掉可信度(confidence)小于0.6的数据项
* @param {Array} data - 要过滤的数组数组项应包含confidence属性
* @returns {Array} 过滤后的新数组
*/
export function filteConfidence(data) {
// 检查输入是否为数组
if (!Array.isArray(data)) {
console.warn('filteConfidence: 输入参数不是数组');
return [];
}
// 过滤并返回新数组
return data.filter(item => {
return item && typeof item === 'object' &&
'confidence' in item &&
Number(item.confidence) >= 0.6;
});
}
/**
* 根据检测结果截取图片中的多个区域
* @param {HTMLImageElement|String} image - 图片元素或图片URL
* @param {Array} detectionResults - 检测结果数组包含box坐标
* @param {Object} options - 可选配置
* @param {Number} options.minConfidence - 最小置信度阈值默认0.5
* @param {String} options.outputFormat - 输出格式,默认'image/png'
* @param {Number} options.outputQuality - 输出质量(0-1)默认0.92
* @returns {Promise<Array<{blob: Blob, data: Object}>>} 返回截取图片和相关数据的数组
*/
export async function cropDetectedRegions(image, detectionResults, options = {}) {
// 合并默认配置
const {
minConfidence = 0.6,
outputFormat = 'image/png',
outputQuality = 0.92
} = options;
// 如果传入的是URL字符串先加载图片
const img = typeof image === 'string'
? await loadImage(image)
: image;
// 创建画布
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 存储所有截取结果
const croppedResults = [];
// 遍历所有检测结果
for (const result of detectionResults) {
// 检查置信度是否达标
if (result.confidence < minConfidence) {
console.log(`跳过置信度低于阈值(${minConfidence})的检测结果:`, result);
continue;
}
const { x1, y1, x2, y2 } = result.box;
const width = x2 - x1;
const height = y2 - y1;
// 检查坐标是否有效
if (!isValidBox(x1, y1, x2, y2, img.width, img.height)) {
console.warn('无效的坐标区域:', result.box);
continue;
}
// 设置画布尺寸
canvas.width = width;
canvas.height = height;
// 清空画布
ctx.clearRect(0, 0, width, height);
// 绘制截取区域
ctx.drawImage(
img,
x1, y1, // 源图像裁剪起始坐标
width, height, // 源图像裁剪宽高
0, 0, // 画布起始坐标
width, height // 画布绘制宽高
);
// 将画布内容转为Blob对象
const blob = await new Promise((resolve) => {
canvas.toBlob(
(blob) => resolve(blob),
outputFormat,
outputQuality
);
});
// 保存结果及相关数据
croppedResults.push({
blob,
data: {
...result,
cropInfo: {
x: x1,
y: y1,
width,
height
}
}
});
}
return croppedResults;
}
/**
* 加载图片
* @param {String} url - 图片URL
* @returns {Promise<HTMLImageElement>}
*/
export function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'Anonymous'; // 处理跨域问题
img.onload = () => resolve(img);
img.onerror = reject;
img.src = url;
});
}
/**
* 检查坐标框是否有效
* @param {Number} x1 - 左上角x坐标
* @param {Number} y1 - 左上角y坐标
* @param {Number} x2 - 右下角x坐标
* @param {Number} y2 - 右下角y坐标
* @param {Number} imgWidth - 图片宽度
* @param {Number} imgHeight - 图片高度
* @returns {Boolean}
*/
function isValidBox(x1, y1, x2, y2, imgWidth, imgHeight) {
return (
x1 >= 0 && y1 >= 0 &&
x2 > x1 && y2 > y1 &&
x2 <= imgWidth && y2 <= imgHeight
);
}

View File

@@ -0,0 +1,98 @@
import { mqttClient } from "./mqtt";
import { getWhiteboardShapes, getWhiteboardHistory } from "@/views/custom/api";
import { useMeterStore } from '@/stores/modules/meter';
const meterStore = useMeterStore();
meterStore.initUdid();
let isRemote = false;
let canvasInstance = null;
// 获取本地缓存 userData
function getLocalUserData() {
const dataStr = localStorage.getItem('userData');
if (!dataStr) return null;
try {
return JSON.parse(dataStr);
} catch (e) {
console.error("解析 userData 失败:", e);
return null;
}
}
export const WhiteboardSync = {
async init(canvas, roomUid) {
if (!canvas || !roomUid) return;
console.log('初始化多人同步:', roomUid);
canvasInstance = canvas;
const localUser = getLocalUserData();
const localUid = localUser?.user?.uid;
try {
// 先连接 MQTT
await mqttClient.connect(meterStore.getSudid());
console.log("✅ MQTT 已连接");
// 获取历史数据
const res = await getWhiteboardHistory({ after_timestamp: 0 }, roomUid);
if (res.meta.code === 200 && res.data.shapes.length > 0) {
canvasInstance.addShape(res.data.shapes);
}
// 订阅当前房间
const topic = `xSynergy/ROOM/${roomUid}/whiteboard/#`;
mqttClient.subscribe(topic, async (shapeData) => {
try {
isRemote = true;
// 如果 shape 来自本地用户,则跳过
if (shapeData.user_uid === localUid) return;
const res = await getWhiteboardHistory({ after_timestamp: shapeData.created_at }, roomUid);
if (res.meta.code === 200) {
canvasInstance.addShape(res.data.shapes);
} else {
console.error("获取历史数据失败");
}
} catch (e) {
console.error("处理MQTT数据失败:", e);
} finally {
isRemote = false;
}
});
console.log("✅ 已订阅:", topic);
} catch (err) {
console.log("初始化多人同步失败:",err)
// console.error("❌ 连接或订阅失败:", err);
}
// 监听画布事件:新增图形
canvas.on('drawingEnd', async (shape) => {
// 如果来自远程,或不是需要同步的类型,跳过
if (isRemote || !['pencil', 'line', 'rectangle', 'circle', 'eraser'].includes(shape.type)) return;
// 如果是本地用户自己的 shape则不调用接口
if (shape.user_uid && shape.user_uid === localUid) return;
shape.room_uid = roomUid;
try {
await getWhiteboardShapes(shape, roomUid);
} catch (err) {
console.error("提交形状失败:", err);
}
});
// 监听画布事件:清空
canvas.on('clear', async () => {
if (!isRemote) {
try {
// TODO: 调用接口,后端再发 MQTT
// await clearWhiteboard(roomUid);
} catch (err) {
console.error("提交清空失败:", err);
}
}
});
},
};

Some files were not shown because too many files have changed in this diff Show More