|
@@ -0,0 +1,503 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="p-6 flex justify-center">
|
|
|
|
|
+ <div class="w-full mx-auto min-h-screen">
|
|
|
|
|
+ <!-- 顶部筛选区 -->
|
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm p-6 mb-6 h-25 flex items-center">
|
|
|
|
|
+ <!-- 课程ID输入 -->
|
|
|
|
|
+ <div class="flex-1 mr-4">
|
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">课程ID</label>
|
|
|
|
|
+ <a-select class="w-full" v-model:value="filters.courseId" placeholder="请选择课程" allow-clear>
|
|
|
|
|
+ <a-select-option v-for="item in courseinfoAllListOptions" :key="item.courseId" :value="item.courseId">
|
|
|
|
|
+ {{ item.courseName }}
|
|
|
|
|
+ </a-select-option>
|
|
|
|
|
+ </a-select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 日期范围选择 -->
|
|
|
|
|
+<!-- <div class="flex-1 mr-4">-->
|
|
|
|
|
+<!-- <label class="block text-sm font-medium text-gray-700 mb-1">开始时间</label>-->
|
|
|
|
|
+<!-- <a-date-picker v-model:value="startDate" class="w-full" placeholder="开始时间" />-->
|
|
|
|
|
+<!-- </div>-->
|
|
|
|
|
+
|
|
|
|
|
+<!-- <div class="flex-1 mr-4">-->
|
|
|
|
|
+<!-- <label class="block text-sm font-medium text-gray-700 mb-1">结束时间</label>-->
|
|
|
|
|
+<!-- <a-date-picker v-model:value="endDate" class="w-full" placeholder="结束时间" />-->
|
|
|
|
|
+<!-- </div>-->
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 操作按钮 -->
|
|
|
|
|
+ <div class="flex space-x-2 ml-4 mt-6">
|
|
|
|
|
+ <a-button @click="refreshData" :loading="loading">
|
|
|
|
|
+ <template #icon>
|
|
|
|
|
+ <ReloadOutlined />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ 刷新
|
|
|
|
|
+ </a-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 核心数据看板 -->
|
|
|
|
|
+ <div class="mb-6">
|
|
|
|
|
+ <!-- 数据卡片 -->
|
|
|
|
|
+ <div class="grid grid-cols-4 gap-6 mb-6">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(card, index) in statsCards"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ class="card-header bg-white p-5 rounded-lg shadow-sm"
|
|
|
|
|
+ :class="card.borderClass"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="flex justify-between items-start">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div class="text-gray-500 text-sm">{{ card.title }}</div>
|
|
|
|
|
+ <div class="font-size-30 font-bold text-gray-800 mt-2">{{ card.value.toLocaleString() }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div :class="card.iconBgClass" class="p-2 rounded-full">
|
|
|
|
|
+ <component :is="card.icon" :class="card.iconClass" class="text-xl" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 图表区 -->
|
|
|
|
|
+ <div class="grid grid-cols-2 gap-6">
|
|
|
|
|
+ <!-- 折线图 -->
|
|
|
|
|
+ <div class="chart-container p-4 bg-white border border-gray-200 rounded">
|
|
|
|
|
+ <h3 class="font-bold text-gray-800 mb-4">访问人数趋势</h3>
|
|
|
|
|
+ <div ref="lineChartRef" class="h-48"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 柱状图 -->
|
|
|
|
|
+ <div class="chart-container p-4 bg-white border border-gray-200 rounded">
|
|
|
|
|
+ <h3 class="font-bold text-gray-800 mb-4">练习平均提交数</h3>
|
|
|
|
|
+ <div ref="barChartRef" class="h-48"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 明细表格区 -->
|
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm p-6">
|
|
|
|
|
+ <div class="flex justify-between items-center mb-4">
|
|
|
|
|
+ <h2 class="text-xl font-bold text-gray-800">学习明细数据</h2>
|
|
|
|
|
+<!-- <div class="text-sm text-gray-500">({{ formatDateRange() }})注:从查询条件时间范围落下来的</div>-->
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <a-table
|
|
|
|
|
+ :columns="tableColumns"
|
|
|
|
|
+ :data-source="tableData"
|
|
|
|
|
+ :pagination="pagination"
|
|
|
|
|
+ :loading="loading"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #bodyCell="{ column, text, record }">
|
|
|
|
|
+ <!-- 状态列 -->
|
|
|
|
|
+ <template v-if="column.key === 'viewCount'" >
|
|
|
|
|
+ <span @click="onItemStudyDetailCourseView(record)">
|
|
|
|
|
+ {{text}}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <template v-if="column.key === 'paperSubmitNum'" >
|
|
|
|
|
+ <span @click="onItemStudyDetailPracticeResult(record)">
|
|
|
|
|
+ {{text}}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </a-table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <ListViewlistViewStudyDetailCourseView ref="listViewListViewlistViewStudyDetailCourseViewRef"></ListViewlistViewStudyDetailCourseView>
|
|
|
|
|
+ <ListViewStudyDetailPracticeResult ref="listViewStudyDetailPracticeResultRef"></ListViewStudyDetailPracticeResult>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+
|
|
|
|
|
+import { ref, reactive, onMounted, onUnmounted, nextTick, watch } from 'vue'
|
|
|
|
|
+ import { ReloadOutlined, UserOutlined, EyeOutlined, FileTextOutlined, MessageOutlined } from '@ant-design/icons-vue'
|
|
|
|
|
+ import * as echarts from 'echarts'
|
|
|
|
|
+ import dayjs from 'dayjs'
|
|
|
|
|
+ import { overviewLearningProgressApi } from '@/api/statisticalAnalysis/overviewLearningProgress'
|
|
|
|
|
+ import { courseinfoAllList } from '@/api/semester/index.js'
|
|
|
|
|
+ import ListViewlistViewStudyDetailCourseView from './listViewStudyDetailCourseView.vue'
|
|
|
|
|
+ import ListViewStudyDetailPracticeResult from './listViewStudyDetailPracticeResult.vue'
|
|
|
|
|
+
|
|
|
|
|
+ import { message } from 'ant-design-vue'
|
|
|
|
|
+
|
|
|
|
|
+ // 响应式数据
|
|
|
|
|
+ const loading = ref(false)
|
|
|
|
|
+ const lineChartRef = ref(null)
|
|
|
|
|
+ const listViewListViewlistViewStudyDetailCourseViewRef = ref(null)
|
|
|
|
|
+ const listViewStudyDetailPracticeResultRef = ref(null)
|
|
|
|
|
+ const barChartRef = ref(null)
|
|
|
|
|
+ const courseinfoAllListOptions = ref([])
|
|
|
|
|
+ let lineChart = null
|
|
|
|
|
+ let barChart = null
|
|
|
|
|
+
|
|
|
|
|
+ // 日期选择器
|
|
|
|
|
+ // const startDate = ref(dayjs('2025-08-04'))
|
|
|
|
|
+ // const endDate = ref(dayjs('2025-08-10'))
|
|
|
|
|
+ const startDate = ref(undefined)
|
|
|
|
|
+ const endDate = ref(undefined)
|
|
|
|
|
+
|
|
|
|
|
+ // 筛选条件
|
|
|
|
|
+ const filters = reactive({
|
|
|
|
|
+ courseId: '',
|
|
|
|
|
+ startTime: undefined,
|
|
|
|
|
+ endTime: undefined,
|
|
|
|
|
+
|
|
|
|
|
+ })
|
|
|
|
|
+ // 分页配置
|
|
|
|
|
+ const pagination = reactive({
|
|
|
|
|
+ current: 1,
|
|
|
|
|
+ size: 10,
|
|
|
|
|
+ total: 0,
|
|
|
|
|
+ showSizeChanger: true,
|
|
|
|
|
+ showQuickJumper: true,
|
|
|
|
|
+ showTotal: (total, range) => `显示 ${range[0]}-${range[1]} 条,共 ${total} 条`,
|
|
|
|
|
+ onChange: (page, pageSize) => {
|
|
|
|
|
+ pagination.current = page
|
|
|
|
|
+ pagination.size = pageSize
|
|
|
|
|
+ fetchStudyDetail()
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ // 统计卡片数据
|
|
|
|
|
+ const statsCards = reactive([
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '开课人数',
|
|
|
|
|
+ value: 0,
|
|
|
|
|
+ key: 'courseOpenStuNum',
|
|
|
|
|
+ icon: UserOutlined,
|
|
|
|
|
+ borderClass: 'border-l-4 !border-blue-500',
|
|
|
|
|
+ iconBgClass: 'bg-blue-100',
|
|
|
|
|
+ iconClass: 'text-blue-600'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '课程访问次数',
|
|
|
|
|
+ value: 0,
|
|
|
|
|
+ key: 'courseViewNum',
|
|
|
|
|
+ icon: EyeOutlined,
|
|
|
|
|
+ borderClass: 'border-l-4 !border-green-500',
|
|
|
|
|
+ iconBgClass: 'bg-green-100',
|
|
|
|
|
+ iconClass: 'text-green-600'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '提交数(作业、测验)',
|
|
|
|
|
+ value: 0,
|
|
|
|
|
+ key: 'paperSubmitNum',
|
|
|
|
|
+ icon: FileTextOutlined,
|
|
|
|
|
+ borderClass: 'border-l-4 !border-orange-500',
|
|
|
|
|
+ iconBgClass: 'bg-orange-100',
|
|
|
|
|
+ iconClass: 'text-orange-500'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '互动数(发帖/回帖)',
|
|
|
|
|
+ value: 0,
|
|
|
|
|
+ key: 'interactionNum',
|
|
|
|
|
+ icon: MessageOutlined,
|
|
|
|
|
+ borderClass: 'border-l-4 !border-blue-500',
|
|
|
|
|
+ iconBgClass: 'bg-blue-100',
|
|
|
|
|
+ iconClass: 'text-blue-600'
|
|
|
|
|
+ }
|
|
|
|
|
+ ])
|
|
|
|
|
+
|
|
|
|
|
+ const tableColumns = [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '课程名称',
|
|
|
|
|
+ dataIndex: 'courseName',
|
|
|
|
|
+ key: 'courseName'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '开课人数',
|
|
|
|
|
+ dataIndex: 'openStuNum',
|
|
|
|
|
+ key: 'openStuNum'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '课程访问量',
|
|
|
|
|
+ dataIndex: 'viewCount',
|
|
|
|
|
+ key: 'viewCount'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '讲义访问量',
|
|
|
|
|
+ dataIndex: 'teachMaterialsNum',
|
|
|
|
|
+ key: 'teachMaterialsNum'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '练习提交量',
|
|
|
|
|
+ dataIndex: 'paperSubmitNum',
|
|
|
|
|
+ key: 'paperSubmitNum'
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ // 图表数据
|
|
|
|
|
+ const chartData = reactive({
|
|
|
|
|
+ visitTrend: [],
|
|
|
|
|
+ submissionTrend: []
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 表格数据
|
|
|
|
|
+ const tableData = ref([])
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ const onItemStudyDetailCourseView = (record) => {
|
|
|
|
|
+ // console.log('123123点了什么123123',record,' listViewRef ',listViewRef)
|
|
|
|
|
+ listViewListViewlistViewStudyDetailCourseViewRef.value.open(record.courseId)
|
|
|
|
|
+ }
|
|
|
|
|
+ const onItemStudyDetailPracticeResult = (record) => {
|
|
|
|
|
+
|
|
|
|
|
+ listViewStudyDetailPracticeResultRef.value.open(record.courseId)
|
|
|
|
|
+ // console.log('点了什么',record)
|
|
|
|
|
+ // const params = {
|
|
|
|
|
+ // courseId: record.courseId,
|
|
|
|
|
+ // startTime: undefined,
|
|
|
|
|
+ // endTime: undefined,
|
|
|
|
|
+ // size : 99999,
|
|
|
|
|
+ // current : 1
|
|
|
|
|
+ // }
|
|
|
|
|
+ // overviewLearningProgressApi.getStudyDetailPracticeResult(params).then((res)=>{
|
|
|
|
|
+ //
|
|
|
|
|
+ // })
|
|
|
|
|
+ }
|
|
|
|
|
+ // 初始化折线图
|
|
|
|
|
+ const initLineChart = () => {
|
|
|
|
|
+ if (!lineChartRef.value) return
|
|
|
|
|
+
|
|
|
|
|
+ lineChart = echarts.init(lineChartRef.value)
|
|
|
|
|
+ updateLineChart()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 更新折线图数据
|
|
|
|
|
+ const updateLineChart = () => {
|
|
|
|
|
+ if (!lineChart) return
|
|
|
|
|
+
|
|
|
|
|
+ const option = {
|
|
|
|
|
+ grid: { top: 30, right: 20, bottom: 20, left: 40 },
|
|
|
|
|
+ tooltip: { trigger: 'axis' },
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ data: chartData.visitTrend.map((item) => item.month)
|
|
|
|
|
+ },
|
|
|
|
|
+ yAxis: { type: 'value', name: '访问人数' },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '访问人数',
|
|
|
|
|
+ type: 'line',
|
|
|
|
|
+ smooth: true,
|
|
|
|
|
+ symbol: 'circle',
|
|
|
|
|
+ symbolSize: 8,
|
|
|
|
|
+ data: chartData.visitTrend.map((item) => item.viewTendencyNum),
|
|
|
|
|
+ lineStyle: { color: '#3A7BFF', width: 3 },
|
|
|
|
|
+ itemStyle: { color: '#3A7BFF' },
|
|
|
|
|
+ areaStyle: {
|
|
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
+ { offset: 0, color: 'rgba(58, 123, 255, 0.3)' },
|
|
|
|
|
+ { offset: 1, color: 'rgba(58, 123, 255, 0.05)' }
|
|
|
|
|
+ ])
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ lineChart.setOption(option)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化柱状图
|
|
|
|
|
+ const initBarChart = () => {
|
|
|
|
|
+ if (!barChartRef.value) return
|
|
|
|
|
+
|
|
|
|
|
+ barChart = echarts.init(barChartRef.value)
|
|
|
|
|
+ updateBarChart()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 更新柱状图数据
|
|
|
|
|
+ const updateBarChart = () => {
|
|
|
|
|
+ if (!barChart) return
|
|
|
|
|
+
|
|
|
|
|
+ const option = {
|
|
|
|
|
+ grid: { top: 30, right: 20, bottom: 20, left: 40 },
|
|
|
|
|
+ tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ data: chartData.submissionTrend.map((item) => item.month)
|
|
|
|
|
+ },
|
|
|
|
|
+ yAxis: { type: 'value', name: '提交数' },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '练习提交数',
|
|
|
|
|
+ type: 'bar',
|
|
|
|
|
+ barWidth: 28,
|
|
|
|
|
+ data: chartData.submissionTrend.map((item) => item.SubmeitTendency),
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
+ { offset: 0, color: '#5B8EFF' },
|
|
|
|
|
+ { offset: 1, color: '#3A7BFF' }
|
|
|
|
|
+ ]),
|
|
|
|
|
+ borderRadius: [4, 4, 0, 0]
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ barChart.setOption(option)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 格式化日期范围显示
|
|
|
|
|
+ const formatDateRange = () => {
|
|
|
|
|
+ const start = startDate.value ? startDate.value.format('YYYY/MM/DD') : filters.startTime
|
|
|
|
|
+ const end = endDate.value ? endDate.value.format('YYYY/MM/DD') : filters.endTime
|
|
|
|
|
+ return `${start}至${end}`
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取统计概览数据
|
|
|
|
|
+ const fetchOverviewStats = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ courseId: filters.courseId,
|
|
|
|
|
+ startTime: startDate.value ? startDate.value.format('YYYY-MM-DD') : filters.startTime,
|
|
|
|
|
+ endTime: endDate.value ? endDate.value.format('YYYY-MM-DD') : filters.endTime
|
|
|
|
|
+ }
|
|
|
|
|
+ const data = await overviewLearningProgressApi.getTopFundamentalDetail(params)
|
|
|
|
|
+ // 更新统计卡片数据
|
|
|
|
|
+ statsCards.forEach((card) => {
|
|
|
|
|
+ card.value = data[card.key] || 0
|
|
|
|
|
+ })
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取统计数据失败:', error)
|
|
|
|
|
+ message.error('获取统计数据失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取趋势数据
|
|
|
|
|
+ const fetchTrendData = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ courseId: filters.courseId,
|
|
|
|
|
+ startTime: startDate.value ? startDate.value.format('YYYY-MM-DD') : filters.startTime,
|
|
|
|
|
+ endTime: endDate.value ? endDate.value.format('YYYY-MM-DD') : filters.endTime
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 并行获取访问趋势和提交趋势数据
|
|
|
|
|
+ const [visitData, submitData] = await Promise.all([
|
|
|
|
|
+ overviewLearningProgressApi.getViewTendency(params),
|
|
|
|
|
+ overviewLearningProgressApi.getPaperSubmitTendency(params)
|
|
|
|
|
+ ])
|
|
|
|
|
+
|
|
|
|
|
+ // 更新图表数据
|
|
|
|
|
+ chartData.visitTrend = visitData || []
|
|
|
|
|
+ chartData.submissionTrend = submitData || []
|
|
|
|
|
+
|
|
|
|
|
+ // 更新图表
|
|
|
|
|
+ updateLineChart()
|
|
|
|
|
+ updateBarChart()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取趋势数据失败:', error)
|
|
|
|
|
+ message.error('获取趋势数据失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取学习明细数据
|
|
|
|
|
+ const fetchStudyDetail = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ courseId: filters.courseId,
|
|
|
|
|
+ current : pagination.current,
|
|
|
|
|
+ size : pagination.size,
|
|
|
|
|
+ startTime: startDate.value ? startDate.value.format('YYYY-MM-DD') : filters.startTime,
|
|
|
|
|
+ endTime: endDate.value ? endDate.value.format('YYYY-MM-DD') : filters.endTime
|
|
|
|
|
+ }
|
|
|
|
|
+ const data = await overviewLearningProgressApi.getStudyDetail(params)
|
|
|
|
|
+
|
|
|
|
|
+ pagination.current = data.current
|
|
|
|
|
+ pagination.total = data.total
|
|
|
|
|
+
|
|
|
|
|
+ // 更新表格数据
|
|
|
|
|
+ tableData.value = (data.records || []).map((item, index) => ({
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ key: item.courseId || index + 1
|
|
|
|
|
+ }))
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取学习明细数据失败:', error)
|
|
|
|
|
+ message.error('获取学习明细数据失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 刷新所有数据
|
|
|
|
|
+ const refreshData = async () => {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 更新筛选条件
|
|
|
|
|
+ filters.startTime = startDate.value ? startDate.value.format('YYYY-MM-DD') : filters.startTime
|
|
|
|
|
+ filters.endTime = endDate.value ? endDate.value.format('YYYY-MM-DD') : filters.endTime
|
|
|
|
|
+
|
|
|
|
|
+ await Promise.all([fetchOverviewStats(), fetchTrendData(), fetchStudyDetail()])
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('刷新数据失败:', error)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 窗口大小变化处理
|
|
|
|
|
+ const handleResize = () => {
|
|
|
|
|
+ if (lineChart) lineChart.resize()
|
|
|
|
|
+ if (barChart) barChart.resize()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const getCourseinfoAllList = () => {
|
|
|
|
|
+ courseinfoAllList()
|
|
|
|
|
+ .then((res) => {
|
|
|
|
|
+ courseinfoAllListOptions.value = res.data
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((err) => {
|
|
|
|
|
+ console.log(err)
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 监听筛选条件变化
|
|
|
|
|
+ watch(
|
|
|
|
|
+ () => [filters.courseId, startDate.value, endDate.value],
|
|
|
|
|
+ () => {
|
|
|
|
|
+ refreshData()
|
|
|
|
|
+ },
|
|
|
|
|
+ { deep: true }
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // 生命周期
|
|
|
|
|
+ onMounted(async () => {
|
|
|
|
|
+ await nextTick()
|
|
|
|
|
+ initLineChart()
|
|
|
|
|
+ initBarChart()
|
|
|
|
|
+ window.addEventListener('resize', handleResize)
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化数据
|
|
|
|
|
+ await refreshData()
|
|
|
|
|
+ getCourseinfoAllList()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ onUnmounted(() => {
|
|
|
|
|
+ if (lineChart) {
|
|
|
|
|
+ lineChart.dispose()
|
|
|
|
|
+ lineChart = null
|
|
|
|
|
+ }
|
|
|
|
|
+ if (barChart) {
|
|
|
|
|
+ barChart.dispose()
|
|
|
|
|
+ barChart = null
|
|
|
|
|
+ }
|
|
|
|
|
+ window.removeEventListener('resize', handleResize)
|
|
|
|
|
+ })
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+ .card-header {
|
|
|
|
|
+ border-left: 4px solid;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .chart-container {
|
|
|
|
|
+ background-color: white;
|
|
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .font-size-30 {
|
|
|
|
|
+ font-size: 30px;
|
|
|
|
|
+ }
|
|
|
|
|
+</style>
|