refactor: update match configuration and user matching logic
- Changed the daily free match limit to a lifetime limit, aligning with backend configurations. - Updated the match price display to include original pricing for better user clarity. - Refactored match quota handling to sync with the server, ensuring accurate match counts and purchase statuses. - Enhanced UI messages to reflect changes in match availability and pricing. - Removed deprecated logic related to daily match counts, streamlining the matching process.
This commit is contained in:
@@ -32,7 +32,7 @@ let MATCH_TYPES = [
|
||||
{ id: 'team', label: '团队招募', matchLabel: '团队招募', icon: 'gamepad', matchFromDB: true, showJoinAfterMatch: true }
|
||||
]
|
||||
|
||||
let FREE_MATCH_LIMIT = 3 // 每日免费匹配次数
|
||||
let FREE_MATCH_LIMIT = 1 // 终身免费匹配次数(不按日重置,与后端 match_config 一致)
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -48,8 +48,7 @@ Page({
|
||||
hasPurchased: false,
|
||||
hasFullBook: false,
|
||||
|
||||
// 匹配次数
|
||||
todayMatchCount: 0,
|
||||
// 匹配次数(以服务端 purchase-status.matchQuota 为准)
|
||||
totalMatchesAllowed: FREE_MATCH_LIMIT,
|
||||
matchesRemaining: FREE_MATCH_LIMIT,
|
||||
needPayToMatch: false,
|
||||
@@ -86,20 +85,21 @@ Page({
|
||||
contactWechat: '',
|
||||
contactSaving: false,
|
||||
|
||||
// 匹配价格(可配置)
|
||||
// 匹配价格(可配置);matchPriceOriginal 为划线原价展示
|
||||
matchPrice: 1,
|
||||
matchPriceOriginal: 9.9,
|
||||
extraMatches: 0
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
async onLoad() {
|
||||
wx.showShareMenu({ withShareTimeline: true })
|
||||
this.setData({
|
||||
statusBarHeight: app.globalData.statusBarHeight || 44
|
||||
})
|
||||
this.loadMatchConfig()
|
||||
await this.loadMatchConfig()
|
||||
this.loadStoredContact()
|
||||
this.loadTodayMatchCount()
|
||||
this.initUserStatus()
|
||||
await this.refreshMatchQuotaFromServer()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
@@ -112,6 +112,7 @@ Page({
|
||||
}
|
||||
}
|
||||
this.initUserStatus()
|
||||
this.refreshMatchQuotaFromServer()
|
||||
},
|
||||
|
||||
// 加载匹配配置
|
||||
@@ -132,12 +133,15 @@ Page({
|
||||
})
|
||||
MATCH_TYPES = types
|
||||
FREE_MATCH_LIMIT = res.data.freeMatchLimit || FREE_MATCH_LIMIT
|
||||
if (FREE_MATCH_LIMIT > 1) FREE_MATCH_LIMIT = 1
|
||||
const matchPrice = res.data.matchPrice || 1
|
||||
const matchPriceOriginal = res.data.matchPriceOriginal != null ? res.data.matchPriceOriginal : 9.9
|
||||
|
||||
this.setData({
|
||||
matchTypes: MATCH_TYPES,
|
||||
totalMatchesAllowed: FREE_MATCH_LIMIT,
|
||||
matchPrice: matchPrice
|
||||
matchPrice: matchPrice,
|
||||
matchPriceOriginal: matchPriceOriginal
|
||||
})
|
||||
|
||||
console.log('[Match] 加载匹配配置成功:', {
|
||||
@@ -162,49 +166,64 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
// 加载今日匹配次数
|
||||
loadTodayMatchCount() {
|
||||
try {
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
const stored = wx.getStorageSync('match_count_data')
|
||||
if (stored) {
|
||||
const data = typeof stored === 'string' ? JSON.parse(stored) : stored
|
||||
if (data.date === today) {
|
||||
this.setData({ todayMatchCount: data.count })
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载匹配次数失败:', e)
|
||||
/** 从服务端同步匹配配额(终身免费 + 已购次数,不按自然日重置) */
|
||||
async refreshMatchQuotaFromServer() {
|
||||
const userId = app.globalData.userInfo?.id
|
||||
const hasFullBookGlobal = !!app.globalData.hasFullBook
|
||||
if (!userId) {
|
||||
this.setData({
|
||||
matchesRemaining: FREE_MATCH_LIMIT,
|
||||
needPayToMatch: false,
|
||||
extraMatches: 0,
|
||||
totalMatchesAllowed: FREE_MATCH_LIMIT
|
||||
})
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await app.request({
|
||||
url: `/api/miniprogram/user/purchase-status?userId=${encodeURIComponent(userId)}`,
|
||||
silent: true
|
||||
})
|
||||
if (!res.success || !res.data) return
|
||||
const hasFullBook = res.data.hasFullBook === true
|
||||
const mq = res.data.matchQuota || {}
|
||||
const remain = mq.remainToday != null ? Number(mq.remainToday) : 0
|
||||
const purchasedRemain = mq.purchasedRemain != null ? Number(mq.purchasedRemain) : 0
|
||||
const purchasedTotal = mq.purchasedTotal != null ? Number(mq.purchasedTotal) : 0
|
||||
if (hasFullBook) {
|
||||
this.setData({
|
||||
hasFullBook: true,
|
||||
matchesRemaining: 999999,
|
||||
needPayToMatch: false,
|
||||
extraMatches: purchasedRemain,
|
||||
totalMatchesAllowed: 999999
|
||||
})
|
||||
return
|
||||
}
|
||||
this.setData({
|
||||
hasFullBook: false,
|
||||
matchesRemaining: Math.max(0, remain),
|
||||
needPayToMatch: remain <= 0,
|
||||
extraMatches: purchasedRemain,
|
||||
totalMatchesAllowed: FREE_MATCH_LIMIT + purchasedTotal
|
||||
})
|
||||
} catch (e) {
|
||||
console.log('[Match] 同步匹配配额失败', e)
|
||||
this.setData({
|
||||
hasFullBook: hasFullBookGlobal,
|
||||
matchesRemaining: hasFullBookGlobal ? 999999 : this.data.matchesRemaining,
|
||||
needPayToMatch: !hasFullBookGlobal && this.data.matchesRemaining <= 0
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 保存今日匹配次数
|
||||
saveTodayMatchCount(count) {
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
wx.setStorageSync('match_count_data', { date: today, count })
|
||||
},
|
||||
|
||||
// 初始化用户状态
|
||||
initUserStatus() {
|
||||
const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData
|
||||
|
||||
// 获取额外购买的匹配次数
|
||||
const extraMatches = wx.getStorageSync('extra_match_count') || 0
|
||||
|
||||
// 总匹配次数 = 每日免费(3) + 额外购买次数
|
||||
// 全书用户无限制
|
||||
const totalMatchesAllowed = hasFullBook ? 999999 : FREE_MATCH_LIMIT + extraMatches
|
||||
const matchesRemaining = hasFullBook ? 999999 : Math.max(0, totalMatchesAllowed - this.data.todayMatchCount)
|
||||
const needPayToMatch = !hasFullBook && matchesRemaining <= 0
|
||||
|
||||
const { isLoggedIn, hasFullBook } = app.globalData
|
||||
this.setData({
|
||||
isLoggedIn,
|
||||
hasFullBook,
|
||||
hasPurchased: true, // 所有用户都可以使用匹配功能
|
||||
totalMatchesAllowed,
|
||||
matchesRemaining,
|
||||
needPayToMatch,
|
||||
extraMatches
|
||||
hasPurchased: true
|
||||
})
|
||||
},
|
||||
|
||||
@@ -473,7 +492,7 @@ Page({
|
||||
} else if (res && !res.success) {
|
||||
matchFailHint = res.message || res.error || ''
|
||||
if (res.code === 'QUOTA_EXCEEDED') {
|
||||
matchFailHint = matchFailHint || '今日免费次数已用完,可购买额外匹配次数后再试'
|
||||
matchFailHint = matchFailHint || '免费次数已用完,可付费购买匹配次数后再试'
|
||||
} else if (res.code === 'NO_USERS') {
|
||||
matchFailHint = matchFailHint || '当前流量池暂无可匹配用户,可稍后再试;补全档案后匹配范围通常更大。'
|
||||
}
|
||||
@@ -500,18 +519,11 @@ Page({
|
||||
return
|
||||
}
|
||||
|
||||
// 增加今日匹配次数
|
||||
const newCount = this.data.todayMatchCount + 1
|
||||
const matchesRemaining = this.data.hasFullBook ? 999999 : Math.max(0, this.data.totalMatchesAllowed - newCount)
|
||||
|
||||
this.setData({
|
||||
isMatching: false,
|
||||
currentMatch: matchedUser,
|
||||
todayMatchCount: newCount,
|
||||
matchesRemaining,
|
||||
needPayToMatch: !this.data.hasFullBook && matchesRemaining <= 0
|
||||
currentMatch: matchedUser
|
||||
})
|
||||
this.saveTodayMatchCount(newCount)
|
||||
this.refreshMatchQuotaFromServer()
|
||||
|
||||
// 上报匹配行为到存客宝
|
||||
this.reportMatch(matchedUser)
|
||||
@@ -729,12 +741,8 @@ Page({
|
||||
})
|
||||
})
|
||||
|
||||
// 支付成功,增加匹配次数
|
||||
const extraMatches = (wx.getStorageSync('extra_match_count') || 0) + 1
|
||||
wx.setStorageSync('extra_match_count', extraMatches)
|
||||
|
||||
wx.showToast({ title: '购买成功', icon: 'success' })
|
||||
this.initUserStatus()
|
||||
await this.refreshMatchQuotaFromServer()
|
||||
} else {
|
||||
throw new Error(res.error || '创建订单失败')
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<view style="height: 16rpx;"></view>
|
||||
|
||||
<!-- 匹配提示条 - 简化显示 -->
|
||||
<view class="match-tip-bar" wx:if="{{matchesRemaining <= 0 && !hasFullBook}}">
|
||||
<view class="match-tip-bar" wx:if="{{isLoggedIn && matchesRemaining <= 0 && !hasFullBook}}">
|
||||
<icon name="zap" size="36" color="#FFD700" customClass="tip-icon"></icon>
|
||||
<text class="tip-text">今日免费次数已用完</text>
|
||||
<text class="tip-text">免费次数已用完,可付费继续匹配</text>
|
||||
<view class="tip-btn" bindtap="showUnlockModal">购买次数</view>
|
||||
</view>
|
||||
|
||||
@@ -302,22 +302,25 @@
|
||||
<view class="modal-content unlock-modal" catchtap="preventBubble">
|
||||
<view class="unlock-icon"><icon name="zap" size="64" color="#FFD700"></icon></view>
|
||||
<text class="unlock-title">购买匹配次数</text>
|
||||
<text class="unlock-desc">今日3次免费匹配已用完,可付费购买额外次数</text>
|
||||
<text class="unlock-desc">每位用户仅有 1 次免费匹配(不按天重置),之后每次 ¥{{matchPrice || 1}}</text>
|
||||
|
||||
<view class="unlock-info">
|
||||
<view class="info-row">
|
||||
<view class="info-row info-row-price">
|
||||
<text class="info-label">单价</text>
|
||||
<text class="info-value text-brand">¥{{matchPrice || 1}} / 次</text>
|
||||
<view class="info-value price-line">
|
||||
<text class="price-original">¥{{matchPriceOriginal}}</text>
|
||||
<text class="text-brand">¥{{matchPrice || 1}} / 次</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">已购买</text>
|
||||
<text class="info-label">已购未用</text>
|
||||
<text class="info-value">{{extraMatches || 0}} 次</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="unlock-buttons">
|
||||
<view class="btn-gold" bindtap="buyMatchCount">立即购买 ¥{{matchPrice || 1}}</view>
|
||||
<view class="btn-ghost" bindtap="closeUnlockModal">明天再来</view>
|
||||
<view class="btn-gold" bindtap="buyMatchCount">立即购买 ¥{{matchPrice || 1}} / 次</view>
|
||||
<view class="btn-ghost" bindtap="closeUnlockModal">稍后再说</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -80,6 +80,25 @@
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.info-row-price {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.price-line {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.price-original {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.text-brand {
|
||||
color: #00CED1;
|
||||
}
|
||||
|
||||
@@ -146,6 +146,15 @@ function appendQueryToPath(path, key, value) {
|
||||
return `${base}${sep}${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`
|
||||
}
|
||||
|
||||
/** 本小程序 wx.navigateTo / switchTab 用的路径:补全前导 / */
|
||||
function normalizeMiniProgramNavPath(p) {
|
||||
if (p == null || typeof p !== 'string') return ''
|
||||
let s = p.trim()
|
||||
if (!s) return ''
|
||||
if (!s.startsWith('/')) s = '/' + s.replace(/^\/+/, '')
|
||||
return s
|
||||
}
|
||||
|
||||
/** 当前用户已绑定手机号(与 app 内登录态一致) */
|
||||
function getLoggedInUserPhone() {
|
||||
const u = app.globalData.userInfo || {}
|
||||
@@ -903,6 +912,27 @@ Page({
|
||||
return
|
||||
}
|
||||
|
||||
// 本小程序内页(链接标签类型 internal,#标签 跳转当前小程序页面)
|
||||
if (tagType === 'internal') {
|
||||
const path = normalizeMiniProgramNavPath(pagePath || url)
|
||||
if (!path) {
|
||||
wx.showToast({ title: '未配置页面路径', icon: 'none' })
|
||||
return
|
||||
}
|
||||
wx.navigateTo({
|
||||
url: path,
|
||||
fail: () => {
|
||||
wx.switchTab({
|
||||
url: path,
|
||||
fail: (err) => {
|
||||
wx.showToast({ title: err.errMsg || '跳转失败', icon: 'none' })
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 小程序类型:用密钥查 linkedMiniprograms 得 appId,再唤醒(需在 app.json 的 navigateToMiniProgramAppIdList 中配置)
|
||||
if (tagType === 'miniprogram') {
|
||||
if (!mpKey && label) {
|
||||
@@ -930,10 +960,22 @@ Page({
|
||||
if (mpKey) wx.showToast({ title: '未找到关联小程序配置', icon: 'none' })
|
||||
}
|
||||
|
||||
// 小程序内部路径(pagePath 或 url 以 /pages/ 开头)
|
||||
const internalPath = pagePath || (url.startsWith('/pages/') ? url : '')
|
||||
// 兼容:仅填 pagePath、或 url 为内页路径(非 internal 类型也可走此分支)
|
||||
const rawInternal =
|
||||
pagePath || (url.startsWith('/pages/') || url.startsWith('pages/') ? url : '')
|
||||
const internalPath = normalizeMiniProgramNavPath(rawInternal)
|
||||
if (internalPath) {
|
||||
wx.navigateTo({ url: internalPath, fail: () => wx.switchTab({ url: internalPath }) })
|
||||
wx.navigateTo({
|
||||
url: internalPath,
|
||||
fail: () => {
|
||||
wx.switchTab({
|
||||
url: internalPath,
|
||||
fail: (err) => {
|
||||
wx.showToast({ title: err.errMsg || '跳转失败', icon: 'none' })
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user