mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-12 07:50:25 +00:00
refactor: webui
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user