|
|
@@ -69,6 +69,41 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
+ <!-- 报名情况(脱敏统计 + 最近报名人,游客可见) -->
|
|
|
+ <view class="roster-section" v-if="activity.requireRegistration === 1 && roster">
|
|
|
+ <text class="section-title">报名情况</text>
|
|
|
+ <view class="roster-stat-row">
|
|
|
+ <view class="roster-stat">
|
|
|
+ <text class="roster-num">{{ roster.approvedCount || 0 }}</text>
|
|
|
+ <text class="roster-label">人已报名</text>
|
|
|
+ </view>
|
|
|
+ <view class="roster-stat" v-if="roster.maxParticipants > 0">
|
|
|
+ <text class="roster-num" :class="{ 'roster-full': roster.remaining === 0 }">{{ roster.remaining !== null ? roster.remaining : 0 }}</text>
|
|
|
+ <text class="roster-label">剩余名额</text>
|
|
|
+ </view>
|
|
|
+ <view class="roster-stat" v-if="roster.pendingCount > 0">
|
|
|
+ <text class="roster-num roster-pending-num">{{ roster.pendingCount }}</text>
|
|
|
+ <text class="roster-label">待审核</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <!-- 进度条(限人数时显示) -->
|
|
|
+ <view class="roster-progress-wrap" v-if="roster.maxParticipants > 0">
|
|
|
+ <view class="roster-progress-track">
|
|
|
+ <view class="roster-progress-bar" :style="{ width: rosterPercent + '%' }"></view>
|
|
|
+ </view>
|
|
|
+ <text class="roster-progress-text">{{ rosterPercent }}%</text>
|
|
|
+ </view>
|
|
|
+ <!-- 最近报名人 -->
|
|
|
+ <view class="roster-list" v-if="roster.registrants && roster.registrants.length > 0">
|
|
|
+ <view class="roster-item" v-for="(r, ri) in roster.registrants" :key="ri">
|
|
|
+ <view class="roster-avatar">{{ rosterAvatarText(r.name) }}</view>
|
|
|
+ <text class="roster-name">{{ r.name }}</text>
|
|
|
+ <text class="roster-time">{{ formatRosterTime(r.registeredAt) }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <text class="roster-empty" v-else>还没有人报名,快来抢占第一个名额吧</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
<!-- 活动简介 -->
|
|
|
<view class="desc-section" v-if="activity.description">
|
|
|
<text class="section-title">活动介绍</text>
|
|
|
@@ -287,7 +322,7 @@
|
|
|
|
|
|
<script>
|
|
|
import { BrandColors } from '../../../utils/brand-colors.js'
|
|
|
-import { getActivityDetail, activityCheckin, cancelActivityRegistration, createActivityOrder, submitActivityFeedback, getActivityFeedbackList, getMyActivityFeedback, getFamilyMemberList, getShareQrCode, getMyMembership, getPosterTemplates } from '@/utils/api.js'
|
|
|
+import { getActivityDetail, activityCheckin, cancelActivityRegistration, createActivityOrder, submitActivityFeedback, getActivityFeedbackList, getMyActivityFeedback, getFamilyMemberList, getShareQrCode, getMyMembership, getPosterTemplates, getActivityRegistrationRoster } from '@/utils/api.js'
|
|
|
import config from '@/config.js'
|
|
|
import shareMixin from '../../../components/share-mixin.js'
|
|
|
import MpHtml from '@/components/mp-html/mp-html.vue'
|
|
|
@@ -341,7 +376,9 @@ export default {
|
|
|
posterInviteText: '',
|
|
|
posterTemplate: 'gradient',
|
|
|
posterBgColor: '', // 封面背景色
|
|
|
- posterTemplates: [] // Web 管理端定义的海报模板(空则隐藏模板选择行)
|
|
|
+ posterTemplates: [], // Web 管理端定义的海报模板(空则隐藏模板选择行)
|
|
|
+ // 报名情况(脱敏统计 + 最近报名人)
|
|
|
+ roster: null
|
|
|
}
|
|
|
},
|
|
|
onLoad: function(options) {
|
|
|
@@ -454,6 +491,7 @@ export default {
|
|
|
self.loadFeedbackStatus()
|
|
|
// registrationStatus 已由后端按当前用户计算返回
|
|
|
self.registrationStatus = res.data.registrationStatus || null
|
|
|
+ self.loadRoster(id)
|
|
|
} else {
|
|
|
self.error = true
|
|
|
self.errorMsg = (res && res.message) || '加载失败'
|
|
|
@@ -468,6 +506,32 @@ export default {
|
|
|
var map = { draft: '草稿', published: '进行中', ended: '已结束' }
|
|
|
return map[status] || '未知'
|
|
|
},
|
|
|
+ loadRoster: function(id) {
|
|
|
+ var self = this
|
|
|
+ getActivityRegistrationRoster(id).then(function(res) {
|
|
|
+ if (res && res.code === 200 && res.data) {
|
|
|
+ self.roster = res.data
|
|
|
+ } else {
|
|
|
+ self.roster = null
|
|
|
+ }
|
|
|
+ }).catch(function() {
|
|
|
+ self.roster = null
|
|
|
+ })
|
|
|
+ },
|
|
|
+ // 报名人首字作为头像占位
|
|
|
+ rosterAvatarText: function(name) {
|
|
|
+ if (!name) return '?'
|
|
|
+ return name.charAt(0)
|
|
|
+ },
|
|
|
+ // 报名时间短格式:MM-DD
|
|
|
+ formatRosterTime: function(t) {
|
|
|
+ if (!t) return ''
|
|
|
+ var d = parseDate(t)
|
|
|
+ if (!d) return ''
|
|
|
+ var mm = d.getMonth() + 1
|
|
|
+ var dd = d.getDate()
|
|
|
+ return (mm < 10 ? '0' + mm : mm) + '-' + (dd < 10 ? '0' + dd : dd)
|
|
|
+ },
|
|
|
onCheckin: function() {
|
|
|
var self = this
|
|
|
if (self.checkinLoading || self.checkinDone) return
|
|
|
@@ -551,6 +615,7 @@ export default {
|
|
|
self.registrationLoading = false
|
|
|
if (res && res.code === 200) {
|
|
|
self.registrationStatus = null
|
|
|
+ self.loadRoster(self.activity.id)
|
|
|
uni.showToast({ title: '已取消报名', icon: 'success' })
|
|
|
} else {
|
|
|
uni.showToast({ title: (res && res.message) || '取消失败', icon: 'none' })
|
|
|
@@ -941,6 +1006,15 @@ export default {
|
|
|
if (fees[i] > 0 && (min === 0 || fees[i] < min)) min = fees[i]
|
|
|
}
|
|
|
return (min / 100).toFixed(2)
|
|
|
+ },
|
|
|
+ // 报名进度百分比(限人数时计算,封顶 100)
|
|
|
+ rosterPercent: function() {
|
|
|
+ var r = this.roster
|
|
|
+ if (!r || !r.maxParticipants || r.maxParticipants <= 0) return 0
|
|
|
+ var p = Math.round((r.approvedCount || 0) / r.maxParticipants * 100)
|
|
|
+ if (p < 0) p = 0
|
|
|
+ if (p > 100) p = 100
|
|
|
+ return p
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -1085,6 +1159,108 @@ export default {
|
|
|
margin-top: 4rpx;
|
|
|
}
|
|
|
|
|
|
+/* 报名情况 */
|
|
|
+.roster-section {
|
|
|
+ margin-top: 30rpx;
|
|
|
+ background: $app-surface;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 24rpx;
|
|
|
+}
|
|
|
+.roster-stat-row {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.roster-stat {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ margin-right: 48rpx;
|
|
|
+}
|
|
|
+.roster-num {
|
|
|
+ font-size: 40rpx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: $brand-primary;
|
|
|
+ line-height: 1.2;
|
|
|
+}
|
|
|
+.roster-num.roster-full {
|
|
|
+ color: $status-danger;
|
|
|
+}
|
|
|
+.roster-pending-num {
|
|
|
+ color: $facade-text-gray-600;
|
|
|
+}
|
|
|
+.roster-label {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: $facade-text-muted;
|
|
|
+ margin-top: 4rpx;
|
|
|
+}
|
|
|
+.roster-progress-wrap {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ margin-top: 20rpx;
|
|
|
+}
|
|
|
+.roster-progress-track {
|
|
|
+ flex: 1;
|
|
|
+ height: 14rpx;
|
|
|
+ background: $facade-fill-2;
|
|
|
+ border-radius: 7rpx;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.roster-progress-bar {
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 7rpx;
|
|
|
+ background: linear-gradient(90deg, $brand-primary, $brand-primary-light);
|
|
|
+ transition: width 0.3s ease;
|
|
|
+}
|
|
|
+.roster-progress-text {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: $facade-text-muted;
|
|
|
+ margin-left: 16rpx;
|
|
|
+ width: 64rpx;
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
+.roster-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ margin-top: 8rpx;
|
|
|
+}
|
|
|
+.roster-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ margin: 12rpx 24rpx 0 0;
|
|
|
+}
|
|
|
+.roster-avatar {
|
|
|
+ width: 48rpx;
|
|
|
+ height: 48rpx;
|
|
|
+ border-radius: 24rpx;
|
|
|
+ background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: 24rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-right: 8rpx;
|
|
|
+}
|
|
|
+.roster-name {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: $facade-text-gray-800;
|
|
|
+}
|
|
|
+.roster-time {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: $facade-text-muted;
|
|
|
+ margin-left: 8rpx;
|
|
|
+}
|
|
|
+.roster-empty {
|
|
|
+ display: block;
|
|
|
+ margin-top: 16rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: $facade-text-muted;
|
|
|
+}
|
|
|
+
|
|
|
/* 活动介绍 */
|
|
|
.desc-section {
|
|
|
margin-top: 30rpx;
|