feat: 优化账户切换时的页面状态一致性,增加登录回跳校验和课程阅读新逻辑

- 增加账户状态监听定时器,页面切换时自动校验账户归属,避免跨账户操作
- 新增登录回跳路径校验函数,限制仅允许指定页面回跳,防止非法跳转
- 课程页改为整篇连续阅读,底部仅一次标记已读,兼容历史记录
- 完善助手、对比页、资料填写页等多处的账户状态校验逻辑,防止错版数据提交
- 优化账户资料请求逻辑,增加版本校验,避免旧请求覆盖新数据
This commit is contained in:
hujun
2026-10-07 10:01:05 +08:00
parent 1723363b7e
commit 71b3c25695
37 changed files with 1389 additions and 323 deletions
+36 -10
View File
@@ -77,7 +77,7 @@
const step = ref(0)
const assessmentType = ref('personal')
const form = ref<AssessmentForm>(emptyAssessmentForm())
let ownerToken = ''; let goingToConfirm = false
let ownerToken = ''; let goingToConfirm = false; let visible = false; let accountTimer = 0
const sameOwner = (): boolean => { const session = getSession(); return session != null && session['token'] == ownerToken }
const customCity = ref('')
const cityChanged = (city: string) => { form.value.city = city; if (city != '其他城市') customCity.value = '' }
@@ -88,6 +88,24 @@
const titles = ['基本信息', '经营 / 收入信息', '资产 / 负债信息', '资料上传 / 授权']
const uploadNames = computed((): string[] => assessmentType.value == 'enterprise' ? ['营业执照', '银行流水', '经营证明'] : ['身份证正面', '身份证反面', '银行卡', '收入证明'])
let initialized = false
const stopAccountTimer = () => { if (accountTimer != 0) clearInterval(accountTimer); accountTimer = 0 }
const ensureOwner = (): boolean => {
if (!visible) return false
if (sameOwner()) return true
const session = getSession()
ownerToken = session?.getString('token') ?? ''
editGeneration++; initialized = false; stopAccountTimer(); clearAssessmentFlow()
form.value = emptyAssessmentForm(); documents.value = []; customCity.value = ''; step.value = 0
documentConsent.value = false; dataConsent.value = false; authorizations.value.forEach((item) => { item.selected = false })
baseRevisionId = ''; editLoading.value = false; editError.value = '账户已变化,请返回重新填写。'
if (session == null) uni.redirectTo({ url: '/packages/account/login/login' })
return false
}
const startAccountTimer = () => {
stopAccountTimer()
if (ownerToken.length == 0) return
accountTimer = setInterval(() => { if (visible && !sameOwner()) ensureOwner() }, 1000)
}
const rememberInput = () => {
if (!initialized || !sameOwner()) return
const source = form.value
@@ -110,15 +128,15 @@
initialized = true
rememberInput()
}
const switchType = (type: string) => { if (editId.value.length > 0 || assessmentType.value == type) return; rememberInput(); restore(type); step.value = 0; rememberInput() }
const switchType = (type: string) => { if (!ensureOwner() || editId.value.length > 0 || assessmentType.value == type) return; rememberInput(); restore(type); step.value = 0; rememberInput() }
const loadEdit = async (type: string) => {
const ticket = ++editGeneration; editLoading.value = true
try {
const saved = await fetchAssessmentEdit(editId.value)
if (ticket != editGeneration || !sameOwner()) return
if (ticket != editGeneration || !ensureOwner()) return
baseRevisionId = saved.getString('baseRevisionId') ?? ''
writeAssessmentFlow(ownerToken, saved); restore(saved.getString('type') ?? type)
} catch (err) { if (ticket == editGeneration && sameOwner()) editError.value = err instanceof Error ? err.message : '资料读取失败,请返回重试。' }
} catch (err) { if (ticket == editGeneration && ensureOwner()) editError.value = err instanceof Error ? err.message : '资料读取失败,请返回重试。' }
finally { if (ticket == editGeneration) editLoading.value = false }
}
onLoad((options) => {
@@ -134,13 +152,16 @@
loadEdit(type)
})
onShow(() => {
goingToConfirm = false
if (initialized && !sameOwner()) { initialized = false; form.value = emptyAssessmentForm(); clearAssessmentFlow(); uni.redirectTo({ url: '/packages/account/login/login' }) }
visible = true; stopAccountTimer()
goingToConfirm = false
if (initialized && !sameOwner()) ensureOwner()
const input = getAssessmentInput(assessmentType.value)
if (initialized && input != null && typeof input['step'] == 'number') step.value = Math.max(0, Math.min(3, input['step'] as number))
startAccountTimer()
})
// 离开填写流程即丢弃未提交内容;进入确认页时只在内存传递。
onHide(() => {
visible = false; stopAccountTimer()
if (!goingToConfirm) {
editGeneration++; if (editId.value.length > 0) editError.value = '已离开修改流程,请返回档案重新进入。'
// 离页清空资料但不切换步骤,避免卸载时又挂载另一组原生输入组件。
@@ -149,7 +170,7 @@
authorizations.value.forEach((item) => { item.selected = false })
}
})
onUnload(() => { editGeneration++; initialized = false; clearAssessmentFlow() })
onUnload(() => { visible = false; stopAccountTimer(); editGeneration++; initialized = false; clearAssessmentFlow() })
// 页内前后步骤只维护当前内存状态,返回入口后不保留填写内容。
onBackPress((): boolean => { rememberInput(); if (step.value > 0) { step.value--; rememberInput(); return true }; return false })
const previous = () => { rememberInput(); if (step.value > 0) step.value--; rememberInput() }
@@ -160,17 +181,22 @@
uni.showToast({ title: name + '已失效,请重新选择', icon: 'none' })
}
const chooseDocument = (name: string) => {
if (!ensureOwner()) return
const ticket = editGeneration
uni.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'], success: (result) => {
if (result.tempFilePaths.length == 0) return
if (ticket != editGeneration || !ensureOwner() || result.tempFilePaths.length == 0) return
documents.value = documents.value.filter((file) => file.name != name)
const document: SelectedDocument = { name, path: result.tempFilePaths[0] }
documents.value.push(document); rememberInput()
}, fail: (error) => { if (!error.errMsg.includes('cancel')) uni.showToast({ title: '选择失败,请重试或检查相册权限', icon: 'none' }) } })
}, fail: (error) => { if (ticket == editGeneration && ensureOwner() && !error.errMsg.includes('cancel')) uni.showToast({ title: '选择失败,请重试或检查相册权限', icon: 'none' }) } })
}
const documentAction = (name: string) => {
if (!ensureOwner()) return
const ticket = editGeneration
const path = documentPath(name)
if (path.length == 0) { chooseDocument(name); return }
uni.showActionSheet({ itemList: ['预览图片', '重新选择', '移除资料'], success: (result) => {
if (ticket != editGeneration || !ensureOwner()) return
if (result.tapIndex == 0) uni.previewImage({ urls: [path], fail: () => { uni.showToast({ title: '本地图片已失效,请重新选择', icon: 'none' }) } })
else if (result.tapIndex == 1) chooseDocument(name)
else if (result.tapIndex == 2) { documents.value = documents.value.filter((file) => file.name != name); rememberInput() }
@@ -196,7 +222,7 @@
if (message.length == 0) return true
uni.showToast({ title: message, icon: 'none' }); return false
}
const next = () => { if (editLoading.value || editError.value.length > 0 || !sameOwner()) return; if (!validateStep()) return; if (step.value < 3) { step.value++; rememberInput() } else { rememberInput(); goingToConfirm = true; uni.navigateTo({ url: '/packages/finance/confirm/confirm', fail: () => { goingToConfirm = false } }) } }
const next = () => { if (!ensureOwner() || editLoading.value || editError.value.length > 0) return; if (!validateStep()) return; if (step.value < 3) { step.value++; rememberInput() } else { rememberInput(); goingToConfirm = true; uni.navigateTo({ url: '/packages/finance/confirm/confirm', fail: () => { goingToConfirm = false } }) } }
</script>
<style>