1866 lines
55 KiB
Vue
1866 lines
55 KiB
Vue
<template>
|
||
<view class="member-card-page">
|
||
<!-- 固定页面头部 -->
|
||
<view class="member-card-page-header">
|
||
<PageHeader title="我的会员卡" subtitle="" :show-back="true" />
|
||
</view>
|
||
|
||
<!-- 滚动内容区域 -->
|
||
<view class="member-card-scroll-wrap">
|
||
<scroll-view scroll-y class="member-card-scroll">
|
||
<view class="member-card-page__body">
|
||
<!-- 卡类型切换 -->
|
||
<view class="mc-type-tabs">
|
||
<view
|
||
class="mc-type-tab"
|
||
:class="{ 'mc-type-tab--active': currentCardType === 'stored' }"
|
||
@tap="switchCardType('stored')"
|
||
>
|
||
<text>储值卡</text>
|
||
</view>
|
||
<view
|
||
class="mc-type-tab"
|
||
:class="{ 'mc-type-tab--active': currentCardType === 'other' }"
|
||
@tap="switchCardType('other')"
|
||
>
|
||
<text>非储值卡</text>
|
||
<text class="mc-type-tab__count" v-if="otherCards.length > 0">{{ otherCards.length }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 储值卡区域 -->
|
||
<view v-if="currentCardType === 'stored'" class="mc-stored-section">
|
||
<!-- 储值卡卡片 -->
|
||
<view class="mc-stored-hero">
|
||
<view class="mc-stored-hero__inner">
|
||
<view class="mc-stored-hero__header">
|
||
<view class="mc-stored-hero__title-row">
|
||
<uni-icons type="star-filled" size="40rpx" color="#FFFFFF" />
|
||
<text class="mc-stored-hero__name">{{ displayStoredCard.name }}</text>
|
||
</view>
|
||
<view class="mc-stored-hero__badge">
|
||
<text>储值卡</text>
|
||
</view>
|
||
</view>
|
||
<view class="mc-stored-hero__balance">
|
||
<text class="mc-stored-hero__balance-label">账户余额</text>
|
||
<view class="mc-stored-hero__balance-amount">
|
||
<text class="mc-stored-hero__balance-symbol">¥</text>
|
||
<text class="mc-stored-hero__balance-num">{{ displayStoredCard.remainingAmount }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="mc-stored-hero__footer">
|
||
<view class="mc-stored-hero__recharge" hover-class="mi-tap-btn--hover" :hover-stay-time="150" @tap="goRecharge">充值</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 非储值卡区域 -->
|
||
<view v-if="currentCardType === 'other'">
|
||
<!-- 筛选条件 -->
|
||
<view class="mc-filter-tabs">
|
||
<view
|
||
class="mc-filter-tab"
|
||
:class="{ 'mc-filter-tab--active': currentFilter === 'all' }"
|
||
@tap="changeFilter('all')"
|
||
>
|
||
<text>全部</text>
|
||
<text class="mc-filter-tab__count">{{ filterCounts.all }}</text>
|
||
</view>
|
||
<view
|
||
class="mc-filter-tab"
|
||
:class="{ 'mc-filter-tab--active': currentFilter === 'active' }"
|
||
@tap="changeFilter('active')"
|
||
>
|
||
<text>有效</text>
|
||
<text class="mc-filter-tab__count">{{ filterCounts.active }}</text>
|
||
</view>
|
||
<view
|
||
class="mc-filter-tab"
|
||
:class="{ 'mc-filter-tab--active': currentFilter === 'expired' }"
|
||
@tap="changeFilter('expired')"
|
||
>
|
||
<text>已失效</text>
|
||
<text class="mc-filter-tab__count">{{ filterCounts.expired }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 卡片轮播展示 -->
|
||
<view class="mc-swiper-cards" v-if="filteredOtherCards.length > 0">
|
||
<swiper
|
||
class="mc-swiper"
|
||
:current="currentOtherCardIndex"
|
||
:previous-margin="'60rpx'"
|
||
:next-margin="'60rpx'"
|
||
:circular="filteredOtherCards.length > 2"
|
||
:autoplay="false"
|
||
@change="onSwiperChange"
|
||
>
|
||
<swiper-item
|
||
v-for="(c, index) in filteredOtherCards"
|
||
:key="c.id || index"
|
||
class="mc-swiper-item"
|
||
>
|
||
<view
|
||
class="mc-swiper-card"
|
||
:class="{
|
||
'mc-swiper-card--active': index === currentOtherCardIndex,
|
||
'mc-swiper-card--blur': index !== currentOtherCardIndex
|
||
}"
|
||
@tap="selectOtherCard(index)"
|
||
>
|
||
<view class="mc-swiper-card__inner" :class="getCardStatusClass(c)">
|
||
<view class="mc-swiper-card__header">
|
||
<view class="mc-swiper-card__title-row">
|
||
<image
|
||
class="mc-swiper-card__crown"
|
||
src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/crown.png"
|
||
mode="aspectFit"
|
||
/>
|
||
<text class="mc-swiper-card__name">{{ c.name }}</text>
|
||
</view>
|
||
<view class="mc-swiper-card__badge" :class="getBadgeClass(c)">
|
||
<text>{{ getStatusLabel(c) }}</text>
|
||
</view>
|
||
</view>
|
||
<text class="mc-swiper-card__validity" v-if="!isCountCard(c)">{{ c.validity }}</text>
|
||
<view class="mc-swiper-card__footer">
|
||
<view class="mc-swiper-card__days-info">
|
||
<text class="mc-swiper-card__days-label">{{ getCardDisplayLabel(c) }}</text>
|
||
<text class="mc-swiper-card__days-num" :class="{ 'mc-swiper-card__days-num--warning': isExpiringSoon(c), 'mc-swiper-card__days-num--expired': isExpired(c) }">
|
||
{{ getCardDisplayValue(c) }}
|
||
</text>
|
||
<text class="mc-swiper-card__days-unit">{{ getCardDisplayUnit(c) }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="mc-swiper-card__warning" v-if="isExpiringSoon(c)">
|
||
<image class="mc-swiper-card__warning-icon" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/clock1.png" mode="aspectFit" />
|
||
<text class="mc-swiper-card__warning-text">即将到期,请及时续费</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</swiper-item>
|
||
</swiper>
|
||
<view class="mc-swiper-dots" v-if="filteredOtherCards.length > 1">
|
||
<view
|
||
v-for="(c, index) in filteredOtherCards"
|
||
:key="index"
|
||
class="mc-swiper-dot"
|
||
:class="{ 'mc-swiper-dot--active': index === currentOtherCardIndex }"
|
||
></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 无卡片提示 -->
|
||
<view class="mc-empty" v-if="filteredOtherCards.length === 0">
|
||
<image class="mc-empty__icon" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/ticket.png" mode="aspectFit" />
|
||
<text class="mc-empty__text">{{ currentFilter === 'expired' ? '暂无失效会员卡' : '暂无会员卡' }}</text>
|
||
<view class="mc-empty__btn" @tap="goPurchaseCard" v-if="currentFilter !== 'expired'">
|
||
<text>购买会员卡</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="mc-section-tabs">
|
||
<view
|
||
class="mc-section-tab"
|
||
:class="{ 'mc-section-tab--active': activeSection === 'records' }"
|
||
@tap="switchSection('records')"
|
||
>
|
||
<text>{{ currentCardType === 'stored' ? '充值记录' : '使用记录' }}</text>
|
||
</view>
|
||
<view
|
||
class="mc-section-tab"
|
||
:class="{ 'mc-section-tab--active': activeSection === 'transactions' }"
|
||
@tap="switchSection('transactions')"
|
||
>
|
||
<text>会员卡流水</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="mc-records" v-if="activeSection === 'records'">
|
||
<view class="mc-records__header">
|
||
<text class="mc-records__title">{{ currentCardType === 'stored' ? '充值记录' : '使用记录' }}</text>
|
||
<view class="mc-records__tabs" v-if="currentCardType !== 'stored'">
|
||
<view
|
||
v-for="tab in recordTabs"
|
||
:key="tab.key"
|
||
class="mc-records__tab"
|
||
:class="{ 'mc-records__tab--active': activeFilter === tab.key }"
|
||
hover-class="mi-tap-tab--hover"
|
||
:hover-stay-time="150"
|
||
@tap="switchFilter(tab.key)"
|
||
>
|
||
<text
|
||
class="mc-records__tab-text"
|
||
:class="{ 'mc-records__tab-text--active': activeFilter === tab.key }"
|
||
>
|
||
{{ tab.label }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="mc-records__divider"></view>
|
||
|
||
<scroll-view
|
||
class="mc-records__scroll"
|
||
scroll-y="true"
|
||
:show-scrollbar="false"
|
||
>
|
||
<!-- 储值卡充值记录 -->
|
||
<template v-if="currentCardType === 'stored'">
|
||
<view
|
||
v-for="(item, index) in rechargeRecords"
|
||
:key="item.id || index"
|
||
class="mc-records__item"
|
||
>
|
||
<view v-if="index > 0" class="mc-records__divider"></view>
|
||
<view class="mc-records__item-inner">
|
||
<view class="mc-records__icon-wrap mc-records__icon-wrap--orange">
|
||
<image
|
||
class="mc-records__icon"
|
||
src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/pluscircle.png"
|
||
mode="aspectFit"
|
||
/>
|
||
</view>
|
||
<view class="mc-records__info">
|
||
<text class="mc-records__item-title">储值卡充值</text>
|
||
<text class="mc-records__item-time">{{ formatRecordTime(item.payTime || item.createdAt) }}</text>
|
||
</view>
|
||
<text
|
||
class="mc-records__value"
|
||
:class="item.status === 'SUCCESS' ? 'mc-records__value--positive' : (item.status === 'FAILED' ? 'mc-records__value--negative' : 'mc-records__value--pending')"
|
||
>
|
||
+¥{{ item.totalAmount }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
<view v-if="!rechargeRecords.length && !loadingRechargeRecords" class="mc-records__empty">
|
||
<text class="mc-records__empty-text">暂无充值记录</text>
|
||
</view>
|
||
<view v-if="loadingRechargeRecords" class="mc-transactions__loading">
|
||
<text>加载中...</text>
|
||
</view>
|
||
</template>
|
||
|
||
<!-- 非储值卡使用记录 -->
|
||
<template v-else>
|
||
<view
|
||
v-for="(item, index) in filteredRecords"
|
||
:key="item.id"
|
||
class="mc-records__item"
|
||
>
|
||
<view v-if="index > 0" class="mc-records__divider"></view>
|
||
<view
|
||
class="mc-records__item-inner"
|
||
hover-class="mi-tap-row--hover"
|
||
:hover-stay-time="150"
|
||
@tap="showRecordDetail(item)"
|
||
>
|
||
<view
|
||
class="mc-records__icon-wrap"
|
||
:class="'mc-records__icon-wrap--' + getUsageRecordIconTheme(item)"
|
||
>
|
||
<text class="mc-records__icon-text">{{ getUsageRecordIcon(item) }}</text>
|
||
</view>
|
||
<view class="mc-records__info">
|
||
<text class="mc-records__item-title">{{ getUsageRecordTitle(item) }}</text>
|
||
<text class="mc-records__item-time">{{ formatTime(item.createdAt) }}</text>
|
||
</view>
|
||
<text
|
||
class="mc-records__value"
|
||
:class="'mc-records__value--negative'"
|
||
>
|
||
-{{ item.changeAmount || 0 }}次
|
||
</text>
|
||
</view>
|
||
</view>
|
||
<view v-if="!filteredRecords.length" class="mc-records__empty">
|
||
<text class="mc-records__empty-text">暂无使用记录</text>
|
||
</view>
|
||
</template>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<view class="mc-transactions" v-if="activeSection === 'transactions'">
|
||
<view class="mc-transactions__header">
|
||
<text class="mc-transactions__title">会员卡流水</text>
|
||
<view class="mc-transactions__refresh" @tap="loadTransactions">
|
||
<text>刷新</text>
|
||
</view>
|
||
</view>
|
||
<scroll-view
|
||
class="mc-transactions__scroll"
|
||
scroll-y="true"
|
||
:show-scrollbar="false"
|
||
>
|
||
<view class="mc-transactions__list">
|
||
<view
|
||
v-for="(item, index) in transactions"
|
||
:key="item.id"
|
||
class="mc-transactions__item"
|
||
>
|
||
<view v-if="index > 0" class="mc-transactions__divider"></view>
|
||
<view
|
||
class="mc-transactions__item-inner"
|
||
hover-class="mi-tap-row--hover"
|
||
:hover-stay-time="150"
|
||
@tap="showTransactionDetail(item)"
|
||
>
|
||
<view
|
||
class="mc-transactions__icon-wrap"
|
||
:class="'mc-transactions__icon-wrap--' + getTransactionTypeClass(item.operationType)"
|
||
>
|
||
<text class="mc-transactions__icon-text">{{ getTransactionIcon(item.operationType) }}</text>
|
||
</view>
|
||
<view class="mc-transactions__info">
|
||
<text class="mc-transactions__item-title">{{ getTransactionTypeName(item.operationType) }}</text>
|
||
<text class="mc-transactions__item-time">{{ formatTime(item.createdAt) }}</text>
|
||
</view>
|
||
<text
|
||
class="mc-transactions__value"
|
||
:class="'mc-transactions__value--' + getTransactionValueClass(item.operationType)"
|
||
>
|
||
{{ formatTransactionValue(item) }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
<view v-if="!transactions.length && !loadingTransactions" class="mc-transactions__empty">
|
||
<text class="mc-transactions__empty-text">暂无流水记录</text>
|
||
</view>
|
||
<view v-if="loadingTransactions" class="mc-transactions__loading">
|
||
<text>加载中...</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, watch } from 'vue'
|
||
import { onShow, onLoad } from '@dcloudio/uni-app'
|
||
import PageHeader from '@/components/index/PageHeader.vue'
|
||
import {
|
||
loadMemberStore,
|
||
computeRemainingDays,
|
||
renewMemberCard,
|
||
saveMemberStore
|
||
} from '@/common/memberInfo/store.js'
|
||
import { PAGE, navigateToPage } from '@/common/constants/routes.js'
|
||
import { getMyMemberCards, getMyMemberCardsWithStatus, getMemberCardTransactions, getMemberCardTransactionsByRecordId, checkPayPasswordSet, createPayment, getStoredCardInfo, getStoredCardRechargeRecords } from '@/api/main.js'
|
||
import { getMemberId } from '@/utils/request.js'
|
||
|
||
const currentFilter = ref('all')
|
||
const currentCardType = ref('stored') // 'stored' 或 'other'
|
||
const cards = ref([])
|
||
const allCards = ref([])
|
||
const storedCards = ref([]) // 储值卡
|
||
const otherCards = ref([]) // 非储值卡
|
||
const currentStoredCard = ref(null) // 当前选中的储值卡
|
||
const activeCards = ref([])
|
||
const expiredCards = ref([])
|
||
const currentOtherCardIndex = ref(0)
|
||
const recordTabs = ref([
|
||
{ key: 'all', label: '全部' },
|
||
{ key: 'GROUP_CLASS', label: '团课消费' },
|
||
{ key: 'CHECK_IN', label: '到店健身' }
|
||
])
|
||
const records = ref([])
|
||
const activeFilter = ref('all')
|
||
const activeSection = ref('records')
|
||
const transactions = ref([])
|
||
const loadingTransactions = ref(false)
|
||
const loading = ref(false)
|
||
const storedCardBalance = ref(0)
|
||
const rechargeRecords = ref([])
|
||
const loadingRechargeRecords = ref(false)
|
||
|
||
const filterCounts = computed(() => ({
|
||
all: otherCards.value.length,
|
||
active: otherCards.value.filter(c => isActiveCard(c)).length,
|
||
expired: otherCards.value.filter(c => isExpired(c)).length
|
||
}))
|
||
|
||
const otherStoredCards = computed(() => {
|
||
if (!currentStoredCard.value) {
|
||
return storedCards.value
|
||
}
|
||
return storedCards.value.filter(card => card.id !== currentStoredCard.value.id)
|
||
})
|
||
|
||
const filteredOtherCards = computed(() => {
|
||
if (currentFilter.value === 'all') return otherCards.value
|
||
if (currentFilter.value === 'active') return otherCards.value.filter(c => isActiveCard(c))
|
||
if (currentFilter.value === 'expired') return otherCards.value.filter(c => isExpired(c))
|
||
return otherCards.value
|
||
})
|
||
|
||
const filteredRecords = computed(() => {
|
||
const deductRecords = transactions.value.filter(item => item.operationType === 'DEDUCT')
|
||
if (activeFilter.value === 'all') {
|
||
return deductRecords
|
||
}
|
||
return deductRecords.filter(item => item.relatedBizType === activeFilter.value)
|
||
})
|
||
|
||
function getUsageRecordTitle(item) {
|
||
const bizMap = {
|
||
'GROUP_CLASS': '团课消费',
|
||
'CHECK_IN': '到店健身',
|
||
'PT_CLASS': '私教课消费'
|
||
}
|
||
return bizMap[item.relatedBizType] || '消费'
|
||
}
|
||
|
||
function getUsageRecordIcon(item) {
|
||
const iconMap = {
|
||
'GROUP_CLASS': '🏋️',
|
||
'CHECK_IN': '✅',
|
||
'PT_CLASS': '💪'
|
||
}
|
||
return iconMap[item.relatedBizType] || '📋'
|
||
}
|
||
|
||
function getUsageRecordIconTheme(item) {
|
||
const themeMap = {
|
||
'GROUP_CLASS': 'orange',
|
||
'CHECK_IN': 'green',
|
||
'PT_CLASS': 'purple'
|
||
}
|
||
return themeMap[item.relatedBizType] || 'default'
|
||
}
|
||
|
||
const displayStoredCard = computed(() => {
|
||
console.log("06281--"+storedCardBalance.value)
|
||
const balance = storedCardBalance.value != null ? storedCardBalance.value : 0
|
||
console.log("06281--"+balance)
|
||
return {
|
||
name: '储值卡',
|
||
remainingAmount: balance.toFixed(2)
|
||
}
|
||
})
|
||
|
||
// 加载储值卡余额
|
||
async function loadStoredCardBalance() {
|
||
const memberId = getMemberId()
|
||
if (!memberId) return
|
||
|
||
try {
|
||
const res = await getStoredCardInfo(memberId)
|
||
storedCardBalance.value = res.balance || 0
|
||
} catch (e) {
|
||
console.error('获取储值卡余额失败:', e)
|
||
storedCardBalance.value = 0
|
||
}
|
||
}
|
||
|
||
// 加载储值卡充值记录
|
||
async function loadRechargeRecords() {
|
||
const memberId = getMemberId()
|
||
if (!memberId) return
|
||
|
||
if (loadingRechargeRecords.value) return
|
||
loadingRechargeRecords.value = true
|
||
|
||
try {
|
||
const res = await getStoredCardRechargeRecords(memberId, 1, 50, { cache: false })
|
||
rechargeRecords.value = res.list || []
|
||
} catch (e) {
|
||
console.error('获取充值记录失败:', e)
|
||
rechargeRecords.value = []
|
||
} finally {
|
||
loadingRechargeRecords.value = false
|
||
}
|
||
}
|
||
|
||
function formatRecordTime(timeStr) {
|
||
if (!timeStr) return ''
|
||
const date = new Date(timeStr)
|
||
const year = date.getFullYear()
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
const hour = String(date.getHours()).padStart(2, '0')
|
||
const minute = String(date.getMinutes()).padStart(2, '0')
|
||
return `${year}-${month}-${day} ${hour}:${minute}`
|
||
}
|
||
|
||
function formatDate(dateStr) {
|
||
if (!dateStr) return '永久有效'
|
||
try {
|
||
const date = new Date(dateStr)
|
||
const year = date.getFullYear()
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
return `${year}-${month}-${day}`
|
||
} catch (e) {
|
||
return dateStr
|
||
}
|
||
}
|
||
|
||
function formatChineseDate(dateStr) {
|
||
if (!dateStr) return ''
|
||
try {
|
||
const date = new Date(dateStr)
|
||
const year = date.getFullYear()
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
return `${year}年${month}月${day}日`
|
||
} catch (e) {
|
||
return dateStr
|
||
}
|
||
}
|
||
|
||
function switchCardType(type) {
|
||
currentCardType.value = type
|
||
currentOtherCardIndex.value = 0
|
||
activeSection.value = 'records'
|
||
|
||
if (type === 'stored') {
|
||
loadStoredCardBalance()
|
||
loadRechargeRecords()
|
||
} else {
|
||
if (filteredOtherCards.value.length > 0) {
|
||
loadTransactions()
|
||
}
|
||
}
|
||
}
|
||
|
||
function switchSection(section) {
|
||
activeSection.value = section
|
||
if (section === 'records') {
|
||
if (currentCardType.value === 'stored') {
|
||
loadRechargeRecords()
|
||
} else {
|
||
loadTransactions()
|
||
}
|
||
} else if (section === 'transactions') {
|
||
loadTransactions()
|
||
}
|
||
}
|
||
|
||
function selectStoredCard(card) {
|
||
currentStoredCard.value = card
|
||
loadTransactions(card.id)
|
||
}
|
||
|
||
function getCardRemainingDays(c) {
|
||
return computeRemainingDays(c.validityEnd || c.expireTime)
|
||
}
|
||
|
||
function getCardDaysDiff(c) {
|
||
const endDateStr = c.validityEnd || c.expireTime
|
||
if (!endDateStr) return 0
|
||
const end = new Date(endDateStr)
|
||
if (isNaN(end.getTime())) return 0
|
||
const now = new Date()
|
||
now.setHours(0, 0, 0, 0)
|
||
end.setHours(0, 0, 0, 0)
|
||
return Math.ceil((end - now) / 86400000)
|
||
}
|
||
|
||
function getCardDaysLabel(c) {
|
||
const diff = getCardDaysDiff(c)
|
||
return diff >= 0 ? '还剩' : '已过期'
|
||
}
|
||
|
||
function getCardDaysValue(c) {
|
||
const diff = getCardDaysDiff(c)
|
||
return Math.abs(diff)
|
||
}
|
||
|
||
function isStoredCard(c) {
|
||
if (c.memberCardType) {
|
||
return c.memberCardType === 'STORED_VALUE_CARD'
|
||
}
|
||
return c.remainingAmount !== null && c.remainingAmount !== undefined && c.remainingAmount > 0
|
||
}
|
||
|
||
function isTimeCard(c) {
|
||
if (c.memberCardType) {
|
||
return c.memberCardType === 'TIME_CARD'
|
||
}
|
||
return false
|
||
}
|
||
|
||
function isCountCard(c) {
|
||
if (c.memberCardType) {
|
||
return c.memberCardType === 'COUNT_CARD'
|
||
}
|
||
return false
|
||
}
|
||
|
||
function getCardDisplayLabel(c) {
|
||
if (isTimeCard(c)) {
|
||
return getCardDaysDiff(c) >= 0 ? '还剩' : '已过期'
|
||
} else if (isCountCard(c)) {
|
||
return '剩余'
|
||
}
|
||
return getCardDaysDiff(c) >= 0 ? '还剩' : '已过期'
|
||
}
|
||
|
||
function getCardDisplayValue(c) {
|
||
if (isTimeCard(c)) {
|
||
return Math.abs(getCardDaysDiff(c))
|
||
} else if (isCountCard(c)) {
|
||
return c.remainingTimes || 0
|
||
}
|
||
return Math.abs(getCardDaysDiff(c))
|
||
}
|
||
|
||
function getCardDisplayUnit(c) {
|
||
if (isTimeCard(c)) {
|
||
return '天'
|
||
} else if (isCountCard(c)) {
|
||
return '次'
|
||
}
|
||
return '天'
|
||
}
|
||
|
||
function isActiveCard(c) {
|
||
if (isCountCard(c)) {
|
||
// 次数卡:检查是否还有剩余次数
|
||
return (c.remainingTimes || 0) > 0 && (c.status === 'active' || c.status === 'ACTIVE')
|
||
}
|
||
// 时长卡:检查是否在有效期内
|
||
const diff = getCardDaysDiff(c)
|
||
return diff > 0 && (c.status === 'active' || c.status === 'ACTIVE')
|
||
}
|
||
|
||
function isExpiringSoon(c) {
|
||
if (isCountCard(c)) {
|
||
// 次数卡不显示临期提示
|
||
return false
|
||
}
|
||
const diff = getCardDaysDiff(c)
|
||
return diff > 0 && diff <= 3
|
||
}
|
||
|
||
function isExpired(c) {
|
||
if (isCountCard(c)) {
|
||
// 次数卡:没有剩余次数即为用完
|
||
return (c.remainingTimes || 0) <= 0 || c.status === 'USED_UP' || c.status === 'used_up'
|
||
}
|
||
// 时长卡:检查是否已过期
|
||
const diff = getCardDaysDiff(c)
|
||
return diff <= 0 || c.status === 'EXPIRED' || c.status === 'expired'
|
||
}
|
||
|
||
function getStatusLabel(c) {
|
||
if (isCountCard(c)) {
|
||
if ((c.remainingTimes || 0) <= 0) return '已用完'
|
||
return '有效'
|
||
}
|
||
if (isExpired(c)) return '已失效'
|
||
if (isExpiringSoon(c)) return '临期'
|
||
return '有效'
|
||
}
|
||
|
||
function getCardStatusClass(c) {
|
||
if (isCountCard(c)) {
|
||
if ((c.remainingTimes || 0) <= 0) return 'mc-swiper-card__inner--expired'
|
||
return 'mc-swiper-card__inner--active'
|
||
}
|
||
if (isExpired(c)) return 'mc-swiper-card__inner--expired'
|
||
if (isExpiringSoon(c)) return 'mc-swiper-card__inner--expiring'
|
||
return 'mc-swiper-card__inner--active'
|
||
}
|
||
|
||
function getBadgeClass(c) {
|
||
if (isCountCard(c)) {
|
||
if ((c.remainingTimes || 0) <= 0) return 'mc-swiper-card__badge--expired'
|
||
return 'mc-swiper-card__badge--active'
|
||
}
|
||
if (isExpired(c)) return 'mc-swiper-card__badge--expired'
|
||
if (isExpiringSoon(c)) return 'mc-swiper-card__badge--expiring'
|
||
return 'mc-swiper-card__badge--active'
|
||
}
|
||
|
||
function onSwiperChange(e) {
|
||
currentOtherCardIndex.value = e.detail.current
|
||
if (activeSection.value === 'records' || activeSection.value === 'transactions') {
|
||
loadTransactions()
|
||
}
|
||
}
|
||
|
||
function selectOtherCard(index) {
|
||
currentOtherCardIndex.value = index
|
||
if (activeSection.value === 'records' || activeSection.value === 'transactions') {
|
||
loadTransactions()
|
||
}
|
||
}
|
||
|
||
function changeFilter(filter) {
|
||
currentFilter.value = filter
|
||
currentOtherCardIndex.value = 0
|
||
}
|
||
|
||
function goPurchaseCard() {
|
||
navigateToPage(PAGE.PURCHASE_CARD)
|
||
}
|
||
|
||
function goRecharge() {
|
||
navigateToPage(`${PAGE.RECHARGE_STORED_CARD}`)
|
||
}
|
||
|
||
// 从后端刷新会员卡数据
|
||
async function refreshFromServer() {
|
||
const memberId = getMemberId()
|
||
if (!memberId) {
|
||
console.log('[MemberCard] 没有 memberId,无法刷新')
|
||
return
|
||
}
|
||
|
||
console.log('[MemberCard] 开始刷新会员卡数据, memberId:', memberId)
|
||
loading.value = true
|
||
uni.showLoading({ title: '加载中...' })
|
||
|
||
try {
|
||
const res = await getMyMemberCardsWithStatus(memberId, 'all')
|
||
console.log('[MemberCard] API 返回:', res)
|
||
|
||
uni.hideLoading()
|
||
loading.value = false
|
||
|
||
let rawCards = []
|
||
if (Array.isArray(res)) {
|
||
rawCards = res
|
||
} else if (res.code === 200 && res.data) {
|
||
rawCards = res.data
|
||
} else if (Array.isArray(res.data)) {
|
||
rawCards = res.data
|
||
}
|
||
|
||
console.log('[MemberCard] 解析后的会员卡列表:', rawCards)
|
||
|
||
const formattedCards = rawCards.map(myCard => {
|
||
const cardName = myCard.memberCardName || myCard.cardName || myCard.name || `会员卡#${myCard.memberCardId || myCard.id}`
|
||
const validityEnd = myCard.validityEnd || myCard.expireTime
|
||
const validityStart = myCard.validityStart || myCard.purchaseTime || myCard.createdAt
|
||
const cardType = myCard.memberCardType || myCard.cardType || ''
|
||
return {
|
||
id: myCard.id || myCard.memberCardRecordId,
|
||
name: cardName,
|
||
status: myCard.status || 'ACTIVE',
|
||
validity: `有效期至 ${formatChineseDate(validityEnd)}`,
|
||
validityEnd: validityEnd,
|
||
validityStart: validityStart,
|
||
remainingTimes: myCard.remainingTimes,
|
||
remainingAmount: myCard.remainingAmount,
|
||
memberCardType: cardType
|
||
}
|
||
})
|
||
|
||
// 分类存储
|
||
allCards.value = formattedCards
|
||
storedCards.value = formattedCards.filter(c => isStoredCard(c))
|
||
otherCards.value = formattedCards.filter(c => !isStoredCard(c))
|
||
activeCards.value = formattedCards.filter(c => isActiveCard(c))
|
||
expiredCards.value = formattedCards.filter(c => isExpired(c))
|
||
|
||
// 设置当前储值卡
|
||
if (storedCards.value.length > 0 && !currentStoredCard.value) {
|
||
currentStoredCard.value = storedCards.value[0]
|
||
} else if (storedCards.value.length === 0) {
|
||
currentStoredCard.value = null
|
||
}
|
||
|
||
// 更新store
|
||
store.cards = formattedCards
|
||
if (formattedCards.length > 0) {
|
||
const firstActiveCard = formattedCards.find(c => isActiveCard(c)) || formattedCards[0]
|
||
store.card = { ...firstActiveCard }
|
||
store.cardInfo = {
|
||
expireDate: firstActiveCard.validity,
|
||
remainingDays: getCardRemainingDays(firstActiveCard)
|
||
}
|
||
}
|
||
saveMemberStore(store)
|
||
|
||
console.log('[MemberCard] 数据刷新成功,总数:', formattedCards.length, '储值卡:', storedCards.value.length, '非储值卡:', otherCards.value.length)
|
||
} catch (e) {
|
||
uni.hideLoading()
|
||
loading.value = false
|
||
console.error('[MemberCard] 刷新会员卡数据失败:', e)
|
||
|
||
try {
|
||
const res = await getMyMemberCards(memberId)
|
||
console.log(111)
|
||
console.log(res)
|
||
let rawCards = []
|
||
if (Array.isArray(res)) {
|
||
rawCards = res
|
||
} else if (res.code === 200 && res.data) {
|
||
rawCards = res.data
|
||
} else if (Array.isArray(res.data)) {
|
||
rawCards = res.data
|
||
}
|
||
|
||
const formattedCards = rawCards.map(myCard => {
|
||
console.log(1212)
|
||
console.log(myCard)
|
||
const cardName = myCard.memberCardName || myCard.cardName || myCard.name || `会员卡#${myCard.memberCardId || myCard.id}`
|
||
const validityEnd = myCard.validityEnd || myCard.expireTime
|
||
const validityStart = myCard.validityStart || myCard.purchaseTime || myCard.createdAt
|
||
const cardType = myCard.memberCardType || myCard.cardType || ''
|
||
return {
|
||
id: myCard.id || myCard.memberCardRecordId,
|
||
name: cardName,
|
||
status: myCard.status || 'ACTIVE',
|
||
validity: `有效期至 ${formatChineseDate(validityEnd)}`,
|
||
validityEnd: validityEnd,
|
||
validityStart: validityStart,
|
||
remainingTimes: myCard.remainingTimes,
|
||
remainingAmount: myCard.remainingAmount,
|
||
memberCardType: cardType
|
||
}
|
||
})
|
||
|
||
allCards.value = formattedCards
|
||
storedCards.value = formattedCards.filter(c => isStoredCard(c))
|
||
otherCards.value = formattedCards.filter(c => !isStoredCard(c))
|
||
activeCards.value = formattedCards.filter(c => isActiveCard(c))
|
||
expiredCards.value = []
|
||
|
||
if (storedCards.value.length > 0 && !currentStoredCard.value) {
|
||
currentStoredCard.value = storedCards.value[0]
|
||
}
|
||
|
||
store.cards = formattedCards
|
||
saveMemberStore(store)
|
||
} catch (e2) {
|
||
console.error('[MemberCard] 旧API也失败:', e2)
|
||
}
|
||
}
|
||
|
||
if (currentCardType.value === 'other' && otherCards.value.length > 0) {
|
||
loadTransactions()
|
||
}
|
||
}
|
||
|
||
function switchFilter(filter) {
|
||
activeFilter.value = filter
|
||
}
|
||
|
||
function showRecordDetail(item) {
|
||
const title = getUsageRecordTitle(item)
|
||
const time = formatTime(item.createdAt)
|
||
const change = `-${item.changeAmount || 0}次`
|
||
uni.showModal({
|
||
title: title,
|
||
content: `${time}\n变动:${change}`,
|
||
showCancel: false
|
||
})
|
||
}
|
||
|
||
async function loadTransactions(cardId) {
|
||
loadingTransactions.value = true
|
||
try {
|
||
let res
|
||
if (currentCardType.value === 'stored') {
|
||
const memberId = getMemberId()
|
||
if (!memberId) {
|
||
loadingTransactions.value = false
|
||
return
|
||
}
|
||
res = await getMemberCardTransactions(memberId)
|
||
} else {
|
||
const currentCard = getCurrentOtherCard()
|
||
if (currentCard && currentCard.id) {
|
||
res = await getMemberCardTransactionsByRecordId(currentCard.id)
|
||
} else {
|
||
transactions.value = []
|
||
loadingTransactions.value = false
|
||
return
|
||
}
|
||
}
|
||
|
||
if (Array.isArray(res)) {
|
||
transactions.value = res
|
||
} else if (res && Array.isArray(res.value)) {
|
||
transactions.value = res.value
|
||
} else if (res && Array.isArray(res.data)) {
|
||
transactions.value = res.data
|
||
} else {
|
||
transactions.value = []
|
||
}
|
||
} catch (e) {
|
||
console.error('加载会员卡流水失败:', e)
|
||
transactions.value = []
|
||
} finally {
|
||
loadingTransactions.value = false
|
||
}
|
||
}
|
||
|
||
function getCurrentOtherCard() {
|
||
if (filteredOtherCards.value.length > 0 && currentOtherCardIndex.value < filteredOtherCards.value.length) {
|
||
return filteredOtherCards.value[currentOtherCardIndex.value]
|
||
}
|
||
return null
|
||
}
|
||
|
||
function getTransactionTypeName(type) {
|
||
const map = {
|
||
PURCHASE: '购买',
|
||
DEDUCT: '扣减',
|
||
RENEW: '续费',
|
||
REFUND: '退款',
|
||
EXPIRE: '过期'
|
||
}
|
||
return map[type] || type
|
||
}
|
||
|
||
function getTransactionIcon(type) {
|
||
const map = {
|
||
PURCHASE: '💰',
|
||
DEDUCT: '💳',
|
||
RENEW: '🔄',
|
||
REFUND: '🔙',
|
||
EXPIRE: '⏰'
|
||
}
|
||
return map[type] || '📋'
|
||
}
|
||
|
||
function getTransactionTypeClass(type) {
|
||
const map = {
|
||
PURCHASE: 'purchase',
|
||
DEDUCT: 'deduct',
|
||
RENEW: 'renew',
|
||
REFUND: 'refund',
|
||
EXPIRE: 'expire'
|
||
}
|
||
return map[type] || 'default'
|
||
}
|
||
|
||
function getTransactionValueClass(type) {
|
||
if (type === 'PURCHASE' || type === 'RENEW') {
|
||
return 'positive'
|
||
}
|
||
return 'negative'
|
||
}
|
||
|
||
function formatTransactionValue(item) {
|
||
const parts = []
|
||
if (item.changeAmount !== null && item.changeAmount !== undefined) {
|
||
const prefix = (item.changeAmount > 0 ? '+' : '')
|
||
parts.push(`${prefix}${item.changeAmount}次`)
|
||
}
|
||
if (item.changeBalance !== null && item.changeBalance !== undefined) {
|
||
const prefix = (item.changeBalance > 0 ? '+' : '')
|
||
parts.push(`${prefix}${item.changeBalance}元`)
|
||
}
|
||
if (parts.length === 0) {
|
||
return '-'
|
||
}
|
||
return parts.join(' ')
|
||
}
|
||
|
||
function formatTime(timeStr) {
|
||
if (!timeStr) return '-'
|
||
try {
|
||
const date = new Date(timeStr)
|
||
const year = date.getFullYear()
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
const hour = String(date.getHours()).padStart(2, '0')
|
||
const minute = String(date.getMinutes()).padStart(2, '0')
|
||
return `${year}-${month}-${day} ${hour}:${minute}`
|
||
} catch (e) {
|
||
return timeStr
|
||
}
|
||
}
|
||
|
||
function showTransactionDetail(item) {
|
||
let content = `时间:${formatTime(item.createdAt)}\n`
|
||
content += `类型:${getTransactionTypeName(item.operationType)}\n`
|
||
content += `变动:${formatTransactionValue(item)}\n`
|
||
if (item.afterRemainingCount !== null && item.afterRemainingCount !== undefined) {
|
||
content += `剩余次数:${item.afterRemainingCount}次\n`
|
||
}
|
||
if (item.afterRemainingBalance !== null && item.afterRemainingBalance !== undefined) {
|
||
content += `剩余金额:${item.afterRemainingBalance}元\n`
|
||
}
|
||
if (item.remark) {
|
||
content += `备注:${item.remark}`
|
||
}
|
||
uni.showModal({
|
||
title: '流水详情',
|
||
content: content,
|
||
showCancel: false
|
||
})
|
||
}
|
||
|
||
watch(activeSection, (newSection) => {
|
||
if (newSection === 'transactions') {
|
||
loadTransactions()
|
||
}
|
||
})
|
||
|
||
onLoad((options) => {
|
||
if (options && options.tab) {
|
||
if (options.tab === 'stored') {
|
||
currentCardType.value = 'stored'
|
||
activeSection.value = 'transactions'
|
||
} else if (options.tab === 'other') {
|
||
currentCardType.value = 'other'
|
||
}
|
||
}
|
||
})
|
||
|
||
// 每次页面显示时从后端刷新会员卡数据
|
||
onShow(() => {
|
||
const memberId = getMemberId()
|
||
console.log('========== 当前登录用户信息 ==========')
|
||
console.log('会员ID (memberId):', memberId)
|
||
console.log('====================================')
|
||
refreshFromServer()
|
||
if (currentCardType.value === 'stored') {
|
||
loadStoredCardBalance()
|
||
loadRechargeRecords()
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
@import '@/common/style/base.css';
|
||
@import '@/common/style/memberInfo/member-info-component-reset.css';
|
||
@import '@/common/style/memberInfo/member-info-tap.css';
|
||
@import '@/common/style/memberInfo/pages/page-reset.css';
|
||
@import '@/common/style/memberInfo/pages/sub-page-base.css';
|
||
|
||
// ================== 页面布局 ==================
|
||
.member-card-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
background: #F5F7FA;
|
||
}
|
||
|
||
.member-card-page-header {
|
||
flex-shrink: 0;
|
||
position: relative;
|
||
z-index: 10;
|
||
background: #F5F7FA;
|
||
}
|
||
|
||
.member-card-scroll-wrap {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.member-card-scroll {
|
||
height: 100%;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.member-card-page__body {
|
||
padding: 24rpx 32rpx;
|
||
}
|
||
|
||
// ================== 卡类型切换 ==================
|
||
.mc-type-tabs {
|
||
display: flex;
|
||
margin-bottom: 24rpx;
|
||
background: #FFFFFF;
|
||
border-radius: 16rpx;
|
||
padding: 8rpx;
|
||
gap: 8rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||
}
|
||
|
||
.mc-type-tab {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8rpx;
|
||
padding: 20rpx 0;
|
||
border-radius: 12rpx;
|
||
font-size: 28rpx;
|
||
color: #718096;
|
||
background: #F7FAFC;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.mc-type-tab--active {
|
||
background: linear-gradient(135deg, #1677FF, #4096FF);
|
||
color: #FFFFFF;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.mc-type-tab__count {
|
||
font-size: 22rpx;
|
||
padding: 4rpx 12rpx;
|
||
border-radius: 20rpx;
|
||
background: rgba(0, 0, 0, 0.08);
|
||
}
|
||
|
||
.mc-type-tab--active .mc-type-tab__count {
|
||
background: rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
// ================== 储值卡 ==================
|
||
.mc-stored-section {
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.mc-stored-hero__inner {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
border-radius: 20rpx;
|
||
padding: 32rpx;
|
||
color: #FFFFFF;
|
||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.25);
|
||
}
|
||
|
||
.mc-stored-hero__header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.mc-stored-hero__title-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.mc-stored-hero__name {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.mc-stored-hero__badge {
|
||
padding: 4px 12px;
|
||
border-radius: 12px;
|
||
font-size: 12px;
|
||
background: rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
.mc-stored-hero__balance {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.mc-stored-hero__balance-label {
|
||
font-size: 13px;
|
||
opacity: 0.8;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.mc-stored-hero__balance-amount {
|
||
display: flex;
|
||
align-items: baseline;
|
||
}
|
||
|
||
.mc-stored-hero__balance-symbol {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.mc-stored-hero__balance-num {
|
||
font-size: 36px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.mc-stored-hero__footer {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.mc-stored-hero__validity {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
|
||
.mc-stored-hero__validity-label {
|
||
font-size: 12px;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.mc-stored-hero__validity-date {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.mc-stored-hero__recharge {
|
||
padding: 16rpx 36rpx;
|
||
border-radius: 32rpx;
|
||
background: #FFFFFF;
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #667eea;
|
||
text-align: center;
|
||
line-height: 1;
|
||
}
|
||
|
||
// 无储值卡提示
|
||
.mc-stored-empty {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 64rpx 32rpx;
|
||
background: #FFFFFF;
|
||
border-radius: 16rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||
}
|
||
|
||
.mc-stored-empty__icon {
|
||
width: 64px;
|
||
height: 64px;
|
||
opacity: 0.4;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.mc-stored-empty__text {
|
||
font-size: 14px;
|
||
color: #8A99B4;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.mc-stored-empty__btn {
|
||
padding: 12px 32px;
|
||
border-radius: 24px;
|
||
background: #1677FF;
|
||
color: #FFFFFF;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
// 储值卡列表
|
||
.mc-stored-list {
|
||
margin: 16px 16px 0;
|
||
background: #FFFFFF;
|
||
border-radius: 12px;
|
||
padding: 12px 16px;
|
||
}
|
||
|
||
.mc-stored-list__title {
|
||
font-size: 13px;
|
||
color: #8A99B4;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.mc-stored-list__item {
|
||
padding: 12px 0;
|
||
}
|
||
|
||
.mc-stored-list__item:not(:last-child) {
|
||
border-bottom: 1px solid #F0F0F0;
|
||
}
|
||
|
||
.mc-stored-list__item-inner {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.mc-stored-list__item-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.mc-stored-list__item-name {
|
||
font-size: 14px;
|
||
color: #1A202C;
|
||
}
|
||
|
||
.mc-stored-list__item-balance {
|
||
font-size: 13px;
|
||
color: #667eea;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.mc-stored-list__item-arrow {
|
||
width: 16px;
|
||
height: 16px;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
// 筛选条件样式
|
||
.mc-filter-tabs {
|
||
display: flex;
|
||
margin-bottom: 24rpx;
|
||
background: #FFFFFF;
|
||
border-radius: 16rpx;
|
||
padding: 8rpx;
|
||
gap: 8rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||
}
|
||
|
||
.mc-filter-tab {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
padding: 10px 0;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
color: #718096;
|
||
background: #F7FAFC;
|
||
transition: all 0.2s;
|
||
|
||
&--active {
|
||
background: #1677FF;
|
||
color: #FFFFFF;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
.mc-filter-tab__count {
|
||
font-size: 12px;
|
||
padding: 2px 6px;
|
||
border-radius: 10px;
|
||
background: rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.mc-filter-tab--active .mc-filter-tab__count {
|
||
background: rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
// 卡片轮播样式
|
||
.mc-swiper-cards {
|
||
margin-bottom: 24rpx;
|
||
padding-bottom: 16rpx;
|
||
}
|
||
|
||
.mc-swiper {
|
||
height: 240px;
|
||
}
|
||
|
||
.mc-swiper-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.mc-swiper-card {
|
||
width: 100%;
|
||
height: 220px;
|
||
transition: all 0.3s ease;
|
||
transform-origin: center center;
|
||
overflow: hidden;
|
||
border-radius: 16px;
|
||
|
||
&--active {
|
||
transform: scale(1);
|
||
filter: blur(0);
|
||
opacity: 1;
|
||
}
|
||
|
||
&--blur {
|
||
transform: scale(0.85);
|
||
filter: blur(4px);
|
||
opacity: 0.6;
|
||
}
|
||
}
|
||
|
||
.mc-swiper-card__inner {
|
||
width: 100%;
|
||
height: 100%;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
border-radius: 16px;
|
||
padding: 20px;
|
||
color: #FFFFFF;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
&--active {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
}
|
||
|
||
&--expiring {
|
||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||
}
|
||
|
||
&--expired {
|
||
background: linear-gradient(135deg, #c0c0c0 0%, #909090 100%);
|
||
}
|
||
}
|
||
|
||
.mc-swiper-card__header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.mc-swiper-card__title-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.mc-swiper-card__crown {
|
||
width: 24px;
|
||
height: 24px;
|
||
}
|
||
|
||
.mc-swiper-card__name {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.mc-swiper-card__badge {
|
||
padding: 4px 12px;
|
||
border-radius: 12px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
|
||
&--active {
|
||
background: rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
&--expiring {
|
||
background: #FFE4E4;
|
||
color: #E74C3C;
|
||
}
|
||
|
||
&--expired {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
}
|
||
}
|
||
|
||
.mc-swiper-card__validity {
|
||
font-size: 13px;
|
||
opacity: 0.8;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.mc-swiper-card__footer {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-top: auto;
|
||
}
|
||
|
||
.mc-swiper-card__days-info {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 4px;
|
||
}
|
||
|
||
.mc-swiper-card__days-label {
|
||
font-size: 14px;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.mc-swiper-card__days-num {
|
||
font-size: 32px;
|
||
font-weight: 700;
|
||
|
||
&--warning {
|
||
color: #FFE4E4;
|
||
}
|
||
|
||
&--expired {
|
||
color: #FF6B6B;
|
||
}
|
||
}
|
||
|
||
.mc-swiper-card__days-unit {
|
||
font-size: 14px;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.mc-swiper-card__renew {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 8px 16px;
|
||
border-radius: 20px;
|
||
background: rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
.mc-swiper-card__renew-icon {
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
|
||
.mc-swiper-card__renew-text {
|
||
font-size: 13px;
|
||
}
|
||
|
||
.mc-swiper-card__warning {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-top: 12px;
|
||
padding: 10px;
|
||
border-radius: 8px;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
.mc-swiper-card__warning-icon {
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
|
||
.mc-swiper-card__warning-text {
|
||
font-size: 13px;
|
||
}
|
||
|
||
.mc-swiper-dots {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.mc-swiper-dot {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
background: rgba(0, 0, 0, 0.2);
|
||
transition: all 0.3s ease;
|
||
|
||
&--active {
|
||
width: 20px;
|
||
border-radius: 3px;
|
||
background: #667eea;
|
||
}
|
||
}
|
||
|
||
// 空状态样式
|
||
.mc-empty {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 60px 16px;
|
||
margin: 0 16px;
|
||
background: #FFFFFF;
|
||
border-radius: 16px;
|
||
}
|
||
|
||
.mc-empty__icon {
|
||
width: 64px;
|
||
height: 64px;
|
||
opacity: 0.4;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.mc-empty__text {
|
||
font-size: 14px;
|
||
color: #8A99B4;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.mc-empty__btn {
|
||
padding: 12px 32px;
|
||
border-radius: 24px;
|
||
background: #1677FF;
|
||
color: #FFFFFF;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.mc-section-tabs {
|
||
display: flex;
|
||
margin-bottom: 24rpx;
|
||
background: #FFFFFF;
|
||
border-radius: 16rpx;
|
||
padding: 8rpx;
|
||
gap: 8rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||
}
|
||
|
||
.mc-section-tab {
|
||
flex: 1;
|
||
padding: 20rpx 0;
|
||
text-align: center;
|
||
border-radius: 12rpx;
|
||
font-size: 28rpx;
|
||
color: #718096;
|
||
background: #F7FAFC;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.mc-section-tab--active {
|
||
background: linear-gradient(135deg, #1677FF, #4096FF);
|
||
color: #FFFFFF;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.mc-transactions {
|
||
background: #FFFFFF;
|
||
border-radius: 16rpx;
|
||
padding: 24rpx 32rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.mc-transactions__header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.mc-transactions__title {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #1E2A3A;
|
||
}
|
||
|
||
.mc-transactions__refresh {
|
||
font-size: 24rpx;
|
||
color: #1677FF;
|
||
padding: 8rpx 16rpx;
|
||
}
|
||
|
||
.mc-transactions__scroll {
|
||
height: 400rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.mc-transactions__list {
|
||
padding: 8px 0;
|
||
}
|
||
|
||
.mc-transactions__divider {
|
||
height: 1rpx;
|
||
background: #F0F0F0;
|
||
}
|
||
|
||
.mc-transactions__item-inner {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 24rpx 0;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.mc-transactions__icon-wrap {
|
||
width: 72rpx;
|
||
height: 72rpx;
|
||
border-radius: 36rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.mc-transactions__icon-wrap--purchase {
|
||
background: rgba(76, 175, 80, 0.1);
|
||
}
|
||
.mc-transactions__icon-wrap--deduct {
|
||
background: rgba(242, 153, 74, 0.1);
|
||
}
|
||
.mc-transactions__icon-wrap--renew {
|
||
background: rgba(33, 150, 243, 0.1);
|
||
}
|
||
.mc-transactions__icon-wrap--refund {
|
||
background: rgba(244, 67, 54, 0.1);
|
||
}
|
||
.mc-transactions__icon-wrap--expire {
|
||
background: rgba(158, 158, 158, 0.1);
|
||
}
|
||
.mc-transactions__icon-wrap--default {
|
||
background: rgba(22, 119, 255, 0.1);
|
||
}
|
||
|
||
.mc-transactions__icon-text {
|
||
font-size: 32rpx;
|
||
}
|
||
|
||
.mc-transactions__info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.mc-transactions__item-title {
|
||
font-size: 28rpx;
|
||
color: #1E2A3A;
|
||
display: block;
|
||
margin-bottom: 6rpx;
|
||
}
|
||
|
||
.mc-transactions__item-time {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.mc-transactions__value {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.mc-transactions__value--positive {
|
||
color: #4CAF50;
|
||
}
|
||
|
||
.mc-transactions__value--negative {
|
||
color: #333;
|
||
}
|
||
|
||
.mc-transactions__empty {
|
||
padding: 64rpx 32rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.mc-transactions__empty-text {
|
||
font-size: 26rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.mc-transactions__loading {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 32px 16px;
|
||
}
|
||
|
||
.mc-records {
|
||
background: #FFFFFF;
|
||
border-radius: 16rpx;
|
||
padding: 24rpx 32rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.mc-records__header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.mc-records__title {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #1E2A3A;
|
||
}
|
||
|
||
.mc-records__tabs {
|
||
display: flex;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.mc-records__tab {
|
||
padding: 12rpx 20rpx;
|
||
border-radius: 12rpx;
|
||
font-size: 24rpx;
|
||
color: #718096;
|
||
background: #F7FAFC;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.mc-records__tab--active {
|
||
background: #1677FF;
|
||
color: #FFFFFF;
|
||
}
|
||
|
||
.mc-records__divider {
|
||
height: 1rpx;
|
||
background: #F0F0F0;
|
||
}
|
||
|
||
.mc-records__scroll {
|
||
height: 400rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.mc-records__item-inner {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
padding: 24rpx 0;
|
||
}
|
||
|
||
.mc-records__icon-wrap {
|
||
width: 72rpx;
|
||
height: 72rpx;
|
||
border-radius: 36rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.mc-records__icon-wrap--orange {
|
||
background: rgba(242, 153, 74, 0.1);
|
||
}
|
||
|
||
.mc-records__icon-wrap--green {
|
||
background: rgba(76, 175, 80, 0.1);
|
||
}
|
||
|
||
.mc-records__icon-wrap--purple {
|
||
background: rgba(156, 39, 176, 0.1);
|
||
}
|
||
|
||
.mc-records__icon-wrap--default {
|
||
background: rgba(22, 119, 255, 0.1);
|
||
}
|
||
|
||
.mc-records__icon {
|
||
width: 36rpx;
|
||
height: 36rpx;
|
||
}
|
||
|
||
.mc-records__icon-text {
|
||
font-size: 32rpx;
|
||
}
|
||
|
||
.mc-records__info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.mc-records__item-title {
|
||
font-size: 28rpx;
|
||
color: #1E2A3A;
|
||
display: block;
|
||
margin-bottom: 6rpx;
|
||
}
|
||
|
||
.mc-records__item-time {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.mc-records__value {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.mc-records__value--positive {
|
||
color: #4CAF50;
|
||
}
|
||
|
||
.mc-records__value--negative {
|
||
color: #333;
|
||
}
|
||
|
||
.mc-records__value--pending {
|
||
color: #F2994A;
|
||
}
|
||
|
||
.mc-records__empty {
|
||
padding: 64rpx 32rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.mc-records__empty-text {
|
||
font-size: 26rpx;
|
||
color: #999;
|
||
}
|
||
</style>
|