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
+55
View File
@@ -0,0 +1,55 @@
import { getSession, requestJson } from '@/services/api.uts'
export type UserMessage = { id: string, type: string, category: string, icon: string, title: string, content: string, time: string, isRead: boolean }
export type MessagePage = { items: UserMessage[], snapshotId: string, nextBeforeId: string, unreadCount: number }
export type MessageDetail = { item: UserMessage, targetUrl: string, targetLabel: string, targetUnavailable: boolean }
const validId = (value: string): boolean => /^[1-9][0-9]{0,9}$/.test(value)
const count = (data: UTSJSONObject): number => {
const value = data.getNumber('unreadCount')
if (value == null || !Number.isInteger(value) || value < 0) throw new Error('未读数量格式无效')
return value
}
function messageFrom(data: UTSJSONObject): UserMessage {
const id = data.getString('id') ?? ''; const type = data.getString('type') ?? ''
if (!validId(id) || !['system', 'service', 'activity'].includes(type)) throw new Error('消息格式无效')
const categories: UTSJSONObject = { system: '系统通知', service: '服务通知', activity: '活动消息' }
const icons: UTSJSONObject = { system: 'system-bell', service: 'approval', activity: 'gift' }
const date = new Date((data.getString('createdAt') ?? '').replace(' ', 'T'))
const now = new Date(); const pad = (n: number): string => n < 10 ? '0' + n.toString() : n.toString()
let time = '未记录'
if (!isNaN(date.getTime())) time = date.getFullYear() == now.getFullYear() && date.getMonth() == now.getMonth() && date.getDate() == now.getDate()
? pad(date.getHours()) + ':' + pad(date.getMinutes()) : date.getFullYear().toString() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate())
return { id, type, category: categories.getString(type) ?? '', icon: icons.getString(type) ?? '', title: data.getString('title') ?? '',
content: data.getString('content') ?? '', time, isRead: data.getBoolean('isRead') ?? false }
}
export async function fetchUnreadMessages(): Promise<number> {
if (getSession() == null) return 0
return count(await requestJson('/messages/unread-count', 'GET', {}))
}
export async function fetchMessages(type: string, beforeId: string = '', snapshotId: string = ''): Promise<MessagePage> {
const params: UTSJSONObject = { type, limit: 20 }
if (beforeId.length > 0) { params['beforeId'] = beforeId; params['snapshotId'] = snapshotId }
const data = await requestJson('/messages', 'GET', params)
const items = data['items'] as UTSJSONObject[]
return { items: items.map((item: UTSJSONObject): UserMessage => messageFrom(item)), snapshotId: data.getString('snapshotId') ?? '',
nextBeforeId: data.getString('nextBeforeId') ?? '', unreadCount: count(data) }
}
export async function fetchMessage(id: string): Promise<MessageDetail> {
if (!validId(id)) throw new Error('消息编号无效')
const data = await requestJson('/messages/' + id, 'GET', {})
const target = data['target'] as UTSJSONObject | null
let targetUrl = ''; let targetLabel = ''
if (target != null) {
const assessmentId = target.getString('assessmentId') ?? ''; const revisionId = target.getString('revisionId') ?? ''
const type = target.getString('type') ?? ''; const kind = target.getString('kind') ?? ''
if (!validId(assessmentId) || !validId(revisionId) || !['personal', 'enterprise'].includes(type)) throw new Error('关联评估格式无效')
// 只构造白名单路由,通知不能下发任意链接;历史失败也只打开原提交版本。
if (kind == 'report') { targetUrl = '/packages/finance/report/report?assessmentId=' + assessmentId + '&revisionId=' + revisionId; targetLabel = '查看报告' }
else if (kind == 'revision') { targetUrl = '/packages/finance/archive/archive?assessmentId=' + assessmentId + '&revisionId=' + revisionId + '&type=' + type; targetLabel = '查看评估' }
else throw new Error('关联评估格式无效')
}
return { item: messageFrom(data['item'] as UTSJSONObject), targetUrl, targetLabel, targetUnavailable: data.getBoolean('targetUnavailable') ?? false }
}
export async function readMessage(id: string): Promise<number> { return count(await requestJson('/messages/' + id + '/read', 'POST', {})) }
export async function readAllMessages(snapshotId: string): Promise<number> { return count(await requestJson('/messages/read-all', 'POST', { snapshotId })) }