feat: 代付做完了

This commit is contained in:
Alex-larget
2026-03-18 12:40:51 +08:00
parent f276595ad6
commit 1fa20756a8
51 changed files with 2380 additions and 791 deletions

View File

@@ -6,58 +6,24 @@
const { parseScene } = require('./utils/scene.js')
const { checkAndExecute } = require('./utils/ruleEngine.js')
const PRODUCTION_URL = 'https://soulapi.quwanzhi.com'
const TEST_URL = 'https://souldev.quwanzhi.com'
const LOCAL_URL = 'http://localhost:8080'
const DEFAULT_APP_ID = 'wxb8bbb2b10dec74aa'
const DEFAULT_MCH_ID = '1318592501'
const DEFAULT_WITHDRAW_TMPL_ID = 'u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE'
function getRuntimeBootstrapConfig() {
try {
const accountInfo = wx.getAccountInfoSync?.()
const envVersion = accountInfo?.miniProgram?.envVersion || 'release'
const extCfg = wx.getExtConfigSync ? (wx.getExtConfigSync() || {}) : {}
// 按运行环境自动切换 API 地址
let baseUrl
if (envVersion === 'release') {
baseUrl = PRODUCTION_URL
} else if (envVersion === 'trial') {
baseUrl = extCfg.apiBaseUrl || wx.getStorageSync('apiBaseUrl') || TEST_URL
} else {
// develop不使用 storage避免被 loadMpConfig 曾写入的生产地址污染env-switch 仍可运行时覆盖
baseUrl = extCfg.apiBaseUrl || LOCAL_URL
}
return {
baseUrl,
appId: extCfg.appId || DEFAULT_APP_ID,
mchId: extCfg.mchId || DEFAULT_MCH_ID,
withdrawSubscribeTmplId: extCfg.withdrawSubscribeTmplId || DEFAULT_WITHDRAW_TMPL_ID
}
} catch (_) {
return {
baseUrl: PRODUCTION_URL,
appId: DEFAULT_APP_ID,
mchId: DEFAULT_MCH_ID,
withdrawSubscribeTmplId: DEFAULT_WITHDRAW_TMPL_ID
}
}
}
const bootstrapConfig = getRuntimeBootstrapConfig()
App({
globalData: {
// API 基础地址:优先外部配置/缓存,其次默认生产环境
baseUrl: bootstrapConfig.baseUrl,
// API 基础地址:开发时修改下面一行切换环境
baseUrl: "https://soulapi.quwanzhi.com",
// baseUrl: 'http://localhost:8080', // 开发
// baseUrl: 'https://souldev.quwanzhi.com', // 测试
// 小程序配置 - 真实AppID
appId: bootstrapConfig.appId,
appId: DEFAULT_APP_ID,
// 订阅消息:用户点击「申请提现」→「立即提现」时会先弹出订阅授权窗
withdrawSubscribeTmplId: bootstrapConfig.withdrawSubscribeTmplId,
withdrawSubscribeTmplId: DEFAULT_WITHDRAW_TMPL_ID,
// 微信支付配置
mchId: bootstrapConfig.mchId,
mchId: DEFAULT_MCH_ID,
// 用户信息
userInfo: null,
@@ -110,14 +76,10 @@ App({
// 审核模式:后端 /api/miniprogram/config 返回 auditMode=true 时隐藏所有支付相关UI
auditMode: false,
// 客服/微信mp_config 返回 supportWechat
supportWechat: '',
// API 域名loadMpConfig 从 config 更新
apiDomain: ''
supportWechat: ''
},
onLaunch(options) {
// 清理已废弃的调试环境 storage取消环境切换后不再使用
try { wx.removeStorageSync('debug_env_override') } catch (_) {}
this.globalData.readSectionIds = wx.getStorageSync('readSectionIds') || []
// 获取系统信息
this.getSystemInfo()
@@ -381,7 +343,7 @@ App({
}
},
// 加载 mpConfigappId、mchId、withdrawSubscribeTmplId、auditMode、supportWechat、apiDomain 等),失败时保留 globalData 默认值
// 加载 mpConfigappId、mchId、withdrawSubscribeTmplId、auditMode、supportWechat 等),失败时保留 globalData 默认值
async loadMpConfig() {
try {
const res = await this.request({ url: '/api/miniprogram/config', silent: true, timeout: 5000 })
@@ -390,15 +352,6 @@ App({
if (mp.appId) this.globalData.appId = mp.appId
if (mp.mchId) this.globalData.mchId = mp.mchId
if (mp.withdrawSubscribeTmplId) this.globalData.withdrawSubscribeTmplId = mp.withdrawSubscribeTmplId
// 仅正式版使用后端 apiDomain开发/体验版保持 bootstrap 的 baseUrl避免被生产地址覆盖
try {
const envVersion = wx.getAccountInfoSync?.()?.miniProgram?.envVersion || 'release'
if (envVersion === 'release' && mp.apiDomain) {
this.globalData.baseUrl = mp.apiDomain
this.globalData.apiDomain = mp.apiDomain
try { wx.setStorageSync('apiBaseUrl', mp.apiDomain) } catch (_) {}
}
} catch (_) {}
this.globalData.auditMode = !!mp.auditMode
this.globalData.supportWechat = mp.supportWechat || mp.customerWechat || mp.serviceWechat || ''
// 通知当前已加载的页面刷新 auditMode从后台切回时配置更新后立即生效

View File

@@ -24,7 +24,8 @@
"pages/profile-edit/profile-edit",
"pages/avatar-nickname/avatar-nickname",
"pages/gift-pay/detail",
"pages/gift-pay/list"
"pages/gift-pay/list",
"pages/gift-pay/redemption-detail"
],
"window": {
"backgroundTextStyle": "light",
@@ -59,9 +60,6 @@
}
]
},
"usingComponents": {
"env-switch": "/components/env-switch/env-switch"
},
"navigateToMiniProgramAppIdList": [
"wx6489c26045912fe1",
"wx3d15ed02e98b04e3"

View File

@@ -83,7 +83,6 @@
<text class="section-lock {{section.isFree || isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1 ? 'lock-open' : 'lock-closed'}}">{{section.isFree || isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1 ? '○' : '●'}}</text>
<text class="section-title {{section.isFree || isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1 ? '' : 'text-muted'}}">{{section.id}} {{section.title}}</text>
<text wx:if="{{section.isNew}}" class="tag tag-new">NEW</text>
<text wx:if="{{section.isPremium}}" class="tag tag-vip">增值</text>
</view>
<view class="section-right">
<text wx:if="{{section.isFree}}" class="tag tag-free">免费</text>
@@ -133,5 +132,4 @@
<!-- 底部留白 -->
<view class="bottom-space"></view>
<env-switch />
</view>

View File

@@ -1,6 +1,6 @@
/**
* Soul创业派对 - 代付详情页
* 好友打开后看到订单信息,点击「帮他付款」完成代付
* 改造后:发起人支付,好友领取。支持单页模式引导、登录检测。
*/
const app = getApp()
@@ -8,42 +8,81 @@ Page({
data: {
statusBarHeight: 44,
requestSn: '',
sectionId: '',
detail: null,
loading: true,
paying: false,
redeeming: false,
isInitiator: false,
requesterMsg: '',
amountDisplay: '0.00' // 预格式化金额WXML 中 toFixed 可能不生效
amountDisplay: '0.00',
isSinglePageMode: false,
showLoginModal: false,
agreeProtocol: false,
// 创建态
isCreateMode: false,
giftQuantity: 1,
unitPrice: 0
},
onLoad(options) {
wx.showShareMenu({ menus: ['shareAppMessage', 'shareTimeline'] })
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 44 })
const requestSn = (options.requestSn || '').trim()
if (!requestSn) {
wx.showToast({ title: '代付链接无效', icon: 'none' })
const sectionId = (options.sectionId || '').trim()
const isSinglePage = (wx.getSystemInfoSync?.()?.mode === 'singlePage') || app.globalData.isSinglePageMode
this.setData({ requestSn, sectionId, isSinglePageMode: !!isSinglePage })
if (requestSn || sectionId) {
this.loadDetail()
} else {
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
const { requestSn, sectionId } = this.data
this.setData({ loading: true })
const userId = app.globalData.userInfo?.id || ''
let url = ''
if (requestSn) {
url = `/api/miniprogram/gift-pay/detail?requestSn=${encodeURIComponent(requestSn)}${userId ? '&userId=' + encodeURIComponent(userId) : ''}`
} else if (sectionId && userId) {
url = `/api/miniprogram/gift-pay/detail?sectionId=${encodeURIComponent(sectionId)}&userId=${encodeURIComponent(userId)}`
} else if (sectionId) {
this.setData({ loading: false })
wx.showToast({ title: '请先登录', icon: 'none' })
return
} else {
this.setData({ loading: false })
return
}
try {
const res = await app.request(`/api/miniprogram/gift-pay/detail?requestSn=${encodeURIComponent(requestSn)}`)
const res = await app.request(url)
if (res && res.success) {
const myId = app.globalData.userInfo?.id || ''
const isInitiator = !!myId && res.initiatorUserId === myId
const requesterMsg = isInitiator
? '分享给好友,好友打开后即可为你完成支付。'
: (res.initiatorMsg || `" 请帮我代付「${res.sectionTitle || res.description || '该商品'}」,非常感谢! "`)
const amountDisplay = (res.amount != null && res.amount !== '')
? Number(res.amount).toFixed(2)
: '0.00'
this.setData({ detail: res, loading: false, isInitiator, requesterMsg, amountDisplay })
const isCreateMode = res.mode === 'create' || res.action === 'create'
const isInitiator = res.isInitiator === true
let requesterMsg = ''
let amountDisplay = '0.00'
if (isCreateMode) {
requesterMsg = '输入发放数量,支付后分享给好友免费领取'
amountDisplay = (res.unitPrice != null ? Number(res.unitPrice) * (this.data.giftQuantity || 1) : 0).toFixed(2)
} else {
requesterMsg = isInitiator
? (res.action === 'pay' ? '支付后分享给好友,好友打开即可免费领取。' : '分享给好友,好友打开即可免费领取。')
: res.initiatorMsg || `" 请帮我代付「${res.sectionTitle || res.description || '该商品'}」,非常感谢! "`
amountDisplay = (res.amount != null && res.amount !== '') ? Number(res.amount).toFixed(2) : '0.00'
}
this.setData({
detail: res,
loading: false,
isInitiator,
isCreateMode,
requesterMsg,
amountDisplay,
unitPrice: res.unitPrice != null ? res.unitPrice : 0
})
if (isCreateMode) this._updateAmountDisplay()
} else {
this.setData({ loading: false })
wx.showToast({ title: res?.error || '加载失败', icon: 'none' })
@@ -54,8 +93,34 @@ Page({
}
},
async doPay() {
// 优先尝试静默获取 openId有会话时可直接发起支付
onGiftQuantityInput(e) {
const raw = (e.detail.value || '').trim()
const v = parseInt(raw, 10)
this.setData({ giftQuantity: isNaN(v) ? (raw === '' ? '' : this.data.giftQuantity) : v })
this._updateAmountDisplay()
},
_updateAmountDisplay() {
const { unitPrice, giftQuantity } = this.data
const q = Math.max(0, parseInt(giftQuantity, 10) || 0)
const amount = (unitPrice || 0) * q
this.setData({ amountDisplay: amount.toFixed(2) })
},
// 发起人支付(改造后:我帮别人付款)
async doInitiatorPay() {
if (this.data.isSinglePageMode) {
wx.showModal({
title: '朋友圈单页',
content: '当前为朋友圈单页,无法支付。请点击底部「前往小程序」进入完整版后再支付。',
showCancel: false
})
return
}
const userId = app.globalData.userInfo?.id || ''
if (!userId) {
wx.showToast({ title: '请先登录后再支付', icon: 'none' })
return
}
let openId = app.globalData.openId || wx.getStorageSync('openId')
if (!openId) {
wx.showLoading({ title: '获取支付凭证...', mask: true })
@@ -63,31 +128,47 @@ Page({
wx.hideLoading()
}
if (!openId) {
const { confirm } = await new Promise(r => {
wx.showModal({
title: '请先登录',
content: '登录后可帮他完成代付',
confirmText: '去登录',
cancelText: '取消',
success: res => r(res)
})
})
if (confirm) wx.switchTab({ url: '/pages/my/my' })
wx.showToast({ title: '请先登录', icon: 'none' })
return
}
const { requestSn, detail } = this.data
if (!requestSn || !detail) return
let { requestSn, sectionId, detail, giftQuantity, isCreateMode } = this.data
if (!requestSn && isCreateMode && sectionId) {
const q = parseInt(giftQuantity, 10)
if (isNaN(q) || q !== Math.floor(q) || q < 1) {
wx.showToast({ title: '发放份数须为正整数', icon: 'none' })
return
}
const quantity = q
wx.showLoading({ title: '创建中...', mask: true })
try {
const createRes = await app.request({
url: '/api/miniprogram/gift-pay/create',
method: 'POST',
data: { userId, productType: 'section', productId: sectionId, quantity }
})
if (!createRes?.success || !createRes.requestSn) {
throw new Error(createRes?.error || '创建失败')
}
requestSn = createRes.requestSn
this.setData({ requestSn, isCreateMode: false })
} catch (e) {
wx.hideLoading()
wx.showToast({ title: e.message || e.error || '创建失败', icon: 'none' })
return
}
}
if (!requestSn) return
this.setData({ paying: true })
wx.showLoading({ title: '创建订单中...', mask: true })
try {
const res = await app.request({
url: '/api/miniprogram/gift-pay/pay',
url: '/api/miniprogram/gift-pay/initiator-pay',
method: 'POST',
data: {
requestSn,
openId,
userId: app.globalData.userInfo?.id || ''
userId
}
})
wx.hideLoading()
@@ -95,47 +176,131 @@ Page({
throw new Error(res?.error || '创建订单失败')
}
const payParams = res.data.payParams
payParams._orderSn = res.data.orderSn
const orderSn = res.data.orderSn
// 与正常章节支付一致:只传 5 个必需参数,不传 appId 等多余字段
await new Promise((resolve, reject) => {
wx.requestPayment({
...payParams,
signType: payParams.signType || 'MD5',
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType || 'RSA',
paySign: payParams.paySign,
success: resolve,
fail: reject
})
})
wx.showToast({ title: '代付成功', icon: 'success' })
wx.showToast({ title: '支付成功', icon: 'success' })
this.setData({ paying: false })
setTimeout(() => {
wx.navigateBack({ fail: () => wx.switchTab({ url: '/pages/index/index' }) })
}, 1500)
// 主动同步订单状态(与 read 页一致)
if (orderSn) {
try {
await app.request(`/api/miniprogram/pay?orderSn=${encodeURIComponent(orderSn)}`, { silent: true })
} catch (e) {
console.warn('[GiftPay] 主动同步订单失败:', e)
}
}
this.loadDetail()
} catch (e) {
this.setData({ paying: false })
const msg = e.message || e.error || e.errMsg || '支付失败'
if (e.errMsg && e.errMsg.includes('cancel')) {
wx.showToast({ title: '已取消支付', icon: 'none' })
} else {
const errMsg = e.message || e.error || '支付失败'
const isPrepayError = /prepay_id|创建订单|支付请求/i.test(errMsg)
if (isPrepayError) {
const { confirm } = await new Promise(r => {
wx.showModal({
title: '订单创建失败',
content: errMsg + '\n\n是否重新创建订单并重试',
confirmText: '重新创建订单',
cancelText: '取消',
success: res => r(res)
})
})
if (confirm) this.doPay()
} else {
wx.showToast({ title: errMsg, icon: 'none' })
}
wx.showToast({ title: msg, icon: 'none', duration: 2500 })
}
}
},
// 好友领取(改造后:免费获得章节)
async doRedeem() {
if (this.data.isSinglePageMode) {
wx.showModal({
title: '朋友圈单页',
content: '当前为朋友圈单页,无法登录领取。请点击底部「前往小程序」进入完整版后再领取。',
showCancel: false
})
return
}
const userId = app.globalData.userInfo?.id
if (!userId) {
this.setData({ showLoginModal: true, agreeProtocol: false })
return
}
await this._doRedeem()
},
async _doRedeem() {
const { requestSn } = this.data
const userId = app.globalData.userInfo?.id
if (!requestSn || !userId) return
this.setData({ redeeming: true })
wx.showLoading({ title: '领取中...', mask: true })
try {
const res = await app.request({
url: '/api/miniprogram/gift-pay/redeem',
method: 'POST',
data: { requestSn, userId }
})
wx.hideLoading()
this.setData({ redeeming: false })
if (res && res.success) {
wx.showToast({ title: '领取成功', icon: 'success' })
const mid = res.sectionMid || res.sectionId
const q = mid ? `mid=${mid}` : `id=${res.sectionId || ''}`
setTimeout(() => {
wx.navigateTo({ url: `/pages/read/read?${q}` })
}, 800)
} else {
wx.showToast({ title: res?.error || '领取失败', icon: 'none' })
}
} catch (e) {
this.setData({ redeeming: false })
wx.hideLoading()
wx.showToast({ title: e.message || e.error || '领取失败', icon: 'none' })
}
},
closeLoginModal() {
this.setData({ showLoginModal: false })
},
toggleAgree() {
this.setData({ agreeProtocol: !this.data.agreeProtocol })
},
async handleWechatLogin() {
if (!this.data.agreeProtocol) {
wx.showToast({ title: '请先阅读并同意用户协议和隐私政策', icon: 'none' })
return
}
try {
const result = await app.login()
if (!result) return
this.setData({ showLoginModal: false, agreeProtocol: false })
await this._doRedeem()
} catch (e) {
wx.showToast({ title: '登录失败', icon: 'none' })
}
},
async handlePhoneLogin(e) {
if (!e.detail.code) return this.handleWechatLogin()
try {
const result = await app.loginWithPhone(e.detail.code)
if (!result) return
this.setData({ showLoginModal: false })
await this._doRedeem()
} catch (e) {
wx.showToast({ title: '登录失败', icon: 'none' })
}
},
stopPropagation() {},
openUserProtocol() {
wx.navigateTo({ url: '/pages/agreement/agreement' })
},
openPrivacy() {
wx.navigateTo({ url: '/pages/privacy/privacy' })
},
goBack() {
app.goBackOrToHome()
},
@@ -156,9 +321,29 @@ Page({
onShareAppMessage() {
const { requestSn } = this.data
const ref = app.getMyReferralCode?.() || app.globalData.userInfo?.referralCode || ''
let path = '/pages/gift-pay/detail'
if (requestSn) {
path = `/pages/gift-pay/detail?requestSn=${requestSn}`
if (ref) path += `&ref=${encodeURIComponent(ref)}`
}
return {
title: '好友请你帮忙代付 - Soul创业派对',
path: requestSn ? `/pages/gift-pay/detail?requestSn=${requestSn}` : '/pages/gift-pay/detail'
title: '好友送你一篇好文 - Soul创业派对',
path
}
},
onShareTimeline() {
const { requestSn } = this.data
const ref = app.getMyReferralCode?.() || app.globalData.userInfo?.referralCode || ''
let query = ''
if (requestSn) {
query = `requestSn=${requestSn}`
if (ref) query += `&ref=${encodeURIComponent(ref)}`
}
return {
title: '好友送你一篇好文 - Soul创业派对',
query: query || ''
}
}
})

View File

@@ -1,4 +1,4 @@
<!-- Soul创业派对 - 代付详情页(参考 yulan代付视角+发起视角,忽略 nav -->
<!-- Soul创业派对 - 代付详情页(改造后:发起人支付,好友领取 -->
<view class="page">
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
<view class="nav-content">
@@ -6,7 +6,7 @@
<text class="back-arrow">←</text>
</view>
<view class="nav-info">
<text class="nav-title">{{isInitiator ? '找朋友代付' : '帮他付款'}}</text>
<text class="nav-title">{{isInitiator ? '代付分享' : (detail.action === 'redeem' ? '免费领取' : '代付详情')}}</text>
</view>
<view class="nav-right-placeholder"></view>
</view>
@@ -31,23 +31,35 @@
<text class="hero-title">{{detail.sectionTitle || detail.description || '代付商品'}}</text>
<text class="hero-desc" wx:if="{{detail.contentPreview}}">{{detail.contentPreview}}</text>
<view class="hero-footer">
<view class="hero-amount-wrap">
<text class="hero-amount-label">应付金额</text>
<view class="hero-amount-wrap" wx:if="{{!isCreateMode}}">
<text class="hero-amount-label">{{detail.quantity > 1 ? '应付金额(' + detail.quantity + '份)' : '应付金额'}}</text>
<view class="hero-amount-row">
<text class="hero-currency">¥</text>
<text class="hero-amount">{{amountDisplay}}</text>
</view>
</view>
<view class="hero-arrow-wrap" bindtap="goToArticle" wx:if="{{detail.productType === 'section' && detail.productId}}">
<view class="hero-amount-wrap" wx:elif="{{isCreateMode}}">
<text class="hero-amount-label">发放份数</text>
<view class="gift-quantity-row">
<input class="gift-quantity-input" type="number" value="{{giftQuantity}}" bindinput="onGiftQuantityInput" placeholder="请输入份数"/>
<text class="hero-amount-label">份 × ¥{{detail.unitPrice || 0}}</text>
</view>
<view class="hero-amount-row">
<text class="hero-currency">¥</text>
<text class="hero-amount">{{amountDisplay}}</text>
</view>
<view class="create-tip">创建后无法退款</view>
</view>
<view class="hero-arrow-wrap" bindtap="goToArticle" wx:if="{{!isCreateMode && !isInitiator && detail.productType === 'section' && detail.productId}}">
<image class="hero-arrow" src="/assets/icons/arrow-right.svg" mode="aspectFit"/>
</view>
<view class="hero-arrow-wrap hero-arrow-placeholder" wx:else></view>
<view class="hero-arrow-wrap hero-arrow-placeholder" wx:elif="{{!isCreateMode && !isInitiator}}"></view>
</view>
</view>
</section>
<!-- 发起人信息 -->
<section class="requester-card">
<!-- 发起人信息(发起人视角不展示) -->
<section class="requester-card" wx:if="{{!isCreateMode && !isInitiator}}">
<view class="requester-header" bindtap="goToInitiatorProfile">
<view class="requester-avatar">
<image wx:if="{{detail.initiatorAvatar}}" class="avatar-img" src="{{detail.initiatorAvatar}}" mode="aspectFill"/>
@@ -64,8 +76,8 @@
</view>
</section>
<!-- 安全徽章 -->
<view class="security-badge">
<!-- 安全徽章(发起人视角不展示) -->
<view class="security-badge" wx:if="{{!isCreateMode && !isInitiator}}">
<text class="security-icon">🛡</text>
<text class="security-text">安全支付保障 · 资金由平台托管</text>
</view>
@@ -87,16 +99,55 @@
<text class="footer-currency">¥</text>{{amountDisplay}}
</text>
</view>
<!-- 发起人:分享给好友 -->
<button wx:if="{{isInitiator}}" class="footer-btn share-btn" open-type="share">
<!-- 单页模式:引导前往小程序 -->
<view wx:if="{{isSinglePageMode}}" class="footer-tip-single">
<text>请点击底部「前往小程序」进入完整版后再操作</text>
</view>
<!-- 发起人 创建态 或 action=pay去支付 -->
<button wx:elif="{{(isCreateMode || (isInitiator && detail.action === 'pay'))}}" class="footer-btn pay-btn" bindtap="doInitiatorPay" disabled="{{paying}}">
<image class="btn-icon" src="/assets/icons/wallet.svg" mode="aspectFit"/>
<text>{{paying ? '支付中...' : (isCreateMode ? '去支付' : '立即支付')}}</text>
</button>
<!-- 发起人 action=share发送给好友 -->
<button wx:elif="{{isInitiator && detail.action === 'share'}}" class="footer-btn share-btn" open-type="share">
<image class="btn-icon" src="/assets/icons/share.svg" mode="aspectFit"/>
<text>发送给好友</text>
</button>
<!-- 好友:帮他付款 -->
<button wx:else class="footer-btn pay-btn" bindtap="doPay" disabled="{{paying}}">
<image class="btn-icon" src="/assets/icons/wallet.svg" mode="aspectFit"/>
<text>{{paying ? '支付中...' : '立即帮他付款'}}</text>
<!-- 好友 action=redeem领取并阅读 -->
<button wx:elif="{{!isInitiator && detail.action === 'redeem'}}" class="footer-btn redeem-btn" bindtap="doRedeem" disabled="{{redeeming}}">
<image class="btn-icon" src="/assets/icons/arrow-right.svg" mode="aspectFit"/>
<text>{{redeeming ? '领取中...' : '领取并阅读'}}</text>
</button>
<!-- 好友 action=alreadyRedeemed已领取去阅读 -->
<button wx:elif="{{!isInitiator && detail.action === 'alreadyRedeemed'}}" class="footer-btn redeem-btn" bindtap="goToArticle">
<image class="btn-icon" src="/assets/icons/arrow-right.svg" mode="aspectFit"/>
<text>已领取,去阅读</text>
</button>
<!-- 好友 action=wait待发起人支付 -->
<view wx:else class="footer-btn footer-btn-disabled">
<text>待发起人支付</text>
</view>
</view>
</view>
<!-- 登录弹窗(好友领取时未登录) -->
<view class="modal-overlay" wx:if="{{showLoginModal}}" bindtap="closeLoginModal">
<view class="modal-content login-modal" catchtap="stopPropagation">
<view class="modal-close" bindtap="closeLoginModal">✕</view>
<view class="login-icon">🔐</view>
<text class="login-title">登录 Soul创业派对</text>
<text class="login-desc">登录后可免费领取并阅读</text>
<button class="btn-wechat {{agreeProtocol ? '' : 'btn-wechat-disabled'}}" bindtap="handleWechatLogin" disabled="{{!agreeProtocol}}">
<text class="btn-wechat-icon">微</text>
<text>微信快捷登录</text>
</button>
<view class="login-agree-row" catchtap="toggleAgree">
<view class="agree-checkbox {{agreeProtocol ? 'agree-checked' : ''}}">{{agreeProtocol ? '✓' : ''}}</view>
<text class="agree-text">我已阅读并同意</text>
<text class="agree-link" bindtap="openUserProtocol">《用户协议》</text>
<text class="agree-text">和</text>
<text class="agree-link" bindtap="openPrivacy">《隐私政策》</text>
</view>
</view>
</view>
@@ -106,5 +157,4 @@
<view class="bg-glow bg-glow-2"></view>
<view class="bg-dots"></view>
</view>
<env-switch />
</view>

View File

@@ -186,6 +186,32 @@
gap: 4rpx;
}
.gift-quantity-row {
display: flex;
align-items: center;
gap: 16rpx;
margin: 12rpx 0;
}
.create-tip {
margin-top: 16rpx;
font-size: 24rpx;
color: rgba(255, 255, 255, 0.45);
}
.gift-quantity-input {
width: 120rpx;
height: 64rpx;
padding: 0 20rpx;
background: rgba(255, 255, 255, 0.06);
border: 1rpx solid rgba(255, 255, 255, 0.1);
border-radius: 12rpx;
font-size: 32rpx;
font-weight: 600;
color: #fff;
text-align: center;
}
.hero-currency {
font-size: 36rpx;
font-weight: 700;
@@ -420,6 +446,29 @@
opacity: 0.6;
}
.redeem-btn {
background: #14b8a6;
color: #000;
}
.redeem-btn[disabled] {
opacity: 0.6;
}
.footer-tip-single {
flex: 1;
font-size: 24rpx;
color: rgba(255, 255, 255, 0.5);
text-align: center;
padding: 0 24rpx;
}
.footer-btn-disabled {
background: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.5);
cursor: not-allowed;
}
.footer-btn:active {
transform: scale(0.98);
}
@@ -430,7 +479,8 @@
}
.share-btn .btn-icon,
.pay-btn .btn-icon {
.pay-btn .btn-icon,
.redeem-btn .btn-icon {
filter: brightness(0);
}
@@ -479,3 +529,68 @@
background-image: radial-gradient(rgba(255,255,255,0.02) 1rpx, transparent 1rpx);
background-size: 64rpx 64rpx;
}
/* 登录弹窗 */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 48rpx;
}
.modal-content.login-modal {
width: 100%;
max-width: 600rpx;
background: #1c1c1e;
border-radius: 32rpx;
padding: 48rpx;
text-align: center;
position: relative;
}
.modal-close {
position: absolute;
top: 24rpx;
right: 24rpx;
font-size: 32rpx;
color: rgba(255, 255, 255, 0.5);
}
.login-icon { font-size: 96rpx; display: block; margin-bottom: 24rpx; }
.login-title { font-size: 36rpx; font-weight: 700; color: #fff; display: block; margin-bottom: 16rpx; }
.login-desc { font-size: 26rpx; color: rgba(255, 255, 255, 0.6); display: block; margin-bottom: 48rpx; }
.btn-wechat {
width: 100%;
padding: 28rpx;
background: #07c160;
color: #fff;
font-size: 30rpx;
font-weight: 600;
border-radius: 24rpx;
border: none;
}
.btn-wechat-disabled { opacity: 0.5; }
.btn-wechat-icon { font-weight: 700; margin-right: 8rpx; }
.login-agree-row {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
margin-top: 32rpx;
font-size: 22rpx;
color: rgba(255, 255, 255, 0.5);
}
.agree-checkbox {
width: 32rpx;
height: 32rpx;
border: 2rpx solid rgba(255, 255, 255, 0.3);
border-radius: 8rpx;
margin-right: 12rpx;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
}
.agree-checked { background: #14b8a6; border-color: #14b8a6; }
.agree-link { color: #14b8a6; }

View File

@@ -1,15 +1,12 @@
/**
* Soul创业派对 - 我的代付
* Tab: 我发起的 / 我帮付的
* Soul创业派对 - 我发起的代付(改造后:仅我发起的,含领取记录)
*/
const app = getApp()
Page({
data: {
statusBarHeight: 44,
tab: 'requests',
requests: [],
payments: [],
loading: false
},
@@ -19,17 +16,11 @@ Page({
},
onShow() {
if (this.data.requests.length > 0 || this.data.payments.length > 0) {
if (this.data.requests.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) {
@@ -38,13 +29,8 @@ Page({
}
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 })
}
const res = await app.request(`/api/miniprogram/gift-pay/my-requests?userId=${encodeURIComponent(userId)}`)
this.setData({ requests: (res && res.list) || [], loading: false })
} catch (e) {
this.setData({ loading: false })
}
@@ -53,18 +39,21 @@ Page({
goToDetail(e) {
const requestSn = e.currentTarget.dataset.sn
if (requestSn) {
wx.navigateTo({ url: `/pages/gift-pay/detail?requestSn=${encodeURIComponent(requestSn)}` })
wx.navigateTo({ url: `/pages/gift-pay/redemption-detail?requestSn=${encodeURIComponent(requestSn)}` })
}
},
shareRequest(e) {
e.stopPropagation()
wx.showToast({ title: '请点击右上角「...」分享给好友', icon: 'none', duration: 2500 })
if (e && typeof e.stopPropagation === 'function') e.stopPropagation()
const requestSn = e?.currentTarget?.dataset?.sn
if (requestSn) {
wx.navigateTo({ url: `/pages/gift-pay/detail?requestSn=${encodeURIComponent(requestSn)}` })
}
},
async cancelRequest(e) {
e.stopPropagation()
const requestSn = e.currentTarget.dataset.sn
if (e && typeof e.stopPropagation === 'function') e.stopPropagation()
const requestSn = e?.currentTarget?.dataset?.sn
if (!requestSn) return
const ok = await new Promise(r => {
wx.showModal({ title: '取消代付', content: '确定取消该代付请求?', success: res => r(res.confirm) })
@@ -78,7 +67,8 @@ Page({
})
if (res && res.success) {
wx.showToast({ title: '已取消', icon: 'success' })
this.loadData()
const requests = (this.data.requests || []).filter(r => r.requestSn !== requestSn)
this.setData({ requests })
} else {
wx.showToast({ title: res?.error || '取消失败', icon: 'none' })
}

View File

@@ -1,4 +1,4 @@
<!-- Soul创业派对 - 我的代付 -->
<!-- Soul创业派对 - 我的代付(改造后:仅我发起的,含领取记录) -->
<view class="page">
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
<view class="nav-content">
@@ -6,59 +6,47 @@
<text class="back-arrow">←</text>
</view>
<view class="nav-info">
<text class="nav-title">我的代付</text>
<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">
<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="{{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" bindtap="goToDetail" data-sn="{{item.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 wx:elif="{{requests.length === 0}}">
<view class="empty">暂无发起的代付</view>
</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" bindtap="goToDetail" data-sn="{{item.requestSn}}">
<view class="card-row">
<text class="desc">{{item.description}}</text>
<text class="amount">¥{{item.amount}}</text>
<view class="card" wx:for="{{requests}}" wx:key="requestSn" bindtap="goToDetail" data-sn="{{item.requestSn}}">
<view class="card-row">
<text class="desc">{{item.description}}</text>
<text class="amount">¥{{item.amount}}</text>
</view>
<view class="card-row card-meta">
<text class="quantity" wx:if="{{item.quantity > 1}}">{{item.quantity}}</text>
<text class="redeemed" wx:if="{{item.status === 'paid'}}">已领 {{item.redeemedCount || 0}}/{{item.quantity || 1}}</text>
<text class="status {{item.status}}">{{item.status === 'pending' || item.status === 'pending_pay' ? '待支付' : item.status === 'paid' ? '已支付' : item.status === 'cancelled' ? '已取消' : '已过期'}}</text>
<view class="actions" wx:if="{{item.status === 'pending' || item.status === 'pending_pay'}}">
<text class="action-text cancel" catchtap="cancelRequest" data-sn="{{item.requestSn}}">取消</text>
</view>
<view class="card-row">
<text class="status {{item.status}}">{{item.status === 'paid' ? '已支付' : item.status}}</text>
<view class="actions" wx:elif="{{item.status === 'paid'}}">
<text class="action-text" catchtap="shareRequest" data-sn="{{item.requestSn}}">分享</text>
</view>
</view>
</block>
<view class="redeem-list" wx:if="{{item.redeemList && item.redeemList.length > 0}}">
<text class="redeem-title">领取记录:</text>
<view class="redeem-item" wx:for="{{item.redeemList}}" wx:for-item="redeem" wx:key="userId">
<text class="redeem-nickname">{{redeem.nickname || '用户'}}</text>
<text class="redeem-time">{{redeem.redeemAt}}</text>
</view>
</view>
</view>
</block>
</view>
</view>

View File

@@ -43,28 +43,6 @@
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;
}
@@ -120,6 +98,46 @@
margin-bottom: 0;
}
.card-meta {
flex-wrap: wrap;
gap: 12rpx;
}
.quantity, .redeemed {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
}
.redeem-list {
margin-top: 16rpx;
padding-top: 16rpx;
border-top: 1rpx solid rgba(255, 255, 255, 0.08);
}
.redeem-title {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.5);
display: block;
margin-bottom: 8rpx;
}
.redeem-item {
display: flex;
justify-content: space-between;
font-size: 24rpx;
color: rgba(255, 255, 255, 0.7);
padding: 4rpx 0;
}
.redeem-nickname {
flex: 1;
}
.redeem-time {
color: rgba(255, 255, 255, 0.5);
font-size: 22rpx;
}
.desc {
font-size: 28rpx;
color: #fff;

View File

@@ -0,0 +1,80 @@
/**
* Soul创业派对 - 代付领取详情(发起人查看:文章信息、领取人明细、剩余份数)
*/
const app = getApp()
Page({
data: {
statusBarHeight: 44,
requestSn: '',
detail: null,
loading: true,
remaining: 0
},
onLoad(options) {
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 44 })
const requestSn = (options.requestSn || '').trim()
if (!requestSn) {
wx.showToast({ title: '链接无效', icon: 'none' })
setTimeout(() => wx.navigateBack(), 1500)
return
}
this.setData({ requestSn })
this.loadDetail()
},
async loadDetail() {
const { requestSn } = this.data
const userId = app.globalData.userInfo?.id || ''
if (!userId) {
this.setData({ loading: false })
wx.showToast({ title: '请先登录', icon: 'none' })
return
}
this.setData({ loading: true })
try {
const res = await app.request(
`/api/miniprogram/gift-pay/detail?requestSn=${encodeURIComponent(requestSn)}&userId=${encodeURIComponent(userId)}`
)
if (res && res.success) {
const q = res.quantity || 0
const redeemed = res.redeemedCount || 0
const remaining = Math.max(0, q - redeemed)
this.setData({
detail: res,
remaining,
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' })
}
},
goToDetail() {
const { requestSn } = this.data
if (requestSn) {
wx.navigateTo({ url: `/pages/gift-pay/detail?requestSn=${encodeURIComponent(requestSn)}` })
}
},
goToArticle() {
const { detail } = this.data
if (!detail) return
const mid = detail.productMid || 0
const id = detail.productId || ''
if (detail.productType === 'section' && (mid || id)) {
const q = mid ? `mid=${mid}` : `id=${id}`
wx.navigateTo({ url: `/pages/read/read?${q}` })
}
},
goBack() {
app.goBackOrToHome()
}
})

View File

@@ -0,0 +1,3 @@
{
"usingComponents": {}
}

View File

@@ -0,0 +1,71 @@
<!-- 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}}">
<!-- 文章信息 -->
<section class="article-card">
<text class="article-title">{{detail.sectionTitle || detail.description || '代付商品'}}</text>
<text class="article-preview" wx:if="{{detail.contentPreview}}">{{detail.contentPreview}}</text>
<view class="article-meta">
<text class="meta-label">总份数</text>
<text class="meta-value">{{detail.quantity || 0}} 份</text>
</view>
<view class="article-meta">
<text class="meta-label">剩余份数</text>
<text class="meta-value highlight">{{remaining}} 份</text>
</view>
<view class="article-actions">
<view class="btn-link" bindtap="goToArticle" wx:if="{{detail.productType === 'section' && (detail.productMid || detail.productId)}}">
<text>去阅读</text>
</view>
<view class="btn-link" bindtap="goToDetail">
<text>{{detail.status === 'paid' ? '去分享' : detail.status === 'pending_pay' ? '去支付' : '查看详情'}}</text>
</view>
</view>
</section>
<!-- 领取人明细 -->
<section class="redeem-section">
<view class="section-header">
<text class="section-title">领取记录</text>
<text class="section-count" wx:if="{{detail.redeemList && detail.redeemList.length > 0}}">共 {{detail.redeemList.length}} 人</text>
</view>
<view class="redeem-list" wx:if="{{detail.redeemList && detail.redeemList.length > 0}}">
<view class="redeem-item" wx:for="{{detail.redeemList}}" wx:key="userId">
<view class="redeem-user">
<image class="redeem-avatar" src="{{item.avatar || '/assets/icons/user.svg'}}" mode="aspectFill"/>
<text class="redeem-nickname">{{item.nickname || '用户'}}</text>
</view>
<text class="redeem-time">{{item.redeemAt}}</text>
</view>
</view>
<view class="redeem-empty" wx:else>
<text>暂无领取记录</text>
</view>
</section>
</block>
<block wx:else>
<view class="empty">
<text>代付请求不存在或已处理</text>
</view>
</block>
</view>
</view>

View File

@@ -0,0 +1,234 @@
/* Soul创业派对 - 代付领取详情 */
.page {
min-height: 100vh;
background: #050505;
}
.nav-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: rgba(5, 5, 5, 0.6);
backdrop-filter: blur(40rpx);
-webkit-backdrop-filter: blur(40rpx);
border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
}
.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: rgba(255, 255, 255, 0.06);
display: flex;
align-items: center;
justify-content: center;
}
.nav-back:active {
opacity: 0.7;
}
.back-arrow {
font-size: 36rpx;
color: rgba(255, 255, 255, 0.9);
}
.nav-title {
font-size: 28rpx;
font-weight: 700;
color: rgba(255, 255, 255, 0.5);
letter-spacing: 0.2em;
text-transform: uppercase;
}
.content {
padding: 24rpx 24rpx 80rpx;
}
.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(20, 184, 166, 0.2);
border-top-color: #14b8a6;
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.45);
}
.empty {
text-align: center;
padding: 80rpx 0;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.4);
}
/* 文章信息卡片 */
.article-card {
background: rgba(24, 24, 27, 0.8);
border: 1rpx solid rgba(255, 255, 255, 0.1);
border-radius: 24rpx;
padding: 32rpx;
margin-bottom: 24rpx;
}
.article-title {
display: block;
font-size: 32rpx;
font-weight: 600;
color: #fff;
line-height: 1.4;
margin-bottom: 16rpx;
}
.article-preview {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.6);
line-height: 1.5;
margin-bottom: 24rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.article-meta {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12rpx 0;
font-size: 28rpx;
}
.meta-label {
color: rgba(255, 255, 255, 0.5);
}
.meta-value {
color: rgba(255, 255, 255, 0.9);
}
.meta-value.highlight {
color: #14b8a6;
font-weight: 600;
}
.article-actions {
display: flex;
gap: 24rpx;
margin-top: 24rpx;
padding-top: 24rpx;
border-top: 1rpx solid rgba(255, 255, 255, 0.06);
}
.btn-link {
padding: 16rpx 32rpx;
background: rgba(20, 184, 166, 0.15);
border-radius: 12rpx;
font-size: 28rpx;
color: #14b8a6;
}
.btn-link:active {
opacity: 0.8;
}
/* 领取记录 */
.redeem-section {
background: rgba(24, 24, 27, 0.8);
border: 1rpx solid rgba(255, 255, 255, 0.1);
border-radius: 24rpx;
padding: 32rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.section-title {
font-size: 28rpx;
font-weight: 600;
color: rgba(255, 255, 255, 0.9);
}
.section-count {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.5);
}
.redeem-list {
max-height: 400rpx;
overflow-y: auto;
}
.redeem-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.06);
font-size: 28rpx;
}
.redeem-item:last-child {
border-bottom: none;
}
.redeem-user {
display: flex;
align-items: center;
gap: 16rpx;
}
.redeem-avatar {
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
}
.redeem-nickname {
color: rgba(255, 255, 255, 0.9);
}
.redeem-time {
color: rgba(255, 255, 255, 0.5);
font-size: 24rpx;
}
.redeem-empty {
padding: 40rpx 0;
text-align: center;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.4);
}

View File

@@ -190,5 +190,4 @@
</view>
</view>
</view>
<env-switch />
</view>

View File

@@ -325,5 +325,4 @@
<!-- 底部留白 -->
<view class="bottom-space"></view>
<env-switch />
</view>

View File

@@ -253,5 +253,4 @@
</view>
<view class="bottom-space"></view>
<env-switch />
</view>

View File

@@ -66,6 +66,8 @@ Page({
// 弹窗
showShareModal: false,
showGiftModal: false,
giftQuantity: 1,
showLoginModal: false,
agreeProtocol: false,
showPosterModal: false,
@@ -666,39 +668,18 @@ Page({
this.setData({ showShareModal: false })
},
// 找好友代付:创建代付请求后跳转代付页(美团式,在代付页分享
async showGiftShareModal() {
// 代付分享:直接跳转代付页,在代付页输入数量并支付(简化流程
showGiftShareModal() {
if (!app.globalData.userInfo?.id) {
wx.showToast({ title: '请先登录', icon: 'none' })
return
}
const { sectionId, sectionMid } = this.data
const productId = sectionId || ''
if (!productId) {
const { sectionId } = this.data
if (!sectionId) {
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) {
wx.navigateTo({ url: `/pages/gift-pay/detail?requestSn=${res.requestSn}` })
} else {
wx.showToast({ title: res?.error || '创建失败', icon: 'none' })
}
} catch (e) {
wx.hideLoading()
wx.showToast({ title: '创建失败', icon: 'none' })
}
wx.navigateTo({ url: `/pages/gift-pay/detail?sectionId=${encodeURIComponent(sectionId)}` })
},
// 复制链接

View File

@@ -195,10 +195,10 @@
<view class="paywall-audit-tip" wx:if="{{auditMode}}">审核中,暂不支持购买</view>
<text class="paywall-tip" wx:if="{{!auditMode}}">分享给好友一起学习,还能赚取佣金</text>
<!-- 代付分享:好友帮我买(审核模式隐藏) -->
<!-- 代付分享:好友买(审核模式隐藏) -->
<view class="gift-share-row" bindtap="showGiftShareModal" wx:if="{{isLoggedIn && !auditMode}}">
<text class="gift-share-icon">🎁</text>
<text class="gift-share-text">找好友代付</text>
<text class="gift-share-text">代付分享</text>
</view>
</view>
@@ -314,5 +314,4 @@
<view class="fab-share" bindtap="shareToMoments">
<text class="fab-moments-icon">🌐</text>
</view>
<env-switch />
</view>

View File

@@ -618,6 +618,55 @@
}
.gift-share-icon { font-size: 32rpx; }
.gift-share-text { font-size: 28rpx; color: #FFD700; }
/* 代付分享弹窗 */
.gift-modal { padding: 32rpx; }
.gift-desc {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
line-height: 1.5;
display: block;
margin-bottom: 32rpx;
}
.gift-form { margin-bottom: 32rpx; }
.gift-label {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.6);
display: block;
margin-bottom: 16rpx;
}
.form-input-wrap {
padding: 16rpx 24rpx;
background: #1F2937;
border-radius: 16rpx;
}
.form-input-inner {
width: 100%;
font-size: 28rpx;
color: #fff;
background: transparent;
}
.gift-actions {
display: flex;
gap: 24rpx;
}
.gift-btn {
flex: 1;
padding: 24rpx;
text-align: center;
font-size: 30rpx;
border-radius: 24rpx;
}
.gift-cancel {
background: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.8);
}
.gift-confirm {
background: #00CED1;
color: #000;
font-weight: 600;
}
.share-modal-desc {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.6);

View File

@@ -329,5 +329,4 @@
</view>
</view>
</view>
<env-switch />
</view>

View File

@@ -112,5 +112,4 @@
</view>
</view>
</view>
<env-switch />
</view>

View File

@@ -177,5 +177,4 @@
</view>
</view>
</view>
<env-switch />
</view>

View File

@@ -52,5 +52,4 @@
</view>
<view class="bottom-space"></view>
<env-switch />
</view>

View File

@@ -78,5 +78,4 @@
</view>
<view class="bottom-space"></view>
<env-switch />
</view>

View File

@@ -23,12 +23,19 @@
"condition": {
"miniprogram": {
"list": [
{
"name": "pages/gift-pay/list",
"pathName": "pages/gift-pay/list",
"query": "",
"scene": null,
"launchMode": "default"
},
{
"name": "代付",
"pathName": "pages/gift-pay/detail",
"query": "requestSn=GPRMP20260317145140501100",
"scene": null,
"launchMode": "default"
"launchMode": "default",
"scene": null
},
{
"name": "唤醒",