initial commit
|
@ -0,0 +1,18 @@
|
||||||
|
/* eslint-env node */
|
||||||
|
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
'extends': [
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'eslint:recommended',
|
||||||
|
'@vue/eslint-config-typescript',
|
||||||
|
'@vue/eslint-config-prettier/skip-formatting'
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'vue/multi-word-component-names': "off"
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea/
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
package-lock.json
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"semi": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"trailingComma": "none"
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
// Generated by unplugin-auto-import
|
||||||
|
export {}
|
||||||
|
declare global {
|
||||||
|
|
||||||
|
}
|
|
@ -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']
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"name": "vue-project",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
|
"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": {
|
||||||
|
"element-plus": "^2.6.1",
|
||||||
|
"mitt": "^3.0.1",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
|
"vue": "^3.4.21",
|
||||||
|
"vue-i18n": "^9.10.2",
|
||||||
|
"vue-router": "^4.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rushstack/eslint-patch": "^1.3.3",
|
||||||
|
"@tsconfig/node20": "^20.1.2",
|
||||||
|
"@types/node": "^20.11.28",
|
||||||
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
|
"@vue/eslint-config-typescript": "^12.0.0",
|
||||||
|
"@vue/tsconfig": "^0.5.1",
|
||||||
|
"autoprefixer": "^10.4.19",
|
||||||
|
"eslint": "^8.49.0",
|
||||||
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
|
"lightningcss": "^1.24.1",
|
||||||
|
"npm-run-all2": "^6.1.2",
|
||||||
|
"postcss": "^8.4.38",
|
||||||
|
"prettier": "^3.0.3",
|
||||||
|
"tailwindcss": "^3.4.1",
|
||||||
|
"typescript": "~5.4.0",
|
||||||
|
"unplugin-auto-import": "^0.17.5",
|
||||||
|
"unplugin-vue-components": "^0.26.0",
|
||||||
|
"vite": "^5.1.6",
|
||||||
|
"vite-svg-loader": "^5.1.0",
|
||||||
|
"vue-tsc": "^2.0.6"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +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";
|
||||||
|
|
||||||
|
const wsState = useWsStore();
|
||||||
|
|
||||||
|
const onClientCtrl = (msg: ControlMsg) => {
|
||||||
|
console.log("App.vue:", msg);
|
||||||
|
if (msg.type === ControlMsgType.WS_EVENT) {
|
||||||
|
wsState.$patch({state: msg.data as ControlEvent})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onServerMsg = (msg: ServerMsg) => {
|
||||||
|
console.log("App.vue:", msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
let websocketService: IWebsocketService;
|
||||||
|
onMounted(() => {
|
||||||
|
// const host = window.location
|
||||||
|
console.log("App.vue mounted")
|
||||||
|
|
||||||
|
const host = "192.168.43.61";
|
||||||
|
websocketService = getWebsocketService();
|
||||||
|
websocketService.init(host, onServerMsg, onClientCtrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
import NavBar from "@/views/navigation/NavBar.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<header>
|
||||||
|
<nav-bar/>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<p class="m-0">This is body</p>
|
||||||
|
<RouterView/>
|
||||||
|
<p>end</p>
|
||||||
|
<!-- <p>{{ test }}</p>-->
|
||||||
|
<el-button type="danger"><p>test</p></el-button>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.app {
|
||||||
|
background-color: #ddd;
|
||||||
|
box-shadow: 0 0 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.router-active {
|
||||||
|
background-color: #666;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,55 @@
|
||||||
|
import {sendJsonMsg} from '@/composables/broadcastChannelDef'
|
||||||
|
|
||||||
|
|
||||||
|
import {type ApiJsonMsg} from '@/api'
|
||||||
|
|
||||||
|
const WifiModuleID = 1;
|
||||||
|
enum WifiCmd {
|
||||||
|
UNKNOWN = 0,
|
||||||
|
WIFI_API_JSON_GET_AP_INFO,
|
||||||
|
WIFI_API_JSON_CONNECT,
|
||||||
|
WIFI_API_JSON_GET_SCAN,
|
||||||
|
WIFI_API_JSON_DISCONNECT,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WifiMsgOut extends ApiJsonMsg {
|
||||||
|
ssid?: string;
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wifi_get_scan_list() {
|
||||||
|
const msg : WifiMsgOut = {
|
||||||
|
module: WifiModuleID,
|
||||||
|
cmd: WifiCmd.WIFI_API_JSON_GET_SCAN,
|
||||||
|
}
|
||||||
|
sendJsonMsg(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wifi_get_ap_info() {
|
||||||
|
const msg : WifiMsgOut = {
|
||||||
|
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: WifiModuleID,
|
||||||
|
cmd: WifiCmd.WIFI_API_JSON_CONNECT,
|
||||||
|
ssid: ssid,
|
||||||
|
password: password,
|
||||||
|
}
|
||||||
|
sendJsonMsg(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WifiInfo {
|
||||||
|
rssi: number;
|
||||||
|
ssid: string;
|
||||||
|
mac: string;
|
||||||
|
wifiLogo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WifiList {
|
||||||
|
scan_list: Array<WifiInfo>;
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
export interface ApiJsonMsg {
|
||||||
|
module: number;
|
||||||
|
cmd: number;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="currentColor" d="M764.288 214.592 512 466.88 259.712 214.592a31.936 31.936 0 0 0-45.12 45.12L466.752 512 214.528 764.224a31.936 31.936 0 1 0 45.12 45.184L512 557.184l252.288 252.288a31.936 31.936 0 0 0 45.12-45.12L557.12 512.064l252.288-252.352a31.936 31.936 0 1 0-45.12-45.184z"></path></svg>
|
After Width: | Height: | Size: 369 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M240-200h120v-240h240v240h120v-360L480-740 240-560v360Zm-80 80v-480l320-240 320 240v480H520v-240h-80v240H160Zm320-350Z"/></svg>
|
After Width: | Height: | Size: 201 B |
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||||
|
<path d="M128 192h768v128H128zm0 256h512v128H128zm0 256h768v128H128zm576-352 192 160-192 128z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 178 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="18" r="2"></circle></svg>
|
After Width: | Height: | Size: 105 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17.671 14.307C16.184 12.819 14.17 12 12 12s-4.184.819-5.671 2.307l1.414 1.414c1.11-1.11 2.621-1.722 4.257-1.722 1.636.001 3.147.612 4.257 1.722l1.414-1.414z"></path><circle cx="12" cy="18" r="2"></circle></svg>
|
After Width: | Height: | Size: 280 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17.671 14.307C16.184 12.819 14.17 12 12 12s-4.184.819-5.671 2.307l1.414 1.414c1.11-1.11 2.621-1.722 4.257-1.722 1.636.001 3.147.612 4.257 1.722l1.414-1.414z"></path><path d="M20.437 11.292c-4.572-4.573-12.301-4.573-16.873 0l1.414 1.414c3.807-3.807 10.238-3.807 14.045 0l1.414-1.414z"></path><circle cx="12" cy="18" r="2"></circle></svg>
|
After Width: | Height: | Size: 406 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 6c3.537 0 6.837 1.353 9.293 3.809l1.414-1.414C19.874 5.561 16.071 4 12 4c-4.071.001-7.874 1.561-10.707 4.395l1.414 1.414C5.163 7.353 8.463 6 12 6zm5.671 8.307c-3.074-3.074-8.268-3.074-11.342 0l1.414 1.414c2.307-2.307 6.207-2.307 8.514 0l1.414-1.414z"></path><path d="M20.437 11.293c-4.572-4.574-12.301-4.574-16.873 0l1.414 1.414c3.807-3.807 10.238-3.807 14.045 0l1.414-1.414z"></path><circle cx="12" cy="18" r="2"></circle></svg>
|
After Width: | Height: | Size: 502 B |
|
@ -0,0 +1,4 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
@tailwind variants;
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const icon = defineAsyncComponent(() =>
|
||||||
|
import('@/assets/icon/' + `${props.name}.svg`)
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<component :is="icon" class="fill-current" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +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));
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
import MyWorker from '@/composables/websocket/ws.sharedworker?sharedworker&inline'
|
||||||
|
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");
|
||||||
|
|
||||||
|
export interface IWebsocketService {
|
||||||
|
init(host: string,
|
||||||
|
msgCallback: (msg: ServerMsg) => any,
|
||||||
|
ctrlCallback: (msg: ControlMsg) => any)
|
||||||
|
: void;
|
||||||
|
|
||||||
|
deinit(): void;
|
||||||
|
|
||||||
|
send(msg: ServerMsg): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Websocket that run in a shared worker, shared across tabs
|
||||||
|
*/
|
||||||
|
class WebsocketShared implements IWebsocketService{
|
||||||
|
private static instance: IWebsocketService;
|
||||||
|
|
||||||
|
private worker: SharedWorker;
|
||||||
|
private msgCallback: (msg: ServerMsg) => any;
|
||||||
|
|
||||||
|
private ctrlCallback: (msg: ControlMsg) => any;
|
||||||
|
|
||||||
|
public static getInstance(): IWebsocketService {
|
||||||
|
if (!WebsocketShared.instance) {
|
||||||
|
WebsocketShared.instance = new WebsocketShared();
|
||||||
|
}
|
||||||
|
return WebsocketShared.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
console.log("Shared Websocket init")
|
||||||
|
this.msgCallback = () => {}
|
||||||
|
this.ctrlCallback = () => {}
|
||||||
|
this.messageEventProxy = this.messageEventProxy.bind(this);
|
||||||
|
this.controlEventProxy = this.controlEventProxy.bind(this);
|
||||||
|
this.worker = new MyWorker();
|
||||||
|
}
|
||||||
|
|
||||||
|
init(host: string, msgCallback: (msg: ServerMsg) => any, ctrlCallback: (msg: ControlMsg) => any): void {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit(): void {
|
||||||
|
toClient.removeEventListener("message", this.messageEventProxy);
|
||||||
|
toClientCtrl.removeEventListener("message", this.controlEventProxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
send(msg: ServerMsg): void {
|
||||||
|
console.log("Websocket Service send (not really)", msg)
|
||||||
|
toServer.postMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
messageEventProxy(ev: MessageEvent<ServerMsg>) {
|
||||||
|
this.msgCallback(ev.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
controlEventProxy(ev: MessageEvent<ControlMsg>) {
|
||||||
|
this.ctrlCallback(ev.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebsocketClassic implements IWebsocketService{
|
||||||
|
private static instance: IWebsocketService;
|
||||||
|
private socket: WebsocketWrapper;
|
||||||
|
private static count: number = 0;
|
||||||
|
|
||||||
|
public static getInstance(): IWebsocketService {
|
||||||
|
if (!WebsocketClassic.instance) {
|
||||||
|
WebsocketClassic.instance = new WebsocketClassic();
|
||||||
|
}
|
||||||
|
return WebsocketClassic.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
this.socket = new WebsocketWrapper();
|
||||||
|
WebsocketClassic.count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
init(host: string, msgCallback: (ev: ServerMsg) => any, ctrlCallback: (msg: ControlMsg) => any): void {
|
||||||
|
console.log("Websocket Service INIT called", WebsocketClassic.count);
|
||||||
|
|
||||||
|
this.socket.init(host, msgCallback, ctrlCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit(): void {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
send(msg: ServerMsg): void {
|
||||||
|
this.socket.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWebsocketService(): IWebsocketService {
|
||||||
|
if (typeof SharedWorker !== 'undefined') {
|
||||||
|
return WebsocketShared.getInstance();
|
||||||
|
} else {
|
||||||
|
return WebsocketClassic.getInstance();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,210 @@
|
||||||
|
import type {ServerMsg, ControlMsg} from "@/composables/broadcastChannelDef";
|
||||||
|
import {ControlEvent, ControlMsgType} from "@/composables/broadcastChannelDef";
|
||||||
|
|
||||||
|
|
||||||
|
interface IWebsocket {
|
||||||
|
|
||||||
|
close(): void;
|
||||||
|
|
||||||
|
send(msg: ServerMsg): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebsocketDummy implements IWebsocket {
|
||||||
|
close(): void {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
send(msg: ServerMsg) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OneTimeWebsocket implements IWebsocket {
|
||||||
|
private readonly heartBeatTimeout: number = 2;
|
||||||
|
private readonly host: string;
|
||||||
|
private readonly intervalId: number;
|
||||||
|
private readonly msgCallback: (ev: ServerMsg) => any;
|
||||||
|
private readonly ctrlCallback: (msg: ControlMsg) => any;
|
||||||
|
private readonly closeCallback: () => void;
|
||||||
|
private socket: WebSocket;
|
||||||
|
private heartBeatTimeCount: number;
|
||||||
|
private stoped: boolean;
|
||||||
|
|
||||||
|
constructor(host: string,
|
||||||
|
msgCallback: (ev: ServerMsg) => any,
|
||||||
|
ctrlCallback: (msg: ControlMsg) => any,
|
||||||
|
closeCallback: () => void
|
||||||
|
) {
|
||||||
|
this.host = host;
|
||||||
|
this.stoped = false;
|
||||||
|
this.msgCallback = msgCallback;
|
||||||
|
this.ctrlCallback = ctrlCallback;
|
||||||
|
this.closeCallback = closeCallback;
|
||||||
|
this.heartBeatTimeCount = 0;
|
||||||
|
this.intervalId = 0;
|
||||||
|
|
||||||
|
this.socket = new WebSocket(`ws://${this.host}/ws`);
|
||||||
|
this.setSocketProp();
|
||||||
|
|
||||||
|
this.intervalId = setInterval(() => {
|
||||||
|
if (this.heartBeatTimeCount > this.heartBeatTimeout) {
|
||||||
|
/* did not receive packet "heartBeatTimeout" times,
|
||||||
|
* connection may be lost: close the socket */
|
||||||
|
this.socket.close();
|
||||||
|
console.log("interval: ", this.heartBeatTimeCount, "state: ", this.socket.readyState);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.heartBeatTimeCount++;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setSocketProp() {
|
||||||
|
this.socket.binaryType = 'arraybuffer'
|
||||||
|
this.heartBeatTimeCount = 0;
|
||||||
|
|
||||||
|
this.socket.onmessage = (ev) => {
|
||||||
|
this.heartBeatTimeCount = 0;
|
||||||
|
if (!ev.data)
|
||||||
|
return
|
||||||
|
|
||||||
|
const msg: ServerMsg = {
|
||||||
|
data: {},
|
||||||
|
type: "json",
|
||||||
|
}
|
||||||
|
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 = () => {
|
||||||
|
console.log('WebSocket Disconnected');
|
||||||
|
|
||||||
|
clearInterval(this.intervalId);
|
||||||
|
this.socket.onclose = null
|
||||||
|
this.socket.onopen = null
|
||||||
|
this.socket.onerror = null
|
||||||
|
this.socket.onmessage = null;
|
||||||
|
|
||||||
|
const msg: ControlMsg = {
|
||||||
|
type: ControlMsgType.WS_EVENT,
|
||||||
|
data: ControlEvent.DISCONNECTED,
|
||||||
|
}
|
||||||
|
this.ctrlCallback(msg);
|
||||||
|
this.closeCallback();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.socket.onerror = (error) => {
|
||||||
|
console.error('WebSocket Error', error);
|
||||||
|
this.socket.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.socket.onopen = ev => {
|
||||||
|
console.log('WebSocket Connected');
|
||||||
|
if (this.stoped) {
|
||||||
|
this.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.heartBeatTimeCount = 0;
|
||||||
|
const msg: ControlMsg = {
|
||||||
|
type: ControlMsgType.WS_EVENT,
|
||||||
|
data: ControlEvent.CONNECTED,
|
||||||
|
}
|
||||||
|
this.ctrlCallback(msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
const msg: ControlMsg = {
|
||||||
|
type: ControlMsgType.WS_EVENT,
|
||||||
|
data: ControlEvent.CONNECTING,
|
||||||
|
}
|
||||||
|
this.ctrlCallback(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.stoped = true;
|
||||||
|
this.socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
send(msg: ServerMsg) {
|
||||||
|
if (this.socket.readyState !== WebSocket.OPEN) {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WebsocketWrapper {
|
||||||
|
private socket: IWebsocket;
|
||||||
|
private msgCallback: (ev: ServerMsg) => any;
|
||||||
|
private ctrlCallback: (msg: ControlMsg) => any;
|
||||||
|
private timeoutId: number;
|
||||||
|
private host: string;
|
||||||
|
private stoped: boolean;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.socket = new WebsocketDummy();
|
||||||
|
this.msgCallback = () => {};
|
||||||
|
this.ctrlCallback = () => {};
|
||||||
|
this.timeoutId = 0;
|
||||||
|
this.host = "";
|
||||||
|
this.stoped = false;
|
||||||
|
|
||||||
|
this.closeCallback = this.closeCallback.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
init(host: string,
|
||||||
|
msgCallback: (ev: ServerMsg) => any,
|
||||||
|
ctrlCallback: (msg: ControlMsg) => any)
|
||||||
|
{
|
||||||
|
if (this.host === host) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.newConnection(host, msgCallback, ctrlCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private newConnection(host: string,
|
||||||
|
msgCallback: (ev: ServerMsg) => any,
|
||||||
|
ctrlCallback: (msg: ControlMsg) => any)
|
||||||
|
{
|
||||||
|
this.socket.close();
|
||||||
|
this.host = host;
|
||||||
|
this.msgCallback = msgCallback;
|
||||||
|
this.ctrlCallback = ctrlCallback;
|
||||||
|
this.socket = new OneTimeWebsocket(host, this.msgCallback, this.ctrlCallback, this.closeCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private closeCallback() {
|
||||||
|
if (this.stoped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.timeoutId = setTimeout(() =>
|
||||||
|
this.newConnection(this.host, this.msgCallback, this.ctrlCallback),
|
||||||
|
2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit() {
|
||||||
|
this.socket.close();
|
||||||
|
this.stoped = true;
|
||||||
|
clearTimeout(this.timeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
send(msg: ServerMsg) {
|
||||||
|
console.log('WebSocket send: not ready', msg);
|
||||||
|
// this.socket.send(msg)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
const websocket = new WebsocketWrapper();
|
||||||
|
let host = "";
|
||||||
|
|
||||||
|
function ctrlBroadcast(msg: ControlMsg) {
|
||||||
|
toClientCtrl.postMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
function msgBroadcast(msg: ServerMsg) {
|
||||||
|
toClient.postMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// self.onconnect
|
||||||
|
self.onconnect = function(event) {
|
||||||
|
const port = event.ports[0];
|
||||||
|
port.onmessage = function (e: MessageEvent<ControlMsg>) {
|
||||||
|
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;
|
||||||
|
websocket.init(host, msgBroadcast, ctrlBroadcast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const msg: ControlMsg = {
|
||||||
|
type: ControlMsgType.WS_EVENT,
|
||||||
|
data: ControlEvent.LOADED,
|
||||||
|
}
|
||||||
|
port.postMessage(msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
toServer.onmessage = (ev: MessageEvent<ServerMsg>) => {
|
||||||
|
websocket.send(ev.data);
|
||||||
|
};
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { createI18n } from 'vue-i18n';
|
||||||
|
import zh from '@/locales/zh'
|
||||||
|
import en from '@/locales/en'
|
||||||
|
|
||||||
|
// const locale = localStorage.getItem('lang') || 'zh';
|
||||||
|
const locale = 'zh';
|
||||||
|
|
||||||
|
console.log("langggggg:", locale);
|
||||||
|
|
||||||
|
const i18n = createI18n({
|
||||||
|
globalInjection: true,
|
||||||
|
legacy: false,
|
||||||
|
locale: locale,
|
||||||
|
fallbackLocale: 'zh',
|
||||||
|
messages: {
|
||||||
|
zh,
|
||||||
|
// en,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
|
@ -0,0 +1,3 @@
|
||||||
|
export default {
|
||||||
|
disconnected: "disconnected"
|
||||||
|
};
|
|
@ -0,0 +1,17 @@
|
||||||
|
export default {
|
||||||
|
DISCONNECTED: "未连接",
|
||||||
|
CONNECTED: "已连接",
|
||||||
|
CONNECTING: "连接中",
|
||||||
|
|
||||||
|
WS: {
|
||||||
|
DISCONNECTED: "未连接",
|
||||||
|
CONNECTED: "已连接",
|
||||||
|
CONNECTING: "连接中",
|
||||||
|
},
|
||||||
|
|
||||||
|
PAGE: {
|
||||||
|
HOME: "主页",
|
||||||
|
ABOUT: "关于",
|
||||||
|
FEEDBACK: "反馈",
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import '@/assets/tailwind.css'
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
import i18n from '@/i18n';
|
||||||
|
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
app.use(createPinia())
|
||||||
|
app.use(router)
|
||||||
|
app.use(i18n);
|
||||||
|
|
||||||
|
app.mount('#app')
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import Home from '@/views/Home.vue'
|
||||||
|
import Wifi from '@/views/Wifi.vue'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: Home
|
||||||
|
}, {
|
||||||
|
path: '/home:ext(.*)',
|
||||||
|
component: Home,
|
||||||
|
}, {
|
||||||
|
path: '/wifi:ext(.*)',
|
||||||
|
component: Wifi,
|
||||||
|
}, {
|
||||||
|
path: '/about:ext(.*)',
|
||||||
|
name: 'about',
|
||||||
|
// route level code-splitting
|
||||||
|
// this generates a separate chunk (About.[hash].js) for this route
|
||||||
|
// which is lazy-loaded when the route is visited.
|
||||||
|
component: () => import('@/views/About.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useCounterStore = defineStore('counter', () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const doubleCount = computed(() => count.value * 2)
|
||||||
|
function increment() {
|
||||||
|
count.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
return { count, doubleCount, increment }
|
||||||
|
})
|
|
@ -0,0 +1,11 @@
|
||||||
|
import {defineStore} from "pinia";
|
||||||
|
import {ControlEvent} from "@/composables/broadcastChannelDef";
|
||||||
|
|
||||||
|
export const useWsStore = defineStore('websocket', {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
state: ControlEvent.DISCONNECTED as ControlEvent,
|
||||||
|
dummy_var: 1 as number,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,12 @@
|
||||||
|
<template>
|
||||||
|
<h2>About Page</h2>
|
||||||
|
<!-- Your about page content goes here -->
|
||||||
|
<h2>About Page</h2>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
let name = "about-a";
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<template>
|
||||||
|
<h4>Skewed</h4>
|
||||||
|
<input class="tgl tgl-skewed" id="cb3" type="checkbox" checked/>
|
||||||
|
<label class="tgl-btn" data-tg-off="低电平" data-tg-on="高电平" for="cb3"></label>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
document.title = "Home";
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,158 @@
|
||||||
|
<template>
|
||||||
|
<div class="wifiView">
|
||||||
|
<h2>Wifi View</h2>
|
||||||
|
<el-form label-width="auto" ref="formRef" :model="ssidValidateForm">
|
||||||
|
<el-form-item
|
||||||
|
label="Wi-Fi名"
|
||||||
|
prop="wifiSsid"
|
||||||
|
:rules="[
|
||||||
|
{ required: true, message: '请输入WIFI名'},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div class="flex w-full">
|
||||||
|
<el-autocomplete
|
||||||
|
v-model="ssidValidateForm.wifiSsid"
|
||||||
|
clearable
|
||||||
|
:placeholder="wifiListPlaceholder"
|
||||||
|
class="inline-input w-full"
|
||||||
|
:fetch-suggestions="querySearch"
|
||||||
|
value-key="ssid"
|
||||||
|
>
|
||||||
|
<template #default="{ item }">
|
||||||
|
<div class="flex">
|
||||||
|
<InlineSvg :name="item.wifiLogo" class="h-6 pr-4"></InlineSvg>
|
||||||
|
<!-- <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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="密码">
|
||||||
|
<el-input
|
||||||
|
v-model="password"
|
||||||
|
show-password
|
||||||
|
type="password"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<el-button @click="onConnect" type="primary">连接</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<el-button class="h-8">扫描</el-button>
|
||||||
|
<el-button type="primary">连接</el-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {inject, onMounted, onUnmounted, reactive, ref} from "vue";
|
||||||
|
import {wifi_get_ap_info, wifi_get_scan_list, type WifiInfo, type WifiList} from "@/api/apiWifi";
|
||||||
|
import type {FormInstance} from "element-plus";
|
||||||
|
import type {ServerMsg,ControlMsg} from "@/composables/broadcastChannelDef";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ControlEvent,
|
||||||
|
ControlMsgType
|
||||||
|
} from "@/composables/broadcastChannelDef";
|
||||||
|
import InlineSvg from "@/components/InlineSvg.vue";
|
||||||
|
import {getWebsocketService} from "@/composables/websocket/websocketService";
|
||||||
|
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
let wifiListPlaceholder = ref("我的WIFI")
|
||||||
|
let ssidValidateForm = reactive({
|
||||||
|
wifiSsid: ""
|
||||||
|
})
|
||||||
|
const password = ref('')
|
||||||
|
|
||||||
|
let scanning = false;
|
||||||
|
let scan_cb: any;
|
||||||
|
let options: Array<WifiInfo> = [
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
const querySearch = (queryString: string, cb: any) => {
|
||||||
|
if (scanning) {
|
||||||
|
scan_cb = cb;
|
||||||
|
} else {
|
||||||
|
cb(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = (ev: MessageEvent<ControlMsg>) => {
|
||||||
|
if (ev.data.type !== ControlMsgType.WS_EVENT) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev.data.data === ControlEvent.CONNECTED) {
|
||||||
|
wifi_get_ap_info();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function onScanClick() {
|
||||||
|
scanning = true;
|
||||||
|
wifi_get_scan_list();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onConnect() {
|
||||||
|
console.log(ssidValidateForm.wifiSsid, password.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.wifiView {
|
||||||
|
background-color: bisque;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,156 @@
|
||||||
|
<template>
|
||||||
|
<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 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">
|
||||||
|
<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>
|
||||||
|
<!-- <a class="flex items-center text-sm text-blue-600 font-bold" href="/">主页6</a>-->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<ul class="hidden absolute top-1/2 left-1/2 transform -translate-y-1/2 -translate-x-1/2 md:flex md:mx-auto md:items-center md:w-auto md:space-x-6">
|
||||||
|
<li><router-link to="/wifi" title="Wifi" class="text-sm text-gray-400 hover:text-gray-800">wifi</router-link></li>
|
||||||
|
<!-- <li><a class="text-sm text-gray-400 hover:text-gray-500" href="#">Home</a></li>-->
|
||||||
|
<!-- <li><a class="text-sm text-blue-600 font-bold">About Us</a></li>-->
|
||||||
|
<!-- <li><a class="text-sm text-gray-400 hover:text-gray-500" href="#">Services</a></li>-->
|
||||||
|
</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>
|
||||||
|
</nav>
|
||||||
|
<div :class='["custom-drawer", {open: sideMenuOpen}]'>
|
||||||
|
<el-drawer
|
||||||
|
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">
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
<li v-for="(item, index) in menuItems" class="mb-1" :key="index">
|
||||||
|
<router-link @click="sideMenuOpen=false" :title="item.name" :to="item.href" :class="sideMenuItemClass">{{ item.name }}</router-link>
|
||||||
|
<!-- <a :href="item.href" :class="sideMenuItemClass">{{ item.name }}</a>-->
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="mt-auto">
|
||||||
|
|
||||||
|
<p class="my-4 text-xs text-center text-gray-400">
|
||||||
|
<span>Copyright kerms 2024</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class='["custom-drawer", {open: stateMenuOpen}]'>
|
||||||
|
<el-drawer
|
||||||
|
v-model="stateMenuOpen"
|
||||||
|
:with-header="false"
|
||||||
|
size=""
|
||||||
|
modal-class="bg-amber-400 bg-opacity-0"
|
||||||
|
:direction="'rtl'">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import InlineSvg from "@/components/InlineSvg.vue";
|
||||||
|
import {computed, ref, toRef} from "vue";
|
||||||
|
import {useWsStore} from "@/stores/websocket";
|
||||||
|
import {useI18n} from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const wsStore = useWsStore();
|
||||||
|
|
||||||
|
const sideMenuItemClass = "block p-4 text-sm font-semibold text-gray-400 hover:bg-blue-50 hover:text-blue-600 rounded"
|
||||||
|
const sideMenuOpen = ref(false);
|
||||||
|
const stateMenuOpen = ref(false)
|
||||||
|
|
||||||
|
const wsState = computed(() => {
|
||||||
|
|
||||||
|
return t(wsStore.state);
|
||||||
|
});
|
||||||
|
|
||||||
|
const menuItems = ([
|
||||||
|
{
|
||||||
|
name: "Home",
|
||||||
|
href: "/",
|
||||||
|
}, {
|
||||||
|
name: "About Us",
|
||||||
|
href: "/about",
|
||||||
|
}, {
|
||||||
|
name: "Services",
|
||||||
|
href: "/",
|
||||||
|
}, {
|
||||||
|
name: "Wifi",
|
||||||
|
href: "/wifi",
|
||||||
|
}, {
|
||||||
|
name: "Contact",
|
||||||
|
href: "/",
|
||||||
|
}, {
|
||||||
|
name: "6",
|
||||||
|
href: "/",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
/*nav *{
|
||||||
|
border: solid 1px;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/* drawer */
|
||||||
|
.custom-drawer :deep(.el-drawer) {
|
||||||
|
transition: all 0.1s; /* Custom duration*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* drawer overlay */
|
||||||
|
.custom-drawer.open :deep(.el-overlay) {
|
||||||
|
transition: all 0s; /* Custom duration*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-drawer :deep(.el-drawer__body) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,12 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
"./index.html",
|
||||||
|
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"include": [
|
||||||
|
"env.d.ts",
|
||||||
|
"src/**/*",
|
||||||
|
"src/**/*.vue",
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.d.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"node_modules/@vue/runtime-core/dist/runtime-core.d.ts"
|
||||||
|
],
|
||||||
|
"exclude": ["src/**/__tests__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"lib": [
|
||||||
|
"DOM", /* for document.* */
|
||||||
|
"WebWorker", /* for *WorkerGlobalScope */
|
||||||
|
"ESNext"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"extends": "@tsconfig/node20/tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"nightwatch.conf.*",
|
||||||
|
"playwright.config.*"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
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 vue from '@vitejs/plugin-vue'
|
||||||
|
import svgLoader from "vite-svg-loader";
|
||||||
|
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|