index.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. <template>
  2. <view class="course-page">
  3. <!-- 进行中的课程(上课状态) -->
  4. <view v-if="activeCourse" class="active-card">
  5. <view class="active-badge">
  6. <text class="active-badge-text">⏰ 课程进行中</text>
  7. </view>
  8. <text class="active-name">{{ activeCourse.level }} {{ activeCourse.name }}</text>
  9. <text class="active-desc">{{ activeCourse.description || '当前正在进行的课程' }}</text>
  10. <view class="active-time">
  11. <text class="active-time-text">开始 {{ fmtTime(activeCourse.startTime) }}</text>
  12. <text class="active-time-text" v-if="activeCourse.endTime">结束 {{ fmtTime(activeCourse.endTime) }}</text>
  13. </view>
  14. <button class="active-btn" :class="{ 'btn-disabled': isReviewing(activeCourse) }"
  15. :disabled="isReviewing(activeCourse)" @click="handleAction(activeCourse)">
  16. {{ actionText(activeCourse) }}
  17. </button>
  18. </view>
  19. <view class="section-title">全部课程</view>
  20. <view class="course-list">
  21. <view class="course-card" v-for="(c, idx) in courses" :key="idx">
  22. <view class="course-head">
  23. <text class="course-level">{{ c.level }}</text>
  24. <text class="course-name">{{ c.name }}</text>
  25. </view>
  26. <view class="course-desc" v-if="c.description">{{ c.description }}</view>
  27. <view class="course-tags">
  28. <text class="course-tag" :class="'st-' + c.effectiveStatus">{{ statusLabel(c.effectiveStatus) }}</text>
  29. <text class="course-tag mine" :class="'my-' + c.myStatus">{{ myLabel(c.myStatus) }}</text>
  30. </view>
  31. <button class="course-btn" :class="{ 'btn-disabled': isReviewing(c) }"
  32. :disabled="isReviewing(c)" @click="handleAction(c)">
  33. {{ actionText(c) }}
  34. </button>
  35. </view>
  36. </view>
  37. </view>
  38. </template>
  39. <script>
  40. import { getCourseList, enterCourse } from '@/utils/api.js'
  41. import { formatDateTime } from '@/utils/format.js'
  42. import { requireLogin } from '@/utils/guard.js'
  43. export default {
  44. data() {
  45. return {
  46. courses: [],
  47. loading: false
  48. }
  49. },
  50. computed: {
  51. isLoggedIn() {
  52. return this.$store.getters.isLoggedIn
  53. },
  54. activeCourse() {
  55. var list = this.courses || []
  56. for (var i = 0; i < list.length; i++) {
  57. if (list[i].effectiveStatus === 'active') {
  58. return list[i]
  59. }
  60. }
  61. return null
  62. }
  63. },
  64. onShow() {
  65. this.loadCourses()
  66. },
  67. methods: {
  68. loadCourses() {
  69. // 未登录也可浏览课程信息(后端 /api/course/list 已放行,我的状态为 none)
  70. var self = this
  71. self.loading = true
  72. getCourseList().then(function(resp) {
  73. self.courses = resp.data || []
  74. }).catch(function() {
  75. uni.showToast({ title: '获取课程列表失败', icon: 'none' })
  76. }).finally(function() {
  77. self.loading = false
  78. })
  79. },
  80. statusLabel(st) {
  81. var map = { upcoming: '未开始', active: '进行中', finished: '已结束', draft: '未发布' }
  82. return map[st] || st || '-'
  83. },
  84. myLabel(st) {
  85. var map = { none: '未报名', pending: '待支付', paid: '已支付', reviewing: '审核中', confirmed: '已进班', rejected: '未通过' }
  86. return map[st] || st || '-'
  87. },
  88. isReviewing(c) {
  89. return c && c.myStatus === 'reviewing'
  90. },
  91. actionText(c) {
  92. if (!c) return ''
  93. var s = c.myStatus
  94. if (s === 'confirmed') return '进入课程'
  95. if (s === 'reviewing') return '等待审核'
  96. if (s === 'pending') return '去支付'
  97. if (s === 'paid' || s === 'rejected') return '申请进班'
  98. return '报名进班'
  99. },
  100. handleAction(c) {
  101. if (!c) return
  102. // 报名/支付/申请进班均为实际操作,未登录需先登录(课程浏览已放行)
  103. if (!requireLogin()) return
  104. var s = c.myStatus
  105. if (s === 'confirmed') {
  106. uni.navigateTo({ url: '/pages/course/detail?courseId=' + c.id })
  107. } else if (s === 'pending') {
  108. uni.navigateTo({ url: '/pages/enroll/list' })
  109. } else if (s === 'paid' || s === 'rejected') {
  110. this.applyEnter(c)
  111. } else {
  112. // 未报名:先走报名流程
  113. uni.navigateTo({ url: '/pages/enroll/list' })
  114. }
  115. },
  116. applyEnter(c) {
  117. var self = this
  118. uni.showModal({
  119. title: '申请进班',
  120. content: '确认申请进入「' + c.name + '」?班主任/管理员审核通过后即可进入课程。',
  121. success: function(res) {
  122. if (!res.confirm) return
  123. enterCourse(c.id).then(function(resp) {
  124. var d = resp.data || {}
  125. uni.showToast({ title: d.message || '申请已提交', icon: 'none' })
  126. self.loadCourses()
  127. }).catch(function(err) {
  128. var msg = (err && err.message) || '申请失败'
  129. uni.showToast({ title: msg, icon: 'none' })
  130. // 未完成报名/支付等场景引导去报名
  131. if (msg.indexOf('报名') !== -1 || msg.indexOf('支付') !== -1) {
  132. setTimeout(function() {
  133. uni.navigateTo({ url: '/pages/enroll/list' })
  134. }, 1200)
  135. }
  136. })
  137. }
  138. })
  139. },
  140. fmtTime(v) {
  141. if (!v) return ''
  142. return formatDateTime(v)
  143. }
  144. }
  145. }
  146. </script>
  147. <style scoped>
  148. .course-page { min-height: 100vh; background: #F5F5F5; padding: 24rpx 32rpx; }
  149. .section-title { font-size: 30rpx; font-weight: 600; color: #1E293B; margin-bottom: 16rpx; border-left: 8rpx solid #F97316; padding-left: 16rpx; }
  150. .active-card { background: linear-gradient(135deg, #F97316, #EA580C); border-radius: 20rpx; padding: 32rpx; margin-bottom: 32rpx; }
  151. .active-badge { display: inline-block; background: rgba(255,255,255,0.2); border-radius: 100rpx; padding: 4rpx 20rpx; margin-bottom: 16rpx; }
  152. .active-badge-text { font-size: 22rpx; color: #FFF; font-weight: 600; }
  153. .active-name { display: block; font-size: 36rpx; font-weight: 700; color: #FFF; margin-bottom: 8rpx; }
  154. .active-desc { display: block; font-size: 26rpx; color: rgba(255,255,255,0.9); margin-bottom: 16rpx; }
  155. .active-time { display: flex; flex-direction: column; margin-bottom: 24rpx; }
  156. .active-time-text { font-size: 22rpx; color: rgba(255,255,255,0.8); margin-bottom: 4rpx; }
  157. .active-btn { background: #FFF; color: #EA580C; font-size: 30rpx; font-weight: 600; border-radius: 48rpx; height: 80rpx; line-height: 80rpx; border: none; margin: 0; }
  158. .active-btn:active { opacity: 0.9; }
  159. .course-list { display: flex; flex-direction: column; }
  160. .course-card { background: #FFF; border-radius: 16rpx; padding: 28rpx; margin-bottom: 20rpx; }
  161. .course-head { display: flex; align-items: center; margin-bottom: 8rpx; }
  162. .course-level { font-size: 24rpx; font-weight: 600; color: #F97316; background: #FFF7ED; border-radius: 8rpx; padding: 4rpx 14rpx; margin-right: 16rpx; }
  163. .course-name { font-size: 30rpx; font-weight: 600; color: #1E293B; flex: 1; }
  164. .course-desc { font-size: 24rpx; color: #64748B; margin-bottom: 16rpx; line-height: 1.6; }
  165. .course-tags { display: flex; align-items: center; margin-bottom: 20rpx; }
  166. .course-tag { font-size: 20rpx; color: #64748B; background: #F1F5F9; border-radius: 6rpx; padding: 4rpx 14rpx; margin-right: 12rpx; }
  167. .course-tag.st-upcoming { color: #F97316; background: #FFF7ED; }
  168. .course-tag.st-active { color: #22C55E; background: #F0FDF4; }
  169. .course-tag.st-finished { color: #94A3B8; background: #F1F5F9; }
  170. .course-tag.mine { color: #6366F1; background: #EEF2FF; }
  171. .course-tag.my-confirmed { color: #22C55E; background: #F0FDF4; }
  172. .course-tag.my-reviewing { color: #F97316; background: #FFF7ED; }
  173. .course-tag.my-rejected { color: #EF5350; background: #FEF2F2; }
  174. .course-btn { background: linear-gradient(135deg, #F97316, #EA580C); color: #FFF; font-size: 28rpx; font-weight: 600; border-radius: 44rpx; height: 76rpx; line-height: 76rpx; border: none; margin: 0; }
  175. .course-btn:active { opacity: 0.9; }
  176. .btn-disabled { background: #E2E8F0; color: #94A3B8; }
  177. </style>