feat(paid-member): 添加付费会员功能模块

- 新增付费会员API接口层,支持会员查询、下单、记录及报价
- 新增付费会员页面,展示权益、推荐商品及购买流程
- 新增付费会员购买记录页面,支持订单状态查看与继续支付
- 新增 PaidMemberPrice 组件用于商品列表和详情页展示会员价
- 个人中心页添加付费会员入口,支持配置化展示标题与背景
- 商品详情页集成会员价展示,支持秒杀和拼团场景下隐藏
- 商品列表组件集成会员价展示,支持批量请求与防竞态
- 支付页适配付费会员支付流程,支持H5轮询与结果回调
- 订单确认页优惠文案更新为"活动优惠(含会员优惠)"
- pages.json 新增付费会员及购买记录页面路由配置
- README 更新升级计划,按时间倒序补充已完成功能明细
This commit is contained in:
pikachu1995@126.com
2026-11-05 22:37:12 +08:00
parent 06af86af3f
commit cc71d20aa6
12 changed files with 1244 additions and 973 deletions

View File

@@ -68,7 +68,7 @@
</template>
<script setup lang="ts">
import { ref, getCurrentInstance, onMounted, computed } from 'vue'
import { onLoad, onBackPress } from '@dcloudio/uni-app'
import { onLoad, onBackPress, onUnload } from '@dcloudio/uni-app'
import { useStore } from '@/store'
import * as API_Trade from '@/api/trade'
import { payCallback } from '@/api/members'
@@ -88,6 +88,8 @@ const paymentType = ref('')
const paymentClient = ref('')
const walletValue = ref(0)
const autoCancelTime = ref(0)
let h5PollTimer: ReturnType<typeof setInterval> | undefined
onUnload(() => { if (h5PollTimer) clearInterval(h5PollTimer) })
onLoad((val) => {
routerVal.value = val || {}
@@ -107,7 +109,9 @@ onLoad((val) => {
onBackPress((e) => {
if (e.from == 'backbutton') {
if (routerVal.value.recharge_sn) {
if (routerVal.value.paid_member_sn) {
uni.redirectTo({ url: '/pages/mine/paidMemberRecords' })
} else if (routerVal.value.recharge_sn) {
uni.switchTab({ url: '/pages/tabbar/user/my' })
} else {
uni.navigateTo({ url: '/pages/order/myOrder?status=0' })
@@ -126,6 +130,10 @@ function hideLoadingIfNeeded() {
}
function callback(paymentMethod: string) {
if (orderType.value === 'PAID_MEMBER') {
uni.redirectTo({ url: '/pages/mine/paidMember?payment_sn=' + encodeURIComponent(sn.value) })
return
}
const goSuccess = () => {
uni.redirectTo({
url:
@@ -145,7 +153,10 @@ function callback(paymentMethod: string) {
function cashierData() {
const parms: Record<string, string> = {}
if (routerVal.value.recharge_sn) {
if (routerVal.value.paid_member_sn) {
sn.value = routerVal.value.paid_member_sn
orderType.value = 'PAID_MEMBER'
} else if (routerVal.value.recharge_sn) {
sn.value = routerVal.value.recharge_sn
orderType.value = 'RECHARGE'
} else if (routerVal.value.trade_sn) {
@@ -212,6 +223,11 @@ function onPayTimeout() {
}
function goPayError(message = '支付失败,如果您已支付,请勿反复支付') {
if (orderType.value === 'PAID_MEMBER') {
uni.showToast({ title: '支付未完成,请在购买记录中查看结果', icon: 'none' })
uni.redirectTo({ url: '/pages/mine/paidMemberRecords' })
return
}
const query = [`orderSn=${encodeURIComponent(sn.value)}`, `message=${encodeURIComponent(message)}`]
uni.navigateTo({ url: `/pages/cart/payment/error?${query.join('&')}` })
}
@@ -276,15 +292,24 @@ async function pay(payment: string) {
})
hideLoadingIfNeeded()
} else {
window.location.href = JSON.parse(response.result).h5_url
const paymentUrl = typeof response.result === 'string' && response.result.startsWith('http')
? response.result : JSON.parse(response.result).h5_url
const returnUrl = orderType.value === 'PAID_MEMBER'
? window.location.href.split('#')[0] + '#/pages/mine/paidMember?payment_sn=' + encodeURIComponent(sn.value)
: window.location.href
window.location.href = paymentUrl + (paymentUrl.includes('?') ? '&' : '?') + 'redirect_url=' + encodeURIComponent(returnUrl)
const searchParams = { ...params, price: cashierParams.value }
const timer = setInterval(() => {
let attempts = 0
if (h5PollTimer) clearInterval(h5PollTimer)
h5PollTimer = setInterval(() => {
if (++attempts > 100) { clearInterval(h5PollTimer); return }
payCallback(searchParams).then((cbRes) => {
if (cbRes.data.result) {
clearInterval(timer)
uni.navigateTo({ url: '/pages/order/myOrder' })
clearInterval(h5PollTimer)
if (orderType.value === 'PAID_MEMBER') callback(paymentMethod)
else uni.navigateTo({ url: '/pages/order/myOrder' })
}
})
}).catch(() => {})
}, 3000)
hideLoadingIfNeeded()
}