update
This commit is contained in:
parent
fec3d35af2
commit
ea7c5e56a3
@ -5,7 +5,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
image: tradus-web:1.3.15
|
image: tradus-web:1.3.16
|
||||||
container_name: tradus-web
|
container_name: tradus-web
|
||||||
ports:
|
ports:
|
||||||
- '6000:80'
|
- '6000:80'
|
||||||
|
|||||||
314
src/App.vue
314
src/App.vue
@ -13,6 +13,7 @@ const userInfo = computed(() => userStore.userInfo)
|
|||||||
const showMobileMenu = ref(false)
|
const showMobileMenu = ref(false)
|
||||||
const showUserMenu = ref(false)
|
const showUserMenu = ref(false)
|
||||||
const showLoginModal = ref(false)
|
const showLoginModal = ref(false)
|
||||||
|
const showResetPasswordModal = ref(false)
|
||||||
const loginMode = ref('login') // 'login' 或 'register'
|
const loginMode = ref('login') // 'login' 或 'register'
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
@ -69,6 +70,13 @@ const formData = ref({
|
|||||||
verificationCode: '',
|
verificationCode: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 重置密码表单数据
|
||||||
|
const resetPasswordData = ref({
|
||||||
|
email: '',
|
||||||
|
verificationCode: '',
|
||||||
|
newPassword: '',
|
||||||
|
})
|
||||||
|
|
||||||
// 表单错误信息
|
// 表单错误信息
|
||||||
const formErrors = ref({
|
const formErrors = ref({
|
||||||
email: '',
|
email: '',
|
||||||
@ -76,15 +84,25 @@ const formErrors = ref({
|
|||||||
verificationCode: '',
|
verificationCode: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 重置密码表单错误信息
|
||||||
|
const resetPasswordErrors = ref({
|
||||||
|
email: '',
|
||||||
|
verificationCode: '',
|
||||||
|
newPassword: '',
|
||||||
|
})
|
||||||
|
|
||||||
// 密码显示状态
|
// 密码显示状态
|
||||||
const showPassword = ref(false)
|
const showPassword = ref(false)
|
||||||
|
const showNewPassword = ref(false)
|
||||||
|
|
||||||
// 验证码相关状态
|
// 验证码相关状态
|
||||||
const sendingCode = ref(false)
|
const sendingCode = ref(false)
|
||||||
const countdown = ref(0)
|
const countdown = ref(0)
|
||||||
|
const resetCountdown = ref(0)
|
||||||
|
|
||||||
// 加载状态
|
// 加载状态
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
const isResetLoading = ref(false)
|
||||||
|
|
||||||
// 发送验证码
|
// 发送验证码
|
||||||
const sendVerificationCode = async () => {
|
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 = () => {
|
const validateForm = () => {
|
||||||
let isValid = true
|
let isValid = true
|
||||||
@ -154,6 +216,83 @@ const validateForm = () => {
|
|||||||
return isValid
|
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 () => {
|
const handleSubmit = async () => {
|
||||||
if (!validateForm()) return
|
if (!validateForm()) return
|
||||||
@ -242,6 +381,28 @@ const openAuthModal = (mode: 'login' | 'register') => {
|
|||||||
showLoginModal.value = true
|
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(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('click', closeMenus)
|
document.addEventListener('click', closeMenus)
|
||||||
if (isAuthenticated.value) {
|
if (isAuthenticated.value) {
|
||||||
@ -590,6 +751,12 @@ onUnmounted(() => {
|
|||||||
<span v-else class="loading-spinner"></span>
|
<span v-else class="loading-spinner"></span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<!-- 忘记密码链接 -->
|
||||||
|
<div class="forgot-password" v-if="loginMode === 'login'">
|
||||||
|
<button class="forgot-password-link" @click="openResetPasswordModal">忘记密码?</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="auth-switch">
|
<div class="auth-switch">
|
||||||
{{ loginMode === 'login' ? '还没有账号?' : '已有账号?' }}
|
{{ loginMode === 'login' ? '还没有账号?' : '已有账号?' }}
|
||||||
<button
|
<button
|
||||||
@ -602,6 +769,132 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -1738,4 +2031,25 @@ html {
|
|||||||
border-color: var(--color-border);
|
border-color: var(--color-border);
|
||||||
color: var(--color-text-secondary);
|
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>
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user