This commit is contained in:
Alex-larget
2026-03-17 18:22:06 +08:00
parent 88915276d1
commit f276595ad6
50 changed files with 2246 additions and 1223 deletions

View File

@@ -6,22 +6,58 @@
const { parseScene } = require('./utils/scene.js')
const { checkAndExecute } = require('./utils/ruleEngine.js')
const PRODUCTION_URL = 'https://soulapi.quwanzhi.com'
const TEST_URL = 'https://souldev.quwanzhi.com'
const LOCAL_URL = 'http://localhost:8080'
const DEFAULT_APP_ID = 'wxb8bbb2b10dec74aa'
const DEFAULT_MCH_ID = '1318592501'
const DEFAULT_WITHDRAW_TMPL_ID = 'u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE'
function getRuntimeBootstrapConfig() {
try {
const accountInfo = wx.getAccountInfoSync?.()
const envVersion = accountInfo?.miniProgram?.envVersion || 'release'
const extCfg = wx.getExtConfigSync ? (wx.getExtConfigSync() || {}) : {}
// 按运行环境自动切换 API 地址
let baseUrl
if (envVersion === 'release') {
baseUrl = PRODUCTION_URL
} else if (envVersion === 'trial') {
baseUrl = extCfg.apiBaseUrl || wx.getStorageSync('apiBaseUrl') || TEST_URL
} else {
// develop不使用 storage避免被 loadMpConfig 曾写入的生产地址污染env-switch 仍可运行时覆盖
baseUrl = extCfg.apiBaseUrl || LOCAL_URL
}
return {
baseUrl,
appId: extCfg.appId || DEFAULT_APP_ID,
mchId: extCfg.mchId || DEFAULT_MCH_ID,
withdrawSubscribeTmplId: extCfg.withdrawSubscribeTmplId || DEFAULT_WITHDRAW_TMPL_ID
}
} catch (_) {
return {
baseUrl: PRODUCTION_URL,
appId: DEFAULT_APP_ID,
mchId: DEFAULT_MCH_ID,
withdrawSubscribeTmplId: DEFAULT_WITHDRAW_TMPL_ID
}
}
}
const bootstrapConfig = getRuntimeBootstrapConfig()
App({
globalData: {
// API 基础地址(切换环境时注释/取消注释)
// baseUrl: 'https://soulapi.quwanzhi.com',
baseUrl: 'http://localhost:8080', // 本地调试
// baseUrl: 'https://souldev.quwanzhi.com', // 测试环境
// API 基础地址:优先外部配置/缓存,其次默认生产环境
baseUrl: bootstrapConfig.baseUrl,
// 小程序配置 - 真实AppID
appId: 'wxb8bbb2b10dec74aa',
appId: bootstrapConfig.appId,
// 订阅消息:用户点击「申请提现」→「立即提现」时会先弹出订阅授权窗
withdrawSubscribeTmplId: 'u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE',
withdrawSubscribeTmplId: bootstrapConfig.withdrawSubscribeTmplId,
// 微信支付配置
mchId: '1318592501', // 商户号
mchId: bootstrapConfig.mchId,
// 用户信息
userInfo: null,
@@ -67,7 +103,16 @@ App({
isSinglePageMode: false,
// 更新检测:上次检测时间戳,避免频繁请求
lastUpdateCheck: 0
lastUpdateCheck: 0,
// mpConfig 上次刷新时间戳onShow 节流,避免频繁请求)
lastMpConfigCheck: 0,
// 审核模式:后端 /api/miniprogram/config 返回 auditMode=true 时隐藏所有支付相关UI
auditMode: false,
// 客服/微信mp_config 返回 supportWechat
supportWechat: '',
// API 域名loadMpConfig 从 config 更新
apiDomain: ''
},
onLaunch(options) {
@@ -102,10 +147,16 @@ App({
this.handleReferralCode(options)
},
// 小程序显示时:处理分享参数、检测更新(从后台切回时)
// 小程序显示时:处理分享参数、检测更新、刷新 mpConfig(从后台切回时)
onShow(options) {
this.handleReferralCode(options)
this.checkUpdate()
// 从后台切回时刷新审核模式等配置(节流 30 秒,避免频繁请求)
const now = Date.now()
if (!this.globalData.lastMpConfigCheck || now - this.globalData.lastMpConfigCheck > 30 * 1000) {
this.globalData.lastMpConfigCheck = now
this.loadMpConfig()
}
},
// 处理推荐码绑定:官方以 options.scene 接收扫码参数(可同时带 mid/id + ref与 utils/scene 解析闭环
@@ -330,15 +381,35 @@ App({
}
},
// 加载 mpConfigappId、mchId、withdrawSubscribeTmplId 等),失败时保留 globalData 默认值
// 加载 mpConfigappId、mchId、withdrawSubscribeTmplId、auditMode、supportWechat、apiDomain 等),失败时保留 globalData 默认值
async loadMpConfig() {
try {
const res = await this.request({ url: '/api/miniprogram/config', silent: true })
const res = await this.request({ url: '/api/miniprogram/config', silent: true, timeout: 5000 })
const mp = (res && res.mpConfig) || (res && res.configs && res.configs.mp_config)
if (mp && typeof mp === 'object') {
if (mp.appId) this.globalData.appId = mp.appId
if (mp.mchId) this.globalData.mchId = mp.mchId
if (mp.withdrawSubscribeTmplId) this.globalData.withdrawSubscribeTmplId = mp.withdrawSubscribeTmplId
// 仅正式版使用后端 apiDomain开发/体验版保持 bootstrap 的 baseUrl避免被生产地址覆盖
try {
const envVersion = wx.getAccountInfoSync?.()?.miniProgram?.envVersion || 'release'
if (envVersion === 'release' && mp.apiDomain) {
this.globalData.baseUrl = mp.apiDomain
this.globalData.apiDomain = mp.apiDomain
try { wx.setStorageSync('apiBaseUrl', mp.apiDomain) } catch (_) {}
}
} catch (_) {}
this.globalData.auditMode = !!mp.auditMode
this.globalData.supportWechat = mp.supportWechat || mp.customerWechat || mp.serviceWechat || ''
// 通知当前已加载的页面刷新 auditMode从后台切回时配置更新后立即生效
try {
const pages = getCurrentPages()
pages.forEach(p => {
if (p && p.data && 'auditMode' in p.data) {
p.setData({ auditMode: this.globalData.auditMode || false })
}
})
} catch (_) {}
}
} catch (e) {
console.warn('[App] loadMpConfig 失败,使用默认值:', e?.message || e)
@@ -426,6 +497,7 @@ App({
url: this.globalData.baseUrl + url,
method: options.method || 'GET',
data: options.data || {},
timeout: options.timeout || 15000,
header: {
'Content-Type': 'application/json',
'Authorization': token ? `Bearer ${token}` : '',

View File

@@ -59,7 +59,9 @@
}
]
},
"usingComponents": {},
"usingComponents": {
"env-switch": "/components/env-switch/env-switch"
},
"navigateToMiniProgramAppIdList": [
"wx6489c26045912fe1",
"wx3d15ed02e98b04e3"

View File

@@ -0,0 +1,82 @@
/**
* 开发环境专用:可拖拽的 baseURL 切换悬浮按钮
* 正式环境release不显示
*/
const PRODUCTION_URL = 'https://soulapi.quwanzhi.com'
const STORAGE_KEY = 'apiBaseUrl'
const POSITION_KEY = 'envSwitchPosition'
const URL_OPTIONS = [
{ label: '生产', url: PRODUCTION_URL },
{ label: '本地', url: 'http://localhost:8080' },
{ label: '测试', url: 'https://souldev.quwanzhi.com' },
]
Component({
data: {
visible: false,
x: 20,
y: 120,
currentLabel: '生产',
areaWidth: 375,
areaHeight: 812,
},
lifetimes: {
attached() {
try {
const accountInfo = wx.getAccountInfoSync?.()
const envVersion = accountInfo?.miniProgram?.envVersion || 'release'
if (envVersion === 'release') {
return
}
const sys = wx.getSystemInfoSync?.() || {}
const areaWidth = sys.windowWidth || 375
const areaHeight = sys.windowHeight || 812
const saved = wx.getStorageSync(POSITION_KEY)
const pos = saved ? JSON.parse(saved) : { x: 20, y: 120 }
// 与 app.js 一致storage 优先,否则用 globalData已按 env 自动切换)
const current = wx.getStorageSync(STORAGE_KEY) || getApp().globalData?.baseUrl || PRODUCTION_URL
const opt = URL_OPTIONS.find(o => o.url === current) || URL_OPTIONS[0]
this.setData({
visible: true,
x: pos.x ?? 20,
y: pos.y ?? 120,
currentLabel: opt.label,
areaWidth,
areaHeight,
})
} catch (_) {
this.setData({ visible: false })
}
},
},
methods: {
onTap() {
const items = URL_OPTIONS.map(o => o.label)
const current = wx.getStorageSync(STORAGE_KEY) || PRODUCTION_URL
const idx = URL_OPTIONS.findIndex(o => o.url === current)
wx.showActionSheet({
itemList: items,
success: (res) => {
const opt = URL_OPTIONS[res.tapIndex]
wx.setStorageSync(STORAGE_KEY, opt.url)
const app = getApp()
if (app && app.globalData) {
app.globalData.baseUrl = opt.url
}
this.setData({ currentLabel: opt.label })
wx.showToast({ title: `已切到${opt.label}`, icon: 'none', duration: 1500 })
},
})
},
onMovableChange(e) {
const { x, y } = e.detail
if (typeof x === 'number' && typeof y === 'number') {
wx.setStorageSync(POSITION_KEY, JSON.stringify({ x, y }))
}
},
},
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,13 @@
<movable-area wx:if="{{visible}}" class="env-area" style="width:{{areaWidth}}px;height:{{areaHeight}}px;">
<movable-view
class="env-btn"
direction="all"
inertia
x="{{x}}"
y="{{y}}"
bindchange="onMovableChange"
bindtap="onTap"
>
<view class="env-btn-inner">{{currentLabel}}</view>
</movable-view>
</movable-area>

View File

@@ -0,0 +1,30 @@
.env-area {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 9999;
}
.env-btn {
width: 72rpx;
height: 72rpx;
pointer-events: auto;
}
.env-btn-inner {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 22rpx;
font-weight: 600;
color: #fff;
box-shadow: 0 4rpx 12rpx rgba(34, 197, 94, 0.4);
border: 2rpx solid rgba(255, 255, 255, 0.3);
}

View File

@@ -40,9 +40,6 @@ Page({
{ id: 'appendix-3', title: '附录3本书提到的工具和资源' }
],
// 每日新增章节(懒加载后暂无,可后续用 latest-chapters 补充)
dailyChapters: [],
// book/parts 加载中
partsLoading: true,
@@ -59,7 +56,6 @@ Page({
this.updateUserStatus()
this.loadVipStatus()
this.loadParts()
this.loadDailyChapters()
this.loadFeatureConfig()
},
@@ -197,36 +193,11 @@ Page({
},
onPullDownRefresh() {
Promise.all([this.loadParts(), this.loadDailyChapters()])
this.loadParts()
.then(() => wx.stopPullDownRefresh())
.catch(() => wx.stopPullDownRefresh())
},
// 每日新增:用 latest-chapters 接口,展示最近更新章节
async loadDailyChapters() {
try {
const res = await app.request({ url: '/api/miniprogram/book/latest-chapters', silent: true })
const list = (res && res.data) ? res.data : []
const pt = (c) => (c.partTitle || c.part_title || '').toLowerCase()
const exclude = c => !pt(c).includes('序言') && !pt(c).includes('尾声') && !pt(c).includes('附录')
const daily = list
.filter(exclude)
.slice(0, 10)
.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,
price: c.price ?? 1,
dateStr: `${d.getMonth() + 1}/${d.getDate()}`
}
})
this.setData({ dailyChapters: daily })
} catch (e) { console.log('[Chapters] 加载每日新增失败:', e) }
},
onShow() {
// 设置TabBar选中状态
if (typeof this.getTabBar === 'function' && this.getTabBar()) {

View File

@@ -40,31 +40,6 @@
<!-- 目录内容 -->
<view class="chapters-content" wx:if="{{!partsLoading}}">
<!-- 每日新增(最近更新章节快捷入口) -->
<view class="daily-section" wx:if="{{dailyChapters.length > 0}}">
<view class="daily-header">
<text class="daily-title">每日新增</text>
<text class="daily-badge">+{{dailyChapters.length}}</text>
</view>
<view class="daily-list">
<view
class="daily-item"
wx:for="{{dailyChapters}}"
wx:key="id"
bindtap="goToRead"
data-id="{{item.id}}"
data-mid="{{item.mid}}"
>
<view class="daily-dot"></view>
<view class="daily-content">
<text class="daily-item-title">{{item.title}}</text>
<text class="daily-item-meta">{{item.dateStr}} · ¥{{item.price}}</text>
</view>
<text class="daily-arrow"></text>
</view>
</view>
</view>
<!-- 序言(优先传 mid阅读页用 by-mid 请求) -->
<view class="chapter-item" bindtap="goToRead" data-id="preface" data-mid="{{fixedSectionsMap.preface}}">
<view class="item-left">
@@ -158,4 +133,5 @@
<!-- 底部留白 -->
<view class="bottom-space"></view>
<env-switch />
</view>

View File

@@ -174,89 +174,6 @@
box-sizing: border-box;
}
/* ===== 每日新增 ===== */
.daily-section {
margin-bottom: 32rpx;
padding: 24rpx;
background: #1c1c1e;
border-radius: 24rpx;
border: 2rpx solid rgba(255, 255, 255, 0.05);
}
.daily-header {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 24rpx;
}
.daily-title {
font-size: 30rpx;
font-weight: 600;
color: #ffffff;
}
.daily-badge {
font-size: 22rpx;
padding: 4rpx 12rpx;
background: #F6AD55;
color: #ffffff;
border-radius: 20rpx;
}
.daily-list {
display: flex;
flex-direction: column;
gap: 0;
}
.daily-item {
display: flex;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.06);
}
.daily-item:last-child {
border-bottom: none;
}
.daily-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: rgba(0, 206, 209, 0.6);
margin-right: 20rpx;
flex-shrink: 0;
}
.daily-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4rpx;
}
.daily-item-title {
font-size: 26rpx;
color: #ffffff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.daily-item-meta {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.4);
}
.daily-arrow {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.4);
margin-left: 16rpx;
}
/* ===== 章节项 ===== */
.chapter-item {
display: flex;
@@ -609,21 +526,6 @@
color: rgba(255, 255, 255, 0.3);
}
/* ===== 每日新增章节 ===== */
.daily-section { margin: 20rpx 0; padding: 24rpx; background: rgba(255,215,0,0.04); border: 1rpx solid rgba(255,215,0,0.12); border-radius: 16rpx; }
.daily-header { display: flex; align-items: center; gap: 12rpx; margin-bottom: 16rpx; }
.daily-title { font-size: 30rpx; font-weight: 600; color: #FFD700; }
.daily-badge { font-size: 22rpx; background: #FFD700; color: #000; padding: 2rpx 12rpx; border-radius: 20rpx; font-weight: bold; }
.daily-list { display: flex; flex-direction: column; gap: 12rpx; }
.daily-item { display: flex; justify-content: space-between; align-items: center; padding: 16rpx; background: rgba(255,255,255,0.03); border-radius: 12rpx; }
.daily-left { display: flex; align-items: center; gap: 10rpx; flex: 1; min-width: 0; }
.daily-new-tag { font-size: 18rpx; background: #FF4444; color: #fff; padding: 2rpx 8rpx; border-radius: 6rpx; font-weight: bold; flex-shrink: 0; }
.daily-item-title { font-size: 26rpx; color: rgba(255,255,255,0.85); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.daily-right { display: flex; align-items: center; gap: 12rpx; flex-shrink: 0; }
.daily-price { font-size: 26rpx; color: #FFD700; font-weight: 600; }
.daily-date { font-size: 20rpx; color: rgba(255,255,255,0.35); }
.daily-note { display: block; font-size: 22rpx; color: rgba(255,215,0,0.5); margin-top: 12rpx; text-align: center; }
/* ===== 底部留白 ===== */
.bottom-space {
height: 40rpx;

View File

@@ -106,4 +106,5 @@
<view class="bg-glow bg-glow-2"></view>
<view class="bg-dots"></view>
</view>
<env-switch />
</view>

View File

@@ -7,6 +7,7 @@
console.log('[Index] ===== 首页文件开始加载 =====')
const app = getApp()
const { trackClick } = require('../../utils/trackClick')
Page({
data: {
@@ -68,7 +69,10 @@ Page({
featuredExpandedLoading: false,
// 功能配置(搜索开关)
searchEnabled: true
searchEnabled: true,
// 审核模式:隐藏支付相关入口
auditMode: false
},
onLoad(options) {
@@ -93,6 +97,7 @@ Page({
onShow() {
console.log('[Index] onShow 触发')
this.setData({ auditMode: app.globalData.auditMode || false })
// 设置TabBar选中状态
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
@@ -309,35 +314,45 @@ Page({
// 跳转到目录
goToChapters() {
trackClick('home', 'nav_click', '阅读进度')
wx.switchTab({ url: '/pages/chapters/chapters' })
},
async loadFeatureConfig() {
try {
if (app.globalData.features && typeof app.globalData.features.searchEnabled === 'boolean') {
this.setData({ searchEnabled: app.globalData.features.searchEnabled })
const hasCachedFeatures = app.globalData.features && typeof app.globalData.features.searchEnabled === 'boolean'
if (hasCachedFeatures) {
this.setData({
searchEnabled: app.globalData.features.searchEnabled,
auditMode: app.globalData.auditMode || false
})
return
}
const res = await app.request({ url: '/api/miniprogram/config', silent: true })
const features = (res && res.features) || {}
const mp = (res && res.mpConfig) || {}
const searchEnabled = features.searchEnabled !== false
const auditMode = !!mp.auditMode
if (!app.globalData.features) app.globalData.features = {}
app.globalData.features.searchEnabled = searchEnabled
this.setData({ searchEnabled })
app.globalData.auditMode = auditMode
this.setData({ searchEnabled, auditMode })
} catch (e) {
this.setData({ searchEnabled: true })
this.setData({ searchEnabled: true, auditMode: app.globalData.auditMode || false })
}
},
// 跳转到搜索页
goToSearch() {
if (!this.data.searchEnabled) return
trackClick('home', 'nav_click', '搜索')
wx.navigateTo({ url: '/pages/search/search' })
},
// 跳转到阅读页(优先传 mid与分享逻辑一致
goToRead(e) {
const id = e.currentTarget.dataset.id
trackClick('home', 'card_click', id || '章节')
const mid = e.currentTarget.dataset.mid || app.getSectionMid(id)
const q = mid ? `mid=${mid}` : `id=${id}`
wx.navigateTo({ url: `/pages/read/read?${q}` })
@@ -349,10 +364,12 @@ Page({
},
goToVip() {
trackClick('home', 'btn_click', '加入创业派对')
wx.navigateTo({ url: '/pages/vip/vip' })
},
async onLinkKaruo() {
trackClick('home', 'btn_click', '链接卡若')
const app = getApp()
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) {
wx.showModal({
@@ -528,6 +545,7 @@ Page({
// 精选推荐:展开/折叠
async toggleFeaturedExpanded() {
if (this.data.featuredExpandedLoading) return
trackClick('home', 'tab_click', this.data.featuredExpanded ? '精选收起' : '精选展开')
if (this.data.featuredExpanded) {
const collapsed = this.data.featuredSectionsFull.length > 0 ? this.data.featuredSectionsFull.slice(0, 3) : this.data.featuredSections
this.setData({ featuredExpanded: false, featuredSections: collapsed })
@@ -564,6 +582,7 @@ Page({
// 最新新增:展开/折叠(默认 5 条,点击展开剩余)
toggleLatestExpanded() {
trackClick('home', 'tab_click', this.data.latestExpanded ? '最新收起' : '最新展开')
const expanded = !this.data.latestExpanded
const display = expanded ? this.data.latestChapters : this.data.latestChapters.slice(0, 5)
this.setData({ latestExpanded: expanded, displayLatestChapters: display })
@@ -598,6 +617,7 @@ Page({
goToMemberDetail(e) {
const id = e.currentTarget.dataset.id
trackClick('home', 'card_click', '超级个体_' + (id || ''))
wx.navigateTo({ url: `/pages/member-detail/member-detail?id=${id}` })
},

View File

@@ -65,8 +65,8 @@
</view>
</view>
<!-- 超级个体(横向滚动,已去掉「查看全部」) -->
<view class="section">
<!-- 超级个体(横向滚动,已去掉「查看全部」;审核模式隐藏 -->
<view class="section" wx:if="{{!auditMode}}">
<view class="section-header">
<text class="section-title">超级个体</text>
</view>
@@ -190,4 +190,5 @@
</view>
</view>
</view>
<env-switch />
</view>

View File

@@ -6,6 +6,7 @@
const app = getApp()
const { checkAndExecute } = require('../../utils/ruleEngine.js')
const { trackClick } = require('../../utils/trackClick')
// 默认匹配类型配置
// 找伙伴:真正的匹配功能,匹配数据库中的真实用户
@@ -198,6 +199,7 @@ Page({
// 选择匹配类型
selectType(e) {
const typeId = e.currentTarget.dataset.type
trackClick('match', 'tab_click', typeId || '类型')
const type = MATCH_TYPES.find(t => t.id === typeId)
this.setData({
selectedType: typeId,
@@ -207,6 +209,7 @@ Page({
// 点击匹配按钮
async handleMatchClick() {
trackClick('match', 'btn_click', '匹配_' + (this.data.selectedType || ''))
const currentType = MATCH_TYPES.find(t => t.id === this.data.selectedType)
// 导师顾问:先播匹配动画,动画完成后再跳转(不在此处直接跳)

View File

@@ -325,4 +325,5 @@
<!-- 底部留白 -->
<view class="bottom-space"></view>
<env-switch />
</view>

View File

@@ -6,6 +6,7 @@
const app = getApp()
const { formatStatNum } = require('../../utils/util.js')
const { trackClick } = require('../../utils/trackClick')
Page({
data: {
@@ -39,6 +40,7 @@ Page({
// 功能配置
matchEnabled: false,
referralEnabled: true,
auditMode: false,
searchEnabled: true,
// VIP状态
@@ -98,6 +100,7 @@ Page({
},
onShow() {
this.setData({ auditMode: app.globalData.auditMode || false })
// 设置TabBar选中状态根据 matchEnabled 动态设置)
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
const tabBar = this.getTabBar()
@@ -118,8 +121,11 @@ Page({
const matchEnabled = features.matchEnabled === true
const referralEnabled = features.referralEnabled !== false
const searchEnabled = features.searchEnabled !== false
const mp = (res && res.mpConfig) || {}
const auditMode = !!mp.auditMode
app.globalData.auditMode = auditMode
app.globalData.features = { matchEnabled, referralEnabled, searchEnabled }
this.setData({ matchEnabled, referralEnabled, searchEnabled })
this.setData({ matchEnabled, referralEnabled, searchEnabled, auditMode })
} catch (error) {
console.log('加载功能配置失败:', error)
this.setData({ matchEnabled: false, referralEnabled: true, searchEnabled: true })
@@ -322,6 +328,7 @@ Page({
// 一键收款:逐条调起微信收款页(有上一页则返回,无则回首页)
async handleOneClickReceive() {
trackClick('my', 'btn_click', '一键收款')
if (!this.data.isLoggedIn) { this.showLogin(); return }
if (this.data.receivingAll) return
@@ -664,6 +671,7 @@ Page({
// 显示登录弹窗(每次打开时协议未勾选,符合审核要求)
showLogin() {
trackClick('my', 'btn_click', '点击登录')
// 朋友圈等单页模式下,不直接弹登录,用官方推荐的方式引导用户「前往小程序」
try {
const sys = wx.getSystemInfoSync()
@@ -764,6 +772,7 @@ Page({
// 点击菜单
handleMenuTap(e) {
const id = e.currentTarget.dataset.id
trackClick('my', 'nav_click', id || '菜单')
if (!this.data.isLoggedIn) {
this.showLogin()
@@ -787,6 +796,7 @@ Page({
// 跳转到阅读页(优先传 mid与分享逻辑一致
goToRead(e) {
const id = e.currentTarget.dataset.id
trackClick('my', 'card_click', id || '章节')
const mid = e.currentTarget.dataset.mid || app.getSectionMid(id)
const q = mid ? `mid=${mid}` : `id=${id}`
wx.navigateTo({ url: `/pages/read/read?${q}` })
@@ -794,16 +804,19 @@ Page({
// 跳转到目录
goToChapters() {
trackClick('my', 'nav_click', '已读章节')
wx.switchTab({ url: '/pages/chapters/chapters' })
},
// 跳转到匹配
goToMatch() {
trackClick('my', 'nav_click', '匹配伙伴')
wx.switchTab({ url: '/pages/match/match' })
},
// 跳转到推广中心(需登录)
goToReferral() {
trackClick('my', 'nav_click', '推广中心')
if (!this.data.isLoggedIn) {
this.showLogin()
return
@@ -919,18 +932,21 @@ Page({
},
goToVip() {
trackClick('my', 'btn_click', '会员中心')
if (!this.data.isLoggedIn) { this.showLogin(); return }
wx.navigateTo({ url: '/pages/vip/vip' })
},
// 进入个人资料编辑页stitch_soul
goToProfileEdit() {
trackClick('my', 'nav_click', '资料编辑')
if (!this.data.isLoggedIn) { this.showLogin(); return }
wx.navigateTo({ url: '/pages/profile-edit/profile-edit' })
},
// 进入个人资料展示页enhanced_professional_profile展示页内可再进编辑
goToProfileShow() {
trackClick('my', 'btn_click', '编辑')
if (!this.data.isLoggedIn) { this.showLogin(); return }
wx.navigateTo({ url: '/pages/profile-show/profile-show' })
},

View File

@@ -39,10 +39,10 @@
<image class="profile-edit-icon" src="/assets/icons/edit-gray.svg" mode="aspectFit"/>
<text class="profile-edit-text">编辑</text>
</view>
<view class="become-member-btn {{isVip ? 'become-member-vip' : ''}}" bindtap="goToVip">{{isVip ? '会员中心' : '成为会员'}}</view>
<view class="become-member-btn {{isVip ? 'become-member-vip' : ''}}" wx:if="{{!auditMode}}" bindtap="goToVip">{{isVip ? '会员中心' : '成为会员'}}</view>
</view>
</view>
<view class="vip-tags">
<view class="vip-tags" wx:if="{{!auditMode}}">
<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>
@@ -63,7 +63,7 @@
<text class="profile-stat-val">{{earnings === '-' ? '--' : earnings}}</text>
<text class="profile-stat-label">我的收益</text>
</view>
<view class="profile-stat" bindtap="handleMenuTap" data-id="wallet">
<view class="profile-stat" wx:if="{{!auditMode}}" bindtap="handleMenuTap" data-id="wallet">
<text class="profile-stat-val">{{walletBalanceText}}</text>
<text class="profile-stat-label">我的余额</text>
</view>
@@ -73,8 +73,8 @@
<!-- 已登录:内容区 -->
<view class="main-content" wx:if="{{isLoggedIn}}">
<!-- 一键收款(仅在有待确认收款时显示) -->
<view class="card receive-card" wx:if="{{pendingConfirmList.length > 0}}">
<!-- 一键收款(仅在有待确认收款时显示;审核模式隐藏 -->
<view class="card receive-card" wx:if="{{pendingConfirmList.length > 0 && !auditMode}}">
<view class="receive-top">
<view class="receive-left">
<view class="receive-title-row">
@@ -253,4 +253,5 @@
</view>
<view class="bottom-space"></view>
<env-switch />
</view>

View File

@@ -77,20 +77,29 @@ Page({
// 余额(用于余额支付)
walletBalance: 0,
// 审核模式:隐藏购买按钮
auditMode: false,
},
onShow() {
this.setData({ auditMode: app.globalData.auditMode || false })
},
async onLoad(options) {
wx.showShareMenu({ menus: ['shareAppMessage', 'shareTimeline'] })
// 预加载 linkTags、linkedMiniprograms供 onLinkTagTap 用密钥查 appId
if (!app.globalData.linkTagsConfig || !app.globalData.linkedMiniprograms) {
app.request({ url: '/api/miniprogram/config', silent: true }).then(cfg => {
if (cfg) {
if (Array.isArray(cfg.linkTags)) app.globalData.linkTagsConfig = cfg.linkTags
if (Array.isArray(cfg.linkedMiniprograms)) app.globalData.linkedMiniprograms = cfg.linkedMiniprograms
}
}).catch(() => {})
}
// 预加载 configlinkTags、auditMode 等(阅读页直接进入时需主动拉取最新审核状态
app.request({ url: '/api/miniprogram/config', silent: true }).then(cfg => {
if (cfg) {
if (Array.isArray(cfg.linkTags)) app.globalData.linkTagsConfig = cfg.linkTags
if (Array.isArray(cfg.linkedMiniprograms)) app.globalData.linkedMiniprograms = cfg.linkedMiniprograms
const mp = (cfg && cfg.mpConfig) || {}
const auditMode = !!mp.auditMode
app.globalData.auditMode = auditMode
if (typeof this.setData === 'function') this.setData({ auditMode })
}
}).catch(() => {})
// 支持 scene扫码、mid、id、ref、gift代付
const sceneStr = (options && options.scene) || ''
@@ -749,6 +758,33 @@ Page({
})
},
// 右下角悬浮按钮:分享到朋友圈(复制文案 + 引导点右上角)
shareToMoments() {
const title = this.data.section?.title || this.data.chapterTitle || '好文推荐'
const raw = (this.data.content || '')
.replace(/<[^>]+>/g, '\n')
.replace(/&nbsp;/g, ' ')
.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').replace(/&quot;/g, '"')
.replace(/[#@]\S+/g, '')
const sentences = raw.split(/[。!?\n]+/).map(s => s.trim()).filter(s => s.length > 4)
const picked = sentences.slice(0, 5)
const copyText = picked.length > 0 ? title + '\n\n' + picked.join('\n\n') : `🔥 刚看完这篇《${title}》,推荐给你!\n\n#Soul创业派对 #真实商业故事`
wx.setClipboardData({
data: copyText,
success: () => {
wx.showModal({
title: '文案已复制',
content: '请点击右上角「···」菜单,选择「分享到朋友圈」即可发布',
showCancel: false,
confirmText: '知道了'
})
},
fail: () => {
wx.showToast({ title: '复制失败,请手动复制', icon: 'none' })
}
})
},
// 分享到朋友圈:带文章标题,过长时截断
onShareTimeline() {
const { section, sectionId, sectionMid, chapterTitle } = this.data

View File

@@ -93,11 +93,14 @@
<text class="action-icon-small">🖼️</text>
<text class="action-text-small">生成海报</text>
</view>
<view class="action-btn-inline btn-gift-inline" bindtap="showGiftShareModal" wx:if="{{isLoggedIn}}">
<view class="action-btn-inline btn-gift-inline" bindtap="showGiftShareModal" wx:if="{{isLoggedIn && !auditMode}}">
<text class="action-icon-small">🎁</text>
<text class="action-text-small">代付分享</text>
</view>
</view>
<view class="share-tip-inline" wx:if="{{!auditMode}}">
<text class="share-tip-text">分享后好友购买,你可获得 90% 收益</text>
</view>
</view>
</view>
@@ -169,8 +172,8 @@
<text class="paywall-title">解锁完整内容</text>
<text class="paywall-desc">已阅读50%,购买后继续阅读</text>
<!-- 购买选项 -->
<view class="purchase-options">
<!-- 购买选项(审核模式隐藏) -->
<view class="purchase-options" wx:if="{{!auditMode}}">
<!-- 购买本章 - 直接调起支付 -->
<view class="purchase-btn purchase-section" bindtap="handlePurchaseSection">
<text class="btn-label">购买本章</text>
@@ -189,10 +192,11 @@
</view>
</view>
</view>
<view class="paywall-audit-tip" wx:if="{{auditMode}}">审核中,暂不支持购买</view>
<text class="paywall-tip">分享给好友一起学习,还能赚取佣金</text>
<!-- 代付分享:让好友帮我买 -->
<view class="gift-share-row" bindtap="showGiftShareModal" wx:if="{{isLoggedIn}}">
<text class="paywall-tip" wx:if="{{!auditMode}}">分享给好友一起学习,还能赚取佣金</text>
<!-- 代付分享:让好友帮我买(审核模式隐藏) -->
<view class="gift-share-row" bindtap="showGiftShareModal" wx:if="{{isLoggedIn && !auditMode}}">
<text class="gift-share-icon">🎁</text>
<text class="gift-share-text">找好友代付</text>
</view>
@@ -306,8 +310,9 @@
</view>
</view>
<!-- 右下角悬浮分享按钮 -->
<button class="fab-share" open-type="share">
<image class="fab-icon" src="/assets/icons/share.svg" mode="aspectFit"></image>
</button>
<!-- 右下角悬浮按钮 - 分享到朋友圈 -->
<view class="fab-share" bindtap="shareToMoments">
<text class="fab-moments-icon">🌐</text>
</view>
<env-switch />
</view>

View File

@@ -336,6 +336,12 @@
text-align: center;
display: block;
}
.paywall-audit-tip {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.5);
text-align: center;
padding: 24rpx 0;
}
/* ===== 章节导航 ===== */
.chapter-nav {
@@ -475,6 +481,15 @@
font-weight: 500;
}
.share-tip-inline {
margin-top: 16rpx;
text-align: center;
}
.share-tip-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.5);
}
/* ===== 推广提示区 ===== */
.promo-section {
margin-top: 32rpx;
@@ -1051,3 +1066,8 @@
display: block;
}
.fab-moments-icon {
font-size: 44rpx;
line-height: 1;
}

View File

@@ -8,6 +8,7 @@
* - 收益统计90%归分发者)
*/
const app = getApp()
const { trackClick } = require('../../utils/trackClick')
Page({
data: {
@@ -258,6 +259,7 @@ Page({
// 切换Tab
switchTab(e) {
const tab = e.currentTarget.dataset.tab
trackClick('referral', 'tab_click', tab || '绑定列表')
let currentBindings = []
if (tab === 'active') {
@@ -278,6 +280,7 @@ Page({
// 复制邀请链接
copyLink() {
trackClick('referral', 'btn_click', '复制链接')
const link = `https://soul.quwanzhi.com/?ref=${this.data.referralCode}`
wx.setClipboardData({
data: link,
@@ -287,6 +290,7 @@ Page({
// 分享到朋友圈 - 1:1 迁移 Next.js 的 handleShareToWechat
shareToWechat() {
trackClick('referral', 'btn_click', '分享朋友圈')
const { referralCode } = this.data
const referralLink = `https://soul.quwanzhi.com/?ref=${referralCode}`
@@ -314,6 +318,7 @@ Page({
// 更多分享方式 - 1:1 迁移 Next.js 的 handleShare
handleMoreShare() {
trackClick('referral', 'btn_click', '更多分享')
const { referralCode } = this.data
const referralLink = `https://soul.quwanzhi.com/?ref=${referralCode}`
@@ -334,6 +339,7 @@ Page({
// 生成推广海报 - 1:1 对齐 Next.js 设计
async generatePoster() {
trackClick('referral', 'btn_click', '生成海报')
wx.showLoading({ title: '生成中...', mask: true })
this.setData({ showPosterModal: true, isGeneratingPoster: true })
@@ -624,6 +630,7 @@ Page({
// 提现 - 直接到微信零钱
async handleWithdraw() {
trackClick('referral', 'btn_click', '申请提现')
const availableEarnings = this.data.availableEarningsNum || 0
const minWithdrawAmount = this.data.minWithdrawAmount || 10
const hasWechatId = this.data.hasWechatId
@@ -670,6 +677,7 @@ Page({
// 跳转提现记录页
goToWithdrawRecords() {
trackClick('referral', 'nav_click', '提现记录')
wx.navigateTo({ url: '/pages/withdraw-records/withdraw-records' })
},

View File

@@ -329,4 +329,5 @@
</view>
</view>
</view>
<env-switch />
</view>

View File

@@ -3,6 +3,7 @@
* 搜索章节标题和内容
*/
const app = getApp()
const { trackClick } = require('../../utils/trackClick')
Page({
data: {
@@ -71,6 +72,7 @@ Page({
// 点击热门关键词
onHotKeyword(e) {
const keyword = e.currentTarget.dataset.keyword
trackClick('search', 'tab_click', keyword || '关键词')
this.setData({ keyword })
this.doSearch()
},
@@ -78,6 +80,7 @@ Page({
// 执行搜索
async doSearch() {
const { keyword } = this.data
if (keyword && keyword.trim().length >= 1) trackClick('search', 'btn_click', '搜索_' + keyword.trim())
if (!keyword || keyword.trim().length < 1) {
wx.showToast({ title: '请输入搜索关键词', icon: 'none' })
return
@@ -108,6 +111,7 @@ Page({
// 跳转阅读(优先传 mid与分享逻辑一致
goToRead(e) {
const id = e.currentTarget.dataset.id
trackClick('search', 'card_click', id || '章节')
const mid = e.currentTarget.dataset.mid || app.getSectionMid(id)
const q = mid ? `mid=${mid}` : `id=${id}`
wx.navigateTo({ url: `/pages/read/read?${q}` })

View File

@@ -112,4 +112,5 @@
</view>
</view>
</view>
<env-switch />
</view>

View File

@@ -177,4 +177,5 @@
</view>
</view>
</view>
<env-switch />
</view>

View File

@@ -1,5 +1,6 @@
import accessManager from '../../utils/chapterAccessManager'
const app = getApp()
const { trackClick } = require('../../utils/trackClick')
Page({
data: {
@@ -64,6 +65,7 @@ Page({
},
async handlePurchase() {
trackClick('vip', 'btn_click', '开通VIP')
let userId = app.globalData.userInfo?.id
let openId = app.globalData.openId || app.globalData.userInfo?.open_id
if (!userId || !openId) {

View File

@@ -52,4 +52,5 @@
</view>
<view class="bottom-space"></view>
<env-switch />
</view>

View File

@@ -10,14 +10,22 @@ Page({
loading: true,
rechargeAmounts: [10, 30, 50, 100],
selectedAmount: 30,
auditMode: false,
},
onLoad() {
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 44 })
this.setData({
statusBarHeight: app.globalData.statusBarHeight || 44,
auditMode: app.globalData.auditMode || false,
})
this.loadBalance()
this.loadTransactions()
},
onShow() {
this.setData({ auditMode: app.globalData.auditMode || false })
},
async loadBalance() {
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) return
const userId = app.globalData.userInfo.id

View File

@@ -20,7 +20,7 @@
</view>
</view>
<view class="section">
<view class="section" wx:if="{{!auditMode}}">
<view class="section-head">
<text class="section-title">选择充值金额</text>
<text class="section-note">当前已选 ¥{{selectedAmount}}</text>
@@ -44,9 +44,12 @@
</view>
</view>
<view class="action-row">
<view class="action-row" wx:if="{{!auditMode}}">
<view class="btn btn-recharge" bindtap="handleRecharge">充值</view>
</view>
<view class="action-row" wx:elif="{{auditMode}}">
<view class="audit-tip">审核中,暂不支持充值</view>
</view>
<view class="section">
<view class="section-head">
@@ -75,4 +78,5 @@
</view>
<view class="bottom-space"></view>
<env-switch />
</view>

View File

@@ -76,6 +76,12 @@
line-height: 1.7;
color: rgba(255, 255, 255, 0.58);
}
.audit-tip {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.5);
text-align: center;
padding: 24rpx;
}
.balance-skeleton {
padding: 40rpx 0;
}