|
@@ -36,7 +36,7 @@
|
|
|
<view class="meta-list">
|
|
<view class="meta-list">
|
|
|
<view class="meta-item" v-if="activity.startTime">
|
|
<view class="meta-item" v-if="activity.startTime">
|
|
|
<text class="meta-icon">🕐</text>
|
|
<text class="meta-icon">🕐</text>
|
|
|
- <text class="meta-text">{{ activity.startTime }}{{ activity.endTime ? ' ~ ' + activity.endTime : '' }}</text>
|
|
|
|
|
|
|
+ <text class="meta-text">{{ formatDate(activity.startTime) }}{{ activity.endTime ? ' ~ ' + formatDate(activity.endTime) : '' }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
<view class="meta-item" v-if="activity.location">
|
|
<view class="meta-item" v-if="activity.location">
|
|
|
<text class="meta-icon">📍</text>
|
|
<text class="meta-icon">📍</text>
|
|
@@ -57,6 +57,12 @@
|
|
|
<text class="section-title">活动介绍</text>
|
|
<text class="section-title">活动介绍</text>
|
|
|
<text class="desc-text">{{ activity.description }}</text>
|
|
<text class="desc-text">{{ activity.description }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 付费不退费说明 -->
|
|
|
|
|
+ <view class="refund-notice" v-if="hasPrice">
|
|
|
|
|
+ <text class="refund-notice-icon">⚠️</text>
|
|
|
|
|
+ <text class="refund-notice-text">本活动为付费活动,报名成功后不支持退费</text>
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
<!-- 底部操作栏 -->
|
|
<!-- 底部操作栏 -->
|
|
@@ -66,15 +72,57 @@
|
|
|
<text class="bottom-vendor" v-if="activity.vendorName">{{ activity.vendorName }}</text>
|
|
<text class="bottom-vendor" v-if="activity.vendorName">{{ activity.vendorName }}</text>
|
|
|
</view>
|
|
</view>
|
|
|
<view class="action-btns">
|
|
<view class="action-btns">
|
|
|
|
|
+ <!-- 未报名 → 显示报名按钮 -->
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-if="!registrationStatus && activity.requireRegistration !== 0"
|
|
|
|
|
+ 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="hasPrice">¥{{ activity.price }} 立即报名</text>
|
|
|
|
|
+ <text v-else>立即报名</text>
|
|
|
|
|
+ </button>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 已报名(pending 待审核) -->
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-if="registrationStatus === 'pending'"
|
|
|
|
|
+ class="btn-register-done"
|
|
|
|
|
+ disabled
|
|
|
|
|
+ >
|
|
|
|
|
+ <text>审核中</text>
|
|
|
|
|
+ </button>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 已报名(approved)→ 显示"已报名" + "签到" -->
|
|
|
|
|
+ <template v-if="registrationStatus === 'approved'">
|
|
|
|
|
+ <button
|
|
|
|
|
+ class="btn-registered"
|
|
|
|
|
+ :disabled="registrationLoading"
|
|
|
|
|
+ @click="onCancelRegistration"
|
|
|
|
|
+ >
|
|
|
|
|
+ <text>已报名</text>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ 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>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 已签到(无报名流程的活动) -->
|
|
|
<button
|
|
<button
|
|
|
- class="btn-checkin"
|
|
|
|
|
- :class="{ 'btn-checkin-done': checkinDone, 'btn-checkin-loading': checkinLoading }"
|
|
|
|
|
- :disabled="checkinLoading || checkinDone"
|
|
|
|
|
- @click="onCheckin"
|
|
|
|
|
|
|
+ v-if="registrationStatus === 'approved' && checkinDone"
|
|
|
|
|
+ class="btn-checkin btn-checkin-done"
|
|
|
|
|
+ disabled
|
|
|
>
|
|
>
|
|
|
- <text v-if="checkinLoading">签到中...</text>
|
|
|
|
|
- <text v-else-if="checkinDone">已签到 ✓</text>
|
|
|
|
|
- <text v-else>签到</text>
|
|
|
|
|
|
|
+ <text>已签到 ✓</text>
|
|
|
</button>
|
|
</button>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
@@ -88,7 +136,9 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
<script>
|
|
|
-import { getActivityDetail, activityCheckin } from '@/utils/api.js'
|
|
|
|
|
|
|
+import { getActivityDetail, activityCheckin, registerActivity, getMyActivityRegistrations, cancelActivityRegistration } from '@/utils/api.js'
|
|
|
|
|
+
|
|
|
|
|
+var WEEKDAYS = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
|
|
|
|
|
|
|
|
export default {
|
|
export default {
|
|
|
data() {
|
|
data() {
|
|
@@ -100,7 +150,17 @@ export default {
|
|
|
errorMsg: '',
|
|
errorMsg: '',
|
|
|
checkinLoading: false,
|
|
checkinLoading: false,
|
|
|
checkinDone: false,
|
|
checkinDone: false,
|
|
|
- checkinPoints: 0
|
|
|
|
|
|
|
+ checkinPoints: 0,
|
|
|
|
|
+ // 报名相关
|
|
|
|
|
+ registrationStatus: '', // '' | 'pending' | 'approved'
|
|
|
|
|
+ registrationLoading: false,
|
|
|
|
|
+ registrationId: null,
|
|
|
|
|
+ paying: false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ hasPrice: function() {
|
|
|
|
|
+ return this.activity && this.activity.price > 0
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
onLoad: function(options) {
|
|
onLoad: function(options) {
|
|
@@ -118,6 +178,24 @@ export default {
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
methods: {
|
|
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) {
|
|
loadDetail: function(id) {
|
|
|
var self = this
|
|
var self = this
|
|
|
self.loading = true
|
|
self.loading = true
|
|
@@ -126,6 +204,8 @@ export default {
|
|
|
self.loading = false
|
|
self.loading = false
|
|
|
if (res && res.code === 200 && res.data) {
|
|
if (res && res.code === 200 && res.data) {
|
|
|
self.activity = res.data
|
|
self.activity = res.data
|
|
|
|
|
+ // 加载详情后检查报名状态
|
|
|
|
|
+ self.loadRegistrationStatus(id)
|
|
|
} else {
|
|
} else {
|
|
|
self.error = true
|
|
self.error = true
|
|
|
self.errorMsg = (res && res.message) || '加载失败'
|
|
self.errorMsg = (res && res.message) || '加载失败'
|
|
@@ -140,6 +220,139 @@ export default {
|
|
|
var map = { draft: '草稿', published: '进行中', ended: '已结束' }
|
|
var map = { draft: '草稿', published: '进行中', ended: '已结束' }
|
|
|
return map[status] || '未知'
|
|
return map[status] || '未知'
|
|
|
},
|
|
},
|
|
|
|
|
+
|
|
|
|
|
+ // ===== 报名相关 =====
|
|
|
|
|
+ loadRegistrationStatus: function(activityId) {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ var childId = uni.getStorageSync('currentChildId')
|
|
|
|
|
+ if (!childId) return
|
|
|
|
|
+
|
|
|
|
|
+ getMyActivityRegistrations({ id: activityId, childId: childId, page: 1, size: 10 }).then(function(res) {
|
|
|
|
|
+ var records, i, reg
|
|
|
|
|
+ if (res && res.code === 200 && res.data) {
|
|
|
|
|
+ records = res.data.records || []
|
|
|
|
|
+ for (i = 0; i < records.length; i++) {
|
|
|
|
|
+ reg = records[i]
|
|
|
|
|
+ if (String(reg.activityId) === String(activityId)) {
|
|
|
|
|
+ self.registrationStatus = reg.status || 'pending'
|
|
|
|
|
+ self.registrationId = reg.id
|
|
|
|
|
+ if (reg.checkinTime) {
|
|
|
|
|
+ self.checkinDone = true
|
|
|
|
|
+ }
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(function(err) {
|
|
|
|
|
+ // 静默失败,不阻塞页面
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ onRegister: function() {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ if (self.registrationLoading || self.paying) return
|
|
|
|
|
+
|
|
|
|
|
+ var childId = uni.getStorageSync('currentChildId')
|
|
|
|
|
+ if (!childId) {
|
|
|
|
|
+ uni.showToast({ title: '请选择孩子身份', icon: 'none' })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 付费活动 → 先确认支付
|
|
|
|
|
+ if (self.hasPrice) {
|
|
|
|
|
+ uni.showModal({
|
|
|
|
|
+ title: '确认报名',
|
|
|
|
|
+ content: '该活动需支付 ¥' + self.activity.price + ',报名成功后不支持退费。是否继续?',
|
|
|
|
|
+ confirmText: '确认支付',
|
|
|
|
|
+ cancelText: '再想想',
|
|
|
|
|
+ success: function(confirmRes) {
|
|
|
|
|
+ if (confirmRes.confirm) {
|
|
|
|
|
+ self.doRegister(childId)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 免费活动 → 直接报名
|
|
|
|
|
+ uni.showModal({
|
|
|
|
|
+ title: '确认报名',
|
|
|
|
|
+ content: '确认报名该活动?',
|
|
|
|
|
+ confirmText: '立即报名',
|
|
|
|
|
+ cancelText: '再想想',
|
|
|
|
|
+ success: function(confirmRes) {
|
|
|
|
|
+ if (confirmRes.confirm) {
|
|
|
|
|
+ self.doRegister(childId)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ doRegister: function(childId) {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ self.registrationLoading = true
|
|
|
|
|
+
|
|
|
|
|
+ // 付费活动模拟支付等待
|
|
|
|
|
+ if (self.hasPrice) {
|
|
|
|
|
+ self.paying = true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ registerActivity({ id: self.activity.id, childId: childId }).then(function(res) {
|
|
|
|
|
+ var msg
|
|
|
|
|
+ self.registrationLoading = false
|
|
|
|
|
+ self.paying = false
|
|
|
|
|
+ if (res && res.code === 200) {
|
|
|
|
|
+ self.registrationStatus = 'pending'
|
|
|
|
|
+ self.registrationId = res.data && res.data.id
|
|
|
|
|
+ uni.showToast({ title: '报名成功', icon: 'success' })
|
|
|
|
|
+ // 重新加载详情获取最新人数
|
|
|
|
|
+ self.loadDetail(self.activityId)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ msg = (res && res.message) || '报名失败'
|
|
|
|
|
+ uni.showToast({ title: msg, icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(function(err) {
|
|
|
|
|
+ self.registrationLoading = false
|
|
|
|
|
+ self.paying = false
|
|
|
|
|
+ uni.showToast({ title: '网络异常', icon: 'none' })
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ onCancelRegistration: function() {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ if (self.registrationLoading) return
|
|
|
|
|
+
|
|
|
|
|
+ uni.showModal({
|
|
|
|
|
+ title: '取消报名',
|
|
|
|
|
+ content: '确定取消报名?',
|
|
|
|
|
+ confirmText: '确认取消',
|
|
|
|
|
+ confirmColor: '#EF4444',
|
|
|
|
|
+ success: function(confirmRes) {
|
|
|
|
|
+ if (confirmRes.confirm) {
|
|
|
|
|
+ self.doCancelRegistration()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ doCancelRegistration: function() {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ self.registrationLoading = true
|
|
|
|
|
+ cancelActivityRegistration({ id: self.activity.id, childId: uni.getStorageSync('currentChildId') }).then(function(res) {
|
|
|
|
|
+ var msg
|
|
|
|
|
+ self.registrationLoading = false
|
|
|
|
|
+ if (res && res.code === 200) {
|
|
|
|
|
+ self.registrationStatus = ''
|
|
|
|
|
+ self.registrationId = null
|
|
|
|
|
+ uni.showToast({ title: '已取消报名', icon: 'success' })
|
|
|
|
|
+ // 重新加载详情
|
|
|
|
|
+ self.loadDetail(self.activityId)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ msg = (res && res.message) || '取消失败'
|
|
|
|
|
+ uni.showToast({ title: msg, icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(function(err) {
|
|
|
|
|
+ self.registrationLoading = false
|
|
|
|
|
+ uni.showToast({ title: '网络异常', icon: 'none' })
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // ===== 签到相关 =====
|
|
|
onCheckin: function() {
|
|
onCheckin: function() {
|
|
|
var self = this
|
|
var self = this
|
|
|
if (self.checkinLoading || self.checkinDone) return
|
|
if (self.checkinLoading || self.checkinDone) return
|
|
@@ -152,6 +365,7 @@ export default {
|
|
|
|
|
|
|
|
self.checkinLoading = true
|
|
self.checkinLoading = true
|
|
|
activityCheckin({ id: self.activity.id, childId: childId }).then(function(res) {
|
|
activityCheckin({ id: self.activity.id, childId: childId }).then(function(res) {
|
|
|
|
|
+ var msg
|
|
|
self.checkinLoading = false
|
|
self.checkinLoading = false
|
|
|
if (res && res.code === 200 && res.data) {
|
|
if (res && res.code === 200 && res.data) {
|
|
|
if (res.data.alreadyCompleted) {
|
|
if (res.data.alreadyCompleted) {
|
|
@@ -166,7 +380,7 @@ export default {
|
|
|
uni.showToast({ title: '签到成功', icon: 'success' })
|
|
uni.showToast({ title: '签到成功', icon: 'success' })
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
- var msg = (res && res.message) || '签到失败'
|
|
|
|
|
|
|
+ msg = (res && res.message) || '签到失败'
|
|
|
uni.showToast({ title: msg, icon: 'none' })
|
|
uni.showToast({ title: msg, icon: 'none' })
|
|
|
}
|
|
}
|
|
|
}).catch(function(err) {
|
|
}).catch(function(err) {
|
|
@@ -314,6 +528,28 @@ export default {
|
|
|
line-height: 1.6;
|
|
line-height: 1.6;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* 付费不退费说明 */
|
|
|
|
|
+.refund-notice {
|
|
|
|
|
+ margin-top: 20rpx;
|
|
|
|
|
+ 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 {
|
|
.bottom-bar {
|
|
|
position: fixed;
|
|
position: fixed;
|
|
@@ -347,6 +583,59 @@ export default {
|
|
|
flex-direction: row;
|
|
flex-direction: row;
|
|
|
gap: 16rpx;
|
|
gap: 16rpx;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+/* 报名按钮 */
|
|
|
|
|
+.btn-register {
|
|
|
|
|
+ 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-register:active {
|
|
|
|
|
+ opacity: 0.85;
|
|
|
|
|
+}
|
|
|
|
|
+.btn-register-loading {
|
|
|
|
|
+ opacity: 0.7;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 已报名(待审核) */
|
|
|
|
|
+.btn-register-done {
|
|
|
|
|
+ background: #9CA3AF;
|
|
|
|
|
+ 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-registered {
|
|
|
|
|
+ background: #F3F4F6;
|
|
|
|
|
+ color: #4B5563;
|
|
|
|
|
+ border: 2rpx solid #D1D5DB;
|
|
|
|
|
+ border-radius: 40rpx;
|
|
|
|
|
+ padding: 14rpx 36rpx;
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+ min-width: 120rpx;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+}
|
|
|
|
|
+.btn-registered:active {
|
|
|
|
|
+ opacity: 0.7;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 签到按钮 */
|
|
|
.btn-checkin {
|
|
.btn-checkin {
|
|
|
background: linear-gradient(135deg, #F97316, #FB923C);
|
|
background: linear-gradient(135deg, #F97316, #FB923C);
|
|
|
color: #fff;
|
|
color: #fff;
|