优化个人中心页面,调整导航栏布局以避让右上角胶囊,增强用户体验。更新匹配功能逻辑,增加未开放提示,确保用户在使用时获得明确反馈。
This commit is contained in:
@@ -54,7 +54,11 @@ Page({
|
||||
partCount: 0,
|
||||
|
||||
// 加载状态
|
||||
loading: true
|
||||
loading: true,
|
||||
|
||||
// 链接卡若 - 留资弹窗
|
||||
showLeadModal: false,
|
||||
leadPhone: ''
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
@@ -124,8 +128,8 @@ Page({
|
||||
// 不再过滤无头像用户,无头像时用首字母展示
|
||||
members = (Array.isArray(res.data) ? res.data : []).slice(0, 4).map(u => ({
|
||||
id: u.id,
|
||||
name: u.vipName || u.vip_name || u.nickname || '会员',
|
||||
avatar: u.vipAvatar || u.vip_avatar || u.avatar || '',
|
||||
name: u.nickname || u.vipName || u.vip_name || '会员', // 超级个体:用户资料优先,随「我的」修改实时生效
|
||||
avatar: u.avatar || '',
|
||||
isVip: true
|
||||
}))
|
||||
if (members.length > 0) {
|
||||
@@ -301,6 +305,113 @@ Page({
|
||||
wx.navigateTo({ url: '/pages/about/about' })
|
||||
},
|
||||
|
||||
async onLinkKaruo() {
|
||||
const app = getApp()
|
||||
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) {
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '请先登录后再链接卡若',
|
||||
confirmText: '去登录',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) wx.switchTab({ url: '/pages/my/my' })
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
const userId = app.globalData.userInfo.id
|
||||
const leadKey = 'karuo_lead_' + userId
|
||||
let phone = (app.globalData.userInfo.phone || '').trim()
|
||||
let wechatId = (app.globalData.userInfo.wechatId || app.globalData.userInfo.wechat_id || '').trim()
|
||||
if (!phone && !wechatId) {
|
||||
try {
|
||||
const profileRes = await app.request({ url: `/api/miniprogram/user/profile?userId=${userId}`, silent: true })
|
||||
if (profileRes?.success && profileRes.data) {
|
||||
phone = (profileRes.data.phone || '').trim()
|
||||
wechatId = (profileRes.data.wechatId || profileRes.data.wechat_id || '').trim()
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
if (phone || wechatId) {
|
||||
const hasLead = wx.getStorageSync(leadKey)
|
||||
if (hasLead) {
|
||||
wx.showToast({ title: '已提交联系方式,卡若会尽快联系你', icon: 'none' })
|
||||
return
|
||||
}
|
||||
wx.showLoading({ title: '提交中...', mask: true })
|
||||
try {
|
||||
const res = await app.request({
|
||||
url: '/api/miniprogram/ckb/lead',
|
||||
method: 'POST',
|
||||
data: {
|
||||
userId,
|
||||
phone: phone || undefined,
|
||||
wechatId: wechatId || undefined,
|
||||
name: (app.globalData.userInfo.nickname || '').trim() || undefined
|
||||
}
|
||||
})
|
||||
wx.hideLoading()
|
||||
if (res && res.success) {
|
||||
wx.setStorageSync(leadKey, true)
|
||||
wx.showToast({ title: res.message || '提交成功', icon: 'success' })
|
||||
} else {
|
||||
wx.showToast({ title: (res && res.message) || '提交失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: e.message || '提交失败', icon: 'none' })
|
||||
}
|
||||
return
|
||||
}
|
||||
this.setData({ showLeadModal: true, leadPhone: '' })
|
||||
},
|
||||
|
||||
closeLeadModal() {
|
||||
this.setData({ showLeadModal: false, leadPhone: '' })
|
||||
},
|
||||
|
||||
onLeadPhoneInput(e) {
|
||||
this.setData({ leadPhone: (e.detail.value || '').trim() })
|
||||
},
|
||||
|
||||
async submitLead() {
|
||||
const phone = (this.data.leadPhone || '').trim().replace(/\s/g, '')
|
||||
if (!phone) {
|
||||
wx.showToast({ title: '请输入手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (phone.length < 11) {
|
||||
wx.showToast({ title: '请输入正确的手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const app = getApp()
|
||||
const userId = app.globalData.userInfo?.id
|
||||
const leadKey = userId ? ('karuo_lead_' + userId) : ''
|
||||
wx.showLoading({ title: '提交中...', mask: true })
|
||||
try {
|
||||
const res = await app.request({
|
||||
url: '/api/miniprogram/ckb/lead',
|
||||
method: 'POST',
|
||||
data: {
|
||||
userId,
|
||||
phone,
|
||||
name: (app.globalData.userInfo?.nickname || '').trim() || undefined
|
||||
}
|
||||
})
|
||||
wx.hideLoading()
|
||||
this.setData({ showLeadModal: false, leadPhone: '' })
|
||||
if (res && res.success) {
|
||||
if (leadKey) wx.setStorageSync(leadKey, true)
|
||||
wx.showToast({ title: res.message || '提交成功,卡若会尽快联系您', icon: 'success' })
|
||||
} else {
|
||||
wx.showToast({ title: (res && res.message) || '提交失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: e.message || '提交失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
goToSuperList() {
|
||||
wx.switchTab({ url: '/pages/match/match' })
|
||||
},
|
||||
@@ -316,8 +427,22 @@ Page({
|
||||
if (candidates.length === 0) {
|
||||
candidates = chapters.filter(exclude)
|
||||
}
|
||||
// 解析「第X场」用于倒序,最新(场次大)放在最上方
|
||||
const sessionNum = (c) => {
|
||||
const title = c.section_title || c.sectionTitle || c.title || c.chapterTitle || ''
|
||||
const m = title.match(/第\s*(\d+)\s*场/) || title.match(/第(\d+)场/)
|
||||
if (m) return parseInt(m[1], 10)
|
||||
const id = c.id != null ? String(c.id) : ''
|
||||
if (/^\d+$/.test(id)) return parseInt(id, 10)
|
||||
return 0
|
||||
}
|
||||
const latest = candidates
|
||||
.sort((a, b) => new Date(b.updatedAt || b.updated_at || 0) - new Date(a.updatedAt || a.updated_at || 0))
|
||||
.sort((a, b) => {
|
||||
const na = sessionNum(a)
|
||||
const nb = sessionNum(b)
|
||||
if (na !== nb) return nb - na // 场次倒序:最新在上
|
||||
return new Date(b.updatedAt || b.updated_at || 0) - new Date(a.updatedAt || a.updated_at || 0)
|
||||
})
|
||||
.slice(0, 10)
|
||||
.map(c => {
|
||||
const d = new Date(c.updatedAt || c.updated_at || Date.now())
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="header-right">
|
||||
<view class="contact-btn" bindtap="goToAbout">
|
||||
<view class="contact-btn" bindtap="onLinkKaruo">
|
||||
<image class="contact-avatar" src="/assets/images/author-avatar.png" mode="aspectFill"/>
|
||||
<text class="contact-text">点击链接卡若</text>
|
||||
</view>
|
||||
@@ -186,4 +186,17 @@
|
||||
|
||||
<!-- 底部留白 -->
|
||||
<view class="bottom-space"></view>
|
||||
|
||||
<!-- 链接卡若 - 留资弹窗(未填手机/微信号时) -->
|
||||
<view class="lead-mask" wx:if="{{showLeadModal}}" catchtap="closeLeadModal">
|
||||
<view class="lead-box" catchtap="">
|
||||
<text class="lead-title">留下联系方式</text>
|
||||
<text class="lead-desc">方便卡若与您联系</text>
|
||||
<input class="lead-input" placeholder="请输入手机号" type="number" maxlength="11" value="{{leadPhone}}" bindinput="onLeadPhoneInput"/>
|
||||
<view class="lead-actions">
|
||||
<button class="lead-btn lead-btn-cancel" bindtap="closeLeadModal">取消</button>
|
||||
<button class="lead-btn lead-btn-submit" bindtap="submitLead">提交</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -842,3 +842,73 @@
|
||||
.bottom-space {
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
/* ===== 链接卡若 - 留资弹窗 ===== */
|
||||
.lead-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.lead-box {
|
||||
width: 100%;
|
||||
max-width: 560rpx;
|
||||
background: #1C1C1E;
|
||||
border-radius: 24rpx;
|
||||
padding: 48rpx 40rpx;
|
||||
border: 2rpx solid rgba(56, 189, 172, 0.3);
|
||||
}
|
||||
.lead-title {
|
||||
display: block;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.lead-desc {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #A0AEC0;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
.lead-input {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: #0a1628;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 16rpx;
|
||||
padding: 0 24rpx;
|
||||
box-sizing: border-box;
|
||||
font-size: 30rpx;
|
||||
color: #ffffff;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
.lead-actions {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
}
|
||||
.lead-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
border-radius: 16rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
}
|
||||
.lead-btn-cancel {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #A0AEC0;
|
||||
}
|
||||
.lead-btn-submit {
|
||||
background: #38bdac;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ const app = getApp()
|
||||
let MATCH_TYPES = [
|
||||
{ id: 'partner', label: '找伙伴', matchLabel: '找伙伴', icon: '⭐', matchFromDB: true, showJoinAfterMatch: false },
|
||||
{ id: 'investor', label: '资源对接', matchLabel: '资源对接', icon: '👥', matchFromDB: true, showJoinAfterMatch: true, requirePurchase: true },
|
||||
{ id: 'mentor', label: '导师顾问', matchLabel: '立即咨询', icon: '❤️', matchFromDB: true, showJoinAfterMatch: true },
|
||||
{ id: 'mentor', label: '导师顾问', matchLabel: '导师顾问', icon: '❤️', matchFromDB: true, showJoinAfterMatch: true },
|
||||
{ id: 'team', label: '团队招募', matchLabel: '团队招募', icon: '🎮', matchFromDB: true, showJoinAfterMatch: true }
|
||||
]
|
||||
|
||||
@@ -108,8 +108,15 @@ Page({
|
||||
})
|
||||
|
||||
if (res.success && res.data) {
|
||||
// 更新全局配置
|
||||
MATCH_TYPES = res.data.matchTypes || MATCH_TYPES
|
||||
// 更新全局配置,导师顾问类型强制显示「导师顾问」
|
||||
let types = res.data.matchTypes || MATCH_TYPES
|
||||
types = types.map(t => {
|
||||
if (t.id === 'mentor') {
|
||||
return { ...t, label: '导师顾问', matchLabel: '导师顾问' }
|
||||
}
|
||||
return t
|
||||
})
|
||||
MATCH_TYPES = types
|
||||
FREE_MATCH_LIMIT = res.data.freeMatchLimit || FREE_MATCH_LIMIT
|
||||
const matchPrice = res.data.matchPrice || 1
|
||||
|
||||
@@ -459,7 +466,7 @@ Page({
|
||||
|
||||
// 生成模拟匹配数据
|
||||
generateMockMatch() {
|
||||
const nicknames = ['创业先锋', '资源整合者', '私域专家', '商业导师', '连续创业者']
|
||||
const nicknames = ['创业先锋', '资源整合者', '私域专家', '导师顾问', '连续创业者']
|
||||
const concepts = [
|
||||
'专注私域流量运营5年,帮助100+品牌实现从0到1的增长。',
|
||||
'连续创业者,擅长商业模式设计和资源整合。',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Soul创业派对 - 超级个体/会员详情页
|
||||
* 接口:优先 /api/miniprogram/vip/members?id=xx(VIP),回退 /api/miniprogram/users?id=xx(任意用户)
|
||||
* 字段映射:name/vipName, avatar/vipAvatar, contact/vipContact/phone, wechatId, project/vipProject/projectIntro,
|
||||
* 头像/昵称:统一用用户资料(nickname/avatar)优先,随「我的」修改实时生效
|
||||
* mbti, region, industry, position, businessScale, skills,
|
||||
* storyBestMonth→bestMonth, storyAchievement→achievement, storyTurning→turningPoint,
|
||||
* helpOffer→canHelp, helpNeed→needHelp
|
||||
@@ -32,8 +32,8 @@ Page({
|
||||
const u = Array.isArray(dbRes.data) ? dbRes.data[0] : dbRes.data
|
||||
if (u) {
|
||||
this.setData({ member: this.enrichAndFormat({
|
||||
id: u.id, name: u.vipName || u.vip_name || u.nickname || '创业者',
|
||||
avatar: u.vipAvatar || u.vip_avatar || u.avatar || '', isVip: !!(u.is_vip),
|
||||
id: u.id, name: u.nickname || u.vipName || u.vip_name || '创业者',
|
||||
avatar: u.avatar || u.vipAvatar || u.vip_avatar || '', isVip: !!(u.is_vip),
|
||||
contactRaw: u.vipContact || u.vip_contact || u.phone || '',
|
||||
wechatId: u.wechatId || u.wechat_id,
|
||||
project: u.vipProject || u.vip_project || u.projectIntro || u.project_intro || '',
|
||||
@@ -63,7 +63,7 @@ Page({
|
||||
const e = (v) => this._emptyIfPlaceholder(v)
|
||||
const merged = {
|
||||
id: raw.id,
|
||||
name: raw.name || raw.vipName || raw.vip_name || raw.nickname || '创业者',
|
||||
name: raw.nickname || raw.name || raw.vipName || raw.vip_name || '创业者',
|
||||
avatar: raw.avatar || raw.vipAvatar || raw.vip_avatar || '',
|
||||
isVip: !!(raw.isVip || raw.is_vip),
|
||||
mbti: e(raw.mbti),
|
||||
@@ -85,20 +85,84 @@ Page({
|
||||
const contact = merged.contactRaw || ''
|
||||
const wechat = merged.wechatRaw || ''
|
||||
const isMatched = (app.globalData.matchedUsers || []).includes(merged.id)
|
||||
const unlockData = this._getUnlockData(merged.id)
|
||||
merged.contactDisplay = contact ? (contact.slice(0, 3) + '****' + (contact.length > 7 ? contact.slice(-2) : '')) : ''
|
||||
merged.contactUnlocked = isMatched
|
||||
merged.contactUnlocked = isMatched || unlockData.contact
|
||||
merged.contactFull = contact
|
||||
merged.wechatDisplay = wechat ? (wechat.slice(0, 4) + '****' + (wechat.length > 8 ? wechat.slice(-3) : '')) : ''
|
||||
merged.wechatUnlocked = isMatched
|
||||
merged.wechatUnlocked = isMatched || unlockData.wechat
|
||||
merged.wechatFull = wechat
|
||||
return merged
|
||||
},
|
||||
|
||||
unlockContact() {
|
||||
_getUnlockData(memberId) {
|
||||
const userId = app.globalData.userInfo?.id
|
||||
if (!userId || !memberId) return { contact: false, wechat: false }
|
||||
try {
|
||||
const raw = wx.getStorageSync('member_unlocks_' + userId)
|
||||
if (Array.isArray(raw) && raw.includes(memberId)) {
|
||||
return { contact: true, wechat: true }
|
||||
}
|
||||
const data = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {}
|
||||
const member = data[memberId]
|
||||
return {
|
||||
contact: !!(member && member.contact),
|
||||
wechat: !!(member && member.wechat)
|
||||
}
|
||||
} catch (e) { return { contact: false, wechat: false } }
|
||||
},
|
||||
|
||||
_addUnlock(memberId, field) {
|
||||
const userId = app.globalData.userInfo?.id
|
||||
if (!userId || !memberId || !field) return
|
||||
let obj = wx.getStorageSync('member_unlocks_' + userId)
|
||||
if (Array.isArray(obj)) {
|
||||
obj = obj.reduce((o, id) => { o[id] = { contact: true, wechat: true }; return o }, {})
|
||||
}
|
||||
obj = obj && typeof obj === 'object' ? obj : {}
|
||||
if (!obj[memberId]) obj[memberId] = {}
|
||||
obj[memberId][field] = true
|
||||
wx.setStorageSync('member_unlocks_' + userId, obj)
|
||||
},
|
||||
|
||||
_hasUsedFreeForMember(memberId) {
|
||||
const d = this._getUnlockData(memberId)
|
||||
return d.contact || d.wechat
|
||||
},
|
||||
|
||||
unlockField(e) {
|
||||
const field = e.currentTarget.dataset.field
|
||||
if (!field) return
|
||||
const member = this.data.member
|
||||
if (!member?.id || (field !== 'contact' && field !== 'wechat')) return
|
||||
const isLoggedIn = app.globalData.isLoggedIn
|
||||
if (!isLoggedIn) {
|
||||
wx.showModal({
|
||||
title: '需要登录',
|
||||
content: '请先登录后再解锁超级个体联系方式',
|
||||
confirmText: '去登录',
|
||||
cancelText: '取消',
|
||||
success: (res) => { if (res.confirm) wx.switchTab({ url: '/pages/my/my' }) }
|
||||
})
|
||||
return
|
||||
}
|
||||
const d = this._getUnlockData(member.id)
|
||||
if (d[field]) return
|
||||
const isVip = app.globalData.hasFullBook
|
||||
const usedFree = this._hasUsedFreeForMember(member.id)
|
||||
if (isVip || !usedFree) {
|
||||
this._addUnlock(member.id, field)
|
||||
const m = this.enrichAndFormat(member)
|
||||
this.setData({ member: m })
|
||||
wx.showToast({ title: field === 'contact' ? '已解锁联系方式' : '已解锁微信号', icon: 'success' })
|
||||
return
|
||||
}
|
||||
wx.showModal({
|
||||
title: '解锁完整联系方式', content: '成为VIP会员并完成匹配后,即可查看完整联系方式',
|
||||
confirmText: '去匹配', cancelText: '知道了',
|
||||
success: (res) => { if (res.confirm) wx.switchTab({ url: '/pages/match/match' }) }
|
||||
title: '解锁' + (field === 'contact' ? '联系方式' : '微信号'),
|
||||
content: '您的免费解锁次数已用完,开通VIP会员(¥1980/年)可无限解锁',
|
||||
confirmText: '去开通',
|
||||
cancelText: '取消',
|
||||
success: (res) => { if (res.confirm) wx.navigateTo({ url: '/pages/vip/vip' }) }
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
@@ -57,8 +57,8 @@
|
||||
<view class="field" wx:if="{{member.contactRaw || member.contactDisplay}}">
|
||||
<text class="f-key">联系方式</text>
|
||||
<view class="f-row">
|
||||
<text class="f-val mono">{{member.contactDisplay || member.contactRaw}}</text>
|
||||
<view class="icon-copy icon-eye-off" wx:if="{{member.contactRaw && !member.contactUnlocked}}" bindtap="unlockContact">
|
||||
<text class="f-val mono">{{member.contactUnlocked ? member.contactFull : (member.contactDisplay || member.contactRaw)}}</text>
|
||||
<view class="icon-copy icon-eye-off" wx:if="{{member.contactRaw && !member.contactUnlocked}}" bindtap="unlockField" data-field="contact">
|
||||
<image class="icon-img" src="/assets/icons/eye-off.svg" mode="aspectFit"/>
|
||||
</view>
|
||||
<view class="icon-copy" wx:elif="{{member.contactRaw && member.contactUnlocked}}" bindtap="copyContact">📋</view>
|
||||
@@ -67,8 +67,8 @@
|
||||
<view class="field" wx:if="{{member.wechatRaw || member.wechatDisplay}}">
|
||||
<text class="f-key">微信号</text>
|
||||
<view class="f-row">
|
||||
<text class="f-val mono">{{member.wechatDisplay || member.wechatRaw}}</text>
|
||||
<view class="icon-copy icon-eye-off" wx:if="{{member.wechatRaw && !member.wechatUnlocked}}" bindtap="unlockContact">
|
||||
<text class="f-val mono">{{member.wechatUnlocked ? member.wechatFull : (member.wechatDisplay || member.wechatRaw)}}</text>
|
||||
<view class="icon-copy icon-eye-off" wx:if="{{member.wechatRaw && !member.wechatUnlocked}}" bindtap="unlockField" data-field="wechat">
|
||||
<image class="icon-img" src="/assets/icons/eye-off.svg" mode="aspectFit"/>
|
||||
</view>
|
||||
<view class="icon-copy" wx:elif="{{member.wechatRaw && member.wechatUnlocked}}" bindtap="copyWechat">📋</view>
|
||||
|
||||
@@ -621,7 +621,7 @@ Page({
|
||||
wx.switchTab({ url: '/pages/match/match' })
|
||||
},
|
||||
|
||||
// 跳转到推广中心
|
||||
// 跳转到推广中心(需登录)
|
||||
goToReferral() {
|
||||
if (!this.data.isLoggedIn) {
|
||||
this.showLogin()
|
||||
@@ -630,7 +630,7 @@ Page({
|
||||
wx.navigateTo({ url: '/pages/referral/referral' })
|
||||
},
|
||||
|
||||
// 跳转到找伙伴页面
|
||||
// 跳转到找伙伴
|
||||
goToMatch() {
|
||||
wx.switchTab({ url: '/pages/match/match' })
|
||||
},
|
||||
@@ -650,14 +650,20 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
// VIP状态查询
|
||||
// VIP状态查询(hasFullBook 优先,兼容模拟支付等本地已置 VIP 的情况)
|
||||
async loadVipStatus() {
|
||||
if (app.globalData.hasFullBook) {
|
||||
this.setData({ isVip: true, vipExpireDate: this.data.vipExpireDate || '' })
|
||||
}
|
||||
const userId = app.globalData.userInfo?.id
|
||||
if (!userId) return
|
||||
try {
|
||||
const res = await app.request({ url: `/api/miniprogram/vip/status?userId=${userId}`, silent: true })
|
||||
if (res?.success) {
|
||||
this.setData({ isVip: res.data?.isVip, vipExpireDate: res.data?.expireDate || '' })
|
||||
this.setData({
|
||||
isVip: res.data?.isVip || app.globalData.hasFullBook,
|
||||
vipExpireDate: res.data?.expireDate || this.data.vipExpireDate || ''
|
||||
})
|
||||
}
|
||||
} catch (e) { console.log('[My] VIP查询失败', e) }
|
||||
},
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!-- 我的页 - 设计稿 1:1 还原 -->
|
||||
<view class="page">
|
||||
<!-- 顶部导航:标题 + 右侧设置齿轮 -->
|
||||
<!-- 顶部导航:左侧资料编辑 + 标题 -->
|
||||
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
|
||||
<text class="nav-title">我的</text>
|
||||
<view class="nav-settings" bindtap="handleMenuTap" data-id="settings">
|
||||
<image class="nav-settings-icon" src="/assets/icons/settings-gray.svg" mode="aspectFit"/>
|
||||
<view class="nav-settings" bindtap="goToProfileEdit">
|
||||
<image class="nav-settings-icon" src="/assets/icons/edit-gray.svg" mode="aspectFit"/>
|
||||
</view>
|
||||
<text class="nav-title">我的</text>
|
||||
</view>
|
||||
<view class="nav-placeholder" style="height: {{statusBarHeight + 44}}px;"></view>
|
||||
|
||||
@@ -38,22 +38,22 @@
|
||||
</view>
|
||||
<view class="vip-tags">
|
||||
<text class="vip-tag {{isVip ? 'vip-tag-active' : ''}}" bindtap="goToVip">会员</text>
|
||||
<text class="vip-tag {{isVip ? 'vip-tag-active' : ''}}" bindtap="goToVip">匹配</text>
|
||||
<text class="vip-tag {{isVip ? 'vip-tag-active' : ''}}" bindtap="goToMatch">匹配</text>
|
||||
<text class="vip-tag {{isVip ? 'vip-tag-active' : ''}}" bindtap="goToVip">排行</text>
|
||||
</view>
|
||||
<text class="user-wechat" bindtap="copyUserId">微信号: {{userWechat || userIdShort || '--'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="profile-stats-row">
|
||||
<view class="profile-stat">
|
||||
<view class="profile-stat" bindtap="goToChapters">
|
||||
<text class="profile-stat-val">{{readCount}}</text>
|
||||
<text class="profile-stat-label">已读章节</text>
|
||||
</view>
|
||||
<view class="profile-stat">
|
||||
<view class="profile-stat" bindtap="goToReferral">
|
||||
<text class="profile-stat-val">{{referralCount}}</text>
|
||||
<text class="profile-stat-label">推荐好友</text>
|
||||
</view>
|
||||
<view class="profile-stat">
|
||||
<view class="profile-stat" bindtap="goToReferral">
|
||||
<text class="profile-stat-val">{{earnings === '-' ? '--' : earnings}}</text>
|
||||
<text class="profile-stat-label">我的收益</text>
|
||||
</view>
|
||||
@@ -70,17 +70,17 @@
|
||||
<text class="card-title">阅读统计</text>
|
||||
</view>
|
||||
<view class="stats-grid">
|
||||
<view class="stat-box">
|
||||
<view class="stat-box" bindtap="goToChapters">
|
||||
<image class="stat-icon-img" src="/assets/icons/book-open-teal.svg" mode="aspectFit"/>
|
||||
<text class="stat-num">{{readCount}}</text>
|
||||
<text class="stat-label">已读章节</text>
|
||||
</view>
|
||||
<view class="stat-box">
|
||||
<view class="stat-box" bindtap="goToChapters">
|
||||
<image class="stat-icon-img" src="/assets/icons/clock-teal.svg" mode="aspectFit"/>
|
||||
<text class="stat-num">{{totalReadTime}}</text>
|
||||
<text class="stat-label">阅读分钟</text>
|
||||
</view>
|
||||
<view class="stat-box">
|
||||
<view class="stat-box" bindtap="goToMatch">
|
||||
<image class="stat-icon-img" src="/assets/icons/users-teal.svg" mode="aspectFit"/>
|
||||
<text class="stat-num">{{matchHistory}}</text>
|
||||
<text class="stat-label">匹配伙伴</text>
|
||||
@@ -208,7 +208,9 @@
|
||||
<text class="modal-title">修改昵称</text>
|
||||
</view>
|
||||
<view class="nickname-input-wrap">
|
||||
<input class="nickname-input" type="nickname" value="{{editingNickname}}" placeholder="点击输入昵称" placeholder-class="nickname-placeholder" bindchange="onNicknameChange" bindinput="onNicknameInput" maxlength="20"/>
|
||||
<view class="nickname-input-inner">
|
||||
<input class="nickname-input" type="nickname" value="{{editingNickname}}" placeholder="点击输入昵称" placeholder-class="nickname-placeholder" bindchange="onNicknameChange" bindinput="onNicknameInput" maxlength="20"/>
|
||||
</view>
|
||||
<text class="input-tip">微信用户可点击自动填充昵称</text>
|
||||
</view>
|
||||
<view class="modal-actions">
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
padding-bottom: calc(220rpx + env(safe-area-inset-bottom, 0px));
|
||||
}
|
||||
|
||||
/* ===== 导航栏(避让右上角系统胶囊) ===== */
|
||||
/* ===== 导航栏(左侧设置 + 标题居中,右侧避让胶囊) ===== */
|
||||
.nav-bar {
|
||||
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
|
||||
background: rgba(18,18,18,0.9); backdrop-filter: blur(8rpx);
|
||||
@@ -18,8 +18,8 @@
|
||||
padding: 0 120rpx 0 32rpx; /* 右侧避让胶囊 */
|
||||
border-bottom: 1rpx solid rgba(255,255,255,0.05);
|
||||
}
|
||||
.nav-title { font-size: 40rpx; font-weight: bold; color: #4FD1C5; flex: 1; }
|
||||
.nav-settings { width: 64rpx; height: 64rpx; display: flex; align-items: center; justify-content: center; margin-right: 16rpx; }
|
||||
.nav-settings { width: 64rpx; height: 64rpx; flex-shrink: 0; display: flex; align-items: center; justify-content: center; margin-right: 16rpx; }
|
||||
.nav-title { font-size: 40rpx; font-weight: bold; color: #4FD1C5; flex: 1; text-align: center; }
|
||||
.nav-settings-icon { width: 44rpx; height: 44rpx; opacity: 0.7; }
|
||||
.nav-placeholder { width: 100%; }
|
||||
|
||||
@@ -217,7 +217,11 @@
|
||||
.nickname-modal .modal-icon { font-size: 48rpx; }
|
||||
.nickname-modal .modal-title { font-size: 36rpx; font-weight: bold; }
|
||||
.nickname-input-wrap { margin-bottom: 32rpx; }
|
||||
.nickname-input { width: 100%; padding: 24rpx; background: #262626; border-radius: 20rpx; font-size: 28rpx; color: #fff; box-sizing: border-box; }
|
||||
.nickname-input-inner {
|
||||
padding: 24rpx 32rpx; background: #262626; border-radius: 20rpx;
|
||||
}
|
||||
.nickname-input { width: 100%; font-size: 28rpx; color: #fff; background: transparent; box-sizing: border-box; }
|
||||
.nickname-placeholder { color: #9CA3AF; }
|
||||
.input-tip { display: block; font-size: 22rpx; color: #9CA3AF; margin-top: 12rpx; }
|
||||
.modal-actions { display: flex; gap: 24rpx; }
|
||||
.modal-btn { flex: 1; height: 80rpx; line-height: 80rpx; text-align: center; border-radius: 20rpx; font-size: 28rpx; }
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
<!-- 头像 -->
|
||||
<view class="avatar-section">
|
||||
<view class="avatar-wrap" bindtap="chooseAvatar">
|
||||
<image wx:if="{{avatar}}" class="avatar-img" src="{{avatar}}" mode="aspectFill"/>
|
||||
<view wx:else class="avatar-placeholder">{{nickname ? nickname[0] : '?'}}</view>
|
||||
<view class="avatar-inner">
|
||||
<image wx:if="{{avatar}}" class="avatar-img" src="{{avatar}}" mode="aspectFill"/>
|
||||
<view wx:else class="avatar-placeholder">{{nickname ? nickname[0] : '?'}}</view>
|
||||
</view>
|
||||
<view class="avatar-camera">📷</view>
|
||||
</view>
|
||||
<text class="avatar-change">更换头像</text>
|
||||
|
||||
@@ -31,18 +31,22 @@
|
||||
|
||||
.avatar-section { display: flex; flex-direction: column; align-items: center; margin-bottom: 48rpx; }
|
||||
.avatar-wrap {
|
||||
position: relative; width: 192rpx; height: 192rpx; border-radius: 50%; overflow: hidden;
|
||||
position: relative; width: 192rpx; height: 192rpx; border-radius: 50%;
|
||||
border: 4rpx solid #5EEAD4; box-shadow: 0 0 30rpx rgba(94,234,212,0.3);
|
||||
}
|
||||
.avatar-inner {
|
||||
width: 100%; height: 100%; border-radius: 50%; overflow: hidden;
|
||||
}
|
||||
.avatar-img { width: 100%; height: 100%; display: block; }
|
||||
.avatar-placeholder {
|
||||
width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;
|
||||
font-size: 72rpx; font-weight: bold; color: #5EEAD4; background: rgba(94,234,212,0.2);
|
||||
}
|
||||
.avatar-camera {
|
||||
position: absolute; bottom: 0; right: 0;
|
||||
position: absolute; bottom: -8rpx; right: -8rpx;
|
||||
width: 56rpx; height: 56rpx; background: #5EEAD4; color: #000;
|
||||
border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 28rpx;
|
||||
border: 4rpx solid #050B14; box-sizing: border-box;
|
||||
}
|
||||
.avatar-change { font-size: 28rpx; color: #5EEAD4; font-weight: 500; margin-top: 16rpx; }
|
||||
|
||||
|
||||
@@ -81,6 +81,8 @@ Page({
|
||||
if (app.globalData.initialSectionMid) delete app.globalData.initialSectionMid
|
||||
if (app.globalData.initialSectionId) delete app.globalData.initialSectionId
|
||||
|
||||
console.log("页面:",mid);
|
||||
|
||||
// mid 有值但无 id 时,从 bookData 或 API 解析 id
|
||||
if (mid && !id) {
|
||||
const bookData = app.globalData.bookData || []
|
||||
@@ -126,7 +128,7 @@ Page({
|
||||
})
|
||||
|
||||
// 统一:先拉章节数据,用 isFree/price===0 判断免费
|
||||
const chapterRes = await app.request({ url: `/api/miniprogram/book/chapter/${id}`, silent: true })
|
||||
const chapterRes = await app.request({ url: this._getChapterUrl({ id, mid }), silent: true })
|
||||
const accessState = await accessManager.determineAccessState(id, chapterRes)
|
||||
const canAccess = accessManager.canAccessFullContent(accessState)
|
||||
|
||||
@@ -196,7 +198,7 @@ Page({
|
||||
const sectionPrice = this.data.sectionPrice ?? 1
|
||||
let res = prefetchedChapter
|
||||
if (!res || !res.content) {
|
||||
res = await app.request({ url: `/api/miniprogram/book/chapter/${id}`, silent: true })
|
||||
res = await app.request({ url: this._getChapterUrl({ id }), silent: true })
|
||||
}
|
||||
const section = {
|
||||
id: res.id || id,
|
||||
@@ -295,6 +297,17 @@ Page({
|
||||
return titles[id] || `章节 ${id}`
|
||||
},
|
||||
|
||||
// 根据 id/mid 构造章节接口路径(优先使用 mid)
|
||||
_getChapterUrl(params = {}) {
|
||||
const { id, mid } = params
|
||||
const finalMid = (mid !== undefined && mid !== null) ? mid : this.data.sectionMid
|
||||
if (finalMid) {
|
||||
return `/api/miniprogram/book/chapter/by-mid/${finalMid}`
|
||||
}
|
||||
const finalId = id || this.data.sectionId
|
||||
return `/api/miniprogram/book/chapter/${finalId}`
|
||||
},
|
||||
|
||||
// 加载内容 - 三级降级方案:API → 本地缓存 → 备用API
|
||||
async loadContent(id) {
|
||||
const cacheKey = `chapter_${id}`
|
||||
@@ -344,7 +357,7 @@ Page({
|
||||
reject(new Error('请求超时'))
|
||||
}, timeout)
|
||||
|
||||
app.request(`/api/miniprogram/book/chapter/${id}`)
|
||||
app.request(this._getChapterUrl({ id }))
|
||||
.then(res => {
|
||||
clearTimeout(timer)
|
||||
resolve(res)
|
||||
@@ -360,14 +373,24 @@ Page({
|
||||
setChapterContent(res) {
|
||||
const lines = res.content.split('\n').filter(line => line.trim())
|
||||
const previewCount = Math.ceil(lines.length * 0.2)
|
||||
const sectionPrice = this.data.sectionPrice ?? 1
|
||||
const sectionTitle = (res.sectionTitle || res.title || '').trim()
|
||||
|
||||
this.setData({
|
||||
// 文章详情标题:只使用后端提供的 sectionTitle,不再拼接其他本地标题信息
|
||||
section: {
|
||||
id: res.id || this.data.sectionId,
|
||||
title: sectionTitle,
|
||||
isFree: res.isFree === true || (res.price !== undefined && res.price === 0),
|
||||
price: res.price ?? sectionPrice
|
||||
},
|
||||
content: res.content,
|
||||
previewContent: lines.slice(0, previewCount).join('\n'),
|
||||
contentParagraphs: lines,
|
||||
previewParagraphs: lines.slice(0, previewCount),
|
||||
partTitle: res.partTitle || '',
|
||||
chapterTitle: res.chapterTitle || ''
|
||||
// 导航栏、分享等使用的文章标题,同样统一为 sectionTitle
|
||||
chapterTitle: sectionTitle
|
||||
})
|
||||
},
|
||||
|
||||
@@ -504,11 +527,16 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// 分享到朋友圈:带文章标题,过长时截断(朋友圈卡片标题显示有限)
|
||||
onShareTimeline() {
|
||||
const { section, sectionId, sectionMid } = this.data
|
||||
const { section, sectionId, sectionMid, chapterTitle } = this.data
|
||||
const ref = app.getMyReferralCode()
|
||||
const q = sectionMid ? `mid=${sectionMid}` : `id=${sectionId}`
|
||||
return { title: section?.title ? `📚 ${section.title}` : 'Soul创业派对 - 真实商业故事', query: ref ? `${q}&ref=${ref}` : q }
|
||||
const articleTitle = (section?.title || chapterTitle || '').trim()
|
||||
const title = articleTitle
|
||||
? (articleTitle.length > 28 ? articleTitle.slice(0, 28) + '...' : articleTitle)
|
||||
: 'Soul创业派对 - 真实商业故事'
|
||||
return { title, query: ref ? `${q}&ref=${ref}` : q }
|
||||
},
|
||||
|
||||
// 显示登录弹窗(每次打开协议未勾选,符合审核要求)
|
||||
@@ -588,7 +616,7 @@ Page({
|
||||
|
||||
// 2. 重新拉取章节数据,用 isFree/price 判断免费
|
||||
const chapterRes = await app.request({
|
||||
url: `/api/miniprogram/book/chapter/${this.data.sectionId}`,
|
||||
url: this._getChapterUrl({}),
|
||||
silent: true
|
||||
})
|
||||
const newAccessState = await accessManager.determineAccessState(
|
||||
@@ -859,7 +887,7 @@ Page({
|
||||
|
||||
// 3. 重新拉取章节并判断权限(应为 unlocked_purchased)
|
||||
const chapterRes = await app.request({
|
||||
url: `/api/miniprogram/book/chapter/${this.data.sectionId}`,
|
||||
url: this._getChapterUrl({}),
|
||||
silent: true
|
||||
})
|
||||
let newAccessState = await accessManager.determineAccessState(
|
||||
@@ -1201,7 +1229,7 @@ Page({
|
||||
|
||||
// 重新拉取章节,用 isFree/price 判断免费
|
||||
const chapterRes = await app.request({
|
||||
url: `/api/miniprogram/book/chapter/${this.data.sectionId}`,
|
||||
url: this._getChapterUrl({}),
|
||||
silent: true
|
||||
})
|
||||
const newAccessState = await accessManager.determineAccessState(
|
||||
|
||||
@@ -13,8 +13,7 @@
|
||||
<text class="back-arrow">←</text>
|
||||
</view>
|
||||
<view class="nav-info">
|
||||
<text class="nav-part" wx:if="{{partTitle}}">{{partTitle}}</text>
|
||||
<text class="nav-chapter" wx:if="{{chapterTitle}}">{{chapterTitle}}</text>
|
||||
<text class="nav-chapter" wx:if="{{section.title || chapterTitle}}">{{section.title || chapterTitle}}</text>
|
||||
</view>
|
||||
<view class="nav-right-placeholder"></view>
|
||||
</view>
|
||||
|
||||
@@ -64,67 +64,6 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// 一键获取收货地址
|
||||
getAddress() {
|
||||
wx.chooseAddress({
|
||||
success: (res) => {
|
||||
console.log('[Settings] 获取地址成功:', res)
|
||||
const fullAddress = `${res.provinceName || ''}${res.cityName || ''}${res.countyName || ''}${res.detailInfo || ''}`
|
||||
|
||||
if (fullAddress.trim()) {
|
||||
wx.setStorageSync('user_address', fullAddress)
|
||||
this.setData({ address: fullAddress })
|
||||
|
||||
// 更新用户信息
|
||||
if (app.globalData.userInfo) {
|
||||
app.globalData.userInfo.address = fullAddress
|
||||
wx.setStorageSync('userInfo', app.globalData.userInfo)
|
||||
}
|
||||
|
||||
// 同步到服务器
|
||||
this.syncAddressToServer(fullAddress)
|
||||
|
||||
wx.showToast({ title: '地址已获取', icon: 'success' })
|
||||
}
|
||||
},
|
||||
fail: (e) => {
|
||||
console.log('[Settings] 获取地址失败:', e)
|
||||
if (e.errMsg?.includes('cancel')) {
|
||||
// 用户取消,不提示
|
||||
return
|
||||
}
|
||||
if (e.errMsg?.includes('auth deny') || e.errMsg?.includes('authorize')) {
|
||||
wx.showModal({
|
||||
title: '需要授权',
|
||||
content: '请在设置中允许获取收货地址',
|
||||
confirmText: '去设置',
|
||||
success: (res) => {
|
||||
if (res.confirm) wx.openSetting()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
wx.showToast({ title: '获取失败,请重试', icon: 'none' })
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 同步地址到服务器
|
||||
async syncAddressToServer(address) {
|
||||
try {
|
||||
const userId = app.globalData.userInfo?.id
|
||||
if (!userId) return
|
||||
|
||||
await app.request('/api/miniprogram/user/update', {
|
||||
method: 'POST',
|
||||
data: { userId, address }
|
||||
})
|
||||
console.log('[Settings] 地址已同步到服务器')
|
||||
} catch (e) {
|
||||
console.log('[Settings] 同步地址失败:', e)
|
||||
}
|
||||
},
|
||||
|
||||
// 切换自动提现
|
||||
async toggleAutoWithdraw(e) {
|
||||
const enabled = e.detail.value
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import accessManager from '../../utils/chapterAccessManager'
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
@@ -88,9 +89,9 @@ Page({
|
||||
if (payRes?.success && payRes.data?.payParams) {
|
||||
wx.requestPayment({
|
||||
...payRes.data.payParams,
|
||||
success: () => {
|
||||
success: async () => {
|
||||
wx.showToast({ title: 'VIP开通成功', icon: 'success' })
|
||||
this.loadVipInfo()
|
||||
await this._onVipPaymentSuccess()
|
||||
},
|
||||
fail: () => wx.showToast({ title: '支付取消', icon: 'none' })
|
||||
})
|
||||
@@ -103,6 +104,28 @@ Page({
|
||||
} finally { this.setData({ purchasing: false }) }
|
||||
},
|
||||
|
||||
async _onVipPaymentSuccess() {
|
||||
wx.showLoading({ title: '同步权益中...', mask: true })
|
||||
try {
|
||||
await new Promise(r => setTimeout(r, 1500))
|
||||
await accessManager.refreshUserPurchaseStatus()
|
||||
await this.loadVipInfo()
|
||||
app.globalData.hasFullBook = true
|
||||
const userInfo = app.globalData.userInfo || {}
|
||||
userInfo.hasFullBook = true
|
||||
app.globalData.userInfo = userInfo
|
||||
wx.setStorageSync('userInfo', userInfo)
|
||||
const pages = getCurrentPages()
|
||||
pages.forEach(p => {
|
||||
if (typeof p.initUserStatus === 'function') p.initUserStatus()
|
||||
else if (typeof p.updateUserStatus === 'function') p.updateUserStatus()
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('[VIP] 支付后同步失败:', e)
|
||||
}
|
||||
wx.hideLoading()
|
||||
},
|
||||
|
||||
goBack() { wx.navigateBack() },
|
||||
|
||||
onShareAppMessage() {
|
||||
|
||||
Reference in New Issue
Block a user