Update project documentation and enhance user interaction features
- Added a new entry for user interaction habit analysis based on agent transcripts, summarizing key insights into communication styles and preferences. - Updated project indices to reflect the latest developments, including the addition of a wallet balance feature and enhancements to the mini program's user interface for better user experience. - Improved the handling of loading states in the chapters page, ensuring a smoother user experience during data retrieval. - Implemented a gift payment sharing feature, allowing users to share payment requests with friends for collaborative purchases.
This commit is contained in:
@@ -9,8 +9,8 @@ App({
|
||||
globalData: {
|
||||
// API 基础地址(切换环境时注释/取消注释)
|
||||
// baseUrl: 'https://soulapi.quwanzhi.com',
|
||||
// baseUrl: 'http://localhost:8080', // 本地调试
|
||||
baseUrl: 'https://souldev.quwanzhi.com', // 测试环境
|
||||
baseUrl: 'http://localhost:8080', // 本地调试
|
||||
// baseUrl: 'https://souldev.quwanzhi.com', // 测试环境
|
||||
|
||||
|
||||
// 小程序配置 - 真实AppID
|
||||
|
||||
@@ -16,13 +16,16 @@
|
||||
"pages/addresses/addresses",
|
||||
"pages/addresses/edit",
|
||||
"pages/withdraw-records/withdraw-records",
|
||||
"pages/wallet/wallet",
|
||||
"pages/vip/vip",
|
||||
"pages/member-detail/member-detail",
|
||||
"pages/mentors/mentors",
|
||||
"pages/mentor-detail/mentor-detail",
|
||||
"pages/profile-show/profile-show",
|
||||
"pages/profile-edit/profile-edit",
|
||||
"pages/avatar-nickname/avatar-nickname"
|
||||
"pages/avatar-nickname/avatar-nickname",
|
||||
"pages/gift-pay/detail",
|
||||
"pages/gift-pay/list"
|
||||
],
|
||||
"window": {
|
||||
"backgroundTextStyle": "light",
|
||||
@@ -58,7 +61,10 @@
|
||||
]
|
||||
},
|
||||
"usingComponents": {},
|
||||
"navigateToMiniProgramAppIdList": [],
|
||||
"navigateToMiniProgramAppIdList": [
|
||||
"wx6489c26045912fe1",
|
||||
"wx3d15ed02e98b04e3"
|
||||
],
|
||||
"__usePrivacyCheck__": true,
|
||||
"lazyCodeLoading": "requiredComponents",
|
||||
"style": "v2",
|
||||
|
||||
@@ -40,7 +40,10 @@ Page({
|
||||
],
|
||||
|
||||
// 每日新增章节(懒加载后暂无,可后续用 latest-chapters 补充)
|
||||
dailyChapters: []
|
||||
dailyChapters: [],
|
||||
|
||||
// book/parts 加载中
|
||||
partsLoading: true
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
@@ -55,16 +58,40 @@ Page({
|
||||
},
|
||||
|
||||
// 懒加载:仅拉取篇章列表 + totalSections + fixedSections
|
||||
// 优先 book/parts,404 或失败时降级为 all-chapters 推导
|
||||
async loadParts() {
|
||||
this.setData({ partsLoading: true })
|
||||
try {
|
||||
const res = await app.request({ url: '/api/miniprogram/book/parts', silent: true })
|
||||
if (!res?.success) {
|
||||
this.setData({ bookData: [], totalSections: 0 })
|
||||
return
|
||||
let res
|
||||
try {
|
||||
res = await app.request({ url: '/api/miniprogram/book/parts', silent: true })
|
||||
} catch (e) {
|
||||
console.log('[Chapters] book/parts 失败,降级 all-chapters:', e?.message || e)
|
||||
res = null
|
||||
}
|
||||
let parts = []
|
||||
let totalSections = 0
|
||||
let fixedSections = []
|
||||
if (res?.success && Array.isArray(res.parts) && res.parts.length > 0) {
|
||||
parts = res.parts
|
||||
totalSections = res.totalSections ?? 0
|
||||
fixedSections = res.fixedSections || []
|
||||
} else {
|
||||
// 降级:从 all-chapters 推导 parts
|
||||
const allRes = await app.request({ url: '/api/miniprogram/book/all-chapters', silent: true })
|
||||
const list = (allRes?.data || allRes?.chapters || [])
|
||||
totalSections = list.length
|
||||
const pt = (c) => (c.partTitle || c.part_title || '').toLowerCase()
|
||||
const exclude = (c) => !pt(c).includes('序言') && !pt(c).includes('尾声') && !pt(c).includes('附录')
|
||||
const partMap = new Map()
|
||||
list.filter(exclude).forEach(c => {
|
||||
const pid = c.partId || c.part_id || 'default'
|
||||
const ptitle = c.partTitle || c.part_title || '未分类'
|
||||
if (!partMap.has(pid)) partMap.set(pid, { id: pid, title: ptitle, subtitle: '', chapterCount: 0 })
|
||||
partMap.get(pid).chapterCount++
|
||||
})
|
||||
parts = Array.from(partMap.values())
|
||||
}
|
||||
const parts = res.parts || []
|
||||
const totalSections = res.totalSections ?? 0
|
||||
const fixedSections = res.fixedSections || []
|
||||
const numbers = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']
|
||||
const fixedMap = {}
|
||||
fixedSections.forEach(f => { fixedMap[f.id] = f.mid })
|
||||
@@ -87,11 +114,12 @@ Page({
|
||||
totalSections,
|
||||
fixedSectionsMap: fixedMap,
|
||||
appendixList,
|
||||
_loadedChapters: {}
|
||||
_loadedChapters: {},
|
||||
partsLoading: false
|
||||
})
|
||||
} catch (e) {
|
||||
console.log('[Chapters] 加载篇章失败:', e)
|
||||
this.setData({ bookData: [], totalSections: 0 })
|
||||
this.setData({ bookData: [], totalSections: 0, partsLoading: false })
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -17,8 +17,14 @@
|
||||
<!-- 导航栏占位 -->
|
||||
<view class="nav-placeholder" style="height: {{statusBarHeight + 44}}px;"></view>
|
||||
|
||||
<!-- 目录加载中 -->
|
||||
<view class="parts-loading" wx:if="{{partsLoading}}">
|
||||
<view class="parts-loading-spinner"></view>
|
||||
<text class="parts-loading-text">加载目录中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 书籍信息卡 -->
|
||||
<view class="book-info-card card-gradient">
|
||||
<view class="book-info-card card-gradient" wx:if="{{!partsLoading}}">
|
||||
<view class="book-icon">
|
||||
<view class="book-icon-inner">📚</view>
|
||||
</view>
|
||||
@@ -33,7 +39,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 目录内容 -->
|
||||
<view class="chapters-content">
|
||||
<view class="chapters-content" wx:if="{{!partsLoading}}">
|
||||
<!-- 序言(优先传 mid,阅读页用 by-mid 请求) -->
|
||||
<view class="chapter-item" bindtap="goToRead" data-id="preface" data-mid="{{fixedSectionsMap.preface}}">
|
||||
<view class="item-left">
|
||||
|
||||
@@ -75,6 +75,34 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ===== 目录加载中 ===== */
|
||||
.parts-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 0;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.parts-loading-spinner {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border: 6rpx solid rgba(255, 255, 255, 0.1);
|
||||
border-top-color: #00CED1;
|
||||
border-radius: 50%;
|
||||
animation: parts-spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
.parts-loading-text {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
@keyframes parts-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* ===== 书籍信息卡 ===== */
|
||||
.book-info-card {
|
||||
display: flex;
|
||||
|
||||
114
miniprogram/pages/gift-pay/detail.js
Normal file
114
miniprogram/pages/gift-pay/detail.js
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Soul创业派对 - 代付详情页
|
||||
* 好友打开后看到订单信息,点击「帮他付款」完成代付
|
||||
*/
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 44,
|
||||
requestSn: '',
|
||||
detail: null,
|
||||
loading: true,
|
||||
paying: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 44 })
|
||||
const requestSn = (options.requestSn || '').trim()
|
||||
if (!requestSn) {
|
||||
wx.showToast({ title: '代付链接无效', icon: 'none' })
|
||||
setTimeout(() => wx.switchTab({ url: '/pages/index/index' }), 1500)
|
||||
return
|
||||
}
|
||||
this.setData({ requestSn })
|
||||
this.loadDetail()
|
||||
},
|
||||
|
||||
async loadDetail() {
|
||||
const { requestSn } = this.data
|
||||
if (!requestSn) return
|
||||
this.setData({ loading: true })
|
||||
try {
|
||||
const res = await app.request(`/api/miniprogram/gift-pay/detail?requestSn=${encodeURIComponent(requestSn)}`)
|
||||
if (res && res.success) {
|
||||
this.setData({ detail: res, loading: false })
|
||||
} else {
|
||||
this.setData({ loading: false })
|
||||
wx.showToast({ title: res?.error || '加载失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
this.setData({ loading: false })
|
||||
wx.showToast({ title: '加载失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
async doPay() {
|
||||
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
setTimeout(() => wx.switchTab({ url: '/pages/my/my' }), 1500)
|
||||
return
|
||||
}
|
||||
const openId = app.globalData.openId || ''
|
||||
if (!openId) {
|
||||
wx.showToast({ title: '请先完成微信授权', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const { requestSn, detail } = this.data
|
||||
if (!requestSn || !detail) return
|
||||
|
||||
this.setData({ paying: true })
|
||||
wx.showLoading({ title: '创建订单中...', mask: true })
|
||||
try {
|
||||
const res = await app.request({
|
||||
url: '/api/miniprogram/gift-pay/pay',
|
||||
method: 'POST',
|
||||
data: {
|
||||
requestSn,
|
||||
openId,
|
||||
userId: app.globalData.userInfo?.id || ''
|
||||
}
|
||||
})
|
||||
wx.hideLoading()
|
||||
if (!res || !res.success || !res.data?.payParams) {
|
||||
throw new Error(res?.error || '创建订单失败')
|
||||
}
|
||||
const payParams = res.data.payParams
|
||||
payParams._orderSn = res.data.orderSn
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
wx.requestPayment({
|
||||
...payParams,
|
||||
signType: payParams.signType || 'MD5',
|
||||
success: resolve,
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
|
||||
wx.showToast({ title: '代付成功', icon: 'success' })
|
||||
this.setData({ paying: false })
|
||||
setTimeout(() => {
|
||||
wx.navigateBack({ fail: () => wx.switchTab({ url: '/pages/index/index' }) })
|
||||
}, 1500)
|
||||
} catch (e) {
|
||||
this.setData({ paying: false })
|
||||
if (e.errMsg && e.errMsg.includes('cancel')) {
|
||||
wx.showToast({ title: '已取消支付', icon: 'none' })
|
||||
} else {
|
||||
wx.showToast({ title: e.message || e.error || '支付失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
goBack() {
|
||||
app.goBackOrToHome()
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
const { requestSn } = this.data
|
||||
return {
|
||||
title: '好友请你帮忙代付 - Soul创业派对',
|
||||
path: requestSn ? `/pages/gift-pay/detail?requestSn=${requestSn}` : '/pages/gift-pay/detail'
|
||||
}
|
||||
}
|
||||
})
|
||||
4
miniprogram/pages/gift-pay/detail.json
Normal file
4
miniprogram/pages/gift-pay/detail.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {}
|
||||
}
|
||||
52
miniprogram/pages/gift-pay/detail.wxml
Normal file
52
miniprogram/pages/gift-pay/detail.wxml
Normal file
@@ -0,0 +1,52 @@
|
||||
<!-- Soul创业派对 - 代付详情页 -->
|
||||
<view class="page">
|
||||
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
|
||||
<view class="nav-content">
|
||||
<view class="nav-back" bindtap="goBack">
|
||||
<text class="back-arrow">←</text>
|
||||
</view>
|
||||
<view class="nav-info">
|
||||
<text class="nav-title">帮他付款</text>
|
||||
</view>
|
||||
<view class="nav-right-placeholder"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="content" style="padding-top: calc({{statusBarHeight}}px + 88rpx);">
|
||||
<block wx:if="{{loading}}">
|
||||
<view class="loading-box">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
</block>
|
||||
<block wx:elif="{{detail}}">
|
||||
<view class="card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">代付订单</text>
|
||||
<text class="initiator">{{detail.initiatorNickname || '好友'}} 请你帮忙付款</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="row">
|
||||
<text class="label">商品</text>
|
||||
<text class="value">{{detail.description || '-'}}</text>
|
||||
</view>
|
||||
<view class="row amount-row">
|
||||
<text class="label">金额</text>
|
||||
<text class="amount">¥{{detail.amount ? detail.amount.toFixed(2) : '0.00'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tips">
|
||||
<text>付款后,{{detail.initiatorNickname || '好友'}}将获得对应权益</text>
|
||||
</view>
|
||||
<button class="pay-btn" bindtap="doPay" disabled="{{paying}}">
|
||||
{{paying ? '支付中...' : '帮他付款'}}
|
||||
</button>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<view class="empty">
|
||||
<text>代付请求不存在或已处理</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
160
miniprogram/pages/gift-pay/detail.wxss
Normal file
160
miniprogram/pages/gift-pay/detail.wxss
Normal file
@@ -0,0 +1,160 @@
|
||||
/* Soul创业派对 - 代付详情页 */
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
border-bottom: 1rpx solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.nav-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24rpx;
|
||||
height: 88rpx;
|
||||
}
|
||||
|
||||
.nav-back {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
background: #1c1c1e;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.back-arrow {
|
||||
font-size: 36rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.loading-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 4rpx solid rgba(0, 206, 209, 0.3);
|
||||
border-top-color: #00CED1;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #1c1c1e;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 32rpx;
|
||||
border-bottom: 1rpx solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.initiator {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.amount-row .amount {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #00CED1;
|
||||
}
|
||||
|
||||
.tips {
|
||||
padding: 0 8rpx 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.pay-btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
background: linear-gradient(90deg, #00CED1 0%, #20B2AA 100%);
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 48rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.pay-btn[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: 120rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
89
miniprogram/pages/gift-pay/list.js
Normal file
89
miniprogram/pages/gift-pay/list.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Soul创业派对 - 我的代付
|
||||
* Tab: 我发起的 / 我帮付的
|
||||
*/
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 44,
|
||||
tab: 'requests',
|
||||
requests: [],
|
||||
payments: [],
|
||||
loading: false
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 44 })
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
if (this.data.requests.length > 0 || this.data.payments.length > 0) {
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
|
||||
switchTab(e) {
|
||||
const tab = e.currentTarget.dataset.tab || 'requests'
|
||||
this.setData({ tab })
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
async loadData() {
|
||||
const userId = app.globalData.userInfo?.id || ''
|
||||
if (!userId) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.setData({ loading: true })
|
||||
try {
|
||||
if (this.data.tab === 'requests') {
|
||||
const res = await app.request(`/api/miniprogram/gift-pay/my-requests?userId=${encodeURIComponent(userId)}`)
|
||||
this.setData({ requests: (res && res.list) || [], loading: false })
|
||||
} else {
|
||||
const res = await app.request(`/api/miniprogram/gift-pay/my-payments?userId=${encodeURIComponent(userId)}`)
|
||||
this.setData({ payments: (res && res.list) || [], loading: false })
|
||||
}
|
||||
} catch (e) {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
},
|
||||
|
||||
async cancelRequest(e) {
|
||||
const requestSn = e.currentTarget.dataset.sn
|
||||
if (!requestSn) return
|
||||
const ok = await new Promise(r => {
|
||||
wx.showModal({ title: '取消代付', content: '确定取消该代付请求?', success: res => r(res.confirm) })
|
||||
})
|
||||
if (!ok) return
|
||||
try {
|
||||
const res = await app.request({
|
||||
url: '/api/miniprogram/gift-pay/cancel',
|
||||
method: 'POST',
|
||||
data: { requestSn, userId: app.globalData.userInfo?.id }
|
||||
})
|
||||
if (res && res.success) {
|
||||
wx.showToast({ title: '已取消', icon: 'success' })
|
||||
this.loadData()
|
||||
} else {
|
||||
wx.showToast({ title: res?.error || '取消失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
wx.showToast({ title: '取消失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
shareRequest(e) {
|
||||
const requestSn = e.currentTarget.dataset.sn
|
||||
wx.showToast({ title: '请点击右上角「...」分享给好友', icon: 'none', duration: 2500 })
|
||||
},
|
||||
|
||||
goBack() {
|
||||
app.goBackOrToHome()
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
return { title: '我的代付 - Soul创业派对', path: '/pages/gift-pay/list' }
|
||||
}
|
||||
})
|
||||
4
miniprogram/pages/gift-pay/list.json
Normal file
4
miniprogram/pages/gift-pay/list.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {}
|
||||
}
|
||||
64
miniprogram/pages/gift-pay/list.wxml
Normal file
64
miniprogram/pages/gift-pay/list.wxml
Normal file
@@ -0,0 +1,64 @@
|
||||
<!-- Soul创业派对 - 我的代付 -->
|
||||
<view class="page">
|
||||
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
|
||||
<view class="nav-content">
|
||||
<view class="nav-back" bindtap="goBack">
|
||||
<text class="back-arrow">←</text>
|
||||
</view>
|
||||
<view class="nav-info">
|
||||
<text class="nav-title">我的代付</text>
|
||||
</view>
|
||||
<view class="nav-right-placeholder"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tabs" style="padding-top: calc({{statusBarHeight}}px + 88rpx);">
|
||||
<view class="tab {{tab === 'requests' ? 'active' : ''}}" data-tab="requests" bindtap="switchTab">我发起的</view>
|
||||
<view class="tab {{tab === 'payments' ? 'active' : ''}}" data-tab="payments" bindtap="switchTab">我帮付的</view>
|
||||
</view>
|
||||
|
||||
<view class="content">
|
||||
<block wx:if="{{loading}}">
|
||||
<view class="loading-box">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
</block>
|
||||
<block wx:elif="{{tab === 'requests'}}">
|
||||
<block wx:if="{{requests.length === 0}}">
|
||||
<view class="empty">暂无发起的代付</view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<view class="card" wx:for="{{requests}}" wx:key="requestSn">
|
||||
<view class="card-row">
|
||||
<text class="desc">{{item.description}}</text>
|
||||
<text class="amount">¥{{item.amount}}</text>
|
||||
</view>
|
||||
<view class="card-row">
|
||||
<text class="status {{item.status}}">{{item.status === 'pending' ? '待支付' : item.status === 'paid' ? '已支付' : item.status === 'cancelled' ? '已取消' : '已过期'}}</text>
|
||||
<view class="actions" wx:if="{{item.status === 'pending'}}">
|
||||
<text class="action-text" bindtap="shareRequest" data-sn="{{item.requestSn}}">分享</text>
|
||||
<text class="action-text cancel" bindtap="cancelRequest" data-sn="{{item.requestSn}}">取消</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<block wx:if="{{payments.length === 0}}">
|
||||
<view class="empty">暂无帮付记录</view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<view class="card" wx:for="{{payments}}" wx:key="requestSn">
|
||||
<view class="card-row">
|
||||
<text class="desc">{{item.description}}</text>
|
||||
<text class="amount">¥{{item.amount}}</text>
|
||||
</view>
|
||||
<view class="card-row">
|
||||
<text class="status {{item.status}}">{{item.status === 'paid' ? '已支付' : item.status}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
160
miniprogram/pages/gift-pay/list.wxss
Normal file
160
miniprogram/pages/gift-pay/list.wxss
Normal file
@@ -0,0 +1,160 @@
|
||||
/* Soul创业派对 - 我的代付 */
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
border-bottom: 1rpx solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.nav-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24rpx;
|
||||
height: 88rpx;
|
||||
}
|
||||
|
||||
.nav-back {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
background: #1c1c1e;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.back-arrow {
|
||||
font-size: 36rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
padding: 24rpx 32rpx;
|
||||
gap: 24rpx;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 12rpx;
|
||||
background: #1c1c1e;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
color: #00CED1;
|
||||
background: rgba(0, 206, 209, 0.15);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 32rpx 32rpx;
|
||||
}
|
||||
|
||||
.loading-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 80rpx 0;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 4rpx solid rgba(0, 206, 209, 0.3);
|
||||
border-top-color: #00CED1;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: 80rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #1c1c1e;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.card-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.card-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #00CED1;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.status.paid {
|
||||
color: #00CED1;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 26rpx;
|
||||
color: #00CED1;
|
||||
}
|
||||
|
||||
.action-text.cancel {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
@@ -47,8 +47,9 @@ Page({
|
||||
superMembers: [],
|
||||
superMembersLoading: true,
|
||||
|
||||
// 最新新增章节
|
||||
// 最新新增章节(完整列表 + 展示列表,用于展开/折叠)
|
||||
latestChapters: [],
|
||||
displayLatestChapters: [],
|
||||
|
||||
// 篇章数(从 bookData 计算)
|
||||
partCount: 0,
|
||||
@@ -58,7 +59,13 @@ Page({
|
||||
|
||||
// 链接卡若 - 留资弹窗
|
||||
showLeadModal: false,
|
||||
leadPhone: ''
|
||||
leadPhone: '',
|
||||
|
||||
// 展开状态(首页精选/最新)
|
||||
featuredExpanded: false,
|
||||
latestExpanded: false,
|
||||
featuredSectionsFull: [], // 展开时用 book/hot 加载的完整列表
|
||||
featuredExpandedLoading: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
@@ -180,7 +187,25 @@ Page({
|
||||
}
|
||||
} catch (e) { console.log('[Index] book/recommended 失败:', e) }
|
||||
|
||||
// 兜底:无 recommended 时从 all-chapters 按更新时间取前3
|
||||
// 兜底:无 recommended 时从 book/hot 取前3
|
||||
if (featured.length === 0) {
|
||||
try {
|
||||
const hotRes = await app.request({ url: '/api/miniprogram/book/hot?limit=10', silent: true })
|
||||
const hotList = (hotRes && hotRes.data) ? hotRes.data : []
|
||||
if (hotList.length > 0) {
|
||||
const tagMap = ['热门', '推荐', '精选']
|
||||
featured = hotList.slice(0, 3).map((s, i) => ({
|
||||
id: s.id || s.section_id,
|
||||
mid: s.mid ?? s.MID ?? 0,
|
||||
title: s.sectionTitle || s.section_title || s.title || s.chapterTitle || '',
|
||||
part: (s.partTitle || s.part_title || '').replace(/[_||]/g, ' ').trim(),
|
||||
tag: tagMap[i] || '精选',
|
||||
tagClass: ['tag-hot', 'tag-rec', 'tag-rec'][i] || 'tag-rec'
|
||||
}))
|
||||
this.setData({ featuredSections: featured })
|
||||
}
|
||||
} catch (e) { console.log('[Index] book/hot 兜底失败:', e) }
|
||||
}
|
||||
if (featured.length === 0) {
|
||||
const res = await app.request({ url: '/api/miniprogram/book/all-chapters', silent: true })
|
||||
const chapters = (res && res.data) || (res && res.chapters) || []
|
||||
@@ -482,6 +507,50 @@ Page({
|
||||
wx.switchTab({ url: '/pages/match/match' })
|
||||
},
|
||||
|
||||
// 精选推荐:展开/折叠
|
||||
async toggleFeaturedExpanded() {
|
||||
if (this.data.featuredExpandedLoading) return
|
||||
if (this.data.featuredExpanded) {
|
||||
const collapsed = this.data.featuredSectionsFull.length > 0 ? this.data.featuredSectionsFull.slice(0, 3) : this.data.featuredSections
|
||||
this.setData({ featuredExpanded: false, featuredSections: collapsed })
|
||||
return
|
||||
}
|
||||
if (this.data.featuredSectionsFull.length > 0) {
|
||||
this.setData({ featuredExpanded: true, featuredSections: this.data.featuredSectionsFull })
|
||||
return
|
||||
}
|
||||
this.setData({ featuredExpandedLoading: true })
|
||||
try {
|
||||
const res = await app.request({ url: '/api/miniprogram/book/hot?limit=50', silent: true })
|
||||
const list = (res && res.data) ? res.data : []
|
||||
const tagMap = ['热门', '推荐', '精选']
|
||||
const full = list.map((s, i) => ({
|
||||
id: s.id || s.section_id,
|
||||
mid: s.mid ?? s.MID ?? 0,
|
||||
title: s.sectionTitle || s.section_title || s.title || s.chapterTitle || '',
|
||||
part: (s.partTitle || s.part_title || '').replace(/[_||]/g, ' ').trim(),
|
||||
tag: tagMap[i % 3] || '精选',
|
||||
tagClass: ['tag-hot', 'tag-rec', 'tag-rec'][i % 3] || 'tag-rec'
|
||||
}))
|
||||
this.setData({
|
||||
featuredSectionsFull: full,
|
||||
featuredSections: full,
|
||||
featuredExpanded: true,
|
||||
featuredExpandedLoading: false
|
||||
})
|
||||
} catch (e) {
|
||||
console.log('[Index] 加载精选更多失败:', e)
|
||||
this.setData({ featuredExpandedLoading: false })
|
||||
}
|
||||
},
|
||||
|
||||
// 最新新增:展开/折叠(默认 5 条,点击展开剩余)
|
||||
toggleLatestExpanded() {
|
||||
const expanded = !this.data.latestExpanded
|
||||
const display = expanded ? this.data.latestChapters : this.data.latestChapters.slice(0, 5)
|
||||
this.setData({ latestExpanded: expanded, displayLatestChapters: display })
|
||||
},
|
||||
|
||||
// 最新新增:用 latest-chapters 接口(后端按 updated_at 取前 N 条),不拉全量,支持万级文章
|
||||
async loadLatestChapters() {
|
||||
try {
|
||||
@@ -491,7 +560,7 @@ Page({
|
||||
const exclude = c => !pt(c).includes('序言') && !pt(c).includes('尾声') && !pt(c).includes('附录')
|
||||
const latest = list
|
||||
.filter(exclude)
|
||||
.slice(0, 10)
|
||||
.slice(0, 20)
|
||||
.map(c => {
|
||||
const d = new Date(c.updatedAt || c.updated_at || Date.now())
|
||||
const title = c.section_title || c.sectionTitle || c.title || c.chapterTitle || ''
|
||||
@@ -504,7 +573,8 @@ Page({
|
||||
dateStr: `${d.getMonth() + 1}/${d.getDate()}`
|
||||
}
|
||||
})
|
||||
this.setData({ latestChapters: latest })
|
||||
const display = this.data.latestExpanded ? latest : latest.slice(0, 5)
|
||||
this.setData({ latestChapters: latest, displayLatestChapters: display })
|
||||
} catch (e) { console.log('[Index] 加载最新新增失败:', e) }
|
||||
},
|
||||
|
||||
|
||||
@@ -52,6 +52,19 @@
|
||||
<view class="banner-action"><text class="banner-action-text">开始阅读</text><view class="banner-arrow">→</view></view>
|
||||
</view>
|
||||
|
||||
<!-- 阅读进度(设计稿:最新更新→阅读进度→超级个体) -->
|
||||
<view class="progress-card" wx:if="{{isLoggedIn}}" bindtap="goToChapters">
|
||||
<view class="progress-header">
|
||||
<text class="progress-title">阅读进度</text>
|
||||
<text class="progress-count">已读 {{readCount}}/{{totalSections}}</text>
|
||||
</view>
|
||||
<view class="progress-bar-wrapper">
|
||||
<view class="progress-bar-bg">
|
||||
<view class="progress-bar-fill" style="width: {{readCount && totalSections ? (readCount / totalSections * 100) : 0}}%;"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 超级个体(横向滚动,已去掉「查看全部」) -->
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
@@ -91,10 +104,14 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 精选推荐(带 tag,已去掉「查看全部」) -->
|
||||
<!-- 精选推荐(带 tag,支持展开更多) -->
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">精选推荐</text>
|
||||
<view class="section-more" wx:if="{{featuredSections.length > 0}}" bindtap="toggleFeaturedExpanded">
|
||||
<text class="more-text">{{featuredExpandedLoading ? '加载中...' : (featuredExpanded ? '收起' : '展开更多')}}</text>
|
||||
<text class="more-arrow">{{featuredExpanded ? '▲' : '▼'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="featured-list">
|
||||
<view
|
||||
@@ -117,18 +134,24 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最新新增(时间线样式) -->
|
||||
<!-- 最新新增(时间线样式,支持展开更多) -->
|
||||
<view class="section" wx:if="{{latestChapters.length > 0}}">
|
||||
<view class="section-header latest-header">
|
||||
<text class="section-title">最新新增</text>
|
||||
<view class="daily-badge-wrap">
|
||||
<text class="daily-badge">+{{latestChapters.length}}</text>
|
||||
<view class="section-header-right">
|
||||
<view class="daily-badge-wrap">
|
||||
<text class="daily-badge">+{{latestChapters.length}}</text>
|
||||
</view>
|
||||
<view class="section-more" wx:if="{{latestChapters.length > 5}}" bindtap="toggleLatestExpanded">
|
||||
<text class="more-text">{{latestExpanded ? '收起' : '展开更多'}}</text>
|
||||
<text class="more-arrow">{{latestExpanded ? '▲' : '▼'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="timeline-wrap">
|
||||
<view class="timeline-line"></view>
|
||||
<view class="timeline-list">
|
||||
<view class="timeline-item {{index === 0 ? 'timeline-item-first' : ''}}" wx:for="{{latestChapters}}" wx:key="id" bindtap="goToRead" data-id="{{item.id}}" data-mid="{{item.mid}}">
|
||||
<view class="timeline-item {{index === 0 ? 'timeline-item-first' : ''}}" wx:for="{{displayLatestChapters}}" wx:key="id" bindtap="goToRead" data-id="{{item.id}}" data-mid="{{item.mid}}">
|
||||
<view class="timeline-dot"></view>
|
||||
<view class="timeline-content">
|
||||
<view class="timeline-row">
|
||||
|
||||
@@ -688,6 +688,12 @@
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.section-header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.daily-badge-wrap {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -76,6 +76,9 @@ Page({
|
||||
|
||||
// 设置入口:开发版、体验版显示
|
||||
showSettingsEntry: false,
|
||||
|
||||
// 我的余额
|
||||
walletBalanceText: '--',
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
@@ -148,6 +151,7 @@ Page({
|
||||
this.loadMyEarnings()
|
||||
this.loadPendingConfirm()
|
||||
this.loadVipStatus()
|
||||
this.loadWalletBalance()
|
||||
} else {
|
||||
const guestReadCount = app.getReadCount()
|
||||
this.setData({
|
||||
@@ -762,8 +766,10 @@ Page({
|
||||
|
||||
const routes = {
|
||||
orders: '/pages/purchases/purchases',
|
||||
giftPay: '/pages/gift-pay/list',
|
||||
referral: '/pages/referral/referral',
|
||||
withdrawRecords: '/pages/withdraw-records/withdraw-records',
|
||||
wallet: '/pages/wallet/wallet',
|
||||
about: '/pages/about/about',
|
||||
settings: '/pages/settings/settings'
|
||||
}
|
||||
@@ -848,6 +854,18 @@ Page({
|
||||
} catch (e) { console.log('[My] VIP查询失败', e) }
|
||||
},
|
||||
|
||||
async loadWalletBalance() {
|
||||
const userId = app.globalData.userInfo?.id
|
||||
if (!userId) return
|
||||
try {
|
||||
const res = await app.request({ url: `/api/miniprogram/balance?userId=${userId}`, silent: true })
|
||||
if (res?.success && res.data) {
|
||||
const balance = res.data.balance || 0
|
||||
this.setData({ walletBalanceText: balance.toFixed(2) })
|
||||
}
|
||||
} catch (e) { console.log('[My] 余额查询失败', e) }
|
||||
},
|
||||
|
||||
// 头像点击:已登录弹出选项(微信头像 / 相册)
|
||||
onAvatarTap() {
|
||||
if (!this.data.isLoggedIn) { this.showLogin(); return }
|
||||
@@ -915,6 +933,12 @@ Page({
|
||||
wx.navigateTo({ url: '/pages/profile-edit/profile-edit' })
|
||||
},
|
||||
|
||||
// 进入个人资料展示页(enhanced_professional_profile),展示页内可再进编辑
|
||||
goToProfileShow() {
|
||||
if (!this.data.isLoggedIn) { this.showLogin(); return }
|
||||
wx.navigateTo({ url: '/pages/profile-show/profile-show' })
|
||||
},
|
||||
|
||||
async handleWithdraw() {
|
||||
if (!this.data.isLoggedIn) { this.showLogin(); return }
|
||||
const amount = parseFloat(this.data.pendingEarnings)
|
||||
|
||||
@@ -34,7 +34,13 @@
|
||||
<view class="profile-meta">
|
||||
<view class="profile-name-row">
|
||||
<text class="user-name" bindtap="editNickname">{{userInfo.nickname || '点击设置昵称'}}</text>
|
||||
<view class="become-member-btn {{isVip ? 'become-member-vip' : ''}}" bindtap="goToVip">{{isVip ? '会员中心' : '成为会员'}}</view>
|
||||
<view class="profile-name-actions">
|
||||
<view class="profile-edit-btn" bindtap="goToProfileShow">
|
||||
<image class="profile-edit-icon" src="/assets/icons/edit-gray.svg" mode="aspectFit"/>
|
||||
<text class="profile-edit-text">编辑</text>
|
||||
</view>
|
||||
<view class="become-member-btn {{isVip ? 'become-member-vip' : ''}}" bindtap="goToVip">{{isVip ? '会员中心' : '成为会员'}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="vip-tags">
|
||||
<text class="vip-tag {{isVip ? 'vip-tag-active' : ''}}" bindtap="goToVip">会员</text>
|
||||
@@ -57,6 +63,10 @@
|
||||
<text class="profile-stat-val">{{earnings === '-' ? '--' : earnings}}</text>
|
||||
<text class="profile-stat-label">我的收益</text>
|
||||
</view>
|
||||
<view class="profile-stat" bindtap="handleMenuTap" data-id="wallet">
|
||||
<text class="profile-stat-val">{{walletBalanceText}}</text>
|
||||
<text class="profile-stat-label">我的余额</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -147,6 +157,13 @@
|
||||
</view>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
<view class="menu-item" bindtap="handleMenuTap" data-id="giftPay">
|
||||
<view class="menu-left">
|
||||
<view class="menu-icon-wrap icon-gold"><image class="menu-icon-img" src="/assets/icons/gift.svg" mode="aspectFit"/></view>
|
||||
<text class="menu-text">我的代付</text>
|
||||
</view>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
<view class="menu-item" bindtap="handleMenuTap" data-id="about">
|
||||
<view class="menu-left">
|
||||
<view class="menu-icon-wrap icon-blue"><image class="menu-icon-img" src="/assets/icons/info-blue.svg" mode="aspectFit"/></view>
|
||||
|
||||
@@ -59,6 +59,10 @@
|
||||
.vip-badge-gray { background: rgba(255,255,255,0.2); color: rgba(255,255,255,0.5); }
|
||||
.profile-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 12rpx; }
|
||||
.profile-name-row { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; flex-wrap: wrap; }
|
||||
.profile-name-actions { display: flex; align-items: center; gap: 16rpx; flex-shrink: 0; }
|
||||
.profile-edit-btn { display: flex; align-items: center; gap: 8rpx; padding: 8rpx 16rpx; background: rgba(255,255,255,0.08); border-radius: 12rpx; }
|
||||
.profile-edit-icon { width: 28rpx; height: 28rpx; opacity: 0.7; }
|
||||
.profile-edit-text { font-size: 24rpx; color: rgba(255,255,255,0.7); }
|
||||
.user-name {
|
||||
font-size: 44rpx; font-weight: bold; color: #fff;
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0;
|
||||
@@ -178,6 +182,8 @@
|
||||
.icon-blue .menu-icon-img { width: 32rpx; height: 32rpx; }
|
||||
.icon-gray { background: rgba(156,163,175,0.15); }
|
||||
.icon-gray .menu-icon-img { width: 32rpx; height: 32rpx; }
|
||||
.icon-gold { background: rgba(200,161,70,0.2); }
|
||||
.icon-gold .menu-icon-img { width: 32rpx; height: 32rpx; }
|
||||
.menu-text { font-size: 28rpx; color: #E5E7EB; font-weight: 500; }
|
||||
.menu-arrow { font-size: 36rpx; color: #9CA3AF; }
|
||||
|
||||
|
||||
@@ -65,6 +65,9 @@ Page({
|
||||
|
||||
// 弹窗
|
||||
showShareModal: false,
|
||||
showGiftShareModal: false,
|
||||
shareMode: '', // 'gift' = 代付分享,onShareAppMessage 返回 gift-pay/detail
|
||||
giftRequestSn: '', // 代付请求号,分享时用
|
||||
showLoginModal: false,
|
||||
agreeProtocol: false,
|
||||
showPosterModal: false,
|
||||
@@ -72,7 +75,10 @@ Page({
|
||||
isGeneratingPoster: false,
|
||||
|
||||
// 章节 mid(扫码/海报分享用,便于分享 path 带 mid)
|
||||
sectionMid: null
|
||||
sectionMid: null,
|
||||
|
||||
// 余额(用于余额支付)
|
||||
walletBalance: 0,
|
||||
},
|
||||
|
||||
async onLoad(options) {
|
||||
@@ -88,12 +94,16 @@ Page({
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// 支持 scene(扫码)、mid、id、ref
|
||||
// 支持 scene(扫码)、mid、id、ref、gift(代付)
|
||||
const sceneStr = (options && options.scene) || ''
|
||||
const parsed = parseScene(sceneStr)
|
||||
const mid = options.mid ? parseInt(options.mid, 10) : (parsed.mid || app.globalData.initialSectionMid || 0)
|
||||
let id = options.id || parsed.id || app.globalData.initialSectionId
|
||||
const ref = options.ref || parsed.ref
|
||||
const isGift = options.gift === '1' || options.gift === 'true'
|
||||
if (isGift && ref) {
|
||||
wx.setStorageSync('gift_for_ref', ref) // 代付模式:好友打开后,购买时传 giftFor(后端待支持)
|
||||
}
|
||||
if (app.globalData.initialSectionMid) delete app.globalData.initialSectionMid
|
||||
if (app.globalData.initialSectionId) delete app.globalData.initialSectionId
|
||||
|
||||
@@ -647,6 +657,55 @@ Page({
|
||||
this.setData({ showShareModal: false })
|
||||
},
|
||||
|
||||
// 代付分享弹窗:创建代付请求后分享到代付页面
|
||||
async showGiftShareModal() {
|
||||
if (!app.globalData.userInfo?.id) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const { sectionId, sectionMid } = this.data
|
||||
const productId = sectionId || ''
|
||||
if (!productId) {
|
||||
wx.showToast({ title: '章节信息异常', icon: 'none' })
|
||||
return
|
||||
}
|
||||
wx.showLoading({ title: '创建代付请求...', mask: true })
|
||||
try {
|
||||
const res = await app.request({
|
||||
url: '/api/miniprogram/gift-pay/create',
|
||||
method: 'POST',
|
||||
data: {
|
||||
userId: app.globalData.userInfo.id,
|
||||
productType: 'section',
|
||||
productId
|
||||
}
|
||||
})
|
||||
wx.hideLoading()
|
||||
if (res && res.success && res.requestSn) {
|
||||
this.setData({ showGiftShareModal: true, giftRequestSn: res.requestSn })
|
||||
} else {
|
||||
wx.showToast({ title: res?.error || '创建失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '创建失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
closeGiftShareModal() {
|
||||
this.setData({ showGiftShareModal: false })
|
||||
},
|
||||
|
||||
// 分享给好友(代付):引导用户点右上角,分享到代付详情页
|
||||
shareGiftToFriend() {
|
||||
this.setData({ shareMode: 'gift', showGiftShareModal: false })
|
||||
wx.showToast({
|
||||
title: '请点击右上角「...」→ 发送给好友',
|
||||
icon: 'none',
|
||||
duration: 2500
|
||||
})
|
||||
},
|
||||
|
||||
// 复制链接
|
||||
copyLink() {
|
||||
const userInfo = app.globalData.userInfo
|
||||
@@ -682,17 +741,27 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
// 分享到微信 - 自动带分享人ID;优先用 mid(扫码/海报闭环),无则用 id
|
||||
// 分享到微信 - 自动带分享人ID;shareMode=gift 时分享到代付详情页
|
||||
onShareAppMessage() {
|
||||
const { section, sectionId, sectionMid } = this.data
|
||||
const { section, sectionId, sectionMid, shareMode, giftRequestSn } = this.data
|
||||
const ref = app.getMyReferralCode()
|
||||
const q = sectionMid ? `mid=${sectionMid}` : `id=${sectionId}`
|
||||
const shareTitle = section?.title
|
||||
let path = ref ? `/pages/read/read?${q}&ref=${ref}` : `/pages/read/read?${q}`
|
||||
let title = section?.title
|
||||
? `📚 ${section.title.length > 20 ? section.title.slice(0, 20) + '...' : section.title}`
|
||||
: '📚 Soul创业派对 - 真实商业故事'
|
||||
if (shareMode === 'gift' && giftRequestSn) {
|
||||
path = `/pages/gift-pay/detail?requestSn=${giftRequestSn}`
|
||||
title = '好友请你帮忙代付 - Soul创业派对'
|
||||
this.setData({ shareMode: '', giftRequestSn: '' })
|
||||
} else {
|
||||
title = section?.title
|
||||
? `📚 ${section.title.length > 20 ? section.title.slice(0, 20) + '...' : section.title}`
|
||||
: '📚 Soul创业派对 - 真实商业故事'
|
||||
}
|
||||
return {
|
||||
title: shareTitle,
|
||||
path: ref ? `/pages/read/read?${q}&ref=${ref}` : `/pages/read/read?${q}`
|
||||
title,
|
||||
path
|
||||
// 不设置 imageUrl,使用当前阅读页截图作为分享卡片中间图片
|
||||
}
|
||||
},
|
||||
@@ -706,16 +775,21 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
// 分享到朋友圈:带文章标题,过长时截断(朋友圈卡片标题显示有限)
|
||||
// 分享到朋友圈:带文章标题,过长时截断;shareMode=gift 时 query 带 gift=1
|
||||
onShareTimeline() {
|
||||
const { section, sectionId, sectionMid, chapterTitle } = this.data
|
||||
const { section, sectionId, sectionMid, chapterTitle, shareMode } = this.data
|
||||
const ref = app.getMyReferralCode()
|
||||
const q = sectionMid ? `mid=${sectionMid}` : `id=${sectionId}`
|
||||
let query = ref ? `${q}&ref=${ref}` : q
|
||||
if (shareMode === 'gift' && ref) {
|
||||
query = `${q}&ref=${ref}&gift=1`
|
||||
this.setData({ shareMode: '' })
|
||||
}
|
||||
const articleTitle = (section?.title || chapterTitle || '').trim()
|
||||
const title = articleTitle
|
||||
? (articleTitle.length > 28 ? articleTitle.slice(0, 28) + '...' : articleTitle)
|
||||
: 'Soul创业派对 - 真实商业故事'
|
||||
return { title, query: ref ? `${q}&ref=${ref}` : q }
|
||||
return { title, query }
|
||||
},
|
||||
|
||||
// 显示登录弹窗(每次打开协议未勾选,符合审核要求)
|
||||
@@ -926,6 +1000,39 @@ Page({
|
||||
wx.showLoading({ title: '正在发起支付...', mask: true })
|
||||
|
||||
try {
|
||||
// 0. 尝试余额支付(若余额足够)
|
||||
const userId = app.globalData.userInfo?.id
|
||||
const referralCode = wx.getStorageSync('referral_code') || ''
|
||||
if (userId) {
|
||||
try {
|
||||
const balanceRes = await app.request({ url: `/api/miniprogram/balance?userId=${userId}`, silent: true })
|
||||
const balance = balanceRes?.data?.balance || 0
|
||||
if (balance >= amount) {
|
||||
const productId = type === 'section' ? sectionId : (type === 'fullbook' ? 'fullbook' : '')
|
||||
const consumeRes = await app.request({
|
||||
url: '/api/miniprogram/balance/consume',
|
||||
method: 'POST',
|
||||
data: {
|
||||
userId,
|
||||
productType: type,
|
||||
productId: type === 'section' ? sectionId : (type === 'fullbook' ? 'fullbook' : 'vip_annual'),
|
||||
amount,
|
||||
referralCode: referralCode || undefined
|
||||
}
|
||||
})
|
||||
if (consumeRes?.success) {
|
||||
wx.hideLoading()
|
||||
this.setData({ isPaying: false })
|
||||
wx.showToast({ title: '购买成功', icon: 'success' })
|
||||
await this.onPaymentSuccess()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Pay] 余额支付失败,改用微信支付:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 先获取openId (支付必需)
|
||||
let openId = app.globalData.openId || wx.getStorageSync('openId')
|
||||
|
||||
|
||||
@@ -93,6 +93,10 @@
|
||||
<text class="action-icon-small">🖼️</text>
|
||||
<text class="action-text-small">生成海报</text>
|
||||
</view>
|
||||
<view class="action-btn-inline btn-gift-inline" bindtap="showGiftShareModal" wx:if="{{isLoggedIn}}">
|
||||
<text class="action-icon-small">🎁</text>
|
||||
<text class="action-text-small">代付分享</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -187,6 +191,11 @@
|
||||
</view>
|
||||
|
||||
<text class="paywall-tip">分享给好友一起学习,还能赚取佣金</text>
|
||||
<!-- 代付分享:让好友帮我买 -->
|
||||
<view class="gift-share-row" bindtap="showGiftShareModal" wx:if="{{isLoggedIn}}">
|
||||
<text class="gift-share-icon">🎁</text>
|
||||
<text class="gift-share-text">找好友代付</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 章节导航 -->
|
||||
@@ -289,6 +298,23 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 代付分享弹窗:分享到代付页面,好友打开后帮他付款 -->
|
||||
<view class="modal-overlay" wx:if="{{showGiftShareModal}}" bindtap="closeGiftShareModal">
|
||||
<view class="modal-content share-modal" catchtap="stopPropagation">
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">找好友代付</text>
|
||||
<view class="modal-close" bindtap="closeGiftShareModal">✕</view>
|
||||
</view>
|
||||
<text class="share-modal-desc">分享给好友,好友打开后点击「帮他付款」即可为你代付本章</text>
|
||||
<view class="share-modal-actions">
|
||||
<view class="share-modal-btn" bindtap="shareGiftToFriend">
|
||||
<text class="btn-icon">👤</text>
|
||||
<text>分享给好友</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 支付中提示 -->
|
||||
<view class="modal-overlay" wx:if="{{isPaying}}" catchtap="">
|
||||
<view class="loading-box">
|
||||
|
||||
@@ -586,6 +586,48 @@
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
/* ===== 代付分享 ===== */
|
||||
.btn-gift-inline {
|
||||
/* 与 btn-share-inline 同风格 */
|
||||
}
|
||||
.gift-share-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
margin-top: 24rpx;
|
||||
padding: 20rpx;
|
||||
background: rgba(255, 215, 0, 0.08);
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid rgba(255, 215, 0, 0.2);
|
||||
}
|
||||
.gift-share-icon { font-size: 32rpx; }
|
||||
.gift-share-text { font-size: 28rpx; color: #FFD700; }
|
||||
.share-modal-desc {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
display: block;
|
||||
margin-bottom: 32rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.share-modal-actions {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
}
|
||||
.share-modal-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
padding: 32rpx 24rpx;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.share-modal-btn .btn-icon { font-size: 48rpx; }
|
||||
.share-modal-btn text:last-child { font-size: 26rpx; color: rgba(255, 255, 255, 0.8); }
|
||||
|
||||
/* ===== 分享弹窗 ===== */
|
||||
.share-link-box {
|
||||
padding: 32rpx;
|
||||
|
||||
@@ -36,7 +36,7 @@ Page({
|
||||
// 加载热门章节(从服务器获取点击量高的章节)
|
||||
async loadHotChapters() {
|
||||
try {
|
||||
const res = await app.request('/api/miniprogram/book/hot')
|
||||
const res = await app.request('/api/miniprogram/book/hot?limit=50')
|
||||
const list = (res && res.data) || (res && res.chapters) || []
|
||||
if (list.length > 0) {
|
||||
const hotChapters = list.map((c, i) => ({
|
||||
|
||||
@@ -84,7 +84,37 @@ Page({
|
||||
}
|
||||
}
|
||||
this.setData({ purchasing: true })
|
||||
const amount = this.data.price
|
||||
try {
|
||||
// 0. 尝试余额支付(若余额足够)
|
||||
const referralCode = wx.getStorageSync('referral_code') || ''
|
||||
try {
|
||||
const balanceRes = await app.request({ url: `/api/miniprogram/balance?userId=${userId}`, silent: true })
|
||||
const balance = balanceRes?.data?.balance || 0
|
||||
if (balance >= amount) {
|
||||
const consumeRes = await app.request({
|
||||
url: '/api/miniprogram/balance/consume',
|
||||
method: 'POST',
|
||||
data: {
|
||||
userId,
|
||||
productType: 'vip',
|
||||
productId: 'vip_annual',
|
||||
amount,
|
||||
referralCode: referralCode || undefined
|
||||
}
|
||||
})
|
||||
if (consumeRes?.success) {
|
||||
this.setData({ purchasing: false })
|
||||
wx.showToast({ title: 'VIP开通成功', icon: 'success' })
|
||||
await this._onVipPaymentSuccess()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[VIP] 余额支付失败,改用微信支付:', e)
|
||||
}
|
||||
|
||||
// 1. 微信支付
|
||||
const payRes = await app.request('/api/miniprogram/pay', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
@@ -92,7 +122,7 @@ Page({
|
||||
userId,
|
||||
productType: 'vip',
|
||||
productId: 'vip_annual',
|
||||
amount: this.data.price,
|
||||
amount,
|
||||
description: '卡若创业派对VIP年度会员(365天)'
|
||||
}
|
||||
})
|
||||
|
||||
134
miniprogram/pages/wallet/wallet.js
Normal file
134
miniprogram/pages/wallet/wallet.js
Normal file
@@ -0,0 +1,134 @@
|
||||
const app = getApp()
|
||||
const { trackClick } = require('../../utils/trackClick')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 44,
|
||||
balance: 0,
|
||||
balanceText: '0.00',
|
||||
transactions: [],
|
||||
loading: true,
|
||||
rechargeAmounts: [10, 30, 50, 100],
|
||||
selectedAmount: 30,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 44 })
|
||||
this.loadBalance()
|
||||
this.loadTransactions()
|
||||
},
|
||||
|
||||
async loadBalance() {
|
||||
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) return
|
||||
const userId = app.globalData.userInfo.id
|
||||
try {
|
||||
const res = await app.request({ url: `/api/miniprogram/balance?userId=${userId}`, silent: true })
|
||||
if (res && res.data) {
|
||||
this.setData({
|
||||
balance: res.data.balance || 0,
|
||||
balanceText: (res.data.balance || 0).toFixed(2),
|
||||
loading: false,
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
},
|
||||
|
||||
async loadTransactions() {
|
||||
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) return
|
||||
const userId = app.globalData.userInfo.id
|
||||
try {
|
||||
const res = await app.request({ url: `/api/miniprogram/balance/transactions?userId=${userId}`, silent: true })
|
||||
if (res && res.data) {
|
||||
const list = (res.data || []).map(t => ({
|
||||
...t,
|
||||
amountText: Math.abs(t.amount || 0).toFixed(2),
|
||||
amountSign: (t.amount || 0) >= 0 ? '+' : '-',
|
||||
description: t.type === 'recharge' ? '充值' : t.type === 'consume' ? '阅读消费' : t.type === 'refund' ? '退款' : '其他',
|
||||
createdAt: t.createdAt ? new Date(t.createdAt).toLocaleString('zh-CN') : '--',
|
||||
}))
|
||||
this.setData({ transactions: list })
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Wallet] load transactions failed', e)
|
||||
}
|
||||
},
|
||||
|
||||
selectAmount(e) {
|
||||
trackClick('wallet', 'tab_click', '选择金额' + (e.currentTarget.dataset.amount || ''))
|
||||
this.setData({ selectedAmount: parseInt(e.currentTarget.dataset.amount) })
|
||||
},
|
||||
|
||||
async handleRecharge() {
|
||||
trackClick('wallet', 'btn_click', '充值')
|
||||
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const userId = app.globalData.userInfo.id
|
||||
const amount = this.data.selectedAmount
|
||||
|
||||
let openId = app.globalData.openId
|
||||
if (!openId) {
|
||||
openId = await app.getOpenId()
|
||||
}
|
||||
if (!openId) {
|
||||
wx.showToast({ title: '获取支付凭证失败,请重新登录', icon: 'none', duration: 2500 })
|
||||
return
|
||||
}
|
||||
|
||||
wx.showLoading({ title: '创建订单...' })
|
||||
try {
|
||||
const res = await app.request({
|
||||
url: '/api/miniprogram/balance/recharge',
|
||||
method: 'POST',
|
||||
data: { userId, amount }
|
||||
})
|
||||
wx.hideLoading()
|
||||
if (res && res.data && res.data.orderSn) {
|
||||
const payRes = await app.request({
|
||||
url: '/api/miniprogram/pay',
|
||||
method: 'POST',
|
||||
data: {
|
||||
openId: openId,
|
||||
productType: 'balance_recharge',
|
||||
productId: res.data.orderSn,
|
||||
amount: amount,
|
||||
description: `余额充值 ¥${amount}`,
|
||||
userId: userId,
|
||||
}
|
||||
})
|
||||
const params = (payRes && payRes.data && payRes.data.payParams) ? payRes.data.payParams : (payRes && payRes.payParams ? payRes.payParams : null)
|
||||
if (params) {
|
||||
wx.requestPayment({
|
||||
...params,
|
||||
success: async () => {
|
||||
await app.request({
|
||||
url: '/api/miniprogram/balance/recharge/confirm',
|
||||
method: 'POST',
|
||||
data: { orderSn: res.data.orderSn }
|
||||
})
|
||||
wx.showToast({ title: '充值成功', icon: 'success' })
|
||||
this.loadBalance()
|
||||
this.loadTransactions()
|
||||
},
|
||||
fail: () => {
|
||||
wx.showToast({ title: '支付取消', icon: 'none' })
|
||||
}
|
||||
})
|
||||
} else {
|
||||
wx.showToast({ title: payRes?.error || '创建支付失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
console.error('[Wallet] recharge error', e)
|
||||
wx.showToast({ title: '充值失败:' + (e.message || e.errMsg || '网络异常'), icon: 'none', duration: 3000 })
|
||||
}
|
||||
},
|
||||
|
||||
goBack() {
|
||||
wx.navigateBack()
|
||||
},
|
||||
})
|
||||
4
miniprogram/pages/wallet/wallet.json
Normal file
4
miniprogram/pages/wallet/wallet.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "我的余额",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
78
miniprogram/pages/wallet/wallet.wxml
Normal file
78
miniprogram/pages/wallet/wallet.wxml
Normal file
@@ -0,0 +1,78 @@
|
||||
<!-- Soul创业派对 - 我的余额 -->
|
||||
<view class="page">
|
||||
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
|
||||
<view class="nav-back" bindtap="goBack">
|
||||
<text class="back-icon">‹</text>
|
||||
</view>
|
||||
<text class="nav-title">我的余额</text>
|
||||
<view class="nav-placeholder"></view>
|
||||
</view>
|
||||
<view class="nav-placeholder-block" style="height: {{statusBarHeight + 44}}px;"></view>
|
||||
|
||||
<view class="balance-card">
|
||||
<view class="balance-main" wx:if="{{!loading}}">
|
||||
<text class="balance-label">当前余额</text>
|
||||
<text class="balance-value">¥{{balanceText}}</text>
|
||||
<text class="balance-tip">充值后可直接用于解锁付费内容,消费记录会展示在下方。</text>
|
||||
</view>
|
||||
<view class="balance-skeleton" wx:else>
|
||||
<text class="skeleton-text">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-head">
|
||||
<text class="section-title">选择充值金额</text>
|
||||
<text class="section-note">当前已选 ¥{{selectedAmount}}</text>
|
||||
</view>
|
||||
<view class="amount-grid">
|
||||
<view
|
||||
class="amount-card {{selectedAmount === item ? 'amount-card-active' : ''}}"
|
||||
wx:for="{{rechargeAmounts}}"
|
||||
wx:key="*this"
|
||||
bindtap="selectAmount"
|
||||
data-amount="{{item}}"
|
||||
>
|
||||
<view class="amount-card-top">
|
||||
<text class="amount-card-value">¥{{item}}</text>
|
||||
<view class="amount-card-check {{selectedAmount === item ? 'amount-card-check-active' : ''}}">
|
||||
<view class="amount-card-check-dot" wx:if="{{selectedAmount === item}}"></view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="amount-card-desc">{{selectedAmount === item ? '已选中,点击充值' : '点击选择此金额'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="action-row">
|
||||
<view class="btn btn-recharge" bindtap="handleRecharge">充值</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-head">
|
||||
<text class="section-title">充值/消费记录</text>
|
||||
<text class="section-note">按时间倒序显示</text>
|
||||
</view>
|
||||
<view class="transactions" wx:if="{{transactions.length > 0}}">
|
||||
<view class="tx-item" wx:for="{{transactions}}" wx:key="id">
|
||||
<view class="tx-icon {{item.type}}">
|
||||
<text wx:if="{{item.type === 'recharge'}}">💰</text>
|
||||
<text wx:elif="{{item.type === 'gift'}}">🎁</text>
|
||||
<text wx:elif="{{item.type === 'refund'}}">↩️</text>
|
||||
<text wx:elif="{{item.type === 'consume'}}">📖</text>
|
||||
<text wx:else>•</text>
|
||||
</view>
|
||||
<view class="tx-info">
|
||||
<text class="tx-desc">{{item.description}}</text>
|
||||
<text class="tx-time">{{item.createdAt || '--'}}</text>
|
||||
</view>
|
||||
<text class="tx-amount {{item.amount >= 0 ? 'tx-amount-plus' : 'tx-amount-minus'}}">{{item.amountSign}}¥{{item.amountText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tx-empty" wx:else>
|
||||
<text>暂无充值或消费记录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bottom-space"></view>
|
||||
</view>
|
||||
256
miniprogram/pages/wallet/wallet.wxss
Normal file
256
miniprogram/pages/wallet/wallet.wxss
Normal file
@@ -0,0 +1,256 @@
|
||||
/* Soul创业派对 - 我的余额 - 深色主题 */
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #0a0a0a;
|
||||
padding-bottom: 64rpx;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: rgba(10, 10, 10, 0.95);
|
||||
backdrop-filter: blur(40rpx);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 32rpx;
|
||||
height: 88rpx;
|
||||
}
|
||||
.nav-back {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
background: #1c1c1e;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-weight: 300;
|
||||
}
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
.nav-placeholder {
|
||||
width: 64rpx;
|
||||
}
|
||||
.nav-placeholder-block {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.balance-card {
|
||||
margin: 24rpx 24rpx 32rpx;
|
||||
background: linear-gradient(135deg, #1c1c1e 0%, rgba(56, 189, 172, 0.15) 100%);
|
||||
border-radius: 32rpx;
|
||||
padding: 40rpx 32rpx;
|
||||
border: 2rpx solid rgba(56, 189, 172, 0.2);
|
||||
}
|
||||
.balance-main {
|
||||
min-height: 220rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.balance-label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.balance-value {
|
||||
font-size: 72rpx;
|
||||
font-weight: 700;
|
||||
color: #38bdac;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
.balance-tip {
|
||||
margin-top: 18rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.7;
|
||||
color: rgba(255, 255, 255, 0.58);
|
||||
}
|
||||
.balance-skeleton {
|
||||
padding: 40rpx 0;
|
||||
}
|
||||
.skeleton-text {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.section {
|
||||
margin: 0 24rpx 32rpx;
|
||||
}
|
||||
.section-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
.section-note {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
|
||||
.amount-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 20rpx;
|
||||
}
|
||||
.amount-card {
|
||||
padding: 26rpx 24rpx;
|
||||
background: linear-gradient(180deg, #19191b 0%, #151517 100%);
|
||||
border-radius: 28rpx;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.1);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.amount-card-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.amount-card-value {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
.amount-card-desc {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.46);
|
||||
}
|
||||
.amount-card-check {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.18);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.amount-card-check-active {
|
||||
border-color: #38bdac;
|
||||
background: rgba(56, 189, 172, 0.18);
|
||||
}
|
||||
.amount-card-check-dot {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
border-radius: 50%;
|
||||
background: #38bdac;
|
||||
}
|
||||
.amount-card-active {
|
||||
background: linear-gradient(180deg, rgba(56, 189, 172, 0.22) 0%, rgba(15, 30, 29, 0.95) 100%);
|
||||
border-color: rgba(56, 189, 172, 0.95);
|
||||
box-shadow: 0 0 0 2rpx rgba(56, 189, 172, 0.1);
|
||||
}
|
||||
.amount-card-active .amount-card-value {
|
||||
color: #52d8c7;
|
||||
}
|
||||
.amount-card-active .amount-card-desc {
|
||||
color: rgba(213, 255, 250, 0.72);
|
||||
}
|
||||
|
||||
.action-row {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin: 0 24rpx 40rpx;
|
||||
}
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
.btn-recharge {
|
||||
background: #38bdac;
|
||||
color: #0a0a0a;
|
||||
}
|
||||
|
||||
.transactions {
|
||||
background: #1c1c1e;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
.tx-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28rpx 32rpx;
|
||||
border-bottom: 2rpx solid rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
.tx-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.tx-icon {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
.tx-icon.recharge {
|
||||
background: rgba(56, 189, 172, 0.2);
|
||||
}
|
||||
.tx-icon.gift {
|
||||
background: rgba(255, 215, 0, 0.15);
|
||||
}
|
||||
.tx-icon.refund {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.tx-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
}
|
||||
.tx-desc {
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
}
|
||||
.tx-time {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
.tx-amount {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
.tx-amount-plus {
|
||||
color: #38bdac;
|
||||
}
|
||||
.tx-amount-minus {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
.tx-empty {
|
||||
padding: 60rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
background: #1c1c1e;
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
|
||||
.bottom-space {
|
||||
height: 48rpx;
|
||||
}
|
||||
25
miniprogram/utils/trackClick.js
Normal file
25
miniprogram/utils/trackClick.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const app = getApp()
|
||||
|
||||
/**
|
||||
* 全局按钮/标签点击埋点
|
||||
* @param {string} module 模块:home|chapters|read|my|vip|wallet|match|referral|search|settings|about
|
||||
* @param {string} action 行为:tab_click|btn_click|nav_click|card_click|link_click 等
|
||||
* @param {string} target 目标标识:按钮文案、章节ID、标签名等
|
||||
* @param {object} [extra] 附加数据
|
||||
*/
|
||||
function trackClick(module, action, target, extra) {
|
||||
const userId = app.globalData.userInfo?.id || ''
|
||||
app.request({
|
||||
url: '/api/miniprogram/track',
|
||||
method: 'POST',
|
||||
data: {
|
||||
userId: userId || undefined,
|
||||
action,
|
||||
target,
|
||||
extraData: Object.assign({ module, page: module }, extra || {})
|
||||
},
|
||||
silent: true
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
module.exports = { trackClick }
|
||||
Reference in New Issue
Block a user