|
|
@@ -58,8 +58,10 @@
|
|
|
<view class="meta-item" v-if="hasFee">
|
|
|
<text class="meta-icon">💰</text>
|
|
|
<view class="meta-text">
|
|
|
- <text class="fee-line">孩子:¥{{ feeText(activity.childFee, activity.childMemberFee) }}</text>
|
|
|
- <text class="fee-line">家长:¥{{ feeText(activity.parentFee, activity.parentMemberFee) }}</text>
|
|
|
+ <!-- A3:成人档恒显;儿童/长者档仅在 audienceType 含该人群且对应价格 > 0 时显示 -->
|
|
|
+ <text class="fee-line">成人:¥{{ feeText(adultFeeRegular, adultFeeMember) }}</text>
|
|
|
+ <text class="fee-line" v-if="showChildFee">儿童:¥{{ feeText(activity.childFee, activity.childMemberFee) }}</text>
|
|
|
+ <text class="fee-line" v-if="showElderFee">长者:¥{{ feeText(adultFeeRegular, adultFeeMember) }}</text>
|
|
|
<text class="fee-line member-fee-link" v-if="!isMember && hasMemberPrice" @click.stop="goUpgrade">开通会员享会员价 ›</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
@@ -260,8 +262,11 @@
|
|
|
@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 class="reg-family-name-line">
|
|
|
+ <text class="reg-family-name">{{ member.name }}</text>
|
|
|
+ <text class="reg-identity-tag">{{ member.identityLabel }}</text>
|
|
|
+ </view>
|
|
|
+ <text class="reg-family-relation">{{ member.relation }}<text class="reg-family-price" v-if="hasFee"> · ¥{{ memberFeeText(member) }}</text></text>
|
|
|
</view>
|
|
|
<text class="reg-checkmark" v-if="isMemberSelected(member)">✓</text>
|
|
|
</view>
|
|
|
@@ -272,9 +277,15 @@
|
|
|
<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 class="reg-entry-head">
|
|
|
+ <text class="reg-identity-tag">{{ registrantIdentityLabel(entry) }}</text>
|
|
|
+ <text class="reg-entry-price" v-if="hasFee">¥{{ registrantFeeText(entry) }}</text>
|
|
|
+ <text class="reg-remove" @click="removeRegistrant(idx)">✕</text>
|
|
|
+ </view>
|
|
|
+ <view class="reg-entry-fields">
|
|
|
+ <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" />
|
|
|
+ </view>
|
|
|
</view>
|
|
|
<button class="reg-add-btn" @click="addManualEntry">+ 添加报名人</button>
|
|
|
</view>
|
|
|
@@ -331,6 +342,19 @@ import ActivityPoster from '@/pages/activity/components/ActivityPoster.vue'
|
|
|
import RelatedContent from '@/components/RelatedContent.vue'
|
|
|
import { backfillProfile } from '../../../utils/profile-backfill.js'
|
|
|
import { parseDate, formatDateTime } from '@/utils/format.js'
|
|
|
+import {
|
|
|
+ adultFees,
|
|
|
+ audienceHasChild,
|
|
|
+ audienceHasElder,
|
|
|
+ classifyIdentity,
|
|
|
+ feeText as feeTextUtil,
|
|
|
+ hasAnyFee,
|
|
|
+ hasAnyMemberFee,
|
|
|
+ identityLabel,
|
|
|
+ minAvailablePrice,
|
|
|
+ posterPriceInfo,
|
|
|
+ priceForIdentity
|
|
|
+} from '@/utils/activity-fee.js'
|
|
|
import { getFontScale, buildContentTagStyle } from '@/utils/fontScale.js'
|
|
|
|
|
|
export default {
|
|
|
@@ -458,16 +482,23 @@ export default {
|
|
|
var s = formatDateTime(dateStr)
|
|
|
return s ? s.substring(0, 16) : ''
|
|
|
},
|
|
|
- // 费用双价展示:非会员价 / 会员价(元)
|
|
|
+ // 费用双价展示:非会员价 / 会员价(元)—— 口径统一到 utils/activity-fee.js
|
|
|
feeText: function(regularFee, memberFee) {
|
|
|
- var regular = (regularFee || 0) / 100
|
|
|
- var member = (memberFee || 0) / 100
|
|
|
- if (member > 0 && member < regular) {
|
|
|
- return member.toFixed(2) + '(会员) / ' + regular.toFixed(2)
|
|
|
- }
|
|
|
- if (regular === 0 && member === 0) return '0.00'
|
|
|
- if (regular === 0) return '会员免费'
|
|
|
- return regular.toFixed(2)
|
|
|
+ return feeTextUtil(regularFee, memberFee)
|
|
|
+ },
|
|
|
+ // A3:报名人身份标签(成人 / 儿童 / 长者)
|
|
|
+ registrantIdentityLabel: function(entry) {
|
|
|
+ return identityLabel(entry && entry.identity)
|
|
|
+ },
|
|
|
+ // A3:报名人对应单价(元)—— 按身份取档位,按当前用户会员状态取价列
|
|
|
+ registrantFeeText: function(entry) {
|
|
|
+ var price = priceForIdentity(this.activity, entry && entry.identity, this.isMember)
|
|
|
+ return (price / 100).toFixed(2)
|
|
|
+ },
|
|
|
+ // A3:家庭成员对应单价(元)
|
|
|
+ memberFeeText: function(member) {
|
|
|
+ var price = priceForIdentity(this.activity, member && member.identity, this.isMember)
|
|
|
+ return (price / 100).toFixed(2)
|
|
|
},
|
|
|
loadDetail: function(id) {
|
|
|
var self = this
|
|
|
@@ -712,28 +743,36 @@ export default {
|
|
|
if (res && res.code === 200 && res.data) {
|
|
|
var list = []
|
|
|
var members = res.data
|
|
|
- // members: flat List<FamilyMemberVO>,source='parent' 为家长(users表),其余为孩子/成员(family_members表)
|
|
|
+ // members: flat List<FamilyMemberVO>,source='parent' 为账号本人(users表),其余为家庭成员(family_members表)
|
|
|
+ // A3:每个成员按 age 归类身份(成人/儿童/长者,age 为空一律成人,与 A2 §2.4 结算口径一致)
|
|
|
for (var i = 0; i < members.length; i++) {
|
|
|
var m = members[i]
|
|
|
+ var identity = classifyIdentity(m)
|
|
|
if (m.source === 'parent') {
|
|
|
list.push({
|
|
|
id: 'p_' + m.id,
|
|
|
name: m.nickname || m.name || '',
|
|
|
phone: m.phone || '',
|
|
|
- relation: '家长',
|
|
|
+ relation: '本人',
|
|
|
memberId: m.id,
|
|
|
source: 'parent',
|
|
|
- targetType: 'user'
|
|
|
+ targetType: 'user',
|
|
|
+ identity: identity,
|
|
|
+ identityLabel: identityLabel(identity),
|
|
|
+ age: m.age
|
|
|
})
|
|
|
} else {
|
|
|
list.push({
|
|
|
id: 'c_' + m.id,
|
|
|
name: m.nickname || m.name || '',
|
|
|
phone: m.phone || '',
|
|
|
- relation: '孩子',
|
|
|
+ relation: '家庭成员',
|
|
|
memberId: m.id,
|
|
|
source: 'child',
|
|
|
- targetType: 'member'
|
|
|
+ targetType: 'member',
|
|
|
+ identity: identity,
|
|
|
+ identityLabel: identityLabel(identity),
|
|
|
+ age: m.age
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
@@ -760,6 +799,8 @@ export default {
|
|
|
phone: member.phone,
|
|
|
childId: member.childId,
|
|
|
targetType: member.targetType,
|
|
|
+ identity: member.identity || 'adult',
|
|
|
+ age: member.age,
|
|
|
prevPhone: member.phone
|
|
|
})
|
|
|
},
|
|
|
@@ -768,7 +809,9 @@ export default {
|
|
|
memberId: null,
|
|
|
name: '',
|
|
|
phone: '',
|
|
|
- memberId: null
|
|
|
+ memberId: null,
|
|
|
+ // 手动添加的报名人无家庭成员档案 → 按成人档计费(A2 §2.4 self)
|
|
|
+ identity: 'adult'
|
|
|
})
|
|
|
},
|
|
|
removeRegistrant: function(idx) {
|
|
|
@@ -814,20 +857,8 @@ export default {
|
|
|
}
|
|
|
if (res && res.data) {
|
|
|
this.posterQrCode = res.data.qrCodeBase64 || ''
|
|
|
- // 费用:优先显示家长常规价,兼容会员价/孩子价
|
|
|
- var p = this.activity
|
|
|
- var parentRegular = (p.parentFee || 0) / 100
|
|
|
- var parentMember = (p.parentMemberFee || 0) / 100
|
|
|
- var childRegular = (p.childFee || 0) / 100
|
|
|
- var childMember = (p.childMemberFee || 0) / 100
|
|
|
- var hasFee = parentRegular > 0 || parentMember > 0 || childRegular > 0 || childMember > 0
|
|
|
- if (!hasFee) {
|
|
|
- this.posterPriceText = '免费'
|
|
|
- } else if (parentRegular > 0 || parentMember > 0) {
|
|
|
- this.posterPriceText = this.feeText(p.parentFee, p.parentMemberFee) + '(家长)'
|
|
|
- } else {
|
|
|
- this.posterPriceText = this.feeText(p.childFee, p.childMemberFee) + '(孩子)'
|
|
|
- }
|
|
|
+ // A3:海报价格文案按 audienceType 选档位标签(成人 / 儿童 / 长者)
|
|
|
+ this.posterPriceText = posterPriceInfo(this.activity).text
|
|
|
this.posterInviteText = '「' + (uni.getStorageSync('nickname') || '好友') + '」邀请你一起参加这个活动'
|
|
|
this.posterBgColor = this.buildPosterBgColor()
|
|
|
this.showPoster = true
|
|
|
@@ -973,15 +1004,33 @@ export default {
|
|
|
return {}
|
|
|
}
|
|
|
},
|
|
|
- // 4 个费用字段是否存在(任一项 > 0 即为付费活动)
|
|
|
- hasFee: function() {
|
|
|
+ // A3:成人档两列费用(price 优先、parentFee 回落,兼容老活动)
|
|
|
+ adultFeeRegular: function() {
|
|
|
+ return adultFees(this.activity).regular
|
|
|
+ },
|
|
|
+ adultFeeMember: function() {
|
|
|
+ return adultFees(this.activity).member
|
|
|
+ },
|
|
|
+ // A3:儿童档是否展示(audienceType 含 child 且儿童档有价)
|
|
|
+ showChildFee: function() {
|
|
|
var a = this.activity || {}
|
|
|
- return (a.childFee > 0) || (a.childMemberFee > 0) || (a.parentFee > 0) || (a.parentMemberFee > 0)
|
|
|
+ if (!audienceHasChild(a.audienceType)) return false
|
|
|
+ return (Number(a.childFee) || 0) > 0 || (Number(a.childMemberFee) || 0) > 0
|
|
|
+ },
|
|
|
+ // A3:长者档是否展示(audienceType 含 elder 且对应价格 > 0;长者复用成人价)
|
|
|
+ showElderFee: function() {
|
|
|
+ var a = this.activity || {}
|
|
|
+ if (!audienceHasElder(a.audienceType)) return false
|
|
|
+ var fees = adultFees(a)
|
|
|
+ return fees.regular > 0 || fees.member > 0
|
|
|
+ },
|
|
|
+ // 活动是否收费(纳入 price/memberPrice —— 只填成人价的老活动不再误显「免费」)
|
|
|
+ hasFee: function() {
|
|
|
+ return hasAnyFee(this.activity)
|
|
|
},
|
|
|
// 是否存在会员专享价(用于非会员引导)
|
|
|
hasMemberPrice: function() {
|
|
|
- var a = this.activity || {}
|
|
|
- return (a.childMemberFee > 0) || (a.parentMemberFee > 0)
|
|
|
+ return hasAnyMemberFee(this.activity)
|
|
|
},
|
|
|
// 报名按钮可用:需报名 + 未报名 + 展示状态为报名中
|
|
|
canRegister: function() {
|
|
|
@@ -991,28 +1040,17 @@ export default {
|
|
|
var st = this.registrationStatus
|
|
|
return !st || st === 'not_registered' || st === 'cancelled' || st === 'rejected'
|
|
|
},
|
|
|
- // 报名弹窗内合计(按当前用户会员价估算,实际以订单为准)
|
|
|
+ // 报名弹窗内合计(按每人身份与当前用户会员状态估算,实际以订单为准)
|
|
|
registrantsTotal: function() {
|
|
|
- var a = this.activity || {}
|
|
|
var total = 0
|
|
|
for (var i = 0; i < this.registrants.length; i++) {
|
|
|
- if (this.registrants[i].memberId) {
|
|
|
- total += (a.childFee > 0 ? a.childFee : a.childMemberFee) || 0
|
|
|
- } else {
|
|
|
- total += (a.parentFee > 0 ? a.parentFee : a.parentMemberFee) || 0
|
|
|
- }
|
|
|
+ total += priceForIdentity(this.activity, this.registrants[i].identity, this.isMember)
|
|
|
}
|
|
|
return (total / 100).toFixed(2)
|
|
|
},
|
|
|
- // 报名按钮起步价文案
|
|
|
+ // 报名按钮起步价文案:当前用户可用档位的最小值(会员用会员价列,非会员用非会员价列)
|
|
|
minFeeText: function() {
|
|
|
- var a = this.activity || {}
|
|
|
- var fees = [a.childFee, a.childMemberFee, a.parentFee, a.parentMemberFee]
|
|
|
- var min = 0
|
|
|
- for (var i = 0; i < fees.length; i++) {
|
|
|
- if (fees[i] > 0 && (min === 0 || fees[i] < min)) min = fees[i]
|
|
|
- }
|
|
|
- return (min / 100).toFixed(2)
|
|
|
+ return (minAvailablePrice(this.activity, this.isMember) / 100).toFixed(2)
|
|
|
},
|
|
|
// 报名进度百分比(限人数时计算,封顶 100)
|
|
|
rosterPercent: function() {
|
|
|
@@ -1580,6 +1618,11 @@ export default {
|
|
|
border: 2rpx solid $brand-primary;
|
|
|
}
|
|
|
.reg-family-info {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+}
|
|
|
+.reg-family-name-line {
|
|
|
display: flex;
|
|
|
flex-direction: row;
|
|
|
align-items: center;
|
|
|
@@ -1596,6 +1639,19 @@ export default {
|
|
|
padding: 2rpx 12rpx;
|
|
|
border-radius: 8rpx;
|
|
|
}
|
|
|
+/* A3:身份标签(成人 / 儿童 / 长者) */
|
|
|
+.reg-identity-tag {
|
|
|
+ font-size: 20rpx;
|
|
|
+ color: $brand-primary;
|
|
|
+ background: $uni-primary-pale;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ padding: 2rpx 10rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.reg-family-price {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: $facade-text-strong;
|
|
|
+}
|
|
|
.reg-checkmark {
|
|
|
color: $brand-primary;
|
|
|
font-size: 32rpx;
|
|
|
@@ -1605,12 +1661,30 @@ export default {
|
|
|
margin-bottom: 20rpx;
|
|
|
}
|
|
|
.reg-entry {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: stretch;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+ gap: 8rpx;
|
|
|
+}
|
|
|
+.reg-entry-head {
|
|
|
display: flex;
|
|
|
flex-direction: row;
|
|
|
align-items: center;
|
|
|
- margin-bottom: 12rpx;
|
|
|
gap: 10rpx;
|
|
|
}
|
|
|
+.reg-entry-fields {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10rpx;
|
|
|
+}
|
|
|
+.reg-entry-price {
|
|
|
+ margin-left: auto;
|
|
|
+ font-size: 26rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: $status-danger;
|
|
|
+}
|
|
|
.reg-input {
|
|
|
flex: 1;
|
|
|
background: $facade-fill-2;
|