login and auth

This commit is contained in:
hptangxi
2025-07-02 20:44:30 +08:00
parent 8e1de8d29f
commit 7b372de6d8
12 changed files with 113 additions and 87 deletions

View File

@@ -52,6 +52,7 @@ export default [
"vue/multiline-html-element-content-newline": "off", "vue/multiline-html-element-content-newline": "off",
"vue/html-indent": ["error", 2], "vue/html-indent": ["error", 2],
"vue/script-indent": ["error", 2], "vue/script-indent": ["error", 2],
"vue/component-tags-order": ["error", { order: ["script", "template", "style"] }],
// Vue 3 Composition API 规则 // Vue 3 Composition API 规则
"vue/no-setup-props-destructure": "error", "vue/no-setup-props-destructure": "error",

View File

@@ -1,12 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import Layout from "@/components/Layout.vue"; import Layout from "@/components/Layout.vue";
import { useAuthKey } from "@/services/auth";
import { NDialogProvider, NMessageProvider } from "naive-ui"; import { NDialogProvider, NMessageProvider } from "naive-ui";
import { computed } from "vue";
const authKey = useAuthKey();
const isLoggedIn = computed(() => !!authKey.value);
</script> </script>
<template> <template>
<n-message-provider> <n-message-provider>
<n-dialog-provider> <n-dialog-provider>
<layout /> <layout v-if="isLoggedIn" />
<router-view v-else />
</n-dialog-provider> </n-dialog-provider>
</n-message-provider> </n-message-provider>
</template> </template>

View File

@@ -9,20 +9,17 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
body { html, body {
margin: 0; height: 100%;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
} }
#app { #app {
max-width: 1280px; max-width: 1280px;
width: 100%; width: 100%;
padding: 20px;
margin: 0 auto; margin: 0 auto;
padding: 2rem; height: 100%;
min-height: 100vh; box-sizing: border-box;
} }
.flex { .flex {

View File

@@ -1,5 +1,10 @@
<script setup lang="ts">
import Logout from "@/components/Logout.vue";
import NavBar from "@/components/NavBar.vue";
</script>
<template> <template>
<n-layout v-if="authService.isLoggedIn()"> <n-layout>
<n-layout-header class="flex items-center"> <n-layout-header class="flex items-center">
<h1 class="layout-header-title">T.COM</h1> <h1 class="layout-header-title">T.COM</h1>
<nav-bar /> <nav-bar />
@@ -9,15 +14,8 @@
<router-view /> <router-view />
</n-layout-content> </n-layout-content>
</n-layout> </n-layout>
<router-view v-else />
</template> </template>
<script setup lang="ts">
import NavBar from "@/components/NavBar.vue";
import Logout from "@/components/Logout.vue";
import { authService } from "@/services/auth";
</script>
<style scoped> <style scoped>
.layout-header-title { .layout-header-title {
margin-top: 0; margin-top: 0;

View File

@@ -1,15 +1,16 @@
<template>
<n-button quaternary round @click="handleLogout">退出</n-button>
</template>
<script setup lang="ts"> <script setup lang="ts">
import { authService } from "@/services/auth"; import { useAuthService } from "@/services/auth";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
const router = useRouter(); const router = useRouter();
const { logout } = useAuthService();
const handleLogout = () => { const handleLogout = () => {
authService.logout(); logout();
router.push("/login"); router.replace("/login");
}; };
</script> </script>
<template>
<n-button quaternary round @click="handleLogout">退出</n-button>
</template>

View File

@@ -1,10 +1,6 @@
<template>
<n-menu mode="horizontal" :options="menuOptions" :value="activeMenu" responsive />
</template>
<script setup lang="ts"> <script setup lang="ts">
import type { MenuOption } from "naive-ui"; import type { MenuOption } from "naive-ui";
import { h, computed } from "vue"; import { computed, h } from "vue";
import { RouterLink, useRoute } from "vue-router"; import { RouterLink, useRoute } from "vue-router";
const menuOptions: MenuOption[] = [ const menuOptions: MenuOption[] = [
@@ -33,3 +29,7 @@ function renderMenuItem(key: string, label: string): MenuOption {
}; };
} }
</script> </script>
<template>
<n-menu mode="horizontal" :options="menuOptions" :value="activeMenu" responsive />
</template>

View File

@@ -2,6 +2,6 @@ import App from "@/App.vue";
import router from "@/router"; import router from "@/router";
import naive from "naive-ui"; import naive from "naive-ui";
import { createApp } from "vue"; import { createApp } from "vue";
import "./style.css"; import "./assets/style.css";
createApp(App).use(router).use(naive).mount("#app"); createApp(App).use(router).use(naive).mount("#app");

View File

@@ -1,4 +1,4 @@
import { authService } from "@/services/auth"; import { useAuthService } from "@/services/auth";
import { createRouter, createWebHistory, type RouteRecordRaw } from "vue-router"; import { createRouter, createWebHistory, type RouteRecordRaw } from "vue-router";
const routes: Array<RouteRecordRaw> = [ const routes: Array<RouteRecordRaw> = [
@@ -34,8 +34,10 @@ const router = createRouter({
routes, routes,
}); });
const { checkLogin } = useAuthService();
router.beforeEach((to, _from, next) => { router.beforeEach((to, _from, next) => {
const loggedIn = authService.isLoggedIn(); const loggedIn = checkLogin();
if (to.path !== "/login" && !loggedIn) { if (to.path !== "/login" && !loggedIn) {
return next({ path: "/login" }); return next({ path: "/login" });
} }

View File

@@ -1,11 +1,20 @@
import http from "@/utils/http"; import http from "@/utils/http";
import { useState } from "@/utils/state";
const AUTH_KEY = "authKey"; const AUTH_KEY = "authKey";
const login = async (authKey: string): Promise<boolean> => { export const useAuthKey = () => {
return useState<string | null>(AUTH_KEY, () => null);
};
export function useAuthService() {
const authKey = useAuthKey();
const login = async (key: string): Promise<boolean> => {
try { try {
await http.post("/auth/login", { auth_key: authKey }); await http.post("/auth/login", { auth_key: key });
localStorage.setItem(AUTH_KEY, authKey); localStorage.setItem(AUTH_KEY, key);
authKey.value = key;
return true; return true;
} catch (error) { } catch (error) {
console.error("Login failed:", error); console.error("Login failed:", error);
@@ -15,19 +24,24 @@ const login = async (authKey: string): Promise<boolean> => {
const logout = (): void => { const logout = (): void => {
localStorage.removeItem(AUTH_KEY); localStorage.removeItem(AUTH_KEY);
authKey.value = null;
}; };
const getAuthKey = (): string | null => { const checkLogin = (): boolean => {
return localStorage.getItem(AUTH_KEY); if (authKey.value) {
return true;
}
const key = localStorage.getItem(AUTH_KEY);
if (key) {
authKey.value = key;
}
return !!authKey.value;
}; };
const isLoggedIn = (): boolean => { return {
return !!localStorage.getItem(AUTH_KEY);
};
export const authService = {
login, login,
logout, logout,
getAuthKey, checkLogin,
isLoggedIn,
}; };
}

View File

@@ -1,6 +1,5 @@
import { authService } from "@/services/auth"; import { useAuthService } from "@/services/auth";
import axios from "axios"; import axios from "axios";
import { useRouter } from "vue-router";
const http = axios.create({ const http = axios.create({
baseURL: "/api", baseURL: "/api",
@@ -10,7 +9,7 @@ const http = axios.create({
// 请求拦截器 // 请求拦截器
http.interceptors.request.use(config => { http.interceptors.request.use(config => {
const authKey = authService.getAuthKey(); const authKey = localStorage.getItem("authKey");
if (authKey) { if (authKey) {
config.headers.Authorization = `Bearer ${authKey}`; config.headers.Authorization = `Bearer ${authKey}`;
} }
@@ -22,8 +21,9 @@ http.interceptors.response.use(
response => response.data, response => response.data,
error => { error => {
if (error.response && error.response.status === 401) { if (error.response && error.response.status === 401) {
authService.logout(); const { logout } = useAuthService();
useRouter().push("/login"); logout();
window.location.href = "/login";
} }
console.error("API Error:", error); console.error("API Error:", error);
return Promise.reject(error); return Promise.reject(error);

View File

@@ -1,13 +1,13 @@
<template>
<base-info-card />
<line-chart class="chart" />
</template>
<script setup lang="ts"> <script setup lang="ts">
import BaseInfoCard from "@/components/BaseInfoCard.vue"; import BaseInfoCard from "@/components/BaseInfoCard.vue";
import LineChart from "@/components/LineChart.vue"; import LineChart from "@/components/LineChart.vue";
</script> </script>
<template>
<base-info-card />
<line-chart class="chart" />
</template>
<style scoped> <style scoped>
.chart { .chart {
margin-top: 20px; margin-top: 20px;

View File

@@ -1,21 +1,5 @@
<template>
<div class="login-container">
<n-card class="login-card" title="Login">
<n-space vertical>
<n-input
v-model:value="authKey"
type="password"
placeholder="Auth Key"
@keyup.enter="handleLogin"
/>
<n-button type="primary" block @click="handleLogin" :loading="loading">Login</n-button>
</n-space>
</n-card>
</div>
</template>
<script setup lang="ts"> <script setup lang="ts">
import { authService } from "@/services/auth"; import { useAuthService } from "@/services/auth";
import { NButton, NCard, NInput, NSpace, useMessage } from "naive-ui"; import { NButton, NCard, NInput, NSpace, useMessage } from "naive-ui";
import { ref } from "vue"; import { ref } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
@@ -24,6 +8,7 @@ const authKey = ref("");
const loading = ref(false); const loading = ref(false);
const router = useRouter(); const router = useRouter();
const message = useMessage(); const message = useMessage();
const { login } = useAuthService();
const handleLogin = async () => { const handleLogin = async () => {
if (!authKey.value) { if (!authKey.value) {
@@ -31,7 +16,7 @@ const handleLogin = async () => {
return; return;
} }
loading.value = true; loading.value = true;
const success = await authService.login(authKey.value); const success = await login(authKey.value);
loading.value = false; loading.value = false;
if (success) { if (success) {
router.push("/"); router.push("/");
@@ -41,16 +26,38 @@ const handleLogin = async () => {
}; };
</script> </script>
<template>
<div class="login-container">
<n-card class="login-card" title="登录">
<div>auth: {{ authKey }}</div>
<n-space vertical>
<n-input
v-model:value="authKey"
type="password"
placeholder="Auth Key"
@keyup.enter="handleLogin"
/>
<n-button class="login-btn" type="primary" block @click="handleLogin" :loading="loading">
Login
</n-button>
</n-space>
</n-card>
</div>
</template>
<style scoped> <style scoped>
.login-container { .login-container {
height: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100vh;
background-color: #f0f2f5;
} }
.login-card { .login-card {
width: 400px; max-width: 400px;
}
.login-btn {
margin-top: 10px;
} }
</style> </style>