增加登录功能
This commit is contained in:
parent
cd00fadea3
commit
4e9720de5c
3
frontend/components.d.ts
vendored
3
frontend/components.d.ts
vendored
@ -15,11 +15,14 @@ declare module 'vue' {
|
|||||||
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
||||||
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
|
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
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']
|
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElOption: typeof import('element-plus/es')['ElOption']
|
ElOption: typeof import('element-plus/es')['ElOption']
|
||||||
|
|||||||
14
frontend/src/api/login.ts
Normal file
14
frontend/src/api/login.ts
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -52,6 +52,11 @@ const router = createRouter({
|
|||||||
name: "search",
|
name: "search",
|
||||||
component: () => import("@/views/components/search.vue"),
|
component: () => import("@/views/components/search.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
name: "login",
|
||||||
|
component: () => import("@/views/login/index.vue"),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, nextTick } from "vue";
|
import { ref, nextTick, onMounted } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { Search, User, Setting, SwitchButton } from "@element-plus/icons-vue";
|
import { Search, User, Setting, SwitchButton } from "@element-plus/icons-vue";
|
||||||
import { ElMessage, ElDialog } from "element-plus";
|
import { ElMessage, ElDialog } from "element-plus";
|
||||||
@ -9,9 +9,36 @@ const showSearchDialog = ref(false);
|
|||||||
const searchText = ref("");
|
const searchText = ref("");
|
||||||
const searchType = ref("articles");
|
const searchType = ref("articles");
|
||||||
const searchInput = ref();
|
const searchInput = ref();
|
||||||
const username = ref("管理员"); // 这里可以从 store 获取
|
const isLoggedIn = ref(false); // 登录状态,默认为false
|
||||||
|
const username = ref(""); // 用户名,从本地存储获取
|
||||||
const router = useRouter();
|
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 = [
|
const searchTypeOptions = [
|
||||||
{ value: "articles", label: "文章" },
|
{ value: "articles", label: "文章" },
|
||||||
@ -78,11 +105,33 @@ const handleCommand = (command: string) => {
|
|||||||
break;
|
break;
|
||||||
case "logout":
|
case "logout":
|
||||||
console.log("退出登录");
|
console.log("退出登录");
|
||||||
// TODO: 实现退出登录逻辑
|
// 执行退出登录逻辑
|
||||||
ElMessage.success("退出登录成功");
|
logout();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 退出登录函数
|
||||||
|
const logout = () => {
|
||||||
|
// 清除登录状态
|
||||||
|
isLoggedIn.value = false;
|
||||||
|
|
||||||
|
// 清除本地存储的所有登录相关信息
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
localStorage.removeItem('user');
|
||||||
|
localStorage.removeItem('isLoggedIn');
|
||||||
|
|
||||||
|
// 显示退出成功消息
|
||||||
|
ElMessage.success("退出登录成功");
|
||||||
|
|
||||||
|
// 留在当前页面,显示未登录状态
|
||||||
|
};
|
||||||
|
|
||||||
|
// 显示登录界面
|
||||||
|
const showLogin = () => {
|
||||||
|
// 跳转到登录页面
|
||||||
|
router.push('/login');
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -119,8 +168,8 @@ const handleCommand = (command: string) => {
|
|||||||
<el-icon><Search /></el-icon>
|
<el-icon><Search /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
<!-- 用户信息 -->
|
<!-- 用户信息或登录按钮 -->
|
||||||
<div class="user-info">
|
<div v-if="isLoggedIn" class="user-info">
|
||||||
<span class="username">{{ username }}</span>
|
<span class="username">{{ username }}</span>
|
||||||
<el-dropdown @command="handleCommand" class="user-dropdown">
|
<el-dropdown @command="handleCommand" class="user-dropdown">
|
||||||
<el-avatar :size="32" class="user-avatar">
|
<el-avatar :size="32" class="user-avatar">
|
||||||
@ -144,6 +193,11 @@ const handleCommand = (command: string) => {
|
|||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="login-section">
|
||||||
|
<el-button type="primary" size="small" @click="showLogin">
|
||||||
|
登录
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</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 {
|
.username {
|
||||||
display: none; // 小屏幕隐藏用户名
|
display: none; // 小屏幕隐藏用户名
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-section {
|
||||||
|
.el-button {
|
||||||
|
height: 28px;
|
||||||
|
padding: 0 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
299
frontend/src/views/login/index.vue
Normal file
299
frontend/src/views/login/index.vue
Normal 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_id,可以作为token使用,或者生成一个会话标识
|
||||||
|
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>
|
||||||
Loading…
Reference in New Issue
Block a user