1028 lines
39 KiB
JavaScript
1028 lines
39 KiB
JavaScript
/**
|
||
* 卡若创业派对 - 首页
|
||
* 开发: 卡若
|
||
* 技术支持: 存客宝
|
||
*/
|
||
|
||
const app = getApp()
|
||
const { trackClick } = require('../../utils/trackClick')
|
||
const { cleanSingleLineField } = require('../../utils/contentParser')
|
||
const { navigateMpPath } = require('../../utils/mpNavigate.js')
|
||
const { isSafeImageSrc } = require('../../utils/imageUrl.js')
|
||
const {
|
||
submitCkbLead,
|
||
getReferralCodeForPay,
|
||
requestWxJsapiPayment,
|
||
syncOrderStatusQuery,
|
||
} = require('../../utils/soulBridge')
|
||
|
||
const DEFAULT_TIP_GIFTS = [
|
||
{ id: 'gift_lite', name: '小心心', price: 9.9, emoji: '❤️', icon: '', micOrder: 1, iconScale: 1, micFeatured: true },
|
||
{ id: 'gift_star', name: '人气票', price: 99, emoji: '🎫', icon: '', micOrder: 0, iconScale: 1, micFeatured: false },
|
||
{ id: 'gift_super', name: '嘉年华', price: 1980, emoji: '🎡', icon: '', micOrder: 2, iconScale: 1, micFeatured: false },
|
||
]
|
||
|
||
/** 置顶人物无头像时的占位图 */
|
||
const DEFAULT_KARUO_LINK_AVATAR = '/assets/images/karuo-link-avatar.png'
|
||
|
||
/** 与首页固定「卡若」获客位重复时从横滑列表剔除(含历史误写「卡路」) */
|
||
function isKaruoHostDuplicateName(displayName) {
|
||
const s = String(displayName || '').trim()
|
||
return s === '卡若' || s === '卡路'
|
||
}
|
||
|
||
/** 超级个体无头像占位:仅展示中文首字,避免头像圆里出现英文字母 */
|
||
function superAvatarLetter(displayName) {
|
||
const s = String(displayName || '').trim()
|
||
if (!s) return '会'
|
||
const ch = s[0]
|
||
return /[\u4e00-\u9fff]/.test(ch) ? ch : '会'
|
||
}
|
||
|
||
/** 旧 OSS 存 16personalities .svg 时改为 png 目录,与后端 resolveAvatarURL 一致 */
|
||
function normalizeLegacyMbtiAvatarUrl(u) {
|
||
if (!u || typeof u !== 'string') return u
|
||
const lower = u.toLowerCase()
|
||
if (!lower.includes('mbti-avatar')) return u
|
||
let s = u.replace(/mbti-avatars-16personalities-male/g, 'mbti-avatars-png-male')
|
||
s = s.replace(/mbti-avatars-16personalities-female/g, 'mbti-avatars-png-female')
|
||
if (/\.svg$/i.test(s)) s = s.replace(/\.svg$/i, '.png')
|
||
return s
|
||
}
|
||
|
||
function parseTimeToMinutes(t) {
|
||
const m = String(t || '').trim().match(/^(\d{1,2}):(\d{2})$/)
|
||
if (!m) return null
|
||
const h = parseInt(m[1], 10)
|
||
const min = parseInt(m[2], 10)
|
||
if (h < 0 || h > 23 || min < 0 || min > 59) return null
|
||
return h * 60 + min
|
||
}
|
||
|
||
/** 是否在直播上麦时段(支持跨午夜 end < start) */
|
||
function isNowInMicWindow(startStr, endStr) {
|
||
const a = parseTimeToMinutes(startStr)
|
||
const b = parseTimeToMinutes(endStr)
|
||
if (a == null || b == null) return false
|
||
const now = new Date()
|
||
const cur = now.getHours() * 60 + now.getMinutes()
|
||
if (a <= b) return cur >= a && cur < b
|
||
return cur >= a || cur < b
|
||
}
|
||
|
||
function normCfgBool(v) {
|
||
return v === true || v === 1 || v === '1' || String(v).toLowerCase() === 'true'
|
||
}
|
||
|
||
function normalizeTipGifts(raw) {
|
||
const base = DEFAULT_TIP_GIFTS
|
||
if (!Array.isArray(raw) || raw.length === 0) return base.map((g) => ({ ...g }))
|
||
return raw.map((row, i) => {
|
||
const dFallback = base[Math.min(i, base.length - 1)]
|
||
const rn = String(row.name != null ? row.name : '').trim()
|
||
let id = row.id != null ? String(row.id).trim() : ''
|
||
let d = dFallback
|
||
// 无 id / 空 id 时禁止按下标套默认 id:后台顺序多为「人气票、小心心、嘉年华」,下标1会变成 gift_star,
|
||
// 导致中间显示小心心¥9.9 却带 gift_star 去支付变成¥29.8。优先按礼物名称、再按 micOrder 对齐默认档。
|
||
if (!id) {
|
||
const byName = rn ? base.find((g) => g.name === rn) : undefined
|
||
if (byName) {
|
||
d = byName
|
||
id = byName.id
|
||
} else {
|
||
let mo = row.micOrder != null ? parseInt(String(row.micOrder), 10) : NaN
|
||
if (!Number.isNaN(mo)) {
|
||
const byOrder = base.find((g) => Number(g.micOrder) === mo)
|
||
if (byOrder) {
|
||
d = byOrder
|
||
id = byOrder.id
|
||
}
|
||
}
|
||
if (!id) {
|
||
d = dFallback
|
||
id = d.id
|
||
}
|
||
}
|
||
}
|
||
const name = String(row.name != null ? row.name : d.name).trim() || d.name
|
||
let price = parseFloat(row.price)
|
||
if (Number.isNaN(price) || price <= 0) price = d.price
|
||
const emoji = String(row.emoji != null ? row.emoji : d.emoji).trim() || d.emoji
|
||
const icon = String(row.icon != null ? row.icon : '').trim()
|
||
let micOrder = row.micOrder != null ? parseInt(String(row.micOrder), 10) : NaN
|
||
if (Number.isNaN(micOrder)) micOrder = 999
|
||
let iconScale = parseFloat(row.iconScale)
|
||
if (Number.isNaN(iconScale) || iconScale <= 0) iconScale = 1
|
||
const micFeatured =
|
||
row.micFeatured === true ||
|
||
row.micFeatured === 1 ||
|
||
row.micFeatured === '1' ||
|
||
String(row.micFeatured).toLowerCase() === 'true'
|
||
return { id, name, price, emoji, icon, micOrder, iconScale, micFeatured }
|
||
})
|
||
}
|
||
|
||
function sortGiftsForMic(arr) {
|
||
return [...arr].sort((a, b) => {
|
||
const oa = a.micOrder != null && !Number.isNaN(Number(a.micOrder)) ? Number(a.micOrder) : 999
|
||
const ob = b.micOrder != null && !Number.isNaN(Number(b.micOrder)) ? Number(b.micOrder) : 999
|
||
if (oa !== ob) return oa - ob
|
||
return 0
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 上麦横排:按礼物 micOrder 排序,最多 3 档;默认档优先 micFeatured,否则中间位
|
||
* micEmojiRpx:默认档再乘 liveMicSchedule.micCenterEmojiScale
|
||
*/
|
||
function layoutMicHorizontalSlots(gifts, liveMicSchedule) {
|
||
const fallback = DEFAULT_TIP_GIFTS.map((g) => ({ ...g }))
|
||
let arr = Array.isArray(gifts) && gifts.length ? gifts : fallback
|
||
arr = sortGiftsForMic(arr)
|
||
const slotsRaw = arr.slice(0, 3)
|
||
let defaultIndex = slotsRaw.length >= 2 ? 1 : 0
|
||
const fi = slotsRaw.findIndex(
|
||
(g) => g.micFeatured === true || g.micFeatured === 1 || g.micFeatured === '1' || String(g.micFeatured).toLowerCase() === 'true'
|
||
)
|
||
if (fi >= 0) defaultIndex = fi
|
||
const def = slotsRaw[defaultIndex]
|
||
const defaultId = def && def.id ? def.id : ''
|
||
let centerScale = 1.55
|
||
if (liveMicSchedule && liveMicSchedule.micCenterEmojiScale != null) {
|
||
const v = parseFloat(String(liveMicSchedule.micCenterEmojiScale))
|
||
if (!Number.isNaN(v) && v >= 1) centerScale = Math.min(v, 2.4)
|
||
}
|
||
const n = slotsRaw.length
|
||
/** 几何中间列:3 档为 index 1;2 档为 index 1;1 档为 0 */
|
||
const centerIdx = n <= 1 ? 0 : Math.floor(n / 2)
|
||
const baseRpx = 44
|
||
const slots = slotsRaw.map((g, idx) => {
|
||
const per = g.iconScale != null && Number(g.iconScale) > 0 ? Number(g.iconScale) : 1
|
||
const isDef = g.id === defaultId
|
||
const isCenterSlot = idx === centerIdx
|
||
let rpx = Math.round(baseRpx * per * (isDef ? centerScale : 1))
|
||
if (isCenterSlot) rpx = Math.round(rpx * 1.22)
|
||
rpx = Math.max(32, Math.min(110, rpx))
|
||
const iconStr = String(g.icon || '').trim()
|
||
const micShowIcon = !!(iconStr && isSafeImageSrc(iconStr))
|
||
return { ...g, micEmojiRpx: rpx, micShowIcon, micIsCenterSlot: isCenterSlot }
|
||
})
|
||
return { slots, defaultId }
|
||
}
|
||
|
||
Page({
|
||
data: {
|
||
// 系统信息
|
||
statusBarHeight: 44,
|
||
navBarHeight: 88,
|
||
|
||
// 用户信息
|
||
isLoggedIn: false,
|
||
hasFullBook: false,
|
||
readCount: 0,
|
||
|
||
// 书籍数据(totalSections 来自 book/parts,初始用 app.getTotalSections() 兜底)
|
||
totalSections: 0, // onLoad 后由 loadBookData 更新
|
||
bookData: [],
|
||
|
||
// 推荐章节(来自 recommended/hot API,初始为空避免占位错误)
|
||
featuredSections: [],
|
||
|
||
// Banner 推荐(优先用 recommended API 第一条,回退 latest-chapters)
|
||
bannerSection: null,
|
||
/** 首屏 Banner 请求完成前为 true;完成后为 false,避免接口全失败时长期显示「加载中」 */
|
||
bannerLoading: true,
|
||
latestLabel: '最新更新',
|
||
|
||
// 内容概览
|
||
partsList: [
|
||
{ id: 'part-1', number: '一', title: '真实的人', subtitle: '人与人之间的底层逻辑' },
|
||
{ id: 'part-2', number: '二', title: '真实的行业', subtitle: '电商、内容、传统行业解析' },
|
||
{ id: 'part-3', number: '三', title: '真实的错误', subtitle: '我和别人犯过的错' },
|
||
{ id: 'part-4', number: '四', title: '真实的赚钱', subtitle: '底层结构与真实案例' },
|
||
{ id: 'part-5', number: '五', title: '真实的社会', subtitle: '未来职业与商业生态' }
|
||
],
|
||
|
||
// 超级个体(VIP会员)
|
||
superMembers: [],
|
||
superMembersLoading: true,
|
||
|
||
// 收益排行榜
|
||
earningsBoard: [],
|
||
earningsBoardEnabled: false,
|
||
|
||
// 最新新增章节(完整列表 + 展示列表,用于展开/折叠)
|
||
latestChapters: [],
|
||
displayLatestChapters: [],
|
||
|
||
// 篇章数(从 bookData 计算)
|
||
partCount: 0,
|
||
|
||
// 加载状态
|
||
loading: true,
|
||
|
||
// 展开状态(首页精选/最新)
|
||
featuredExpanded: false,
|
||
latestExpanded: false,
|
||
featuredSectionsFull: [], // 精选排行榜全量(最多 50),默认只展示前 3 条
|
||
|
||
// 功能配置(搜索开关)
|
||
searchEnabled: true,
|
||
|
||
// 审核模式:隐藏支付相关入口
|
||
auditMode: false,
|
||
|
||
// mp_config.mpUi.homePage(后台系统设置 mpUi)
|
||
mpUiLogoTitle: '卡若创业派对',
|
||
mpUiLogoSubtitle: '来自派对房的真实故事',
|
||
/** 仅当有置顶 @人物时展示,文案与头像由 _applyHomeMpUi 写入 */
|
||
mpUiLinkKaruoText: '',
|
||
mpUiLinkKaruoDisplay: DEFAULT_KARUO_LINK_AVATAR,
|
||
mpUiSearchPlaceholder: '搜索章节标题或内容...',
|
||
mpUiBannerTag: '推荐',
|
||
mpUiBannerReadMore: '点击阅读',
|
||
mpUiSuperTitle: '超级个体',
|
||
mpUiPickTitle: '精选推荐',
|
||
mpUiLatestTitle: '最新新增',
|
||
|
||
/** 后台 @列表置顶人物:有则右上角展示绑定用户头像 + @名称,点击走 ckb/lead */
|
||
homePinnedPerson: null,
|
||
|
||
/** 首页入口:打赏 + 直播时段上麦(mpUi.homePage.linkKaruoReward / liveMicSchedule) */
|
||
linkKaruoRewardEnabled: false,
|
||
linkKaruoRewardUi: {
|
||
entryButtonText: '支持一下',
|
||
modalTitle: '打赏支持',
|
||
modalSubtitle: '感谢支持,任选礼物与数量',
|
||
payButtonText: '立即支付',
|
||
leadLinkText: '留下联系方式',
|
||
afterPayToast: '支付成功,感谢支持!',
|
||
},
|
||
liveMicSchedule: {
|
||
enabled: false,
|
||
micStart: '18:00',
|
||
micEnd: '21:00',
|
||
micEmoji: '🎤',
|
||
micButtonText: '申请上麦',
|
||
micModalTitle: '申请上麦',
|
||
micModalContent: '',
|
||
micAfterSubmitToast: '支付成功,感谢支持!',
|
||
micCenterEmojiScale: 1.55,
|
||
},
|
||
homeMicEligible: false,
|
||
tipGifts: [],
|
||
showTipModal: false,
|
||
tipHomeMicMode: false,
|
||
tipModalTitle: '',
|
||
tipModalSubtitle: '',
|
||
selectedTipGiftId: '',
|
||
tipQty: 1,
|
||
tipLineTotal: '0.00',
|
||
/** 上麦弹窗横排礼物(与后台 gifts 顺序一致) */
|
||
micHorizontalSlots: [],
|
||
micDefaultGiftId: '',
|
||
/** 右上角唯一入口:由置顶超级个体决定 */
|
||
headerCornerAction: { show: false, kind: '', label: '', emoji: '', avatar: '' },
|
||
|
||
/** 文旅/落地体验链:未登录时弹一键登录,成功后继续打赏/上麦/留资 */
|
||
showLoginModal: false,
|
||
showPrivacyModal: false,
|
||
loginModalDesc: '登录后可参与打赏、上麦与链接嘉宾',
|
||
pendingAfterLogin: null,
|
||
},
|
||
|
||
onLoad(options) {
|
||
console.log('[Index] ===== onLoad 触发 =====')
|
||
|
||
// 获取系统信息
|
||
this.setData({
|
||
statusBarHeight: app.globalData.statusBarHeight,
|
||
navBarHeight: app.globalData.navBarHeight
|
||
})
|
||
|
||
// 处理分享参数(推荐码绑定)
|
||
if (options && options.ref) {
|
||
console.log('[Index] 检测到推荐码:', options.ref)
|
||
app.handleReferralCode({ query: options })
|
||
}
|
||
|
||
wx.showShareMenu({ withShareTimeline: true })
|
||
this.loadFeatureConfig()
|
||
this.initData()
|
||
},
|
||
|
||
async onShow() {
|
||
console.log('[Index] onShow 触发')
|
||
try {
|
||
await app.getConfig(true)
|
||
} catch (_) {}
|
||
this.setData({ auditMode: app.globalData.auditMode || false })
|
||
this._refreshHomeEntryFlags()
|
||
void this.loadHomePinnedPerson()
|
||
// 首页 tab 再次展示时重拉超级个体(避免首进网络失败或仅 onLoad 拉一次后长期空白)
|
||
if (!app.globalData.auditMode) {
|
||
void this.loadSuperMembers()
|
||
}
|
||
|
||
// 设置TabBar选中状态
|
||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
||
const tabBar = this.getTabBar()
|
||
console.log('[Index] TabBar 组件:', tabBar ? '已找到' : '未找到')
|
||
|
||
// 主动触发配置加载
|
||
if (tabBar && tabBar.loadFeatureConfig) {
|
||
console.log('[Index] 主动调用 TabBar.loadFeatureConfig()')
|
||
tabBar.loadFeatureConfig()
|
||
}
|
||
|
||
// 更新选中状态
|
||
if (tabBar && tabBar.updateSelected) {
|
||
tabBar.updateSelected()
|
||
} else if (tabBar) {
|
||
tabBar.setData({ selected: 0 })
|
||
}
|
||
} else {
|
||
console.log('[Index] TabBar 组件未找到或 getTabBar 方法不存在')
|
||
}
|
||
|
||
// 更新用户状态
|
||
this.updateUserStatus()
|
||
},
|
||
|
||
// 初始化数据:首次进页面并行异步加载,加快首屏展示
|
||
initData() {
|
||
this.setData({ loading: false })
|
||
this.loadBookData()
|
||
this.loadFeaturedAndLatest()
|
||
this.loadSuperMembers()
|
||
this.loadEarningsBoard()
|
||
},
|
||
|
||
async loadSuperMembers() {
|
||
this.setData({ superMembersLoading: true })
|
||
try {
|
||
// 仅走后端 VIP 列表排序(vip_sort、vip_activated_at),不在端上拼普通用户
|
||
const vipRes = await app.request({ url: '/api/miniprogram/vip/members?limit=24', silent: true }).catch(() => null)
|
||
let members = []
|
||
if (vipRes && vipRes.success && Array.isArray(vipRes.data) && vipRes.data.length > 0) {
|
||
members = vipRes.data.map(u => {
|
||
const raw = u.name || u.nickname || u.vipName || u.vip_name || '会员'
|
||
const name = cleanSingleLineField(raw) || '会员'
|
||
return {
|
||
id: u.id,
|
||
name,
|
||
avatar: u.avatar || '',
|
||
isVip: true,
|
||
avatarLetter: superAvatarLetter(name)
|
||
}
|
||
}).filter((m) => !isKaruoHostDuplicateName(m.name))
|
||
console.log('[Index] 超级个体(后端排序):', members.length, '人')
|
||
}
|
||
this.setData({ superMembers: members, superMembersLoading: false })
|
||
} catch (e) {
|
||
console.log('[Index] 加载超级个体失败:', e)
|
||
this.setData({ superMembersLoading: false })
|
||
}
|
||
},
|
||
|
||
async loadEarningsBoard() {
|
||
try {
|
||
const res = await app.request({ url: '/api/miniprogram/earnings/leaderboard', silent: true }).catch(() => null)
|
||
if (res && res.success && res.enabled && Array.isArray(res.data) && res.data.length > 0) {
|
||
const board = res.data.map(item => ({
|
||
rank: item.rank,
|
||
nickname: item.nickname || '分享达人',
|
||
avatar: normalizeLegacyMbtiAvatarUrl(item.avatar || ''),
|
||
avatarLetter: superAvatarLetter(item.nickname || '创')
|
||
}))
|
||
this.setData({ earningsBoard: board, earningsBoardEnabled: true })
|
||
console.log('[Index] 收益排行榜:', board.length, '人')
|
||
} else {
|
||
this.setData({ earningsBoardEnabled: res && res.enabled === false ? false : true })
|
||
}
|
||
} catch (e) {
|
||
console.log('[Index] 加载收益排行榜失败:', e)
|
||
}
|
||
},
|
||
|
||
// 精选推荐 + 最新更新 + 最新列表:顺序以后端为准(recommended=排行榜算法,latest=updated_at)
|
||
async loadFeaturedAndLatest() {
|
||
this.setData({ bannerLoading: true })
|
||
try {
|
||
const tagClassForTag = (tag) => (tag === '热门' ? 'tag-hot' : 'tag-rec')
|
||
const toSectionFromRanking = (s) => {
|
||
const tag = s.tag || '精选'
|
||
return {
|
||
id: s.id || s.section_id,
|
||
mid: s.mid ?? s.MID ?? 0,
|
||
title: s.section_title || s.sectionTitle || s.title || s.chapterTitle || '',
|
||
part: (s.part_title || s.partTitle || '').replace(/[_||]/g, ' ').trim(),
|
||
tag,
|
||
tagClass: tagClassForTag(tag)
|
||
}
|
||
}
|
||
const fallbackTags = ['热门', '推荐', '精选']
|
||
const toSectionFromHot = (s, i) => {
|
||
const tag = fallbackTags[i % 3]
|
||
return {
|
||
id: s.id || s.section_id,
|
||
mid: s.mid ?? s.MID ?? 0,
|
||
title: s.section_title || s.sectionTitle || s.title || s.chapterTitle || '',
|
||
part: (s.part_title || s.partTitle || '').replace(/[_||]/g, ' ').trim(),
|
||
tag,
|
||
tagClass: tagClassForTag(tag)
|
||
}
|
||
}
|
||
|
||
const [recRes, latestRes] = await Promise.all([
|
||
app.request({ url: '/api/miniprogram/book/recommended?limit=50', silent: true }).catch(() => null),
|
||
app.request({ url: '/api/miniprogram/book/latest-chapters', silent: true }).catch(() => null)
|
||
])
|
||
|
||
// 1. 精选推荐:一次拉全量(≤50),默认只显示 3 条;点列表下三角展开(与「最新新增」一致)
|
||
let featuredFull = []
|
||
if (recRes && recRes.success && Array.isArray(recRes.data) && recRes.data.length > 0) {
|
||
featuredFull = recRes.data.map((s) => toSectionFromRanking(s))
|
||
}
|
||
if (featuredFull.length === 0) {
|
||
try {
|
||
const hotRes = await app.request({ url: '/api/miniprogram/book/hot?limit=50', silent: true })
|
||
const hotList = (hotRes && hotRes.data) ? hotRes.data : []
|
||
if (hotList.length > 0) featuredFull = hotList.map((s, i) => toSectionFromHot(s, i))
|
||
} catch (e) { console.log('[Index] book/hot 兜底失败:', e) }
|
||
}
|
||
if (featuredFull.length > 0) {
|
||
this.setData({
|
||
featuredSectionsFull: featuredFull,
|
||
featuredSections: featuredFull.slice(0, 3),
|
||
featuredExpanded: false
|
||
})
|
||
} else {
|
||
this.setData({
|
||
featuredSectionsFull: [],
|
||
featuredSections: [],
|
||
featuredExpanded: false
|
||
})
|
||
}
|
||
|
||
// 2. Banner 推荐:优先取 recommended 第一条,回退 latest 第一条
|
||
const rawList = (latestRes && latestRes.data) ? latestRes.data : []
|
||
// 按更新时间倒序,最新在前(与后台展示一致)
|
||
const latestList = [...rawList].sort((a, b) => {
|
||
const ta = new Date(a.updatedAt || a.updated_at || 0).getTime()
|
||
const tb = new Date(b.updatedAt || b.updated_at || 0).getTime()
|
||
return tb - ta
|
||
})
|
||
if (featuredFull.length > 0) {
|
||
this.setData({ bannerSection: featuredFull[0] })
|
||
} else if (latestList.length > 0) {
|
||
const l = latestList[0]
|
||
this.setData({
|
||
bannerSection: {
|
||
id: l.id,
|
||
mid: l.mid ?? l.MID ?? 0,
|
||
title: l.section_title || l.sectionTitle || l.title || l.chapterTitle || '',
|
||
part: l.part_title || l.partTitle || ''
|
||
}
|
||
})
|
||
}
|
||
const latestChapters = latestList.slice(0, 20).map(c => {
|
||
const d = new Date(c.updatedAt || c.updated_at || Date.now())
|
||
const title = c.section_title || c.sectionTitle || c.title || c.chapterTitle || ''
|
||
return {
|
||
id: c.id,
|
||
mid: c.mid ?? c.MID ?? 0,
|
||
title,
|
||
desc: '',
|
||
price: c.price ?? 1,
|
||
dateStr: `${d.getMonth() + 1}/${d.getDate()}`
|
||
}
|
||
})
|
||
const display = this.data.latestExpanded ? latestChapters : latestChapters.slice(0, 5)
|
||
this.setData({ latestChapters, displayLatestChapters: display })
|
||
} catch (e) {
|
||
console.log('[Index] 从服务端加载推荐失败:', e)
|
||
} finally {
|
||
this.setData({ bannerLoading: false })
|
||
}
|
||
},
|
||
|
||
async loadBookData() {
|
||
try {
|
||
const res = await app.request({ url: '/api/miniprogram/book/parts', silent: true })
|
||
if (res?.success) {
|
||
const total = res.totalSections ?? 0
|
||
const parts = res.parts || []
|
||
app.globalData.totalSections = (total != null && total > 0) ? total : app.getTotalSections()
|
||
this.setData({
|
||
totalSections: app.globalData.totalSections,
|
||
partCount: parts.length || 5
|
||
})
|
||
}
|
||
} catch (e) {
|
||
this.setData({ totalSections: app.getTotalSections(), partCount: 5 })
|
||
}
|
||
},
|
||
|
||
// 更新用户状态(已读数 = 用户实际打开过的章节数,仅统计有权限阅读的)
|
||
updateUserStatus() {
|
||
const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData
|
||
const readCount = Math.min(app.getReadCount(), this.data.totalSections || app.getTotalSections())
|
||
this.setData({
|
||
isLoggedIn,
|
||
hasFullBook,
|
||
readCount
|
||
})
|
||
},
|
||
|
||
// 跳转到目录
|
||
goToChapters() {
|
||
trackClick('home', 'nav_click', '阅读进度')
|
||
wx.switchTab({ url: '/pages/chapters/chapters' })
|
||
},
|
||
|
||
_applyHomeMpUi() {
|
||
const h = app.globalData.configCache?.mpConfig?.mpUi?.homePage || {}
|
||
const baseTitle = String(h.logoTitle || '卡若创业派对').trim() || '卡若创业派对'
|
||
const prefix = String(h.pinnedTitlePrefix != null ? h.pinnedTitlePrefix : '派对会员').trim()
|
||
const tpl = String(h.pinnedMainTitleTemplate || '').trim()
|
||
const patch = {
|
||
mpUiLogoTitle: baseTitle,
|
||
mpUiLogoSubtitle: String(h.logoSubtitle || '来自派对房的真实故事').trim() || '来自派对房的真实故事',
|
||
mpUiSearchPlaceholder: String(h.searchPlaceholder || '搜索章节标题或内容...').trim() || '搜索章节标题或内容...',
|
||
mpUiBannerTag: String(h.bannerTag || '推荐').trim() || '推荐',
|
||
mpUiBannerReadMore: String(h.bannerReadMoreText || '点击阅读').trim() || '点击阅读',
|
||
mpUiSuperTitle: String(h.superSectionTitle || '超级个体').trim() || '超级个体',
|
||
mpUiPickTitle: String(h.pickSectionTitle || '精选推荐').trim() || '精选推荐',
|
||
mpUiLatestTitle: String(h.latestSectionTitle || '最新新增').trim() || '最新新增',
|
||
}
|
||
const pinned = this.data.homePinnedPerson
|
||
if (pinned && pinned.token) {
|
||
const displayAv =
|
||
pinned.avatar && isSafeImageSrc(pinned.avatar) ? pinned.avatar : DEFAULT_KARUO_LINK_AVATAR
|
||
const nm = pinned.name || '好友'
|
||
patch.mpUiLinkKaruoText = `点击链接${nm}`
|
||
patch.mpUiLinkKaruoDisplay = displayAv
|
||
let mainTitle = baseTitle
|
||
if (tpl) {
|
||
mainTitle = tpl
|
||
.replace(/\{\{name\}\}/g, nm)
|
||
.replace(/\{\{prefix\}\}/g, prefix)
|
||
.trim() || baseTitle
|
||
} else if (prefix) {
|
||
mainTitle = baseTitle
|
||
} else {
|
||
mainTitle = `@${nm}`
|
||
}
|
||
patch.mpUiLogoTitle = mainTitle
|
||
} else {
|
||
patch.mpUiLinkKaruoText = ''
|
||
patch.mpUiLinkKaruoDisplay = DEFAULT_KARUO_LINK_AVATAR
|
||
}
|
||
this.setData(patch, () => {
|
||
try {
|
||
wx.setNavigationBarTitle({ title: patch.mpUiLogoTitle || '首页' })
|
||
} catch (_) {}
|
||
this._syncHeaderCornerAction()
|
||
})
|
||
},
|
||
|
||
/** 拉取后台置顶 @人物,合并到首页右上角「链接」区;同时拿到该人的 homeEntryConfig */
|
||
async loadHomePinnedPerson() {
|
||
let pinnedPerson = null
|
||
let override
|
||
try {
|
||
const res = await app.request({ url: '/api/miniprogram/ckb/pinned-person', silent: true })
|
||
if (res && res.success && res.data && res.data.token) {
|
||
const name = cleanSingleLineField(res.data.name) || '好友'
|
||
let av = String(res.data.avatar || '').trim()
|
||
if (!isSafeImageSrc(av)) av = ''
|
||
pinnedPerson = {
|
||
token: String(res.data.token).trim(),
|
||
name,
|
||
avatar: av,
|
||
homeEntryConfig: res.data.homeEntryConfig || null,
|
||
}
|
||
if (pinnedPerson.homeEntryConfig) {
|
||
override = {}
|
||
if (pinnedPerson.homeEntryConfig.linkKaruoReward) override.linkKaruoReward = pinnedPerson.homeEntryConfig.linkKaruoReward
|
||
if (pinnedPerson.homeEntryConfig.liveMicSchedule) override.liveMicSchedule = pinnedPerson.homeEntryConfig.liveMicSchedule
|
||
}
|
||
} else {
|
||
pinnedPerson = null
|
||
}
|
||
} catch (e) {
|
||
console.log('[Index] pinned-person:', e)
|
||
pinnedPerson = null
|
||
}
|
||
this.setData({ homePinnedPerson: pinnedPerson }, () => {
|
||
this._applyHomeMpUi()
|
||
this._refreshHomeEntryFlags(override)
|
||
})
|
||
},
|
||
|
||
async loadFeatureConfig() {
|
||
try {
|
||
await app.getConfig(true)
|
||
const res = app.globalData.configCache
|
||
if (res) {
|
||
const features = res.features || (res.data && res.data.features) || {}
|
||
const searchEnabled = features.searchEnabled !== false
|
||
if (!app.globalData.features) app.globalData.features = {}
|
||
app.globalData.features.searchEnabled = searchEnabled
|
||
if (typeof features.matchEnabled === 'boolean') app.globalData.features.matchEnabled = features.matchEnabled
|
||
if (typeof features.referralEnabled === 'boolean') app.globalData.features.referralEnabled = features.referralEnabled
|
||
const mp = res.mpConfig || {}
|
||
app.globalData.auditMode = !!mp.auditMode
|
||
}
|
||
await app.getAuditMode()
|
||
const searchEnabled = app.globalData.features?.searchEnabled !== false
|
||
this.setData({
|
||
searchEnabled,
|
||
auditMode: app.globalData.auditMode || false
|
||
})
|
||
this._applyHomeMpUi()
|
||
this._refreshHomeEntryFlags()
|
||
} catch (e) {
|
||
try {
|
||
await app.getAuditMode()
|
||
} catch (_) {}
|
||
this.setData({
|
||
searchEnabled: app.globalData.features?.searchEnabled !== false,
|
||
auditMode: app.globalData.auditMode || false
|
||
})
|
||
this._applyHomeMpUi()
|
||
this._refreshHomeEntryFlags()
|
||
}
|
||
await this.loadHomePinnedPerson()
|
||
},
|
||
|
||
_refreshHomeEntryFlags(override) {
|
||
const audit = !!app.globalData.auditMode
|
||
// 有置顶人物时:入口开关严格按该人物配置,不再回退全局,避免“人字拖未开上麦却显示申请上麦”。
|
||
// 无置顶人物时:沿用全局 mpUi.homePage。
|
||
const hasPinned = !!(this.data.homePinnedPerson && this.data.homePinnedPerson.token)
|
||
const personCfg = override || ((this.data.homePinnedPerson && this.data.homePinnedPerson.homeEntryConfig) || null)
|
||
const h = app.globalData.configCache?.mpConfig?.mpUi?.homePage || {}
|
||
const rw = hasPinned
|
||
? ((personCfg && personCfg.linkKaruoReward) || {})
|
||
: (h.linkKaruoReward || {})
|
||
const schReal = hasPinned
|
||
? ((personCfg && personCfg.liveMicSchedule) || {})
|
||
: (h.liveMicSchedule || {})
|
||
const rewardEnabled = normCfgBool(rw.enabled)
|
||
const micEnabled = normCfgBool(schReal.enabled)
|
||
const inWindow = isNowInMicWindow(schReal.micStart, schReal.micEnd)
|
||
const homeMicEligible = !audit && micEnabled && inWindow
|
||
const gifts = normalizeTipGifts(Array.isArray(rw.gifts) && rw.gifts.length ? rw.gifts : null)
|
||
const tipGifts = gifts.length ? gifts : DEFAULT_TIP_GIFTS.map((g) => ({ ...g }))
|
||
this.setData({
|
||
linkKaruoRewardEnabled: rewardEnabled,
|
||
linkKaruoRewardUi: {
|
||
entryButtonText: String(rw.entryButtonText || '支持一下').trim() || '支持一下',
|
||
modalTitle: String(rw.modalTitle || '打赏支持').trim() || '打赏支持',
|
||
modalSubtitle: String(rw.modalSubtitle || '感谢支持,任选礼物与数量').trim() || '感谢支持,任选礼物与数量',
|
||
payButtonText: String(rw.payButtonText || '立即支付').trim() || '立即支付',
|
||
leadLinkText: String(rw.leadLinkText || '留下联系方式').trim() || '留下联系方式',
|
||
afterPayToast: String(rw.afterPayToast || '支付成功,感谢支持!').trim() || '支付成功,感谢支持!',
|
||
},
|
||
liveMicSchedule: {
|
||
enabled: micEnabled,
|
||
micStart: String(schReal.micStart || '18:00').trim() || '18:00',
|
||
micEnd: String(schReal.micEnd || '21:00').trim() || '21:00',
|
||
micEmoji: String(schReal.micEmoji || '🎤').trim() || '🎤',
|
||
micButtonText: String(schReal.micButtonText || '申请上麦').trim() || '申请上麦',
|
||
micModalTitle: String(schReal.micModalTitle || '申请上麦').trim() || '申请上麦',
|
||
micModalContent: String(
|
||
schReal.micModalContent || '当前为直播时段,提交后工作人员将按顺序处理连麦申请。',
|
||
).trim(),
|
||
micAfterSubmitToast: String(schReal.micAfterSubmitToast || '支付成功,感谢支持!').trim() || '支付成功,感谢支持!',
|
||
micCenterEmojiScale: (() => {
|
||
const v = parseFloat(String(schReal.micCenterEmojiScale != null ? schReal.micCenterEmojiScale : '1.55'))
|
||
if (Number.isNaN(v) || v < 1) return 1.55
|
||
return Math.min(v, 2.4)
|
||
})(),
|
||
},
|
||
homeMicEligible,
|
||
tipGifts,
|
||
})
|
||
this._syncHeaderCornerAction()
|
||
},
|
||
|
||
/** 右上角单一入口:上麦时段 > 置顶超级个体 > 打赏 */
|
||
_syncHeaderCornerAction() {
|
||
if (app.globalData.auditMode) {
|
||
this.setData({ headerCornerAction: { show: false, kind: '', label: '', emoji: '', avatar: '' } })
|
||
return
|
||
}
|
||
const mic = !!this.data.homeMicEligible
|
||
const pinned = !!(this.data.homePinnedPerson && this.data.homePinnedPerson.token)
|
||
const reward = !!this.data.linkKaruoRewardEnabled
|
||
const sch = this.data.liveMicSchedule || {}
|
||
let show = false, kind = '', label = '', emoji = '', avatar = ''
|
||
if (mic) {
|
||
show = true; kind = 'mic'
|
||
label = String(sch.micButtonText || '申请上麦').trim() || '申请上麦'
|
||
emoji = String(sch.micEmoji || '🎤').trim() || '🎤'
|
||
} else if (pinned) {
|
||
show = true; kind = 'ckb'
|
||
label = String(this.data.mpUiLinkKaruoText || '').trim() || '链接卡若'
|
||
avatar = this.data.mpUiLinkKaruoDisplay || DEFAULT_KARUO_LINK_AVATAR
|
||
} else if (reward) {
|
||
show = true; kind = 'reward'
|
||
const rwUi = this.data.linkKaruoRewardUi || {}
|
||
label = String(rwUi.entryButtonText || '支持一下').trim() || '支持一下'
|
||
emoji = ''
|
||
}
|
||
this.setData({ headerCornerAction: { show, kind, label, emoji, avatar } })
|
||
},
|
||
|
||
/** 右上角统一点击 */
|
||
onHeaderCornerTap() {
|
||
const a = this.data.headerCornerAction
|
||
if (!a || !a.show) return
|
||
if (a.kind === 'ckb') {
|
||
this.onLinkKaruo()
|
||
return
|
||
}
|
||
this._openHorizontalGiftModal(a.kind === 'mic' ? 'live_mic' : 'home_reward')
|
||
},
|
||
|
||
/**
|
||
* 体验环节未登录:弹公用 login-modal(单页模式仍引导「前往小程序」)
|
||
* @param {{ type: 'tip', tipSource: string } | { type: 'link_karuo' }} pending
|
||
*/
|
||
_showHomeLoginModal(pending, desc) {
|
||
try {
|
||
const sys = wx.getSystemInfoSync()
|
||
const isSinglePage = (sys && sys.mode === 'singlePage') || app.globalData.isSinglePageMode
|
||
if (isSinglePage) {
|
||
wx.showModal({
|
||
title: '请前往完整小程序',
|
||
content: '当前为单页模式,登录与支付需点击底部「前往小程序」进入完整版。',
|
||
showCancel: false,
|
||
confirmText: '我知道了',
|
||
})
|
||
return
|
||
}
|
||
} catch (e) {
|
||
console.warn('[Index] 检测单页模式失败,回退为登录弹窗:', e)
|
||
}
|
||
this.setData({
|
||
pendingAfterLogin: pending,
|
||
loginModalDesc: desc || '登录后可参与打赏、上麦与链接嘉宾',
|
||
showLoginModal: true,
|
||
})
|
||
},
|
||
|
||
onLoginModalClose() {
|
||
this.setData({ showLoginModal: false, showPrivacyModal: false, pendingAfterLogin: null })
|
||
},
|
||
onLoginModalPrivacyAgree() {
|
||
this.setData({ showPrivacyModal: false })
|
||
},
|
||
onLoginModalSuccess() {
|
||
const pending = this.data.pendingAfterLogin
|
||
this.setData({ showLoginModal: false, pendingAfterLogin: null })
|
||
this.updateUserStatus()
|
||
wx.showToast({ title: '登录成功', icon: 'success' })
|
||
if (!pending) return
|
||
const run = (fn) => {
|
||
if (typeof wx.nextTick === 'function') wx.nextTick(fn)
|
||
else setTimeout(fn, 0)
|
||
}
|
||
if (pending.type === 'tip' && pending.tipSource) {
|
||
run(() => this._openHorizontalGiftModal(pending.tipSource))
|
||
} else if (pending.type === 'link_karuo') {
|
||
run(() => this.onLinkKaruo())
|
||
}
|
||
},
|
||
|
||
/** 打开横排礼物弹窗(上麦/打赏统一入口) */
|
||
_openHorizontalGiftModal(tipSource) {
|
||
if (app.globalData.auditMode) return
|
||
const label = tipSource === 'live_mic' ? '申请上麦' : '打赏支持'
|
||
trackClick('home', tipSource === 'live_mic' ? 'live_mic_request' : 'btn_click', label)
|
||
if (!app.globalData.isLoggedIn) {
|
||
const desc =
|
||
tipSource === 'live_mic'
|
||
? '登录后可申请上麦并完成礼遇支付'
|
||
: '登录后可打赏支持'
|
||
this._showHomeLoginModal({ type: 'tip', tipSource }, desc)
|
||
return
|
||
}
|
||
const sch = this.data.liveMicSchedule
|
||
const gifts = this.data.tipGifts || []
|
||
const { slots, defaultId } = layoutMicHorizontalSlots(gifts, sch)
|
||
const title = tipSource === 'live_mic'
|
||
? String(sch.micModalTitle || '申请上麦').trim()
|
||
: String(this.data.linkKaruoRewardUi.modalTitle || '打赏支持').trim()
|
||
const sub = tipSource === 'live_mic'
|
||
? String(sch.micModalContent || '当前为直播时段,提交后工作人员将按顺序处理连麦申请。').trim()
|
||
: String(this.data.linkKaruoRewardUi.modalSubtitle || '感谢支持,点选礼物直接支付').trim()
|
||
this.setData({
|
||
showTipModal: true,
|
||
tipHomeMicMode: tipSource === 'live_mic',
|
||
tipModalTitle: title,
|
||
tipModalSubtitle: sub,
|
||
micHorizontalSlots: slots,
|
||
micDefaultGiftId: defaultId,
|
||
selectedTipGiftId: defaultId,
|
||
tipQty: 1,
|
||
})
|
||
},
|
||
|
||
closeTipModal() {
|
||
this.setData({ showTipModal: false, micHorizontalSlots: [], micDefaultGiftId: '' })
|
||
},
|
||
|
||
stopTipModalBubble() {},
|
||
|
||
/** 横排礼物卡片点击 → 直接调起微信支付 */
|
||
onHorizontalTipPay(e) {
|
||
const id = e.currentTarget.dataset.id
|
||
if (!id) return
|
||
const src = this.data.tipHomeMicMode ? 'live_mic' : 'home_reward'
|
||
this._executeLinkKaruoTipPay(src, id, 1)
|
||
},
|
||
|
||
async _executeLinkKaruoTipPay(tipSource, giftId, qty) {
|
||
const gid = String(giftId || '').trim()
|
||
const q = Math.max(1, parseInt(String(qty), 10) || 1)
|
||
if (!gid) {
|
||
wx.showToast({ title: '请选择礼物', icon: 'none' })
|
||
return
|
||
}
|
||
if (!app.globalData.isLoggedIn || !app.globalData.userInfo?.id) {
|
||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||
return
|
||
}
|
||
const openId = await app.ensurePayOpenId()
|
||
if (!openId) {
|
||
wx.showModal({
|
||
title: '无法拉起支付',
|
||
content: '未获取到微信支付标识,请完全关闭小程序后重新进入,或退出账号重新登录。',
|
||
showCancel: false,
|
||
})
|
||
return
|
||
}
|
||
const g = (this.data.tipGifts || []).find((x) => String(x.id) === gid)
|
||
const amount = g && Number(g.price) > 0 ? Math.round(Number(g.price) * q * 100) / 100 : 0
|
||
if (amount <= 0) {
|
||
wx.showToast({ title: '礼物价格无效', icon: 'none' })
|
||
return
|
||
}
|
||
wx.showLoading({ title: '下单中...', mask: true })
|
||
try {
|
||
const referralCode = getReferralCodeForPay(app) || undefined
|
||
const res = await app.request({
|
||
url: '/api/miniprogram/pay',
|
||
method: 'POST',
|
||
silent: true,
|
||
data: {
|
||
openId,
|
||
productType: 'link_karuo_tip',
|
||
productId: tipSource === 'live_mic' ? `live_mic|${gid}|${q}` : `${gid}|${q}`,
|
||
amount,
|
||
description: tipSource === 'live_mic' ? '上麦礼遇' : '首页打赏',
|
||
userId: app.globalData.userInfo.id,
|
||
referralCode,
|
||
tipSource: tipSource === 'live_mic' ? 'live_mic' : 'home_reward',
|
||
},
|
||
})
|
||
wx.hideLoading()
|
||
if (!res || !res.success || !res.data || !res.data.payParams) {
|
||
const errText = (res && (res.error || res.message)) || '下单失败'
|
||
wx.showToast({ title: String(errText).slice(0, 36), icon: 'none' })
|
||
return
|
||
}
|
||
await requestWxJsapiPayment(res.data.payParams)
|
||
const sn = res.data.orderSn
|
||
if (sn) {
|
||
try {
|
||
await syncOrderStatusQuery(app, sn)
|
||
} catch (_) {}
|
||
}
|
||
const toast =
|
||
tipSource === 'live_mic'
|
||
? this.data.liveMicSchedule.micAfterSubmitToast || '支付成功'
|
||
: this.data.linkKaruoRewardUi.afterPayToast || '支付成功'
|
||
wx.showToast({ title: toast, icon: 'success' })
|
||
this.closeTipModal()
|
||
} catch (e) {
|
||
wx.hideLoading()
|
||
const msg = (e && e.errMsg) || (e && e.message) || '支付失败'
|
||
if (String(msg).indexOf('cancel') >= 0) {
|
||
wx.showToast({ title: '已取消支付', icon: 'none' })
|
||
return
|
||
}
|
||
wx.showToast({ title: String(msg).slice(0, 28), icon: 'none' })
|
||
}
|
||
},
|
||
|
||
// 跳转到搜索页
|
||
goToSearch() {
|
||
if (!this.data.searchEnabled) return
|
||
trackClick('home', 'nav_click', '搜索')
|
||
wx.navigateTo({ url: '/pages/search/search' })
|
||
},
|
||
|
||
// 跳转到阅读页(传 mid,与分享一致;无 mid 时传 id)
|
||
goToRead(e) {
|
||
const id = e.currentTarget.dataset.id
|
||
const mid = e.currentTarget.dataset.mid
|
||
trackClick('home', 'card_click', id || '章节')
|
||
const q = mid ? `mid=${mid}` : `id=${id}`
|
||
wx.navigateTo({ url: `/pages/read/read?${q}` })
|
||
},
|
||
|
||
// 跳转到匹配页
|
||
goToMatch() {
|
||
wx.switchTab({ url: '/pages/match/match' })
|
||
},
|
||
|
||
goToVip() {
|
||
trackClick('home', 'btn_click', '加入创业派对')
|
||
wx.navigateTo({ url: '/pages/vip/vip' })
|
||
},
|
||
|
||
async onLinkKaruo() {
|
||
const pinned = this.data.homePinnedPerson
|
||
if (!pinned || !pinned.token) return
|
||
trackClick('home', 'btn_click', '置顶@人物留资')
|
||
if (!app.globalData.isLoggedIn) {
|
||
this._showHomeLoginModal({ type: 'link_karuo' }, '登录后可向嘉宾留下联系方式')
|
||
return
|
||
}
|
||
await submitCkbLead(getApp(), {
|
||
targetUserId: pinned.token,
|
||
targetNickname: pinned.name,
|
||
source: 'home_pinned_person',
|
||
})
|
||
},
|
||
|
||
goToSuperList() {
|
||
wx.switchTab({ url: '/pages/match/match' })
|
||
},
|
||
|
||
// 精选推荐:列表下方小三角展开(数据已在 loadFeaturedAndLatest 一次拉齐)
|
||
expandFeaturedChapters() {
|
||
if (this.data.featuredExpanded) return
|
||
const full = this.data.featuredSectionsFull || []
|
||
if (full.length <= 3) return
|
||
trackClick('home', 'tab_click', '精选展开_底部三角')
|
||
this.setData({ featuredExpanded: true, featuredSections: full })
|
||
},
|
||
|
||
// 最新新增:列表下方小三角展开(无「收起」,展开后整页向下滚动查看)
|
||
expandLatestChapters() {
|
||
if (this.data.latestExpanded) return
|
||
trackClick('home', 'tab_click', '最新展开_底部三角')
|
||
const full = this.data.latestChapters || []
|
||
this.setData({
|
||
latestExpanded: true,
|
||
displayLatestChapters: full
|
||
})
|
||
},
|
||
|
||
goToMemberDetail(e) {
|
||
const id = e.currentTarget.dataset.id
|
||
trackClick('home', 'card_click', '超级个体_' + (id || ''))
|
||
wx.navigateTo({ url: `/pages/member-detail/member-detail?id=${id}` })
|
||
},
|
||
|
||
// 跳转到我的页面
|
||
goToMy() {
|
||
wx.switchTab({ url: '/pages/my/my' })
|
||
},
|
||
|
||
// 下拉刷新(等待各异步加载完成后再结束)
|
||
async onPullDownRefresh() {
|
||
try {
|
||
await app.getConfig(true)
|
||
} catch (_) {}
|
||
this.setData({ auditMode: app.globalData.auditMode || false })
|
||
this._refreshHomeEntryFlags()
|
||
await Promise.all([
|
||
this.loadBookData(),
|
||
this.loadFeaturedAndLatest(),
|
||
this.loadSuperMembers()
|
||
])
|
||
this.updateUserStatus()
|
||
wx.stopPullDownRefresh()
|
||
},
|
||
|
||
onShareAppMessage() {
|
||
const ref = app.getMyReferralCode()
|
||
return {
|
||
title: '卡若创业派对 - 真实商业故事',
|
||
path: ref ? `/pages/index/index?ref=${ref}` : '/pages/index/index'
|
||
}
|
||
},
|
||
|
||
onShareTimeline() {
|
||
const ref = app.getMyReferralCode()
|
||
return { title: '卡若创业派对 - 真实商业故事', query: ref ? `ref=${ref}` : '' }
|
||
}
|
||
})
|