feat: 完成用户登录界面,api还需完善
This commit is contained in:
1 parent
4c69f57aaa
commit
d7dcc473a9
18 files changed
+679
-127
No files matched your search
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<svg t="1736783746369" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="26076">
|
||||
<path
|
||||
d="M512 0C229.240755 0 0 229.240755 0 512s229.240755 512 512 512 512-229.240755 512-512S794.759245 0 512 0z m328.597736 872.573585c-2.125283-3.526038-4.250566-7.052075-6.424151-10.529811-0.676226-1.062642-1.01434-2.366792-2.270189-5.264906 4.926792 1.932075 8.404528 3.284528 12.655095 4.975094-10.771321-47.770566-30.961509-90.855849-59.314717-130.221887-28.449811-39.462642-62.744151-73.032453-103.510944-101.047547 8.501132-5.409811 16.615849-3.091321 25.068679-1.78717-7.148679-19.320755-19.900377-34.487547-32.26566-50.378867 16.953962-9.080755 24.295849-24.923774 31.396226-39.945661-7.824906-0.966038-15.408302-1.883774-25.020377-3.043019 4.975094-4.685283 8.549434-7.438491 11.350944-10.819622 11.882264-14.297358 17.436981-31.541132 20.335094-49.267925 4.34717-26.517736 6.858868-53.373585 10.384906-80.036226 0.434717-3.381132 1.835472-7.052075 3.815849-9.756981 8.404528-11.350943 11.978868-24.58566 13.524528-38.013585 3.187925-27.193962 5.409811-54.53283 7.390189-81.823397 1.062642-14.490566 0.724528-29.077736 1.159245-43.616603 0.289811-8.646038 1.207547-17.243774 1.352453-25.889812 0.096604-5.89283-3.429434-8.259623-9.322264-8.45283-11.833962-0.338113-22.073962 4.298868-31.97585 9.805283-30.478491 17.002264-56.754717 39.607547-81.823396 63.516981-9.515472 9.080755-19.803774 15.504906-32.845283 17.243774-2.753208 0.386415-6.037736 0.338113-8.404528-0.821132-39.94566-19.272453-82.258113-26.759245-126.261132-23.909434-23.281509 1.497358-45.983396 6.617358-67.767547 15.36-13.57283 5.458113-27.628679 5.168302-41.443019 1.78717-4.540377-1.110943-8.259623-5.554717-12.220378-8.69434-28.594717-22.653585-57.09283-45.355472-85.687547-67.960755-5.699623-4.492075-8.259623-4.34717-10.481509 2.753208-3.284528 10.433208-6.279245 21.349434-6.665661 32.169057-1.352453 37.289057 5.651321 73.805283 12.848302 110.176603 1.738868 8.935849 4.298868 17.243774 1.110944 26.904151-4.298868 12.993208-1.255849 26.710943 1.738868 39.607547 7.196981 30.864906 15.456604 61.44 23.57132 93.270944-4.250566 0.531321-9.756981 1.255849-15.843018 1.980377 2.270189 15.649811 8.984151 29.802264 18.354717 42.698868 9.322264 12.848302 19.272453 25.213585 27.91849 36.419623-4.636981 6.424151-9.273962 12.896604-13.910943 19.369056 0.434717 0.579623 0.869434 1.159245 1.352453 1.78717 10.529811-4.685283 20.818113-6.810566 29.415849 1.159245-5.651321 5.941132-11.206038 11.78566-16.760755 17.678491l1.738868 1.883774c3.815849-2.318491 7.583396-4.636981 12.365283-7.486793l-18.258113 30.768302c6.375849-2.221887 10.626415-3.670943 14.925283-5.168302-13.476226 11.544151-19.369057 26.904151-23.426415 43.858113 2.56-0.966038 4.878491-1.835472 8.935849-3.33283-13.186415 33.569811-10.723019 66.415094-3.236227 100.033208-4.34717-1.01434-7.535094-1.78717-10.771321-2.56-3.381132 11.689057-1.110943 19.030943 13.572831 43.036981-5.603019-0.821132-9.708679-1.449057-14.393963-2.173585-0.724528 6.424151 2.076981 12.606792 5.892831 18.161509 2.704906 3.912453 2.753208 6.955472 0.869433 11.350944-4.10566 9.515472-7.293585 19.369057-11.254339 30.188679 2.173585-1.01434 3.526038-1.642264 5.216604-2.463396-6.472453 20.673208-10.191698 41.491321-12.26868 62.454339C125.391698 855.184906 24.150943 695.402264 24.150943 512 24.150943 242.572075 242.572075 24.150943 512 24.150943s487.849057 218.421132 487.849057 487.849057c0 142.828679-61.391698 271.36-159.251321 360.573585z"
|
||||
fill="#15aa87" p-id="26077"></path>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -35,6 +35,14 @@ export const useUserStore = defineStore("user", () => {
|
||||
_token.value = value;
|
||||
localStorage.setItem("miao-token", value);
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
setLoginStatus(false);
|
||||
_token.value = undefined;
|
||||
_tokenExpireTime.value = undefined;
|
||||
localStorage.removeItem("miao-token");
|
||||
localStorage.removeItem("miao-token-expire-time");
|
||||
};
|
||||
return {
|
||||
isLogin,
|
||||
setLoginStatus,
|
||||
@@ -43,7 +51,8 @@ export const useUserStore = defineStore("user", () => {
|
||||
tokenExpireTime,
|
||||
setTokenExpireTime,
|
||||
_tokenExpireTime,
|
||||
_token
|
||||
_token,
|
||||
logout
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
export const uploadPost = (body: {
|
||||
title: string,
|
||||
summary: string,
|
||||
type: string,
|
||||
tags: string[],
|
||||
url: string,
|
||||
date: number,
|
||||
content: string
|
||||
}) => $fetch('/api/posts/uploadPost', {
|
||||
method: 'POST',
|
||||
body
|
||||
})
|
||||
|
||||
export const updatePost = (body: {
|
||||
id: number,
|
||||
title: string,
|
||||
summary: string,
|
||||
type: string,
|
||||
tags: string[],
|
||||
url: string,
|
||||
date: number,
|
||||
content: string
|
||||
}) => $fetch('/api/posts/uploadPost', {
|
||||
method: 'POST',
|
||||
body
|
||||
})
|
||||
@@ -0,0 +1,106 @@
|
||||
import needRegister from "~/server/api/user/needRegister";
|
||||
import useUserStore from "./pinia/useUserStore"
|
||||
|
||||
|
||||
export default function useFetch() {
|
||||
const userStore = useUserStore();
|
||||
return {
|
||||
post: {
|
||||
uploadPost: (body: {
|
||||
title: string,
|
||||
summary: string,
|
||||
type: string,
|
||||
tags: string[],
|
||||
url: string,
|
||||
date: number,
|
||||
content: string
|
||||
}) => $fetch('/api/posts/uploadPost', {
|
||||
method: 'POST',
|
||||
body,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${userStore.token}`
|
||||
}
|
||||
}),
|
||||
updatePost: (body: {
|
||||
id: number,
|
||||
title: string,
|
||||
summary: string,
|
||||
type: string,
|
||||
tags: string[],
|
||||
url: string,
|
||||
date: number,
|
||||
content: string
|
||||
}) => $fetch('/api/posts/editPost', {
|
||||
method: 'POST',
|
||||
body,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${userStore.token}`
|
||||
}
|
||||
}),
|
||||
getPost: (id: number) => $fetch(`/api/posts/getPost?id=${id}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${userStore.token}`
|
||||
}
|
||||
}),
|
||||
getPostList: (body: {
|
||||
page?: number,
|
||||
tag?: string
|
||||
}) => $fetch('/api/posts/getPostList', {
|
||||
method: 'POST',
|
||||
body,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${userStore.token}`
|
||||
}
|
||||
}),
|
||||
},
|
||||
user: {
|
||||
login: (body: {
|
||||
username: string,
|
||||
password: string
|
||||
}) => $fetch('/api/user/login', {
|
||||
method: 'POST',
|
||||
body
|
||||
}),
|
||||
register: (body: {
|
||||
username: string,
|
||||
password: string
|
||||
}) => $fetch('/api/user/register', {
|
||||
method: 'POST',
|
||||
body
|
||||
}),
|
||||
needRegister: () => $fetch('/api/user/needRegister'),
|
||||
refreshToken: () => $fetch('/api/user/refreshToken', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${userStore.token}`
|
||||
}
|
||||
}),
|
||||
// 暂未实现
|
||||
updatePassword: (body: {
|
||||
oldPassword: string,
|
||||
newPassword: string
|
||||
}) => $fetch('/api/user/updatePassword', {
|
||||
method: 'POST',
|
||||
body,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${userStore.token}`
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const testAuth = () => {
|
||||
const userStore = useUserStore();
|
||||
(async () => {
|
||||
const res = await $fetch('/api/test/testDir', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${userStore.token}`
|
||||
}
|
||||
})
|
||||
console.log(res)
|
||||
})()
|
||||
}
|
||||
@@ -53,10 +53,6 @@ onMounted(async () => {
|
||||
// await sleep(1000)
|
||||
postsData.value = await getData()
|
||||
isInit.value = true;
|
||||
(async () => {
|
||||
const res = await $fetch('/api/test/testDir')
|
||||
console.log(res)
|
||||
})()
|
||||
// console.log(postsData.value)
|
||||
})
|
||||
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0 auto;
|
||||
max-width: 800px;
|
||||
// max-width: 800px;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
+12
-74
@@ -1,90 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
index
|
||||
<div>
|
||||
<el-button @click="uploadTest">
|
||||
uploadPostTest
|
||||
<el-button @click="handleLogout">
|
||||
logout
|
||||
</el-button>
|
||||
<el-button @click="editTest">
|
||||
editPostTest
|
||||
</el-button>
|
||||
<el-button @click="testLogin">
|
||||
testLogin
|
||||
</el-button>
|
||||
<el-button @click="testRegister">
|
||||
testRegister
|
||||
</el-button>
|
||||
<el-button @click="testRefreshToken">
|
||||
testRefreshToken
|
||||
</el-button>
|
||||
</div>
|
||||
<nuxt-link to="user/test"> test</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useUserStore from '~/hooks/pinia/useUserStore';
|
||||
import { uploadPost, updatePost } from '~/hooks/useFetch';
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const uploadTest = () => {
|
||||
uploadPost({
|
||||
title: `测试上传`,
|
||||
summary: `测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要`,
|
||||
type:'markdown',
|
||||
tags: ['test', 'test1'],
|
||||
url: '',
|
||||
date: parseInt(new Date().getTime().toFixed()),
|
||||
content: '测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传'
|
||||
})
|
||||
const handleLogout = () => {
|
||||
userStore.logout()
|
||||
router.push('user/login')
|
||||
}
|
||||
|
||||
const editTest = () => {
|
||||
updatePost({
|
||||
id: 1,
|
||||
title: `测试上传`,
|
||||
summary: `测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要`,
|
||||
type:'markdown',
|
||||
tags: ['test', 'test1'],
|
||||
url: '',
|
||||
date: parseInt(new Date().getTime().toFixed()),
|
||||
content: '测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传'
|
||||
})
|
||||
}
|
||||
|
||||
const testLogin = async () => {
|
||||
const response = await $fetch('/api/user/login', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
username: 'admin',
|
||||
password: 'admin'
|
||||
onBeforeMount(() => {
|
||||
if (userStore.isLogin === false) {
|
||||
return router.push('user/login')
|
||||
}
|
||||
})
|
||||
const token = response.token
|
||||
userStore.setToken(token)
|
||||
}
|
||||
|
||||
const testRegister = () => {
|
||||
$fetch('/api/user/register', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
username: 'admin',
|
||||
password: 'admin'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const testRefreshToken = async () => {
|
||||
const response = await $fetch('/api/user/refreshToken', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
token: userStore.token
|
||||
}
|
||||
})
|
||||
const token = response.token
|
||||
userStore.setToken(token)
|
||||
}
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style scoped></style>
|
||||
+213
-2
@@ -1,13 +1,224 @@
|
||||
<template>
|
||||
<div>
|
||||
login
|
||||
<div class="login-warpper">
|
||||
<div class="login-card">
|
||||
<div class="login-logo">
|
||||
<cat></cat>
|
||||
</div>
|
||||
<!-- <div>杪Blog后台</div> -->
|
||||
<div class="login-form">
|
||||
<el-input class="login-form-input" v-model="username" placeholder="用户名" />
|
||||
<el-input class="login-form-input" v-model="password" placeholder="密码" type="password" show-password />
|
||||
<div :class="['login-form-message', `login-form-message-${message.type}`]">
|
||||
<span>{{ message.message }}</span>
|
||||
<nuxt-link to="/user/register">注册账户</nuxt-link>
|
||||
</div>
|
||||
<div class="login-form-button-warpper">
|
||||
<el-button class="login-form-button" @click="handleLogin">
|
||||
登 录
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useUserStore from '~/hooks/pinia/useUserStore';
|
||||
import cat from '~/components/icons/cat.vue';
|
||||
import useMiaoFetch from '~/hooks/useMiaoFetch';
|
||||
|
||||
const { user: { login } } = useMiaoFetch()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
|
||||
let isLogining = false
|
||||
|
||||
const message = ref({
|
||||
type: 'warning',
|
||||
message: ''
|
||||
})
|
||||
|
||||
watch(() => username.value + password.value, () => {
|
||||
message.value = {
|
||||
type: 'default',
|
||||
message: '',
|
||||
}
|
||||
})
|
||||
|
||||
const handleLogin = async () => {
|
||||
if (isLogining) return
|
||||
if (username.value === '' || password.value === '') {
|
||||
message.value = {
|
||||
type: 'warning',
|
||||
message: '用户名或密码不能为空'
|
||||
}
|
||||
return
|
||||
}
|
||||
isLogining = true
|
||||
|
||||
message.value = {
|
||||
type: 'default',
|
||||
message: '登录中...'
|
||||
}
|
||||
try {
|
||||
const res = await login({
|
||||
username: username.value,
|
||||
password: password.value
|
||||
})
|
||||
isLogining = false
|
||||
message.value = {
|
||||
type: 'default',
|
||||
message: '登录成功'
|
||||
}
|
||||
userStore.setLoginStatus(true)
|
||||
userStore.setToken(res.token)
|
||||
userStore.setTokenExpireTime(res.expiresIn)
|
||||
router.push('/user')
|
||||
} catch (e) {
|
||||
isLogining = false
|
||||
// @ts-ignore
|
||||
const code = e.statusCode ?? undefined
|
||||
console.log(code, code === 401)
|
||||
if (code === 401) {
|
||||
message.value = {
|
||||
type: 'warning',
|
||||
message: '用户名或密码错误'
|
||||
}
|
||||
} else {
|
||||
message.value = {
|
||||
type: 'warning',
|
||||
message: '登录失败'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (userStore.isLogin === true) {
|
||||
return router.push('/user')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-warpper {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
min-height: calc(100vh - 60px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.login-card {
|
||||
position: relative;
|
||||
width: 300px;
|
||||
// aspect-ratio: 9/16;
|
||||
border-radius: 20px;
|
||||
margin: auto;
|
||||
box-shadow: rgba(50, 50, 93, 0.25) 0px 30px 60px -12px inset, rgba(0, 0, 0, 0.3) 0px 18px 36px -18px inset;
|
||||
|
||||
.login-logo {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
// aspect-ratio: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 80px auto 40px;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.login-form {
|
||||
width: 180px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin: 40px auto 80px;
|
||||
|
||||
&-message {
|
||||
margin: -10px 0 0px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font-size: 10px;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&-warning {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
&-default {
|
||||
color: #15aa87;
|
||||
}
|
||||
|
||||
& > a {
|
||||
color: #15aa87;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-input {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
--el-input-focus-border-color: #15aa87;
|
||||
--el-input-hover-border-color: #15aa875d;
|
||||
}
|
||||
|
||||
&-button-warpper {
|
||||
.login-form-button {
|
||||
width: 150px;
|
||||
--el-button-active-border-color: #15aa87;
|
||||
--el-button-hover-border-color: #15aa875d;
|
||||
--el-button-hover-bg-color: #ffffff;
|
||||
--el-button-hover-text-color: #15aa87;
|
||||
--el-button-outline-color: #15aa87a0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// &::before {
|
||||
// content: '';
|
||||
// display: block;
|
||||
// width: 20vw;
|
||||
// aspect-ratio: 1;
|
||||
// border-radius: 50%;
|
||||
// background-color: aqua;
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// left: 40%;
|
||||
// }
|
||||
|
||||
// &::after {
|
||||
// content: '';
|
||||
// display: block;
|
||||
// width: 20vw;
|
||||
// aspect-ratio: 1;
|
||||
// border-radius: 50%;
|
||||
// background-color: rgb(35, 191, 79);
|
||||
// position: absolute;
|
||||
// bottom: 0;
|
||||
// left: 40%;
|
||||
// }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<style>
|
||||
/* .login-form-input .el-input__wrapper {
|
||||
transition: box-shadow .3s ease;
|
||||
}
|
||||
.login-form-input .el-input__wrapper.is-focus {
|
||||
box-shadow: 0 0 0 1px #15aa87 !important;
|
||||
} */
|
||||
</style>
|
||||
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<div class="login-warpper">
|
||||
<div class="login-card">
|
||||
<div class="login-logo">
|
||||
<cat></cat>
|
||||
</div>
|
||||
<!-- <div>杪Blog后台</div> -->
|
||||
<div class="login-form">
|
||||
<el-input class="login-form-input" v-model="username" placeholder="用户名" />
|
||||
<el-input class="login-form-input" v-model="password" placeholder="密码" type="password" show-password />
|
||||
<div :class="['login-form-message', `login-form-message-${message.type}`]">
|
||||
<span>{{ message.message }}</span>
|
||||
<nuxt-link to="/user/login">登录账户</nuxt-link>
|
||||
</div>
|
||||
<div class="login-form-button-warpper">
|
||||
<el-button class="login-form-button" @click="handleRegister">
|
||||
注 册
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useUserStore from '~/hooks/pinia/useUserStore';
|
||||
import cat from '~/components/icons/cat.vue';
|
||||
import useMiaoFetch from '~/hooks/useMiaoFetch';
|
||||
|
||||
const { user: { register } } = useMiaoFetch()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
|
||||
let isLogining = false
|
||||
|
||||
const message = ref({
|
||||
type: 'warning',
|
||||
message: ''
|
||||
})
|
||||
|
||||
watch(() => username.value + password.value, () => {
|
||||
message.value = {
|
||||
type: 'default',
|
||||
message: '',
|
||||
}
|
||||
})
|
||||
|
||||
const handleRegister = async () => {
|
||||
if (isLogining) return
|
||||
if (username.value === '' || password.value === '') {
|
||||
message.value = {
|
||||
type: 'warning',
|
||||
message: '用户名或密码不能为空'
|
||||
}
|
||||
return
|
||||
}
|
||||
isLogining = true
|
||||
|
||||
message.value = {
|
||||
type: 'default',
|
||||
message: '注册中...'
|
||||
}
|
||||
try {
|
||||
const res = await register({
|
||||
username: username.value,
|
||||
password: password.value
|
||||
})
|
||||
message.value = {
|
||||
type: 'default',
|
||||
message: '注册成功'
|
||||
}
|
||||
userStore.setLoginStatus(true)
|
||||
userStore.setToken(res.token)
|
||||
userStore.setTokenExpireTime(res.expiresIn)
|
||||
router.push('/user')
|
||||
} catch (e) {
|
||||
message.value = {
|
||||
type: 'warning',
|
||||
message: '注册失败'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-warpper {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
min-height: calc(100vh - 60px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.login-card {
|
||||
position: relative;
|
||||
width: 300px;
|
||||
// aspect-ratio: 9/16;
|
||||
border-radius: 20px;
|
||||
margin: auto;
|
||||
box-shadow: rgba(50, 50, 93, 0.25) 0px 30px 60px -12px inset, rgba(0, 0, 0, 0.3) 0px 18px 36px -18px inset;
|
||||
|
||||
.login-logo {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
// aspect-ratio: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 80px auto 40px;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.login-form {
|
||||
width: 180px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin: 40px auto 80px;
|
||||
|
||||
&-message {
|
||||
margin: -10px 0 0px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font-size: 10px;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&-warning {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
&-default {
|
||||
color: #15aa87;
|
||||
}
|
||||
|
||||
&>a {
|
||||
color: #15aa87;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-input {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
--el-input-focus-border-color: #15aa87;
|
||||
--el-input-hover-border-color: #15aa875d;
|
||||
}
|
||||
|
||||
&-button-warpper {
|
||||
.login-form-button {
|
||||
width: 150px;
|
||||
--el-button-active-border-color: #15aa87;
|
||||
--el-button-hover-border-color: #15aa875d;
|
||||
--el-button-hover-bg-color: #ffffff;
|
||||
--el-button-hover-text-color: #15aa87;
|
||||
--el-button-outline-color: #15aa87a0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<div>
|
||||
its the page which is used to test the api
|
||||
<div>
|
||||
<el-button @click="uploadTest">
|
||||
uploadPostTest
|
||||
</el-button>
|
||||
<el-button @click="editTest">
|
||||
editPostTest
|
||||
</el-button>
|
||||
<el-button @click="testLogin">
|
||||
testLogin
|
||||
</el-button>
|
||||
<el-button @click="testRegister">
|
||||
testRegister
|
||||
</el-button>
|
||||
<el-button @click="testRefreshToken">
|
||||
testRefreshToken
|
||||
</el-button>
|
||||
<el-button @click="testAuth">
|
||||
testAuth
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useUserStore from '~/hooks/pinia/useUserStore';
|
||||
import useMiaoFetch, { testAuth } from '~/hooks/useMiaoFetch';
|
||||
|
||||
const { post: { uploadPost, updatePost } } = useMiaoFetch()
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const uploadTest = () => {
|
||||
uploadPost({
|
||||
title: `测试上传`,
|
||||
summary: `测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要`,
|
||||
type: 'markdown',
|
||||
tags: ['test', 'test1'],
|
||||
url: '',
|
||||
date: parseInt(new Date().getTime().toFixed()),
|
||||
content: '测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传'
|
||||
})
|
||||
}
|
||||
|
||||
const editTest = () => {
|
||||
updatePost({
|
||||
id: 1,
|
||||
title: `测试上传`,
|
||||
summary: `测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要测试摘要`,
|
||||
type: 'markdown',
|
||||
tags: ['test', 'test1'],
|
||||
url: '',
|
||||
date: parseInt(new Date().getTime().toFixed()),
|
||||
content: '测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传测试上传'
|
||||
})
|
||||
}
|
||||
|
||||
const testLogin = async () => {
|
||||
const response = await $fetch('/api/user/login', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
username: 'admin',
|
||||
password: 'admin'
|
||||
}
|
||||
})
|
||||
const token = response.token
|
||||
userStore.setToken(token)
|
||||
}
|
||||
|
||||
const testRegister = () => {
|
||||
$fetch('/api/user/register', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
username: 'admin',
|
||||
password: 'admin'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const testRefreshToken = async () => {
|
||||
const response = await $fetch('/api/user/refreshToken', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
token: userStore.token
|
||||
}
|
||||
})
|
||||
const token = response.token
|
||||
userStore.setToken(token)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// testAuth()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -19,7 +19,7 @@ export default defineEventHandler(async (event) => {
|
||||
userDB.prepare('UPDATE user SET lastLoginTime = ? WHERE id = ?').run(Date.now(), user.id);
|
||||
|
||||
// 生成 JWT
|
||||
const token = createToken(user.id);
|
||||
const token = createToken({ userId: user.id, password: user.password, username: user.username });
|
||||
|
||||
userDB.close();
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// import bcrypt from 'bcryptjs';
|
||||
import BetterSqlite3 from 'better-sqlite3';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
// const body = await readBody(event);
|
||||
const userDB = BetterSqlite3('./userData/db/user.db');
|
||||
|
||||
// 临时使用,若表中已有用户,则拒绝创建新的用户
|
||||
const existingUser = userDB.prepare('SELECT * FROM user').get();
|
||||
userDB.close();
|
||||
if (existingUser) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
});
|
||||
@@ -9,9 +9,13 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
try {
|
||||
// 验证旧的 Token
|
||||
const decoded = jwt.verify(token, jwtSecret) as { userId: number };
|
||||
const decoded = jwt.verify(token, jwtSecret) as any;
|
||||
// 生成新的 Token
|
||||
const newToken = jwt.sign({ userId: decoded.userId }, jwtSecret, { expiresIn: '168h' });
|
||||
const newToken = jwt.sign({
|
||||
username: decoded.username,
|
||||
password: decoded.password,
|
||||
userId: decoded.userId,
|
||||
}, jwtSecret, { expiresIn: '168h' });
|
||||
|
||||
return { token: newToken, expiresIn: Date.now() + 168 * 60 * 60 * 1000 };
|
||||
} catch (err) {
|
||||
|
||||
@@ -4,16 +4,23 @@ import BetterSqlite3 from 'better-sqlite3';
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
const { username, password } = body;
|
||||
|
||||
// 检查用户名是否已存在
|
||||
const userDB = BetterSqlite3('./userData/db/user.db');
|
||||
const existingUser = userDB.prepare('SELECT * FROM user WHERE username = ?').get(username);
|
||||
|
||||
// 临时使用,若表中已有用户,则拒绝创建新的用户
|
||||
const existingUser = userDB.prepare('SELECT * FROM user').get();
|
||||
if (existingUser) {
|
||||
userDB.close();
|
||||
throw createError({ statusCode: 400, statusMessage: 'Username already exists' });
|
||||
throw createError({ statusCode: 400, statusMessage: 'User already exists' });
|
||||
}
|
||||
|
||||
// 哈希密码
|
||||
// // 检查用户名是否已存在
|
||||
// const existingUser = userDB.prepare('SELECT * FROM user WHERE username = ?').get(username);
|
||||
// if (existingUser) {
|
||||
// userDB.close();
|
||||
// throw createError({ statusCode: 400, statusMessage: 'Username already exists' });
|
||||
// }
|
||||
|
||||
// 哈希密码,暂未实现,未来实现
|
||||
// const salt = getKey().substring(0, 10);
|
||||
// const hashedPassword = bcrypt.hashSync(password, salt);
|
||||
const hashedPassword = password
|
||||
@@ -26,7 +33,7 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
// 生成 JWT
|
||||
const userId = result.lastInsertRowid as number;
|
||||
const token = createToken(userId);
|
||||
const token = createToken({ userId, password: hashedPassword, username });
|
||||
|
||||
userDB.close();
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import BetterSqlite3 from 'better-sqlite3';
|
||||
|
||||
// 暂未实现
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
const { oldPassword, newPassword } = body;
|
||||
@@ -23,7 +25,7 @@ export default defineEventHandler(async (event) => {
|
||||
userDB.prepare('UPDATE user SET password = ? WHERE id = ?').run(hashedPassword, userId);
|
||||
|
||||
// 生成新的 JWT
|
||||
const token = createToken(userId);
|
||||
const token = createToken({ userId, password: user.password, username: user.username });
|
||||
|
||||
userDB.close();
|
||||
|
||||
|
||||
@@ -5,21 +5,29 @@ import getKey from "../utils/getKey"
|
||||
|
||||
const pathNeedAuth = serverConfig.auth.api
|
||||
|
||||
export default defineEventHandler((event) => {
|
||||
const key = getKey()
|
||||
export default defineEventHandler(async (event) => {
|
||||
const path = getRequestURL(event).pathname
|
||||
// console.log(path)
|
||||
if (pathNeedAuth.has(path)) {
|
||||
const authHeader = event.node.req.headers.authorization
|
||||
const key = getKey()
|
||||
// @ts-ignore
|
||||
const authHeader = event.headers.get('authorization');
|
||||
// console.log(authHeader)
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
throw createError({ statusCode: 401, statusMessage: 'Unauthorized' })
|
||||
}
|
||||
const token = authHeader.split('Bearer ')[1].trim()
|
||||
try {
|
||||
const decoded = jwt.verify(token, key)
|
||||
const decoded = jwt.verify(token, key) as { userId: number, password: string, username: string, exp: number }
|
||||
if (decoded.exp > Date.now()) {
|
||||
// console.log('token expired')
|
||||
throw createError({ statusCode: 401, statusMessage: 'Token invalid or expired', data: { err: 'token expired', token } })
|
||||
}
|
||||
// console.log(decoded)
|
||||
// 将用户ID挂载到事件对象,供后续使用
|
||||
// event.context.userId = decoded.userId
|
||||
} catch (err) {
|
||||
throw createError({ statusCode: 401, statusMessage: 'Token invalid or expired' })
|
||||
throw createError({ statusCode: 401, statusMessage: 'Token invalid or expired', data: { err, token } })
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -2,7 +2,10 @@ export default {
|
||||
auth: {
|
||||
// 需要进行鉴权的api
|
||||
api: new Set([
|
||||
'/api/test/testDir'
|
||||
'/api/test/testDir',
|
||||
'/api/posts/editPost',
|
||||
'/api/posts/uploadPost',
|
||||
'/api/user/updatePassword'
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import jwt from "jsonwebtoken"
|
||||
import getKey from "./getKey"
|
||||
|
||||
|
||||
export default function createToken(userId: number): string {
|
||||
export default function createToken(payload: { userId: number, password: string, username: string }): string {
|
||||
const key = getKey()
|
||||
return jwt.sign({ userId }, key, { expiresIn: '168h' })
|
||||
return jwt.sign(payload, key, { expiresIn: '168h' })
|
||||
}
|
||||
Reference in new issue
Block a user