Compare commits
2
Commits
08711b7628
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef53fc7c40 | ||
|
|
ed134f0e99 |
+145
-5
@@ -71,13 +71,16 @@ export type TodayLearningCourse = {
|
|||||||
time: string
|
time: string
|
||||||
subject: string
|
subject: string
|
||||||
content: string
|
content: string
|
||||||
|
stageName: string
|
||||||
checkedIn: boolean
|
checkedIn: boolean
|
||||||
|
statusText: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TodayLearningScore = {
|
export type TodayLearningScore = {
|
||||||
accuracy: number
|
accuracy: number | null
|
||||||
yesterdayAccuracy: number
|
yesterdayAccuracy: number | null
|
||||||
change: number
|
change: number | null
|
||||||
|
hasYesterdayData: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TodayLearningPoint = {
|
export type TodayLearningPoint = {
|
||||||
@@ -86,6 +89,64 @@ export type TodayLearningPoint = {
|
|||||||
points: number
|
points: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 接口原始结构:今日学习概览 */
|
||||||
|
export type ParentTodayLearningOverview = {
|
||||||
|
StudyDurationMinute: number
|
||||||
|
AnswerDurationMinute: number
|
||||||
|
AnswerQuestionCount: number
|
||||||
|
CompletedPackCount: number
|
||||||
|
TotalPackCount: number
|
||||||
|
OvercomeKnowledgePointCount: number
|
||||||
|
IsCheckedIn: boolean
|
||||||
|
CheckInStatusName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 接口原始结构:今日课节与打卡 */
|
||||||
|
export type ParentTodayCourseItem = {
|
||||||
|
CourseId: string
|
||||||
|
BaseSubjectId: string
|
||||||
|
BaseSubjectName: string
|
||||||
|
CourseName: string
|
||||||
|
StudyStage: number
|
||||||
|
StudyStageName: string
|
||||||
|
PlanStartTime: string
|
||||||
|
PlanEndTime: string
|
||||||
|
TimeText: string
|
||||||
|
IsCheckedIn: boolean
|
||||||
|
CheckInStatusName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 接口原始结构:正确率对比 */
|
||||||
|
export type ParentTodayAccuracyComparison = {
|
||||||
|
TodayAccuracyRate: number | null
|
||||||
|
YesterdayAccuracyRate: number | null
|
||||||
|
AccuracyRateDiff: number | null
|
||||||
|
HasYesterdayData: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 接口原始结构:积分明细 */
|
||||||
|
export type ParentTodayPointItem = {
|
||||||
|
Type: number
|
||||||
|
Name: string
|
||||||
|
Points: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 接口原始结构:今日获得积分 */
|
||||||
|
export type ParentTodayPointResult = {
|
||||||
|
TotalPoints: number
|
||||||
|
Items: ParentTodayPointItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 接口原始结构:今日学习情况 */
|
||||||
|
export type ParentTodayLearningSituationResult = {
|
||||||
|
UserId: string
|
||||||
|
Date: string
|
||||||
|
Overview: ParentTodayLearningOverview
|
||||||
|
Courses: ParentTodayCourseItem[]
|
||||||
|
Accuracy: ParentTodayAccuracyComparison
|
||||||
|
Points: ParentTodayPointResult
|
||||||
|
}
|
||||||
|
|
||||||
export type StudyReportSubject = {
|
export type StudyReportSubject = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
@@ -200,6 +261,7 @@ export type TodayLearningDetail = {
|
|||||||
studentName: string
|
studentName: string
|
||||||
dateText: string
|
dateText: string
|
||||||
checkedIn: boolean
|
checkedIn: boolean
|
||||||
|
checkInStatusName: string
|
||||||
learningMinutes: number
|
learningMinutes: number
|
||||||
answerMinutes: number
|
answerMinutes: number
|
||||||
answerCount: number
|
answerCount: number
|
||||||
@@ -491,10 +553,88 @@ export function apiGetStudentLearningWarnings(studentId: string, showLoading: bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取学生今日学习详情及课程打卡记录。
|
* 获取学生今日学习情况。
|
||||||
|
* 接口字段为 PascalCase,这里统一转换为页面使用的驼峰结构。
|
||||||
*/
|
*/
|
||||||
export function apiGetTodayLearningDetail(studentId: string, showLoading: boolean = true): Promise<ApiResponse<TodayLearningDetail> | null> {
|
export function apiGetTodayLearningDetail(studentId: string, showLoading: boolean = true): Promise<ApiResponse<TodayLearningDetail> | null> {
|
||||||
return get<TodayLearningDetail>(`/api/parent/today-learning?studentId=${studentId}`, undefined, { showLoading })
|
return get<ParentTodayLearningSituationResult>(`/api/v1/ParentLearningSituation/GetToday?userId=${encodeURIComponent(studentId)}`, undefined, { showLoading }).then((res) => {
|
||||||
|
if (!res || !res.data) return null
|
||||||
|
const data = res.data
|
||||||
|
const overview = data.Overview
|
||||||
|
const accuracy = data.Accuracy
|
||||||
|
const points = data.Points
|
||||||
|
const courses: TodayLearningCourse[] = (Array.isArray(data.Courses) ? data.Courses : []).map((course: ParentTodayCourseItem) => {
|
||||||
|
const checkedIn = Boolean(course.IsCheckedIn)
|
||||||
|
return {
|
||||||
|
id: String(course.CourseId ?? ''),
|
||||||
|
time: course.TimeText || formatTodayLearningTime(course.PlanStartTime),
|
||||||
|
subject: course.BaseSubjectName || '',
|
||||||
|
content: course.CourseName || '',
|
||||||
|
stageName: course.StudyStageName || '',
|
||||||
|
checkedIn,
|
||||||
|
statusText: course.CheckInStatusName || (checkedIn ? '已打卡' : '未打卡')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const pointDetails: TodayLearningPoint[] = (Array.isArray(points?.Items) ? points.Items : []).map((item: ParentTodayPointItem) => ({
|
||||||
|
id: String(item.Type ?? ''),
|
||||||
|
label: item.Name || '其他积分',
|
||||||
|
points: Number(item.Points) || 0
|
||||||
|
}))
|
||||||
|
return {
|
||||||
|
code: res.code,
|
||||||
|
message: res.message,
|
||||||
|
data: {
|
||||||
|
studentId: String(data.UserId ?? studentId),
|
||||||
|
studentName: '',
|
||||||
|
dateText: formatTodayLearningDateText(data.Date),
|
||||||
|
checkedIn: Boolean(overview?.IsCheckedIn),
|
||||||
|
checkInStatusName: overview?.CheckInStatusName || (overview?.IsCheckedIn ? '已打卡' : '未打卡'),
|
||||||
|
learningMinutes: Number(overview?.StudyDurationMinute) || 0,
|
||||||
|
answerMinutes: Number(overview?.AnswerDurationMinute) || 0,
|
||||||
|
answerCount: Number(overview?.AnswerQuestionCount) || 0,
|
||||||
|
completedPackages: Number(overview?.CompletedPackCount) || 0,
|
||||||
|
totalPackages: Number(overview?.TotalPackCount) || 0,
|
||||||
|
masteredPoints: Number(overview?.OvercomeKnowledgePointCount) || 0,
|
||||||
|
courses,
|
||||||
|
score: {
|
||||||
|
accuracy: toNullableNumber(accuracy?.TodayAccuracyRate),
|
||||||
|
yesterdayAccuracy: toNullableNumber(accuracy?.YesterdayAccuracyRate),
|
||||||
|
change: toNullableNumber(accuracy?.AccuracyRateDiff),
|
||||||
|
hasYesterdayData: Boolean(accuracy?.HasYesterdayData)
|
||||||
|
},
|
||||||
|
pointsTotal: Number(points?.TotalPoints) || 0,
|
||||||
|
pointDetails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function toNullableNumber(value: number | null | undefined): number | null {
|
||||||
|
if (value === null || value === undefined) return null
|
||||||
|
return Number(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTodayLearningTime(value: string | null | undefined): string {
|
||||||
|
if (!value) return '--'
|
||||||
|
const text = String(value)
|
||||||
|
return text.length >= 16 ? text.substring(11, 16) : text
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTodayLearningDateText(value: string | null | undefined): string {
|
||||||
|
if (!value) return ''
|
||||||
|
const text = String(value)
|
||||||
|
if (text.length < 10) return text
|
||||||
|
const year = Number(text.substring(0, 4))
|
||||||
|
const month = Number(text.substring(5, 7))
|
||||||
|
const day = Number(text.substring(8, 10))
|
||||||
|
return `${month}月${day}日 ${weekdayName(year, month, day)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function weekdayName(year: number, month: number, day: number): string {
|
||||||
|
const offsets = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]
|
||||||
|
const adjustedYear = month < 3 ? year - 1 : year
|
||||||
|
const index = (adjustedYear + Math.floor(adjustedYear / 4) - Math.floor(adjustedYear / 100) + Math.floor(adjustedYear / 400) + offsets[month - 1] + day) % 7
|
||||||
|
return ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][index]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+37
-18
@@ -8,59 +8,70 @@
|
|||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "登录",
|
"navigationBarTitleText": "登录",
|
||||||
"navigationBarBackgroundColor": "#FFFFFF",
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
"navigationBarTextStyle": "black"
|
"navigationBarTextStyle": "black",
|
||||||
|
"hideStatusBar": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/index/index",
|
"path": "pages/index/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "首页"
|
"navigationBarTitleText": "首页",
|
||||||
|
"navigationBarBackgroundColor": "#5d3de8",
|
||||||
|
"navigationBarTextStyle": "white",
|
||||||
|
"hideStatusBar": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/study/index",
|
"path": "pages/study/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom",
|
||||||
|
"hideStatusBar": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/vocabulary/index",
|
"path": "pages/vocabulary/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom",
|
||||||
|
"hideStatusBar": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/growth/index",
|
"path": "pages/growth/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom",
|
||||||
|
"hideStatusBar": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/tasks/index",
|
"path": "pages/tasks/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "任务"
|
"navigationBarTitleText": "任务",
|
||||||
|
"hideStatusBar": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/profile/index",
|
"path": "pages/profile/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "我的"
|
"navigationBarTitleText": "我的",
|
||||||
|
"hideStatusBar": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/profile/verify-student",
|
"path": "pages/profile/verify-student",
|
||||||
"style": { "navigationBarTitleText": "验证学生" }
|
"style": { "navigationBarTitleText": "验证学生", "hideStatusBar": true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/alerts/index",
|
"path": "pages/alerts/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "预警信息"
|
"navigationBarTitleText": "预警信息",
|
||||||
|
"hideStatusBar": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/today-learning/index",
|
"path": "pages/today-learning/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom",
|
||||||
|
"hideStatusBar": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -68,7 +79,8 @@
|
|||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "验证码登录",
|
"navigationBarTitleText": "验证码登录",
|
||||||
"navigationBarBackgroundColor": "#FFFFFF",
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
"navigationBarTextStyle": "black"
|
"navigationBarTextStyle": "black",
|
||||||
|
"hideStatusBar": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -76,7 +88,8 @@
|
|||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "忘记密码",
|
"navigationBarTitleText": "忘记密码",
|
||||||
"navigationBarBackgroundColor": "#FFFFFF",
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
"navigationBarTextStyle": "black"
|
"navigationBarTextStyle": "black",
|
||||||
|
"hideStatusBar": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -84,7 +97,8 @@
|
|||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "家长注册",
|
"navigationBarTitleText": "家长注册",
|
||||||
"navigationBarBackgroundColor": "#FFFFFF",
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
"navigationBarTextStyle": "black"
|
"navigationBarTextStyle": "black",
|
||||||
|
"hideStatusBar": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -92,7 +106,8 @@
|
|||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "家长注册",
|
"navigationBarTitleText": "家长注册",
|
||||||
"navigationBarBackgroundColor": "#FFFFFF",
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
"navigationBarTextStyle": "black"
|
"navigationBarTextStyle": "black",
|
||||||
|
"hideStatusBar": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -100,7 +115,8 @@
|
|||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "家长注册",
|
"navigationBarTitleText": "家长注册",
|
||||||
"navigationBarBackgroundColor": "#FFFFFF",
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
"navigationBarTextStyle": "black"
|
"navigationBarTextStyle": "black",
|
||||||
|
"hideStatusBar": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -108,7 +124,8 @@
|
|||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "家长注册",
|
"navigationBarTitleText": "家长注册",
|
||||||
"navigationBarBackgroundColor": "#FFFFFF",
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
"navigationBarTextStyle": "black"
|
"navigationBarTextStyle": "black",
|
||||||
|
"hideStatusBar": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -116,7 +133,8 @@
|
|||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "注册完成",
|
"navigationBarTitleText": "注册完成",
|
||||||
"navigationBarBackgroundColor": "#FFFFFF",
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
"navigationBarTextStyle": "black"
|
"navigationBarTextStyle": "black",
|
||||||
|
"hideStatusBar": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -124,7 +142,8 @@
|
|||||||
"navigationBarTextStyle": "black",
|
"navigationBarTextStyle": "black",
|
||||||
"navigationBarTitleText": "家长学情",
|
"navigationBarTitleText": "家长学情",
|
||||||
"navigationBarBackgroundColor": "#FFFFFF",
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
"backgroundColor": "#F5F5F5"
|
"backgroundColor": "#F5F5F5",
|
||||||
|
"hideStatusBar": true
|
||||||
},
|
},
|
||||||
"uniIdRouter": {}
|
"uniIdRouter": {}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-6
@@ -7,8 +7,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import UniIcons from '../../uni_modules/uni-icons-x/components/uni-icons/uni-icons.uvue'
|
import UniIcons from '../../uni_modules/uni-icons-x/components/uni-icons/uni-icons.uvue'
|
||||||
import { apiGetStudentGrowth, apiGetStudentHomeProfile, apiGetStudentPointRecords, type StudentGrowth } from '../../api/student'
|
import { apiGetBoundStudents, apiGetStudentGrowth, apiGetStudentPointRecords, type StudentGrowth } from '../../api/student'
|
||||||
import { homeStudentProfile, selectedStudentChangeVersion, setHomeStudentProfile } from '../../store/home'
|
import { applyBoundStudent, getSavedSelectedStudent, homeStudentProfile, saveSelectedStudent, selectedStudentChangeVersion } from '../../store/home'
|
||||||
|
|
||||||
const growth = ref<StudentGrowth>({ studentId: '', studentName: '', studentGrade: '', studentClass: '', pointsBalance: 0, level: 0, rank: 0, streakDays: 0, totalPoints: 0, records: [] })
|
const growth = ref<StudentGrowth>({ studentId: '', studentName: '', studentGrade: '', studentClass: '', pointsBalance: 0, level: 0, rank: 0, streakDays: 0, totalPoints: 0, records: [] })
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
@@ -33,10 +33,18 @@
|
|||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
let studentId = homeStudentProfile.value?.id ?? ''
|
let studentId = homeStudentProfile.value?.id ?? ''
|
||||||
if (!studentId) {
|
if (!studentId) {
|
||||||
const profileRes = await apiGetStudentHomeProfile(undefined, false)
|
const savedStudent = getSavedSelectedStudent()
|
||||||
if (profileRes && profileRes.data) {
|
if (savedStudent) {
|
||||||
setHomeStudentProfile(profileRes.data)
|
applyBoundStudent(savedStudent)
|
||||||
studentId = profileRes.data.id
|
studentId = savedStudent.id
|
||||||
|
} else {
|
||||||
|
const res = await apiGetBoundStudents()
|
||||||
|
const firstStudent = res?.data?.students[0]
|
||||||
|
if (firstStudent) {
|
||||||
|
saveSelectedStudent(firstStudent)
|
||||||
|
applyBoundStudent(firstStudent)
|
||||||
|
studentId = firstStudent.id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (studentId) {
|
if (studentId) {
|
||||||
|
|||||||
@@ -338,7 +338,8 @@
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 15px 20px 17px;
|
padding: 15px 20px 17px;
|
||||||
background: linear-gradient(135deg, #5d3de8 0%, #8236e9 100%);
|
background-color: #5d3de8;
|
||||||
|
background-image: linear-gradient(to bottom right, #5d3de8, #8236e9);
|
||||||
border-radius: 0 0 0 0;
|
border-radius: 0 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+166
-19
@@ -2,15 +2,15 @@
|
|||||||
<view class="page-container">
|
<view class="page-container">
|
||||||
<view class="header-panel">
|
<view class="header-panel">
|
||||||
<view class="header-back" @tap="goBack"><UniIcons type="back" class="header-back-icon"></UniIcons></view>
|
<view class="header-back" @tap="goBack"><UniIcons type="back" class="header-back-icon"></UniIcons></view>
|
||||||
<view class="header-copy"><text class="header-title">今日学习</text><text class="header-subtitle">{{ detail.dateText }} {{ homeStudentProfile?.name ?? detail.studentName }} · {{ homeStudentProfile?.school ?? '' }} {{ homeStudentProfile?.className ?? '' }}</text></view>
|
<view class="header-copy"><text class="header-title">今日学习</text><text class="header-subtitle">{{ headerSubtitle }}</text></view>
|
||||||
<view class="header-action" @tap="goBack"><UniIcons type="home-filled" class="header-action-icon"></UniIcons></view>
|
<view class="header-action" @tap="openStudentPicker"><UniIcons type="person-filled" class="header-action-icon"></UniIcons></view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<scroll-view class="content-scroll" direction="vertical" scroll-y="true">
|
<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-if="isLoading" class="status-panel"><text class="status-text">今日学习信息加载中</text></view>
|
||||||
<view v-else class="content-panel">
|
<view v-else class="content-panel">
|
||||||
<view class="overview-card">
|
<view class="overview-card">
|
||||||
<view class="overview-header"><text class="section-title">今日学习概览</text><text class="checkin-status" :class="detail.checkedIn ? 'checkin-complete' : 'checkin-pending'">{{ detail.checkedIn ? '已打卡' : '未打卡' }}</text></view>
|
<view class="overview-header"><text class="section-title">今日学习概览</text><text class="checkin-status" :class="detail.checkedIn ? 'checkin-complete' : 'checkin-pending'">{{ detail.checkInStatusName }}</text></view>
|
||||||
<view class="metrics-grid">
|
<view class="metrics-grid">
|
||||||
<view class="metric-card"><text class="metric-label">学习时长</text><text class="metric-value">{{ detail.learningMinutes }} 分钟</text></view>
|
<view class="metric-card"><text class="metric-label">学习时长</text><text class="metric-value">{{ detail.learningMinutes }} 分钟</text></view>
|
||||||
<view class="metric-card"><text class="metric-label">答题时长</text><text class="metric-value">{{ detail.answerMinutes }} 分钟</text></view>
|
<view class="metric-card"><text class="metric-label">答题时长</text><text class="metric-value">{{ detail.answerMinutes }} 分钟</text></view>
|
||||||
@@ -22,11 +22,18 @@
|
|||||||
|
|
||||||
<view class="courses-card">
|
<view class="courses-card">
|
||||||
<text class="section-title">今日课表与每课打卡</text>
|
<text class="section-title">今日课表与每课打卡</text>
|
||||||
<view class="course-list">
|
<view v-if="detail.courses.length === 0" class="course-empty"><text class="course-empty-text">今日暂无课程</text></view>
|
||||||
|
<view v-else class="course-list">
|
||||||
<view v-for="course in detail.courses" :key="course.id" class="course-item">
|
<view v-for="course in detail.courses" :key="course.id" class="course-item">
|
||||||
<text class="course-time">{{ course.time }}</text>
|
<text class="course-time">{{ course.time }}</text>
|
||||||
<view class="course-copy"><text class="course-subject">{{ course.subject }}</text><text class="course-content">{{ course.content }}</text></view>
|
<view class="course-copy">
|
||||||
<text class="course-status" :class="course.checkedIn ? 'course-complete' : 'course-pending'">{{ course.checkedIn ? '已打卡' : '未打卡' }}</text>
|
<view class="course-title-row">
|
||||||
|
<text class="course-subject">{{ course.subject }}</text>
|
||||||
|
<text v-if="course.stageName" class="course-stage">{{ course.stageName }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="course-content">{{ course.content }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="course-status" :class="course.checkedIn ? 'course-complete' : 'course-pending'">{{ course.statusText }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -34,37 +41,69 @@
|
|||||||
<view class="score-card">
|
<view class="score-card">
|
||||||
<text class="section-title">正确率(对比昨日)</text>
|
<text class="section-title">正确率(对比昨日)</text>
|
||||||
<text class="score-description">整体正确率较昨日全部答题目统计,与昨日对比</text>
|
<text class="score-description">整体正确率较昨日全部答题目统计,与昨日对比</text>
|
||||||
<view class="score-row"><text class="score-label">整体正确率</text><view class="score-values"><text class="score-value">{{ detail.score.accuracy }}%</text><text class="score-change">+{{ detail.score.change }}%</text></view></view>
|
<view class="score-row">
|
||||||
<text class="score-yesterday">昨日 {{ detail.score.yesterdayAccuracy }}%</text>
|
<text class="score-label">整体正确率</text>
|
||||||
<view class="score-track"><view class="score-progress" :style="{ width: detail.score.accuracy + '%' }"></view></view>
|
<view class="score-values">
|
||||||
|
<text class="score-value">{{ formatAccuracy(detail.score.accuracy) }}</text>
|
||||||
|
<text v-if="detail.score.hasYesterdayData && detail.score.change !== null" class="score-change" :class="accuracyDiffClass(detail.score.change)">{{ formatAccuracyDiff(detail.score.change) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<text class="score-yesterday">昨日 {{ formatAccuracy(detail.score.yesterdayAccuracy) }}</text>
|
||||||
|
<view class="score-track"><view class="score-progress" :style="{ width: (detail.score.accuracy ?? 0) + '%' }"></view></view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="points-card">
|
<view class="points-card">
|
||||||
<view class="points-header"><text class="section-title">今日获得积分</text><text class="points-link">积分明细 ›</text></view>
|
<view class="points-header"><text class="section-title">今日获得积分</text><text class="points-link">积分明细 ›</text></view>
|
||||||
<view class="points-total"><text class="points-number">+{{ detail.pointsTotal }}</text><text class="points-unit">积分</text></view>
|
<view class="points-total"><text class="points-number">+{{ detail.pointsTotal }}</text><text class="points-unit">积分</text></view>
|
||||||
<view class="points-list">
|
<view v-if="detail.pointDetails.length === 0" class="points-empty"><text class="points-empty-text">今日暂无积分</text></view>
|
||||||
|
<view v-else class="points-list">
|
||||||
<view v-for="item in detail.pointDetails" :key="item.id" class="point-item"><text class="point-label">{{ item.label }}</text><text class="point-value">+{{ item.points }}</text></view>
|
<view v-for="item in detail.pointDetails" :key="item.id" class="point-item"><text class="point-label">{{ item.label }}</text><text class="point-value">+{{ item.points }}</text></view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
<MainTabBar active-tab="home"></MainTabBar>
|
<MainTabBar active-tab="home"></MainTabBar>
|
||||||
|
|
||||||
|
<page-container :show="showStudentPicker" position="bottom" :round="true" :overlay="true" :close-on-slide-down="true" @beforeleave="closeStudentPicker">
|
||||||
|
<view class="student-picker-panel">
|
||||||
|
<view class="student-picker-header">
|
||||||
|
<text class="student-picker-title">切换孩子</text>
|
||||||
|
<text class="student-picker-subtitle">切换后将刷新当前孩子的今日学习</text>
|
||||||
|
</view>
|
||||||
|
<scroll-view class="student-picker-list" direction="vertical" scroll-y="true">
|
||||||
|
<view v-if="isStudentsLoading" class="student-picker-empty"><text class="student-picker-empty-text">学生信息加载中</text></view>
|
||||||
|
<view v-else-if="boundStudents.length === 0" class="student-picker-empty"><text class="student-picker-empty-text">暂无已绑定学生</text></view>
|
||||||
|
<view v-else v-for="(item, index) in boundStudents" :key="item.id" class="student-picker-item" :class="selectedStudentId === item.id ? 'student-picker-item-selected' : ''" @tap="selectStudent(item.id)">
|
||||||
|
<view class="student-picker-avatar" :class="studentPickerAvatarClass(index)"><image v-if="item.avatar" class="student-picker-avatar-image" :src="item.avatar" mode="aspectFill"></image><text v-else class="student-picker-avatar-text">{{ studentPickerInitial(item.name) }}</text></view>
|
||||||
|
<view class="student-picker-info"><view class="student-picker-name-row"><text class="student-picker-name">{{ item.name }}</text><text class="student-picker-grade">{{ item.school }} {{ item.className }}</text></view><text class="student-picker-detail">账号:{{ item.account }}</text></view>
|
||||||
|
<text class="student-picker-action" :class="selectedStudentId === item.id ? 'student-picker-action-selected' : ''">{{ selectedStudentId === item.id ? '当前查看' : '切换' }}</text>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="student-picker-footer"><view class="student-picker-cancel" @tap="closeStudentPicker"><text class="student-picker-cancel-text">取消</text></view><view class="student-picker-confirm" @tap="confirmStudent"><text class="student-picker-confirm-text">确定</text></view></view>
|
||||||
|
</view>
|
||||||
|
</page-container>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import MainTabBar from '../../components/main-tab-bar.uvue'
|
import MainTabBar from '../../components/main-tab-bar.uvue'
|
||||||
import UniIcons from '../../uni_modules/uni-icons-x/components/uni-icons/uni-icons.uvue'
|
import UniIcons from '../../uni_modules/uni-icons-x/components/uni-icons/uni-icons.uvue'
|
||||||
import { apiGetTodayLearningDetail, type TodayLearningDetail } from '../../api/student'
|
import { apiGetBoundStudents, apiGetStudentHomeProfile, apiGetTodayLearningDetail, type BoundStudentSummary, type TodayLearningDetail } from '../../api/student'
|
||||||
import { homeStudentProfile, selectedStudentChangeVersion } from '../../store/home'
|
import { homeStudentProfile, selectedStudentChangeVersion, setHomeStudentProfile, switchBoundStudent } from '../../store/home'
|
||||||
|
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
|
const isStudentsLoading = ref(false)
|
||||||
|
const showStudentPicker = ref(false)
|
||||||
|
const boundStudents = ref<BoundStudentSummary[]>([])
|
||||||
|
const selectedStudentId = ref('')
|
||||||
const detail = ref<TodayLearningDetail>({
|
const detail = ref<TodayLearningDetail>({
|
||||||
studentId: '', studentName: '', dateText: '', checkedIn: false, learningMinutes: 0, answerMinutes: 0,
|
studentId: '', studentName: '', dateText: '', checkedIn: false, checkInStatusName: '未打卡', learningMinutes: 0, answerMinutes: 0,
|
||||||
answerCount: 0, completedPackages: 0, totalPackages: 0, masteredPoints: 0, courses: [],
|
answerCount: 0, completedPackages: 0, totalPackages: 0, masteredPoints: 0, courses: [],
|
||||||
score: { accuracy: 0, yesterdayAccuracy: 0, change: 0 }, pointsTotal: 0, pointDetails: []
|
score: { accuracy: null, yesterdayAccuracy: null, change: null, hasYesterdayData: false }, pointsTotal: 0, pointDetails: []
|
||||||
})
|
})
|
||||||
const observedStudentVersion = ref(selectedStudentChangeVersion.value)
|
const observedStudentVersion = ref(selectedStudentChangeVersion.value)
|
||||||
|
const headerStudentName = computed((): string => homeStudentProfile.value?.name || detail.value.studentName || '孩子')
|
||||||
|
const headerSubtitle = computed((): string => `${detail.value.dateText || formatCurrentDate()} · ${headerStudentName.value}`)
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
loadTodayLearning()
|
loadTodayLearning()
|
||||||
@@ -77,23 +116,95 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
async function loadTodayLearning() {
|
async function loadTodayLearning(showLoading: boolean = true) {
|
||||||
isLoading.value = true
|
isLoading.value = showLoading
|
||||||
const studentId = homeStudentProfile.value?.id ?? ''
|
let studentId = homeStudentProfile.value?.id ?? ''
|
||||||
|
if (!studentId) {
|
||||||
|
const profileRes = await apiGetStudentHomeProfile(undefined, false)
|
||||||
|
if (profileRes && profileRes.data) {
|
||||||
|
setHomeStudentProfile(profileRes.data)
|
||||||
|
studentId = profileRes.data.id
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!studentId) {
|
if (!studentId) {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const res = await apiGetTodayLearningDetail(studentId)
|
const res = await apiGetTodayLearningDetail(studentId, showLoading)
|
||||||
if (res && res.data) {
|
if (res && res.data) {
|
||||||
detail.value = res.data
|
detail.value = res.data
|
||||||
}
|
}
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openStudentPicker() {
|
||||||
|
showStudentPicker.value = true
|
||||||
|
selectedStudentId.value = homeStudentProfile.value?.id ?? ''
|
||||||
|
isStudentsLoading.value = true
|
||||||
|
const res = await apiGetBoundStudents()
|
||||||
|
if (res && res.data) {
|
||||||
|
boundStudents.value = res.data.students
|
||||||
|
if (!selectedStudentId.value && res.data.currentStudentId) selectedStudentId.value = res.data.currentStudentId
|
||||||
|
}
|
||||||
|
isStudentsLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeStudentPicker() {
|
||||||
|
showStudentPicker.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectStudent(studentId: string) {
|
||||||
|
selectedStudentId.value = studentId
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmStudent() {
|
||||||
|
if (!selectedStudentId.value) {
|
||||||
|
uni.showToast({ title: '请选择学生', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const selectedStudent = boundStudents.value.find((item) => item.id === selectedStudentId.value)
|
||||||
|
if (selectedStudent) {
|
||||||
|
switchBoundStudent(selectedStudent)
|
||||||
|
observedStudentVersion.value = selectedStudentChangeVersion.value
|
||||||
|
}
|
||||||
|
closeStudentPicker()
|
||||||
|
await loadTodayLearning(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function studentPickerInitial(name: string): string {
|
||||||
|
return name.length > 1 ? name.substring(1, 2) : name.substring(0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function studentPickerAvatarClass(index: number): string {
|
||||||
|
if (index === 0) return 'student-picker-avatar-0'
|
||||||
|
if (index === 1) return 'student-picker-avatar-1'
|
||||||
|
return 'student-picker-avatar-2'
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCurrentDate(): string {
|
||||||
|
const now = new Date()
|
||||||
|
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||||
|
return `${now.getMonth() + 1}月${now.getDate()}日 ${weekdays[now.getDay()]}`
|
||||||
|
}
|
||||||
|
|
||||||
function goBack() {
|
function goBack() {
|
||||||
uni.navigateBack()
|
uni.navigateBack()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatAccuracy(rate: number | null): string {
|
||||||
|
if (rate === null) return '暂无数据'
|
||||||
|
return `${rate}%`
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAccuracyDiff(diff: number | null): string {
|
||||||
|
if (diff === null) return ''
|
||||||
|
return `${diff > 0 ? '+' : ''}${diff}%`
|
||||||
|
}
|
||||||
|
|
||||||
|
function accuracyDiffClass(diff: number | null): string {
|
||||||
|
if (diff === null || diff >= 0) return 'score-change-up'
|
||||||
|
return 'score-change-down'
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -128,18 +239,24 @@
|
|||||||
.course-item { display: flex; flex-direction: row; align-items: center; min-height: 58px; margin-bottom: 10px; padding: 10px 12px; border-radius: 8px; background-color: #f6f8fb; }
|
.course-item { display: flex; flex-direction: row; align-items: center; min-height: 58px; margin-bottom: 10px; padding: 10px 12px; border-radius: 8px; background-color: #f6f8fb; }
|
||||||
.course-time { width: 56px; font-size: 12px; color: #596275; }
|
.course-time { width: 56px; font-size: 12px; color: #596275; }
|
||||||
.course-copy { display: flex; flex-direction: column; flex: 1; }
|
.course-copy { display: flex; flex-direction: column; flex: 1; }
|
||||||
|
.course-title-row { display: flex; flex-direction: row; align-items: center; }
|
||||||
.course-subject { font-size: 13px; font-weight: 600; color: #2a3042; }
|
.course-subject { font-size: 13px; font-weight: 600; color: #2a3042; }
|
||||||
|
.course-stage { margin-left: 6px; padding: 1px 6px; border-radius: 8px; font-size: 10px; color: #6558e9; background-color: #efedff; }
|
||||||
.course-content { margin-top: 4px; font-size: 12px; color: #596275; }
|
.course-content { margin-top: 4px; font-size: 12px; color: #596275; }
|
||||||
.course-status { padding: 4px 7px; border-radius: 10px; font-size: 10px; }
|
.course-status { padding: 4px 7px; border-radius: 10px; font-size: 10px; }
|
||||||
.course-complete { color: #00a66a; background-color: #e9fbf3; }
|
.course-complete { color: #00a66a; background-color: #e9fbf3; }
|
||||||
.course-pending { color: #f08a1a; background-color: #fff4e5; }
|
.course-pending { color: #f08a1a; background-color: #fff4e5; }
|
||||||
|
.course-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 90px; margin-top: 12px; border-radius: 8px; background-color: #f6f8fb; }
|
||||||
|
.course-empty-text { font-size: 12px; color: #8992a5; }
|
||||||
.score-card { display: flex; flex-direction: column; margin-top: 16px; padding: 16px; border-radius: 8px; background-color: #ffffff; }
|
.score-card { display: flex; flex-direction: column; margin-top: 16px; padding: 16px; border-radius: 8px; background-color: #ffffff; }
|
||||||
.score-description { margin-top: 6px; font-size: 11px; color: #8992a5; }
|
.score-description { margin-top: 6px; font-size: 11px; color: #8992a5; }
|
||||||
.score-row { display: flex; flex-direction: row; align-items: center; justify-content: space-between; margin-top: 14px; }
|
.score-row { display: flex; flex-direction: row; align-items: center; justify-content: space-between; margin-top: 14px; }
|
||||||
.score-label { font-size: 12px; color: #596275; }
|
.score-label { font-size: 12px; color: #596275; }
|
||||||
.score-values { display: flex; flex-direction: row; align-items: center; }
|
.score-values { display: flex; flex-direction: row; align-items: center; }
|
||||||
.score-value { font-size: 13px; font-weight: 700; color: #20283a; }
|
.score-value { font-size: 13px; font-weight: 700; color: #20283a; }
|
||||||
.score-change { margin-left: 8px; font-size: 12px; color: #00a66a; }
|
.score-change { margin-left: 8px; font-size: 12px; }
|
||||||
|
.score-change-up { color: #00a66a; }
|
||||||
|
.score-change-down { color: #e05b5b; }
|
||||||
.score-yesterday { margin-top: 6px; font-size: 12px; color: #8992a5; }
|
.score-yesterday { margin-top: 6px; font-size: 12px; color: #8992a5; }
|
||||||
.score-track { height: 8px; margin-top: 8px; border-radius: 4px; background-color: #edf1f7; }
|
.score-track { height: 8px; margin-top: 8px; border-radius: 4px; background-color: #edf1f7; }
|
||||||
.score-progress { height: 8px; border-radius: 4px; background-color: #7659ee; }
|
.score-progress { height: 8px; border-radius: 4px; background-color: #7659ee; }
|
||||||
@@ -153,4 +270,34 @@
|
|||||||
.point-item { display: flex; flex-direction: row; justify-content: space-between; margin-bottom: 8px; padding: 10px 12px; border-radius: 8px; background-color: #f6f8fb; }
|
.point-item { display: flex; flex-direction: row; justify-content: space-between; margin-bottom: 8px; padding: 10px 12px; border-radius: 8px; background-color: #f6f8fb; }
|
||||||
.point-label { font-size: 12px; color: #596275; }
|
.point-label { font-size: 12px; color: #596275; }
|
||||||
.point-value { font-size: 12px; font-weight: 600; color: #ee9500; }
|
.point-value { font-size: 12px; font-weight: 600; color: #ee9500; }
|
||||||
|
.points-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 60px; margin-top: 12px; border-radius: 8px; background-color: #f6f8fb; }
|
||||||
|
.points-empty-text { font-size: 12px; color: #8992a5; }
|
||||||
|
.student-picker-panel { display: flex; flex-direction: column; background-color: #ffffff; }
|
||||||
|
.student-picker-header { display: flex; flex-direction: column; padding: 18px 20px 12px; }
|
||||||
|
.student-picker-title { font-size: 18px; font-weight: 700; color: #242b3d; }
|
||||||
|
.student-picker-subtitle { margin-top: 5px; font-size: 12px; color: #8992a5; }
|
||||||
|
.student-picker-list { max-height: 340px; padding: 0 20px; }
|
||||||
|
.student-picker-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 150px; }
|
||||||
|
.student-picker-empty-text { font-size: 13px; color: #8992a5; }
|
||||||
|
.student-picker-item { display: flex; flex-direction: row; align-items: center; padding: 10px; margin-bottom: 10px; border: 1px solid #edf0f5; border-radius: 15px; background-color: #ffffff; }
|
||||||
|
.student-picker-item-selected { background-color: #f0efff; }
|
||||||
|
.student-picker-avatar { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 46px; height: 46px; border-radius: 14px; background-color: #7161ed; }
|
||||||
|
.student-picker-avatar-0 { background-color: #6558e9; }
|
||||||
|
.student-picker-avatar-1 { background-color: #12a9df; }
|
||||||
|
.student-picker-avatar-2 { background-color: #e63a9c; }
|
||||||
|
.student-picker-avatar-image { width: 46px; height: 46px; border-radius: 14px; }
|
||||||
|
.student-picker-avatar-text { font-size: 18px; font-weight: 700; color: #ffffff; }
|
||||||
|
.student-picker-info { display: flex; flex-direction: column; flex: 1; margin-left: 11px; }
|
||||||
|
.student-picker-name-row { display: flex; flex-direction: row; align-items: center; margin-bottom: 5px; }
|
||||||
|
.student-picker-name { margin-right: 8px; font-size: 15px; font-weight: 700; color: #2a3042; }
|
||||||
|
.student-picker-grade { padding: 2px 6px; border-radius: 7px; font-size: 10px; color: #554be9; background-color: #e8e7ff; }
|
||||||
|
.student-picker-detail { margin-top: 2px; font-size: 11px; color: #7e8799; }
|
||||||
|
.student-picker-action { flex-shrink: 0; padding: 5px 8px; border-radius: 10px; font-size: 9px; color: #6e7c91; background-color: #f1f4f8; }
|
||||||
|
.student-picker-action-selected { color: #ffffff; background-color: #554be9; }
|
||||||
|
.student-picker-footer { display: flex; flex-direction: row; padding: 12px 20px 24px; border-top: 1px solid #edf0f5; }
|
||||||
|
.student-picker-cancel, .student-picker-confirm { display: flex; flex-direction: column; align-items: center; justify-content: center; flex: 1; height: 44px; border-radius: 11px; }
|
||||||
|
.student-picker-cancel { margin-right: 10px; background-color: #f0f2f7; }
|
||||||
|
.student-picker-confirm { background-color: #554be9; }
|
||||||
|
.student-picker-cancel-text { font-size: 15px; color: #596275; }
|
||||||
|
.student-picker-confirm-text { font-size: 15px; color: #ffffff; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+1
-244
@@ -19,10 +19,8 @@ export type RequestConfig = {
|
|||||||
showError?: boolean
|
showError?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const BASE_URL = 'https://localhost:7145'
|
const BASE_URL = 'https://28j56530c4.oicp.vip'
|
||||||
const TIMEOUT = 10000
|
const TIMEOUT = 10000
|
||||||
const USE_MOCK_API = true
|
|
||||||
const MOCK_RESPONSE_MAX_DELAY = 2000
|
|
||||||
let loadingCount = 0
|
let loadingCount = 0
|
||||||
|
|
||||||
function showLoading(text: string = '加载中...') {
|
function showLoading(text: string = '加载中...') {
|
||||||
@@ -56,9 +54,6 @@ function requestInterceptor(config: RequestConfig): RequestConfig {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 响应拦截器 - 统一处理响应
|
|
||||||
*/
|
|
||||||
function responseInterceptor<T>(response: any, config: RequestConfig): ApiResponse<T> | null {
|
function responseInterceptor<T>(response: any, config: RequestConfig): ApiResponse<T> | null {
|
||||||
if (config.showLoading !== false) hideLoading()
|
if (config.showLoading !== false) hideLoading()
|
||||||
|
|
||||||
@@ -67,7 +62,6 @@ function responseInterceptor<T>(response: any, config: RequestConfig): ApiRespon
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析响应数据,兼容后端 PascalCase 与前端 camelCase 两种包装格式。
|
|
||||||
const rawResult = response.data as any
|
const rawResult = response.data as any
|
||||||
const result: ApiResponse<T> = {
|
const result: ApiResponse<T> = {
|
||||||
code: Number(rawResult?.code ?? rawResult?.Code),
|
code: Number(rawResult?.code ?? rawResult?.Code),
|
||||||
@@ -105,246 +99,9 @@ function handleError(code: number, message: string, showError: boolean = true) {
|
|||||||
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, pendingPackCount: 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, pendingPackCount: 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> {
|
export function request<T>(config: RequestConfig): Promise<ApiResponse<T> | null> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const finalConfig = requestInterceptor(config)
|
const finalConfig = requestInterceptor(config)
|
||||||
|
|
||||||
if (USE_MOCK_API && !finalConfig.url.includes('/api/v1/Parents/') && !finalConfig.url.includes('/api/v1/ParentStudentToday/') && !finalConfig.url.includes('/api/v1/ParentLearningWarning/') && !finalConfig.url.includes('/api/v1/ParentStudyPack/') && !finalConfig.url.includes('/api/v1/ParentLearningReport/')) {
|
|
||||||
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({
|
uni.request({
|
||||||
url: finalConfig.url,
|
url: finalConfig.url,
|
||||||
method: finalConfig.method ?? 'GET',
|
method: finalConfig.method ?? 'GET',
|
||||||
|
|||||||
Reference in New Issue
Block a user