LearningStatistics.vue 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. <template>
  2. <div class="learning-statistics">
  3. <div class="stat-picker">
  4. <a-range-picker v-model:value="dateRange" style="width: 240px" />
  5. </div>
  6. <!-- 顶部统计卡片 -->
  7. <div class="stat-cards">
  8. <a-card class="stat-card">
  9. <div class="stat-title">学习人数</div>
  10. <div class="stat-value">{{ statData.studentCount }}<span class="stat-unit">人</span></div>
  11. <div class="stat-desc">较上周 +1000</div>
  12. </a-card>
  13. <a-card class="stat-card">
  14. <div class="stat-title">学习总时长</div>
  15. <div class="stat-value">{{ statData.totalStudyTime }}<span class="stat-unit">小时</span></div>
  16. <div class="stat-desc">较上周 -200</div>
  17. </a-card>
  18. <a-card class="stat-card">
  19. <div class="stat-title">人均学习时长</div>
  20. <div class="stat-value">{{ statData.avgStudyTime }}<span class="stat-unit">小时</span></div>
  21. <div class="stat-desc">较上周 +40</div>
  22. </a-card>
  23. <a-card class="stat-card">
  24. <div class="stat-title">完课率</div>
  25. <div class="stat-value">{{ statData.completionRate }}<span class="stat-unit">%</span></div>
  26. <div class="stat-desc">较上周 -200</div>
  27. </a-card>
  28. </div>
  29. <!-- 学习人数趋势图 -->
  30. <div class="chart-section">
  31. <a-card class="chart-card">
  32. <div class="chart-title">学习人数趋势</div>
  33. <div ref="mainChartRef" style="height: 260px; width: 100%"></div>
  34. <div class="chart-legend">
  35. <span class="legend-item blue">学习人数</span>
  36. <span class="legend-item">学习时长</span>
  37. <span class="legend-item">人均学习时长</span>
  38. <span class="legend-item">完课率</span>
  39. </div>
  40. </a-card>
  41. </div>
  42. <!-- 学习时段平均人数分布 -->
  43. <div class="chart-section">
  44. <a-card class="chart-card">
  45. <div class="chart-title">学习时段平均人数分布</div>
  46. <div ref="timeChartRef" style="height: 220px; width: 100%"></div>
  47. <div class="chart-legend">
  48. <span class="legend-item blue">学习人数</span>
  49. </div>
  50. </a-card>
  51. </div>
  52. <!-- 用户学习数据表格 -->
  53. <div class="user-table-section">
  54. <div class="table-toolbar">
  55. <a-input-search
  56. v-model:value="searchValue"
  57. placeholder="姓名/学号/手机"
  58. style="width: 200px; margin-right: 12px"
  59. />
  60. <a-select v-model:value="selectedClass" placeholder="选择学生班别" style="width: 160px; margin-right: 12px">
  61. <a-select-option value="">全部班级</a-select-option>
  62. <a-select-option value="一班">一班</a-select-option>
  63. <a-select-option value="二班">二班</a-select-option>
  64. </a-select>
  65. <a-select v-model:value="selectedStatus" placeholder="状态筛选" style="width: 120px; margin-right: 12px">
  66. <a-select-option value="">全部状态</a-select-option>
  67. <a-select-option value="正常">正常</a-select-option>
  68. <a-select-option value="异常">异常</a-select-option>
  69. </a-select>
  70. <a-button type="primary" style="margin-right: 8px">导出</a-button>
  71. <a-button>重置</a-button>
  72. </div>
  73. <a-table
  74. :columns="columns"
  75. :data-source="tableData"
  76. :pagination="pagination"
  77. row-key="id"
  78. bordered
  79. size="middle"
  80. style="margin-top: 16px"
  81. >
  82. <template #bodyCell="{ column }">
  83. <template v-if="column.key === 'action'">
  84. <a-space>
  85. <a-button type="link" size="small">详情</a-button>
  86. <a-button type="link" size="small">编辑</a-button>
  87. <a-button type="link" size="small">导出</a-button>
  88. <a-button type="link" size="small" danger>删除</a-button>
  89. </a-space>
  90. </template>
  91. </template>
  92. </a-table>
  93. </div>
  94. </div>
  95. </template>
  96. <script setup>
  97. import { ref, onMounted } from 'vue'
  98. import * as echarts from 'echarts'
  99. const statData = ref({
  100. studentCount: 900,
  101. totalStudyTime: 1800,
  102. avgStudyTime: 2.0,
  103. completionRate: 20
  104. })
  105. const dateRange = ref([])
  106. const mainChartRef = ref(null)
  107. const timeChartRef = ref(null)
  108. const mainChartOption = {
  109. tooltip: { trigger: 'axis' },
  110. grid: { left: 40, right: 20, top: 40, bottom: 40 },
  111. xAxis: {
  112. type: 'category',
  113. data: ['03-01', '03-02', '03-03', '03-04', '03-05', '03-06', '03-07']
  114. },
  115. yAxis: { type: 'value' },
  116. series: [
  117. {
  118. name: '学习人数',
  119. type: 'line',
  120. data: [200, 220, 210, 500, 230, 210, 200],
  121. smooth: true,
  122. areaStyle: { color: 'rgba(52,122,255,0.08)' },
  123. lineStyle: { color: '#347aff', width: 3 },
  124. itemStyle: { color: '#347aff' },
  125. markLine: {
  126. data: [{ xAxis: '03-04' }],
  127. lineStyle: { color: '#347aff', type: 'dashed' },
  128. label: { show: false }
  129. },
  130. symbolSize: 8
  131. }
  132. ]
  133. }
  134. const timeChartOption = {
  135. tooltip: { trigger: 'axis' },
  136. grid: { left: 40, right: 20, top: 40, bottom: 40 },
  137. xAxis: {
  138. type: 'category',
  139. data: ['00:00', '00:04', '00:08', '00:12', '00:16', '00:20', '00:24']
  140. },
  141. yAxis: { type: 'value' },
  142. series: [
  143. {
  144. name: '学习人数',
  145. type: 'line',
  146. data: [1000, 1200, 1100, 1520, 1400, 1200, 1000],
  147. smooth: true,
  148. areaStyle: { color: 'rgba(52,122,255,0.08)' },
  149. lineStyle: { color: '#347aff', width: 3 },
  150. itemStyle: { color: '#347aff' },
  151. markLine: {
  152. data: [{ xAxis: '00:12' }],
  153. lineStyle: { color: '#347aff', type: 'dashed' },
  154. label: { show: false }
  155. },
  156. symbolSize: 8
  157. }
  158. ]
  159. }
  160. const searchValue = ref('')
  161. const selectedClass = ref('')
  162. const selectedStatus = ref('')
  163. const columns = [
  164. { title: '学号/账号', dataIndex: 'id', key: 'id', align: 'center' },
  165. { title: '姓名', dataIndex: 'name', key: 'name', align: 'center' },
  166. { title: '进度', dataIndex: 'progress', key: 'progress', align: 'center' },
  167. { title: '速率', dataIndex: 'rate', key: 'rate', align: 'center' },
  168. { title: '今日学习时长', dataIndex: 'todayTime', key: 'todayTime', align: 'center' },
  169. { title: '累计学习时长', dataIndex: 'totalTime', key: 'totalTime', align: 'center' },
  170. { title: '首次学习时间', dataIndex: 'firstTime', key: 'firstTime', align: 'center' },
  171. { title: '最近登录', dataIndex: 'lastLogin', key: 'lastLogin', align: 'center' },
  172. { title: '操作', key: 'action', align: 'center' }
  173. ]
  174. const tableData = ref(
  175. Array.from({ length: 10 }).map((_, i) => ({
  176. id: `xy00000${i + 1}`,
  177. name: '张小刚',
  178. progress: '20%',
  179. rate: '20%',
  180. todayTime: '1h30min',
  181. totalTime: '13h30min',
  182. firstTime: '2020-11-25 23:26:08',
  183. lastLogin: '2020-11-25 23:26:08'
  184. }))
  185. )
  186. const pagination = {
  187. pageSize: 10,
  188. total: 50,
  189. showTotal: (total) => `共${total}条`,
  190. showQuickJumper: true,
  191. showSizeChanger: true
  192. }
  193. onMounted(() => {
  194. if (mainChartRef.value) {
  195. const chart = echarts.init(mainChartRef.value)
  196. chart.setOption(mainChartOption)
  197. }
  198. if (timeChartRef.value) {
  199. const chart = echarts.init(timeChartRef.value)
  200. chart.setOption(timeChartOption)
  201. }
  202. })
  203. </script>
  204. <style lang="less" scoped>
  205. .learning-statistics {
  206. width: 1200px;
  207. margin: 0 auto;
  208. height: calc(100vh - 400px);
  209. overflow: auto;
  210. .stat-cards {
  211. display: flex;
  212. align-items: flex-start;
  213. margin-bottom: 24px;
  214. .stat-card {
  215. flex: 1;
  216. margin-right: 16px;
  217. border-radius: 12px;
  218. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04);
  219. border: none;
  220. .stat-title {
  221. font-size: 16px;
  222. color: #888;
  223. margin-bottom: 8px;
  224. }
  225. .stat-value {
  226. font-size: 32px;
  227. font-weight: 600;
  228. color: #222;
  229. .stat-unit {
  230. font-size: 16px;
  231. margin-left: 4px;
  232. }
  233. }
  234. .stat-desc {
  235. color: #aaa;
  236. font-size: 13px;
  237. margin-top: 8px;
  238. }
  239. }
  240. .stat-card:last-child {
  241. margin-right: 0;
  242. }
  243. }
  244. .stat-picker {
  245. display: flex;
  246. flex-direction: row-reverse;
  247. margin: 10px 0;
  248. }
  249. .chart-section {
  250. margin-bottom: 24px;
  251. .chart-card {
  252. border-radius: 12px;
  253. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04);
  254. border: none;
  255. padding-bottom: 12px;
  256. .chart-title {
  257. font-size: 16px;
  258. font-weight: 600;
  259. color: #222;
  260. margin-bottom: 12px;
  261. }
  262. .chart-legend {
  263. margin-top: 8px;
  264. .legend-item {
  265. display: inline-block;
  266. margin-right: 24px;
  267. font-size: 14px;
  268. color: #888;
  269. &.blue {
  270. color: #347aff;
  271. font-weight: 600;
  272. }
  273. }
  274. }
  275. }
  276. }
  277. .user-table-section {
  278. background: #fff;
  279. border-radius: 12px;
  280. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04);
  281. padding: 24px 24px 12px 24px;
  282. .table-toolbar {
  283. display: flex;
  284. align-items: center;
  285. margin-bottom: 8px;
  286. }
  287. }
  288. }
  289. </style>