今日学习
This commit is contained in:
+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]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+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
-1
@@ -333,7 +333,7 @@ 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/')) {
|
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/') && !finalConfig.url.includes('/api/v1/ParentLearningSituation/')) {
|
||||||
const delay = Math.floor(Math.random() * (MOCK_RESPONSE_MAX_DELAY + 1))
|
const delay = Math.floor(Math.random() * (MOCK_RESPONSE_MAX_DELAY + 1))
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (finalConfig.showLoading !== false) {
|
if (finalConfig.showLoading !== false) {
|
||||||
|
|||||||
Reference in New Issue
Block a user