- 增加账户状态监听定时器,页面切换时自动校验账户归属,避免跨账户操作 - 新增登录回跳路径校验函数,限制仅允许指定页面回跳,防止非法跳转 - 课程页改为整篇连续阅读,底部仅一次标记已读,兼容历史记录 - 完善助手、对比页、资料填写页等多处的账户状态校验逻辑,防止错版数据提交 - 优化账户资料请求逻辑,增加版本校验,避免旧请求覆盖新数据
144 lines
11 KiB
Plaintext
144 lines
11 KiB
Plaintext
<template>
|
|
<view class="page" :style="pageStyle()">
|
|
<AppHeader title="个人中心" :back="false" />
|
|
<scroll-view scroll-y :style="contentStyle(226, false)">
|
|
<view class="profile-content">
|
|
<view class="profile-card" @click="openAccount"><view class="avatar"><image class="profile-avatar" :src="avatar.length > 0 ? avatar : DEFAULT_AVATAR" mode="aspectFill" @error="avatar = ''" /></view><view class="profile-main"><text class="profile-name">{{ displayName }}</text><text class="profile-phone">{{ loggedIn ? maskedPhone : '登录后管理您的融资信息' }}</text></view><text v-if="!loggedIn" class="account-state">未登录</text><AppIcon v-if="loggedIn" name="chevron-right" :size="28" /></view>
|
|
<view class="stat-row"><view class="stat-item" @click="startAssessment"><AppIcon name="edit-file" :size="42" /><text class="stat-label">开始评估</text></view><view class="stat-item" @click="openReport"><AppIcon name="file-text" :size="42" /><text class="stat-label">{{ reportLoading ? '正在查找…' : '最近报告' }}</text></view><view class="stat-item" @click="go('/packages/account/points/points')"><AppIcon name="points" :size="42" /><text class="stat-label">积分中心</text></view></view>
|
|
<view class="menu-list"><view class="menu-row" v-for="item in menus" :key="item.name" @click="open(item.key)"><AppIcon :name="item.icon" :size="46" /><text class="menu-name">{{ item.name }}</text><text v-if="item.note.length > 0" class="menu-note">{{ item.note }}</text><AppIcon name="chevron-right" :size="28" /></view></view>
|
|
<button class="logout" :disabled="loggingOut" @click="logout">{{ loggingOut ? '退出中…' : (loggedIn ? '退出登录' : '登录 / 注册') }}</button>
|
|
<text class="version-note">AI融资 · 让融资更简单</text>
|
|
</view>
|
|
</scroll-view>
|
|
<BottomNav active="profile" />
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="uts">
|
|
|
|
|
|
import { usePageLayout } from '@/services/layout.uts'
|
|
import AppHeader from '@/components/AppHeader.uvue'
|
|
import BottomNav from '@/components/BottomNav.uvue'
|
|
import AppIcon from '@/components/AppIcon.uvue'
|
|
import { getSession } from '@/services/api.uts'
|
|
import { logoutAccount } from '@/packages/account/services/auth.uts'
|
|
import { findLatestReportEntry } from '@/packages/account/services/latest-report.uts'
|
|
import { showAgreementNotice } from '@/services/assessment.uts'
|
|
import { DEFAULT_AVATAR, getAccountProfile, fetchAccountProfile, maskPhone } from '@/packages/account/services/account.uts'
|
|
const { contentStyle, pageStyle } = usePageLayout()
|
|
type ProfileMenu = { icon: string, name: string, note: string, key: string }
|
|
const menus: ProfileMenu[] = [{ icon: 'archive', name: '我的融资档案', note: '', key: 'archive' }, { icon: 'shield', name: '账户安全', note: '', key: 'security' }, { icon: 'privacy', name: '隐私与授权', note: '', key: 'privacy' }, { icon: 'help', name: '帮助与反馈', note: '', key: 'help' }, { icon: 'info', name: '关于我们', note: 'V1.0.0', key: 'about' }]
|
|
const loggedIn = ref(false)
|
|
const displayName = ref('登录 / 注册')
|
|
const maskedPhone = ref('')
|
|
const avatar = ref('')
|
|
const reportLoading = ref(false)
|
|
const loggingOut = ref(false)
|
|
let profileVisible = false; let reportRequest = 0; let generation = 0; let accountTimer = 0; let ownerToken = ''; let ownerId = ''
|
|
const stopAccountTimer = () => { clearInterval(accountTimer); accountTimer = 0 }
|
|
const syncProfile = () => {
|
|
const request = ++generation; reportRequest++; reportLoading.value = false; loggingOut.value = false
|
|
const session = getSession()
|
|
ownerToken = session?.getString('token') ?? ''; ownerId = session?.getString('userId') ?? ''
|
|
loggedIn.value = session != null
|
|
const profile = getAccountProfile()
|
|
maskedPhone.value = maskPhone(profile.phone); avatar.value = loggedIn.value ? profile.avatar : ''
|
|
displayName.value = loggedIn.value ? profile.nickname : '登录 / 注册'
|
|
if (loggedIn.value) fetchAccountProfile().then((fresh) => {
|
|
if (!profileVisible || request != generation || getSession()?.getString('token') != ownerToken || getSession()?.getString('userId') != ownerId) return
|
|
displayName.value = fresh.nickname; avatar.value = fresh.avatar; maskedPhone.value = maskPhone(fresh.phone)
|
|
}).catch(() => { /* 读取失败保留已归属当前账户的缓存,不显示其他账户资料。 */ })
|
|
}
|
|
const sameOwner = (): boolean => {
|
|
if (!profileVisible) return false
|
|
const session = getSession()
|
|
if ((session?.getString('token') ?? '') == ownerToken && (session?.getString('userId') ?? '') == ownerId) return true
|
|
syncProfile(); return false
|
|
}
|
|
onShow(() => { profileVisible = true; syncProfile(); stopAccountTimer(); accountTimer = setInterval(() => { sameOwner() }, 1000) })
|
|
const go = (url: string) => { uni.navigateTo({ url }) }
|
|
const startAssessment = () => {
|
|
if (!sameOwner()) return
|
|
const session = getSession()
|
|
if (session == null) { go('/packages/account/login/login'); return }
|
|
const view = generation
|
|
uni.showActionSheet({ itemList: ['个人评估', '企业评估'], success: (result) => {
|
|
if (view == generation && sameOwner()) go('/packages/finance/entry/entry?type=' + (result.tapIndex == 0 ? 'personal' : 'enterprise'))
|
|
} })
|
|
}
|
|
const openReport = async () => {
|
|
if (!sameOwner() || reportLoading.value) return
|
|
const session = getSession()
|
|
if (session == null) { go('/packages/account/login/login'); return }
|
|
const token = session.getString('token') ?? ''; const request = ++reportRequest
|
|
const current = (): boolean => profileVisible && request == reportRequest && getSession()?.getString('token') == token
|
|
reportLoading.value = true
|
|
try {
|
|
const entry = await findLatestReportEntry()
|
|
if (!current()) return
|
|
if (entry != null && entry.status == 'succeeded') {
|
|
go('/packages/finance/report/report?assessmentId=' + entry.id)
|
|
} else if (entry != null) {
|
|
const id = entry.id
|
|
uni.showModal({ title: '报告生成中', content: '您有一份评估正在排队或分析,可查看当前进度。', confirmText: '查看进度', cancelText: '暂不查看', success: (result) => {
|
|
if (result.confirm && current()) go('/packages/finance/analyzing/analyzing?assessmentId=' + id)
|
|
} })
|
|
} else {
|
|
uni.showModal({ title: '暂无报告', content: '暂无已完成的评估报告。您可以开始评估,已提交的记录可在“我的融资档案”查看。', confirmText: '开始评估', cancelText: '暂不评估', success: (result) => {
|
|
if (result.confirm && current()) startAssessment()
|
|
} })
|
|
}
|
|
} catch (err) {
|
|
if (!profileVisible || request != reportRequest) return
|
|
if (getSession() == null) { go('/packages/account/login/login'); return }
|
|
if (current()) uni.showToast({ title: err instanceof Error ? err.message : '报告查找失败,请重试', icon: 'none' })
|
|
} finally { if (request == reportRequest) reportLoading.value = false }
|
|
}
|
|
// 离页或退出后,旧查找结果及弹窗回调不得把用户带到前一个账户的评估。
|
|
const hideProfile = () => { profileVisible = false; generation++; reportRequest++; reportLoading.value = false; stopAccountTimer(); displayName.value = '登录 / 注册'; avatar.value = ''; maskedPhone.value = ''; loggedIn.value = false }
|
|
onHide(() => hideProfile())
|
|
onUnload(() => hideProfile())
|
|
const open = (key: string) => {
|
|
if (!sameOwner()) return
|
|
if (key == 'archive') go('/packages/finance/archive/archive')
|
|
else if (key == 'help') go('/packages/content/help/help')
|
|
else if (key == 'security') go(loggedIn.value ? '/packages/account/security/security' : '/packages/account/login/login')
|
|
else if (key == 'privacy') { const view = generation; uni.showActionSheet({ itemList: ['查看隐私说明', '管理资料与授权'], success: (result) => { if (view != generation || !sameOwner()) return; if (result.tapIndex == 0) showAgreementNotice('隐私说明'); else if (result.tapIndex == 1) { go('/packages/finance/archive/archive') } } }) }
|
|
else if (key == 'about') uni.showModal({ title: '关于 AI融资', content: '版本 V1.0.0\n帮助您整理融资信息、了解融资知识与融资方案。评估结果仅供参考,不构成授信承诺。', showCancel: false, confirmText: '我知道了' })
|
|
}
|
|
const openAccount = () => { if (sameOwner()) go(loggedIn.value ? '/packages/account/edit-profile/edit-profile' : '/packages/account/login/login') }
|
|
const logout = () => {
|
|
if (!sameOwner() || loggingOut.value) return
|
|
if (!loggedIn.value) { go('/packages/account/login/login'); return }
|
|
const view = generation; const token = ownerToken; const userId = ownerId
|
|
uni.showModal({ title: '退出登录', content: '将清除本机账户缓存和未提交的填写内容,已提交的评估记录不受影响。', confirmText: '退出登录', success: (result) => {
|
|
if (!result.confirm || view != generation || !sameOwner() || loggingOut.value) return
|
|
loggingOut.value = true
|
|
logoutAccount().then(() => { if (profileVisible && view == generation && getSession() == null) uni.reLaunch({ url: '/packages/account/login/login' }) })
|
|
.catch(() => { if (profileVisible && view == generation && getSession()?.getString('token') == token && getSession()?.getString('userId') == userId) uni.showToast({ title: '退出失败,请检查网络后重试', icon: 'none' }) })
|
|
.finally(() => { if (view == generation) loggingOut.value = false })
|
|
}, fail: () => { if (view == generation && sameOwner()) uni.showToast({ title: '暂时无法打开确认框,请重试', icon: 'none' }) } })
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.profile-content { padding: 26rpx 30rpx 90rpx; }
|
|
.profile-card { min-height: 166rpx; padding: 28rpx 24rpx; border-radius: 18rpx 18rpx 0 0; background-color: #fff; flex-direction: row; align-items: center; }
|
|
.avatar { width: 84rpx; height: 84rpx; align-items: center; justify-content: center; margin-right: 20rpx; flex-shrink: 0; }
|
|
.profile-avatar { width: 84rpx; height: 84rpx; border-radius: 42rpx; background-color: #eaf2ff; }
|
|
.profile-main { flex: 1; min-width: 0; }
|
|
.profile-name { color: #23344e; font-size: 32rpx; font-weight: 700; }
|
|
.profile-phone { color: #8190a4; font-size: 24rpx; line-height: 36rpx; margin-top: 8rpx; }
|
|
.account-state { padding: 8rpx 12rpx; border-radius: 8rpx; background-color: #eef4fd; color: #6c84a5; font-size: 22rpx; flex-shrink: 0; margin-left: 10rpx; }
|
|
.stat-row { min-height: 136rpx; background-color: #fff; border-top: 1rpx solid #edf1f6; border-radius: 0 0 18rpx 18rpx; flex-direction: row; }
|
|
.stat-item { flex: 1; align-items: center; justify-content: center; }
|
|
.stat-label { color: #7d8ca1; font-size: 24rpx; margin-top: 10rpx; }
|
|
.menu-list { margin-top: 24rpx; padding: 0 24rpx; border-radius: 18rpx; background-color: #fff; }
|
|
.menu-row { min-height: 106rpx; flex-direction: row; align-items: center; border-bottom: 1rpx solid #edf1f6; }
|
|
.menu-name { flex: 1; font-size: 28rpx; color: #2a3a53; margin-left: 20rpx; }
|
|
.menu-note { color: #8793a6; font-size: 23rpx; margin-right: 12rpx; }
|
|
.logout { margin-top: 26rpx; height: 92rpx; line-height: 92rpx; border-radius: 16rpx; color: #56667d; background-color: #fff; border: 1rpx solid #e5ecf5; font-size: 28rpx; }
|
|
.version-note { color: #94a0b0; font-size: 22rpx; text-align: center; margin-top: 28rpx; }
|
|
</style>
|