feat: 接入内容服务端真实接口并优化页面交互

- 迁移内容数据到服务端接口,移除本地模拟数据
- 首页新增推荐课程加载与未读消息检查
- 资讯、课程、搜索页面支持分页加载与错误重试
- 登录页新增恢复课程、消息、帮助页的路由逻辑
- 评估分析页新增处理中状态动效与阶段提示
- API 环境切换到正式域名 airz.hqling.cloud
This commit is contained in:
hujun
2026-10-01 20:26:41 +08:00
parent 3b2a0139e4
commit 2a9ff403c4
41 changed files with 21742 additions and 427 deletions
+26 -10
View File
@@ -22,11 +22,13 @@
<view v-else-if="errorMessage.length > 0" class="state-panel"><text class="state-title">搜索暂时不可用</text><text class="state-copy">{{ errorMessage }}</text><button class="retry-button" @click="search">重新搜索</button></view>
<view v-else-if="results.length == 0" class="state-panel"><view class="empty-symbol"><image class="empty-icon" src="/static/icons/search.svg" mode="aspectFit" /></view><text class="state-title">没有找到相关内容</text><text class="state-copy">试试更简短的关键词,或切换搜索分类</text></view>
<view v-else>
<text class="result-count">找到 {{ results.length }} 条相关内容</text>
<text class="result-count">找到 {{ total }} 条相关内容</text>
<view class="result-card" v-for="item in results" :key="item.type + ':' + item.id" @click="openResult(item)">
<view class="result-copy"><view class="result-heading"><text :class="item.type == 'news' ? 'result-kind' : 'result-kind lesson-kind'">{{ item.type == 'news' ? '新闻' : '融资课堂' }}</text><text class="result-category">{{ item.category }}</text></view><text class="result-title">{{ item.title }}</text><text class="result-summary">{{ item.summary }}</text><text class="result-meta">{{ item.meta }}</text></view>
<image v-if="item.image.length > 0" class="result-image" :src="item.image" mode="aspectFill" />
</view>
<text v-if="moreError.length > 0" class="state-copy">{{ moreError }}</text>
<button v-if="hasMore" class="retry-button" @click="more">{{ loadingMore ? '正在加载…' : '加载更多' }}</button>
</view>
</view>
</scroll-view>
@@ -48,29 +50,43 @@
const errorMessage = ref('')
const results = ref<ContentSearchResult[]>([])
const listVersion = ref(0)
const total = ref(0); const hasMore = ref(false); const loadingMore = ref(false); const moreError = ref('')
let page = 1
let requestVersion = 0
const resetResults = () => { requestVersion++; results.value = []; searched.value = false; loading.value = false; errorMessage.value = ''; listVersion.value++ }
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 clearSearch = () => { keyword.value = ''; resetResults() }
const search = async () => {
async function load(append: boolean) {
if (append && (loading.value || loadingMore.value || !hasMore.value)) return
keyword.value = keyword.value.trim()
if (keyword.value.length == 0) { clearSearch(); return }
uni.hideKeyboard()
const currentRequest = ++requestVersion
searched.value = true; loading.value = true; errorMessage.value = ''; results.value = []; listVersion.value++
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++ }
try {
const items = await searchContent(keyword.value, activeType.value)
const result = await searchContent(keyword.value, activeType.value, append ? page + 1 : 1)
// 输入、切换分类或退出页面后,忽略旧请求,避免结果被覆盖。
if (currentRequest == requestVersion) results.value = items
} catch (_error) {
if (currentRequest == requestVersion) errorMessage.value = '请检查网络连接后重试'
} finally { if (currentRequest == requestVersion) loading.value = false }
if (currentRequest != requestVersion) return
results.value = append ? results.value.concat(result.items) : result.items
total.value = result.total; hasMore.value = result.hasMore
if (append) page++
} catch (err) {
if (currentRequest == requestVersion) {
const message = err instanceof Error ? err.message : '请检查网络连接后重试'
if (append) moreError.value = message; else errorMessage.value = message
}
} finally { if (currentRequest == requestVersion) { loading.value = false; loadingMore.value = false } }
}
const search = () => { load(false) }
const more = () => { load(true) }
const selectType = (type: string) => { if (activeType.value == type) return; activeType.value = type; if (searched.value) search() }
const searchWord = (word: string) => { keyword.value = word; search() }
const openResult = (item: ContentSearchResult) => {
const route = item.type == 'news' ? '/packages/content/article/article' : '/packages/content/lesson/lesson'
uni.navigateTo({ url: route + '?id=' + encodeURIComponent(item.id) })
}
onShow(() => { if (searched.value) search() })
onHide(() => { requestVersion++; loading.value = false; loadingMore.value = false })
onUnload(() => { requestVersion++ })
</script>