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,
       },
     }
   },