feat: 完善后台管理+搜索功能+分销系统

主要更新:
- 后台菜单精简(9项→6项)
- 新增搜索功能(敏感信息过滤)
- 分销绑定和提现系统完善
- 数据库初始化API(自动修复表结构)
- 用户管理:显示绑定关系详情
- 小程序:上下章导航优化、匹配页面重构
- 修复hydration和数据类型问题
This commit is contained in:
卡若
2026-01-25 19:37:59 +08:00
parent 65d2831a45
commit 4dd2f9f4a7
49 changed files with 5921 additions and 636 deletions

View File

@@ -1,5 +1,5 @@
/**
* Soul创业实验 - 小程序入口
* Soul创业派对 - 小程序入口
* 开发: 卡若
*/
@@ -27,6 +27,9 @@ App({
purchasedSections: [],
hasFullBook: false,
// 推荐绑定
pendingReferralCode: null, // 待绑定的推荐码
// 主题配置
theme: {
brandColor: '#00CED1',
@@ -45,7 +48,7 @@ App({
currentTab: 0
},
onLaunch() {
onLaunch(options) {
// 获取系统信息
this.getSystemInfo()
@@ -57,6 +60,75 @@ App({
// 检查更新
this.checkUpdate()
// 处理分享参数(推荐码绑定)
this.handleReferralCode(options)
},
// 小程序显示时也检查分享参数
onShow(options) {
this.handleReferralCode(options)
},
// 处理推荐码绑定
handleReferralCode(options) {
const query = options?.query || {}
const refCode = query.ref || query.referralCode
if (refCode) {
console.log('[App] 检测到推荐码:', refCode)
// 检查是否已经绑定过
const boundRef = wx.getStorageSync('boundReferralCode')
if (boundRef && boundRef !== refCode) {
console.log('[App] 已绑定过其他推荐码,跳过')
return
}
// 保存待绑定的推荐码
this.globalData.pendingReferralCode = refCode
wx.setStorageSync('pendingReferralCode', refCode)
// 如果已登录,立即绑定
if (this.globalData.isLoggedIn && this.globalData.userInfo) {
this.bindReferralCode(refCode)
}
}
},
// 绑定推荐码到用户
async bindReferralCode(refCode) {
try {
const userId = this.globalData.userInfo?.id
if (!userId || !refCode) return
// 检查是否已绑定
const boundRef = wx.getStorageSync('boundReferralCode')
if (boundRef) {
console.log('[App] 已绑定推荐码,跳过')
return
}
console.log('[App] 绑定推荐码:', refCode, '到用户:', userId)
// 调用API绑定推荐关系
const res = await this.request('/api/referral/bind', {
method: 'POST',
data: {
userId,
referralCode: refCode
}
})
if (res.success) {
console.log('[App] 推荐码绑定成功')
wx.setStorageSync('boundReferralCode', refCode)
this.globalData.pendingReferralCode = null
wx.removeStorageSync('pendingReferralCode')
}
} catch (e) {
console.error('[App] 绑定推荐码失败:', e)
}
},
// 获取系统信息
@@ -267,8 +339,8 @@ App({
mockLogin() {
const mockUser = {
id: 'user_' + Date.now(),
nickname: '卡若',
phone: '15880802661',
nickname: '访客用户',
phone: '',
avatar: '',
referralCode: 'SOUL' + Date.now().toString(36).toUpperCase().slice(-6),
purchasedSections: [],

View File

@@ -8,12 +8,13 @@
"pages/about/about",
"pages/referral/referral",
"pages/purchases/purchases",
"pages/settings/settings"
"pages/settings/settings",
"pages/search/search"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#000000",
"navigationBarTitleText": "Soul创业实验",
"navigationBarTitleText": "Soul创业派对",
"navigationBarTextStyle": "white",
"backgroundColor": "#000000",
"navigationStyle": "custom"

View File

@@ -1,5 +1,5 @@
/**
* Soul创业实验 - 关于作者页
* Soul创业派对 - 关于作者页
* 开发: 卡若
*/
const app = getApp()
@@ -17,10 +17,8 @@ Page({
{ label: '连续直播', value: '365天' },
{ label: '派对分享', value: '1000+' }
],
contact: {
wechat: '28533368',
phone: '15880802661'
},
// 联系方式已移至后台配置
contact: null,
highlights: [
'5年私域运营经验',
'帮助100+品牌从0到1增长',
@@ -67,19 +65,13 @@ Page({
}
},
// 复制微信号
// 联系方式功能已禁用
copyWechat() {
wx.setClipboardData({
data: this.data.author.contact.wechat,
success: () => wx.showToast({ title: '微信号已复制', icon: 'success' })
})
wx.showToast({ title: '请在派对房联系作者', icon: 'none' })
},
// 拨打电话
callPhone() {
wx.makePhoneCall({
phoneNumber: this.data.author.contact.phone
})
wx.showToast({ title: '请在派对房联系作者', icon: 'none' })
},
// 返回

View File

@@ -57,24 +57,18 @@
</view>
</view>
<!-- 联系方式 -->
<!-- 联系方式 - 引导到Soul派对房 -->
<view class="contact-card">
<text class="card-title">联系作者</text>
<view class="contact-item" bindtap="copyWechat">
<text class="contact-icon">💬</text>
<view class="contact-item">
<text class="contact-icon">🎉</text>
<view class="contact-info">
<text class="contact-label">微信</text>
<text class="contact-value">{{author.contact.wechat}}</text>
<text class="contact-label">Soul派对房</text>
<text class="contact-value">每天早上6-9点开播</text>
</view>
<text class="contact-btn">复制</text>
</view>
<view class="contact-item" bindtap="callPhone">
<text class="contact-icon">📱</text>
<view class="contact-info">
<text class="contact-label">电话</text>
<text class="contact-value">{{author.contact.phone}}</text>
</view>
<text class="contact-btn">拨打</text>
<view class="contact-tip">
<text>在Soul App搜索"创业实验"或"卡若",加入派对房直接交流</text>
</view>
</view>
</view>

View File

@@ -1,5 +1,5 @@
/**
* Soul创业实验 - 目录页
* Soul创业派对 - 目录页
* 开发: 卡若
* 技术支持: 存客宝
* 数据: 完整真实文章标题

View File

@@ -1,5 +1,5 @@
/**
* Soul创业实验 - 首页
* Soul创业派对 - 首页
* 开发: 卡若
* 技术支持: 存客宝
*/
@@ -28,12 +28,9 @@ Page({
{ id: '8.1', title: '流量杠杆:抖音、Soul、飞书', tag: '推荐', tagClass: 'tag-purple', part: '真实的赚钱' }
],
// 最新章节
latestSection: {
id: '9.14',
title: '大健康私域一个月150万的70后',
part: '真实的赚钱'
},
// 最新章节(动态计算)
latestSection: null,
latestLabel: '最新更新',
// 内容概览
partsList: [
@@ -48,13 +45,19 @@ Page({
loading: true
},
onLoad() {
onLoad(options) {
// 获取系统信息
this.setData({
statusBarHeight: app.globalData.statusBarHeight,
navBarHeight: app.globalData.navBarHeight
})
// 处理分享参数(推荐码绑定)
if (options && options.ref) {
console.log('[Index] 检测到推荐码:', options.ref)
app.handleReferralCode({ query: options })
}
// 初始化数据
this.initData()
},
@@ -76,6 +79,8 @@ Page({
try {
// 获取书籍数据
await this.loadBookData()
// 计算推荐章节
this.computeLatestSection()
} catch (e) {
console.error('初始化失败:', e)
} finally {
@@ -83,6 +88,50 @@ Page({
}
},
// 计算推荐章节根据用户ID随机、优先未付款
computeLatestSection() {
const { hasFullBook, purchasedSections } = app.globalData
const userId = app.globalData.userInfo?.id || wx.getStorageSync('userId') || 'guest'
// 所有章节列表
const allSections = [
{ id: '9.14', title: '大健康私域一个月150万的70后', part: '真实的赚钱' },
{ id: '9.13', title: 'AI工具推广一个隐藏的高利润赛道', part: '真实的赚钱' },
{ id: '9.12', title: '美业整合:一个人的公司如何月入十万', part: '真实的赚钱' },
{ id: '8.6', title: '云阿米巴:分不属于自己的钱', part: '真实的赚钱' },
{ id: '8.1', title: '流量杠杆:抖音、Soul、飞书', part: '真实的赚钱' },
{ id: '3.1', title: '3000万流水如何跑出来', part: '真实的行业' },
{ id: '5.1', title: '拍卖行抱朴一天240万的摇号生意', part: '真实的行业' },
{ id: '4.1', title: '旅游号:30天10万粉的真实逻辑', part: '真实的行业' }
]
// 用户ID生成的随机种子同一用户每天看到的不同
const today = new Date().toISOString().split('T')[0]
const seed = (userId + today).split('').reduce((a, b) => a + b.charCodeAt(0), 0)
// 筛选未付款章节
let candidates = allSections
if (!hasFullBook) {
const purchased = purchasedSections || []
const unpurchased = allSections.filter(s => !purchased.includes(s.id))
if (unpurchased.length > 0) {
candidates = unpurchased
}
}
// 根据种子选择章节
const index = seed % candidates.length
const selected = candidates[index]
// 设置标签(如果有新增章节显示"最新更新",否则显示"推荐阅读"
const label = candidates === allSections ? '推荐阅读' : '为你推荐'
this.setData({
latestSection: selected,
latestLabel: label
})
},
// 加载书籍数据
async loadBookData() {
try {
@@ -114,6 +163,11 @@ Page({
wx.switchTab({ url: '/pages/chapters/chapters' })
},
// 跳转到搜索页
goToSearch() {
wx.navigateTo({ url: '/pages/search/search' })
},
// 跳转到阅读页
goToRead(e) {
const id = e.currentTarget.dataset.id

View File

@@ -1,5 +1,5 @@
<!--pages/index/index.wxml-->
<!--Soul创业实验 - 首页 1:1还原Web版本-->
<!--Soul创业派对 - 首页 1:1还原Web版本-->
<view class="page page-transition">
<!-- 自定义导航栏占位 -->
<view class="nav-placeholder" style="height: {{statusBarHeight + 44}}px;"></view>
@@ -14,7 +14,7 @@
<view class="logo-info">
<view class="logo-title">
<text class="text-white">Soul</text>
<text class="brand-color">创业实验</text>
<text class="brand-color">创业派对</text>
</view>
<text class="logo-subtitle">来自派对房的真实故事</text>
</view>
@@ -25,12 +25,12 @@
</view>
<!-- 搜索栏 -->
<view class="search-bar" bindtap="goToChapters">
<view class="search-bar" bindtap="goToSearch">
<view class="search-icon">
<view class="search-circle"></view>
<view class="search-handle"></view>
</view>
<text class="search-placeholder">搜索章节...</text>
<text class="search-placeholder">搜索章节标题或内容...</text>
</view>
</view>

View File

@@ -1,5 +1,5 @@
/**
* Soul创业实验 - 找伙伴页
* Soul创业派对 - 找伙伴页
* 按H5网页端完全重构
* 开发: 卡若
*/
@@ -55,7 +55,11 @@ Page({
needBindFirst: false,
// 解锁弹窗
showUnlockModal: false
showUnlockModal: false,
// 匹配价格(可配置)
matchPrice: 1,
extraMatches: 0
},
onLoad() {
@@ -86,15 +90,18 @@ Page({
// 更新全局配置
MATCH_TYPES = res.data.matchTypes || MATCH_TYPES
FREE_MATCH_LIMIT = res.data.freeMatchLimit || FREE_MATCH_LIMIT
const matchPrice = res.data.matchPrice || 1
this.setData({
matchTypes: MATCH_TYPES,
totalMatchesAllowed: FREE_MATCH_LIMIT
totalMatchesAllowed: FREE_MATCH_LIMIT,
matchPrice: matchPrice
})
console.log('[Match] 加载匹配配置成功:', {
types: MATCH_TYPES.length,
freeLimit: FREE_MATCH_LIMIT
freeLimit: FREE_MATCH_LIMIT,
price: matchPrice
})
}
} catch (e) {
@@ -240,7 +247,7 @@ Page({
showPurchaseTip() {
wx.showModal({
title: '需要购买书籍',
content: '购买《一场Soul创业实验》后即可使用匹配功能仅需9.9元',
content: '购买《Soul创业派对》后即可使用匹配功能仅需9.9元',
confirmText: '去购买',
success: (res) => {
if (res.confirm) {
@@ -334,7 +341,7 @@ Page({
concept: concepts[index % concepts.length],
wechat: wechats[index % wechats.length],
commonInterests: [
{ icon: '📚', text: '都在读《创业实验》' },
{ icon: '📚', text: '都在读《创业派对》' },
{ icon: '💼', text: '对私域运营感兴趣' },
{ icon: '🎯', text: '相似的创业方向' }
]

View File

@@ -1,5 +1,5 @@
<!--pages/match/match.wxml-->
<!--Soul创业实验 - 找伙伴页 按H5网页端完全重构-->
<!--Soul创业派对 - 找伙伴页 按H5网页端完全重构-->
<view class="page">
<!-- 自定义导航栏 -->
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
@@ -11,6 +11,9 @@
</view>
</view>
<view class="nav-placeholder" style="height: {{statusBarHeight + 44}}px;"></view>
<!-- 顶部留白,让内容往下 -->
<view style="height: 30rpx;"></view>
<!-- 匹配提示条 - 简化显示 -->
<view class="match-tip-bar" wx:if="{{matchesRemaining <= 0 && !hasFullBook}}">
@@ -73,21 +76,38 @@
</view>
</block>
<!-- 匹配中状态 -->
<!-- 匹配中状态 - 美化特效 -->
<block wx:if="{{isMatching}}">
<view class="matching-state">
<view class="matching-animation">
<view class="matching-ring"></view>
<view class="matching-center">
<text class="matching-icon">👥</text>
<view class="matching-animation-v2">
<!-- 外层旋转光环 -->
<view class="matching-outer-ring"></view>
<!-- 中层脉冲环 -->
<view class="matching-pulse-ring"></view>
<!-- 内层球体 -->
<view class="matching-core">
<view class="matching-core-inner">
<text class="matching-icon-v2">🔍</text>
</view>
</view>
<view class="ripple ripple-1"></view>
<view class="ripple ripple-2"></view>
<view class="ripple ripple-3"></view>
<!-- 粒子效果 -->
<view class="particle particle-1"></view>
<view class="particle particle-2">💫</view>
<view class="particle particle-3">⭐</view>
<view class="particle particle-4">🌟</view>
<!-- 扩散波纹 -->
<view class="ripple-v2 ripple-v2-1"></view>
<view class="ripple-v2 ripple-v2-2"></view>
<view class="ripple-v2 ripple-v2-3"></view>
</view>
<text class="matching-title">正在匹配{{currentTypeLabel}}...</text>
<text class="matching-count">已匹配 {{matchAttempts}} 次</text>
<view class="cancel-btn" bindtap="cancelMatch">取消匹配</view>
<text class="matching-title-v2">正在匹配{{currentTypeLabel}}...</text>
<text class="matching-subtitle-v2">正在从 {{matchAttempts * 127 + 89}} 位创业者中为你寻找</text>
<view class="matching-tips">
<text class="tip-item" wx:if="{{matchAttempts >= 1}}">✓ 分析兴趣标签</text>
<text class="tip-item" wx:if="{{matchAttempts >= 2}}">✓ 匹配创业方向</text>
<text class="tip-item" wx:if="{{matchAttempts >= 3}}">✓ 筛选优质伙伴</text>
</view>
<view class="cancel-btn-v2" bindtap="cancelMatch">取消</view>
</view>
</block>
@@ -241,7 +261,7 @@
<view class="unlock-info">
<view class="info-row">
<text class="info-label">单价</text>
<text class="info-value text-brand">¥1 / 次</text>
<text class="info-value text-brand">¥{{matchPrice || 1}} / 次</text>
</view>
<view class="info-row">
<text class="info-label">已购买</text>
@@ -250,7 +270,7 @@
</view>
<view class="unlock-buttons">
<view class="btn-gold" bindtap="buyMatchCount">立即购买 ¥1</view>
<view class="btn-gold" bindtap="buyMatchCount">立即购买 ¥{{matchPrice || 1}}</view>
<view class="btn-ghost" bindtap="closeUnlockModal">明天再来</view>
</view>
</view>

View File

@@ -983,3 +983,195 @@
.bottom-space {
height: 40rpx;
}
/* ===== 新版匹配动画 V2 ===== */
.matching-animation-v2 {
position: relative;
width: 440rpx;
height: 440rpx;
margin: 0 auto 48rpx;
}
/* 外层旋转光环 */
.matching-outer-ring {
position: absolute;
inset: -20rpx;
border-radius: 50%;
background: conic-gradient(
from 0deg,
transparent 0deg,
#00CED1 60deg,
#7B61FF 120deg,
#E91E63 180deg,
#FFD700 240deg,
#00CED1 300deg,
transparent 360deg
);
animation: rotateRingV2 2s linear infinite;
opacity: 0.8;
}
.matching-outer-ring::before {
content: '';
position: absolute;
inset: 8rpx;
border-radius: 50%;
background: #000;
}
@keyframes rotateRingV2 {
to { transform: rotate(360deg); }
}
/* 中层脉冲环 */
.matching-pulse-ring {
position: absolute;
inset: 20rpx;
border-radius: 50%;
border: 4rpx solid rgba(0, 206, 209, 0.5);
animation: pulseRingV2 1.5s ease-in-out infinite;
}
@keyframes pulseRingV2 {
0%, 100% { transform: scale(1); opacity: 0.5; }
50% { transform: scale(1.1); opacity: 1; }
}
/* 内层核心球体 */
.matching-core {
position: absolute;
inset: 60rpx;
border-radius: 50%;
background: linear-gradient(135deg, #1a2a4a 0%, #0a1628 50%, #16213e 100%);
box-shadow:
0 0 60rpx rgba(0, 206, 209, 0.4),
0 0 120rpx rgba(123, 97, 255, 0.2),
inset 0 0 80rpx rgba(0, 206, 209, 0.1);
display: flex;
align-items: center;
justify-content: center;
animation: floatCoreV2 2s ease-in-out infinite;
}
.matching-core-inner {
width: 160rpx;
height: 160rpx;
border-radius: 50%;
background: radial-gradient(circle, rgba(0, 206, 209, 0.3) 0%, transparent 70%);
display: flex;
align-items: center;
justify-content: center;
}
@keyframes floatCoreV2 {
0%, 100% { transform: translateY(0) scale(1); }
50% { transform: translateY(-10rpx) scale(1.02); }
}
.matching-icon-v2 {
font-size: 80rpx;
animation: searchIconV2 1s ease-in-out infinite;
}
@keyframes searchIconV2 {
0%, 100% { transform: rotate(-15deg); }
50% { transform: rotate(15deg); }
}
/* 粒子效果 */
.particle {
position: absolute;
font-size: 32rpx;
animation: floatParticle 3s ease-in-out infinite;
opacity: 0.8;
}
.particle-1 { top: 10%; left: 15%; animation-delay: 0s; }
.particle-2 { top: 20%; right: 10%; animation-delay: 0.5s; }
.particle-3 { bottom: 20%; left: 10%; animation-delay: 1s; }
.particle-4 { bottom: 15%; right: 15%; animation-delay: 1.5s; }
@keyframes floatParticle {
0%, 100% { transform: translateY(0) rotate(0deg); opacity: 0.4; }
50% { transform: translateY(-20rpx) rotate(180deg); opacity: 1; }
}
/* 扩散波纹 V2 */
.ripple-v2 {
position: absolute;
inset: 40rpx;
border-radius: 50%;
border: 3rpx solid;
border-color: rgba(0, 206, 209, 0.6);
animation: rippleExpandV2 2.5s ease-out infinite;
}
.ripple-v2-1 { animation-delay: 0s; }
.ripple-v2-2 { animation-delay: 0.8s; }
.ripple-v2-3 { animation-delay: 1.6s; }
@keyframes rippleExpandV2 {
0% { transform: scale(1); opacity: 0.8; }
100% { transform: scale(1.8); opacity: 0; }
}
/* 新版匹配文字 */
.matching-title-v2 {
display: block;
font-size: 38rpx;
font-weight: 700;
color: #ffffff;
text-align: center;
margin-bottom: 12rpx;
background: linear-gradient(90deg, #00CED1, #7B61FF, #00CED1);
background-size: 200% auto;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: shineText 2s linear infinite;
}
@keyframes shineText {
to { background-position: 200% center; }
}
.matching-subtitle-v2 {
display: block;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.5);
text-align: center;
margin-bottom: 32rpx;
}
.matching-tips {
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
margin-bottom: 40rpx;
}
.tip-item {
font-size: 26rpx;
color: #00CED1;
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
}
.tip-item:nth-child(1) { animation-delay: 0.5s; }
.tip-item:nth-child(2) { animation-delay: 1.5s; }
.tip-item:nth-child(3) { animation-delay: 2.5s; }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20rpx); }
to { opacity: 1; transform: translateY(0); }
}
.cancel-btn-v2 {
display: inline-block;
padding: 20rpx 60rpx;
background: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.6);
font-size: 28rpx;
border-radius: 40rpx;
border: 1rpx solid rgba(255, 255, 255, 0.2);
}

View File

@@ -1,5 +1,5 @@
/**
* Soul创业实验 - 我的页面
* Soul创业派对 - 我的页面
* 开发: 卡若
* 技术支持: 存客宝
*/

View File

@@ -1,5 +1,5 @@
/**
* Soul创业实验 - 阅读页
* Soul创业派对 - 阅读页
* 开发: 卡若
* 技术支持: 存客宝
*/
@@ -62,9 +62,11 @@ Page({
sectionId: id
})
// 保存推荐码
// 处理推荐码绑定
if (ref) {
console.log('[Read] 检测到推荐码:', ref)
wx.setStorageSync('referral_code', ref)
app.handleReferralCode({ query: { ref } })
}
this.initSection(id)
@@ -281,15 +283,33 @@ Page({
})
},
// 分享到微信
// 分享到微信 - 自动带分享人ID
onShareAppMessage() {
const { section, sectionId } = this.data
const userInfo = app.globalData.userInfo
const referralCode = userInfo?.referralCode || wx.getStorageSync('referralCode') || ''
// 分享标题优化
const shareTitle = section?.title
? `📚 ${section.title.length > 20 ? section.title.slice(0, 20) + '...' : section.title}`
: '📚 Soul创业派对 - 真实商业故事'
return {
title: shareTitle,
path: `/pages/read/read?id=${sectionId}${referralCode ? '&ref=' + referralCode : ''}`,
imageUrl: '/assets/share-cover.png' // 可配置分享封面图
}
},
// 分享到朋友圈
onShareTimeline() {
const { section, sectionId } = this.data
const userInfo = app.globalData.userInfo
const referralCode = userInfo?.referralCode || ''
return {
title: `📚 ${section?.title || '推荐阅读'}`,
path: `/pages/read/read?id=${sectionId}${referralCode ? '&ref=' + referralCode : ''}`
title: `${section?.title || 'Soul创业派对'} - 来自派对房的真实故事`,
query: `id=${sectionId}${referralCode ? '&ref=' + referralCode : ''}`
}
},

View File

@@ -1,5 +1,5 @@
<!--pages/read/read.wxml-->
<!--Soul创业实验 - 阅读页 1:1还原Web版本-->
<!--Soul创业派对 - 阅读页-->
<view class="page">
<!-- 阅读进度条 -->
<view class="progress-bar-fixed" style="top: {{statusBarHeight}}px;">
@@ -138,6 +138,36 @@
<text class="paywall-tip">邀请好友加入享90%推广收益</text>
</view>
<!-- 章节导航 - 付费内容也显示 -->
<view class="chapter-nav chapter-nav-locked">
<view class="nav-buttons">
<view
class="nav-btn nav-prev {{!prevSection ? 'nav-disabled' : ''}}"
bindtap="goToPrev"
wx:if="{{prevSection}}"
>
<text class="btn-label">上一篇</text>
<text class="btn-title">章节 {{prevSection.id}}</text>
</view>
<view class="nav-btn-placeholder" wx:else></view>
<view
class="nav-btn nav-next"
bindtap="goToNext"
wx:if="{{nextSection}}"
>
<text class="btn-label">下一篇</text>
<view class="btn-row">
<text class="btn-title">{{nextSection.title}}</text>
<text class="btn-arrow">→</text>
</view>
</view>
<view class="nav-btn nav-end" wx:else>
<text class="btn-end-text">已是最后一篇 🎉</text>
</view>
</view>
</view>
</view>
</view>
@@ -177,7 +207,7 @@
<view class="modal-content login-modal" catchtap="stopPropagation">
<view class="modal-close" bindtap="closeLoginModal">✕</view>
<view class="login-icon">🔐</view>
<text class="login-title">登录 Soul创业实验</text>
<text class="login-title">登录 Soul创业派对</text>
<text class="login-desc">登录后可购买章节、参与匹配、赚取佣金</text>
<button class="btn-wechat" bindtap="handleWechatLogin">

View File

@@ -1,5 +1,5 @@
/**
* Soul创业实验 - 分销中心页
* Soul创业派对 - 分销中心页
* 1:1还原Web版本
*/
const app = getApp()
@@ -140,7 +140,7 @@ Page({
// 分享到朋友圈
shareToMoments() {
const shareText = `🔥 发现一本超棒的创业实战书《一场Soul创业实验》!\n\n💡 62个真实商业案例从私域运营到资源整合干货满满\n\n🎁 通过我的链接购买立享5%优惠,我是 ${this.data.userInfo?.nickname || '卡若'} 推荐!\n\n👉 ${this.data.referralCode} 是我的专属邀请码\n\n#创业实验 #私域运营 #商业案例`
const shareText = `🔥 发现一本超棒的创业实战书《Soul创业派对》!\n\n💡 62个真实商业案例从私域运营到资源整合干货满满\n\n🎁 通过我的链接购买立享5%优惠,我是 ${this.data.userInfo?.nickname || '卡若'} 推荐!\n\n👉 ${this.data.referralCode} 是我的专属邀请码\n\n#创业派对 #私域运营 #商业案例`
wx.setClipboardData({
data: shareText,
@@ -155,14 +155,78 @@ Page({
})
},
// 提现
handleWithdraw() {
const earnings = parseFloat(this.data.earnings)
if (earnings < 10) {
// 提现 - 直接到微信零钱
async handleWithdraw() {
const pendingEarnings = parseFloat(this.data.pendingEarnings) || 0
if (pendingEarnings < 10) {
wx.showToast({ title: '满10元可提现', icon: 'none' })
return
}
wx.showToast({ title: '提现功能开发中', icon: 'none' })
// 确认提现
wx.showModal({
title: '确认提现',
content: `将提现 ¥${pendingEarnings.toFixed(2)} 到您的微信零钱`,
confirmText: '立即提现',
success: async (res) => {
if (res.confirm) {
await this.doWithdraw(pendingEarnings)
}
}
})
},
// 执行提现
async doWithdraw(amount) {
wx.showLoading({ title: '提现中...' })
try {
const userId = app.globalData.userInfo?.id
if (!userId) {
wx.hideLoading()
wx.showToast({ title: '请先登录', icon: 'none' })
return
}
const res = await app.request('/api/withdraw', {
method: 'POST',
data: { userId, amount }
})
wx.hideLoading()
if (res.success) {
wx.showModal({
title: '提现成功 🎉',
content: `¥${amount.toFixed(2)} 已到账您的微信零钱`,
showCancel: false,
confirmText: '好的'
})
// 刷新数据
this.initData()
} else {
if (res.needBind) {
wx.showModal({
title: '需要绑定微信',
content: '请先在设置中绑定微信账号后再提现',
confirmText: '去绑定',
success: (modalRes) => {
if (modalRes.confirm) {
wx.navigateTo({ url: '/pages/settings/settings' })
}
}
})
} else {
wx.showToast({ title: res.error || '提现失败', icon: 'none' })
}
}
} catch (e) {
wx.hideLoading()
console.error('[Referral] 提现失败:', e)
wx.showToast({ title: '提现失败,请重试', icon: 'none' })
}
},
// 显示通知
@@ -184,11 +248,20 @@ Page({
})
},
// 分享
// 分享 - 带推荐码
onShareAppMessage() {
return {
title: '📚 一场SOUL的创业实验场 - 来自派对房的真实商业故事',
path: `/pages/index/index?ref=${this.data.referralCode}`
title: '📚 Soul创业派对 - 来自派对房的真实商业故事',
path: `/pages/index/index?ref=${this.data.referralCode}`,
imageUrl: '/assets/share-cover.png'
}
},
// 分享到朋友圈
onShareTimeline() {
return {
title: `Soul创业派对 - 62个真实商业案例`,
query: `ref=${this.data.referralCode}`
}
},

View File

@@ -0,0 +1,87 @@
/**
* Soul创业派对 - 章节搜索页
* 搜索章节标题和内容
*/
const app = getApp()
Page({
data: {
statusBarHeight: 44,
keyword: '',
results: [],
loading: false,
searched: false,
total: 0,
// 热门搜索
hotKeywords: ['私域', '电商', '流量', '赚钱', '创业', 'Soul', '抖音']
},
onLoad() {
this.setData({
statusBarHeight: app.globalData.statusBarHeight || 44
})
},
// 输入关键词
onInput(e) {
this.setData({ keyword: e.detail.value })
},
// 清空搜索
clearSearch() {
this.setData({
keyword: '',
results: [],
searched: false,
total: 0
})
},
// 点击热门关键词
onHotKeyword(e) {
const keyword = e.currentTarget.dataset.keyword
this.setData({ keyword })
this.doSearch()
},
// 执行搜索
async doSearch() {
const { keyword } = this.data
if (!keyword || keyword.trim().length < 1) {
wx.showToast({ title: '请输入搜索关键词', icon: 'none' })
return
}
this.setData({ loading: true, searched: true })
try {
const res = await app.request(`/api/book/search?q=${encodeURIComponent(keyword.trim())}`)
if (res && res.success) {
this.setData({
results: res.results || [],
total: res.total || 0
})
} else {
this.setData({ results: [], total: 0 })
}
} catch (e) {
console.error('搜索失败:', e)
wx.showToast({ title: '搜索失败', icon: 'none' })
this.setData({ results: [], total: 0 })
} finally {
this.setData({ loading: false })
}
},
// 跳转阅读
goToRead(e) {
const id = e.currentTarget.dataset.id
wx.navigateTo({ url: `/pages/read/read?id=${id}` })
},
// 返回上一页
goBack() {
wx.navigateBack()
}
})

View File

@@ -0,0 +1,5 @@
{
"usingComponents": {},
"navigationStyle": "custom",
"navigationBarTitleText": "搜索"
}

View File

@@ -0,0 +1,92 @@
<!--pages/search/search.wxml-->
<!--章节搜索页-->
<view class="page">
<!-- 自定义导航栏 -->
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
<view class="nav-content">
<view class="back-btn" bindtap="goBack">
<text class="back-icon">←</text>
</view>
<view class="search-input-wrap">
<view class="search-icon-small">🔍</view>
<input
class="search-input"
placeholder="搜索章节标题或内容..."
value="{{keyword}}"
bindinput="onInput"
bindconfirm="doSearch"
confirm-type="search"
focus="{{true}}"
/>
<view class="clear-btn" wx:if="{{keyword}}" bindtap="clearSearch">×</view>
</view>
<view class="search-btn" bindtap="doSearch">搜索</view>
</view>
</view>
<!-- 主内容区 -->
<view class="main-content" style="padding-top: {{statusBarHeight + 56}}px;">
<!-- 热门搜索(未搜索时显示) -->
<view class="hot-section" wx:if="{{!searched}}">
<text class="section-title">热门搜索</text>
<view class="hot-tags">
<view
class="hot-tag"
wx:for="{{hotKeywords}}"
wx:key="*this"
bindtap="onHotKeyword"
data-keyword="{{item}}"
>{{item}}</view>
</view>
</view>
<!-- 搜索结果 -->
<view class="results-section" wx:if="{{searched}}">
<!-- 加载中 -->
<view class="loading-wrap" wx:if="{{loading}}">
<view class="loading-spinner"></view>
<text class="loading-text">搜索中...</text>
</view>
<!-- 结果列表 -->
<block wx:elif="{{results.length > 0}}">
<view class="results-header">
<text class="results-count">找到 {{total}} 个结果</text>
</view>
<view class="results-list">
<view
class="result-item"
wx:for="{{results}}"
wx:key="id"
bindtap="goToRead"
data-id="{{item.id}}"
>
<view class="result-header">
<text class="result-chapter">{{item.chapterLabel}}</text>
<view class="result-tags">
<text class="tag tag-match" wx:if="{{item.matchType === 'title'}}">标题匹配</text>
<text class="tag tag-match" wx:elif="{{item.matchType === 'content'}}">内容匹配</text>
<text class="tag tag-free" wx:if="{{item.isFree}}">免费</text>
</view>
</view>
<text class="result-title">{{item.title}}</text>
<text class="result-part">{{item.part}}</text>
<view class="result-content" wx:if="{{item.matchedContent}}">
<text class="content-preview">{{item.matchedContent}}</text>
</view>
<view class="result-arrow">→</view>
</view>
</view>
</block>
<!-- 无结果 -->
<view class="empty-wrap" wx:elif="{{!loading}}">
<text class="empty-icon">🔍</text>
<text class="empty-text">未找到相关章节</text>
<text class="empty-hint">换个关键词试试</text>
</view>
</view>
</view>
</view>

View File

@@ -0,0 +1,264 @@
/* 章节搜索页样式 */
.page {
min-height: 100vh;
background: linear-gradient(180deg, #0a0a0a 0%, #111111 100%);
}
/* 导航栏 */
.nav-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: rgba(10, 10, 10, 0.95);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
.nav-content {
display: flex;
align-items: center;
padding: 8rpx 24rpx;
height: 88rpx;
}
.back-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
font-size: 40rpx;
color: #00CED1;
}
.search-input-wrap {
flex: 1;
display: flex;
align-items: center;
background: rgba(255,255,255,0.08);
border-radius: 40rpx;
padding: 0 24rpx;
height: 64rpx;
margin: 0 16rpx;
}
.search-icon-small {
font-size: 28rpx;
margin-right: 12rpx;
}
.search-input {
flex: 1;
font-size: 28rpx;
color: #fff;
}
.search-input::placeholder {
color: rgba(255,255,255,0.4);
}
.clear-btn {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: rgba(255,255,255,0.5);
}
.search-btn {
font-size: 28rpx;
color: #00CED1;
padding: 0 16rpx;
}
/* 主内容 */
.main-content {
padding: 24rpx;
}
/* 热门搜索 */
.hot-section {
padding: 24rpx 0;
}
.section-title {
font-size: 28rpx;
color: rgba(255,255,255,0.6);
margin-bottom: 24rpx;
display: block;
}
.hot-tags {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.hot-tag {
background: rgba(0, 206, 209, 0.15);
color: #00CED1;
padding: 16rpx 32rpx;
border-radius: 32rpx;
font-size: 28rpx;
border: 1rpx solid rgba(0, 206, 209, 0.3);
}
/* 搜索结果 */
.results-section {
padding: 16rpx 0;
}
.results-header {
margin-bottom: 24rpx;
}
.results-count {
font-size: 26rpx;
color: rgba(255,255,255,0.5);
}
.results-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.result-item {
background: rgba(255,255,255,0.05);
border-radius: 24rpx;
padding: 28rpx;
position: relative;
border: 1rpx solid rgba(255,255,255,0.08);
}
.result-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16rpx;
}
.result-chapter {
font-size: 24rpx;
color: #00CED1;
font-weight: 500;
}
.result-tags {
display: flex;
gap: 12rpx;
}
.tag {
font-size: 20rpx;
padding: 6rpx 16rpx;
border-radius: 20rpx;
}
.tag-match {
background: rgba(147, 112, 219, 0.2);
color: #9370DB;
}
.tag-free {
background: rgba(76, 175, 80, 0.2);
color: #4CAF50;
}
.result-title {
font-size: 30rpx;
color: #fff;
font-weight: 500;
line-height: 1.5;
display: block;
margin-bottom: 8rpx;
}
.result-part {
font-size: 24rpx;
color: rgba(255,255,255,0.5);
display: block;
}
.result-content {
margin-top: 16rpx;
padding-top: 16rpx;
border-top: 1rpx solid rgba(255,255,255,0.1);
}
.content-preview {
font-size: 24rpx;
color: rgba(255,255,255,0.6);
line-height: 1.6;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.result-arrow {
position: absolute;
right: 28rpx;
top: 50%;
transform: translateY(-50%);
font-size: 32rpx;
color: rgba(255,255,255,0.3);
}
/* 加载状态 */
.loading-wrap {
display: flex;
flex-direction: column;
align-items: center;
padding: 100rpx 0;
}
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid rgba(0, 206, 209, 0.3);
border-top-color: #00CED1;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-text {
margin-top: 24rpx;
font-size: 28rpx;
color: rgba(255,255,255,0.5);
}
/* 空状态 */
.empty-wrap {
display: flex;
flex-direction: column;
align-items: center;
padding: 100rpx 0;
}
.empty-icon {
font-size: 80rpx;
margin-bottom: 24rpx;
}
.empty-text {
font-size: 32rpx;
color: rgba(255,255,255,0.6);
margin-bottom: 12rpx;
}
.empty-hint {
font-size: 26rpx;
color: rgba(255,255,255,0.4);
}

View File

@@ -1,5 +1,5 @@
/**
* Soul创业实验 - 设置页
* Soul创业派对 - 设置页
* 账号绑定功能
*/
const app = getApp()
@@ -115,7 +115,7 @@ Page({
return
}
// 保存绑定信息
// 保存绑定信息到本地
if (bindType === 'phone') {
wx.setStorageSync('user_phone', bindValue)
this.setData({ phoneNumber: bindValue })
@@ -127,9 +127,122 @@ Page({
this.setData({ alipayAccount: bindValue })
}
// 同步到服务器
this.syncProfileToServer()
this.setData({ showBindModal: false })
wx.showToast({ title: '绑定成功', icon: 'success' })
},
// 同步资料到服务器
async syncProfileToServer() {
try {
const userId = app.globalData.userInfo?.id
if (!userId) return
const res = await app.request('/api/user/profile', {
method: 'POST',
data: {
userId,
phone: this.data.phoneNumber || undefined,
wechatId: this.data.wechatId || undefined
}
})
if (res.success) {
console.log('[Settings] 资料同步成功')
// 更新本地用户信息
if (app.globalData.userInfo) {
app.globalData.userInfo.phone = this.data.phoneNumber
app.globalData.userInfo.wechatId = this.data.wechatId
wx.setStorageSync('userInfo', app.globalData.userInfo)
}
}
} catch (e) {
console.log('[Settings] 资料同步失败:', e)
}
},
// 获取微信头像(新版授权)
async getWechatAvatar() {
try {
const res = await wx.getUserProfile({
desc: '用于完善会员资料'
})
if (res.userInfo) {
const { nickName, avatarUrl } = res.userInfo
// 更新本地
this.setData({
userInfo: {
...this.data.userInfo,
nickname: nickName,
avatar: avatarUrl
}
})
// 同步到服务器
const userId = app.globalData.userInfo?.id
if (userId) {
await app.request('/api/user/profile', {
method: 'POST',
data: { userId, nickname: nickName, avatar: avatarUrl }
})
}
// 更新全局
if (app.globalData.userInfo) {
app.globalData.userInfo.nickname = nickName
app.globalData.userInfo.avatar = avatarUrl
wx.setStorageSync('userInfo', app.globalData.userInfo)
}
wx.showToast({ title: '头像更新成功', icon: 'success' })
}
} catch (e) {
console.log('[Settings] 获取头像失败:', e)
wx.showToast({ title: '获取头像失败', icon: 'none' })
}
},
// 获取微信手机号需要button组件配合
async getPhoneNumber(e) {
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
wx.showToast({ title: '授权失败', icon: 'none' })
return
}
try {
// 需要将code发送到服务器解密获取手机号
const code = e.detail.code
if (!code) {
wx.showToast({ title: '获取失败,请手动输入', icon: 'none' })
return
}
// 调用服务器解密手机号
const res = await app.request('/api/wechat/phone', {
method: 'POST',
data: { code }
})
if (res.success && res.phoneNumber) {
wx.setStorageSync('user_phone', res.phoneNumber)
this.setData({ phoneNumber: res.phoneNumber })
// 同步到服务器
this.syncProfileToServer()
wx.showToast({ title: '手机号绑定成功', icon: 'success' })
} else {
wx.showToast({ title: '获取失败,请手动输入', icon: 'none' })
}
} catch (e) {
console.log('[Settings] 获取手机号失败:', e)
wx.showToast({ title: '获取失败,请手动输入', icon: 'none' })
}
},
// 关闭绑定弹窗
closeBindModal() {
@@ -177,12 +290,9 @@ Page({
})
},
// 联系客服
// 联系客服 - 跳转到Soul派对房
contactService() {
wx.setClipboardData({
data: '28533368',
success: () => wx.showToast({ title: '客服微信已复制', icon: 'success' })
})
wx.showToast({ title: '请在Soul派对房联系客服', icon: 'none' })
},
// 阻止冒泡

View File

@@ -2,7 +2,7 @@
"compileType": "miniprogram",
"miniprogramRoot": "",
"projectname": "soul-startup",
"description": "一场SOUL的创业实验场 - 来自Soul派对房的真实商业故事",
"description": "Soul创业派对 - 来自派对房的真实商业故事",
"appid": "wxb8bbb2b10dec74aa",
"setting": {
"urlCheck": false,