|
|
@@ -77,8 +77,201 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 底部操作栏 -->
|
|
|
- <view class="bottom-bar">
|
|
|
+ </scroll-view>
|
|
|
+
|
|
|
+ <!-- 底部操作栏:必须与 scroll-view 平级,否则 iOS 上 position: fixed 在 scroll-view 内不生效(按钮不显示) -->
|
|
|
+ <view class="bottom-bar" v-if="!loading && activity.id">
|
|
|
+ <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">
|
|
|
+ <button class="share-btn" @click="generatePoster">
|
|
|
+ <text class="share-btn-icon">📤</text>
|
|
|
+ <text class="share-btn-text">分享</text>
|
|
|
+ </button>
|
|
|
+ <!-- 报名按钮(仅报名中/未报名时显示) -->
|
|
|
+ <button
|
|
|
+ v-if="canRegister"
|
|
|
+ class="btn-register"
|
|
|
+ @click="onRegister"
|
|
|
+ >
|
|
|
+ <text v-if="hasFee">¥{{ minFeeText }} 立即报名</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' || registrationStatus === 'registered')"
|
|
|
+ class="btn-cancel"
|
|
|
+ :disabled="registrationLoading"
|
|
|
+ @click="onCancelRegistration"
|
|
|
+ >
|
|
|
+ <text>取消报名</text>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <!-- 签到按钮(仅进行中时显示) -->
|
|
|
+ <button
|
|
|
+ v-if="activity.displayStatus === 'ongoing'"
|
|
|
+ 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 && activity.requireCheckinReview === 1">已签到待审核</text>
|
|
|
+ <text v-else-if="checkinDone">已签到 ✓</text>
|
|
|
+ <text v-else>签到</text>
|
|
|
+ </button>
|
|
|
+ <text v-if="activity.requireCheckinReview === 1 && !checkinDone && activity.displayStatus === 'ongoing'" class="checkin-review-hint">签到需要管理员审核</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 空状态 -->
|
|
|
+ <view v-else class="empty-wrap">
|
|
|
+ <text class="empty-text">活动不存在或已下架</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 能量扣减提示弹窗:必须与 scroll-view 平级,否则 iOS 上 position: fixed 在 scroll-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>
|
|
|
+
|
|
|
+ <!-- 活动评价 -->
|
|
|
+ <view class="feedback-section" v-if="activity.status === 'ended' && checkinDone">
|
|
|
+ <view class="feedback-header">
|
|
|
+ <text class="feedback-title">活动评价</text>
|
|
|
+ </view>
|
|
|
+ <!-- Already submitted feedback -->
|
|
|
+ <view v-if="submittedFeedback" class="feedback-submitted">
|
|
|
+ <view class="feedback-ratings-display">
|
|
|
+ <view class="fr-row" v-for="(val, key) in parsedSubmittedRatings" :key="key">
|
|
|
+ <text class="fr-label">{{ key }}</text>
|
|
|
+ <text class="fr-stars">{{ getStarDisplay(val) }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <text class="feedback-comment-text" v-if="submittedFeedback.comment">{{ submittedFeedback.comment }}</text>
|
|
|
+ <text class="feedback-thanks">感谢您的评价!</text>
|
|
|
+ </view>
|
|
|
+ <!-- Submit feedback button -->
|
|
|
+ <button v-else class="feedback-btn" @click="openFeedbackModal">
|
|
|
+ <text>提交活动评价</text>
|
|
|
+ </button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 评价弹窗:必须与 scroll-view 平级,否则 iOS 上 position: fixed 在 scroll-view 内不生效(按钮不显示) -->
|
|
|
+ <modal v-if="showFeedbackModal" class="feedback-modal-overlay" :show="showFeedbackModal" @close="showFeedbackModal = false">
|
|
|
+ <view class="feedback-modal">
|
|
|
+ <text class="feedback-modal-title">活动评价</text>
|
|
|
+
|
|
|
+ <view class="rating-row" v-for="(dim, dimIdx) in feedbackDimensions" :key="dimIdx">
|
|
|
+ <text class="rating-label">{{ dim }}</text>
|
|
|
+ <view class="stars">
|
|
|
+ <text v-for="star in 5" :key="star"
|
|
|
+ class="star-icon"
|
|
|
+ :class="{'star-active': star <= (feedbackRatings[dim] || 0)}"
|
|
|
+ @click="feedbackRatings[dim] = star">★</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <textarea class="feedback-textarea" v-model="feedbackComment" placeholder="说说您的体验..." maxlength="500" />
|
|
|
+
|
|
|
+ <view class="feedback-modal-actions">
|
|
|
+ <button class="feedback-cancel-btn" @click="showFeedbackModal = false">取消</button>
|
|
|
+ <button class="feedback-submit-btn" :disabled="feedbackSubmitting" @click="submitFeedback">
|
|
|
+ <text v-if="feedbackSubmitting">提交中...</text>
|
|
|
+ <text v-else>提交评价</text>
|
|
|
+ </button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </modal>
|
|
|
+
|
|
|
+ <!-- 报名表单弹窗:必须与 scroll-view 平级,否则 iOS 上 position: fixed 在 scroll-view 内不生效(按钮不显示) -->
|
|
|
+ <view v-if="showRegModal" class="reg-modal-overlay" @click.self="closeRegModal">
|
|
|
+ <view class="reg-modal">
|
|
|
+ <text class="reg-modal-title">活动报名</text>
|
|
|
+
|
|
|
+ <!-- 家庭成员选择 -->
|
|
|
+ <view class="reg-family-section" v-if="familyMembers.length > 0">
|
|
|
+ <text class="reg-section-label">从家庭成员中选择</text>
|
|
|
+ <view class="reg-family-list">
|
|
|
+ <view
|
|
|
+ class="reg-family-item"
|
|
|
+ v-for="member in familyMembers"
|
|
|
+ :key="member.id"
|
|
|
+ :class="{'reg-family-item-selected': isMemberSelected(member)}"
|
|
|
+ @click="toggleFamilyMember(member)"
|
|
|
+ >
|
|
|
+ <view class="reg-family-info">
|
|
|
+ <text class="reg-family-name">{{ member.name }}</text>
|
|
|
+ <text class="reg-family-relation">{{ member.relation }}</text>
|
|
|
+ </view>
|
|
|
+ <text class="reg-checkmark" v-if="isMemberSelected(member)">✓</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 报名人列表 -->
|
|
|
+ <view class="reg-list">
|
|
|
+ <text class="reg-section-label">报名人</text>
|
|
|
+ <view class="reg-entry" v-for="(entry, idx) in registrants" :key="idx">
|
|
|
+ <input class="reg-input reg-input-name" v-model="entry.name" placeholder="姓名" />
|
|
|
+ <input class="reg-input reg-input-phone" v-model="entry.phone" placeholder="手机号" type="number" maxlength="11" />
|
|
|
+ <text class="reg-remove" @click="removeRegistrant(idx)">✕</text>
|
|
|
+ </view>
|
|
|
+ <button class="reg-add-btn" @click="addManualEntry">+ 添加报名人</button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 合计 -->
|
|
|
+ <view class="reg-total" v-if="hasFee">
|
|
|
+ <text>共 {{ registrants.length }} 人 · 合计 ¥{{ registrantsTotal }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 操作按钮 -->
|
|
|
+ <view class="reg-actions">
|
|
|
+ <button class="reg-cancel" @click="closeRegModal">取消</button>
|
|
|
+ <button class="reg-submit" :disabled="regSubmitting || registrants.length === 0" @click="submitRegistration">
|
|
|
+ <text v-if="regSubmitting">提交中...</text>
|
|
|
+ <text v-else>确认报名</text>
|
|
|
+ </button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 空状态 -->
|
|
|
+ <view v-else class="empty-wrap">
|
|
|
+ <text class="empty-text">活动不存在或已下架</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 分享海报 -->
|
|
|
+ <ContentSharePoster
|
|
|
+ :show="showPoster"
|
|
|
+ :title="activity && activity.title"
|
|
|
+ :coverImage="activity && getImageUrl(activity.coverImage)"
|
|
|
+ :qrCodeBase64="posterQrCode"
|
|
|
+ typeLabel="活动推荐"
|
|
|
+ :dimensionCode="activity && activity.dimensionCode"
|
|
|
+ :summary="posterSummary"
|
|
|
+ :priceText="posterPriceText"
|
|
|
+ :inviteText="posterInviteText"
|
|
|
+ @close="closePoster"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 底部操作栏:必须与 scroll-view 平级,否则 iOS 上 position: fixed 在 scroll-view 内不生效(按钮不显示) -->
|
|
|
+ <view class="bottom-bar" v-if="!loading && activity.id">
|
|
|
<view class="price-info">
|
|
|
<text class="bottom-type">活动</text>
|
|
|
<text class="bottom-vendor" v-if="activity.vendorName">{{ activity.vendorName }}</text>
|
|
|
@@ -143,28 +336,6 @@
|
|
|
</view>
|
|
|
</modal>
|
|
|
|
|
|
- <!-- 活动评价 -->
|
|
|
- <view class="feedback-section" v-if="activity.status === 'ended' && checkinDone">
|
|
|
- <view class="feedback-header">
|
|
|
- <text class="feedback-title">活动评价</text>
|
|
|
- </view>
|
|
|
- <!-- Already submitted feedback -->
|
|
|
- <view v-if="submittedFeedback" class="feedback-submitted">
|
|
|
- <view class="feedback-ratings-display">
|
|
|
- <view class="fr-row" v-for="(val, key) in parsedSubmittedRatings" :key="key">
|
|
|
- <text class="fr-label">{{ key }}</text>
|
|
|
- <text class="fr-stars">{{ getStarDisplay(val) }}</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- <text class="feedback-comment-text" v-if="submittedFeedback.comment">{{ submittedFeedback.comment }}</text>
|
|
|
- <text class="feedback-thanks">感谢您的评价!</text>
|
|
|
- </view>
|
|
|
- <!-- Submit feedback button -->
|
|
|
- <button v-else class="feedback-btn" @click="openFeedbackModal">
|
|
|
- <text>提交活动评价</text>
|
|
|
- </button>
|
|
|
- </view>
|
|
|
-
|
|
|
<!-- 评价弹窗 -->
|
|
|
<modal v-if="showFeedbackModal" class="feedback-modal-overlay" :show="showFeedbackModal" @close="showFeedbackModal = false">
|
|
|
<view class="feedback-modal">
|
|
|
@@ -273,6 +444,7 @@ import shareMixin from '../../../components/share-mixin.js'
|
|
|
import MpHtml from '@/components/mp-html/mp-html.vue'
|
|
|
import ContentSharePoster from '@/components/ContentSharePoster.vue'
|
|
|
import { backfillProfile } from '../../../utils/profile-backfill.js'
|
|
|
+import { parseDate } from '@/utils/format.js'
|
|
|
|
|
|
export default {
|
|
|
mixins: [shareMixin],
|
|
|
@@ -348,8 +520,8 @@ export default {
|
|
|
},
|
|
|
formatDate: function(dateStr) {
|
|
|
if (!dateStr) return ''
|
|
|
- var d = new Date(dateStr.replace(/-/g, '/'))
|
|
|
- if (isNaN(d.getTime())) return dateStr
|
|
|
+ var d = parseDate(dateStr)
|
|
|
+ if (!d) return dateStr
|
|
|
var year = d.getFullYear()
|
|
|
var month = d.getMonth() + 1
|
|
|
var day = d.getDate()
|