feat: 支持会员卡设置

This commit is contained in:
richarjiang
2026-04-07 16:47:56 +08:00
parent 91abedcb86
commit 23bdd05811
8 changed files with 667 additions and 429 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -47,7 +47,7 @@
<view class="card-header-left">
<text class="card-name">{{ m.cardType.name }}</text>
<view class="card-type-badge">
<text class="card-type-badge-text">{{ typeLabel(m.cardType.type) }}</text>
<text class="card-type-badge-text">{{ getCardTypeLabel(m.cardType.type) }}</text>
</view>
</view>
<view class="status-badge status-badge--active">
@@ -70,11 +70,11 @@
<view class="progress-bar">
<view
class="progress-fill"
:style="{ width: progressWidth(m) }"
:style="{ width: getMembershipProgressWidth(m) }"
/>
</view>
<text class="progress-label">
已使用 {{ usedTimes(m) }} / {{ m.cardType.totalTimes }}
已使用 {{ getMembershipUsedTimes(m) }} / {{ m.cardType.totalTimes }}
</text>
</view>
</template>
@@ -110,7 +110,7 @@
<view class="card-header-left">
<text class="card-name card-name--dim">{{ m.cardType.name }}</text>
<view class="card-type-badge card-type-badge--dim">
<text class="card-type-badge-text">{{ typeLabel(m.cardType.type) }}</text>
<text class="card-type-badge-text">{{ getCardTypeLabel(m.cardType.type) }}</text>
</view>
</view>
<view class="status-badge" :class="statusBadgeClass(m.status)">
@@ -148,6 +148,7 @@ import type { MembershipWithCardType } from '@mp-pilates/shared'
import { MembershipStatus, CardTypeCategory } from '@mp-pilates/shared'
import { useUserStore } from '../../stores/user'
import { getSystemLayout } from '../../utils/system'
import { getCardTypeLabel, getMembershipProgressWidth, getMembershipUsedTimes } from '../../utils/format'
import CustomNavBar from '../../components/CustomNavBar.vue'
const userStore = useUserStore()
@@ -170,14 +171,6 @@ const inactiveMemberships = computed(() =>
)
// ─── Helpers ──────────────────────────────────────────────
function typeLabel(type: CardTypeCategory): string {
const map: Record<CardTypeCategory, string> = {
[CardTypeCategory.TIMES]: '次卡',
[CardTypeCategory.DURATION]: '月卡',
[CardTypeCategory.TRIAL]: '体验卡',
}
return map[type] ?? '会员卡'
}
function statusLabel(status: MembershipStatus): string {
const map: Record<MembershipStatus, string> = {
@@ -206,17 +199,6 @@ function headerClass(type: CardTypeCategory): string {
return 'card-header--times'
}
function progressWidth(m: MembershipWithCardType): string {
if (m.remainingTimes === null || !m.cardType.totalTimes) return '0%'
const pct = (m.remainingTimes / m.cardType.totalTimes) * 100
return `${Math.max(0, Math.min(100, pct))}%`
}
function usedTimes(m: MembershipWithCardType): number {
if (m.remainingTimes === null || !m.cardType.totalTimes) return 0
return m.cardType.totalTimes - m.remainingTimes
}
// ─── Data loading ─────────────────────────────────────────
async function loadMemberships() {
loading.value = true

View File

@@ -1,6 +1,11 @@
import type { CardType } from '@mp-pilates/shared'
import { CardTypeCategory } from '@mp-pilates/shared'
/** Minimal membership shape needed by progress/usage helpers. */
interface MembershipLike {
readonly remainingTimes: number | null
readonly cardType: { readonly totalTimes: number | null }
}
/** 格式化金额:分 → 元 */
export function formatPrice(cents: number): string {
return (cents / 100).toFixed(2)
@@ -49,13 +54,13 @@ export function getDateRange(days: number): ReadonlyArray<{ readonly date: strin
}
/** 会员卡类型标签 */
export function getCardTypeLabel(type: CardTypeCategory): string {
const map: Record<CardTypeCategory, string> = {
export function getCardTypeLabel(type: CardTypeCategory | string): string {
const map: Record<string, string> = {
[CardTypeCategory.TIMES]: '次卡',
[CardTypeCategory.DURATION]: '月卡',
[CardTypeCategory.TRIAL]: '体验',
[CardTypeCategory.TRIAL]: '体验',
}
return map[type] ?? '会员'
return map[type] ?? '会员'
}
/** 会员卡封面 CSS 类名 */
@@ -70,3 +75,23 @@ export function isSlotPast(date: string, startTime: string): boolean {
const slotDateTime = new Date(`${date}T${startTime}:00`)
return new Date() > slotDateTime
}
/** 会员卡渐变 CSS 类名前缀 */
export function getCardGradientClass(type: CardTypeCategory | string): string {
if (type === CardTypeCategory.DURATION) return 'gradient--duration'
if (type === CardTypeCategory.TRIAL) return 'gradient--trial'
return 'gradient--times'
}
/** 会员卡进度百分比(剩余 / 总次数) */
export function getMembershipProgressWidth(membership: MembershipLike): string {
if (membership.remainingTimes === null || !membership.cardType.totalTimes) return '0%'
const pct = (membership.remainingTimes / membership.cardType.totalTimes) * 100
return `${Math.max(0, Math.min(100, pct))}%`
}
/** 已使用次数 */
export function getMembershipUsedTimes(membership: MembershipLike): number {
if (membership.remainingTimes === null || !membership.cardType.totalTimes) return 0
return membership.cardType.totalTimes - membership.remainingTimes
}