fix: 修复帮助内容加载、搜索滚动和账户同步的问题

- 完善帮助内容接口的响应校验,增加数据格式和范围验证
- 优化搜索页面的滚动位置管理,避免H5页面激活时的组件重建问题
- 增加账户切换时的定时器清理,防止内存泄漏和重复请求
This commit is contained in:
hujun
2026-10-08 17:43:53 +08:00
parent 71b3c25695
commit 25cb06b71c
9 changed files with 1317 additions and 39 deletions
+17 -9
View File
@@ -82,7 +82,8 @@
const targetId = ref(''); const targetItem = ref<FeedbackItem | null>(null); const targetLoading = ref(false); const targetError = ref('')
let detailRequest = 0
let visible = false; let generation = 0; let faqRequest = 0; let listRequest = 0; let page = 0
let owner = ''
let owner = ''; let accountTimer = 0
function stopAccountTimer() { if (accountTimer != 0) { clearInterval(accountTimer); accountTimer = 0 } }
const groups = computed((): HelpGroup[] => {
const results: HelpGroup[] = []
helpCategories.forEach((category: string) => {
@@ -122,8 +123,10 @@
if (!visible || ticket != generation) return false
const sessionToken = getSession()?.getString('token') ?? ''
if (sessionToken == token) return true
generation++; clearFeedback(); targetId.value = ''; owner = sessionToken; loggedIn.value = owner.length > 0
if (loggedIn.value) { restorePending(); reloadAfterAccountChange() }
generation++; faqRequest++; loading.value = false; errorMessage.value = ''; expanded.value = ''
clearFeedback(); targetId.value = ''; owner = sessionToken; loggedIn.value = owner.length > 0
if (loggedIn.value) restorePending()
reloadAfterAccountChange()
return false
}
reconcileAccount = () => { current(generation, owner) }
@@ -143,10 +146,12 @@
}
const load = async () => {
if (!visible || loading.value) return
const account = generation; const token = owner
if (!current(account, token)) return
const ticket = ++faqRequest
loading.value = true; errorMessage.value = ''
try { const items = await fetchHelpQuestions(); if (visible && ticket == faqRequest) questions.value = items }
catch (_error) { if (visible && ticket == faqRequest) errorMessage.value = '暂时无法加载帮助内容,请稍后重试' }
loading.value = true; errorMessage.value = ''; questions.value = []; expanded.value = ''
try { const items = await fetchHelpQuestions(); if (current(account, token) && ticket == faqRequest) questions.value = items }
catch (_error) { if (current(account, token) && ticket == faqRequest) errorMessage.value = '暂时无法加载帮助内容,请稍后重试' }
finally { if (ticket == faqRequest) loading.value = false }
}
async function loadFeedback(append: boolean) {
@@ -170,9 +175,11 @@
} finally { if (ticket == generation && request == listRequest) { feedbackLoading.value = false; loadingMore.value = false } }
}
function reloadFeedback() { loadFeedback(false) }
reloadAfterAccountChange = () => { reloadTarget(); reloadFeedback() }
reloadAfterAccountChange = () => { load(); if (tab.value == 'feedback') { reloadTarget(); reloadFeedback() } }
const loadMore = () => { loadFeedback(true) }
const changeTab = (value: string) => {
if (!visible) return
reconcileAccount()
tab.value = value
if (value == 'feedback') { reloadTarget(); reloadFeedback() } else if (questions.value.length == 0) load()
}
@@ -226,9 +233,10 @@
visible = true; generation++; const token = getSession()?.getString('token') ?? ''
if (token != owner) { if (owner.length > 0) targetId.value = ''; clearFeedback() }
owner = token; loggedIn.value = token.length > 0; restorePending(); load(); if (tab.value == 'feedback') { reloadTarget(); reloadFeedback() }
stopAccountTimer(); accountTimer = setInterval(() => { reconcileAccount() }, 1000)
})
onHide(() => { visible = false; generation++; faqRequest++; listRequest++; detailRequest++; targetItem.value = null; targetLoading.value = false; targetError.value = ''; loading.value = false; feedbackLoading.value = false; loadingMore.value = false; sending.value = false; records.value = [] })
onUnload(() => { visible = false; generation++; faqRequest++; clearFeedback() })
onHide(() => { visible = false; stopAccountTimer(); generation++; faqRequest++; listRequest++; detailRequest++; targetItem.value = null; targetLoading.value = false; targetError.value = ''; loading.value = false; feedbackLoading.value = false; loadingMore.value = false; sending.value = false; records.value = [] })
onUnload(() => { visible = false; stopAccountTimer(); generation++; faqRequest++; clearFeedback() })
</script>
<style>
.help-body { overflow: hidden; }
+9 -4
View File
@@ -12,7 +12,7 @@
<view class="search-tabs">
<view v-for="item in tabs" :key="item.id" :class="activeType == item.id ? 'search-tab selected-tab' : 'search-tab'" @click="selectType(item.id)"><text :class="activeType == item.id ? 'tab-label selected-label' : 'tab-label'">{{ item.label }}</text></view>
</view>
<scroll-view scroll-y :key="listVersion" :style="contentStyle(296, false)">
<scroll-view scroll-y :scroll-top="scrollTop" :style="contentStyle(296, false)">
<view class="search-content" :style="bottomStyle(40)">
<view v-if="!searched" class="search-guide">
<text class="guide-title">想了解什么?</text><text class="guide-copy">查找融资资讯,学习实用知识</text>
@@ -50,12 +50,17 @@
const loading = ref(false)
const errorMessage = ref('')
const results = ref<ContentSearchResult[]>([])
const listVersion = ref(0)
const scrollTop = ref(0)
const total = ref(0); const hasMore = ref(false); const loadingMore = ref(false); const moreError = ref('')
let page = 1
let requestVersion = 0
let visible = false
const resetResults = () => { requestVersion++; results.value = []; total.value = 0; hasMore.value = false; loadingMore.value = false; moreError.value = ''; searched.value = false; loading.value = false; errorMessage.value = ''; listVersion.value++ }
const resetScroll = () => {
// 保留滚动组件,避免H5返回页激活时重建导致框架读取空引用。
scrollTop.value = 1
nextTick(() => { scrollTop.value = 0 })
}
const resetResults = () => { requestVersion++; results.value = []; total.value = 0; hasMore.value = false; loadingMore.value = false; moreError.value = ''; searched.value = false; loading.value = false; errorMessage.value = ''; resetScroll() }
const clearSearch = () => { keyword.value = ''; resetResults() }
async function load(append: boolean) {
if (!visible) return
@@ -65,7 +70,7 @@
uni.hideKeyboard()
const currentRequest = append ? requestVersion : ++requestVersion
if (append) { loadingMore.value = true; moreError.value = '' }
else { searched.value = true; loading.value = true; loadingMore.value = false; errorMessage.value = ''; moreError.value = ''; results.value = []; total.value = 0; hasMore.value = false; page = 1; listVersion.value++ }
else { searched.value = true; loading.value = true; loadingMore.value = false; errorMessage.value = ''; moreError.value = ''; results.value = []; total.value = 0; hasMore.value = false; page = 1; resetScroll() }
try {
const result = await searchContent(keyword.value, activeType.value, append ? page + 1 : 1)
// 输入、切换分类或退出页面后,忽略旧请求,避免结果被覆盖。
+12 -8
View File
@@ -4,15 +4,19 @@ export type HelpQuestion = { id: string, category: string, question: string, ans
export const helpCategories: string[] = ['账户使用', '融资评估', '资料与隐私']
export async function fetchHelpQuestions(): Promise<HelpQuestion[]> {
const response = await requestJson('/help/faqs', 'GET', {})
const items = response['items'] as UTSJSONObject[] | null
if (items == null) throw new Error('帮助内容加载失败')
const items = response['items']
if (items == null || !Array.isArray(items)) throw new Error('帮助内容响应不完整,请重试')
const results: HelpQuestion[] = []
items.forEach((item) => {
const id = item['id'] as string ?? ''; const category = item['category'] as string ?? ''
const question = item['question'] as string ?? ''; const answer = item['answer'] as string ?? ''
if (id.length > 0 && helpCategories.includes(category) && question.length > 0 && answer.length > 0 && !results.some((entry: HelpQuestion): boolean => entry.id == id)) {
const result: HelpQuestion = { id, category, question, answer }; results.push(result)
}
;(items as any[]).forEach((value: any) => {
if (value == null || typeof value != 'object' || Array.isArray(value)) throw new Error('帮助内容响应不完整,请重试')
const item = value as UTSJSONObject
const id = item.getString('id'); const category = item.getString('category')
const question = item.getString('question'); const answer = item.getString('answer')
if (id == null || !/^[1-9][0-9]{0,9}$/.test(id) || parseFloat(id) > 2147483647
|| category == null || !helpCategories.includes(category) || question == null || question.trim().length == 0
|| answer == null || answer.trim().length == 0 || results.some((entry: HelpQuestion): boolean => entry.id == id))
throw new Error('帮助内容响应不完整,请重试')
const result: HelpQuestion = { id, category, question, answer }; results.push(result)
})
return results
}