feat: 增加反馈消息闭环相关功能及文件
新增反馈目标登录白名单、账户切换守卫、待确认反馈存储、缓存生命周期管理,补充相关文档及截图,完善消息跳转逻辑
This commit is contained in:
+109
-32
@@ -17,7 +17,11 @@
|
||||
</view>
|
||||
<scroll-view scroll-y class="home-scroll" :style="contentStyle(104, false)">
|
||||
<view class="home-content">
|
||||
<view class="ai-banner" @click="go('/packages/finance/assistant/assistant')">
|
||||
<view v-if="banners.length > 0" class="ai-banner" @click="openBanner(banners[0].actionUrl)">
|
||||
<view class="banner-copy"><text class="banner-title">{{ banners[0].title }}</text><text class="banner-subtitle">{{ banners[0].subtitle }}</text></view>
|
||||
<image class="robot-img" :src="banners[0].image" mode="aspectFit"></image>
|
||||
</view>
|
||||
<view v-else class="ai-banner" @click="go('/packages/finance/assistant/assistant')">
|
||||
<view class="banner-copy"><text class="banner-title">AI智能评估</text><text class="banner-subtitle">精准匹配融资方案</text><text class="banner-desc">让合适的资金主动找上您</text></view>
|
||||
<image class="robot-img" src="/static/mobile/images/robot-cutout.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
@@ -32,13 +36,17 @@
|
||||
<view class="shortcut" @click="go('/packages/account/points/points')"><view class="shortcut-icon"><AppIcon name="gift" :size="60" /></view><text class="shortcut-label">邀请有礼</text></view>
|
||||
</view>
|
||||
<view class="archive-entry" @click="go('/packages/finance/archive/archive')"><AppIcon name="archive" :size="42" /><text class="archive-label">我的融资档案</text><text class="archive-action">去完善</text><AppIcon name="chevron-right" :size="28" /></view>
|
||||
<view class="section-head"><text class="section-title">我的融资方案</text><view class="more-link" @click="go('/packages/finance/matches/matches')"><text class="more">查看全部</text><AppIcon name="chevron-right" :size="24" /></view></view>
|
||||
<view class="plan-card" @click="go('/packages/finance/matches/matches')"><view class="plan-main"><text class="plan-name">信用融资方案</text><text class="plan-meta">了解额度、利率与申请条件</text></view><view class="score"><text class="score-number">3</text><text class="score-label">可查看方案</text></view></view>
|
||||
<view class="section-head"><text class="section-title">我的融资方案</text><view class="more-link" @click="go('/packages/finance/archive/archive')"><text class="more">查看档案</text><AppIcon name="chevron-right" :size="24" /></view></view>
|
||||
<view class="plan-card" @click="openSummary">
|
||||
<view class="plan-main"><text class="plan-name">{{ summaryName() }}</text><text class="plan-meta">{{ summaryMeta() }}</text></view>
|
||||
<view class="score"><text class="score-number">{{ summaryCount() }}</text><text class="score-label">{{ summaryLabel() }}</text></view>
|
||||
</view>
|
||||
<view class="section-head"><text class="section-title">融资课堂</text><view class="more-link" @click="go('/packages/content/classroom/classroom')"><text class="more">进入课堂</text><AppIcon name="chevron-right" :size="24" /></view></view>
|
||||
<view v-if="contentLoading" class="home-content-state"><text>正在加载课程…</text></view>
|
||||
<view v-else-if="contentError.length > 0" class="home-content-state" @click="loadRecommendations"><text>{{ contentError }}</text><text class="home-content-retry">重新加载</text></view>
|
||||
<view v-else-if="recommendedLessons.length == 0" class="home-content-state"><text>暂无推荐课程</text></view>
|
||||
<view v-else class="news-preview" @click="go('/packages/content/lesson/lesson?id=' + recommendedLessons[0].id)"><view class="news-copy"><text class="news-title">{{ recommendedLessons[0].title }}</text><text class="news-meta">{{ recommendedLessons[0].tag }} · {{ recommendedLessons[0].duration }}</text></view><image class="news-art" :src="recommendedLessons[0].image" mode="aspectFill"></image></view>
|
||||
<view v-else-if="contentError.length > 0" class="home-content-state" @click="loadRecommendationsRetry"><text>{{ contentError }}</text><text class="home-content-retry">重新加载</text></view>
|
||||
<view v-else-if="recommendedLessons.length == 0 && recommendedArticles.length == 0" class="home-content-state"><text>暂无推荐内容</text></view>
|
||||
<view v-else-if="recommendedLessons.length > 0" class="news-preview" @click="go('/packages/content/lesson/lesson?id=' + recommendedLessons[0].id)"><view class="news-copy"><text class="news-title">{{ recommendedLessons[0].title }}</text><text class="news-meta">{{ recommendedLessons[0].tag }} · {{ recommendedLessons[0].duration }}</text></view><image class="news-art" :src="recommendedLessons[0].image" mode="aspectFill"></image></view>
|
||||
<view v-else class="news-preview" @click="go('/packages/content/article/article?id=' + recommendedArticles[0].id)"><view class="news-copy"><text class="news-title">{{ recommendedArticles[0].title }}</text><text class="news-meta">{{ recommendedArticles[0].category }} · {{ recommendedArticles[0].date }}</text></view><image class="news-art" :src="recommendedArticles[0].image" mode="aspectFill"></image></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<BottomNav active="home" />
|
||||
@@ -48,7 +56,8 @@
|
||||
<script setup lang="uts">
|
||||
import { getSession } from '@/services/api.uts'
|
||||
import { fetchUnreadMessages } from '@/services/messages.uts'
|
||||
import { fetchContentHome, FinanceLesson } from '@/services/content.uts'
|
||||
import { fetchContentHome, FinanceLesson, NewsArticle } from '@/services/content.uts'
|
||||
import { fetchHome, HomeAssessmentSummary, HomeBanner } from '@/services/home.uts'
|
||||
|
||||
import { usePageLayout } from '@/services/layout.uts'
|
||||
const { contentStyle, statusStyle, pageStyle, navigationStyle, navigationActionStyle, navigationBrandStyle } = usePageLayout()
|
||||
@@ -60,32 +69,100 @@
|
||||
if (consumeInitialLaunch()) uni.reLaunch({ url: '/packages/account/index/index' })
|
||||
})
|
||||
const hasUnread = ref(false)
|
||||
const recommendedLessons = ref<FinanceLesson[]>([])
|
||||
const contentLoading = ref(false); const contentError = ref('')
|
||||
let contentGeneration = 0
|
||||
async function loadRecommendations() {
|
||||
const request = ++contentGeneration
|
||||
contentLoading.value = true; contentError.value = ''; recommendedLessons.value = []
|
||||
try {
|
||||
const result = await fetchContentHome()
|
||||
if (request == contentGeneration) recommendedLessons.value = result.lessons
|
||||
} catch (_) { if (request == contentGeneration) contentError.value = '推荐课程加载失败' }
|
||||
finally { if (request == contentGeneration) contentLoading.value = false }
|
||||
}
|
||||
let unreadGeneration = 0
|
||||
onShow(async () => {
|
||||
loadRecommendations()
|
||||
const request = ++unreadGeneration; const token = getSession()?.getString('token') ?? ''
|
||||
hasUnread.value = false
|
||||
if (token.length == 0) return
|
||||
try {
|
||||
const unread = await fetchUnreadMessages()
|
||||
if (request == unreadGeneration && getSession()?.getString('token') == token) hasUnread.value = unread > 0
|
||||
} catch (_) { /* 首页其他入口仍可用,消息页提供明确的加载错误与重试。 */ }
|
||||
})
|
||||
onHide(() => { unreadGeneration++; contentGeneration++; hasUnread.value = false })
|
||||
onUnload(() => { unreadGeneration++; contentGeneration++ })
|
||||
const banners = ref<HomeBanner[]>([])
|
||||
const assessmentSummary = ref<HomeAssessmentSummary | null>(null)
|
||||
const homeError = ref('')
|
||||
const homeLoading = ref(false)
|
||||
const recommendedLessons = ref<FinanceLesson[]>([])
|
||||
const recommendedArticles = ref<NewsArticle[]>([])
|
||||
const contentLoading = ref(false); const contentError = ref('')
|
||||
let generation = 0; let visible = false; let owner = ''
|
||||
|
||||
const clearHome = () => {
|
||||
banners.value = []; assessmentSummary.value = null; hasUnread.value = false
|
||||
recommendedLessons.value = []; recommendedArticles.value = []
|
||||
homeError.value = ''; contentError.value = ''; contentLoading.value = false
|
||||
}
|
||||
const active = (ticket: number, token: string): boolean => {
|
||||
if (!visible || ticket != generation) return false
|
||||
const currentToken = getSession()?.getString('token') ?? ''
|
||||
if (currentToken == token) return true
|
||||
generation++; clearHome(); owner = currentToken
|
||||
return false
|
||||
}
|
||||
const applyRecommendations = (lessons: FinanceLesson[], articles: NewsArticle[]) => {
|
||||
recommendedLessons.value = lessons; recommendedArticles.value = articles; contentError.value = ''
|
||||
}
|
||||
const go = (url: string) => { uni.navigateTo({ url: url }) }
|
||||
async function loadRecommendations(ticket: number = generation, token: string = owner) {
|
||||
contentLoading.value = true; contentError.value = ''
|
||||
try {
|
||||
const result = await fetchContentHome()
|
||||
if (active(ticket, token)) applyRecommendations(result.lessons, result.articles)
|
||||
} catch (_) { if (active(ticket, token)) contentError.value = '推荐内容加载失败' }
|
||||
finally { if (ticket == generation) contentLoading.value = false }
|
||||
}
|
||||
async function loadFallback(ticket: number, token: string) {
|
||||
// 聚合接口失败时只补读公开内容;个人摘要仍保持空值,避免伪造账户数据。
|
||||
loadRecommendations(ticket, token)
|
||||
if (token.length == 0) return
|
||||
try {
|
||||
const unread = await fetchUnreadMessages()
|
||||
if (active(ticket, token)) hasUnread.value = unread > 0
|
||||
} catch (_) { /* 消息页会显示明确错误,首页不把失败当成未读为零。 */ }
|
||||
}
|
||||
async function loadHome(retryAfterSessionChange: boolean = true) {
|
||||
const ticket = ++generation; const token = getSession()?.getString('token') ?? ''
|
||||
owner = token; clearHome(); homeLoading.value = true
|
||||
try {
|
||||
const result = await fetchHome()
|
||||
if (!active(ticket, token)) return
|
||||
banners.value = result.banners; assessmentSummary.value = result.assessmentSummary
|
||||
hasUnread.value = result.unreadCount > 0
|
||||
applyRecommendations(result.recommendations.lessons, result.recommendations.articles)
|
||||
} catch (_) {
|
||||
// 请求过程中会话被共享请求层清除后,以当前会话(通常是匿名)重试,
|
||||
// 避免旧 token 的错误阻止公开内容回填。
|
||||
const currentToken = getSession()?.getString('token') ?? ''
|
||||
if (retryAfterSessionChange && currentToken != token) {
|
||||
if (visible) await loadHome(false)
|
||||
return
|
||||
}
|
||||
if (!active(ticket, token)) return
|
||||
homeError.value = '首页聚合数据暂时不可用'
|
||||
await loadFallback(ticket, token)
|
||||
} finally { if (ticket == generation) homeLoading.value = false }
|
||||
}
|
||||
const loadRecommendationsRetry = () => { loadRecommendations(generation, owner) }
|
||||
const summaryName = (): string => {
|
||||
if (homeLoading.value) return '正在读取融资方案'
|
||||
if (homeError.value.length > 0) return '融资方案暂不可用'
|
||||
if (assessmentSummary.value == null) return owner.length > 0 ? '暂无融资评估' : '登录后查看融资方案'
|
||||
const latest = assessmentSummary.value!.latest
|
||||
return latest == null ? '我的融资评估' : (latest.statusLabel.length > 0 ? latest.statusLabel : '我的融资评估')
|
||||
}
|
||||
const summaryMeta = (): string => {
|
||||
if (homeError.value.length > 0) return '点击重试,或进入消息与档案页面查看'
|
||||
if (assessmentSummary.value == null) return owner.length > 0 ? '完成一次评估后,这里会显示最新状态' : '登录后可查看自己的评估状态'
|
||||
const latest = assessmentSummary.value!.latest
|
||||
if (latest == null) return '已有评估记录,可进入融资档案查看'
|
||||
return latest.hasReport ? '报告已生成,可查看分析结果' : '进入融资档案查看分析进度'
|
||||
}
|
||||
const summaryCount = (): string => assessmentSummary.value == null ? '—' : assessmentSummary.value!.total.toString()
|
||||
const summaryLabel = (): string => assessmentSummary.value == null ? '评估记录' : '份评估记录'
|
||||
const openSummary = () => {
|
||||
const latest = assessmentSummary.value?.latest
|
||||
if (latest == null) { go('/packages/finance/archive/archive'); return }
|
||||
if (latest.status == 'succeeded' && latest.hasReport) {
|
||||
go('/packages/finance/report/report?assessmentId=' + latest.assessmentId + (latest.revisionId != null ? '&revisionId=' + latest.revisionId : ''))
|
||||
} else go('/packages/finance/archive/archive?assessmentId=' + latest.assessmentId + '&type=' + latest.type)
|
||||
}
|
||||
const openBanner = (url: string) => {
|
||||
if (url.startsWith('/pages/') || url.startsWith('/packages/')) go(url)
|
||||
}
|
||||
onShow(() => { visible = true; loadHome() })
|
||||
onHide(() => { visible = false; generation++; clearHome(); homeLoading.value = false })
|
||||
onUnload(() => { visible = false; generation++; clearHome() })
|
||||
const openEntry = (type: string) => { go('/packages/finance/entry/entry?type=' + type) }
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user