添加登录功能
This commit is contained in:
7
components.d.ts
vendored
7
components.d.ts
vendored
@@ -10,9 +10,16 @@ declare module 'vue' {
|
|||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
Editor: typeof import('./src/components/Editor.vue')['default']
|
Editor: typeof import('./src/components/Editor.vue')['default']
|
||||||
Editors: typeof import('./src/components/Editors.vue')['default']
|
Editors: typeof import('./src/components/Editors.vue')['default']
|
||||||
|
Login: typeof import('./src/components/Login.vue')['default']
|
||||||
|
NAlert: typeof import('naive-ui')['NAlert']
|
||||||
NButton: typeof import('naive-ui')['NButton']
|
NButton: typeof import('naive-ui')['NButton']
|
||||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||||
NFlex: typeof import('naive-ui')['NFlex']
|
NFlex: typeof import('naive-ui')['NFlex']
|
||||||
|
NForm: typeof import('naive-ui')['NForm']
|
||||||
|
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||||
|
NInput: typeof import('naive-ui')['NInput']
|
||||||
|
NModal: typeof import('naive-ui')['NModal']
|
||||||
|
NModalProvider: typeof import('naive-ui')['NModalProvider']
|
||||||
NSplit: typeof import('naive-ui')['NSplit']
|
NSplit: typeof import('naive-ui')['NSplit']
|
||||||
NTabPane: typeof import('naive-ui')['NTabPane']
|
NTabPane: typeof import('naive-ui')['NTabPane']
|
||||||
NTabs: typeof import('naive-ui')['NTabs']
|
NTabs: typeof import('naive-ui')['NTabs']
|
||||||
|
|||||||
26
src/App.vue
26
src/App.vue
@@ -1,6 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { dateZhCN, zhCN } from "naive-ui"
|
import { dateZhCN, zhCN } from "naive-ui"
|
||||||
import { useMagicKeys, whenever } from "@vueuse/core"
|
import { useMagicKeys, whenever } from "@vueuse/core"
|
||||||
|
import Login from "./components/Login.vue"
|
||||||
|
import { onMounted, watch } from "vue"
|
||||||
|
import { getMyProfile } from "./api"
|
||||||
|
import { authed, username } from "./store/user"
|
||||||
|
|
||||||
const { ctrl_s } = useMagicKeys({
|
const { ctrl_s } = useMagicKeys({
|
||||||
passive: false,
|
passive: false,
|
||||||
@@ -17,15 +21,27 @@ const { ctrl_r } = useMagicKeys({
|
|||||||
})
|
})
|
||||||
whenever(ctrl_s, () => {})
|
whenever(ctrl_s, () => {})
|
||||||
whenever(ctrl_r, () => {})
|
whenever(ctrl_r, () => {})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const data = await getMyProfile()
|
||||||
|
username.value = data
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(authed, (v) => {
|
||||||
|
if (v) {
|
||||||
|
localStorage.setItem("web-isloggedin", "true")
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem("web-isloggedin")
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-config-provider
|
<n-config-provider class="myContainer" :locale="zhCN" :date-locale="dateZhCN">
|
||||||
class="myContainer"
|
<n-modal-provider>
|
||||||
:locale="zhCN"
|
|
||||||
:date-locale="dateZhCN"
|
|
||||||
>
|
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
|
<Login />
|
||||||
|
</n-modal-provider>
|
||||||
</n-config-provider>
|
</n-config-provider>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
49
src/api.ts
Normal file
49
src/api.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import axios from "axios"
|
||||||
|
import { router } from "./router"
|
||||||
|
|
||||||
|
const http = axios.create({
|
||||||
|
baseURL: "http://localhost:8000/api",
|
||||||
|
xsrfCookieName: "xsrfCookieName",
|
||||||
|
xsrfHeaderName: "X-CSRFTOKEN",
|
||||||
|
withCredentials: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
http.interceptors.response.use(
|
||||||
|
(res) => {
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
if (err.response) {
|
||||||
|
switch (err.response.status) {
|
||||||
|
case 401: // 未授权
|
||||||
|
localStorage.removeItem("web-isloggedin")
|
||||||
|
alert("未登录")
|
||||||
|
router.push("/")
|
||||||
|
break
|
||||||
|
case 403: // 禁止访问
|
||||||
|
alert("禁止访问")
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
console.error("出现错误:", err.response.status, err.response.data)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("Network error:", err.message)
|
||||||
|
}
|
||||||
|
return Promise.reject(err)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export async function login(username: string, password: string) {
|
||||||
|
const res = await http.post("/account/login", { username, password })
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function logout() {
|
||||||
|
const res = await http.post("/account/logout")
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMyProfile() {
|
||||||
|
const res = await http.get("/account/profile")
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
@@ -67,8 +67,10 @@
|
|||||||
</n-flex>
|
</n-flex>
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<n-flex class="suffix">
|
<n-flex align="center" class="suffix">
|
||||||
<!-- <n-button>登录</n-button> -->
|
<!-- <span>{{ username }}</span>
|
||||||
|
<n-button v-if="!authed" @click="handleLogin">登录</n-button>
|
||||||
|
<n-button v-else @click="handleLogout">退出</n-button> -->
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</template>
|
</template>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
@@ -76,7 +78,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import Editor from "./Editor.vue"
|
import Editor from "./Editor.vue"
|
||||||
import { css, html, js, reset, size, tab } from "../store"
|
import { html, css, js, tab, size, reset } from "../store/editors"
|
||||||
|
import { username, authed } from "../store/user"
|
||||||
|
import { loginModal } from "../store/modal"
|
||||||
|
import { logout } from "../api"
|
||||||
|
|
||||||
function changeTab(name: string) {
|
function changeTab(name: string) {
|
||||||
tab.value = name
|
tab.value = name
|
||||||
@@ -85,6 +90,15 @@ function changeTab(name: string) {
|
|||||||
function changeSize(num: number) {
|
function changeSize(num: number) {
|
||||||
size.value = num
|
size.value = num
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleLogin() {
|
||||||
|
loginModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLogout() {
|
||||||
|
await logout()
|
||||||
|
username.value = ""
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.pane {
|
.pane {
|
||||||
|
|||||||
52
src/components/Login.vue
Normal file
52
src/components/Login.vue
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<template>
|
||||||
|
<n-modal
|
||||||
|
preset="card"
|
||||||
|
title="登录"
|
||||||
|
style="width: 400px"
|
||||||
|
v-model:show="loginModal"
|
||||||
|
>
|
||||||
|
<n-form>
|
||||||
|
<n-form-item label="用户名">
|
||||||
|
<n-input v-model:value="name"></n-input>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="密码">
|
||||||
|
<n-input v-model:value="password"></n-input>
|
||||||
|
</n-form-item>
|
||||||
|
<n-alert
|
||||||
|
type="error"
|
||||||
|
v-if="showMeesage"
|
||||||
|
class="message"
|
||||||
|
title="登录失败"
|
||||||
|
></n-alert>
|
||||||
|
<n-flex>
|
||||||
|
<n-button @click="submit">登录</n-button>
|
||||||
|
</n-flex>
|
||||||
|
</n-form>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue"
|
||||||
|
import { login } from "../api"
|
||||||
|
import { loginModal } from "../store/modal"
|
||||||
|
import { username } from "../store/user"
|
||||||
|
|
||||||
|
const name = ref("")
|
||||||
|
const password = ref("")
|
||||||
|
|
||||||
|
const showMeesage = ref(false)
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
try {
|
||||||
|
const data = await login(name.value, password.value)
|
||||||
|
username.value = data
|
||||||
|
loginModal.value = false
|
||||||
|
} catch (err) {
|
||||||
|
showMeesage.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.message {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { watchDebounced } from "@vueuse/core"
|
import { watchDebounced } from "@vueuse/core"
|
||||||
import { css, html, js } from "../store"
|
import { html, css, js } from "../store/editors"
|
||||||
import { onMounted, useTemplateRef } from "vue"
|
import { onMounted, useTemplateRef } from "vue"
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { css, html, js, tab } from "../store"
|
import { html, css, js, tab } from "../store/editors"
|
||||||
import { onMounted, ref, useTemplateRef, watch } from "vue"
|
import { onMounted, ref, useTemplateRef, watch } from "vue"
|
||||||
import { marked } from "marked"
|
import { marked } from "marked"
|
||||||
import { useStorage } from "@vueuse/core"
|
import { useStorage } from "@vueuse/core"
|
||||||
@@ -61,7 +61,7 @@ function addButton() {
|
|||||||
const btn = copy.children[1] as HTMLDivElement
|
const btn = copy.children[1] as HTMLDivElement
|
||||||
btn.onclick = () => {
|
btn.onclick = () => {
|
||||||
tab.value = lang
|
tab.value = lang
|
||||||
const content = pre.children[1].textContent
|
const content = pre.children[1].textContent!
|
||||||
if (lang === "html") html.value = content
|
if (lang === "html") html.value = content
|
||||||
if (lang === "css") css.value = content
|
if (lang === "css") css.value = content
|
||||||
if (lang === "js") js.value = content
|
if (lang === "js") js.value = content
|
||||||
|
|||||||
@@ -41,10 +41,11 @@ marked.use(
|
|||||||
marked.use(alert({ variants: alertVariants }))
|
marked.use(alert({ variants: alertVariants }))
|
||||||
|
|
||||||
const template = `<div class="markedown-body-preview">{preview}</div>`
|
const template = `<div class="markedown-body-preview">{preview}</div>`
|
||||||
marked.use(preview({template}))
|
marked.use(preview({ template }))
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
const naive = create()
|
const naive = create()
|
||||||
|
|
||||||
app.use(naive)
|
app.use(naive)
|
||||||
app.use(router)
|
app.use(router)
|
||||||
app.mount("#app")
|
app.mount("#app")
|
||||||
|
|||||||
1
src/pages/Protected.vue
Normal file
1
src/pages/Protected.vue
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<template>秘密花园</template>
|
||||||
@@ -1,10 +1,30 @@
|
|||||||
import { createWebHistory, createRouter } from "vue-router"
|
import { createWebHistory, createRouter } from "vue-router"
|
||||||
|
import { loginModal } from "./store/modal"
|
||||||
|
|
||||||
import Home from "./pages/Home.vue"
|
import Home from "./pages/Home.vue"
|
||||||
|
import Protected from "./pages/Protected.vue"
|
||||||
|
|
||||||
const routes = [{ path: "/", component: Home }]
|
const routes = [
|
||||||
|
{ path: "/", component: Home },
|
||||||
|
{
|
||||||
|
path: "/protected",
|
||||||
|
name: "protected",
|
||||||
|
component: Protected,
|
||||||
|
meta: { requiresAuth: true },
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
export const router = createRouter({
|
export const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes,
|
routes,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
const isLoggedIn = localStorage.getItem("web-isloggedin") === "true"
|
||||||
|
if (to.meta.requiresAuth && !isLoggedIn) {
|
||||||
|
loginModal.value = true
|
||||||
|
next(false)
|
||||||
|
} else {
|
||||||
|
next() // 允许访问
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
3
src/store/modal.ts
Normal file
3
src/store/modal.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
export const loginModal = ref(false)
|
||||||
4
src/store/user.ts
Normal file
4
src/store/user.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { computed, ref } from "vue"
|
||||||
|
|
||||||
|
export const username = ref("")
|
||||||
|
export const authed = computed(() => !!username.value)
|
||||||
Reference in New Issue
Block a user