Browse Source

feat: 报名中心按课程分组展示班次;报名表单/我的报名/详情页区分课程与班次

liaoxg 1 week ago
parent
commit
575de83b12

+ 17 - 1
train-frontend/pages/course/detail.vue

@@ -41,8 +41,14 @@
       <button class="action-btn" :disabled="enterLoading" @click="applyEnter">重新申请进班</button>
     </view>
 
-    <!-- 已进班:按课程前/中/后阶段显示要做的事 -->
+    <!-- 已进班:课程学习安排(班次=上课期次,负责上课时间地点) -->
     <template v-if="enrollmentStatus === 'confirmed'">
+      <view class="class-info-card" v-if="className">
+        <text class="class-info-title">上课班次</text>
+        <text class="class-info-name">{{ className }}</text>
+        <text class="class-info-time" v-if="classTime">上课时间:{{ classTime }}</text>
+        <text class="class-info-place" v-if="classPlace">上课地点:{{ classPlace }}</text>
+      </view>
       <!-- 课程前:课前准备 -->
       <view class="stage-card" v-if="stage === 'upcoming'">
         <text class="stage-icon">📅</text>
@@ -97,6 +103,8 @@ export default {
     return {
       course: {},
       className: '',
+      classTime: '',
+      classPlace: '',
       enrollment: null,
       progress: { done: 0, total: 0, items: [] },
       enterLoading: false
@@ -163,6 +171,8 @@ export default {
         var c = d.course || {}
         self.course = c
         self.className = d.className || ''
+        self.classTime = d.classTime || ''
+        self.classPlace = d.classPlace || ''
         self.enrollment = d.enrollment || null
       }).catch(function() {})
       getCourseProgress().then(function(resp) {
@@ -250,6 +260,12 @@ export default {
 .course-time { font-size: 24rpx; color: #94A3B8; margin-bottom: 8rpx; }
 .course-class { font-size: 24rpx; color: #1E293B; background: #F1F5F9; border-radius: 8rpx; padding: 8rpx 16rpx; align-self: flex-start; }
 
+.class-info-card { background: #FFF; border-radius: 16rpx; padding: 28rpx 32rpx; margin-bottom: 24rpx; display: flex; flex-direction: column; }
+.class-info-title { font-size: 24rpx; color: #94A3B8; margin-bottom: 8rpx; }
+.class-info-name { font-size: 30rpx; font-weight: 700; color: #1E293B; margin-bottom: 8rpx; }
+.class-info-time { font-size: 26rpx; color: #475569; margin-bottom: 4rpx; }
+.class-info-place { font-size: 26rpx; color: #475569; }
+
 .stage-card { display: flex; align-items: center; background: #FFF; border-radius: 16rpx; padding: 28rpx 32rpx; margin-bottom: 24rpx; }
 .stage-icon { font-size: 48rpx; margin-right: 20rpx; }
 .stage-text { display: flex; flex-direction: column; }

+ 24 - 1
train-frontend/pages/enroll/form.vue

@@ -4,6 +4,17 @@
       <text class="section-title">报名信息</text>
       <text class="card-desc">请填写您的联系方式与准备情况</text>
 
+      <view class="summary-box" v-if="courseName || className">
+        <view class="summary-row" v-if="courseName">
+          <text class="summary-label">报名课程</text>
+          <text class="summary-value">{{ courseName }}</text>
+        </view>
+        <view class="summary-row" v-if="className">
+          <text class="summary-label">上课期次</text>
+          <text class="summary-value">{{ className }}</text>
+        </view>
+      </view>
+
       <view class="form-item">
         <text class="form-label">姓名</text>
         <input class="form-input" v-model="form.name" placeholder="请输入真实姓名" />
@@ -55,6 +66,7 @@ export default {
     return {
       form: {
         classId: '',
+        courseId: '',
         name: '',
         phone: '',
         bringLaptop: 0,
@@ -62,17 +74,22 @@ export default {
         topProblems: '',
         inviteCode: ''
       },
+      courseName: '',
+      className: '',
       dataReadyList: [],
       loading: false
     }
   },
   onLoad(options) {
     this.form.classId = options && options.classId ? options.classId : ''
+    this.form.courseId = options && options.courseId ? options.courseId : ''
+    this.courseName = options && options.courseName ? decodeURIComponent(options.courseName) : ''
+    this.className = options && options.className ? decodeURIComponent(options.className) : ''
     this.form.inviteCode = options && options.inviteCode ? decodeURIComponent(options.inviteCode) : ''
     this.form.name = options && options.name ? decodeURIComponent(options.name) : ''
     this.form.phone = options && options.phone ? decodeURIComponent(options.phone) : ''
     if (!this.form.classId) {
-      uni.showToast({ title: '请先选择班次', icon: 'none' })
+      uni.showToast({ title: '请先选择期次(班次', icon: 'none' })
       setTimeout(function() {
         uni.navigateBack({ delta: 1 })
       }, 1500)
@@ -107,6 +124,7 @@ export default {
       var self = this
       var payload = {
         classId: self.form.classId,
+        courseId: self.form.courseId,
         name: name,
         phone: phone,
         bringLaptop: self.form.bringLaptop,
@@ -140,6 +158,11 @@ export default {
 .section-card { background: #FFF; border-radius: 16rpx; padding: 32rpx; }
 .section-title { display: block; font-size: 32rpx; font-weight: 700; color: #1E293B; margin-bottom: 8rpx; border-left: 8rpx solid #F97316; padding-left: 16rpx; }
 .card-desc { display: block; font-size: 26rpx; color: #64748B; margin-bottom: 32rpx; }
+.summary-box { background: #FFF7ED; border-radius: 8rpx; padding: 20rpx 24rpx; margin-bottom: 24rpx; border: 2rpx solid #FED7AA; }
+.summary-row { display: flex; align-items: center; margin-bottom: 8rpx; }
+.summary-row:last-child { margin-bottom: 0; }
+.summary-label { font-size: 24rpx; color: #64748B; width: 140rpx; flex-shrink: 0; }
+.summary-value { font-size: 26rpx; color: #1E293B; font-weight: 600; flex: 1; }
 .form-item { margin-bottom: 24rpx; }
 .form-label { display: block; font-size: 26rpx; color: #475569; font-weight: 500; margin-bottom: 12rpx; }
 .form-input { height: 72rpx; border: 2rpx solid #E2E8F0; border-radius: 8rpx; padding: 0 16rpx; font-size: 28rpx; color: #1E293B; background: #F8FAFC; }

+ 59 - 26
train-frontend/pages/enroll/list.vue

@@ -1,29 +1,34 @@
 <template>
   <view class="enroll-page">
     <view class="section-card">
-      <text class="section-title">可报名班次</text>
-      <text class="card-desc">选择您想参加的班次,填写报名信息</text>
+      <text class="section-title">选择课程</text>
+      <text class="card-desc">课程决定学习内容,报名后按所选期次(班次)进班上课</text>
       <view v-if="loading" class="empty-tip">加载中…</view>
-      <view v-else-if="classList.length === 0" class="empty-tip">暂无可报名班次</view>
+      <view v-else-if="courseGroups.length === 0" class="empty-tip">暂无可报名课程</view>
       <view v-else>
-        <view class="class-card" v-for="item in classList" :key="item.id">
-          <view class="class-head">
-            <text class="class-name">{{ item.name }}</text>
-            <text class="class-price" v-if="item.memberPrice > 0 && item.memberPrice < item.price">
-              会员价 ¥{{ fenToYuan(item.memberPrice) }}
-            </text>
-            <text class="class-price" v-else-if="item.price > 0">¥{{ fenToYuan(item.price) }}</text>
-            <text class="class-price-free" v-else>免费</text>
+        <view class="course-group" v-for="(g, gi) in courseGroups" :key="gi">
+          <view class="group-head">
+            <text class="group-course">{{ g.courseName }}</text>
           </view>
-          <text class="class-time">{{ item.time || '时间待定' }}</text>
-          <text class="class-place">{{ item.place || '地点待定' }}</text>
-          <view class="class-foot">
-            <text class="class-capacity">
-              已报名 {{ item.num }} / {{ item.capacity }}
-              <text v-if="isFull(item)" class="full-tag">已满</text>
-              <text v-else class="remain-tag">余 {{ item.capacity - item.num }}</text>
-            </text>
-            <button class="enroll-btn" :disabled="isFull(item)" @click="goForm(item.id)">立即报名</button>
+          <view class="class-card" v-for="item in g.classes" :key="item.id">
+            <view class="class-head">
+              <text class="class-name">{{ item.name }}</text>
+              <text class="class-price" v-if="item.memberPrice > 0 && item.memberPrice < item.price">
+                会员价 ¥{{ fenToYuan(item.memberPrice) }}
+              </text>
+              <text class="class-price" v-else-if="item.price > 0">¥{{ fenToYuan(item.price) }}</text>
+              <text class="class-price-free" v-else>免费</text>
+            </view>
+            <text class="class-time">{{ item.time || '时间待定' }}</text>
+            <text class="class-place">{{ item.place || '地点待定' }}</text>
+            <view class="class-foot">
+              <text class="class-capacity">
+                已报名 {{ item.num }} / {{ item.capacity }}
+                <text v-if="isFull(item)" class="full-tag">已满</text>
+                <text v-else class="remain-tag">余 {{ item.capacity - item.num }}</text>
+              </text>
+              <button class="enroll-btn" :disabled="isFull(item)" @click="goForm(item)">立即报名</button>
+            </view>
           </view>
         </view>
       </view>
@@ -36,7 +41,10 @@
       <view v-else>
         <view class="mine-item" v-for="item in mineList" :key="item.id">
           <view class="mine-head">
-            <text class="mine-class">{{ item.className || ('班次' + item.classId) }}</text>
+            <text class="mine-class">
+              <text v-if="item.courseName" class="mine-course">{{ item.courseName }}</text>
+              {{ item.className || '' }}
+            </text>
             <text class="mine-status">{{ statusLabel(item.status) }}</text>
           </view>
           <text class="mine-time" v-if="item.createdAt">报名时间:{{ formatDateTime(item.createdAt) }}</text>
@@ -67,6 +75,24 @@ export default {
     this.loadClasses()
     this.loadMine()
   },
+  computed: {
+    // 报名对象=课程:可报名班次按课程分组(一组=一门课程下的各期班次)
+    courseGroups() {
+      var groups = []
+      var index = {}
+      var list = this.classList || []
+      for (var i = 0; i < list.length; i++) {
+        var item = list[i]
+        var key = item.courseId || ('course-' + item.id)
+        if (!index[key]) {
+          index[key] = { courseId: item.courseId, courseName: item.courseName || '课程', classes: [] }
+          groups.push(index[key])
+        }
+        index[key].classes.push(item)
+      }
+      return groups
+    }
+  },
   methods: {
     fenToYuan: fenToYuan,
     formatDateTime: formatDateTime,
@@ -94,12 +120,15 @@ export default {
       return item.capacity != null && item.num != null && item.num >= item.capacity
     },
     statusLabel(status) {
-      var map = { pending: '待支付', paid: '已支付', confirmed: '已确认', cancelled: '已取消' }
+      var map = { pending: '待支付', paid: '已支付', reviewing: '进班审核中', confirmed: '已进班', rejected: '进班被拒', cancelled: '已取消' }
       return map[status] || status || '未知'
     },
-    goForm(classId) {
+    goForm(item) {
       var userInfo = uni.getStorageSync('userInfo') || {}
-      uni.navigateTo({ url: '/pages/enroll/form?classId=' + classId + '&name=' + (userInfo.name || '') + '&phone=' + (userInfo.phone || '') })
+      // 报名对象=课程:透传 courseId(报名单挂课程)+ 课程名,班次为所选期次
+      var courseName = ''
+      if (item.courseName) courseName = item.courseName
+      uni.navigateTo({ url: '/pages/enroll/form?classId=' + item.id + '&courseId=' + (item.courseId || '') + '&courseName=' + encodeURIComponent(courseName) + '&className=' + encodeURIComponent(item.name || '') + '&name=' + (userInfo.name || '') + '&phone=' + (userInfo.phone || '') })
     },
     goPay(item) {
       uni.navigateTo({ url: '/pages/pay/index?enrollmentId=' + item.id })
@@ -130,7 +159,10 @@ export default {
 .section-title { display: block; font-size: 32rpx; font-weight: 700; color: #1E293B; margin-bottom: 8rpx; border-left: 8rpx solid #F97316; padding-left: 16rpx; }
 .card-desc { display: block; font-size: 26rpx; color: #64748B; margin-bottom: 24rpx; }
 .empty-tip { font-size: 26rpx; color: #94A3B8; padding: 32rpx 0; text-align: center; }
-.class-card { border: 2rpx solid #E2E8F0; border-radius: 12rpx; padding: 24rpx; margin-bottom: 20rpx; }
+.course-group { margin-bottom: 8rpx; }
+.group-head { display: flex; align-items: center; margin-bottom: 16rpx; }
+.group-course { font-size: 28rpx; font-weight: 600; color: #1E293B; }
+.class-card { border: 2rpx solid #E2E8F0; border-radius: 12rpx; padding: 24rpx; margin-bottom: 20rpx; margin-left: 16rpx; }
 .class-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12rpx; }
 .class-name { font-size: 30rpx; font-weight: 600; color: #1E293B; flex: 1; }
 .class-price { font-size: 28rpx; color: #F97316; font-weight: 600; }
@@ -146,7 +178,8 @@ export default {
 .mine-item { padding: 20rpx 0; border-bottom: 1rpx solid #F1F5F9; }
 .mine-item:last-child { border-bottom: none; }
 .mine-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8rpx; }
-.mine-class { font-size: 28rpx; color: #1E293B; font-weight: 500; }
+.mine-class { font-size: 28rpx; color: #1E293B; font-weight: 500; flex: 1; }
+.mine-course { font-weight: 700; margin-right: 8rpx; }
 .mine-status { font-size: 24rpx; color: #F97316; }
 .mine-time { font-size: 24rpx; color: #94A3B8; }
 .mine-actions { display: flex; justify-content: flex-end; margin-top: 16rpx; }

+ 6 - 2
train-web/src/views/Enrollments.vue

@@ -16,8 +16,9 @@
     </div>
 
     <el-table :data="list" v-loading="loading" border stripe style="width:100%">
-      <el-table-column prop="id" label="ID" width="80" />
-      <el-table-column prop="className" label="班次" min-width="140" />
+<el-table-column prop="id" label="ID" width="80" />
+<el-table-column prop="courseName" label="课程" min-width="150" />
+<el-table-column prop="className" label="班次" min-width="140" />
       <el-table-column prop="name" label="姓名" width="100" />
       <el-table-column prop="phone" label="手机号" width="130" />
       <el-table-column label="来源" width="90">
@@ -49,6 +50,9 @@
         <el-form-item label="学员">
           <span>{{ auditRow.name }}({{ auditRow.phone }})</span>
         </el-form-item>
+        <el-form-item label="课程">
+          <span>{{ auditRow.courseName || '-' }}</span>
+        </el-form-item>
         <el-form-item label="班次">
           <span>{{ auditRow.className }}</span>
         </el-form-item>