Переглянути джерело

fix: 统一活动详情页,删除 discover-detail 重复文件

活动中心 index.vue 导航指向 discover-detail 版本的简化详情页(无评价/分享/会员价)。改为指向 activity 分包的增强版,并删除重复的 discover-detail/activity-detail.vue。

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
iwt 3 місяців тому
батько
коміт
4ba677bd14

+ 2 - 8
cfc-frontend/pages.json

@@ -1036,12 +1036,6 @@
           "style": {
             "navigationBarTitleText": "商品详情"
           }
-        },
-        {
-          "path": "activity-detail/activity-detail",
-          "style": {
-            "navigationBarTitleText": "活动详情"
-          }
         }
       ]
     },
@@ -1225,7 +1219,7 @@
     {
       "root": "pages/member-detail",
       "pages": [
-{
+        {
           "path": "report-detail",
           "style": {
             "navigationBarTitleText": "报告详情"
@@ -1437,4 +1431,4 @@
       "pages/*": "components/@dcloudio/uni-$2/components/uni-$1/uni-$1.vue"
     }
   }
-}
+}

+ 1 - 1
cfc-frontend/pages/activity/index.vue

@@ -650,7 +650,7 @@ export default {
       })
     },
     goDetail: function(act) {
-      uni.navigateTo({ url: '/pages/discover-detail/activity-detail/activity-detail?id=' + act.id })
+      uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + act.id })
     },
     switchToDiscover: function() {
       this.currentTab = 'discover'

+ 0 - 671
cfc-frontend/pages/discover-detail/activity-detail/activity-detail.vue

@@ -1,671 +0,0 @@
-<template>
-  <view class="container">
-    <!-- 加载状态 -->
-    <view v-if="loading" class="loading-wrap">
-      <view class="loading-spinner"></view>
-      <text class="loading-text">加载中...</text>
-    </view>
-
-    <!-- 错误状态 -->
-    <view v-else-if="error" class="error-wrap">
-      <text class="error-icon">📋</text>
-      <text class="error-text">{{ errorMsg }}</text>
-      <button class="retry-btn" @click="loadDetail(activityId)">重新加载</button>
-    </view>
-
-    <!-- 活动内容 -->
-    <scroll-view v-else-if="activity.id" scroll-y class="content">
-      <!-- 封面图 -->
-      <image
-        v-if="activity.coverImage"
-        class="cover"
-        :src="activity.coverImage"
-        mode="widthFix"
-      />
-      <image v-else class="cover placeholder-cover" src="/static/default-activity.png" mode="aspectFill" />
-
-      <view class="info-section">
-        <text class="activity-title">{{ activity.title }}</text>
-
-        <!-- 活动状态标签 -->
-        <view class="status-row">
-          <text class="status-tag" :class="'status-' + (activity.status || 'upcoming')">{{ statusText(activity.status) }}</text>
-        </view>
-
-        <!-- 活动元信息 -->
-        <view class="meta-list">
-          <view class="meta-item" v-if="activity.startTime">
-            <text class="meta-icon">🕐</text>
-            <text class="meta-text">{{ formatDate(activity.startTime) }}{{ activity.endTime ? ' ~ ' + formatDate(activity.endTime) : '' }}</text>
-          </view>
-          <view class="meta-item" v-if="activity.location">
-            <text class="meta-icon">📍</text>
-            <text class="meta-text">{{ activity.location }}</text>
-          </view>
-          <view class="meta-item" v-if="activity.maxParticipants">
-            <text class="meta-icon">👥</text>
-            <text class="meta-text">{{ activity.currentParticipants || 0 }} / {{ activity.maxParticipants }} 人</text>
-          </view>
-          <view class="meta-item">
-            <text class="meta-icon">💰</text>
-            <text class="meta-text">{{ activity.priceLabel || (activity.price > 0 ? '¥' + activity.price : '免费') }}</text>
-          </view>
-        </view>
-
-        <!-- 活动简介 -->
-        <view class="desc-section" v-if="activity.description">
-          <text class="section-title">活动介绍</text>
-          <text class="desc-text">{{ activity.description }}</text>
-        </view>
-
-        <!-- 付费不退费说明 -->
-        <view class="refund-notice" v-if="activity.price > 0">
-          <text class="refund-notice-icon">⚠️</text>
-          <text class="refund-notice-text">本活动为付费活动,报名成功后不支持退费</text>
-        </view>
-      </view>
-
-      <!-- 底部操作栏 -->
-      <view class="bottom-bar">
-        <view class="price-info">
-          <text class="bottom-type">活动</text>
-          <text class="bottom-vendor" v-if="activity.vendorName">{{ activity.vendorName }}</text>
-        </view>
-        <view class="action-btns">
-          <!-- 报名按钮(仅 requireRegistration=1 时显示) -->
-          <button
-            v-if="activity.requireRegistration === 1 && !registrationStatus"
-            class="btn-register"
-            :class="{ 'btn-register-loading': registrationLoading || paying }"
-            :disabled="registrationLoading || paying"
-            @click="onRegister"
-          >
-            <text v-if="registrationLoading">报名中...</text>
-            <text v-else-if="paying">支付中...</text>
-            <text v-else-if="activity.price > 0">¥{{ activity.price }} 立即报名</text>
-            <text v-else>立即报名</text>
-          </button>
-          
-          <!-- 已报名待审核 -->
-          <button
-            v-if="activity.requireRegistration === 1 && registrationStatus === 'pending'"
-            class="btn-register-done"
-            disabled
-          >
-            <text>报名待审核</text>
-          </button>
-          
-          <!-- 已报名可取消 -->
-          <button
-            v-if="activity.requireRegistration === 1 && registrationStatus === 'approved'"
-            class="btn-cancel"
-            :disabled="registrationLoading"
-            @click="onCancelRegistration"
-          >
-            <text>取消报名</text>
-          </button>
-          
-          <!-- 签到按钮(无需报名时始终显示;需报名时仅审核通过后显示) -->
-          <button
-            v-if="!activity.requireRegistration || registrationStatus === 'approved'"
-            class="btn-checkin"
-            :class="{ 'btn-checkin-done': checkinDone, 'btn-checkin-loading': checkinLoading }"
-            :disabled="checkinLoading || checkinDone"
-            @click="onCheckin"
-          >
-            <text v-if="checkinLoading">签到中...</text>
-            <text v-else-if="checkinDone">已签到 ✓</text>
-            <text v-else>签到</text>
-          </button>
-        </view>
-      </view>
-      
-      <!-- 能量扣减提示弹窗 -->
-      <modal v-if="showPenaltyModal" class="penalty-modal" :show="showPenaltyModal" @close="showPenaltyModal = false">
-        <view class="penalty-content">
-          <text class="penalty-title">签到成功但已扣能量</text>
-          <text class="penalty-desc">您未报名参加此活动,已被扣除 {{ penaltyAmount }} 点能量值</text>
-          <button class="penalty-btn" @click="showPenaltyModal = false">我知道了</button>
-        </view>
-      </modal>
-    </scroll-view>
-
-    <!-- 空状态 -->
-    <view v-else class="empty-wrap">
-      <text class="empty-text">活动不存在或已下架</text>
-    </view>
-  </view>
-</template>
-
-<script>
-import { getActivityDetail, activityCheckin, registerActivity, cancelActivityRegistration, getMyActivityRegistrations } from '@/utils/api.js'
-
-export default {
-  data() {
-    return {
-      activityId: null,
-      activity: {},
-      loading: false,
-      error: false,
-      errorMsg: '',
-      checkinLoading: false,
-      checkinDone: false,
-      checkinPoints: 0,
-      registrationStatus: null,
-      registrationLoading: false,
-      paying: false,
-      showPenaltyModal: false,
-      penaltyAmount: 0
-    }
-  },
-  onLoad: function(options) {
-    if (options && options.id) {
-      this.activityId = options.id
-      this.loadDetail(options.id)
-    } else {
-      this.error = true
-      this.errorMsg = '缺少活动ID'
-    }
-
-    // 检查是否已经签到(从路由参数获取)
-    if (options && options.checkinDone === '1') {
-      this.checkinDone = true
-    }
-  },
-  methods: {
-    // ===== 工具方法 =====
-    formatDate: function(dateStr) {
-      if (!dateStr) return ''
-      var d = new Date(dateStr.replace(/-/g, '/'))
-      if (isNaN(d.getTime())) return dateStr
-      var year = d.getFullYear()
-      var month = d.getMonth() + 1
-      var day = d.getDate()
-      var hour = d.getHours()
-      var minute = d.getMinutes()
-      var ampm = hour < 12 ? '上午' : '下午'
-      var h12 = hour % 12
-      if (h12 === 0) h12 = 12
-      var minStr = minute < 10 ? '0' + minute : minute
-      return year + '年' + month + '月' + day + '日 ' + ampm + h12 + ':' + minStr
-    },
-    loadDetail: function(id) {
-      var self = this
-      self.loading = true
-      self.error = false
-      getActivityDetail(id).then(function(res) {
-        self.loading = false
-        if (res && res.code === 200 && res.data) {
-          self.activity = res.data
-          // Load registration status if logged in and activity requires registration
-          if (self.activity.requireRegistration === 1) {
-            var childId = uni.getStorageSync('currentChildId')
-            if (childId) {
-              getMyActivityRegistrations({ childId: childId, page: 1, size: 50 }).then(function(regRes) {
-                if (regRes && regRes.data && regRes.data.records) {
-                  var records = regRes.data.records
-                  for (var i = 0; i < records.length; i++) {
-                    if (records[i].activityId === self.activity.id) {
-                      self.registrationStatus = records[i].status
-                      break
-                    }
-                  }
-                }
-              }).catch(function() {})
-            }
-          }
-        } else {
-          self.error = true
-          self.errorMsg = (res && res.message) || '加载失败'
-        }
-      }).catch(function(err) {
-        self.loading = false
-        self.error = true
-        self.errorMsg = '网络异常,请稍后重试'
-      })
-    },
-    statusText: function(status) {
-      var map = { draft: '草稿', published: '进行中', ended: '已结束' }
-      return map[status] || '未知'
-    },
-    onCheckin: function() {
-      var self = this
-      if (self.checkinLoading || self.checkinDone) return
-
-      // 检查活动是否已开始(只有进行中状态且当前时间在活动时间内才能签到)
-      if (self.activity.status !== 'published') {
-        uni.showToast({ title: '活动尚未开始,无法签到', icon: 'none' })
-        return
-      }
-
-      // 检查当前时间是否在活动时间内
-      if (self.activity.startTime) {
-        var now = new Date()
-        var startTime = new Date(self.activity.startTime.replace(/-/g, '/'))
-        if (startTime > now) {
-          uni.showToast({ title: '活动尚未开始,无法签到', icon: 'none' })
-          return
-        }
-      }
-
-      // 需报名的活动:未审核通过不允许签到
-      if (self.activity.requireRegistration === 1 && self.registrationStatus !== 'approved') {
-        uni.showToast({ title: '请先报名活动', icon: 'none' })
-        return
-      }
-
-      var childId = uni.getStorageSync('currentChildId')
-      if (!childId) {
-        uni.showToast({ title: '请选择孩子身份', icon: 'none' })
-        return
-      }
-
-      self.checkinLoading = true
-      activityCheckin({ id: self.activity.id, childId: childId }).then(function(res) {
-        self.checkinLoading = false
-        if (res && res.code === 200 && res.data) {
-          // 检查是否有能量扣减
-          if (res.data.energyPenalty) {
-            self.penaltyAmount = res.data.energyPenalty
-            self.showPenaltyModal = true
-            self.checkinDone = true
-            return
-          }
-          if (res.data.alreadyCompleted) {
-            self.checkinDone = true
-            uni.showToast({ title: '已签到', icon: 'success' })
-          } else if (res.data.pointsEarned && res.data.pointsEarned > 0) {
-            self.checkinDone = true
-            self.checkinPoints = res.data.pointsEarned
-            uni.showToast({ title: '签到成功 +' + res.data.pointsEarned + '积分', icon: 'success' })
-          } else {
-            self.checkinDone = true
-            uni.showToast({ title: '签到成功', icon: 'success' })
-          }
-        } else {
-          var msg = (res && res.message) || '签到失败'
-          // 如果是报名待审核,提示用户
-          if (msg.indexOf('报名') !== -1 || msg.indexOf('审核') !== -1) {
-            uni.showModal({ title: '提示', content: msg, showCancel: false })
-          } else {
-            uni.showToast({ title: msg, icon: 'none' })
-          }
-        }
-      }).catch(function(err) {
-        self.checkinLoading = false
-        uni.showToast({ title: '网络异常', icon: 'none' })
-      })
-    },
-    onRegister: function() {
-      var self = this
-      var childId = uni.getStorageSync('currentChildId')
-      if (!childId) {
-        uni.showToast({ title: '请先选择孩子身份', icon: 'none' })
-        return
-      }
-
-      // 付费活动 → 先确认支付
-      if (self.activity && self.activity.price > 0) {
-        uni.showModal({
-          title: '确认报名',
-          content: '该活动需支付 ¥' + self.activity.price + ',报名成功后不支持退费。是否继续?',
-          confirmText: '确认支付',
-          cancelText: '再想想',
-          success: function(confirmRes) {
-            if (confirmRes.confirm) {
-              self.doRegister(childId)
-            }
-          }
-        })
-      } else {
-        self.doRegister(childId)
-      }
-    },
-    doRegister: function(childId) {
-      var self = this
-      self.registrationLoading = true
-      if (self.activity && self.activity.price > 0) {
-        self.paying = true
-      }
-      registerActivity({ id: self.activity.id, childId: childId }).then(function(res) {
-        self.registrationLoading = false
-        self.paying = false
-        if (res && res.code === 200) {
-          self.registrationStatus = 'pending'
-          uni.showToast({ title: '报名成功,请等待审核', icon: 'success' })
-        } else {
-          uni.showToast({ title: (res && res.message) || '报名失败', icon: 'none' })
-        }
-      }).catch(function(err) {
-        self.registrationLoading = false
-        self.paying = false
-        uni.showToast({ title: '网络异常', icon: 'none' })
-      })
-    },
-    onCancelRegistration: function() {
-      var self = this
-      uni.showModal({
-        title: '提示',
-        content: '确定要取消报名吗?',
-        success: function(res) {
-          if (res.confirm) {
-            var childId = uni.getStorageSync('currentChildId')
-            self.registrationLoading = true
-            cancelActivityRegistration({ id: self.activity.id, childId: childId }).then(function(res) {
-              self.registrationLoading = false
-              if (res && res.code === 200) {
-                self.registrationStatus = null
-                uni.showToast({ title: '已取消报名', icon: 'success' })
-              } else {
-                uni.showToast({ title: (res && res.message) || '取消失败', icon: 'none' })
-              }
-            }).catch(function() {
-              self.registrationLoading = false
-              uni.showToast({ title: '网络异常', icon: 'none' })
-            })
-          }
-        }
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.container {
-  min-height: 100vh;
-  background: #FFF7ED;
-}
-
-/* 加载 / 错误 / 空状态 */
-.loading-wrap,
-.error-wrap,
-.empty-wrap {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  padding: 120rpx 40rpx;
-}
-.loading-spinner {
-  width: 60rpx;
-  height: 60rpx;
-  border: 4rpx solid #FDE68A;
-  border-top-color: #F97316;
-  border-radius: 50%;
-  animation: spin 0.8s linear infinite;
-  margin-bottom: 20rpx;
-}
-@keyframes spin {
-  to { transform: rotate(360deg); }
-}
-.loading-text,
-.error-text,
-.empty-text {
-  font-size: 28rpx;
-  color: #999;
-  margin-bottom: 20rpx;
-}
-.error-icon {
-  font-size: 60rpx;
-  margin-bottom: 16rpx;
-}
-.retry-btn {
-  background: #F97316;
-  color: #fff;
-  border: none;
-  border-radius: 40rpx;
-  padding: 12rpx 40rpx;
-  font-size: 26rpx;
-  line-height: 1.5;
-}
-
-/* 内容区 */
-.content {
-  height: 100vh;
-  padding-bottom: 120rpx;
-}
-.cover {
-  width: 100%;
-  display: block;
-}
-.placeholder-cover {
-  height: 400rpx;
-  background: #f0f0f0;
-}
-
-/* 信息区 */
-.info-section {
-  padding: 30rpx;
-}
-.activity-title {
-  font-size: 36rpx;
-  font-weight: bold;
-  color: #1f2937;
-  line-height: 1.4;
-}
-.status-row {
-  margin-top: 16rpx;
-}
-.status-tag {
-  display: inline-block;
-  font-size: 22rpx;
-  padding: 4rpx 16rpx;
-  border-radius: 8rpx;
-}
-.status-draft { background: #F5F5F5; color: #999; }
-.status-published { background: #F0FDF4; color: #22C55E; }
-.status-ended { background: #F5F5F5; color: #999; }
-.status-upcoming { background: #FFF7ED; color: #F97316; }
-
-/* 元信息列表 */
-.meta-list {
-  margin-top: 24rpx;
-  background: #fff;
-  border-radius: 16rpx;
-  padding: 24rpx;
-}
-.meta-item {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  margin-bottom: 16rpx;
-}
-.meta-item:last-child {
-  margin-bottom: 0;
-}
-.meta-icon {
-  font-size: 28rpx;
-  margin-right: 12rpx;
-  width: 32rpx;
-  text-align: center;
-}
-.meta-text {
-  font-size: 26rpx;
-  color: #4b5563;
-  flex: 1;
-  line-height: 1.4;
-}
-
-/* 活动介绍 */
-.desc-section {
-  margin-top: 30rpx;
-  background: #fff;
-  border-radius: 16rpx;
-  padding: 24rpx;
-}
-.section-title {
-  font-size: 28rpx;
-  font-weight: 600;
-  color: #1f2937;
-  display: block;
-  margin-bottom: 12rpx;
-}
-.desc-text {
-  font-size: 26rpx;
-  color: #4b5563;
-  line-height: 1.6;
-}
-
-/* 付费不退费说明 */
-.refund-notice {
-  margin-top: 20rpx;
-  margin-left: 30rpx;
-  margin-right: 30rpx;
-  margin-bottom: 10rpx;
-  background: #FEF2F2;
-  border: 2rpx solid #FECACA;
-  border-radius: 12rpx;
-  padding: 16rpx 20rpx;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-}
-.refund-notice-icon {
-  font-size: 24rpx;
-  margin-right: 10rpx;
-  flex-shrink: 0;
-}
-.refund-notice-text {
-  font-size: 24rpx;
-  color: #DC2626;
-  line-height: 1.4;
-}
-
-/* 底部操作栏 */
-.bottom-bar {
-  position: fixed;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background: #fff;
-  padding: 20rpx 30rpx;
-  padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  box-shadow: 0 -2rpx 12rpx rgba(0,0,0,0.06);
-}
-.price-info {
-  display: flex;
-  flex-direction: column;
-}
-.bottom-type {
-  font-size: 22rpx;
-  color: #999;
-}
-.bottom-vendor {
-  font-size: 22rpx;
-  color: #999;
-  margin-top: 4rpx;
-}
-.action-btns {
-  display: flex;
-  flex-direction: row;
-  gap: 16rpx;
-}
-.btn-checkin {
-  background: linear-gradient(135deg, #F97316, #FB923C);
-  color: #fff;
-  border: none;
-  border-radius: 40rpx;
-  padding: 16rpx 48rpx;
-  font-size: 28rpx;
-  font-weight: 600;
-  line-height: 1.5;
-  min-width: 160rpx;
-  text-align: center;
-}
-.btn-checkin:active {
-  opacity: 0.85;
-}
-.btn-checkin-done {
-  background: #22C55E;
-}
-.btn-checkin-loading {
-  opacity: 0.7;
-}
-
-/* 报名按钮 */
-.btn-register {
-  background: linear-gradient(135deg, #10B981, #34D399);
-  color: #fff;
-  border: none;
-  border-radius: 40rpx;
-  padding: 16rpx 32rpx;
-  font-size: 26rpx;
-  font-weight: 600;
-  line-height: 1.5;
-  min-width: 140rpx;
-  text-align: center;
-}
-.btn-register:active { opacity: 0.85; }
-.btn-register-loading { opacity: 0.7; }
-.btn-register-done {
-  background: #F5F5F5;
-  color: #999;
-  border: none;
-  border-radius: 40rpx;
-  padding: 16rpx 32rpx;
-  font-size: 26rpx;
-  line-height: 1.5;
-  min-width: 140rpx;
-  text-align: center;
-}
-.btn-cancel {
-  background: #fff;
-  color: #999;
-  border: 2rpx solid #ddd;
-  border-radius: 40rpx;
-  padding: 14rpx 28rpx;
-  font-size: 24rpx;
-  line-height: 1.5;
-  min-width: 120rpx;
-  text-align: center;
-}
-.btn-cancel:active { opacity: 0.7; }
-
-/* 能量扣减弹窗 */
-.penalty-modal {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background: rgba(0,0,0,0.5);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  z-index: 999;
-}
-.penalty-content {
-  background: #fff;
-  border-radius: 24rpx;
-  padding: 48rpx 40rpx;
-  width: 560rpx;
-  text-align: center;
-}
-.penalty-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #EF4444;
-  display: block;
-  margin-bottom: 20rpx;
-}
-.penalty-desc {
-  font-size: 26rpx;
-  color: #666;
-  display: block;
-  margin-bottom: 40rpx;
-  line-height: 1.6;
-}
-.penalty-btn {
-  background: #F97316;
-  color: #fff;
-  border: none;
-  border-radius: 40rpx;
-  padding: 16rpx 60rpx;
-  font-size: 28rpx;
-}
-</style>