搜索功能增强 + 设置页自动提现 + 部署规则

## 小程序
1. 搜索页面:添加热门章节推荐
2. 我的页面:点击ID可复制
3. 设置页面:添加自动提现开关

## 后台
1. 新增热门章节API (/api/book/hot)
2. 章节保存时自动去掉Markdown标题

## 规则
1. .cursorrules添加完整部署流程
This commit is contained in:
卡若
2026-01-29 12:18:51 +08:00
parent 6a556c2470
commit 0f50fb7c3b
12 changed files with 402 additions and 8 deletions

View File

@@ -26,3 +26,63 @@
- 使用 @v0 前缀调用v0生成代码
- 默认使用 claude-opus 模型
- 生成前先说明需求,确保理解正确
---
## 自动部署规则
### 服务器信息
- **服务器IP**: 122.51.107.140
- **用户**: ubuntu
- **项目路径**: /www/wwwroot/soul
- **PM2进程名**: soul
- **端口**: 3006
### GitHub仓库
- **地址**: https://github.com/fnvtk/Mycontent.git
- **分支**: soul-content
### 小程序
- **AppID**: wxb8bbb2b10dec74aa
- **项目路径**: ./miniprogram
### 部署流程(每次提交后自动执行)
1. **提交代码到Git**
```bash
git add -A
git commit -m "描述"
```
2. **推送到GitHub**
```bash
git push origin soul-content
```
3. **部署到服务器**
```bash
ssh ubuntu@122.51.107.140 "cd /www/wwwroot/soul && git pull && pnpm build && pm2 restart soul"
```
如果SSH连接失败手动登录宝塔面板执行
```bash
cd /www/wwwroot/soul
git pull
pnpm build
pm2 restart soul
```
4. **上传小程序**
```bash
/Applications/wechatwebdevtools.app/Contents/MacOS/cli upload --project "./miniprogram" -v "版本号" -d "描述"
```
5. **打开微信公众平台**
```bash
open "https://mp.weixin.qq.com/"
```
在「版本管理」设为体验版测试
### 注意事项
- 小程序版本号:未发布前保持 1.14,正式发布后递增
- 后台部署后需等待约30秒生效
- 数据库腾讯云MySQL读取优先级数据库 > 本地文件

View File

@@ -130,11 +130,21 @@ export default function ContentPage() {
setIsSaving(true)
try {
// 自动去掉内容中的重复标题(如# 1.2 xxx
// 自动去掉内容中的重复标题(如# 1.2 xxx 或 # 1.4 人性的三角结构...
let content = editingSection.content || ''
// 匹配 # 数字.数字 开头的标题并去掉
const titlePattern = new RegExp(`^#\\s*${editingSection.id}\\s+.*$`, 'm')
content = content.replace(titlePattern, '').trim()
// 匹配多种格式的Markdown标题并去掉
// 1. # 1.2 标题内容
// 2. # 1.2 标题内容(多个空格)
// 3. ## 1.2 标题内容
const titlePatterns = [
new RegExp(`^#+\\s*${editingSection.id.replace('.', '\\.')}\\s+.*$`, 'gm'), // # 1.4 xxx
new RegExp(`^#+\\s*${editingSection.id.replace('.', '\\.')}[:].*$`, 'gm'), // # 1.4xxx
new RegExp(`^#\\s+.*${editingSection.title?.slice(0, 10).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*$`, 'gm') // # xxx标题内容
]
for (const pattern of titlePatterns) {
content = content.replace(pattern, '')
}
content = content.replace(/^\s*\n+/, '').trim() // 去掉开头的空行
const res = await fetch('/api/db/book', {
method: 'PUT',

74
app/api/book/hot/route.ts Normal file
View File

@@ -0,0 +1,74 @@
/**
* 热门章节API
* 返回点击量最高的章节
*/
import { NextResponse } from 'next/server'
import { query } from '@/lib/db'
export async function GET() {
try {
// 从数据库查询点击量高的章节(如果有统计表)
let hotChapters = []
try {
// 尝试从订单表统计购买量高的章节
const rows = await query(`
SELECT
section_id as id,
COUNT(*) as purchase_count
FROM orders
WHERE status = 'completed' AND section_id IS NOT NULL
GROUP BY section_id
ORDER BY purchase_count DESC
LIMIT 10
`) as any[]
if (rows && rows.length > 0) {
// 补充章节信息
const sectionInfo: Record<string, any> = {
'1.1': { title: '荷包:电动车出租的被动收入模式', part: '真实的人', tag: '免费' },
'9.12': { title: '美业整合:一个人的公司如何月入十万', part: '真实的赚钱', tag: '热门' },
'3.1': { title: '3000万流水如何跑出来', part: '真实的行业', tag: '热门' },
'8.1': { title: '流量杠杆:抖音、Soul、飞书', part: '真实的赚钱', tag: '推荐' },
'9.13': { title: 'AI工具推广一个隐藏的高利润赛道', part: '真实的赚钱', tag: '最新' },
'9.14': { title: '大健康私域一个月150万的70后', part: '真实的赚钱', tag: '热门' },
'1.2': { title: '老墨:资源整合高手的社交方法', part: '真实的人', tag: '推荐' },
'2.1': { title: '电商的底层逻辑', part: '真实的行业', tag: '推荐' },
'4.1': { title: '我的第一次创业失败', part: '真实的错误', tag: '热门' },
'5.1': { title: '未来职业的三个方向', part: '真实的社会', tag: '推荐' }
}
hotChapters = rows.map((row: any) => ({
id: row.id,
...(sectionInfo[row.id] || { title: `章节${row.id}`, part: '', tag: '热门' }),
purchaseCount: row.purchase_count
}))
}
} catch (e) {
console.log('[Hot] 数据库查询失败,使用默认数据')
}
// 如果没有数据,返回默认热门章节
if (hotChapters.length === 0) {
hotChapters = [
{ id: '1.1', title: '荷包:电动车出租的被动收入模式', tag: '免费', part: '真实的人' },
{ id: '9.12', title: '美业整合:一个人的公司如何月入十万', tag: '热门', part: '真实的赚钱' },
{ id: '3.1', title: '3000万流水如何跑出来', tag: '热门', part: '真实的行业' },
{ id: '8.1', title: '流量杠杆:抖音、Soul、飞书', tag: '推荐', part: '真实的赚钱' },
{ id: '9.13', title: 'AI工具推广一个隐藏的高利润赛道', tag: '最新', part: '真实的赚钱' }
]
}
return NextResponse.json({
success: true,
chapters: hotChapters
})
} catch (error) {
console.error('[Hot] Error:', error)
return NextResponse.json({
success: false,
chapters: []
})
}
}

View File

@@ -177,6 +177,21 @@ Page({
}
})
},
// 复制用户ID
copyUserId() {
const userId = this.data.userInfo?.id || ''
if (!userId) {
wx.showToast({ title: '暂无ID', icon: 'none' })
return
}
wx.setClipboardData({
data: userId,
success: () => {
wx.showToast({ title: 'ID已复制', icon: 'success' })
}
})
},
// 切换Tab
switchTab(e) {

View File

@@ -44,8 +44,9 @@
<text class="user-name">{{userInfo.nickname || '微信用户'}}</text>
<text class="edit-name-icon">✎</text>
</view>
<view class="user-id-row">
<view class="user-id-row" bindtap="copyUserId">
<text class="user-id">ID: {{userIdShort}}</text>
<text class="copy-icon">📋</text>
</view>
</view>
</view>

View File

@@ -164,6 +164,12 @@
.user-id-row {
display: flex;
align-items: center;
gap: 8rpx;
}
.copy-icon {
font-size: 22rpx;
opacity: 0.5;
}
.user-wechat {

View File

@@ -12,14 +12,36 @@ Page({
loading: false,
searched: false,
total: 0,
// 热门搜索
hotKeywords: ['私域', '电商', '流量', '赚钱', '创业', 'Soul', '抖音']
// 热门搜索关键词
hotKeywords: ['私域', '电商', '流量', '赚钱', '创业', 'Soul', '抖音', '变现'],
// 热门章节推荐
hotChapters: [
{ id: '1.1', title: '荷包:电动车出租的被动收入模式', tag: '免费', part: '真实的人' },
{ id: '9.12', title: '美业整合:一个人的公司如何月入十万', tag: '热门', part: '真实的赚钱' },
{ id: '3.1', title: '3000万流水如何跑出来', tag: '热门', part: '真实的行业' },
{ id: '8.1', title: '流量杠杆:抖音、Soul、飞书', tag: '推荐', part: '真实的赚钱' },
{ id: '9.13', title: 'AI工具推广一个隐藏的高利润赛道', tag: '最新', part: '真实的赚钱' }
]
},
onLoad() {
this.setData({
statusBarHeight: app.globalData.statusBarHeight || 44
})
// 加载热门章节
this.loadHotChapters()
},
// 加载热门章节(从服务器获取点击量高的章节)
async loadHotChapters() {
try {
const res = await app.request('/api/book/hot')
if (res && res.success && res.chapters?.length > 0) {
this.setData({ hotChapters: res.chapters })
}
} catch (e) {
console.log('加载热门章节失败,使用默认数据')
}
},
// 输入关键词

View File

@@ -40,6 +40,27 @@
>{{item}}</view>
</view>
</view>
<!-- 热门章节推荐(未搜索时显示) -->
<view class="hot-chapters" wx:if="{{!searched && hotChapters.length > 0}}">
<text class="section-title">热门章节</text>
<view class="chapter-list">
<view
class="chapter-item"
wx:for="{{hotChapters}}"
wx:key="id"
bindtap="goToRead"
data-id="{{item.id}}"
>
<view class="chapter-rank">{{index + 1}}</view>
<view class="chapter-info">
<text class="chapter-title">{{item.title}}</text>
<text class="chapter-part">{{item.part}}</text>
</view>
<view class="chapter-tag {{item.tag === '免费' ? 'tag-free' : item.tag === '热门' ? 'tag-hot' : 'tag-new'}}">{{item.tag}}</view>
</view>
</view>
</view>
<!-- 搜索结果 -->
<view class="results-section" wx:if="{{searched}}">

View File

@@ -110,6 +110,77 @@
border: 1rpx solid rgba(0, 206, 209, 0.3);
}
/* 热门章节 */
.hot-chapters {
padding: 32rpx 0;
margin-top: 16rpx;
}
.chapter-list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.chapter-item {
display: flex;
align-items: center;
background: rgba(255,255,255,0.05);
border-radius: 20rpx;
padding: 24rpx;
gap: 20rpx;
}
.chapter-rank {
width: 48rpx;
height: 48rpx;
background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
font-weight: 600;
color: #000;
flex-shrink: 0;
}
.chapter-item:nth-child(1) .chapter-rank { background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); }
.chapter-item:nth-child(2) .chapter-rank { background: linear-gradient(135deg, #C0C0C0 0%, #A9A9A9 100%); }
.chapter-item:nth-child(3) .chapter-rank { background: linear-gradient(135deg, #CD7F32 0%, #8B4513 100%); }
.chapter-info {
flex: 1;
min-width: 0;
}
.chapter-title {
font-size: 28rpx;
color: #fff;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chapter-part {
font-size: 22rpx;
color: rgba(255,255,255,0.4);
margin-top: 6rpx;
display: block;
}
.chapter-tag {
padding: 8rpx 16rpx;
border-radius: 16rpx;
font-size: 22rpx;
flex-shrink: 0;
}
.chapter-tag.tag-free { background: rgba(76, 175, 80, 0.2); color: #4CAF50; }
.chapter-tag.tag-hot { background: rgba(255, 87, 34, 0.2); color: #FF5722; }
.chapter-tag.tag-new { background: rgba(233, 30, 99, 0.2); color: #E91E63; }
/* 搜索结果 */
.results-section {
padding: 16rpx 0;

View File

@@ -16,6 +16,9 @@ Page({
wechatId: '',
alipayAccount: '',
// 自动提现
autoWithdrawEnabled: false,
// 绑定弹窗
showBindModal: false,
bindType: '', // phone | wechat | alipay
@@ -43,16 +46,66 @@ Page({
const phoneNumber = wx.getStorageSync('user_phone') || userInfo.phone || ''
const wechatId = wx.getStorageSync('user_wechat') || userInfo.wechat || ''
const alipayAccount = wx.getStorageSync('user_alipay') || userInfo.alipay || ''
const autoWithdrawEnabled = wx.getStorageSync('auto_withdraw_enabled') || false
this.setData({
isLoggedIn: true,
userInfo,
phoneNumber,
wechatId,
alipayAccount
alipayAccount,
autoWithdrawEnabled
})
}
},
// 切换自动提现
async toggleAutoWithdraw(e) {
const enabled = e.detail.value
// 检查是否绑定了支付方式
if (enabled && !this.data.wechatId && !this.data.alipayAccount) {
wx.showToast({ title: '请先绑定微信号或支付宝', icon: 'none' })
this.setData({ autoWithdrawEnabled: false })
return
}
// 开启时需要确认
if (enabled) {
wx.showModal({
title: '开启自动提现',
content: `收益将自动打款到您的${this.data.alipayAccount ? '支付宝' : '微信'}账户,确认开启吗?`,
success: async (res) => {
if (res.confirm) {
this.setData({ autoWithdrawEnabled: true })
wx.setStorageSync('auto_withdraw_enabled', true)
// 同步到服务器
try {
await app.request('/api/user/update', {
method: 'POST',
data: {
userId: app.globalData.userInfo?.id,
autoWithdraw: true,
withdrawAccount: this.data.alipayAccount || this.data.wechatId
}
})
} catch (e) {
console.log('同步自动提现设置失败', e)
}
wx.showToast({ title: '已开启自动提现', icon: 'success' })
} else {
this.setData({ autoWithdrawEnabled: false })
}
}
})
} else {
this.setData({ autoWithdrawEnabled: false })
wx.setStorageSync('auto_withdraw_enabled', false)
wx.showToast({ title: '已关闭自动提现', icon: 'success' })
}
},
// 绑定手机号
bindPhone() {

View File

@@ -70,6 +70,36 @@
</view>
</view>
<!-- 自动提现设置 -->
<view class="bind-card auto-withdraw-card" wx:if="{{isLoggedIn && (wechatId || alipayAccount)}}">
<view class="card-header">
<text class="card-icon">💰</text>
<view class="card-title-wrap">
<text class="card-title">自动提现</text>
<text class="card-desc">收益自动打款到您的账户</text>
</view>
</view>
<view class="auto-withdraw-content">
<view class="withdraw-switch-row">
<text class="switch-label">开启自动提现</text>
<switch checked="{{autoWithdrawEnabled}}" bindchange="toggleAutoWithdraw" color="#00CED1"/>
</view>
<view class="withdraw-info" wx:if="{{autoWithdrawEnabled}}">
<view class="info-item">
<text class="info-label">提现方式</text>
<text class="info-value">{{alipayAccount ? '支付宝' : '微信零钱'}}</text>
</view>
<view class="info-item">
<text class="info-label">提现账户</text>
<text class="info-value">{{alipayAccount || wechatId}}</text>
</view>
<text class="withdraw-tip">收益将在每笔订单完成后自动打款</text>
</view>
</view>
</view>
<!-- 提现提示 -->
<view class="tip-banner" wx:if="{{isLoggedIn && !wechatId && !alipayAccount}}">
<text class="tip-text">提示:绑定至少一个支付方式(微信或支付宝)才能使用提现功能</text>

View File

@@ -44,6 +44,37 @@
}
.get-phone-btn::after { border: none; }
/* 自动提现卡片 */
.auto-withdraw-card { margin-top: 24rpx; }
.auto-withdraw-content { padding-top: 16rpx; }
.withdraw-switch-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx 0;
}
.switch-label { font-size: 28rpx; color: #fff; }
.withdraw-info {
background: rgba(0,206,209,0.1);
border-radius: 16rpx;
padding: 20rpx;
margin-top: 16rpx;
}
.info-item {
display: flex;
justify-content: space-between;
padding: 8rpx 0;
}
.info-label { font-size: 26rpx; color: rgba(255,255,255,0.6); }
.info-value { font-size: 26rpx; color: #00CED1; }
.withdraw-tip {
display: block;
font-size: 22rpx;
color: rgba(255,255,255,0.4);
margin-top: 12rpx;
text-align: center;
}
/* 提现提示 */
.tip-banner { background: rgba(255,165,0,0.1); border: 2rpx solid rgba(255,165,0,0.3); border-radius: 20rpx; padding: 20rpx 24rpx; margin-bottom: 24rpx; }
.tip-text { font-size: 24rpx; color: #FFA500; line-height: 1.5; }