refactor: webui

This commit is contained in:
pk5ls20
2024-11-15 23:39:19 +08:00
parent 1ec1040e43
commit fe0bda11d3
33 changed files with 647 additions and 499 deletions

View File

@@ -1,56 +1,55 @@
<template>
<div class="dashboard-container">
<SidebarMenu :menuItems="menuItems" class="sidebar-menu" />
<div class="content">
<router-view />
<div class="dashboard-container">
<SidebarMenu :menu-items="menuItems" class="sidebar-menu" />
<div class="content">
<router-view />
</div>
</div>
</div>
</template>
<script>
<script setup lang="ts">
import { ref } from 'vue';
import SidebarMenu from './webui/Nav.vue';
export default {
components: {
SidebarMenu
},
data() {
return {
menuItems: [
{ value: 'item1', icon: 'dashboard', label: '基础信息', route: '/dashboard/basic-info' },
{ value: 'item3', icon: 'wifi-1', label: '网络配置', route: '/dashboard/network-config' },
{ value: 'item4', icon: 'setting', label: '其余配置', route: '/dashboard/other-config' },
{ value: 'item5', icon: 'system-log', label: '日志查看', route: '/dashboard/log-view' },
{ value: 'item6', icon: 'info-circle', label: '关于我们', route: '/dashboard/about-us' }
]
};
}
interface MenuItem {
value: string;
icon: string;
label: string;
route: string;
}
const menuItems = ref<MenuItem[]>([
{ value: 'item1', icon: 'dashboard', label: '基础信息', route: '/dashboard/basic-info' },
{ value: 'item3', icon: 'wifi-1', label: '网络配置', route: '/dashboard/network-config' },
{ value: 'item4', icon: 'setting', label: '其余配置', route: '/dashboard/other-config' },
{ value: 'item5', icon: 'system-log', label: '日志查看', route: '/dashboard/log-view' },
{ value: 'item6', icon: 'info-circle', label: '关于我们', route: '/dashboard/about-us' },
]);
</script>
<style scoped>
.dashboard-container {
display: flex;
flex-direction: row;
height: 100vh;
display: flex;
flex-direction: row;
height: 100vh;
}
.sidebar-menu {
position: relative;
z-index: 2;
position: relative;
z-index: 2;
}
.content {
flex: 1;
padding: 20px;
overflow: auto;
position: relative;
z-index: 1;
flex: 1;
padding: 20px;
overflow: auto;
position: relative;
z-index: 1;
}
@media (max-width: 768px) {
.content {
padding: 10px;
}
.content {
padding: 10px;
}
}
</style>
</style>

View File

@@ -2,67 +2,91 @@
<div class="login-container">
<h2 class="sotheby-font">QQ Login</h2>
<div class="login-methods">
<t-button id="quick-login" class="login-method" :class="{ active: loginMethod === 'quick' }"
@click="loginMethod = 'quick'">Quick Login</t-button>
<t-button id="qrcode-login" class="login-method" :class="{ active: loginMethod === 'qrcode' }"
@click="loginMethod = 'qrcode'">QR Code</t-button>
<t-button
id="quick-login"
class="login-method"
:class="{ active: loginMethod === 'quick' }"
@click="loginMethod = 'quick'"
>Quick Login</t-button
>
<t-button
id="qrcode-login"
class="login-method"
:class="{ active: loginMethod === 'qrcode' }"
@click="loginMethod = 'qrcode'"
>QR Code</t-button
>
</div>
<div id="quick-login-dropdown" class="login-form" v-show="loginMethod === 'quick'">
<t-select id="quick-login-select" v-model="selectedAccount" @change="selectAccount"
placeholder="Select Account">
<div v-show="loginMethod === 'quick'" id="quick-login-dropdown" class="login-form">
<t-select
id="quick-login-select"
v-model="selectedAccount"
placeholder="Select Account"
@change="selectAccount"
>
<t-option v-for="account in quickLoginList" :key="account" :value="account">{{ account }}</t-option>
</t-select>
</div>
<div id="qrcode" class="qrcode" v-show="loginMethod === 'qrcode'">
<div v-show="loginMethod === 'qrcode'" id="qrcode" class="qrcode">
<canvas ref="qrcodeCanvas"></canvas>
</div>
</div>
</template>
<script setup>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import QRCode from 'qrcode';
import * as QRCode from 'qrcode';
import { useRouter } from 'vue-router';
import { MessagePlugin } from 'tdesign-vue-next';
import { QQLoginManager } from '../backend/shell.ts';
import { QQLoginManager } from '@/backend/shell';
const router = useRouter();
const loginMethod = ref('quick');
const quickLoginList = ref([]);
const selectedAccount = ref('');
const qrcodeCanvas = ref(null);
const qqLoginManager = new QQLoginManager(localStorage.getItem('auth'));
let heartBeatTimer = null;
const loginMethod = ref<'quick' | 'qrcode'>('quick');
const quickLoginList = ref<string[]>([]);
const selectedAccount = ref<string>('');
const qrcodeCanvas = ref<HTMLCanvasElement | null>(null);
const qqLoginManager = new QQLoginManager(localStorage.getItem('auth') || '');
let heartBeatTimer: number | null = null;
const selectAccount = async (accountName) => {
const selectAccount = async (accountName: string): Promise<void> => {
const { result, errMsg } = await qqLoginManager.setQuickLogin(accountName);
if (result) {
MessagePlugin.success("登录成功即将跳转");
await MessagePlugin.success('登录成功即将跳转');
await router.push({ path: '/dashboard/basic-info' });
} else {
MessagePlugin.error("登录失败," + errMsg);
await MessagePlugin.error('登录失败,' + errMsg);
}
};
const generateQrCode = (data, canvas) => {
QRCode.toCanvas(canvas, data, function (error) {
if (error) console.log(error);
console.log('QR Code generated!');
const generateQrCode = (data: string, canvas: HTMLCanvasElement | null): void => {
if (!canvas) {
console.error('Canvas element not found');
return;
}
QRCode.toCanvas(canvas, data, function (error: Error | null | undefined) {
if (error) {
console.error('Error generating QR Code:', error);
} else {
console.log('QR Code generated!');
}
});
};
const HeartBeat = async () => {
let isLogined = await qqLoginManager.checkQQLoginStatus();
const HeartBeat = async (): Promise<void> => {
const isLogined = await qqLoginManager.checkQQLoginStatus();
if (isLogined) {
clearInterval(heartBeatTimer);
if (heartBeatTimer) {
clearInterval(heartBeatTimer);
}
await router.push({ path: '/dashboard/basic-info' });
}
};
const InitPages = async () => {
const InitPages = async (): Promise<void> => {
quickLoginList.value = await qqLoginManager.getQQQuickLoginList();
generateQrCode(await qqLoginManager.getQQLoginQrcode(), qrcodeCanvas.value);
heartBeatTimer = setInterval(HeartBeat, 3000);
const qrcodeData = await qqLoginManager.getQQLoginQrcode();
generateQrCode(qrcodeData, qrcodeCanvas.value);
heartBeatTimer = window.setInterval(HeartBeat, 3000);
};
onMounted(() => {
@@ -103,7 +127,7 @@ onMounted(() => {
.login-method.active {
background-color: #e6f0ff;
color: #007BFF;
color: #007bff;
}
.login-form,
@@ -125,7 +149,7 @@ onMounted(() => {
font-family: Sotheby, Helvetica, monospace;
font-size: 3.125rem;
line-height: 1.2;
text-shadow: 0 1px 2px rgba(0, 0, 0, .1);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.footer {
@@ -140,4 +164,4 @@ onMounted(() => {
width: 100%;
background-color: white;
}
</style>
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="login-container">
<h2 class="sotheby-font">WebUi Login</h2>
<t-form ref="form" :data="formData" :colon="true" :label-width="0" @submit="onSubmit">
<t-form ref="form" :data="formData" colon :label-width="0" @submit="onSubmit">
<t-form-item name="password">
<t-input v-model="formData.token" type="password" clearable placeholder="请输入Token">
<template #prefix-icon>
@@ -14,32 +14,34 @@
</t-form-item>
</t-form>
</div>
<div class="footer">
Power By NapCat.WebUi
</div>
<div class="footer">Power By NapCat.WebUi</div>
</template>
<script setup>
<script setup lang="ts">
import '../css/style.css';
import '../css/font.css';
import { reactive, ref, onMounted } from 'vue';
import { reactive, onMounted } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { LockOnIcon } from 'tdesign-icons-vue-next';
import { useRouter } from 'vue-router';
import { QQLoginManager } from '../backend/shell';
import { QQLoginManager } from '@/backend/shell';
const router = useRouter();
const formData = reactive({
interface FormData {
token: string;
}
const formData: FormData = reactive({
token: '',
});
const handleLoginSuccess = async (credential) => {
const handleLoginSuccess = async (credential: string) => {
localStorage.setItem('auth', credential);
await checkLoginStatus();
};
const handleLoginFailure = (message) => {
const handleLoginFailure = (message: string) => {
MessagePlugin.error(message);
};
@@ -63,7 +65,7 @@ const checkLoginStatus = async () => {
}
};
const loginWithToken = async (token) => {
const loginWithToken = async (token: string) => {
const loginManager = new QQLoginManager('');
const credential = await loginManager.loginWithToken(token);
if (credential) {
@@ -75,15 +77,15 @@ const loginWithToken = async (token) => {
onMounted(() => {
const url = new URL(window.location.href);
const token = url.searchParams.get("token");
const token = url.searchParams.get('token');
if (token) {
loginWithToken(token);
}
checkLoginStatus();
});
const onSubmit = async ({ validateResult }) => {
if (validateResult === true) {
const onSubmit = async ({ validateResult }: { validateResult: boolean }) => {
if (validateResult) {
await loginWithToken(formData.token);
} else {
handleLoginFailure('请填写Token');
@@ -131,7 +133,7 @@ const onSubmit = async ({ validateResult }) => {
font-family: Sotheby, Helvetica, monospace;
font-size: 3.125rem;
line-height: 1.2;
text-shadow: 0 1px 2px rgba(0, 0, 0, .1);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.footer {
@@ -146,4 +148,4 @@ const onSubmit = async ({ validateResult }) => {
width: 100%;
background-color: white;
}
</style>
</style>

View File

@@ -1,77 +1,71 @@
<template>
<t-menu theme="light" default-value="2-1" :collapsed="collapsed" class="sidebar-menu">
<template #logo>
</template>
<router-link v-for="item in menuItems" :key="item.value" :to="item.route">
<t-menu-item :value="item.value" :disabled="item.disabled" class="menu-item">
<template #icon>
<t-icon :name="item.icon" />
<t-menu theme="light" default-value="2-1" :collapsed="collapsed" class="sidebar-menu">
<template #logo> </template>
<router-link v-for="item in menuItems" :key="item.value" :to="item.route">
<t-menu-item :value="item.value" :disabled="item.disabled" class="menu-item">
<template #icon>
<t-icon :name="item.icon" />
</template>
{{ item.label }}
</t-menu-item>
</router-link>
<template #operations>
<t-button class="t-demo-collapse-btn" variant="text" shape="square" @click="changeCollapsed">
<template #icon><t-icon :name="iconName" /></template>
</t-button>
</template>
{{ item.label }}
</t-menu-item>
</router-link>
<template #operations>
<t-button class="t-demo-collapse-btn" variant="text" shape="square" @click="changeCollapsed">
<template #icon><t-icon :name="iconName" /></template>
</t-button>
</template>
</t-menu>
</t-menu>
</template>
<script>
import { defineComponent, ref, onMounted } from 'vue';
<script setup lang="ts">
import { ref, defineProps } from 'vue';
export default defineComponent({
name: 'SidebarMenu',
props: {
menuItems: {
type: Array,
required: true
}
},
setup() {
const collapsed = ref(localStorage.getItem('sidebar-collapsed') === 'true');
const iconName = ref(collapsed.value ? 'menu-unfold' : 'menu-fold');
type MenuItem = {
value: string;
label: string;
route: string;
icon?: string;
disabled?: boolean;
};
const changeCollapsed = () => {
collapsed.value = !collapsed.value;
iconName.value = collapsed.value ? 'menu-unfold' : 'menu-fold';
localStorage.setItem('sidebar-collapsed', collapsed.value);
};
defineProps<{
menuItems: MenuItem[];
}>();
return {
collapsed,
iconName,
changeCollapsed
};
}
});
const collapsed = ref<boolean>(localStorage.getItem('sidebar-collapsed') === 'true');
const iconName = ref<string>(collapsed.value ? 'menu-unfold' : 'menu-fold');
const changeCollapsed = (): void => {
collapsed.value = !collapsed.value;
iconName.value = collapsed.value ? 'menu-unfold' : 'menu-fold';
localStorage.setItem('sidebar-collapsed', collapsed.value.toString());
};
</script>
<style scoped>
.sidebar-menu {
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 200px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 200px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
@media (max-width: 768px) {
.sidebar-menu {
width: 100px; /* 移动端侧边栏宽度 */
}
.sidebar-menu {
width: 100px; /* 移动端侧边栏宽度 */
}
}
.logo-text {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.menu-item {
margin-bottom: 10px;
margin-bottom: 10px;
}
</style>
</style>