增加登录功能

This commit is contained in:
李志强 2025-12-26 17:20:55 +08:00
parent cd00fadea3
commit 4e9720de5c
5 changed files with 399 additions and 6 deletions

View File

@ -15,11 +15,14 @@ declare module 'vue' {
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
ElButton: typeof import('element-plus/es')['ElButton']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElOption: typeof import('element-plus/es')['ElOption']

14
frontend/src/api/login.ts Normal file
View File

@ -0,0 +1,14 @@
//进行接口API的统一管理
import { request } from "./axios";
export class login {
/**
* @description article文章详情
* @param {string} account -
* @param {string} password -
* @return {Promise}
*/
static async goLogin(account: string, password: string) {
return request("/index/user/login", { account,password }, "post");
}
}

View File

@ -52,6 +52,11 @@ const router = createRouter({
name: "search",
component: () => import("@/views/components/search.vue"),
},
{
path: "/login",
name: "login",
component: () => import("@/views/login/index.vue"),
},
],
});

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { ref, nextTick } from "vue";
import { ref, nextTick, onMounted } from "vue";
import { useRouter } from "vue-router";
import { Search, User, Setting, SwitchButton } from "@element-plus/icons-vue";
import { ElMessage, ElDialog } from "element-plus";
@ -9,9 +9,36 @@ const showSearchDialog = ref(false);
const searchText = ref("");
const searchType = ref("articles");
const searchInput = ref();
const username = ref("管理员"); // store
const isLoggedIn = ref(false); // false
const username = ref(""); //
const router = useRouter();
//
const initLoginStatus = () => {
const token = localStorage.getItem('token');
const userStr = localStorage.getItem('user');
const isLoggedInFlag = localStorage.getItem('isLoggedIn');
if (token && userStr && isLoggedInFlag === 'true') {
try {
const userData = JSON.parse(userStr);
username.value = userData.user_name || userData.user_account || '用户';
isLoggedIn.value = true;
} catch (error) {
console.error('解析用户信息失败:', error);
//
localStorage.removeItem('token');
localStorage.removeItem('user');
localStorage.removeItem('isLoggedIn');
}
}
};
//
onMounted(() => {
initLoginStatus();
});
//
const searchTypeOptions = [
{ value: "articles", label: "文章" },
@ -78,11 +105,33 @@ const handleCommand = (command: string) => {
break;
case "logout":
console.log("退出登录");
// TODO: 退
ElMessage.success("退出登录成功");
// 退
logout();
break;
}
};
// 退
const logout = () => {
//
isLoggedIn.value = false;
//
localStorage.removeItem('token');
localStorage.removeItem('user');
localStorage.removeItem('isLoggedIn');
// 退
ElMessage.success("退出登录成功");
//
};
//
const showLogin = () => {
//
router.push('/login');
};
</script>
<template>
@ -119,8 +168,8 @@ const handleCommand = (command: string) => {
<el-icon><Search /></el-icon>
</el-button>
<!-- 用户信息 -->
<div class="user-info">
<!-- 用户信息或登录按钮 -->
<div v-if="isLoggedIn" class="user-info">
<span class="username">{{ username }}</span>
<el-dropdown @command="handleCommand" class="user-dropdown">
<el-avatar :size="32" class="user-avatar">
@ -144,6 +193,11 @@ const handleCommand = (command: string) => {
</template>
</el-dropdown>
</div>
<div v-else class="login-section">
<el-button type="primary" size="small" @click="showLogin">
登录
</el-button>
</div>
</div>
</div>
@ -303,6 +357,16 @@ const handleCommand = (command: string) => {
}
}
}
.login-section {
flex-shrink: 0;
.el-button {
height: 32px;
padding: 0 16px;
font-size: 14px;
}
}
}
}
}
@ -334,6 +398,14 @@ const handleCommand = (command: string) => {
.username {
display: none; //
}
.login-section {
.el-button {
height: 28px;
padding: 0 12px;
font-size: 13px;
}
}
}
}
}

View File

@ -0,0 +1,299 @@
<template>
<div class="login-bg">
<div class="login-container">
<div class="login-left">
<img
src="@/assets/imgs/logo-light.png"
alt="Yunzer Logo"
class="login-logo"
/>
<div class="login-welcome">
<h2>欢迎来到云享社区</h2>
<p>集学习成长与分享于一体的平台</p>
</div>
</div>
<div class="login-form-area">
<h3 class="login-title">用户登录</h3>
<el-form
:model="form"
:rules="rules"
ref="loginForm"
class="login-form"
@submit.native.prevent="onLogin"
>
<el-form-item prop="username">
<el-input
v-model="form.username"
size="large"
placeholder="账号/邮箱"
prefix-icon="el-icon-user"
clearable
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="form.password"
size="large"
placeholder="登录密码"
prefix-icon="el-icon-lock"
show-password
clearable
@keyup.enter="onLogin"
/>
</el-form-item>
<el-form-item>
<div class="login-actions">
<el-checkbox v-model="form.remember">记住我</el-checkbox>
<a class="link" @click.prevent="toForgot">忘记密码?</a>
</div>
</el-form-item>
<el-form-item>
<el-button
type="primary"
class="login-btn"
size="large"
:loading="loading"
@click="onLogin"
block
>
登录
</el-button>
</el-form-item>
</el-form>
<div class="login-footer">
没有账号
<a class="link" @click.prevent="toRegister">立即注册</a>
</div>
</div>
</div>
<div class="login-copyright">© 2025 Yunzer | 苏ICP备2023006641号</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { login } from "@/api/login";
//
const router = useRouter();
const form = ref({
username: "",
password: "",
remember: true,
});
const loading = ref(false);
const loginForm = ref();
const rules = {
username: [{ required: true, message: "请输入账号/邮箱", trigger: "blur" }],
password: [{ required: true, message: "请输入登录密码", trigger: "blur" }],
};
const onLogin = () => {
loginForm.value.validate(async (valid: boolean) => {
if (!valid) {
return;
}
loading.value = true;
try {
const response = await login.goLogin(form.value.username, form.value.password);
if (response.data.code === 0) {
//
ElMessage.success(response.data.msg || "登录成功,欢迎回来!");
//
if (response.data.data) {
//
localStorage.setItem('user', JSON.stringify(response.data.data));
// user_idtoken使
if (response.data.data.user_id) {
localStorage.setItem('token', response.data.data.user_id.toString());
} else {
// user_id使token
localStorage.setItem('token', Date.now().toString());
}
//
localStorage.setItem('isLoggedIn', 'true');
}
router.push("/");
} else {
ElMessage.error(response.data.msg || "登录失败");
}
} catch (error) {
console.error("登录失败:", error);
ElMessage.error("登录失败,请检查网络连接");
} finally {
loading.value = false;
}
});
};
function toForgot() {
ElMessage.info("请联系管理员重置密码");
}
function toRegister() {
router.push("/register");
}
</script>
<style scoped>
.login-bg {
min-height: 100vh;
background: linear-gradient(120deg, #5bbefa 0%, #786de0 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
}
.login-container {
background: #fff;
min-width: 380px;
max-width: 920px;
width: 90vw;
min-height: 450px;
display: flex;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 12px 36px 2px rgba(103, 117, 214, 0.13),
0 2px 4px rgba(64, 158, 255, 0.08);
margin: 0 auto;
}
.login-left {
flex: 1.05;
background: linear-gradient(136deg, #67cee8 0%, #5347c9 100%);
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.login-logo {
width: 90px;
margin-bottom: 10px;
}
.login-welcome h2 {
font-weight: 700;
font-size: 26px;
margin-bottom: 5px;
}
.login-welcome p {
margin: 0;
font-size: 15px;
opacity: 0.86;
letter-spacing: 1px;
}
.login-form-area {
flex: 1;
display: flex;
flex-direction: column;
padding: 54px 38px 36px 38px;
justify-content: center;
background: #fff;
}
.login-title {
font-weight: 600;
font-size: 23px;
margin-bottom: 32px;
color: #303133;
letter-spacing: 2px;
text-align: left;
}
.login-form :deep(.el-input__wrapper) {
background: #f6f9ff;
border: none;
border-radius: 9px;
}
.login-form :deep(.el-input__inner) {
font-size: 16px;
color: #333;
}
.login-actions {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.login-btn {
width: 100%;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
background-image: linear-gradient(88deg, #67cee8 0%, #5347c9 100%);
border: none;
box-shadow: 0 2px 10px rgba(76, 130, 255, 0.07);
transition: background 0.19s;
}
.login-btn:hover {
background-image: linear-gradient(88deg, #5bbefa 0%, #786de0 100%);
}
.link {
color: #7364ec;
cursor: pointer;
margin-left: 6px;
text-decoration: none;
font-size: 14px;
transition: color 0.19s;
}
.link:hover {
color: #6daff7;
text-decoration: underline;
}
.login-footer {
margin-top: 16px;
text-align: center;
font-size: 14px;
color: #aaa;
}
.login-footer .link {
font-weight: 600;
}
.login-copyright {
position: absolute;
bottom: 14px;
left: 0;
width: 100vw;
text-align: center;
color: #e6f6ff;
font-size: 13px;
letter-spacing: 0.5px;
opacity: 0.88;
z-index: 1;
}
/* 响应式 */
@media (max-width: 750px) {
.login-container {
flex-direction: column;
min-width: 280px;
max-width: 99vw;
width: 99vw;
border-radius: 10px;
}
.login-left {
min-height: 135px;
padding: 28px 0;
border-radius: 12px 12px 0 0;
flex: none;
}
.login-form-area {
padding: 32px 12vw 32px 12vw;
min-width: unset;
}
}
@media (max-width: 520px) {
.login-form-area {
padding: 24px 8vw 20px 8vw;
}
.login-container {
min-width: 0;
}
}
</style>