From c7d1dff0d0a47a4142560bfb9b41620114b242aa Mon Sep 17 00:00:00 2001 From: kerms <kerms@niazo.org> Date: Mon, 1 Apr 2024 15:40:39 +0800 Subject: [PATCH] feat(wifi,ws) view page --- .env.development | 1 + .env.production | 1 + .gitignore | 5 + auto-imports.d.ts | 9 - components.d.ts | 20 -- env.d.ts | 2 + index.html | 2 +- package.json | 4 + src/App.vue | 57 ++-- src/api/apiWifi.ts | 29 +- src/api/index.ts | 39 +++ src/assets/icon/favicon.svg | 45 +++ src/assets/icon/view-hide.svg | 1 + src/assets/icon/view.svg | 1 + src/assets/icon/wifi-exclamation.svg | 3 + src/assets/navigation.css | 3 + src/assets/page.css | 16 + src/assets/toggle_skewed.css | 135 ++++++++ src/components/passwordViewer.vue | 25 ++ src/components/radioSkewed.vue | 30 ++ src/composables/broadcastChannelDef.ts | 39 --- src/composables/buildMode.ts | 3 + src/composables/importFavicon.ts | 20 ++ src/composables/logConsoleMsg.ts | 22 ++ src/composables/notification.ts | 22 ++ src/composables/websocket/websocketService.ts | 32 +- src/composables/websocket/websocketWrapper.ts | 73 ++-- src/composables/websocket/ws.sharedworker.ts | 11 +- src/i18n.ts | 4 +- src/layouts/textLayout.vue | 13 + src/locales/index.ts | 14 + src/locales/zh.ts | 25 +- src/main.ts | 7 +- src/router/index.ts | 63 ++-- src/router/msgRouter.ts | 41 +++ src/stores/websocket.ts | 3 +- src/utils/emitter.ts | 5 + src/views/About.vue | 54 ++- src/views/Feedback.vue | 20 ++ src/views/Home.vue | 16 +- src/views/Uart.vue | 9 + src/views/Wifi.vue | 322 ++++++++++++++---- src/views/navigation/NavBar.vue | 124 +++---- vite.config.ts | 71 +++- 44 files changed, 1098 insertions(+), 343 deletions(-) create mode 100644 .env.development create mode 100644 .env.production delete mode 100644 auto-imports.d.ts delete mode 100644 components.d.ts create mode 100755 src/assets/icon/favicon.svg create mode 100644 src/assets/icon/view-hide.svg create mode 100644 src/assets/icon/view.svg create mode 100644 src/assets/icon/wifi-exclamation.svg create mode 100644 src/assets/navigation.css create mode 100644 src/assets/page.css create mode 100644 src/assets/toggle_skewed.css create mode 100644 src/components/passwordViewer.vue create mode 100644 src/components/radioSkewed.vue create mode 100644 src/composables/buildMode.ts create mode 100644 src/composables/importFavicon.ts create mode 100644 src/composables/logConsoleMsg.ts create mode 100644 src/composables/notification.ts create mode 100644 src/layouts/textLayout.vue create mode 100644 src/locales/index.ts create mode 100644 src/router/msgRouter.ts create mode 100644 src/utils/emitter.ts create mode 100644 src/views/Feedback.vue create mode 100644 src/views/Uart.vue diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..304ac25 --- /dev/null +++ b/.env.development @@ -0,0 +1 @@ +VITE_APP_MODE=dev \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..1714480 --- /dev/null +++ b/.env.production @@ -0,0 +1 @@ +VITE_APP_MODE=prod \ No newline at end of file diff --git a/.gitignore b/.gitignore index a3842d4..a8fbe0c 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,8 @@ coverage *.tsbuildinfo package-lock.json +components.d.ts +auto-imports.d.ts + +# Personal +**/_priv_* \ No newline at end of file diff --git a/auto-imports.d.ts b/auto-imports.d.ts deleted file mode 100644 index 1d89ee8..0000000 --- a/auto-imports.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* eslint-disable */ -/* prettier-ignore */ -// @ts-nocheck -// noinspection JSUnusedGlobalSymbols -// Generated by unplugin-auto-import -export {} -declare global { - -} diff --git a/components.d.ts b/components.d.ts deleted file mode 100644 index 1a27d34..0000000 --- a/components.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* eslint-disable */ -/* prettier-ignore */ -// @ts-nocheck -// Generated by unplugin-vue-components -// Read more: https://github.com/vuejs/core/pull/3399 -export {} - -declare module 'vue' { - export interface GlobalComponents { - ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete'] - ElButton: typeof import('element-plus/es')['ElButton'] - ElDrawer: typeof import('element-plus/es')['ElDrawer'] - ElForm: typeof import('element-plus/es')['ElForm'] - ElFormItem: typeof import('element-plus/es')['ElFormItem'] - ElInput: typeof import('element-plus/es')['ElInput'] - InlineSvg: typeof import('./src/components/InlineSvg.vue')['default'] - RouterLink: typeof import('vue-router')['RouterLink'] - RouterView: typeof import('vue-router')['RouterView'] - } -} diff --git a/env.d.ts b/env.d.ts index 11f02fe..0fdf4a4 100644 --- a/env.d.ts +++ b/env.d.ts @@ -1 +1,3 @@ /// <reference types="vite/client" /> +declare const __APP_VERSION__: string; // defined in vite.config.ts, imported from package.json.version +declare const __BUILD_TIME__: string; \ No newline at end of file diff --git a/index.html b/index.html index a888544..ddb979e 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ <html lang="en"> <head> <meta charset="UTF-8"> - <link rel="icon" href="/favicon.ico"> + <link id="favicon" rel="icon" href="data:,"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vite App</title> </head> diff --git a/package.json b/package.json index 47c463e..a481d8e 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "build": "run-p type-check \"build-only {@}\" --", "preview": "vite preview", "build-only": "vite build", + "build:dev": "vite build --mode development", "type-check": "vue-tsc --build --force", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", "format": "prettier --write src/" @@ -40,6 +41,9 @@ "unplugin-auto-import": "^0.17.5", "unplugin-vue-components": "^0.26.0", "vite": "^5.1.6", + "vite-plugin-css-injected-by-js": "^3.5.0", + "vite-plugin-html": "^3.2.2", + "vite-plugin-singlefile": "^2.0.1", "vite-svg-loader": "^5.1.0", "vue-tsc": "^2.0.6" } diff --git a/src/App.vue b/src/App.vue index 1189d23..5044a79 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,64 +1,63 @@ <script setup lang="ts"> import {useWsStore} from "@/stores/websocket"; -import type {ControlMsg, ServerMsg} from "@/composables/broadcastChannelDef"; import type {IWebsocketService} from "@/composables/websocket/websocketService"; -import {ControlEvent, ControlMsgType} from "@/composables/broadcastChannelDef"; import {getWebsocketService} from "@/composables/websocket/websocketService"; import {onMounted, onUnmounted} from "vue"; +import {changeFavicon} from "@/composables/importFavicon"; +import {logHelloMessage} from "@/composables/logConsoleMsg"; +import NavBar from "@/views/navigation/NavBar.vue"; +import type {ControlMsg, ServerMsg} from "@/api"; +import {ControlEvent, ControlMsgType} from "@/api"; +import {routeCtrlMsg, routeModuleServerMsg} from "@/router/msgRouter"; +import {globalNotify} from "@/composables/notification"; +import {isDevMode} from "@/composables/buildMode"; const wsState = useWsStore(); const onClientCtrl = (msg: ControlMsg) => { - console.log("App.vue:", msg); + if (isDevMode()) { + console.log("App.vue:", msg); + } if (msg.type === ControlMsgType.WS_EVENT) { wsState.$patch({state: msg.data as ControlEvent}) + routeCtrlMsg(msg); + if (msg.data === ControlEvent.CONNECTED) { + globalNotify("调试器已连接", "success"); + } } }; const onServerMsg = (msg: ServerMsg) => { - console.log("App.vue:", msg); + if (isDevMode()) { + console.log("App.vue:", msg); + } + routeModuleServerMsg(msg); }; let websocketService: IWebsocketService; onMounted(() => { - // const host = window.location - console.log("App.vue mounted") - const host = "192.168.43.61"; + logHelloMessage(); + + let host = ""; + if (isDevMode()) { + host = "192.168.43.61"; + } else { + host = window.location.host + } websocketService = getWebsocketService(); websocketService.init(host, onServerMsg, onClientCtrl); + changeFavicon(); }); onUnmounted(() => { }); - -import NavBar from "@/views/navigation/NavBar.vue"; </script> <template> <header> <nav-bar/> </header> - - <p class="m-0">This is body</p> <RouterView/> - <p>end</p> - <!-- <p>{{ test }}</p>--> - <el-button type="danger"><p>test</p></el-button> - </template> - -<style> -.app { - background-color: #ddd; - box-shadow: 0 0 10px; - border-radius: 10px; - padding: 20px; -} - -.router-active { - background-color: #666; - cursor: default; -} -</style> diff --git a/src/api/apiWifi.ts b/src/api/apiWifi.ts index 9ef8e61..a07ebf5 100644 --- a/src/api/apiWifi.ts +++ b/src/api/apiWifi.ts @@ -1,15 +1,13 @@ -import {sendJsonMsg} from '@/composables/broadcastChannelDef' +import {type ApiJsonMsg, sendJsonMsg} from '@/api' - -import {type ApiJsonMsg} from '@/api' - -const WifiModuleID = 1; -enum WifiCmd { +export const WifiModuleID = 1; +export enum WifiCmd { UNKNOWN = 0, - WIFI_API_JSON_GET_AP_INFO, + WIFI_API_JSON_STA_GET_AP_INFO, WIFI_API_JSON_CONNECT, WIFI_API_JSON_GET_SCAN, WIFI_API_JSON_DISCONNECT, + WIFI_API_JSON_AP_GET_INFO, } interface WifiMsgOut extends ApiJsonMsg { @@ -25,10 +23,18 @@ export function wifi_get_scan_list() { sendJsonMsg(msg); } -export function wifi_get_ap_info() { +export function wifi_sta_get_ap_info() { const msg : WifiMsgOut = { module: WifiModuleID, - cmd: WifiCmd.WIFI_API_JSON_GET_AP_INFO, + cmd: WifiCmd.WIFI_API_JSON_STA_GET_AP_INFO, + } + sendJsonMsg(msg); +} + +export function wifi_ap_get_info() { + const msg : WifiMsgOut = { + module: WifiModuleID, + cmd: WifiCmd.WIFI_API_JSON_AP_GET_INFO, } sendJsonMsg(msg); } @@ -43,10 +49,13 @@ export function wifi_connect_to(ssid: string, password: string) { sendJsonMsg(msg); } -export interface WifiInfo { +export interface WifiInfo extends ApiJsonMsg { rssi: number; ssid: string; + gateway: string; + ip: string; mac: string; + netmask: string; wifiLogo?: string; } diff --git a/src/api/index.ts b/src/api/index.ts index 582c7df..faf7ad1 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,4 +1,43 @@ +import {getWebsocketService} from "@/composables/websocket/websocketService"; + export interface ApiJsonMsg { module: number; cmd: number; +} + +export enum ControlMsgType { + WS_EVENT = "WS_EVENT", + WS_SET_HOST = "WS_SET_HOST", + WS_GET_STATE = "WS_GET_STATE", +} + +export enum ControlEvent { + DISCONNECTED = "DISCONNECTED", + LOADED = "LOADED", + CONNECTED = "CONNECTED", + CONNECTING = "CONNECTING", + BROKEN = "BROKEN", +} + +export interface ControlMsg { + type: ControlMsgType, + data: ControlEvent | string, +} + +export interface ServerMsg { + type: "json" | "binary" + data: ApiJsonMsg | object; +} + +export function sendJsonMsg(apiJsonMsg: ApiJsonMsg) { + const msg: ServerMsg = { + type: "json", + data: apiJsonMsg, + }; + getWebsocketService().send(msg); + // toServer.postMessage(msg); +} + +export function sendBinMsg(msg: ApiJsonMsg) { + // toServer.postMessage(JSON.stringify(msg)); } \ No newline at end of file diff --git a/src/assets/icon/favicon.svg b/src/assets/icon/favicon.svg new file mode 100755 index 0000000..0144e92 --- /dev/null +++ b/src/assets/icon/favicon.svg @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 170 170"> +<style type="text/css"> + .st0{fill:#FEAD04;} + .st1{fill:#032E3E;} + .st2{fill:#FFFFFF;} + .st3{fill:#FF0135;} + .st4{fill:#FA7E0C;} +</style> +<path class="st0" d="M20.1,59.7h29.7V35.5H21.6c-0.8,0-1.3,0.2-1.8,0.7L9.5,45.4c-3,2.9-2.6,5.1,0.5,7.5l8.7,6.4 + C19.1,59.6,19.6,59.7,20.1,59.7L20.1,59.7z"/> +<path class="st1" d="M27.1,50.5h23.4v-7H27.1c-2,0-3.6,1.6-3.6,3.5S25.1,50.5,27.1,50.5L27.1,50.5z"/> +<path class="st1" d="M79.7,168c31.7,0.7,58.5-13.8,80.2-54.7c2.7-5.5-2.1-9.9-11-9.9c-13.7,0.1-35.1-17.6-36.6-26.4 + c-17.5,6.1-26.8,14-40.1,14c-11.1,0-15-0.8-22.3-4.9c-1.9,0.6-3.9,1-6.2,0.9c-8.3-0.3-22.9-7.6-34.4-24.1c-3.8-5.5-5.9-2.6-6.9,3.5 + C-6.4,116.8,29.1,166.3,79.7,168L79.7,168z"/> +<path class="st2" d="M76.4,91.7c-11.1,0-19.5-4.5-23.6-7.1V36.3C57.5,28.1,67.7,23,79.5,23h0.6c9.6,0.1,28,8.6,29.3,28.4v24.5 + c-3.3,4-12.8,13.8-28.1,15.6C79.7,91.7,78,91.7,76.4,91.7z"/> +<path class="st1" d="M79.5,26c0.2,0,0.4,0,0.6,0c4,0.1,10.5,2,15.9,6.1c6.4,4.8,9.9,11.3,10.4,19.4v23.3 + c-3.6,4.1-12.1,12.1-25.4,13.7c-1.5,0.2-3,0.3-4.5,0.3c-9.2,0-16.5-3.3-20.6-5.8V37.1C60.2,30.2,69.1,26,79.5,26L79.5,26 M79.5,20 + c-13.7,0-24.7,6.2-29.7,15.5v50.7c2.5,1.8,12.5,8.6,26.6,8.6c1.7,0,3.4-0.1,5.2-0.3c18.6-2.2,28.9-15.1,30.7-17.5 + c0-8.5,0-17.1,0-25.6C111,29,90.5,20.1,80.2,20C79.9,20,79.7,20,79.5,20L79.5,20z M49.8,86.2L49.8,86.2L49.8,86.2z M49.8,86.2 + L49.8,86.2L49.8,86.2L49.8,86.2z"/> +<circle class="st1" cx="77.6" cy="47.9" r="7.5"/> +<path class="st2" d="M112.4,76.9c-37.2-15.2-45.8-17.1-62.6,9.3c-10.3,16.3-10.5,31.2-6.4,45c3.3,11,11.6,24.1,27.9,27 + c0.3,0.1-6.7-13.9,1.6-35.5C78.4,108.6,92.3,88.1,112.4,76.9L112.4,76.9z"/> +<path class="st3" d="M78.4,167.9c0.4,0,0.9,0,1.3,0.1c31.7,0.7,58.5-13.8,80.2-54.7c2.7-5.5-2.1-9.9-11-9.9 + c-11.3,0.1-28-12.1-34.2-21.3c-5.4,2.9-10.9,6.4-16,10.8c-11,9.3-20.9,20.3-24.9,34.6c-2.7,10-2.7,21.8,1.8,36.1L78.4,167.9 + L78.4,167.9z"/> +<path class="st0" d="M156.7,43.3l-22.1,71.4c-0.2,0.7,0.2,1.9,0.9,1.8l6.5-0.9c0.8-0.1,1.5-0.2,1.8-0.9l25.1-64.5 + c0.3-0.7-0.2-1.5-0.9-1.8l-3.6-1.5l-5.8-4.5C157.9,41.9,156.9,42.6,156.7,43.3L156.7,43.3z"/> +<path class="st4" d="M143.6,114.8c0-0.1,0.1-0.1,0.1-0.2l25.1-64.5c0.3-0.7-0.2-1.5-0.9-1.8l-3-1.2l-24.5,66.5L143.6,114.8 + L143.6,114.8z"/> +<path class="st0" d="M139.3,46.2L126,117.5c-0.1,0.8,0.4,1.5,1.1,1.6l8.6,1.6c0.8,0.1,1.5-0.4,1.6-1.1l13.3-71.3 + c0.1-0.8-0.4-1.5-1.1-1.6l-8.6-1.6C140.2,44.9,139.4,45.4,139.3,46.2z"/> +<path class="st0" d="M121,45.3l1.6,76.8c0,0.8,0.7,1.4,1.4,1.4l8.8-0.2c0.8,0,1.4-0.7,1.4-1.4l-1.6-76.8c0-0.8-0.7-1.4-1.4-1.4 + l-8.8,0.2C121.6,43.9,121,44.6,121,45.3z"/> +<path class="st4" d="M132.9,123.3c0.8,0,1.4-0.7,1.4-1.4L133,58.5l-1,4.2L132.9,123.3z"/> +<path class="st4" d="M138,111.1c0.8,0.1,1.5-0.4,1.6-1.1l11.1-62.4l-1.8,3.9L138,111.1z"/> +<path class="st1" d="M163.3,70.5c-14.3,0.1-41.6,2.9-64.6,22.4c-11,9.3-20.9,20.3-24.9,34.6c-2.7,10-2.7,21.8,1.8,36.1l-9.4-1.8 + c-3.8-14.1-3.5-26.1-0.7-36.5C70,108.9,81,96.7,93.1,86.4c27.1-22.9,59.5-24.7,73.6-24.4L163.3,70.5L163.3,70.5z"/> +<path class="st1" d="M79.7,168c22.2,0.5,42.1-6.5,59.5-24.9c-1.5-1.6-3.8-4.1-4.5-4.5c-0.9-0.5-2.8-1.9-6.6,2.1 + c-17.8,19.3-51.4,18.1-51.4,18.1c-1.1,0.1-2.2-0.1-3.2,0.1c-5.5,1.2-8.5,4.1-9.5,7C69.1,167,74.3,167.8,79.7,168L79.7,168z"/> +<path class="st1" d="M94.2,132.9c-2.3-0.4-3.8-2.6-3.4-4.9c0.4-2.3,2.6-3.8,4.9-3.4c0.1,0,22.6,4.4,39.9-13.4c1.6-1.7,4.3-1.7,6-0.1 + s1.7,4.3,0.1,6C121.1,138.1,94.2,132.9,94.2,132.9z"/> +</svg> diff --git a/src/assets/icon/view-hide.svg b/src/assets/icon/view-hide.svg new file mode 100644 index 0000000..e4a56c7 --- /dev/null +++ b/src/assets/icon/view-hide.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="currentColor" d="M876.8 156.8c0-9.6-3.2-16-9.6-22.4-6.4-6.4-12.8-9.6-22.4-9.6-9.6 0-16 3.2-22.4 9.6L736 220.8c-64-32-137.6-51.2-224-60.8-160 16-288 73.6-377.6 176C44.8 438.4 0 496 0 512s48 73.6 134.4 176c22.4 25.6 44.8 48 73.6 67.2l-86.4 89.6c-6.4 6.4-9.6 12.8-9.6 22.4 0 9.6 3.2 16 9.6 22.4 6.4 6.4 12.8 9.6 22.4 9.6 9.6 0 16-3.2 22.4-9.6l704-710.4c3.2-6.4 6.4-12.8 6.4-22.4Zm-646.4 528c-76.8-70.4-128-128-153.6-172.8 28.8-48 80-105.6 153.6-172.8C304 272 400 230.4 512 224c64 3.2 124.8 19.2 176 44.8l-54.4 54.4C598.4 300.8 560 288 512 288c-64 0-115.2 22.4-160 64s-64 96-64 160c0 48 12.8 89.6 35.2 124.8L256 707.2c-9.6-6.4-19.2-16-25.6-22.4Zm140.8-96c-12.8-22.4-19.2-48-19.2-76.8 0-44.8 16-83.2 48-112 32-28.8 67.2-48 112-48 28.8 0 54.4 6.4 73.6 19.2zM889.599 336c-12.8-16-28.8-28.8-41.6-41.6l-48 48c73.6 67.2 124.8 124.8 150.4 169.6-28.8 48-80 105.6-153.6 172.8-73.6 67.2-172.8 108.8-284.8 115.2-51.2-3.2-99.2-12.8-140.8-28.8l-48 48c57.6 22.4 118.4 38.4 188.8 44.8 160-16 288-73.6 377.6-176C979.199 585.6 1024 528 1024 512s-48.001-73.6-134.401-176Z"></path><path fill="currentColor" d="M511.998 672c-12.8 0-25.6-3.2-38.4-6.4l-51.2 51.2c28.8 12.8 57.6 19.2 89.6 19.2 64 0 115.2-22.4 160-64 41.6-41.6 64-96 64-160 0-32-6.4-64-19.2-89.6l-51.2 51.2c3.2 12.8 6.4 25.6 6.4 38.4 0 44.8-16 83.2-48 112-32 28.8-67.2 48-112 48Z"></path></svg> \ No newline at end of file diff --git a/src/assets/icon/view.svg b/src/assets/icon/view.svg new file mode 100644 index 0000000..5c6e2da --- /dev/null +++ b/src/assets/icon/view.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="currentColor" d="M512 160c320 0 512 352 512 352S832 864 512 864 0 512 0 512s192-352 512-352m0 64c-225.28 0-384.128 208.064-436.8 288 52.608 79.872 211.456 288 436.8 288 225.28 0 384.128-208.064 436.8-288-52.608-79.872-211.456-288-436.8-288zm0 64a224 224 0 1 1 0 448 224 224 0 0 1 0-448m0 64a160.192 160.192 0 0 0-160 160c0 88.192 71.744 160 160 160s160-71.808 160-160-71.744-160-160-160"></path></svg> \ No newline at end of file diff --git a/src/assets/icon/wifi-exclamation.svg b/src/assets/icon/wifi-exclamation.svg new file mode 100644 index 0000000..3f55d61 --- /dev/null +++ b/src/assets/icon/wifi-exclamation.svg @@ -0,0 +1,3 @@ +<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path d="M12 19.4998H12.01M2 8.81929C3.69692 7.30051 5.74166 6.16236 8 5.53906M5 12.8584C5.86251 12.0129 6.87754 11.3223 8 10.8319M16 5.53906C18.2583 6.16236 20.3031 7.30051 22 8.81929M16 10.8319C17.1225 11.3223 18.1375 12.0129 19 12.8584M12 4.5V15.4998" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> \ No newline at end of file diff --git a/src/assets/navigation.css b/src/assets/navigation.css new file mode 100644 index 0000000..6d4563b --- /dev/null +++ b/src/assets/navigation.css @@ -0,0 +1,3 @@ +.todo-menu-item { + @apply opacity-30 +} \ No newline at end of file diff --git a/src/assets/page.css b/src/assets/page.css new file mode 100644 index 0000000..68acde3 --- /dev/null +++ b/src/assets/page.css @@ -0,0 +1,16 @@ +.text-layout { + @apply m-auto max-w-2xl min-w-min px-2 +} + +.page-title { + @apply text-center my-6 text-4xl font-bold tracking-tight md:text-5xl lg:text-6xl +} + +.router-link { + @apply text-sm text-gray-500 hover:text-gray-500 +} + +.router-link-active { + @apply text-blue-600 font-bold; + cursor: default; +} diff --git a/src/assets/toggle_skewed.css b/src/assets/toggle_skewed.css new file mode 100644 index 0000000..016683e --- /dev/null +++ b/src/assets/toggle_skewed.css @@ -0,0 +1,135 @@ +.tgl { + display: none; +} +.tgl, .tgl:after, .tgl:before, .tgl *, .tgl *:after, .tgl *:before, .tgl + .tgl-btn { + box-sizing: border-box; +} + +.tgl + .tgl-btn { + border-bottom: 2px ridge; + display: block; + width: 4em; + height: 2em; + position: relative; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.tgl + .tgl-btn:after, .tgl + .tgl-btn:before { + position: relative; + display: block; + content: ""; + width: 50%; + height: 100%; +} +.tgl + .tgl-btn:after { + left: 0; +} +.tgl + .tgl-btn:before { + display: none; +} +.tgl:checked + .tgl-btn:after { + left: 50%; +} + +/** + * Skewed switch + */ +.tgl-skewed + .tgl-btn { + overflow: hidden; + backface-visibility: hidden; + transition: all 0.2s ease; + font-family: sans-serif; + background: #888; + border-radius: 4px; +} +.tgl-skewed + .tgl-btn:after, .tgl-skewed + .tgl-btn:before { + display: inline-block; + transition: all 0.1s ease; + width: 100%; + text-align: center; + position: absolute; + line-height: 2em; + font-weight: bold; + color: #fff; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.4); +} +.tgl-skewed + .tgl-btn:after { + left: 100%; + content: attr(data-tg-on); +} +.tgl-skewed + .tgl-btn:before { + left: 0; + content: attr(data-tg-off); +} +.tgl-skewed + .tgl-btn:active { + background: #888; +} +.tgl-skewed + .tgl-btn:active:before { + left: -10%; +} +.tgl-skewed:checked + .tgl-btn { + background: #86d993; +} +.tgl-skewed:checked + .tgl-btn:before { + left: -100%; +} +.tgl-skewed:checked + .tgl-btn:after { + left: 0; +} +.tgl-skewed:checked + .tgl-btn:active:after { + left: 10%; +} + +.tgl-skewed:disabled + .tgl-btn { + opacity: 0.4; + cursor: not-allowed; + border: none; +} + +/* FLIP */ +.tgl-flip + .tgl-btn { + padding: 2px; + transition: all 0.2s ease; + font-family: sans-serif; + perspective: 100px; +} +.tgl-flip + .tgl-btn:after, .tgl-flip + .tgl-btn:before { + display: inline-block; + transition: all 0.4s ease; + width: 100%; + text-align: center; + position: absolute; + line-height: 2em; + font-weight: bold; + color: #fff; + top: 0; + left: 0; + backface-visibility: hidden; + border-radius: 4px; +} +.tgl-flip + .tgl-btn:after { + content: attr(data-tg-on); + background: #02C66F; + transform: rotateY(-180deg); +} +.tgl-flip + .tgl-btn:before { + background: #FF3A19; + content: attr(data-tg-off); +} +.tgl-flip + .tgl-btn:active:before { + transform: rotateY(-20deg); +} +.tgl-flip:checked + .tgl-btn:before { + transform: rotateY(180deg); +} +.tgl-flip:checked + .tgl-btn:after { + transform: rotateY(0); + left: 0; + background: #7FC6A6; +} +.tgl-flip:checked + .tgl-btn:active:after { + transform: rotateY(20deg); +} diff --git a/src/components/passwordViewer.vue b/src/components/passwordViewer.vue new file mode 100644 index 0000000..440c877 --- /dev/null +++ b/src/components/passwordViewer.vue @@ -0,0 +1,25 @@ +<template> + <div class="flex justify-between"> + <p v-if="isPasswordVisible">{{ password }}</p> + <p v-else>••••••••</p> + <span @click="togglePasswordVisibility" class="flex items-center"> + <InlineSvg v-show="isPasswordVisible" name="view" width="16"></InlineSvg> + <InlineSvg v-show="!isPasswordVisible" name="view-hide" width="16"></InlineSvg> + </span> + </div> +</template> + +<script setup lang="ts"> +import { ref } from 'vue'; +import InlineSvg from "@/components/InlineSvg.vue"; + +const isPasswordVisible = ref(false); + +defineProps<{ + password: string +}>() + +const togglePasswordVisibility = () => { + isPasswordVisible.value = !isPasswordVisible.value; +}; +</script> \ No newline at end of file diff --git a/src/components/radioSkewed.vue b/src/components/radioSkewed.vue new file mode 100644 index 0000000..fbe23a9 --- /dev/null +++ b/src/components/radioSkewed.vue @@ -0,0 +1,30 @@ +<script setup lang="ts"> +import {ref} from "vue"; + +const isChecked = ref(false); + +const props = defineProps({ + id: { + type: String, + required: true, + }, + class: { + type: String, + required: false, + } +}); + +const clickEmit = defineEmits(['click']); + +const handleClick = () => { + clickEmit('click', props.id, !isChecked.value); +}; + +</script> + +<template> + <div :class="props.class"> + <input :id="props.id" type="checkbox" class="tgl tgl-skewed" v-model="isChecked" @click="handleClick"/> + <label data-tg-off="OFF" data-tg-on="ON" :for="props.id" class="tgl-btn"></label> + </div> +</template> diff --git a/src/composables/broadcastChannelDef.ts b/src/composables/broadcastChannelDef.ts index 2f90e6e..48cc7af 100644 --- a/src/composables/broadcastChannelDef.ts +++ b/src/composables/broadcastChannelDef.ts @@ -1,43 +1,4 @@ -import {type ApiJsonMsg} from "@/api" -import {getWebsocketService} from "@/composables/websocket/websocketService"; - export const toServer = new BroadcastChannel("toServer"); export const toClient = new BroadcastChannel("toClient"); export const toWebsocketCtrl = new BroadcastChannel("toWebsocketCtrl"); export const toClientCtrl = new BroadcastChannel("toClientCtrl"); - -export enum ControlMsgType { - WS_EVENT = "WS_EVENT", - WS_SET_HOST = "WS_SET_HOST", - WS_GET_STATE = "WS_GET_STATE", -} - -export enum ControlEvent { - DISCONNECTED = "DISCONNECTED", - LOADED = "LOADED", - CONNECTED = "CONNECTED", - CONNECTING = "CONNECTING", -} - -export interface ControlMsg { - type: ControlMsgType, - data: ControlEvent | string, -} - -export interface ServerMsg { - type: "json" | "binary" - data: object -} - -export function sendJsonMsg(apiJsonMsg: ApiJsonMsg) { - const msg: ServerMsg = { - type: "json", - data: apiJsonMsg, - }; - getWebsocketService().send(msg); - // toServer.postMessage(msg); -} - -export function sendBinMsg(msg: ApiJsonMsg) { - // toServer.postMessage(JSON.stringify(msg)); -} diff --git a/src/composables/buildMode.ts b/src/composables/buildMode.ts new file mode 100644 index 0000000..840a858 --- /dev/null +++ b/src/composables/buildMode.ts @@ -0,0 +1,3 @@ +export function isDevMode() { + return import.meta.env.VITE_APP_MODE === 'dev'; +} \ No newline at end of file diff --git a/src/composables/importFavicon.ts b/src/composables/importFavicon.ts new file mode 100644 index 0000000..d59ab4e --- /dev/null +++ b/src/composables/importFavicon.ts @@ -0,0 +1,20 @@ +import {h, render} from "vue"; +import MYSVG from "@/assets/icon/favicon.svg"; + +export function changeFavicon() { + const SVGComponent = MYSVG; + const container = document.createElement('div'); + render(h(SVGComponent), container); + + const svgElement = container.innerHTML; + const svgEncoded = encodeURIComponent(svgElement) + .replace(/'/g, '%27') + .replace(/"/g, '%22'); + + const link = document.getElementById('favicon'); + if (link instanceof HTMLLinkElement) { + link.href = `data:image/svg+xml,${svgEncoded}`; + } +} + + diff --git a/src/composables/logConsoleMsg.ts b/src/composables/logConsoleMsg.ts new file mode 100644 index 0000000..fd61683 --- /dev/null +++ b/src/composables/logConsoleMsg.ts @@ -0,0 +1,22 @@ +export function logHelloMessage() { + console.log( + " ███████\n" + + " █▒ ██\n" + + " ▒▒▒▒▒▒▒▒█ ██ ██ ▒\n" + + " ▒▒▒▒▒▒▒▒▒█ ██░ ░█ ▒▒▒ ▒▒ ▒▒▒\n" + + " █ ██ ░█ ▒▒▒▒▒▒▒▒▒\n" + + "███ ██ ░█ ██████▓▓█\n" + + "█████ █▒ ░███▒▒▒▒▒▒▒▒\n" + + "█████████░ ▓██▓▓▓▓▒▒▒▒▒▒\n" + + "████████▒ ▒██▓▓▓▓▓▓▒▒▒▒▒\n" + + " ███████ ██▓▓▓▓▓▓▓▓▒▒▒▒▓▓▓\n" + + " ███████ ██▓▓▓▓▓▓▓▓▓▒██▓▓▓▓\n" + + " ██████ ░█▓▓▓▓███████▓▓▓▓▓\n" + + " █████ ██▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n" + + " █████ ██▓▓▓▓▓▓▓▓▓▓███\n" + + " ████▒█▓▓▓▓▓█████\n" + + " ██████████\n" + + "\n" + + "Logo是什么意义?答:意义就是...没有意义。\n" + + "大概是一起去整点赛博薯条吧。-来自法国鸽子"); +} diff --git a/src/composables/notification.ts b/src/composables/notification.ts new file mode 100644 index 0000000..0ffe27a --- /dev/null +++ b/src/composables/notification.ts @@ -0,0 +1,22 @@ +import {ElMessage, ElNotification} from "element-plus"; + +type NotificationType = 'error' | 'warning' | 'info' | 'success' ; + +export function globalNotify(msg: string, type: NotificationType) { + ElMessage({ + message: msg, + grouping: true, + type: type, + duration: 2000, + showClose: true, + offset: 50, + }) +} + +export function globalNotifyRightSide(msg: string, type: NotificationType) { + ElNotification({ + message: msg, + type: type, + duration: 1500, + }) +} diff --git a/src/composables/websocket/websocketService.ts b/src/composables/websocket/websocketService.ts index c81de5e..910f7bc 100644 --- a/src/composables/websocket/websocketService.ts +++ b/src/composables/websocket/websocketService.ts @@ -1,9 +1,9 @@ -import MyWorker from '@/composables/websocket/ws.sharedworker?sharedworker&inline' +import MyWorker from '@/composables/websocket/ws.sharedworker?sharedworker' import {WebsocketWrapper} from "@/composables/websocket/websocketWrapper"; -import type {ControlMsg, ServerMsg} from "@/composables/broadcastChannelDef"; -import {ControlMsgType, toClient, toClientCtrl} from "@/composables/broadcastChannelDef"; - -const toServer = new BroadcastChannel("toServer"); +import {toClient, toClientCtrl, toServer} from "@/composables/broadcastChannelDef"; +import type {ControlMsg, ServerMsg} from "@/api"; +import {ControlEvent, ControlMsgType} from "@/api"; +import {isDevMode} from "@/composables/buildMode"; export interface IWebsocketService { init(host: string, @@ -29,13 +29,18 @@ class WebsocketShared implements IWebsocketService{ public static getInstance(): IWebsocketService { if (!WebsocketShared.instance) { + if (isDevMode()) { + console.log("New Shared Worker"); + } WebsocketShared.instance = new WebsocketShared(); } return WebsocketShared.instance; } private constructor() { - console.log("Shared Websocket init") + if (isDevMode()) { + console.log("Shared Websocket init"); + } this.msgCallback = () => {} this.ctrlCallback = () => {} this.messageEventProxy = this.messageEventProxy.bind(this); @@ -44,14 +49,15 @@ class WebsocketShared implements IWebsocketService{ } init(host: string, msgCallback: (msg: ServerMsg) => any, ctrlCallback: (msg: ControlMsg) => any): void { - console.log("webworker init") + if (isDevMode()) { + console.log("webworker init"); + } toClient.removeEventListener("message", this.messageEventProxy); toClientCtrl.removeEventListener("message", this.controlEventProxy); this.msgCallback = msgCallback; this.ctrlCallback = ctrlCallback; toClient.addEventListener("message", this.messageEventProxy); toClientCtrl.addEventListener("message", this.controlEventProxy); - // this.worker.port.onmessage = this.controlEventProxy; this.worker.port.postMessage({type: ControlMsgType.WS_SET_HOST, data: host} as ControlMsg) } @@ -60,9 +66,11 @@ class WebsocketShared implements IWebsocketService{ toClientCtrl.removeEventListener("message", this.controlEventProxy); } + reload(): void { + // this.worker.terminate(); + } send(msg: ServerMsg): void { - console.log("Websocket Service send (not really)", msg) toServer.postMessage(msg); } @@ -71,6 +79,7 @@ class WebsocketShared implements IWebsocketService{ } controlEventProxy(ev: MessageEvent<ControlMsg>) { + this.ctrlCallback(ev.data); } } @@ -93,8 +102,9 @@ class WebsocketClassic implements IWebsocketService{ } init(host: string, msgCallback: (ev: ServerMsg) => any, ctrlCallback: (msg: ControlMsg) => any): void { - console.log("Websocket Service INIT called", WebsocketClassic.count); - + if (isDevMode()) { + console.log("Websocket Service INIT called", WebsocketClassic.count); + } this.socket.init(host, msgCallback, ctrlCallback); } diff --git a/src/composables/websocket/websocketWrapper.ts b/src/composables/websocket/websocketWrapper.ts index c7613b0..9f1a771 100644 --- a/src/composables/websocket/websocketWrapper.ts +++ b/src/composables/websocket/websocketWrapper.ts @@ -1,5 +1,7 @@ -import type {ServerMsg, ControlMsg} from "@/composables/broadcastChannelDef"; -import {ControlEvent, ControlMsgType} from "@/composables/broadcastChannelDef"; + +import type {ApiJsonMsg, ControlMsg, ServerMsg} from "@/api"; +import {ControlEvent, ControlMsgType} from "@/api"; +import {isDevMode} from "@/composables/buildMode"; interface IWebsocket { @@ -21,7 +23,7 @@ class WebsocketDummy implements IWebsocket { } class OneTimeWebsocket implements IWebsocket { - private readonly heartBeatTimeout: number = 2; + private readonly heartBeatTimeout: number = 1; private readonly host: string; private readonly intervalId: number; private readonly msgCallback: (ev: ServerMsg) => any; @@ -30,6 +32,8 @@ class OneTimeWebsocket implements IWebsocket { private socket: WebSocket; private heartBeatTimeCount: number; private stoped: boolean; + private cleared: boolean; + private hasBeenConnected: boolean; constructor(host: string, msgCallback: (ev: ServerMsg) => any, @@ -38,6 +42,8 @@ class OneTimeWebsocket implements IWebsocket { ) { this.host = host; this.stoped = false; + this.cleared = false; + this.hasBeenConnected = false; this.msgCallback = msgCallback; this.ctrlCallback = ctrlCallback; this.closeCallback = closeCallback; @@ -51,8 +57,13 @@ class OneTimeWebsocket implements IWebsocket { if (this.heartBeatTimeCount > this.heartBeatTimeout) { /* did not receive packet "heartBeatTimeout" times, * connection may be lost: close the socket */ - this.socket.close(); - console.log("interval: ", this.heartBeatTimeCount, "state: ", this.socket.readyState); + if (this.socket.readyState === this.socket.OPEN) { + this.close(); + this.clear(); + } + if (isDevMode()) { + console.log("interval: ", this.heartBeatTimeCount, "state: ", this.socket.readyState); + } } this.heartBeatTimeCount++; @@ -72,48 +83,47 @@ class OneTimeWebsocket implements IWebsocket { data: {}, type: "json", } - this.msgCallback(msg); if (typeof ev.data === "string") { try { - msg.data = JSON.parse(ev.data); - this.msgCallback(msg); + msg.data = JSON.parse(ev.data) as ApiJsonMsg; + if ((msg.data as ApiJsonMsg).cmd === undefined || + (msg.data as ApiJsonMsg).module === undefined + ){ + console.log("Server msg has no cmd or module"); + return; + } } catch (e) { + console.log(e); return; } } else { + msg.type = "binary"; + msg.data = ev.data; console.log(typeof ev.data); } + this.msgCallback(msg); } this.socket.onclose = () => { - console.log('WebSocket Disconnected'); - - clearInterval(this.intervalId); this.socket.onclose = null this.socket.onopen = null this.socket.onerror = null this.socket.onmessage = null; - - const msg: ControlMsg = { - type: ControlMsgType.WS_EVENT, - data: ControlEvent.DISCONNECTED, - } - this.ctrlCallback(msg); - this.closeCallback(); + this.clear(); }; this.socket.onerror = (error) => { - console.error('WebSocket Error', error); - this.socket.close(); + this.close(); }; this.socket.onopen = ev => { - console.log('WebSocket Connected'); + // console.log('WebSocket Connected'); if (this.stoped) { this.close(); return; } this.heartBeatTimeCount = 0; + this.hasBeenConnected = true; const msg: ControlMsg = { type: ControlMsgType.WS_EVENT, data: ControlEvent.CONNECTED, @@ -145,6 +155,21 @@ class OneTimeWebsocket implements IWebsocket { this.socket.send(JSON.stringify(msg.data)); } } + + clear() { + if (this.cleared) { + return; + } + this.cleared = true; + clearInterval(this.intervalId); + + const msg: ControlMsg = { + type: ControlMsgType.WS_EVENT, + data: ControlEvent.DISCONNECTED, + } + this.ctrlCallback(msg); + this.closeCallback(); + } } export class WebsocketWrapper { @@ -185,7 +210,6 @@ export class WebsocketWrapper { this.msgCallback = msgCallback; this.ctrlCallback = ctrlCallback; this.socket = new OneTimeWebsocket(host, this.msgCallback, this.ctrlCallback, this.closeCallback); - } private closeCallback() { @@ -194,7 +218,7 @@ export class WebsocketWrapper { } this.timeoutId = setTimeout(() => this.newConnection(this.host, this.msgCallback, this.ctrlCallback), - 2000); + 1000); } deinit() { @@ -204,7 +228,6 @@ export class WebsocketWrapper { } send(msg: ServerMsg) { - console.log('WebSocket send: not ready', msg); - // this.socket.send(msg) + this.socket.send(msg) } } diff --git a/src/composables/websocket/ws.sharedworker.ts b/src/composables/websocket/ws.sharedworker.ts index c35d43f..b303a9e 100644 --- a/src/composables/websocket/ws.sharedworker.ts +++ b/src/composables/websocket/ws.sharedworker.ts @@ -1,8 +1,11 @@ +import type {ControlMsg, ServerMsg} from "@/api"; + declare const self: SharedWorkerGlobalScope; import {WebsocketWrapper} from "@/composables/websocket/websocketWrapper"; -import type {ControlMsg, ServerMsg} from "@/composables/broadcastChannelDef"; -import {ControlEvent, ControlMsgType, toClient, toClientCtrl, toServer} from "@/composables/broadcastChannelDef"; +import {toClient, toClientCtrl, toServer} from "@/composables/broadcastChannelDef"; +import {ControlEvent, ControlMsgType} from "@/api"; +import {isDevMode} from "@/composables/buildMode"; const websocket = new WebsocketWrapper(); let host = ""; @@ -19,7 +22,9 @@ function msgBroadcast(msg: ServerMsg) { self.onconnect = function(event) { const port = event.ports[0]; port.onmessage = function (e: MessageEvent<ControlMsg>) { - console.log('Received message in SharedWorker:', e.data); + if (isDevMode()) { + console.log('Received message in SharedWorker:', e.data); + } if (e.data.type === ControlMsgType.WS_SET_HOST) { if (host === "" && e.data.data !== "") { host = e.data.data; diff --git a/src/i18n.ts b/src/i18n.ts index 2fd3a6a..45a36b8 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -3,9 +3,7 @@ import zh from '@/locales/zh' import en from '@/locales/en' // const locale = localStorage.getItem('lang') || 'zh'; -const locale = 'zh'; - -console.log("langggggg:", locale); +export const locale = 'zh'; const i18n = createI18n({ globalInjection: true, diff --git a/src/layouts/textLayout.vue b/src/layouts/textLayout.vue new file mode 100644 index 0000000..8ebd00b --- /dev/null +++ b/src/layouts/textLayout.vue @@ -0,0 +1,13 @@ +<script setup lang="ts"> + +</script> + +<template> + <div class="my-2"> + <slot/> + </div> +</template> + +<style scoped> + +</style> \ No newline at end of file diff --git a/src/locales/index.ts b/src/locales/index.ts new file mode 100644 index 0000000..0d4b347 --- /dev/null +++ b/src/locales/index.ts @@ -0,0 +1,14 @@ +import i18n from '@/i18n'; +import zh from '@/locales/zh'; + +type NestedKeyOf<ObjectType extends object> = { + [Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object + ? `${Key}` | `${Key}.${NestedKeyOf<ObjectType[Key]>}` + : `${Key}` +}[keyof ObjectType & (string | number)]; + +type TranslationKeys = NestedKeyOf<typeof zh>; + +export function translate<K extends TranslationKeys>(key: K | string): string { + return i18n.global.t(key.toLowerCase()); +} diff --git a/src/locales/zh.ts b/src/locales/zh.ts index 80defde..17be1d1 100644 --- a/src/locales/zh.ts +++ b/src/locales/zh.ts @@ -1,17 +1,20 @@ export default { - DISCONNECTED: "未连接", - CONNECTED: "已连接", - CONNECTING: "连接中", + disconnected: "未连接", + connected: "已连接", + connecting: "连接中", - WS: { - DISCONNECTED: "未连接", - CONNECTED: "已连接", - CONNECTING: "连接中", + ws: { + disconnected: "未连接", + connected: "已连接", + connecting: "连接中", }, - PAGE: { - HOME: "主页", - ABOUT: "关于", - FEEDBACK: "反馈", + page: { + home: "主页", + wifi: "Wi-Fi", + about: "关于", + uart: "UART透传", + feedback: "反馈", + close: "关闭", }, } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index e91a666..5841e0b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,9 @@ import '@/assets/tailwind.css' +import '@/assets/toggle_skewed.css' +import '@/assets/page.css' +import '@/assets/navigation.css' +import 'element-plus/dist/index.css'; + import { createApp } from 'vue' import { createPinia } from 'pinia' @@ -10,7 +15,7 @@ import router from './router' const app = createApp(App) app.use(createPinia()) -app.use(router) app.use(i18n); +app.use(router) app.mount('#app') diff --git a/src/router/index.ts b/src/router/index.ts index f24dc5b..207b1f3 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,29 +1,48 @@ -import { createRouter, createWebHistory } from 'vue-router' +import {createRouter, createWebHistory} from 'vue-router' import Home from '@/views/Home.vue' import Wifi from '@/views/Wifi.vue' +import Feedback from '@/views/Feedback.vue' +import About from '@/views/About.vue' +import Uart from '@/views/Uart.vue' +import {translate} from "@/locales"; + const router = createRouter({ - history: createWebHistory(import.meta.env.BASE_URL), - routes: [ - { - path: '/', - name: 'home', - component: Home - }, { - path: '/home:ext(.*)', - component: Home, - }, { - path: '/wifi:ext(.*)', - component: Wifi, - }, { - path: '/about:ext(.*)', - name: 'about', - // route level code-splitting - // this generates a separate chunk (About.[hash].js) for this route - // which is lazy-loaded when the route is visited. - component: () => import('@/views/About.vue') - } - ] + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'home', + meta: {title: translate("page.home")}, + component: Home + }, { + path: '/home:ext(.*)', + meta: {title: translate("page.home")}, + redirect: () => '/', + }, { + path: '/wifi:ext(.*)', + meta: {title: translate('page.wifi')}, + component: Wifi, + }, { + path: '/about:ext(.*)', + meta: {title: translate('page.about')}, + component: About, + }, { + path: '/uart:ext(.*)', + meta: {title: translate('page.uart')}, + component: Uart, + }, { + path: '/feedback:ext(.*)', + meta: {title: translate('page.feedback')}, + name: 'feedback', + component: Feedback, + }, + ] }) +router.beforeEach((to, from, next) => { + document.title = typeof to.meta.title === 'string' ? to.meta.title + " | 允斯工作室" : '允斯调试器'; + next(); +}); + export default router diff --git a/src/router/msgRouter.ts b/src/router/msgRouter.ts new file mode 100644 index 0000000..d9edb39 --- /dev/null +++ b/src/router/msgRouter.ts @@ -0,0 +1,41 @@ +import type {ApiJsonMsg, ControlMsg, ServerMsg} from "@/api"; + +export interface IModuleCallback { + ctrlCallback: (msg: ControlMsg) => void; + serverMsgCallback: (msg: ServerMsg) => void; +} + +const moduleMap = new Map<number, IModuleCallback>(); + +export function registerModule(moduleId: number, moduleCallback: IModuleCallback): boolean { + if (moduleMap.has(moduleId)) { + return false; + } + + moduleMap.set(moduleId, moduleCallback); + return true; +} + +export function unregisterModule(moduleId: number) { + moduleMap.delete(moduleId); +} + +export function routeModuleServerMsg(msg: ServerMsg) { + if (msg.type == "json") { + const module = (msg.data as ApiJsonMsg).module; + const moduleHandler = moduleMap.get(module); + if (moduleHandler) { + moduleHandler.serverMsgCallback(msg); + } else { + console.log("routeModuleServerMsg module not loaded", module); + } + } else { + console.log("routeModuleServerMsg ignored:", msg); + } +} + +export function routeCtrlMsg(msg: ControlMsg) { + for (const item of moduleMap) { + item[1].ctrlCallback(msg); + } +} diff --git a/src/stores/websocket.ts b/src/stores/websocket.ts index 4a2f39f..ca669a6 100644 --- a/src/stores/websocket.ts +++ b/src/stores/websocket.ts @@ -1,5 +1,6 @@ import {defineStore} from "pinia"; -import {ControlEvent} from "@/composables/broadcastChannelDef"; + +import {ControlEvent} from "@/api"; export const useWsStore = defineStore('websocket', { state: () => { diff --git a/src/utils/emitter.ts b/src/utils/emitter.ts new file mode 100644 index 0000000..4b06ecd --- /dev/null +++ b/src/utils/emitter.ts @@ -0,0 +1,5 @@ +import mitt from 'mitt'; + +const emitter = mitt(); + +export default emitter; diff --git a/src/views/About.vue b/src/views/About.vue index 7d99a3d..9e5c67e 100644 --- a/src/views/About.vue +++ b/src/views/About.vue @@ -1,12 +1,50 @@ +<script setup lang="ts"> +const version = __APP_VERSION__; +const compileTime = __BUILD_TIME__; +</script> + <template> - <h2>About Page</h2> - <!-- Your about page content goes here --> - <h2>About Page</h2> + <div class="text-layout"> + <el-divider></el-divider> + <el-divider>关于上位机</el-divider> + <el-divider></el-divider> + + <el-descriptions border :column="1" class="mt-5 description-style"> + <el-descriptions-item label="上位机版本">{{version}}</el-descriptions-item> + <el-descriptions-item label="发布时间">{{compileTime}}</el-descriptions-item> + <el-descriptions-item label="许可证">MIT</el-descriptions-item> + </el-descriptions> + + <el-descriptions title="鸣谢" border :column="1" class="mt-5 description-style"> + <el-descriptions-item label="vuejs"><a href="https://github.com/vuejs/vue/blob/main/LICENSE">MIT</a></el-descriptions-item> + <el-descriptions-item label="typescript"><a + href="https://github.com/microsoft/TypeScript/blob/main/LICENSE.txt">Apache 2.0</a></el-descriptions-item> + <el-descriptions-item label="vite"><a href="https://github.com/vitejs/vite/blob/main/LICENSE">MIT</a></el-descriptions-item> + <el-descriptions-item label="tailwindcss"><a href="https://github.com/tailwindlabs/tailwindcss/blob/master/LICENSE">MIT</a></el-descriptions-item> + <el-descriptions-item label="element-plus"><a + href="https://github.com/element-plus/element-plus/blob/dev/LICENSE">MIT</a></el-descriptions-item> + <el-descriptions-item label="pinia"><a href="https://github.com/vuejs/pinia/blob/v2/LICENSE">MIT</a></el-descriptions-item> + <el-descriptions-item label="mitt"><a href="https://github.com/developit/mitt/blob/main/LICENSE">MIT</a></el-descriptions-item> + <el-descriptions-item label="vue-router"><a href="https://github.com/vuejs/vue-router/blob/dev/LICENSE">MIT</a></el-descriptions-item> + <el-descriptions-item label="vue-i18n"><a href="https://github.com/kazupon/vue-i18n?tab=MIT-1-ov-file#readme">MIT</a></el-descriptions-item> + <el-descriptions-item label="lightningcss"><a href="https://github.com/parcel-bundler/lightningcss/blob/master/LICENSE">MPL-2.0 license</a></el-descriptions-item> + </el-descriptions> + + <el-descriptions title="作者:空空(kerms)" border :column="1" class="mt-5 description-style"> + <el-descriptions-item label="github"><a href="https://github.com/kerms">https://github.com/kerms</a></el-descriptions-item> + <el-descriptions-item label="邮箱">kerms@niazo.org</el-descriptions-item> + <el-descriptions-item label="BiliBili"><a href="https://space.bilibili.com/38669852">UID38669852</a></el-descriptions-item> + <el-descriptions-item label="QQ群">642246000</el-descriptions-item> + <el-descriptions-item label="备注">欢迎大家来打扰啊~</el-descriptions-item> + </el-descriptions> + </div> + <el-divider></el-divider> + </template> +<style scoped> +.description-style :deep(.el-descriptions__label) { + @apply w-32 +} -<script setup lang="ts"> - -let name = "about-a"; - -</script> +</style> \ No newline at end of file diff --git a/src/views/Feedback.vue b/src/views/Feedback.vue new file mode 100644 index 0000000..e3757f1 --- /dev/null +++ b/src/views/Feedback.vue @@ -0,0 +1,20 @@ +<template> + + <div class="text-layout"> + <el-divider></el-divider> + <el-divider>反馈</el-divider> + <el-divider></el-divider> + + <el-descriptions title="反馈/建议/需要新功能" border :column="1"> + <el-descriptions-item label="QQ群">642246000</el-descriptions-item> + <el-descriptions-item label="作者邮箱">kerms@niazo.org</el-descriptions-item> + </el-descriptions> + + </div> + <el-divider></el-divider> +</template> + + +<script setup lang="ts"> + +</script> diff --git a/src/views/Home.vue b/src/views/Home.vue index 50aff6c..50d46f2 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -1,20 +1,12 @@ <template> - <h2>Home Page</h2> - <!-- Your home page content goes here --> - <h2>Home Page</h2> - <nav> -<!-- <RouterLink to="/">Home</RouterLink>--> -<!-- <RouterLink to="/wifi">Wifi</RouterLink>--> -<!-- <RouterLink to="/about">About</RouterLink>--> -<!-- <RouterLink to="/test">Test</RouterLink>--> -<!-- <RouterLink to="/home">home</RouterLink>--> - </nav> + <div class="text-layout"> + <h2 class="page-title">主页</h2> + <p>空空如也,暂不知道放什么。</p> + </div> </template> <script setup lang="ts"> -document.title = "Home"; - </script> diff --git a/src/views/Uart.vue b/src/views/Uart.vue new file mode 100644 index 0000000..51dbd02 --- /dev/null +++ b/src/views/Uart.vue @@ -0,0 +1,9 @@ +<script setup lang="ts"> + +</script> + +<template> + <div class="text-layout"> + <h2 class="page-title opacity-10">尽请期待</h2> + </div> +</template> diff --git a/src/views/Wifi.vue b/src/views/Wifi.vue index eda250f..9c48909 100644 --- a/src/views/Wifi.vue +++ b/src/views/Wifi.vue @@ -1,7 +1,13 @@ <template> - <div class="wifiView"> - <h2>Wifi View</h2> - <el-form label-width="auto" ref="formRef" :model="ssidValidateForm"> + <div class="text-layout"> + <h1 class="page-title"> + Wi-Fi 配置 + </h1> + <el-divider></el-divider> + + + <h2 class="mb-4 text-xl font-bold tracking-tight md:text-2xl lg:text-3xl">连接Wi-Fi</h2> + <el-form label-width="auto" ref="formRef" :model="ssidValidateForm" class="m-auto"> <el-form-item label="Wi-Fi名" prop="wifiSsid" @@ -19,22 +25,21 @@ value-key="ssid" > <template #default="{ item }"> - <div class="flex"> + <div class="flex items-center border-b"> <InlineSvg :name="item.wifiLogo" class="h-6 pr-4"></InlineSvg> -<!-- <span class="w-10">{{ item.rssi }}</span>--> + <!-- <span class="w-10">{{ item.rssi }}</span>--> <div>{{ item.ssid }}</div> </div> </template> </el-autocomplete> <div class="h-8"> - <el-button class="h-8" @click="onScanClick">扫描</el-button> + <el-button class="h-8" @click="onScanClick">{{ scanText }}</el-button> </div> </div> - </el-form-item> <el-form-item label="密码"> <el-input - v-model="password" + v-model="ssidValidateForm.password" show-password type="password" clearable @@ -42,106 +47,296 @@ </el-form-item> <div class="flex justify-center"> - <el-button @click="onConnect" type="primary">连接</el-button> + <el-button @click="onConnectClick" type="primary">连接</el-button> </div> </el-form> + <el-divider></el-divider> + + + <el-descriptions + title="Wi-Fi终端信息" + :column="1" + border + class="description-style" + > + <el-descriptions-item label="asd"> + <template #label > + <div> + 信号强度 + </div> + </template> + <template #default > + {{ wifiStaApInfo.rssi }} + </template> + </el-descriptions-item> + <el-descriptions-item span="1"> + <template #label> + <div> + SSID + </div> + </template> + {{ wifiStaApInfo.ssid }} + </el-descriptions-item> +<!-- <el-descriptions-item span="6" >--> +<!-- <template #label>--> +<!-- <div>--> +<!-- 密码--> +<!-- </div>--> +<!-- </template>--> +<!-- <password-viewer password="asdasdasd"></password-viewer>--> +<!-- </el-descriptions-item>--> + <el-descriptions-item span="4"> + <template #label> + <div> + IP + </div> + </template> + {{ wifiStaApInfo.ip }} + </el-descriptions-item> + <el-descriptions-item span="4"> + <template #label> + <div> + MAC + </div> + </template> + {{ wifiStaApInfo.mac }} + </el-descriptions-item> + <el-descriptions-item span="4"> + <template #label> + <div> + 网关 + </div> + </template> + {{ wifiStaApInfo.gateway }} + </el-descriptions-item> + + <el-descriptions-item span="4"> + <template #label> + <div> + 掩码 + </div> + </template> + {{ wifiStaApInfo.netmask }} + </el-descriptions-item> + </el-descriptions> + + <el-divider></el-divider> + + <el-descriptions + title="Wi-Fi热点信息" + :column="1" + border + class="description-style" + > + <el-descriptions-item span="6"> + <template #label> + <div> + SSID + </div> + </template> + {{ wifiApInfo.ssid }} + </el-descriptions-item> +<!-- <el-descriptions-item span="6">--> +<!-- <template #label>--> +<!-- <div>--> +<!-- 密码--> +<!-- </div>--> +<!-- </template>--> +<!-- <password-viewer password="asdasdasd"></password-viewer>--> +<!-- </el-descriptions-item>--> + <el-descriptions-item span="4"> + <template #label> + <div> + IP + </div> + </template> + {{ wifiApInfo.ip }} + </el-descriptions-item> + + <el-descriptions-item span="4"> + <template #label> + <div> + MAC + </div> + </template> + {{ wifiApInfo.mac }} + </el-descriptions-item> + + <el-descriptions-item span="4"> + <template #label> + <div> + 网关 + </div> + </template> + {{ wifiApInfo.gateway }} + </el-descriptions-item> + + <el-descriptions-item span="4"> + <template #label> + <div> + 掩码 + </div> + </template> + {{ wifiApInfo.netmask }} + </el-descriptions-item> + </el-descriptions> + + <el-divider></el-divider> </div> - <el-button class="h-8">扫描</el-button> - <el-button type="primary">连接</el-button> + </template> <script setup lang="ts"> -import {inject, onMounted, onUnmounted, reactive, ref} from "vue"; -import {wifi_get_ap_info, wifi_get_scan_list, type WifiInfo, type WifiList} from "@/api/apiWifi"; -import type {FormInstance} from "element-plus"; -import type {ServerMsg,ControlMsg} from "@/composables/broadcastChannelDef"; - +import {computed, onMounted, onUnmounted, reactive, ref} from "vue"; import { - ControlEvent, - ControlMsgType -} from "@/composables/broadcastChannelDef"; -import InlineSvg from "@/components/InlineSvg.vue"; -import {getWebsocketService} from "@/composables/websocket/websocketService"; + wifi_sta_get_ap_info, + wifi_get_scan_list, + WifiCmd, + type WifiInfo, + type WifiList, + WifiModuleID, + wifi_ap_get_info +} from "@/api/apiWifi"; +import type {FormInstance} from "element-plus"; +import InlineSvg from "@/components/InlineSvg.vue"; +import type {ApiJsonMsg, ControlMsg, ServerMsg} from "@/api"; +import {ControlEvent, ControlMsgType} from "@/api"; +import {registerModule, unregisterModule} from "@/router/msgRouter"; +import {useWsStore} from "@/stores/websocket"; +import {globalNotify, globalNotifyRightSide} from "@/composables/notification"; const formRef = ref<FormInstance>() let wifiListPlaceholder = ref("我的WIFI") let ssidValidateForm = reactive({ - wifiSsid: "" + wifiSsid: "", + password: "", }) -const password = ref('') -let scanning = false; + +let wsStore = useWsStore(); + +const defWifiInfo: WifiInfo = { + cmd: 1, + module: 1, + gateway: "未连接", + ip: "未连接", + mac: "未连接", + rssi: 0, + netmask: "未连接", + ssid: "未连接", +} + +let wifiStaApInfo = reactive<WifiInfo>({...defWifiInfo}); +let wifiApInfo = reactive<WifiInfo>({...defWifiInfo}); + +let scanning = ref(false); let scan_cb: any; -let options: Array<WifiInfo> = [ - -] +let options: Array<WifiInfo> = []; +const scanText = computed(() => { + return scanning.value ? "扫描中" : "扫描"; +}); const querySearch = (queryString: string, cb: any) => { - if (scanning) { + if (scanning.value) { scan_cb = cb; } else { cb(options); } } -const onClientMsg = (ev: MessageEvent<ServerMsg>) => { - if (ev.data.type !== "json") { +const onClientMsg = (msg: ServerMsg) => { + if (msg.type !== "json") { return; } - const json: object = ev.data.data; + let json = msg.data as ApiJsonMsg; - let wifiList: WifiList; - try { - wifiList = ev.data.data as WifiList; - console.log(wifiList); - } catch (e) { - return; - } - scanning = false; - wifiList.scan_list.forEach(value => { - if (value.rssi > -50) { - value.wifiLogo = "wifi-3"; - } else if (value.rssi > -65) { - value.wifiLogo = "wifi-2"; - } else { - value.wifiLogo = "wifi-1"; + switch (json.cmd as WifiCmd) { + case WifiCmd.UNKNOWN: + break; + case WifiCmd.WIFI_API_JSON_STA_GET_AP_INFO: { + const info = msg.data as WifiInfo; + Object.assign(wifiStaApInfo, info); + break; + } + case WifiCmd.WIFI_API_JSON_CONNECT: + break; + case WifiCmd.WIFI_API_JSON_GET_SCAN: { + const list = msg.data as WifiList; + scanning.value = false; + list.scan_list.forEach(value => { + if (value.rssi > -50) { + value.wifiLogo = "wifi-3"; + } else if (value.rssi > -65) { + value.wifiLogo = "wifi-2"; + } else { + value.wifiLogo = "wifi-1"; + } + }); + options = list.scan_list; + if (scan_cb) { + scan_cb(options); + scan_cb = null; + } + globalNotifyRightSide("扫描完成", "success"); + break; + } + case WifiCmd.WIFI_API_JSON_DISCONNECT: + break; + case WifiCmd.WIFI_API_JSON_AP_GET_INFO: { + const info = msg.data as WifiInfo; + Object.assign(wifiApInfo, info); + break; } - }); - options = wifiList.scan_list; - if (scan_cb) { - scan_cb(options); - scan_cb = null; } }; -const onClientCtrl = (ev: MessageEvent<ControlMsg>) => { - if (ev.data.type !== ControlMsgType.WS_EVENT) { +const onClientCtrl = (msg: ControlMsg) => { + if (msg.type !== ControlMsgType.WS_EVENT) { return } - if (ev.data.data === ControlEvent.CONNECTED) { - wifi_get_ap_info(); + if (msg.data === ControlEvent.DISCONNECTED) { + Object.assign(wifiStaApInfo, defWifiInfo); + Object.assign(wifiApInfo, defWifiInfo); + } + + if (msg.data === ControlEvent.CONNECTED) { + wifi_sta_get_ap_info(); + wifi_ap_get_info(); } }; function onScanClick() { - scanning = true; + if (wsStore.state !== ControlEvent.CONNECTED) { + globalNotify("调试器未连接", 'error'); + return; + } + scanning.value = true; wifi_get_scan_list(); } -function onConnect() { - console.log(ssidValidateForm.wifiSsid, password.value); +function onConnectClick() { + if (wsStore.state !== ControlEvent.CONNECTED) { + globalNotify("调试器未连接", 'error'); + return; + } + console.log(ssidValidateForm.wifiSsid, ssidValidateForm.password); } onMounted(() => { - + registerModule(WifiModuleID, { + ctrlCallback: onClientCtrl, + serverMsgCallback: onClientMsg + }); + wifi_sta_get_ap_info(); + wifi_ap_get_info(); }); onUnmounted(() => { - + unregisterModule(WifiModuleID); }); @@ -149,10 +344,7 @@ onUnmounted(() => { <style scoped> -.wifiView { - background-color: bisque; - border-radius: 5px; - padding: 20px; +.description-style :deep(.el-descriptions__label) { + @apply w-32 } - </style> diff --git a/src/views/navigation/NavBar.vue b/src/views/navigation/NavBar.vue index 05eb146..658830d 100644 --- a/src/views/navigation/NavBar.vue +++ b/src/views/navigation/NavBar.vue @@ -1,44 +1,38 @@ <template> - <nav class="relative px-4 py-1 flex justify-between items-center border-b"> + <nav class="relative px-2 py-1 flex justify-between items-center border-b"> <div class="flex"> <button @click.prevent="sideMenuOpen=true" class="flex items-center hover:text-blue-600 p-3"> <svg class="block h-4 w-4 fill-current" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> - <title>Mobile menu</title> + <title>导航侧栏</title> <path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"></path> </svg> </button> - <router-link to="/" class="text-3xl px-4 font-bold leading-none"> - <InlineSvg name="home" class="h-10"></InlineSvg> + <router-link to="/" class="text-3xl px-4 font-bold leading-none" title="走,去码头整点薯条"> + <InlineSvg name="favicon" class="h-10"></InlineSvg> </router-link> <!-- <a class="text-3xl px-4 font-bold leading-none" href="/">--> <!-- <InlineSvg name="home" class="h-10"></InlineSvg>--> <!-- </a>--> - <router-link to="/" class="flex items-center text-sm text-blue-600 font-bold">主页</router-link> +<!-- <router-link to="/" class="flex items-center text-sm text-blue-600 font-bold">主页</router-link>--> <!-- <a class="flex items-center text-sm text-blue-600 font-bold" href="/">主页6</a>--> </div> <div class="flex"> <ul class="hidden absolute top-1/2 left-1/2 transform -translate-y-1/2 -translate-x-1/2 md:flex md:mx-auto md:items-center md:w-auto md:space-x-6"> - <li><router-link to="/wifi" title="Wifi" class="text-sm text-gray-400 hover:text-gray-800">wifi</router-link></li> -<!-- <li><a class="text-sm text-gray-400 hover:text-gray-500" href="#">Home</a></li>--> -<!-- <li><a class="text-sm text-blue-600 font-bold">About Us</a></li>--> -<!-- <li><a class="text-sm text-gray-400 hover:text-gray-500" href="#">Services</a></li>--> + <li v-for="(item, index) in menuItems" :key="index" class="router-link"> + <router-link :to="item.href" :class="item?.class">{{item.name}}</router-link> + </li> </ul> </div> <!-- <a class="md:ml-auto md:mr-3"></a>--> <div class="flex"> - <button @click="stateMenuOpen=true" - class="py-2 px-6 bg-blue-500 hover:bg-blue-600 text-sm text-white font-bold rounded-xl transition duration-200"> - <span class="flex"> - <svg class="mr-2" width="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> - <path d="M12 19.4998H12.01M2 8.81929C3.69692 7.30051 5.74166 6.16236 8 5.53906M5 12.8584C5.86251 12.0129 6.87754 11.3223 8 10.8319M16 5.53906C18.2583 6.16236 20.3031 7.30051 22 8.81929M16 10.8319C17.1225 11.3223 18.1375 12.0129 19 12.8584M12 4.5V15.4998" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> - </svg> - <span>{{ wsState }}</span> - </span> - </button> -<!-- <span>{{ $t("Disconnected") }}</span>--> + <el-button @click="stateMenuOpen=true" :type="wsColor" size="large" class="transition duration-1000"> + <InlineSvg v-show="wsColor!=='success'" name="wifi-exclamation" class="mr-2" width="20"></InlineSvg> + <InlineSvg v-show="wsColor==='success'" name="wifi-3" class="mr-2" width="20"></InlineSvg> + {{ wsState }} + </el-button> </div> </nav> <div :class='["custom-drawer", {open: sideMenuOpen}]'> @@ -46,26 +40,30 @@ v-model="sideMenuOpen" :with-header="false" size="" - :direction="'ltr'"> - <div :class="[sideMenuItemClass]" class="px-6" @click="sideMenuOpen=false"> - <InlineSvg name="cross" class="w-6"></InlineSvg> - </div> - <div class="flex-col justify-between m-4 mt-0"> + :direction="'ltr'" + > + <div id="testborder" :class="[sideMenuItemClass]" class="pr-6 flex text-gray-500" @click="sideMenuOpen=false"> + <InlineSvg name="cross" class="h-6"></InlineSvg> <div> - <ul> - <li v-for="(item, index) in menuItems" class="mb-1" :key="index"> - <router-link @click="sideMenuOpen=false" :title="item.name" :to="item.href" :class="sideMenuItemClass">{{ item.name }}</router-link> -<!-- <a :href="item.href" :class="sideMenuItemClass">{{ item.name }}</a>--> - </li> - </ul> + <p class="h-6 flex items-center">{{ $t("page.close") }}</p> </div> - <div class="mt-auto"> + </div> - <p class="my-4 text-xs text-center text-gray-400"> - <span>Copyright kerms 2024</span> + <div class="flex flex-col justify-between m-4 mt-0"> + <ul> + <li v-for="(item, index) in menuItems" class="mb-1" :key="index"> + <router-link @click="sideMenuOpen=false" :title="item.name" :to="item.href" :class="[sideMenuItemClass, item?.class]">{{ item.name }}</router-link> + </li> + </ul> + </div> + + <template #footer> + <div> + <p class="text-xs text-center text-gray-400"> + <span>Copyright <a href="http://github.com/kerms">kerms</a> 2024</span> </p> </div> - </div> + </template> </el-drawer> </div> @@ -79,12 +77,7 @@ <div class="flex-col justify-between m-4 bg-white"> <div class="mt-auto"> - <div class="pt-6"> - <a class="block px-4 py-3 mb-3 leading-loose text-xs text-center font-semibold bg-gray-50 hover:bg-gray-100 rounded-xl" - href="#">Sign in</a> - <a class="block px-4 py-3 mb-2 leading-loose text-xs text-center text-white font-semibold bg-blue-600 hover:bg-blue-700 rounded-xl" - href="#">Sign Up</a> - </div> + </div> </div> </el-drawer> @@ -93,41 +86,55 @@ <script lang="ts" setup> import InlineSvg from "@/components/InlineSvg.vue"; -import {computed, ref, toRef} from "vue"; +import {computed, ref} from "vue"; import {useWsStore} from "@/stores/websocket"; -import {useI18n} from "vue-i18n"; +import {translate} from "@/locales"; +import {ControlEvent} from "@/api"; -const { t } = useI18n() const wsStore = useWsStore(); -const sideMenuItemClass = "block p-4 text-sm font-semibold text-gray-400 hover:bg-blue-50 hover:text-blue-600 rounded" +const sideMenuItemClass = "block p-4 text-sm font-semibold hover:bg-blue-50 hover:text-blue-600 rounded" const sideMenuOpen = ref(false); const stateMenuOpen = ref(false) -const wsState = computed(() => { +const wsColor = computed(() => { + let ret = "danger"; + switch (wsStore.state) { + case ControlEvent.DISCONNECTED: + ret = "danger"; + break + case ControlEvent.CONNECTED: + ret = "success"; + break + case ControlEvent.CONNECTING: + ret = "warning"; + break + } + return ret; +}); - return t(wsStore.state); +const wsState = computed(() => { + return translate(wsStore.state); }); const menuItems = ([ { - name: "Home", + name: translate("page.home"), href: "/", + }, { - name: "About Us", - href: "/about", - }, { - name: "Services", - href: "/", - }, { - name: "Wifi", + name: translate("page.wifi"), href: "/wifi", }, { - name: "Contact", - href: "/", + name: translate("page.about"), + href: "/about", }, { - name: "6", - href: "/", + name: translate("page.feedback"), + href: "/feedback", + }, { + name: translate("page.uart"), + href: "/uart", + class: "todo-menu-item", }, ]); @@ -153,4 +160,5 @@ const menuItems = ([ padding: 0; } + </style> \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index d59fc90..6f1869e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,7 +5,9 @@ import Components from 'unplugin-vue-components/vite' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import svgLoader from "vite-svg-loader"; - +import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js"; +import { viteSingleFile } from 'vite-plugin-singlefile' +import packageJson from "./package.json" // https://vitejs.dev/config/ export default defineConfig({ @@ -18,18 +20,46 @@ export default defineConfig({ resolvers: [ElementPlusResolver()], }), svgLoader(), + cssInjectedByJsPlugin(), + viteSingleFile(), ], + define: { + '__APP_VERSION__': JSON.stringify(packageJson.version), + '__BUILD_TIME__': JSON.stringify(new Date().toISOString()), + }, resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, + + cacheDir: "/tmp/zhuang/cache", + worker: { + rollupOptions: { + output: { + inlineDynamicImports: true, + minifyInternalExports: true, + // entryFileNames: (chunkInfo) => { + // // console.log(chunkInfo) + // if (chunkInfo.name.includes("shared")) { + // console.log(chunkInfo.name); + // } + // return "worker.js"; + // }, + entryFileNames: "[name].js", + } + } + }, build: { + // target: 'es2015', outDir: '/tmp/zhuang/dap-web-dist/', emptyOutDir: true, cssMinify: 'lightningcss', + rollupOptions: { output: { + inlineDynamicImports: true, + minifyInternalExports: true, assetFileNames: (assetInfo) => { if (!assetInfo || !assetInfo.name) { return 'default-filename.ext'; @@ -44,15 +74,25 @@ export default defineConfig({ extType = "css"; return "style.css" } - // return `[name]-[hash][extname]`; - return `[name][extname]`; + console.log(assetInfo) + return `[name]-[hash][extname]`; }, // chunkFileNames: "[name]-[hash].js", - chunkFileNames: "[name].js", + // chunkFileNames: "[name][hash].js", + chunkFileNames(chunkInfo) { + // Check if this chunk is your SharedWorker + // console.log(chunkInfo) + + // For other chunks, use the default naming scheme + return 'assets/[name]-[hash].js'; + }, // entryFileNames: "[name]-[hash].js", entryFileNames: (chunkInfo) => { // console.log(chunkInfo) - return "script.js" + if (chunkInfo.name.includes("shared")) { + console.log(chunkInfo.name); + } + return "script.js"; }, sourcemapFileNames: "map-[name].js", @@ -61,16 +101,17 @@ export default defineConfig({ // console.log(chunkInfo) // return `${chunkInfo.name}.js` // }, - manualChunks(id) { - /* if (id.match('.*!/src/.*shared[a-zA-Z0-9-_]*[.](ts|js).*')) { - // Prevent bundling node_modules into common chunks - return 'bundle-shared'; - } - else */{ - // Prevent bundling node_modules into common chunks - return 'script' - } - }, + // manualChunks(id) { + // /* if (id.match('.*!/src/.*shared[a-zA-Z0-9-_]*[.](ts|js).*')) { + // // Prevent bundling node_modules into common chunks + // return 'bundle-shared'; + // } + // else */{ + // // Prevent bundling node_modules into common chunks + // return 'script' + // } + // }, + manualChunks: undefined, }, } },