Files
airz-uniapp/packages/account/password/password.uvue
T
hujun 71b3c25695 feat: 优化账户切换时的页面状态一致性,增加登录回跳校验和课程阅读新逻辑
- 增加账户状态监听定时器,页面切换时自动校验账户归属,避免跨账户操作
- 新增登录回跳路径校验函数,限制仅允许指定页面回跳,防止非法跳转
- 课程页改为整篇连续阅读,底部仅一次标记已读,兼容历史记录
- 完善助手、对比页、资料填写页等多处的账户状态校验逻辑,防止错版数据提交
- 优化账户资料请求逻辑,增加版本校验,避免旧请求覆盖新数据
2026-10-07 10:01:05 +08:00

70 lines
6.3 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page" :style="pageStyle()"><AppHeader :title="mode == 'password' ? '修改密码' : '重设密码'" />
<scroll-view scroll-y :style="contentStyle(96, false)"><view class="account-form-content" :style="bottomStyle(40)">
<text class="form-title">{{ mode == 'password' ? '设置新的登录密码' : '验证手机号,重设密码' }}</text>
<text class="form-description">{{ mode == 'password' ? '为保障账户安全,请先验证当前密码。' : '验证码将发送至绑定手机号 ' + maskPhone(phone) }}</text>
<view class="form-card">
<template v-if="mode == 'password'"><text class="form-label">当前密码</text><input v-model="oldPassword" class="account-input old-password" :disabled="submitting" :password="!visible" maxlength="64" placeholder="请输入当前密码" /></template>
<template v-else><text class="form-label">短信验证码</text><SecurityCode v-model="code" :disabled="submitting" :active="pageVisible" :ownerToken="ownerToken" purpose="password" @error="codeError" @sent="errorMessage = ''" /></template>
<text class="form-label">新密码</text><input v-model="newPassword" class="account-input new-password" :disabled="submitting" :password="!visible" maxlength="32" placeholder="8–32 位,包含字母和数字" />
<text class="form-label">确认新密码</text><input v-model="confirmation" class="account-input confirm-password" :disabled="submitting" :password="!visible" maxlength="32" placeholder="请再次输入新密码" confirm-type="done" @confirm="submit" />
<text class="visibility-control" @click="toggleVisibility">{{ visible ? '隐藏密码' : '显示密码' }}</text>
</view>
<text class="form-hint">密码可包含英文符号,不支持空格。修改成功后请使用新密码重新登录。</text>
<text v-if="errorMessage.length > 0" class="form-error">{{ errorMessage }}</text>
<button class="primary-button form-submit" :disabled="submitting" @click="submit">{{ submitting ? '提交中…' : '确认修改' }}</button>
<text class="form-link" @click="switchMode">{{ mode == 'password' ? '忘记当前密码?使用短信验证' : '使用当前密码验证' }}</text>
</view></scroll-view>
</view>
</template>
<script setup lang="uts">
import AppHeader from '@/components/AppHeader.uvue'
import SecurityCode from '@/packages/account/components/SecurityCode.uvue'
import { usePageLayout } from '@/services/layout.uts'
import { getSession, clearSession } from '@/services/api.uts'
import { getAccountProfile, maskPhone, validNewPassword, validCode, changeAccountPassword } from '@/packages/account/services/account.uts'
const { pageStyle, contentStyle, bottomStyle } = usePageLayout()
const mode = ref('password'); const phone = ref(''); const oldPassword = ref(''); const newPassword = ref(''); const confirmation = ref(''); const code = ref(''); const visible = ref(false); const submitting = ref(false); const errorMessage = ref('')
const ownerToken = ref(''); const pageVisible = ref(false)
let ownerId = ''; let generation = 0; let accountTimer = 0
onLoad((options) => { const session = getSession(); ownerToken.value = session?.getString('token') ?? ''; ownerId = session?.getString('userId') ?? ''; phone.value = getAccountProfile().phone; if (options != null && options['mode'] == 'sms') mode.value = 'sms' })
const clearSecrets = () => { oldPassword.value = ''; newPassword.value = ''; confirmation.value = ''; code.value = ''; visible.value = false }
const stopAccountTimer = () => { clearInterval(accountTimer); accountTimer = 0 }
const sameOwner = (): boolean => {
if (!pageVisible.value) return false
const session = getSession()
if (ownerToken.value.length > 0 && session?.getString('token') == ownerToken.value && session?.getString('userId') == ownerId) return true
generation++; clearSecrets(); phone.value = ''; errorMessage.value = ''; stopAccountTimer()
uni.redirectTo({ url: session == null ? '/packages/account/login/login' : '/packages/account/security/security' })
return false
}
const switchMode = () => { if (!sameOwner() || submitting.value) return; clearSecrets(); errorMessage.value = ''; mode.value = mode.value == 'password' ? 'sms' : 'password' }
const toggleVisibility = () => { if (sameOwner()) visible.value = !visible.value }
const codeError = (message: string) => { if (sameOwner()) errorMessage.value = message }
onShow(() => { pageVisible.value = true; generation++; stopAccountTimer(); if (sameOwner()) accountTimer = setInterval(() => { sameOwner() }, 1000) })
const submit = async () => {
if (!sameOwner() || submitting.value) return
errorMessage.value = ''
if (mode.value == 'password' && oldPassword.value.length == 0) { errorMessage.value = '请输入当前密码'; return }
if (mode.value == 'sms' && !validCode(code.value)) { errorMessage.value = '请输入 6 位数字验证码'; return }
if (!validNewPassword(newPassword.value)) { errorMessage.value = '新密码需为 8–32 位,包含字母和数字,可使用符号'; return }
if (newPassword.value != confirmation.value) { errorMessage.value = '两次输入的新密码不一致'; return }
submitting.value = true
const view = generation; const token = ownerToken.value; const userId = ownerId
try {
await changeAccountPassword(mode.value, oldPassword.value, code.value, newPassword.value)
// 服务端已撤销原账户会话;离页仍清理该会话,但不能影响新账户或跳转隐藏页面。
if (getSession()?.getString('token') != token || getSession()?.getString('userId') != userId) return
clearSecrets(); clearSession()
if (view == generation && pageVisible.value) { uni.reLaunch({ url: '/packages/account/login/login' }); uni.showToast({ title: '密码已修改,请重新登录', icon: 'none' }) }
} catch (error) { if (view == generation && sameOwner()) errorMessage.value = error instanceof Error ? error.message : '修改失败,请稍后重试' }
finally { submitting.value = false }
}
onHide(() => { pageVisible.value = false; generation++; stopAccountTimer(); clearSecrets(); errorMessage.value = '' })
onUnload(() => { pageVisible.value = false; generation++; stopAccountTimer(); clearSecrets(); phone.value = '' })
</script>
<style>
@import '../styles/forms.css';
.visibility-control { color: #0868f7; font-size: 24rpx; padding: 20rpx 0 4rpx; text-align: right; }
</style>