index.vue 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. <template>
  2. <div class="platform-status">
  3. <div class="header">
  4. <h1>平台运行状态总览</h1>
  5. <p>实时掌握全院课程与人员信息、热门课程、开课动态与访问分析</p>
  6. </div>
  7. <!-- 统计卡片 -->
  8. <div class="stats-grid">
  9. <div class="stat-card">
  10. <div class="stat-title">当前创建课程总数</div>
  11. <div class="stat-value">{{ platformStats.totalCourses }}</div>
  12. </div>
  13. <div class="stat-card">
  14. <div class="stat-title">开课总数</div>
  15. <div class="stat-value">{{ platformStats.openedCourses }}</div>
  16. </div>
  17. <div class="stat-card">
  18. <div class="stat-title">教员总数</div>
  19. <div class="stat-value">{{ platformStats.totalTeachers }}</div>
  20. </div>
  21. <div class="stat-card">
  22. <div class="stat-title">学员总数</div>
  23. <div class="stat-value">{{ platformStats.totalStudents.toLocaleString() }}</div>
  24. </div>
  25. </div>
  26. <!-- 热门课程分析 -->
  27. <div class="section">
  28. <h2>受欢迎课程 & 引用最受欢迎的课程统计</h2>
  29. <a-table :columns="hotCoursesColumns" :data-source="hotCoursesData" :pagination="false" row-key="id" />
  30. </div>
  31. <!-- 开课信息列表 -->
  32. <div class="section">
  33. <h2>全院开课信息</h2>
  34. <a-table
  35. :columns="courseInfoColumns"
  36. :data-source="courseInfoData"
  37. :pagination="{
  38. current: courseInfoPagination.current,
  39. pageSize: courseInfoPagination.pageSize,
  40. total: courseInfoPagination.total,
  41. showSizeChanger: true,
  42. showQuickJumper: true,
  43. showTotal: (total) => `共 ${total} 条记录`,
  44. onChange: handlePageChange,
  45. onShowSizeChange: handlePageChange
  46. }"
  47. row-key="id"
  48. />
  49. </div>
  50. <!-- 访问统计弹窗 -->
  51. <a-modal
  52. v-model:visible="visitModalVisible"
  53. :title="modalTitle"
  54. width="500px"
  55. :footer="null"
  56. @cancel="closeVisitModal"
  57. >
  58. <p style="color: #7f8c8d; margin: 10px 0; text-align: center">显示近7天的访问数据趋势</p>
  59. <div ref="visitChart" style="width: 100%; height: 320px; margin-top: 20px"></div>
  60. </a-modal>
  61. </div>
  62. </template>
  63. <script setup>
  64. import { ref, reactive, nextTick, onMounted } from 'vue'
  65. import * as echarts from 'echarts'
  66. import {
  67. getPlatformStats,
  68. getHotCourses,
  69. getCourseInfo,
  70. getCourseVisitStats
  71. } from '@/api/statisticalAnalysis/platformStatusOverview'
  72. // 平台统计数据
  73. const platformStats = reactive({
  74. totalCourses: 0,
  75. openedCourses: 0,
  76. totalTeachers: 0,
  77. totalStudents: 0
  78. })
  79. // 热门课程表格列定义
  80. const hotCoursesColumns = [
  81. {
  82. title: '排名',
  83. dataIndex: 'rank',
  84. key: 'rank',
  85. width: 80,
  86. align: 'center'
  87. },
  88. {
  89. title: '课程名称',
  90. dataIndex: 'name',
  91. key: 'name'
  92. },
  93. {
  94. title: '主讲教员',
  95. dataIndex: 'teacher',
  96. key: 'teacher'
  97. },
  98. {
  99. title: '访问量',
  100. dataIndex: 'visits',
  101. key: 'visits',
  102. align: 'center'
  103. },
  104. {
  105. title: '被引用次数',
  106. dataIndex: 'references',
  107. key: 'references',
  108. align: 'center'
  109. }
  110. ]
  111. // 热门课程数据
  112. const hotCoursesData = ref([])
  113. // 开课信息表格列定义
  114. const courseInfoColumns = [
  115. {
  116. title: '课程名称',
  117. dataIndex: 'name',
  118. key: 'name'
  119. },
  120. {
  121. title: '主讲教员',
  122. dataIndex: 'teacher',
  123. key: 'teacher'
  124. },
  125. {
  126. title: '开课时间',
  127. dataIndex: 'startDate',
  128. key: 'startDate'
  129. },
  130. {
  131. title: '学员数',
  132. dataIndex: 'studentCount',
  133. key: 'studentCount',
  134. align: 'center'
  135. },
  136. {
  137. title: '访问量',
  138. dataIndex: 'visits',
  139. key: 'visits',
  140. align: 'center'
  141. },
  142. {
  143. title: '统计分析',
  144. key: 'action',
  145. align: 'center',
  146. customRender: ({ record }) => {
  147. console.log("分析呢",record)
  148. return h('button', { class: 'btn-view', onClick: () => showVisitModal(record.id,record.name) }, '查看')
  149. }
  150. }
  151. ]
  152. // 开课信息数据
  153. const courseInfoData = ref([])
  154. const courseInfoPagination = reactive({
  155. current: 1,
  156. pageSize: 10,
  157. total: 0
  158. })
  159. // 弹窗相关
  160. const visitModalVisible = ref(false)
  161. const modalTitle = ref('')
  162. const visitChart = ref(null)
  163. let chartInstance = null
  164. // 加载数据的方法
  165. const loadPlatformStats = async () => {
  166. try {
  167. const response = await getPlatformStats()
  168. Object.assign(platformStats, response.data)
  169. } catch (error) {
  170. console.error('获取平台统计数据失败:', error)
  171. }
  172. }
  173. const loadHotCourses = async () => {
  174. try {
  175. const response = await getHotCourses()
  176. hotCoursesData.value = response.data
  177. } catch (error) {
  178. console.error('获取热门课程数据失败:', error)
  179. }
  180. }
  181. const loadCourseInfo = async (params = {}) => {
  182. try {
  183. const response = await getCourseInfo(params)
  184. console.log("888",response)
  185. courseInfoData.value = response.data.records
  186. courseInfoPagination.total = response.data.total
  187. courseInfoPagination.current = response.data.current
  188. } catch (error) {
  189. console.error('获取开课信息失败:', error)
  190. }
  191. }
  192. // 分页变化处理
  193. const handlePageChange = (page, pageSize) => {
  194. courseInfoPagination.current = page
  195. courseInfoPagination.pageSize = pageSize
  196. loadCourseInfo({
  197. current: page,
  198. size: pageSize
  199. })
  200. }
  201. // 显示访问统计弹窗
  202. const showVisitModal = async (courseId,name) => {
  203. modalTitle.value = `${name} - 近7天访问统计`
  204. visitModalVisible.value = true
  205. try {
  206. const response = await getCourseVisitStats({ courseId })
  207. console.log('如家七天',response)
  208. nextTick(() => {
  209. initVisitChart(response.data)
  210. })
  211. } catch (error) {
  212. console.error('获取课程访问统计失败:', error)
  213. }
  214. }
  215. // 初始化访问统计图表
  216. const initVisitChart = (data) => {
  217. if (!visitChart.value) return
  218. if (chartInstance) {
  219. chartInstance.dispose()
  220. }
  221. chartInstance = echarts.init(visitChart.value)
  222. const { visitData, dateRange } = data
  223. const option = {
  224. title: {
  225. text: '近7天访问趋势',
  226. left: 'center',
  227. textStyle: {
  228. fontSize: 14,
  229. color: '#2c3e50'
  230. }
  231. },
  232. tooltip: {
  233. trigger: 'axis',
  234. formatter: function (params) {
  235. return params[0].name + '<br/>' + params[0].marker + '访问量: ' + params[0].value + ' 次'
  236. }
  237. },
  238. xAxis: {
  239. type: 'category',
  240. data: dateRange,
  241. axisLabel: {
  242. color: '#2c3e50'
  243. }
  244. },
  245. yAxis: {
  246. type: 'value',
  247. name: '访问量',
  248. nameTextStyle: {
  249. color: '#2c3e50'
  250. },
  251. axisLabel: {
  252. color: '#2c3e50'
  253. }
  254. },
  255. series: [
  256. {
  257. name: '访问量',
  258. type: 'line',
  259. data: visitData,
  260. smooth: true,
  261. itemStyle: { color: '#3498db' },
  262. areaStyle: {
  263. color: {
  264. type: 'linear',
  265. x: 0,
  266. y: 0,
  267. x2: 0,
  268. y2: 1,
  269. colorStops: [
  270. { offset: 0, color: 'rgba(52,152,219,0.3)' },
  271. { offset: 1, color: 'rgba(52,152,219,0.1)' }
  272. ]
  273. }
  274. },
  275. lineStyle: {
  276. width: 3
  277. },
  278. symbol: 'circle',
  279. symbolSize: 8
  280. }
  281. ]
  282. }
  283. chartInstance.setOption(option)
  284. // 响应式处理
  285. window.addEventListener('resize', () => {
  286. chartInstance?.resize()
  287. })
  288. }
  289. // 关闭访问统计弹窗
  290. const closeVisitModal = () => {
  291. visitModalVisible.value = false
  292. if (chartInstance) {
  293. chartInstance.dispose()
  294. chartInstance = null
  295. }
  296. }
  297. // 初始化数据
  298. const initData = async () => {
  299. await Promise.all([loadPlatformStats(), loadHotCourses(), loadCourseInfo()])
  300. }
  301. // 组件挂载时加载数据
  302. onMounted(() => {
  303. initData()
  304. })
  305. // 将showVisitModal方法暴露到全局,供表格按钮调用
  306. window.showVisitModal = showVisitModal
  307. </script>
  308. <style scoped>
  309. .platform-status {
  310. background: linear-gradient(135deg, #e0eafc 0%, #cfdef3 100%);
  311. min-height: 100vh;
  312. padding: 30px 20px;
  313. }
  314. .header {
  315. background: #fff;
  316. border-radius: 16px;
  317. padding: 30px 0 20px 0;
  318. margin-bottom: 30px;
  319. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
  320. text-align: center;
  321. margin-left: auto;
  322. margin-right: auto;
  323. margin-bottom: 30px;
  324. }
  325. .header h1 {
  326. font-size: 2.2em;
  327. color: #2c3e50;
  328. margin-bottom: 10px;
  329. }
  330. .header p {
  331. color: #7f8c8d;
  332. font-size: 1.1em;
  333. }
  334. .stats-grid {
  335. display: grid;
  336. grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  337. gap: 25px;
  338. margin-bottom: 30px;
  339. margin-left: auto;
  340. margin-right: auto;
  341. margin-bottom: 30px;
  342. }
  343. .stat-card {
  344. background: #fff;
  345. border-radius: 14px;
  346. padding: 28px 20px;
  347. box-shadow: 0 4px 16px rgba(44, 62, 80, 0.07);
  348. text-align: center;
  349. transition: box-shadow 0.2s;
  350. }
  351. .stat-card:hover {
  352. box-shadow: 0 8px 32px rgba(44, 62, 80, 0.13);
  353. }
  354. .stat-title {
  355. color: #7f8c8d;
  356. font-size: 1em;
  357. margin-bottom: 10px;
  358. }
  359. .stat-value {
  360. font-size: 2.3em;
  361. font-weight: bold;
  362. color: #3498db;
  363. margin-bottom: 5px;
  364. }
  365. .section {
  366. background: #fff;
  367. border-radius: 14px;
  368. padding: 25px 20px;
  369. margin-bottom: 30px;
  370. box-shadow: 0 4px 16px rgba(44, 62, 80, 0.07);
  371. margin-left: auto;
  372. margin-right: auto;
  373. margin-bottom: 30px;
  374. }
  375. .section h2 {
  376. font-size: 1.3em;
  377. color: #2c3e50;
  378. margin-bottom: 18px;
  379. }
  380. :deep(.ant-table-thead > tr > th) {
  381. background: #3498db;
  382. color: #fff;
  383. font-weight: 600;
  384. }
  385. :deep(.ant-table-tbody > tr:hover > td) {
  386. background: #f6faff;
  387. }
  388. :deep(.btn-view) {
  389. background: linear-gradient(135deg, #27ae60, #229954);
  390. color: #fff;
  391. border: none;
  392. border-radius: 8px;
  393. padding: 7px 18px;
  394. cursor: pointer;
  395. font-size: 0.95em;
  396. transition: background 0.2s;
  397. }
  398. :deep(.btn-view:hover) {
  399. background: linear-gradient(135deg, #229954, #27ae60);
  400. }
  401. @media (max-width: 900px) {
  402. .stats-grid {
  403. grid-template-columns: 1fr 1fr;
  404. }
  405. }
  406. @media (max-width: 600px) {
  407. .stats-grid {
  408. grid-template-columns: 1fr;
  409. }
  410. .platform-status {
  411. padding: 10px;
  412. }
  413. }
  414. </style>