feat: connect comprehensive profile and stored value payment
This commit is contained in:
@@ -20,6 +20,9 @@ const pagesJson = readFileSync(resolve(srcRoot, 'pages.json'), 'utf8')
|
|||||||
const mallPage = readFileSync(resolve(srcRoot, 'pages/comprehensive/mall/index.vue'), 'utf8')
|
const mallPage = readFileSync(resolve(srcRoot, 'pages/comprehensive/mall/index.vue'), 'utf8')
|
||||||
const availablePage = readFileSync(resolve(srcRoot, 'pages/comprehensive/membership/available-sessions.vue'), 'utf8')
|
const availablePage = readFileSync(resolve(srcRoot, 'pages/comprehensive/membership/available-sessions.vue'), 'utf8')
|
||||||
const bookingDetail = readFileSync(resolve(srcRoot, 'pages/comprehensive/membership/booking-detail.vue'), 'utf8')
|
const bookingDetail = readFileSync(resolve(srcRoot, 'pages/comprehensive/membership/booking-detail.vue'), 'utf8')
|
||||||
|
const profilePage = readFileSync(resolve(srcRoot, 'pages/comprehensive/profile/index.vue'), 'utf8')
|
||||||
|
const orderPage = readFileSync(resolve(srcRoot, 'pages/comprehensive/profile/orders.vue'), 'utf8')
|
||||||
|
const payPage = readFileSync(resolve(srcRoot, 'pages/comprehensive/pay/index.vue'), 'utf8')
|
||||||
|
|
||||||
expectIncludes(membershipApi, "url: '/api/membership/plans'", 'membership plans')
|
expectIncludes(membershipApi, "url: '/api/membership/plans'", 'membership plans')
|
||||||
expectIncludes(membershipApi, "url: '/api/membership/orders'", 'membership order')
|
expectIncludes(membershipApi, "url: '/api/membership/orders'", 'membership order')
|
||||||
@@ -40,6 +43,14 @@ expectIncludes(mallPage, 'face_value', 'face value display')
|
|||||||
expectIncludes(availablePage, 'available_card_ids', 'available card selection')
|
expectIncludes(availablePage, 'available_card_ids', 'available card selection')
|
||||||
expectExcludes(bookingDetail, 'booking.order', 'card booking has no order')
|
expectExcludes(bookingDetail, 'booking.order', 'card booking has no order')
|
||||||
|
|
||||||
|
expectIncludes(profilePage, '/pages/comprehensive/mall/index', 'mall entry')
|
||||||
|
expectIncludes(profilePage, '/pages/comprehensive/membership/cards', 'my cards entry')
|
||||||
|
expectIncludes(profilePage, '/pages/comprehensive/profile/balance', 'balance records entry')
|
||||||
|
expectIncludes(profilePage, '/pages/comprehensive/profile/orders', 'consumption records entry')
|
||||||
|
expectIncludes(orderPage, "value: 'membership'", 'membership filter')
|
||||||
|
expectIncludes(orderPage, "value: 'stored_value'", 'stored value filter')
|
||||||
|
expectIncludes(payPage, "order.value?.business_type === 'stored_value'", 'stored value payment guard')
|
||||||
|
|
||||||
if (failures.length) {
|
if (failures.length) {
|
||||||
console.error(`Comprehensive commerce verification failed with ${failures.length} issue(s):`)
|
console.error(`Comprehensive commerce verification failed with ${failures.length} issue(s):`)
|
||||||
for (const failure of failures) console.error(`- ${failure}`)
|
for (const failure of failures) console.error(`- ${failure}`)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
<view class="card method-card">
|
<view class="card method-card">
|
||||||
<text class="card-title">支付方式</text>
|
<text class="card-title">支付方式</text>
|
||||||
<view
|
<view
|
||||||
|
v-if="!isStoredValueOrder"
|
||||||
class="method-item"
|
class="method-item"
|
||||||
:class="{ active: payMethod === 'balance', disabled: !canUseBalance }"
|
:class="{ active: payMethod === 'balance', disabled: !canUseBalance }"
|
||||||
@tap="selectMethod('balance')"
|
@tap="selectMethod('balance')"
|
||||||
@@ -73,7 +74,11 @@ const isSubmitting = ref(false)
|
|||||||
const nowTimestamp = ref(Date.now())
|
const nowTimestamp = ref(Date.now())
|
||||||
let countdownTimer = null
|
let countdownTimer = null
|
||||||
|
|
||||||
const canUseBalance = computed(() => toAmountNumber(balance.value.balance) >= toAmountNumber(order.value?.amount))
|
const isStoredValueOrder = computed(() => order.value?.business_type === 'stored_value')
|
||||||
|
const canUseBalance = computed(() => {
|
||||||
|
if (isStoredValueOrder.value) return false
|
||||||
|
return toAmountNumber(balance.value.balance) >= toAmountNumber(order.value?.amount)
|
||||||
|
})
|
||||||
const canPay = computed(() => order.value?.status === 'pending_pay' && (payMethod.value !== 'balance' || canUseBalance.value))
|
const canPay = computed(() => order.value?.status === 'pending_pay' && (payMethod.value !== 'balance' || canUseBalance.value))
|
||||||
const payButtonText = computed(() => {
|
const payButtonText = computed(() => {
|
||||||
if (order.value?.status !== 'pending_pay') return '订单不可支付'
|
if (order.value?.status !== 'pending_pay') return '订单不可支付'
|
||||||
@@ -108,7 +113,11 @@ const loadPage = async () => {
|
|||||||
])
|
])
|
||||||
order.value = orderData
|
order.value = orderData
|
||||||
balance.value = balanceData
|
balance.value = balanceData
|
||||||
if (!canUseBalance.value) payMethod.value = 'wechat'
|
if (isStoredValueOrder.value) {
|
||||||
|
payMethod.value = 'wechat'
|
||||||
|
} else if (!canUseBalance.value) {
|
||||||
|
payMethod.value = 'wechat'
|
||||||
|
}
|
||||||
updateCountdownTimer()
|
updateCountdownTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,15 +140,22 @@ const stopCountdown = () => {
|
|||||||
const padTime = (value) => String(value).padStart(2, '0')
|
const padTime = (value) => String(value).padStart(2, '0')
|
||||||
|
|
||||||
const selectMethod = (method) => {
|
const selectMethod = (method) => {
|
||||||
if (method === 'balance' && !canUseBalance.value) {
|
if (method === 'balance') {
|
||||||
|
if (isStoredValueOrder.value) {
|
||||||
|
uni.showToast({ title: '储值卡订单仅支持微信支付', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!canUseBalance.value) {
|
||||||
uni.showToast({ title: '余额不足', icon: 'none' })
|
uni.showToast({ title: '余额不足', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
payMethod.value = method
|
payMethod.value = method
|
||||||
}
|
}
|
||||||
|
|
||||||
const pay = async () => {
|
const pay = async () => {
|
||||||
if (!canPay.value || isSubmitting.value) return
|
if (!canPay.value || isSubmitting.value) return
|
||||||
|
if (isStoredValueOrder.value && payMethod.value === 'balance') return
|
||||||
isSubmitting.value = true
|
isSubmitting.value = true
|
||||||
try {
|
try {
|
||||||
if (payMethod.value === 'balance') {
|
if (payMethod.value === 'balance') {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<view v-if="session.member?.phone" class="hero-sub-row">
|
<view v-if="session.member?.phone" class="hero-sub-row">
|
||||||
<text class="hero-phone">{{ session.member.phone }}</text>
|
<text class="hero-phone">{{ session.member.phone }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
<text class="hero-balance">余额 ¥{{ balanceText }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="hero-right">
|
<view class="hero-right">
|
||||||
@@ -71,25 +72,40 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useComprehensiveSessionStore } from '@/modules/comprehensive/stores/session'
|
import { useComprehensiveSessionStore } from '@/modules/comprehensive/stores/session'
|
||||||
|
import { memberApi } from '@/modules/comprehensive/api/member'
|
||||||
|
import { formatAmount } from '@/modules/comprehensive/utils/money'
|
||||||
import VenueTabBar from '@/components/VenueTabBar.vue'
|
import VenueTabBar from '@/components/VenueTabBar.vue'
|
||||||
import AgreementLinks from '@/components/AgreementLinks.vue'
|
import AgreementLinks from '@/components/AgreementLinks.vue'
|
||||||
import UnauthLoginPanel from '@/components/UnauthLoginPanel.vue'
|
import UnauthLoginPanel from '@/components/UnauthLoginPanel.vue'
|
||||||
|
|
||||||
const session = useComprehensiveSessionStore()
|
const session = useComprehensiveSessionStore()
|
||||||
const avatarInitial = computed(() => (session.member?.nickname || '用户').slice(0, 1))
|
const avatarInitial = computed(() => (session.member?.nickname || '用户').slice(0, 1))
|
||||||
|
const balance = ref('0.00')
|
||||||
|
const balanceText = computed(() => formatAmount(balance.value))
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ title: '包场预约', url: '/pages/comprehensive/private/index', theme: 'private-theme', icon: 'private' },
|
{ title: '包场预约', url: '/pages/comprehensive/private/index', theme: 'private-theme', icon: 'private' },
|
||||||
{ title: '篮球门票', url: '/pages/comprehensive/tickets/index', theme: 'ticket-theme', icon: 'ticket' },
|
{ title: '篮球门票', url: '/pages/comprehensive/tickets/index', theme: 'ticket-theme', icon: 'ticket' },
|
||||||
|
{ title: '商城', url: '/pages/comprehensive/mall/index', theme: 'order-theme', icon: 'order' },
|
||||||
|
{ title: '充值', url: '/pages/comprehensive/mall/index', theme: 'order-theme', icon: 'order' },
|
||||||
|
{ title: '我的次卡', url: '/pages/comprehensive/membership/cards', theme: 'profile-theme', icon: 'profile' },
|
||||||
{ title: '我的订单', url: '/pages/comprehensive/profile/orders', theme: 'order-theme', icon: 'order' },
|
{ title: '我的订单', url: '/pages/comprehensive/profile/orders', theme: 'order-theme', icon: 'order' },
|
||||||
|
{ title: '余额流水', url: '/pages/comprehensive/profile/balance', theme: 'order-theme', icon: 'order' },
|
||||||
|
{ title: '消费记录', url: '/pages/comprehensive/profile/orders', theme: 'order-theme', icon: 'order' },
|
||||||
{ title: '个人信息', url: '/pages/comprehensive/profile/detail', theme: 'profile-theme', icon: 'profile' },
|
{ title: '个人信息', url: '/pages/comprehensive/profile/detail', theme: 'profile-theme', icon: 'profile' },
|
||||||
{ title: '切换场馆', url: '/pages/venue-select/index?mode=switch', theme: 'profile-theme', icon: 'profile' }
|
{ title: '切换场馆', url: '/pages/venue-select/index?mode=switch', theme: 'profile-theme', icon: 'profile' }
|
||||||
]
|
]
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await session.init()
|
await session.init()
|
||||||
|
try {
|
||||||
|
const data = await memberApi.getBalance()
|
||||||
|
balance.value = data.balance
|
||||||
|
} catch {
|
||||||
|
balance.value = '0.00'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
@@ -159,6 +175,7 @@ const go = (url) => {
|
|||||||
|
|
||||||
.hero-name,
|
.hero-name,
|
||||||
.hero-phone,
|
.hero-phone,
|
||||||
|
.hero-balance,
|
||||||
.menu-title {
|
.menu-title {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -176,6 +193,13 @@ const go = (url) => {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hero-balance {
|
||||||
|
margin-top: 4rpx;
|
||||||
|
color: $color-primary;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
.hero-arrow,
|
.hero-arrow,
|
||||||
.menu-arrow {
|
.menu-arrow {
|
||||||
width: 18rpx;
|
width: 18rpx;
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ const businessOptions = [
|
|||||||
{ label: '全部', value: '' },
|
{ label: '全部', value: '' },
|
||||||
{ label: '课程', value: 'course' },
|
{ label: '课程', value: 'course' },
|
||||||
{ label: '包场', value: 'private_booking' },
|
{ label: '包场', value: 'private_booking' },
|
||||||
{ label: '门票', value: 'ticket' }
|
{ label: '门票', value: 'ticket' },
|
||||||
|
{ label: '会员卡', value: 'membership' },
|
||||||
|
{ label: '储值卡', value: 'stored_value' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const statusOptions = [
|
const statusOptions = [
|
||||||
|
|||||||
Reference in New Issue
Block a user