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 @@ /// +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 @@ - + Vite App 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 @@ - - 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 @@ + + + + + + + + + + + + + + + + + + + + + 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 @@ + \ 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 @@ + \ 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 @@ + + + \ 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 @@ + + + \ 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 @@ + + + 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) { + 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) { - 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 @@ + + + + + \ 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 = { + [Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object + ? `${Key}` | `${Key}.${NestedKeyOf}` + : `${Key}` +}[keyof ObjectType & (string | number)]; + +type TranslationKeys = NestedKeyOf; + +export function translate(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(); + +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 @@ + + + \ 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 @@ + + + + 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 @@ 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 @@ + + + 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 @@