index.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. <!-- 学时任务列表 -->
  2. <template>
  3. <view class="safetyEducationExamination-studyHourTask">
  4. <!-- 顶部类型标签 -->
  5. <scroll-view scroll-x class="tag-scroll" enable-flex>
  6. <view class="tag-scroll-inner">
  7. <text
  8. v-for="(tag, index) in typeTags"
  9. :key="index"
  10. class="filter-tag"
  11. :class="{ 'filter-tag-active': activeTagIndex === index }"
  12. @click="onTagChange(index)"
  13. >{{ tag.label }}</text>
  14. </view>
  15. </scroll-view>
  16. <!-- 课程列表 -->
  17. <scroll-view scroll-y class="scroll-content" @scrolltolower="onScrollToLower">
  18. <view class="panel">
  19. <view
  20. v-for="(item, index) in courseList"
  21. :key="index"
  22. class="course-card"
  23. @click="goCourseDetail(item)"
  24. >
  25. <view class="course-cover" :style="'background:' + item.coverGradient">
  26. <image v-if="item.materialType == 1 && item.exampleImg" :src="baseUrl + item.exampleImg" class="cover-image" mode="aspectFill" />
  27. <text v-else class="cover-icon">{{ item.coverIcon || '📖' }}</text>
  28. </view>
  29. <view class="course-info">
  30. <view class="course-info-top">
  31. <text class="course-name">{{ item.coursewareName }}</text>
  32. <text class="status-tag" :class="item.statusClass">{{ item.statusName }}</text>
  33. </view>
  34. <text class="course-meta">{{ formatDuration(item.minLearningDuration) }}分钟 | {{ item.creditHours }}学时 | {{ item.points }}积分</text>
  35. </view>
  36. </view>
  37. <view v-if="!courseList[0] && !loading" class="empty-tip">暂无学时任务</view>
  38. <view v-if="loading" class="loading-tip">加载中...</view>
  39. <view v-if="noMore && courseList[0]" class="loading-tip">没有更多了</view>
  40. </view>
  41. </scroll-view>
  42. </view>
  43. </template>
  44. <script>
  45. import {
  46. goLearning,
  47. } from '@/pages_safetyEducationExamination/utils/index.js'
  48. import {
  49. config
  50. } from '@/api/request/config.js'
  51. import {
  52. examUserCoursewareLearningList,
  53. examElKnowledgePointTreeList,
  54. } from '@/pages_safetyEducationExamination/api/index.js'
  55. // 封面渐变色池
  56. const COVER_GRADIENTS = [
  57. 'linear-gradient(135deg,#ffd666,#fa8c16)',
  58. 'linear-gradient(135deg,#b37feb,#722ed1)',
  59. 'linear-gradient(135deg,#ff9c6e,#f5222d)',
  60. 'linear-gradient(135deg,#5cdbd3,#13c2c2)',
  61. 'linear-gradient(135deg,#1857d4,#5b8af0)',
  62. 'linear-gradient(135deg,#10a37f,#34d399)',
  63. 'linear-gradient(135deg,#6c63ff,#a78bfa)',
  64. 'linear-gradient(135deg,#95de64,#237804)',
  65. ];
  66. // 学习状态映射
  67. function getStatusInfo(status) {
  68. const map = {
  69. 0: { name: '未开始', cls: 'tag-gray' },
  70. 1: { name: '学习中', cls: 'tag-orange' },
  71. 2: { name: '已完成', cls: 'tag-green' },
  72. };
  73. return map[status] || map[0];
  74. }
  75. export default {
  76. data() {
  77. return {
  78. baseUrl: config.base_url,
  79. typeTags: [],
  80. activeTagIndex: 0,
  81. courseList: [],
  82. loading: false,
  83. page: 1,
  84. pageSize: 10,
  85. total: 0,
  86. noMore: false,
  87. knowledgePointIds: null, // 从上个页面传来的知识点ID数组
  88. }
  89. },
  90. onLoad(options) {
  91. console.log('options',options);
  92. // 接收知识点ID参数
  93. if (options.knowledgePointId) {
  94. this.knowledgePointIds = options.knowledgePointId;
  95. }
  96. },
  97. created() {
  98. },
  99. async onShow() {
  100. await this.loadTypeTags();
  101. this.resetList();
  102. },
  103. methods: {
  104. // 递归扁平化树形结构,提取所有节点
  105. flattenTree(nodes, result = []) {
  106. if (!nodes || !Array.isArray(nodes)) return result;
  107. nodes.forEach(node => {
  108. result.push(node);
  109. if (node.children && node.children.length > 0) {
  110. this.flattenTree(node.children, result);
  111. }
  112. });
  113. return result;
  114. },
  115. // 加载知识点标签(根据传入的knowledgePointIds过滤)
  116. async loadTypeTags() {
  117. try {
  118. const { data } = await examElKnowledgePointTreeList({ dataScope: 1 });
  119. if (data.code === 200 && data.data) {
  120. // 扁平化树形结构,获取所有节点
  121. const allNodes = this.flattenTree(data.data);
  122. let tags = allNodes;
  123. // 如果有传入的知识点ID,则过滤标签
  124. if (this.knowledgePointIds) {
  125. const idsArray = this.knowledgePointIds.split(',').map(id => id.trim());
  126. tags = allNodes.filter(item => idsArray.includes(String(item.id)));
  127. }
  128. const typeTags = tags.map(item => ({
  129. label: item.knowledgePointName,
  130. value: item.id,
  131. }));
  132. this.$set(this, 'typeTags', typeTags);
  133. // 默认选中第一个
  134. if (typeTags.length > 0) {
  135. this.activeTagIndex = 0;
  136. }
  137. }
  138. } catch (e) {
  139. console.error('获取知识点类型失败', e);
  140. }
  141. },
  142. onTagChange(index) {
  143. if (this.activeTagIndex !== index) {
  144. this.activeTagIndex = index;
  145. this.resetList();
  146. }
  147. },
  148. resetList() {
  149. this.page = 1;
  150. this.noMore = false;
  151. this.courseList = [];
  152. this.loadCourseList();
  153. },
  154. // 学时任务列表(materialType为空,查询全部类型)
  155. async loadCourseList() {
  156. if (this.loading || this.noMore) return;
  157. this.loading = true;
  158. try {
  159. const tag = this.typeTags[this.activeTagIndex];
  160. const { data } = await examUserCoursewareLearningList({
  161. page: this.page,
  162. pageSize: this.pageSize,
  163. materialType: '', // 空字符串,查询全部类型
  164. knowledgePointId: tag ? tag.value : undefined,
  165. });
  166. if (data.code === 200) {
  167. const records = (data.data.records || []).map((item, i) => {
  168. const s = getStatusInfo(item.learnStatus);
  169. const colorIndex = (this.courseList.length + i) % COVER_GRADIENTS.length;
  170. return {
  171. ...item,
  172. coverGradient: COVER_GRADIENTS[colorIndex],
  173. statusName: s.name,
  174. statusClass: s.cls,
  175. };
  176. });
  177. const newList = this.page === 1 ? records : [...this.courseList, ...records];
  178. this.$set(this, 'courseList', newList);
  179. this.total = data.data.total;
  180. if (newList.length >= this.total) {
  181. this.noMore = true;
  182. } else {
  183. this.page++;
  184. }
  185. }
  186. } catch (e) {
  187. console.error('获取学时任务失败', e);
  188. } finally {
  189. this.loading = false;
  190. }
  191. },
  192. onScrollToLower() {
  193. this.loadCourseList();
  194. },
  195. // 秒数转分钟
  196. formatDuration(seconds) {
  197. return seconds ? Math.ceil(seconds / 60) : 0;
  198. },
  199. goCourseDetail(item) {
  200. // 跳转课程详情,传递 coursewareId
  201. goLearning(item)
  202. },
  203. },
  204. }
  205. </script>
  206. <style lang="stylus" scoped>
  207. .safetyEducationExamination-studyHourTask
  208. height 100%
  209. display flex
  210. flex-direction column
  211. overflow hidden
  212. background-color #f0f4fb
  213. // 类型标签横滚
  214. .tag-scroll
  215. background #fff
  216. white-space nowrap
  217. padding 16rpx 24rpx
  218. border-bottom 1rpx solid #e8edf3
  219. flex-shrink 0
  220. .tag-scroll-inner
  221. display inline-flex
  222. gap 16rpx
  223. .filter-tag
  224. display inline-block
  225. font-size 22rpx
  226. padding 8rpx 20rpx
  227. border-radius 30rpx
  228. background #eef1f6
  229. color #666
  230. border 1rpx solid transparent
  231. white-space nowrap
  232. .filter-tag-active
  233. background #e6f0ff
  234. color #1677ff
  235. border-color #91caff
  236. font-weight 600
  237. // 滚动区
  238. .scroll-content
  239. flex 1
  240. overflow hidden
  241. .panel
  242. padding 24rpx
  243. // 课程卡片
  244. .course-card
  245. background #fff
  246. border-radius 20rpx
  247. margin-bottom 20rpx
  248. overflow hidden
  249. box-shadow 0 2rpx 12rpx rgba(0,0,0,0.04)
  250. border 1rpx solid #dde3ed
  251. .course-cover
  252. width 100%
  253. height 180rpx
  254. display flex
  255. align-items center
  256. justify-content center
  257. .cover-icon
  258. font-size 64rpx
  259. .cover-image
  260. width 100%
  261. height 100%
  262. .course-info
  263. padding 20rpx 24rpx
  264. .course-info-top
  265. display flex
  266. justify-content space-between
  267. align-items center
  268. margin-bottom 8rpx
  269. .course-name
  270. flex 1
  271. font-size 28rpx
  272. font-weight 600
  273. color #222
  274. margin-right 12rpx
  275. .course-meta
  276. font-size 22rpx
  277. color #999
  278. // 状态标签
  279. .status-tag
  280. font-size 20rpx
  281. padding 4rpx 12rpx
  282. border-radius 8rpx
  283. border 1rpx solid transparent
  284. flex-shrink 0
  285. .tag-gray
  286. background #f5f5f5
  287. color #999
  288. border-color #e8e8e8
  289. .tag-orange
  290. background #fff7e6
  291. color #d46b08
  292. border-color #ffd591
  293. .tag-green
  294. background #f6ffed
  295. color #389e0d
  296. border-color #b7eb8f
  297. // 提示
  298. .empty-tip
  299. text-align center
  300. line-height 80rpx
  301. color #aaa
  302. font-size 24rpx
  303. .loading-tip
  304. text-align center
  305. line-height 60rpx
  306. color #aaa
  307. font-size 22rpx
  308. </style>