|
|
@@ -189,6 +189,58 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
</modal>
|
|
|
+
|
|
|
+ <!-- 报名表单弹窗 -->
|
|
|
+ <modal v-if="showRegModal" class="reg-modal-overlay" :show="showRegModal" @close="showRegModal = false">
|
|
|
+ <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="activity.price > 0">
|
|
|
+ <text>共 {{ registrants.length }} 人 · 合计 ¥{{ activity.price * registrants.length }}</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>
|
|
|
+ </modal>
|
|
|
</scroll-view>
|
|
|
|
|
|
<!-- 空状态 -->
|
|
|
@@ -199,7 +251,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import { getActivityDetail, activityCheckin, registerActivity, cancelActivityRegistration, getMyActivityRegistrations, submitActivityFeedback, getActivityFeedbackList, getMyActivityFeedback } from '@/utils/api.js'
|
|
|
+import { getActivityDetail, activityCheckin, registerActivity, cancelActivityRegistration, getMyActivityRegistrations, submitActivityFeedback, getActivityFeedbackList, getMyActivityFeedback, getFamilyMembers } from '@/utils/api.js'
|
|
|
import shareMixin from '../../../components/share-mixin.js'
|
|
|
import MpHtml from '@/components/mp-html/mp-html.vue'
|
|
|
|
|
|
@@ -227,7 +279,12 @@ export default {
|
|
|
feedbackComment: '',
|
|
|
submittedFeedback: null,
|
|
|
feedbackSubmitting: false,
|
|
|
- feedbackList: []
|
|
|
+ feedbackList: [],
|
|
|
+ // registration form modal
|
|
|
+ showRegModal: false,
|
|
|
+ familyMembers: [],
|
|
|
+ registrants: [],
|
|
|
+ regSubmitting: false
|
|
|
}
|
|
|
},
|
|
|
onLoad: function(options) {
|
|
|
@@ -352,28 +409,10 @@ export default {
|
|
|
},
|
|
|
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)
|
|
|
- }
|
|
|
+ // Open registration form modal
|
|
|
+ self.showRegModal = true
|
|
|
+ self.registrants = []
|
|
|
+ self.loadFamilyMembers()
|
|
|
},
|
|
|
doRegister: function(childId) {
|
|
|
var self = this
|
|
|
@@ -493,6 +532,136 @@ export default {
|
|
|
var full = parseInt(val) || 0
|
|
|
var empty = 5 - full
|
|
|
return '★'.repeat(full) + '☆'.repeat(empty)
|
|
|
+ },
|
|
|
+ // ===== 报名表单 =====
|
|
|
+ loadFamilyMembers: function() {
|
|
|
+ var self = this
|
|
|
+ getFamilyMembers().then(function(res) {
|
|
|
+ if (res && res.code === 200 && res.data) {
|
|
|
+ var list = []
|
|
|
+ // parents
|
|
|
+ var parents = res.data.parents || []
|
|
|
+ for (var i = 0; i < parents.length; i++) {
|
|
|
+ list.push({
|
|
|
+ id: 'p_' + parents[i].id,
|
|
|
+ name: parents[i].nickname || parents[i].name || '',
|
|
|
+ phone: parents[i].phone || '',
|
|
|
+ relation: '家长',
|
|
|
+ childId: parents[i].id,
|
|
|
+ source: 'parent'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ // children
|
|
|
+ var children = res.data.children || []
|
|
|
+ for (var j = 0; j < children.length; j++) {
|
|
|
+ list.push({
|
|
|
+ id: 'c_' + children[j].id,
|
|
|
+ name: children[j].nickname || children[j].name || '',
|
|
|
+ phone: children[j].phone || '',
|
|
|
+ relation: '孩子',
|
|
|
+ childId: children[j].userId || children[j].id,
|
|
|
+ source: 'child'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ self.familyMembers = list
|
|
|
+ }
|
|
|
+ }).catch(function() {})
|
|
|
+ },
|
|
|
+ isMemberSelected: function(member) {
|
|
|
+ for (var i = 0; i < this.registrants.length; i++) {
|
|
|
+ if (this.registrants[i].memberId === member.id) return true
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ },
|
|
|
+ toggleFamilyMember: function(member) {
|
|
|
+ for (var i = 0; i < this.registrants.length; i++) {
|
|
|
+ if (this.registrants[i].memberId === member.id) {
|
|
|
+ this.registrants.splice(i, 1)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.registrants.push({
|
|
|
+ memberId: member.id,
|
|
|
+ name: member.name,
|
|
|
+ phone: member.phone,
|
|
|
+ childId: member.childId
|
|
|
+ })
|
|
|
+ },
|
|
|
+ addManualEntry: function() {
|
|
|
+ this.registrants.push({
|
|
|
+ memberId: null,
|
|
|
+ name: '',
|
|
|
+ phone: '',
|
|
|
+ childId: null
|
|
|
+ })
|
|
|
+ },
|
|
|
+ removeRegistrant: function(idx) {
|
|
|
+ this.registrants.splice(idx, 1)
|
|
|
+ },
|
|
|
+ closeRegModal: function() {
|
|
|
+ this.showRegModal = false
|
|
|
+ this.registrants = []
|
|
|
+ this.familyMembers = []
|
|
|
+ },
|
|
|
+ submitRegistration: function() {
|
|
|
+ var self = this
|
|
|
+ // Validate
|
|
|
+ if (self.registrants.length === 0) {
|
|
|
+ uni.showToast({ title: '请至少添加一个报名人', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ for (var i = 0; i < self.registrants.length; i++) {
|
|
|
+ if (!self.registrants[i].name || self.registrants[i].name.trim() === '') {
|
|
|
+ uni.showToast({ title: '请输入第 ' + (i + 1) + ' 个报名人的姓名', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 付费活动 → 先确认支付
|
|
|
+ if (self.activity && self.activity.price > 0) {
|
|
|
+ var total = self.activity.price * self.registrants.length
|
|
|
+ uni.showModal({
|
|
|
+ title: '确认报名',
|
|
|
+ content: '共 ' + self.registrants.length + ' 人,需支付 ¥' + total + ',报名成功后不支持退费。是否继续?',
|
|
|
+ confirmText: '确认支付',
|
|
|
+ cancelText: '再想想',
|
|
|
+ success: function(confirmRes) {
|
|
|
+ if (confirmRes.confirm) {
|
|
|
+ self.doBatchRegister()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ self.doBatchRegister()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ doBatchRegister: function() {
|
|
|
+ var self = this
|
|
|
+ self.regSubmitting = true
|
|
|
+
|
|
|
+ var registrantsData = []
|
|
|
+ for (var i = 0; i < self.registrants.length; i++) {
|
|
|
+ var entry = self.registrants[i]
|
|
|
+ registrantsData.push({
|
|
|
+ name: entry.name.trim(),
|
|
|
+ phone: entry.phone || '',
|
|
|
+ childId: entry.childId
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ registerActivity({ id: self.activity.id, registrants: registrantsData }).then(function(res) {
|
|
|
+ self.regSubmitting = false
|
|
|
+ if (res && res.code === 200) {
|
|
|
+ self.closeRegModal()
|
|
|
+ self.registrationStatus = 'pending'
|
|
|
+ uni.showToast({ title: '报名成功,请等待审核', icon: 'success' })
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: (res && res.message) || '报名失败', icon: 'none' })
|
|
|
+ }
|
|
|
+ }).catch(function() {
|
|
|
+ self.regSubmitting = false
|
|
|
+ uni.showToast({ title: '网络异常', icon: 'none' })
|
|
|
+ })
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
@@ -877,4 +1046,165 @@ export default {
|
|
|
.share-btn::after { border: none; }
|
|
|
.share-btn-icon { font-size: 28rpx; margin-right: 4rpx; }
|
|
|
.share-btn-text { font-size: 24rpx; }
|
|
|
+
|
|
|
+/* 报名表单弹窗 */
|
|
|
+.reg-modal-overlay {
|
|
|
+ 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;
|
|
|
+}
|
|
|
+.reg-modal {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 24rpx;
|
|
|
+ padding: 40rpx;
|
|
|
+ width: 85%;
|
|
|
+ max-height: 80vh;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+.reg-modal-title {
|
|
|
+ font-size: 34rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ text-align: center;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+}
|
|
|
+.reg-section-label {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #555;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+}
|
|
|
+.reg-family-section {
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+}
|
|
|
+.reg-family-list {
|
|
|
+ max-height: 300rpx;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+.reg-family-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 16rpx 20rpx;
|
|
|
+ background: #f9f9f9;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ margin-bottom: 10rpx;
|
|
|
+}
|
|
|
+.reg-family-item-selected {
|
|
|
+ background: #FFF7ED;
|
|
|
+ border: 2rpx solid #F97316;
|
|
|
+}
|
|
|
+.reg-family-info {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.reg-family-name {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ margin-right: 12rpx;
|
|
|
+}
|
|
|
+.reg-family-relation {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+ background: #eee;
|
|
|
+ padding: 2rpx 12rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+}
|
|
|
+.reg-checkmark {
|
|
|
+ color: #F97316;
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.reg-list {
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+.reg-entry {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 12rpx;
|
|
|
+ gap: 10rpx;
|
|
|
+}
|
|
|
+.reg-input {
|
|
|
+ flex: 1;
|
|
|
+ background: #f5f5f5;
|
|
|
+ border: none;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ padding: 16rpx 20rpx;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+.reg-input-name {
|
|
|
+ flex: 2;
|
|
|
+}
|
|
|
+.reg-input-phone {
|
|
|
+ flex: 3;
|
|
|
+}
|
|
|
+.reg-remove {
|
|
|
+ color: #EF4444;
|
|
|
+ font-size: 28rpx;
|
|
|
+ padding: 10rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.reg-add-btn {
|
|
|
+ background: none;
|
|
|
+ border: 2rpx dashed #ddd;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ padding: 16rpx;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #F97316;
|
|
|
+ text-align: center;
|
|
|
+ width: 100%;
|
|
|
+ margin-top: 6rpx;
|
|
|
+}
|
|
|
+.reg-total {
|
|
|
+ text-align: center;
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #F97316;
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+ padding: 16rpx;
|
|
|
+ background: #FFF7ED;
|
|
|
+ border-radius: 12rpx;
|
|
|
+}
|
|
|
+.reg-actions {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 20rpx;
|
|
|
+}
|
|
|
+.reg-cancel {
|
|
|
+ flex: 1;
|
|
|
+ background: #f5f5f5;
|
|
|
+ color: #666;
|
|
|
+ border: none;
|
|
|
+ border-radius: 40rpx;
|
|
|
+ padding: 20rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+.reg-submit {
|
|
|
+ flex: 1;
|
|
|
+ background: linear-gradient(135deg, #10B981, #34D399);
|
|
|
+ color: #fff;
|
|
|
+ border: none;
|
|
|
+ border-radius: 40rpx;
|
|
|
+ padding: 20rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+.reg-submit:active { opacity: 0.85; }
|
|
|
+.reg-submit[disabled] { opacity: 0.5; }
|
|
|
</style>
|