Guide friend tasks and default membership carousel to quarterly plan

This commit is contained in:
Glore Zeng 2026-11-02 01:47:28 +08:00
parent 2135ca78e2
commit 48793491c3
5 changed files with 138 additions and 60 deletions

View File

@ -758,7 +758,7 @@ page {
<image class="u_apply_img" mode="widthFix" src="https://images.ufutx.com/202105/31/37e0eda3fe1fcc0af2cb3ca2a51d2291.png"></image> <image class="u_apply_img" mode="widthFix" src="https://images.ufutx.com/202105/31/37e0eda3fe1fcc0af2cb3ca2a51d2291.png"></image>
<view class="font_34 color333 bold">打个招呼吧</view> <view class="font_34 color333 bold">打个招呼吧</view>
</view> </view>
<view class="font_28 color666 ui-pt-16" v-if="!friendEligibility.unlimited">完善资料并完成真人认证和学历认证后,每天可免费加好友3次</view> <view class="font_28 color666 ui-pt-16" v-if="!friendEligibility.unlimited">真人认证、真人头像审核通过、完善资料、学历认证,每完成一项每天可免费加好友1次,最多4次</view>
</view> </view>
<view class="text-left ff ui-pt-30 ui-pl-36 ui-pr-36"> <view class="text-left ff ui-pt-30 ui-pl-36 ui-pr-36">
<textarea type="text" disabled show-confirm-bar="{{false}}" placeholder="输入打招呼内容" class="inputStyle" v-model="message" @input="typing" maxlength="60"/> <textarea type="text" disabled show-confirm-bar="{{false}}" placeholder="输入打招呼内容" class="inputStyle" v-model="message" @input="typing" maxlength="60"/>
@ -771,7 +771,7 @@ page {
<view class="u_apply_can f-fcc font_32 color999" @tap="modalName = ''">取消</view> <view class="u_apply_can f-fcc font_32 color999" @tap="modalName = ''">取消</view>
<view class="font_30 u_apply_btn white f-fcc" @tap="sendMsg">发送</view> <view class="font_30 u_apply_btn white f-fcc" @tap="sendMsg">发送</view>
</view> </view>
<view class="font_28 color999 text-center ui-pt-40" v-if="!friendEligibility.unlimited">今日还剩{{friendEligibility.remaining}}次,<text class="color-theme" @tap="jumpPath('vip')">成为会员</text>将不限制打招呼</view> <view class="font_28 color999 text-center ui-pt-40" v-if="!friendEligibility.unlimited">已获得每天{{friendEligibility.daily_limit}}次免费机会,今日还剩{{friendEligibility.remaining}}次</view>
</view> </view>
</view> </view>
</view> </view>
@ -827,7 +827,7 @@ wepy.page({
modalName: '', // 弹框 modalName: '', // 弹框
pairedScore: 0, pairedScore: 0,
userData: {}, // 自己的数据信息 userData: {}, // 自己的数据信息
friendEligibility: {unlimited: false, remaining: 0}, friendEligibility: {unlimited: false, remaining: 0, daily_limit: 0},
checkingFriendEligibility: false, checkingFriendEligibility: false,
userInfo: {}, // 当前用户详情数据 userInfo: {}, // 当前用户详情数据
profile_courtship: {}, profile_courtship: {},
@ -1256,7 +1256,7 @@ wepy.page({
if (code == 0) { if (code == 0) {
vm.modalName = '' vm.modalName = ''
vm.$showToast('消息已发送') vm.$showToast('消息已发送')
// Refresh authoritative quota; the third successful request opens membership checkout. // Refresh quota; guide unfinished tasks before offering membership.
vm.ensureFriendEligibility() vm.ensureFriendEligibility()
} }
vm.throttle = true vm.throttle = true
@ -1282,7 +1282,7 @@ wepy.page({
// 联系朋友 // 联系朋友
async contactFriendFn() { async contactFriendFn() {
let vm = this let vm = this
// 登录后由服务端依次检查完整资料、认证及剩余次数。 // 登录后由服务端计算四项任务奖励及当日剩余次数。
if (!wx.getStorageSync('token')) { if (!wx.getStorageSync('token')) {
vm.loginShow = true vm.loginShow = true
return return
@ -1297,30 +1297,36 @@ wepy.page({
const {code, data} = await this.$get({url: `${service.host}/friend/request/eligibility`}) const {code, data} = await this.$get({url: `${service.host}/friend/request/eligibility`})
if (code !== 0 || !data) return false if (code !== 0 || !data) return false
this.friendEligibility = data this.friendEligibility = data
if (data.action === 'profile') { if (data.allowed === true) return true
this.modalName = '' this.modalName = ''
const tasks = {
real: {name: '真人认证', url: '/pages/users/realName'},
photo: {name: '上传真人头像并通过审核', url: '/pages/users/unmarriV2?progressShow=false'},
profile: {name: '完善资料', url: '/pages/users/unmarriV2?progressShow=false'},
education: {name: '学历认证', url: '/pages/users/degreeCertificate'}
}
const required = (data.required || []).filter(key => tasks[key])
if (required.length) {
wx.showModal({ wx.showModal({
title: '请先完善个人资料', title: '领取更多免费机会',
content: '完善个人资料后,再完成真人认证和学历认证,即可获得每天3次免费加好友机会。', content: '当前免费次数已用完。尚未完成:' + required.map(key => tasks[key].name).join('、') + '。每完成一项,每天增加1次免费加好友机会,最多4次/天。',
confirmText: '去完善', confirmText: '去完成',
success: res => { if (res.confirm) wx.navigateTo({url: '/pages/users/unmarriV2?progressShow=false'}) } success: res => {
if (!res.confirm) return
wx.showActionSheet({
itemList: required.map(key => tasks[key].name),
success: choice => { const task = tasks[required[choice.tapIndex]]; if (task) wx.navigateTo({url: task.url}) }
})
}
}) })
return false return false
} }
if (data.action === 'purchase') { if (data.action === 'purchase') {
this.modalName = ''
wx.navigateTo({url: '/pages/users/upgradeVIP'})
return false
}
if (data.action === 'verify') {
this.modalName = ''
const required = data.required || []
const real = required.indexOf('real') !== -1
wx.showModal({ wx.showModal({
title: '完成认证领取免费机会', title: '今日免费次数已用完',
content: real ? (required.indexOf('education') !== -1 ? '完成真人认证和学历认证后,每天可免费加好友3次。请先完成真人认证。' : '完成真人认证后,每天可免费加好友3次。') : '完成学历认证后,每天可免费加好友3次。', content: '四项任务已全部完成,今天的4次免费机会已用完。升级会员可无限次加好友。',
confirmText: real ? '真人认证' : '学历认证', confirmText: '升级会员',
success: res => { if (res.confirm) wx.navigateTo({url: real ? '/pages/users/realName' : '/pages/users/degreeCertificate'}) } success: res => { if (res.confirm) wx.navigateTo({url: '/pages/users/upgradeVIP'}) }
}) })
return false return false
} }

View File

@ -23,14 +23,17 @@
<view class="font_24 white flo_l" style="color: #f7e4ad;width: 70%; "> <view class="font_24 white flo_l" style="color: #f7e4ad;width: 70%; ">
{{ user.deadline_str ? user.deadline_str + ' 到期' : '未激活' }} {{ user.deadline_str ? user.deadline_str + ' 到期' : '未激活' }}
</view> </view>
<swiper class="card-swiper" circular="true" @change="cardSwiper"> <view class="plan-heading">选择适合你的会员</view>
<swiper-item v-for="(item,index) in rankList" :key="index" class="{{cardCur==index?'cur':''}}" <view class="plan-hint">左右滑动或点击卡片,比较套餐价格</view>
@tap="showFn(index)"> <view class="membership-carousel" @touchstart="planTouchStart" @touchend="planTouchEnd">
<view class="swiper-item radius shadow " style="border-radius: 0"> <view v-for="(item,index) in rankList" :key="item.id" class="membership-plan {{item.position}}" @tap="selectPlan(index)">
<image :src="item.pic" mode="aspectFit"></image> <view class="plan-badge">{{cardCur == index ? '✓ 已选套餐' : '点击选择'}}</view>
<image class="plan-picture" v-if="item.pic" :src="item.pic" mode="aspectFit"></image>
<view class="plan-name">{{item.name}}</view>
<view class="plan-price"><text class="plan-currency" v-if="item.displayPrice != '待确认'">¥</text>{{item.displayPrice}}</view>
<view class="plan-duration">{{item.month}}个月</view>
</view>
</view> </view>
</swiper-item>
</swiper>
</view> </view>
<view class="mainWrapper animation-slide-bottom bg_f"> <view class="mainWrapper animation-slide-bottom bg_f">
<view class="title _vipRights">会员套餐权益:</view> <view class="title _vipRights">会员套餐权益:</view>
@ -75,7 +78,7 @@ background: linear-gradient(270deg, #E6C7AB 0%, #F5E6D8 100%);">
<view class="payBtn" @tap="showFn()"> <view class="payBtn" @tap="showFn()">
<view class="l_text"> <view class="l_text">
<span> <span>
{{purchaseEnabled ? '点击查看当前套餐价格' : '会员购买暂不可用'}} {{selectedPlan.name || '请选择套餐'}} · {{selectedPlan.displayPrice == '待确认' ? '价格待确认' : '¥' + selectedPlan.displayPrice}}
</span> </span>
</view> </view>
<view class="r_text"> <view class="r_text">
@ -166,6 +169,7 @@ import https from '../../mixins/https'
import base from '../../mixins/base' import base from '../../mixins/base'
import {service} from '../../config.js' import {service} from '../../config.js'
import {buyMembership} from '../../utils/virtual-payment' import {buyMembership} from '../../utils/virtual-payment'
import {membershipPlans, planPrice, defaultPlanIndex} from '../../utils/membership-plans'
const app = getApp().$wepy.$options const app = getApp().$wepy.$options
@ -179,6 +183,8 @@ wepy.page({
activeV2: false, activeV2: false,
swiperList: [], swiperList: [],
cardCur: 0, cardCur: 0,
planTouchX: 0,
planTouchY: 0,
specialVIP: [], specialVIP: [],
payData: { payData: {
coin: '', coin: '',
@ -211,6 +217,7 @@ wepy.page({
loginShow: false loginShow: false
}, },
computed: { computed: {
selectedPlan() { return this.rankList[this.cardCur] || {name: '', displayPrice: '待确认'} },
residue() { residue() {
return (this.score - (this.price - this.my_rank_price)).toFixed(2) return (this.score - (this.price - this.my_rank_price)).toFixed(2)
} }
@ -258,13 +265,39 @@ wepy.page({
watch: { watch: {
cardCur() { cardCur() {
this.payData.title = `超级会员(${this.rankList[this.cardCur].name})` this.syncSelectedPlan()
this.payData.price = this.rankList[this.cardCur].price
this.payData.discount_price = this.rankList[this.cardCur].discount_price
} }
}, },
methods: { methods: {
syncSelectedPlan() {
const item = this.rankList[this.cardCur]
if (!item) return
this.rankList = this.rankList.map((plan, index) => ({...plan,
displayPrice: planPrice(plan, this.virtualProducts),
position: index === this.cardCur ? 'is-selected' : index === (this.cardCur + this.rankList.length - 1) % this.rankList.length ? 'is-left' : 'is-right'
}))
this.payData.title = `超级会员(${item.name})`
this.payData.price = item.price
this.payData.discount_price = this.rankList[this.cardCur].displayPrice
this.shareId = item.id
},
selectPlan(index) {
if (this.purchasing || !this.rankList[index]) return
this.cardSwiper({$wx: {detail: {current: index}}})
this.syncSelectedPlan()
},
planTouchStart(e) {
const touch = (e.$wx || e).touches[0]
this.planTouchX = touch.clientX
this.planTouchY = touch.clientY
},
planTouchEnd(e) {
const touch = (e.$wx || e).changedTouches[0]
const dx = touch.clientX - this.planTouchX, dy = touch.clientY - this.planTouchY
if (Math.abs(dx) < 40 || Math.abs(dx) <= Math.abs(dy) || this.rankList.length < 2) return
this.selectPlan((this.cardCur + (dx < 0 ? 1 : -1) + this.rankList.length) % this.rankList.length)
},
pendingStorageKey() {const u = wx.getStorageSync('userInfo') || {}; return 'virtual-member-order:' + (u.id || u.user_id || 'anonymous')}, pendingStorageKey() {const u = wx.getStorageSync('userInfo') || {}; return 'virtual-member-order:' + (u.id || u.user_id || 'anonymous')},
async checkRecentOrder() { async checkRecentOrder() {
const orderNo = wx.getStorageSync(this.pendingStorageKey()) const orderNo = wx.getStorageSync(this.pendingStorageKey())
@ -282,6 +315,7 @@ wepy.page({
async refreshPurchase() { async refreshPurchase() {
this.purchaseEnabled = false this.purchaseEnabled = false
try {const status = await this.paymentAPI().get('/rank/purchase/status'); this.virtualProducts = status.products || []; this.purchaseEnabled = status.enabled === true} catch (e) {this.virtualProducts = []} try {const status = await this.paymentAPI().get('/rank/purchase/status'); this.virtualProducts = status.products || []; this.purchaseEnabled = status.enabled === true} catch (e) {this.virtualProducts = []}
this.syncSelectedPlan()
}, },
gotoApp(item) { gotoApp(item) {
let URL = `https://love.ufutx.com/h5/#/serviceAgreementV3?title=${item}` let URL = `https://love.ufutx.com/h5/#/serviceAgreementV3?title=${item}`
@ -294,14 +328,10 @@ wepy.page({
vm.user = data.rank_info vm.user = data.rank_info
let {type, is_subscribe} = wx.getStorageSync('userInfo') let {type, is_subscribe} = wx.getStorageSync('userInfo')
vm.specialVIP = data.ranks vm.specialVIP = data.ranks
vm.rankList = [] vm.rankList = membershipPlans(data.ranks.sub_ranks)
data.ranks.sub_ranks.forEach((item) => { vm.cardCur = defaultPlanIndex(vm.rankList)
vm.rankList.push(item)
})
if (!vm.rankList.length) {wx.hideLoading();return} if (!vm.rankList.length) {wx.hideLoading();return}
vm.payData.title = `超级会员(${vm.rankList[0].name})` vm.syncSelectedPlan()
vm.payData.price = `${vm.rankList[0].price}`
vm.payData.discount_price = `${vm.rankList[0].discount_price}`
vm.payData.pay_type = data.pay_type vm.payData.pay_type = data.pay_type
vm.shareId = vm.rankList[vm.cardCur].id vm.shareId = vm.rankList[vm.cardCur].id
if (wx.getStorageSync('from_user_id')) { if (wx.getStorageSync('from_user_id')) {
@ -429,8 +459,10 @@ page {
width: 100%; width: 100%;
background: #171718; background: #171718;
position: relative; position: relative;
height: 70vw; min-height: 610rpx;
height: auto;
padding-top: 6vw; padding-top: 6vw;
padding-bottom: 24rpx;
.userinfo-avatar { .userinfo-avatar {
width: 110rpx; width: 110rpx;
@ -535,13 +567,23 @@ page {
} }
} }
.card-swiper { .plan-heading { clear: both; padding: 34rpx 32rpx 0; color: #fff; font-size: 32rpx; font-weight: 600; }
width: 100% !important; .plan-hint { padding: 10rpx 32rpx 0; color: #cbbdc1; font-size: 23rpx; }
.membership-carousel { position: relative; height: 386rpx; margin: 18rpx 20rpx 0; }
.swiper-item { .membership-plan { position: absolute; box-sizing: border-box; width: 28%; top: 30rpx; height: 316rpx; padding: 18rpx 10rpx 12rpx; border: 2rpx solid #EED3DB; border-radius: 24rpx; background: #FFF9F5; text-align: center; color: #4E4145; transition: left .25s, width .25s, top .25s, height .25s; }
border-radius: 22rpx; .membership-plan.is-left { left: 0; }
} .membership-plan.is-right { left: 72%; }
} .membership-plan.is-selected { left: 30%; top: 0; width: 40%; height: 376rpx; border: 4rpx solid #D92554; background: #FFF1F4; box-shadow: 0 12rpx 30rpx rgba(217,37,84,.2); z-index: 2; }
.plan-badge { color: #97717D; font-size: 20rpx; height: 30rpx; }
.is-selected .plan-badge { color: #D92554; font-weight: 600; }
.plan-picture { width: 100%; height: 80rpx; display: block; margin: 6rpx auto 8rpx; }
.is-selected .plan-picture { height: 102rpx; }
.plan-name { color: #A3294D; font-size: 28rpx; font-weight: 600; }
.is-selected .plan-name { font-size: 34rpx; }
.plan-price { color: #A3294D; font-size: 42rpx; line-height: 1.4; font-weight: 700; white-space: nowrap; }
.is-selected .plan-price { color: #D92554; font-size: 60rpx; }
.plan-currency { font-size: 24rpx; margin-right: 2rpx; }
.plan-duration { font-size: 22rpx; color: #97717D; }
.iconV2 { .iconV2 {
height: 36rpx; height: 36rpx;

View File

@ -0,0 +1,17 @@
// Only show durations supplied by the API; never manufacture a missing plan or price.
export function membershipPlans(rows) {
const plans = [1, 3, 12].map(month => (rows || []).find(item => Number(item.month) === month)).filter(Boolean)
return plans.map(item => ({...item, displayPrice: '待确认', position: ''}))
}
export function planPrice(item, products) {
const product = (products || []).find(p => Number(p.sub_rank_id) === Number(item.id))
const raw = product ? Number(product.price_fen) / 100 : (item.discount_price == null ? item.price : item.discount_price)
if (raw === '' || raw == null || !Number.isFinite(Number(raw)) || Number(raw) < 0) return '待确认'
return Number(raw).toFixed(2).replace(/\.00$/, '')
}
export function defaultPlanIndex(plans) {
const quarter = plans.findIndex(item => Number(item.month) === 3)
return quarter < 0 ? 0 : quarter
}

View File

@ -1,19 +1,20 @@
const fs=require('fs'),vm=require('vm'),assert=require('assert'); const fs=require('fs'),vm=require('vm'),assert=require('assert');
const source=fs.readFileSync('ufutx_love_mp/src/pages/home/information.wpy','utf8').match(/<script>([\s\S]*?)<\/script>/)[1].replace(/^import .*$/gm,''); const source=fs.readFileSync('ufutx_love_mp/src/pages/home/information.wpy','utf8').match(/<script>([\s\S]*?)<\/script>/)[1].replace(/^import .*$/gm,'');
let options,modal,navigations=[]; let options,modal,sheet,navigations=[];
const wx={getStorageSync:()=> 'synthetic-token',showModal:o=>{modal=o},navigateTo:o=>navigations.push(o.url)}; const wx={getStorageSync:()=> 'synthetic-token',showModal:o=>{modal=o},showActionSheet:o=>{sheet=o},navigateTo:o=>navigations.push(o.url)};
vm.runInNewContext(source,{wepy:{page:o=>{options=o}},https:{},base:{},service:{host:'https://synthetic.invalid'},wx,console}); vm.runInNewContext(source,{wepy:{page:o=>{options=o}},https:{},base:{},service:{host:'https://synthetic.invalid'},wx,console});
const allow={allowed:true,action:'allow',remaining:1,unlimited:false}; const allow={allowed:true,action:'allow',remaining:1,unlimited:false,required:['education']};
function page(statuses){let gets=0,posts=0,greetings=0;return Object.assign({},options.methods,{id:123,message:'synthetic',throttle:true,modalName:'',checkingFriendEligibility:false,$signInVerify:()=>true,$showToast:()=>{},$get:async()=>({code:0,data:statuses[Math.min(gets++,statuses.length-1)]}),$post:async()=>{posts++;return {code:0}},getGreet:()=>{greetings++},counts:()=>({gets,posts,greetings})})} function page(statuses){let gets=0,posts=0,greetings=0;return Object.assign({},options.methods,{id:123,message:'synthetic',throttle:true,modalName:'',checkingFriendEligibility:false,$signInVerify:()=>true,$showToast:()=>{},$get:async()=>({code:0,data:statuses[Math.min(gets++,statuses.length-1)]}),$post:async()=>{posts++;return {code:0}},getGreet:()=>{greetings++},counts:()=>({gets,posts,greetings})})}
(async()=>{ (async()=>{
let incomplete=page([{action:'profile',required:['profile'],allowed:false,remaining:0}]);incomplete.$signInVerify=()=>false;await incomplete.contactFriendFn();assert.equal(incomplete.counts().greetings,0);assert.equal(modal.title,'请先完善个人资料');modal.success({confirm:true});assert.equal(navigations.pop(),'/pages/users/unmarriV2?progressShow=false');await incomplete.sendMsg();assert.equal(incomplete.counts().posts,0); for(const [key,url] of Object.entries({real:'/pages/users/realName',photo:'/pages/users/unmarriV2?progressShow=false',profile:'/pages/users/unmarriV2?progressShow=false',education:'/pages/users/degreeCertificate'})) {
let p=page([{action:'verify',required:['real','education'],allowed:false}]);await p.contactFriendFn();assert.equal(p.counts().greetings,0);modal.success({confirm:true});assert.equal(navigations.pop(),'/pages/users/realName'); const p=page([{action:'tasks',required:[key],allowed:false,remaining:0}]);await p.contactFriendFn();assert.equal(p.counts().greetings,0);assert(!modal.content.includes('升级会员'));modal.success({confirm:false});assert.equal(navigations.length,0);modal.success({confirm:true});sheet.success({tapIndex:0});assert.equal(navigations.pop(),url);await p.sendMsg();assert.equal(p.counts().posts,0);
p=page([{action:'verify',required:['education'],allowed:false}]);await p.contactFriendFn();modal.success({confirm:true});assert.equal(navigations.pop(),'/pages/users/degreeCertificate'); }
p=page([{action:'purchase',remaining:0,allowed:false}]);await p.contactFriendFn();assert.equal(navigations.pop(),'/pages/users/upgradeVIP');assert.equal(p.counts().greetings,0); let p=page([{action:'tasks',required:['real','photo','profile','education'],allowed:false}]);await p.contactFriendFn();modal.success({confirm:true});assert.equal(sheet.itemList.length,4);
p=page([{action:'purchase',required:[],remaining:0,allowed:false}]);await p.contactFriendFn();assert.equal(navigations.length,0);assert(modal.content.includes('无限次'));modal.success({confirm:true});assert.equal(navigations.pop(),'/pages/users/upgradeVIP');
p=page([allow]);await p.contactFriendFn();assert.equal(p.counts().greetings,1); p=page([allow]);await p.contactFriendFn();assert.equal(p.counts().greetings,1);
p=page([allow,{action:'purchase',remaining:0,allowed:false}]);await p.sendMsg();await new Promise(setImmediate);assert.equal(p.counts().posts,1);assert.equal(navigations.pop(),'/pages/users/upgradeVIP'); p=page([allow,{action:'tasks',required:['photo'],remaining:0,allowed:false}]);await p.sendMsg();await new Promise(setImmediate);assert.equal(p.counts().posts,1);assert(modal.content.includes('真人头像'));assert.equal(navigations.length,0);
p=page([{action:'verify',required:['real'],allowed:false}]);await p.sendMsg();assert.equal(p.counts().posts,0); p=page([allow,{action:'purchase',required:[],remaining:0,allowed:false}]);await p.sendMsg();await new Promise(setImmediate);assert.equal(p.counts().posts,1);assert.equal(modal.confirmText,'升级会员');
p=page([{allowed:true,unlimited:true,remaining:-1,action:'allow'}]);await p.sendMsg();await new Promise(setImmediate);assert.equal(navigations.length,0); p=page([{allowed:true,unlimited:true,remaining:-1,action:'allow'}]);await p.sendMsg();await new Promise(setImmediate);assert.equal(navigations.length,0);
p=page([]);p.$get=async()=>{throw Error('synthetic offline')};await p.sendMsg();assert.equal(p.counts().posts,0);assert(p.throttle); p=page([]);p.$get=async()=>{throw Error('synthetic offline')};await p.sendMsg();assert.equal(p.counts().posts,0);assert(p.throttle);
console.log('PASS: real/education guidance, verified entry, exhausted redirect, third successful request redirect, direct-send gating, member unchanged, network failure closed; no real requests'); console.log('PASS task choice, four independent rewards, exhausted guidance, membership confirmation, post-send refresh, member bypass, network failure; no real requests');
})().catch(e=>{console.error(e);process.exitCode=1}); })().catch(e=>{console.error(e);process.exitCode=1});

View File

@ -0,0 +1,12 @@
const fs=require('fs'),vm=require('vm'),assert=require('assert');
const helpers={};vm.createContext(helpers);vm.runInContext(fs.readFileSync('ufutx_love_mp/src/utils/membership-plans.js','utf8').replace(/export /g,''),helpers);
const rows=[{id:12,month:12,name:'年卡',discount_price:388},{id:15,month:0,name:'隐藏测试'},{id:10,month:1,name:'月卡',discount_price:78},{id:11,month:3,name:'季卡',discount_price:108}];
const plans=helpers.membershipPlans(rows);assert.equal(plans.length,3);assert.equal(plans[helpers.defaultPlanIndex(plans)].id,11);assert.equal(plans[0].id,10);assert.equal(plans[2].id,12);
assert.equal(helpers.planPrice(plans[1],[]),'108');assert.equal(helpers.planPrice(plans[1],[{sub_rank_id:11,price_fen:10999}]),'109.99');assert.equal(helpers.planPrice({id:99},[]),'待确认');assert.equal(helpers.defaultPlanIndex([]),0);
let options;const wx={getStorageSync:()=>''};const source=fs.readFileSync('ufutx_love_mp/src/pages/users/upgradeVIP.wpy','utf8').match(/<script>([\s\S]*?)<\/script>/)[1].replace(/^import .*$/gm,'');
vm.runInNewContext(source,{...helpers,membershipPlans:helpers.membershipPlans,planPrice:helpers.planPrice,defaultPlanIndex:helpers.defaultPlanIndex,wepy:{page:o=>options=o},https:{},base:{},service:{},buyMembership:()=>{},wx,getApp:()=>({$wepy:{$options:{}}}),setTimeout:()=>0,clearTimeout:()=>{}});
const p=Object.assign({},options.methods,{rankList:plans,cardCur:1,payData:{},virtualProducts:[],purchasing:false});p.syncSelectedPlan();assert.equal(p.rankList[1].position,'is-selected');assert.equal(p.rankList[0].position,'is-left');assert.equal(p.rankList[2].position,'is-right');assert.equal(p.payData.discount_price,'108');
p.selectPlan(0);assert.equal(p.shareId,10);assert.equal(p.rankList[0].position,'is-selected');
p.planTouchStart({touches:[{clientX:200,clientY:20}]});p.planTouchEnd({changedTouches:[{clientX:100,clientY:25}]});assert.equal(p.cardCur,1);assert.equal(p.shareId,11);
p.planTouchStart({touches:[{clientX:100,clientY:20}]});p.planTouchEnd({changedTouches:[{clientX:103,clientY:120}]});assert.equal(p.cardCur,1);
console.log('PASS quarter default, duration order, live prices, unknown price, selection, swipe and vertical-scroll guard');