userCoursePeriod.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. <template>
  2. <div class="app-container">
  3. <!-- 没有数据提示 -->
  4. <div v-if="campList.length === 0 && !leftLoading" class="no-data">
  5. <span>—— 没有数据 ——</span>
  6. </div>
  7. <el-container v-else>
  8. <!-- 左侧区域 -->
  9. <el-aside width="360px" class="left-aside">
  10. <!-- 顶部区域 -->
  11. <!-- 训练营列表 -->
  12. <div class="camp-list" ref="campList" @scroll="handleScroll" v-loading="leftLoading">
  13. <div
  14. v-for="(item, index) in campList"
  15. :key="index"
  16. class="camp-item"
  17. :class="{ 'active': activeCampIndex === index }"
  18. @click="selectCamp(index)"
  19. >
  20. <div class="camp-content">
  21. <div class="camp-title">
  22. <i class="el-icon-s-flag camp-icon"></i>
  23. {{ item.trainingCampName }}
  24. </div>
  25. <div class="camp-info">
  26. <span>序号:{{ item.orderNumber }}</span>
  27. <span>最新营期开课:{{ item.recentDate || '-' }}</span>
  28. </div>
  29. </div>
  30. </div>
  31. <!-- 底部加载更多提示 -->
  32. <div v-if="loadingMore" class="loading-more">
  33. <i class="el-icon-loading"></i>
  34. <span>加载中...</span>
  35. </div>
  36. <!-- 所有数据加载完毕提示 -->
  37. <div v-if="campList.length > 0 && !loadingMore" class="no-more-data">
  38. <span>—— 已加载全部训练营 ——</span>
  39. </div>
  40. </div>
  41. </el-aside>
  42. <!-- 右侧区域 -->
  43. <el-main><userCourseStatic ref="userCourseStatic" /></el-main>
  44. </el-container>
  45. </div>
  46. </template>
  47. <script>
  48. import { listCamp} from "@/api/course/userCourseCamp";
  49. import userCourseStatic from './userCourseStatic.vue';
  50. export default {
  51. name: "userCoursePeriod",
  52. components: {
  53. userCourseStatic
  54. },
  55. data() {
  56. return {
  57. // 加载更多状态
  58. loadingMore: false,
  59. // 激活的训练营索引
  60. activeCampIndex: null,
  61. // 训练营列表
  62. campList: [],
  63. // 遮罩层
  64. loading: true,
  65. updateDateOpen: false,
  66. // 左侧遮罩层
  67. leftLoading: true,
  68. // 左侧查询参数
  69. leftQueryParams: {
  70. pageNum: 1,
  71. pageSize: 10,
  72. hasNextPage: false,
  73. scs: 'order_number(desc),training_camp_id(desc)',
  74. trainingCampName: null,
  75. userId:null,
  76. },
  77. };
  78. },
  79. created() {
  80. // this.getLeftList();
  81. },
  82. methods: {
  83. getDetails(orderId) {
  84. this.userId=orderId;
  85. this.leftQueryParams.userId=orderId;
  86. this.getLeftList();
  87. },
  88. /** 查询左侧列表 */
  89. getLeftList() {
  90. this.leftLoading = true;
  91. // 重置页码和加载更多状态
  92. this.leftQueryParams.pageNum = 1;
  93. this.loadingMore = false;
  94. // 训练营数据
  95. listCamp(this.leftQueryParams).then(response => {
  96. if (response && response.code === 200) {
  97. this.campList = response.data.list || [];
  98. this.leftQueryParams.hasNextPage = response.data.hasNextPage;
  99. this.activeCampIndex = this.campList.length > 0 ? 0 : null;
  100. this.selectCamp(this.activeCampIndex);
  101. // 如果当前显示的列表高度不足以触发滚动,但还有更多数据,自动加载下一页
  102. this.$nextTick(() => {
  103. const scrollEl = this.$refs.campList;
  104. if (scrollEl && this.leftQueryParams.hasNextPage && scrollEl.scrollHeight <= scrollEl.clientHeight) {
  105. this.loadMoreCamps();
  106. }
  107. });
  108. } else {
  109. this.$message.error(response.msg || '获取训练营列表失败');
  110. this.campList = [];
  111. this.leftQueryParams.hasNextPage = false;
  112. }
  113. this.leftLoading = false;
  114. }).catch(error => {
  115. console.error('获取训练营列表失败:', error);
  116. this.$message.error('获取训练营列表失败');
  117. this.campList = [];
  118. this.leftQueryParams.hasNextPage = false;
  119. this.leftLoading = false;
  120. });
  121. },
  122. /** 选中训练营 */
  123. selectCamp(index) {
  124. if(index == null || index == undefined) return;
  125. this.activeCampIndex = index;
  126. // 加载对应的训练营营期数据
  127. const selectedCamp = this.campList[index];
  128. console.log(this.userId)
  129. this.$refs.userCourseStatic.getDetails(selectedCamp,this.userId);
  130. // this.queryParams.trainingCampId = selectedCamp.trainingCampId;
  131. // this.getList();
  132. },
  133. /** 处理滚动事件,实现滚动到底部加载更多 */
  134. handleScroll() {
  135. // 如果正在节流中或者正在加载中,则不处理
  136. if (this.scrollThrottle || this.loadingMore) return;
  137. // 设置节流,200ms内不再处理滚动事件
  138. this.scrollThrottle = true;
  139. setTimeout(() => {
  140. this.scrollThrottle = false;
  141. }, 200);
  142. const scrollEl = this.$refs.campList;
  143. if (!scrollEl) return;
  144. // 判断是否滚动到底部:滚动高度 + 可视高度 >= 总高度 - 30(添加30px的容差,提前触发加载)
  145. const isBottom = scrollEl.scrollTop + scrollEl.clientHeight >= scrollEl.scrollHeight - 30;
  146. // 如果滚动到底部,且有下一页数据,且当前不在加载中,则加载更多
  147. if (isBottom && this.leftQueryParams.hasNextPage && !this.leftLoading && !this.loadingMore) {
  148. this.loadMoreCamps();
  149. }
  150. },
  151. /** 加载更多训练营数据 */
  152. loadMoreCamps() {
  153. // 已在加载中,防止重复加载
  154. if (this.leftLoading || this.loadingMore) return;
  155. // 设置加载状态
  156. this.loadingMore = true;
  157. // 页码加1
  158. this.leftQueryParams.pageNum += 1;
  159. // 加载下一页数据
  160. listCamp(this.leftQueryParams).then(response => {
  161. if (response && response.code === 200) {
  162. // 将新数据追加到列表中
  163. const newList = response.data.list || [];
  164. if (newList.length > 0) {
  165. this.campList = [...this.campList, ...newList];
  166. }
  167. // 更新是否有下一页的标志
  168. this.leftQueryParams.hasNextPage = response.data.hasNextPage;
  169. // 如果当前显示的列表高度不足以触发滚动,但还有更多数据,自动加载下一页
  170. this.$nextTick(() => {
  171. const scrollEl = this.$refs.campList;
  172. if (scrollEl && this.leftQueryParams.hasNextPage && scrollEl.scrollHeight <= scrollEl.clientHeight) {
  173. // 延迟一点再加载下一页,避免过快加载
  174. setTimeout(() => {
  175. this.loadMoreCamps();
  176. }, 300);
  177. }
  178. });
  179. } else {
  180. this.$message.error(response.msg || '加载更多训练营失败');
  181. }
  182. this.loadingMore = false;
  183. }).catch(error => {
  184. console.error('加载更多训练营失败:', error);
  185. this.$message.error('加载更多训练营失败');
  186. this.loadingMore = false;
  187. });
  188. },
  189. },
  190. };
  191. </script>
  192. <style scoped>
  193. .left-aside {
  194. background-color: #fff;
  195. border-right: 1px solid #EBEEF5;
  196. padding: 0;
  197. display: flex;
  198. flex-direction: column;
  199. height: 800px;
  200. }
  201. .left-header {
  202. padding: 10px;
  203. border-bottom: 1px solid #EBEEF5;
  204. }
  205. .left-header-top {
  206. display: flex;
  207. justify-content: space-between;
  208. align-items: center;
  209. margin-bottom: 10px;
  210. }
  211. .search-btn {
  212. width: 50%;
  213. height: 36px;
  214. background-color: #409EFF;
  215. color: white;
  216. border: none;
  217. }
  218. .search-input-wrapper {
  219. margin-bottom: 10px;
  220. }
  221. .sort-wrapper {
  222. display: flex;
  223. align-items: center;
  224. margin-bottom: 10px;
  225. }
  226. .sort-label {
  227. width: 70px;
  228. font-size: 14px;
  229. font-weight: 600;
  230. color: #909399;
  231. }
  232. .sort-select {
  233. margin-left: 10px;
  234. width: 280px;
  235. }
  236. .color-wrapper {
  237. display: flex;
  238. align-items: center;
  239. margin-bottom: 10px;
  240. }
  241. .color-label {
  242. width: 70px;
  243. color: #606266;
  244. }
  245. .color-hint {
  246. margin-left: 10px;
  247. color: #606266;
  248. font-size: 12px;
  249. }
  250. .color-help {
  251. margin-left: 5px;
  252. color: #909399;
  253. cursor: pointer;
  254. }
  255. .hint-text {
  256. color: #606266;
  257. font-size: 12px;
  258. margin-top: 5px;
  259. }
  260. .camp-list {
  261. flex: 1;
  262. overflow-y: auto;
  263. padding: 3px;
  264. }
  265. .camp-item {
  266. margin-bottom: 5px;
  267. padding: 15px;
  268. background-color: #ffffff;
  269. position: relative;
  270. cursor: pointer;
  271. display: flex;
  272. justify-content: space-between;
  273. border: 1px solid #eaedf2;
  274. }
  275. .camp-item:last-child {
  276. margin-bottom: 0;
  277. }
  278. .camp-item:hover {
  279. background-color: #f5f9ff;
  280. }
  281. .camp-item.active {
  282. background-color: #eaf4ff;
  283. border-left: 1px solid #75b8fc;
  284. }
  285. .camp-content {
  286. flex: 1;
  287. padding-right: 10px;
  288. }
  289. .camp-title {
  290. font-weight: bold;
  291. font-size: 16px;
  292. margin-bottom: 8px;
  293. color: #333;
  294. display: flex;
  295. align-items: center;
  296. }
  297. .camp-icon {
  298. font-size: 16px;
  299. margin-right: 6px;
  300. color: #409EFF;
  301. }
  302. .camp-info {
  303. display: flex;
  304. justify-content: space-between;
  305. margin-bottom: 8px;
  306. font-size: 12px;
  307. color: #c4c1c1;
  308. line-height: 1.5;
  309. }
  310. .camp-stats {
  311. display: flex;
  312. justify-content: space-between;
  313. font-size: 12px;
  314. color: #666;
  315. background-color: #f5f9ff;
  316. padding: 6px 10px;
  317. border-radius: 4px;
  318. line-height: 1.5;
  319. }
  320. .stat-item {
  321. display: flex;
  322. align-items: center;
  323. }
  324. .stat-item i {
  325. margin-right: 4px;
  326. font-size: 14px;
  327. color: #409EFF;
  328. }
  329. .camp-actions {
  330. display: flex;
  331. flex-direction: column;
  332. justify-content: center;
  333. align-items: flex-end;
  334. gap: 8px;
  335. border-left: 1px dashed #eaedf2;
  336. padding-left: 12px;
  337. min-width: 50px;
  338. }
  339. .action-btn {
  340. padding: 2px 5px;
  341. font-size: 12px;
  342. border-radius: 4px;
  343. transition: all 0.2s;
  344. }
  345. .action-btn:hover {
  346. background-color: rgba(255, 255, 255, 0.8);
  347. }
  348. .delete-btn {
  349. color: #f56c6c;
  350. }
  351. .delete-btn:hover {
  352. background-color: rgba(245, 108, 108, 0.1);
  353. }
  354. .copy-btn {
  355. color: #409EFF;
  356. }
  357. .copy-btn:hover {
  358. background-color: rgba(64, 158, 255, 0.1);
  359. }
  360. .warning-icon {
  361. color: #E6A23C;
  362. font-size: 16px;
  363. margin-left: 5px;
  364. }
  365. .el-main {
  366. padding: 10px;
  367. }
  368. /* 添加训练营表单样式 */
  369. .drawer-footer {
  370. /* position: absolute; */
  371. bottom: 0;
  372. left: 0;
  373. right: 0;
  374. padding: 20px;
  375. background: #fff;
  376. text-align: right;
  377. border-top: 1px solid #e8e8e8;
  378. }
  379. .el-input-number {
  380. width: 100%;
  381. }
  382. /* 加载更多样式 */
  383. .loading-more {
  384. display: flex;
  385. align-items: center;
  386. justify-content: center;
  387. padding: 12px 0;
  388. color: #909399;
  389. font-size: 14px;
  390. }
  391. .loading-more i {
  392. margin-right: 5px;
  393. font-size: 16px;
  394. }
  395. /* 无更多数据提示 */
  396. .no-more-data {
  397. display: flex;
  398. align-items: center;
  399. justify-content: center;
  400. padding: 12px 0;
  401. color: #c0c4cc;
  402. font-size: 13px;
  403. }
  404. .no-more-data span {
  405. position: relative;
  406. display: flex;
  407. align-items: center;
  408. }
  409. /* 添加没有数据提示的样式 */
  410. .no-data {
  411. display: flex;
  412. justify-content: center;
  413. align-items: center;
  414. height: 100vh; /* 让提示居中,覆盖整个页面 */
  415. font-size: 18px;
  416. color: #c0c4cc;
  417. background-color: #f5f5f5;
  418. }
  419. </style>