feat(wifi,ws) view page

This commit is contained in:
kerms 2024-04-01 15:40:39 +08:00
parent 1bae314449
commit c7d1dff0d0
44 changed files with 1098 additions and 343 deletions

1
.env.development Normal file
View File

@ -0,0 +1 @@
VITE_APP_MODE=dev

1
.env.production Normal file
View File

@ -0,0 +1 @@
VITE_APP_MODE=prod

5
.gitignore vendored
View File

@ -29,3 +29,8 @@ coverage
*.tsbuildinfo
package-lock.json
components.d.ts
auto-imports.d.ts
# Personal
**/_priv_*

9
auto-imports.d.ts vendored
View File

@ -1,9 +0,0 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
}

20
components.d.ts vendored
View File

@ -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']
}
}

2
env.d.ts vendored
View File

@ -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;

View File

@ -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>

View File

@ -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"
}

View File

@ -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>

View File

@ -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;
}

View File

@ -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));
}

45
src/assets/icon/favicon.svg Executable file
View File

@ -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>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.4 KiB

1
src/assets/icon/view.svg Normal file
View File

@ -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>

After

Width:  |  Height:  |  Size: 477 B

View File

@ -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>

After

Width:  |  Height:  |  Size: 419 B

View File

@ -0,0 +1,3 @@
.todo-menu-item {
@apply opacity-30
}

16
src/assets/page.css Normal file
View File

@ -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;
}

View File

@ -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);
}

View File

@ -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>

View File

@ -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>

View File

@ -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));
}

View File

@ -0,0 +1,3 @@
export function isDevMode() {
return import.meta.env.VITE_APP_MODE === 'dev';
}

View File

@ -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}`;
}
}

View File

@ -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" +
"大概是一起去整点赛博薯条吧。-来自法国鸽子");
}

View File

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

View File

@ -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);
}

View File

@ -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)
}
}

View File

@ -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;

View File

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

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
</script>
<template>
<div class="my-2">
<slot/>
</div>
</template>
<style scoped>
</style>

14
src/locales/index.ts Normal file
View File

@ -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());
}

View File

@ -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: "关闭",
},
}

View File

@ -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')

View File

@ -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

41
src/router/msgRouter.ts Normal file
View File

@ -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);
}
}

View File

@ -1,5 +1,6 @@
import {defineStore} from "pinia";
import {ControlEvent} from "@/composables/broadcastChannelDef";
import {ControlEvent} from "@/api";
export const useWsStore = defineStore('websocket', {
state: () => {

5
src/utils/emitter.ts Normal file
View File

@ -0,0 +1,5 @@
import mitt from 'mitt';
const emitter = mitt();
export default emitter;

View File

@ -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>

20
src/views/Feedback.vue Normal file
View File

@ -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>

View File

@ -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>

9
src/views/Uart.vue Normal file
View File

@ -0,0 +1,9 @@
<script setup lang="ts">
</script>
<template>
<div class="text-layout">
<h2 class="page-title opacity-10">尽请期待</h2>
</div>
</template>

View File

@ -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>

View File

@ -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>

View File

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