Compare commits
10 Commits
1bae314449
...
3fbb21aa1d
Author | SHA1 | Date |
---|---|---|
|
3fbb21aa1d | |
|
10bea9c95e | |
|
2f2982d4af | |
|
7b2c05bc57 | |
|
275f3ac859 | |
|
5b1304e927 | |
|
7d2f3868aa | |
|
bb169b53e1 | |
|
63e1e1c5f3 | |
|
c7d1dff0d0 |
|
@ -0,0 +1 @@
|
|||
VITE_APP_MODE=dev
|
|
@ -0,0 +1 @@
|
|||
VITE_APP_MODE=prod
|
|
@ -28,4 +28,9 @@ coverage
|
|||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
package-lock.json
|
||||
components.d.ts
|
||||
auto-imports.d.ts
|
||||
|
||||
# Personal
|
||||
**/_priv_*
|
||||
Makefile
|
|
@ -0,0 +1,21 @@
|
|||
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.
|
|
@ -1,9 +0,0 @@
|
|||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
export {}
|
||||
declare global {
|
||||
|
||||
}
|
|
@ -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,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>
|
||||
|
|
14
package.json
|
@ -4,16 +4,19 @@
|
|||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev": ". ./set_env.sh && vite",
|
||||
"devh": ". ./set_env.sh && vite --host",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"preview": ". ./set_env.sh && vite preview",
|
||||
"build-only": ". ./set_env.sh && vite build",
|
||||
"build:dev": ". ./set_env.sh && 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/"
|
||||
},
|
||||
"dependencies": {
|
||||
"element-plus": "^2.6.1",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"element-plus": "^2.7.3",
|
||||
"mitt": "^3.0.1",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.4.21",
|
||||
|
@ -40,6 +43,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"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
export VITE_APP_GIT_TAG=$(git describe --tags | cut -d'-' -f1,2)
|
||||
export VITE_APP_LAST_COMMIT=$(git log -1 --format=%cd)
|
54
src/App.vue
|
@ -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);
|
||||
}
|
||||
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);
|
||||
};
|
||||
|
||||
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 = import.meta.env.VITE_DEVICE_HOST_NAME || "dap.local";
|
||||
} else {
|
||||
host = window.location.host
|
||||
}
|
||||
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>
|
||||
|
||||
<p class="m-0">This is body</p>
|
||||
<RouterView/>
|
||||
<p>end</p>
|
||||
<!-- <p>{{ test }}</p>-->
|
||||
<el-button type="danger"><p>test</p></el-button>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.app {
|
||||
background-color: #ddd;
|
||||
box-shadow: 0 0 10px;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.router-active {
|
||||
background-color: #666;
|
||||
cursor: default;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
import {sendJsonMsg} from '@/composables/broadcastChannelDef'
|
||||
import {type ApiJsonMsg, sendJsonMsg, WtModuleID} from '@/api'
|
||||
|
||||
|
||||
import {type ApiJsonMsg} from '@/api'
|
||||
|
||||
const WifiModuleID = 1;
|
||||
enum WifiCmd {
|
||||
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 {
|
||||
|
@ -19,23 +16,31 @@ interface WifiMsgOut extends ApiJsonMsg {
|
|||
|
||||
export function wifi_get_scan_list() {
|
||||
const msg : WifiMsgOut = {
|
||||
module: WifiModuleID,
|
||||
module: WtModuleID.WIFI,
|
||||
cmd: WifiCmd.WIFI_API_JSON_GET_SCAN,
|
||||
}
|
||||
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,
|
||||
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,
|
||||
}
|
||||
sendJsonMsg(msg);
|
||||
}
|
||||
|
||||
export function wifi_connect_to(ssid: string, password: string) {
|
||||
const msg: WifiMsgOut = {
|
||||
module: WifiModuleID,
|
||||
module: WtModuleID.WIFI,
|
||||
cmd: WifiCmd.WIFI_API_JSON_CONNECT,
|
||||
ssid: ssid,
|
||||
password: password,
|
||||
|
@ -43,13 +48,16 @@ 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;
|
||||
}
|
||||
|
||||
export interface WifiList {
|
||||
export interface WifiList extends ApiJsonMsg {
|
||||
scan_list: Array<WifiInfo>;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
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,
|
||||
};
|
||||
}
|
|
@ -1,4 +1,60 @@
|
|||
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);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<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>
|
After Width: | Height: | Size: 206 B |
|
@ -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 |
|
@ -0,0 +1 @@
|
|||
<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>
|
After Width: | Height: | Size: 667 B |
|
@ -0,0 +1 @@
|
|||
<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>
|
After Width: | Height: | Size: 712 B |
|
@ -0,0 +1 @@
|
|||
<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>
|
After Width: | Height: | Size: 171 B |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -0,0 +1,3 @@
|
|||
.todo-menu-item {
|
||||
@apply opacity-30
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
.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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export function isDevMode() {
|
||||
return import.meta.env.VITE_APP_MODE === 'dev';
|
||||
}
|
|
@ -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}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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" +
|
||||
"大概是一起去整点赛博薯条吧。");
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
}
|
|
@ -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 {
|
||||
if (isDevMode()) {
|
||||
console.log("Websocket Service INIT called", WebsocketClassic.count);
|
||||
|
||||
}
|
||||
this.socket.init(host, msgCallback, ctrlCallback);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,9 +57,15 @@ 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();
|
||||
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.heartBeatTimeCount++;
|
||||
}, 2000);
|
||||
|
@ -69,51 +81,40 @@ class OneTimeWebsocket implements IWebsocket {
|
|||
return
|
||||
|
||||
const msg: ServerMsg = {
|
||||
data: {},
|
||||
data: ev.data,
|
||||
type: "json",
|
||||
}
|
||||
this.msgCallback(msg);
|
||||
if (typeof ev.data === "string") {
|
||||
try {
|
||||
msg.data = JSON.parse(ev.data);
|
||||
this.msgCallback(msg);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
msg.type = "json"
|
||||
} else {
|
||||
console.log(typeof ev.data);
|
||||
msg.type = "binary";
|
||||
}
|
||||
this.msgCallback(msg);
|
||||
}
|
||||
|
||||
this.socket.onclose = () => {
|
||||
console.log('WebSocket Disconnected');
|
||||
|
||||
clearInterval(this.intervalId);
|
||||
this.socket.onclose = (ev) => {
|
||||
if (isDevMode()) {
|
||||
console.log("ws closed", ev.reason, ev.code);
|
||||
}
|
||||
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,
|
||||
|
@ -137,13 +138,26 @@ class OneTimeWebsocket implements IWebsocket {
|
|||
if (this.socket.readyState !== WebSocket.OPEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDevMode()) {
|
||||
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.socket.send(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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,7 +199,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 +207,7 @@ export class WebsocketWrapper {
|
|||
}
|
||||
this.timeoutId = setTimeout(() =>
|
||||
this.newConnection(this.host, this.msgCallback, this.ctrlCallback),
|
||||
2000);
|
||||
1000);
|
||||
}
|
||||
|
||||
deinit() {
|
||||
|
@ -204,7 +217,6 @@ export class WebsocketWrapper {
|
|||
}
|
||||
|
||||
send(msg: ServerMsg) {
|
||||
console.log('WebSocket send: not ready', msg);
|
||||
// this.socket.send(msg)
|
||||
this.socket.send(msg)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>) {
|
||||
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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-2">
|
||||
<slot/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -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());
|
||||
}
|
|
@ -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: "关闭",
|
||||
},
|
||||
}
|
|
@ -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')
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
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),
|
||||
|
@ -8,22 +14,41 @@ const router = createRouter({
|
|||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: Home
|
||||
meta: {title: translate("page.home")},
|
||||
// component: Wifi
|
||||
redirect: () => '/wifi',
|
||||
}, {
|
||||
path: '/home:ext(.*)',
|
||||
component: Home,
|
||||
meta: {title: translate("page.home")},
|
||||
redirect: () => '/',
|
||||
}, {
|
||||
path: '/wifi:ext(.*)',
|
||||
meta: {title: translate('page.wifi')},
|
||||
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')
|
||||
}
|
||||
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,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
document.title = typeof to.meta.title === 'string' ? to.meta.title + " | 允斯工作室" : '允斯调试器';
|
||||
next();
|
||||
});
|
||||
|
||||
export default router
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import {defineStore} from "pinia";
|
||||
import {ControlEvent} from "@/composables/broadcastChannelDef";
|
||||
|
||||
import {ControlEvent} from "@/api";
|
||||
|
||||
export const useWsStore = defineStore('websocket', {
|
||||
state: () => {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import mitt from 'mitt';
|
||||
|
||||
const emitter = mitt();
|
||||
|
||||
export default emitter;
|
|
@ -0,0 +1,17 @@
|
|||
<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>
|
|
@ -1,12 +1,77 @@
|
|||
<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>
|
||||
<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-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>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.description-style :deep(.el-descriptions__label) {
|
||||
@apply w-32
|
||||
}
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
let name = "about-a";
|
||||
|
||||
</script>
|
||||
</style>
|
|
@ -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>
|
|
@ -1,23 +1,18 @@
|
|||
<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>
|
||||
|
||||
<router-link to="/wifi">
|
||||
<el-card>
|
||||
<p class="text-center">Wi-Fi设置</p>
|
||||
</el-card>
|
||||
</router-link>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
document.title = "Home";
|
||||
|
||||
|
||||
import InlineSvg from "@/components/InlineSvg.vue";
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="text-layout">
|
||||
<h2 class="page-title opacity-10">尽请期待</h2>
|
||||
</div>
|
||||
</template>
|
|
@ -1,7 +1,12 @@
|
|||
<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,89 +24,254 @@
|
|||
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
|
||||
/>
|
||||
</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="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>
|
||||
<el-button class="h-8">扫描</el-button>
|
||||
<el-button type="primary">连接</el-button>
|
||||
</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>
|
||||
|
||||
</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,
|
||||
wifi_ap_get_info, wifi_connect_to
|
||||
} 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, WtModuleID} 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 connectBtnClicked = 0;
|
||||
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") {
|
||||
return;
|
||||
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);
|
||||
}
|
||||
|
||||
const json: object = ev.data.data;
|
||||
|
||||
let wifiList: WifiList;
|
||||
try {
|
||||
wifiList = ev.data.data as WifiList;
|
||||
console.log(wifiList);
|
||||
} catch (e) {
|
||||
return;
|
||||
if (connectBtnClicked) {
|
||||
connectBtnClicked = 0;
|
||||
globalNotifyRightSide(wifiStaApInfo.ssid + " 连接成功", "success");
|
||||
}
|
||||
scanning = false;
|
||||
wifiList.scan_list.forEach(value => {
|
||||
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) {
|
||||
|
@ -110,38 +280,72 @@ const onClientMsg = (ev: MessageEvent<ServerMsg>) => {
|
|||
value.wifiLogo = "wifi-1";
|
||||
}
|
||||
});
|
||||
options = wifiList.scan_list;
|
||||
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 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;
|
||||
}
|
||||
if (ssidValidateForm.wifiSsid !== "") {
|
||||
wifi_connect_to(ssidValidateForm.wifiSsid, ssidValidateForm.password);
|
||||
connectBtnClicked = 1;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
registerModule(WtModuleID.WIFI, {
|
||||
ctrlCallback: onClientCtrl,
|
||||
serverJsonMsgCallback: onClientMsg,
|
||||
serverBinMsgCallback: () => {},
|
||||
});
|
||||
wifi_sta_get_ap_info();
|
||||
wifi_ap_get_info();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
unregisterModule(WtModuleID.WIFI);
|
||||
});
|
||||
|
||||
|
||||
|
@ -149,10 +353,7 @@ onUnmounted(() => {
|
|||
|
||||
|
||||
<style scoped>
|
||||
.wifiView {
|
||||
background-color: bisque;
|
||||
border-radius: 5px;
|
||||
padding: 20px;
|
||||
.description-style :deep(.el-descriptions__label) {
|
||||
@apply w-32
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,44 +1,54 @@
|
|||
<template>
|
||||
<nav class="relative px-4 py-1 flex justify-between items-center border-b">
|
||||
<nav class="relative px-2 py-0.5 sm:py-1 flex justify-between items-center border-b h-full">
|
||||
<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>
|
||||
<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>
|
||||
<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 hidden items-center sm:flex" title="走,去码头整点薯条">
|
||||
<InlineSvg name="favicon" class="h-5 lg:h-8"></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 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 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>
|
||||
</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>-->
|
||||
<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>
|
||||
</nav>
|
||||
<div :class='["custom-drawer", {open: sideMenuOpen}]'>
|
||||
|
@ -46,26 +56,33 @@
|
|||
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 :class="[sideMenuItemClass]" class="pr-6 flex text-gray-500" @click="sideMenuOpen=false">
|
||||
<InlineSvg name="cross" class="h-6"></InlineSvg>
|
||||
<div>
|
||||
<p class="h-6 flex items-center">{{ $t("page.close") }}</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.name }}</router-link>
|
||||
<!-- <a :href="item.href" :class="sideMenuItemClass">{{ item.name }}</a>-->
|
||||
<router-link @click="sideMenuOpen=false" :title="item.name" :to="item.href" :class="[sideMenuItemClass, item?.class]">{{ item.name }}</router-link>
|
||||
</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>
|
||||
<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>
|
||||
|
||||
|
@ -79,12 +96,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 +105,60 @@
|
|||
|
||||
<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";
|
||||
import {useRoute} from "vue-router";
|
||||
import { useFullscreen } from '@vueuse/core'
|
||||
|
||||
const { t } = useI18n()
|
||||
const wsStore = useWsStore();
|
||||
const {isFullscreen, toggle} = useFullscreen();
|
||||
const route = useRoute();
|
||||
|
||||
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(() => {
|
||||
|
||||
return t(wsStore.state);
|
||||
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 menuItems = ([
|
||||
{
|
||||
name: "Home",
|
||||
const wsState = computed(() => {
|
||||
return translate(wsStore.state);
|
||||
});
|
||||
|
||||
type Item = {
|
||||
name: string;
|
||||
href: string;
|
||||
class?: string;
|
||||
};
|
||||
|
||||
const menuItems: Item[] = ([
|
||||
/* {
|
||||
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",
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -139,18 +170,23 @@ const menuItems = ([
|
|||
border: solid 1px;
|
||||
}*/
|
||||
|
||||
/* drawer */
|
||||
.custom-drawer :deep(.el-drawer) {
|
||||
transition: all 0.1s; /* Custom duration*/
|
||||
}
|
||||
|
||||
/* drawer overlay */
|
||||
.custom-drawer.open :deep(.el-overlay) {
|
||||
.custom-drawer :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>
|
|
@ -2,13 +2,18 @@ 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 { defineConfig } from 'vite'
|
||||
import {ConfigEnv, defineConfig, loadEnv} 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 defineConfig({
|
||||
export default ({mode}: ConfigEnv) => {
|
||||
process.env = {...process.env, ...loadEnv(mode, process.cwd())};
|
||||
|
||||
return defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({
|
||||
|
@ -18,18 +23,43 @@ export default defineConfig({
|
|||
resolvers: [ElementPlusResolver()],
|
||||
}),
|
||||
svgLoader(),
|
||||
cssInjectedByJsPlugin(),
|
||||
viteSingleFile(),
|
||||
],
|
||||
define: {},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
},
|
||||
build: {
|
||||
outDir: '/tmp/zhuang/dap-web-dist/',
|
||||
emptyOutDir: true,
|
||||
cssMinify: 'lightningcss',
|
||||
|
||||
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';
|
||||
|
@ -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,17 +101,19 @@ 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,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
};
|
||||
|
|