Files
pingxiangcard/src/packages/user/pages/card/list.vue
T
jacobxu666andClaude 5524d78229 feat: 景区详情页优化 + 代码质量修复 + 首页适配
- 详情页:地图按钮改为复制地址,电话点击直接拨号
- 详情页:移除免X次/年徽章,增加信息卡片与卡种卡片间距
- 详情页:运营状态实时判断(根据营业时间自动显示营业中/已闭园)
- 详情页:轮播圆点仅多图时显示,加大尺寸+投影,z-index置顶
- 详情页:景区电话箭头靠右对齐
- 首页:热门景点两列布局改用padding方案,修复小屏手机单列显示
- 代码质量:card/order API移入分包,消除主包未使用JS文件
- 代码质量:manifest启用minified和lazyCodeLoading
- 构建:vite配置静默Sass弃用警告

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-25 19:19:40 +08:00

277 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page">
<!-- 顶部风景头图 + 标题 -->
<view class="list-hero">
<image
class="hero-bg"
src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?fm=jpg&q=80&w=750&h=300&fit=crop"
mode="aspectFill"
/>
<view class="hero-overlay" />
<view class="hero-inner">
<text class="hero-title">旅游卡</text>
<text class="hero-sub">畅游赣湘,一卡在手</text>
</view>
</view>
<!-- 分类筛选 -->
<view class="filter-bar">
<view
v-for="cat in categories"
:key="cat.value"
class="filter-tag"
:class="{ active: currentCategory === cat.value }"
@click="selectCategory(cat.value)"
>
<text>{{ cat.label }}</text>
</view>
</view>
<!-- 搜索栏 -->
<view class="search-bar">
<uni-search-bar v-model="keyword" placeholder="搜索景区、年卡" @confirm="handleSearch" @clear="handleSearch" />
</view>
<!-- 年卡列表 -->
<view class="card-list">
<view v-if="loading" class="loading">
<view class="loading-dot" />
<text>加载中...</text>
</view>
<view v-else-if="cardList.length === 0" class="empty-state">
<view class="empty-icon">
<view class="icon-ticket" />
</view>
<text class="empty-text">暂无符合条件的年卡</text>
<text class="empty-hint">换个关键词试试</text>
</view>
<view v-else>
<card-item v-for="card in cardList" :key="card.id" :card="card" @click="goDetail" />
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getCardList } from '../../api/card'
import CardItem from '../../../../components/card-item.vue'
import type { CardInfo } from '../../../../api/types'
const cardList = ref<CardInfo[]>([])
const loading = ref(false)
const keyword = ref('')
const currentCategory = ref('')
const categories = [
{ label: '全部', value: '' },
{ label: '旅游年卡', value: 'travel' },
{ label: '亲子年卡', value: 'family' },
{ label: '情侣年卡', value: 'couple' }
]
async function fetchCardList(params?: { keyword?: string; category?: string }) {
loading.value = true
try {
cardList.value = await getCardList(params)
} finally {
loading.value = false
}
}
onLoad(() => {
fetchCardList()
})
function handleSearch() {
fetchCardList({ keyword: keyword.value, category: currentCategory.value })
}
function selectCategory(value: string) {
currentCategory.value = value
handleSearch()
}
function goDetail(card: CardInfo) {
uni.navigateTo({ url: `/packages/user/pages/card/detail?id=${card.id}` })
}
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: $uv-bg-color;
}
/* Hero header */
.list-hero {
position: relative;
height: 280rpx;
overflow: hidden;
}
.hero-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.hero-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
180deg,
rgba(48, 49, 51, 0.3) 0%,
rgba(48, 49, 51, 0.6) 100%
);
}
.hero-inner {
position: relative;
z-index: 2;
padding: $spacing-12 $spacing-4 $spacing-4;
display: flex;
flex-direction: column;
}
.hero-title {
font-family: $font-family-display;
font-size: $font-size-2xl;
font-weight: $font-weight-bold;
color: #fff;
line-height: $line-height-tight;
margin-bottom: $spacing-1;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
}
.hero-sub {
font-size: $font-size-sm;
color: rgba(255, 255, 255, 0.8);
letter-spacing: 2rpx;
}
/* Filter bar */
.filter-bar {
display: flex;
gap: $spacing-2;
padding: $spacing-3 $spacing-3 $spacing-1;
background: $uv-bg-color;
}
.filter-tag {
padding: $spacing-1 $spacing-3;
border-radius: $radius-full;
font-size: $font-size-sm;
color: $uv-content-color;
background: $color-card;
border: $border-width solid $uv-border-color;
font-weight: $font-weight-medium;
transition: all $transition-fast;
&.active {
background: $uv-primary;
color: #fff;
border-color: $uv-primary;
font-weight: $font-weight-semibold;
}
&:active {
transform: scale(0.96);
}
}
/* Search bar */
.search-bar {
padding: $spacing-1 $spacing-3 $spacing-2;
background: $uv-bg-color;
}
/* Card list */
.card-list {
padding: 0 $spacing-3 $spacing-3;
}
/* Loading */
.loading {
display: flex;
flex-direction: column;
align-items: center;
padding: $spacing-10 0;
gap: $spacing-2;
text {
font-size: $font-size-sm;
color: $uv-tips-color;
}
}
.loading-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: $uv-primary;
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.4; transform: scale(0.8); }
50% { opacity: 1; transform: scale(1.2); }
}
/* Empty state */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: $spacing-10 0;
}
.empty-icon {
width: 96rpx;
height: 96rpx;
border-radius: 50%;
background: $uv-primary-light;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: $spacing-3;
}
.icon-ticket {
width: 40rpx;
height: 28rpx;
border: 3rpx solid $uv-primary;
border-radius: 6rpx;
position: relative;
&::after {
content: '';
position: absolute;
top: 50%;
left: -3rpx;
right: -3rpx;
height: 3rpx;
background: $uv-primary;
transform: translateY(-50%);
}
}
.empty-text {
font-size: $font-size-base;
color: $uv-main-color;
font-weight: $font-weight-medium;
margin-bottom: $spacing-1;
}
.empty-hint {
font-size: $font-size-sm;
color: $uv-tips-color;
}
</style>