196 lines
12 KiB
Plaintext
196 lines
12 KiB
Plaintext
<template>
|
||
<view class="page-container">
|
||
<view class="header-panel">
|
||
<view class="back-button" @tap="goBack"><text class="back-icon">‹</text></view>
|
||
<view class="header-copy"><text class="header-title">学习预警</text><text class="header-subtitle">{{ studentName }}</text></view>
|
||
<view class="header-action" @tap="openStudentPicker"><UniIcons type="person-filled" class="header-action-icon"></UniIcons></view>
|
||
</view>
|
||
<scroll-view class="content-scroll" direction="vertical" scroll-y="true">
|
||
<view v-if="isLoading" class="status-panel"><text class="status-text">预警信息加载中</text></view>
|
||
<view v-else-if="alerts.length === 0" class="status-panel"><text class="status-text">暂无预警信息</text></view>
|
||
<view v-else class="content-body">
|
||
<view class="summary-card">
|
||
<text class="summary-title">今日触发预警</text>
|
||
<view class="summary-count-row"><text class="summary-count">{{ triggeredCount }}</text><text class="summary-unit">条</text></view>
|
||
<text class="summary-desc">其中 {{ triggeredCount }} 条与作业截止相关,建议今晚提醒孩子提交。</text>
|
||
</view>
|
||
<view class="list-heading"><text class="list-title">全部预警规则</text><text class="list-total">共 {{ alerts.length }} 条</text></view>
|
||
<view v-for="alert in alerts" :key="alert.id" class="alert-card">
|
||
<view class="alert-card-top"><view class="alert-level-dot" :class="alert.isTriggered ? 'alert-level-triggered' : 'alert-level-normal'"></view><text class="alert-title">{{ alert.title }}</text><text class="alert-status" :class="alert.isTriggered ? 'alert-status-triggered' : 'alert-status-normal'">{{ alert.isTriggered ? '已触发' : '正常' }}</text></view>
|
||
<text class="alert-content">{{ alert.content }}</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
<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>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import UniIcons from '../../uni_modules/uni-icons-x/components/uni-icons/uni-icons.uvue'
|
||
import { apiGetBoundStudents, apiGetStudentLearningWarnings, type BoundStudentSummary, type StudentAlert } from '../../api/student'
|
||
import { homeStudentProfile, selectedStudentChangeVersion, switchBoundStudent } from '../../store/home'
|
||
|
||
const alerts = ref<StudentAlert[]>([])
|
||
const isLoading = ref(true)
|
||
const isStudentsLoading = ref(false)
|
||
const showStudentPicker = ref(false)
|
||
const boundStudents = ref<BoundStudentSummary[]>([])
|
||
const selectedStudentId = ref('')
|
||
const observedStudentVersion = ref(selectedStudentChangeVersion.value)
|
||
const studentName = computed((): string => homeStudentProfile.value?.name ?? '孩子')
|
||
const triggeredCount = computed((): number => alerts.value.filter((item: StudentAlert) => item.isTriggered).length)
|
||
|
||
onLoad(() => {
|
||
loadAlerts()
|
||
})
|
||
|
||
onShow(() => {
|
||
if (observedStudentVersion.value !== selectedStudentChangeVersion.value) {
|
||
observedStudentVersion.value = selectedStudentChangeVersion.value
|
||
loadAlerts()
|
||
}
|
||
})
|
||
|
||
async function loadAlerts() {
|
||
isLoading.value = true
|
||
const studentId = homeStudentProfile.value?.id ?? ''
|
||
if (!studentId) {
|
||
isLoading.value = false
|
||
return
|
||
}
|
||
const res = await apiGetStudentLearningWarnings(studentId)
|
||
if (res && res.data) {
|
||
alerts.value = res.data.list
|
||
}
|
||
isLoading.value = false
|
||
}
|
||
|
||
function goBack() {
|
||
uni.navigateBack()
|
||
}
|
||
|
||
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 loadAlerts()
|
||
}
|
||
|
||
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'
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.page-container { display: flex; flex-direction: column; flex: 1; background-color: #f6f7fb; }
|
||
.header-panel { display: flex; flex-direction: row; align-items: center; padding: 6px 4px 8px; background-color: #ffffff; }
|
||
.back-button { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 18px; background-color: #eef2f7; }
|
||
.back-icon { font-size: 30px; line-height: 32px; color: #415066; }
|
||
.header-copy { display: flex; flex-direction: column; flex: 1; margin-left: 7px; }
|
||
.header-title { font-size: 15px; font-weight: 700; color: #111827; }
|
||
.header-subtitle { margin-top: 4px; font-size: 11px; color: #7c8aa0; }
|
||
.header-action { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 36px; height: 36px; margin-right: 4px; border-radius: 18px; background-color: #edf1ff; }
|
||
.header-action-icon { font-size: 18px; color: #5947ea; }
|
||
.content-scroll { flex: 1; padding: 14px 4px 8px; }
|
||
.content-body { display: flex; flex-direction: column; }
|
||
.status-panel { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 160px; }
|
||
.status-text { font-size: 14px; color: #8992a5; }
|
||
.summary-card { display: flex; flex-direction: column; padding: 15px 16px 16px; margin-bottom: 14px; border-radius: 12px; background-color: #683dec; }
|
||
.summary-title { font-size: 12px; font-weight: 600; color: #ffffff; }
|
||
.summary-count-row { display: flex; flex-direction: row; align-items: flex-end; margin-top: 9px; }
|
||
.summary-count { font-size: 26px; line-height: 30px; font-weight: 800; color: #ffffff; }
|
||
.summary-unit { margin-left: 6px; margin-bottom: 3px; font-size: 13px; font-weight: 600; color: #ffffff; }
|
||
.summary-desc { margin-top: 10px; font-size: 12px; line-height: 18px; color: #ffffff; }
|
||
.list-heading { display: flex; flex-direction: row; align-items: center; justify-content: space-between; margin-bottom: 9px; }
|
||
.list-title { font-size: 15px; font-weight: 700; color: #121826; }
|
||
.list-total { font-size: 12px; color: #7e8ba0; }
|
||
.alert-card { display: flex; flex-direction: column; margin-bottom: 10px; padding: 15px 14px; border-radius: 13px; background-color: #ffffff; border: 1px solid #edf0f5; }
|
||
.alert-card-top { display: flex; flex-direction: row; align-items: center; }
|
||
.alert-level-dot { width: 7px; height: 7px; margin-right: 11px; border-radius: 4px; }
|
||
.alert-level-triggered { background-color: #ff3f6b; }
|
||
.alert-level-normal { background-color: #d2dce8; }
|
||
.alert-title { flex: 1; font-size: 14px; font-weight: 700; color: #1f2a3d; }
|
||
.alert-status { padding: 4px 8px; border-radius: 12px; font-size: 10px; }
|
||
.alert-status-triggered { color: #ff3f6b; background-color: #fff0f4; }
|
||
.alert-status-normal { color: #00a86b; background-color: #eafaf3; }
|
||
.alert-content { margin-top: 10px; margin-left: 18px; font-size: 12px; line-height: 19px; color: #536278; }
|
||
.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>
|