181 lines
3.7 KiB
Vue
181 lines
3.7 KiB
Vue
<template>
|
|
<view class="mall-page">
|
|
<view class="tab-bar">
|
|
<view
|
|
class="tab-item"
|
|
:class="{ active: activeTab === 'stored_value' }"
|
|
@tap="switchTab('stored_value')"
|
|
>
|
|
会员卡
|
|
</view>
|
|
<view
|
|
class="tab-item"
|
|
:class="{ active: activeTab === 'membership' }"
|
|
@tap="switchTab('membership')"
|
|
>
|
|
课程套餐
|
|
</view>
|
|
</view>
|
|
|
|
<view v-if="isLoading" class="load-more-tip">加载中...</view>
|
|
|
|
<view v-else class="card-list">
|
|
<view
|
|
v-for="item in currentList"
|
|
:key="item.id"
|
|
class="card item-card"
|
|
@tap="goDetail(item)"
|
|
>
|
|
<view class="card-body">
|
|
<text class="item-name">{{ item.name }}</text>
|
|
<text class="item-price">¥{{ item.price }}</text>
|
|
</view>
|
|
</view>
|
|
<view v-if="currentList.length === 0" class="empty-state">暂无可购买的商品</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, onMounted, ref } from 'vue'
|
|
import { membershipApi } from '@/modules/comprehensive/api/membership'
|
|
import { storedValueApi } from '@/modules/comprehensive/api/storedValue'
|
|
import { useComprehensiveSessionStore } from '@/modules/comprehensive/stores/session'
|
|
|
|
const session = useComprehensiveSessionStore()
|
|
const activeTab = ref('stored_value')
|
|
const plans = ref([])
|
|
const cards = ref([])
|
|
const isLoading = ref(false)
|
|
|
|
const currentList = computed(() => {
|
|
if (activeTab.value === 'membership') {
|
|
return plans.value.map((p) => ({ ...p, _type: 'membership' }))
|
|
}
|
|
return cards.value.map((c) => ({ ...c, _type: 'stored_value' }))
|
|
})
|
|
|
|
onMounted(async () => {
|
|
await session.init()
|
|
await loadPage()
|
|
})
|
|
|
|
const loadPage = async () => {
|
|
isLoading.value = true
|
|
try {
|
|
const [planData, cardData] = await Promise.all([
|
|
membershipApi.getPlans(),
|
|
storedValueApi.getCards()
|
|
])
|
|
plans.value = planData
|
|
cards.value = cardData
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
const goDetail = (item) => {
|
|
uni.navigateTo({
|
|
url: `/pages/comprehensive/mall/detail?type=${item._type}&id=${item.id}`
|
|
})
|
|
}
|
|
|
|
const switchTab = (tab) => {
|
|
if (activeTab.value === tab) return
|
|
activeTab.value = tab
|
|
loadPage()
|
|
}
|
|
|
|
const refresh = async () => {
|
|
await session.init()
|
|
await loadPage()
|
|
}
|
|
|
|
defineExpose({ refresh })
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.mall-page {
|
|
min-height: 100vh;
|
|
background: $bg-color-soft;
|
|
}
|
|
|
|
.tab-bar {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
display: flex;
|
|
height: 88rpx;
|
|
background: $bg-color-main;
|
|
border-bottom: 1rpx solid $border-color;
|
|
}
|
|
|
|
.tab-item {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 28rpx;
|
|
font-weight: 700;
|
|
color: $text-color-muted;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.tab-item.active {
|
|
color: $text-color-main;
|
|
font-weight: 900;
|
|
position: relative;
|
|
}
|
|
|
|
.tab-item.active::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 48rpx;
|
|
height: 4rpx;
|
|
border-radius: 2rpx;
|
|
background: $text-color-main;
|
|
}
|
|
|
|
.card-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: $spacing-sm;
|
|
padding: $spacing-md;
|
|
padding-bottom: calc($spacing-lg + env(safe-area-inset-bottom));
|
|
}
|
|
|
|
.item-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.item-card:active {
|
|
background-color: $bg-color-hover;
|
|
}
|
|
|
|
.card-body {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: $spacing-sm;
|
|
}
|
|
|
|
.item-name {
|
|
flex: 1;
|
|
color: $text-color-main;
|
|
font-size: 30rpx;
|
|
font-weight: 800;
|
|
line-height: 1.35;
|
|
}
|
|
|
|
.item-price {
|
|
color: $color-primary;
|
|
font-size: 36rpx;
|
|
font-weight: 900;
|
|
}
|
|
</style>
|