DCFronted/src/components/forget_module.vue
2025-07-24 21:53:02 +08:00

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>