- 统一学校端各管理页的头部排版、背景和外边距,在移动端左对齐标题并增加合理留白 - 优化筛选条、搜索框和操作按钮在小屏下的栅格布局,确保控件整行展示且不被压缩 - 调整统计卡片、列表和空状态在手机上的排列方式,提升阅读性和交互体验 Made-with: Cursor
462 lines
22 KiB
Vue
462 lines
22 KiB
Vue
<template>
|
|
<div class="min-h-100vh bg-[linear-gradient(180deg,#FFF8F0_0%,#FFFFFF_100%)] px-4 py-4 md:px-6 md:py-6">
|
|
<!-- 页面头部 -->
|
|
<div class="rounded-[20px] py-6 px-8 mb-6 bg-[linear-gradient(135deg,#FF8C42_0%,#FFB347_100%)]">
|
|
<div class="flex justify-between items-center gap-4 max-md:flex-col max-md:items-start">
|
|
<div class="flex items-center gap-4">
|
|
<div class="w-16 h-16 rounded-2xl flex items-center justify-center bg-white/20">
|
|
<BarChartOutlined class="text-[32px] text-white" />
|
|
</div>
|
|
<div>
|
|
<h2 class="text-white text-2xl font-700 m-0">数据报告</h2>
|
|
<p class="text-white/80 text-sm mt-1 m-0">查看学校教学数据和统计分析</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex gap-3 flex-wrap w-full md:w-auto">
|
|
<a-range-picker
|
|
v-model:value="dateRange"
|
|
:placeholder="['开始日期', '结束日期']"
|
|
class="w-full md:w-[240px]"
|
|
/>
|
|
<a-button
|
|
class="w-full md:w-auto !bg-white !border-0 rounded-xl text-[#FF8C42] font-600 export-btn hover:!bg-[#FFF8F0] hover:!text-[#FF7A2A]"
|
|
>
|
|
<DownloadOutlined class="mr-2 text-base" />
|
|
导出报告
|
|
</a-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 概览卡片 -->
|
|
<a-spin :spinning="loading">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-5 mb-6 overview-cards">
|
|
<div class="bg-white rounded-2xl p-5 flex items-center gap-4 shadow-[0_4px_12px_rgba(0,0,0,0.05)] transition-all duration-300 hover:translate-y-[-4px] hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)]">
|
|
<div class="w-14 h-14 rounded-[14px] flex items-center justify-center text-2xl text-white bg-[linear-gradient(135deg,#667eea_0%,#764ba2_100%)]">
|
|
<BookOutlined />
|
|
</div>
|
|
<div>
|
|
<div class="text-[28px] font-700 text-[#2D3436]">{{ totalLessons }}</div>
|
|
<div class="text-[13px] text-[#636E72] mt-1">总授课次数</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-white rounded-2xl p-5 flex items-center gap-4 shadow-[0_4px_12px_rgba(0,0,0,0.05)] transition-all duration-300 hover:translate-y-[-4px] hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)]">
|
|
<div class="w-14 h-14 rounded-[14px] flex items-center justify-center text-2xl text-white bg-[linear-gradient(135deg,#f093fb_0%,#f5576c_100%)]">
|
|
<SolutionOutlined />
|
|
</div>
|
|
<div>
|
|
<div class="text-[28px] font-700 text-[#2D3436]">{{ activeTeacherCount }}</div>
|
|
<div class="text-[13px] text-[#636E72] mt-1">活跃教师</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-white rounded-2xl p-5 flex items-center gap-4 shadow-[0_4px_12px_rgba(0,0,0,0.05)] transition-all duration-300 hover:translate-y-[-4px] hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)]">
|
|
<div class="w-14 h-14 rounded-[14px] flex items-center justify-center text-2xl text-white bg-[linear-gradient(135deg,#43e97b_0%,#38f9d7_100%)]">
|
|
<ReadOutlined />
|
|
</div>
|
|
<div>
|
|
<div class="text-[28px] font-700 text-[#2D3436]">{{ usedCourseCount }}</div>
|
|
<div class="text-[13px] text-[#636E72] mt-1">使用课程</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-white rounded-2xl p-5 flex items-center gap-4 shadow-[0_4px_12px_rgba(0,0,0,0.05)] transition-all duration-300 hover:translate-y-[-4px] hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)]">
|
|
<div class="w-14 h-14 rounded-[14px] flex items-center justify-center text-2xl text-white bg-[linear-gradient(135deg,#4facfe_0%,#00f2fe_100%)]">
|
|
<StarFilled />
|
|
</div>
|
|
<div>
|
|
<div class="text-[28px] font-700 text-[#2D3436]">{{ avgRating }}</div>
|
|
<div class="text-[13px] text-[#636E72] mt-1">平均评分</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</a-spin>
|
|
|
|
<!-- 标签页 -->
|
|
<div class="bg-white rounded-[20px] overflow-hidden shadow-[0_4px_12px_rgba(0,0,0,0.05)] report-tabs">
|
|
<div class="flex border-b border-[#F0F0F0] px-6 overflow-x-auto max-md:px-0 tab-header">
|
|
<div
|
|
v-for="tab in tabs"
|
|
:key="tab.key"
|
|
class="flex items-center gap-2 py-4 px-6 max-md:py-3 max-md:px-4 cursor-pointer border-b-3 border-transparent transition-all duration-200 whitespace-nowrap tab-item"
|
|
:class="activeTab === tab.key ? 'text-[#FF8C42] border-b-[#FF8C42]' : 'text-[#636E72]'"
|
|
@click="activeTab = tab.key"
|
|
>
|
|
<BarChartOutlined v-if="tab.icon === 'bar-chart'" class="text-lg" />
|
|
<SolutionOutlined v-else-if="tab.icon === 'solution'" class="text-lg" />
|
|
<ReadOutlined v-else-if="tab.icon === 'read'" class="text-lg" />
|
|
<TeamOutlined v-else-if="tab.icon === 'team'" class="text-lg" />
|
|
<span class="font-500">{{ tab.label }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-6 tab-content">
|
|
<!-- 整体概览 -->
|
|
<div v-if="activeTab === 'overview'">
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 chart-grid">
|
|
<div class="bg-[#F8F9FA] rounded-2xl p-5">
|
|
<div class="flex items-center gap-2 mb-5">
|
|
<LineChartOutlined class="text-xl text-[#FF8C42]" />
|
|
<h4 class="m-0 text-base font-600 text-[#2D3436]">课程使用趋势</h4>
|
|
</div>
|
|
<div class="h-[250px] flex flex-col justify-end chart-placeholder">
|
|
<div class="flex items-end justify-around h-[200px] px-5 placeholder-bars">
|
|
<div class="w-[30px] rounded-t bg-[linear-gradient(180deg,#FF8C42_0%,#FFB347_100%)] bar" v-for="i in 7" :key="i" :style="{ height: Math.random() * 80 + 20 + '%' }"></div>
|
|
</div>
|
|
<div class="flex justify-around pt-3 px-5 text-xs text-[#636E72] placeholder-labels">
|
|
<span v-for="day in ['一', '二', '三', '四', '五', '六', '日']" :key="day">周{{ day }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-[#F8F9FA] rounded-2xl p-5">
|
|
<div class="flex items-center gap-2 mb-5">
|
|
<AimOutlined class="text-xl text-[#FF8C42]" />
|
|
<h4 class="m-0 text-base font-600 text-[#2D3436]">教师活跃度</h4>
|
|
</div>
|
|
<div class="h-[250px] flex items-center justify-center chart-placeholder">
|
|
<div class="relative w-[180px] h-[180px] circle-chart">
|
|
<svg viewBox="0 0 100 100" class="w-full h-full">
|
|
<circle cx="50" cy="50" r="40" fill="none" stroke="#F0F0F0" stroke-width="12" />
|
|
<circle cx="50" cy="50" r="40" fill="none" stroke="url(#gradient1)" stroke-width="12"
|
|
stroke-dasharray="188 251" stroke-linecap="round" transform="rotate(-90 50 50)" />
|
|
<defs>
|
|
<linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
<stop offset="0%" style="stop-color:#667eea" />
|
|
<stop offset="100%" style="stop-color:#764ba2" />
|
|
</linearGradient>
|
|
</defs>
|
|
</svg>
|
|
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center circle-text">
|
|
<span class="block text-[32px] font-700 text-[#2D3436]">75%</span>
|
|
<span class="text-xs text-[#636E72]">活跃率</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 教师报告 -->
|
|
<div v-if="activeTab === 'teacher'">
|
|
<div class="grid gap-5 teacher-cards" style="grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));" v-if="teacherData.length > 0">
|
|
<div v-for="teacher in teacherData" :key="teacher.id" class="bg-[#F8F9FA] rounded-2xl p-5">
|
|
<div class="flex items-center gap-3 mb-4">
|
|
<div class="w-12 h-12 rounded-xl flex items-center justify-center text-2xl text-white bg-[linear-gradient(135deg,#667eea_0%,#764ba2_100%)]">
|
|
<SolutionOutlined />
|
|
</div>
|
|
<div>
|
|
<div class="text-base font-600 text-[#2D3436]">{{ teacher.name }}</div>
|
|
<div class="flex items-center gap-0.5 mt-1">
|
|
<StarFilled v-for="i in 5" :key="i" class="text-xs" :class="i <= Math.round(teacher.avgRating) ? 'text-[#FFB800]' : 'text-[#D0D0D0]'" />
|
|
<span class="ml-1 text-xs text-[#FF8C42] font-600">{{ teacher.avgRating.toFixed(1) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex justify-around py-4 border-t border-b border-[#E0E0E0]">
|
|
<div class="text-center">
|
|
<BookOutlined class="block text-xl mb-1 text-[#FF8C42]" />
|
|
<span class="text-xl font-700 text-[#2D3436]">{{ teacher.lessonCount }}</span>
|
|
<span class="text-[11px] text-[#636E72]">授课次数</span>
|
|
</div>
|
|
<div class="text-center">
|
|
<ReadOutlined class="block text-xl mb-1 text-[#FF8C42]" />
|
|
<span class="text-xl font-700 text-[#2D3436]">{{ teacher.courseCount }}</span>
|
|
<span class="text-[11px] text-[#636E72]">使用课程</span>
|
|
</div>
|
|
<div class="text-center">
|
|
<MessageOutlined class="block text-xl mb-1 text-[#FF8C42]" />
|
|
<span class="text-xl font-700 text-[#2D3436]">{{ teacher.feedbackCount }}</span>
|
|
<span class="text-[11px] text-[#636E72]">反馈次数</span>
|
|
</div>
|
|
</div>
|
|
<div class="pt-3 text-right">
|
|
<a-button type="link" @click="viewTeacherDetail(teacher)">
|
|
<FileTextOutlined class="mr-1" />
|
|
查看详情
|
|
</a-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<a-empty v-else description="暂无教师数据" />
|
|
</div>
|
|
|
|
<!-- 课程报告 -->
|
|
<div v-if="activeTab === 'course'">
|
|
<div class="flex flex-col gap-3" v-if="courseData.length > 0">
|
|
<div v-for="(course, index) in courseData" :key="course.id" class="flex items-center gap-4 py-4 px-5 bg-[#F8F9FA] rounded-xl transition-all duration-200 hover:bg-[#FFF8F0]">
|
|
<div class="w-10 h-10 rounded-[10px] flex items-center justify-center text-base font-600 bg-[#E0E0E0] text-[#636E72]" :class="index < 3 ? '' : ''">
|
|
<TrophyOutlined v-if="index < 3" :class="index === 0 ? 'text-[#FFD700]' : index === 1 ? 'text-[#C0C0C0]' : 'text-[#CD7F32]'" />
|
|
<span v-else>{{ index + 1 }}</span>
|
|
</div>
|
|
<div class="flex-1 min-w-0">
|
|
<div class="text-[15px] font-600 text-[#2D3436]">{{ course.name }}</div>
|
|
<div class="text-xs text-[#636E72] mt-1 flex gap-4">
|
|
<span><BookOutlined class="mr-1" />授课{{ course.lessonCount }}次</span>
|
|
<span><SolutionOutlined class="mr-1" />{{ course.teacherCount }}位教师</span>
|
|
<span><TeamOutlined class="mr-1" />{{ course.studentCount }}名学生</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-1">
|
|
<StarFilled class="text-lg text-[#FFB800]" />
|
|
<span class="text-base font-600 text-[#FF8C42]">{{ course.avgRating.toFixed(1) }}</span>
|
|
</div>
|
|
<a-button type="link" size="small" @click="viewCourseDetail(course)">详情</a-button>
|
|
</div>
|
|
</div>
|
|
<a-empty v-else description="暂无课程数据" />
|
|
</div>
|
|
|
|
<!-- 学生报告 -->
|
|
<div v-if="activeTab === 'student'">
|
|
<div class="grid gap-4 student-report-grid" style="grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));" v-if="studentData.length > 0">
|
|
<div v-for="student in studentData" :key="student.id" class="bg-[#F8F9FA] rounded-2xl p-4">
|
|
<div class="flex items-center gap-3 mb-4">
|
|
<div class="w-10 h-10 rounded-full flex items-center justify-center text-xl text-white bg-[linear-gradient(135deg,#f093fb_0%,#f5576c_100%)]">
|
|
<UserOutlined />
|
|
</div>
|
|
<div>
|
|
<div class="text-[15px] font-600 text-[#2D3436]">{{ student.name }}</div>
|
|
<div class="text-xs text-[#636E72]">{{ student.className }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-col gap-3">
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-xs text-[#636E72] min-w-[70px]"><BookOutlined class="mr-1" />参与课程</span>
|
|
<span class="text-xs font-600 text-[#2D3436] ml-auto">{{ student.lessonCount }} 次</span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-xs text-[#636E72] min-w-[70px]"><AimOutlined class="mr-1" />专注度</span>
|
|
<div class="flex-1 h-1.5 bg-[#E0E0E0] rounded overflow-hidden">
|
|
<div class="h-full rounded bg-[linear-gradient(90deg,#43e97b_0%,#38f9d7_100%)]" :style="{ width: student.avgFocus * 20 + '%' }"></div>
|
|
</div>
|
|
<span class="text-xs font-600 text-[#2D3436] ml-auto">{{ student.avgFocus }}/5</span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-xs text-[#636E72] min-w-[70px]"><FireOutlined class="mr-1" />参与度</span>
|
|
<div class="flex-1 h-1.5 bg-[#E0E0E0] rounded overflow-hidden">
|
|
<div class="h-full rounded bg-[linear-gradient(90deg,#f093fb_0%,#f5576c_100%)]" :style="{ width: student.avgParticipation * 20 + '%' }"></div>
|
|
</div>
|
|
<span class="text-xs font-600 text-[#2D3436] ml-auto">{{ student.avgParticipation }}/5</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<a-empty v-else description="暂无学生数据" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 教师详情弹窗 -->
|
|
<a-modal
|
|
v-model:open="teacherDetailVisible"
|
|
:title="`${selectedTeacher?.name} - 教师报告详情`"
|
|
width="600px"
|
|
:footer="null"
|
|
>
|
|
<div v-if="selectedTeacher" class="py-2">
|
|
<div class="flex items-center gap-4">
|
|
<div class="w-16 h-16 rounded-2xl flex items-center justify-center text-[32px] text-white bg-[linear-gradient(135deg,#667eea_0%,#764ba2_100%)]">
|
|
<SolutionOutlined />
|
|
</div>
|
|
<div>
|
|
<h3 class="m-0 text-xl font-600 text-[#2D3436]">{{ selectedTeacher.name }}</h3>
|
|
<div class="flex items-center gap-1 mt-2 detail-rating">
|
|
<StarFilled v-for="i in 5" :key="i" class="text-base" :class="i <= Math.round(selectedTeacher.avgRating) ? 'text-[#FFB800]' : 'text-[#D0D0D0]'" />
|
|
<span class="ml-2 text-sm text-[#FF8C42] font-600">{{ selectedTeacher.avgRating.toFixed(1) }} 分</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<a-divider />
|
|
<div class="grid grid-cols-3 gap-4 text-center">
|
|
<div class="p-4 bg-[#F8F9FA] rounded-xl">
|
|
<div class="text-[32px] font-700 text-[#FF8C42]">{{ selectedTeacher.lessonCount }}</div>
|
|
<div class="text-[13px] text-[#636E72] mt-1">授课次数</div>
|
|
</div>
|
|
<div class="p-4 bg-[#F8F9FA] rounded-xl">
|
|
<div class="text-[32px] font-700 text-[#FF8C42]">{{ selectedTeacher.courseCount }}</div>
|
|
<div class="text-[13px] text-[#636E72] mt-1">使用课程数</div>
|
|
</div>
|
|
<div class="p-4 bg-[#F8F9FA] rounded-xl">
|
|
<div class="text-[32px] font-700 text-[#FF8C42]">{{ selectedTeacher.feedbackCount }}</div>
|
|
<div class="text-[13px] text-[#636E72] mt-1">反馈次数</div>
|
|
</div>
|
|
</div>
|
|
<a-divider />
|
|
<div>
|
|
<h4 class="m-0 mb-3 text-[15px] font-600 text-[#2D3436] flex items-center"><BookOutlined class="mr-2" />教学概况</h4>
|
|
<p class="m-0 text-sm text-[#636E72] leading-[1.8]">
|
|
{{ selectedTeacher.name }} 老师共完成 {{ selectedTeacher.lessonCount }} 次授课,
|
|
使用了 {{ selectedTeacher.courseCount }} 门不同的课程,
|
|
累计获得 {{ selectedTeacher.feedbackCount }} 次教学反馈。
|
|
<template v-if="selectedTeacher.avgRating > 0">
|
|
平均评分为 {{ selectedTeacher.avgRating.toFixed(1) }} 分,
|
|
{{ selectedTeacher.avgRating >= 4.5 ? '教学效果优秀!' : selectedTeacher.avgRating >= 3.5 ? '教学效果良好。' : '继续努力!' }}
|
|
</template>
|
|
<template v-else>
|
|
暂无评分数据。
|
|
</template>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</a-modal>
|
|
|
|
<!-- 课程详情弹窗 -->
|
|
<a-modal
|
|
v-model:open="courseDetailVisible"
|
|
:title="`${selectedCourse?.name} - 课程报告详情`"
|
|
width="600px"
|
|
:footer="null"
|
|
>
|
|
<div v-if="selectedCourse" class="py-2">
|
|
<div class="flex items-center gap-4">
|
|
<div class="w-16 h-16 rounded-2xl flex items-center justify-center text-[32px] text-white bg-[linear-gradient(135deg,#43e97b_0%,#38f9d7_100%)]">
|
|
<ReadOutlined />
|
|
</div>
|
|
<div>
|
|
<h3 class="m-0 text-xl font-600 text-[#2D3436]">{{ selectedCourse.name }}</h3>
|
|
<div class="flex items-center gap-1 mt-2">
|
|
<StarFilled v-for="i in 5" :key="i" class="text-base" :class="i <= Math.round(selectedCourse.avgRating) ? 'text-[#FFB800]' : 'text-[#D0D0D0]'" />
|
|
<span class="ml-2 text-sm text-[#FF8C42] font-600">{{ selectedCourse.avgRating.toFixed(1) }} 分</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<a-divider />
|
|
<div class="grid grid-cols-3 gap-4 text-center">
|
|
<div class="p-4 bg-[#F8F9FA] rounded-xl">
|
|
<div class="text-[32px] font-700 text-[#FF8C42]">{{ selectedCourse.lessonCount }}</div>
|
|
<div class="text-[13px] text-[#636E72] mt-1">授课次数</div>
|
|
</div>
|
|
<div class="p-4 bg-[#F8F9FA] rounded-xl">
|
|
<div class="text-[32px] font-700 text-[#FF8C42]">{{ selectedCourse.teacherCount }}</div>
|
|
<div class="text-[13px] text-[#636E72] mt-1">授课教师</div>
|
|
</div>
|
|
<div class="p-4 bg-[#F8F9FA] rounded-xl">
|
|
<div class="text-[32px] font-700 text-[#FF8C42]">{{ selectedCourse.studentCount }}</div>
|
|
<div class="text-[13px] text-[#636E72] mt-1">覆盖学生</div>
|
|
</div>
|
|
</div>
|
|
<a-divider />
|
|
<div>
|
|
<h4 class="m-0 mb-3 text-[15px] font-600 text-[#2D3436] flex items-center"><ReadOutlined class="mr-2" />课程概况</h4>
|
|
<p class="m-0 text-sm text-[#636E72] leading-[1.8]">
|
|
《{{ selectedCourse.name }}》共被授课 {{ selectedCourse.lessonCount }} 次,
|
|
有 {{ selectedCourse.teacherCount }} 位教师使用该课程进行教学,
|
|
累计覆盖 {{ selectedCourse.studentCount }} 名学生。
|
|
<template v-if="selectedCourse.avgRating > 0">
|
|
课程平均评分为 {{ selectedCourse.avgRating.toFixed(1) }} 分,
|
|
{{ selectedCourse.avgRating >= 4.5 ? '深受师生好评!' : selectedCourse.avgRating >= 3.5 ? '反馈良好。' : '有待改进。' }}
|
|
</template>
|
|
<template v-else>
|
|
暂无评分数据。
|
|
</template>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</a-modal>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted } from 'vue';
|
|
import { message } from 'ant-design-vue';
|
|
import dayjs, { Dayjs } from 'dayjs';
|
|
import {
|
|
BarChartOutlined,
|
|
DownloadOutlined,
|
|
BookOutlined,
|
|
SolutionOutlined,
|
|
ReadOutlined,
|
|
StarFilled,
|
|
LineChartOutlined,
|
|
AimOutlined,
|
|
MessageOutlined,
|
|
FileTextOutlined,
|
|
TeamOutlined,
|
|
UserOutlined,
|
|
TrophyOutlined,
|
|
FireOutlined,
|
|
} from '@ant-design/icons-vue';
|
|
import {
|
|
getReportOverview,
|
|
getTeacherReports,
|
|
getCourseReports,
|
|
getStudentReports,
|
|
type TeacherReport,
|
|
type CourseReport,
|
|
type StudentReport,
|
|
} from '@/api/school';
|
|
|
|
const activeTab = ref('overview');
|
|
const dateRange = ref<[Dayjs, Dayjs] | undefined>(undefined);
|
|
const loading = ref(false);
|
|
|
|
const tabs = [
|
|
{ key: 'overview', label: '整体概览', icon: 'bar-chart' },
|
|
{ key: 'teacher', label: '教师报告', icon: 'solution' },
|
|
{ key: 'course', label: '课程报告', icon: 'read' },
|
|
{ key: 'student', label: '学生报告', icon: 'team' },
|
|
];
|
|
|
|
// 概览数据
|
|
const overviewData = ref({
|
|
totalLessons: 0,
|
|
activeTeacherCount: 0,
|
|
usedCourseCount: 0,
|
|
avgRating: 0,
|
|
});
|
|
|
|
const teacherData = ref<TeacherReport[]>([]);
|
|
const courseData = ref<CourseReport[]>([]);
|
|
const studentData = ref<StudentReport[]>([]);
|
|
|
|
const totalLessons = computed(() => overviewData.value.totalLessons);
|
|
const activeTeacherCount = computed(() => overviewData.value.activeTeacherCount);
|
|
const usedCourseCount = computed(() => overviewData.value.usedCourseCount);
|
|
const avgRating = computed(() => overviewData.value.avgRating.toFixed(1));
|
|
|
|
// 加载数据
|
|
const loadData = async () => {
|
|
loading.value = true;
|
|
try {
|
|
const [overview, teachers, courses, students] = await Promise.all([
|
|
getReportOverview(),
|
|
getTeacherReports(),
|
|
getCourseReports(),
|
|
getStudentReports(),
|
|
]);
|
|
overviewData.value = overview;
|
|
teacherData.value = teachers;
|
|
courseData.value = courses;
|
|
studentData.value = students;
|
|
} catch (error: any) {
|
|
message.error(error.response?.data?.message || '加载数据失败');
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
// 教师详情弹窗
|
|
const teacherDetailVisible = ref(false);
|
|
const selectedTeacher = ref<TeacherReport | null>(null);
|
|
|
|
// 课程详情弹窗
|
|
const courseDetailVisible = ref(false);
|
|
const selectedCourse = ref<CourseReport | null>(null);
|
|
|
|
const viewTeacherDetail = (teacher: TeacherReport) => {
|
|
selectedTeacher.value = teacher;
|
|
teacherDetailVisible.value = true;
|
|
};
|
|
|
|
const viewCourseDetail = (course: CourseReport) => {
|
|
selectedCourse.value = course;
|
|
courseDetailVisible.value = true;
|
|
};
|
|
|
|
onMounted(() => {
|
|
loadData();
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* 仅保留无法用原子类表达的响应式覆盖(如需可在此添加 :deep 等) */
|
|
</style>
|