完成一键登录和支付功能

This commit is contained in:
future
2026-06-28 14:49:21 +08:00
parent 74876e0923
commit 09e587cb65
94 changed files with 10284 additions and 2666 deletions
@@ -0,0 +1,398 @@
<template>
<view class="scroll-container theme-light">
<view class="card-detail-page">
<view v-if="card" class="card-detail-page__body">
<view class="cd-hero">
<view class="cd-hero__card">
<view class="cd-hero__card-header">
<text class="cd-hero__card-name">{{ card.memberCardName || card.cardName }}</text>
<view class="cd-hero__card-tag" :class="'cd-hero__card-tag--' + getTagClass(card.memberCardType || card.cardType)">
<text class="cd-hero__card-tag-text">{{ getCardTypeName(card.memberCardType || card.cardType) }}</text>
</view>
</view>
<view class="cd-hero__card-body">
<view class="cd-hero__card-item" v-if="card.memberCardValidityDays || card.validityDays">
<text class="cd-hero__card-label">有效期</text>
<text class="cd-hero__card-value">{{ card.memberCardValidityDays || card.validityDays }}</text>
</view>
<view class="cd-hero__card-item" v-if="(card.memberCardType === 'COUNT_CARD' || card.cardType === 'COUNT') && (card.memberCardTotalTimes || card.totalTimes)">
<text class="cd-hero__card-label">总次数</text>
<text class="cd-hero__card-value">{{ card.memberCardTotalTimes || card.totalTimes }}</text>
</view>
<view class="cd-hero__card-item" v-if="(card.memberCardType === 'STORED_VALUE_CARD' || card.cardType === 'BALANCE') && (card.memberCardAmount || card.amount)">
<text class="cd-hero__card-label">储值金额</text>
<text class="cd-hero__card-value">{{ card.memberCardAmount || card.amount }}</text>
</view>
</view>
<view class="cd-hero__card-footer">
<text class="cd-hero__card-price">¥{{ card.memberCardPrice || card.price }}</text>
</view>
</view>
</view>
<view class="cd-section">
<text class="cd-section__title">会员卡权益</text>
<view class="cd-benefits">
<view class="cd-benefit">
<view class="cd-benefit__icon">💪</view>
<view class="cd-benefit__content">
<text class="cd-benefit__title">全场器械使用</text>
<text class="cd-benefit__desc">免费使用所有健身器械和设备</text>
</view>
</view>
<view class="cd-benefit">
<view class="cd-benefit__icon">🏋</view>
<view class="cd-benefit__content">
<text class="cd-benefit__title">免费团课</text>
<text class="cd-benefit__desc">预约参加各类团课次卡除外</text>
</view>
</view>
<view class="cd-benefit">
<view class="cd-benefit__icon">🧴</view>
<view class="cd-benefit__content">
<text class="cd-benefit__title">淋浴服务</text>
<text class="cd-benefit__desc">免费使用淋浴间和储物柜</text>
</view>
</view>
<view class="cd-benefit">
<view class="cd-benefit__icon">📱</view>
<view class="cd-benefit__content">
<text class="cd-benefit__title">在线预约</text>
<text class="cd-benefit__desc">APP在线预约课程和签到</text>
</view>
</view>
</view>
</view>
<view class="cd-section" v-if="card.extraConfig">
<text class="cd-section__title">详细解读</text>
<view class="cd-desc">
<text class="cd-desc__text">{{ parseExtraConfig(card.extraConfig) }}</text>
</view>
</view>
<view class="cd-section">
<text class="cd-section__title">购买须知</text>
<view class="cd-notes">
<text class="cd-notes__item">1. 会员卡一经购买非特殊情况不予退款</text>
<text class="cd-notes__item">2. 请在有效期内使用过期自动失效</text>
<text class="cd-notes__item">3. 会员卡仅限本人使用不得转借他人</text>
<text class="cd-notes__item">4. 支付订单有效时间为15分钟超时自动取消</text>
</view>
</view>
<view class="cd-bottom">
<view class="cd-bottom__total">
<text class="cd-bottom__total-label">价格</text>
<text class="cd-bottom__total-price">¥{{ card.memberCardPrice || card.price }}</text>
</view>
<view class="cd-bottom__btn" @tap="handleSelectCard">
<text class="cd-bottom__btn-text">选择该卡</text>
</view>
</view>
</view>
<view v-else class="cd-loading">
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { getMemberCardById } from '@/api/main.js'
const card = ref(null)
const loading = ref(false)
function getCardTypeName(type) {
const map = {
TIME_CARD: '时长卡',
DURATION: '时长卡',
COUNT_CARD: '次卡',
COUNT: '次卡',
STORED_VALUE_CARD: '储值卡',
BALANCE: '储值卡'
}
return map[type] || type
}
function getTagClass(type) {
if (type === 'TIME_CARD' || type === 'DURATION') return 'time'
if (type === 'COUNT_CARD' || type === 'COUNT') return 'count'
if (type === 'STORED_VALUE_CARD' || type === 'BALANCE') return 'value'
return 'default'
}
function parseExtraConfig(config) {
if (!config) return ''
try {
const obj = JSON.parse(config)
return obj.description || obj.desc || config
} catch (e) {
return config
}
}
function handleSelectCard() {
if (!card.value) return
uni.navigateTo({ url: `/pages/memberInfo/purchaseCard?cardId=${card.value.memberCardId || card.value.id}` })
}
async function loadCardDetail(cardId) {
if (!cardId) return
loading.value = true
try {
const res = await getMemberCardById(cardId)
if (res && res.memberCardId) {
card.value = res
} else if (res && res.code === 200 && res.data) {
card.value = res.data
} else {
uni.showToast({ title: res?.message || '加载失败', icon: 'none' })
}
} catch (e) {
console.error('加载会员卡详情失败:', e)
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
loading.value = false
}
}
onMounted(() => {
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const options = currentPage.options || {}
const cardId = options.cardId || options.id
if (cardId) {
loadCardDetail(cardId)
}
})
onUnmounted(() => {
})
</script>
<style lang="scss">
@import '@/common/style/base.css';
@import '@/common/style/memberInfo/pages/page-reset.css';
@import '@/common/style/memberInfo/pages/sub-page-base.css';
@import '@/common/style/memberInfo/member-info-component-reset.css';
@import '@/common/style/memberInfo/member-info-sub-nav.css';
.card-detail-page {
min-height: 100vh;
background: #F5F7FA;
}
.card-detail-page__body {
padding-bottom: 120px;
}
.cd-loading {
display: flex;
justify-content: center;
align-items: center;
height: 60vh;
font-size: 14px;
color: #718096;
}
.cd-hero {
background: linear-gradient(135deg, #1A365D 0%, #2C5282 100%);
padding: 32px 24px;
}
.cd-hero__card {
background: rgba(255, 255, 255, 0.1);
border-radius: 16px;
padding: 24px;
backdrop-filter: blur(10px);
}
.cd-hero__card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.cd-hero__card-name {
font-size: 22px;
font-weight: 700;
color: #FFFFFF;
}
.cd-hero__card-tag {
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
&--time { background: rgba(66, 153, 225, 0.3); color: #90CDF4; }
&--count { background: rgba(237, 137, 54, 0.3); color: #FBD38D; }
&--value { background: rgba(56, 161, 105, 0.3); color: #9AE6B4; }
&--default { background: rgba(160, 174, 192, 0.3); color: #CBD5E0; }
}
.cd-hero__card-body {
display: flex;
gap: 24px;
margin-bottom: 20px;
}
.cd-hero__card-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.cd-hero__card-label {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
}
.cd-hero__card-value {
font-size: 18px;
font-weight: 600;
color: #FFFFFF;
}
.cd-hero__card-footer {
display: flex;
align-items: baseline;
gap: 8px;
padding-top: 16px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.cd-hero__card-price {
font-size: 32px;
font-weight: 700;
color: #F6E05E;
}
.cd-hero__card-original {
font-size: 14px;
color: rgba(255, 255, 255, 0.5);
text-decoration: line-through;
}
.cd-section {
margin: 16px;
background: #FFFFFF;
border-radius: 16px;
padding: 20px;
}
.cd-section__title {
font-size: 16px;
font-weight: 600;
color: #1A202C;
margin-bottom: 16px;
display: block;
}
.cd-benefits {
display: flex;
flex-direction: column;
gap: 16px;
}
.cd-benefit {
display: flex;
align-items: flex-start;
gap: 12px;
}
.cd-benefit__icon {
font-size: 24px;
flex-shrink: 0;
}
.cd-benefit__content {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.cd-benefit__title {
font-size: 14px;
font-weight: 600;
color: #2D3748;
}
.cd-benefit__desc {
font-size: 12px;
color: #718096;
}
.cd-desc__text {
font-size: 14px;
color: #4A5568;
line-height: 1.6;
}
.cd-notes {
display: flex;
flex-direction: column;
gap: 8px;
}
.cd-notes__item {
font-size: 13px;
color: #718096;
line-height: 1.5;
}
.cd-bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 24px;
padding-bottom: calc(16px + env(safe-area-inset-bottom));
background: #FFFFFF;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.05);
z-index: 100;
}
.cd-bottom__total {
display: flex;
align-items: baseline;
gap: 8px;
}
.cd-bottom__total-label {
font-size: 14px;
color: #718096;
}
.cd-bottom__total-price {
font-size: 28px;
font-weight: 700;
color: #E53E3E;
}
.cd-bottom__btn {
background: linear-gradient(135deg, #1677FF 0%, #0958D9 100%);
border-radius: 12px;
padding: 16px 48px;
transition: all 0.2s;
&:active {
transform: scale(0.98);
opacity: 0.9;
}
}
.cd-bottom__btn-text {
font-size: 16px;
font-weight: 600;
color: #FFFFFF;
}
</style>