Browse Source

feat: init

master
guoapeng 2 weeks ago
commit
8e5a22212c
  1. 7
      .env.dev
  2. 7
      .env.pre
  3. 7
      .env.prod
  4. 7
      .env.test
  5. 27
      .gitignore
  6. 0
      .npmrc
  7. 28
      .postcssrc.js
  8. 10
      .prettierrc
  9. 23
      README.md
  10. BIN
      commitlint.config.js
  11. 19
      eslint.config.js
  12. 43
      increment-version.js
  13. 27
      index.html
  14. 90
      package.json
  15. BIN
      public/favicon.ico
  16. 20
      server/index.js
  17. 33
      server/routes/auth.js
  18. 74
      server/routes/control.js
  19. 28
      server/utils/websocket.js
  20. 5
      src/apis/container.ts
  21. 34
      src/apis/crafts.ts
  22. 13
      src/apis/home.ts
  23. 4
      src/apis/login.ts
  24. 4
      src/apis/point.ts
  25. 4
      src/apis/self.ts
  26. 9
      src/apis/solution.ts
  27. 12
      src/apis/system.ts
  28. 7
      src/apis/user.ts
  29. 118
      src/app.vue
  30. 1
      src/assets/images/background.svg
  31. 1
      src/assets/images/empty.svg
  32. 1
      src/assets/images/error.svg
  33. 1
      src/assets/images/expand.svg
  34. 1
      src/assets/images/icon_add_s.svg
  35. 1
      src/assets/images/icon_arr_s.svg
  36. 1
      src/assets/images/icon_del_s.svg
  37. 1
      src/assets/images/ing.svg
  38. 1
      src/assets/images/jaw.svg
  39. 1
      src/assets/images/liquied/liquied_bottle.svg
  40. 1
      src/assets/images/login.svg
  41. 1
      src/assets/images/logo.svg
  42. 1
      src/assets/images/logout.svg
  43. 1
      src/assets/images/menuIcon/n_debug.svg
  44. 1
      src/assets/images/menuIcon/n_expe.svg
  45. 1
      src/assets/images/menuIcon/n_home.svg
  46. 1
      src/assets/images/menuIcon/n_liquid.svg
  47. 1
      src/assets/images/menuIcon/n_liquid_config.svg
  48. 1
      src/assets/images/menuIcon/n_log.svg
  49. 1
      src/assets/images/menuIcon/n_ore.svg
  50. 1
      src/assets/images/menuIcon/n_point.svg
  51. 1
      src/assets/images/menuIcon/n_ps.svg
  52. 1
      src/assets/images/menuIcon/n_setting.svg
  53. 1
      src/assets/images/menuIcon/n_user.svg
  54. 1
      src/assets/images/menuIcon/s_debug.svg
  55. 1
      src/assets/images/menuIcon/s_expe.svg
  56. 1
      src/assets/images/menuIcon/s_home.svg
  57. 1
      src/assets/images/menuIcon/s_liquid.svg
  58. 1
      src/assets/images/menuIcon/s_liquid_config.svg
  59. 1
      src/assets/images/menuIcon/s_log.svg
  60. 1
      src/assets/images/menuIcon/s_ore.svg
  61. 1
      src/assets/images/menuIcon/s_point.svg
  62. 1
      src/assets/images/menuIcon/s_ps.svg
  63. 1
      src/assets/images/menuIcon/s_setting.svg
  64. 1
      src/assets/images/menuIcon/s_user.svg
  65. 1
      src/assets/images/password_icon.svg
  66. 1
      src/assets/images/run.svg
  67. 1
      src/assets/images/success.svg
  68. 22
      src/assets/images/user-cancel.svg
  69. 1
      src/assets/images/user.svg
  70. 1
      src/assets/images/user_icon.svg
  71. 1
      src/assets/images/wait.svg
  72. 1
      src/assets/images/wifi-active.svg
  73. 1
      src/assets/images/wifi.svg
  74. 91
      src/assets/styles/common.scss
  75. 72
      src/assets/styles/element.scss
  76. 3
      src/assets/styles/main.scss
  77. 5
      src/assets/styles/variable.scss
  78. 109
      src/components/SavePosition/index.vue
  79. 158
      src/components/common/FTButton/index.vue
  80. 76
      src/components/common/FTDialog/index.vue
  81. 182
      src/components/common/FTStream/index.vue
  82. 7
      src/components/common/FTTable/expand.ts
  83. 216
      src/components/common/FTTable/index.vue
  84. 235
      src/components/container/Item/index.vue
  85. 391
      src/components/craft/AddCraft/index.vue
  86. 285
      src/components/craft/AddCraftDialog.vue
  87. 257
      src/components/craft/CraftStatus.vue
  88. 40
      src/components/craft/TransferLeft.vue
  89. 161
      src/components/craft/TransferRight.vue
  90. 202
      src/components/home/AddLiquid/index.vue
  91. 502
      src/components/home/CheckCraft/index.vue
  92. 107
      src/components/home/Countdown/Countdown.vue
  93. 190
      src/components/home/ExecuteCraft/index.vue
  94. 218
      src/components/home/ExtractLiquid/index.vue
  95. 117
      src/components/home/FillSolution/index.vue
  96. 64
      src/components/home/Liquid/index.vue
  97. 131
      src/components/home/SetTemperature/index.vue
  98. 201
      src/components/home/StartClean/index.vue
  99. 50
      src/components/home/StartExperiment/index.vue
  100. 396
      src/components/home/Tube/index.vue

7
.env.dev

@ -0,0 +1,7 @@
# 开发环境
FT_NODE_ENV=dev
FT_WS_URL=ws://192.168.1.199:8080/ws
FT_PROXY=http://localhost:8080
FT_API_BASE=/api

7
.env.pre

@ -0,0 +1,7 @@
# 预发环境
FT_NODE_ENV=pre
FT_WS_URL=ws://192.168.8.108:8080/ws
FT_PROXY=http://192.168.8.108
FT_API_BASE=http://192.168.8.108:8080/api

7
.env.prod

@ -0,0 +1,7 @@
# 生产环境
FT_NODE_ENV=prod
FT_WS_URL=ws://192.168.8.168:8080/ws
FT_PROXY=http://192.168.8.168:8080
FT_API_BASE=/api

7
.env.test

@ -0,0 +1,7 @@
# 测试环境
FT_NODE_ENV=test
FT_WS_URL=ws://192.168.1.199:8081/ws
FT_PROXY=http://127.0.0.1:8081
FT_API_BASE=/api

27
.gitignore

@ -0,0 +1,27 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
auto-imports.d.ts
.eslintrc-auto-import.json
pnpm-lock.yaml
node_modules
dist
dist-*
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

0
.npmrc

28
.postcssrc.js

@ -0,0 +1,28 @@
export default {
plugins: {
'postcss-import': {},
'postcss-url': {},
'postcss-aspect-ratio-mini': {},
'postcss-write-svg': {
utf8: false,
},
'postcss-px-to-viewport-8-plugin': {
viewportWidth: 1120,
viewportHeight: 736,
unitPrecision: 3, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除)
viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw
selectorBlackList: ['.ignore', '.hairlines', ':after'], // 指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名
minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值
mediaQuery: false, // 允许在媒体查询中转换`px`
},
'postcss-viewport-units': {
filterRule: rule =>
!/::?(?:before|after)/i.test(rule.selector),
},
'cssnano': {
'autoprefixer': false,
'postcss-zindex': false,
},
},
}

10
.prettierrc

@ -0,0 +1,10 @@
{
"printWidth": 120,
"tabWidth": 2,
"endOfLine": "lf",
"singleQuote": true,
"semi": true,
"trailingComma": "none",
"bracketSpacing": true,
"arrowParens": "avoid"
}

23
README.md

@ -0,0 +1,23 @@
### 前端项目模板
##### 文件目录
- src
- components 组件目录
- apis 接口文件目录
- assets 静态资源目录
- hooks hooks目录
- libs 公共工具类
- router 页面路由
- stores 项目公共状态管理
- types 类型申明
- views 页面目录
##### 项目运行
项目采用pnpm 进行依赖构建,请先安装pnpm
[pnpm - 速度快、节省磁盘空间的软件包管理器](https://pnpm.io/)
```bash
npm install pnpm -g
```
```bash
pnpm install // 安装依赖
pnpm dev // 运行项目
```

BIN
commitlint.config.js

19
eslint.config.js

@ -0,0 +1,19 @@
// import eslint_js from '@eslint/js'
// import eslint_ts from 'typescript-eslint';
// import eslint_vue from 'eslint-plugin-vue';
// import vue_parser from 'vue-eslint-parser';
import lintConfig from '@antfu/eslint-config'
export default lintConfig({
vue: true,
markdown: true,
ignores: [],
rules: {
'no-console': 0,
'antfu/top-level-function': 0,
'ts/no-use-before-define': 0,
'no-alert': 0,
},
globals: { process: 'readonly' },
})

43
increment-version.js

@ -0,0 +1,43 @@
import { execSync } from 'node:child_process' // 引入 child_process 模块用于执行 Git 命令
import fs from 'node:fs'
import path, { dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import semver from 'semver'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const packagePath = path.resolve(__dirname, 'package.json')
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'))
// 读取命令行参数(默认使用 'patch')
// eslint-disable-next-line node/prefer-global/process
const versionType = process.argv[2] || 'patch'
// 递增版本
const newVersion = semver.inc(packageJson.version, versionType)
if (!newVersion) {
throw new Error(`Invalid version type: ${versionType}`)
}
packageJson.version = newVersion
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2))
console.log(`Version updated to: ${newVersion}`)
// 新增:自动提交 package.json 到远程仓库
try {
// 将 package.json 添加到暂存区
execSync('git add package.json')
console.log('Added package.json to staging area.')
// 提交更改
execSync(`git commit -m "fix: Update version to V${newVersion}"`)
console.log(`Committed changes with message: Update version to ${newVersion}`)
// 推送到远程仓库
execSync('git push')
console.log('Pushed changes to remote repository.')
}
catch (error) {
console.error('Failed to commit and push changes:', error.message)
}

27
index.html

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>颜色滴定</title>
<style>
html,body{
height:100%;
width:100%;
margin: 0;
padding: 0;
}
#app {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

90
package.json

@ -0,0 +1,90 @@
{
"name": "color-titration-web",
"type": "module",
"version": "0.0.0",
"description": "",
"author": "",
"license": "ISC",
"keywords": [],
"main": "index.js",
"scripts": {
"dev": "vite --mode dev",
"dev:server": "concurrently -n server,client -c green,blue \"node server/index.js\" \"vite --mode dev\"",
"test": "vite --mode test",
"prod": "vite --mode prod",
"pre": "vite --mode pre",
"build": "vite build --mode dev",
"build:test": "vite build --mode test",
"build:prod:patch": "node increment-version.js patch && vite build --mode prod",
"build:prod:minor": "node increment-version.js minor && vite build --mode prod",
"build:prod:major": "node increment-version.js major && vite build --mode prod",
"build:pre": "vite build --mode pre",
"build:dev": "vite build --mode dev",
"prepare": "husky",
"lint:lint-staged": "lint-staged",
"lint": "vue-tsc --noEmit --skipLibCheck && eslint",
"eslint": "eslint --fix --ext .ts,.vue src",
"prettier": "prettier --write ."
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"autoprefixer": "^10.4.20",
"axios": "1.8.2",
"cssnano": "^7.0.6",
"element-plus": "^2.9.5",
"express": "^5.1.0",
"konva": "^9.3.18",
"lodash": "^4.17.21",
"pinia": "^3.0.1",
"pinia-plugin-persistedstate": "^4.2.0",
"postcss": "^8.5.3",
"postcss-aspect-ratio-mini": "^1.1.0",
"postcss-import": "^16.1.0",
"postcss-px-to-viewport-8-plugin": "^1.2.5",
"postcss-url": "^10.1.3",
"postcss-viewport-units": "^0.1.6",
"postcss-write-svg": "^3.0.1",
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"ws": "^8.18.1"
},
"devDependencies": {
"@antfu/eslint-config": "^4.3.0",
"@commitlint/cli": "^19.7.1",
"@commitlint/config-conventional": "^19.7.1",
"@types/node": "^22.13.5",
"@typescript-eslint/eslint-plugin": "^8.25.0",
"@typescript-eslint/parser": "^8.25.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/eslint-config-prettier": "^10.2.0",
"concurrently": "^9.1.2",
"eslint": "^9.21.0",
"eslint-config-prettier": "^10.0.2",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-vue": "^9.32.0",
"husky": "^9.1.7",
"lint-staged": "^15.4.3",
"prettier": "^3.5.2",
"sass": "^1.85.1",
"semver": "^7.7.1",
"typescript": "^5.7.3",
"unplugin-auto-import": "^19.1.1",
"unplugin-vue-components": "^28.4.1",
"vite": "6.2.6",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-eslint": "^1.8.1",
"vue-tsc": "^2.2.4"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,jsx,vue,ts,tsx}": [
"eslint --fix",
"prettier --write",
"lint-staged"
]
}
}

BIN
public/favicon.ico

20
server/index.js

@ -0,0 +1,20 @@
// const express = require('express')
import express from 'express'
import { authRoutes } from './routes/auth.js'
import { debugRoutes } from './routes/control.js'
import { initWebSocketServer } from './utils/websocket.js'
const app = express()
const PORT = 8080 // 可根据需要更改端口号
app.listen(PORT, () => {
console.log(`服务器已启动,正在监听端口 ${PORT}`)
})
app.use(express.json())
// 初始化 WebSocket 服务
initWebSocketServer()
// 注册路由模块
debugRoutes(app)
authRoutes(app)

33
server/routes/auth.js

@ -0,0 +1,33 @@
const baseUrl = '/api/auth'
export const authRoutes = (app) => {
app.post(`${baseUrl}/login`, (req, res) => {
// const { username, password } = req.body
const mockResponse = {
code: '0',
msg: '成功',
data: {
id: 1,
createTime: '2025-04-27 07:44:01',
updateTime: '2025-04-27 07:44:01',
username: 'admin',
nickname: 'Admin',
password: null,
role: 'ADMIN',
deleted: 'DISABLE',
fixedUser: 'ENABLE',
},
}
setTimeout(() => {
res.json(mockResponse)
}, 2000)
})
app.post(`${baseUrl}/logout`, (req, res) => {
// const { username, password } = req.body
const mockResponse = {
code: '0',
msg: '成功',
data: null,
}
res.json(mockResponse)
})
}

74
server/routes/control.js

@ -0,0 +1,74 @@
import { broadcast } from '../utils/websocket.js'
export const debugRoutes = (app) => {
const statusList = ['info', 'warn', 'success', 'error', 'finish']
app.post('/api/debug/cmd', (req, res) => {
const { commandId, command, params } = req.body
console.log('收到命令:', command, '参数:', params)
// 模拟返回数据
const mockResponse = {
code: '0',
msg: '成功',
data: null,
}
let messageNum = 0
// 异步广播消息
setTimeout(() => {
res.json(mockResponse)
const poll = setInterval(() => {
if (messageNum === 10) {
clearInterval(poll)
}
broadcast({
type: 'cmd_debug',
data: {
commandId,
command,
status: statusList[Math.floor(Math.random() * statusList.length)],
title: `步骤${messageNum}执行完成`,
content: `具体信息${messageNum}`,
},
})
messageNum++
}, Math.floor(Math.random() * (1000 - 500 + 1)) + 500)
}, 1000)
})
app.post('/api/cmd', (req, res) => {
const { commandId, command, params } = req.body
console.log('收到命令:', command, '参数:', params)
// 模拟返回数据
const mockResponse = {
code: '0',
msg: '成功',
data: null,
}
let messageNum = 0
// 异步广播消息
setTimeout(() => {
res.json(mockResponse)
const poll = setInterval(() => {
if (messageNum === 10) {
clearInterval(poll)
}
broadcast({
type: 'cmd_debug',
data: {
commandId,
command,
status: statusList[Math.floor(Math.random() * statusList.length)],
title: `步骤${messageNum}执行完成`,
content: `具体信息${messageNum}`,
},
})
messageNum++
}, Math.floor(Math.random() * (1000 - 500 + 1)) + 500)
}, 1000)
})
}

28
server/utils/websocket.js

@ -0,0 +1,28 @@
import { WebSocketServer } from 'ws'
export const clients = new Set()
export const broadcast = (message) => {
try {
const jsonMessage = JSON.stringify(message)
clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(jsonMessage)
}
})
}
catch (e) {
console.log('广播发送失败:', e, message)
}
}
export const initWebSocketServer = () => {
const wss = new WebSocketServer({ port: 9527 })
wss.on('connection', (ws) => {
clients.add(ws)
ws.on('close', () => {
console.log('客户端断开')
})
})
}

5
src/apis/container.ts

@ -0,0 +1,5 @@
import http from 'libs/http'
export const getContainerList = (): Promise<Container.ContainerItem[]> => http.get(`/container/list`)
export const updateContainer = (params: Container.ContainerItem): Promise<null> => http.put(`/container`, params)

34
src/apis/crafts.ts

@ -0,0 +1,34 @@
import http from 'libs/http'
interface Craft {
id?: number
name?: string
steps?: string
oresId?: number
}
export const getCraftList = (params: System.Page = { pageNum: 1, pageSize: 999 }): Promise<System.PageResponse<CraftTypes.Craft>> => http.get(`/crafts/list`, { params })
export const createCraft = (params: Craft): Promise<null> => http.post('/crafts', params)
export const updateCraft = (params: Craft): Promise<null> => http.put(`/crafts`, params)
export const delCraft = (ids: string): Promise<null> => http.delete(`/crafts/${ids}`)
// 开始执行工艺
export const startCraft = (params: { heatId?: string, craftId?: number, columns?: number[] }): Promise<null> => http.post(`/crafts/start`, params)
export const pauseCraft = (params: { heatId: string }): Promise<null> => http.post(`/crafts/pause`, params)
export const resumeCraft = (params: { heatId: string }): Promise<null> => http.post(`/crafts/resume`, params)
export const stopCraft = (params: { heatId: string }): Promise<null> => http.post(`/crafts/stop`, params)
// 加热区配置工艺
export const setCraft = (params: { heatId?: string | number, craftId?: string | number }): Promise<null> => http.post(`/crafts/set`, params)
export const craftstatus = (): Promise<CraftTypes.CraftState[]> => http.get(`/monitor/crafts/status`)
export const craftstatusByHeatId = (heatId: string): Promise<CraftTypes.CraftState> => http.get(`/monitor/crafts/status/${heatId}`)
export const craftList = (): Promise<any> => http.get(`/crafts/monitor/list`)
export const craftRestart = (params: any): Promise<CraftTypes.CraftState> => http.post(`/crafts/restart`, params)
export const craftRestore = (isRestore: boolean): Promise<null> => http.get(`/crafts/restore/isRestore=${isRestore}`)
export const craftRemove = (monitorId: string): Promise<CraftTypes.CraftState> => http.delete(`/crafts/remove`, { params: { monitorId } })

13
src/apis/home.ts

@ -0,0 +1,13 @@
import http from 'libs/http'
const baseUrl = '/tasks/'
export const setTargetTemperature = (params: Home.SetTargetTemperatureParams): Promise<null> => http.post('/heat/target-temperature', params)
export const trayTube = (params: Home.TrayTubeParams): Promise<null> => http.post('/tray/tube', params)
export const addTask = (params: Task.TaskAdd): Promise<null> => http.post(baseUrl, params)
export const stopTask = (): Promise<null> => http.post(`${baseUrl}stop`)
export const getTask = (id: number): Promise<Task.Task> => http.get(`${baseUrl}${id}`)
export const taskList = (params: Task.TaskQuery): Promise<Task.Task[]> => http.get(`${baseUrl}list`, { params })
export const getTaskIng = (): Promise<null> => http.get(`${baseUrl}getIngTask`)
export const delTask = (params: string): Promise<null> => http.delete(`${baseUrl}${params}`)
export const trayIn = (): Promise<null> => http.post('tray/in')
export const trayOut = (): Promise<null> => http.post('tray/out')

4
src/apis/login.ts

@ -0,0 +1,4 @@
import http from 'libs/http'
export const login = (params: User.Login): Promise<User.User> => http.post('/auth/login', params)
export const logout = (): Promise<null> => http.post('/auth/logout')

4
src/apis/point.ts

@ -0,0 +1,4 @@
import http from 'libs/http'
export const getPointList = (): Promise<Point.Point[]> => http.get('/device-point/list')
export const updatePoint = (params: Point.UpdateParams): Promise<null> => http.put('/device-point', params)

4
src/apis/self.ts

@ -0,0 +1,4 @@
import http from 'libs/http'
export const getSelfStatus = (): Promise<Record<string, boolean>> => http.get('/self-test/status')
export const getSelfFinish = (params: boolean): Promise<Record<string, boolean>> => http.post('/self-test/finish', { params: { mode: params } })

9
src/apis/solution.ts

@ -0,0 +1,9 @@
import http from 'libs/http'
export const getSolsList = (params: System.Page = { pageNum: 1, pageSize: 999 }): Promise<System.PageResponse<Solution.SolutionItem>> => http.get(`/sols/list`, { params })
export const saveSols = (params: { name: string }): Promise<null> => http.post(`/sols`, params)
export const editSols = (params: { id: number, name: string }): Promise<null> => http.put(`/sols`, params)
export const delSols = (ids: string): Promise<null> => http.delete(`/sols/${ids}`)

12
src/apis/system.ts

@ -0,0 +1,12 @@
import http from 'libs/http'
export const debugControl = <T>(params: System.CmdControlParams<T>): Promise<null> => http.post('/debug/cmd', params)
export const control = <T>(params: System.CmdControlParams<T>): Promise<null> => http.post('/cmd', params)
export const getStatus = (): Promise<System.SystemStatus> => http.get('/sys/device-status')
export const getPoint = (motor: string): Promise<string> => http.get(`/motor/position/${motor}`)
export const requireOutTray = (): Promise<{ moduleCode: string }[]> => http.get('/self-test/require-out-tray')
export const setIgnoreItem = (params: { ignoreSelfTestType: string, ignore: boolean }): Promise<null> => http.post('/self-test/set-ignore-item', params)
export const getTime = (): Promise<number> => http.get('/sys/datetime')
export const setTime = (params: { datetime?: string }): Promise<number> => http.post('/sys/datetime', params)
export const configList = (): Promise<any[]> => http.get('/sys/config-list')
export const updateConfig = (params: System.SystemConfig): Promise<number> => http.post('/sys/update-config', params)

7
src/apis/user.ts

@ -0,0 +1,7 @@
import http from 'libs/http'
export const current = (): Promise<User.User> => http.get('/auth/current')
export const addUser = (params: User.User): Promise<null> => http.post('/user', params)
export const updateUser = (params: User.User): Promise<null> => http.put('/user', params)
export const userList = (params: System.Page): Promise<System.PageResponse<User.User>> => http.get('/user/list', { params })
export const delUser = (params: string): Promise<null> => http.delete(`/user/${params}`)

118
src/app.vue

@ -0,0 +1,118 @@
<script setup lang="ts">
// import { getStatus } from 'apis/system'
import { socket } from 'libs/socket'
import { useSystemStore } from 'stores/systemStore'
import { onBeforeUnmount, onMounted, ref } from 'vue'
const systemStore = useSystemStore()
onMounted(async () => {
// const res = await getStatus()
// console.log(res)
// console.log(systemStore.systemStatus)
// systemStore.updateSystemStatus(res)
startProgress()
})
socket.init((data: System.SystemStatus) => {
console.log(data)
systemStore.updateSystemStatus(data)
}, 'status')
const progress = ref(0)
let timer: any = null
const version = __APP_VERSION__
const startProgress = () => {
const max = Math.floor(Math.random() * (90 - 80 + 1)) + 80
timer = setInterval(() => {
const randomStep = Math.floor(Math.random() * 9 + 1)
if (systemStore.systemStatus.initComplete) {
//
progress.value = Math.min(progress.value + randomStep, 100)
}
else {
progress.value = Math.min(progress.value + randomStep, max)
}
if (progress.value >= 100) {
clearInterval(timer)
}
}, 100)
}
//
onBeforeUnmount(() => {
clearInterval(timer)
})
</script>
<template>
<!-- 进度条容器 -->
<div v-if="progress < 100" class="main-content">
<div class="progress-container">
<div class="progress-bar" :style="{ width: `${progress}%` }" />
<div class="progress-text">
v{{ version }}系统初始化中 {{ progress }}%
</div>
</div>
</div>
<router-view v-else v-slot="{ Component }" class="main-content">
<transition name="el-zoom-in-center">
<component :is="Component" />
</transition>
</router-view>
</template>
<style scoped lang="scss">
.main-content {
width: 100%;
height: 100%;
background: url("assets/images/background.svg") no-repeat center;
background-size: cover;
overflow: hidden;
}
.login-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background: #f0f2f5;
}
img {
width: 600px;
position: absolute;
top: 40%;
right: 20%;
}
.progress-container {
width: 50%;
height: 20px;
background: #e4e7ed;
border-radius: 30px;
position: relative;
top: 90%;
margin: 0 auto;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #1989FA, #096ae0);
border-radius: 30px;
transition: width 0.3s ease;
}
.progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: 12px;
line-height: 12px;
font-weight: 500;
}
</style>

1
src/assets/images/background.svg
File diff suppressed because it is too large
View File

1
src/assets/images/empty.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="160" height="217" viewBox="0 0 160 217"><defs><clipPath id="master_svg0_701_8873"><rect x="0" y="0" width="160" height="217" rx="0"/></clipPath><linearGradient x1="0.8473145961761475" y1="-0.13048571348190308" x2="-0.21846741185260635" y2="0.45531219158762304" id="master_svg1_701_9703"><stop offset="0%" stop-color="#9CA0FF" stop-opacity="1"/><stop offset="100%" stop-color="#9CCFFF" stop-opacity="1"/></linearGradient><linearGradient x1="1.0152618885040283" y1="0.07957261055707932" x2="0.19026522035783114" y2="1.0885049011631125" id="master_svg2_701_9704"><stop offset="0%" stop-color="#F2F3FF" stop-opacity="1"/><stop offset="100%" stop-color="#F2F9FF" stop-opacity="1"/></linearGradient></defs><g clip-path="url(#master_svg0_701_8873)"><g><rect x="41.62646484375" y="0" width="76.74667358398438" height="42.228694915771484" rx="5" fill="url(#master_svg1_701_9703)" fill-opacity="1" style="mix-blend-mode:passthrough"/></g><g><rect x="0" y="21.1171875" width="160" height="195.88375854492188" rx="5" fill="url(#master_svg2_701_9704)" fill-opacity="1" style="mix-blend-mode:passthrough"/></g><g><path d="M108.87600390624999,94.94093875L51.01304390625,94.94093875C48.43994390625,94.94098875,46.35400390625,92.85729875,46.35400390625,90.28692875C46.35605003625,87.71803875,48.44143390625,85.6366722972,51.01304390625,85.63671875L108.87590390625,85.63671875C111.44750390625,85.6366722972,113.53290390625,87.71803875,113.53490390625,90.28692875C113.53500390625,92.85724875,111.44910390625,94.94098875,108.87600390624999,94.94093875Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g><g><path d="M108.87600390624999,123.71047L51.01304390625,123.71047C48.43994390625,123.71052,46.35400390625,121.62683,46.35400390625,119.05646C46.35605003625,116.48757,48.44143390625,114.4062035472,51.01304390625,114.40625L108.87590390625,114.40625C111.44750390625,114.4062035472,113.53290390625,116.48757,113.53490390625,119.05646C113.53500390625,121.62678,111.44910390625,123.71052,108.87600390624999,123.71047Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g><g><path d="M108.87600390624999,152.48000125L51.01304390625,152.48000125C48.43994390625,152.48000125,46.35400390625,150.39631125,46.35400390625,147.82599125C46.35614304625,145.25715125,48.44143390625,143.17578125,51.01304390625,143.17578125L108.87590390625,143.17578125C111.44750390625,143.17578125,113.53280390625,145.25715125,113.53490390625,147.82599125C113.53500390625,150.39631125,111.44910390625,152.48000125,108.87600390624999,152.48000125Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/error.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><defs><clipPath id="master_svg0_685_8859"><rect x="0" y="0" width="80" height="80" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_685_8859)"><g><path d="M39.9853890625,4.94140625C20.7118890625,4.94140625,4.9462890625,20.70700625,4.9462890625,39.98050625C4.9462890625,59.25390625,20.7118890625,75.01950625,39.9853890625,75.01950625C59.2587890625,75.01950625,75.0243890625,59.25390625,75.0243890625,39.98050625C75.0243890625,20.70700625,59.2587890625,4.94140625,39.9853890625,4.94140625ZM56.7274890625,52.05080625C57.8993890625,53.22260625,57.8993890625,55.36330625,56.7274890625,56.52730625C56.1415890625,57.11330625,55.3681890625,57.50390625,54.3915890625,57.50390625C53.6103890625,57.50390625,52.6415890625,57.11330625,52.0556890625,56.52730625L40.7665890625,45.23830625L29.4774890625,56.52730625C28.3056890625,57.69920625,26.1650890625,57.69920625,25.0009890625,56.52730625C23.8368890625,55.35550625,23.8290890625,53.21480625,25.0009890625,52.05080625L36.2900890625,40.76170625L25.0009890625,29.47260625C24.4150890625,28.88670625,24.0243890625,28.11330625,24.0243890625,27.13670625C24.0243890625,26.35550625,24.4150890625,25.38670625,25.0009890625,24.80080625C26.1728890625,23.62890625,28.3134890625,23.62890625,29.4774890625,24.80080625L40.7665890625,36.089806249999995L52.0556890625,24.80080625C52.6415890625,24.21480625,53.4150890625,23.82420625,54.3915890625,23.82420625C55.1728890625,23.82420625,56.1415890625,24.21480625,56.7275890625,24.80080625C57.3134890625,25.38670625,57.7040890625,26.16020625,57.7040890625,27.13670625C57.7040890625,27.91800625,57.3134890625,28.88670625,56.7275890625,29.47260625L45.4384890625,40.76170625L56.7274890625,52.05080625Z" fill="#DF1515" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/expand.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="30.666667938232422" height="26.83333396911621" viewBox="0 0 30.666667938232422 26.83333396911621"><g><path d="M0,0L30.6667,0L30.6667,3.83333L0,3.83333L0,0ZM0,23L30.6667,23L30.6667,26.8333L0,26.8333L0,23 ZM0,11.5L30.6667,11.5L30.6667,15.3333L0,15.3333L0,11.5Z" fill="#1989FA" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></svg>

1
src/assets/images/icon_add_s.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="26.303199768066406" height="26.303173065185547" viewBox="0 0 26.303199768066406 26.303173065185547"><g><path d="M24.5875,14.8666L14.8674,14.8666L14.8674,24.5882C14.8674,25.5356,14.099,26.3032,13.1516,26.3032C12.2043,26.3032,11.4359,25.5356,11.4359,24.5882L11.4359,14.8666L1.71574,14.8666C0.767578,14.8666,0,14.099,0,13.1516C0,12.2046,0.767666,11.4366,1.71574,11.4366L11.4359,11.4366L11.4359,1.71495C11.4359,0.76793,12.2042,0,13.1516,0C14.099,0,14.8673,0.767959,14.8673,1.71495L14.8673,11.4367L24.5874,11.4367C25.5356,11.4367,26.3032,12.2046,26.3032,13.1515C26.3032,14.0989,25.5356,14.8665,24.5874,14.8665L24.5875,14.8666Z" fill="#479CF1" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></svg>

1
src/assets/images/icon_arr_s.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="15" height="23" viewBox="0 0 15 23"><g transform="matrix(-1,0,0,-1,30,46)"><path d="M27.436500000000002,23C27.436500000000002,23,30,25.37044,30,25.37044C30,25.37044,20.12696,34.5,20.12696,34.5C20.12696,34.5,30,43.629599999999996,30,43.629599999999996C30,43.629599999999996,27.436500000000002,46,27.436500000000002,46C27.436500000000002,46,15,34.5,15,34.5C15,34.5,27.436500000000002,23,27.436500000000002,23C27.436500000000002,23,27.436500000000002,23,27.436500000000002,23Z" fill-rule="evenodd" fill="#384D5D" fill-opacity="1"/></g></svg>

1
src/assets/images/icon_del_s.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="33" height="33" viewBox="0 0 33 33"><defs><clipPath id="master_svg0_685_8832"><rect x="0" y="0" width="33" height="33" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_685_8832)"><g><path d="M28.5707296875,29.2627375C27.6653296875,30.1682375,26.1906296875,30.1682375,25.2831296875,29.2627375L4.4580476875,8.4376975C3.5526096875,7.5301975,3.5505466875,6.0575675,4.4580476875,5.1500705C5.3614196875,4.2425705,6.8361096875000005,4.2425705,7.7477296875,5.1500705L28.5707296875,25.9730375C29.4761296875,26.8847375,29.4803296875,28.3552375,28.5707296875,29.2627375ZM28.5460296875,8.4273875L7.7229796875000005,29.2503375C6.8134196875,30.1599375,5.3407996875,30.1599375,4.4332966875,29.2503375C3.5257966875,28.3449375,3.5257966875,26.8743375,4.4353596875,25.9627375L25.2583296875,5.1418205C26.1679296875,4.2343205,27.6426296875,4.2343205,28.5460296875,5.1397585C29.4555296875,6.0472575,29.4514296875,7.519887499999999,28.5460296875,8.4273875Z" fill="#7F7F7F" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/ing.svg
File diff suppressed because it is too large
View File

1
src/assets/images/jaw.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1749632093752" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4247" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M854.33 557.59L750.14 446.03c0.69-5.85 1.05-11.8 1.05-17.84V98.63c0-18.86-15.35-34.21-34.21-34.21H304.23c-18.86 0-34.21 15.35-34.21 34.21v329.56c0 6.94 0.48 13.77 1.39 20.47L169.67 557.59c-54.93 5.75-97.9 52.33-97.9 108.75 0 52.13 36.69 95.79 85.59 106.68l64.18 177.62c1.99 5.49 7.19 9.07 12.91 9.07 0.58 0 1.17-0.03 1.76-0.11 0.29-0.04 0.52-0.19 0.8-0.25 0.41 0.08 0.75 0.37 1.18 0.37 0.45 0 0.91-0.04 1.36-0.13L317.02 944c3.37-0.68 6.29-2.67 7.98-5.47 1.58-2.63 1.98-5.69 1.11-8.62l-58.26-197.28c12.82-16.78 20.97-37.32 22.31-59.7l94.42-97.67c11.87 2.99 24.28 4.59 37.07 4.59h177.91c13.58 0 26.75-1.81 39.29-5.17l94.98 98.26c1.34 22.38 9.49 42.92 22.31 59.7l-58.26 197.28c-0.87 2.93-0.47 5.99 1.11 8.62 1.69 2.8 4.61 4.79 7.98 5.47l77.46 15.59c0.46 0.09 0.91 0.13 1.36 0.13 0.43 0 0.77-0.29 1.18-0.37 0.28 0.05 0.51 0.21 0.8 0.25 0.59 0.08 1.18 0.11 1.76 0.11 5.72 0 10.92-3.57 12.91-9.07l64.18-177.62c48.9-10.89 85.59-54.55 85.59-106.68 0.02-56.43-42.95-103.01-97.88-108.76zM425.69 168.2c15.23 0 27.57 12.34 27.57 27.57s-12.34 27.57-27.57 27.57-27.57-12.34-27.57-27.57c0-15.22 12.34-27.57 27.57-27.57z m-71.1 0c15.23 0 27.57 12.34 27.57 27.57s-12.34 27.57-27.57 27.57-27.57-12.34-27.57-27.57c0-15.22 12.35-27.57 27.57-27.57z m-57.11 140.93h426.25v58.59H297.48v-58.59z m-71.12 574L187.43 775.4c18.26-1.05 35.29-6.56 50.07-15.51l-11.14 123.24z m36.03-207.04c-0.03 0.24-0.08 0.48-0.11 0.72-0.42 3.08-0.98 6.12-1.71 9.09-2.05 8.31-5.48 16.06-9.84 23.16-0.36 0.59-0.64 1.24-1.02 1.82-2.25 3.46-4.79 6.71-7.52 9.79-0.94 1.05-2 1.98-2.99 2.99-1.93 1.96-3.9 3.88-6.01 5.64-1.27 1.05-2.61 2-3.94 2.97a82.524 82.524 0 0 1-6.34 4.26 79.83 79.83 0 0 1-4.44 2.47c-2.3 1.19-4.66 2.24-7.08 3.21-1.52 0.61-3.04 1.24-4.61 1.76-2.71 0.9-5.5 1.58-8.33 2.19-1.39 0.3-2.75 0.7-4.16 0.93-4.29 0.7-8.67 1.13-13.15 1.13-2.78 0-5.52-0.14-8.23-0.42-2.49-0.25-4.95-0.64-7.37-1.11-0.19-0.04-0.39-0.05-0.57-0.09a81.34 81.34 0 0 1-14.97-4.57c-29.75-12.28-50.75-41.57-50.75-75.71 0-39.2 27.7-72.02 64.54-80 2.74-0.59 5.53-1.05 8.36-1.37l0.2-0.03c2.89-0.32 5.82-0.49 8.79-0.49 3.6 0 7.13 0.3 10.61 0.77 0.55 0.07 1.1 0.17 1.65 0.25 3.36 0.53 6.68 1.2 9.9 2.11 28.09 7.91 50.04 30.43 57.19 58.84 0.76 3.01 1.34 6.08 1.77 9.2 0.04 0.28 0.09 0.55 0.13 0.83 0.42 3.24 0.66 6.54 0.66 9.89-0.02 3.33-0.25 6.58-0.66 9.77z m24.06-38.84c-10.67-38.57-41.93-68.62-81.2-77.5l74.05-79.29c13.69 37.17 41.54 67.55 76.99 84.55l-69.84 72.24z m451.1 0l-70.73-73.18c35.22-17.5 62.71-48.3 75.86-85.78l76.07 81.46c-39.27 8.88-70.52 38.93-81.2 77.5z m60.09 245.88L786.5 759.88c14.77 8.96 31.81 14.47 50.07 15.51l-38.93 107.74z m76.37-141.08a82.117 82.117 0 0 1-14.97 4.57c-0.19 0.04-0.38 0.05-0.57 0.09-2.42 0.47-4.87 0.86-7.37 1.11a82.9 82.9 0 0 1-8.23 0.42c-4.48 0-8.86-0.44-13.15-1.13-1.42-0.23-2.77-0.63-4.16-0.93-2.82-0.61-5.62-1.29-8.33-2.19-1.57-0.52-3.08-1.15-4.61-1.76-2.42-0.97-4.78-2.02-7.08-3.21a79.83 79.83 0 0 1-4.44-2.47c-2.19-1.31-4.29-2.75-6.34-4.26-1.33-0.97-2.68-1.92-3.94-2.97-2.12-1.76-4.08-3.68-6.01-5.64-0.99-1.01-2.05-1.93-2.99-2.99-2.73-3.07-5.27-6.33-7.52-9.79-0.38-0.58-0.65-1.23-1.02-1.82-4.37-7.1-7.79-14.85-9.84-23.16-0.73-2.97-1.3-6.01-1.71-9.09-0.03-0.24-0.08-0.48-0.11-0.72-0.4-3.2-0.64-6.44-0.64-9.75 0-3.36 0.24-6.65 0.66-9.89 0.03-0.28 0.09-0.56 0.13-0.83 0.43-3.12 1.01-6.2 1.77-9.2 7.14-28.4 29.09-50.92 57.19-58.84 3.22-0.91 6.54-1.58 9.9-2.11 0.55-0.08 1.09-0.18 1.65-0.25 3.48-0.47 7.01-0.77 10.61-0.77 2.97 0 5.9 0.17 8.79 0.49l0.2 0.03c2.83 0.32 5.62 0.78 8.36 1.37 36.84 7.99 64.54 40.8 64.54 80-0.02 34.12-21.02 63.41-50.77 75.69z" fill="#2c2c2c" p-id="4248"></path><path d="M842.86 666.34m-38.04 0a38.04 38.04 0 1 0 76.08 0 38.04 38.04 0 1 0-76.08 0Z" fill="#2c2c2c" p-id="4249"></path><path d="M181.14 666.34m-38.04 0a38.04 38.04 0 1 0 76.08 0 38.04 38.04 0 1 0-76.08 0Z" fill="#2c2c2c" p-id="4250"></path><path d="M511.3 660.66c59.33 0 91.96-44.32 94.93-57.42H416.37c8.9 31.77 51.13 57.42 94.93 57.42z" fill="#2c2c2c" p-id="4251"></path></svg>

1
src/assets/images/liquied/liquied_bottle.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="162.44374084472656" height="227.99989318847656" viewBox="0 0 162.44374084472656 227.99989318847656"><g><path d="M0,54.0618L0,64.2474C0,65.1128,0.699314,65.8144,1.56196,65.8144C2.4246,65.8144,3.12392,66.516,3.12392,67.3814L3.12392,73.6495C3.12392,74.5149,2.4246,75.2165,1.56196,75.2165C0.699314,75.2165,0,75.918,0,76.7835L0,90.8866C0,91.752,0.699314,92.4536,1.56196,92.4536C2.4246,92.4536,3.12392,93.1551,3.12392,94.0206L3.12392,100.289C3.12392,101.154,2.4246,101.856,1.56196,101.856C0.699314,101.856,0,102.557,0,103.423L0,122.227C0,123.092,0.699314,123.794,1.56196,123.794C2.4246,123.794,3.12392,124.495,3.12392,125.361L3.12392,131.629C3.12392,132.494,2.4246,133.196,1.56196,133.196C0.699314,133.196,0,133.897,0,134.763L0,153.567C0,154.432,0.699314,155.134,1.56196,155.134C2.4246,155.134,3.12392,155.836,3.12392,156.701L3.12392,162.969C3.12392,163.834,2.4246,164.536,1.56196,164.536C0.699314,164.536,0,165.238,0,166.103L0,183.34C0,184.206,0.699314,184.907,1.56196,184.907C2.4246,184.907,3.12392,185.609,3.12392,186.474L3.12392,193.526C3.12392,194.391,2.4246,195.093,1.56196,195.093C0.699314,195.093,0,195.794,0,196.66L0,206.062C0,218.178,9.79038,228,21.8674,228L140.576,228C152.653,228,162.444,218.178,162.444,206.062L162.444,196.66C162.444,195.794,161.744,195.093,160.882,195.093C160.019,195.093,159.32,194.391,159.32,193.526L159.32,186.474C159.32,185.609,160.019,184.907,160.882,184.907C161.744,184.907,162.444,184.206,162.444,183.34L162.444,166.103C162.444,165.238,161.744,164.536,160.882,164.536C160.019,164.536,159.32,163.834,159.32,162.969L159.32,156.701C159.32,155.836,160.019,155.134,160.882,155.134C161.744,155.134,162.444,154.432,162.444,153.567L162.444,134.763C162.444,133.897,161.744,133.196,160.882,133.196C160.019,133.196,159.32,132.494,159.32,131.629L159.32,125.361C159.32,124.495,160.019,123.794,160.882,123.794C161.744,123.794,162.444,123.092,162.444,122.227L162.444,103.423C162.444,102.557,161.744,101.856,160.882,101.856C160.019,101.856,159.32,101.154,159.32,100.289L159.32,94.0206C159.32,93.1551,160.019,92.4536,160.882,92.4536C161.744,92.4536,162.444,91.752,162.444,90.8866L162.444,76.7835C162.444,75.918,161.744,75.2165,160.882,75.2165C160.019,75.2165,159.32,74.5149,159.32,73.6495L159.32,67.3814C159.32,66.516,160.019,65.8144,160.882,65.8144C161.744,65.8144,162.444,65.1128,162.444,64.2474L162.444,54.0618C162.444,41.9457,152.653,32.1237,140.576,32.1237L134.962,32.1237C132.711,32.1237,131.583,29.3931,133.175,27.7961C137.914,23.042,140.576,16.594,140.576,9.87062L140.576,4.70103C140.576,2.10472,138.478,0,135.89,0L26.5533,0C23.9654,0,21.8674,2.10472,21.8674,4.70103L21.8674,9.87062C21.8674,16.594,24.5297,23.042,29.2685,27.7961C30.8603,29.3931,29.7329,32.1237,27.4817,32.1237L21.8674,32.1237C9.79038,32.1237,0,41.9457,0,54.0618Z" fill="#EEEFF8" fill-opacity="1"/></g></svg>

1
src/assets/images/login.svg
File diff suppressed because it is too large
View File

1
src/assets/images/logo.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="224" height="233" viewBox="0 0 224 233"><g><g><path d="M223.99979977416993,114.45162603759766C223.99979977416993,124.83402603759765,215.86279977416993,133.25062603759767,205.82579977416992,133.25062603759767C195.7877997741699,133.25062603759767,89.86419977416992,119.90242603759765,46.42679977416992,114.45162603759766C95.25419977416992,108.22022603759766,195.7877997741699,95.65262603759766,205.82579977416992,95.65262603759766C215.86279977416993,95.65264605499766,223.99979977416993,104.06923603759766,223.99979977416993,114.45162603759766Z" fill="#FAC03D" fill-opacity="1"/></g><g><path d="M201.21678225097656,176.92624592285156C201.23438225097658,182.56134592285156,196.82298225097657,187.13904592285155,191.37518225097656,187.13904592285155C185.94078225097655,187.13904592285155,128.59708225097657,179.92384592285157,105.07118225097656,176.95904592285157C131.50778225097656,173.58095592285156,185.94078225097655,166.77894592285156,191.37518225097656,166.77894592285156C196.79818225097657,166.77890588795157,201.19928225097658,171.31673592285156,201.21678225097656,176.92624592285156Z" fill="#FAC03D" fill-opacity="1"/></g><g><path d="M215.93901684570312,149.52852961425782C215.94201684570314,157.61312961425782,209.60701684570313,164.16892961425782,201.79101684570313,164.16892961425782C193.97901684570314,164.16892961425782,114.44741684570312,153.77242961425782,80.63601684570312,149.52852961425782C118.64531684570312,144.68120961425782,193.97901684570314,134.8947296142578,201.79101684570313,134.8947296142578C209.60501684570312,134.8947296142578,215.93901684570312,141.44651961425782,215.93901684570312,149.52852961425782Z" fill="#FAC03D" fill-opacity="1"/></g><g><path d="M112.735,0.0000200174C33.3546,-0.0135318,-21.121,82.656,7.90892,159.078C36.9389,235.5,131.249,257.697,189.387,201.791C181.175,201.01,163.001,198.721,131.435,194.661L127.085,194.11C85.6178,216.585,34.3882,200.014,12.688,157.106C-9.01216,114.197,7.04277,61.2171,48.539,38.7996C90.0352,16.382,141.243,33.0249,162.887,75.9634C169.261,88.781,184.624,93.5998,196.807,86.6023C208.989,79.6049,213.157,63.5677,206.008,51.1954C185.072,19.1593,150.118,-0.0260227,112.735,0.0000200174Z" fill="#275EFB" fill-opacity="1"/></g></g></svg>

1
src/assets/images/logout.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="42" height="43" viewBox="0 0 42 43"><g><g><path d="M23.9012,26.80821171875L23.5184,26.80821171875C19.5186,26.80821171875,15.5189,26.80861171875,11.519,26.80821171875C10.926,26.80801171875,10.5396,26.52971171875,10.4443,26.03191171875C10.4255,25.93251171875,10.4273,25.82801171875,10.4273,25.72631171875C10.4266,22.08721171875,10.4265,18.44831171875,10.4268,14.80931171875C10.4269,14.11901171875,10.792,13.75181171875,11.4767,13.75181171875C15.4946,13.75161171875,19.5126,13.75161171875,23.5307,13.75161171875L23.9012,13.75161171875L23.9012,13.42041171875C23.9012,10.18168171875,23.9008,6.94273171875,23.9016,3.70375171875C23.9016,3.25804771875,24.1092,2.90257571875,24.4607,2.76641631875C24.8652,2.60961481875,25.224,2.68183999875,25.5289,3.01164571875C25.7037,3.20077071875,25.8941,3.3747387187499998,26.0736,3.55867171875C28.3386,5.87259171875,30.6032,8.18756171875,32.8676,10.50147171875C35.7762,13.47271171875,38.6848,16.44331171875,41.5933,19.41441171875C42.0776,19.90851171875,42.0802,20.42981171875,41.6017,20.91861171875C37.4394,25.17281171875,33.2774,29.42731171875,29.1138,33.67981171875C27.9297,34.88921171875,26.7392,36.09131171875,25.5561,37.30171171875C25.2516,37.61371171875,24.912,37.75041171875,24.4963,37.58871171875C24.092,37.43171171875,23.9017,37.10601171875,23.9017,36.59371171875C23.901,33.45751171875,23.9012,30.32081171875,23.9012,27.18461171875L23.9012,26.80821171875L23.9012,26.80821171875ZM16.4243,2.86945471875L16.4243,6.546731718749999L16.0943,6.546731718749999C13.049,6.546731718749999,10.0037,6.54631171875,6.95843,6.54694171875C5.02868,6.54723171875,3.60763,8.00513171875,3.60755,9.98472171875C3.60726,16.83461171875,3.61953,23.68451171875,3.59949,30.53391171875C3.59397,32.34621171875,4.94067,33.65191171875,6.18574,33.89041171875C6.46085,33.94251171875,6.74508,33.96611171875,7.02513,33.96681171875C10.0431,33.97221171875,13.0612,33.970111718750005,16.0793,33.970111718750005L16.4189,33.970111718750005L16.4189,37.62641171875C16.3759,37.63521171875,16.3258,37.65491171875,16.2756,37.65491171875C13.0666,37.65491171875,9.85733,37.68831171875,6.64918,37.64061171875C3.51369,37.59461171875,0.737073,35.15841171875,0.138685,32.005211718750004C0.0542352,31.56101171875,0.0129305,31.10101171875,0.0124397,30.64841171875C0.00466952,23.72381171875,-0.0132019,16.79961171875,0.0166111,9.87552171875C0.0290843,6.98330171875,1.43263,4.90707171875,3.88372,3.55774671875C4.74846,3.08148071875,5.70366,2.86639671875,6.68823,2.86388471875C9.8698,2.85509271875,13.0514,2.86053471875,16.2331,2.86074471875C16.2868,2.86078671875,16.3405,2.86560171875,16.4243,2.86945471875Z" fill="#1989FA" fill-opacity="1"/></g></g></svg>

1
src/assets/images/menuIcon/n_debug.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><defs><clipPath id="master_svg0_592_42038"><rect x="19" y="18.921875" width="41" height="41" rx="0"/></clipPath></defs><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#EEEFF8" fill-opacity="1"/></g><g clip-path="url(#master_svg0_592_42038)"><g><path d="M53.8413984375,21.65234443077L25.1580984375,21.65234443077C23.2666884375,21.652344090385,21.733398779614,23.17788375,21.733399121728,25.05973375L21.733399121728,47.20904375L57.2666984375,47.20904375L57.2666984375,25.05973375C57.2670984375,23.17798375,55.7333984375,21.652344090385,53.8413984375,21.65234443077ZM52.9319984375,35.12904375C52.9319984375,35.83334375,52.3579984375,36.405643749999996,51.6500984375,36.406943749999996L47.5684984375,36.406943749999996C47.3869984375,36.40704375,47.2220984375,36.30194375,47.1462984375,36.13784375L46.7859984375,35.36184375C46.7178984375,35.21544375,46.5724984375,35.11984375,46.410498437499996,35.11504375C46.2483984375,35.11024375,46.0974984375,35.19694375,46.0206984375,35.33904375L42.2247984375,42.38224375C42.0768984375,42.65614375,41.7907984375,42.828143749999995,41.478398437500005,42.83104375C41.165898437500005,42.83394375,40.8765984375,42.66724375,40.7235984375,42.39624375L34.6923984375,31.69304375C34.6111984375,31.54864375,34.4531984375,31.46415375,34.2872984375,31.47640375C34.1214984375,31.48864375,33.9777984375,31.59540375,33.9187984375,31.75014375L32.3505984375,35.85744375C32.224298437499996,36.18814375,31.9057984375,36.40684375,31.5502384375,36.406943749999996L27.3519484375,36.406943749999996C26.6425784375,36.406943749999996,26.067528437500002,35.83474375,26.067528437500002,35.12904375C26.067528437500002,34.42324375,26.6425784375,33.85104375,27.3519484375,33.85104375L29.7753884375,33.85104375C30.1312484375,33.85104375,30.4494884375,33.63214375,30.5757584375,33.30154375L33.1400984375,26.58196375C33.2582984375,26.27280375,33.5454984375,26.05956375,33.8769984375,26.03496375C34.2084984375,26.01035375,34.5243984375,26.17881375,34.687298437500004,26.46711375L41.053898437499996,37.75784375C41.1302984375,37.89354375,41.2749984375,37.97704375,41.431298437500004,37.97554375C41.587698437499995,37.974143749999996,41.7307984375,37.887943750000005,41.804598437500005,37.75084375L45.789198437500005,30.36372375C45.9430984375,30.079963749999997,46.244798437499995,29.906903749999998,46.568698437500004,29.916573749999998C46.8925984375,29.926243749999998,47.1832984375,30.11697375,47.3197984375,30.40940375L48.6858984375,33.35554375C48.8261984375,33.65754375,49.129098437500005,33.85044375,49.4632984375,33.85104375L51.6500984375,33.85104375C52.3592984375,33.85104375,52.9344984375,34.423443750000004,52.9344984375,35.12904375L52.9319984375,35.12904375ZM21.733399121728,48.66654375L21.733399121728,53.77824375C21.733398779614,55.66014375,23.2666884375,57.18564375,25.1580984375,57.18564375L53.8413984375,57.18564375C55.7327984375,57.18564375,57.2660984375,55.66014375,57.2660984375,53.77824375L57.2660984375,48.66654375L21.733399121728,48.66654375ZM42.338998437499995,52.74404375C42.3368984375,53.44894375,41.7630984375,54.01984375,41.054598437500005,54.02194375L27.3519484375,54.02194375C26.6425784375,54.02194375,26.067528437500002,53.449743749999996,26.067528437500002,52.74404375C26.067528437500002,52.03824375,26.6425784375,51.46604375,27.3519484375,51.46604375L41.0519984375,51.46604375C41.761498437499995,51.46674375,42.3368984375,52.03814375,42.338998437499995,52.74404375ZM52.9319984375,52.74404375C52.9291984375,53.447443750000005,52.3570984375,54.01724375,51.6500984375,54.02064375L46.510498437500004,54.02064375C45.8010984375,54.02064375,45.2260984375,53.44854375,45.2260984375,52.74274375C45.2260984375,52.03694375,45.8010984375,51.46484375,46.510498437500004,51.46484375L51.6500984375,51.46484375C52.358098437500004,51.46834375,52.930598437499995,52.03954375,52.9319984375,52.74404375Z" fill="#4F85FB" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></svg>

1
src/assets/images/menuIcon/n_expe.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#EEEFF8" fill-opacity="1"/></g><g><path d="M54.037,56.006L28.80688,56.006C27.22599,56.006,25.93399,54.6781,25.93399,53.0441C25.93399,51.6375,26.888939999999998,50.4627,28.16489,50.1565L28.16489,21.0455056C25.82164,21.380591,24,23.46143,24,25.96836L24,53.0523C24,55.7785,26.15066,58,28.79886,58L54.037,58C54.5706,58,55,57.5574,55,57.0072C55.004000000000005,56.457,54.5706,56.006,54.037,56.006ZM42.742000000000004,37.799800000000005L41.847300000000004,37.799800000000005L41.847300000000004,39.1815L40.5031,39.1815L40.5031,40.1081L41.847300000000004,40.1081L41.847300000000004,41.4898L42.742000000000004,41.4898L42.742000000000004,40.1081L44.090199999999996,40.1081L44.090199999999996,39.1815L42.742000000000004,39.1815L42.742000000000004,37.799800000000005ZM44.090199999999996,29.05031L40.5031,29.05031L40.5031,33.3775L39.2994,35.6817C39.817,35.5824,40.334599999999995,35.5617,40.852199999999996,35.6155C42.2605,35.7644,42.569500000000005,36.8814,44.4393,36.8814C44.985,36.8814,45.4384,36.8193,45.807500000000005,36.6952L44.090199999999996,33.3981L44.090199999999996,29.05031ZM53.2425,21L30.09086,21L30.09086,50.1276L30.5563,50.1276C30.612470000000002,50.1358,30.67266,50.1358,30.73284,50.1358L53.2425,50.1358C54.125299999999996,50.1358,54.8475,49.3912,54.8475,48.4811L54.8475,22.65888C54.8515,21.744634,54.125299999999996,21.000000315617,53.2425,21ZM49.3224,42.424800000000005C49.1619,42.6978,48.881,42.8674,48.5721,42.875699999999995L36.0212,42.875699999999995C35.7042,42.875699999999995,35.4113,42.7061,35.2508,42.424800000000005C35.0903,42.1435,35.082300000000004,41.8001,35.2348,41.5105L39.5963,33.2161L39.5963,29.05031L38.5089,29.05031C38.2521,29.05031,38.0475,28.8352,38.0475,28.57457C38.0475,28.309820000000002,38.2561,28.09884,38.5089,28.09884L46.0643,28.09884C46.3211,28.09884,46.5257,28.31395,46.5257,28.57457C46.5257,28.83933,46.317099999999996,29.05031,46.0643,29.05031L44.9769,29.05031L44.9769,33.2203L46.561800000000005,36.231899999999996L49.3425,41.5105C49.490899999999996,41.8001,49.4869,42.1434,49.3224,42.424800000000005Z" fill="#4F85FB" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/menuIcon/n_home.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#EEEFF8" fill-opacity="1"/></g><g><path d="M20.0370799,43.6256C20.866486,47.6246,22.91091,51.222300000000004,25.94731,54.0277C26.3526,54.4028,26.9106,54.5752,27.46181,54.4956C28.0134,54.4165,28.49784,54.0943,28.77694,53.6208L31.1482,49.591300000000004C31.5442,48.9178,31.451999999999998,48.070499999999996,30.92,47.4942C29.68039,46.1482,28.77662,44.537800000000004,28.28009,42.7903C28.06083,42.015,27.34183,41.4784,26.52231,41.4784L21.82395,41.4784C21.27636,41.4781,20.757754,41.7198,20.411853,42.1363C20.0647341,42.552,19.9270634,43.0991,20.0370799,43.6256ZM44.1657,51.909C43.7696,51.233000000000004,42.9726,50.8869,42.1963,51.053799999999995C40.231899999999996,51.4761,38.1899,51.3965,36.2658,50.8227C35.453900000000004,50.580600000000004,34.579,50.9191,34.1541,51.6398L31.8262,55.5982C31.5578,56.055,31.5084,56.6046,31.691000000000003,57.1004C31.872999999999998,57.5967,32.2697,57.9884,32.7742,58.1703C34.9795,58.9695,37.3089,59.375,39.695,59.375C41.6678,59.375,43.6156,59.0954,45.482600000000005,58.543C46.0156,58.3847,46.446,57.9968,46.652,57.4891C46.8572,56.9806,46.814099999999996,56.408,46.535,55.9346L44.1657,51.909ZM52.5476,24.57127C52.2201,24.3027,51.806799999999996,24.15552,51.379999999999995,24.15542C51.2742,24.15542,51.1663,24.166330000000002,51.060500000000005,24.18424C50.531,24.2771,50.0703,24.59457,49.8016,25.05176L47.4685,29.01324C47.045100000000005,29.73472,47.183499999999995,30.6473,47.802800000000005,31.2165C49.39,32.6707,50.5347,34.5286,51.1096,36.5833C51.3275,37.3604,52.0476,37.8989,52.8691,37.899100000000004L57.568,37.899100000000004C58.1145,37.8988,58.6321,37.658,58.9784,37.2432C59.3258,36.8281,59.464,36.2815,59.3546,35.7553C58.4579,31.3953,56.0407,27.42316,52.5476,24.57127ZM46.652,21.88784C46.446600000000004,21.37975,46.0159,20.99153,45.482600000000005,20.833661C43.6053,20.279568,41.6553,19.99869145,39.695,20.00000417001C37.3089,20.00000417001,34.9795,20.405219,32.7742,21.20446C31.714199999999998,21.58978,31.262,22.8179,31.8262,23.77903L34.1541,27.73687C34.579,28.457549999999998,35.453900000000004,28.79609,36.2658,28.55402C37.3775,28.22171,38.5332,28.05317,39.6953,28.05394C40.530100000000004,28.05394,41.372299999999996,28.14349,42.1963,28.32119C42.9721,28.48767,43.7685,28.14262,44.1657,27.46794L46.5353,23.44182C46.8143,22.96858,46.857299999999995,22.39608,46.652,21.88784ZM57.568,41.4781L52.8691,41.4781C52.0475,41.477599999999995,51.327,42.0163,51.1096,42.7936C50.5345,44.848299999999995,49.3898,46.7063,47.802800000000005,48.160799999999995C47.1834,48.729,47.0449,49.6411,47.4685,50.3618L49.8016,54.3249C50.07,54.7824,50.5309,55.1001,51.060500000000005,55.1927C51.1663,55.2106,51.2742,55.2196,51.379999999999995,55.2196C51.8066,55.2202,52.2199,55.0737,52.5476,54.8057C56.0407,51.9518,58.4579,47.9814,59.3546,43.622C59.4641,43.0957,59.3259,42.549099999999996,58.9784,42.134C58.6326,41.718599999999995,58.1147,41.4778,57.568,41.4781ZM20.411568,37.241C20.757316,37.6573,21.27588,37.8989,21.82367,37.8989L26.52259,37.8989C27.34233,37.8986,28.06128,37.361999999999995,28.280369999999998,36.587C28.776699999999998,34.8389,29.68048,33.2279,30.920299999999997,31.8814C31.4524,31.3057,31.544600000000003,30.4588,31.1485,29.785890000000002L28.77722,25.75641C28.49772,25.28303,28.01361,24.96039,27.46209,24.87994C27.37433,24.86765,27.28578,24.86166,27.19713,24.86203C26.73679,24.86203,26.28729,25.03413,25.9476,25.349510000000002C22.91091,28.15468,20.866486,31.7521,20.0370799,35.7513C19.9271883,36.2779,20.0647273,36.825,20.411568,37.241Z" fill="#4F85FB" fill-opacity="1"/></g></g></svg>

1
src/assets/images/menuIcon/n_liquid.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#EEEFF8" fill-opacity="1"/></g><g><path d="M30.42768,34.2143L30.42768,52.673C30.42768,53.4027,31.06121,54.0357,31.77429,54.0357L48.5584,54.0357C49.2714,54.0357,49.794,53.4027,49.794,52.673L49.794,34.2143L30.42768,34.2143ZM47.8574,46.726600000000005C47.8574,47.091499999999996,47.5684,47.387299999999996,47.2118,47.387299999999996C46.8553,47.387299999999996,46.5663,47.091499999999996,46.5663,46.726600000000005L46.5663,42.762299999999996C46.5663,42.397400000000005,46.8553,42.101600000000005,47.2118,42.101600000000005C47.5684,42.101600000000005,47.8574,42.397400000000005,47.8574,42.762299999999996L47.8574,46.726600000000005ZM47.2673,40.780100000000004C46.910799999999995,40.780100000000004,46.6218,40.4844,46.6218,40.1194C46.6218,39.7545,46.910799999999995,39.4587,47.2673,39.4587C47.6239,39.4587,47.9129,39.7545,47.9129,40.1194C47.9129,40.484300000000005,47.6239,40.780100000000004,47.2673,40.780100000000004ZM54.7629,21L29.21229,21C24.827023,21,24,23.26509,24,24.28292C26.44097,24.63393,26.55442,24.67522,26.55442,27.318080000000002L26.55442,52.673C26.55442,55.5922,28.92203,58,31.77429,58L48.5383,58C51.3904,58,53.6673,55.5922,53.6673,52.673L53.6673,24.96429C53.6673,23.28368,54.6596,21.803098,54.7579,21.6456C54.856300000000005,21.488186,55,21.331926,55,21.236123C55,21.139411,54.9664,21,54.7629,21ZM51.0851,24.96429L51.0851,52.673C51.0851,54.153,50.081,55.3571,48.679500000000004,55.3571L31.77429,55.3571C30.352719999999998,55.3571,29.136589999999998,54.128,29.136589999999998,52.673L29.136589999999998,27.318080000000002C29.136589999999998,26.34254,29.22487,24.24163,28.8277,23.6877C28.90202,23.62221,29.07898,23.64286,29.19211,23.64286L51.1939,23.64286C51.1345,23.64286,51.0851,24.53474,51.0851,24.96429Z" fill="#4F85FB" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/menuIcon/n_liquid_config.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#EEEFF8" fill-opacity="1"/></g><g><path d="M25.010964,27.14686L25.010964,52.3976C25.00721385,52.5305,25.0362768,52.6662,25.0915901,52.791799999999995C25.490033,53.8368,27.08099,54.6141,29.95072,55.1751C33.25546,55.7635,36.6258,56.0395,39.9999,55.9955C43.375,56.0285,46.7406,55.7525,50.0501,55.1741C52.915099999999995,54.6031,54.536100000000005,53.8083,54.9083,52.7909C54.9636,52.6662,54.992599999999996,52.5342,54.9889,52.3976L54.9889,27.14686C55.003699999999995,27.099980000000002,55.003699999999995,27.04982,54.9889,27.00294L54.9889,26.77745C54.9889,25.48132,53.3829,24.50509,50.0454,23.860689999999998C46.734700000000004,23.255981,43.3715,22.9686265,40.0037,23.00271173C36.6286,22.9697126,33.26296,23.245622,29.95353,23.824024C26.64599,24.468420000000002,25.0109623,25.44373,25.0109623,26.74078C24.999922606,26.81525,24.999922606,26.89089,25.0109623,26.96536C24.996346,27.011960000000002,24.996346,27.06176,25.0109623,27.10836L25.0109623,27.14502L25.010964,27.14686ZM46.0047,44.9994C46.0122,44.4503,46.524100000000004,44.0048,47.1578,43.9975L51.8398,43.9975L51.8398,45.9976L47.1578,45.9976C46.524100000000004,45.994,46.0122,45.548500000000004,46.0047,44.9994ZM46.0047,40.4987C46.0122,39.950500000000005,46.524100000000004,39.504999999999995,47.1578,39.497699999999995L51.7292,39.497699999999995L51.7292,41.4868L47.1578,41.4868C46.5278,41.4868,46.0122,41.047799999999995,46.0047,40.4996L46.0047,40.4987ZM46.0047,35.9888C46.0122,35.4388,46.5278,35.001599999999996,47.1578,35.001599999999996L51.8398,35.001599999999996L51.8398,36.990700000000004L47.1578,36.990700000000004C46.524100000000004,36.9824,46.0122,36.537,46.0047,35.987899999999996L46.0047,35.9888ZM29.501649999999998,45.4953C29.501649999999998,44.674,29.71822,43.8637,30.13072,43.1533C31.08773,41.4645,32.13675,39.827200000000005,33.27327,38.2483L33.27327,38.1823L33.51327,38.054L33.578900000000004,37.9981L33.96984,37.9981L33.96984,38.0531L34.035470000000004,38.1191Q34.09172,38.1191,34.09172,38.186C35.5242,39.727599999999995,36.7882,41.411100000000005,37.8633,43.209199999999996C38.841300000000004,44.8373,38.690200000000004,46.8866,37.483599999999996,48.3607C36.2789,49.8228,34.27922,50.3746,32.49232,49.730199999999996C30.70542,49.0858,29.51572,47.3928,29.52697,45.4953L29.501649999999998,45.4953ZM27.45319,27.739919999999998C28.58383,26.75362,29.8326,26.11288,31.13293,25.858060000000002C34.019059999999996,25.06777,37.0069,24.68957,40.0037,24.73517C42.9756,24.6875,45.9494,25.06699,48.8744,25.858060000000002C50.174800000000005,26.10555,51.4273,26.74628,52.5542,27.739919999999998C51.419799999999995,28.71615,50.171,29.35688,48.8744,29.62179C45.9848,30.39315,42.9988,30.76495,40.0037,30.72635C37.0084,30.76678,34.02223,30.39495,31.13293,29.62179C29.83728,29.3633,28.58383,28.72623,27.45319,27.739919999999998Z" fill="#4F85FB" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/menuIcon/n_log.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#EEEFF8" fill-opacity="1"/></g><g><path d="M32,30.00793C32.8,30.00793,33.5,29.30731,33.5,28.506610000000002L33.5,22.50132C33.5,21.700617,32.8,21,32,21C31.2,21,30.5,21.700617,30.5,22.50132L30.5,28.506610000000002C30.5,29.30731,31.2,30.00793,32,30.00793ZM45,30.00793C45.8,30.00793,46.5,29.30731,46.5,28.506610000000002L46.5,22.50132C46.5,21.700617,45.8,21,45,21C44.2,21,43.5,21.700617,43.5,22.50132L43.5,28.506610000000002C43.5,29.30731,44.2,30.00793,45,30.00793ZM40,50.0255C40,45.1212,43,40.8174,47.2,39.0159L28.5,39.0159C27.7,39.0159,27,38.315200000000004,27,37.5145C27,36.7138,27.7,36.0132,28.5,36.0132L47.5,36.0132C48.3,36.0132,49,36.7138,49,37.5145C49,37.9149,48.8,38.215199999999996,48.6,38.5154C49.7,38.215199999999996,50.8,38.015,52,38.015C52.3,38.015,52.7,38.015,53,38.1151L53,29.00705C53,26.80511,51.2,25.00352,49,25.00352L49,28.506610000000002C49,31.0088,47,33.0106,44.5,33.0106C42,33.0106,40,31.0088,40,28.506610000000002L40,25.00352L36,25.00352L36,28.506610000000002C36,31.0088,34,33.0106,31.5,33.0106C29,33.0106,27,31.0088,27,28.506610000000002L27,25.00352C24.8,25.00352,23,26.80511,23,29.00705L23,55.03C23,57.2319,24.8,59.0335,27,59.0335L44.1,59.0335C41.6,56.8315,40,53.6287,40,50.0255ZM37.5,51.026399999999995L28.5,51.026399999999995C27.7,51.026399999999995,27,50.3258,27,49.525099999999995C27,48.7244,27.7,48.0238,28.5,48.0238L37.5,48.0238C38.3,48.0238,39,48.7244,39,49.525099999999995C39,50.3258,38.3,51.026399999999995,37.5,51.026399999999995ZM37.5,45.021100000000004L28.5,45.021100000000004C27.7,45.021100000000004,27,44.320499999999996,27,43.519800000000004C27,42.7191,27.7,42.0185,28.5,42.0185L37.5,42.0185C38.3,42.0185,39,42.7191,39,43.519800000000004C39,44.320499999999996,38.3,45.021100000000004,37.5,45.021100000000004ZM52,41.0176C47,41.0176,43,45.1212,43,50.0255C43,54.9299,47.1,59.0335,52,59.0335C56.9,59.0335,61,54.9299,61,50.0255C61,45.1212,57,41.0176,52,41.0176ZM55.5,53.0282L50.5,53.0282C49.7,53.0282,49,52.327600000000004,49,51.5269L49,45.5216C49,44.7209,49.7,44.0203,50.5,44.0203C51.3,44.0203,52,44.7209,52,45.5216L52,50.0255L55.5,50.0255C56.3,50.0255,57,50.7262,57,51.5269C57,52.327600000000004,56.3,53.0282,55.5,53.0282Z" fill="#4F85FB" fill-opacity="1"/></g></g></svg>

1
src/assets/images/menuIcon/n_ore.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#EEEFF8" fill-opacity="1"/></g><g><path d="M51.951499999999996,39.8059C50.843199999999996,40.323899999999995,49.9413,40.7615,48.9636,41.225899999999996C48.4242,40.9535,47.7542,40.408699999999996,47.004000000000005,40.2435C44.3744,39.6987,41.8501,42.1279,41.9554,44.9411C41.9554,45.378699999999995,41.593,46.0619,41.2348,46.1958C39.8694,46.687,39.2499,47.723,39.174099999999996,49.0894C39.0982,51.2194,38.9423,53.4297,41.5467,54.2513C41.7531,54.3049,41.829,54.7693,41.959599999999995,55.0417C41.652,55.2069,41.3149,55.5329,41.0072,55.5329C37.5305,55.5597,34.0496,55.5597,30.5729,55.5597C28.97572,55.5597,27.37854,55.5865,25.78137,55.5597C23.745912,55.5061,23,54.6577,23,52.527699999999996L23,23.88686C23,21.895306,23.720627,21.0468861,25.62966,21.020094C33.539699999999996,20.99330188,41.4497,20.99330188,49.364000000000004,21.020094C51.037,21.020094,51.9136,21.895306,51.9388,23.83327C52.0021,29.18278,51.951499999999996,34.5635,51.951499999999996,39.8059ZM37.6232,24.75761C37.6232,24.730809999999998,37.6232,24.730809999999998,37.6232,24.75761C35.2001,24.75761,32.776920000000004,24.730809999999998,30.35798,24.75761C29.17379,24.75761,28.19188,25.76678,28.21717,26.86079C28.26774,27.9816,29.144289999999998,28.79876,30.35798,28.79876C35.0737,28.79876,39.763999999999996,28.79876,44.4797,28.77197C45.950500000000005,28.77197,46.591,28.00839,46.565799999999996,26.56161C46.5405,25.44081,45.765100000000004,24.78886,44.4797,24.78886C42.183,24.730809999999998,39.915800000000004,24.75761,37.6232,24.75761ZM33.7841,36.3899C35.0189,36.3899,36.2578,36.4435,37.4673,36.3631C38.445,36.3095,39.1951,35.7335,39.2963,34.6171C39.4269,33.3624,38.6515,32.4336,37.6485,32.38C35.0737,32.2416,32.46928,32.4336,29.8902,32.5184C28.68073,32.5452,28.242449999999998,33.5544,28.26774,34.568C28.29302,35.7424,29.09372,36.3988,30.25262,36.3988L33.7841,36.3988L33.7841,36.3899ZM56.9495,50.3441C57.156,51.326499999999996,56.7177,51.9294,55.8412,52.3089C54.2693,52.9921,53.856300000000005,53.7825,54.1934,55.5016C54.3999,56.5644,53.7046,57.002,53.0851,57.4128C52.4403,57.8236,51.7956,58.3952,50.893699999999995,57.6048C49.6295,56.4572,48.727599999999995,56.6225,47.210499999999996,57.5245C46.6964,57.8236,45.8198,57.7968,45.2256,57.578C44.504999999999995,57.3057,43.7549,56.8145,43.9867,55.6669C44.3491,53.8941,43.5231,52.7465,42.081900000000005,52.143699999999995C40.9988,51.6793,41.1547,50.7237,41.18,49.9869C41.2053,49.415400000000005,41.6436,48.566900000000004,42.1071,48.3213C43.5231,47.584599999999995,44.1679,47.0085,44.0415,44.6062C43.965599999999995,43.324600000000004,44.787400000000005,43.021,45.4827,42.695C45.9463,42.4494,46.8228,42.3958,47.185199999999995,42.695C48.5001,43.815799999999996,49.7348,43.7577,50.948499999999996,42.7218C52.0821,41.7662,52.7269,42.775400000000005,53.5529,43.2398C54.3536,43.6774,54.2988,44.2757,54.0923,45.0706C53.7847,46.298500000000004,54.4547,47.6917,55.6136,48.0757C56.7177,48.4062,57.0802,49.143,56.9495,50.3441ZM45.7145,49.9333C45.6892,52.143699999999995,46.9787,53.5905,49.039500000000004,53.6173C50.7673,53.6441,52.259100000000004,52.2509,52.313900000000004,50.585300000000004C52.3645,48.2365,51.0497,46.682500000000005,48.9383,46.5977C47.210499999999996,46.5486,45.7398,48.049,45.7145,49.9333Z" fill="#4F85FB" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/menuIcon/n_point.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><defs><clipPath id="master_svg0_94_4549"><rect x="18" y="18" width="44" height="44" rx="0"/></clipPath></defs><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#EEEFF8" fill-opacity="1"/></g><g clip-path="url(#master_svg0_94_4549)"><g><path d="M39.9964013671875,20.74951171875C31.6590513671875,20.74951171875,24.8739013671875,27.53466171875,24.8739013671875,35.87201171875C24.8739013671875,43.92101171875,38.4294013671875,58.21571171875,39.0059013671875,58.82171171875C39.2646013671875,59.09521171875,39.6268013671875,59.25041171875,40.0038013671875,59.25041171875L40.0333013671875,59.25041171875C40.4177013671875,59.25041171875,40.7872013671875,59.07311171875,41.0385013671875,58.77741171875L45.7320013671875,53.40401171875C51.9702013671875,45.76881171875,55.126201367187505,39.87061171875,55.126201367187505,35.87941171875C55.126201367187505,27.53471171875,48.3411013671875,20.74955640795,39.9964013671875,20.74951171875ZM39.9964013671875,42.745811718750005C36.1973013671875,42.745811718750005,33.1225213671875,39.67101171875,33.1225213671875,35.87201171875C33.1225213671875,32.07291171875,36.1973013671875,28.998131718750003,39.9964013671875,28.998131718750003C43.7954013671875,28.998131718750003,46.870201367187505,32.07291171875,46.870201367187505,35.87201171875C46.870201367187505,39.67101171875,43.7954013671875,42.745811718750005,39.9964013671875,42.745811718750005Z" fill="#4F85FB" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></svg>

1
src/assets/images/menuIcon/n_ps.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><defs><clipPath id="master_svg0_94_4745"><rect x="21" y="59" width="38" height="38" rx="0"/></clipPath></defs><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#479CF1" fill-opacity="1"/></g><g transform="matrix(1,0,0,-1,0,118)" clip-path="url(#master_svg0_94_4745)"><g><path d="M56.9528,97L23.04725,97C21.916585,97,21,96.0834,21,94.9528L21,61.04725C21,59.91675,21.91675,59,23.04725,59L56.9551,59C58.0832,59,59,59.91675,59,61.04725L59,94.9551C58.9987,96.0849,58.0825,97,56.9528,97ZM50.8134,83.21549999999999L50.8134,64.88525C50.8108,64.25542,50.300799999999995,63.74548,49.671,63.74288C49.0412,63.74548,48.5312,64.25541,48.5286,64.88525L48.5286,83.2226C46.297200000000004,83.8023,44.843599999999995,85.9491,45.1338,88.2362C45.424099999999996,90.5233,47.3679,92.239,49.6734,92.24289999999999C51.9827,92.2411,53.9304,90.52250000000001,54.2198,88.2313C54.5091,85.9402,53.0497,83.7914,50.8134,83.21549999999999ZM41.142399999999995,73.5754L41.142399999999995,64.90663C41.1398,64.27679,40.6298,63.76685,40,63.76425C39.3702,63.76685,38.8602,64.27679,38.857600000000005,64.90663L38.857600000000005,73.5754C36.839,74.1033,35.4307,75.9268,35.430099999999996,78.0133C35.4296,80.0999,36.8369,81.9241,38.8553,82.4531L38.8553,91.1219C38.8553,91.74889999999999,39.370599999999996,92.2642,39.9976,92.2642C40.6275,92.2616,41.1374,91.7517,41.14,91.1219L41.14,82.4531C43.1652,81.93209999999999,44.5811,80.10640000000001,44.5816,78.0152C44.5822,75.924,43.1673,74.0975,41.142399999999995,73.5754ZM30.33138,63.73575C28.02941,63.74415,26.09074,65.45857,25.80085,67.74223C25.51095,70.02590000000001,26.95977,72.1703,29.186619999999998,72.7536L29.186619999999998,91.09100000000001C29.186619999999998,91.718,29.701999999999998,92.2334,30.329,92.2334C30.95883,92.2308,31.4688,91.7208,31.4714,91.09100000000001L31.4714,72.7536C33.7053,72.1781,35.1629,70.0312,34.873599999999996,67.74254C34.584199999999996,65.45384,32.6383,63.73726,30.33138,63.73575Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></svg>

1
src/assets/images/menuIcon/n_setting.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#EEEFF8" fill-opacity="1"/></g><g><g><path d="M57.7309,36.6424L54.4199,35.1065C52.2821,34.1115,51.0169,31.80957,51.2919,29.41513L51.7134,25.70348C49.9764,24.13487,47.9673,22.91636,45.7913,22.111649L42.809,24.29289C40.9002,25.70218,38.3223,25.67794,36.438900000000004,24.23298L33.5495,22C31.3612,22.766417,29.33189,23.9468,27.56639,25.48018L27.92691,29.18911C28.16995,31.58721,26.871940000000002,33.8705,24.71933,34.8315L21.35269,36.3074C20.9039103,38.6357,20.883231,41.0297,21.291719,43.3658L24.60269,44.9071C26.74368,45.8995,28.01241,48.202,27.73869,50.5985L27.30925,54.2911C29.0484,55.858,31.056800000000003,57.0779,33.2313,57.8884L36.1897,55.7098C38.098,54.2997,40.6759,54.3229,42.559799999999996,55.767L45.4705,58C47.6601,57.2361,49.6906,56.0565,51.456199999999995,54.5225L51.0984,50.8054C50.8651,48.409099999999995,52.158699999999996,46.1352,54.306,45.1685L57.6461,43.6953C58.0967,41.3643,58.1179,38.9679,57.7044,36.6287L57.7309,36.6424ZM39.5113,49.514700000000005C34.3981,49.514700000000005,30.25307,45.2566,30.25307,40.0041C30.25307,34.7515,34.3981,30.49349,39.5113,30.49349C44.6245,30.49349,48.7696,34.7515,48.7696,40.0041C48.7696,45.2566,44.6245,49.514700000000005,39.5113,49.514700000000005Z" fill="#4F85FB" fill-opacity="1" style="mix-blend-mode:passthrough"/></g><g><path d="M39.503226597900394,32.666666984558105C35.85293659790039,32.6648691245581,32.89285659790039,35.650166984558105,32.89285659790039,39.3333369845581C32.89285659790039,43.01646698455811,35.85293659790039,46.0017669845581,39.503226597900394,45.99996698455811C43.15095659790039,45.998166984558104,46.10715659790039,43.01396698455811,46.10715659790039,39.3333369845581C46.10715659790039,35.65270698455811,43.15095659790039,32.66846320455811,39.503226597900394,32.666666984558105ZM39.503226597900394,43.0462669845581C37.535416597900394,42.95376698455811,35.98716659790039,41.316986984558106,35.98716659790039,39.329296984558106C35.98716659790039,37.34159698455811,37.535416597900394,35.704856984558106,39.503226597900394,35.61227698455811C41.495636597900386,35.7097269845581,43.08915659790039,37.317586984558105,43.185756597900394,39.3279469845581C43.17395659790039,41.372516984558104,41.52956659790039,43.024566984558106,39.503226597900394,43.0274669845581L39.503226597900394,43.0462669845581Z" fill="#4F85FB" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></svg>

1
src/assets/images/menuIcon/n_user.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#EEEFF8" fill-opacity="1"/></g><g><path d="M29.44471,32.3043C29.44472,37.9952,34.0687,42.608599999999996,39.772800000000004,42.608599999999996C45.4768,42.608599999999996,50.100899999999996,37.9952,50.100899999999996,32.3043C50.100899999999996,26.6134,45.4768,22,39.772800000000004,22C34.0687,22,29.44472,26.6134,29.44471,32.3043ZM55.678,46.5004L51.0002,46.5004C50.2702,46.5004,49.678200000000004,45.9097,49.678200000000004,45.1814C49.678200000000004,44.4531,50.2702,43.8625,51.0002,43.8625L55.678,43.8625C56.408,43.8625,57,44.4531,57,45.1814C57,45.9097,56.408,46.5004,55.678,46.5004ZM55.678,52.2502L47.580799999999996,52.2502C46.8508,52.2502,46.2588,51.6595,46.2588,50.931200000000004C46.2588,50.2029,46.8508,49.612300000000005,47.580799999999996,49.612300000000005L55.678,49.612300000000005C56.408,49.612300000000005,57,50.2029,57,50.931200000000004C57,51.6595,56.408,52.2502,55.678,52.2502ZM55.678,58L47.580799999999996,58C46.8508,58,46.2588,57.4094,46.2588,56.681C46.2588,55.9527,46.8508,55.3621,47.580799999999996,55.3621L55.678,55.3621C56.408,55.3621,57,55.9527,57,56.681C57,57.4094,56.408,58,55.678,58ZM41.260000000000005,52.9303C41.260000000000005,48.602900000000005,43.934200000000004,44.898700000000005,47.723299999999995,43.3724C48.3662,43.113600000000005,48.4289,42.229,47.825,41.8898C45.435,40.546099999999996,42.6911,39.782,39.772800000000004,39.782C30.50933,39.782,23,47.4797,23,56.9757C23,57.0475,23.000413211,57.1188,23.00123932,57.1901C23.0066098,57.6398,23.376768,58,23.827485,58L41.2753,58C41.865700000000004,58,42.262299999999996,57.4015,42.037099999999995,56.857C41.5364,55.6469,41.260000000000005,54.3209,41.260000000000005,52.9303Z" fill="#4F85FB" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/menuIcon/s_debug.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><defs><clipPath id="master_svg0_592_42099"><rect x="19" y="18.921875" width="41" height="41" rx="0"/></clipPath></defs><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#479CF1" fill-opacity="1"/></g><g clip-path="url(#master_svg0_592_42099)"><g><path d="M53.8413984375,21.65234443077L25.1580984375,21.65234443077C23.2666884375,21.652344090385,21.733398779614,23.17788375,21.733399121728,25.05973375L21.733399121728,47.20904375L57.2666984375,47.20904375L57.2666984375,25.05973375C57.2670984375,23.17798375,55.7333984375,21.652344090385,53.8413984375,21.65234443077ZM52.9319984375,35.12904375C52.9319984375,35.83334375,52.3579984375,36.405643749999996,51.6500984375,36.406943749999996L47.5684984375,36.406943749999996C47.3869984375,36.40704375,47.2220984375,36.30194375,47.1462984375,36.13784375L46.7859984375,35.36184375C46.7178984375,35.21544375,46.5724984375,35.11984375,46.410498437499996,35.11504375C46.2483984375,35.11024375,46.0974984375,35.19694375,46.0206984375,35.33904375L42.2247984375,42.38224375C42.0768984375,42.65614375,41.7907984375,42.828143749999995,41.478398437500005,42.83104375C41.165898437500005,42.83394375,40.8765984375,42.66724375,40.7235984375,42.39624375L34.6923984375,31.69304375C34.6111984375,31.54864375,34.4531984375,31.46415375,34.2872984375,31.47640375C34.1214984375,31.48864375,33.9777984375,31.59540375,33.9187984375,31.75014375L32.3505984375,35.85744375C32.224298437499996,36.18814375,31.9057984375,36.40684375,31.5502384375,36.406943749999996L27.3519484375,36.406943749999996C26.6425784375,36.406943749999996,26.067528437500002,35.83474375,26.067528437500002,35.12904375C26.067528437500002,34.42324375,26.6425784375,33.85104375,27.3519484375,33.85104375L29.7753884375,33.85104375C30.1312484375,33.85104375,30.4494884375,33.63214375,30.5757584375,33.30154375L33.1400984375,26.58196375C33.2582984375,26.27280375,33.5454984375,26.05956375,33.8769984375,26.03496375C34.2084984375,26.01035375,34.5243984375,26.17881375,34.687298437500004,26.46711375L41.053898437499996,37.75784375C41.1302984375,37.89354375,41.2749984375,37.97704375,41.431298437500004,37.97554375C41.587698437499995,37.974143749999996,41.7307984375,37.887943750000005,41.804598437500005,37.75084375L45.789198437500005,30.36372375C45.9430984375,30.079963749999997,46.244798437499995,29.906903749999998,46.568698437500004,29.916573749999998C46.8925984375,29.926243749999998,47.1832984375,30.11697375,47.3197984375,30.40940375L48.6858984375,33.35554375C48.8261984375,33.65754375,49.129098437500005,33.85044375,49.4632984375,33.85104375L51.6500984375,33.85104375C52.3592984375,33.85104375,52.9344984375,34.423443750000004,52.9344984375,35.12904375L52.9319984375,35.12904375ZM21.733399121728,48.66654375L21.733399121728,53.77824375C21.733398779614,55.66014375,23.2666884375,57.18564375,25.1580984375,57.18564375L53.8413984375,57.18564375C55.7327984375,57.18564375,57.2660984375,55.66014375,57.2660984375,53.77824375L57.2660984375,48.66654375L21.733399121728,48.66654375ZM42.338998437499995,52.74404375C42.3368984375,53.44894375,41.7630984375,54.01984375,41.054598437500005,54.02194375L27.3519484375,54.02194375C26.6425784375,54.02194375,26.067528437500002,53.449743749999996,26.067528437500002,52.74404375C26.067528437500002,52.03824375,26.6425784375,51.46604375,27.3519484375,51.46604375L41.0519984375,51.46604375C41.761498437499995,51.46674375,42.3368984375,52.03814375,42.338998437499995,52.74404375ZM52.9319984375,52.74404375C52.9291984375,53.447443750000005,52.3570984375,54.01724375,51.6500984375,54.02064375L46.510498437500004,54.02064375C45.8010984375,54.02064375,45.2260984375,53.44854375,45.2260984375,52.74274375C45.2260984375,52.03694375,45.8010984375,51.46484375,46.510498437500004,51.46484375L51.6500984375,51.46484375C52.358098437500004,51.46834375,52.930598437499995,52.03954375,52.9319984375,52.74404375Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></svg>

1
src/assets/images/menuIcon/s_expe.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#479CF1" fill-opacity="1"/></g><g><path d="M54.037,56.006L28.80688,56.006C27.22599,56.006,25.93399,54.6781,25.93399,53.0441C25.93399,51.6375,26.888939999999998,50.4627,28.16489,50.1565L28.16489,21.0455056C25.82164,21.380591,24,23.46143,24,25.96836L24,53.0523C24,55.7785,26.15066,58,28.79886,58L54.037,58C54.5706,58,55,57.5574,55,57.0072C55.004000000000005,56.457,54.5706,56.006,54.037,56.006ZM42.742000000000004,37.799800000000005L41.847300000000004,37.799800000000005L41.847300000000004,39.1815L40.5031,39.1815L40.5031,40.1081L41.847300000000004,40.1081L41.847300000000004,41.4898L42.742000000000004,41.4898L42.742000000000004,40.1081L44.090199999999996,40.1081L44.090199999999996,39.1815L42.742000000000004,39.1815L42.742000000000004,37.799800000000005ZM44.090199999999996,29.05031L40.5031,29.05031L40.5031,33.3775L39.2994,35.6817C39.817,35.5824,40.334599999999995,35.5617,40.852199999999996,35.6155C42.2605,35.7644,42.569500000000005,36.8814,44.4393,36.8814C44.985,36.8814,45.4384,36.8193,45.807500000000005,36.6952L44.090199999999996,33.3981L44.090199999999996,29.05031ZM53.2425,21L30.09086,21L30.09086,50.1276L30.5563,50.1276C30.612470000000002,50.1358,30.67266,50.1358,30.73284,50.1358L53.2425,50.1358C54.125299999999996,50.1358,54.8475,49.3912,54.8475,48.4811L54.8475,22.65888C54.8515,21.744634,54.125299999999996,21.000000315617,53.2425,21ZM49.3224,42.424800000000005C49.1619,42.6978,48.881,42.8674,48.5721,42.875699999999995L36.0212,42.875699999999995C35.7042,42.875699999999995,35.4113,42.7061,35.2508,42.424800000000005C35.0903,42.1435,35.082300000000004,41.8001,35.2348,41.5105L39.5963,33.2161L39.5963,29.05031L38.5089,29.05031C38.2521,29.05031,38.0475,28.8352,38.0475,28.57457C38.0475,28.309820000000002,38.2561,28.09884,38.5089,28.09884L46.0643,28.09884C46.3211,28.09884,46.5257,28.31395,46.5257,28.57457C46.5257,28.83933,46.317099999999996,29.05031,46.0643,29.05031L44.9769,29.05031L44.9769,33.2203L46.561800000000005,36.231899999999996L49.3425,41.5105C49.490899999999996,41.8001,49.4869,42.1434,49.3224,42.424800000000005Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/menuIcon/s_home.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#479CF1" fill-opacity="1"/></g><g><path d="M20.0370799,43.6256C20.866486,47.6246,22.91091,51.222300000000004,25.94731,54.0277C26.3526,54.4028,26.9106,54.5752,27.46181,54.4956C28.0134,54.4165,28.49784,54.0943,28.77694,53.6208L31.1482,49.591300000000004C31.5442,48.9178,31.451999999999998,48.070499999999996,30.92,47.4942C29.68039,46.1482,28.77662,44.537800000000004,28.28009,42.7903C28.06083,42.015,27.34183,41.4784,26.52231,41.4784L21.82395,41.4784C21.27636,41.4781,20.757754,41.7198,20.411853,42.1363C20.0647341,42.552,19.9270634,43.0991,20.0370799,43.6256ZM44.1657,51.909C43.7696,51.233000000000004,42.9726,50.8869,42.1963,51.053799999999995C40.231899999999996,51.4761,38.1899,51.3965,36.2658,50.8227C35.453900000000004,50.580600000000004,34.579,50.9191,34.1541,51.6398L31.8262,55.5982C31.5578,56.055,31.5084,56.6046,31.691000000000003,57.1004C31.872999999999998,57.5967,32.2697,57.9884,32.7742,58.1703C34.9795,58.9695,37.3089,59.375,39.695,59.375C41.6678,59.375,43.6156,59.0954,45.482600000000005,58.543C46.0156,58.3847,46.446,57.9968,46.652,57.4891C46.8572,56.9806,46.814099999999996,56.408,46.535,55.9346L44.1657,51.909ZM52.5476,24.57127C52.2201,24.3027,51.806799999999996,24.15552,51.379999999999995,24.15542C51.2742,24.15542,51.1663,24.166330000000002,51.060500000000005,24.18424C50.531,24.2771,50.0703,24.59457,49.8016,25.05176L47.4685,29.01324C47.045100000000005,29.73472,47.183499999999995,30.6473,47.802800000000005,31.2165C49.39,32.6707,50.5347,34.5286,51.1096,36.5833C51.3275,37.3604,52.0476,37.8989,52.8691,37.899100000000004L57.568,37.899100000000004C58.1145,37.8988,58.6321,37.658,58.9784,37.2432C59.3258,36.8281,59.464,36.2815,59.3546,35.7553C58.4579,31.3953,56.0407,27.42316,52.5476,24.57127ZM46.652,21.88784C46.446600000000004,21.37975,46.0159,20.99153,45.482600000000005,20.833661C43.6053,20.279568,41.6553,19.99869145,39.695,20.00000417001C37.3089,20.00000417001,34.9795,20.405219,32.7742,21.20446C31.714199999999998,21.58978,31.262,22.8179,31.8262,23.77903L34.1541,27.73687C34.579,28.457549999999998,35.453900000000004,28.79609,36.2658,28.55402C37.3775,28.22171,38.5332,28.05317,39.6953,28.05394C40.530100000000004,28.05394,41.372299999999996,28.14349,42.1963,28.32119C42.9721,28.48767,43.7685,28.14262,44.1657,27.46794L46.5353,23.44182C46.8143,22.96858,46.857299999999995,22.39608,46.652,21.88784ZM57.568,41.4781L52.8691,41.4781C52.0475,41.477599999999995,51.327,42.0163,51.1096,42.7936C50.5345,44.848299999999995,49.3898,46.7063,47.802800000000005,48.160799999999995C47.1834,48.729,47.0449,49.6411,47.4685,50.3618L49.8016,54.3249C50.07,54.7824,50.5309,55.1001,51.060500000000005,55.1927C51.1663,55.2106,51.2742,55.2196,51.379999999999995,55.2196C51.8066,55.2202,52.2199,55.0737,52.5476,54.8057C56.0407,51.9518,58.4579,47.9814,59.3546,43.622C59.4641,43.0957,59.3259,42.549099999999996,58.9784,42.134C58.6326,41.718599999999995,58.1147,41.4778,57.568,41.4781ZM20.411568,37.241C20.757316,37.6573,21.27588,37.8989,21.82367,37.8989L26.52259,37.8989C27.34233,37.8986,28.06128,37.361999999999995,28.280369999999998,36.587C28.776699999999998,34.8389,29.68048,33.2279,30.920299999999997,31.8814C31.4524,31.3057,31.544600000000003,30.4588,31.1485,29.785890000000002L28.77722,25.75641C28.49772,25.28303,28.01361,24.96039,27.46209,24.87994C27.37433,24.86765,27.28578,24.86166,27.19713,24.86203C26.73679,24.86203,26.28729,25.03413,25.9476,25.349510000000002C22.91091,28.15468,20.866486,31.7521,20.0370799,35.7513C19.9271883,36.2779,20.0647273,36.825,20.411568,37.241Z" fill="#FFFFFF" fill-opacity="1"/></g></g></svg>

1
src/assets/images/menuIcon/s_liquid.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#479CF1" fill-opacity="1"/></g><g><path d="M30.42768,34.2143L30.42768,52.673C30.42768,53.4027,31.06121,54.0357,31.77429,54.0357L48.5584,54.0357C49.2714,54.0357,49.794,53.4027,49.794,52.673L49.794,34.2143L30.42768,34.2143ZM47.8574,46.726600000000005C47.8574,47.091499999999996,47.5684,47.387299999999996,47.2118,47.387299999999996C46.8553,47.387299999999996,46.5663,47.091499999999996,46.5663,46.726600000000005L46.5663,42.762299999999996C46.5663,42.397400000000005,46.8553,42.101600000000005,47.2118,42.101600000000005C47.5684,42.101600000000005,47.8574,42.397400000000005,47.8574,42.762299999999996L47.8574,46.726600000000005ZM47.2673,40.780100000000004C46.910799999999995,40.780100000000004,46.6218,40.4844,46.6218,40.1194C46.6218,39.7545,46.910799999999995,39.4587,47.2673,39.4587C47.6239,39.4587,47.9129,39.7545,47.9129,40.1194C47.9129,40.484300000000005,47.6239,40.780100000000004,47.2673,40.780100000000004ZM54.7629,21L29.21229,21C24.827023,21,24,23.26509,24,24.28292C26.44097,24.63393,26.55442,24.67522,26.55442,27.318080000000002L26.55442,52.673C26.55442,55.5922,28.92203,58,31.77429,58L48.5383,58C51.3904,58,53.6673,55.5922,53.6673,52.673L53.6673,24.96429C53.6673,23.28368,54.6596,21.803098,54.7579,21.6456C54.856300000000005,21.488186,55,21.331926,55,21.236123C55,21.139411,54.9664,21,54.7629,21ZM51.0851,24.96429L51.0851,52.673C51.0851,54.153,50.081,55.3571,48.679500000000004,55.3571L31.77429,55.3571C30.352719999999998,55.3571,29.136589999999998,54.128,29.136589999999998,52.673L29.136589999999998,27.318080000000002C29.136589999999998,26.34254,29.22487,24.24163,28.8277,23.6877C28.90202,23.62221,29.07898,23.64286,29.19211,23.64286L51.1939,23.64286C51.1345,23.64286,51.0851,24.53474,51.0851,24.96429Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/menuIcon/s_liquid_config.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#479CF1" fill-opacity="1"/></g><g><path d="M25.010964,27.14686L25.010964,52.3976C25.00721385,52.5305,25.0362768,52.6662,25.0915901,52.791799999999995C25.490033,53.8368,27.08099,54.6141,29.95072,55.1751C33.25546,55.7635,36.6258,56.0395,39.9999,55.9955C43.375,56.0285,46.7406,55.7525,50.0501,55.1741C52.915099999999995,54.6031,54.536100000000005,53.8083,54.9083,52.7909C54.9636,52.6662,54.992599999999996,52.5342,54.9889,52.3976L54.9889,27.14686C55.003699999999995,27.099980000000002,55.003699999999995,27.04982,54.9889,27.00294L54.9889,26.77745C54.9889,25.48132,53.3829,24.50509,50.0454,23.860689999999998C46.734700000000004,23.255981,43.3715,22.9686265,40.0037,23.00271173C36.6286,22.9697126,33.26296,23.245622,29.95353,23.824024C26.64599,24.468420000000002,25.0109623,25.44373,25.0109623,26.74078C24.999922606,26.81525,24.999922606,26.89089,25.0109623,26.96536C24.996346,27.011960000000002,24.996346,27.06176,25.0109623,27.10836L25.0109623,27.14502L25.010964,27.14686ZM46.0047,44.9994C46.0122,44.4503,46.524100000000004,44.0048,47.1578,43.9975L51.8398,43.9975L51.8398,45.9976L47.1578,45.9976C46.524100000000004,45.994,46.0122,45.548500000000004,46.0047,44.9994ZM46.0047,40.4987C46.0122,39.950500000000005,46.524100000000004,39.504999999999995,47.1578,39.497699999999995L51.7292,39.497699999999995L51.7292,41.4868L47.1578,41.4868C46.5278,41.4868,46.0122,41.047799999999995,46.0047,40.4996L46.0047,40.4987ZM46.0047,35.9888C46.0122,35.4388,46.5278,35.001599999999996,47.1578,35.001599999999996L51.8398,35.001599999999996L51.8398,36.990700000000004L47.1578,36.990700000000004C46.524100000000004,36.9824,46.0122,36.537,46.0047,35.987899999999996L46.0047,35.9888ZM29.501649999999998,45.4953C29.501649999999998,44.674,29.71822,43.8637,30.13072,43.1533C31.08773,41.4645,32.13675,39.827200000000005,33.27327,38.2483L33.27327,38.1823L33.51327,38.054L33.578900000000004,37.9981L33.96984,37.9981L33.96984,38.0531L34.035470000000004,38.1191Q34.09172,38.1191,34.09172,38.186C35.5242,39.727599999999995,36.7882,41.411100000000005,37.8633,43.209199999999996C38.841300000000004,44.8373,38.690200000000004,46.8866,37.483599999999996,48.3607C36.2789,49.8228,34.27922,50.3746,32.49232,49.730199999999996C30.70542,49.0858,29.51572,47.3928,29.52697,45.4953L29.501649999999998,45.4953ZM27.45319,27.739919999999998C28.58383,26.75362,29.8326,26.11288,31.13293,25.858060000000002C34.019059999999996,25.06777,37.0069,24.68957,40.0037,24.73517C42.9756,24.6875,45.9494,25.06699,48.8744,25.858060000000002C50.174800000000005,26.10555,51.4273,26.74628,52.5542,27.739919999999998C51.419799999999995,28.71615,50.171,29.35688,48.8744,29.62179C45.9848,30.39315,42.9988,30.76495,40.0037,30.72635C37.0084,30.76678,34.02223,30.39495,31.13293,29.62179C29.83728,29.3633,28.58383,28.72623,27.45319,27.739919999999998Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/menuIcon/s_log.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#479CF1" fill-opacity="1"/></g><g><path d="M32,30.00793C32.8,30.00793,33.5,29.30731,33.5,28.506610000000002L33.5,22.50132C33.5,21.700617,32.8,21,32,21C31.2,21,30.5,21.700617,30.5,22.50132L30.5,28.506610000000002C30.5,29.30731,31.2,30.00793,32,30.00793ZM45,30.00793C45.8,30.00793,46.5,29.30731,46.5,28.506610000000002L46.5,22.50132C46.5,21.700617,45.8,21,45,21C44.2,21,43.5,21.700617,43.5,22.50132L43.5,28.506610000000002C43.5,29.30731,44.2,30.00793,45,30.00793ZM40,50.0255C40,45.1212,43,40.8174,47.2,39.0159L28.5,39.0159C27.7,39.0159,27,38.315200000000004,27,37.5145C27,36.7138,27.7,36.0132,28.5,36.0132L47.5,36.0132C48.3,36.0132,49,36.7138,49,37.5145C49,37.9149,48.8,38.215199999999996,48.6,38.5154C49.7,38.215199999999996,50.8,38.015,52,38.015C52.3,38.015,52.7,38.015,53,38.1151L53,29.00705C53,26.80511,51.2,25.00352,49,25.00352L49,28.506610000000002C49,31.0088,47,33.0106,44.5,33.0106C42,33.0106,40,31.0088,40,28.506610000000002L40,25.00352L36,25.00352L36,28.506610000000002C36,31.0088,34,33.0106,31.5,33.0106C29,33.0106,27,31.0088,27,28.506610000000002L27,25.00352C24.8,25.00352,23,26.80511,23,29.00705L23,55.03C23,57.2319,24.8,59.0335,27,59.0335L44.1,59.0335C41.6,56.8315,40,53.6287,40,50.0255ZM37.5,51.026399999999995L28.5,51.026399999999995C27.7,51.026399999999995,27,50.3258,27,49.525099999999995C27,48.7244,27.7,48.0238,28.5,48.0238L37.5,48.0238C38.3,48.0238,39,48.7244,39,49.525099999999995C39,50.3258,38.3,51.026399999999995,37.5,51.026399999999995ZM37.5,45.021100000000004L28.5,45.021100000000004C27.7,45.021100000000004,27,44.320499999999996,27,43.519800000000004C27,42.7191,27.7,42.0185,28.5,42.0185L37.5,42.0185C38.3,42.0185,39,42.7191,39,43.519800000000004C39,44.320499999999996,38.3,45.021100000000004,37.5,45.021100000000004ZM52,41.0176C47,41.0176,43,45.1212,43,50.0255C43,54.9299,47.1,59.0335,52,59.0335C56.9,59.0335,61,54.9299,61,50.0255C61,45.1212,57,41.0176,52,41.0176ZM55.5,53.0282L50.5,53.0282C49.7,53.0282,49,52.327600000000004,49,51.5269L49,45.5216C49,44.7209,49.7,44.0203,50.5,44.0203C51.3,44.0203,52,44.7209,52,45.5216L52,50.0255L55.5,50.0255C56.3,50.0255,57,50.7262,57,51.5269C57,52.327600000000004,56.3,53.0282,55.5,53.0282Z" fill="#FFFFFF" fill-opacity="1"/></g></g></svg>

1
src/assets/images/menuIcon/s_ore.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#479CF1" fill-opacity="1"/></g><g><path d="M51.951499999999996,39.8059C50.843199999999996,40.323899999999995,49.9413,40.7615,48.9636,41.225899999999996C48.4242,40.9535,47.7542,40.408699999999996,47.004000000000005,40.2435C44.3744,39.6987,41.8501,42.1279,41.9554,44.9411C41.9554,45.378699999999995,41.593,46.0619,41.2348,46.1958C39.8694,46.687,39.2499,47.723,39.174099999999996,49.0894C39.0982,51.2194,38.9423,53.4297,41.5467,54.2513C41.7531,54.3049,41.829,54.7693,41.959599999999995,55.0417C41.652,55.2069,41.3149,55.5329,41.0072,55.5329C37.5305,55.5597,34.0496,55.5597,30.5729,55.5597C28.97572,55.5597,27.37854,55.5865,25.78137,55.5597C23.745912,55.5061,23,54.6577,23,52.527699999999996L23,23.88686C23,21.895306,23.720627,21.0468861,25.62966,21.020094C33.539699999999996,20.99330188,41.4497,20.99330188,49.364000000000004,21.020094C51.037,21.020094,51.9136,21.895306,51.9388,23.83327C52.0021,29.18278,51.951499999999996,34.5635,51.951499999999996,39.8059ZM37.6232,24.75761C37.6232,24.730809999999998,37.6232,24.730809999999998,37.6232,24.75761C35.2001,24.75761,32.776920000000004,24.730809999999998,30.35798,24.75761C29.17379,24.75761,28.19188,25.76678,28.21717,26.86079C28.26774,27.9816,29.144289999999998,28.79876,30.35798,28.79876C35.0737,28.79876,39.763999999999996,28.79876,44.4797,28.77197C45.950500000000005,28.77197,46.591,28.00839,46.565799999999996,26.56161C46.5405,25.44081,45.765100000000004,24.78886,44.4797,24.78886C42.183,24.730809999999998,39.915800000000004,24.75761,37.6232,24.75761ZM33.7841,36.3899C35.0189,36.3899,36.2578,36.4435,37.4673,36.3631C38.445,36.3095,39.1951,35.7335,39.2963,34.6171C39.4269,33.3624,38.6515,32.4336,37.6485,32.38C35.0737,32.2416,32.46928,32.4336,29.8902,32.5184C28.68073,32.5452,28.242449999999998,33.5544,28.26774,34.568C28.29302,35.7424,29.09372,36.3988,30.25262,36.3988L33.7841,36.3988L33.7841,36.3899ZM56.9495,50.3441C57.156,51.326499999999996,56.7177,51.9294,55.8412,52.3089C54.2693,52.9921,53.856300000000005,53.7825,54.1934,55.5016C54.3999,56.5644,53.7046,57.002,53.0851,57.4128C52.4403,57.8236,51.7956,58.3952,50.893699999999995,57.6048C49.6295,56.4572,48.727599999999995,56.6225,47.210499999999996,57.5245C46.6964,57.8236,45.8198,57.7968,45.2256,57.578C44.504999999999995,57.3057,43.7549,56.8145,43.9867,55.6669C44.3491,53.8941,43.5231,52.7465,42.081900000000005,52.143699999999995C40.9988,51.6793,41.1547,50.7237,41.18,49.9869C41.2053,49.415400000000005,41.6436,48.566900000000004,42.1071,48.3213C43.5231,47.584599999999995,44.1679,47.0085,44.0415,44.6062C43.965599999999995,43.324600000000004,44.787400000000005,43.021,45.4827,42.695C45.9463,42.4494,46.8228,42.3958,47.185199999999995,42.695C48.5001,43.815799999999996,49.7348,43.7577,50.948499999999996,42.7218C52.0821,41.7662,52.7269,42.775400000000005,53.5529,43.2398C54.3536,43.6774,54.2988,44.2757,54.0923,45.0706C53.7847,46.298500000000004,54.4547,47.6917,55.6136,48.0757C56.7177,48.4062,57.0802,49.143,56.9495,50.3441ZM45.7145,49.9333C45.6892,52.143699999999995,46.9787,53.5905,49.039500000000004,53.6173C50.7673,53.6441,52.259100000000004,52.2509,52.313900000000004,50.585300000000004C52.3645,48.2365,51.0497,46.682500000000005,48.9383,46.5977C47.210499999999996,46.5486,45.7398,48.049,45.7145,49.9333Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/menuIcon/s_point.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><defs><clipPath id="master_svg0_94_4654"><rect x="18" y="18" width="44" height="44" rx="0"/></clipPath></defs><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#479CF1" fill-opacity="1"/></g><g clip-path="url(#master_svg0_94_4654)"><g><path d="M39.9964013671875,20.74951171875C31.6590513671875,20.74951171875,24.8739013671875,27.53466171875,24.8739013671875,35.87201171875C24.8739013671875,43.92101171875,38.4294013671875,58.21571171875,39.0059013671875,58.82171171875C39.2646013671875,59.09521171875,39.6268013671875,59.25041171875,40.0038013671875,59.25041171875L40.0333013671875,59.25041171875C40.4177013671875,59.25041171875,40.7872013671875,59.07311171875,41.0385013671875,58.77741171875L45.7320013671875,53.40401171875C51.9702013671875,45.76881171875,55.126201367187505,39.87061171875,55.126201367187505,35.87941171875C55.126201367187505,27.53471171875,48.3411013671875,20.74955640795,39.9964013671875,20.74951171875ZM39.9964013671875,42.745811718750005C36.1973013671875,42.745811718750005,33.1225213671875,39.67101171875,33.1225213671875,35.87201171875C33.1225213671875,32.07291171875,36.1973013671875,28.998131718750003,39.9964013671875,28.998131718750003C43.7954013671875,28.998131718750003,46.870201367187505,32.07291171875,46.870201367187505,35.87201171875C46.870201367187505,39.67101171875,43.7954013671875,42.745811718750005,39.9964013671875,42.745811718750005Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></svg>

1
src/assets/images/menuIcon/s_ps.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><defs><clipPath id="master_svg0_94_4553"><rect x="21" y="59" width="38" height="38" rx="0"/></clipPath></defs><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#EEEFF8" fill-opacity="1"/></g><g transform="matrix(1,0,0,-1,0,118)" clip-path="url(#master_svg0_94_4553)"><g><path d="M56.9528,97L23.04725,97C21.916585,97,21,96.0834,21,94.9528L21,61.04725C21,59.91675,21.91675,59,23.04725,59L56.9551,59C58.0832,59,59,59.91675,59,61.04725L59,94.9551C58.9987,96.0849,58.0825,97,56.9528,97ZM50.8134,83.21549999999999L50.8134,64.88525C50.8108,64.25542,50.300799999999995,63.74548,49.671,63.74288C49.0412,63.74548,48.5312,64.25541,48.5286,64.88525L48.5286,83.2226C46.297200000000004,83.8023,44.843599999999995,85.9491,45.1338,88.2362C45.424099999999996,90.5233,47.3679,92.239,49.6734,92.24289999999999C51.9827,92.2411,53.9304,90.52250000000001,54.2198,88.2313C54.5091,85.9402,53.0497,83.7914,50.8134,83.21549999999999ZM41.142399999999995,73.5754L41.142399999999995,64.90663C41.1398,64.27679,40.6298,63.76685,40,63.76425C39.3702,63.76685,38.8602,64.27679,38.857600000000005,64.90663L38.857600000000005,73.5754C36.839,74.1033,35.4307,75.9268,35.430099999999996,78.0133C35.4296,80.0999,36.8369,81.9241,38.8553,82.4531L38.8553,91.1219C38.8553,91.74889999999999,39.370599999999996,92.2642,39.9976,92.2642C40.6275,92.2616,41.1374,91.7517,41.14,91.1219L41.14,82.4531C43.1652,81.93209999999999,44.5811,80.10640000000001,44.5816,78.0152C44.5822,75.924,43.1673,74.0975,41.142399999999995,73.5754ZM30.33138,63.73575C28.02941,63.74415,26.09074,65.45857,25.80085,67.74223C25.51095,70.02590000000001,26.95977,72.1703,29.186619999999998,72.7536L29.186619999999998,91.09100000000001C29.186619999999998,91.718,29.701999999999998,92.2334,30.329,92.2334C30.95883,92.2308,31.4688,91.7208,31.4714,91.09100000000001L31.4714,72.7536C33.7053,72.1781,35.1629,70.0312,34.873599999999996,67.74254C34.584199999999996,65.45384,32.6383,63.73726,30.33138,63.73575Z" fill="#4F85FB" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></svg>

1
src/assets/images/menuIcon/s_setting.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#479CF1" fill-opacity="1"/></g><g><g><path d="M57.7309,36.6424L54.4199,35.1065C52.2821,34.1115,51.0169,31.80957,51.2919,29.41513L51.7134,25.70348C49.9764,24.13487,47.9673,22.91636,45.7913,22.111649L42.809,24.29289C40.9002,25.70218,38.3223,25.67794,36.438900000000004,24.23298L33.5495,22C31.3612,22.766417,29.33189,23.9468,27.56639,25.48018L27.92691,29.18911C28.16995,31.58721,26.871940000000002,33.8705,24.71933,34.8315L21.35269,36.3074C20.9039103,38.6357,20.883231,41.0297,21.291719,43.3658L24.60269,44.9071C26.74368,45.8995,28.01241,48.202,27.73869,50.5985L27.30925,54.2911C29.0484,55.858,31.056800000000003,57.0779,33.2313,57.8884L36.1897,55.7098C38.098,54.2997,40.6759,54.3229,42.559799999999996,55.767L45.4705,58C47.6601,57.2361,49.6906,56.0565,51.456199999999995,54.5225L51.0984,50.8054C50.8651,48.409099999999995,52.158699999999996,46.1352,54.306,45.1685L57.6461,43.6953C58.0967,41.3643,58.1179,38.9679,57.7044,36.6287L57.7309,36.6424ZM39.5113,49.514700000000005C34.3981,49.514700000000005,30.25307,45.2566,30.25307,40.0041C30.25307,34.7515,34.3981,30.49349,39.5113,30.49349C44.6245,30.49349,48.7696,34.7515,48.7696,40.0041C48.7696,45.2566,44.6245,49.514700000000005,39.5113,49.514700000000005Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g><g><path d="M39.503226597900394,32.666666984558105C35.85293659790039,32.6648691245581,32.89285659790039,35.650166984558105,32.89285659790039,39.3333369845581C32.89285659790039,43.01646698455811,35.85293659790039,46.0017669845581,39.503226597900394,45.99996698455811C43.15095659790039,45.998166984558104,46.10715659790039,43.01396698455811,46.10715659790039,39.3333369845581C46.10715659790039,35.65270698455811,43.15095659790039,32.66846320455811,39.503226597900394,32.666666984558105ZM39.503226597900394,43.0462669845581C37.535416597900394,42.95376698455811,35.98716659790039,41.316986984558106,35.98716659790039,39.329296984558106C35.98716659790039,37.34159698455811,37.535416597900394,35.704856984558106,39.503226597900394,35.61227698455811C41.495636597900386,35.7097269845581,43.08915659790039,37.317586984558105,43.185756597900394,39.3279469845581C43.17395659790039,41.372516984558104,41.52956659790039,43.024566984558106,39.503226597900394,43.0274669845581L39.503226597900394,43.0462669845581Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></g></svg>

1
src/assets/images/menuIcon/s_user.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#479CF1" fill-opacity="1"/></g><g><path d="M29.44471,32.3043C29.44472,37.9952,34.0687,42.608599999999996,39.772800000000004,42.608599999999996C45.4768,42.608599999999996,50.100899999999996,37.9952,50.100899999999996,32.3043C50.100899999999996,26.6134,45.4768,22,39.772800000000004,22C34.0687,22,29.44472,26.6134,29.44471,32.3043ZM55.678,46.5004L51.0002,46.5004C50.2702,46.5004,49.678200000000004,45.9097,49.678200000000004,45.1814C49.678200000000004,44.4531,50.2702,43.8625,51.0002,43.8625L55.678,43.8625C56.408,43.8625,57,44.4531,57,45.1814C57,45.9097,56.408,46.5004,55.678,46.5004ZM55.678,52.2502L47.580799999999996,52.2502C46.8508,52.2502,46.2588,51.6595,46.2588,50.931200000000004C46.2588,50.2029,46.8508,49.612300000000005,47.580799999999996,49.612300000000005L55.678,49.612300000000005C56.408,49.612300000000005,57,50.2029,57,50.931200000000004C57,51.6595,56.408,52.2502,55.678,52.2502ZM55.678,58L47.580799999999996,58C46.8508,58,46.2588,57.4094,46.2588,56.681C46.2588,55.9527,46.8508,55.3621,47.580799999999996,55.3621L55.678,55.3621C56.408,55.3621,57,55.9527,57,56.681C57,57.4094,56.408,58,55.678,58ZM41.260000000000005,52.9303C41.260000000000005,48.602900000000005,43.934200000000004,44.898700000000005,47.723299999999995,43.3724C48.3662,43.113600000000005,48.4289,42.229,47.825,41.8898C45.435,40.546099999999996,42.6911,39.782,39.772800000000004,39.782C30.50933,39.782,23,47.4797,23,56.9757C23,57.0475,23.000413211,57.1188,23.00123932,57.1901C23.0066098,57.6398,23.376768,58,23.827485,58L41.2753,58C41.865700000000004,58,42.262299999999996,57.4015,42.037099999999995,56.857C41.5364,55.6469,41.260000000000005,54.3209,41.260000000000005,52.9303Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/password_icon.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="32.66561508178711" height="39.13572692871094" viewBox="0 0 32.66561508178711 39.13572692871094"><g><path d="M29.768,16.829C29.4345,16.829,27.5584,16.8498,27.204,16.8498L27.204,10.9347C27.204,4.37387,22.9722,0.0208277,16.3641,0C9.75591,0,5.4408,4.58215,5.4408,10.9347L5.4408,16.7873C5.21149,16.7873,2.8142,16.8082,2.60574,16.8082C1.02145,16.8082,0,18.162,0,19.6199L0,36.2615C0,37.9069,0.87553,39.1357,2.56405,39.1357L29.8514,39.1357C31.1647,39.1357,32.6656,37.9485,32.6656,35.8033L32.6656,19.9948C32.6656,18.3703,32.1236,16.829,29.768,16.829ZM19.9704,31.7626C19.9704,33.8246,18.3444,35.4908,16.3432,35.4908C14.342,35.4908,12.716,33.8246,12.716,31.7626L12.716,31.7001C12.716,30.3255,13.4456,29.1175,14.5296,28.4718L14.5296,21.5986C14.5296,20.6197,15.3009,20.5155,16.2598,20.5155C17.2188,20.5155,18.1568,20.6197,18.1568,21.5986L18.1568,28.4718C19.2408,29.1175,19.9704,30.3255,19.9704,31.7001L19.9704,31.7626ZM23.6185,16.7873L9.08884,16.7873L9.08884,9.28927C9.08884,5.68603,13.1121,3.7282,16.1348,3.7282L16.8018,3.7282C19.8245,3.7282,23.6185,5.54024,23.6185,9.28927L23.6185,16.7873Z" fill="#1989FA" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></svg>

1
src/assets/images/run.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="36" height="36" viewBox="0 0 36 36"><g><path d="M0.0338876,21.6006C0.791888,25.2568,2.6603,28.5461,5.43529,31.1111C5.80569,31.454,6.31565,31.6116,6.8194,31.5389C7.3235,31.4665,7.76624,31.1719,8.02131,30.7391L10.1884,27.0549C10.5503,26.4391,10.4661,25.6645,9.97991,25.1375C8.84698,23.9069,8.02102,22.4346,7.56723,20.8368C7.36685,20.128,6.70975,19.6374,5.96078,19.6374L1.66693,19.6374C1.16648,19.6372,0.692517,19.8581,0.376395,20.2389C0.0591609,20.619,-0.0666573,21.1192,0.0338876,21.6006ZM22.0852,29.174C21.7232,28.5559,20.9949,28.2395,20.2854,28.3921C18.4901,28.7781,16.6239,28.7054,14.8655,28.1807C14.1235,27.9594,13.3239,28.2689,12.9355,28.9278L10.808,32.5469C10.5628,32.9646,10.5176,33.4671,10.6845,33.9204C10.8509,34.3741,11.2133,34.7322,11.6745,34.8985C13.6899,35.6293,15.8187,36,17.9994,36C19.8024,36,21.5824,35.7444,23.2887,35.2393C23.7758,35.0946,24.1692,34.7399,24.3574,34.2758C24.545,33.8109,24.5056,33.2873,24.2505,32.8545L22.0852,29.174ZM29.7455,4.17944C29.4462,3.9339,29.0685,3.79934,28.6784,3.79924C28.5817,3.79924,28.4831,3.80922,28.3864,3.82559C27.9025,3.91049,27.4815,4.20075,27.2359,4.61875L25.1037,8.24068C24.7167,8.90032,24.8432,9.73465,25.4092,10.255C26.8597,11.5847,27.9059,13.2833,28.4313,15.1619C28.6304,15.8724,29.2886,16.3647,30.0393,16.3649L34.3337,16.3649C34.8331,16.3646,35.3062,16.1445,35.6226,15.7652C35.9401,15.3857,36.0664,14.886,35.9664,14.4048C35.1469,10.4185,32.9378,6.78689,29.7455,4.17944ZM24.3574,1.72602C24.1697,1.26149,23.7761,0.906541,23.2887,0.762205C21.5731,0.255605,19.791,-0.00119639,17.9994,0.00000381258C15.8187,0.00000381258,13.6899,0.370486,11.6745,1.10122C10.7057,1.45352,10.2924,2.57637,10.808,3.45511L12.9355,7.07371C13.3239,7.73262,14.1235,8.04214,14.8655,7.82082C15.8814,7.51699,16.9376,7.3629,17.9996,7.3636C18.7626,7.3636,19.5323,7.44547,20.2854,7.60794C20.9944,7.76016,21.7222,7.44468,22.0852,6.82783L24.2508,3.1468C24.5058,2.71413,24.5451,2.1907,24.3574,1.72602ZM34.3337,19.6371L30.0393,19.6371C29.2884,19.6367,28.63,20.1292,28.4313,20.8399C27.9057,22.7185,26.8595,24.4171,25.4092,25.747C24.8431,26.2665,24.7165,27.1004,25.1037,27.7593L27.2359,31.3828C27.4812,31.8011,27.9024,32.0915,28.3864,32.1762C28.4832,32.1926,28.5817,32.2008,28.6784,32.2008C29.0683,32.2013,29.446,32.0673,29.7455,31.8223C32.9378,29.2131,35.1469,25.583,35.9665,21.5972C36.0665,21.1161,35.9402,20.6163,35.6226,20.2368C35.3066,19.857,34.8333,19.6368,34.3337,19.6371ZM0.376135,15.7632C0.692116,16.1438,1.16603,16.3647,1.66666,16.3647L5.96105,16.3647C6.71021,16.3644,7.36726,15.8738,7.5675,15.1652C8.02109,13.5669,8.84706,12.0941,9.98017,10.863C10.4664,10.3367,10.5507,9.56236,10.1887,8.9471L8.02157,5.263C7.76613,4.8302,7.3237,4.53522,6.81966,4.46166C6.73946,4.45042,6.65853,4.44495,6.57751,4.44528C6.1568,4.44528,5.746,4.60263,5.43555,4.89099C2.6603,7.45571,0.791888,10.7448,0.0338876,14.4012C-0.0665431,14.8826,0.0591547,15.3828,0.376135,15.7632Z" fill="#4F85FB" fill-opacity="1"/></g></svg>

1
src/assets/images/success.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="70" height="70" viewBox="0 0 70 70"><defs><clipPath id="master_svg0_685_8843"><rect x="0" y="0" width="70" height="70" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_685_8843)"><g><path d="M35,70C15.6712,70,0,54.3288,0,35C0,15.6712,15.6712,0,35,0C54.3288,0,70,15.6712,70,35C70,54.3288,54.3288,70,35,70ZM27.195,48.7433C28.6189,50.1676,30.9278,50.1676,32.3517,48.7433L54.4542,26.6408C55.8781,25.2169,55.8781,22.9081,54.4542,21.4842C53.0302,20.0602,50.7215,20.0602,49.2975,21.4842L29.6158,40.8508L20.8075,32.0425C19.3838,30.6166,17.0736,30.6158,15.6488,32.0405C14.2241,33.4653,14.225,35.7755,15.6508,37.1992L27.195,48.7433Z" fill="#14A656" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

22
src/assets/images/user-cancel.svg

@ -0,0 +1,22 @@
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_447_4258)">
<circle cx="54" cy="50" r="50" fill="url(#paint0_linear_447_4258)"/>
<path d="M54 45.1119L71.1119 28L76 32.8881L58.8881 50L76 67.1119L71.1119 72L54 54.8881L36.8881 72L32 67.1119L49.1119 50L32 32.8881L36.8881 28L54 45.1119Z" fill="white"/>
</g>
<defs>
<filter id="filter0_d_447_4258" x="0" y="0" width="108" height="108" 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" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.877278 0 0 0 0 0.28742 0 0 0 0 0.421121 0 0 0 1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_447_4258"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_447_4258" result="shape"/>
</filter>
<linearGradient id="paint0_linear_447_4258" x1="30.5" y1="17" x2="84.5" y2="100" gradientUnits="userSpaceOnUse">
<stop stop-color="#F08564"/>
<stop offset="1" stop-color="#EA467D"/>
</linearGradient>
</defs>
</svg>

1
src/assets/images/user.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="40" height="40" viewBox="0 0 40 40"><defs><clipPath id="master_svg0_609_03555/609_03497"><rect x="8" y="8" width="24" height="24" rx="0"/></clipPath></defs><g><rect x="0" y="0" width="40" height="40" rx="20" fill="#1989FA" fill-opacity="1"/><g clip-path="url(#master_svg0_609_03555/609_03497)"><g><path d="M28.120604907226564,25.896875C27.67840490722656,24.849575,27.03680490722656,23.898375,26.231504907226565,23.096075C25.428604907226564,22.291475,24.47750490722656,21.649974999999998,23.430704907226563,21.207075C23.421304907226563,21.202375,23.41200490722656,21.199975000000002,23.40260490722656,21.195275000000002C24.862704907226565,20.140625,25.812004907226562,18.422655,25.812004907226562,16.484375C25.812004907226562,13.273435,23.21040490722656,10.671875,19.99946490722656,10.671875C16.788524907226563,10.671875,14.186964907226562,13.273435,14.186964907226562,16.484375C14.186964907226562,18.422655,15.136184907226562,20.140625,16.596334907226563,21.197675C16.586964907226562,21.202375,16.577584907226562,21.204675,16.56821490722656,21.209375C15.518214907226563,21.652375,14.576024907226563,22.287475,13.767434907226562,23.098475C12.962844907226563,23.901375,12.321344907226562,24.852475,11.878369907226563,25.899175C11.443186907226563,26.924075,11.208483707226563,28.022775,11.186963515026562,29.135975C11.186337956226563,29.160975,11.190725517226563,29.185875,11.199867807226562,29.209175C11.209010107226563,29.232375,11.222722007226562,29.253675,11.240195707226562,29.271575C11.257669407226562,29.289475,11.278551207226563,29.303675,11.301610907226562,29.313375C11.324669907226562,29.323175,11.349440907226562,29.328175,11.374463907226563,29.328075C11.374463907226563,29.328075,12.780714907226562,29.328075,12.780714907226562,29.328075C12.883834907226563,29.328075,12.965864907226562,29.246075,12.968214907226562,29.145275C13.015084907226562,27.335975,13.741654907226563,25.641375,15.026024907226562,24.357075000000002C16.354934907226564,23.028174999999997,18.119774907226564,22.296875,19.99946490722656,22.296875C21.879104907226562,22.296875,23.644004907226563,23.028174999999997,24.972904907226564,24.357075000000002C26.257304907226562,25.641375,26.983804907226563,27.335975,27.030704907226564,29.145275C27.033104907226562,29.248475,27.115104907226563,29.328075,27.218204907226564,29.328075C27.218204907226564,29.328075,28.624504907226562,29.328075,28.624504907226562,29.328075C28.64950490722656,29.328175,28.674304907226563,29.323175,28.697304907226563,29.313375C28.720404907226563,29.303675,28.741304907226564,29.289475,28.758704907226562,29.271575C28.776204907226564,29.253675,28.789904907226564,29.232375,28.799104907226564,29.209175C28.808204907226564,29.185875,28.81260490722656,29.160975,28.812004907226562,29.135975C28.788504907226564,28.015675,28.55650490722656,26.925775,28.120604907226564,25.896875C28.120604907226564,25.896875,28.120604907226564,25.896875,28.120604907226564,25.896875ZM19.99946490722656,20.515625C18.923684907226562,20.515625,17.911184907226563,20.096095,17.149464907226562,19.334375C16.387744907226562,18.572655,15.968214907226564,17.560155,15.968214907226564,16.484375C15.968214907226564,15.408595,16.387744907226562,14.396094999999999,17.149464907226562,13.634375C17.911184907226563,12.872655,18.923684907226562,12.453125,19.99946490722656,12.453125C21.075244907226562,12.453125,22.087704907226563,12.872655,22.84950490722656,13.634375C23.611204907226565,14.396094999999999,24.030704907226564,15.408595,24.030704907226564,16.484375C24.030704907226564,17.560155,23.611204907226565,18.572655,22.84950490722656,19.334375C22.087704907226563,20.096095,21.075244907226562,20.515625,19.99946490722656,20.515625Z" fill="#FFFFFF" fill-opacity="1"/></g></g></g></svg>

1
src/assets/images/user_icon.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="33.87468338012695" height="33.84542465209961" viewBox="0 0 33.87468338012695 33.84542465209961"><g><path d="M33.8747,32.442C33.8747,25.5468,28.9971,19.6889,22.2628,17.6265C24.8808,15.9057,26.6152,12.9605,26.6152,9.61666C26.6152,4.31204,22.275,0,16.9373,0C11.5996,0,7.25944,4.31204,7.25944,9.61259C7.25944,12.9565,8.99389,15.9017,11.6118,17.6224C4.87763,19.6889,0,25.5427,0,32.442L0,33.8454L33.8747,33.8454L33.8747,32.4623L33.8747,32.442Z" fill="#1989FA" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></svg>

1
src/assets/images/wait.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><defs><clipPath id="master_svg0_685_8855"><rect x="0" y="0" width="80" height="80" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_685_8855)"><g><path d="M40.00636015625,5.01953125C20.68606015625,5.01953125,5.01416254044,20.69143125,5.01416254044,40.01173125C5.01416254044,59.33203125,20.68606015625,74.99613125,40.00636015625,74.99613125C59.32666015625,74.99613125,74.99856015625,59.32423125,74.99856015625,40.00393125C74.99856015625,20.683631249999998,59.32666015625,5.01953125,40.00636015625,5.01953125ZM21.97506015625,45.97263125C18.678260156249998,45.97263125,16.00636015625,43.30073125,16.00636015625,40.00393125C16.00636015625,36.70703125,18.678260156249998,34.03513125,21.97506015625,34.03513125C25.27196015625,34.03513125,27.94386015625,36.70703125,27.94386015625,40.00393125C27.94386015625,43.30073125,25.27196015625,45.97263125,21.97506015625,45.97263125ZM39.92046015625,45.97263125C36.62356015625,45.97263125,33.95166015625,43.30073125,33.95166015625,40.00393125C33.95166015625,36.70703125,36.62356015625,34.03513125,39.92046015625,34.03513125C43.21726015625,34.03513125,45.88916015625,36.70703125,45.88916015625,40.00393125C45.88916015625,43.30073125,43.21726015625,45.97263125,39.92046015625,45.97263125ZM57.86576015625,45.97263125C54.56886015625,45.97263125,51.89696015625,43.30073125,51.89696015625,40.00393125C51.89696015625,36.70703125,54.56886015625,34.03513125,57.86576015625,34.03513125C61.16256015625,34.03513125,63.83446015625,36.70703125,63.83446015625,40.00393125C63.83446015625,43.30073125,61.16256015625,45.97263125,57.86576015625,45.97263125Z" fill="#EE8223" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/wifi-active.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="42" height="34" viewBox="0 0 42 34"><g><path d="M41.5238,8.34171C30.1914,-2.78057,11.812,-2.78057,0.479575,8.34171C-0.159858,8.96929,-0.159858,9.98404,0.479575,10.6072C1.11901,11.2347,2.15289,11.2347,2.78777,10.6072C12.8459,0.735472,29.1575,0.735472,39.2156,10.6072C39.8551,11.2347,40.889,11.2347,41.5238,10.6072C42.1587,9.984,42.1587,8.96929,41.5238,8.34171ZM6.63327,15.2314C5.99383,15.859,5.99383,16.8737,6.63327,17.4968C7.2727,18.1244,8.30658,18.1244,8.94146,17.4968C15.5985,10.9632,26.3958,10.9632,33.0529,17.4968C33.6923,18.1244,34.7262,18.1244,35.3611,17.4968C36.0005,16.8692,36.0005,15.8545,35.3611,15.2314C27.4297,7.44712,14.5691,7.44712,6.63327,15.2314L6.63327,15.2314ZM29.2346,22.5395L29.2436,22.5306C24.7089,18.0799,17.358,18.0799,12.8278,22.5306C12.1883,23.1581,12.1883,24.1728,12.8278,24.7959C13.4672,25.4235,14.5011,25.4235,15.136,24.7959C18.3965,21.5959,23.6795,21.5959,26.9355,24.7959L26.9445,24.7871C26.9828,24.8337,27.0237,24.8783,27.0669,24.9206C27.7064,25.5481,28.7403,25.5481,29.3752,24.9206C30.0146,24.293,30.0146,23.2783,29.3752,22.6552C29.3253,22.6151,29.2799,22.5795,29.2346,22.5395ZM18.596,31.5966C18.596,32.924,19.6923,34,21.0448,34C22.3972,34,23.4935,32.924,23.4935,31.5966C23.4935,30.2693,22.3972,29.1932,21.0448,29.1932C19.6923,29.1932,18.596,30.2692,18.596,31.5966Z" fill="#1989FA" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></svg>

1
src/assets/images/wifi.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="42" height="34" viewBox="0 0 42 34"><g><path d="M41.5238,8.34171C30.1914,-2.78057,11.812,-2.78057,0.479575,8.34171C-0.159858,8.96929,-0.159858,9.98404,0.479575,10.6072C1.11901,11.2347,2.15289,11.2347,2.78777,10.6072C12.8459,0.735472,29.1575,0.735472,39.2156,10.6072C39.8551,11.2347,40.889,11.2347,41.5238,10.6072C42.1587,9.984,42.1587,8.96929,41.5238,8.34171ZM6.63327,15.2314C5.99383,15.859,5.99383,16.8737,6.63327,17.4968C7.2727,18.1244,8.30658,18.1244,8.94146,17.4968C15.5985,10.9632,26.3958,10.9632,33.0529,17.4968C33.6923,18.1244,34.7262,18.1244,35.3611,17.4968C36.0005,16.8692,36.0005,15.8545,35.3611,15.2314C27.4297,7.44712,14.5691,7.44712,6.63327,15.2314L6.63327,15.2314ZM29.2346,22.5395L29.2436,22.5306C24.7089,18.0799,17.358,18.0799,12.8278,22.5306C12.1883,23.1581,12.1883,24.1728,12.8278,24.7959C13.4672,25.4235,14.5011,25.4235,15.136,24.7959C18.3965,21.5959,23.6795,21.5959,26.9355,24.7959L26.9445,24.7871C26.9828,24.8337,27.0237,24.8783,27.0669,24.9206C27.7064,25.5481,28.7403,25.5481,29.3752,24.9206C30.0146,24.293,30.0146,23.2783,29.3752,22.6552C29.3253,22.6151,29.2799,22.5795,29.2346,22.5395ZM18.596,31.5966C18.596,32.924,19.6923,34,21.0448,34C22.3972,34,23.4935,32.924,23.4935,31.5966C23.4935,30.2693,22.3972,29.1932,21.0448,29.1932C19.6923,29.1932,18.596,30.2692,18.596,31.5966Z" fill="#DF1515" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></svg>

91
src/assets/styles/common.scss

@ -0,0 +1,91 @@
/* CSS Document */
html {
font-family: 'PingFangSC-Regular', 'PingFang SC', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
height: 100vh;
font-size: 14px;
}
html, body {
overflow: hidden;
}
html,
body,
ol,
dl,
dd,
dt,
p,
h1,
h2,
h3,
h4,
h5,
h6,
form,
fieldset,
legend,
img {
margin: 0;
padding: 0;
}
fieldset {
border: none;
}
img {
display: block;
}
address,
caption,
cite,
code,
dfn,
th,
var {
font-style: normal;
font-weight: normal;
}
ul,
ol,
li {
list-style: none;
}
a {
color: #666;
text-decoration: none;
}
* {
box-sizing: border-box !important;
}
a {
&:visited {
color: inherit;
}
}
input,
button,
select,
textarea {
outline: none;
}
textarea {
resize: none;
}
input[type='number'] {
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
}
-moz-appearance: textfield;
}
.clear {
clear: both;
}
.konvajs-content{
canvas{
border: 1px solid #ccc !important;
}
}

72
src/assets/styles/element.scss

@ -0,0 +1,72 @@
:root {
// --el-font-size-base: 50px;
// --el-button-size: 80px;
--el-color-primary: #1989fa;
//--el-button-active-bg-color: linear-gradient(90deg, #0657C0 24%, #096AE0 101%);
--text-color-primary: #17213c;
//--el-color-success: rgba(88, 162, 95, 1);
//--text-color-info: #838b99;
//--el-input-border: #dae0f2;
//--el-font-weight-primary: 400;
//--color-red: #f56c6c;
//--color-green: #67c23a;
//--color-yellow: #e6a23c;
//--color-blue: --el-color-primary;
--el-font-family: 'PingFangSC-Regular', 'PingFang SC', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
}
.el-dialog {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
margin: 0;
padding: 0;
.el-dialog__header {
background: rgba(0, 0, 0, .03);
padding: 10px;
}
.el-dialog__body {
padding: 10px 20px;
}
.el-dialog__footer {
padding: 10px;
}
}
.el-input-group__append {
padding: 0 10px;
.el-icon {
margin: 0;
}
}
.init-message {
.el-message-box__content {
.el-message-box__container {
align-items: flex-start;
ul {
padding-left: 10px;
font-size: 15px;
li {
font-weight: 700;
color: #FE0A0A;
}
}
}
}
}
.button-popper {
inset: auto 242px 20px auto !important;
.el-popper__arrow {
top: 100px !important;
}
}
.el-message__content {
font-size: 16px;
}

3
src/assets/styles/main.scss

@ -0,0 +1,3 @@
@use './common.scss';
@use './element.scss';

5
src/assets/styles/variable.scss

@ -0,0 +1,5 @@
$primary-color: #1989FA;
$success-color: #14A656;
$danger-color: #DF1515;
$warn-color: #EE8223;
$info-color: #909399;

109
src/components/SavePosition/index.vue

@ -0,0 +1,109 @@
<script setup lang="ts">
import { getPointList, updatePoint } from 'apis/point'
import { getPoint } from 'apis/system'
import { onMounted, ref } from 'vue'
const emits = defineEmits(['ok', 'cancel'])
const pointList = ref<Point.Point[]>([])
onMounted(async () => {
pointList.value = await getPointList()
})
const form = ref<Point.UpdateParams>({})
const formRef = ref()
const rules = {
id: [
{ required: true, message: '请选择名称', trigger: 'change' },
],
position: [
{ required: true, message: '请输入坐标', trigger: 'blur' },
],
}
const okHandle = async () => {
try {
const valid = await formRef.value.validate()
if (!valid) {
return
}
await updatePoint(form.value!)
emits('ok')
}
catch (error) {
console.log(error)
}
}
const cancel = () => {
emits('cancel')
}
const motor = [
{
name: '转运机械臂三维点',
code: 'Transfer',
},
{
name: 'X轴电机',
code: 'XSV',
},
{
name: 'Z轴电机',
code: 'ZM',
},
{
name: '门电机',
code: 'DoorM',
},
{
name: '加液臂电机',
code: 'LiquidM',
},
]
const selectChange = async (val: string) => {
form.value!.position = await getPoint(val)
}
</script>
<template>
<FtDialog visible title="保存坐标" width="40%" :ok-handle="okHandle" @cancel="cancel">
<el-form ref="formRef" label-width="auto" :model="form" :rules="rules">
<el-form-item label="选择电机">
<el-select v-model="form.type" clearable placeholder="请选择电机" @change="selectChange">
<el-option
v-for="item in motor"
:key="item.code"
:label="item.name"
:value="item.code"
/>
</el-select>
</el-form-item>
<el-form-item label="当前坐标" prop="position">
<el-input v-model="form.position" />
</el-form-item>
<el-form-item label="名称" prop="id">
<el-select v-model="form.id" placeholder="请选择名称">
<el-option
v-for="item in pointList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-form>
</FtDialog>
</template>
<style scoped lang="scss">
.item-box {
display: flex;
align-items: center;
justify-content: space-between;
padding: 5px;
}
</style>

158
src/components/common/FTButton/index.vue

@ -0,0 +1,158 @@
<script setup lang="ts">
import { ref } from 'vue'
interface FTButton {
type?: 'default' | 'primary' | 'info' | 'danger'
size?: 'small' | 'default' | 'large'
disabled?: boolean
loading?: boolean
clickHandle?: () => any
}
const props = withDefaults(defineProps<FTButton>(), {
type: 'default',
size: 'default',
disabled: false,
loading: false,
clickHandle: () => {},
})
const isLoading = ref(false)
async function handleClick() {
if (!props.clickHandle || isLoading.value)
return
isLoading.value = true // loading
try {
await props.clickHandle() //
}
finally {
isLoading.value = false // loading
}
}
const setLoading = (loading: boolean) => {
isLoading.value = loading
}
defineExpose({
setLoading,
})
</script>
<template>
<div class="ft-button" :class="{ 'ft-button-disabled': disabled || isLoading, [`ft-button-${size}`]: true }" @click="handleClick">
<!-- 添加 loading 判断 -->
<div v-show="disabled || isLoading" class="my-button-shadow" /> <!-- 添加 loading 判断 -->
<div
class="my-button" :class="{
[`my-button-${type}`]: true,
[`my-button-${size}`]: true,
'button-disabled': disabled || isLoading, // loading
}"
>
<el-icon v-if="isLoading" :color="type === 'default' ? '#26509C' : '#fff'">
<!-- 添加 loading 判断 -->
<Loading class="rotate-loading" /> <!-- 添加旋转类 -->
</el-icon>
<slot />
</div>
</div>
</template>
<style scoped lang="scss">
.ft-button {
position: relative;
display: inline-block;
margin-right: 10px;
}
.ft-button-small {
margin-right: 5px;
}
.ft-button-disabled {
pointer-events: none;
}
.my-button-shadow {
position: absolute;
width: 100%;
height: 100%;
z-index: 100;
}
.my-button {
height: 30px;
padding: 5px 20px;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 14px;
//width: fit-content;
position: relative;
.el-icon {
position: absolute;
left: 5px;
svg {
width: 35px;
}
}
}
.button-disabled {
opacity: 0.5;
}
.my-button-large {
height: 40px;
font-size: 14px;
padding: 3px 15px;
.el-icon {
position: absolute;
left: 3px;
svg {
width: 25px;
}
}
}
.my-button-small {
height: 25px;
font-size: 12px;
padding: 3px 15px;
.el-icon {
position: absolute;
left: 3px;
svg {
width: 25px;
}
}
}
.my-button-default {
background: #fff;
color: $primary-color;
border: 1px solid $primary-color;
}
.my-button-primary {
background: $primary-color;
color: #fff;
border: 1px solid $primary-color;
}
.my-button-info {
background: #335AA5;
color: #fff;
border: 1px solid #335AA5;
}
.my-button-danger {
background: #e74444;
color: #fff;
border: 1px solid #e74444;
}
.rotate-loading {
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>

76
src/components/common/FTDialog/index.vue

@ -0,0 +1,76 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
const props = defineProps({
title: {
type: String,
default: '',
},
visible: {
type: Boolean,
default: false,
},
width: {
type: String,
default: '50%',
},
okHandle: {
type: Function,
default: () => {},
},
})
const emits = defineEmits(['update:visible', 'ok', 'cancel'])
const cancel = () => {
show.value = false
emits('cancel')
}
const show = ref(false)
watch(
() => props.visible,
(newVal) => {
show.value = newVal
},
{
//
deep: true,
immediate: true,
},
)
</script>
<template>
<el-dialog
v-model="show"
center
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
destroy-on-close
append-to-body
:title="title"
:width="width"
:before-close="cancel"
>
<slot />
<template #footer>
<div v-if="$slots.footer" class="dialog-footer">
<slot name="footer" />
</div>
<div v-else class="dialog-footer">
<ft-button :click-handle="cancel">
取消
</ft-button>
<ft-button type="primary" :click-handle="okHandle">
确认
</ft-button>
</div>
</template>
</el-dialog>
</template>
<style scoped lang="scss">
</style>

182
src/components/common/FTStream/index.vue

@ -0,0 +1,182 @@
<script setup lang="ts">
import { cmdNameMap } from 'libs/utils'
import { useSystemStore } from 'stores/systemStore'
import { computed, nextTick, ref, watch } from 'vue'
defineProps({
visible: {
type: Boolean,
default: false,
},
})
const systemStore = useSystemStore()
const title = computed(() => {
const commandKey = systemStore.systemList[0]?.command.replace(/^debug_/, '')
return cmdNameMap[commandKey as keyof typeof cmdNameMap] || systemStore.systemList[0]?.command
})
const maskBodyRef = ref<HTMLElement | null>(null)
const maskRef = ref<HTMLElement | null>(null)
const maskHeaderRef = ref<HTMLElement | null>(null)
const statusMap = {
error: 'danger',
fail: 'danger',
success: 'success',
finish: 'primary',
SEND: 'primary',
receive: 'primary',
start: 'primary',
result: 'primary',
}
watch(
() => systemStore.systemList,
async () => {
await nextTick()
if (maskBodyRef.value) {
maskBodyRef.value.scrollTop = maskBodyRef.value.scrollHeight
}
},
{ deep: true },
)
//
let isDragging = false
let offsetX = 0
let offsetY = 0
const handleMouseDown = (event: MouseEvent | TouchEvent) => {
if (maskRef.value && maskHeaderRef.value) {
isDragging = true
const clientX = 'clientX' in event ? event.clientX : event.touches[0].clientX
const clientY = 'clientY' in event ? event.clientY : event.touches[0].clientY
offsetX = clientX - maskRef.value.offsetLeft
offsetY = clientY - maskRef.value.offsetTop
document.addEventListener('mousemove', handleMouseMove)
document.addEventListener('mouseup', handleMouseUp)
document.addEventListener('touchmove', handleMouseMove)
document.addEventListener('touchend', handleMouseUp)
}
}
const handleMouseMove = (event: MouseEvent | TouchEvent) => {
if (maskRef.value && isDragging) {
const clientX = 'clientX' in event ? event.clientX : event.touches[0].clientX
const clientY = 'clientY' in event ? event.clientY : event.touches[0].clientY
// body
const bodyWidth = document.body.clientWidth
const bodyHeight = document.body.clientHeight
// body
const newLeft = Math.max(0, Math.min(clientX - offsetX, bodyWidth - maskRef.value.offsetWidth))
const newTop = Math.max(0, Math.min(clientY - offsetY, bodyHeight - maskRef.value.offsetHeight))
maskRef.value.style.left = `${newLeft}px`
maskRef.value.style.top = `${newTop}px`
}
}
const handleMouseUp = () => {
isDragging = false
document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseup', handleMouseUp)
document.removeEventListener('touchmove', handleMouseMove)
document.removeEventListener('touchend', handleMouseUp)
}
</script>
<template>
<teleport to="body">
<!-- 使用 transition 组件包裹 mask 元素 -->
<transition name="mask-fade">
<div
v-if="visible && systemStore.isDebug"
ref="maskRef"
class="mask"
>
<div
ref="maskHeaderRef" class="mask-header" @mousedown="handleMouseDown"
@touchstart="handleMouseDown"
>
<p>{{ title }}</p>
<el-icon @click="systemStore.updateStreamVisible(false)">
<Close />
</el-icon>
</div>
<div ref="maskBodyRef" class="mask-body">
<el-timeline>
<el-timeline-item
v-for="item in systemStore.systemList" :key="item"
:timestamp="JSON.stringify(item.content)"
>
<el-tag :type="statusMap[item.status as keyof typeof statusMap]" class="mask-tag">
{{ item.title }}
</el-tag>
</el-timeline-item>
</el-timeline>
</div>
</div>
</transition>
</teleport>
</template>
<style scoped lang="scss">
.mask {
width: 400px;
height: 250px;
padding: 5px 10px;
background: #fff;
box-shadow: var(--el-box-shadow-light);
position: absolute;
bottom: 20px;
right: 20px;
border-radius: 8px;
font-size: 30px;
z-index: 5000;
.mask-header {
display: flex;
justify-content: space-between;
align-items: center;
height: 30px;
font-size: 16px;
border-bottom: 1px solid #ddd;
cursor: move; //
.el-icon svg{
width: 18px;
cursor: pointer;
}
}
.mask-body {
padding: 5px 2px;
height: calc(100% - 31px);
overflow: auto;
}
}
/* 定义过渡效果 */
//.mask-fade-enter-active, .mask-fade-leave-active {
// transition: transform 0.5s ease;
//}
//
//.mask-fade-enter-from {
// transform: translateX(100%);
//}
//
//.mask-fade-leave-to {
// transform: translateX(100%);
//}
:deep(.el-timeline-item__timestamp.is-bottom) {
white-space: pre-wrap; /* 保留空格和换行符,允许自动换行 */
word-wrap: break-word; /* 允许长单词或URL强制断行 */
overflow-wrap: break-word;
}
.mask-tag {
max-width: 100%;
height: fit-content;
padding: 5px;
white-space: pre-wrap;
}
</style>

7
src/components/common/FTTable/expand.ts

@ -0,0 +1,7 @@
export default {
props: ['row', 'render', 'index', 'column'],
inheritAttrs: false,
setup(props: any) {
return () => props.render(props.row)
},
}

216
src/components/common/FTTable/index.vue

@ -0,0 +1,216 @@
<script setup lang="ts">
import type { VNode } from 'vue'
import { onMounted, reactive } from 'vue'
import Expand from './expand'
defineOptions({
name: 'FtTable',
})
const props = withDefaults(defineProps<TableProp>(), {
columns: () => [],
mustInit: true,
hasHeader: false,
hasPage: false,
searchList: () => [],
getDataFn: () => Promise.resolve([]),
})
const emits = defineEmits([
'add',
'edit',
'detail',
'copy',
'sort',
'save',
'cancel',
'del',
'import',
'export',
'clickTreeNode',
'newTreeNode',
'editTreeNode',
'delTreeNode',
'rowClick',
'rowDblclick',
])
enum ColumnType {
index = 'index',
selection = 'selection',
expand = 'expand',
}
interface TableColumn {
title: string
key: string
type?: ColumnType
width?: number //
fixed?: 'left' | 'right' | undefined //
render?: (row: any) => VNode //
selectable?: (row: any) => boolean
}
interface Btn {
name: string
icon?: string
type?: string
serverUrl: string
serverCondition?: 0 | 1 | 2
}
interface Search {
name: string
icon?: string
key?: string
type?: string
serverUrl: string
}
interface TableProp {
columns?: TableColumn[]
getDataFn?: (params: any) => Promise<any> //
mustInit?: boolean // mountedgetDataFn
hasHeader?: boolean
hasPage?: boolean
btnList?: Btn[]
searchList?: Search[]
}
async function methodParent(fn: any) {
const newFn = fn[0] === '/' ? fn.slice(1) : fn
emits(newFn as never, state.selectedRows)
}
onMounted(() => {
if (props.mustInit) {
initData()
}
})
const state = reactive({
loading: false,
dataTotal: 0,
tableData: [],
selectedRows: [],
filterObj: { pageSize: 10, pageNum: 1 },
})
function initData() {
state.loading = true
props
.getDataFn(state.filterObj)
.then((data) => {
console.log(data)
state.tableData = props.hasPage ? data.list : data
state.dataTotal = data.total
})
.finally(() => {
state.loading = false
})
}
const handleSelectionChange = (val: any) => {
state.selectedRows = val
}
defineExpose({
initData,
})
</script>
<template>
<div class="ft-table">
<div v-if="hasHeader" class="header">
<div v-for="search in searchList" :key="search.key" class="search">
<el-input v-model="state.filterObj[search.key]" :placeholder="search.title" clearable>
<template #append>
<el-icon class="el-input__icon" @click="initData">
<Search />
</el-icon>
</template>
</el-input>
</div>
<div v-for="btn in btnList" :key="btn.serverUrl">
<ft-button
:icon="btn.icon"
:type="btn.type"
:disabled="
!btn.serverCondition
? false
: btn.serverCondition === 1
? state.selectedRows.length !== 1
: state.selectedRows.length < 1
"
@click="methodParent(btn.serverUrl)"
>
{{ btn.name }}
</ft-button>
</div>
</div>
<div class="table-main">
<el-table
v-loading="state.loading"
:="$attrs"
:data="state.tableData"
style="width: 100%"
height="100%"
:highlight-current-row="true"
class="container-table"
header-row-class-name="header-row-class"
v-on="$attrs"
@selection-change="handleSelectionChange"
>
<template v-for="(column, index) in columns" :key="column.key">
<el-table-column
show-overflow-tooltip
:prop="column.key"
:label="column.title"
:width="column.width"
:type="column.type"
:fixed="column.fixed"
:selectable="column.selectable"
>
<template v-if="column.render" #default="scope">
<Expand :column="column" :row="scope.row" :render="column.render" :index="index" />
</template>
</el-table-column>
</template>
</el-table>
</div>
<div v-if="hasPage" class="table-page">
<el-pagination v-model:current-page="state.filterObj.pageNum" v-model:page-size="state.filterObj.pageSize" size="small" background layout="sizes, prev, pager, next" :total="state.dataTotal" @change="initData" />
</div>
</div>
</template>
<style lang="scss" scoped>
.ft-table {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.header {
display: flex;
justify-content: flex-start;
align-items: center;
margin-bottom: 10px;
.search {
width: 200px;
}
}
.ft-button {
margin: 0 5px;
}
.table-main {
flex: 1;
overflow: auto;
}
.table-page {
margin-top: 10px;
display: flex;
justify-content: flex-end;
}
:deep(.header-row-class) {
th {
background-color: rgba(0,0,0,0.02 ) !important;
color: rgba(0, 0, 0, 0.85)
}
}
</style>

235
src/components/container/Item/index.vue

@ -0,0 +1,235 @@
<script lang="ts" setup>
import { ElMessageBox } from 'element-plus'
import { FtMessage } from 'libs/message'
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import { updateContainer } from '@/apis/container'
import { useSolutionStore } from '@/stores/useSolutionStore'
const props = defineProps({
itemIndex: {
type: Number,
required: true,
},
solutionItem: {
type: Object,
default: () => {},
},
})
const emits = defineEmits<{
(e: 'ok'): void
}>()
const router = useRouter()
const visible = ref(false)
const solutionStore = useSolutionStore()
const solutionId = ref()
const selectedSolutionItem = ref()
const solutionInfo = ref(props.solutionItem)
const solutionStyle = computed(() => {
const difference = (solutionInfo.value.capacityTotal - solutionInfo.value.capacityUsed) / solutionInfo.value.capacityTotal
const process = 100 - (difference * 100)
const filter = difference > 0.4 ? 'hue-rotate(270deg) saturate(100) brightness(81%)' : difference > 0.1 ? 'hue-rotate(150deg) saturate(100)' : 'hue-rotate(120deg) saturate(100)'
return {
'filter': filter,
'-webkit-mask': `linear-gradient(to bottom, transparent ${process}%, #EEEFF8 ${process + 0.1}%)`,
}
})
const onInputBlur = async () => {
if (solutionInfo.value.capacityUsed > solutionInfo.value.capacityTotal) {
FtMessage.error(`容器容量不能超过${solutionInfo.value.capacityTotal}mL`)
solutionInfo.value.capacityUsed = 5000
return
}
await ElMessageBox.confirm(
'确认保存吗? ',
'提示',
{
confirmButtonText: '确认',
showClose: false,
showCancelButton: true,
closeOnClickModal: false,
closeOnPressEscape: false,
type: 'warning',
},
)
await saveContainer()
}
const saveContainer = async () => {
if (!solutionInfo.value.capacityUsed) {
FtMessage.warning('请输入当前容量')
return
}
if (!solutionInfo.value.solutionId) {
FtMessage.warning('请选择酸液')
return
}
const params: Container.ContainerItem = {
id: solutionInfo.value.id,
type: 0,
solutionId: solutionInfo.value.solutionId,
pumpId: solutionInfo.value.pumpId,
capacityTotal: solutionInfo.value.capacityTotal,
capacityUsed: solutionInfo.value.capacityUsed,
filled: solutionInfo.value.filled,
}
await updateContainer(params)
FtMessage.success('保存成功')
emits('ok')
}
const onSolutionChange = (value: number) => {
if (value) {
solutionId.value = value
solutionInfo.value.solutionId = value
selectedSolutionItem.value = solutionStore.solutionList.filter(item => item.id === value)[0]
}
}
const onClose = () => {
solutionId.value = null
visible.value = false
}
const onSubmitSolution = () => {
if (!solutionStore.solutionList.length) {
//
router.push('/solution')
return
}
solutionInfo.value.solutionName = selectedSolutionItem.value.name
solutionInfo.value.solutionId = selectedSolutionItem.value.id
saveContainer()
onClose()
}
</script>
<template>
<div class="liquied-item">
<div class="header">
<div class="solution-select">
<div class="solution-name">
<span v-if="solutionInfo.solutionName">{{ solutionInfo.solutionName }}</span>
<span v-else style="color:#d2d2d2">选择酸液</span>
</div>
</div>
</div>
<div class="content">
<div class="bottle_base">
<img class="content-img" src="@/assets/images/liquied/liquied_bottle.svg" alt="chemical-bottle">
</div>
<div class="bottle" :style="solutionStyle">
<img class="content-img" src="@/assets/images/liquied/liquied_bottle.svg" alt="chemical-bottle">
</div>
</div>
<div class="footer">
<div class="footer-content">
<span>{{ solutionInfo.capacityTotal }}mL</span>
</div>
<div class="footer-edit">
<span>已使用</span>
<el-input v-model="solutionInfo.capacityUsed" type="number" @blur="onInputBlur">
<template #append>
mL
</template>
</el-input>
</div>
</div>
<FtDialog v-model="visible" title="选择酸液" :ok-handle="onSubmitSolution" @cancel="onClose">
<div v-if="solutionStore.solutionList.length">
<el-radio-group v-model="solutionId" size="large" class="radio-group" @change="onSolutionChange">
<el-radio-button v-for="item in solutionStore.solutionList" :key="item.id" :label="item.name" :value="item.id" class="radio-marge" />
</el-radio-group>
</div>
<div v-else>
<el-empty description="description">
<template #description>
未添加酸液请在溶液管理中配置
</template>
</el-empty>
</div>
</FtDialog>
</div>
</template>
<style scoped>
.liquied-item {
border: 1px solid #ccc;
border-radius: 10px;
width: 200px;
height: 300px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}
.content{
position: relative;
display: flex;
justify-content: center;
}
.bottle_base{
position: relative;
}
.bottle {
position: absolute;
}
.header {
display: flex;
align-items: center;
background: rgba(25, 137, 250, 0.1216);
justify-content: center;
border-radius: 10px 10px 0 0;
padding:10px;
width: 100%;
}
.content-img{
height: 150px;
margin: 10px;
}
.footer-content{
display: flex;
justify-content: center;
}
.footer-edit {
display: flex;
justify-content: space-around;
align-items: center;
padding: 10px 0;
.el-input {
margin-left: 10px;
width: 120px;
}
}
.checked {
text-decoration: line-through;
}
.solution-select{
display:flex;
width: 120px;
height: 25px;
text-align: center;
margin-left: 5px;
align-items: center;
border-radius: 5px;
}
.solution-name{
width: 100px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
}
.add-icon{
margin-right: -4px;
}
.button-icon{
height: 25px;
width: 25px;
margin: 3px;
}
.radio-marge{
margin: 10px;
border: 1px solid #e0e0e0;
}
</style>

391
src/components/craft/AddCraft/index.vue

@ -0,0 +1,391 @@
<script setup lang="ts">
import { getContainerList } from 'apis/container'
import { createCraft, updateCraft } from 'apis/crafts'
import { getSolsList } from 'apis/solution'
import { configList } from 'apis/system'
import emptyIcon from 'assets/images/empty.svg'
import { FtMessage } from 'libs/message'
import { allPropertiesDefined } from 'libs/utils'
import { cloneDeep } from 'lodash'
import { computed, onMounted, ref } from 'vue'
const props = defineProps({
sourceData: {
type: Object,
default: () => ({}),
},
})
const emits = defineEmits(['ok', 'cancel'])
const containerList = ref<Container.ContainerItem[]>([])
const solutionList = ref<Solution.SolutionItem[]>([])
const configData = ref<any[]>([])
onMounted(async () => {
containerList.value = await getContainerList()
solutionList.value = (await getSolsList()).list
configData.value = await configList()
if (props.sourceData) {
form.value = { ...props.sourceData, stepList: JSON.parse(props.sourceData.steps || '[]') }
form.value.stepList.forEach((step: any) => {
if (step.params.time) {
step.params.minutes = Math.floor(step.params.time / 60)
step.params.seconds = step.params.time % 60
}
})
}
})
const heatMax = computed(() => {
return configData.value.find(item => item.code === 'heat_temperature')?.value || 0
})
const dryMax = computed(() => {
return configData.value.find(item => item.code === 'dry_temperature')?.value || 0
})
const annealMax = computed(() => {
return configData.value.find(item => item.code === 'anneal_temperature')?.value || 0
})
const heightMax = computed(() => {
return configData.value.find(item => item.code === 'needle_drop_height')?.value || 0
})
const form = ref({
stepList: [],
})
const formRef = ref()
const rules = {
name: [{ required: true, trigger: 'blur', message: '请输入工艺名称' }],
}
const okHandle = async () => {
try {
const valid = await formRef.value.validate()
if (!valid) {
return
}
//
const errorMsg: string[] = []
const invalidStepIndex = form.value.stepList.findIndex((step: any, index) => {
if (['heat', 'dry', 'anneal'].includes(step.method)) {
if (step.params.minutes || step.params.seconds) {
step.params.time = (step.params.minutes || 0) * 60 + (step.params.seconds || 0) || undefined
}
else {
step.params.time = undefined
}
if (+step.params.temperature > +annealMax.value && step.method === 'anneal') {
errorMsg.push(`步骤${index + 1}: 退火温度不能超过${annealMax.value}`)
}
if (+step.params.temperature > +heatMax.value && step.method === 'heat') {
errorMsg.push(`步骤${index + 1}: 加热温度不能超过${heatMax.value}`)
}
if (+step.params.temperature > +dryMax.value && step.method === 'dry') {
errorMsg.push(`步骤${index + 1}: 烘干温度不能超过${dryMax.value}`)
}
}
if (['clean', 'reduceLiquid', 'addThick'].includes(step.method)) {
if (+step.params.height > +heightMax.value) {
errorMsg.push(`步骤${index + 1}: 针头高度不能超过${heightMax.value}mm`)
}
}
step.params.description = `${index + 1}.`
switch (step.method) {
case 'clean':
step.params.description += `针头高度${step.params.height}mm, 加${step.params.volume}ml水清洗${step.params.cycle}`
break
case 'addThin':
step.params.description += `添加稀硝酸 ${step.params.volume}mL`
break
case 'addThick':
step.params.description += `添加浓硝酸 ${step.params.volume}mL`
break
case 'reduceLiquid':
step.params.description += `针头高度${step.params.height}mm抽取液体`
break
case 'preHeat':
step.params.description += `${step.params.type === 'heat' ? '加热区' : '退火区'}预热到${step.params.temperature}`
break
case 'heat':
step.params.description += `加热: ${step.params.temperature}度, 保持${step.params.minutes || 0}${step.params.seconds || 0}`
break
case 'dry':
step.params.description += `烘干: ${step.params.temperature}度, 保持${step.params.minutes || 0}${step.params.seconds || 0}`
break
case 'anneal':
step.params.description += `退火: ${step.params.temperature}度, 保持${step.params.minutes || 0}${step.params.seconds || 0}`
break
}
return !allPropertiesDefined(step.params, ['minutes', 'seconds', 'description'])
})
console.log(form.value)
if (invalidStepIndex !== -1) {
FtMessage.error(`步骤${invalidStepIndex + 1}: 请填写完整参数`)
return
}
if (errorMsg.length) {
FtMessage.error(errorMsg.join('; '))
return
}
form.value.steps = JSON.stringify(form.value.stepList)
if (form.value.id) {
await updateCraft(form.value)
}
else {
await createCraft(form.value)
}
FtMessage.success('保存成功')
emits('ok')
}
catch (error) {
console.log(error)
}
}
const cancel = () => {
emits('cancel')
}
const stepMap = {
preHeat: { name: '预热', method: 'preHeat', params: { type: 'heat', temperature: undefined, description: undefined } },
addThin: {
name: '加稀硝酸',
method: 'addThin',
params: { volume: undefined, description: undefined },
},
addThick: {
name: '加浓硝酸',
method: 'addThick',
params: { height: undefined, volume: undefined, description: undefined },
},
heat: {
name: '加热',
method: 'heat',
params: { temperature: undefined, time: undefined, description: undefined, minutes: undefined, seconds: undefined },
},
// reduceLiquid: { name: '', method: 'reduceLiquid', params: { height: undefined, description: undefined } },
clean: {
name: '清洗',
method: 'clean',
params: { cycle: undefined, height: undefined, volume: 2, description: undefined },
},
dry: {
name: '烘干',
method: 'dry',
params: { temperature: undefined, time: undefined, description: undefined, minutes: undefined, seconds: undefined },
},
anneal: {
name: '退火',
method: 'anneal',
params: {
temperature: undefined,
time: undefined,
description: undefined,
minutes: undefined,
seconds: undefined,
},
},
}
const addStep = (data: any) => {
form.value.stepList.push(cloneDeep(data))
}
</script>
<template>
<FtDialog visible :title="form.id ? '编辑工艺' : '新增工艺'" width="80%" :ok-handle="okHandle" @cancel="cancel">
<el-form ref="formRef" label-width="auto" :model="form" :rules="rules" class="form-box" label-position="left">
<el-row :gutter="30">
<el-col :span="10">
<el-form-item label="工艺名称" prop="name">
<el-input v-model="form.name" placeholder="请输入工艺名称" />
</el-form-item>
<el-form-item label="步骤列表">
<div class="button-content">
<el-tag v-for="item in stepMap" :key="item" size="large" @click="() => addStep(item)">
<div style="display: flex; align-items: center; justify-content: space-around; width: 100%">
<el-icon><Plus /></el-icon>
<span> {{ item.name }}</span>
</div>
</el-tag>
</div>
</el-form-item>
</el-col>
<el-col :span="14">
<div v-if="form.stepList.length" class="step-box">
<div v-for="(item, index) in form.stepList" :key="index" class="step-item">
<el-form-item :label="`${index + 1}: ${item.name}`">
<template v-if="item.method === 'clean'">
<el-input v-model="item.params.height" type="number" size="small" placeholder="请输入高度">
<template #append>
mm
</template>
</el-input>
<el-input
v-model="item.params.volume"
style="width: 100px"
type="number"
size="small"
placeholder="请输入加水量"
>
<template #append>
mL
</template>
</el-input>
<el-input v-model="item.params.cycle" type="number" size="small" placeholder="请输入次数">
<template #append>
</template>
</el-input>
</template>
<template v-else-if="['addThin', 'addThick'].includes(item.method)">
<el-input v-model="item.params.volume" type="number" size="small" placeholder="请输入容量">
<template #append>
mL
</template>
</el-input>
<el-input v-if="item.method === 'addThick'" v-model="item.params.height" type="number" size="small" placeholder="请输入高度">
<template #append>
mm
</template>
</el-input>
</template>
<template v-else-if="item.method === 'reduceLiquid'">
<el-input v-model="item.params.height" type="number" size="small" placeholder="请输入高度">
<template #append>
mm
</template>
</el-input>
</template>
<template v-else-if="item.method === 'preHeat'">
<el-select v-model="item.params.type" size="small">
<el-option label="加热区" value="heat" />
<el-option label="退火区" value="anneal" />
</el-select>
<el-input
v-model="item.params.temperature"
type="number"
size="small"
placeholder="请输入温度"
>
<template #append>
</template>
</el-input>
</template>
<template v-else-if="['heat', 'dry', 'anneal'].includes(item.method)">
<el-input v-model="item.params.temperature" type="number" :max="item.method === 'anneal' ? 400 : 200" size="small" placeholder="加热温度">
<template #append>
</template>
</el-input>
<el-select
v-model="item.params.minutes"
style="width: 70px"
clearable
size="small"
placeholder="请选择"
>
<el-option v-for="i in 60" :key="i" :label="i" :value="i" />
</el-select>
<span class="unit-text"></span>
<el-select
v-model="item.params.seconds"
style="width: 70px"
clearable
size="small"
placeholder="请选择"
>
<el-option v-for="i in 60" :key="i" :label="i" :value="i" />
</el-select>
<span class="unit-text"></span>
</template>
<el-icon style="margin-left: auto" @click="() => form.stepList.splice(index, 1)">
<Close />
</el-icon>
</el-form-item>
</div>
</div>
<div v-else class="empty-box">
<img :src="emptyIcon" alt="">
<span>暂无步骤</span>
</div>
</el-col>
</el-row>
</el-form>
</FtDialog>
</template>
<style scoped lang="scss">
.form-box {
height: 50vh;
.el-row {
height: 100%;
.el-col:first-child {
border-right: 1px solid #eee;
}
.el-col {
height: 100%;
.step-box {
height: 100%;
overflow: auto;
}
}
}
}
.button-content {
width: 100%;
display: grid;
grid-template-columns: repeat(2, 1fr); /* 创建3列等宽轨道 */
grid-template-rows: repeat(4, auto); /* 创建2行自动高度 */
gap: 20px;
}
:deep(.el-tag__content) {
width: 100%;
}
.empty-box {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #ccc;
img {
margin-bottom: 10px;
}
}
.step-item {
.el-form-item {
background: rgba(82, 148, 215, 0.06);
padding: 5px;
margin-bottom: 10px;
:deep(.el-form-item__label) {
height: 25px;
line-height: 25px;
}
:deep(.el-form-item__content) {
width: 100%;
display: flex;
align-items: center;
position: relative;
.el-input,
.el-select {
width: 120px;
margin: 0 5px;
}
.info-box {
width: 80%;
height: 100%;
}
}
}
}
.unit-text {
font-size: 12px;
line-height: 25px;
}
</style>

285
src/components/craft/AddCraftDialog.vue

@ -0,0 +1,285 @@
<script lang="ts" setup>
import { ElMessage } from 'element-plus'
import { StepCmdDescMap } from 'views/craft/craft_constant'
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import { createCraft, updateCraft } from '@/apis/crafts'
import FtButton from '../common/FTButton/index.vue'
import FTDialog from '../common/FTDialog/index.vue'
import TransferLeft from './TransferLeft.vue'
import TransferRight from './TransferRight.vue'
const emit = defineEmits<{
(e: 'ok'): void
}>()
// const stepMap = {
// preHeat: { name: '', data: { method: 'preHeat', params: { temperature: undefined } } },
// addLiquid: { name: '', data: { method: 'addLiquid', params: { volume: undefined, id: undefined } } },
// reduceLiquid: { name: '', data: { method: 'reduceLiquid', params: { volume: undefined, id: undefined } } },
// heatStart: { name: '', data: { method: 'heatStart', params: { temperature: undefined, time: undefined } } },
// fanStart: { name: '', data: { method: 'fanStart', params: { temperature: undefined } } },
// }
const visible = ref(false)
const stepStructs = ref<CraftTypes.StepStruct[]>([])
const craftObj = ref<CraftTypes.Craft>({})
const loading = ref(false)
const saveRef = ref<CraftTypes.SaveRef | null>(null)
const tempStepStructs = ref<CraftTypes.StepStruct[]>([])
const route = useRoute()
const oresId: number = route.query.oreId as unknown as number
const stepCmds: CraftTypes.StepCmd[] = [
// 'upTray',
// 'downTray',
'addLiquid',
// 'moveToSol',
// 'moveToHeat',
// 'shaking',
'startHeating',
// 'stopHeating',
// 'takePhoto',
// 'delay',
]
const openDialog = () => {
visible.value = true
}
const closeDialog = () => {
stepStructs.value = []
craftObj.value.name = ''
visible.value = false
}
const editDialog = (craftInfo: CraftTypes.Craft) => {
craftObj.value = { ...craftInfo }
if (craftInfo && craftInfo.steps) {
const step = JSON.parse(craftInfo.steps)
console.log(craftInfo)
if (step && step.length) {
step.forEach((item: CraftTypes.StepStruct) => {
if (item.method === 'addLiquid') {
const list = item.params.tubeSolList
if (list && list.length === 16 && item.params.tubeSolList) {
item.params.tubeSolList = [{
tubeNum: 0,
addLiquidList: item.params.tubeSolList[0].addLiquidList,
}]
}
}
})
}
stepStructs.value = step
}
openDialog()
}
function onConfirm() {
if (!craftObj.value.name) {
ElMessage.warning('请输入工艺名称')
return
}
const stepList = JSON.parse(JSON.stringify(stepStructs.value))
for (const step of stepList) {
if (step.method === 'addLiquid') {
const list: CraftTypes.TubeSolStruct[] = step.params.tubeSolList
if (list && list.length) {
list.forEach((item) => {
const tubeNum = item.tubeNum
if (tubeNum === 0) {
const tubeSolList: CraftTypes.TubeSolStruct[] = []
for (let index = 0; index < 16; index++) {
tubeSolList.push({
tubeNum: index + 1,
addLiquidList: item.addLiquidList,
})
}
step.params.tubeSolList = tubeSolList
}
})
}
}
}
craftObj.value.steps = JSON.stringify(stepList)
confirmCraftEdit(craftObj.value)
}
const confirmCraftEdit = (craft: CraftTypes.Craft) => {
let req
if (craft.id) {
req = updateCraft(craft)
}
else {
craft = { name: craft.name, steps: craft.steps, oresId }
req = createCraft(craft)
}
loading.value = true
saveRef.value && saveRef.value.setLoading(true)
req.then(() => {
saveRef.value && saveRef.value.setLoading(false)
ElMessage.success('保存成功')
emit('ok')
closeDialog()
}).catch(() => {
saveRef.value && saveRef.value.setLoading(false)
})
}
function addStep(step: CraftTypes.StepCmd) {
let st: CraftTypes.StepStruct
if (step === 'addLiquid') {
st = {
method: step,
params: {
tubeSolList: [{
tubeNum: 0,
addLiquidList: [{
solId: 1,
volume: 10,
}],
}],
},
}
}
else if (step === 'startHeating') {
st = {
method: step,
params: {
temperature: 100,
second: 10,
},
}
}
else if (step === 'delay') {
st = {
method: step,
params: {
second: 10,
},
}
}
else if (step === 'shaking') {
st = {
method: step,
params: {
second: 30,
},
}
}
else {
st = {
method: step,
}
}
stepStructs.value = [...stepStructs.value, st]
tempStepStructs.value = JSON.parse(JSON.stringify(stepStructs.value))
}
function onStepItemDel(order: number) {
stepStructs.value = stepStructs.value.filter((s, i) => i !== order - 1)
}
function transferChange(stepData: CraftTypes.StepStruct, order: number) {
console.log('order === ', stepStructs.value, stepData, order)
}
defineExpose({
openDialog,
closeDialog,
editDialog,
})
</script>
<template>
<FTDialog v-model="visible" title="添加工艺" style="width:70vw; height:70vh">
<div>
<div class="mt-5 mb-8 flex items-center">
<label class="font-medium mr-4">工艺名称</label>
<el-input
v-model.trim="craftObj.name"
style="width: 200px"
size="small"
type="text"
placeholder="请输入名称"
class="flex-auto bg-[#f6f6f6] h-11 leading-10 rounded-sm px-4"
/>
</div>
<div class="craft-title">
<div>
工艺步骤
</div>
<div class="title-right">
选择的步骤
</div>
</div>
<div class="step-content">
<div class="transfer-left">
<TransferLeft v-for="cmd in stepCmds" :key="cmd" :title="StepCmdDescMap[cmd]" @click="addStep(cmd)" />
</div>
<div v-if="stepStructs && stepStructs.length" class="transfer-right">
<TransferRight v-for="(step, idx) in stepStructs" :key="idx" :order="idx + 1" :step="step" type="add" @del="onStepItemDel" @transfer-change="transferChange" />
</div>
<div v-else>
<el-empty description="description">
<template #description>
未选择工艺步骤
</template>
</el-empty>
</div>
</div>
</div>
<template #footer>
<div class="footer">
<FtButton @click="closeDialog">
取消
</FtButton>
<FtButton ref="saveRef" type="primary" :loading="loading" :click-handle="onConfirm">
确定
</FtButton>
</div>
</template>
</FTDialog>
</template>
<style lang="scss" scoped>
.transfer-left{
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
width:150px;
place-items: center;
height: 3rem;
}
.transfer-right{
max-height: 40vh;
overflow: auto;
width:30rem;
}
.step-content{
margin-top:1rem;
display: grid;
grid-template-columns: 1fr 1fr;
height: 21rem;
}
.footer{
display: flex;
height: 5rem;
justify-content: center;
align-items: center;
}
.craft-title{
display: grid;
grid-template-columns: 1fr 1fr;
div{
font-size:12px;
margin-top: 5px;
}
.title-right{
display: flex;
justify-content: center;
}
}
</style>

257
src/components/craft/CraftStatus.vue

@ -0,0 +1,257 @@
<script lang="ts" setup>
import { ElMessage } from 'element-plus'
import { nextTick, ref } from 'vue'
import { craftstatus, craftstatusByHeatId, stopCraft } from '@/apis/crafts'
import TransferRight from './TransferRight.vue'
const statusVisible = ref(false)
const byHeatIdTimes = ref()
const stateList = ref<CraftTypes.CraftState[]>([])
const craftValue = ref()
const craftList = ref<CraftTypes.CraftState[]>([])
const stateMap: Record<string, string> = {
RUNNING: '执行中',
FINISHED: '执行完成',
ERROR: '执行失败',
}
const heatModuleMap: Record<string, string> = {
heat_module_01: '加热区01',
heat_module_02: '加热区02',
heat_module_03: '加热区03',
heat_module_04: '加热区04',
heat_module_05: '加热区05',
heat_module_06: '加热区06',
}
// const queryCraftStatus = () => {
// intervalTimes.value = setInterval(() => {
// craftstatus().then((res) => {
// if (res && res.length === 0) {
// clearInterval(intervalTimes.value)
// return
// }
// const craftStateList = res
// const uniqueData: CraftTypes.CraftState[] = []
// const idMap: Record<string | number, string | boolean> = {}
// res.forEach((item) => {
// const craftsId = item.craftsId
// if (!idMap[craftsId]) {
// idMap[craftsId] = true
// uniqueData.push(item)
// }
// })
// craftList.value = uniqueData
// if (uniqueData.length) {
// const craftsId = uniqueData[0].craftsId
// craftValue.value = craftsId
// onSelectChange(craftsId)
// }
// stateList.value.push(...uniqueData)
// if (craftStateList && craftStateList.length) {
// const finishedList = []
// uniqueData.forEach((item) => {
// const state = item.state
// if (state === 'FINISHED') {
// finishedList.push(state)
// }
// })
// if (finishedList.length === uniqueData.length) {
// clearInterval(intervalTimes.value)
// }
// }
// })
// }, 1000)
// }
const loading = ref(false)
const queryCraftStatus = () => {
loading.value = true
craftstatus().then((res) => {
loading.value = false
if (res && res.length) {
const uniqueData: CraftTypes.CraftState[] = []
const idMap: Record<string | number, string | boolean> = {}
res.forEach((item) => {
const craftsId = item.craftsId
if (!idMap[craftsId]) {
idMap[craftsId] = true
uniqueData.push(item)
}
})
craftList.value = uniqueData
queryCraftStatusByHeatId(res[0].heatId)
}
})
}
const queryCraftStatusByHeatId = (heatId: string) => {
byHeatIdTimes.value = setInterval(() => {
craftstatusByHeatId(heatId).then((res) => {
currentSteps.value = res.steps
const craftsId = res.craftsId
craftValue.value = craftsId
stateList.value.push(res)
if (res.state === 'FINISHED' || res.state === 'ERROR') {
clearInterval(byHeatIdTimes.value)
}
onHandleSteps(res.steps)
scrollToBottom()
})
}, 1000)
}
const showDialog = () => {
statusVisible.value = true
queryCraftStatus()
}
const onCloseDialog = () => {
stateList.value = []
}
const currentSteps = ref<CraftTypes.StepStruct[]>([])
const currentHeatId = ref()
const onSelectChange = (value: string | number) => {
stateList.value = []
currentHeatId.value = value
const list = craftList.value.filter(item => item.craftsId === value)
list.forEach((item) => {
const steps = item.steps
clearInterval(byHeatIdTimes.value)
queryCraftStatusByHeatId(item.heatId)
onHandleSteps(steps)
currentSteps.value = []
nextTick(() => {
currentSteps.value = steps
})
})
}
const onHandleSteps = (steps: CraftTypes.StepStruct[]) => {
if (!steps) {
return
}
steps.forEach((item) => {
if (item.method === 'addLiquid') {
const list = item.params.tubeSolList
if (list && list.length === 16 && item.params.tubeSolList) {
item.params.tubeSolList = [{
tubeNum: 0,
addLiquidList: item.params.tubeSolList[0].addLiquidList,
}]
}
}
})
}
const statusAreaRef = ref<HTMLElement | null>(null)
const scrollToBottom = () => {
setTimeout(() => {
if (statusAreaRef.value) {
statusAreaRef.value.scrollTop = statusAreaRef.value.scrollHeight
}
}, 100)
}
const onStopCraft = () => {
stopCraft({ heatId: currentHeatId.value }).then(() => {
clearInterval(byHeatIdTimes.value)
ElMessage.success('工艺已停止')
})
}
defineExpose({
showDialog,
})
</script>
<template>
<el-dialog v-model="statusVisible" style="width:80vw; height:60vh" :close-on-click-modal="false" @close="onCloseDialog">
<div class="state-container">
<div class="item1">
<div class="state-left" style="width:40vw">
<div>工艺列表</div>
<div style="margin-left:10px">
<el-select v-model="craftValue" style="width: 100px" size="small" placeholder="请选择" @change="onSelectChange">
<el-option v-for="item in craftList" :key="item.craftsId" :label="item.craftsName" :value="item.craftsId" />
</el-select>
<FtButton size="small" style="margin-left:10px" @click="onStopCraft">
停止工艺
</FtButton>
</div>
</div>
<div v-if="currentSteps.length" style="margin-top: 10px">
<TransferRight v-for="(step, idx) in currentSteps" :key="idx" :order="idx + 1" :step="step" type="showlog" @del="() => {}" @transfer-change="() => {}" />
</div>
<div v-else class="state-log">
<el-empty description="description" style="height:30vh">
<template #description>
<span style="color:#c2c2c2">没有正在执行的工艺</span>
</template>
</el-empty>
</div>
</div>
<div v-loading="loading" class="item2">
<div>工艺执行状态</div>
<div v-if="stateList.length" ref="statusAreaRef" class="state-log state-back">
<div v-for="item in stateList" :key="item.state">
<span class="state-span"><label class="state-log-label">矿石名称</label><span class="state-log-text">{{ item.oresName }}</span></span>
<span class="state-span"><label class="state-log-label">工艺名称</label><span class="state-log-text">{{ item.craftsName }}</span></span>
<span class="state-span"><label class="state-log-label">加热区</label><span class="state-log-text">{{ heatModuleMap[item.heatId] }}</span></span>
<span class="state-span"><label class="state-log-label">执行状态</label><span class="state-log-text">{{ stateMap[item.state] }}</span></span>
</div>
</div>
<div v-else class="state-log">
<el-empty description="description" style="height:30vh">
<template #description>
<span style="color:#c2c2c2">没有正在执行的工艺</span>
</template>
</el-empty>
</div>
</div>
</div>
</el-dialog>
</template>
<style lang="scss" scoped>
.state-container{
display: grid;
grid-template-columns: repeat(3, 1fr);
.state-left{
display: flex;
justify-content: center;
align-items: center;
}
.item1 {
grid-column: 1 / 2;
}
.item2 {
grid-column: 2 / 4;
}
}
.state-log{
height: 50vh;
max-height: 50vh;
overflow: auto;
padding: 10px;
}
.state-back{
background: #0d0d0d;
color: #0f0;
border-radius: 10px;
overflow-y: auto;
margin-bottom: 10px;
}
.state-span{
padding-left: 10px;
}
.state-log-label{
font-weight: 600;
font-size: 10px;
color: rgb(217, 180, 69);
}
.state-log-text{
font-size: 10px;
}
</style>

40
src/components/craft/TransferLeft.vue

@ -0,0 +1,40 @@
<script lang="ts" setup>
defineProps<{ title: string }>()
</script>
<template>
<div class="transfer-left btn-light">
{{ title }}
<img
src="@/assets/images/icon_add_s.svg"
alt="add"
class="add_icon"
>
</div>
</template>
<style lang="scss" scoped>
.transfer-left{
display: flex;
}
.btn-light{
display: flex;
justify-content: center;
align-items: center;
background-color: transparent;
border-radius: 4px;
color: '#1989fa';
border: solid 1px '#1989fa';
height: 11px;
}
.add_icon {
right: 1rem;
width: .875rem;
}
@media (min-width: 56.25rem) {
.add_icon {
right: 1.875rem;
}
}
</style>

161
src/components/craft/TransferRight.vue

@ -0,0 +1,161 @@
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue'
import { getContainerList } from '@/apis/container'
import { getSolsList } from '@/apis/solution'
import { useSolutionStore } from '@/stores/useSolutionStore'
import { StepCmdDescMap } from '../../views/craft/craft_constant'
const props = defineProps<{
order: number
step: CraftTypes.StepStruct
type: string
}>()
const $emit = defineEmits<{
(e: 'del', order: number): void
(e: 'transferChange', stepData: CraftTypes.StepStruct, order: number): void
}>()
const solutionStore = useSolutionStore()
const stepInfo = ref(props.step)
onMounted(() => {
querySolutionList()
})
watch(stepInfo, (newVal) => {
$emit('transferChange', newVal, props.order)
}, {
deep: true,
})
const solutionList = ref<Solution.SolutionItem[]>([])
const querySolutionList = () => {
getSolsList().then((res) => {
if (res && res.list) {
solutionList.value = res.list
solutionStore.updateSolution(res.list)
queryContainerList()
}
})
}
const containerLiquiedList = ref<Container.ContainerItem[]>([])
const queryContainerList = () => {
getContainerList().then((res) => {
res.forEach((item) => {
if (item.solutionId) {
solutionList.value.forEach((soluItem) => {
if (item.solutionId === soluItem.id) {
containerLiquiedList.value.push({
...item,
solutionName: soluItem.name,
})
}
})
}
})
})
}
</script>
<template>
<div class="right-container">
<section class="right-main">
<span class="right-seq">{{ order }}</span>
<span class="right-base">{{ StepCmdDescMap[stepInfo.method] }}</span>
<div v-if="type !== 'showlog'" class="text-item" @click="$emit('del', order)">
<img class="item-img" src="@/assets/images/icon_del_s.svg" alt="del">
</div>
</section>
<template v-if="stepInfo.method !== 'takePhoto'">
<section v-if="stepInfo.method === 'addLiquid'" class="right-liquid right-base">
<div v-for="(tubeItem, index) in stepInfo.params.tubeSolList" :key="index" class="right-liquid">
<!-- <el-select v-model="tubeItem.tubeNum" size="small" placeholder="请选择" style="width: 120px" class="right-base" :disabled="type === 'showlog'"> -->
<!-- <el-option v-for="item in tubeSolList" :key="item.id" :label="item.name" :value="item.id" /> -->
<!-- </el-select> -->
<div v-for="(liquidItem, liquidIndex) in tubeItem.addLiquidList" :key="liquidIndex" class="right-liquid right-base">
<el-select v-model="liquidItem.solId" size="small" placeholder="请选择" style="width: 100px" class="right-base" :disabled="type === 'showlog'">
<el-option v-for="item in containerLiquiedList" :key="item.id" :label="item.solutionName" :value="item.id" />
</el-select>
<div>
<el-input v-model="liquidItem.volume" size="small" style="width: 100px" :disabled="type === 'showlog'" />
<span class="mL-2">mL</span>
</div>
</div>
</div>
</section>
<section v-if="stepInfo.method === 'shaking' || stepInfo.method === 'delay'" class="right-shaking">
<div class="flex items-center right-base">
<el-input v-model="stepInfo.params.second" style="width: 100px" size="small" class="right-base" :disabled="type === 'showlog'" />
<span class="mL-2"></span>
</div>
</section>
<section v-if="stepInfo.method === 'startHeating'" class="right-shaking">
<div class="flex items-center right-base">
加热温度<el-input v-model="stepInfo.params.temperature" style="width: 100px" size="small" :disabled="type === 'showlog'" />
<span class="mL-2">°C</span>
</div>
<div class="flex items-center right-base">
加热时间<el-input v-model="stepInfo.params.second" style="width: 100px" size="small" :disabled="type === 'showlog'" />
<span class="mL-2"></span>
</div>
</section>
</template>
</div>
<div style="height:5px;background: white;" />
</template>
<style lang="scss" scoped>
div{
font-size: 12px;
}
span{
font-size: 12px;
}
.right-container{
background-color: rgb(82 148 215 / .06);
border-radius: 10px;
min-height: 40px;
padding-top: 5px;
}
.right-main{
display: flex;
align-items: center;
}
.right-base{
line-height: 1.5rem;
margin-left: 1.25rem;
}
.text-item{
margin-left: auto;
width: 2.5rem;
height: 2.5rem;
display: flex;
justify-content: center;
align-items: center;
.item-img{
width:1rem
}
}
.right-liquid{
display: flex;
height: 3rem;
}
.right-shaking{
display: flex;
height: 2.5rem;
}
.right-seq{
padding-left: 20px;
color: #19b370;
}
.el-select-dropdown__item{
display: flex;
align-items: center;
}
</style>

202
src/components/home/AddLiquid/index.vue

@ -0,0 +1,202 @@
<script setup lang="ts">
import { getContainerList } from 'apis/container'
import { getSolsList } from 'apis/solution'
import { socket } from 'libs/socket'
import { useHomeStore } from 'stores/homeStore'
import { useSystemStore } from 'stores/systemStore'
import { onMounted, onUnmounted, ref } from 'vue'
const emits = defineEmits(['ok', 'cancel'])
const homeStore = useHomeStore()
const systemStore = useSystemStore()
onMounted(async () => {
await getSols()
await queryContainerList()
socket.init(receiveMessage, 'cmd_debug')
socket.init(receiveMessage, 'cmd_response')
})
onUnmounted(() => {
socket.unregisterCallback(receiveMessage, 'cmd_debug')
socket.unregisterCallback(receiveMessage, 'cmd_response')
})
const queryContainerList = async () => {
containerList.value = await getContainerList()
}
let currentCommandId = ''
const receiveMessage = (data: Socket.cmdData) => {
data.commandId === currentCommandId && systemStore.pushSystemList(data)
}
const form = ref<{
columns?: number[]
containerId?: number
volume?: number
}>({})
const formRef = ref()
const validateHandle = (rule: any, value: any, callback: any) => {
if (!value?.length) {
callback(new Error('请选择试管'))
}
else {
callback()
}
}
const rules = {
columns: [
{ required: true, message: '请选择试管', trigger: 'change', validator: validateHandle },
],
containerId: [
{ required: true, message: '请选择溶液', trigger: 'change' },
],
volume: [
{ required: true, message: '请输入容量', trigger: 'blur' },
],
}
const okHandle = async () => {
try {
const valid = await formRef.value.validate()
if (!valid) {
return
}
currentCommandId = Date.now().toString()
const params = {
commandId: currentCommandId,
command: 'liquid_add',
params: form.value,
}
await homeStore.sendControl(params)
emits('ok')
}
catch (error) {
console.log(error)
}
}
const cancel = () => {
emits('cancel')
}
const solsList = ref<Solution.SolutionItem[]>([])
const containerList = ref<Container.ContainerItem[]>([])
const getSols = async () => {
const res = await getSolsList()
solsList.value = res.list
}
//
// const tubes = computed(() => {
// const tray = systemStore.systemStatus.trays?.find(item => item.inSolutionPositon)
// return tray?.tubes || []
// })
const selectedColumns = ref(Array.from({ length: 5 }).fill(false))
const mousedownHandle = async (index: number) => {
// if (!tubes.value.find(item => item.columnNum === index)?.exists) {
// FtMessage.error('')
// return
// }
selectedColumns.value[index - 1] = !selectedColumns.value[index - 1]
form.value.columns = selectedColumns.value.map((item, index) => {
return item ? index + 1 : false
}).filter(item => item !== false)
formRef.value.validateField('columns')
}
</script>
<template>
<FtDialog visible title="添加溶液" width="40%" :ok-handle="okHandle" @cancel="cancel">
<el-form ref="formRef" :model="form" :rules="rules" label-width="auto">
<el-form-item label="选择试管" prop="columns">
<div class="tube-item">
<div
v-for="item in 5"
:key="item"
class="tube-line"
:class="{ 'tube-line-active': selectedColumns[item - 1] }"
@click.prevent="() => mousedownHandle(item)"
@touch.prevent="() => mousedownHandle(item)"
>
<span v-for="i in 8" :key="i" class="tube-line-inner" />
</div>
</div>
</el-form-item>
<el-form-item label="选择溶液" prop="solutionId">
<el-select v-model="form.containerId" placeholder="请选择溶液">
<el-option v-for="item in containerList.filter(i => i.type === 0)" :key="item.id" :label="solsList.find(s => s.id === item.solutionId)?.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="容量" prop="volume">
<el-input v-model="form.volume" type="number" placeholder="请输入容量">
<template #append>
mL
</template>
</el-input>
</el-form-item>
</el-form>
</FtDialog>
</template>
<style scoped lang="scss">
.el-tag {
margin-right: 5px;
}
.el-row {
height: 450px;
.el-col {
height: 100%;
overflow: auto;
:deep(.el-tag) {
width: 100%;
margin-bottom: 5px;
.el-tag__content {
display: flex;
width: 100%;
justify-content: space-between;
}
}
}
}
.tube-item {
padding: 5px;
background: #384D5D;
border-radius: 10px;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(1, 1fr);
grid-gap: 5px;
position: relative;
.tube-line {
display: flex;
flex-direction: column;
.tube-line-inner {
display: inline-block;
width: 25px;
height: 25px;
border-radius: 50%;
background: #fff;
margin: 2px;
transition: background 0.5s;
}
}
.tube-line-disable {
.tube-line-inner {
background: #C6C6C6;
}
}
.tube-line-active {
.tube-line-inner {
background: #26D574;
}
}
}
</style>

502
src/components/home/CheckCraft/index.vue

@ -0,0 +1,502 @@
<script setup lang="ts">
import { craftRemove, craftRestart } from 'apis/crafts'
import { ElMessageBox } from 'element-plus'
import { FtMessage } from 'libs/message'
import { allPropertiesDefined } from 'libs/utils'
import { cloneDeep } from 'lodash'
import { useHomeStore } from 'stores/homeStore'
import { computed, onMounted, ref } from 'vue'
const props = defineProps({
sourceData: {
type: Object,
default: () => ({
monitorId: 61, // id
heatId: 'heat_module_02', // id
craftsId: 3, // id
craftsName: '仅加热', //
currentStepIndex: 2, //
currentStepResult: '已经加热分钟已经加热分钟已经加热分钟已经加热分钟已经加热分钟已经加热分钟:2', //
steps: [
{
name: '预热',
method: 'preHeat',
params: {
temperature: 12,
description: '1.预热到12度',
},
},
{
name: '加稀硝酸',
method: 'addThin',
params: {
volume: 3,
description: '2.添加稀硝酸 3ml',
},
},
{
name: '加热',
method: 'heat',
params: {
temperature: 4,
time: 60,
description: '3.加热: 4度, 保持1分0秒',
minutes: 1,
},
},
{
name: '加浓硝酸',
method: 'addThick',
params: {
height: 6,
volume: 5,
description: '4.添加浓硝酸 5ml',
},
},
{
name: '清洗',
method: 'clean',
params: {
cycle: 8,
height: 7,
volume: 2,
description: '5.针头高度7mm, 加2ml水清洗8次',
},
},
{
name: '烘干',
method: 'dry',
params: {
temperature: 9,
time: 60,
description: '6.烘干: 9度, 保持1分0秒',
minutes: 1,
},
},
{
name: '退火',
method: 'anneal',
params: {
temperature: 11,
time: 420,
description: '7.退火: 11度, 保持7分0秒',
minutes: 7,
},
},
],
columns: [//
1,
5,
],
}),
},
})
const emits = defineEmits(['ok', 'close'])
const homeStore = useHomeStore()
onMounted(async () => {
form.value = {
craftId: props.sourceData.craftsId,
heatId: props.sourceData.heatId,
craftMonitorId: props.sourceData.monitorId,
modifyParam: {
},
columns: cloneDeep(props.sourceData.columns),
steps: cloneDeep(props.sourceData.steps),
}
selectedColumns.value = selectedColumns.value.map((item, index) => {
return props.sourceData.columns.includes(index + 1)
})
})
const form = ref<any>({})
const validateHandle = (rule: any, value: any, callback: any) => {
if (!value?.length) {
callback(new Error('请选择试管'))
}
else {
callback()
}
}
const rules = {
columns: [
{ required: true, message: '请选择试管', trigger: 'change', validator: validateHandle },
],
}
const hearInfo = computed(() => {
return homeStore.heatAreaList.find(item => item.value === props.sourceData.heatId)
})
const resumeCraftHandle = async () => {
try {
const valid = await formRef.value.validate()
if (!valid) {
return
}
//
const errorMsg: string[] = []
const invalidStepIndex = form.value.steps.findIndex((step: any, index: number) => {
if (['heat', 'dry', 'anneal'].includes(step.method)) {
if (step.params.minutes || step.params.seconds) {
step.params.time = (step.params.minutes || 0) * 60 + (step.params.seconds || 0) || undefined
}
else {
step.params.time = undefined
}
if (step.params.temperature > 400 && step.method === 'anneal') {
errorMsg.push(`步骤${index + 1}: 退火温度不能超过400度`)
}
if (step.params.temperature > 200 && step.method === 'heat') {
errorMsg.push(`步骤${index + 1}: 加热温度不能超过200度`)
}
if (step.params.temperature > 200 && step.method === 'dry') {
errorMsg.push(`步骤${index + 1}: 烘干温度不能超过200度`)
}
}
step.params.description = `${index + 1}.`
switch (step.method) {
case 'clean':
step.params.description += `针头高度${step.params.height}mm, 加${step.params.volume}ml水清洗${step.params.cycle}`
break
case 'addThin':
step.params.description += `添加稀硝酸 ${step.params.volume}mL`
break
case 'addThick':
step.params.description += `添加浓硝酸 ${step.params.volume}mL`
break
case 'reduceLiquid':
step.params.description += `针头高度${step.params.height}mm抽取液体`
break
case 'preHeat':
step.params.description += `预热到${step.params.temperature}`
break
case 'heat':
step.params.description += `加热: ${step.params.temperature}度, 保持${step.params.minutes || 0}${step.params.seconds || 0}`
break
case 'dry':
step.params.description += `烘干: ${step.params.temperature}度, 保持${step.params.minutes || 0}${step.params.seconds || 0}`
break
case 'anneal':
step.params.description += `退火: ${step.params.temperature}度, 保持${step.params.minutes || 0}${step.params.seconds || 0}`
break
}
return !allPropertiesDefined(step.params, ['minutes', 'seconds', 'description'])
})
if (invalidStepIndex !== -1) {
FtMessage.error(`步骤${invalidStepIndex + 1}: 请填写完整参数`)
return
}
if (errorMsg.length) {
FtMessage.error(errorMsg.join('; '))
return
}
form.value.modifyParam = form.value.steps?.[props.sourceData.currentStepIndex]?.params
await craftRestart(form.value)
FtMessage.success('恢复成功')
emits('ok')
}
catch (e) {
console.log(e)
}
}
const stopCraftHandle = async () => {
await ElMessageBox.confirm('确定删除当前工艺?', '消息', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
await craftRemove(props.sourceData.monitorId)
FtMessage.success('删除成功')
emits('ok')
}
const cancel = () => {
emits('close')
}
const stepMap = {
addThin: '加稀硝酸',
addThick: '加浓硝酸',
clean: '清洗',
preHeat: '预热',
heat: '加热',
dry: '烘干',
anneal: ' 退火',
}
const formRef = ref()
const selectVisible = ref(false)
const checkChange = () => {
selectedColumns.value = Array.from({ length: 5 }).fill(selectVisible.value)
form.value.columns = selectedColumns.value.map((item, index) => index + 1).filter(item => selectedColumns.value[item - 1])
formRef.value.validateField('columns')
}
const selectedColumns = ref(Array.from({ length: 5 }).fill(false))
const mousedownHandle = async (index: number) => {
selectedColumns.value[index - 1] = !selectedColumns.value[index - 1]
form.value.columns = selectedColumns.value.map((item, index) => {
return item ? index + 1 : false
}).filter(item => item !== false)
formRef.value.validateField('columns')
}
</script>
<template>
<FtDialog visible title="工艺异常" width="80%">
<el-form ref="formRef" :model="form" :rules="rules" label-width="auto" class="form-box" label-position="left">
<el-row>
<el-col :span="10">
<el-form-item label="加热区">
<el-tag>{{ hearInfo?.label }}</el-tag>
</el-form-item>
<el-form-item label="工艺名称">
<el-tooltip :content="sourceData.craftsName" placement="top" trigger="click">
<el-tag>{{ sourceData.craftsName }}</el-tag>
</el-tooltip>
</el-form-item>
<el-form-item label="当前步骤">
<el-tooltip :content="sourceData?.steps?.[sourceData?.currentStepIndex - 1]?.params?.description" placement="top" trigger="click">
<el-tag>{{ sourceData?.steps?.[sourceData?.currentStepIndex - 1]?.params?.description }}</el-tag>
</el-tooltip>
</el-form-item>
<el-form-item label="步骤结果">
<el-tooltip :content="sourceData?.currentStepResult" placement="top" trigger="click">
<el-tag>{{ sourceData?.currentStepResult }}</el-tag>
</el-tooltip>
</el-form-item>
<el-form-item label="选择试管" prop="columns">
<el-checkbox v-model="selectVisible" style="margin-right: 10px" @change="checkChange">
全选
</el-checkbox>
<div class="tube-item">
<div
v-for="item in 5"
:key="item"
class="tube-line"
:class="{ 'tube-line-active': selectedColumns[item - 1] }"
@click.prevent="() => mousedownHandle(item)"
@touch.prevent="() => mousedownHandle(item)"
>
<span v-for="i in 8" :key="i" class="tube-line-inner" />
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="14">
<div v-if="form.steps.length" class="step-box">
<div
v-for="(item, index) in form.steps"
:key="index"
class="step-item"
:class="{ 'step-item-success': sourceData?.currentStepIndex > index, 'step-item-ing': sourceData?.currentStepIndex === index }"
>
<el-form-item :label="sourceData?.currentStepIndex === index ? `${index + 1}: ${stepMap[item.method]}` : ''">
<template v-if="sourceData?.currentStepIndex !== index">
<span>{{ item.params.description }}</span>
</template>
<template v-else>
<template v-if="item.method === 'clean'">
<el-input v-model="item.params.height" type="number" size="small" placeholder="请输入高度">
<template #append>
mm
</template>
</el-input>
<el-input
v-model="item.params.volume"
style="width: 100px"
type="number"
size="small"
placeholder="请输入加水量"
>
<template #append>
mL
</template>
</el-input>
<el-input v-model="item.params.cycle" type="number" size="small" placeholder="请输入次数">
<template #append>
</template>
</el-input>
</template>
<template v-else-if="['addThin', 'addThick'].includes(item.method)">
<el-input v-model="item.params.volume" type="number" size="small" placeholder="请输入容量">
<template #append>
mL
</template>
</el-input>
<el-input v-if="item.method === 'addThick'" v-model="item.params.height" type="number" size="small" placeholder="请输入高度">
<template #append>
mm
</template>
</el-input>
</template>
<template v-else-if="item.method === 'reduceLiquid'">
<el-input v-model="item.params.height" type="number" size="small" placeholder="请输入高度">
<template #append>
mm
</template>
</el-input>
</template>
<template v-else-if="item.method === 'preHeat'">
<el-input
v-model="item.params.temperature"
type="number"
size="small"
placeholder="请输入温度"
>
<template #append>
</template>
</el-input>
</template>
<template v-else-if="['heat', 'dry', 'anneal'].includes(item.method)">
<el-input v-model="item.params.temperature" type="number" :max="item.method === 'anneal' ? 400 : 200" size="small" placeholder="加热温度">
<template #append>
</template>
</el-input>
<el-select
v-model="item.params.minutes"
style="width: 70px"
clearable
size="small"
placeholder="请选择"
>
<el-option v-for="i in 60" :key="i" :label="i" :value="i" />
</el-select>
<span class="unit-text"></span>
<el-select
v-model="item.params.seconds"
style="width: 70px"
clearable
size="small"
placeholder="请选择"
>
<el-option v-for="i in 60" :key="i" :label="i" :value="i" />
</el-select>
<span class="unit-text"></span>
</template>
</template>
</el-form-item>
</div>
</div>
</el-col>
</el-row>
</el-form>
<template #footer>
<ft-button type="primary" :click-handle="resumeCraftHandle">
恢复工艺
</ft-button>
<ft-button type="danger" :click-handle="stopCraftHandle">
删除工艺
</ft-button>
<ft-button @click="cancel">
关闭
</ft-button>
</template>
</FtDialog>
</template>
<style scoped lang="scss">
.step-item-success {
.el-form-item {
background: rgba(168,225,168,0.4) !important;
}
}
.step-item-ing {
.el-form-item {
background: rgba(25,137,250,0.4) !important;
}
}
.step-item {
.el-form-item {
background: rgba(82, 148, 215, 0.06);
padding: 5px;
margin-bottom: 10px;
:deep(.el-form-item__label) {
height: 25px;
line-height: 25px;
}
:deep(.el-form-item__content) {
width: 100%;
display: flex;
align-items: center;
position: relative;
.el-input,
.el-select {
width: 120px;
margin: 0 5px;
}
.info-box {
width: 80%;
height: 100%;
}
}
}
}
.unit-text {
font-size: 12px;
line-height: 25px;
}
.tube-item {
padding: 5px;
background: #384D5D;
border-radius: 10px;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(1, 1fr);
grid-gap: 5px;
position: relative;
.tube-line {
display: flex;
flex-direction: column;
.tube-line-inner {
display: inline-block;
width: 25px;
height: 25px;
border-radius: 50%;
background: #fff;
margin: 2px;
transition: background 0.5s;
}
}
.tube-line-disable {
.tube-line-inner {
background: #C6C6C6;
}
}
.tube-line-active {
.tube-line-inner {
background: #26D574;
}
}
}
.el-form-item {
:deep(.el-form-item__content) {
width: 100%;
display: flex;
align-items: center;
position: relative;
.el-tag {
max-width: 90%;
.el-tag__content {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
</style>

107
src/components/home/Countdown/Countdown.vue

@ -0,0 +1,107 @@
<script setup lang="ts">
import { useSystemStore } from 'stores/systemStore'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
const props = defineProps<{
current: number
startTime: number
duration: number //
}>()
const systemStore = useSystemStore()
const remaining = ref(0)
let timer: number | null = null
const calculateRemaining = () => {
//
const endTime = props.startTime + props.duration * 1000
// = - -
remaining.value = Math.max(0, endTime - new Date(systemStore.currentTime).getTime())
console.log('remaining', remaining.value)
}
const updateCountdown = () => {
if (remaining.value <= 0) {
stopTimer()
return
}
remaining.value -= 1000
}
const startTimer = () => {
updateCountdown() //
timer = window.setInterval(() => {
calculateRemaining()
updateCountdown()
}, 1000)
}
const stopTimer = () => {
if (timer) {
clearInterval(timer)
timer = null
}
}
onMounted(() => {
startTimer()
})
onBeforeUnmount(() => {
stopTimer()
})
//
const days = ref(0)
const hours = ref('00')
const minutes = ref('00')
const seconds = ref('00')
const visible = ref(true)
//
watch(
() => remaining.value,
(newVal) => {
if (newVal <= 0 || !newVal) {
visible.value = false
days.value = 0
hours.value = '00'
minutes.value = '00'
seconds.value = '00'
return
}
else {
visible.value = true
}
days.value = Math.floor(newVal / (1000 * 60 * 60 * 24))
const hoursNum = Math.floor((newVal % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
hours.value = hoursNum.toString().padStart(2, '0')
const minutesNum = Math.floor((newVal % (1000 * 60 * 60)) / (1000 * 60))
minutes.value = minutesNum.toString().padStart(2, '0')
const secondsNum = Math.floor((newVal % (1000 * 60)) / 1000)
seconds.value = secondsNum.toString().padStart(2, '0')
},
{ immediate: true },
)
</script>
<template>
<div v-if="visible" class="countdown">
<span v-if="days">{{ days }}</span>
{{ hours }}:{{ minutes }}:{{ seconds }}
</div>
</template>
<style scoped>
.countdown {
text-align: center;
font-size: 12px;
color: #FF4500;
}
</style>

190
src/components/home/ExecuteCraft/index.vue

@ -0,0 +1,190 @@
<script setup lang="ts">
import { getCraftList, startCraft } from 'apis/crafts'
import { FtMessage } from 'libs/message'
import { useHomeStore } from 'stores/homeStore'
import { onMounted, ref } from 'vue'
const emits = defineEmits(['ok', 'cancel'])
const homeStore = useHomeStore()
onMounted(() => {
getOres()
})
const form = ref<{
columns?: number[]
craftId?: number
heatId?: number
}>({})
const formRef = ref()
const validateHandle = (rule: any, value: any, callback: any) => {
if (!value?.length) {
callback(new Error('请选择试管'))
}
else {
callback()
}
}
const rules = {
columns: [
{ required: true, message: '请选择试管', trigger: 'change', validator: validateHandle },
],
craftId: [
{ required: true, message: '请选择工艺', trigger: 'change' },
],
}
const okHandle = async () => {
try {
const valid = await formRef.value.validate()
if (!valid) {
return
}
await startCraft({
heatId: useHomeStore().heatAreaList.find(item => item.selected)?.value,
...form.value,
})
FtMessage.success('工艺已开始')
emits('ok')
}
catch (error) {
console.log(error)
}
}
const cancel = () => {
emits('cancel')
}
//
// const tubes = computed(() => {
// return systemStore.systemStatus.trays?.find(item => item.heatModuleCode === homeStore.heatAreaList.find(item => item.selected)?.value)?.tubes
// })
const selectedColumns = ref(Array.from({ length: 5 }).fill(false))
const mousedownHandle = async (index: number) => {
// if (!tubes.value?.find(item => item.columnNum === index)?.exists) {
// FtMessage.error('')
// return
// }
selectedColumns.value[index - 1] = !selectedColumns.value[index - 1]
form.value.columns = selectedColumns.value.map((item, index) => {
return item ? index + 1 : false
}).filter(item => item !== false)
formRef.value.validateField('columns')
}
const craftList = ref<CraftTypes.Craft[]>([])
const getOres = async () => {
const res = await getCraftList()
craftList.value = res.list
}
const selectVisible = ref(false)
const checkChange = () => {
selectedColumns.value = Array.from({ length: 5 }).fill(selectVisible.value)
form.value.columns = selectedColumns.value.map((item, index) => index + 1).filter(item => selectedColumns.value[item - 1])
formRef.value.validateField('columns')
}
</script>
<template>
<FtDialog visible title="执行工艺" width="40%" :ok-handle="okHandle" @cancel="cancel">
<el-form ref="formRef" :model="form" :rules="rules" label-width="auto">
<el-form-item v-if=" homeStore.heatAreaList.filter(item => item.selected).length" label="加热区">
<el-tag v-for="item in homeStore.heatAreaList.filter(item => item.selected)" :key="item.value" class="mask-tag">
{{ item.label }}
</el-tag>
</el-form-item>
<el-form-item label="选择试管" prop="columns">
<el-checkbox v-model="selectVisible" style="margin-right: 10px" @change="checkChange">
全选
</el-checkbox>
<div class="tube-item">
<div
v-for="item in 5"
:key="item"
class="tube-line"
:class="{ 'tube-line-active': selectedColumns[item - 1] }"
@click.prevent="() => mousedownHandle(item)"
@touch.prevent="() => mousedownHandle(item)"
>
<span v-for="i in 8" :key="i" class="tube-line-inner" />
</div>
</div>
</el-form-item>
<el-form-item label="工艺" prop="craftId">
<el-select v-model="form.craftId" placeholder="请选择工艺">
<el-option
v-for="item in craftList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-form>
</FtDialog>
</template>
<style scoped lang="scss">
.el-tag {
margin-right: 5px;
}
.el-row {
height: 450px;
.el-col {
height: 100%;
overflow: auto;
:deep(.el-tag) {
width: 100%;
margin-bottom: 5px;
.el-tag__content {
display: flex;
width: 100%;
justify-content: space-between;
}
}
}
}
.tube-item {
padding: 5px;
background: #384D5D;
border-radius: 10px;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(1, 1fr);
grid-gap: 5px;
position: relative;
.tube-line {
display: flex;
flex-direction: column;
.tube-line-inner {
display: inline-block;
width: 25px;
height: 25px;
border-radius: 50%;
background: #fff;
margin: 2px;
transition: background 0.5s;
}
}
.tube-line-disable {
.tube-line-inner {
background: #C6C6C6;
}
}
.tube-line-active {
.tube-line-inner {
background: #26D574;
}
}
}
</style>

218
src/components/home/ExtractLiquid/index.vue

@ -0,0 +1,218 @@
<script setup lang="ts">
import { getSolsList } from 'apis/solution'
import { configList } from 'apis/system'
import { socket } from 'libs/socket'
import { useHomeStore } from 'stores/homeStore'
import { useSystemStore } from 'stores/systemStore'
import { computed, onMounted, onUnmounted, ref } from 'vue'
const emits = defineEmits(['ok', 'cancel'])
const homeStore = useHomeStore()
const systemStore = useSystemStore()
const configData = ref<any[]>([])
onMounted(async () => {
await getSols()
configData.value = await configList()
socket.init(receiveMessage, 'cmd_debug')
socket.init(receiveMessage, 'cmd_response')
})
onUnmounted(() => {
socket.unregisterCallback(receiveMessage, 'cmd_debug')
socket.unregisterCallback(receiveMessage, 'cmd_response')
})
const heightMax = computed(() => {
return configData.value.find(item => item.code === 'needle_drop_height')?.value || 0
})
let currentCommandId = ''
const receiveMessage = (data: Socket.cmdData) => {
data.commandId === currentCommandId && systemStore.pushSystemList(data)
}
const form = ref<{
columns?: number[]
height?: number
}>({})
const formRef = ref()
const validateHandle = (rule: any, value: any, callback: any) => {
if (!value?.length) {
callback(new Error('请选择试管'))
}
else {
callback()
}
}
const validateHandle1 = (rule: any, value: any, callback: any) => {
if (!value) {
callback(new Error('请输入高度'))
}
else
if (value && (value < 0 || +value > +heightMax.value)) {
callback(new Error(`针头高度范围0-${heightMax.value}mm`))
}
else {
callback()
}
}
const rules = {
columns: [
{ required: true, message: '请选择试管', trigger: 'change', validator: validateHandle },
],
height: [
{ required: true, trigger: 'blur', validator: validateHandle1 },
],
}
const okHandle = async () => {
try {
const valid = await formRef.value.validate()
if (!valid) {
return
}
currentCommandId = Date.now().toString()
const params = {
commandId: currentCommandId,
command: 'liquid_reduce',
params: form.value,
}
await homeStore.sendControl(params)
emits('ok')
}
catch (error) {
console.log(error)
}
}
const cancel = () => {
emits('cancel')
}
const solsList = ref<Solution.SolutionItem[]>([])
const getSols = async () => {
const res = await getSolsList()
solsList.value = res.list
}
// const tubes = computed(() => {
// const tray = systemStore.systemStatus.trays?.find(item => item.inSolutionPositon)
// return tray?.tubes || []
// })
const selectedColumns = ref(Array.from({ length: 5 }).fill(false))
const mousedownHandle = async (index: number) => {
// if (!tubes.value.find(item => item.columnNum === index)?.exists) {
// FtMessage.error('')
// return
// }
selectedColumns.value[index - 1] = !selectedColumns.value[index - 1]
form.value.columns = selectedColumns.value.map((item, index) => {
return item ? index + 1 : false
}).filter(item => item !== false)
formRef.value.validateField('columns')
}
</script>
<template>
<FtDialog visible title="抽取溶液" width="40%" :ok-handle="okHandle" @cancel="cancel">
<el-form ref="formRef" :model="form" :rules="rules" label-width="auto">
<el-form-item label="选择试管" prop="columns">
<div class="tube-item">
<!-- <div -->
<!-- v-for="item in 5" -->
<!-- :key="item" -->
<!-- class="tube-line" -->
<!-- :class="{ 'tube-line-active': selectedColumns[item - 1], 'tube-line-disable': !tubes.find(tu => tu.columnNum === item)?.exists }" -->
<!-- @click.prevent="() => mousedownHandle(item)" -->
<!-- @touch.prevent="() => mousedownHandle(item)" -->
<!-- > -->
<!-- <span v-for="i in 8" :key="i" class="tube-line-inner" /> -->
<!-- </div> -->
<div
v-for="item in 5"
:key="item"
class="tube-line"
:class="{ 'tube-line-active': selectedColumns[item - 1] }"
@click.prevent="() => mousedownHandle(item)"
@touch.prevent="() => mousedownHandle(item)"
>
<span v-for="i in 8" :key="i" class="tube-line-inner" />
</div>
</div>
</el-form-item>
<el-form-item label="针头下降高度" prop="height">
<el-input v-model="form.height" type="number" placeholder="请输入高度">
<template #append>
mm
</template>
</el-input>
</el-form-item>
</el-form>
</FtDialog>
</template>
<style scoped lang="scss">
.el-tag {
margin-right: 5px;
}
.el-row {
height: 450px;
.el-col {
height: 100%;
overflow: auto;
:deep(.el-tag) {
width: 100%;
margin-bottom: 5px;
.el-tag__content {
display: flex;
width: 100%;
justify-content: space-between;
}
}
}
}
.tube-item {
padding: 5px;
background: #384D5D;
border-radius: 10px;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(1, 1fr);
grid-gap: 5px;
position: relative;
.tube-line {
display: flex;
flex-direction: column;
.tube-line-inner {
display: inline-block;
width: 25px;
height: 25px;
border-radius: 50%;
background: #fff;
margin: 2px;
transition: background 0.5s;
}
}
.tube-line-disable {
.tube-line-inner {
background: #C6C6C6;
}
}
.tube-line-active {
.tube-line-inner {
background: #26D574;
}
}
}
</style>

117
src/components/home/FillSolution/index.vue

@ -0,0 +1,117 @@
<script setup lang="ts">
import { getContainerList } from 'apis/container'
import { getSolsList } from 'apis/solution'
import { FtMessage } from 'libs/message'
import { useHomeStore } from 'stores/homeStore'
import { useSolutionStore } from 'stores/useSolutionStore'
import { onMounted, ref } from 'vue'
const emits = defineEmits(['ok', 'cancel'])
const homeStore = useHomeStore()
let currentCommandId = ''
onMounted(() => {
queryContainerList()
querySolutionList()
})
const containerList = ref<Container.ContainerItem[]>([])
const queryContainerList = async () => {
const res = await getContainerList()
containerList.value = res.filter(item => item.type === 0)
}
const cancel = async () => {
currentCommandId = Date.now().toString()
const params = {
commandId: currentCommandId,
command: 'liquid_motor_origin',
params: {},
}
await homeStore.sendControl(params)
emits('cancel')
}
const solutionList = ref<Solution.SolutionItem[]>([])
const solutionMap = ref<Record<string | number, string>>({})
const solutionStore = useSolutionStore()
const querySolutionList = async () => {
const res = await getSolsList()
if (res && res.list) {
solutionList.value = res.list
solutionList.value.forEach((item) => {
if (item.id) {
solutionMap.value[item.id] = item.name
}
})
solutionStore.updateSolution(res.list)
}
}
const filled_solution_start = async () => {
if (!checked.value) {
FtMessage.warning('请选择要预充的溶液')
return
}
currentCommandId = Date.now().toString()
const params = {
commandId: currentCommandId,
command: 'liquid_pre_fill_start',
params: {
solutionId: checked.value,
},
}
await homeStore.sendControl(params)
}
const filled_solution_stop = async () => {
currentCommandId = Date.now().toString()
const params = {
commandId: currentCommandId,
command: 'liquid_pre_fill_stop',
params: {},
}
await homeStore.sendControl(params)
}
const checked = ref<number>()
</script>
<template>
<FtDialog visible title="预充管路" width="50%">
<el-radio-group v-model="checked">
<el-radio v-for="item in containerList" :key="item.id" border :label="solutionMap[item.solutionId]" :value="item.id" />
</el-radio-group>
<template #footer>
<ft-button type="primary" :click-handle="filled_solution_start">
开始预充
</ft-button>
<ft-button type="primary" :click-handle="filled_solution_stop">
停止预充
</ft-button>
<ft-button :click-handle="cancel">
完成预充
</ft-button>
</template>
</FtDialog>
</template>
<style scoped lang="scss">
.item-box {
display: flex;
align-items: center;
justify-content: space-between;
padding: 5px;
}
.el-checkbox {
margin: 10px 5px;
min-width: 100px;
}
.button-box {
margin-top: 10px;
display: flex;
justify-content: center;
}
</style>

64
src/components/home/Liquid/index.vue

@ -0,0 +1,64 @@
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps({
data: {
type: Object,
default: () => {
return {}
},
},
})
const solutionStyle = computed(() => {
const difference = (props.data.capacityTotal - props.data.capacityUsed) / props.data.capacityTotal
console.log(difference)
const process = 100 - difference * 100
console.log(process)
const filter = difference > 0.4 ? 'hue-rotate(270deg) saturate(100) brightness(81%)' : difference > 0.1 ? 'hue-rotate(150deg) saturate(100)' : 'hue-rotate(120deg) saturate(100)'
return {
'filter': filter,
'-webkit-mask': `linear-gradient(to bottom, transparent ${process}%, #EEEFF8 ${process + 0.01}%)`,
}
})
const percentage = computed(() => Number(((props.data.capacityTotal - props.data.capacityUsed) / props.data.capacityTotal).toFixed(2)) * 100)
</script>
<template>
<div class="liquid-content">
<div class="bottle_base">
<img class="content-img" src="@/assets/images/liquied/liquied_bottle.svg" alt="chemical-bottle">
</div>
<div class="bottle" :style="solutionStyle">
<img class="content-img" src="@/assets/images/liquied/liquied_bottle.svg" alt="chemical-bottle">
</div>
<div class="num" :style="{ color: percentage > 40 ? '#fff' : percentage > 10 ? '#FF8E00' : '#FF1C00' }">
{{ percentage }} %
</div>
</div>
</template>
<style scoped lang="scss">
.liquid-content{
position: relative;
display: flex;
justify-content: center;
}
.bottle_base{
position: relative;
}
.bottle {
position: absolute;
}
.content-img{
height: 100px;
margin: 10px;
}
.num {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-weight: 700;
}
</style>

131
src/components/home/SetTemperature/index.vue

@ -0,0 +1,131 @@
<script setup lang="ts">
import { setTargetTemperature } from 'apis/home'
import { configList } from 'apis/system'
import { FtMessage } from 'libs/message'
import { useSystemStore } from 'stores/systemStore'
import { computed, inject, onMounted, ref } from 'vue'
const emits = defineEmits(['ok', 'cancel'])
const data = inject('currentTemperatureData')
const configData = ref<any[]>([])
onMounted(async () => {
configData.value = await configList()
})
const heatMax = computed(() => {
return configData.value.find(item => item.code === 'heat_temperature')?.value || 0
})
const dryMax = computed(() => {
return configData.value.find(item => item.code === 'dry_temperature')?.value || 0
})
const annealMax = computed(() => {
return configData.value.find(item => item.code === 'anneal_temperature')?.value || 0
})
const heatModule = computed(() => {
return useSystemStore().systemStatus.heatModule.find(item => item.moduleCode === data.value.id)
})
const form = ref({
dryTemperature: heatModule.value?.dryTemperature,
annealTemperature: heatModule.value?.annealTemperature,
heatTemperature: heatModule.value?.heatTemperature,
})
const formRef = ref()
const validateHandle1 = (rule: any, value: any, callback: any) => {
if (value && (value < 0 || +value > +heatMax.value)) {
callback(new Error(`加热温度设置范围0℃-${heatMax.value}`))
}
else {
callback()
}
}
const validateHandle2 = (rule: any, value: any, callback: any) => {
if (value && (value < 0 || +value > +dryMax.value)) {
callback(new Error(`烘干温度设置范围0℃-${dryMax.value}`))
}
else {
callback()
}
}
const validateHandle3 = (rule: any, value: any, callback: any) => {
if (value && (value < 0 || +value > +annealMax.value)) {
callback(new Error(`退火温度设置范围0℃-${annealMax.value}`))
}
else {
callback()
}
}
const rules = {
heatTemperature: [
{ required: false, trigger: 'blur', validator: validateHandle1 },
],
dryTemperature: [
{ required: false, trigger: 'blur', validator: validateHandle2 },
],
annealTemperature: [
{ required: false, trigger: 'blur', validator: validateHandle3 },
],
}
const okHandle = async () => {
try {
const valid = await formRef.value.validate()
if (!valid) {
return
}
await setTargetTemperature({
...form.value,
moduleCode: data.value.id,
})
FtMessage.success('设置成功')
emits('ok')
}
catch (error) {
console.log(error)
}
}
const cancel = () => {
emits('cancel')
}
</script>
<template>
<FtDialog visible title="设置目标温度" width="40%" :ok-handle="okHandle" @cancel="cancel">
<el-form ref="formRef" label-width="auto" :model="form" :rules="rules">
<el-form-item label="加热区">
<el-tag>{{ data?.label }}</el-tag>
</el-form-item>
<el-form-item label="加热温度" prop="heatTemperature">
<el-input v-model="form.heatTemperature" type="number" placeholder="请输入加热温度">
<template #append>
</template>
</el-input>
</el-form-item>
<el-form-item label="烘干温度" prop="dryTemperature">
<el-input v-model="form.dryTemperature" type="number" placeholder="请输入烘干温度">
<template #append>
</template>
</el-input>
</el-form-item>
<el-form-item label="退火温度" prop="annealTemperature">
<el-input v-model="form.annealTemperature" type="number" placeholder="请输入退火温度">
<template #append>
</template>
</el-input>
</el-form-item>
</el-form>
</FtDialog>
</template>
<style scoped lang="scss">
.el-tag {
margin-right: 5px;
}
</style>

201
src/components/home/StartClean/index.vue

@ -0,0 +1,201 @@
<script setup lang="ts">
import { configList } from 'apis/system'
import { useHomeStore } from 'stores/homeStore'
import { computed, onMounted, ref } from 'vue'
const emits = defineEmits(['ok', 'cancel'])
const configData = ref<any[]>([])
onMounted(async () => {
configData.value = await configList()
})
const heightMax = computed(() => {
return configData.value.find(item => item.code === 'needle_drop_height')?.value || 0
})
const homeStore = useHomeStore()
const form = ref({
cycle: undefined,
columns: [],
height: undefined,
volume: undefined,
})
const formRef = ref()
const validateHandle = (rule: any, value: any, callback: any) => {
if (!value?.length) {
callback(new Error('请选择试管'))
}
else {
callback()
}
}
const validateHandle1 = (rule: any, value: any, callback: any) => {
if (!value) {
callback(new Error('请输入高度'))
}
else
if (value && (value < 0 || +value > +heightMax.value)) {
callback(new Error(`针头高度范围0-${heightMax.value}mm`))
}
else {
callback()
}
}
const rules = {
columns: [
{ required: true, message: '请选择试管', trigger: 'change', validator: validateHandle },
],
cycle: [
{ required: true, trigger: 'blur', message: '请输入清洗次数' },
],
height: [
{ required: true, trigger: 'blur', validator: validateHandle1 },
],
volume: [
{ required: true, trigger: 'blur', message: '请输入加水量' },
],
}
const okHandle = async () => {
try {
const valid = await formRef.value.validate()
if (!valid) {
return
}
await commandHandle('clean_start', form.value)
emits('ok')
}
catch (error) {
console.log(error)
}
}
const cancel = () => {
emits('cancel')
}
let currentCommandId = ''
const commandHandle = async (command: string, params?: unknown) => {
currentCommandId = Date.now().toString()
const data = {
commandId: currentCommandId,
command,
params,
}
await homeStore.sendControl(data)
}
const selectedColumns = ref(Array.from({ length: 5 }).fill(false))
const mousedownHandle = async (index: number) => {
// if (!tubes.value.find(item => item.columnNum === index)?.exists) {
// FtMessage.error('')
// return
// }
selectedColumns.value[index - 1] = !selectedColumns.value[index - 1]
form.value.columns = selectedColumns.value.map((item, index) => {
return item ? index + 1 : false
}).filter(item => item !== false)
formRef.value.validateField('columns')
}
</script>
<template>
<FtDialog visible title="设置清洗次数" width="40%" :ok-handle="okHandle" @cancel="cancel">
<el-form ref="formRef" label-width="auto" :model="form" :rules="rules">
<el-form-item label="选择试管" prop="columns">
<div class="tube-item">
<div
v-for="item in 5"
:key="item"
class="tube-line"
:class="{ 'tube-line-active': selectedColumns[item - 1] }"
@click.prevent="() => mousedownHandle(item)"
@touch.prevent="() => mousedownHandle(item)"
>
<span v-for="i in 8" :key="i" class="tube-line-inner" />
</div>
</div>
</el-form-item>
<el-form-item label="清洗次数" prop="cycle">
<el-select
v-model="form.cycle"
filterable
allow-create
default-first-option
placeholder="请选择或输入次数"
style="width: 100%"
>
<el-option
v-for="item in 3"
:key="item"
:label="`${item}次`"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="针头高度" prop="height">
<el-input v-model="form.height" type="number" placeholder="请输入针头高度">
<template #append>
mm
</template>
</el-input>
</el-form-item>
<el-form-item label="加水量" prop="volume">
<el-input v-model="form.volume" type="number" placeholder="请输入加水量">
<template #append>
mL
</template>
</el-input>
</el-form-item>
</el-form>
</FtDialog>
</template>
<style scoped lang="scss">
.el-tag {
margin-right: 5px;
}
.tube-item {
padding: 5px;
background: #384D5D;
border-radius: 10px;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(1, 1fr);
grid-gap: 5px;
position: relative;
.tube-line {
display: flex;
flex-direction: column;
.tube-line-inner {
display: inline-block;
width: 25px;
height: 25px;
border-radius: 50%;
background: #fff;
margin: 2px;
transition: background 0.5s;
}
}
.tube-line-disable {
.tube-line-inner {
background: #C6C6C6;
}
}
.tube-line-active {
.tube-line-inner {
background: #26D574;
}
}
}
</style>

50
src/components/home/StartExperiment/index.vue

@ -0,0 +1,50 @@
<script setup lang="ts">
import { addTask } from 'apis/home'
import { FtMessage } from 'libs/message'
import { ref } from 'vue'
const emits = defineEmits(['ok', 'cancel'])
const form = ref({
name: '',
})
const formRef = ref()
const rules = {
name: [
{ required: true, message: '请输入记录名称', trigger: 'blur' },
],
}
const okHandle = async () => {
try {
const valid = await formRef.value.validate()
if (!valid) {
return
}
await addTask(form.value)
FtMessage.success('已开始记录')
emits('ok')
}
catch (error) {
console.log(error)
}
}
const cancel = () => {
emits('cancel')
}
</script>
<template>
<FtDialog visible title="开始新记录" width="30%" :ok-handle="okHandle" @cancel="cancel">
<el-form ref="formRef" label-width="auto" :model="form" :rules="rules">
<el-form-item label="记录名称" prop="name">
<el-input v-model="form.name" placeholder="" />
</el-form-item>
</el-form>
</FtDialog>
</template>
<style scoped lang="scss">
</style>

396
src/components/home/Tube/index.vue

@ -0,0 +1,396 @@
<script setup lang="ts">
import { craftList, pauseCraft, resumeCraft, stopCraft } from 'apis/crafts'
import { trayTube } from 'apis/home'
import errorIcon from 'assets/images/error.svg'
import ingIcon from 'assets/images/ing.svg'
import jaw_icon from 'assets/images/jaw.svg'
import successIcon from 'assets/images/success.svg'
import waitIcon from 'assets/images/wait.svg'
import CHeckCraft from 'components/home/CheckCraft/index.vue'
import CountDown from 'components/home/Countdown/Countdown.vue'
import { useHomeStore } from 'stores/homeStore'
import { useSystemStore } from 'stores/systemStore'
import { computed, ref } from 'vue'
const props = withDefaults(defineProps<{ data?: System.HeatArea }>(), {
data: () => ({
moduleCode: 'heat_module_01',
enable: true,
trayStatus: true,
heatingType: 'stop',
fanOpen: true,
dryTemperature: 0,
annealTemperature: 0,
heatTemperature: 0,
targetTemperature: 0,
temperature: 0,
}),
})
const emits = defineEmits(['selectChange', 'setTemperature', 'openCraft'])
const homeStore = useHomeStore()
const systemStore = useSystemStore()
const mousedownHandle = async (index: number) => {
await trayTube({
trayUuid: tray.value?.uuid,
tubes: [
{
columnNum: index,
exists: !tray.value?.tubes.find(t => t.columnNum === index)?.exists,
},
],
})
}
const activeTubeBox = ref(false)
const tubeSelect = () => {
emits('selectChange')
}
const hearInfo = computed(() => {
return homeStore.heatAreaList.find(item => item.value === props.data.moduleCode)
})
console.log(hearInfo.value)
const craft = computed(() => {
return systemStore.systemStatus.trays?.find(item => item.heatModuleCode === props.data.moduleCode)?.crafts
})
const tray = computed(() => {
return systemStore.systemStatus.trays?.find(item => item.heatModuleCode === props.data.moduleCode)
})
const craftSteps = computed(() => {
const steps = systemStore.systemStatus.trays?.find(item => item.heatModuleCode === props.data.moduleCode)?.crafts?.craft?.steps
return steps ? JSON.parse(steps) : undefined
})
const errCraft = computed(() => {
return systemStore.errCraftList.find(item => item.heatId === props.data.moduleCode)
})
console.log(errCraft.value)
const setTemperature = () => {
emits('setTemperature', props.data.moduleCode)
}
const pauseCraftHandle = async () => {
await pauseCraft({
heatId: props.data.moduleCode,
})
}
const resumeCraftHandle = async () => {
await resumeCraft({
heatId: props.data.moduleCode,
})
}
const stopCraftHandle = async () => {
await stopCraft({
heatId: props.data.moduleCode,
})
}
const craftVisible = ref(false)
const openCraft = () => {
craftVisible.value = true
}
const checkCraft = async () => {
const res = await craftList()
systemStore.errorCraft = res && res.length > 0
systemStore.errCraftList = res
}
defineExpose({
activeTubeBox,
})
</script>
<template>
<div class="tube" :class="{ 'tube-active': hearInfo?.selected, 'tube-shadow': data.trayStatus }">
<div class="header">
<span>{{ hearInfo?.label }}</span>
<img v-if="tray?.useArm" :src="jaw_icon" alt="">
<el-tag v-show="!data.trayStatus" type="info">
空置
</el-tag>
<el-tag v-show="data.trayStatus" type="success">
已放置
</el-tag>
</div>
<div
class="tube-item" :class="{
'tube-item-anneal': ['annealing'].includes(data.heatingType),
'tube-item-dry': ['drying'].includes(data.heatingType),
'tube-item-heat': ['heating', 'constant'].includes(data.heatingType),
'tube-item-fan': data.fanOpen }"
>
<div v-if="!data.trayStatus" class="tube-disable" />
<div
v-if="craft?.state"
class="status" :class="{
'status-success': false,
'status-wait': craft?.state === 'READY',
'status-PAUSED': craft?.state === 'PAUSED',
'status-error': craft?.state === 'ERROR',
'status-ing': craft?.state === 'RUNNING',
}"
>
<img v-if="craft?.state === 'FINISHED'" :src="successIcon" alt="">
<img v-if="craft?.state === 'RUNNING'" :src="ingIcon" alt="">
<img v-if="craft?.state === 'READY'" :src="waitIcon" alt="">
<img v-if="craft?.state === 'PAUSED'" :src="waitIcon" alt="">
<img v-if="craft?.state === 'ERROR'" :src="errorIcon" alt="">
<span class="status-name">{{ craft?.craft?.name || ' ' }}</span>
<span v-if="craft?.state === 'RUNNING'" class="status-text">工艺执行中</span>
<span v-if="craft?.state === 'READY'" class="status-text">工艺等待执行</span>
<span v-if="craft?.state === 'PAUSED'" class="status-text">工艺已暂停</span>
<span v-if="craft?.state === 'ERROR'" class="status-text">工艺执行错误</span>
<span v-if="craft?.state === 'FINISHED'" class="status-text">工艺执行成功</span>
<el-tooltip v-if="craft?.state === 'RUNNING' && craftSteps && craftSteps[craft.currentIndex || 0]?.params?.description" :content="`${craftSteps[craft.currentIndex || 0].params.description}`" placement="top" trigger="click">
<div class="status-description">
{{ craftSteps[craft.currentIndex || 0].params.description }}
</div>
</el-tooltip>
<div class="status-operation">
<ft-button v-if="craft?.state === 'RUNNING'" type="primary" size="small" :click-handle="pauseCraftHandle">
暂停
</ft-button>
<ft-button v-if="craft?.state === 'PAUSED'" type="primary" size="small" :click-handle="resumeCraftHandle">
继续
</ft-button>
<ft-button v-if="['RUNNING', 'PAUSED'].includes(craft?.state)" size="small" :click-handle="stopCraftHandle">
停止
</ft-button>
</div>
</div>
<div
v-for="item in 5"
:key="item"
class="tube-line"
:class="{ 'tube-line-active': tray?.tubes.find(tu => tu.columnNum === item)?.exists }"
@click.prevent="() => mousedownHandle(item)"
@touch.prevent="() => mousedownHandle(item)"
>
<span v-for="i in 8" :key="i" class="tube-line-inner" />
</div>
</div>
<div class="temperature-box">
<span>
<span>当前温度: </span>
<span>{{ data.temperature || '--' }}</span>
<span></span>
</span>
<span v-show="data.fanOpen" style="color: #6CD3FF;font-weight: bold">降温中</span>
<span v-show="data.heatingType === 'heating'" style="color: #e00d0d;font-weight: bold ">加热中</span>
<span v-show="data.heatingType === 'constant'" style="color: #FE0A0A;font-weight: bold ">恒温中</span>
<span v-show="data.heatingType === 'drying'" style="color: #F2652D;font-weight: bold ">烘干中</span>
<span v-show="data.heatingType === 'annealing'" style="color: red;font-weight: bold ">退火中</span>
<!-- <span v-show="data.heatingType === 'finish'" style="color: #FE0A0A;font-weight: bold ">加热完成</span> -->
<!-- <span v-show="data.heatingType === 'stop'" style="color: #606266;font-weight: bold ">停止加热</span> -->
</div>
<CountDown v-if="data.startHeatTime && data.targetTime" :current="new Date().getTime()" :start-time="data.startHeatTime" :duration="data.targetTime" />
<div class="footer">
<div class="tem-box" @click="setTemperature">
<span :class="{ 'active-footer': hearInfo?.selected }"> {{ data.targetTemperature || '--' }}</span>
<el-icon><Setting /></el-icon>
</div>
<el-icon v-if="errCraft" color="#DF1515" size="30" @click="openCraft">
<WarningFilled />
</el-icon>
<ft-button size="small" :type="hearInfo?.selected ? 'primary' : 'default'" @click="tubeSelect">
选择
</ft-button>
</div>
<CHeckCraft v-if="craftVisible" :source-data="errCraft" @close="craftVisible = false" @ok="craftVisible = false;checkCraft()" />
</div>
</template>
<style scoped lang="scss">
.tube-active {
border-color: #275EFB !important;
}
.tube-shadow {
box-shadow: 0 0 10px rgba(0,0,0,0.9);
}
.tube {
box-sizing: border-box;
width: 100%;
height: 95%;
background: #E9F3FF;
border-radius: 10px;
padding: 10px;
font-size: 14px;
display: flex;
flex-direction: column;
justify-content: space-between;
border: 2px solid #E9F3FF;
transition: all 0.3s;
position: relative;
overflow: hidden;
.header {
display: flex;
justify-content: space-between;
align-items: center;
color: #4D6882;
img {
width: 20px;
}
}
.tube-item-heat {
background: #e00d0d !important;
}
.tube-item-fan {
background: #6CD3FF !important;
}
.tube-item-dry {
background: #F2652D !important;
}
.tube-item-anneal {
background: red !important;
}
.tube-item {
padding: 5px;
background: #384D5D;
border-radius: 10px;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(1, 1fr);
grid-gap: 5px;
position: relative;
.tube-disable {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgba(255,255,255,0.9);
border-radius: 9px;
}
.tube-line {
display: flex;
flex-direction: column;
.tube-line-inner {
display: inline-block;
width: 25px;
height: 25px;
border-radius: 50%;
background: #fff;
margin: 2px;
transition: background 0.5s;
}
}
.tube-line-active {
.tube-line-inner {
background: #26D574;
}
}
}
.temperature-box {
display: flex;
justify-content: space-between;
background: #fff;
padding: 5px;
border-radius: 5px;
font-size: 12px;
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
color: #4D6882;
.active-footer {
color: #1562B7;
}
.ft-button {
margin-right: 0;
}
}
}
.status {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255,255,255,0.9);
z-index: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 10px;
img {
width: 30px;
margin: 10px 0;
}
.status-name {
font-size: 14px;
}
.status-text {
font-size: 16px;
font-weight: 700;
}
.status-operation {
width: 100%;
margin-top: auto;
margin-bottom: 10px;
display: flex;
justify-content: space-around;
.ft-button {
margin-right: 5px;
}
}
}
.status-wait {
background: rgba(242,235,231, 0.9);
border: 1px solid #EE8223;
color: #EE8223;
}
.status-PAUSED {
background: rgba(242,235,231, 0.9);
border: 1px solid #EE8223;
color: #EE8223;
}
.status-error {
background: rgba(232,212,222, 0.9);
border: 1px solid #DF1515;
color: #DF1515;
}
.status-ing {
background: rgba(205,223,255, 0.9);
border: 1px solid #0256FF;
color: #0256FF;
}
.tem-box {
display: flex;
align-items: center;
.el-icon {
margin-left: 5px;
}
}
.status-description {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 95%;
padding: 0 3px;
display: inline-block;
background: #fff;
border-radius: 2px;
margin-top: auto;
}
</style>

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

Loading…
Cancel
Save