完善预警和积分接口接入
This commit is contained in:
+96
-11
@@ -200,6 +200,14 @@ export type StudentGrowth = {
|
||||
records: GrowthPointRecord[]
|
||||
}
|
||||
|
||||
export type StudentPointRecordsPage = {
|
||||
records: GrowthPointRecord[]
|
||||
total: number
|
||||
pageIndex: number
|
||||
pageSize: number
|
||||
lastId: number | null
|
||||
}
|
||||
|
||||
export type LearningTaskItem = {
|
||||
id: string
|
||||
title: string
|
||||
@@ -244,6 +252,7 @@ export type StudentAlert = {
|
||||
content: string
|
||||
occurredAt: string
|
||||
isRead: boolean
|
||||
statusName?: string
|
||||
}
|
||||
|
||||
export type StudentAlertList = {
|
||||
@@ -272,6 +281,15 @@ export type StudentLearningWarningResult = {
|
||||
items: StudentLearningWarningItem[]
|
||||
}
|
||||
|
||||
const learningWarningTypeMap: { [key: number]: { title: string, level: StudentAlertLevel } } = {
|
||||
1: { title: '今日未打卡', level: 'high' },
|
||||
2: { title: '仅学无交', level: 'medium' },
|
||||
3: { title: '作业未完成', level: 'high' },
|
||||
4: { title: '攻克失败', level: 'critical' },
|
||||
5: { title: '连续 3 天未学习', level: 'high' },
|
||||
6: { title: '正确率明显下滑', level: 'medium' }
|
||||
}
|
||||
|
||||
// 验证学生二维码请求参数
|
||||
export type VerifyQRCodeParams = {
|
||||
qrCode: string
|
||||
@@ -372,21 +390,26 @@ export function apiGetStudentHomeMetrics(studentId: string, showLoading: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定学生今日学习预警,仅返回已触发的预警规则。
|
||||
* 获取指定学生今日学习预警,可选择仅返回已触发的预警规则。
|
||||
*/
|
||||
export function apiGetStudentLearningWarnings(studentId: string, showLoading: boolean = true): Promise<ApiResponse<StudentAlertList> | null> {
|
||||
export function apiGetStudentLearningWarnings(studentId: string, showLoading: boolean = true, onlyTriggered: boolean = true): Promise<ApiResponse<StudentAlertList> | null> {
|
||||
return get<any>(`/api/v1/ParentLearningWarning/GetTodayList?userId=${studentId}`, undefined, { showLoading }).then((res) => {
|
||||
if (!res || !res.data || !Array.isArray(res.data.items)) return null
|
||||
const list: StudentAlert[] = res.data.items
|
||||
.filter((item: any) => Boolean(item.isTriggered))
|
||||
.map((item: any, index: number) => ({
|
||||
if (!res || !res.data) return null
|
||||
const items = Array.isArray(res.data.items) ? res.data.items : []
|
||||
const list: StudentAlert[] = items
|
||||
.filter((item: any) => !onlyTriggered || Boolean(item.isTriggered))
|
||||
.map((item: any, index: number) => {
|
||||
const warningType = learningWarningTypeMap[Number(item.type)]
|
||||
return {
|
||||
id: `${studentId}-${item.type ?? index}`,
|
||||
level: 'high',
|
||||
title: item.title || '学习预警',
|
||||
level: warningType?.level ?? 'medium',
|
||||
title: item.title || warningType?.title || '学习预警',
|
||||
content: item.description || item.statusName || '',
|
||||
occurredAt: res.data.date || '',
|
||||
isRead: false
|
||||
}))
|
||||
isRead: !item.isTriggered,
|
||||
statusName: item.statusName || (item.isTriggered ? '已触发' : '正常')
|
||||
}
|
||||
})
|
||||
return {
|
||||
code: res.code,
|
||||
message: res.message,
|
||||
@@ -452,7 +475,69 @@ export function apiGetStudentVocabulary(studentId: string, showLoading: boolean
|
||||
}
|
||||
|
||||
export function apiGetStudentGrowth(studentId: string, showLoading: boolean = true): Promise<ApiResponse<StudentGrowth> | null> {
|
||||
return get<StudentGrowth>(`/api/parent/growth?studentId=${studentId}`, undefined, { showLoading })
|
||||
return get<any>(`/api/v1/StudentPoints/GetStudentPointSummary/${studentId}`, undefined, { showLoading }).then((res) => {
|
||||
if (!res || !res.data) return null
|
||||
return {
|
||||
code: res.code,
|
||||
message: res.message,
|
||||
data: {
|
||||
studentId: String(res.data.userId ?? studentId),
|
||||
studentName: '',
|
||||
studentGrade: '',
|
||||
studentClass: '',
|
||||
pointsBalance: Number(res.data.totalPoints) || 0,
|
||||
level: Number(res.data.level) || 0,
|
||||
rank: Number(res.data.classRank) || 0,
|
||||
streakDays: Number(res.data.noPointDays) || 0,
|
||||
totalPoints: Number(res.data.todayPoints) || 0,
|
||||
records: []
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页获取学生积分流水。
|
||||
*/
|
||||
export function apiGetStudentPointRecords(studentId: string, pageIndex: number = 1, pageSize: number = 20, prevPageId?: number, showLoading: boolean = true): Promise<ApiResponse<StudentPointRecordsPage> | null> {
|
||||
const prevPageQuery = prevPageId === undefined || prevPageId === null ? '' : `&PrevPageId=${prevPageId}`
|
||||
return get<any>(`/api/v1/StudentPoints/GetStudentPointRecords?UserId=${studentId}&PageIndex=${pageIndex}&PageSize=${pageSize}${prevPageQuery}`, undefined, { showLoading }).then((res) => {
|
||||
if (!res || !res.data) return null
|
||||
const page = res.data
|
||||
const records: GrowthPointRecord[] = (Array.isArray(page.data) ? page.data : []).map((item: any) => ({
|
||||
id: String(item.id),
|
||||
title: item.pointMessage || '积分变动',
|
||||
timeText: formatPointRecordTime(item.bizTime),
|
||||
points: Number(item.points) || 0,
|
||||
icon: pointRecordIcon(Number(item.type))
|
||||
}))
|
||||
return {
|
||||
code: res.code,
|
||||
message: res.message,
|
||||
data: {
|
||||
records,
|
||||
total: Number(page.total) || 0,
|
||||
pageIndex: Number(page.pageIndex) || pageIndex,
|
||||
pageSize: Number(page.pageSize) || pageSize,
|
||||
lastId: page.id === null || page.id === undefined ? null : Number(page.id)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function pointRecordIcon(type: number): GrowthPointRecord['icon'] {
|
||||
if (type === 1) return 'calendar'
|
||||
if (type === 2 || type === 3) return 'assignment'
|
||||
if (type === 4) return 'streak'
|
||||
if (type === 5 || type === 6) return 'accuracy'
|
||||
if (type === 7 || type === 8) return 'mastery'
|
||||
return 'note'
|
||||
}
|
||||
|
||||
function formatPointRecordTime(value: any): string {
|
||||
if (!value) return '--'
|
||||
const text = String(value)
|
||||
return text.length >= 16 ? text.substring(5, 16).replace('T', ' ') : text
|
||||
}
|
||||
|
||||
export function apiGetStudentLearningTasks(studentId: string, showLoading: boolean = true): Promise<ApiResponse<StudentLearningTasks> | null> {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<scroll-view class="content-scroll" direction="vertical" scroll-y="true">
|
||||
<view v-if="isLoading" class="status-panel"><text class="status-text">预警信息加载中</text></view>
|
||||
<view v-else-if="alerts.length === 0" class="status-panel"><text class="status-text">暂无预警信息</text></view>
|
||||
<view v-else v-for="alert in alerts" :key="alert.id" class="alert-card">
|
||||
<view class="alert-card-top"><view class="alert-level-dot" :class="levelDotClass(alert.level)"></view><text class="alert-title">{{ alert.title }}</text><text v-if="!alert.isRead" class="unread-mark">未读</text></view>
|
||||
<view v-else v-for="alert in alerts" :key="alert.id" class="alert-card">
|
||||
<view class="alert-card-top"><view class="alert-level-dot" :class="levelDotClass(alert.level)"></view><text class="alert-title">{{ alert.title }}</text><text class="alert-status" :class="alert.isRead ? 'alert-status-normal' : 'alert-status-triggered'">{{ alert.statusName }}</text></view>
|
||||
<text class="alert-content">{{ alert.content }}</text>
|
||||
<text class="alert-time">{{ formatTime(alert.occurredAt) }}</text>
|
||||
</view>
|
||||
@@ -14,7 +14,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { apiGetStudentAlerts, type StudentAlert, type StudentAlertLevel } from '../../api/student'
|
||||
import { apiGetStudentLearningWarnings, type StudentAlert, type StudentAlertLevel } from '../../api/student'
|
||||
import { homeStudentProfile } from '../../store/home'
|
||||
|
||||
const alerts = ref<StudentAlert[]>([])
|
||||
@@ -31,7 +31,7 @@
|
||||
isLoading.value = false
|
||||
return
|
||||
}
|
||||
const res = await apiGetStudentAlerts(studentId, 1, 20)
|
||||
const res = await apiGetStudentLearningWarnings(studentId, true, false)
|
||||
if (res && res.data) {
|
||||
alerts.value = res.data.list
|
||||
}
|
||||
@@ -62,7 +62,9 @@
|
||||
.alert-level-medium { background-color: #e8a129; }
|
||||
.alert-level-low { background-color: #4d87cf; }
|
||||
.alert-title { flex: 1; font-size: 15px; font-weight: 700; color: #30384a; }
|
||||
.unread-mark { padding: 3px 7px; border-radius: 8px; font-size: 10px; color: #554be9; background-color: #eeefff; }
|
||||
.alert-status { padding: 3px 7px; border-radius: 8px; font-size: 10px; }
|
||||
.alert-status-triggered { color: #e25a5a; background-color: #fff0f1; }
|
||||
.alert-status-normal { color: #16a979; background-color: #e8fbf3; }
|
||||
.alert-content { margin-top: 10px; font-size: 13px; line-height: 20px; color: #626c7e; }
|
||||
.alert-time { margin-top: 10px; font-size: 11px; color: #9aa2b1; }
|
||||
</style>
|
||||
|
||||
+43
-3
@@ -1,17 +1,23 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="header-panel"><view class="header-back" @tap="goHome"><UniIcons type="back" class="header-back-icon"></UniIcons></view><view class="header-copy"><text class="header-title">成长激励</text><text class="header-subtitle">{{ homeStudentProfile?.name ?? growth.studentName }} · {{ homeStudentProfile?.school ?? '' }} {{ homeStudentProfile?.className ?? growth.studentClass }}</text></view><view class="header-action"><UniIcons type="person-filled" class="header-action-icon"></UniIcons></view></view>
|
||||
<scroll-view class="content-scroll" direction="vertical" scroll-y="true"><view v-if="isLoading" class="status-panel"><text class="status-text">成长数据加载中</text></view><view v-else class="content-panel"><view class="points-card"><view class="points-heading"><view class="points-copy"><text class="points-label">积分余额</text><text class="points-value">{{ growth.pointsBalance }}</text></view><view class="level-badge"><text class="level-label">等级</text><text class="level-value">Lv.{{ growth.level }}</text></view></view><view class="points-stats"><view class="points-stat"><text class="points-stat-value">{{ growth.rank }}</text><text class="points-stat-label">班级排名</text></view><view class="points-stat points-stat-middle"><text class="points-stat-value">{{ growth.streakDays }}</text><text class="points-stat-label">未能连续</text></view><view class="points-stat"><text class="points-stat-value">{{ growth.totalPoints }}</text><text class="points-stat-label">获得总量</text></view></view></view><view class="flow-card"><text class="section-title">积分流水</text><text class="section-description">记录孩子每天做了哪些正向行为</text><view class="flow-list"><view v-for="record in growth.records" :key="record.id" class="flow-item"><view class="flow-icon" :class="'flow-icon-' + record.icon"><UniIcons :type="record.icon === 'calendar' ? 'calendar' : record.icon === 'assignment' ? 'checkbox' : record.icon === 'streak' ? 'fire' : record.icon === 'accuracy' ? 'circle-filled' : record.icon === 'mastery' ? 'star-filled' : 'compose'" class="flow-icon-glyph"></UniIcons></view><view class="flow-copy"><text class="flow-title">{{ record.title }}</text><text class="flow-time">{{ record.timeText }}</text></view><text class="flow-points">+{{ record.points }}</text></view></view></view></view></scroll-view>
|
||||
<scroll-view class="content-scroll" direction="vertical" scroll-y="true" refresher-enabled="true" :refresher-triggered="isRefreshing" @refresherrefresh="refreshGrowth" @scrolltolower="loadMoreRecords"><view v-if="isLoading" class="status-panel"><text class="status-text">成长数据加载中</text></view><view v-else class="content-panel"><view class="points-card"><view class="points-heading"><view class="points-copy"><text class="points-label">总积分</text><text class="points-value">{{ growth.pointsBalance }}</text></view><view class="level-badge"><text class="level-label">等级</text><text class="level-value">Lv.{{ growth.level }}</text></view></view><view class="points-stats"><view class="points-stat"><text class="points-stat-value">{{ growth.rank }}</text><text class="points-stat-label">班级排名</text></view><view class="points-stat points-stat-middle"><text class="points-stat-value">{{ growth.streakDays }}</text><text class="points-stat-label">未获积分天数</text></view><view class="points-stat"><text class="points-stat-value">{{ growth.totalPoints }}</text><text class="points-stat-label">今日获得</text></view></view></view><view class="flow-card"><text class="section-title">积分流水</text><text class="section-description">记录孩子每天做了哪些正向行为</text><view v-if="growth.records.length === 0" class="flow-empty"><text class="flow-empty-text">暂无积分流水</text></view><view v-else class="flow-list"><view v-for="record in growth.records" :key="record.id" class="flow-item"><view class="flow-icon" :class="'flow-icon-' + record.icon"><UniIcons :type="record.icon === 'calendar' ? 'calendar' : record.icon === 'assignment' ? 'checkbox' : record.icon === 'streak' ? 'fire' : record.icon === 'accuracy' ? 'circle-filled' : record.icon === 'mastery' ? 'star-filled' : 'compose'" class="flow-icon-glyph"></UniIcons></view><view class="flow-copy"><text class="flow-title">{{ record.title }}</text><text class="flow-time">{{ record.timeText }}</text></view><text class="flow-points">{{ record.points >= 0 ? '+' : '' }}{{ record.points }}</text></view></view><view v-if="isLoadingMore" class="records-footer"><text class="records-footer-text">加载更多...</text></view><view v-else-if="!hasMoreRecords && growth.records.length > 0" class="records-footer"><text class="records-footer-text">没有更多记录</text></view></view></view></scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import UniIcons from '../../uni_modules/uni-icons-x/components/uni-icons/uni-icons.uvue'
|
||||
import { apiGetStudentGrowth, apiGetStudentHomeProfile, type StudentGrowth } from '../../api/student'
|
||||
import { homeStudentProfile, setHomeStudentProfile } from '../../store/home'
|
||||
import { apiGetStudentGrowth, apiGetStudentHomeProfile, apiGetStudentPointRecords, type StudentGrowth } from '../../api/student'
|
||||
import { homeStudentProfile, setHomeStudentProfile } from '../../store/home'
|
||||
|
||||
const growth = ref<StudentGrowth>({ studentId: '', studentName: '', studentGrade: '', studentClass: '', pointsBalance: 0, level: 0, rank: 0, streakDays: 0, totalPoints: 0, records: [] })
|
||||
const isLoading = ref(true)
|
||||
const isRefreshing = ref(false)
|
||||
const isLoadingMore = ref(false)
|
||||
const hasMoreRecords = ref(true)
|
||||
const recordsPageIndex = ref(1)
|
||||
const recordsLastId = ref<number | null>(null)
|
||||
const currentStudentId = ref('')
|
||||
|
||||
onLoad(() => { loadGrowth() })
|
||||
|
||||
@@ -25,12 +31,44 @@
|
||||
}
|
||||
}
|
||||
if (studentId) {
|
||||
currentStudentId.value = studentId
|
||||
const res = await apiGetStudentGrowth(studentId)
|
||||
if (res && res.data) growth.value = res.data
|
||||
await loadPointRecords(false)
|
||||
}
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
async function loadPointRecords(append: boolean) {
|
||||
if (!currentStudentId.value || isLoadingMore.value) return
|
||||
if (append && !hasMoreRecords.value) return
|
||||
if (append) isLoadingMore.value = true
|
||||
const pageIndex = append ? recordsPageIndex.value + 1 : 1
|
||||
const res = await apiGetStudentPointRecords(currentStudentId.value, pageIndex, 20, append ? recordsLastId.value ?? undefined : undefined, !append)
|
||||
if (res && res.data) {
|
||||
const records = append ? growth.value.records.concat(res.data.records) : res.data.records
|
||||
growth.value = { ...growth.value, records }
|
||||
recordsPageIndex.value = res.data.pageIndex
|
||||
recordsLastId.value = res.data.lastId
|
||||
hasMoreRecords.value = records.length < res.data.total && res.data.records.length > 0
|
||||
}
|
||||
isLoadingMore.value = false
|
||||
}
|
||||
|
||||
async function loadMoreRecords() {
|
||||
await loadPointRecords(true)
|
||||
}
|
||||
|
||||
async function refreshGrowth() {
|
||||
if (isRefreshing.value) return
|
||||
isRefreshing.value = true
|
||||
hasMoreRecords.value = true
|
||||
recordsPageIndex.value = 1
|
||||
recordsLastId.value = null
|
||||
await loadGrowth()
|
||||
isRefreshing.value = false
|
||||
}
|
||||
|
||||
function goHome() { uni.reLaunch({ url: '/pages/index/index' }) }
|
||||
</script>
|
||||
|
||||
@@ -72,4 +110,6 @@
|
||||
.flow-title { font-size: 13px; color: #242b3d; }
|
||||
.flow-time { margin-top: 3px; font-size: 10px; color: #94a0b1; }
|
||||
.flow-points { font-size: 13px; font-weight: 700; color: #f29900; }
|
||||
.flow-empty, .records-footer { display: flex; flex-direction: column; align-items: center; padding: 14px 0 4px; }
|
||||
.flow-empty-text, .records-footer-text { font-size: 11px; color: #94a0b1; }
|
||||
</style>
|
||||
|
||||
@@ -209,10 +209,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
if (student.value.id) {
|
||||
if (selectedId) {
|
||||
await Promise.all([
|
||||
loadStudentHomeMetrics(selectedId || student.value.id, showLoading),
|
||||
loadStudentAlerts(selectedId || student.value.id, showLoading)
|
||||
loadStudentHomeMetrics(selectedId, showLoading),
|
||||
loadStudentAlerts(selectedId, showLoading)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
+23
-366
@@ -3,14 +3,12 @@
|
||||
* 支持请求拦截、响应拦截、Loading显示、错误处理
|
||||
*/
|
||||
|
||||
// 响应数据类型
|
||||
export type ApiResponse<T> = {
|
||||
code: number
|
||||
message: string
|
||||
data: T | null
|
||||
}
|
||||
|
||||
// 请求配置类型
|
||||
export type RequestConfig = {
|
||||
url: string
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||||
@@ -21,37 +19,17 @@ export type RequestConfig = {
|
||||
showError?: boolean
|
||||
}
|
||||
|
||||
// 基础URL配置 - 虚拟接口
|
||||
const BASE_URL = 'https://localhost:7145'
|
||||
|
||||
// 请求超时时间
|
||||
const TIMEOUT = 10000
|
||||
|
||||
// 联调前统一使用测试响应,保证所有页面流程可完整验证。
|
||||
const USE_MOCK_API = true
|
||||
|
||||
// Mock 接口模拟正常网络环境,响应延迟范围为 0 到 2 秒。
|
||||
const MOCK_RESPONSE_MAX_DELAY = 2000
|
||||
|
||||
// Loading引用计数
|
||||
let loadingCount = 0
|
||||
|
||||
/**
|
||||
* 显示Loading
|
||||
*/
|
||||
function showLoading(text: string = '加载中...') {
|
||||
loadingCount++
|
||||
if (loadingCount === 1) {
|
||||
uni.showLoading({
|
||||
title: text,
|
||||
mask: true
|
||||
})
|
||||
uni.showLoading({ title: text, mask: true })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏Loading
|
||||
*/
|
||||
function hideLoading() {
|
||||
loadingCount--
|
||||
if (loadingCount <= 0) {
|
||||
@@ -60,413 +38,92 @@ function hideLoading() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Token
|
||||
*/
|
||||
function getToken(): string {
|
||||
return uni.getStorageSync('token') as string ?? ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求拦截器 - 添加token等
|
||||
*/
|
||||
function requestInterceptor(config: RequestConfig): RequestConfig {
|
||||
// 添加完整URL
|
||||
if (!config.url.startsWith('http')) {
|
||||
config.url = BASE_URL + config.url
|
||||
}
|
||||
|
||||
// 添加请求头
|
||||
if (!config.url.startsWith('http')) config.url = BASE_URL + config.url
|
||||
|
||||
const token = getToken()
|
||||
config.header = config.header ?? {}
|
||||
config.header['Content-Type'] = 'application/json'
|
||||
|
||||
if (token) {
|
||||
config.header['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
|
||||
// 显示Loading
|
||||
if (config.showLoading !== false) {
|
||||
showLoading(config.loadingText)
|
||||
}
|
||||
|
||||
if (token) config.header['Authorization'] = `Bearer ${token}`
|
||||
|
||||
if (config.showLoading !== false) showLoading(config.loadingText)
|
||||
return config
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应拦截器 - 统一处理响应
|
||||
*/
|
||||
function responseInterceptor<T>(response: UniNamespace.RequestSuccessCallbackResult, config: RequestConfig): ApiResponse<T> | null {
|
||||
// 隐藏Loading
|
||||
if (config.showLoading !== false) {
|
||||
hideLoading()
|
||||
}
|
||||
|
||||
// HTTP状态码检查
|
||||
if (config.showLoading !== false) hideLoading()
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
handleError(response.statusCode, '网络请求失败', config.showError)
|
||||
return null
|
||||
}
|
||||
|
||||
// 解析响应数据
|
||||
|
||||
const result = response.data as ApiResponse<T>
|
||||
|
||||
// 业务状态码检查
|
||||
if (result.code !== 200) {
|
||||
handleError(result.code, result.message, config.showError)
|
||||
return null
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误处理
|
||||
*/
|
||||
function handleError(code: number, message: string, showError: boolean = true) {
|
||||
console.error(`[Request Error] Code: ${code}, Message: ${message}`)
|
||||
|
||||
if (!showError) {
|
||||
return
|
||||
}
|
||||
|
||||
// 根据错误码显示不同提示
|
||||
if (!showError) return
|
||||
|
||||
let errorMsg = message
|
||||
|
||||
if (code === 401) {
|
||||
errorMsg = '登录已过期,请重新登录'
|
||||
// 清除token并跳转登录页
|
||||
uni.removeStorageSync('token')
|
||||
uni.removeStorageSync('userInfo')
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index'
|
||||
})
|
||||
uni.reLaunch({ url: '/pages/login/index' })
|
||||
} else if (code === 403) {
|
||||
errorMsg = '没有权限访问'
|
||||
} else if (code === 404) {
|
||||
errorMsg = '请求的资源不存在'
|
||||
} else if (code === 500) {
|
||||
errorMsg = '服务器错误,请稍后重试'
|
||||
} else if (code === -1) {
|
||||
errorMsg = '网络异常'
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: errorMsg,
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
|
||||
uni.showToast({ title: errorMsg, icon: 'none', duration: 2000 })
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据接口生成页面流程所需的测试数据。
|
||||
*/
|
||||
function createMockResponse<T>(config: RequestConfig): ApiResponse<T> {
|
||||
const requestData = (config.data ?? {}) as Record<string, any>
|
||||
let data: any = null
|
||||
|
||||
if (
|
||||
config.url.includes('/api/auth/login') ||
|
||||
config.url.includes('/api/auth/login-by-code') ||
|
||||
config.url.includes('/api/auth/register')
|
||||
) {
|
||||
data = {
|
||||
userId: 'test-parent-001',
|
||||
phone: requestData.phone ?? '13800000000',
|
||||
nickname: requestData.parentName ?? '测试家长',
|
||||
avatar: '',
|
||||
token: 'test-token',
|
||||
refreshToken: 'test-refresh-token'
|
||||
}
|
||||
} else if (config.url.includes('/api/auth/send-code')) {
|
||||
data = { expireTime: 300 }
|
||||
} else if (config.url.includes('/api/auth/forgot-password/verify')) {
|
||||
data = { token: 'test-reset-token' }
|
||||
} else if (config.url.includes('/api/auth/refresh-token')) {
|
||||
data = { token: 'test-token', refreshToken: 'test-refresh-token' }
|
||||
} else if (
|
||||
config.url.includes('/api/student/verify-qrcode') ||
|
||||
config.url.includes('/api/student/verify-account')
|
||||
) {
|
||||
const studentAccount = requestData.account ?? 'test-student'
|
||||
data = {
|
||||
id: `test-student-${studentAccount}`,
|
||||
name: requestData.name ?? '测试学生',
|
||||
account: studentAccount,
|
||||
avatar: '',
|
||||
grade: '三年级',
|
||||
class: '一班'
|
||||
}
|
||||
} else if (config.url.includes('/api/parent/check-phone')) {
|
||||
data = { exists: false }
|
||||
} else if (config.url.includes('/api/parent/students')) {
|
||||
data = {
|
||||
currentStudentId: 'test-student-001',
|
||||
students: [
|
||||
{ id: 'test-student-001', name: '张一诺', account: '20241001', grade: '初二', school: '实验中学', avatar: '' },
|
||||
{ id: 'test-student-002', name: '张一鸣', account: '20241002', grade: '五年级', school: '实验小学', avatar: '' },
|
||||
{ id: 'test-student-003', name: '张一宁', account: '20241003', grade: '高二', school: '实验中学', avatar: '' }
|
||||
]
|
||||
}
|
||||
} else if (config.url.includes('/api/parent/home-metrics')) {
|
||||
const isSecondStudent = config.url.includes('studentId=test-student-002')
|
||||
data = isSecondStudent ? {
|
||||
studentId: 'test-student-002', onlineToday: true, checkedInToday: false, learningMinutesToday: 78,
|
||||
accuracyToday: 88, learningDaysThisWeek: 4, shouldStudyDaysThisWeek: 5, pendingHomeworkCount: 2, updatedAt: '2026-10-28T08:30:00+08:00'
|
||||
} : {
|
||||
studentId: 'test-student-001', onlineToday: true, checkedInToday: true, learningMinutesToday: 155,
|
||||
accuracyToday: 92, learningDaysThisWeek: 5, shouldStudyDaysThisWeek: 5, pendingHomeworkCount: 1, updatedAt: '2026-10-28T08:30:00+08:00'
|
||||
}
|
||||
} else if (config.url.includes('/api/parent/today-learning')) {
|
||||
const isSecondStudent = config.url.includes('studentId=test-student-002')
|
||||
data = isSecondStudent ? {
|
||||
studentId: 'test-student-002', studentName: '张一鸣', dateText: '10月13日 周一', checkedIn: false,
|
||||
learningMinutes: 78, answerMinutes: 56, answerCount: 42, completedPackages: 3, totalPackages: 6, masteredPoints: 8,
|
||||
score: { accuracy: 84, yesterdayAccuracy: 81, change: 3 }, pointsTotal: 28,
|
||||
pointDetails: [
|
||||
{ id: 'point-201', label: '每日登录', points: 10 },
|
||||
{ id: 'point-202', label: '每日提交作业', points: 8 },
|
||||
{ id: 'point-203', label: '连续打卡', points: 6 },
|
||||
{ id: 'point-204', label: '攻克知识点', points: 4 }
|
||||
],
|
||||
courses: [
|
||||
{ id: 'course-201', time: '08:30', subject: '语文', content: '古诗词诵读与默写', checkedIn: true },
|
||||
{ id: 'course-202', time: '10:00', subject: '数学', content: '分数应用题练习', checkedIn: true },
|
||||
{ id: 'course-203', time: '15:30', subject: '英语', content: 'Unit 4 单词听写', checkedIn: false }
|
||||
]
|
||||
} : {
|
||||
studentId: 'test-student-001', studentName: '张一诺', dateText: '10月13日 周一', checkedIn: true,
|
||||
learningMinutes: 155, answerMinutes: 118, answerCount: 68, completedPackages: 5, totalPackages: 6, masteredPoints: 12,
|
||||
score: { accuracy: 89, yesterdayAccuracy: 85, change: 4 }, pointsTotal: 42,
|
||||
pointDetails: [
|
||||
{ id: 'point-101', label: '每日登录', points: 10 },
|
||||
{ id: 'point-102', label: '每日提交作业', points: 12 },
|
||||
{ id: 'point-103', label: '连续打卡', points: 8 },
|
||||
{ id: 'point-104', label: '攻克知识点', points: 12 }
|
||||
],
|
||||
courses: [
|
||||
{ id: 'course-101', time: '08:00', subject: '语文', content: '《背影》精读与批注', checkedIn: true },
|
||||
{ id: 'course-102', time: '10:20', subject: '数学', content: '一元二次方程专项训练', checkedIn: true },
|
||||
{ id: 'course-103', time: '14:00', subject: '英语', content: 'Unit 5 单词听写与跟读', checkedIn: true },
|
||||
{ id: 'course-104', time: '19:30', subject: '物理', content: '声现象章节测试', checkedIn: false }
|
||||
]
|
||||
}
|
||||
} else if (config.url.includes('/api/parent/vocabulary')) {
|
||||
const isSecondStudent = config.url.includes('studentId=test-student-002')
|
||||
data = isSecondStudent ? {
|
||||
studentId: 'test-student-002', studentName: '张一鸣', studentGrade: '五年级', studentClass: '二班', bookCount: 4,
|
||||
books: [
|
||||
{ id: 'vocab-201', name: '人教版八年级上册', subtitle: '总词量 620 词', totalWords: 620, learnedWords: 420, status: 'learning', statusText: '在学', levelText: '初中同步', color: 'green' },
|
||||
{ id: 'vocab-202', name: '中考核心词汇 1600', subtitle: '总词量 1600 词', totalWords: 1600, learnedWords: 280, status: 'unfinished', statusText: '未学完', levelText: '中考冲刺', color: 'yellow' },
|
||||
{ id: 'vocab-203', name: '人教版三年级起点·五年级上', subtitle: '总词量 380 词', totalWords: 380, learnedWords: 380, status: 'completed', statusText: '已学完', levelText: '小学基础', color: 'blue' },
|
||||
{ id: 'vocab-204', name: '高中必修 5 核心词汇', subtitle: '总词量 1080 词', totalWords: 1080, learnedWords: 0, status: 'not_started', statusText: '未开始', levelText: '高中同步', color: 'gray' }
|
||||
]
|
||||
} : {
|
||||
studentId: 'test-student-001', studentName: '张一诺', studentGrade: '初二', studentClass: '3班', bookCount: 4,
|
||||
books: [
|
||||
{ id: 'vocab-101', name: '人教版八年级上册', subtitle: '总词量 620 词', totalWords: 620, learnedWords: 520, status: 'learning', statusText: '在学', levelText: '初中同步', color: 'green' },
|
||||
{ id: 'vocab-102', name: '中考核心词汇 1600', subtitle: '总词量 1600 词', totalWords: 1600, learnedWords: 860, status: 'unfinished', statusText: '未学完', levelText: '中考冲刺', color: 'yellow' },
|
||||
{ id: 'vocab-103', name: '人教版三年级起点·五年级上', subtitle: '总词量 380 词', totalWords: 380, learnedWords: 380, status: 'completed', statusText: '已学完', levelText: '小学基础', color: 'blue' },
|
||||
{ id: 'vocab-104', name: '高中必修 5 核心词汇', subtitle: '总词量 1080 词', totalWords: 1080, learnedWords: 0, status: 'not_started', statusText: '未开始', levelText: '高中同步', color: 'gray' }
|
||||
]
|
||||
}
|
||||
} else if (config.url.includes('/api/parent/learning-tasks')) {
|
||||
const isSecondStudent = config.url.includes('studentId=test-student-002')
|
||||
data = isSecondStudent ? {
|
||||
studentId: 'test-student-002', studentName: '张一鸣', studentGrade: '五年级', studentClass: '二班', pendingCount: 2, completedCount: 3, failedCount: 0,
|
||||
groups: [
|
||||
{ id: 'task-group-201', subject: '生物', unit: '预习', lesson: '第 3 课节', startText: '今天 14:00', endText: '14:40 开课', knowledgeCount: 0, note: '学生尚未进入课程,暂无学习句与知识点,进入课程后将自动生成', tasks: [] },
|
||||
{ id: 'task-group-202', subject: '物理', unit: '复习', lesson: '第 4 课节', startText: '今天 16:00', endText: '16:40 开课', knowledgeCount: 3, note: '', tasks: [{ id: 'task-201', title: '声音的特性', status: 'success', statusText: '攻克成功', accuracyText: '正确率 92%', answerText: '答题时长 6 分钟', deadlineText: '今天 16:35 完成' }, { id: 'task-202', title: '回声与测距', status: 'in_progress', statusText: '攻克中', accuracyText: '正确率 78%', answerText: '答题时长 12 分钟', deadlineText: '今晚 21:00 截止' }, { id: 'task-203', title: '噪声的防治', status: 'failed', statusText: '攻克失败', accuracyText: '', answerText: '', deadlineText: '' }] }
|
||||
]
|
||||
} : {
|
||||
studentId: 'test-student-001', studentName: '张一诺', studentGrade: '初二', studentClass: '3班', pendingCount: 3, completedCount: 5, failedCount: 1,
|
||||
groups: [{ id: 'task-group-101', subject: '生物', unit: '预习', lesson: '第 3 课节', startText: '今天 14:00', endText: '14:40 开课', knowledgeCount: 0, note: '学生尚未进入课程,暂无学习句与知识点,进入课程后将自动生成', tasks: [] }, { id: 'task-group-102', subject: '物理', unit: '复习', lesson: '第 4 课节', startText: '今天 16:00', endText: '16:40 开课', knowledgeCount: 3, note: '', tasks: [{ id: 'task-101', title: '声音的特性', status: 'success', statusText: '攻克成功', accuracyText: '正确率 92%', answerText: '答题时长 6 分钟', deadlineText: '今天 16:35 完成' }, { id: 'task-102', title: '回声与测距', status: 'in_progress', statusText: '攻克中', accuracyText: '正确率 78%', answerText: '答题时长 12 分钟', deadlineText: '今晚 21:00 截止' }, { id: 'task-103', title: '噪声的防治', status: 'failed', statusText: '攻克失败', accuracyText: '', answerText: '', deadlineText: '' }] }]
|
||||
}
|
||||
} else if (config.url.includes('/api/parent/growth')) {
|
||||
const isSecondStudent = config.url.includes('studentId=test-student-002')
|
||||
data = isSecondStudent ? {
|
||||
studentId: 'test-student-002', studentName: '张一鸣', studentGrade: '五年级', studentClass: '二班', pointsBalance: 862, level: 4, rank: 128, streakDays: 3, totalPoints: 24,
|
||||
records: [
|
||||
{ id: 'growth-201', title: '每日登录', timeText: '今天 07:35', points: 10, icon: 'calendar' },
|
||||
{ id: 'growth-202', title: '每日提交作业', timeText: '今天 19:22', points: 8, icon: 'assignment' },
|
||||
{ id: 'growth-203', title: '连续打卡', timeText: '今天 07:40', points: 6, icon: 'streak' },
|
||||
{ id: 'growth-204', title: '正确率≥80%', timeText: '昨天 20:05', points: 10, icon: 'accuracy' },
|
||||
{ id: 'growth-205', title: '攻克知识点', timeText: '昨天 19:40', points: 8, icon: 'mastery' },
|
||||
{ id: 'growth-206', title: '上传笔记', timeText: '10月12日 18:20', points: 5, icon: 'note' }
|
||||
]
|
||||
} : {
|
||||
studentId: 'test-student-001', studentName: '张一诺', studentGrade: '初二', studentClass: '3班', pointsBalance: 1268, level: 7, rank: 86, streakDays: 3, totalPoints: 42,
|
||||
records: [
|
||||
{ id: 'growth-101', title: '每日登录', timeText: '今天 07:35', points: 10, icon: 'calendar' },
|
||||
{ id: 'growth-102', title: '每日提交作业', timeText: '今天 19:22', points: 12, icon: 'assignment' },
|
||||
{ id: 'growth-103', title: '连续打卡', timeText: '今天 07:40', points: 8, icon: 'streak' },
|
||||
{ id: 'growth-104', title: '正确率≥80%', timeText: '昨天 20:05', points: 10, icon: 'accuracy' },
|
||||
{ id: 'growth-105', title: '攻克知识点', timeText: '昨天 19:40', points: 12, icon: 'mastery' },
|
||||
{ id: 'growth-106', title: '上传笔记', timeText: '10月12日 18:20', points: 5, icon: 'note' }
|
||||
]
|
||||
}
|
||||
} else if (config.url.includes('/api/parent/study-report')) {
|
||||
const isSecondStudent = config.url.includes('studentId=test-student-002')
|
||||
const subjects = [
|
||||
{ id: 'all', name: '全部科目' }, { id: 'math', name: '数学' }, { id: 'chinese', name: '语文' },
|
||||
{ id: 'english', name: '英语' }, { id: 'physics', name: '物理' }
|
||||
]
|
||||
const studentOneWeekActivity = { summary: '周六学习最投入,周四效率最高。', activeDays: 6, totalDurationText: '14小时20分', points: [{ label: '周一', studyMinutes: 0, plannedMinutes: 120, newMasteredCount: 0, studyHeight: 0, plannedHeight: 49, newMasteredHeight: 0 }, { label: '周二', studyMinutes: 0, plannedMinutes: 95, newMasteredCount: 0, studyHeight: 0, plannedHeight: 39, newMasteredHeight: 0 }, { label: '周三', studyMinutes: 142, plannedMinutes: 120, newMasteredCount: 2, studyHeight: 58, plannedHeight: 49, newMasteredHeight: 34 }, { label: '周四', studyMinutes: 88, plannedMinutes: 88, newMasteredCount: 2, studyHeight: 36, plannedHeight: 36, newMasteredHeight: 34 }, { label: '周五', studyMinutes: 0, plannedMinutes: 148, newMasteredCount: 0, studyHeight: 0, plannedHeight: 61, newMasteredHeight: 0 }, { label: '周六', studyMinutes: 178, plannedMinutes: 166, newMasteredCount: 1, studyHeight: 74, plannedHeight: 68, newMasteredHeight: 17 }, { label: '周日', studyMinutes: 0, plannedMinutes: 152, newMasteredCount: 0, studyHeight: 0, plannedHeight: 62, newMasteredHeight: 0 }] }
|
||||
const studentOneMonthActivity = { summary: '本月学习节奏稳定,周末自主学习投入更高。', activeDays: 24, totalDurationText: '52小时40分', points: [{ label: '第1周', studyMinutes: 610, plannedMinutes: 650, newMasteredCount: 3, studyHeight: 52, plannedHeight: 56, newMasteredHeight: 24 }, { label: '第2周', studyMinutes: 690, plannedMinutes: 660, newMasteredCount: 4, studyHeight: 60, plannedHeight: 57, newMasteredHeight: 33 }, { label: '第3周', studyMinutes: 720, plannedMinutes: 680, newMasteredCount: 4, studyHeight: 64, plannedHeight: 59, newMasteredHeight: 33 }, { label: '第4周', studyMinutes: 780, plannedMinutes: 700, newMasteredCount: 3, studyHeight: 72, plannedHeight: 61, newMasteredHeight: 24 }] }
|
||||
const studentOneWeekMasteries = [{ subjectName: '数学', masteredCount: 38, totalCount: 40, masteredPercent: 95 }, { subjectName: '语文', masteredCount: 26, totalCount: 28, masteredPercent: 93 }, { subjectName: '英语', masteredCount: 18, totalCount: 33, masteredPercent: 55 }, { subjectName: '物理', masteredCount: 9, totalCount: 18, masteredPercent: 50 }]
|
||||
const studentOneMonthMasteries = [{ subjectName: '数学', masteredCount: 112, totalCount: 120, masteredPercent: 93 }, { subjectName: '语文', masteredCount: 84, totalCount: 92, masteredPercent: 91 }, { subjectName: '英语', masteredCount: 68, totalCount: 96, masteredPercent: 71 }, { subjectName: '物理', masteredCount: 42, totalCount: 68, masteredPercent: 62 }]
|
||||
const studentTwoWeekActivity = { summary: '本周学习节奏平稳,周末阅读投入较多。', activeDays: 5, totalDurationText: '11小时10分', points: [{ label: '周一', studyMinutes: 82, plannedMinutes: 100, newMasteredCount: 1, studyHeight: 34, plannedHeight: 42, newMasteredHeight: 17 }, { label: '周二', studyMinutes: 0, plannedMinutes: 92, newMasteredCount: 0, studyHeight: 0, plannedHeight: 38, newMasteredHeight: 0 }, { label: '周三', studyMinutes: 110, plannedMinutes: 105, newMasteredCount: 1, studyHeight: 46, plannedHeight: 43, newMasteredHeight: 17 }, { label: '周四', studyMinutes: 96, plannedMinutes: 100, newMasteredCount: 1, studyHeight: 40, plannedHeight: 42, newMasteredHeight: 17 }, { label: '周五', studyMinutes: 0, plannedMinutes: 110, newMasteredCount: 0, studyHeight: 0, plannedHeight: 46, newMasteredHeight: 0 }, { label: '周六', studyMinutes: 152, plannedMinutes: 128, newMasteredCount: 2, studyHeight: 63, plannedHeight: 53, newMasteredHeight: 34 }, { label: '周日', studyMinutes: 130, plannedMinutes: 126, newMasteredCount: 1, studyHeight: 54, plannedHeight: 52, newMasteredHeight: 17 }] }
|
||||
const studentTwoMonthActivity = { summary: '本月完成大部分学习计划,建议保持阅读习惯。', activeDays: 21, totalDurationText: '45小时30分', points: [{ label: '第1周', studyMinutes: 520, plannedMinutes: 600, newMasteredCount: 2, studyHeight: 44, plannedHeight: 51, newMasteredHeight: 17 }, { label: '第2周', studyMinutes: 590, plannedMinutes: 610, newMasteredCount: 3, studyHeight: 50, plannedHeight: 52, newMasteredHeight: 24 }, { label: '第3周', studyMinutes: 640, plannedMinutes: 620, newMasteredCount: 2, studyHeight: 55, plannedHeight: 53, newMasteredHeight: 17 }, { label: '第4周', studyMinutes: 670, plannedMinutes: 640, newMasteredCount: 3, studyHeight: 58, plannedHeight: 55, newMasteredHeight: 24 }] }
|
||||
const studentTwoWeekMasteries = [{ subjectName: '数学', masteredCount: 32, totalCount: 40, masteredPercent: 80 }, { subjectName: '语文', masteredCount: 24, totalCount: 28, masteredPercent: 86 }, { subjectName: '英语', masteredCount: 16, totalCount: 30, masteredPercent: 53 }, { subjectName: '科学', masteredCount: 12, totalCount: 22, masteredPercent: 55 }]
|
||||
const studentTwoMonthMasteries = [{ subjectName: '数学', masteredCount: 92, totalCount: 120, masteredPercent: 77 }, { subjectName: '语文', masteredCount: 76, totalCount: 92, masteredPercent: 83 }, { subjectName: '英语', masteredCount: 58, totalCount: 90, masteredPercent: 64 }, { subjectName: '科学', masteredCount: 38, totalCount: 68, masteredPercent: 56 }]
|
||||
data = isSecondStudent ? {
|
||||
studentId: 'test-student-002', studentName: '张一鸣', studentGrade: '五年级', studentClass: '二班', updatedAtText: '10月13日 08:30', subjects,
|
||||
week: {
|
||||
rangeText: '10月7日 - 10月13日', statusTitle: '本周学习状态良好', statusDescription: '本周学习完成度稳定,建议保持每天的阅读与口算练习。',
|
||||
newMasteredPoints: 2, consecutiveDays: 8, averageAccuracy: 86, scaleLabels: ['120个', '116个', '112个', '108个'],
|
||||
trends: [
|
||||
{ subjectId: 'all', points: [{ label: '周一', value: 109, average: 110, valueHeight: 24, averageHeight: 30 }, { label: '周二', value: 110, average: 111, valueHeight: 30, averageHeight: 35 }, { label: '周三', value: 111, average: 111, valueHeight: 36, averageHeight: 35 }, { label: '周四', value: 112, average: 112, valueHeight: 42, averageHeight: 42 }, { label: '周五', value: 112, average: 113, valueHeight: 42, averageHeight: 48 }, { label: '周六', value: 114, average: 113, valueHeight: 54, averageHeight: 48 }, { label: '周日', value: 115, average: 114, valueHeight: 60, averageHeight: 54 }] },
|
||||
{ subjectId: 'math', points: [{ label: '周一', value: 108, average: 109, valueHeight: 18, averageHeight: 24 }, { label: '周二', value: 109, average: 110, valueHeight: 24, averageHeight: 30 }, { label: '周三', value: 110, average: 110, valueHeight: 30, averageHeight: 30 }, { label: '周四', value: 111, average: 111, valueHeight: 36, averageHeight: 36 }, { label: '周五', value: 112, average: 112, valueHeight: 42, averageHeight: 42 }, { label: '周六', value: 113, average: 112, valueHeight: 48, averageHeight: 42 }, { label: '周日', value: 114, average: 113, valueHeight: 54, averageHeight: 48 }] }
|
||||
], activity: studentTwoWeekActivity, subjectMasteries: studentTwoWeekMasteries
|
||||
},
|
||||
month: {
|
||||
rangeText: '9月14日 - 10月13日', statusTitle: '本月学习状态稳步提升', statusDescription: '本月坚持学习,多个学科的知识点掌握情况有所提升。',
|
||||
newMasteredPoints: 9, consecutiveDays: 8, averageAccuracy: 87, scaleLabels: ['120个', '116个', '112个', '108个'],
|
||||
trends: [{ subjectId: 'all', points: [{ label: '第1周', value: 107, average: 108, valueHeight: 14, averageHeight: 18 }, { label: '第2周', value: 110, average: 109, valueHeight: 30, averageHeight: 24 }, { label: '第3周', value: 113, average: 111, valueHeight: 46, averageHeight: 36 }, { label: '第4周', value: 116, average: 113, valueHeight: 62, averageHeight: 48 }] }], activity: studentTwoMonthActivity, subjectMasteries: studentTwoMonthMasteries
|
||||
}
|
||||
} : {
|
||||
studentId: 'test-student-001', studentName: '张一诺', studentGrade: '初二', studentClass: '3班', updatedAtText: '10月13日 08:30', subjects,
|
||||
week: {
|
||||
rangeText: '10月7日 - 10月13日', statusTitle: '本周学习状态良好', statusDescription: '本周状态良好,数学与英语逐渐稳定,物理薄弱题型进步明显。',
|
||||
newMasteredPoints: 3, consecutiveDays: 12, averageAccuracy: 90, scaleLabels: ['120个', '116个', '112个', '108个'],
|
||||
trends: [
|
||||
{ subjectId: 'all', points: [{ label: '周一', value: 112, average: 111, valueHeight: 32, averageHeight: 26 }, { label: '周二', value: 112, average: 113, valueHeight: 32, averageHeight: 38 }, { label: '周三', value: 113, average: 113, valueHeight: 40, averageHeight: 38 }, { label: '周四', value: 114, average: 114, valueHeight: 48, averageHeight: 48 }, { label: '周五', value: 114, average: 114, valueHeight: 48, averageHeight: 48 }, { label: '周六', value: 116, average: 114, valueHeight: 64, averageHeight: 48 }, { label: '周日', value: 116, average: 115, valueHeight: 64, averageHeight: 56 }] },
|
||||
{ subjectId: 'math', points: [{ label: '周一', value: 110, average: 110, valueHeight: 20, averageHeight: 20 }, { label: '周二', value: 111, average: 111, valueHeight: 28, averageHeight: 28 }, { label: '周三', value: 113, average: 112, valueHeight: 42, averageHeight: 35 }, { label: '周四', value: 114, average: 112, valueHeight: 49, averageHeight: 35 }, { label: '周五', value: 115, average: 113, valueHeight: 56, averageHeight: 42 }, { label: '周六', value: 116, average: 113, valueHeight: 63, averageHeight: 42 }, { label: '周日', value: 117, average: 114, valueHeight: 70, averageHeight: 49 }] }
|
||||
], activity: studentOneWeekActivity, subjectMasteries: studentOneWeekMasteries
|
||||
},
|
||||
month: {
|
||||
rangeText: '9月14日 - 10月13日', statusTitle: '本月学习状态持续向好', statusDescription: '本月学习计划完成度高,学科掌握情况保持上升趋势。',
|
||||
newMasteredPoints: 14, consecutiveDays: 12, averageAccuracy: 89, scaleLabels: ['120个', '116个', '112个', '108个'],
|
||||
trends: [{ subjectId: 'all', points: [{ label: '第1周', value: 108, average: 109, valueHeight: 16, averageHeight: 23 }, { label: '第2周', value: 111, average: 111, valueHeight: 38, averageHeight: 38 }, { label: '第3周', value: 114, average: 112, valueHeight: 53, averageHeight: 45 }, { label: '第4周', value: 116, average: 114, valueHeight: 68, averageHeight: 53 }] }], activity: studentOneMonthActivity, subjectMasteries: studentOneMonthMasteries
|
||||
}
|
||||
}
|
||||
} else if (config.url.includes('/api/parent/alerts')) {
|
||||
const isSecondStudent = config.url.includes('studentId=test-student-002')
|
||||
const alerts = isSecondStudent ? [
|
||||
{ id: 'alert-201', level: 'medium', title: '作业未完成', content: '英语《单元词汇练习》还有 2 题未完成', occurredAt: '2026-10-28T09:20:00+08:00', isRead: false },
|
||||
{ id: 'alert-202', level: 'low', title: '学习时长提醒', content: '今日学习时长低于本周日均时长', occurredAt: '2026-10-28T08:40:00+08:00', isRead: true }
|
||||
] : [
|
||||
{ id: 'alert-101', level: 'medium', title: '作业未完成', content: '物理《声现象章节测试》今晚 20:30 截止,尚未提交', occurredAt: '2026-10-28T09:30:00+08:00', isRead: false },
|
||||
{ id: 'alert-102', level: 'high', title: '正确率下降', content: '今日数学正确率 78%,低于近 7 日均值 12%', occurredAt: '2026-10-28T09:10:00+08:00', isRead: false },
|
||||
{ id: 'alert-103', level: 'high', title: '攻读失败', content: '数学《几何证明》已用满 2 次攻读机会,仍未完成', occurredAt: '2026-10-28T08:50:00+08:00', isRead: true },
|
||||
{ id: 'alert-104', level: 'low', title: '上线提醒', content: '今日首次上线时间较平日延后 30 分钟', occurredAt: '2026-10-28T08:10:00+08:00', isRead: true }
|
||||
]
|
||||
const list = config.url.includes('limit=3') ? alerts.slice(0, 3) : alerts
|
||||
data = { list, pagination: { page: 1, pageSize: list.length, total: alerts.length, hasMore: false } }
|
||||
} else if (config.url.includes('/api/parent/home-profile')) {
|
||||
const isSecondStudent = config.url.includes('studentId=test-student-002')
|
||||
data = isSecondStudent ? {
|
||||
id: 'test-student-002', name: '张一鸣', account: '20241002', grade: '五年级', school: '实验小学', avatar: '', comprehensiveLevel: 'good'
|
||||
} : {
|
||||
id: 'test-student-001', name: '张一诺', account: '20241001', grade: '初二', school: '实验中学', avatar: '', comprehensiveLevel: 'excellent'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: 'success',
|
||||
data: data as T
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用请求方法
|
||||
*/
|
||||
export function request<T>(config: RequestConfig): Promise<ApiResponse<T> | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 请求拦截
|
||||
return new Promise((resolve) => {
|
||||
const finalConfig = requestInterceptor(config)
|
||||
|
||||
if (USE_MOCK_API && !finalConfig.url.includes('/api/v1/Parents/')) {
|
||||
const delay = Math.floor(Math.random() * (MOCK_RESPONSE_MAX_DELAY + 1))
|
||||
setTimeout(() => {
|
||||
if (finalConfig.showLoading !== false) {
|
||||
hideLoading()
|
||||
}
|
||||
resolve(createMockResponse<T>(finalConfig))
|
||||
}, delay)
|
||||
return
|
||||
}
|
||||
|
||||
// 发起请求
|
||||
uni.request({
|
||||
url: finalConfig.url,
|
||||
method: finalConfig.method ?? 'GET',
|
||||
data: finalConfig.data,
|
||||
header: finalConfig.header,
|
||||
timeout: TIMEOUT,
|
||||
success: (res) => {
|
||||
// 响应拦截
|
||||
const result = responseInterceptor<T>(res, finalConfig)
|
||||
resolve(result)
|
||||
},
|
||||
success: (res) => resolve(responseInterceptor<T>(res, finalConfig)),
|
||||
fail: (err) => {
|
||||
// 隐藏Loading
|
||||
if (finalConfig.showLoading !== false) {
|
||||
hideLoading()
|
||||
}
|
||||
|
||||
// 网络错误处理
|
||||
if (finalConfig.showLoading !== false) hideLoading()
|
||||
handleError(-1, err.errMsg ?? '网络连接失败', finalConfig.showError)
|
||||
// 将网络失败转换为统一的空响应,避免页面事件中出现未处理 Promise 异常。
|
||||
resolve(null)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* GET请求
|
||||
*/
|
||||
export function get<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<ApiResponse<T> | null> {
|
||||
return request<T>({
|
||||
url,
|
||||
method: 'GET',
|
||||
data,
|
||||
...config
|
||||
})
|
||||
return request<T>({ url, method: 'GET', data, ...config })
|
||||
}
|
||||
|
||||
/**
|
||||
* POST请求
|
||||
*/
|
||||
export function post<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<ApiResponse<T> | null> {
|
||||
return request<T>({
|
||||
url,
|
||||
method: 'POST',
|
||||
data,
|
||||
...config
|
||||
})
|
||||
return request<T>({ url, method: 'POST', data, ...config })
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT请求
|
||||
*/
|
||||
export function put<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<ApiResponse<T> | null> {
|
||||
return request<T>({
|
||||
url,
|
||||
method: 'PUT',
|
||||
data,
|
||||
...config
|
||||
})
|
||||
return request<T>({ url, method: 'PUT', data, ...config })
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE请求
|
||||
*/
|
||||
export function del<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<ApiResponse<T> | null> {
|
||||
return request<T>({
|
||||
url,
|
||||
method: 'DELETE',
|
||||
data,
|
||||
...config
|
||||
})
|
||||
return request<T>({ url, method: 'DELETE', data, ...config })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user