Compare commits

..

No commits in common. "3fbb21aa1d099f940c125a03ae30af1941f611cf" and "1bae31444952af3464e1b06fedecc86eb0df1773" have entirely different histories.

54 changed files with 447 additions and 7041 deletions

View File

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

View File

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

7
.gitignore vendored
View File

@ -28,9 +28,4 @@ coverage
*.sw?
*.tsbuildinfo
components.d.ts
auto-imports.d.ts
# Personal
**/_priv_*
Makefile
package-lock.json

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2024 kerms
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,3 +1 @@
# 允斯无线透传器的内嵌网页版上位机
# 允斯调试器的内嵌网页版上位机

9
auto-imports.d.ts vendored Normal file
View File

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

20
components.d.ts vendored Normal file
View File

@ -0,0 +1,20 @@
/* 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 @@
/// <reference types="vite/client" />
/// <reference types="vite/client" />

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<link id="favicon" rel="icon" href="data:,">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>

5620
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,19 +4,16 @@
"private": true,
"type": "module",
"scripts": {
"dev": ". ./set_env.sh && vite",
"devh": ". ./set_env.sh && vite --host",
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": ". ./set_env.sh && vite preview",
"build-only": ". ./set_env.sh && vite build",
"build:dev": ". ./set_env.sh && vite build --mode development",
"preview": "vite preview",
"build-only": "vite build",
"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/"
},
"dependencies": {
"@vueuse/core": "^10.9.0",
"element-plus": "^2.7.3",
"element-plus": "^2.6.1",
"mitt": "^3.0.1",
"pinia": "^2.1.7",
"vue": "^3.4.21",
@ -43,9 +40,6 @@
"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,3 +0,0 @@
#!/bin/bash
export VITE_APP_GIT_TAG=$(git describe --tags | cut -d'-' -f1,2)
export VITE_APP_LAST_COMMIT=$(git log -1 --format=%cd)

View File

@ -1,64 +1,64 @@
<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) => {
if (isDevMode()) {
console.log("App.vue:", msg);
}
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) => {
if (isDevMode()) {
console.log("App.vue:", msg);
}
routeModuleServerMsg(msg);
console.log("App.vue:", msg);
};
let websocketService: IWebsocketService;
onMounted(() => {
// const host = window.location
console.log("App.vue mounted")
logHelloMessage();
let host = "";
if (isDevMode()) {
host = import.meta.env.VITE_DEVICE_HOST_NAME || "dap.local";
} else {
host = window.location.host
}
const host = "192.168.43.61";
websocketService = getWebsocketService();
websocketService.init(host, onServerMsg, onClientCtrl);
changeFavicon();
});
onUnmounted(() => {
});
import NavBar from "@/views/navigation/NavBar.vue";
</script>
<template>
<div class="flex flex-col h-screen">
<header>
<nav-bar/>
</header>
<RouterView/>
</div>
<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,12 +1,15 @@
import {type ApiJsonMsg, sendJsonMsg, WtModuleID} from '@/api'
import {sendJsonMsg} from '@/composables/broadcastChannelDef'
export enum WifiCmd {
import {type ApiJsonMsg} from '@/api'
const WifiModuleID = 1;
enum WifiCmd {
UNKNOWN = 0,
WIFI_API_JSON_STA_GET_AP_INFO,
WIFI_API_JSON_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 {
@ -16,31 +19,23 @@ interface WifiMsgOut extends ApiJsonMsg {
export function wifi_get_scan_list() {
const msg : WifiMsgOut = {
module: WtModuleID.WIFI,
module: WifiModuleID,
cmd: WifiCmd.WIFI_API_JSON_GET_SCAN,
}
sendJsonMsg(msg);
}
export function wifi_sta_get_ap_info() {
export function wifi_get_ap_info() {
const msg : WifiMsgOut = {
module: WtModuleID.WIFI,
cmd: WifiCmd.WIFI_API_JSON_STA_GET_AP_INFO,
}
sendJsonMsg(msg);
}
export function wifi_ap_get_info() {
const msg : WifiMsgOut = {
module: WtModuleID.WIFI,
cmd: WifiCmd.WIFI_API_JSON_AP_GET_INFO,
module: WifiModuleID,
cmd: WifiCmd.WIFI_API_JSON_GET_AP_INFO,
}
sendJsonMsg(msg);
}
export function wifi_connect_to(ssid: string, password: string) {
const msg: WifiMsgOut = {
module: WtModuleID.WIFI,
module: WifiModuleID,
cmd: WifiCmd.WIFI_API_JSON_CONNECT,
ssid: ssid,
password: password,
@ -48,16 +43,13 @@ export function wifi_connect_to(ssid: string, password: string) {
sendJsonMsg(msg);
}
export interface WifiInfo extends ApiJsonMsg {
export interface WifiInfo {
rssi: number;
ssid: string;
gateway: string;
ip: string;
mac: string;
netmask: string;
wifiLogo?: string;
}
export interface WifiList extends ApiJsonMsg {
export interface WifiList {
scan_list: Array<WifiInfo>;
}

View File

@ -1,52 +0,0 @@
import type {WtModuleID} from "@/api/index";
export enum WtDataType {
RESERVED = 0x00,
/* primitive type */
EVENT = 0x02,
ROUTE_HDR = 0x03,
RAW_BROADCAST = 0x04,
/* broadcast data */
CMD_BROADCAST = 0x11,
/* targeted data */
RAW = 0x20,
CMD = 0x21,
RESPONSE = 0x22,
/* standard protocols */
PROTOBUF = 0x40,
JSON = 0x41,
MQTT = 0x42,
}
export interface ApiBinaryMsg {
data_type: WtDataType,
module: WtModuleID,
sub_mod: number,
payload: Uint8Array;
}
export function decodeHeader(arrayBuffer: ArrayBuffer) : ApiBinaryMsg {
// Create a DataView to access the data in the ArrayBuffer
const dataView = new DataView(arrayBuffer);
// Extract the data_type from the first byte
const data_type = dataView.getUint8(0) as WtDataType;
// Extract the module_id and sub_id from the next bytes
const module = dataView.getUint8(1);
const sub_mod = dataView.getUint8(2);
const payload = new Uint8Array(arrayBuffer.slice(4));
// Constructing the header object
return {
data_type,
module,
sub_mod,
payload,
};
}

View File

@ -1,60 +1,4 @@
import {getWebsocketService} from "@/composables/websocket/websocketService";
import type {ApiBinaryMsg} from "@/api/binDataDef";
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: string | ArrayBuffer;
}
export enum WtModuleID {
WIFI = 1,
DATA_FLOW = 2,
UART = 4,
}
export function sendJsonMsg(apiJsonMsg: ApiJsonMsg) {
const msg: ServerMsg = {
type: "json",
data: JSON.stringify(apiJsonMsg),
};
getWebsocketService().send(msg);
}
export function sendBinMsg(binMsg: ApiBinaryMsg) {
const buffer = new Uint8Array(4 + binMsg.payload.length);
buffer[0] = binMsg.data_type;
buffer[1] = binMsg.module;
buffer[2] = binMsg.sub_mod;
buffer[3] = 0; // Reserved byte
buffer.set(binMsg.payload, 4); // Append payload after header
const msg: ServerMsg = {
type: "binary",
data: buffer,
};
getWebsocketService().send(msg);
}

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#5f6368"><path d="m136-80-56-56 264-264H160v-80h320v320h-80v-184L136-80Zm344-400v-320h80v184l264-264 56 56-264 264h184v80H480Z"/></svg>

Before

Width:  |  Height:  |  Size: 206 B

View File

@ -1,45 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.949 14.121 19.071 12a5.008 5.008 0 0 0 0-7.071 5.006 5.006 0 0 0-7.071 0l-.707.707 1.414 1.414.707-.707a3.007 3.007 0 0 1 4.243 0 3.005 3.005 0 0 1 0 4.243l-2.122 2.121a2.723 2.723 0 0 1-.844.57L13.414 12l1.414-1.414-.707-.707a4.965 4.965 0 0 0-3.535-1.465c-.235 0-.464.032-.691.066L3.707 2.293 2.293 3.707l18 18 1.414-1.414-5.536-5.536c.277-.184.538-.396.778-.636zm-6.363 3.536a3.007 3.007 0 0 1-4.243 0 3.005 3.005 0 0 1 0-4.243l1.476-1.475-1.414-1.414L4.929 12a5.008 5.008 0 0 0 0 7.071 4.983 4.983 0 0 0 3.535 1.462A4.982 4.982 0 0 0 12 19.071l.707-.707-1.414-1.414-.707.707z"></path></svg>

Before

Width:  |  Height:  |  Size: 667 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8.465 11.293c1.133-1.133 3.109-1.133 4.242 0l.707.707 1.414-1.414-.707-.707c-.943-.944-2.199-1.465-3.535-1.465s-2.592.521-3.535 1.465L4.929 12a5.008 5.008 0 0 0 0 7.071 4.983 4.983 0 0 0 3.535 1.462A4.982 4.982 0 0 0 12 19.071l.707-.707-1.414-1.414-.707.707a3.007 3.007 0 0 1-4.243 0 3.005 3.005 0 0 1 0-4.243l2.122-2.121z"></path><path d="m12 4.929-.707.707 1.414 1.414.707-.707a3.007 3.007 0 0 1 4.243 0 3.005 3.005 0 0 1 0 4.243l-2.122 2.121c-1.133 1.133-3.109 1.133-4.242 0L10.586 12l-1.414 1.414.707.707c.943.944 2.199 1.465 3.535 1.465s2.592-.521 3.535-1.465L19.071 12a5.008 5.008 0 0 0 0-7.071 5.006 5.006 0 0 0-7.071 0z"></path></svg>

Before

Width:  |  Height:  |  Size: 712 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#5f6368"><path d="M120-120v-320h80v184l504-504H520v-80h320v320h-80v-184L256-200h184v80H120Z"/></svg>

Before

Width:  |  Height:  |  Size: 171 B

View File

@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 477 B

View File

@ -1,3 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 419 B

View File

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

View File

@ -1,21 +0,0 @@
.text-layout {
@apply mx-auto max-w-2xl w-full sm:min-w-[640px] 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;
}
.el-checkbox:hover {
background-color: var(--el-color-primary-light-9);
border-color: var(--el-color-primary-light-8);
}

View File

@ -1,135 +0,0 @@
.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

@ -1,25 +0,0 @@
<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

@ -1,30 +0,0 @@
<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,4 +1,43 @@
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

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

View File

@ -1,20 +0,0 @@
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

@ -1,22 +0,0 @@
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

@ -1,22 +0,0 @@
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'
import MyWorker from '@/composables/websocket/ws.sharedworker?sharedworker&inline'
import {WebsocketWrapper} from "@/composables/websocket/websocketWrapper";
import {toClient, toClientCtrl, toServer} from "@/composables/broadcastChannelDef";
import type {ControlMsg, ServerMsg} from "@/api";
import {ControlEvent, ControlMsgType} from "@/api";
import {isDevMode} from "@/composables/buildMode";
import type {ControlMsg, ServerMsg} from "@/composables/broadcastChannelDef";
import {ControlMsgType, toClient, toClientCtrl} from "@/composables/broadcastChannelDef";
const toServer = new BroadcastChannel("toServer");
export interface IWebsocketService {
init(host: string,
@ -29,18 +29,13 @@ 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() {
if (isDevMode()) {
console.log("Shared Websocket init");
}
console.log("Shared Websocket init")
this.msgCallback = () => {}
this.ctrlCallback = () => {}
this.messageEventProxy = this.messageEventProxy.bind(this);
@ -49,15 +44,14 @@ class WebsocketShared implements IWebsocketService{
}
init(host: string, msgCallback: (msg: ServerMsg) => any, ctrlCallback: (msg: ControlMsg) => any): void {
if (isDevMode()) {
console.log("webworker init");
}
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)
}
@ -66,11 +60,9 @@ 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);
}
@ -79,7 +71,6 @@ class WebsocketShared implements IWebsocketService{
}
controlEventProxy(ev: MessageEvent<ControlMsg>) {
this.ctrlCallback(ev.data);
}
}
@ -102,9 +93,8 @@ class WebsocketClassic implements IWebsocketService{
}
init(host: string, msgCallback: (ev: ServerMsg) => any, ctrlCallback: (msg: ControlMsg) => any): void {
if (isDevMode()) {
console.log("Websocket Service INIT called", WebsocketClassic.count);
}
console.log("Websocket Service INIT called", WebsocketClassic.count);
this.socket.init(host, msgCallback, ctrlCallback);
}

View File

@ -1,7 +1,5 @@
import type {ApiJsonMsg, ControlMsg, ServerMsg} from "@/api";
import {ControlEvent, ControlMsgType} from "@/api";
import {isDevMode} from "@/composables/buildMode";
import type {ServerMsg, ControlMsg} from "@/composables/broadcastChannelDef";
import {ControlEvent, ControlMsgType} from "@/composables/broadcastChannelDef";
interface IWebsocket {
@ -23,7 +21,7 @@ class WebsocketDummy implements IWebsocket {
}
class OneTimeWebsocket implements IWebsocket {
private readonly heartBeatTimeout: number = 1;
private readonly heartBeatTimeout: number = 2;
private readonly host: string;
private readonly intervalId: number;
private readonly msgCallback: (ev: ServerMsg) => any;
@ -32,8 +30,6 @@ 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,
@ -42,8 +38,6 @@ 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;
@ -57,14 +51,8 @@ class OneTimeWebsocket implements IWebsocket {
if (this.heartBeatTimeCount > this.heartBeatTimeout) {
/* did not receive packet "heartBeatTimeout" times,
* connection may be lost: close the socket */
if (this.socket.readyState === this.socket.OPEN) {
console.log("No heart beat, break connection");
this.close();
this.clear();
}
if (isDevMode()) {
console.log("interval: ", this.heartBeatTimeCount, "state: ", this.socket.readyState);
}
this.socket.close();
console.log("interval: ", this.heartBeatTimeCount, "state: ", this.socket.readyState);
}
this.heartBeatTimeCount++;
@ -81,40 +69,51 @@ class OneTimeWebsocket implements IWebsocket {
return
const msg: ServerMsg = {
data: ev.data,
data: {},
type: "json",
}
if (typeof ev.data === "string") {
msg.type = "json"
} else {
msg.type = "binary";
}
this.msgCallback(msg);
if (typeof ev.data === "string") {
try {
msg.data = JSON.parse(ev.data);
this.msgCallback(msg);
} catch (e) {
return;
}
} else {
console.log(typeof ev.data);
}
}
this.socket.onclose = (ev) => {
if (isDevMode()) {
console.log("ws closed", ev.reason, ev.code);
}
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;
this.clear();
const msg: ControlMsg = {
type: ControlMsgType.WS_EVENT,
data: ControlEvent.DISCONNECTED,
}
this.ctrlCallback(msg);
this.closeCallback();
};
this.socket.onerror = (error) => {
this.close();
console.error('WebSocket Error', error);
this.socket.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,
@ -138,26 +137,13 @@ class OneTimeWebsocket implements IWebsocket {
if (this.socket.readyState !== WebSocket.OPEN) {
return;
}
if (isDevMode()) {
console.log('WebSocket proxies data ', msg);
}
this.socket.send(msg.data);
}
clear() {
if (this.cleared) {
return;
console.log('WebSocket proxies data ', msg);
if (msg.type === "binary") {
// this.socket.send(msg.data);
} else if (msg.type === "json") {
this.socket.send(JSON.stringify(msg.data));
}
this.cleared = true;
clearInterval(this.intervalId);
const msg: ControlMsg = {
type: ControlMsgType.WS_EVENT,
data: ControlEvent.DISCONNECTED,
}
this.ctrlCallback(msg);
this.closeCallback();
}
}
@ -199,6 +185,7 @@ export class WebsocketWrapper {
this.msgCallback = msgCallback;
this.ctrlCallback = ctrlCallback;
this.socket = new OneTimeWebsocket(host, this.msgCallback, this.ctrlCallback, this.closeCallback);
}
private closeCallback() {
@ -207,7 +194,7 @@ export class WebsocketWrapper {
}
this.timeoutId = setTimeout(() =>
this.newConnection(this.host, this.msgCallback, this.ctrlCallback),
1000);
2000);
}
deinit() {
@ -217,6 +204,7 @@ export class WebsocketWrapper {
}
send(msg: ServerMsg) {
this.socket.send(msg)
console.log('WebSocket send: not ready', msg);
// this.socket.send(msg)
}
}

View File

@ -1,11 +1,8 @@
import type {ControlMsg, ServerMsg} from "@/api";
declare const self: SharedWorkerGlobalScope;
import {WebsocketWrapper} from "@/composables/websocket/websocketWrapper";
import {toClient, toClientCtrl, toServer} from "@/composables/broadcastChannelDef";
import {ControlEvent, ControlMsgType} from "@/api";
import {isDevMode} from "@/composables/buildMode";
import type {ControlMsg, ServerMsg} from "@/composables/broadcastChannelDef";
import {ControlEvent, ControlMsgType, toClient, toClientCtrl, toServer} from "@/composables/broadcastChannelDef";
const websocket = new WebsocketWrapper();
let host = "";
@ -22,9 +19,7 @@ function msgBroadcast(msg: ServerMsg) {
self.onconnect = function(event) {
const port = event.ports[0];
port.onmessage = function (e: MessageEvent<ControlMsg>) {
if (isDevMode()) {
console.log('Received message in SharedWorker:', e.data);
}
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,7 +3,9 @@ import zh from '@/locales/zh'
import en from '@/locales/en'
// const locale = localStorage.getItem('lang') || 'zh';
export const locale = 'zh';
const locale = 'zh';
console.log("langggggg:", locale);
const i18n = createI18n({
globalInjection: true,

View File

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

View File

@ -1,14 +0,0 @@
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,20 +1,17 @@
export default {
disconnected: "未连接",
connected: "已连接",
connecting: "连接中",
DISCONNECTED: "未连接",
CONNECTED: "已连接",
CONNECTING: "连接中",
ws: {
disconnected: "未连接",
connected: "已连接",
connecting: "连接中",
WS: {
DISCONNECTED: "未连接",
CONNECTED: "已连接",
CONNECTING: "连接中",
},
page: {
home: "主页",
wifi: "Wi-Fi",
about: "关于",
uart: "UART透传",
feedback: "反馈",
close: "关闭",
PAGE: {
HOME: "主页",
ABOUT: "关于",
FEEDBACK: "反馈",
},
}

View File

@ -1,9 +1,4 @@
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'
@ -15,7 +10,7 @@ import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(i18n);
app.use(router)
app.use(i18n);
app.mount('#app')

View File

@ -1,54 +1,29 @@
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 Page404 from '@/views/404.vue'
import {translate} from "@/locales";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
meta: {title: translate("page.home")},
// component: Wifi
redirect: () => '/wifi',
}, {
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,
}, {
path: '/:catchAll(.*)', // This will match all paths that aren't matched by above routes
name: 'NotFound',
component: Page404,
},
]
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')
}
]
})
router.beforeEach((to, from, next) => {
document.title = typeof to.meta.title === 'string' ? to.meta.title + " | 允斯工作室" : '允斯调试器';
next();
});
export default router

View File

@ -1,76 +0,0 @@
import type {ApiJsonMsg, ControlMsg, ServerMsg} from "@/api";
import {isDevMode} from "@/composables/buildMode";
import {type ApiBinaryMsg, decodeHeader} from "@/api/binDataDef";
export interface IModuleCallback {
ctrlCallback: (msg: ControlMsg) => void;
serverJsonMsgCallback: (msg: ApiJsonMsg) => void;
serverBinMsgCallback: (msg: ApiBinaryMsg) => 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") {
let jsonMsg: ApiJsonMsg;
try {
jsonMsg = JSON.parse(msg.data as string) as ApiJsonMsg;
if (jsonMsg.cmd === undefined ||
jsonMsg.module === undefined
){
console.log("Server msg has no cmd or module", msg.data);
return;
}
} catch (e) {
console.log(e);
return;
}
const module = jsonMsg.module;
const moduleHandler = moduleMap.get(module);
if (moduleHandler) {
moduleHandler.serverJsonMsgCallback(jsonMsg);
} else {
if (isDevMode()) {
console.log("routeModuleServerMsg module not loaded", module);
}
}
} else {
const arr = msg.data as ArrayBuffer;
if (arr.byteLength < 4) {
if (isDevMode()) {
console.log("binary message too short");
}
return;
}
const binaryMsg = decodeHeader(msg.data as ArrayBuffer);
const moduleHandler = moduleMap.get(binaryMsg.module);
if (moduleHandler) {
moduleHandler.serverBinMsgCallback(binaryMsg);
} else {
if (isDevMode()) {
console.log("routeModuleServerMsg ignored:", msg, binaryMsg);
}
}
}
}
export function routeCtrlMsg(msg: ControlMsg) {
for (const item of moduleMap) {
item[1].ctrlCallback(msg);
}
}

View File

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

View File

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

View File

@ -1,17 +0,0 @@
<script setup lang="ts">
</script>
<template>
<div class="text-layout">
<h1 class="page-title">404</h1>
<h2 class="text-center">页面不存在</h2>
<RouterLink to="/">
<el-card class="text-center">返回首页</el-card>
</RouterLink>
</div>
</template>
<style scoped>
</style>

View File

@ -1,77 +1,12 @@
<script setup lang="ts">
const version = import.meta.env.VITE_APP_GIT_TAG || "v0.0.0";
const compileTime = import.meta.env.VITE_APP_LAST_COMMIT || "1970-00-00";
</script>
<template>
<div class="text-layout">
<el-divider></el-divider>
<el-divider>关于</el-divider>
<el-divider></el-divider>
<el-collapse>
<el-collapse-item title="关于网页版上位机">
<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 target="_blank" 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 target="_blank" 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 target="_blank" href="https://github.com/vuejs/pinia/blob/v2/LICENSE">MIT</a>
</el-descriptions-item>
<el-descriptions-item label="mitt"><a target="_blank" 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 target="_blank" 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-collapse-item>
<el-collapse-item title="关于下位机">
<el-descriptions border :column="1" class="mt-5 description-style">
<el-descriptions-item label="官网"><a target="_blank" href="https://yunsi.studio/wireless-proxy">允斯工作室</a></el-descriptions-item>
<el-descriptions-item label="版本">-</el-descriptions-item>
</el-descriptions>
<el-descriptions title="鸣谢" border :column="1" class="mt-5 description-style">
<el-descriptions-item label="windowsair"><a target="_blank" href="https://github.com/windowsair/wireless-esp8266-dap">wireless-esp8266-dap</a>
</el-descriptions-item>
</el-descriptions>
</el-collapse-item>
</el-collapse>
<el-descriptions title="作者:空空(kerms)" border :column="1" class="mt-5 description-style">
<el-descriptions-item label="官网"><a target="_blank" href="https://yunsi.studio/">允斯工作室https://yunsi.studio/</a></el-descriptions-item>
<el-descriptions-item label="github"><a target="_blank" 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 target="_blank" href="https://space.bilibili.com/3461571571353885">3461571571353885</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>
<h2>About Page</h2>
<!-- Your about page content goes here -->
<h2>About Page</h2>
</template>
<style scoped>
.description-style :deep(.el-descriptions__label) {
@apply w-32
}
</style>
<script setup lang="ts">
let name = "about-a";
</script>

View File

@ -1,20 +0,0 @@
<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,18 +1,23 @@
<template>
<div class="text-layout">
<h2 class="page-title">主页</h2>
<router-link to="/wifi">
<el-card>
<p class="text-center">Wi-Fi设置</p>
</el-card>
</router-link>
</div>
<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>
</template>
<script setup lang="ts">
document.title = "Home";
import InlineSvg from "@/components/InlineSvg.vue";
</script>
</script>
<style scoped>
</style>

View File

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

View File

@ -1,12 +1,7 @@
<template>
<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">
<div class="wifiView">
<h2>Wifi View</h2>
<el-form label-width="auto" ref="formRef" :model="ssidValidateForm">
<el-form-item
label="Wi-Fi名"
prop="wifiSsid"
@ -24,328 +19,129 @@
value-key="ssid"
>
<template #default="{ item }">
<div class="flex items-center border-b">
<div class="flex">
<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">{{ scanText }}</el-button>
<el-button class="h-8" @click="onScanClick">扫描</el-button>
</div>
</div>
</el-form-item>
<el-form-item label="密码">
<el-input
v-model="ssidValidateForm.password"
v-model="password"
show-password
type="password"
clearable
/>
</el-form-item>
<div class="mb-2">
<el-alert type="info" show-icon>
如果不是通过透传器的热点连接更换Wi-Fi将导致此界面与透传器断开连接
</el-alert>
</div>
<div class="flex justify-center">
<el-button @click="onConnectClick" type="primary">连接</el-button>
<el-button @click="onConnect" 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 {computed, onMounted, onUnmounted, reactive, ref} from "vue";
import {
wifi_sta_get_ap_info,
wifi_get_scan_list,
WifiCmd,
type WifiInfo,
type WifiList,
wifi_ap_get_info, wifi_connect_to
} from "@/api/apiWifi";
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 {
ControlEvent,
ControlMsgType
} from "@/composables/broadcastChannelDef";
import InlineSvg from "@/components/InlineSvg.vue";
import type {ApiJsonMsg, ControlMsg, ServerMsg} from "@/api";
import {ControlEvent, ControlMsgType, WtModuleID} from "@/api";
import {registerModule, unregisterModule} from "@/router/msgRouter";
import {useWsStore} from "@/stores/websocket";
import {globalNotify, globalNotifyRightSide} from "@/composables/notification";
import {getWebsocketService} from "@/composables/websocket/websocketService";
const formRef = ref<FormInstance>()
let wifiListPlaceholder = ref("我的WIFI")
let ssidValidateForm = reactive({
wifiSsid: "",
password: "",
wifiSsid: ""
})
const password = ref('')
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 scanning = false;
let scan_cb: any;
let connectBtnClicked = 0;
let options: Array<WifiInfo> = [];
const scanText = computed(() => {
return scanning.value ? "扫描中" : "扫描";
});
let options: Array<WifiInfo> = [
]
const querySearch = (queryString: string, cb: any) => {
if (scanning.value) {
if (scanning) {
scan_cb = cb;
} else {
cb(options);
}
}
const onClientMsg = (msg: ApiJsonMsg) => {
switch (msg.cmd as WifiCmd) {
case WifiCmd.UNKNOWN:
break;
case WifiCmd.WIFI_API_JSON_STA_GET_AP_INFO: {
const info = msg as WifiInfo;
if (info.rssi === 0) {
Object.assign(wifiStaApInfo, defWifiInfo);
} else {
Object.assign(wifiStaApInfo, info);
}
if (connectBtnClicked) {
connectBtnClicked = 0;
globalNotifyRightSide(wifiStaApInfo.ssid + " 连接成功", "success");
}
break;
}
case WifiCmd.WIFI_API_JSON_CONNECT:
break;
case WifiCmd.WIFI_API_JSON_GET_SCAN: {
const list = msg 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 as WifiInfo;
Object.assign(wifiApInfo, info);
break;
const onClientMsg = (ev: MessageEvent<ServerMsg>) => {
if (ev.data.type !== "json") {
return;
}
const json: object = ev.data.data;
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";
}
});
options = wifiList.scan_list;
if (scan_cb) {
scan_cb(options);
scan_cb = null;
}
};
const onClientCtrl = (msg: ControlMsg) => {
if (msg.type !== ControlMsgType.WS_EVENT) {
const onClientCtrl = (ev: MessageEvent<ControlMsg>) => {
if (ev.data.type !== ControlMsgType.WS_EVENT) {
return
}
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();
if (ev.data.data === ControlEvent.CONNECTED) {
wifi_get_ap_info();
}
};
function onScanClick() {
if (wsStore.state !== ControlEvent.CONNECTED) {
globalNotify("调试器未连接", 'error');
return;
}
scanning.value = true;
scanning = true;
wifi_get_scan_list();
}
function onConnectClick() {
if (wsStore.state !== ControlEvent.CONNECTED) {
globalNotify("调试器未连接", 'error');
return;
}
if (ssidValidateForm.wifiSsid !== "") {
wifi_connect_to(ssidValidateForm.wifiSsid, ssidValidateForm.password);
connectBtnClicked = 1;
}
function onConnect() {
console.log(ssidValidateForm.wifiSsid, password.value);
}
onMounted(() => {
registerModule(WtModuleID.WIFI, {
ctrlCallback: onClientCtrl,
serverJsonMsgCallback: onClientMsg,
serverBinMsgCallback: () => {},
});
wifi_sta_get_ap_info();
wifi_ap_get_info();
});
onUnmounted(() => {
unregisterModule(WtModuleID.WIFI);
});
@ -353,7 +149,10 @@ onUnmounted(() => {
<style scoped>
.description-style :deep(.el-descriptions__label) {
@apply w-32
.wifiView {
background-color: bisque;
border-radius: 5px;
padding: 20px;
}
</style>

View File

@ -1,54 +1,44 @@
<template>
<nav class="relative px-2 py-0.5 sm:py-1 flex justify-between items-center border-b h-full">
<nav class="relative px-4 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 pl-1 mx-4">
<svg class="block h-3 lg:h-4 lg:w-4 fill-current" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<title>导航侧栏</title>
<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>
<path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"></path>
</svg>
</button>
<router-link to="/" class="text-3xl px-4 font-bold leading-none hidden items-center sm:flex" title="走,去码头整点薯条">
<InlineSvg name="favicon" class="h-5 lg:h-8"></InlineSvg>
<router-link to="/" class="text-3xl px-4 font-bold leading-none">
<InlineSvg name="home" 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 class="flex pt-0.5 sm:pt-1 ml-4 text-sm items-center sm:hidden">
<router-link :to="route.fullPath">{{ route.meta.title }}</router-link>
</div>
</div>
<div class="flex">
<ul class="hidden absolute top-1/2 left-1/2 transform -translate-y-1/2 -translate-x-1/2 sm:flex sm:mx-auto sm:items-center sm:w-auto sm:space-x-6">
<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 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>-->
</ul>
</div>
<!-- <a class="md:ml-auto md:mr-3"></a>-->
<div class="flex h-full">
<div id="page-spec-slot" class="content-center h-full flex flex-row"></div>
<div class="lg:hidden">
<el-button :type="wsColor" size="small" class="transition duration-1000 min-h-full">
<InlineSvg v-show="wsColor!=='success'" name="link-off" class="mr-2" width="20"></InlineSvg>
<InlineSvg v-show="wsColor==='success'" name="link" class="mr-2" width="20"></InlineSvg>
<div class="text-xs sm:text-sm lg:text-base">{{ wsState }}</div>
</el-button>
</div>
<div class="hidden lg:flex">
<el-button :type="wsColor" size="large" class="transition duration-1000 min-h-full">
<InlineSvg v-show="wsColor!=='success'" name="link-off" class="mr-2" width="20"></InlineSvg>
<InlineSvg v-show="wsColor==='success'" name="link" class="mr-2" width="20"></InlineSvg>
<div class="text-base">{{ wsState }}</div>
</el-button>
</div>
<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>-->
</div>
</nav>
<div :class='["custom-drawer", {open: sideMenuOpen}]'>
@ -56,33 +46,26 @@
v-model="sideMenuOpen"
:with-header="false"
size=""
:direction="'ltr'"
>
<div :class="[sideMenuItemClass]" class="pr-6 flex text-gray-500" @click="sideMenuOpen=false">
<InlineSvg name="cross" class="h-6"></InlineSvg>
: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">
<div>
<p class="h-6 flex items-center">{{ $t("page.close") }}</p>
<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>
</div>
<div class="mt-auto">
<p class="my-4 text-xs text-center text-gray-400">
<span>Copyright kerms 2024</span>
</p>
</div>
</div>
<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>
<el-button @click="toggle">
<InlineSvg v-if="!isFullscreen" name="open-in-full" width="16px" fill="#000000"></InlineSvg>
<p v-if="!isFullscreen">全屏</p>
<InlineSvg v-if="isFullscreen" name="close-fullscreen" width="16px" fill="#000000"></InlineSvg>
<p v-if="isFullscreen">缩小</p>
</el-button>
</div>
</template>
</el-drawer>
</div>
@ -96,7 +79,12 @@
<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>
@ -105,60 +93,41 @@
<script lang="ts" setup>
import InlineSvg from "@/components/InlineSvg.vue";
import {computed, ref} from "vue";
import {computed, ref, toRef} from "vue";
import {useWsStore} from "@/stores/websocket";
import {translate} from "@/locales";
import {ControlEvent} from "@/api";
import {useRoute} from "vue-router";
import { useFullscreen } from '@vueuse/core'
import {useI18n} from "vue-i18n";
const { t } = useI18n()
const wsStore = useWsStore();
const {isFullscreen, toggle} = useFullscreen();
const route = useRoute();
const sideMenuItemClass = "block p-4 text-sm font-semibold hover:bg-blue-50 hover:text-blue-600 rounded"
const sideMenuItemClass = "block p-4 text-sm font-semibold text-gray-400 hover:bg-blue-50 hover:text-blue-600 rounded"
const sideMenuOpen = ref(false);
const stateMenuOpen = ref(false)
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;
});
const wsState = computed(() => {
return translate(wsStore.state);
return t(wsStore.state);
});
type Item = {
name: string;
href: string;
class?: string;
};
const menuItems: Item[] = ([
/* {
name: translate("page.home"),
const menuItems = ([
{
name: "Home",
href: "/",
}, */{
name: translate("page.wifi"),
href: "/wifi",
}, {
name: translate("page.about"),
name: "About Us",
href: "/about",
}, {
name: translate("page.feedback"),
href: "/feedback",
name: "Services",
href: "/",
}, {
name: "Wifi",
href: "/wifi",
}, {
name: "Contact",
href: "/",
}, {
name: "6",
href: "/",
},
]);
@ -170,23 +139,18 @@ const menuItems: Item[] = ([
border: solid 1px;
}*/
/* drawer */
.custom-drawer :deep(.el-drawer) {
transition: all 0.1s; /* Custom duration*/
}
/* drawer overlay */
.custom-drawer :deep(.el-overlay) {
.custom-drawer.open :deep(.el-overlay) {
transition: all 0s; /* Custom duration*/
}
.custom-drawer :deep(.el-drawer) {
transition: all 0s; /* Custom duration*/
}
.custom-drawer.open :deep(.el-drawer) {
transition: all 0.05s; /* Custom duration*/
}
.custom-drawer :deep(.el-drawer__body) {
padding: 0;
}
</style>

View File

@ -2,118 +2,76 @@ import { fileURLToPath, URL } from 'node:url'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import {ConfigEnv, defineConfig, loadEnv} from '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'
// https://vitejs.dev/config/
export default ({mode}: ConfigEnv) => {
process.env = {...process.env, ...loadEnv(mode, process.cwd())};
return defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
svgLoader(),
cssInjectedByJsPlugin(),
viteSingleFile(),
],
define: {},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
cacheDir: process.env.VITE_CACHE_DIR || undefined,
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: process.env.VITE_OUTPUT_DIR || undefined,
emptyOutDir: true,
cssMinify: 'lightningcss',
rollupOptions: {
output: {
inlineDynamicImports: true,
minifyInternalExports: true,
assetFileNames: (assetInfo) => {
if (!assetInfo || !assetInfo.name) {
return 'default-filename.ext';
}
const info = assetInfo.name.split(".");
let extType = info[info.length - 1];
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
extType = "img";
} else if (/woff|woff2/.test(extType)) {
extType = "css";
} else if (/css/.test(extType)) {
extType = "css";
return "style.css"
}
console.log(assetInfo)
return `[name]-[hash][extname]`;
},
// chunkFileNames: "[name]-[hash].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)
if (chunkInfo.name.includes("shared")) {
console.log(chunkInfo.name);
}
return "script.js";
},
sourcemapFileNames: "map-[name].js",
// sanitizeFileName: "anit-[name].js",
// entryFileNames: (chunkInfo) => {
// 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: undefined,
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
svgLoader(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
build: {
outDir: '/tmp/zhuang/dap-web-dist/',
emptyOutDir: true,
cssMinify: 'lightningcss',
rollupOptions: {
output: {
assetFileNames: (assetInfo) => {
if (!assetInfo || !assetInfo.name) {
return 'default-filename.ext';
}
const info = assetInfo.name.split(".");
let extType = info[info.length - 1];
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
extType = "img";
} else if (/woff|woff2/.test(extType)) {
extType = "css";
} else if (/css/.test(extType)) {
extType = "css";
return "style.css"
}
// return `[name]-[hash][extname]`;
return `[name][extname]`;
},
}
},
})
};
// chunkFileNames: "[name]-[hash].js",
chunkFileNames: "[name].js",
// entryFileNames: "[name]-[hash].js",
entryFileNames: (chunkInfo) => {
// console.log(chunkInfo)
return "script.js"
},
sourcemapFileNames: "map-[name].js",
// sanitizeFileName: "anit-[name].js",
// entryFileNames: (chunkInfo) => {
// 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'
}
},
},
}
},
})