574 lines
13 KiB
Vue
574 lines
13 KiB
Vue
<template>
|
|
<div class="login-form">
|
|
<div>忘记密码</div>
|
|
<!-- <form class="login-form-container" @submit.prevent="handleForgetPassword">-->
|
|
<!-- <div class="input-container">-->
|
|
<!-- <label for="username">QQ号</label>-->
|
|
<!-- <input-->
|
|
<!-- type="text"-->
|
|
<!-- id="username"-->
|
|
<!-- v-model="username"-->
|
|
<!-- placeholder="请输入QQ号"-->
|
|
<!-- :class="{ 'error': usernameError }"-->
|
|
<!-- />-->
|
|
<!-- <span class="error-message" v-if="usernameError">{{ usernameError }}</span>-->
|
|
<!-- </div>-->
|
|
<!-- <div class="input-container">-->
|
|
<!-- <label for="newPassword">新密码</label>-->
|
|
<!-- <input-->
|
|
<!-- type="password"-->
|
|
<!-- id="newPassword"-->
|
|
<!-- v-model="newPassword"-->
|
|
<!-- placeholder="请输入新密码"-->
|
|
<!-- :class="{ 'error': newPasswordError }"-->
|
|
<!-- />-->
|
|
<!-- <span class="error-message" v-if="newPasswordError">{{ newPasswordError }}</span>-->
|
|
<!-- </div>-->
|
|
<!-- <div class="input-container">-->
|
|
<!-- <label for="confirmPassword">确认新密码</label>-->
|
|
<!-- <input-->
|
|
<!-- type="password"-->
|
|
<!-- id="confirmPassword"-->
|
|
<!-- v-model="confirmPassword"-->
|
|
<!-- placeholder="请再次输入新密码"-->
|
|
<!-- :class="{ 'error': confirmPasswordError }"-->
|
|
<!-- />-->
|
|
<!-- <span class="error-message" v-if="confirmPasswordError">{{ confirmPasswordError }}</span>-->
|
|
<!-- </div>-->
|
|
<!-- <div class="input-container captcha-container">-->
|
|
<!-- <label for="captcha">验证码</label>-->
|
|
<!-- <div class="captcha-wrapper">-->
|
|
<!-- <input-->
|
|
<!-- type="text"-->
|
|
<!-- id="captcha"-->
|
|
<!-- v-model="captcha"-->
|
|
<!-- placeholder="请输入验证码"-->
|
|
<!-- :class="{ 'error': captchaError }"-->
|
|
<!-- />-->
|
|
<!-- <img-->
|
|
<!-- v-if="captchaImage"-->
|
|
<!-- :src="captchaImage"-->
|
|
<!-- alt="验证码"-->
|
|
<!-- class="captcha-image"-->
|
|
<!-- @click="refreshCaptcha"-->
|
|
<!-- />-->
|
|
<!-- </div>-->
|
|
<!-- <span class="error-message" v-if="captchaError">{{ captchaError }}</span>-->
|
|
<!-- </div>-->
|
|
<!-- <div class="login-button">-->
|
|
<!-- <button type="submit" :disabled="isSubmitting">-->
|
|
<!-- {{ isSubmitting ? '提交中...' : '重置密码' }}-->
|
|
<!-- </button>-->
|
|
<!-- </div>-->
|
|
<!-- <div class="register-link">-->
|
|
<!-- <a @click.prevent="$emit('login')">返回登录</a>-->
|
|
<!-- </div>-->
|
|
<!-- </form>-->
|
|
<form form class="login-form-container" @submit.prevent="uuid_handleForgetPassword">
|
|
<!-- <div class="input-container">-->
|
|
<!-- <label for="username">UUID</label>-->
|
|
<!-- <input-->
|
|
<!-- id="username"-->
|
|
<!-- type="text"-->
|
|
<!-- placeholder="请输入UUID"-->
|
|
<!-- v-model="uuid"-->
|
|
<!-- />-->
|
|
<!-- </div>-->
|
|
<div class="input-container">
|
|
<label for="username">QQ号</label>
|
|
<input
|
|
type="text"
|
|
id="username"
|
|
v-model="username"
|
|
placeholder="请输入QQ号"
|
|
:class="{ 'error': usernameError }"
|
|
/>
|
|
<span class="error-message" v-if="usernameError">{{ usernameError }}</span>
|
|
</div>
|
|
<div class="login-button">
|
|
<button type="submit" :disabled="isSubmitting || cooldown > 0">
|
|
<template v-if="isSubmitting">提交中...</template>
|
|
<template v-else-if="cooldown > 0">请稍候 ({{ cooldown }}s)</template>
|
|
<template v-else>重置密码</template>
|
|
</button>
|
|
</div>
|
|
<div class="register-link">
|
|
<a @click.prevent="$emit('login')">返回登录</a>
|
|
</div>
|
|
</form>
|
|
<ErrorDialog
|
|
:visible="showError"
|
|
:title="errorTitle"
|
|
:message="errorMessage"
|
|
@close="showError = false"
|
|
/>
|
|
<SuccessDialog
|
|
:visible="showSuccess"
|
|
:title="successTitle"
|
|
:message="successMessage"
|
|
@close="handleSuccessClose"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted } from 'vue'
|
|
import {getCaptcha, requestResetPassword, getUserByInfo} from '../api/login'
|
|
import ErrorDialog from './ErrorDialog.vue'
|
|
import SuccessDialog from './SuccessDialog.vue'
|
|
|
|
const username = ref('')
|
|
const newPassword = ref('')
|
|
const confirmPassword = ref('')
|
|
const captcha = ref('')
|
|
const captchaImage = ref('')
|
|
const captchaToken = ref('')
|
|
const uuid = ref('')
|
|
|
|
// 错误信息
|
|
const usernameError = ref('')
|
|
const newPasswordError = ref('')
|
|
const confirmPasswordError = ref('')
|
|
const captchaError = ref('')
|
|
const uuidError = ref('')
|
|
|
|
// 状态
|
|
const isSubmitting = ref(false)
|
|
const cooldown = ref(0)
|
|
let cooldownTimer = null
|
|
|
|
// 错误弹窗相关
|
|
const showError = ref(false)
|
|
const errorTitle = ref('错误提示')
|
|
const errorMessage = ref('')
|
|
|
|
// 成功弹窗相关
|
|
const showSuccess = ref(false)
|
|
const successTitle = ref('成功')
|
|
const successMessage = ref('密码重置成功,请使用新密码登录')
|
|
|
|
const showErrorMessage = (message, title = '错误提示') => {
|
|
errorMessage.value = message
|
|
errorTitle.value = title
|
|
showError.value = true
|
|
}
|
|
|
|
const showSuccessMessage = (message, title = '成功') => {
|
|
successMessage.value = message
|
|
successTitle.value = title
|
|
showSuccess.value = true
|
|
}
|
|
|
|
const refreshCaptcha = async () => {
|
|
try {
|
|
const response = await getCaptcha()
|
|
captchaImage.value = `data:image/png;base64,${response.img}`
|
|
captchaToken.value = response.token
|
|
captcha.value = '' // 清空验证码输入
|
|
} catch (error) {
|
|
console.error('获取验证码失败:', error)
|
|
const apiError = error.response?.data?.detail || error.response?.data?.message
|
|
if (apiError) {
|
|
showErrorMessage(apiError)
|
|
} else {
|
|
showErrorMessage('获取验证码失败,请刷新页面重试')
|
|
}
|
|
}
|
|
}
|
|
|
|
const handleForgetPassword = async () => {
|
|
try {
|
|
// 验证表单
|
|
if (!validateForm()) {
|
|
return
|
|
}
|
|
|
|
isSubmitting.value = true
|
|
|
|
// 调用忘记密码API
|
|
await forgetPassword(
|
|
username.value,
|
|
newPassword.value,
|
|
captchaToken.value,
|
|
captcha.value
|
|
)
|
|
|
|
// 显示成功消息
|
|
showSuccessMessage('密码重置成功,请使用新密码登录')
|
|
|
|
} catch (error) {
|
|
console.error('重置密码失败:', error)
|
|
if (error.response) {
|
|
const apiError = error.response?.data?.detail || error.response?.data?.message
|
|
if (apiError) {
|
|
showErrorMessage(apiError)
|
|
} else {
|
|
showErrorMessage('重置密码失败,请稍后重试')
|
|
}
|
|
} else if (error.message === 'Network Error') {
|
|
showErrorMessage('无法连接服务器,请稍后重试')
|
|
} else {
|
|
showErrorMessage(error.message || '重置密码失败,请稍后重试')
|
|
}
|
|
// 重置失败时刷新验证码
|
|
refreshCaptcha()
|
|
} finally {
|
|
isSubmitting.value = false
|
|
}
|
|
}
|
|
|
|
const uuid_handleForgetPassword = async () => {
|
|
try{
|
|
if (!uuid_validateForm()) {
|
|
return
|
|
}
|
|
const user = await getUserByInfo({qq_code:username.value})
|
|
await requestResetPassword(user.uuid)
|
|
isSubmitting.value = true
|
|
cooldown.value = 60
|
|
cooldownTimer = setInterval(() => {
|
|
if (cooldown.value > 0) {
|
|
cooldown.value--
|
|
} else {
|
|
clearInterval(cooldownTimer)
|
|
cooldownTimer = null
|
|
}
|
|
}, 1000)
|
|
}catch ( error){
|
|
showErrorMessage(error.message || '不是正确的uuid')
|
|
}finally {
|
|
isSubmitting.value = false
|
|
}
|
|
}
|
|
|
|
|
|
const uuid_validateForm = () =>{
|
|
usernameError.value = ''
|
|
if (!username.value) {
|
|
usernameError.value = '请输入qq'
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
const validateForm = () => {
|
|
// 重置所有错误信息
|
|
usernameError.value = ''
|
|
newPasswordError.value = ''
|
|
confirmPasswordError.value = ''
|
|
captchaError.value = ''
|
|
|
|
// 验证用户名
|
|
if (!username.value) {
|
|
usernameError.value = '请输入QQ号码'
|
|
return false
|
|
}
|
|
|
|
// 验证新密码
|
|
if (!newPassword.value) {
|
|
newPasswordError.value = '请输入新密码'
|
|
return false
|
|
}
|
|
if (newPassword.value.length < 4) {
|
|
newPasswordError.value = '密码长度不能小于4个字符'
|
|
return false
|
|
}
|
|
if (newPassword.value.length > 20) {
|
|
newPasswordError.value = '密码长度不能超过20个字符'
|
|
return false
|
|
}
|
|
|
|
// 验证确认密码
|
|
if (!confirmPassword.value) {
|
|
confirmPasswordError.value = '请确认新密码'
|
|
return false
|
|
}
|
|
if (newPassword.value !== confirmPassword.value) {
|
|
confirmPasswordError.value = '两次输入的密码不一致'
|
|
return false
|
|
}
|
|
|
|
// 验证验证码
|
|
if (!captcha.value) {
|
|
captchaError.value = '请输入验证码'
|
|
return false
|
|
}
|
|
if (captcha.value.length !== 4) {
|
|
captchaError.value = '验证码长度不正确'
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
const handleSuccessClose = () => {
|
|
showSuccess.value = false
|
|
// 重置表单
|
|
// resetForm()
|
|
uuid_resetForm()
|
|
// 切换到登录页面
|
|
$emit('login')
|
|
}
|
|
|
|
// 重置表单数据
|
|
const resetForm = () => {
|
|
username.value = ''
|
|
newPassword.value = ''
|
|
confirmPassword.value = ''
|
|
captcha.value = ''
|
|
usernameError.value = ''
|
|
newPasswordError.value = ''
|
|
confirmPasswordError.value = ''
|
|
captchaError.value = ''
|
|
refreshCaptcha()
|
|
}
|
|
|
|
const uuid_resetForm = () =>{
|
|
username.value = ''
|
|
}
|
|
|
|
// 暴露resetForm方法给父组件
|
|
defineExpose({
|
|
// resetForm
|
|
uuid_resetForm
|
|
})
|
|
|
|
onMounted(() => {
|
|
// refreshCaptcha()
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.login-form {
|
|
width: 340px;
|
|
margin: 0 auto;
|
|
background: rgba(255, 255, 255, 0.95);
|
|
border-radius: 16px;
|
|
box-shadow: 0 4px 32px rgba(0, 0, 0, 0.10);
|
|
padding: 36px 32px 28px 32px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.login-form>div:first-child {
|
|
font-size: 26px;
|
|
font-weight: bold;
|
|
color: #222;
|
|
margin-bottom: 28px;
|
|
letter-spacing: 2px;
|
|
}
|
|
|
|
.login-form-container {
|
|
width: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 18px;
|
|
}
|
|
|
|
.input-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
}
|
|
|
|
.input-container label {
|
|
font-size: 14px;
|
|
color: #666;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.input-container input {
|
|
height: 40px;
|
|
border: 1px solid #d0d7de;
|
|
border-radius: 6px;
|
|
padding: 0 12px;
|
|
font-size: 15px;
|
|
background: #f7fbfd;
|
|
transition: border 0.2s;
|
|
}
|
|
|
|
.input-container input:focus {
|
|
border: 1.5px solid #409eff;
|
|
outline: none;
|
|
background: #fff;
|
|
}
|
|
|
|
.input-container input.error {
|
|
border-color: #f56c6c;
|
|
}
|
|
|
|
.error-message {
|
|
color: #f56c6c;
|
|
font-size: 12px;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.login-button {
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.login-button button {
|
|
width: 100%;
|
|
height: 42px;
|
|
background: linear-gradient(90deg, #409eff 0%, #6dd5fa 100%);
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 6px;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.10);
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.login-button button:hover {
|
|
background: linear-gradient(90deg, #66b1ff 0%, #6dd5fa 100%);
|
|
}
|
|
|
|
.login-button button:disabled {
|
|
background: #a0cfff;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.register-link {
|
|
margin-top: 12px;
|
|
text-align: right;
|
|
}
|
|
|
|
.register-link a {
|
|
color: #409eff;
|
|
font-size: 14px;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
transition: color 0.2s;
|
|
}
|
|
|
|
.register-link a:hover {
|
|
color: #1a73e8;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
/* VAPTCHA 相关样式 */
|
|
.VAPTCHA-init-main {
|
|
display: table;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: #f7fbfd;
|
|
border-radius: 6px;
|
|
border: 1px solid #d0d7de;
|
|
}
|
|
|
|
.VAPTCHA-init-loading {
|
|
display: table-cell;
|
|
vertical-align: middle;
|
|
text-align: center;
|
|
}
|
|
|
|
.VAPTCHA-init-loading>a {
|
|
display: inline-block;
|
|
width: 18px;
|
|
height: 18px;
|
|
border: none;
|
|
}
|
|
|
|
.VAPTCHA-init-loading .VAPTCHA-text {
|
|
font-family: sans-serif;
|
|
font-size: 12px;
|
|
color: #666;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.captcha-container {
|
|
margin-bottom: 16px;
|
|
width: 100%;
|
|
}
|
|
|
|
.captcha-wrapper {
|
|
display: flex;
|
|
gap: 8px;
|
|
align-items: center;
|
|
width: 100%;
|
|
position: relative;
|
|
}
|
|
|
|
.captcha-wrapper input {
|
|
flex: 1;
|
|
height: 40px;
|
|
border: 1px solid #d0d7de;
|
|
border-radius: 6px;
|
|
padding: 0 12px;
|
|
font-size: 15px;
|
|
background: #f7fbfd;
|
|
transition: border 0.2s;
|
|
width: calc(100% - 120px); /* 减去验证码图片的宽度和间距 */
|
|
}
|
|
|
|
.captcha-wrapper input:focus {
|
|
border: 1.5px solid #409eff;
|
|
outline: none;
|
|
background: #fff;
|
|
}
|
|
|
|
.captcha-image {
|
|
width: 110px;
|
|
height: 40px;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
object-fit: cover;
|
|
border: 1px solid #d0d7de;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.captcha-image:hover {
|
|
opacity: 0.8;
|
|
}
|
|
|
|
/* 移动端适配 */
|
|
@media screen and (max-width: 480px) {
|
|
.login-form {
|
|
width: 90%;
|
|
max-width: 340px;
|
|
padding: 24px 20px 20px 20px;
|
|
}
|
|
|
|
.captcha-wrapper {
|
|
gap: 6px;
|
|
}
|
|
|
|
.captcha-wrapper input {
|
|
width: calc(100% - 100px);
|
|
}
|
|
|
|
.captcha-image {
|
|
width: 90px;
|
|
}
|
|
|
|
.input-container input,
|
|
.captcha-wrapper input {
|
|
height: 38px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.login-button button {
|
|
height: 40px;
|
|
font-size: 15px;
|
|
}
|
|
}
|
|
|
|
/* 超小屏幕适配 */
|
|
@media screen and (max-width: 320px) {
|
|
.login-form {
|
|
padding: 20px 16px 16px 16px;
|
|
}
|
|
|
|
.captcha-wrapper input {
|
|
width: calc(100% - 90px);
|
|
padding: 0 8px;
|
|
}
|
|
|
|
.captcha-image {
|
|
width: 80px;
|
|
}
|
|
}
|
|
</style> |