Files
ss-tiku-manage-h5/src/pages/stats/student.vue
2026-01-30 14:08:23 +08:00

1031 lines
20 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="student-stats-container">
<!-- 页面标题 -->
<view class="page-header">
<view class="header-left" @click="goBack">
<view class="back-icon"></view>
</view>
<view class="header-title">学员学情统计</view>
<view class="header-right" @click="exportData">
<view class="export-btn">导出</view>
</view>
</view>
<!-- 筛选条件 -->
<view class="filter-section">
<view class="filter-row">
<view class="filter-item">
<view class="filter-label">时间范围</view>
<view class="filter-control">
<picker
:range="dateOptions"
:value="dateIndex"
@change="onDateChange"
class="picker"
>
<view class="picker-text">{{ dateOptions[dateIndex] }}</view>
</picker>
</view>
</view>
<view class="filter-item">
<view class="filter-label">班级</view>
<view class="filter-control">
<picker
:range="classOptions"
:value="classIndex"
@change="onClassChange"
class="picker"
>
<view class="picker-text">{{ classOptions[classIndex] }}</view>
</picker>
</view>
</view>
</view>
</view>
<!-- 核心指标 -->
<view class="metrics-section">
<view class="metrics-grid">
<view class="metric-card">
<view class="metric-icon">🎓</view>
<view class="metric-content">
<view class="metric-value">{{ metrics.totalStudents }}</view>
<view class="metric-label">总学员数</view>
</view>
</view>
<view class="metric-card">
<view class="metric-icon">👥</view>
<view class="metric-content">
<view class="metric-value">{{ metrics.memberStudents }}</view>
<view class="metric-label">会员学员</view>
</view>
</view>
<view class="metric-card">
<view class="metric-icon">📊</view>
<view class="metric-content">
<view class="metric-value">{{ metrics.avgCorrectRate }}</view>
<view class="metric-label">平均正确率</view>
</view>
</view>
<view class="metric-card">
<view class="metric-icon"></view>
<view class="metric-content">
<view class="metric-value">{{ metrics.avgExamScore }}</view>
<view class="metric-label">平均模考分</view>
</view>
</view>
</view>
</view>
<!-- 学习趋势 -->
<view class="chart-section">
<view class="section-title">学习趋势</view>
<view class="chart-container">
<!-- 模拟图表实际项目中应使用图表库 -->
<view class="mock-chart">
<view class="chart-header">
<view class="chart-title">近30天学习时长趋势</view>
</view>
<view class="chart-body">
<view class="chart-bars">
<view v-for="(day, index) in trendData" :key="index" class="chart-bar">
<view class="bar-container">
<view class="bar" :style="{ height: day.value + '%' }"></view>
</view>
<view class="bar-label">{{ day.date }}</view>
<view class="bar-value">{{ day.hours }}h</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 学习情况分布 -->
<view class="distribution-section">
<view class="section-title">学习情况分布</view>
<view class="distribution-grid">
<view
v-for="(item, index) in learningDistribution"
:key="index"
class="distribution-card"
>
<view class="distribution-icon">{{ item.icon }}</view>
<view class="distribution-content">
<view class="distribution-title">{{ item.level }}</view>
<view class="distribution-stats">
<view class="stat-item">
<view class="stat-label">人数</view>
<view class="stat-value">{{ item.count }}</view>
</view>
<view class="stat-item">
<view class="stat-label">占比</view>
<view class="stat-value">{{ item.percentage }}</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 学员列表 -->
<view class="student-section">
<view class="section-title">学员列表</view>
<view class="student-list">
<view
v-for="(student, index) in studentList"
:key="index"
class="student-item"
@click="viewStudentDetail(student.id)"
>
<view class="student-info">
<view class="student-name">{{ student.name }}</view>
<view class="student-meta">
<view class="meta-item">
<view class="meta-label">班级</view>
<view class="meta-value">{{ student.class }}</view>
</view>
<view class="meta-item">
<view class="meta-label">会员</view>
<view class="meta-value" :class="{ 'member': student.isMember }">{{ student.isMember ? '是' : '否' }}</view>
</view>
</view>
</view>
<view class="student-stats">
<view class="stat-item">
<view class="stat-label">正确率</view>
<view class="stat-value">{{ student.correctRate }}</view>
</view>
<view class="stat-item">
<view class="stat-label">模考分</view>
<view class="stat-value">{{ student.examScore }}</view>
</view>
</view>
<view class="student-arrow">
</view>
</view>
</view>
<view class="load-more" @click="loadMoreStudents">
加载更多
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from "vue"
// 筛选条件
const dateOptions = ['近7天', '近30天', '近90天', '今年', '全部']
const dateIndex = ref(0)
const classOptions = ['全部班级', '一班', '二班', '三班', '四班']
const classIndex = ref(0)
// 核心指标
const metrics = ref({
totalStudents: 1234,
memberStudents: 890,
avgCorrectRate: '78.5%',
avgExamScore: '82.3'
})
// 学习趋势数据
const trendData = ref([
{ date: '1/20', hours: 2.5, value: 30 },
{ date: '1/21', hours: 3.2, value: 40 },
{ date: '1/22', hours: 2.8, value: 35 },
{ date: '1/23', hours: 4.5, value: 60 },
{ date: '1/24', hours: 3.8, value: 50 },
{ date: '1/25', hours: 4.2, value: 55 },
{ date: '1/26', hours: 3.5, value: 45 }
])
// 学习情况分布
const learningDistribution = ref([
{
level: '优秀',
icon: '🏆',
count: 345,
percentage: '28.0%'
},
{
level: '良好',
icon: '📈',
count: 456,
percentage: '37.0%'
},
{
level: '一般',
icon: '📊',
count: 321,
percentage: '26.0%'
},
{
level: '待提高',
icon: '⚠️',
count: 112,
percentage: '9.0%'
}
])
// 学员列表
const studentList = ref([
{
id: 1,
name: '张**',
class: '一班',
isMember: true,
correctRate: '92.5%',
examScore: '95'
},
{
id: 2,
name: '李**',
class: '二班',
isMember: true,
correctRate: '88.2%',
examScore: '90'
},
{
id: 3,
name: '王**',
class: '一班',
isMember: false,
correctRate: '76.8%',
examScore: '82'
},
{
id: 4,
name: '刘**',
class: '三班',
isMember: true,
correctRate: '94.3%',
examScore: '97'
},
{
id: 5,
name: '陈**',
class: '二班',
isMember: false,
correctRate: '72.1%',
examScore: '78'
}
])
// 返回上一页
function goBack() {
uni.navigateBack({ delta: 1 })
}
// 导出数据
function exportData() {
uni.showModal({
title: '导出数据',
content: '是否导出当前统计数据?',
success: function(res) {
if (res.confirm) {
uni.showLoading({ title: '导出中...' })
setTimeout(() => {
uni.hideLoading()
uni.showToast({ title: '导出成功', icon: 'success' })
}, 1500)
}
}
})
}
// 日期变化
function onDateChange(e) {
const value = e.detail.value
dateIndex.value = value
// 实际项目中应根据选择的日期重新获取数据
refreshData()
}
// 班级变化
function onClassChange(e) {
const value = e.detail.value
classIndex.value = value
// 实际项目中应根据选择的班级重新获取数据
refreshData()
}
// 刷新数据
function refreshData() {
uni.showLoading({ title: '加载中...' })
setTimeout(() => {
uni.hideLoading()
// 实际项目中应从接口获取数据
}, 1000)
}
// 查看学员详情
function viewStudentDetail(studentId) {
uni.navigateTo({
url: `/pages/student/detail?id=${studentId}`
})
}
// 加载更多学员
function loadMoreStudents() {
uni.showLoading({ title: '加载中...' })
setTimeout(() => {
uni.hideLoading()
// 实际项目中应从接口获取更多学员数据
uni.showToast({ title: '已加载全部数据', icon: 'none' })
}, 1000)
}
onMounted(() => {
// 实际项目中应从接口获取数据
// loadStatsData()
})
</script>
<style lang="scss" scoped>
/* #ifndef APP-NVUE */
page {
display: flex;
flex-direction: column;
box-sizing: border-box;
background-color: #f5f7fa;
min-height: 100%;
height: auto;
}
view {
font-size: 14px;
line-height: inherit;
}
/* #endif */
.student-stats-container {
flex: 1;
display: flex;
flex-direction: column;
}
/* 页面头部 */
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 120rpx;
background-color: #fff;
padding: 0 32rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.header-left {
width: 60rpx;
}
.back-icon {
font-size: 40rpx;
color: #303133;
cursor: pointer;
}
.header-title {
font-size: 32rpx;
font-weight: bold;
color: #303133;
}
.header-right {
width: 60rpx;
text-align: right;
}
.export-btn {
font-size: 28rpx;
color: #409eff;
font-weight: 600;
cursor: pointer;
}
/* 筛选条件 */
.filter-section {
padding: 24rpx 32rpx;
background-color: #fff;
margin: 16rpx;
border-radius: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
}
.filter-row {
display: flex;
justify-content: space-between;
gap: 16rpx;
}
.filter-item {
flex: 1;
}
.filter-label {
font-size: 24rpx;
color: #606266;
margin-bottom: 8rpx;
}
.filter-control {
background-color: #f9f9f9;
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
padding: 16rpx;
}
.picker {
display: flex;
justify-content: space-between;
align-items: center;
}
.picker-text {
font-size: 24rpx;
color: #303133;
}
/* 核心指标 */
.metrics-section {
padding: 32rpx;
background-color: #fff;
margin: 0 16rpx 16rpx;
border-radius: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16rpx;
}
.metric-card {
display: flex;
align-items: center;
padding: 24rpx;
background-color: #f9f9f9;
border-radius: 12rpx;
}
.metric-icon {
font-size: 48rpx;
margin-right: 24rpx;
}
.metric-content {
flex: 1;
}
.metric-value {
font-size: 32rpx;
font-weight: bold;
color: #303133;
margin-bottom: 8rpx;
}
.metric-label {
font-size: 24rpx;
color: #606266;
}
/* 图表区域 */
.chart-section {
padding: 32rpx;
background-color: #fff;
margin: 0 16rpx 16rpx;
border-radius: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #303133;
margin-bottom: 24rpx;
padding-left: 12rpx;
border-left: 8rpx solid #409eff;
line-height: 1.2;
}
.chart-container {
background-color: #f9f9f9;
border-radius: 12rpx;
padding: 24rpx;
}
.mock-chart {
display: flex;
flex-direction: column;
}
.chart-header {
margin-bottom: 24rpx;
}
.chart-title {
font-size: 28rpx;
font-weight: 600;
color: #303133;
text-align: center;
}
.chart-bars {
display: flex;
justify-content: space-between;
align-items: flex-end;
height: 300rpx;
gap: 16rpx;
}
.chart-bar {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.bar-container {
flex: 1;
width: 100%;
display: flex;
align-items: flex-end;
justify-content: center;
margin-bottom: 12rpx;
}
.bar {
width: 40rpx;
background-color: #409eff;
border-radius: 4rpx 4rpx 0 0;
transition: height 0.5s ease;
}
.bar-label {
font-size: 20rpx;
color: #606266;
margin-bottom: 8rpx;
}
.bar-value {
font-size: 20rpx;
font-weight: 600;
color: #303133;
}
/* 学习情况分布 */
.distribution-section {
padding: 32rpx;
background-color: #fff;
margin: 0 16rpx 16rpx;
border-radius: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
}
.distribution-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16rpx;
}
.distribution-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 24rpx;
background-color: #f9f9f9;
border-radius: 12rpx;
}
.distribution-icon {
font-size: 48rpx;
margin-bottom: 16rpx;
}
.distribution-content {
display: flex;
flex-direction: column;
align-items: center;
}
.distribution-title {
font-size: 24rpx;
font-weight: 600;
color: #303133;
margin-bottom: 16rpx;
}
.distribution-stats {
display: flex;
flex-direction: column;
gap: 8rpx;
align-items: center;
}
.stat-item {
display: flex;
align-items: center;
}
.stat-label {
font-size: 20rpx;
color: #606266;
margin-right: 8rpx;
}
.stat-value {
font-size: 20rpx;
font-weight: 600;
color: #303133;
}
/* 学员列表 */
.student-section {
padding: 32rpx;
background-color: #fff;
margin: 0 16rpx 32rpx;
border-radius: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
}
.student-list {
display: flex;
flex-direction: column;
gap: 16rpx;
margin-bottom: 24rpx;
}
.student-item {
display: flex;
align-items: center;
padding: 24rpx;
background-color: #f9f9f9;
border-radius: 12rpx;
transition: all 0.3s ease;
}
.student-item:active {
transform: translateY(2rpx);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.student-info {
flex: 1;
margin-right: 24rpx;
}
.student-name {
font-size: 28rpx;
font-weight: 600;
color: #303133;
margin-bottom: 8rpx;
}
.student-meta {
display: flex;
gap: 32rpx;
}
.meta-item {
display: flex;
align-items: center;
}
.meta-label {
font-size: 20rpx;
color: #606266;
margin-right: 8rpx;
}
.meta-value {
font-size: 20rpx;
color: #303133;
}
.meta-value.member {
color: #67c23a;
font-weight: 600;
}
.student-stats {
display: flex;
flex-direction: column;
gap: 8rpx;
margin-right: 24rpx;
}
.student-arrow {
font-size: 32rpx;
color: #909399;
}
.load-more {
text-align: center;
padding: 24rpx;
background-color: #f9f9f9;
border-radius: 12rpx;
font-size: 24rpx;
color: #409eff;
cursor: pointer;
}
/* 平板响应式 */
@media screen and (min-width: 768px) {
.student-stats-container {
max-width: 900px;
margin: 0 auto;
width: 100%;
}
.page-header {
height: 140rpx;
padding: 0 48rpx;
}
.header-title {
font-size: 36rpx;
}
.back-icon {
font-size: 48rpx;
}
.export-btn {
font-size: 32rpx;
}
.filter-section {
margin: 24rpx;
padding: 32rpx;
}
.filter-row {
gap: 24rpx;
}
.filter-label {
font-size: 26rpx;
}
.filter-control {
padding: 20rpx;
}
.picker-text {
font-size: 26rpx;
}
.metrics-section,
.chart-section,
.distribution-section,
.student-section {
margin: 0 24rpx 24rpx;
padding: 40rpx;
}
.section-title {
font-size: 36rpx;
margin-bottom: 32rpx;
}
.metrics-grid {
grid-template-columns: repeat(4, 1fr);
gap: 24rpx;
}
.metric-card {
padding: 32rpx;
}
.metric-icon {
font-size: 56rpx;
margin-right: 32rpx;
}
.metric-value {
font-size: 36rpx;
}
.metric-label {
font-size: 26rpx;
}
.chart-container {
padding: 32rpx;
}
.chart-title {
font-size: 32rpx;
}
.chart-bars {
height: 400rpx;
gap: 24rpx;
}
.bar {
width: 60rpx;
}
.bar-label,
.bar-value {
font-size: 22rpx;
}
.distribution-grid {
gap: 24rpx;
}
.distribution-card {
padding: 32rpx;
}
.distribution-icon {
font-size: 56rpx;
margin-bottom: 24rpx;
}
.distribution-title {
font-size: 26rpx;
margin-bottom: 24rpx;
}
.stat-label,
.stat-value {
font-size: 22rpx;
}
.student-item {
padding: 32rpx;
}
.student-name {
font-size: 32rpx;
}
.student-meta {
gap: 40rpx;
}
.meta-label,
.meta-value {
font-size: 22rpx;
}
.student-arrow {
font-size: 36rpx;
}
.load-more {
padding: 32rpx;
font-size: 26rpx;
}
}
/* 大屏设备响应式 */
@media screen and (min-width: 1024px) {
.student-stats-container {
max-width: 1000px;
}
.page-header {
height: 160rpx;
padding: 0 64rpx;
}
.header-title {
font-size: 40rpx;
}
.back-icon {
font-size: 56rpx;
}
.export-btn {
font-size: 36rpx;
}
.filter-section {
padding: 40rpx;
}
.filter-label {
font-size: 28rpx;
}
.filter-control {
padding: 24rpx;
}
.picker-text {
font-size: 28rpx;
}
.metrics-section,
.chart-section,
.distribution-section,
.student-section {
padding: 48rpx;
}
.section-title {
font-size: 40rpx;
margin-bottom: 40rpx;
}
.metrics-grid {
gap: 32rpx;
}
.metric-card {
padding: 40rpx;
}
.metric-icon {
font-size: 64rpx;
margin-right: 40rpx;
}
.metric-value {
font-size: 40rpx;
}
.metric-label {
font-size: 28rpx;
}
.chart-container {
padding: 40rpx;
}
.chart-title {
font-size: 36rpx;
}
.chart-bars {
height: 450rpx;
gap: 32rpx;
}
.bar {
width: 72rpx;
}
.bar-label,
.bar-value {
font-size: 24rpx;
}
.distribution-card {
padding: 40rpx;
}
.distribution-icon {
font-size: 64rpx;
margin-bottom: 32rpx;
}
.distribution-title {
font-size: 28rpx;
margin-bottom: 32rpx;
}
.stat-label,
.stat-value {
font-size: 24rpx;
}
.student-item {
padding: 40rpx;
}
.student-name {
font-size: 36rpx;
}
.student-meta {
gap: 48rpx;
}
.meta-label,
.meta-value {
font-size: 24rpx;
}
.student-arrow {
font-size: 40rpx;
}
.load-more {
padding: 40rpx;
font-size: 28rpx;
}
}
</style>