statistics.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. <template>
  2. <div class="statistics-container">
  3. <!-- 营期课程选择 -->
  4. <div class="fixed-header">
  5. <el-form :inline="true" :model="queryParams" class="demo-form-inline">
  6. <el-form-item label="营期课程">
  7. <el-select
  8. v-model="queryParams.videoIdList"
  9. multiple
  10. placeholder="请选择营期课程"
  11. style="width: 400px"
  12. >
  13. <el-option
  14. v-for="item in courseOptions"
  15. :key="item.videoId"
  16. :label="item.videoName"
  17. :value="item.videoId"
  18. />
  19. </el-select>
  20. </el-form-item>
  21. <el-form-item label="公司">
  22. <el-select
  23. v-model="queryParams.companyId"
  24. placeholder="请选择公司"
  25. clearable
  26. style="width: 400px"
  27. >
  28. <el-option
  29. v-for="item in companyOptions"
  30. :key="item.companyId"
  31. :label="item.companyName"
  32. :value="item.companyId"
  33. />
  34. </el-select>
  35. </el-form-item>
  36. <el-form-item>
  37. <el-button type="primary" @click="handleQuery">查询</el-button>
  38. </el-form-item>
  39. </el-form>
  40. <!-- 统计数据展示 -->
  41. <el-row :gutter="20" class="statistics-row">
  42. <el-col :span="3">
  43. <div class="statistics-item">
  44. <div class="statistics-title">完播人数</div>
  45. <div class="statistics-value">{{ statistics.courseCompleteNum || 0 }}</div>
  46. </div>
  47. </el-col>
  48. <el-col :span="3">
  49. <div class="statistics-item">
  50. <div class="statistics-title">观看人数</div>
  51. <div class="statistics-value">{{ statistics.courseWatchNum || 0 }}</div>
  52. </div>
  53. </el-col>
  54. <el-col :span="3">
  55. <div class="statistics-item">
  56. <div class="statistics-title">完播率</div>
  57. <div class="statistics-value">{{ statistics.completeRate || '0%' }}</div>
  58. </div>
  59. </el-col>
  60. <el-col :span="3">
  61. <div class="statistics-item">
  62. <div class="statistics-title">观看总次数</div>
  63. <div class="statistics-value">{{ statistics.courseWatchTimes || 0 }}</div>
  64. </div>
  65. </el-col>
  66. <el-col :span="3">
  67. <div class="statistics-item">
  68. <div class="statistics-title">答题总次数</div>
  69. <div class="statistics-value">{{ statistics.answerTimes || 0 }}</div>
  70. </div>
  71. </el-col>
  72. <el-col :span="3">
  73. <div class="statistics-item">
  74. <div class="statistics-title">答题正确总次数</div>
  75. <div class="statistics-value">{{ statistics.answerRightTimes || 0 }}</div>
  76. </div>
  77. </el-col>
  78. <el-col :span="3">
  79. <div class="statistics-item">
  80. <div class="statistics-title">奖励金额总计(元)</div>
  81. <div class="statistics-value">{{ statistics.redPacketAmount || 0 }}</div>
  82. </div>
  83. </el-col>
  84. </el-row>
  85. </div>
  86. <!-- 列表统计展示 -->
  87. <div class="table-wrapper">
  88. <el-table v-loading="loading" :data="list" border height="calc(100vh - 450px)">
  89. <el-table-column type="index" label="序号" width="50" align="center" fixed/>
  90. <el-table-column prop="title" label="课程名称" align="center" min-width="250" fixed/>
  91. <el-table-column prop="dayDate" label="营期日期" align="center" min-width="120" fixed/>
  92. <el-table-column prop="countDetailsVO.courseWatchTimes" label="观看次数" align="center" min-width="100"/>
  93. <el-table-column prop="countDetailsVO.courseCompleteTimes" label="完播次数" align="center" min-width="100"/>
  94. <el-table-column prop="countDetailsVO.courseWatchNum" label="观看人数" align="center" min-width="100"/>
  95. <el-table-column prop="countDetailsVO.courseCompleteNum" label="完播人数" align="center" min-width="100"/>
  96. <el-table-column prop="countDetailsVO.completeRate" label="完播率" align="center" min-width="100">
  97. <template slot-scope="scope">
  98. {{ scope.row.countDetailsVO.completeRate || 0 }}%
  99. </template>
  100. </el-table-column>
  101. <el-table-column prop="countDetailsVO.answerTimes" label="答题次数" align="center" min-width="100"/>
  102. <el-table-column prop="countDetailsVO.answerNum" label="答题人数" align="center" min-width="100"/>
  103. <el-table-column prop="countDetailsVO.answerRightNum" label="正确人数" align="center" min-width="100"/>
  104. <el-table-column prop="countDetailsVO.answerRightRate" label="正确率" align="center" min-width="100">
  105. <template slot-scope="scope">
  106. {{ scope.row.countDetailsVO.answerRightRate || 0 }}%
  107. </template>
  108. </el-table-column>
  109. <el-table-column prop="countDetailsVO.redPacketNum" label="答题红包个数" align="center" min-width="120"/>
  110. <el-table-column prop="countDetailsVO.redPacketAmount" label="答题红包金额(元)" align="center" min-width="150"/>
  111. </el-table>
  112. <!-- 分页 -->
  113. <div class="custom-pagination-container">
  114. <pagination
  115. v-show="total > 0"
  116. :total="total"
  117. :page.sync="queryParams.pageNum"
  118. :limit.sync="queryParams.pageSize"
  119. @pagination="getCountList"
  120. />
  121. </div>
  122. </div>
  123. </div>
  124. </template>
  125. <script>
  126. import {getDays, periodCountSelect, getPeriodCompanyList} from "@/api/course/userCoursePeriod";
  127. export default {
  128. name: "CourseStatistics",
  129. props: {
  130. periodId: {
  131. type: [String, Number],
  132. default: ''
  133. },
  134. active: {
  135. type: Boolean,
  136. default: false
  137. }
  138. },
  139. data() {
  140. return {
  141. // 遮罩层
  142. loading: false,
  143. // 总条数
  144. total: 0,
  145. // 课程选项
  146. courseOptions: [],
  147. companyOptions: [],
  148. // 统计数据
  149. statistics: {
  150. courseCompleteNum: 0,
  151. courseWatchNum: 0,
  152. completeRate: '0%',
  153. courseWatchTimes: 0,
  154. answerTimes: 0,
  155. answerRightTimes: 0,
  156. redPacketAmount: 0
  157. },
  158. // 列表数据
  159. list: [],
  160. // 查询参数
  161. queryParams: {
  162. pageNum: 1,
  163. pageSize: 10,
  164. videoIdList: [],
  165. // videoId: '',
  166. periodId: ''
  167. },
  168. // 是否已初始化
  169. initialized: false
  170. };
  171. },
  172. watch: {
  173. periodId: {
  174. handler(newVal) {
  175. this.queryParams.periodId = newVal;
  176. if (this.active && !this.initialized) {
  177. this.initializeData();
  178. }
  179. },
  180. immediate: true
  181. },
  182. active: {
  183. handler(newVal) {
  184. if (newVal && !this.initialized) {
  185. this.initializeData();
  186. }
  187. },
  188. immediate: true
  189. }
  190. },
  191. methods: {
  192. /** 初始化数据 */
  193. initializeData() {
  194. this.getCourseOptions();
  195. this.getCountList();
  196. this.getCompanyOptions()
  197. this.initialized = true;
  198. },
  199. getCompanyOptions() {
  200. getPeriodCompanyList({
  201. periodId: this.periodId
  202. }).then(response => {
  203. this.companyOptions = response.data || [];
  204. });
  205. },
  206. /** 获取课程选项 */
  207. getCourseOptions() {
  208. this.loading = true;
  209. getDays(this.queryParams).then(r => {
  210. if (r.code === 200) {
  211. this.courseOptions = r.rows;
  212. this.loading = false;
  213. } else {
  214. this.$message.error(r.msg || '获取数据失败');
  215. }
  216. this.loading = false;
  217. }).catch(() => {
  218. this.loading = false;
  219. });
  220. },
  221. /** 查询按钮操作 */
  222. handleQuery() {
  223. this.queryParams.pageNum = 1;
  224. this.getCountList();
  225. },
  226. /** 课程选择变化 */
  227. handleCourseChange() {
  228. },
  229. /** 获取列表数据 */
  230. getCountList() {
  231. this.loading = true;
  232. periodCountSelect(this.queryParams).then(response => {
  233. if (response.code === 200) {
  234. // 设置列表数据
  235. this.list = response.rows;
  236. this.total = response.total || 0;
  237. // 计算总统计数据
  238. this.calculateTotalStatistics();
  239. console.log('列表数据:', this.list);
  240. } else {
  241. this.$message.error(response.msg || '获取数据失败');
  242. }
  243. this.loading = false;
  244. }).catch(error => {
  245. console.error('获取数据失败:', error);
  246. this.$message.error('获取数据失败');
  247. this.loading = false;
  248. });
  249. },
  250. /** 计算总统计数据 */
  251. calculateTotalStatistics() {
  252. // 初始化统计数据
  253. this.statistics = {
  254. courseCompleteNum: 0,
  255. courseWatchNum: 0,
  256. completeRate: '0%',
  257. courseWatchTimes: 0,
  258. answerTimes: 0,
  259. answerRightTimes: 0,
  260. redPacketAmount: 0
  261. };
  262. // 如果没有数据,直接返回
  263. if (!this.list || this.list.length === 0) {
  264. return;
  265. }
  266. // 遍历列表数据,累加各项统计数据
  267. this.list.forEach(item => {
  268. const details = item.countDetailsVO || {};
  269. // 累加各项数据
  270. this.statistics.courseCompleteNum += details.courseCompleteNum || 0;
  271. this.statistics.courseWatchNum += details.courseWatchNum || 0;
  272. this.statistics.courseWatchTimes += details.courseWatchTimes || 0;
  273. this.statistics.answerTimes += details.answerTimes || 0;
  274. this.statistics.answerRightTimes += details.answerRightNum || 0;
  275. // this.statistics.redPacketAmount += details.redPacketAmount || 0;
  276. // 累加时:先转整数计算
  277. this.statistics.redPacketAmount = (Math.round(this.statistics.redPacketAmount * 100) + // 现有金额转分
  278. Math.round((details.redPacketAmount || 0) * 100) // 新增金额转分
  279. ) / 100; // 最后转回元
  280. });
  281. // 计算完播率
  282. if (this.statistics.courseWatchNum > 0) {
  283. const rate = (this.statistics.courseCompleteNum / this.statistics.courseWatchNum * 100).toFixed(2);
  284. this.statistics.completeRate = rate + '%';
  285. }
  286. }
  287. }
  288. };
  289. </script>
  290. <style scoped>
  291. .statistics-container {
  292. height: 100%;
  293. overflow: hidden;
  294. position: relative;
  295. }
  296. .fixed-header {
  297. position: sticky;
  298. top: 0;
  299. z-index: 10;
  300. background-color: #fff;
  301. padding: 10px 0;
  302. border-bottom: 1px solid #EBEEF5;
  303. }
  304. .table-wrapper {
  305. height: calc(100% - 220px);
  306. overflow: visible;
  307. position: relative;
  308. }
  309. .custom-pagination-container {
  310. padding: 10px 0 12px 0;
  311. text-align: right;
  312. background-color: #fff;
  313. position: relative;
  314. z-index: 1;
  315. }
  316. /* 覆盖原有的pagination-container样式 */
  317. :deep(.pagination-container) {
  318. height: auto !important;
  319. margin-bottom: 0 !important;
  320. margin-top: 0 !important;
  321. padding: 0 !important;
  322. }
  323. .statistics-row {
  324. margin: 20px 0;
  325. }
  326. .statistics-item {
  327. background-color: #f5f7fa;
  328. border-radius: 4px;
  329. padding: 15px;
  330. text-align: center;
  331. height: 100px;
  332. display: flex;
  333. flex-direction: column;
  334. justify-content: center;
  335. }
  336. .statistics-title {
  337. font-size: 14px;
  338. color: #606266;
  339. margin-bottom: 10px;
  340. }
  341. .statistics-value {
  342. font-size: 20px;
  343. font-weight: bold;
  344. color: #303133;
  345. }
  346. </style>