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:
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;
|
||||
}
|
||||
Reference in New Issue
Block a user