feat: 完善课程订阅
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
</view>
|
||||
|
||||
<!-- Error state -->
|
||||
<view v-else-if="!card" class="error-wrap">
|
||||
<view v-else-if="!card && !showAll" class="error-wrap">
|
||||
<text class="error-icon">😕</text>
|
||||
<text class="error-text">会员卡信息加载失败</text>
|
||||
<view class="retry-btn" @tap="loadCard">
|
||||
@@ -20,7 +20,50 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Card content -->
|
||||
<!-- All cards list mode -->
|
||||
<template v-else-if="showAll">
|
||||
<view v-if="loading" class="loading-wrap">
|
||||
<view class="skeleton-header" />
|
||||
<view class="skeleton-body">
|
||||
<view class="skeleton-line w80" />
|
||||
<view class="skeleton-line w60" />
|
||||
<view class="skeleton-line w40" />
|
||||
</view>
|
||||
</view>
|
||||
<view v-else-if="allCards.length" class="all-cards-list">
|
||||
<view
|
||||
v-for="c in allCards"
|
||||
:key="c.id"
|
||||
class="card-row"
|
||||
@tap="goToDetail(c.id)"
|
||||
>
|
||||
<view class="card-thumb" :class="thumbClass(c)">
|
||||
<view class="thumb-fallback">
|
||||
<text class="thumb-name">{{ truncate(c.name, 8) }}</text>
|
||||
<text class="thumb-price">¥{{ formatPrice(c.price) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-info">
|
||||
<text class="card-name">{{ c.name }}</text>
|
||||
<text class="card-validity">有效期:{{ c.durationDays }} 天</text>
|
||||
<view class="price-row">
|
||||
<text class="price-current">¥{{ formatPrice(c.price) }}</text>
|
||||
<text
|
||||
v-if="c.originalPrice && c.originalPrice > c.price"
|
||||
class="price-original"
|
||||
>
|
||||
原价:¥{{ formatPrice(c.originalPrice) }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="empty-state">
|
||||
<text class="empty-text">暂无可购买的会员卡</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- Card content (single card mode) -->
|
||||
<template v-else>
|
||||
<!-- Hero section -->
|
||||
<view class="card-hero" :class="heroClass">
|
||||
@@ -142,9 +185,11 @@ const navBarHeight = ref('64px')
|
||||
// ─── Route params ──────────────────────────────────────────
|
||||
const cardId = ref<string>('')
|
||||
const isTrial = ref(false)
|
||||
const showAll = ref(false)
|
||||
|
||||
// ─── State ────────────────────────────────────────────────
|
||||
const card = ref<CardType | null>(null)
|
||||
const allCards = ref<CardType[]>([])
|
||||
const loading = ref(false)
|
||||
const buying = ref(false)
|
||||
|
||||
@@ -181,7 +226,12 @@ async function loadCard() {
|
||||
loading.value = true
|
||||
try {
|
||||
const types = await get<CardType[]>('/membership/card-types')
|
||||
const activeTypes = types.filter((c) => c.isActive)
|
||||
const activeTypes = types.filter((c) => c.isActive).sort((a, b) => a.sortOrder - b.sortOrder)
|
||||
|
||||
if (showAll.value) {
|
||||
allCards.value = activeTypes
|
||||
return
|
||||
}
|
||||
|
||||
if (isTrial.value) {
|
||||
// Auto-find the trial card type
|
||||
@@ -193,12 +243,30 @@ async function loadCard() {
|
||||
card.value = activeTypes.find((c) => c.id === cardId.value) ?? null
|
||||
}
|
||||
} catch {
|
||||
card.value = null
|
||||
if (!showAll.value) {
|
||||
card.value = null
|
||||
}
|
||||
allCards.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Helpers ───────────────────────────────────────────────
|
||||
function goToDetail(id: string) {
|
||||
uni.navigateTo({ url: `/pages/card/detail?id=${id}` })
|
||||
}
|
||||
|
||||
function thumbClass(card: CardType): string {
|
||||
if (card.type === CardTypeCategory.TRIAL) return 'thumb--trial'
|
||||
if (card.type === CardTypeCategory.DURATION) return 'thumb--duration'
|
||||
return 'thumb--times'
|
||||
}
|
||||
|
||||
function truncate(str: string, maxLen: number): string {
|
||||
return str.length > maxLen ? str.slice(0, maxLen) + '…' : str
|
||||
}
|
||||
|
||||
// ─── Buy flow ─────────────────────────────────────────────
|
||||
async function handleBuy() {
|
||||
if (buying.value || !card.value) return
|
||||
@@ -286,6 +354,7 @@ onMounted(() => {
|
||||
const options = (current as { options?: Record<string, string> }).options ?? {}
|
||||
cardId.value = options.id ?? ''
|
||||
isTrial.value = options.trial === '1'
|
||||
showAll.value = options.showAll === '1'
|
||||
loadCard()
|
||||
})
|
||||
</script>
|
||||
@@ -629,4 +698,123 @@ onMounted(() => {
|
||||
color: $primary-dark;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
/* ── All cards list ────────────────────────────────────── */
|
||||
.all-cards-list {
|
||||
padding: 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.card-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
padding: 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.card-thumb {
|
||||
width: 200rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.thumb-fallback {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
padding: 12rpx;
|
||||
}
|
||||
|
||||
.thumb--times .thumb-fallback {
|
||||
background: linear-gradient(135deg, #3a3a3a, #555);
|
||||
}
|
||||
|
||||
.thumb--duration .thumb-fallback {
|
||||
background: linear-gradient(135deg, #6c3483, #9b59b6);
|
||||
}
|
||||
|
||||
.thumb--trial .thumb-fallback {
|
||||
background: linear-gradient(135deg, #5a7a8a, $primary-dark);
|
||||
}
|
||||
|
||||
.thumb-name {
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.thumb-price {
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.card-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.card-name {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
margin-bottom: 8rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.card-validity {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.price-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.price-current {
|
||||
font-size: 40rpx;
|
||||
font-weight: 800;
|
||||
color: #e53935;
|
||||
}
|
||||
|
||||
.price-original {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* ── Empty state ─────────────────────────────────────── */
|
||||
.empty-state {
|
||||
padding: 160rpx 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #bbb;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user