update
This commit is contained in:
parent
fec3d35af2
commit
ea7c5e56a3
@ -5,7 +5,7 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: tradus-web:1.3.15
|
||||
image: tradus-web:1.3.16
|
||||
container_name: tradus-web
|
||||
ports:
|
||||
- '6000:80'
|
||||
|
||||
314
src/App.vue
314
src/App.vue
@ -13,6 +13,7 @@ const userInfo = computed(() => userStore.userInfo)
|
||||
const showMobileMenu = ref(false)
|
||||
const showUserMenu = ref(false)
|
||||
const showLoginModal = ref(false)
|
||||
const showResetPasswordModal = ref(false)
|
||||
const loginMode = ref('login') // 'login' 或 'register'
|
||||
|
||||
// 获取用户信息
|
||||
@ -69,6 +70,13 @@ const formData = ref({
|
||||
verificationCode: '',
|
||||
})
|
||||
|
||||
// 重置密码表单数据
|
||||
const resetPasswordData = ref({
|
||||
email: '',
|
||||
verificationCode: '',
|
||||
newPassword: '',
|
||||
})
|
||||
|
||||
// 表单错误信息
|
||||
const formErrors = ref({
|
||||
email: '',
|
||||
@ -76,15 +84,25 @@ const formErrors = ref({
|
||||
verificationCode: '',
|
||||
})
|
||||
|
||||
// 重置密码表单错误信息
|
||||
const resetPasswordErrors = ref({
|
||||
email: '',
|
||||
verificationCode: '',
|
||||
newPassword: '',
|
||||
})
|
||||
|
||||
// 密码显示状态
|
||||
const showPassword = ref(false)
|
||||
const showNewPassword = ref(false)
|
||||
|
||||
// 验证码相关状态
|
||||
const sendingCode = ref(false)
|
||||
const countdown = ref(0)
|
||||
const resetCountdown = ref(0)
|
||||
|
||||
// 加载状态
|
||||
const isLoading = ref(false)
|
||||
const isResetLoading = ref(false)
|
||||
|
||||
// 发送验证码
|
||||
const sendVerificationCode = async () => {
|
||||
@ -116,6 +134,50 @@ const sendVerificationCode = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 发送重置密码验证码
|
||||
const sendResetVerificationCode = async () => {
|
||||
if (!resetPasswordData.value.email) {
|
||||
resetPasswordErrors.value.email = '请输入邮箱'
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
sendingCode.value = true
|
||||
resetPasswordErrors.value.email = ''
|
||||
|
||||
// 调用发送验证码接口
|
||||
const response = await fetch(
|
||||
`${import.meta.env.MODE === 'development' ? 'http://127.0.0.1:8000' : 'https://api.ibtc.work'}/user/send-verification-code`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ mail: resetPasswordData.value.email }),
|
||||
},
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('发送验证码失败,请重试')
|
||||
}
|
||||
|
||||
resetCountdown.value = 60
|
||||
|
||||
// 倒计时
|
||||
const timer = setInterval(() => {
|
||||
resetCountdown.value--
|
||||
if (resetCountdown.value <= 0) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
}, 1000)
|
||||
} catch (err) {
|
||||
resetPasswordErrors.value.email = err instanceof Error ? err.message : '发送验证码失败,请重试'
|
||||
console.error('发送重置密码验证码失败:', err)
|
||||
} finally {
|
||||
sendingCode.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 验证表单
|
||||
const validateForm = () => {
|
||||
let isValid = true
|
||||
@ -154,6 +216,83 @@ const validateForm = () => {
|
||||
return isValid
|
||||
}
|
||||
|
||||
// 验证重置密码表单
|
||||
const validateResetPasswordForm = () => {
|
||||
let isValid = true
|
||||
resetPasswordErrors.value = {
|
||||
email: '',
|
||||
verificationCode: '',
|
||||
newPassword: '',
|
||||
}
|
||||
|
||||
// 验证邮箱
|
||||
if (!resetPasswordData.value.email) {
|
||||
resetPasswordErrors.value.email = '请输入邮箱'
|
||||
isValid = false
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(resetPasswordData.value.email)) {
|
||||
resetPasswordErrors.value.email = '请输入有效的邮箱地址'
|
||||
isValid = false
|
||||
}
|
||||
|
||||
// 验证验证码
|
||||
if (!resetPasswordData.value.verificationCode) {
|
||||
resetPasswordErrors.value.verificationCode = '请输入验证码'
|
||||
isValid = false
|
||||
}
|
||||
|
||||
// 验证新密码
|
||||
if (!resetPasswordData.value.newPassword) {
|
||||
resetPasswordErrors.value.newPassword = '请输入新密码'
|
||||
isValid = false
|
||||
} else if (resetPasswordData.value.newPassword.length < 6) {
|
||||
resetPasswordErrors.value.newPassword = '密码长度不能小于6位'
|
||||
isValid = false
|
||||
}
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
// 处理重置密码提交
|
||||
const handleResetPasswordSubmit = async () => {
|
||||
if (!validateResetPasswordForm()) return
|
||||
|
||||
isResetLoading.value = true
|
||||
try {
|
||||
// 调用重置密码接口
|
||||
const response = await fetch(
|
||||
`${import.meta.env.MODE === 'development' ? 'http://127.0.0.1:8000' : 'https://api.ibtc.work'}/user/reset_password`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
mail: resetPasswordData.value.email,
|
||||
verification_code: resetPasswordData.value.verificationCode,
|
||||
new_password: resetPasswordData.value.newPassword,
|
||||
}),
|
||||
},
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json()
|
||||
throw new Error(errorData.message || '重置密码失败')
|
||||
}
|
||||
|
||||
// 重置密码成功,关闭模态框
|
||||
closeResetPasswordModal()
|
||||
// 提示用户重置成功,并打开登录模态框
|
||||
alert('密码重置成功,请使用新密码登录')
|
||||
openAuthModal('login')
|
||||
} catch (error) {
|
||||
console.error('重置密码失败:', error)
|
||||
resetPasswordErrors.value.email =
|
||||
error instanceof Error ? error.message : '重置密码失败,请检查输入信息'
|
||||
} finally {
|
||||
isResetLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理表单提交
|
||||
const handleSubmit = async () => {
|
||||
if (!validateForm()) return
|
||||
@ -242,6 +381,28 @@ const openAuthModal = (mode: 'login' | 'register') => {
|
||||
showLoginModal.value = true
|
||||
}
|
||||
|
||||
// 关闭重置密码模态框
|
||||
const closeResetPasswordModal = () => {
|
||||
showResetPasswordModal.value = false
|
||||
// 重置表单和错误信息
|
||||
resetPasswordData.value = {
|
||||
email: '',
|
||||
verificationCode: '',
|
||||
newPassword: '',
|
||||
}
|
||||
resetPasswordErrors.value = {
|
||||
email: '',
|
||||
verificationCode: '',
|
||||
newPassword: '',
|
||||
}
|
||||
}
|
||||
|
||||
// 打开重置密码模态框
|
||||
const openResetPasswordModal = () => {
|
||||
closeAuthModal() // 关闭登录模态框
|
||||
showResetPasswordModal.value = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', closeMenus)
|
||||
if (isAuthenticated.value) {
|
||||
@ -590,6 +751,12 @@ onUnmounted(() => {
|
||||
<span v-else class="loading-spinner"></span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- 忘记密码链接 -->
|
||||
<div class="forgot-password" v-if="loginMode === 'login'">
|
||||
<button class="forgot-password-link" @click="openResetPasswordModal">忘记密码?</button>
|
||||
</div>
|
||||
|
||||
<div class="auth-switch">
|
||||
{{ loginMode === 'login' ? '还没有账号?' : '已有账号?' }}
|
||||
<button
|
||||
@ -602,6 +769,132 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 忘记密码模态框 -->
|
||||
<div class="modal-overlay" v-if="showResetPasswordModal" @click="closeResetPasswordModal">
|
||||
<div class="modal-container" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h2>重置密码</h2>
|
||||
<button class="close-button" @click="closeResetPasswordModal">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
>
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-content">
|
||||
<form class="auth-form" @submit.prevent="handleResetPasswordSubmit">
|
||||
<div class="form-group">
|
||||
<label for="reset-email">邮箱</label>
|
||||
<div class="input-container">
|
||||
<input
|
||||
type="email"
|
||||
id="reset-email"
|
||||
v-model="resetPasswordData.email"
|
||||
:class="{ error: resetPasswordErrors.email }"
|
||||
placeholder="请输入邮箱"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="code-button"
|
||||
@click="sendResetVerificationCode"
|
||||
:disabled="sendingCode || resetCountdown > 0"
|
||||
>
|
||||
{{
|
||||
resetCountdown > 0
|
||||
? `${resetCountdown}秒后重试`
|
||||
: sendingCode
|
||||
? '发送中...'
|
||||
: '获取验证码'
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
<span class="error-message" v-if="resetPasswordErrors.email">{{
|
||||
resetPasswordErrors.email
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="reset-verification-code">邮件验证码</label>
|
||||
<input
|
||||
type="text"
|
||||
id="reset-verification-code"
|
||||
v-model="resetPasswordData.verificationCode"
|
||||
:class="{ error: resetPasswordErrors.verificationCode }"
|
||||
placeholder="请输入验证码"
|
||||
/>
|
||||
<span class="error-message" v-if="resetPasswordErrors.verificationCode">
|
||||
{{ resetPasswordErrors.verificationCode }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="new-password">新密码</label>
|
||||
<div class="password-input-container">
|
||||
<input
|
||||
:type="showNewPassword ? 'text' : 'password'"
|
||||
id="new-password"
|
||||
v-model="resetPasswordData.newPassword"
|
||||
:class="{ error: resetPasswordErrors.newPassword }"
|
||||
placeholder="请输入新密码"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="toggle-password-button"
|
||||
@click="showNewPassword = !showNewPassword"
|
||||
>
|
||||
<svg
|
||||
v-if="!showNewPassword"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
height="20"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
>
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</svg>
|
||||
<svg
|
||||
v-else
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
height="20"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"
|
||||
/>
|
||||
<line x1="1" y1="1" x2="23" y2="23" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<span class="error-message" v-if="resetPasswordErrors.newPassword">
|
||||
{{ resetPasswordErrors.newPassword }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="submit-button" :disabled="isResetLoading">
|
||||
<span v-if="!isResetLoading">重置密码</span>
|
||||
<span v-else class="loading-spinner"></span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="auth-switch">
|
||||
<button class="switch-button" @click="closeResetPasswordModal()">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -1738,4 +2031,25 @@ html {
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* 忘记密码链接样式 */
|
||||
.forgot-password {
|
||||
text-align: right;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.forgot-password-link {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-accent);
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.forgot-password-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user