登录,退出登录

newzwz
lee-zt 2 months ago
parent fa9627a9de
commit d3158ec4f9

@ -28,7 +28,8 @@
"eslintConfig": {
"root": true,
"env": {
"node": true
"node": true,
"vue/setup-compiler-macros": true
},
"extends": [
"plugin:vue/vue3-essential",

@ -38,11 +38,15 @@
<!-- 登录/注册按钮 -->
<div class="nav-section">
<button v-if="!isLoggedIn" @click="showModal" class="login-btn">/</button>
<div v-else class="user-avatar">
<div v-else class="user-avatar" @mouseenter="showDropdown" @mouseleave="hideDropdown">
<img :src="userInfo.avatar || defaultAvatar" alt="用户头像" class="avatar-img" />
<div class="dropdown-menu">
<button @click="goToProfile"></button>
<button @click="logout">退</button>
<!-- 悬浮板块 -->
<div class="user-dropdown-menu" v-show="isDropdownVisible">
<p class="user-name">{{ userInfo.userName }}</p>
<div class="button-container">
<button @click="goToProfile" class="dropdown-btn">个人中心</button>
<button @click="logout" class="dropdown-btn">退出登录</button>
</div>
</div>
</div>
</div>
@ -54,7 +58,7 @@
<script setup name="Header">
import { ref, computed } from 'vue';
import { useUserStore } from '@/stores/user.ts';
import { useUserStore } from '@/stores/user.js';
import LoginRegisterModal from './LoginRegisterModal.vue';
//
@ -62,6 +66,7 @@ const userStore = useUserStore();
//
const isModalVisible = ref(false);
const isDropdownVisible = ref(false);
const colleges = ref([
{ name: '武大官网', url: 'https://www.whu.edu.cn/' },
{ name: '计算机学院', url: 'https://cs.whu.edu.cn/' },
@ -82,7 +87,7 @@ const colleges = ref([
]);
//
const defaultAvatar = '@/assets/default-avatar.png';
const defaultAvatar = '@/assets/default-avatar/boy_4.png';
//
const isLoggedIn = computed(() => userStore.isLoggedIn);
@ -104,7 +109,25 @@ const goToProfile = () => {
const logout = () => {
userStore.logout();
window.location.href = '/';
isDropdownVisible.value = false; //
};
let showTimer = null; //
let hideTimer = null; //
const showDropdown = () => {
console.log('showDropdown');
clearTimeout(hideTimer); //
showTimer = setTimeout(() => {
isDropdownVisible.value = true; // 0.5
}, 200);
};
const hideDropdown = () => {
clearTimeout(showTimer); //
hideTimer = setTimeout(() => {
isDropdownVisible.value = false; // 1
}, 400);
};
</script>
@ -217,7 +240,7 @@ const logout = () => {
position: absolute;
top: 100%;
left: 0;
background-color: white;
background-color: rgb(255, 255, 255);
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
@ -293,4 +316,61 @@ const logout = () => {
color: #6fbd87;
background: #f0f0f0;
}
.avatar-img {
width: 35px;
height: 35px;
border-radius: 50%;
cursor: pointer;
}
/* 头像悬浮板块样式 */
.user-dropdown-menu {
position: absolute;
top: 100%;
right: 0;
background-color: white;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
padding: 10px;
z-index: 1000;
display: flex;
flex-direction: column;
align-items: center; /* 水平居中用户名 */
text-align: center; /* 确保文字居中 */
gap: 10px; /* 增加子元素之间的间距 */
width: 200px; /* 固定宽度,确保布局一致 */
}
/* 用户名样式 */
.user-name {
font-size: 20px;
font-weight: bold;
margin-bottom: 10px;
color: #54ac52;
}
.button-container {
display: flex;
justify-content: space-between; /* 按钮左右排列 */
width: 100%; /* 按钮容器占满父容器宽度 */
}
/* 悬浮板块按钮样式 */
.dropdown-btn {
width: 45%; /* 按钮占据父容器的 45% 宽度 */
padding: 8px 10px;
background: none;
border: 0;
border-radius: 4px;
text-align: center;
font-size: 14px;
color: #333;
cursor: pointer;
transition: background-color 0.3s, color 0.3s;
}
.dropdown-btn:hover {
background-color: #f0f0f0;
color: #6fbd87;
}
</style>

@ -15,7 +15,8 @@
</div>
<!-- 登录表单 -->
<form @submit.prevent="login" class="login-form">
<button @click="login" v-if="false"></button>
<form @submit.prevent="Login1" class="login-form">
<div class="input-group">
<!-- 用户标识输入框用户名/邮箱/手机 -->
<input
@ -70,98 +71,102 @@
</div>
</template>
<script>
<script setup name="UserLogin">
import { ref, computed } from 'vue';
import axios from 'axios';
import request from '@/utils/request';
import { ElMessage } from 'element-plus';
import { useUserStore } from '@/stores/user.ts';
export default {
name: 'UserLogin',
//TODO: props, { emit }
setup() {
const currentType = ref('username'); //
const cooldown = ref(0); //
const userStore = useUserStore(); //
//
const loginTypes = [
import { useUserStore } from '@/stores/user.js';
const currentType = ref('username'); //
const cooldown = ref(0); //
const userStore = useUserStore(); //
const emit = defineEmits(['toggleForm', 'LoginSuccess']); //
//
const loginTypes = [
{ label: '用户名登录', value: 'username' },
{ label: '邮箱登录', value: 'email' },
{ label: '手机登录', value: 'phone' }
];
];
//
const loginForm = ref({
//
const loginForm = ref({
userFlag: '', //
password: '', //
verifyCode: '', // /使
remember: false //
});
});
//
const inputType = computed(() => {
switch(currentType.value) {
case 'email': return 'email'; //
case 'phone': return 'tel'; //
default: return 'text'; //
//
const inputType = computed(() => {
switch (currentType.value) {
case 'email':
return 'email'; //
case 'phone':
return 'tel'; //
default:
return 'text'; //
}
});
});
//
const placeholder = computed(() => {
switch(currentType.value) {
case 'username': return '请输入用户名';
case 'email': return '请输入邮箱';
case 'phone': return '请输入手机号';
default: return '';
//
const placeholder = computed(() => {
switch (currentType.value) {
case 'username':
return '请输入用户名';
case 'email':
return '请输入邮箱';
case 'phone':
return '请输入手机号';
default:
return '';
}
});
});
//
const showVerifyCode = computed(() => {
//
const showVerifyCode = computed(() => {
return ['email', 'phone'].includes(currentType.value);
});
});
//
const isValidInput = computed(() => {
const value = loginForm.value.userFlag
if(!value) return false; //
//
const isValidInput = computed(() => {
const value = loginForm.value.userFlag;
if (!value) return false; //
switch(currentType.value) {
switch (currentType.value) {
case 'email':
return /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+$/.test(value);
case 'phone':
return /^1[3-9]\d{9}$/.test(value);
default:
return value.length > 0; //
return value.length > 0; //
}
});
});
//
const isvalidForm = computed(() => {
if(!isValidInput.value) return false;
//
const isvalidForm = computed(() => {
if (!isValidInput.value) return false;
if(showVerifyCode.value){
return loginForm.value.verifyCode.length === 6; //
if (showVerifyCode.value) {
return loginForm.value.verifyCode.length === 6; //
} else {
return loginForm.value.password.length >= 6; //
return loginForm.value.password.length >= 6; //
}
});
});
//
async function sendCode() {
if (cooldown.value > 0 || !isValidInput.value) return; //
//
async function sendCode() {
if (cooldown.value > 0 || !isValidInput.value) return; //
try {
//TODO:
// TODO:
const response = await axios.post('/user/sendCode', {
type: currentType.value,
target: loginForm.value.userFlag
});
if (response.data.success) {
//60
// 60
cooldown.value = 60;
const timer = setInterval(() => {
cooldown.value--;
@ -173,36 +178,34 @@ export default {
} catch (error) {
console.error('发送验证码失败:', error);
}
}
//
/*
function login1() {
}
//
function Login1() {
userStore.login({
avatar: '/assets/default-avatar/boy_1.png',
avatar:require('@/assets/default-avatar/boy_1.png'),
userName: '珈人一号'
})
}*/
async function login() {
});
emit('LoginSuccess');
}
//
async function login() {
if (!isvalidForm.value) return;
try {
const loginData = {
userFlag: loginForm.value.userFlag,
password: loginForm.value.password
}
};
if (showVerifyCode.value) {
loginData.verifyCode = loginForm.value.verifyCode; //
loginData.verifyCode = loginForm.value.verifyCode; //
}
//post
// post
const response = await request.post('/user/login', loginData);
if (response.code === 200) {
//token
//
// token
const { accessToken, refreshToken } = response.data;
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
@ -210,55 +213,35 @@ export default {
userStore.login({
avatar: '/assets/default-avatar/boy_1.png',
userName: '珈人一号'
})
});
ElMessage({
message: '登录成功',
type: 'success',
duration: 500
});
//TODO:
//or
}else{
//
// TODO:
} else {
//
ElMessage({
message: '登陆失败,用户名或密码错误',
type: 'error',
duration: 500
});
}
} catch (error) {
console.error('登录失败:', error);
alert(error.response?.message ||'登录失败,请稍后重试');
}
alert(error.response?.message || '登录失败,请稍后重试');
}
}
//
const switchLoginType = (type) => {
//
const switchLoginType = (type) => {
currentType.value = type;
loginForm.value.userFlag = '';
loginForm.value.password = '';
loginForm.value.verifyCode = '';
}
// 使
return {
currentType,
loginTypes,
loginForm,
inputType,
placeholder,
showVerifyCode,
cooldown,
sendCode,
//login1,
login,
switchLoginType,
isValidInput
};
}
}
};
</script>
<style scoped>

@ -8,7 +8,7 @@
<!-- 模态框内容区域 -->
<div class="modal-content">
<!-- 根据 isLogin 状态动态切换登录/注册组件 -->
<UserLogin v-if="isLogin" @toggleForm="toggleForm" />
<UserLogin v-if="isLogin" @toggleForm="toggleForm" @LoginSuccess="close"/>
<UserRegister v-else @toggleForm="toggleForm" />
</div>
</div>
@ -39,7 +39,7 @@ export default {
return {
isLogin,
toggleForm,
close: () => emit('close') //
close: () => {emit('close')} //
};
}
}

Loading…
Cancel
Save