login and auth
This commit is contained in:
@@ -52,6 +52,7 @@ export default [
|
||||
"vue/multiline-html-element-content-newline": "off",
|
||||
"vue/html-indent": ["error", 2],
|
||||
"vue/script-indent": ["error", 2],
|
||||
"vue/component-tags-order": ["error", { order: ["script", "template", "style"] }],
|
||||
|
||||
// Vue 3 Composition API 规则
|
||||
"vue/no-setup-props-destructure": "error",
|
||||
|
@@ -1,12 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import Layout from "@/components/Layout.vue";
|
||||
import { useAuthKey } from "@/services/auth";
|
||||
import { NDialogProvider, NMessageProvider } from "naive-ui";
|
||||
import { computed } from "vue";
|
||||
|
||||
const authKey = useAuthKey();
|
||||
const isLoggedIn = computed(() => !!authKey.value);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-message-provider>
|
||||
<n-dialog-provider>
|
||||
<layout />
|
||||
<layout v-if="isLoggedIn" />
|
||||
<router-view v-else />
|
||||
</n-dialog-provider>
|
||||
</n-message-provider>
|
||||
</template>
|
||||
|
@@ -9,20 +9,17 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
min-height: 100vh;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.flex {
|
@@ -1,5 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import Logout from "@/components/Logout.vue";
|
||||
import NavBar from "@/components/NavBar.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-layout v-if="authService.isLoggedIn()">
|
||||
<n-layout>
|
||||
<n-layout-header class="flex items-center">
|
||||
<h1 class="layout-header-title">T.COM</h1>
|
||||
<nav-bar />
|
||||
@@ -9,15 +14,8 @@
|
||||
<router-view />
|
||||
</n-layout-content>
|
||||
</n-layout>
|
||||
<router-view v-else />
|
||||
</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>
|
||||
.layout-header-title {
|
||||
margin-top: 0;
|
||||
|
@@ -1,15 +1,16 @@
|
||||
<template>
|
||||
<n-button quaternary round @click="handleLogout">退出</n-button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { authService } from "@/services/auth";
|
||||
import { useAuthService } from "@/services/auth";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
const { logout } = useAuthService();
|
||||
|
||||
const handleLogout = () => {
|
||||
authService.logout();
|
||||
router.push("/login");
|
||||
logout();
|
||||
router.replace("/login");
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-button quaternary round @click="handleLogout">退出</n-button>
|
||||
</template>
|
||||
|
@@ -1,10 +1,6 @@
|
||||
<template>
|
||||
<n-menu mode="horizontal" :options="menuOptions" :value="activeMenu" responsive />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { MenuOption } from "naive-ui";
|
||||
import { h, computed } from "vue";
|
||||
import { computed, h } from "vue";
|
||||
import { RouterLink, useRoute } from "vue-router";
|
||||
|
||||
const menuOptions: MenuOption[] = [
|
||||
@@ -33,3 +29,7 @@ function renderMenuItem(key: string, label: string): MenuOption {
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-menu mode="horizontal" :options="menuOptions" :value="activeMenu" responsive />
|
||||
</template>
|
||||
|
@@ -2,6 +2,6 @@ import App from "@/App.vue";
|
||||
import router from "@/router";
|
||||
import naive from "naive-ui";
|
||||
import { createApp } from "vue";
|
||||
import "./style.css";
|
||||
import "./assets/style.css";
|
||||
|
||||
createApp(App).use(router).use(naive).mount("#app");
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { authService } from "@/services/auth";
|
||||
import { useAuthService } from "@/services/auth";
|
||||
import { createRouter, createWebHistory, type RouteRecordRaw } from "vue-router";
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
@@ -34,8 +34,10 @@ const router = createRouter({
|
||||
routes,
|
||||
});
|
||||
|
||||
const { checkLogin } = useAuthService();
|
||||
|
||||
router.beforeEach((to, _from, next) => {
|
||||
const loggedIn = authService.isLoggedIn();
|
||||
const loggedIn = checkLogin();
|
||||
if (to.path !== "/login" && !loggedIn) {
|
||||
return next({ path: "/login" });
|
||||
}
|
||||
|
@@ -1,33 +1,47 @@
|
||||
import http from "@/utils/http";
|
||||
import { useState } from "@/utils/state";
|
||||
|
||||
const AUTH_KEY = "authKey";
|
||||
|
||||
const login = async (authKey: string): Promise<boolean> => {
|
||||
try {
|
||||
await http.post("/auth/login", { auth_key: authKey });
|
||||
localStorage.setItem(AUTH_KEY, authKey);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Login failed:", error);
|
||||
return false;
|
||||
}
|
||||
export const useAuthKey = () => {
|
||||
return useState<string | null>(AUTH_KEY, () => null);
|
||||
};
|
||||
|
||||
const logout = (): void => {
|
||||
localStorage.removeItem(AUTH_KEY);
|
||||
};
|
||||
export function useAuthService() {
|
||||
const authKey = useAuthKey();
|
||||
|
||||
const getAuthKey = (): string | null => {
|
||||
return localStorage.getItem(AUTH_KEY);
|
||||
};
|
||||
const login = async (key: string): Promise<boolean> => {
|
||||
try {
|
||||
await http.post("/auth/login", { auth_key: key });
|
||||
localStorage.setItem(AUTH_KEY, key);
|
||||
authKey.value = key;
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Login failed:", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const isLoggedIn = (): boolean => {
|
||||
return !!localStorage.getItem(AUTH_KEY);
|
||||
};
|
||||
const logout = (): void => {
|
||||
localStorage.removeItem(AUTH_KEY);
|
||||
authKey.value = null;
|
||||
};
|
||||
|
||||
export const authService = {
|
||||
login,
|
||||
logout,
|
||||
getAuthKey,
|
||||
isLoggedIn,
|
||||
};
|
||||
const checkLogin = (): boolean => {
|
||||
if (authKey.value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const key = localStorage.getItem(AUTH_KEY);
|
||||
if (key) {
|
||||
authKey.value = key;
|
||||
}
|
||||
return !!authKey.value;
|
||||
};
|
||||
|
||||
return {
|
||||
login,
|
||||
logout,
|
||||
checkLogin,
|
||||
};
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { authService } from "@/services/auth";
|
||||
import { useAuthService } from "@/services/auth";
|
||||
import axios from "axios";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const http = axios.create({
|
||||
baseURL: "/api",
|
||||
@@ -10,7 +9,7 @@ const http = axios.create({
|
||||
|
||||
// 请求拦截器
|
||||
http.interceptors.request.use(config => {
|
||||
const authKey = authService.getAuthKey();
|
||||
const authKey = localStorage.getItem("authKey");
|
||||
if (authKey) {
|
||||
config.headers.Authorization = `Bearer ${authKey}`;
|
||||
}
|
||||
@@ -22,8 +21,9 @@ http.interceptors.response.use(
|
||||
response => response.data,
|
||||
error => {
|
||||
if (error.response && error.response.status === 401) {
|
||||
authService.logout();
|
||||
useRouter().push("/login");
|
||||
const { logout } = useAuthService();
|
||||
logout();
|
||||
window.location.href = "/login";
|
||||
}
|
||||
console.error("API Error:", error);
|
||||
return Promise.reject(error);
|
||||
|
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<base-info-card />
|
||||
<line-chart class="chart" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BaseInfoCard from "@/components/BaseInfoCard.vue";
|
||||
import LineChart from "@/components/LineChart.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<base-info-card />
|
||||
<line-chart class="chart" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.chart {
|
||||
margin-top: 20px;
|
||||
|
@@ -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">
|
||||
import { authService } from "@/services/auth";
|
||||
import { useAuthService } from "@/services/auth";
|
||||
import { NButton, NCard, NInput, NSpace, useMessage } from "naive-ui";
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
@@ -24,6 +8,7 @@ const authKey = ref("");
|
||||
const loading = ref(false);
|
||||
const router = useRouter();
|
||||
const message = useMessage();
|
||||
const { login } = useAuthService();
|
||||
|
||||
const handleLogin = async () => {
|
||||
if (!authKey.value) {
|
||||
@@ -31,7 +16,7 @@ const handleLogin = async () => {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
const success = await authService.login(authKey.value);
|
||||
const success = await login(authKey.value);
|
||||
loading.value = false;
|
||||
if (success) {
|
||||
router.push("/");
|
||||
@@ -41,16 +26,38 @@ const handleLogin = async () => {
|
||||
};
|
||||
</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>
|
||||
.login-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 400px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user