Просмотр исходного кода

feat(admin): enhance activity list - view ended/published activities, disable edit for non-draft, click participant count to review registrations inline

Xiaogang Liao 2 месяцев назад
Родитель
Сommit
59487a5465
1 измененных файлов с 140 добавлено и 9 удалено
  1. 140 9
      cfc-web/src/views/admin/Activities.vue

+ 140 - 9
cfc-web/src/views/admin/Activities.vue

@@ -40,7 +40,11 @@
           <template slot-scope="{ row }">{{ row.price ? (row.price / 100).toFixed(2) : '免费' }}</template>
           <template slot-scope="{ row }">{{ row.price ? (row.price / 100).toFixed(2) : '免费' }}</template>
         </el-table-column>
         </el-table-column>
         <el-table-column label="人数" width="90">
         <el-table-column label="人数" width="90">
-          <template slot-scope="{ row }">{{ row.currentParticipants || 0 }}/{{ row.maxParticipants || '不限' }}</template>
+          <template slot-scope="{ row }">
+            <el-button type="text" size="mini" @click="showRegistrations(row)" style="padding:0;">
+              {{ row.currentParticipants || 0 }}/{{ row.maxParticipants || '不限' }}
+            </el-button>
+          </template>
         </el-table-column>
         </el-table-column>
         <el-table-column label="状态" width="100">
         <el-table-column label="状态" width="100">
           <template slot-scope="{ row }">
           <template slot-scope="{ row }">
@@ -51,9 +55,10 @@
         </el-table-column>
         </el-table-column>
         <el-table-column prop="startTime" label="开始时间" width="170" />
         <el-table-column prop="startTime" label="开始时间" width="170" />
         <el-table-column prop="endTime" label="结束时间" width="170" />
         <el-table-column prop="endTime" label="结束时间" width="170" />
-        <el-table-column label="操作" width="240">
+        <el-table-column label="操作" width="280">
           <template slot-scope="{ row }">
           <template slot-scope="{ row }">
-            <el-button size="mini" @click="editActivity(row)" :disabled="row.status === 'ended'">编辑</el-button>
+            <el-button size="mini" @click="viewActivity(row)" v-if="row.status !== 'draft'">查看</el-button>
+            <el-button size="mini" @click="editActivity(row)" :disabled="row.status !== 'draft'" v-else>编辑</el-button>
             <el-button
             <el-button
               size="mini"
               size="mini"
               type="success"
               type="success"
@@ -83,12 +88,12 @@
     </el-card>
     </el-card>
 
 
     <el-dialog
     <el-dialog
-      :title="editId ? '编辑活动' : '创建活动'"
+      :title="viewMode ? '查看活动' : (editId ? '编辑活动' : '创建活动')"
       :visible.sync="dialogVisible"
       :visible.sync="dialogVisible"
       width="700px"
       width="700px"
       @closed="resetForm"
       @closed="resetForm"
     >
     >
-      <el-form label-width="120px">
+      <el-form label-width="120px" ref="activityForm">
         <el-form-item label="活动标题">
         <el-form-item label="活动标题">
           <el-input v-model="form.title" placeholder="请输入活动标题" />
           <el-input v-model="form.title" placeholder="请输入活动标题" />
         </el-form-item>
         </el-form-item>
@@ -188,16 +193,50 @@
         </el-form-item>
         </el-form-item>
       </el-form>
       </el-form>
       <div slot="footer">
       <div slot="footer">
-        <el-button @click="dialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="saveActivity" :loading="saving">保存</el-button>
+        <el-button @click="dialogVisible = false">{{ viewMode ? '关闭' : '取消' }}</el-button>
+        <el-button v-if="!viewMode" type="primary" @click="saveActivity" :loading="saving">保存</el-button>
       </div>
       </div>
     </el-dialog>
     </el-dialog>
+
+    <!-- 报名列表对话框 -->
+    <el-dialog
+      :title="'报名列表 - ' + (currentActivity ? currentActivity.title : '')"
+      :visible.sync="regDialogVisible"
+      width="800px"
+      @closed="regDialogVisible = false"
+    >
+      <el-table :data="registrations" v-loading="regLoading" border stripe>
+        <el-table-column prop="id" label="ID" width="60" />
+        <el-table-column prop="userId" label="用户ID" width="70" />
+        <el-table-column prop="childId" label="孩子ID" width="70" />
+        <el-table-column label="状态" width="90">
+          <template slot-scope="{ row }">
+            <el-tag :type="row.status === 'approved' ? 'success' : row.status === 'rejected' ? 'danger' : 'warning'">
+              {{ row.status === 'approved' ? '已通过' : row.status === 'rejected' ? '已拒绝' : '待审核' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="registeredAt" label="报名时间" width="160">
+          <template slot-scope="{ row }">
+            {{ formatTime(row.registeredAt || row.createdAt) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button v-if="row.status === 'pending'" type="success" size="small" @click="handleRegApprove(row)">通过</el-button>
+            <el-button v-if="row.status === 'pending'" type="danger" size="small" @click="handleRegReject(row)">拒绝</el-button>
+            <span v-else style="color: #999;">已处理</span>
+          </template>
+        </el-table-column>
+      </el-table>
+      <p v-if="!registrations.length && !regLoading" style="text-align:center;color:#999;">暂无报名记录</p>
+    </el-dialog>
   </div>
   </div>
 </template>
 </template>
 
 
 <script>
 <script>
 import DimensionWeightPicker from '@/components/DimensionWeightPicker.vue'
 import DimensionWeightPicker from '@/components/DimensionWeightPicker.vue'
-import { getActivityList, createActivity, updateActivity, publishActivity, endActivity } from '@/api/activity'
+import { getActivityList, createActivity, updateActivity, publishActivity, endActivity, getRegistrationList, approveRegistration, rejectRegistration } from '@/api/activity'
 import { saveDimensionWeights, getDimensionWeights } from '@/api/dimension-weight.js'
 import { saveDimensionWeights, getDimensionWeights } from '@/api/dimension-weight.js'
 
 
 const STATUS_MAP = {
 const STATUS_MAP = {
@@ -228,6 +267,7 @@ export default {
       activities: [],
       activities: [],
       total: 0,
       total: 0,
       editId: null,
       editId: null,
+      viewMode: false,
       dialogVisible: false,
       dialogVisible: false,
       filter: {
       filter: {
         status: '',
         status: '',
@@ -250,7 +290,12 @@ export default {
         visibleScope: '',
         visibleScope: '',
         visibleTo: '',
         visibleTo: '',
         checkinPoints: 0
         checkinPoints: 0
-      }
+      },
+      // 报名列表对话框
+      regDialogVisible: false,
+      regLoading: false,
+      currentActivity: null,
+      registrations: []
     }
     }
   },
   },
   computed: {
   computed: {
@@ -334,10 +379,12 @@ export default {
     },
     },
     openCreate() {
     openCreate() {
       this.editId = null
       this.editId = null
+      this.viewMode = false
       this.resetForm()
       this.resetForm()
       this.dialogVisible = true
       this.dialogVisible = true
     },
     },
     resetForm() {
     resetForm() {
+      this.viewMode = false
       this.form = {
       this.form = {
         title: '',
         title: '',
         coverImage: '',
         coverImage: '',
@@ -358,6 +405,7 @@ export default {
     },
     },
     editActivity(row) {
     editActivity(row) {
       this.editId = row.id
       this.editId = row.id
+      this.viewMode = false
       this.form = {
       this.form = {
         title: row.title || '',
         title: row.title || '',
         coverImage: row.coverImage || '',
         coverImage: row.coverImage || '',
@@ -458,6 +506,89 @@ export default {
       } catch (e) {
       } catch (e) {
         this.$message.error('操作失败')
         this.$message.error('操作失败')
       }
       }
+    },
+    // ===== 查看活动(只读,使用同一对话框) =====
+    viewActivity(row) {
+      this.viewMode = true
+      this.editId = row.id
+      this.form = {
+        title: row.title || '',
+        coverImage: row.coverImage || '',
+        activityType: row.activityType || '',
+        description: row.description || '',
+        dimensionCode: row.dimensionCode || '',
+        dimensionWeights: [],
+        location: row.location || '',
+        maxParticipants: row.maxParticipants || null,
+        priceYuan: row.price ? row.price / 100 : null,
+        startTime: row.startTime || null,
+        endTime: row.endTime || null,
+        visibility: row.visibility || 'public',
+        visibleScope: row.visibleScope || '',
+        visibleTo: row.visibleTo || '',
+        checkinPoints: row.checkinPoints || 0
+      }
+      this.dialogVisible = true
+      this.loadDimensionWeights(row.id)
+      this.$nextTick(() => {
+        if (this.$refs.activityForm) {
+          // 禁用所有 el-form-item 内的输入控件
+          this.$refs.activityForm.$el.querySelectorAll('input, textarea, .el-select, .el-input-number, .el-switch, .el-date-editor, .el-upload').forEach(el => {
+            el.classList.add('is-disabled')
+            const inp = el.querySelector('input') || el.querySelector('textarea')
+            if (inp) inp.disabled = true
+          })
+        }
+      })
+    },
+    // ===== 报名列表 =====
+    showRegistrations(row) {
+      this.currentActivity = row
+      this.regDialogVisible = true
+      this.loadRegistrations(row.id)
+    },
+    async loadRegistrations(activityId) {
+      this.regLoading = true
+      try {
+        const res = await getRegistrationList({ activityId })
+        if (res.code === 200) {
+          this.registrations = res.data || []
+        }
+      } catch (e) {
+        this.$message.error('加载报名列表失败')
+      } finally {
+        this.regLoading = false
+      }
+    },
+    formatTime(t) {
+      if (!t) return '-'
+      return t.replace ? t.replace('T', ' ').substring(0, 19) : t
+    },
+    async handleRegApprove(row) {
+      try {
+        const res = await approveRegistration({ registrationId: row.id })
+        if (res.code === 200) {
+          this.$message.success('已通过')
+          this.loadRegistrations(this.currentActivity.id)
+        } else {
+          this.$message.error(res.message || '操作失败')
+        }
+      } catch (e) {
+        this.$message.error('操作失败')
+      }
+    },
+    async handleRegReject(row) {
+      try {
+        const res = await rejectRegistration({ registrationId: row.id })
+        if (res.code === 200) {
+          this.$message.success('已拒绝')
+          this.loadRegistrations(this.currentActivity.id)
+        } else {
+          this.$message.error(res.message || '操作失败')
+        }
+      } catch (e) {
+        this.$message.error('操作失败')
+      }
     }
     }
   }
   }
 }
 }