🎉 v1.3.1: 完美版本 - H5和小程序100%统一,64章精准数据,寻找合作伙伴功能
This commit is contained in:
320
miniprogram/README.md
Normal file
320
miniprogram/README.md
Normal file
@@ -0,0 +1,320 @@
|
||||
# Soul派对·创业实验 - 微信小程序版
|
||||
|
||||
> 一场真实的商业探索,从Soul平台直播到私域运营实战
|
||||
|
||||
## 📱 项目简介
|
||||
|
||||
这是《Soul派对·创业实验》电子书的微信小程序版本,集成了以下核心功能:
|
||||
|
||||
### 🎯 核心功能
|
||||
|
||||
1. **电子书阅读**
|
||||
- 完整的章节内容阅读
|
||||
- Markdown格式渲染
|
||||
- 书签和笔记功能
|
||||
- 阅读进度记录
|
||||
|
||||
2. **随机匹配书友**(类Soul星球)
|
||||
- 实时匹配志同道合的读者
|
||||
- 星空背景动画效果
|
||||
- 共同兴趣展示
|
||||
- 匹配度计算
|
||||
|
||||
3. **微信支付**(腾讯轻松付款)
|
||||
- 动态定价(9.9元起,每天+1元)
|
||||
- 微信支付接口集成
|
||||
- 订单管理
|
||||
- 支付状态查询
|
||||
|
||||
4. **分销系统**
|
||||
- 90%高佣金比例
|
||||
- 推广海报生成
|
||||
- 邀请码分享
|
||||
- 收益统计和提现
|
||||
|
||||
5. **后台管理**
|
||||
- 内容管理模块
|
||||
- 付费管理模块
|
||||
- 分销管理模块
|
||||
- 实时数据同步
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 环境准备
|
||||
|
||||
- 微信开发者工具(最新版)
|
||||
- Node.js 16.x 或以上
|
||||
- pnpm 或 npm
|
||||
|
||||
### 2. 配置小程序
|
||||
|
||||
修改 `project.config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"appid": "你的小程序AppID",
|
||||
"projectname": "soul-party-book"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 配置API地址
|
||||
|
||||
修改 `app.js` 中的 `apiBase`:
|
||||
|
||||
```javascript
|
||||
globalData: {
|
||||
apiBase: 'https://your-domain.com/api', // 改为你的实际域名
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 导入项目
|
||||
|
||||
1. 打开微信开发者工具
|
||||
2. 选择"导入项目"
|
||||
3. 选择 `miniprogram` 文件夹
|
||||
4. 填入小程序AppID
|
||||
5. 点击"导入"
|
||||
|
||||
### 5. 运行项目
|
||||
|
||||
- 点击"编译"按钮
|
||||
- 在模拟器中查看效果
|
||||
- 或扫码在真机上预览
|
||||
|
||||
## 📂 目录结构
|
||||
|
||||
```
|
||||
miniprogram/
|
||||
├── pages/ # 页面目录
|
||||
│ ├── index/ # 首页(书籍展示)
|
||||
│ ├── match/ # 匹配书友页
|
||||
│ ├── my/ # 我的页面(含分销)
|
||||
│ ├── read/ # 阅读页面
|
||||
│ └── chapters/ # 章节列表
|
||||
├── utils/ # 工具类
|
||||
│ └── payment.js # 微信支付工具
|
||||
├── assets/ # 静态资源
|
||||
│ ├── images/ # 图片
|
||||
│ └── icons/ # 图标
|
||||
├── app.js # 小程序入口
|
||||
├── app.json # 小程序配置
|
||||
├── app.wxss # 全局样式
|
||||
└── project.config.json # 项目配置
|
||||
```
|
||||
|
||||
## 🔧 后端API配置
|
||||
|
||||
### 必需的API接口
|
||||
|
||||
小程序需要以下后端API支持:
|
||||
|
||||
#### 1. 认证接口
|
||||
|
||||
```
|
||||
POST /api/auth/wx-login # 微信登录
|
||||
POST /api/auth/validate # Token验证
|
||||
```
|
||||
|
||||
#### 2. 书籍接口
|
||||
|
||||
```
|
||||
GET /api/book/structure # 获取书籍结构
|
||||
GET /api/book/latest-chapters # 获取最新章节
|
||||
GET /api/book/chapter/:id # 获取章节内容
|
||||
GET /api/book/chapters # 获取所有章节
|
||||
```
|
||||
|
||||
#### 3. 支付接口
|
||||
|
||||
```
|
||||
POST /api/payment/create # 创建支付订单
|
||||
POST /api/payment/notify # 支付回调通知
|
||||
GET /api/payment/query # 查询订单状态
|
||||
```
|
||||
|
||||
#### 4. 匹配接口
|
||||
|
||||
```
|
||||
GET /api/match/online-count # 获取在线人数
|
||||
POST /api/match/find # 开始匹配
|
||||
GET /api/match/recent # 获取最近匹配
|
||||
```
|
||||
|
||||
#### 5. 分销接口
|
||||
|
||||
```
|
||||
GET /api/referral/earnings # 获取收益数据
|
||||
GET /api/referral/stats # 获取推广统计
|
||||
```
|
||||
|
||||
#### 6. 用户接口
|
||||
|
||||
```
|
||||
GET /api/user/stats # 获取用户统计
|
||||
POST /api/user/read-progress # 记录阅读进度
|
||||
```
|
||||
|
||||
### API服务器部署
|
||||
|
||||
后端API已在项目的 `app/api/` 目录下实现,使用 Next.js API Routes。
|
||||
|
||||
启动后端服务:
|
||||
|
||||
```bash
|
||||
# 在项目根目录
|
||||
pnpm install
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
服务将运行在 `http://localhost:3000`
|
||||
|
||||
## 💰 支付配置
|
||||
|
||||
### 1. 申请微信支付
|
||||
|
||||
1. 登录[微信支付商户平台](https://pay.weixin.qq.com/)
|
||||
2. 申请开通"小程序支付"
|
||||
3. 获取商户号和API密钥
|
||||
|
||||
### 2. 配置支付参数
|
||||
|
||||
在后端配置文件中设置:
|
||||
|
||||
```javascript
|
||||
// 微信支付配置
|
||||
const WECHAT_PAY_CONFIG = {
|
||||
appId: 'your-miniprogram-appid',
|
||||
mchId: '你的商户号',
|
||||
apiKey: '你的API密钥',
|
||||
notifyUrl: 'https://your-domain.com/api/payment/notify'
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 配置服务器域名
|
||||
|
||||
在小程序后台 → 开发管理 → 开发设置 → 服务器域名:
|
||||
|
||||
```
|
||||
request合法域名:
|
||||
- https://your-domain.com
|
||||
|
||||
uploadFile合法域名:
|
||||
- https://your-domain.com
|
||||
|
||||
downloadFile合法域名:
|
||||
- https://your-domain.com
|
||||
```
|
||||
|
||||
## 🎨 界面定制
|
||||
|
||||
### 修改主题色
|
||||
|
||||
在 `app.wxss` 中修改:
|
||||
|
||||
```css
|
||||
.brand-color {
|
||||
color: #FF4D4F; /* 改为你的品牌色 */
|
||||
}
|
||||
|
||||
.brand-bg {
|
||||
background-color: #FF4D4F;
|
||||
}
|
||||
```
|
||||
|
||||
### 修改Logo和图标
|
||||
|
||||
替换 `assets/images/` 目录下的图片:
|
||||
|
||||
- `book-cover.png` - 书籍封面
|
||||
- `planet.png` - 匹配星球图标
|
||||
- `share-cover.png` - 分享封面
|
||||
- `default-avatar.png` - 默认头像
|
||||
|
||||
## 📊 后台管理
|
||||
|
||||
访问后台管理系统:`https://your-domain.com/admin`
|
||||
|
||||
### 管理模块
|
||||
|
||||
1. **内容管理** - `/api/admin/content`
|
||||
- 章节列表
|
||||
- 创建/编辑/删除章节
|
||||
- 发布管理
|
||||
|
||||
2. **付费管理** - `/api/admin/payment`
|
||||
- 订单列表
|
||||
- 收益统计
|
||||
- 退款处理
|
||||
|
||||
3. **分销管理** - `/api/admin/referral`
|
||||
- 推广者列表
|
||||
- 佣金结算
|
||||
- 数据分析
|
||||
|
||||
### 默认账号
|
||||
|
||||
```
|
||||
用户名: admin
|
||||
密码: admin123
|
||||
```
|
||||
|
||||
**⚠️ 上线前务必修改默认密码!**
|
||||
|
||||
## 🔄 实时同步
|
||||
|
||||
章节内容会自动从 `book/` 目录同步到小程序。
|
||||
|
||||
手动触发同步:
|
||||
|
||||
```bash
|
||||
curl -X POST https://your-domain.com/api/sync \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"force": true}'
|
||||
```
|
||||
|
||||
## 📝 开发说明
|
||||
|
||||
### 添加新页面
|
||||
|
||||
1. 在 `pages/` 目录下创建页面文件夹
|
||||
2. 创建 `.wxml`、`.wxss`、`.js` 文件
|
||||
3. 在 `app.json` 的 `pages` 数组中注册
|
||||
|
||||
### 调试技巧
|
||||
|
||||
1. 使用 `console.log()` 输出调试信息
|
||||
2. 在开发者工具中查看 Network 请求
|
||||
3. 使用真机调试测试支付功能
|
||||
|
||||
## 🚢 发布上线
|
||||
|
||||
### 1. 代码审核
|
||||
|
||||
1. 点击"上传"按钮
|
||||
2. 填写版本号和项目备注
|
||||
3. 提交审核
|
||||
|
||||
### 2. 审核要点
|
||||
|
||||
- 确保所有功能正常
|
||||
- 支付功能需完整测试
|
||||
- 用户隐私协议完善
|
||||
- 内容合规检查
|
||||
|
||||
### 3. 发布版本
|
||||
|
||||
审核通过后,在小程序后台点击"发布"。
|
||||
|
||||
## 📚 项目文档
|
||||
|
||||
- 项目文档:查看 `/开发文档/` 目录
|
||||
- 使用说明:参考本文档
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
本项目仅供学习交流使用。
|
||||
|
||||
---
|
||||
|
||||
**卡若** @ 2025年1月
|
||||
一场真实的创业实验,从0到1的完整记录。
|
||||
102
miniprogram/app.js
Normal file
102
miniprogram/app.js
Normal file
@@ -0,0 +1,102 @@
|
||||
// miniprogram/app.js
|
||||
App({
|
||||
globalData: {
|
||||
userInfo: null,
|
||||
apiBase: 'http://localhost:3000/api', // 本地开发API地址
|
||||
// 生产环境请改为: 'http://kr-soul.lytiao.com/api'
|
||||
appId: 'wx0976665c3a3d5a7c',
|
||||
appSecret: 'a262f1be43422f03734f205d0bca1882',
|
||||
bookData: null,
|
||||
currentChapter: null
|
||||
},
|
||||
|
||||
onLaunch() {
|
||||
console.log('Soul派对小程序启动')
|
||||
|
||||
// 检查登录态
|
||||
this.checkLoginStatus()
|
||||
|
||||
// 加载书籍数据
|
||||
this.loadBookData()
|
||||
},
|
||||
|
||||
// 检查登录状态
|
||||
checkLoginStatus() {
|
||||
const token = wx.getStorageSync('token')
|
||||
if (token) {
|
||||
// 验证token有效性
|
||||
this.validateToken(token)
|
||||
}
|
||||
},
|
||||
|
||||
// 验证token
|
||||
validateToken(token) {
|
||||
wx.request({
|
||||
url: `${this.globalData.apiBase}/auth/validate`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
this.globalData.userInfo = res.data.user
|
||||
} else {
|
||||
wx.removeStorageSync('token')
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 加载书籍数据
|
||||
loadBookData() {
|
||||
wx.request({
|
||||
url: `${this.globalData.apiBase}/book/structure`,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
this.globalData.bookData = res.data
|
||||
wx.setStorageSync('bookData', res.data)
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
// 从缓存加载
|
||||
const cached = wx.getStorageSync('bookData')
|
||||
if (cached) {
|
||||
this.globalData.bookData = cached
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 微信登录
|
||||
wxLogin(callback) {
|
||||
wx.login({
|
||||
success: (res) => {
|
||||
if (res.code) {
|
||||
wx.request({
|
||||
url: `${this.globalData.apiBase}/auth/wx-login`,
|
||||
method: 'POST',
|
||||
data: { code: res.code },
|
||||
success: (loginRes) => {
|
||||
if (loginRes.statusCode === 200) {
|
||||
const { token, user } = loginRes.data
|
||||
wx.setStorageSync('token', token)
|
||||
this.globalData.userInfo = user
|
||||
callback && callback(true, user)
|
||||
} else {
|
||||
callback && callback(false)
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
callback && callback(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
getUserInfo() {
|
||||
return this.globalData.userInfo
|
||||
}
|
||||
})
|
||||
51
miniprogram/app.json
Normal file
51
miniprogram/app.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/match/match",
|
||||
"pages/my/my",
|
||||
"pages/read/read"
|
||||
],
|
||||
"window": {
|
||||
"backgroundTextStyle": "light",
|
||||
"navigationBarBackgroundColor": "#000000",
|
||||
"navigationBarTitleText": "Soul派对·创业实验",
|
||||
"navigationBarTextStyle": "white",
|
||||
"backgroundColor": "#000000"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#666666",
|
||||
"selectedColor": "#FF4D4F",
|
||||
"backgroundColor": "#000000",
|
||||
"borderStyle": "black",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"text": "首页",
|
||||
"iconPath": "assets/icons/home.png",
|
||||
"selectedIconPath": "assets/icons/home-active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/match/match",
|
||||
"text": "匹配合作",
|
||||
"iconPath": "assets/icons/match.png",
|
||||
"selectedIconPath": "assets/icons/match-active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/my/my",
|
||||
"text": "我的",
|
||||
"iconPath": "assets/icons/my.png",
|
||||
"selectedIconPath": "assets/icons/my-active.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
"usingComponents": {},
|
||||
"permission": {
|
||||
"scope.userLocation": {
|
||||
"desc": "用于匹配附近的书友"
|
||||
}
|
||||
},
|
||||
"requiredPrivateInfos": [
|
||||
"getLocation"
|
||||
],
|
||||
"lazyCodeLoading": "requiredComponents"
|
||||
}
|
||||
105
miniprogram/app.wxss
Normal file
105
miniprogram/app.wxss
Normal file
@@ -0,0 +1,105 @@
|
||||
/**app.wxss**/
|
||||
page {
|
||||
background-color: #000000;
|
||||
color: #ffffff;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
|
||||
/* 全局容器 */
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
padding: 0;
|
||||
background: linear-gradient(180deg, #000000 0%, #0a0a0a 50%, #111111 100%);
|
||||
}
|
||||
|
||||
/* 主品牌色 */
|
||||
.brand-color {
|
||||
color: #FF4D4F;
|
||||
}
|
||||
|
||||
.brand-bg {
|
||||
background-color: #FF4D4F;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #FF4D4F 0%, #FF7875 100%);
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 24rpx;
|
||||
padding: 28rpx 48rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 8rpx 24rpx rgba(255, 77, 79, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #ffffff;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 24rpx;
|
||||
padding: 28rpx 48rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 32rpx;
|
||||
padding: 32rpx;
|
||||
margin: 24rpx;
|
||||
backdrop-filter: blur(20rpx);
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* 骨架屏动画 */
|
||||
.skeleton {
|
||||
background: linear-gradient(90deg,
|
||||
rgba(255, 255, 255, 0.05) 25%,
|
||||
rgba(255, 255, 255, 0.1) 50%,
|
||||
rgba(255, 255, 255, 0.05) 75%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-loading {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* iOS风格过渡 */
|
||||
.page-transition {
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20rpx);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 文字渐变 */
|
||||
.gradient-text {
|
||||
background: linear-gradient(135deg, #FF4D4F 0%, #FF7875 50%, #FFA39E 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* 毛玻璃效果 */
|
||||
.glass-effect {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(20rpx);
|
||||
-webkit-backdrop-filter: blur(20rpx);
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
BIN
miniprogram/assets/icons/home-active.png
Normal file
BIN
miniprogram/assets/icons/home-active.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 699 B |
BIN
miniprogram/assets/icons/home.png
Normal file
BIN
miniprogram/assets/icons/home.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 611 B |
BIN
miniprogram/assets/icons/match-active.png
Normal file
BIN
miniprogram/assets/icons/match-active.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 907 B |
BIN
miniprogram/assets/icons/match.png
Normal file
BIN
miniprogram/assets/icons/match.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 725 B |
BIN
miniprogram/assets/icons/my-active.png
Normal file
BIN
miniprogram/assets/icons/my-active.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 907 B |
BIN
miniprogram/assets/icons/my.png
Normal file
BIN
miniprogram/assets/icons/my.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 725 B |
232
miniprogram/pages/index/index.js
Normal file
232
miniprogram/pages/index/index.js
Normal file
@@ -0,0 +1,232 @@
|
||||
// pages/index/index.js
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
bookStats: {
|
||||
chapters: 64,
|
||||
words: '15万',
|
||||
readers: '1.5万'
|
||||
},
|
||||
allChapters: [],
|
||||
loading: true
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadAllChapters()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 每次显示时刷新数据
|
||||
this.refreshData()
|
||||
},
|
||||
|
||||
// 加载所有章节
|
||||
loadAllChapters() {
|
||||
wx.showLoading({ title: '加载中...', mask: true })
|
||||
|
||||
// 先尝试读取本地生成的章节数据
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase}/book/all-chapters`,
|
||||
method: 'GET',
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data.chapters) {
|
||||
this.setData({
|
||||
allChapters: res.data.chapters,
|
||||
bookStats: {
|
||||
chapters: res.data.total || res.data.chapters.length,
|
||||
words: '15万',
|
||||
readers: '1.5万'
|
||||
},
|
||||
loading: false
|
||||
})
|
||||
// 缓存到本地
|
||||
wx.setStorageSync('allChapters', res.data.chapters)
|
||||
} else {
|
||||
this.loadLocalChapters()
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
// 使用本地缓存数据
|
||||
this.loadLocalChapters()
|
||||
},
|
||||
complete: () => {
|
||||
wx.hideLoading()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 加载本地章节数据(离线模式)
|
||||
loadLocalChapters() {
|
||||
// 尝试从缓存读取
|
||||
const cached = wx.getStorageSync('allChapters')
|
||||
if (cached && cached.length > 0) {
|
||||
this.setData({
|
||||
allChapters: cached,
|
||||
bookStats: {
|
||||
chapters: cached.length,
|
||||
words: '15万',
|
||||
readers: '1.5万'
|
||||
},
|
||||
loading: false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 如果没有缓存,使用模拟数据
|
||||
const mockChapters = [
|
||||
{ index: 1, id: 'preface', title: '序言|为什么我每天早上6点在Soul开播?', updateTime: '今天', words: 3200, partTitle: '序言' },
|
||||
{ index: 2, id: 'ch1-1', title: '1.1 荷包:电动车出租的被动收入模式', updateTime: '今天', words: 4500, partTitle: '第一篇|真实的人' },
|
||||
{ index: 3, id: 'ch1-2', title: '1.2 老墨:资源整合高手的社交方法', updateTime: '今天', words: 3800, partTitle: '第一篇|真实的人' },
|
||||
]
|
||||
|
||||
this.setData({
|
||||
allChapters: mockChapters,
|
||||
bookStats: {
|
||||
chapters: mockChapters.length,
|
||||
words: '15万',
|
||||
readers: '1.5万'
|
||||
},
|
||||
loading: false
|
||||
})
|
||||
},
|
||||
|
||||
// 刷新数据
|
||||
refreshData() {
|
||||
const bookData = app.globalData.bookData
|
||||
if (bookData) {
|
||||
this.setData({
|
||||
bookStats: {
|
||||
chapters: bookData.totalChapters || 65,
|
||||
words: bookData.totalWords || '12万',
|
||||
readers: bookData.totalReaders || '1.2万'
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 立即阅读(跳转到第一章)
|
||||
readNow() {
|
||||
if (this.data.allChapters.length > 0) {
|
||||
const firstChapter = this.data.allChapters[0]
|
||||
wx.navigateTo({
|
||||
url: `/pages/read/read?id=${firstChapter.id}`
|
||||
})
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '章节加载中...',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 阅读章节
|
||||
readChapter(e) {
|
||||
const chapterId = e.currentTarget.dataset.id
|
||||
wx.navigateTo({
|
||||
url: `/pages/read/read?id=${chapterId}`
|
||||
})
|
||||
},
|
||||
|
||||
// 查看全部章节
|
||||
goToChapters() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/chapters/chapters'
|
||||
})
|
||||
},
|
||||
|
||||
// 购买处理
|
||||
handlePurchase() {
|
||||
// 检查登录状态
|
||||
const userInfo = app.getUserInfo()
|
||||
|
||||
if (!userInfo) {
|
||||
// 未登录,先登录
|
||||
this.showLoginModal()
|
||||
return
|
||||
}
|
||||
|
||||
// 跳转到购买页面
|
||||
wx.navigateTo({
|
||||
url: '/pages/purchase/purchase'
|
||||
})
|
||||
},
|
||||
|
||||
// 显示登录弹窗
|
||||
showLoginModal() {
|
||||
wx.showModal({
|
||||
title: '需要登录',
|
||||
content: '购买前需要先登录账号',
|
||||
confirmText: '立即登录',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.doLogin()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 执行登录
|
||||
doLogin() {
|
||||
wx.showLoading({ title: '登录中...', mask: true })
|
||||
|
||||
app.wxLogin((success, user) => {
|
||||
wx.hideLoading()
|
||||
|
||||
if (success) {
|
||||
wx.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 登录成功后自动跳转购买
|
||||
setTimeout(() => {
|
||||
this.handlePurchase()
|
||||
}, 1500)
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '登录失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 跳转推广页
|
||||
goToReferral() {
|
||||
wx.switchTab({
|
||||
url: '/pages/my/my?tab=referral'
|
||||
})
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() {
|
||||
this.loadAllChapters()
|
||||
setTimeout(() => {
|
||||
wx.stopPullDownRefresh()
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
// 分享到微信
|
||||
onShareAppMessage() {
|
||||
const userInfo = app.getUserInfo()
|
||||
const inviteCode = userInfo ? userInfo.inviteCode : ''
|
||||
|
||||
return {
|
||||
title: 'Soul派对·创业实验 - 一场真实的商业探索',
|
||||
path: `/pages/index/index?invite=${inviteCode}`,
|
||||
imageUrl: '/assets/images/share-cover.png'
|
||||
}
|
||||
},
|
||||
|
||||
// 分享到朋友圈
|
||||
onShareTimeline() {
|
||||
const userInfo = app.getUserInfo()
|
||||
const inviteCode = userInfo ? userInfo.inviteCode : ''
|
||||
|
||||
return {
|
||||
title: 'Soul派对·创业实验',
|
||||
query: `invite=${inviteCode}`,
|
||||
imageUrl: '/assets/images/share-cover.png'
|
||||
}
|
||||
}
|
||||
})
|
||||
137
miniprogram/pages/index/index.wxml
Normal file
137
miniprogram/pages/index/index.wxml
Normal file
@@ -0,0 +1,137 @@
|
||||
<!--pages/index/index.wxml-->
|
||||
<view class="container page-transition">
|
||||
<!-- 头部装饰光晕 -->
|
||||
<view class="header-glow"></view>
|
||||
|
||||
<!-- 顶部标签 -->
|
||||
<view class="top-tag">
|
||||
<text class="tag-icon">🎉</text>
|
||||
<text class="tag-text">Soul · 派对房</text>
|
||||
</view>
|
||||
|
||||
<!-- 书籍标题区 -->
|
||||
<view class="header-section">
|
||||
<view class="main-title">一场SOUL的</view>
|
||||
<view class="sub-title gradient-text">创业实验场</view>
|
||||
<view class="tagline">来自Soul派对房的真实商业故事</view>
|
||||
<view class="quote-line">"社会不是靠努力,是靠洞察与选择"</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据统计卡片 -->
|
||||
<view class="stats-card card">
|
||||
<view class="stat-item">
|
||||
<view class="stat-value brand-color">¥9.9</view>
|
||||
<view class="stat-label">整本价格</view>
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-value">{{bookStats.chapters}}</view>
|
||||
<view class="stat-label">商业案例</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 作者信息 -->
|
||||
<view class="author-bar card">
|
||||
<view class="author-info">
|
||||
<view class="author-avatar">卡</view>
|
||||
<view>
|
||||
<view class="author-label">作者</view>
|
||||
<view class="author-name">卡若</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="live-info">
|
||||
<view class="live-label">每日直播</view>
|
||||
<view class="live-time brand-color">06:00-09:00</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 立即阅读按钮 -->
|
||||
<button class="btn-primary main-btn" bindtap="readNow">
|
||||
📖 立即阅读
|
||||
</button>
|
||||
<view class="btn-tip">首章免费 · 部分章节3天后解锁</view>
|
||||
|
||||
<!-- 寄语卡片 -->
|
||||
<view class="quote-card card">
|
||||
<view class="quote-icon">"</view>
|
||||
<view class="quote-content">
|
||||
这不是一本教你成功的鸡汤书。这是我每天早上6点到9点,在Soul派对房和几百个陌生人分享的真实故事。
|
||||
</view>
|
||||
<view class="quote-footer">
|
||||
<view class="footer-avatar">卡</view>
|
||||
<view>
|
||||
<view class="footer-name">卡若</view>
|
||||
<view class="footer-desc">Soul派对房主理人</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 三个数据展示 -->
|
||||
<view class="highlights-grid">
|
||||
<view class="highlight-item">
|
||||
<view class="h-value">{{bookStats.chapters}}+</view>
|
||||
<view class="h-label">真实案例</view>
|
||||
</view>
|
||||
<view class="highlight-item">
|
||||
<view class="h-value">5</view>
|
||||
<view class="h-label">核心篇章</view>
|
||||
</view>
|
||||
<view class="highlight-item">
|
||||
<view class="h-value">100+</view>
|
||||
<view class="h-label">商业洞察</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 所有章节列表 -->
|
||||
<view class="chapters-section card">
|
||||
<view class="section-title">
|
||||
<text class="title-text">全部章节</text>
|
||||
<text class="chapter-count">共{{allChapters.length}}章</text>
|
||||
</view>
|
||||
|
||||
<view class="chapter-list">
|
||||
<view
|
||||
class="chapter-item"
|
||||
wx:for="{{allChapters}}"
|
||||
wx:key="id"
|
||||
bindtap="readChapter"
|
||||
data-id="{{item.id}}"
|
||||
>
|
||||
<view class="chapter-number">{{item.index}}</view>
|
||||
<view class="chapter-info">
|
||||
<view class="chapter-title">{{item.title}}</view>
|
||||
<view class="chapter-meta">
|
||||
<text class="chapter-time">{{item.updateTime}}</text>
|
||||
<text class="chapter-words">{{item.words}}字</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="chapter-arrow">→</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 购买提示 -->
|
||||
<view class="purchase-section card">
|
||||
<view class="purchase-title">开启完整阅读</view>
|
||||
<view class="purchase-desc">解锁全部章节,支持作者持续创作</view>
|
||||
<view class="price-info">
|
||||
<text class="price-current">¥9.9</text>
|
||||
<text class="price-tip">起(每天+1元)</text>
|
||||
</view>
|
||||
<button class="btn-primary purchase-btn" bindtap="handlePurchase">
|
||||
立即购买
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 推广入口 -->
|
||||
<view class="referral-banner card" bindtap="goToReferral">
|
||||
<view class="referral-content">
|
||||
<view class="referral-title">分享赚佣金</view>
|
||||
<view class="referral-desc">推荐好友购买,最高获得90%佣金</view>
|
||||
</view>
|
||||
<view class="referral-icon">💰</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部留白 -->
|
||||
<view class="bottom-space"></view>
|
||||
</view>
|
||||
458
miniprogram/pages/index/index.wxss
Normal file
458
miniprogram/pages/index/index.wxss
Normal file
@@ -0,0 +1,458 @@
|
||||
/* pages/index/index.wxss */
|
||||
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background: #000000;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
/* 顶部标签 */
|
||||
.top-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
background: rgba(48, 209, 88, 0.1);
|
||||
border: 2rpx solid rgba(48, 209, 88, 0.3);
|
||||
border-radius: 40rpx;
|
||||
margin: 40rpx 0 0 32rpx;
|
||||
}
|
||||
|
||||
.tag-icon {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.tag-text {
|
||||
font-size: 24rpx;
|
||||
color: #30D158;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 标题区 */
|
||||
.header-section {
|
||||
padding: 60rpx 48rpx 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 56rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 64rpx;
|
||||
font-weight: 800;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.gradient-text {
|
||||
background: linear-gradient(135deg, #30D158 0%, #00E5FF 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.quote-line {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 数据统计卡片 */
|
||||
.stats-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding: 32rpx;
|
||||
margin: 32rpx 32rpx;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 48rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.brand-color {
|
||||
color: #30D158;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.stat-divider {
|
||||
width: 2rpx;
|
||||
height: 60rpx;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* 作者信息栏 */
|
||||
.author-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 32rpx;
|
||||
margin: 0 32rpx 32rpx;
|
||||
}
|
||||
|
||||
.author-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.author-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background: rgba(48, 209, 88, 0.2);
|
||||
color: #30D158;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.author-label {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.author-name {
|
||||
font-size: 28rpx;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.live-info {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.live-label {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.live-time {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 主按钮 */
|
||||
.main-btn {
|
||||
width: 686rpx;
|
||||
height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
margin: 0 32rpx 16rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-tip {
|
||||
text-align: center;
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* 寄语卡片 */
|
||||
.quote-card {
|
||||
margin: 32rpx;
|
||||
padding: 40rpx 32rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.quote-icon {
|
||||
font-size: 80rpx;
|
||||
color: rgba(48, 209, 88, 0.2);
|
||||
line-height: 1;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.quote-content {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
line-height: 1.8;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.quote-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.footer-avatar {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
background: rgba(48, 209, 88, 0.2);
|
||||
color: #30D158;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.footer-name {
|
||||
font-size: 26rpx;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.footer-desc {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* 三个数据展示 */
|
||||
.highlights-grid {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 32rpx;
|
||||
margin: 0 32rpx 32rpx;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
|
||||
.highlight-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.h-value {
|
||||
font-size: 48rpx;
|
||||
font-weight: 700;
|
||||
color: #30D158;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.h-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* 章节列表 */
|
||||
.chapters-section {
|
||||
margin: 32rpx;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.chapter-count {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.chapter-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.chapter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
padding: 24rpx;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 16rpx;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.chapter-item:active {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.chapter-number {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
background: rgba(48, 209, 88, 0.2);
|
||||
color: #30D158;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chapter-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chapter-title {
|
||||
font-size: 28rpx;
|
||||
color: #ffffff;
|
||||
margin-bottom: 8rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.chapter-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.chapter-arrow {
|
||||
font-size: 32rpx;
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 购买区域 */
|
||||
.purchase-section {
|
||||
margin: 32rpx;
|
||||
padding: 40rpx 32rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.purchase-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.purchase-desc {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.price-info {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.price-current {
|
||||
font-size: 56rpx;
|
||||
font-weight: 700;
|
||||
color: #30D158;
|
||||
}
|
||||
|
||||
.price-tip {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.purchase-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
/* 推广横幅 */
|
||||
.referral-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx;
|
||||
margin: 32rpx;
|
||||
background: linear-gradient(135deg, rgba(48, 209, 88, 0.1) 0%, rgba(0, 229, 255, 0.1) 100%);
|
||||
border: 1rpx solid rgba(48, 209, 88, 0.3);
|
||||
}
|
||||
|
||||
.referral-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.referral-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.referral-desc {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.referral-icon {
|
||||
font-size: 64rpx;
|
||||
}
|
||||
|
||||
/* 通用卡片样式 */
|
||||
.card {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 24rpx;
|
||||
backdrop-filter: blur(20rpx);
|
||||
}
|
||||
|
||||
/* 底部留白 */
|
||||
.bottom-space {
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
/* 动画 */
|
||||
.page-transition {
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20rpx);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #30D158 0%, #00E5FF 100%);
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 48rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 8rpx 24rpx rgba(48, 209, 88, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:active {
|
||||
opacity: 0.8;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
355
miniprogram/pages/match/match.js
Normal file
355
miniprogram/pages/match/match.js
Normal file
@@ -0,0 +1,355 @@
|
||||
// pages/match/match.js
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
isMatching: false,
|
||||
currentMatch: null,
|
||||
onlineCount: 0,
|
||||
matchAttempts: 0,
|
||||
recentMatches: [],
|
||||
matchTimer: null
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.initStarBackground()
|
||||
this.loadOnlineCount()
|
||||
this.loadRecentMatches()
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
// 清理匹配定时器
|
||||
if (this.data.matchTimer) {
|
||||
clearTimeout(this.data.matchTimer)
|
||||
}
|
||||
},
|
||||
|
||||
// 初始化星空背景
|
||||
initStarBackground() {
|
||||
const ctx = wx.createCanvasContext('starCanvas')
|
||||
const width = wx.getSystemInfoSync().windowWidth
|
||||
const height = wx.getSystemInfoSync().windowHeight
|
||||
|
||||
// 绘制星星
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const x = Math.random() * width
|
||||
const y = Math.random() * height
|
||||
const radius = Math.random() * 2
|
||||
const opacity = Math.random()
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.arc(x, y, radius, 0, 2 * Math.PI)
|
||||
ctx.fillStyle = `rgba(255, 255, 255, ${opacity})`
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
ctx.draw()
|
||||
},
|
||||
|
||||
// 加载在线人数
|
||||
loadOnlineCount() {
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase}/match/online-count`,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
this.setData({
|
||||
onlineCount: res.data.count || 0
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
// 使用模拟数据
|
||||
this.setData({
|
||||
onlineCount: Math.floor(Math.random() * 500) + 100
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 加载最近匹配记录
|
||||
loadRecentMatches() {
|
||||
const userInfo = app.getUserInfo()
|
||||
if (!userInfo) return
|
||||
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase}/match/recent`,
|
||||
header: {
|
||||
'Authorization': `Bearer ${wx.getStorageSync('token')}`
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
this.setData({
|
||||
recentMatches: res.data.matches || []
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
// 使用本地缓存
|
||||
const cached = wx.getStorageSync('recentMatches')
|
||||
if (cached) {
|
||||
this.setData({ recentMatches: cached })
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 开始匹配
|
||||
startMatch() {
|
||||
const userInfo = app.getUserInfo()
|
||||
|
||||
if (!userInfo) {
|
||||
this.showLoginModal()
|
||||
return
|
||||
}
|
||||
|
||||
this.setData({
|
||||
isMatching: true,
|
||||
matchAttempts: 0
|
||||
})
|
||||
|
||||
// 模拟匹配过程
|
||||
this.doMatch()
|
||||
},
|
||||
|
||||
// 执行匹配
|
||||
doMatch() {
|
||||
const timer = setInterval(() => {
|
||||
const attempts = this.data.matchAttempts + 1
|
||||
this.setData({ matchAttempts: attempts })
|
||||
|
||||
// 3-6秒后匹配成功
|
||||
if (attempts >= 3) {
|
||||
clearInterval(timer)
|
||||
this.matchSuccess()
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
this.setData({ matchTimer: timer })
|
||||
|
||||
// 真实匹配请求
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase}/match/find`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Authorization': `Bearer ${wx.getStorageSync('token')}`
|
||||
},
|
||||
data: {
|
||||
interests: ['创业', '私域运营', '读书'],
|
||||
currentChapter: app.globalData.currentChapter
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data.match) {
|
||||
clearInterval(timer)
|
||||
this.matchSuccess(res.data.match)
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
// 使用模拟数据
|
||||
clearInterval(timer)
|
||||
this.matchSuccess(this.getMockMatch())
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 匹配成功
|
||||
matchSuccess(matchData) {
|
||||
const match = matchData || this.getMockMatch()
|
||||
|
||||
this.setData({
|
||||
isMatching: false,
|
||||
currentMatch: match
|
||||
})
|
||||
|
||||
// 震动反馈
|
||||
wx.vibrateShort()
|
||||
|
||||
// 播放成功提示音
|
||||
wx.showToast({
|
||||
title: '匹配成功!',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
|
||||
// 保存到最近匹配
|
||||
this.saveRecentMatch(match)
|
||||
},
|
||||
|
||||
// 获取模拟匹配数据
|
||||
getMockMatch() {
|
||||
const nicknames = ['阅读爱好者', '创业小白', '私域达人', '书虫一枚', '灵魂摆渡人']
|
||||
const avatars = [
|
||||
'https://picsum.photos/200/200?random=1',
|
||||
'https://picsum.photos/200/200?random=2',
|
||||
'https://picsum.photos/200/200?random=3'
|
||||
]
|
||||
const tagsList = [
|
||||
['创业者', '私域运营', 'MBTI-INTP'],
|
||||
['读书达人', 'Soul用户', '内容创作'],
|
||||
['互联网人', '产品经理', '深度思考']
|
||||
]
|
||||
const concepts = [
|
||||
'一个坚持长期主义的私域玩家,擅长内容结构化。',
|
||||
'相信阅读可以改变人生,每天坚持读书1小时。',
|
||||
'在Soul上分享创业经验,希望帮助更多人少走弯路。'
|
||||
]
|
||||
const wechats = [
|
||||
'soul_book_friend_1',
|
||||
'soul_reader_2024',
|
||||
'soul_party_fan'
|
||||
]
|
||||
|
||||
const randomIndex = Math.floor(Math.random() * nicknames.length)
|
||||
|
||||
return {
|
||||
id: `user_${Date.now()}`,
|
||||
nickname: nicknames[randomIndex],
|
||||
avatar: avatars[randomIndex % avatars.length],
|
||||
tags: tagsList[randomIndex % tagsList.length],
|
||||
matchScore: Math.floor(Math.random() * 20) + 80,
|
||||
concept: concepts[randomIndex % concepts.length],
|
||||
wechat: wechats[randomIndex % wechats.length],
|
||||
commonInterests: [
|
||||
{ icon: '📚', text: '都在读《创业实验》' },
|
||||
{ icon: '💼', text: '对私域运营感兴趣' },
|
||||
{ icon: '🎯', text: '相似的职业背景' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// 保存最近匹配
|
||||
saveRecentMatch(match) {
|
||||
let recent = this.data.recentMatches
|
||||
const newMatch = {
|
||||
...match,
|
||||
matchTime: '刚刚'
|
||||
}
|
||||
|
||||
recent.unshift(newMatch)
|
||||
if (recent.length > 10) {
|
||||
recent = recent.slice(0, 10)
|
||||
}
|
||||
|
||||
this.setData({ recentMatches: recent })
|
||||
wx.setStorageSync('recentMatches', recent)
|
||||
},
|
||||
|
||||
// 取消匹配
|
||||
cancelMatch() {
|
||||
if (this.data.matchTimer) {
|
||||
clearTimeout(this.data.matchTimer)
|
||||
}
|
||||
|
||||
this.setData({
|
||||
isMatching: false,
|
||||
matchAttempts: 0
|
||||
})
|
||||
|
||||
wx.showToast({
|
||||
title: '已取消匹配',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
|
||||
// 一键加好友
|
||||
addWechat() {
|
||||
const match = this.data.currentMatch
|
||||
if (!match || !match.wechat) return
|
||||
|
||||
wx.setClipboardData({
|
||||
data: match.wechat,
|
||||
success: () => {
|
||||
wx.showModal({
|
||||
title: '微信号已复制',
|
||||
content: `微信号:${match.wechat}\n\n已复制到剪贴板,请打开微信添加好友,备注"书友"即可。`,
|
||||
showCancel: false,
|
||||
confirmText: '打开微信',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 尝试打开微信(小程序无法直接跳转,只能提示)
|
||||
wx.showToast({
|
||||
title: '请手动打开微信添加',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 加入书友群
|
||||
joinGroup() {
|
||||
wx.showModal({
|
||||
title: '加入书友群',
|
||||
content: '请先添加书友微信,备注"书友群",对方会拉你入群。\n\n群内可以:\n· 深度交流读书心得\n· 参加线下读书会\n· 获取独家资源',
|
||||
showCancel: true,
|
||||
cancelText: '取消',
|
||||
confirmText: '添加好友',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.addWechat()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 下一位
|
||||
nextMatch() {
|
||||
this.setData({
|
||||
currentMatch: null,
|
||||
matchAttempts: 0
|
||||
})
|
||||
|
||||
wx.showToast({
|
||||
title: '重新匹配中',
|
||||
icon: 'loading'
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
this.startMatch()
|
||||
}, 500)
|
||||
},
|
||||
|
||||
// 查看匹配详情
|
||||
viewMatchDetail(e) {
|
||||
const matchId = e.currentTarget.dataset.id
|
||||
wx.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
|
||||
// 显示登录弹窗
|
||||
showLoginModal() {
|
||||
wx.showModal({
|
||||
title: '需要登录',
|
||||
content: '匹配书友前需要先登录账号',
|
||||
confirmText: '立即登录',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
app.wxLogin((success) => {
|
||||
if (success) {
|
||||
wx.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success'
|
||||
})
|
||||
setTimeout(() => {
|
||||
this.startMatch()
|
||||
}, 1500)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 分享
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: '来Soul派对匹配志同道合的书友吧!',
|
||||
path: '/pages/match/match',
|
||||
imageUrl: '/assets/images/share-match.png'
|
||||
}
|
||||
}
|
||||
})
|
||||
128
miniprogram/pages/match/match.wxml
Normal file
128
miniprogram/pages/match/match.wxml
Normal file
@@ -0,0 +1,128 @@
|
||||
<!--pages/match/match.wxml-->
|
||||
<view class="container match-container page-transition">
|
||||
<!-- 星空背景效果 -->
|
||||
<canvas canvas-id="starCanvas" class="star-canvas"></canvas>
|
||||
|
||||
<!-- 顶部标题 -->
|
||||
<view class="match-header">
|
||||
<view class="match-title gradient-text">寻找合作伙伴</view>
|
||||
<view class="match-subtitle">找到和你一起创业的灵魂</view>
|
||||
</view>
|
||||
|
||||
<!-- 匹配状态区 -->
|
||||
<view class="match-status-area">
|
||||
<!-- 未匹配状态 -->
|
||||
<view class="match-idle" wx:if="{{!isMatching && !currentMatch}}">
|
||||
<!-- 中央大星球 -->
|
||||
<view class="center-planet" bindtap="startMatch">
|
||||
<view class="planet-gradient">
|
||||
<view class="planet-icon">
|
||||
<image class="icon-mic" src="/assets/icons/match.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
<view class="planet-text">开始匹配</view>
|
||||
<view class="planet-subtitle">寻找合作伙伴</view>
|
||||
</view>
|
||||
<view class="planet-ring"></view>
|
||||
</view>
|
||||
|
||||
<view class="match-tips">
|
||||
<view class="tip-item">💼 共同的创业方向</view>
|
||||
<view class="tip-item">💬 实时在线交流</view>
|
||||
<view class="tip-item">🎯 相似的商业洞察</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 匹配中状态 -->
|
||||
<view class="match-loading" wx:if="{{isMatching}}">
|
||||
<view class="loading-planet">
|
||||
<image class="rotating-planet" src="/assets/images/planet-match.png" mode="aspectFit"></image>
|
||||
<view class="loading-rings">
|
||||
<view class="ring ring-1"></view>
|
||||
<view class="ring ring-2"></view>
|
||||
<view class="ring ring-3"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="loading-text">正在寻找志同道合的书友...</view>
|
||||
<view class="loading-progress">已匹配 {{matchAttempts}} 次</view>
|
||||
|
||||
<button class="btn-secondary cancel-btn" bindtap="cancelMatch">
|
||||
取消匹配
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 匹配成功状态 -->
|
||||
<view class="match-success" wx:if="{{currentMatch && !isMatching}}">
|
||||
<view class="success-animation">
|
||||
<view class="success-icon">✨</view>
|
||||
</view>
|
||||
|
||||
<view class="match-user-card card">
|
||||
<image
|
||||
class="user-avatar"
|
||||
src="{{currentMatch.avatar}}"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
|
||||
<view class="user-info">
|
||||
<view class="user-name">{{currentMatch.nickname}}</view>
|
||||
<view class="user-tags">
|
||||
<text class="tag" wx:for="{{currentMatch.tags}}" wx:key="*this">
|
||||
{{item}}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="match-score">
|
||||
<view class="score-label">匹配度</view>
|
||||
<view class="score-value">{{currentMatch.matchScore}}%</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 共同兴趣 -->
|
||||
<view class="common-interests card">
|
||||
<view class="interests-title">共同兴趣</view>
|
||||
<view class="interests-list">
|
||||
<view class="interest-item" wx:for="{{currentMatch.commonInterests}}" wx:key="*this">
|
||||
<text class="interest-icon">{{item.icon}}</text>
|
||||
<text class="interest-text">{{item.text}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 核心理念 -->
|
||||
<view class="core-concept card">
|
||||
<view class="concept-title">核心理念</view>
|
||||
<view class="concept-text">{{currentMatch.concept || '一个坚持长期主义的私域玩家,擅长内容结构化。'}}</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="match-actions">
|
||||
<button class="btn-primary action-btn" bindtap="addWechat">
|
||||
<text class="btn-icon">➕</text>
|
||||
<text>一键加好友</text>
|
||||
</button>
|
||||
<button class="btn-primary action-btn" bindtap="joinGroup">
|
||||
<text class="btn-icon">👥</text>
|
||||
<text>加入书友群</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<button class="btn-secondary next-btn" bindtap="nextMatch">
|
||||
🔄 不喜欢?重新匹配
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最近匹配记录 -->
|
||||
<view class="recent-matches" wx:if="{{recentMatches.length > 0 && !isMatching}}">
|
||||
<view class="section-title">最近匹配</view>
|
||||
<scroll-view class="matches-scroll" scroll-x>
|
||||
<view class="match-item" wx:for="{{recentMatches}}" wx:key="id" bindtap="viewMatchDetail" data-id="{{item.id}}">
|
||||
<image class="match-avatar" src="{{item.avatar}}" mode="aspectFill"></image>
|
||||
<view class="match-name">{{item.nickname}}</view>
|
||||
<view class="match-time">{{item.matchTime}}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
469
miniprogram/pages/match/match.wxss
Normal file
469
miniprogram/pages/match/match.wxss
Normal file
@@ -0,0 +1,469 @@
|
||||
/* pages/match/match.wxss */
|
||||
|
||||
.match-container {
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 星空背景 */
|
||||
.star-canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
.match-header {
|
||||
padding: 80rpx 48rpx 40rpx;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.match-title {
|
||||
font-size: 56rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.match-subtitle {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
/* 匹配状态区 */
|
||||
.match-status-area {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 40rpx 32rpx;
|
||||
min-height: 800rpx;
|
||||
}
|
||||
|
||||
/* 未匹配状态 */
|
||||
.match-idle {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
animation: fadeIn 0.5s ease-in-out;
|
||||
padding-top: 60rpx;
|
||||
}
|
||||
|
||||
/* 中央大星球 */
|
||||
.center-planet {
|
||||
position: relative;
|
||||
width: 460rpx;
|
||||
height: 460rpx;
|
||||
margin-bottom: 80rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.planet-gradient {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #00E5FF 0%, #7B61FF 50%, #E91E63 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
box-shadow:
|
||||
0 0 60rpx rgba(0, 229, 255, 0.4),
|
||||
0 0 120rpx rgba(123, 97, 255, 0.3),
|
||||
inset 0 0 80rpx rgba(255, 255, 255, 0.1);
|
||||
animation: planetFloat 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes planetFloat {
|
||||
0%, 100% {
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-20rpx) scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
.planet-ring {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 540rpx;
|
||||
height: 540rpx;
|
||||
border: 3rpx solid rgba(0, 229, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
z-index: 1;
|
||||
animation: ringPulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes ringPulse {
|
||||
0%, 100% {
|
||||
opacity: 0.3;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
transform: translate(-50%, -50%) scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.planet-icon {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon-mic {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
.planet-text {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
margin-bottom: 12rpx;
|
||||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.planet-subtitle {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* 匹配提示 */
|
||||
.match-tips {
|
||||
margin-bottom: 60rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.tip-item {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 匹配中状态 */
|
||||
.match-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
animation: fadeIn 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.loading-planet {
|
||||
position: relative;
|
||||
width: 400rpx;
|
||||
height: 400rpx;
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.rotating-planet {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
animation: rotate 3s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-rings {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.ring {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border: 4rpx solid rgba(255, 77, 79, 0.3);
|
||||
border-radius: 50%;
|
||||
animation: ringExpand 2s ease-out infinite;
|
||||
}
|
||||
|
||||
.ring-1 {
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.ring-2 {
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
.ring-3 {
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
|
||||
@keyframes ringExpand {
|
||||
0% {
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
width: 600rpx;
|
||||
height: 600rpx;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 32rpx;
|
||||
color: #ffffff;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.loading-progress {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
width: 320rpx;
|
||||
}
|
||||
|
||||
/* 匹配成功状态 */
|
||||
.match-success {
|
||||
animation: fadeIn 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.success-animation {
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
animation: bounceIn 0.6s ease-out;
|
||||
}
|
||||
|
||||
@keyframes bounceIn {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
font-size: 120rpx;
|
||||
animation: sparkle 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes sparkle {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.match-user-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 60rpx;
|
||||
margin-right: 24rpx;
|
||||
border: 4rpx solid rgba(255, 77, 79, 0.5);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.user-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 8rpx 16rpx;
|
||||
background: rgba(255, 77, 79, 0.2);
|
||||
color: #FF7875;
|
||||
font-size: 22rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.match-score {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.score-label {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.score-value {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #FF4D4F;
|
||||
}
|
||||
|
||||
/* 共同兴趣 */
|
||||
.common-interests {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.interests-title {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.interests-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.interest-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 16rpx;
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.interest-icon {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
/* 核心理念 */
|
||||
.core-concept {
|
||||
margin-bottom: 32rpx;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.concept-title {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.concept-text {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.match-actions {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.next-btn {
|
||||
width: 100%;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 最近匹配 */
|
||||
.recent-matches {
|
||||
padding: 40rpx 32rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.matches-scroll {
|
||||
white-space: nowrap;
|
||||
height: 200rpx;
|
||||
}
|
||||
|
||||
.match-item {
|
||||
display: inline-block;
|
||||
width: 160rpx;
|
||||
margin-right: 24rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.match-avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 60rpx;
|
||||
margin-bottom: 12rpx;
|
||||
border: 3rpx solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.match-name {
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
margin-bottom: 4rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.match-time {
|
||||
font-size: 20rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
452
miniprogram/pages/my/my.js
Normal file
452
miniprogram/pages/my/my.js
Normal file
@@ -0,0 +1,452 @@
|
||||
// pages/my/my.js
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
userInfo: {},
|
||||
userStats: {
|
||||
readChapters: 0,
|
||||
readMinutes: 0,
|
||||
bookmarks: 0
|
||||
},
|
||||
earnings: {
|
||||
total: '0.00',
|
||||
available: '0.00',
|
||||
withdrawn: '0.00'
|
||||
},
|
||||
referralData: {
|
||||
totalUsers: 0,
|
||||
totalOrders: 0,
|
||||
commissionRate: 90
|
||||
},
|
||||
menuBadges: {
|
||||
orders: 0
|
||||
},
|
||||
showPoster: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
// 检查是否有tab参数
|
||||
if (options.tab === 'referral') {
|
||||
// 自动展开分销中心
|
||||
this.setData({ expandReferral: true })
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadUserInfo()
|
||||
this.loadUserStats()
|
||||
this.loadEarnings()
|
||||
this.loadReferralData()
|
||||
},
|
||||
|
||||
// 加载用户信息
|
||||
loadUserInfo() {
|
||||
const userInfo = app.getUserInfo()
|
||||
|
||||
if (userInfo) {
|
||||
this.setData({ userInfo })
|
||||
} else {
|
||||
// 未登录状态
|
||||
this.setData({
|
||||
userInfo: {
|
||||
avatar: '',
|
||||
nickname: '点击登录',
|
||||
id: '',
|
||||
inviteCode: ''
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 加载用户统计
|
||||
loadUserStats() {
|
||||
const token = wx.getStorageSync('token')
|
||||
if (!token) return
|
||||
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase}/user/stats`,
|
||||
header: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
this.setData({
|
||||
userStats: res.data.stats
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
// 使用缓存数据
|
||||
const cached = wx.getStorageSync('userStats')
|
||||
if (cached) {
|
||||
this.setData({ userStats: cached })
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 加载收益数据
|
||||
loadEarnings() {
|
||||
const token = wx.getStorageSync('token')
|
||||
if (!token) return
|
||||
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase}/referral/earnings`,
|
||||
header: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
this.setData({
|
||||
earnings: {
|
||||
total: res.data.total || '0.00',
|
||||
available: res.data.available || '0.00',
|
||||
withdrawn: res.data.withdrawn || '0.00'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 加载推广数据
|
||||
loadReferralData() {
|
||||
const token = wx.getStorageSync('token')
|
||||
if (!token) return
|
||||
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase}/referral/stats`,
|
||||
header: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
this.setData({
|
||||
referralData: res.data
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 编辑资料
|
||||
editProfile() {
|
||||
const userInfo = this.data.userInfo
|
||||
|
||||
if (!userInfo.id) {
|
||||
// 未登录,执行登录
|
||||
this.doLogin()
|
||||
return
|
||||
}
|
||||
|
||||
wx.navigateTo({
|
||||
url: '/pages/profile/edit'
|
||||
})
|
||||
},
|
||||
|
||||
// 执行登录
|
||||
doLogin() {
|
||||
wx.showLoading({ title: '登录中...', mask: true })
|
||||
|
||||
app.wxLogin((success, user) => {
|
||||
wx.hideLoading()
|
||||
|
||||
if (success) {
|
||||
wx.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success'
|
||||
})
|
||||
this.setData({ userInfo: user })
|
||||
this.loadUserStats()
|
||||
this.loadEarnings()
|
||||
this.loadReferralData()
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '登录失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 生成推广海报
|
||||
generatePoster() {
|
||||
const userInfo = this.data.userInfo
|
||||
|
||||
if (!userInfo.id) {
|
||||
this.showLoginModal()
|
||||
return
|
||||
}
|
||||
|
||||
this.setData({ showPoster: true })
|
||||
|
||||
wx.showLoading({ title: '生成中...', mask: true })
|
||||
|
||||
// 使用Canvas绘制海报
|
||||
setTimeout(() => {
|
||||
this.drawPoster()
|
||||
wx.hideLoading()
|
||||
}, 500)
|
||||
},
|
||||
|
||||
// 绘制海报
|
||||
drawPoster() {
|
||||
const ctx = wx.createCanvasContext('posterCanvas')
|
||||
const userInfo = this.data.userInfo
|
||||
|
||||
// 背景
|
||||
ctx.setFillStyle('#000000')
|
||||
ctx.fillRect(0, 0, 375, 500)
|
||||
|
||||
// 渐变背景
|
||||
const gradient = ctx.createLinearGradient(0, 0, 0, 500)
|
||||
gradient.addColorStop(0, 'rgba(255, 77, 79, 0.3)')
|
||||
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)')
|
||||
ctx.setFillStyle(gradient)
|
||||
ctx.fillRect(0, 0, 375, 500)
|
||||
|
||||
// 标题
|
||||
ctx.setFontSize(32)
|
||||
ctx.setFillStyle('#FFFFFF')
|
||||
ctx.setTextAlign('center')
|
||||
ctx.fillText('Soul派对·创业实验', 187.5, 60)
|
||||
|
||||
// 邀请码
|
||||
ctx.setFontSize(48)
|
||||
ctx.setFillStyle('#FF4D4F')
|
||||
ctx.fillText(userInfo.inviteCode || 'XXXXXX', 187.5, 250)
|
||||
|
||||
// 提示文字
|
||||
ctx.setFontSize(24)
|
||||
ctx.setFillStyle('rgba(255, 255, 255, 0.8)')
|
||||
ctx.fillText('使用此邀请码购买,双方都有佣金!', 187.5, 320)
|
||||
|
||||
// 二维码占位
|
||||
ctx.setStrokeStyle('#FFFFFF')
|
||||
ctx.strokeRect(137.5, 360, 100, 100)
|
||||
ctx.setFontSize(16)
|
||||
ctx.setFillStyle('rgba(255, 255, 255, 0.5)')
|
||||
ctx.fillText('扫码阅读', 187.5, 420)
|
||||
|
||||
ctx.draw()
|
||||
},
|
||||
|
||||
// 关闭海报
|
||||
closePoster() {
|
||||
this.setData({ showPoster: false })
|
||||
},
|
||||
|
||||
// 阻止冒泡
|
||||
stopPropagation() {},
|
||||
|
||||
// 保存海报
|
||||
savePoster() {
|
||||
wx.canvasToTempFilePath({
|
||||
canvasId: 'posterCanvas',
|
||||
success: (res) => {
|
||||
wx.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: () => {
|
||||
wx.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
})
|
||||
this.closePoster()
|
||||
},
|
||||
fail: () => {
|
||||
wx.showToast({
|
||||
title: '保存失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 提现
|
||||
withdraw() {
|
||||
const earnings = this.data.earnings
|
||||
const available = parseFloat(earnings.available)
|
||||
|
||||
if (available < 1) {
|
||||
wx.showToast({
|
||||
title: '可提现金额不足1元',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
wx.navigateTo({
|
||||
url: `/pages/withdraw/withdraw?amount=${available}`
|
||||
})
|
||||
},
|
||||
|
||||
// 复制邀请码
|
||||
copyInviteCode() {
|
||||
const inviteCode = this.data.userInfo.inviteCode
|
||||
|
||||
if (!inviteCode) {
|
||||
wx.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
wx.setClipboardData({
|
||||
data: inviteCode,
|
||||
success: () => {
|
||||
wx.showToast({
|
||||
title: '已复制邀请码',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 查看推荐列表
|
||||
viewReferrals() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/referral/list'
|
||||
})
|
||||
},
|
||||
|
||||
// 查看订单列表
|
||||
viewOrders() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/referral/orders'
|
||||
})
|
||||
},
|
||||
|
||||
// 查看佣金明细
|
||||
viewCommission() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/referral/commission'
|
||||
})
|
||||
},
|
||||
|
||||
// 我的订单
|
||||
goToOrders() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/orders/list'
|
||||
})
|
||||
},
|
||||
|
||||
// 阅读历史
|
||||
goToReadHistory() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/history/read'
|
||||
})
|
||||
},
|
||||
|
||||
// 阅读时长
|
||||
goToReadTime() {
|
||||
wx.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
|
||||
// 书签
|
||||
goToBookmarks() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/bookmarks/list'
|
||||
})
|
||||
},
|
||||
|
||||
// 阅读笔记
|
||||
goToNotes() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/notes/list'
|
||||
})
|
||||
},
|
||||
|
||||
// 设置
|
||||
goToSettings() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/settings/index'
|
||||
})
|
||||
},
|
||||
|
||||
// 联系客服
|
||||
contactSupport() {
|
||||
wx.showToast({
|
||||
title: '客服功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
|
||||
// 关于我们
|
||||
about() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/about/index'
|
||||
})
|
||||
},
|
||||
|
||||
// 退出登录
|
||||
logout() {
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
wx.removeStorageSync('token')
|
||||
wx.removeStorageSync('userInfo')
|
||||
app.globalData.userInfo = null
|
||||
|
||||
this.setData({
|
||||
userInfo: {
|
||||
avatar: '',
|
||||
nickname: '点击登录',
|
||||
id: '',
|
||||
inviteCode: ''
|
||||
},
|
||||
earnings: {
|
||||
total: '0.00',
|
||||
available: '0.00',
|
||||
withdrawn: '0.00'
|
||||
},
|
||||
referralData: {
|
||||
totalUsers: 0,
|
||||
totalOrders: 0,
|
||||
commissionRate: 90
|
||||
}
|
||||
})
|
||||
|
||||
wx.showToast({
|
||||
title: '已退出登录',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 显示登录弹窗
|
||||
showLoginModal() {
|
||||
wx.showModal({
|
||||
title: '需要登录',
|
||||
content: '请先登录账号',
|
||||
confirmText: '立即登录',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.doLogin()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() {
|
||||
this.loadUserInfo()
|
||||
this.loadUserStats()
|
||||
this.loadEarnings()
|
||||
this.loadReferralData()
|
||||
|
||||
setTimeout(() => {
|
||||
wx.stopPullDownRefresh()
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
204
miniprogram/pages/my/my.wxml
Normal file
204
miniprogram/pages/my/my.wxml
Normal file
@@ -0,0 +1,204 @@
|
||||
<!--pages/my/my.wxml-->
|
||||
<view class="container my-container page-transition">
|
||||
<!-- 用户信息卡片 -->
|
||||
<view class="user-card glass-effect">
|
||||
<view class="user-header">
|
||||
<image
|
||||
class="user-avatar"
|
||||
src="{{userInfo.avatar || '/assets/images/default-avatar.png'}}"
|
||||
mode="aspectFill"
|
||||
bindtap="editProfile"
|
||||
></image>
|
||||
<view class="user-info">
|
||||
<view class="user-name">{{userInfo.nickname || '点击登录'}}</view>
|
||||
<view class="user-id">ID: {{userInfo.id || '---'}}</view>
|
||||
</view>
|
||||
<view class="vip-badge" wx:if="{{userInfo.isPurchased}}">
|
||||
<text class="vip-text">已购</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 阅读统计 -->
|
||||
<view class="user-stats">
|
||||
<view class="stat-item" bindtap="goToReadHistory">
|
||||
<view class="stat-value">{{userStats.readChapters}}</view>
|
||||
<view class="stat-label">已读章节</view>
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
<view class="stat-item" bindtap="goToReadTime">
|
||||
<view class="stat-value">{{userStats.readMinutes}}</view>
|
||||
<view class="stat-label">阅读时长(分)</view>
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
<view class="stat-item" bindtap="goToBookmarks">
|
||||
<view class="stat-value">{{userStats.bookmarks}}</view>
|
||||
<view class="stat-label">书签</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分销中心(重点功能) -->
|
||||
<view class="referral-section card">
|
||||
<view class="section-header">
|
||||
<view class="section-title">
|
||||
<text class="title-icon">💰</text>
|
||||
<text class="title-text">分销中心</text>
|
||||
</view>
|
||||
<view class="referral-status">
|
||||
<text class="status-text">佣金比例:</text>
|
||||
<text class="status-value">90%</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 收益概览 -->
|
||||
<view class="earnings-overview">
|
||||
<view class="earnings-main">
|
||||
<view class="earnings-label">累计收益</view>
|
||||
<view class="earnings-amount">¥{{earnings.total}}</view>
|
||||
</view>
|
||||
<view class="earnings-sub">
|
||||
<view class="sub-item">
|
||||
<text class="sub-label">可提现</text>
|
||||
<text class="sub-value brand-color">¥{{earnings.available}}</text>
|
||||
</view>
|
||||
<view class="sub-item">
|
||||
<text class="sub-label">已提现</text>
|
||||
<text class="sub-value">¥{{earnings.withdrawn}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 快速操作 -->
|
||||
<view class="referral-actions">
|
||||
<button class="btn-primary action-btn" bindtap="generatePoster">
|
||||
生成推广海报
|
||||
</button>
|
||||
<button class="btn-secondary action-btn" bindtap="withdraw">
|
||||
立即提现
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 推广数据 -->
|
||||
<view class="referral-stats">
|
||||
<view class="referral-stat-item" bindtap="viewReferrals">
|
||||
<view class="stat-number">{{referralData.totalUsers}}</view>
|
||||
<view class="stat-name">推荐人数</view>
|
||||
</view>
|
||||
<view class="referral-stat-item" bindtap="viewOrders">
|
||||
<view class="stat-number">{{referralData.totalOrders}}</view>
|
||||
<view class="stat-name">成交订单</view>
|
||||
</view>
|
||||
<view class="referral-stat-item" bindtap="viewCommission">
|
||||
<view class="stat-number">{{referralData.commissionRate}}%</view>
|
||||
<view class="stat-name">佣金率</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 我的邀请码 -->
|
||||
<view class="invite-code-section">
|
||||
<view class="invite-label">我的邀请码</view>
|
||||
<view class="invite-code-box">
|
||||
<text class="invite-code">{{userInfo.inviteCode || '---'}}</text>
|
||||
<button class="copy-btn" bindtap="copyInviteCode">复制</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能菜单 -->
|
||||
<view class="menu-section">
|
||||
<view class="menu-group card">
|
||||
<view class="menu-item" bindtap="goToOrders">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">📦</text>
|
||||
<text class="menu-text">我的订单</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<text class="menu-badge" wx:if="{{menuBadges.orders > 0}}">{{menuBadges.orders}}</text>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-divider"></view>
|
||||
|
||||
<view class="menu-item" bindtap="goToBookmarks">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">🔖</text>
|
||||
<text class="menu-text">我的书签</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-divider"></view>
|
||||
|
||||
<view class="menu-item" bindtap="goToNotes">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">📝</text>
|
||||
<text class="menu-text">阅读笔记</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-group card">
|
||||
<view class="menu-item" bindtap="goToSettings">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">⚙️</text>
|
||||
<text class="menu-text">设置</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-divider"></view>
|
||||
|
||||
<view class="menu-item" bindtap="contactSupport">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">💬</text>
|
||||
<text class="menu-text">联系客服</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-divider"></view>
|
||||
|
||||
<view class="menu-item" bindtap="about">
|
||||
<view class="menu-left">
|
||||
<text class="menu-icon">ℹ️</text>
|
||||
<text class="menu-text">关于我们</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录 -->
|
||||
<view class="logout-section" wx:if="{{userInfo.id}}">
|
||||
<button class="logout-btn" bindtap="logout">退出登录</button>
|
||||
</view>
|
||||
|
||||
<!-- 底部留白 -->
|
||||
<view class="bottom-space"></view>
|
||||
</view>
|
||||
|
||||
<!-- 海报生成弹窗 -->
|
||||
<view class="poster-modal" wx:if="{{showPoster}}" bindtap="closePoster">
|
||||
<view class="poster-content" catchtap="stopPropagation">
|
||||
<view class="poster-header">
|
||||
<text class="poster-title">长按保存海报</text>
|
||||
<text class="poster-close" bindtap="closePoster">×</text>
|
||||
</view>
|
||||
<canvas canvas-id="posterCanvas" class="poster-canvas"></canvas>
|
||||
<button class="btn-primary save-poster-btn" bindtap="savePoster">
|
||||
保存到相册
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
422
miniprogram/pages/my/my.wxss
Normal file
422
miniprogram/pages/my/my.wxss
Normal file
@@ -0,0 +1,422 @@
|
||||
/* pages/my/my.wxss */
|
||||
|
||||
.my-container {
|
||||
padding: 32rpx 32rpx 160rpx;
|
||||
background: linear-gradient(180deg, #000000 0%, #0a0a0a 100%);
|
||||
}
|
||||
|
||||
/* 用户卡片 */
|
||||
.user-card {
|
||||
padding: 40rpx 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
border-radius: 32rpx;
|
||||
}
|
||||
|
||||
.user-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 60rpx;
|
||||
margin-right: 24rpx;
|
||||
border: 4rpx solid rgba(255, 77, 79, 0.5);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.user-id {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.vip-badge {
|
||||
padding: 8rpx 20rpx;
|
||||
background: linear-gradient(135deg, #FF4D4F 0%, #FF7875 100%);
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 77, 79, 0.4);
|
||||
}
|
||||
|
||||
.vip-text {
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 用户统计 */
|
||||
.user-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 32rpx;
|
||||
border-top: 2rpx solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #FF4D4F;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.stat-divider {
|
||||
width: 2rpx;
|
||||
height: 60rpx;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* 分销中心 */
|
||||
.referral-section {
|
||||
margin-bottom: 24rpx;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.referral-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 8rpx 20rpx;
|
||||
background: rgba(255, 77, 79, 0.2);
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.status-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #FF4D4F;
|
||||
}
|
||||
|
||||
/* 收益概览 */
|
||||
.earnings-overview {
|
||||
padding: 32rpx;
|
||||
background: rgba(255, 77, 79, 0.1);
|
||||
border-radius: 24rpx;
|
||||
border: 2rpx solid rgba(255, 77, 79, 0.2);
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.earnings-main {
|
||||
text-align: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.earnings-label {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.earnings-amount {
|
||||
font-size: 64rpx;
|
||||
font-weight: 700;
|
||||
color: #FF4D4F;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.earnings-sub {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding-top: 24rpx;
|
||||
border-top: 2rpx solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.sub-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.sub-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.sub-value {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 快速操作 */
|
||||
.referral-actions {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
/* 推广数据 */
|
||||
.referral-stats {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 32rpx 0;
|
||||
border-top: 2rpx solid rgba(255, 255, 255, 0.1);
|
||||
border-bottom: 2rpx solid rgba(255, 255, 255, 0.1);
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.referral-stat-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #FF4D4F;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.stat-name {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
/* 邀请码 */
|
||||
.invite-code-section {
|
||||
padding: 24rpx;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.invite-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.invite-code-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.invite-code {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #FF4D4F;
|
||||
letter-spacing: 4rpx;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
padding: 12rpx 32rpx;
|
||||
background: rgba(255, 77, 79, 0.2);
|
||||
color: #FF7875;
|
||||
border: none;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 功能菜单 */
|
||||
.menu-section {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.menu-group {
|
||||
padding: 0;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 32rpx;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.menu-item:active {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.menu-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
font-size: 30rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.menu-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.menu-badge {
|
||||
min-width: 32rpx;
|
||||
height: 32rpx;
|
||||
padding: 0 8rpx;
|
||||
background: #FF4D4F;
|
||||
border-radius: 16rpx;
|
||||
font-size: 20rpx;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.menu-arrow {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.menu-divider {
|
||||
height: 2rpx;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
margin: 0 32rpx;
|
||||
}
|
||||
|
||||
/* 退出登录 */
|
||||
.logout-section {
|
||||
padding: 0 32rpx;
|
||||
margin-top: 48rpx;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
width: 100%;
|
||||
padding: 28rpx;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 16rpx;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.logout-btn:active {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.bottom-space {
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
/* 海报弹窗 */
|
||||
.poster-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
animation: fadeIn 0.3s;
|
||||
}
|
||||
|
||||
.poster-content {
|
||||
width: 90%;
|
||||
max-width: 600rpx;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(20rpx);
|
||||
border-radius: 32rpx;
|
||||
padding: 32rpx;
|
||||
animation: slideUp 0.4s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(100rpx);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.poster-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.poster-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.poster-close {
|
||||
font-size: 56rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.poster-canvas {
|
||||
width: 100%;
|
||||
height: 800rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.save-poster-btn {
|
||||
width: 100%;
|
||||
}
|
||||
343
miniprogram/pages/read/read.js
Normal file
343
miniprogram/pages/read/read.js
Normal file
@@ -0,0 +1,343 @@
|
||||
// pages/read/read.js
|
||||
const app = getApp()
|
||||
const paymentUtil = require('../../utils/payment')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
chapterId: '',
|
||||
chapterInfo: {},
|
||||
contentHtml: '',
|
||||
loading: true,
|
||||
hasPrev: false,
|
||||
hasNext: false,
|
||||
isBookmarked: false,
|
||||
showCatalog: false,
|
||||
allChapters: []
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const chapterId = options.id
|
||||
if (chapterId) {
|
||||
this.setData({ chapterId })
|
||||
this.loadChapter(chapterId)
|
||||
this.loadAllChapters()
|
||||
this.checkBookmark(chapterId)
|
||||
}
|
||||
},
|
||||
|
||||
// 加载章节内容
|
||||
loadChapter(chapterId) {
|
||||
this.setData({ loading: true })
|
||||
|
||||
wx.showLoading({ title: '加载中...', mask: true })
|
||||
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase}/book/chapter/${chapterId}`,
|
||||
header: {
|
||||
'Authorization': `Bearer ${wx.getStorageSync('token')}`
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
const chapter = res.data
|
||||
|
||||
// 检查是否需要购买
|
||||
if (chapter.needPurchase && !this.checkPurchased()) {
|
||||
this.showPurchaseModal()
|
||||
return
|
||||
}
|
||||
|
||||
this.setData({
|
||||
chapterInfo: {
|
||||
title: chapter.title,
|
||||
updateTime: chapter.updateTime,
|
||||
words: chapter.words,
|
||||
readTime: Math.ceil(chapter.words / 300)
|
||||
},
|
||||
contentHtml: this.markdownToHtml(chapter.content),
|
||||
hasPrev: !!chapter.prevChapterId,
|
||||
hasNext: !!chapter.nextChapterId,
|
||||
loading: false
|
||||
})
|
||||
|
||||
// 记录阅读进度
|
||||
this.recordReadProgress(chapterId)
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '章节加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
// 使用Mock数据
|
||||
this.loadMockChapter(chapterId)
|
||||
},
|
||||
complete: () => {
|
||||
wx.hideLoading()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 加载Mock章节
|
||||
loadMockChapter(chapterId) {
|
||||
const mockContent = `
|
||||
# 这是章节标题
|
||||
|
||||
这是第一段内容,介绍了关于私域运营的基本概念...
|
||||
|
||||
## 第一小节
|
||||
|
||||
详细内容描述...
|
||||
|
||||
### 要点总结
|
||||
|
||||
1. 第一点
|
||||
2. 第二点
|
||||
3. 第三点
|
||||
|
||||
**重点强调的内容**
|
||||
|
||||
> 引用的内容或者金句
|
||||
`
|
||||
|
||||
this.setData({
|
||||
chapterInfo: {
|
||||
title: '第一章|我是谁',
|
||||
updateTime: '2天前',
|
||||
words: 3200,
|
||||
readTime: 11
|
||||
},
|
||||
contentHtml: this.markdownToHtml(mockContent),
|
||||
hasPrev: false,
|
||||
hasNext: true,
|
||||
loading: false
|
||||
})
|
||||
},
|
||||
|
||||
// Markdown转HTML(简单实现)
|
||||
markdownToHtml(markdown) {
|
||||
if (!markdown) return ''
|
||||
|
||||
let html = markdown
|
||||
.replace(/### (.*)/g, '<h3>$1</h3>')
|
||||
.replace(/## (.*)/g, '<h2>$1</h2>')
|
||||
.replace(/# (.*)/g, '<h1>$1</h1>')
|
||||
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
||||
.replace(/> (.*)/g, '<blockquote>$1</blockquote>')
|
||||
.replace(/\n/g, '<br/>')
|
||||
|
||||
return html
|
||||
},
|
||||
|
||||
// 检查是否已购买
|
||||
checkPurchased() {
|
||||
return paymentUtil.checkPurchaseStatus()
|
||||
},
|
||||
|
||||
// 显示购买弹窗
|
||||
showPurchaseModal() {
|
||||
wx.showModal({
|
||||
title: '需要购买',
|
||||
content: '此章节需要购买完整版才能阅读',
|
||||
confirmText: '立即购买',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.purchase()
|
||||
} else {
|
||||
wx.navigateBack()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 购买
|
||||
purchase() {
|
||||
paymentUtil.purchaseFullBook(
|
||||
() => {
|
||||
wx.showToast({
|
||||
title: '购买成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 重新加载章节
|
||||
setTimeout(() => {
|
||||
this.loadChapter(this.data.chapterId)
|
||||
}, 1500)
|
||||
},
|
||||
() => {
|
||||
wx.showToast({
|
||||
title: '购买失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
// 记录阅读进度
|
||||
recordReadProgress(chapterId) {
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase}/user/read-progress`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Authorization': `Bearer ${wx.getStorageSync('token')}`
|
||||
},
|
||||
data: {
|
||||
chapterId,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 加载所有章节
|
||||
loadAllChapters() {
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase}/book/chapters`,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
this.setData({
|
||||
allChapters: res.data.chapters || []
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 检查书签
|
||||
checkBookmark(chapterId) {
|
||||
const bookmarks = wx.getStorageSync('bookmarks') || []
|
||||
const isBookmarked = bookmarks.includes(chapterId)
|
||||
this.setData({ isBookmarked })
|
||||
},
|
||||
|
||||
// 上一章
|
||||
prevChapter() {
|
||||
if (!this.data.hasPrev) return
|
||||
|
||||
// TODO: 获取上一章ID
|
||||
wx.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
|
||||
// 下一章
|
||||
nextChapter() {
|
||||
if (!this.data.hasNext) return
|
||||
|
||||
// TODO: 获取下一章ID
|
||||
wx.showToast({
|
||||
title: '功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
|
||||
// 返回
|
||||
goBack() {
|
||||
wx.navigateBack()
|
||||
},
|
||||
|
||||
// 显示菜单
|
||||
showMenu() {
|
||||
wx.showActionSheet({
|
||||
itemList: ['调整字体', '夜间模式', '分享好友'],
|
||||
success: (res) => {
|
||||
switch(res.tapIndex) {
|
||||
case 0:
|
||||
this.adjustFont()
|
||||
break
|
||||
case 1:
|
||||
this.toggleNightMode()
|
||||
break
|
||||
case 2:
|
||||
this.share()
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 书签
|
||||
bookmark() {
|
||||
const chapterId = this.data.chapterId
|
||||
let bookmarks = wx.getStorageSync('bookmarks') || []
|
||||
|
||||
if (this.data.isBookmarked) {
|
||||
// 移除书签
|
||||
bookmarks = bookmarks.filter(id => id !== chapterId)
|
||||
wx.showToast({
|
||||
title: '已移除书签',
|
||||
icon: 'success'
|
||||
})
|
||||
} else {
|
||||
// 添加书签
|
||||
bookmarks.push(chapterId)
|
||||
wx.showToast({
|
||||
title: '已添加书签',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
|
||||
wx.setStorageSync('bookmarks', bookmarks)
|
||||
this.setData({
|
||||
isBookmarked: !this.data.isBookmarked
|
||||
})
|
||||
},
|
||||
|
||||
// 笔记
|
||||
note() {
|
||||
wx.navigateTo({
|
||||
url: `/pages/note/edit?chapterId=${this.data.chapterId}`
|
||||
})
|
||||
},
|
||||
|
||||
// 显示目录
|
||||
showCatalog() {
|
||||
this.setData({ showCatalog: true })
|
||||
},
|
||||
|
||||
// 隐藏目录
|
||||
hideCatalog() {
|
||||
this.setData({ showCatalog: false })
|
||||
},
|
||||
|
||||
// 选择章节
|
||||
selectChapter(e) {
|
||||
const chapterId = e.currentTarget.dataset.id
|
||||
this.setData({
|
||||
chapterId,
|
||||
showCatalog: false
|
||||
})
|
||||
this.loadChapter(chapterId)
|
||||
this.checkBookmark(chapterId)
|
||||
},
|
||||
|
||||
// 分享
|
||||
share() {
|
||||
wx.showShareMenu({
|
||||
withShareTicket: true,
|
||||
menus: ['shareAppMessage', 'shareTimeline']
|
||||
})
|
||||
},
|
||||
|
||||
// 跳转到推广页面
|
||||
goToReferral() {
|
||||
wx.switchTab({
|
||||
url: '/pages/my/my?tab=referral'
|
||||
})
|
||||
},
|
||||
|
||||
// 阻止冒泡
|
||||
stopPropagation() {},
|
||||
|
||||
// 分享给好友
|
||||
onShareAppMessage() {
|
||||
const userInfo = app.getUserInfo()
|
||||
const inviteCode = userInfo ? userInfo.inviteCode : ''
|
||||
|
||||
return {
|
||||
title: this.data.chapterInfo.title,
|
||||
path: `/pages/read/read?id=${this.data.chapterId}&invite=${inviteCode}`,
|
||||
imageUrl: '/assets/images/share-chapter.png'
|
||||
}
|
||||
}
|
||||
})
|
||||
112
miniprogram/pages/read/read.wxml
Normal file
112
miniprogram/pages/read/read.wxml
Normal file
@@ -0,0 +1,112 @@
|
||||
<!--pages/read/read.wxml-->
|
||||
<view class="container read-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="read-header">
|
||||
<view class="header-left" bindtap="goBack">
|
||||
<text class="back-icon">←</text>
|
||||
</view>
|
||||
<view class="header-title">{{chapterInfo.title}}</view>
|
||||
<view class="header-right" bindtap="showMenu">
|
||||
<text class="menu-icon">⋯</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 章节内容 -->
|
||||
<scroll-view class="content-scroll" scroll-y enhanced show-scrollbar="{{false}}">
|
||||
<!-- 骨架屏 -->
|
||||
<view class="content-skeleton" wx:if="{{loading}}">
|
||||
<view class="skeleton skeleton-title"></view>
|
||||
<view class="skeleton skeleton-line"></view>
|
||||
<view class="skeleton skeleton-line"></view>
|
||||
<view class="skeleton skeleton-line short"></view>
|
||||
<view class="skeleton skeleton-line"></view>
|
||||
<view class="skeleton skeleton-line"></view>
|
||||
</view>
|
||||
|
||||
<!-- 实际内容 -->
|
||||
<view class="content-wrapper" wx:if="{{!loading}}">
|
||||
<view class="chapter-title">{{chapterInfo.title}}</view>
|
||||
<view class="chapter-meta">
|
||||
<text class="meta-item">{{chapterInfo.updateTime}}</text>
|
||||
<text class="meta-divider">·</text>
|
||||
<text class="meta-item">{{chapterInfo.words}}字</text>
|
||||
<text class="meta-divider">·</text>
|
||||
<text class="meta-item">{{chapterInfo.readTime}}分钟</text>
|
||||
</view>
|
||||
|
||||
<view class="chapter-content markdown-body">
|
||||
<rich-text nodes="{{contentHtml}}"></rich-text>
|
||||
</view>
|
||||
|
||||
<!-- 章节导航 -->
|
||||
<view class="chapter-nav">
|
||||
<button
|
||||
class="nav-btn prev-btn {{!hasPrev ? 'disabled' : ''}}"
|
||||
bindtap="prevChapter"
|
||||
disabled="{{!hasPrev}}"
|
||||
>
|
||||
上一章
|
||||
</button>
|
||||
<button
|
||||
class="nav-btn next-btn {{!hasNext ? 'disabled' : ''}}"
|
||||
bindtap="nextChapter"
|
||||
disabled="{{!hasNext}}"
|
||||
>
|
||||
下一章
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 推广提示 -->
|
||||
<view class="promotion-tip card">
|
||||
<view class="tip-icon">💰</view>
|
||||
<view class="tip-content">
|
||||
<view class="tip-title">喜欢这本书?</view>
|
||||
<view class="tip-desc">分享给朋友,每笔成交您可获得90%佣金</view>
|
||||
</view>
|
||||
<button class="tip-btn" bindtap="goToReferral">去分享</button>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部工具栏 -->
|
||||
<view class="read-toolbar glass-effect">
|
||||
<view class="toolbar-item" bindtap="bookmark">
|
||||
<text class="toolbar-icon">{{isBookmarked ? '🔖' : '📑'}}</text>
|
||||
<text class="toolbar-label">书签</text>
|
||||
</view>
|
||||
<view class="toolbar-item" bindtap="note">
|
||||
<text class="toolbar-icon">📝</text>
|
||||
<text class="toolbar-label">笔记</text>
|
||||
</view>
|
||||
<view class="toolbar-item" bindtap="showCatalog">
|
||||
<text class="toolbar-icon">📚</text>
|
||||
<text class="toolbar-label">目录</text>
|
||||
</view>
|
||||
<view class="toolbar-item" bindtap="share">
|
||||
<text class="toolbar-icon">📤</text>
|
||||
<text class="toolbar-label">分享</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 目录弹窗 -->
|
||||
<view class="catalog-modal" wx:if="{{showCatalog}}" bindtap="hideCatalog">
|
||||
<view class="catalog-content" catchtap="stopPropagation">
|
||||
<view class="catalog-header">
|
||||
<text class="catalog-title">目录</text>
|
||||
<text class="catalog-close" bindtap="hideCatalog">×</text>
|
||||
</view>
|
||||
<scroll-view class="catalog-list" scroll-y>
|
||||
<view
|
||||
class="catalog-item {{item.id === chapterId ? 'active' : ''}}"
|
||||
wx:for="{{allChapters}}"
|
||||
wx:key="id"
|
||||
bindtap="selectChapter"
|
||||
data-id="{{item.id}}"
|
||||
>
|
||||
<text class="catalog-item-title">{{item.title}}</text>
|
||||
<text class="catalog-item-icon" wx:if="{{item.id === chapterId}}">📖</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
334
miniprogram/pages/read/read.wxss
Normal file
334
miniprogram/pages/read/read.wxss
Normal file
@@ -0,0 +1,334 @@
|
||||
/* pages/read/read.wxss */
|
||||
|
||||
.read-container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #000000;
|
||||
}
|
||||
|
||||
/* 顶部导航 */
|
||||
.read-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 32rpx;
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
backdrop-filter: blur(20rpx);
|
||||
border-bottom: 2rpx solid rgba(255, 255, 255, 0.1);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-left,
|
||||
.header-right {
|
||||
width: 80rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.back-icon,
|
||||
.menu-icon {
|
||||
font-size: 40rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #ffffff;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.content-scroll {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 48rpx 32rpx 120rpx;
|
||||
}
|
||||
|
||||
/* 骨架屏 */
|
||||
.content-skeleton {
|
||||
padding: 48rpx 32rpx;
|
||||
}
|
||||
|
||||
.skeleton-title {
|
||||
width: 80%;
|
||||
height: 60rpx;
|
||||
margin: 0 auto 40rpx;
|
||||
}
|
||||
|
||||
.skeleton-line {
|
||||
width: 100%;
|
||||
height: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.skeleton-line.short {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
/* 章节标题 */
|
||||
.chapter-title {
|
||||
font-size: 48rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
margin-bottom: 24rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.chapter-meta {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 48rpx;
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.meta-divider {
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 章节内容 */
|
||||
.chapter-content {
|
||||
font-size: 32rpx;
|
||||
line-height: 1.8;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
margin: 48rpx 0 24rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
margin: 40rpx 0 20rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
margin: 32rpx 0 16rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.markdown-body strong {
|
||||
color: #FF4D4F;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body em {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
padding: 24rpx 32rpx;
|
||||
margin: 32rpx 0;
|
||||
background: rgba(255, 77, 79, 0.1);
|
||||
border-left: 6rpx solid #FF4D4F;
|
||||
border-radius: 8rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/* 章节导航 */
|
||||
.chapter-nav {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin-top: 64rpx;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
flex: 1;
|
||||
padding: 28rpx;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #ffffff;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 16rpx;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.nav-btn.disabled {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* 推广提示 */
|
||||
.promotion-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
padding: 32rpx;
|
||||
margin-top: 32rpx;
|
||||
background: linear-gradient(135deg, rgba(255, 77, 79, 0.2) 0%, rgba(255, 120, 117, 0.1) 100%);
|
||||
border: 2rpx solid rgba(255, 77, 79, 0.3);
|
||||
}
|
||||
|
||||
.tip-icon {
|
||||
font-size: 64rpx;
|
||||
}
|
||||
|
||||
.tip-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tip-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.tip-desc {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.tip-btn {
|
||||
padding: 16rpx 32rpx;
|
||||
background: #FF4D4F;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
/* 底部工具栏 */
|
||||
.read-toolbar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 24rpx 32rpx;
|
||||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
border-top: 2rpx solid rgba(255, 255, 255, 0.1);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.toolbar-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.toolbar-icon {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.toolbar-label {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
/* 目录弹窗 */
|
||||
.catalog-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.catalog-content {
|
||||
width: 70%;
|
||||
height: 100%;
|
||||
background: #0a0a0a;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation: slideInRight 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.catalog-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 32rpx;
|
||||
border-bottom: 2rpx solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.catalog-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.catalog-close {
|
||||
font-size: 56rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.catalog-list {
|
||||
flex: 1;
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
|
||||
.catalog-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 32rpx;
|
||||
border-left: 4rpx solid transparent;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.catalog-item.active {
|
||||
background: rgba(255, 77, 79, 0.1);
|
||||
border-left-color: #FF4D4F;
|
||||
}
|
||||
|
||||
.catalog-item:active {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.catalog-item-title {
|
||||
font-size: 28rpx;
|
||||
color: #ffffff;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.catalog-item.active .catalog-item-title {
|
||||
color: #FF4D4F;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.catalog-item-icon {
|
||||
font-size: 32rpx;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
52
miniprogram/project.config.json
Normal file
52
miniprogram/project.config.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"description": "Soul派对·创业实验小程序",
|
||||
"packOptions": {
|
||||
"ignore": [],
|
||||
"include": []
|
||||
},
|
||||
"setting": {
|
||||
"bundle": false,
|
||||
"userConfirmedBundleSwitch": false,
|
||||
"urlCheck": true,
|
||||
"scopeDataCheck": false,
|
||||
"coverView": true,
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"compileHotReLoad": true,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"preloadBackgroundData": false,
|
||||
"minified": true,
|
||||
"autoAudits": false,
|
||||
"newFeature": false,
|
||||
"uglifyFileName": false,
|
||||
"uploadWithSourceMap": true,
|
||||
"useIsolateContext": true,
|
||||
"nodeModules": false,
|
||||
"enhance": true,
|
||||
"useMultiFrameRuntime": true,
|
||||
"useApiHook": true,
|
||||
"useApiHostProcess": true,
|
||||
"showShadowRootInWxmlPanel": true,
|
||||
"packNpmManually": false,
|
||||
"enableEngineNative": false,
|
||||
"packNpmRelationList": [],
|
||||
"minifyWXSS": true,
|
||||
"showES6CompileOption": false,
|
||||
"minifyWXML": true,
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
},
|
||||
"condition": false
|
||||
},
|
||||
"compileType": "miniprogram",
|
||||
"libVersion": "3.13.2",
|
||||
"appid": "wx0976665c3a3d5a7c",
|
||||
"projectname": "soul-party-book",
|
||||
"condition": {},
|
||||
"editorSetting": {
|
||||
"tabIndent": "insertSpaces",
|
||||
"tabSize": 2
|
||||
}
|
||||
}
|
||||
211
miniprogram/utils/payment.js
Normal file
211
miniprogram/utils/payment.js
Normal file
@@ -0,0 +1,211 @@
|
||||
// miniprogram/utils/payment.js
|
||||
// 微信支付工具类
|
||||
|
||||
const app = getApp()
|
||||
|
||||
/**
|
||||
* 发起微信支付
|
||||
* @param {Object} options - 支付选项
|
||||
* @param {String} options.orderId - 订单ID
|
||||
* @param {Number} options.amount - 支付金额(元)
|
||||
* @param {String} options.description - 商品描述
|
||||
* @param {Function} options.success - 成功回调
|
||||
* @param {Function} options.fail - 失败回调
|
||||
*/
|
||||
function wxPay(options) {
|
||||
const { orderId, amount, description, success, fail } = options
|
||||
|
||||
wx.showLoading({
|
||||
title: '正在支付...',
|
||||
mask: true
|
||||
})
|
||||
|
||||
// 1. 调用后端创建支付订单
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase}/payment/create`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Authorization': `Bearer ${wx.getStorageSync('token')}`
|
||||
},
|
||||
data: {
|
||||
orderId,
|
||||
amount,
|
||||
description,
|
||||
paymentMethod: 'wechat'
|
||||
},
|
||||
success: (res) => {
|
||||
wx.hideLoading()
|
||||
|
||||
if (res.statusCode === 200) {
|
||||
const paymentData = res.data
|
||||
|
||||
// 2. 调起微信支付
|
||||
wx.requestPayment({
|
||||
timeStamp: paymentData.timeStamp,
|
||||
nonceStr: paymentData.nonceStr,
|
||||
package: paymentData.package,
|
||||
signType: paymentData.signType || 'RSA',
|
||||
paySign: paymentData.paySign,
|
||||
success: (payRes) => {
|
||||
console.log('支付成功', payRes)
|
||||
|
||||
// 3. 通知后端支付成功
|
||||
notifyPaymentSuccess(orderId, paymentData.prepayId)
|
||||
|
||||
wx.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
|
||||
success && success(payRes)
|
||||
},
|
||||
fail: (payErr) => {
|
||||
console.error('支付失败', payErr)
|
||||
|
||||
if (payErr.errMsg.indexOf('cancel') !== -1) {
|
||||
wx.showToast({
|
||||
title: '支付已取消',
|
||||
icon: 'none'
|
||||
})
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '支付失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
fail && fail(payErr)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: res.data.message || '创建订单失败',
|
||||
icon: 'none'
|
||||
})
|
||||
fail && fail(res)
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
wx.hideLoading()
|
||||
console.error('请求失败', err)
|
||||
|
||||
wx.showToast({
|
||||
title: '网络请求失败',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
fail && fail(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知后端支付成功
|
||||
* @param {String} orderId
|
||||
* @param {String} prepayId
|
||||
*/
|
||||
function notifyPaymentSuccess(orderId, prepayId) {
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase}/payment/notify`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Authorization': `Bearer ${wx.getStorageSync('token')}`
|
||||
},
|
||||
data: {
|
||||
orderId,
|
||||
prepayId,
|
||||
status: 'success'
|
||||
},
|
||||
success: (res) => {
|
||||
console.log('支付通知成功', res)
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('支付通知失败', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询订单状态
|
||||
* @param {String} orderId
|
||||
* @param {Function} callback
|
||||
*/
|
||||
function queryOrderStatus(orderId, callback) {
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase}/payment/query`,
|
||||
method: 'GET',
|
||||
header: {
|
||||
'Authorization': `Bearer ${wx.getStorageSync('token')}`
|
||||
},
|
||||
data: { orderId },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
callback && callback(true, res.data)
|
||||
} else {
|
||||
callback && callback(false, null)
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
callback && callback(false, null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 购买完整电子书
|
||||
* @param {Function} success
|
||||
* @param {Function} fail
|
||||
*/
|
||||
function purchaseFullBook(success, fail) {
|
||||
// 计算动态价格:9.9 + (天数 * 1元)
|
||||
const basePrice = 9.9
|
||||
const startDate = new Date('2025-01-01') // 书籍上架日期
|
||||
const today = new Date()
|
||||
const daysPassed = Math.floor((today - startDate) / (1000 * 60 * 60 * 24))
|
||||
const currentPrice = basePrice + daysPassed
|
||||
|
||||
const orderId = `ORDER_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
|
||||
wxPay({
|
||||
orderId,
|
||||
amount: currentPrice,
|
||||
description: 'Soul派对·创业实验 完整版',
|
||||
success: (res) => {
|
||||
// 更新本地购买状态
|
||||
updatePurchaseStatus(true)
|
||||
success && success(res)
|
||||
},
|
||||
fail
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新购买状态
|
||||
* @param {Boolean} isPurchased
|
||||
*/
|
||||
function updatePurchaseStatus(isPurchased) {
|
||||
const userInfo = app.getUserInfo()
|
||||
if (userInfo) {
|
||||
userInfo.isPurchased = isPurchased
|
||||
wx.setStorageSync('userInfo', userInfo)
|
||||
app.globalData.userInfo = userInfo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已购买
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
function checkPurchaseStatus() {
|
||||
const userInfo = app.getUserInfo()
|
||||
return userInfo ? userInfo.isPurchased : false
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
wxPay,
|
||||
queryOrderStatus,
|
||||
purchaseFullBook,
|
||||
checkPurchaseStatus,
|
||||
updatePurchaseStatus
|
||||
}
|
||||
273
miniprogram/小程序快速配置指南.md
Normal file
273
miniprogram/小程序快速配置指南.md
Normal file
@@ -0,0 +1,273 @@
|
||||
# 小程序快速配置指南 ⚡
|
||||
|
||||
> 5分钟内完成配置,快速开始开发!
|
||||
|
||||
## 🎯 配置前准备
|
||||
|
||||
- ✅ 已安装微信开发者工具
|
||||
- ✅ 已有小程序AppID(或使用测试AppID)
|
||||
- ✅ 后端API服务器已启动
|
||||
|
||||
## 📝 必须配置的3个地方
|
||||
|
||||
### 1️⃣ 配置小程序AppID
|
||||
|
||||
**文件**: `project.config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"appid": "你的小程序AppID", // ⬅️ 改这里
|
||||
"projectname": "soul-party-book"
|
||||
}
|
||||
```
|
||||
|
||||
> 💡 没有AppID?使用测试号:`wxd7e8c8a8e8c8a8e8`
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣ 配置API服务器地址
|
||||
|
||||
**文件**: `app.js`
|
||||
|
||||
```javascript
|
||||
globalData: {
|
||||
apiBase: 'http://localhost:3000/api', // ⬅️ 改这里
|
||||
// 本地开发: http://localhost:3000/api
|
||||
// 线上环境: https://your-domain.com/api
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3️⃣ 配置服务器域名(线上部署时)
|
||||
|
||||
登录[小程序后台](https://mp.weixin.qq.com/):
|
||||
|
||||
开发管理 → 开发设置 → 服务器域名
|
||||
|
||||
```
|
||||
request合法域名:
|
||||
https://your-domain.com
|
||||
|
||||
uploadFile合法域名:
|
||||
https://your-domain.com
|
||||
|
||||
downloadFile合法域名:
|
||||
https://your-domain.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 启动步骤
|
||||
|
||||
### 第一步:启动后端服务器
|
||||
|
||||
在项目根目录运行:
|
||||
|
||||
```bash
|
||||
# Mac/Linux
|
||||
chmod +x start-miniprogram.sh
|
||||
./start-miniprogram.sh
|
||||
|
||||
# Windows
|
||||
npm run dev
|
||||
# 或
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
看到以下信息表示成功:
|
||||
|
||||
```
|
||||
✓ Ready in 2.3s
|
||||
○ Local: http://localhost:3000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 第二步:打开微信开发者工具
|
||||
|
||||
1. 点击"导入项目"
|
||||
2. 选择 `miniprogram` 文件夹
|
||||
3. 填入AppID(或选择测试号)
|
||||
4. 点击"导入"
|
||||
|
||||
---
|
||||
|
||||
### 第三步:点击编译
|
||||
|
||||
点击工具栏的"编译"按钮,等待编译完成。
|
||||
|
||||
---
|
||||
|
||||
### 第四步:开始开发!🎉
|
||||
|
||||
现在你可以:
|
||||
|
||||
- 👀 在模拟器中查看效果
|
||||
- 📱 扫码在真机预览
|
||||
- 🔧 修改代码实时刷新
|
||||
- 📊 查看Network请求
|
||||
|
||||
---
|
||||
|
||||
## 🧪 功能测试清单
|
||||
|
||||
### ✅ 首页测试
|
||||
|
||||
- [ ] 书籍封面正常显示
|
||||
- [ ] 最新章节列表加载
|
||||
- [ ] 点击章节可跳转阅读
|
||||
- [ ] 购买按钮有响应
|
||||
|
||||
### ✅ 匹配书友测试
|
||||
|
||||
- [ ] 星空背景动画流畅
|
||||
- [ ] 点击"开始匹配"有动画
|
||||
- [ ] 3-6秒后匹配成功
|
||||
- [ ] 显示匹配用户信息
|
||||
|
||||
### ✅ 我的页面测试
|
||||
|
||||
- [ ] 点击头像可登录
|
||||
- [ ] 分销中心数据显示
|
||||
- [ ] 生成推广海报功能
|
||||
- [ ] 复制邀请码功能
|
||||
|
||||
### ✅ 阅读页测试
|
||||
|
||||
- [ ] 章节内容正常渲染
|
||||
- [ ] 书签功能正常
|
||||
- [ ] 目录侧滑打开
|
||||
- [ ] 分享功能正常
|
||||
|
||||
---
|
||||
|
||||
## 🔧 常见问题
|
||||
|
||||
### Q1: 编译报错 "Cannot find module"
|
||||
|
||||
**解决**:检查后端服务器是否启动
|
||||
|
||||
```bash
|
||||
# 重新启动后端
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Q2: 页面空白,没有数据
|
||||
|
||||
**解决**:检查API地址配置
|
||||
|
||||
1. 打开 `app.js`
|
||||
2. 确认 `apiBase` 地址正确
|
||||
3. 在浏览器访问 `http://localhost:3000/api` 测试
|
||||
|
||||
---
|
||||
|
||||
### Q3: 图片不显示
|
||||
|
||||
**解决**:图片路径问题
|
||||
|
||||
临时方案:使用在线图片URL
|
||||
|
||||
```javascript
|
||||
// 将本地路径
|
||||
src="/assets/images/book-cover.png"
|
||||
|
||||
// 改为在线URL
|
||||
src="https://picsum.photos/400/560"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Q4: 支付测试失败
|
||||
|
||||
**解决**:本地开发暂时无法测试真实支付
|
||||
|
||||
- 使用Mock数据模拟支付成功
|
||||
- 真实支付需要:
|
||||
1. 配置微信支付商户号
|
||||
2. 部署到HTTPS域名
|
||||
3. 在小程序后台配置支付权限
|
||||
|
||||
---
|
||||
|
||||
### Q5: 模拟器和真机效果不一致
|
||||
|
||||
**解决**:以真机为准
|
||||
|
||||
```bash
|
||||
# 真机调试步骤:
|
||||
1. 点击工具栏"预览"
|
||||
2. 手机微信扫码
|
||||
3. 在手机上调试
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 获取帮助
|
||||
|
||||
### 技术支持
|
||||
|
||||
- **文档**: 查看 `开发文档/` 目录
|
||||
|
||||
### 官方文档
|
||||
|
||||
- [微信小程序官方文档](https://developers.weixin.qq.com/miniprogram/dev/framework/)
|
||||
- [微信支付文档](https://pay.weixin.qq.com/wiki/doc/api/index.html)
|
||||
|
||||
---
|
||||
|
||||
## 🎨 自定义配置(可选)
|
||||
|
||||
### 修改主题色
|
||||
|
||||
**文件**: `app.wxss`
|
||||
|
||||
```css
|
||||
.brand-color {
|
||||
color: #FF4D4F; /* 改成你的品牌色 */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 修改TabBar图标
|
||||
|
||||
替换 `assets/icons/` 目录下的图片:
|
||||
|
||||
- `home.png` / `home-active.png` - 首页
|
||||
- `match.png` / `match-active.png` - 匹配
|
||||
- `my.png` / `my-active.png` - 我的
|
||||
|
||||
要求:尺寸81x81像素,PNG格式
|
||||
|
||||
---
|
||||
|
||||
### 修改分享海报
|
||||
|
||||
**文件**: `pages/my/my.js` 中的 `drawPoster()` 函数
|
||||
|
||||
可自定义:
|
||||
|
||||
- 背景颜色
|
||||
- 文字内容
|
||||
- 二维码位置
|
||||
- Logo展示
|
||||
|
||||
---
|
||||
|
||||
## ✨ 下一步
|
||||
|
||||
配置完成后,你可以:
|
||||
|
||||
1. 📖 阅读[开发文档](../开发文档/小程序开发完成说明.md)
|
||||
2. 🎨 自定义UI样式
|
||||
3. 🔧 添加新功能
|
||||
4. 🚀 准备上线发布
|
||||
|
||||
---
|
||||
|
||||
**祝开发顺利!** 🎉
|
||||
|
||||
464
miniprogram/小程序部署说明.md
Normal file
464
miniprogram/小程序部署说明.md
Normal file
@@ -0,0 +1,464 @@
|
||||
# 🚀 Soul派对小程序 - 部署完成说明
|
||||
|
||||
**部署时间**: 2025年1月14日
|
||||
**配置状态**: ✅ 已完成配置
|
||||
|
||||
---
|
||||
|
||||
## ✅ 当前配置信息
|
||||
|
||||
### 小程序配置
|
||||
|
||||
| 项目 | 配置值 |
|
||||
|------|--------|
|
||||
| **AppID** | `wx0976665c3a3d5a7c` |
|
||||
| **AppSecret** | `a262f1be43422f03734f205d0bca1882` |
|
||||
| **API域名** | `http://kr-soul.lytiao.com` |
|
||||
| **API路径** | `http://kr-soul.lytiao.com/api` |
|
||||
|
||||
### 已配置文件
|
||||
|
||||
✅ `miniprogram/project.config.json` - AppID已配置
|
||||
✅ `miniprogram/app.js` - API地址已配置
|
||||
✅ `.env.production` - 生产环境配置
|
||||
✅ `app/api/wechat/login/route.ts` - 微信登录接口
|
||||
✅ `app/api/book/latest-chapters/route.ts` - 章节接口
|
||||
|
||||
---
|
||||
|
||||
## 🎯 快速测试(3步骤)
|
||||
|
||||
### 第1步:启动本地服务器
|
||||
|
||||
```bash
|
||||
cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验"
|
||||
|
||||
# 安装依赖(如果还没安装)
|
||||
pnpm install
|
||||
|
||||
# 启动开发服务器
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
✅ 看到 `Ready in 2.3s` 表示成功
|
||||
|
||||
---
|
||||
|
||||
### 第2步:打开微信开发者工具
|
||||
|
||||
1. 打开微信开发者工具
|
||||
2. 点击 **"导入项目"**
|
||||
3. 选择目录:
|
||||
```
|
||||
/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram
|
||||
```
|
||||
4. AppID会自动识别:`wx0976665c3a3d5a7c`
|
||||
5. 点击 **"导入"**
|
||||
|
||||
---
|
||||
|
||||
### 第3步:本地联调测试
|
||||
|
||||
在微信开发者工具中:
|
||||
|
||||
1. 点击右上角 **"详情"**
|
||||
2. 找到 **"本地设置"**
|
||||
3. 勾选 **"不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书"**
|
||||
4. 点击 **"编译"** 按钮
|
||||
|
||||
✅ 现在可以在模拟器中测试了!
|
||||
|
||||
---
|
||||
|
||||
## 📱 功能测试清单
|
||||
|
||||
### 首页测试
|
||||
|
||||
- [ ] 书籍封面显示
|
||||
- [ ] 最新章节列表
|
||||
- [ ] 点击章节跳转
|
||||
- [ ] 购买按钮响应
|
||||
|
||||
### 匹配书友测试
|
||||
|
||||
- [ ] 星空动画流畅
|
||||
- [ ] 匹配功能运行
|
||||
- [ ] 匹配成功显示
|
||||
|
||||
### 我的页面测试
|
||||
|
||||
- [ ] 点击登录功能
|
||||
- [ ] 分销中心展示
|
||||
- [ ] 海报生成功能
|
||||
|
||||
### 阅读页测试
|
||||
|
||||
- [ ] 章节内容加载
|
||||
- [ ] 目录侧滑
|
||||
- [ ] 书签功能
|
||||
|
||||
---
|
||||
|
||||
## 🌐 正式部署到服务器
|
||||
|
||||
### 域名配置检查
|
||||
|
||||
你的域名:`http://kr-soul.lytiao.com`
|
||||
|
||||
#### ⚠️ 重要:需要配置HTTPS
|
||||
|
||||
小程序要求所有网络请求必须使用HTTPS!
|
||||
|
||||
**配置SSL证书步骤**:
|
||||
|
||||
1. 登录阿里云控制台
|
||||
2. 进入 **"SSL证书"** 服务
|
||||
3. 申请免费SSL证书(DV证书)
|
||||
4. 下载证书文件
|
||||
5. 在服务器上配置证书
|
||||
|
||||
**配置后域名应该是**:
|
||||
```
|
||||
https://kr-soul.lytiao.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 服务器部署步骤
|
||||
|
||||
#### 1. 将代码上传到服务器
|
||||
|
||||
```bash
|
||||
# 方式1:使用Git
|
||||
cd /var/www
|
||||
git clone your-repo-url soul-party
|
||||
cd soul-party
|
||||
|
||||
# 方式2:使用SCP上传
|
||||
scp -r ./一场soul的创业实验 root@kr-soul.lytiao.com:/var/www/soul-party
|
||||
```
|
||||
|
||||
#### 2. 安装依赖并构建
|
||||
|
||||
```bash
|
||||
# 在服务器上执行
|
||||
cd /var/www/soul-party
|
||||
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 构建生产版本
|
||||
npm run build
|
||||
```
|
||||
|
||||
#### 3. 使用PM2启动服务
|
||||
|
||||
```bash
|
||||
# 安装PM2(如果没有)
|
||||
npm install -g pm2
|
||||
|
||||
# 启动服务
|
||||
pm2 start npm --name "soul-party" -- start
|
||||
|
||||
# 设置开机自启
|
||||
pm2 startup
|
||||
pm2 save
|
||||
```
|
||||
|
||||
#### 4. 配置Nginx反向代理
|
||||
|
||||
创建Nginx配置文件:`/etc/nginx/sites-available/soul-party`
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name kr-soul.lytiao.com;
|
||||
|
||||
# 强制跳转HTTPS
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name kr-soul.lytiao.com;
|
||||
|
||||
# SSL证书配置
|
||||
ssl_certificate /path/to/your/cert.pem;
|
||||
ssl_certificate_key /path/to/your/key.pem;
|
||||
|
||||
# API代理
|
||||
location /api {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
|
||||
# 静态文件
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
启用配置:
|
||||
|
||||
```bash
|
||||
# 创建软链接
|
||||
ln -s /etc/nginx/sites-available/soul-party /etc/nginx/sites-enabled/
|
||||
|
||||
# 测试配置
|
||||
nginx -t
|
||||
|
||||
# 重启Nginx
|
||||
systemctl restart nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 小程序后台配置
|
||||
|
||||
#### 1. 登录小程序后台
|
||||
|
||||
访问:https://mp.weixin.qq.com/
|
||||
|
||||
使用AppID `wx0976665c3a3d5a7c` 对应的账号登录
|
||||
|
||||
#### 2. 配置服务器域名
|
||||
|
||||
**开发管理** → **开发设置** → **服务器域名**
|
||||
|
||||
添加以下域名:
|
||||
|
||||
```
|
||||
request合法域名:
|
||||
https://kr-soul.lytiao.com
|
||||
|
||||
uploadFile合法域名:
|
||||
https://kr-soul.lytiao.com
|
||||
|
||||
downloadFile合法域名:
|
||||
https://kr-soul.lytiao.com
|
||||
```
|
||||
|
||||
⚠️ **注意**:必须是HTTPS域名,HTTP会被拒绝!
|
||||
|
||||
#### 3. 配置业务域名(可选)
|
||||
|
||||
如果需要在小程序内打开网页:
|
||||
|
||||
**开发管理** → **开发设置** → **业务域名**
|
||||
|
||||
添加:`kr-soul.lytiao.com`
|
||||
|
||||
---
|
||||
|
||||
## 📤 上传代码到微信后台
|
||||
|
||||
### 1. 上传代码
|
||||
|
||||
在微信开发者工具中:
|
||||
|
||||
1. 点击工具栏 **"上传"** 按钮
|
||||
2. 填写版本号:`1.0.0`
|
||||
3. 填写项目备注:`Soul派对小程序正式版`
|
||||
4. 点击 **"上传"**
|
||||
|
||||
✅ 上传成功后,代码会出现在小程序后台
|
||||
|
||||
---
|
||||
|
||||
### 2. 提交审核
|
||||
|
||||
登录小程序后台:
|
||||
|
||||
1. **版本管理** → **开发版本**
|
||||
2. 找到刚上传的版本
|
||||
3. 点击 **"提交审核"**
|
||||
4. 填写审核信息:
|
||||
- 类别:图书/阅读
|
||||
- 标签:电子书、创业、私域运营
|
||||
- 功能说明:提供电子书阅读和分销功能
|
||||
|
||||
审核时间:通常1-3个工作日
|
||||
|
||||
---
|
||||
|
||||
### 3. 发布上线
|
||||
|
||||
审核通过后:
|
||||
|
||||
1. **版本管理** → **审核版本**
|
||||
2. 点击 **"发布"**
|
||||
3. 全量发布给所有用户
|
||||
|
||||
🎉 **上线成功!**
|
||||
|
||||
---
|
||||
|
||||
## 🔧 本地开发配置
|
||||
|
||||
### 方式1:使用本地API(推荐开发时)
|
||||
|
||||
**文件**: `miniprogram/app.js`
|
||||
|
||||
```javascript
|
||||
apiBase: 'http://localhost:3000/api'
|
||||
```
|
||||
|
||||
然后在开发者工具中勾选 **"不校验合法域名"**
|
||||
|
||||
---
|
||||
|
||||
### 方式2:使用线上API
|
||||
|
||||
**文件**: `miniprogram/app.js`
|
||||
|
||||
```javascript
|
||||
apiBase: 'https://kr-soul.lytiao.com/api'
|
||||
```
|
||||
|
||||
必须配置好HTTPS和域名白名单
|
||||
|
||||
---
|
||||
|
||||
## 📊 API接口测试
|
||||
|
||||
### 测试微信登录接口
|
||||
|
||||
```bash
|
||||
curl -X POST http://kr-soul.lytiao.com/api/wechat/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"code":"test_code"}'
|
||||
```
|
||||
|
||||
### 测试章节列表接口
|
||||
|
||||
```bash
|
||||
curl http://kr-soul.lytiao.com/api/book/latest-chapters
|
||||
```
|
||||
|
||||
### 测试后台管理接口
|
||||
|
||||
```bash
|
||||
curl http://kr-soul.lytiao.com/api/admin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 生成小程序码
|
||||
|
||||
### 方式1:使用微信开发者工具
|
||||
|
||||
1. 点击工具栏 **"预览"**
|
||||
2. 自动生成小程序码
|
||||
3. 用微信扫码即可预览
|
||||
|
||||
---
|
||||
|
||||
### 方式2:使用官方API生成
|
||||
|
||||
需要调用微信接口:
|
||||
|
||||
```javascript
|
||||
// 获取小程序码
|
||||
POST https://api.weixin.qq.com/wxa/getwxacode?access_token=TOKEN
|
||||
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"width": 430
|
||||
}
|
||||
```
|
||||
|
||||
会生成二维码图片,保存后可分享
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 常见问题
|
||||
|
||||
### Q1: 提示"不在以下request合法域名列表中"
|
||||
|
||||
**解决**:
|
||||
1. 开发时:勾选"不校验合法域名"
|
||||
2. 正式环境:在小程序后台配置域名白名单
|
||||
|
||||
---
|
||||
|
||||
### Q2: API请求失败
|
||||
|
||||
**检查清单**:
|
||||
- [ ] 服务器是否启动?
|
||||
- [ ] 域名是否配置HTTPS?
|
||||
- [ ] 小程序后台是否配置域名?
|
||||
- [ ] API接口是否正常?
|
||||
|
||||
---
|
||||
|
||||
### Q3: 登录失败
|
||||
|
||||
**解决**:
|
||||
1. 检查AppID和AppSecret是否正确
|
||||
2. 查看控制台错误信息
|
||||
3. 确认微信登录接口正常
|
||||
|
||||
---
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
### 联系方式
|
||||
|
||||
- **项目路径**: `/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验`
|
||||
|
||||
### 快速命令
|
||||
|
||||
```bash
|
||||
# 启动开发服务器
|
||||
cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验"
|
||||
pnpm dev
|
||||
|
||||
# 构建生产版本
|
||||
pnpm build
|
||||
|
||||
# 启动生产服务器
|
||||
pnpm start
|
||||
|
||||
# 查看日志(如果使用PM2)
|
||||
pm2 logs soul-party
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 配置完成清单
|
||||
|
||||
- [x] AppID配置完成
|
||||
- [x] API地址配置完成
|
||||
- [x] 微信登录接口创建完成
|
||||
- [x] 书籍接口创建完成
|
||||
- [x] 环境变量配置完成
|
||||
- [x] 部署脚本创建完成
|
||||
- [ ] HTTPS证书配置(需要在服务器上操作)
|
||||
- [ ] 小程序后台域名配置(需要在微信后台操作)
|
||||
- [ ] 代码上传审核(需要在开发者工具操作)
|
||||
|
||||
---
|
||||
|
||||
## 🎉 下一步
|
||||
|
||||
1. **本地测试** - 在开发者工具中测试所有功能
|
||||
2. **服务器部署** - 将代码部署到 `kr-soul.lytiao.com`
|
||||
3. **配置HTTPS** - 申请并配置SSL证书
|
||||
4. **配置域名** - 在小程序后台配置服务器域名
|
||||
5. **提交审核** - 上传代码并提交审核
|
||||
6. **发布上线** - 审核通过后发布
|
||||
|
||||
---
|
||||
|
||||
**祝部署顺利!** 🚀
|
||||
|
||||
366
miniprogram/测试二维码.html
Normal file
366
miniprogram/测试二维码.html
Normal file
@@ -0,0 +1,366 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Soul派对小程序 - 测试二维码</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', sans-serif;
|
||||
background: linear-gradient(135deg, #000000 0%, #1a1a1a 100%);
|
||||
color: #ffffff;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 32px;
|
||||
padding: 60px 40px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0 32px 64px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #FF4D4F 0%, #FF7875 50%, #FFA39E 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 20px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.config-info {
|
||||
background: rgba(255, 77, 79, 0.1);
|
||||
border: 2px solid rgba(255, 77, 79, 0.3);
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.config-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #FF4D4F;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.config-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.config-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.config-label {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.config-value {
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.qrcode-section {
|
||||
text-align: center;
|
||||
margin: 40px 0;
|
||||
}
|
||||
|
||||
.qrcode-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 24px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.qrcode-placeholder {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 16px;
|
||||
margin: 0 auto 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: 0 16px 32px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.qrcode-icon {
|
||||
font-size: 80px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.qrcode-text {
|
||||
font-size: 18px;
|
||||
color: #666666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.steps {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.steps-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 24px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.step {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.step:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: linear-gradient(135deg, #FF4D4F 0%, #FF7875 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.step-desc {
|
||||
font-size: 15px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-top: 12px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
color: #50fa7b;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.notice {
|
||||
background: rgba(255, 193, 7, 0.1);
|
||||
border: 2px solid rgba(255, 193, 7, 0.3);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.notice-icon {
|
||||
font-size: 32px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notice-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.notice-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #FFC107;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.notice-text {
|
||||
font-size: 15px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.status {
|
||||
background: rgba(76, 175, 80, 0.2);
|
||||
border: 2px solid rgba(76, 175, 80, 0.5);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #4CAF50;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div class="title">Soul派对·创业实验</div>
|
||||
<div class="subtitle">微信小程序测试版</div>
|
||||
</div>
|
||||
|
||||
<div class="status">
|
||||
<div class="status-icon">✅</div>
|
||||
<div class="status-text">配置完成,可以开始测试!</div>
|
||||
</div>
|
||||
|
||||
<div class="config-info">
|
||||
<div class="config-title">📋 当前配置</div>
|
||||
<div class="config-item">
|
||||
<div class="config-label">小程序AppID</div>
|
||||
<div class="config-value">wx0976665c3a3d5a7c</div>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<div class="config-label">API域名</div>
|
||||
<div class="config-value">http://kr-soul.lytiao.com</div>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<div class="config-label">本地开发地址</div>
|
||||
<div class="config-value">http://localhost:3000</div>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<div class="config-label">配置状态</div>
|
||||
<div class="config-value">✅ 已完成</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="qrcode-section">
|
||||
<div class="qrcode-title">📱 扫码体验小程序</div>
|
||||
<div class="qrcode-placeholder">
|
||||
<div class="qrcode-icon">📱</div>
|
||||
<div class="qrcode-text">请在微信开发者工具中生成预览码</div>
|
||||
</div>
|
||||
<p style="color: rgba(255, 255, 255, 0.6); font-size: 15px;">
|
||||
在开发者工具中点击"预览"按钮,自动生成小程序码
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="steps">
|
||||
<div class="steps-title">🚀 快速测试步骤</div>
|
||||
|
||||
<div class="step">
|
||||
<div class="step-number">1</div>
|
||||
<div class="step-content">
|
||||
<div class="step-title">打开微信开发者工具</div>
|
||||
<div class="step-desc">
|
||||
选择"导入项目",导入以下目录:
|
||||
<div class="code-block">/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram</div>
|
||||
AppID会自动识别为:wx0976665c3a3d5a7c
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step">
|
||||
<div class="step-number">2</div>
|
||||
<div class="step-content">
|
||||
<div class="step-title">启用本地调试</div>
|
||||
<div class="step-desc">
|
||||
点击右上角"详情" → "本地设置" → 勾选"不校验合法域名"<br>
|
||||
(这样可以使用本地API: http://localhost:3000)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step">
|
||||
<div class="step-number">3</div>
|
||||
<div class="step-content">
|
||||
<div class="step-title">点击编译运行</div>
|
||||
<div class="step-desc">
|
||||
在模拟器中查看效果,测试所有功能:<br>
|
||||
- 首页书籍展示<br>
|
||||
- 匹配书友功能<br>
|
||||
- 我的页面和分销中心<br>
|
||||
- 阅读页面
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step">
|
||||
<div class="step-number">4</div>
|
||||
<div class="step-content">
|
||||
<div class="step-title">真机预览测试</div>
|
||||
<div class="step-desc">
|
||||
点击工具栏"预览"按钮,生成小程序码<br>
|
||||
用微信扫码即可在手机上预览
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="notice">
|
||||
<div class="notice-icon">⚠️</div>
|
||||
<div class="notice-content">
|
||||
<div class="notice-title">正式发布前注意事项</div>
|
||||
<div class="notice-text">
|
||||
1. 必须配置HTTPS证书(小程序要求必须HTTPS)<br>
|
||||
2. 在小程序后台配置服务器域名白名单<br>
|
||||
3. 将API地址改为:https://kr-soul.lytiao.com/api<br>
|
||||
4. 上传代码到微信后台提交审核
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 显示当前时间
|
||||
console.log('Soul派对小程序测试页面加载成功');
|
||||
console.log('配置时间:', new Date().toLocaleString('zh-CN'));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
71
miniprogram/生成图标.html
Normal file
71
miniprogram/生成图标.html
Normal file
@@ -0,0 +1,71 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>生成小程序图标</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>小程序底部导航图标生成器</h2>
|
||||
<div id="icons"></div>
|
||||
|
||||
<script>
|
||||
// 生成简单的图标(使用Canvas)
|
||||
const icons = [
|
||||
{ name: 'home', color: '#666', activeColor: '#FF4D4F', text: '首' },
|
||||
{ name: 'match', color: '#666', activeColor: '#FF4D4F', text: '匹' },
|
||||
{ name: 'my', color: '#666', activeColor: '#FF4D4F', text: '我' }
|
||||
];
|
||||
|
||||
const container = document.getElementById('icons');
|
||||
|
||||
icons.forEach(icon => {
|
||||
// 普通状态
|
||||
const canvas1 = document.createElement('canvas');
|
||||
canvas1.width = 81;
|
||||
canvas1.height = 81;
|
||||
const ctx1 = canvas1.getContext('2d');
|
||||
ctx1.fillStyle = icon.color;
|
||||
ctx1.font = 'bold 48px Arial';
|
||||
ctx1.textAlign = 'center';
|
||||
ctx1.textBaseline = 'middle';
|
||||
ctx1.fillText(icon.text, 40, 40);
|
||||
|
||||
// 激活状态
|
||||
const canvas2 = document.createElement('canvas');
|
||||
canvas2.width = 81;
|
||||
canvas2.height = 81;
|
||||
const ctx2 = canvas2.getContext('2d');
|
||||
ctx2.fillStyle = icon.activeColor;
|
||||
ctx2.font = 'bold 48px Arial';
|
||||
ctx2.textAlign = 'center';
|
||||
ctx2.textBaseline = 'middle';
|
||||
ctx2.fillText(icon.text, 40, 40);
|
||||
|
||||
// 显示并提供下载
|
||||
const div = document.createElement('div');
|
||||
div.style.margin = '20px';
|
||||
div.innerHTML = `
|
||||
<h3>${icon.name}</h3>
|
||||
<p>普通状态: <a href="${canvas1.toDataURL()}" download="${icon.name}.png">下载</a></p>
|
||||
<img src="${canvas1.toDataURL()}" style="border:1px solid #ccc">
|
||||
<p>激活状态: <a href="${canvas2.toDataURL()}" download="${icon.name}-active.png">下载</a></p>
|
||||
<img src="${canvas2.toDataURL()}" style="border:1px solid #ccc">
|
||||
`;
|
||||
container.appendChild(div);
|
||||
|
||||
// 自动下载
|
||||
setTimeout(() => {
|
||||
const a1 = document.createElement('a');
|
||||
a1.href = canvas1.toDataURL();
|
||||
a1.download = `${icon.name}.png`;
|
||||
a1.click();
|
||||
|
||||
const a2 = document.createElement('a');
|
||||
a2.href = canvas2.toDataURL();
|
||||
a2.download = `${icon.name}-active.png`;
|
||||
a2.click();
|
||||
}, 100);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
82
miniprogram/自动部署.sh
Executable file
82
miniprogram/自动部署.sh
Executable file
@@ -0,0 +1,82 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Soul派对小程序 - 自动部署脚本
|
||||
# 自动编译、测试、上传小程序
|
||||
|
||||
echo "=================================="
|
||||
echo " Soul派对小程序 自动部署 "
|
||||
echo "=================================="
|
||||
echo ""
|
||||
|
||||
# 微信开发者工具CLI路径
|
||||
CLI="/Applications/wechatwebdevtools.app/Contents/MacOS/cli"
|
||||
|
||||
# 项目路径
|
||||
PROJECT_PATH="/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/miniprogram"
|
||||
|
||||
# 检查CLI是否存在
|
||||
if [ ! -f "$CLI" ]; then
|
||||
echo "❌ 未找到微信开发者工具CLI"
|
||||
echo "请确保微信开发者工具已安装"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ 找到微信开发者工具"
|
||||
echo ""
|
||||
|
||||
# 1. 打开项目
|
||||
echo "📂 步骤1:打开项目..."
|
||||
$CLI -o "$PROJECT_PATH"
|
||||
sleep 2
|
||||
echo "✅ 项目已打开"
|
||||
echo ""
|
||||
|
||||
# 2. 编译项目(使用新的v2命令格式)
|
||||
echo "🔨 步骤2:编译项目..."
|
||||
$CLI build-npm --project "$PROJECT_PATH"
|
||||
sleep 3
|
||||
echo "✅ 编译完成"
|
||||
echo ""
|
||||
|
||||
# 3. 预览(生成二维码)
|
||||
echo "📱 步骤3:生成预览二维码..."
|
||||
$CLI preview --project "$PROJECT_PATH" --qr-format image --qr-output "$PROJECT_PATH/preview.png"
|
||||
if [ -f "$PROJECT_PATH/preview.png" ]; then
|
||||
echo "✅ 二维码已生成: $PROJECT_PATH/preview.png"
|
||||
open "$PROJECT_PATH/preview.png"
|
||||
else
|
||||
echo "⚠️ 二维码生成失败,请手动点击预览"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 4. 上传代码(使用新的v2命令格式)
|
||||
echo "📤 步骤4:上传代码到微信后台..."
|
||||
VERSION="1.0.0"
|
||||
DESC="初始版本:3按钮导航+星球匹配功能,H5和小程序界面统一"
|
||||
|
||||
$CLI upload --project "$PROJECT_PATH" --version "$VERSION" --desc "$DESC"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ 代码上传成功!"
|
||||
echo ""
|
||||
echo "版本:$VERSION"
|
||||
echo "说明:$DESC"
|
||||
echo ""
|
||||
echo "=================================="
|
||||
echo "🎉 部署完成!"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
echo "下一步操作:"
|
||||
echo "1. 登录小程序后台:https://mp.weixin.qq.com"
|
||||
echo "2. 进入「版本管理」→「开发版本」"
|
||||
echo "3. 找到刚上传的版本"
|
||||
echo "4. 点击「提交审核」"
|
||||
echo ""
|
||||
else
|
||||
echo "❌ 上传失败"
|
||||
echo ""
|
||||
echo "可能原因:"
|
||||
echo "1. 需要在微信开发者工具中登录"
|
||||
echo "2. 需要手动上传(点击工具栏的上传按钮)"
|
||||
echo ""
|
||||
fi
|
||||
Reference in New Issue
Block a user