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:
Alex-larget
2026-03-17 11:44:36 +08:00
parent b971420090
commit 0d12ab1d07
65 changed files with 3836 additions and 180 deletions

View 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()
},
})

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "我的余额",
"navigationStyle": "custom"
}

View 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>

View 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;
}