Files
pingxiangcard/src/packages/content/pages/scenic-list.vue
T

1033 lines
37 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="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-content" :style="{ height: navContentHeight + 'px' }">
<view class="nav-side">
<view class="nav-back" @click="goBack">
<view class="back-arrow" />
</view>
</view>
<text class="nav-title">全部合作景区</text>
<view class="nav-side" />
</view>
</view>
<!-- 页面主体(避开导航栏 + 吸顶筛选区) -->
<view class="page-body" :style="{ paddingTop: (navTotalHeight + stickyHeaderHeight) + 'px' }">
<!-- 吸顶搜索筛选区 -->
<view class="sticky-header" :style="{ top: navTotalHeight + 'px' }">
<!-- 搜索框 -->
<view class="search-box">
<view class="search-icon">
<view class="icon-circle" />
<view class="icon-handle" />
</view>
<input
class="search-input"
v-model="searchText"
placeholder="搜索景区名称"
placeholder-class="search-placeholder"
/>
<view v-if="searchText" class="clear-btn" @click="clearSearch">
<text>×</text>
</view>
</view>
<!-- 下拉筛选区 -->
<view class="dropdown-row">
<view class="dropdown-trigger" @click="openPicker('region')">
<text class="dropdown-label" :class="{ active: selectedRegion }">{{ selectedRegion || '景区地区' }}</text>
<view class="dropdown-arrow" />
</view>
<view class="dropdown-trigger" @click="openPicker('level')">
<text class="dropdown-label" :class="{ active: selectedLevel }">{{ selectedLevel || '景区等级' }}</text>
<view class="dropdown-arrow" />
</view>
<view class="dropdown-trigger" @click="openPicker('cardType')">
<text class="dropdown-label" :class="{ active: selectedCardType }">{{ selectedCardType ? (selectedCardType === '年卡' ? '赣湘全域旅游年卡' : '超级玩家卡') : '全部卡种' }}</text>
<view class="dropdown-arrow" />
</view>
</view>
</view>
<!-- 景区列表 -->
<view class="list-area">
<view v-if="filteredScenics.length === 0" class="empty-state">
<view class="empty-icon">
<view class="empty-circle" />
<view class="empty-line line-1" />
<view class="empty-line line-2" />
</view>
<text class="empty-text">暂无匹配景区</text>
</view>
<view v-else class="list-container">
<view
v-for="scenic in filteredScenics"
:key="scenic.id"
class="scenic-card"
@click="onCardClick(scenic)"
>
<!-- 封面图 -->
<view class="card-cover">
<image class="cover-img" :src="scenic.image" mode="aspectFill" />
<view class="region-tag">
<view class="tag-dot" />
<text>{{ scenic.region }}</text>
</view>
<view v-if="scenic.level" class="level-tag" :class="`level-${scenic.level}`">
<text>{{ scenic.level }}</text>
</view>
</view>
<!-- 信息区 -->
<view class="card-body">
<!-- 名称 + 挂牌价 同行 -->
<view class="name-price-row">
<text class="scenic-name">{{ scenic.name }}</text>
<view class="price-group">
<text v-if="scenic.price === 0" class="price-free">免费</text>
<template v-else>
<text class="price-symbol">¥</text>
<text class="price-value">{{ scenic.price }}</text>
</template>
</view>
</view>
<!-- 营业时间 + 营业状态 同行 -->
<view class="hours-status-row">
<view class="time-block">
<view class="time-icon" />
<text class="time-text">{{ scenic.hours }}</text>
</view>
<view class="status-block">
<view class="status-icon" :class="scenic.status === '正常营业' ? 'open' : 'closed'" />
<text class="status-text" :class="scenic.status === '正常营业' ? 'open' : 'closed'">
{{ scenic.status }}
</text>
</view>
</view>
<!-- 剩余次数 + 免票标签 + 预约标签 同行 -->
<view class="benefit-row">
<view v-if="scenic.freeCount > 0" class="remaining-tag">
<text>全年可用{{ scenic.freeCount }}次</text>
</view>
<view class="benefit-tag primary">
<text>{{ scenic.freeCount > 0 ? `免${scenic.freeCount}次` : '免费' }}</text>
</view>
<view class="benefit-tag" :class="scenic.needReservation ? 'warning' : 'success'">
<text>{{ scenic.needReservation ? '需预约' : '免预约' }}</text>
</view>
</view>
<!-- 温馨提示 -->
<view v-if="scenic.tips" class="tips-bar">
<view class="tips-icon">!</view>
<text class="tips-text">{{ scenic.tips }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 筛选弹窗(单实例,动态内容) -->
<uni-popup
ref="filterPopup"
type="bottom"
:mask-click="true"
:safe-area="true"
background-color="#ffffff"
@change="onPopupChange"
>
<view class="popup-sheet" @touchmove.stop.prevent>
<!-- 头部:标题 + 重置 -->
<view class="popup-header">
<text class="popup-title">{{ popupTitle }}</text>
<view class="popup-reset" @click="resetFilter">
<text>重置</text>
</view>
</view>
<!-- 可滚动选项列表 -->
<scroll-view scroll-y class="popup-scroll">
<view
v-for="item in currentOptions"
:key="item.value"
class="popup-option"
:class="{ active: tempValue === item.value }"
@click="onOptionTap(item.value)"
>
<text>{{ item.label }}</text>
<view v-if="tempValue === item.value" class="check-mark" />
</view>
</scroll-view>
<!-- 底部确定按钮 -->
<view class="popup-footer" @click="confirmFilter">
<text>确定筛选</text>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, getCurrentInstance } from 'vue'
interface ScenicItem {
id: number
name: string
price: number
freeCount: number
level: string
status: '正常营业' | '暂停营业'
hours: string
needReservation: boolean
tips: string
type: string
region: string
image: string
cardTypes: string[]
}
// ========== 导航栏动态高度 ==========
const statusBarHeight = ref(20)
const navContentHeight = ref(44)
const navTotalHeight = ref(64)
const stickyHeaderHeight = ref(96) // 预估,onMounted 后精确测量
// 页面加载时获取系统信息
;(function initNavBar() {
try {
const windowInfo = uni.getWindowInfo()
statusBarHeight.value = windowInfo.statusBarHeight || 20
} catch {
statusBarHeight.value = 20
}
// #ifdef MP-WEIXIN
try {
const menuBtn = uni.getMenuButtonBoundingClientRect()
const capsuleHeight = menuBtn.height
const capsuleTop = menuBtn.top
const capsuleBottom = capsuleTop + capsuleHeight
// 导航栏内容区高度:胶囊上下各留等距间距
const contentH = (capsuleBottom - statusBarHeight.value) + (capsuleTop - statusBarHeight.value)
navContentHeight.value = Math.max(contentH, 44)
navTotalHeight.value = statusBarHeight.value + navContentHeight.value
} catch {
navContentHeight.value = 44
navTotalHeight.value = statusBarHeight.value + 44
}
// #endif
// #ifndef MP-WEIXIN
navContentHeight.value = 44
navTotalHeight.value = statusBarHeight.value + 44
// #endif
})()
// 吸顶筛选区高度:onMounted 后精确测量,预估值兜底
stickyHeaderHeight.value = 100
onMounted(() => {
const query = uni.createSelectorQuery().in(getCurrentInstance())
query.select('.sticky-header').boundingClientRect((rect: any) => {
if (rect && rect.height) {
// 实际高度 + 4px 安全间距,防止不同机型小数精度导致遮挡
stickyHeaderHeight.value = Math.ceil(rect.height) + 4
}
}).exec()
})
// ========== 数据 ==========
const scenicList: ScenicItem[] = [
{ id: 1, name: '武功山金顶景区', price: 70, freeCount: 1, level: '5A', status: '正常营业', hours: '08:00-18:00', needReservation: true, tips: '建议提前1天预约,山顶温差大请备好外套', type: '自然风光', region: '芦溪县', image: 'https://images.unsplash.com/photo-1513415564515-763d91423bdd?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 2, name: '杨岐山孽龙洞景区', price: 68, freeCount: 6, level: '4A', status: '正常营业', hours: '08:30-17:30', needReservation: false, tips: '洞内温度恒定18℃,建议携带薄外套', type: '自然风光', region: '上栗县', image: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 3, name: '仙凤三宝景区', price: 50, freeCount: 6, level: '4A', status: '正常营业', hours: '09:00-17:00', needReservation: false, tips: '适合亲子游玩,园内有小动物喂养体验', type: '乡村田园', region: '芦溪县', image: 'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 4, name: '武功山君澜温泉', price: 228, freeCount: 6, level: '4A', status: '正常营业', hours: '10:00-22:00', needReservation: true, tips: '需提前预约,请自备泳衣泳帽', type: '休闲娱乐', region: '芦溪县', image: 'https://images.unsplash.com/photo-1433086966358-54859d0ed716?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 5, name: '凯光新天地生态旅游区', price: 90, freeCount: 6, level: '4A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '园区较大,建议穿舒适运动鞋', type: '休闲娱乐', region: '安源区', image: 'https://images.unsplash.com/photo-1441974231531-c6227db76b6e?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 6, name: '和平农场·哎马乐园', price: 79, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-17:30', needReservation: false, tips: '亲子乐园,周末人流较多建议错峰', type: '乡村田园', region: '湘东区', image: 'https://images.unsplash.com/photo-1501854140801-50d01698950b?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 7, name: '武功山滑草场', price: 60, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-17:00', needReservation: false, tips: '雨天暂停开放,请关注天气', type: '休闲娱乐', region: '芦溪县', image: 'https://images.unsplash.com/photo-1551632811-561732d1e306?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 8, name: '芦溪仙凤三宝农业休闲观光园', price: 50, freeCount: 6, level: '3A', status: '正常营业', hours: '08:30-17:30', needReservation: false, tips: '可体验采摘,季节性水果品种丰富', type: '乡村田园', region: '芦溪县', image: 'https://images.unsplash.com/photo-1500382017468-9049fed747ef?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 9, name: '麻山幸福村', price: 40, freeCount: 6, level: '3A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '乡村旅游景区,可体验农家生活', type: '乡村田园', region: '湘东区', image: 'https://images.unsplash.com/photo-1523712999610-f77fbcfc3843?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 10, name: '东桥双月湾', price: 38, freeCount: 6, level: '3A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '临水景区,请注意安全', type: '自然风光', region: '湘东区', image: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 11, name: '上栗花炮文化博览园', price: 45, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-17:00', needReservation: false, tips: '了解花炮历史文化,适合研学', type: '文化古迹', region: '上栗县', image: 'https://images.unsplash.com/photo-1533174072545-7a4b6ad7a6c3?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 12, name: '杨岐普通寺', price: 30, freeCount: 6, level: '3A', status: '正常营业', hours: '08:00-17:00', needReservation: false, tips: '佛教圣地,请保持安静', type: '文化古迹', region: '上栗县', image: 'https://images.unsplash.com/photo-1518241353330-0f7941c2d9b5?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 13, name: '孽龙洞漂流', price: 88, freeCount: 6, level: '3A', status: '暂停营业', hours: '暂停开放', needReservation: false, tips: '季节性项目,预计6月开放', type: '休闲娱乐', region: '上栗县', image: 'https://images.unsplash.com/photo-1504280390367-361c6d9f38f4?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 14, name: '安源路矿工人运动纪念馆', price: 0, freeCount: 0, level: '4A', status: '正常营业', hours: '09:00-16:30', needReservation: false, tips: '红色教育基地,周一闭馆', type: '红色教育', region: '安源区', image: 'https://images.unsplash.com/photo-1565008576549-57569a49371d?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 15, name: '莲花荷博园', price: 35, freeCount: 6, level: '3A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '夏季荷花盛开时最佳观赏期', type: '自然风光', region: '莲花县', image: 'https://images.unsplash.com/photo-1490750967868-88aa4486c946?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 16, name: '山口岩水利风景区', price: 25, freeCount: 6, level: '3A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '水利工程景观,可了解水利知识', type: '自然风光', region: '芦溪县', image: 'https://images.unsplash.com/photo-1439066615861-d1af74d74000?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 17, name: '宣风三宝水上乐园', price: 65, freeCount: 6, level: '3A', status: '暂停营业', hours: '暂停开放', needReservation: false, tips: '夏季开放,请关注开园公告', type: '休闲娱乐', region: '芦溪县', image: 'https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 18, name: '万龙山温泉', price: 168, freeCount: 6, level: '4A', status: '正常营业', hours: '10:00-22:00', needReservation: true, tips: '需提前预约,温泉养生好去处', type: '休闲娱乐', region: '芦溪县', image: 'https://images.unsplash.com/photo-1545569341-9eb8b30979d9?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 19, name: '杨岐山野生动物科普园', price: 42, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-17:00', needReservation: false, tips: '适合亲子科普教育', type: '乡村田园', region: '上栗县', image: 'https://images.unsplash.com/photo-1518241353330-0f7941c2d9b5?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 20, name: '鸡冠山森林步道', price: 0, freeCount: 0, level: '3A', status: '正常营业', hours: '全天开放', needReservation: false, tips: '森林氧吧,适合徒步健身', type: '自然风光', region: '上栗县', image: 'https://images.unsplash.com/photo-1551632811-561732d1e306?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 21, name: '赤山杨岐山民宿集群配套景区', price: 28, freeCount: 6, level: '2A', status: '正常营业', hours: '08:00-20:00', needReservation: false, tips: '可体验民宿住宿,感受乡村风情', type: '乡村田园', region: '上栗县', image: 'https://images.unsplash.com/photo-1510798831971-661eb04b3739?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 22, name: '湘东傩文化园', price: 32, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-17:00', needReservation: false, tips: '了解傩文化,感受非遗魅力', type: '文化古迹', region: '湘东区', image: 'https://images.unsplash.com/photo-1533174072545-7a4b6ad7a6c3?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 23, name: '广寒寨四季花海', price: 36, freeCount: 6, level: '3A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '四季花卉轮番绽放,拍照打卡好去处', type: '自然风光', region: '湘东区', image: 'https://images.unsplash.com/photo-1490750967868-88aa4486c946?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 24, name: '上栗泉水湾度假村', price: 58, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-18:00', needReservation: false, tips: '休闲度假好去处,可垂钓烧烤', type: '休闲娱乐', region: '上栗县', image: 'https://images.unsplash.com/photo-1571896349842-33c89424de2d?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 25, name: '莲花棋盘山', price: 22, freeCount: 6, level: '2A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '登山步道完善,适合休闲健身', type: '自然风光', region: '莲花县', image: 'https://images.unsplash.com/photo-1444464666168-49d633b86797?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 26, name: '芦溪东阳古村', price: 26, freeCount: 6, level: '3A', status: '正常营业', hours: '08:30-17:30', needReservation: false, tips: '古建筑群保存完好,可感受传统村落文化', type: '文化古迹', region: '芦溪县', image: 'https://images.unsplash.com/photo-1528181304800-259b08848526?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 27, name: '安源欢乐谷', price: 72, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-18:00', needReservation: false, tips: '游乐设施丰富,适合亲子游玩', type: '休闲娱乐', region: '安源区', image: 'https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 28, name: '湘东太阳广场游乐区', price: 48, freeCount: 6, level: '2A', status: '正常营业', hours: '09:00-21:00', needReservation: false, tips: '夜间灯光秀值得一看', type: '休闲娱乐', region: '湘东区', image: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 29, name: '长平养生谷', price: 52, freeCount: 6, level: '3A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '养生休闲好去处,空气清新', type: '自然风光', region: '上栗县', image: 'https://images.unsplash.com/photo-1518241353330-0f7941c2d9b5?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 30, name: '坊楼红色文化园', price: 0, freeCount: 0, level: '3A', status: '正常营业', hours: '09:00-17:00', needReservation: false, tips: '红色教育基地,免费参观', type: '红色教育', region: '莲花县', image: 'https://images.unsplash.com/photo-1565008576549-57569a49371d?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 31, name: '高坑生态采摘园', price: 33, freeCount: 6, level: '2A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '季节性水果采摘,体验田园乐趣', type: '乡村田园', region: '安源区', image: 'https://images.unsplash.com/photo-1500382017468-9049fed747ef?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 32, name: '源南田园综合体', price: 39, freeCount: 6, level: '3A', status: '正常营业', hours: '08:30-17:30', needReservation: false, tips: '田园风光优美,可体验农耕文化', type: '乡村田园', region: '芦溪县', image: 'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 33, name: '下埠陶瓷文化小镇', price: 29, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-17:00', needReservation: false, tips: '可体验陶瓷制作,了解陶瓷文化', type: '文化古迹', region: '湘东区', image: 'https://images.unsplash.com/photo-1528181304800-259b08848526?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 34, name: '桐木水上乐园', price: 66, freeCount: 6, level: '3A', status: '暂停营业', hours: '暂停开放', needReservation: false, tips: '夏季开放,请关注开园时间', type: '休闲娱乐', region: '上栗县', image: 'https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 35, name: '东源温泉度假村', price: 138, freeCount: 6, level: '4A', status: '正常营业', hours: '10:00-22:00', needReservation: true, tips: '需提前预约,温泉品质优良', type: '休闲娱乐', region: '上栗县', image: 'https://images.unsplash.com/photo-1545569341-9eb8b30979d9?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 36, name: '长丰山水景区', price: 24, freeCount: 6, level: '2A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '山水相依,适合休闲散步', type: '自然风光', region: '莲花县', image: 'https://images.unsplash.com/photo-1439066615861-d1af74d74000?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 37, name: '白竺青草湖', price: 27, freeCount: 6, level: '3A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '湖光山色,可垂钓泛舟', type: '自然风光', region: '湘东区', image: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 38, name: '荷尧古渡口', price: 0, freeCount: 0, level: '2A', status: '正常营业', hours: '全天开放', needReservation: false, tips: '古渡口遗址,感受历史文化', type: '文化古迹', region: '湘东区', image: 'https://images.unsplash.com/photo-1504280390367-361c6d9f38f4?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 39, name: '新泉红色研学基地', price: 31, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-17:00', needReservation: true, tips: '研学活动需提前预约', type: '红色教育', region: '芦溪县', image: 'https://images.unsplash.com/photo-1565008576549-57569a49371d?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 40, name: '张佳坊山地乐园', price: 55, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-17:30', needReservation: false, tips: '山地运动项目丰富,注意安全', type: '休闲娱乐', region: '芦溪县', image: 'https://images.unsplash.com/photo-1551632811-561732d1e306?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 41, name: '南坑户外拓展基地', price: 44, freeCount: 6, level: '3A', status: '正常营业', hours: '08:30-18:00', needReservation: true, tips: '团队拓展需提前预约', type: '休闲娱乐', region: '莲花县', image: 'https://images.unsplash.com/photo-1501854140801-50d01698950b?fm=jpg&q=80&w=800&h=400&fit=crop' },
{ id: 42, name: '老关荷花景区', price: 34, freeCount: 6, level: '2A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '夏季荷花盛开,景色宜人', type: '自然风光', region: '湘东区', image: 'https://images.unsplash.com/photo-1490750967868-88aa4486c946?fm=jpg&q=80&w=800&h=400&fit=crop', cardTypes: ['年卡', '超级卡'] },
]
// 卡种分配:部分景区仅支持年卡,部分仅支持超级卡,其余两者均可
;(function assignCardTypes() {
const annualOnly = new Set([4, 14, 18, 30, 35])
const superOnly = new Set([1, 13, 17, 34, 38])
scenicList.forEach(s => {
if (annualOnly.has(s.id)) s.cardTypes = ['年卡']
else if (superOnly.has(s.id)) s.cardTypes = ['超级卡']
else if (!s.cardTypes) s.cardTypes = ['年卡', '超级卡']
})
})()
// ========== 搜索筛选 ==========
const searchText = ref('')
const selectedRegion = ref('')
const selectedLevel = ref('')
const selectedCardType = ref('')
// uni-popup 单实例引用
const filterPopup = ref()
// 当前弹窗类型 & 临时选中值
const activePickerType = ref<'region' | 'level' | 'cardType'>('region')
const tempValue = ref('')
const regionOptions = [
{ label: '全部地区', value: '' },
{ label: '安源区', value: '安源区' },
{ label: '湘东区', value: '湘东区' },
{ label: '芦溪县', value: '芦溪县' },
{ label: '上栗县', value: '上栗县' },
{ label: '莲花县', value: '莲花县' },
]
const levelOptions = [
{ label: '全部等级', value: '' },
{ label: '5A', value: '5A' },
{ label: '4A', value: '4A' },
{ label: '3A', value: '3A' },
{ label: '2A', value: '2A' },
]
const cardTypeOptions = [
{ label: '全部卡种', value: '' },
{ label: '赣湘全域旅游年卡', value: '年卡' },
{ label: '超级玩家卡', value: '超级卡' },
]
// 动态标题 & 选项列表
const popupTitle = computed(() => {
const map = { region: '选择地区', level: '选择等级', cardType: '选择卡种' }
return map[activePickerType.value]
})
const currentOptions = computed(() => {
const map = { region: regionOptions, level: levelOptions, cardType: cardTypeOptions }
return map[activePickerType.value]
})
function openPicker(type: 'region' | 'level' | 'cardType') {
activePickerType.value = type
const valMap = { region: selectedRegion.value, level: selectedLevel.value, cardType: selectedCardType.value }
tempValue.value = valMap[type]
filterPopup.value?.open()
}
function onOptionTap(value: string) {
tempValue.value = value
}
function confirmFilter() {
const map = { region: selectedRegion, level: selectedLevel, cardType: selectedCardType }
map[activePickerType.value].value = tempValue.value
filterPopup.value?.close()
}
function resetFilter() {
tempValue.value = ''
}
function onPopupChange(e: { show: boolean }) {
// 弹窗关闭时无需额外处理
}
function clearSearch() {
searchText.value = ''
}
// 过滤后的景区列表
const filteredScenics = computed(() => {
return scenicList.filter(scenic => {
if (searchText.value && !scenic.name.toLowerCase().includes(searchText.value.toLowerCase())) {
return false
}
if (selectedRegion.value && scenic.region !== selectedRegion.value) {
return false
}
if (selectedLevel.value && scenic.level !== selectedLevel.value) {
return false
}
if (selectedCardType.value && !scenic.cardTypes.includes(selectedCardType.value)) {
return false
}
return true
})
})
function goBack() {
uni.navigateBack()
}
function onCardClick(scenic: ScenicItem) {
uni.navigateTo({
url: `/packages/content/pages/scenic-detail?id=${scenic.id}`,
})
}
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: $color-background;
position: relative;
font-family: 'SourceHanSerifCN', '思源宋体', serif;
}
/* ====== 固定导航栏 ====== */
.nav-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: $color-card;
box-shadow: $shadow-sm;
}
.nav-content {
display: flex;
align-items: center;
padding: 0 $spacing-3;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: $radius-base;
flex-shrink: 0;
transition: background $transition-fast;
&:active {
background: $color-muted;
}
}
.back-arrow {
width: 20rpx;
height: 20rpx;
border-left: 4rpx solid $color-foreground;
border-bottom: 4rpx solid $color-foreground;
transform: rotate(45deg);
margin-left: 6rpx;
}
.nav-title {
flex: 1;
text-align: center;
font-size: $font-size-lg;
font-weight: $font-weight-medium;
color: $color-foreground;
}
.nav-side {
width: 80rpx;
flex-shrink: 0;
}
/* ====== 页面主体 ====== */
.page-body {
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ====== 吸顶搜索筛选区 ====== */
.sticky-header {
position: fixed;
left: 0;
right: 0;
z-index: 50;
background: $color-background;
padding: $spacing-2 $spacing-3;
box-shadow: $shadow-sm;
}
/* 搜索框 */
.search-box {
display: flex;
align-items: center;
background: $color-card;
border-radius: 32rpx;
padding: 0 $spacing-3;
height: 72rpx;
margin-bottom: $spacing-2;
}
.search-icon {
position: relative;
width: 32rpx;
height: 32rpx;
margin-right: $spacing-2;
}
.icon-circle {
width: 20rpx;
height: 20rpx;
border: 3rpx solid $uv-tips-color;
border-radius: 50%;
position: absolute;
top: 0;
left: 0;
}
.icon-handle {
width: 10rpx;
height: 3rpx;
background: $uv-tips-color;
position: absolute;
bottom: 4rpx;
right: 0;
transform: rotate(45deg);
}
.search-input {
flex: 1;
font-size: $font-size-base;
color: $color-foreground;
}
.search-placeholder {
color: $uv-tips-color;
}
.clear-btn {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
color: $uv-tips-color;
font-size: 40rpx;
&:active {
color: $color-foreground;
}
}
/* 下拉筛选行 */
.dropdown-row {
display: flex;
gap: $spacing-2;
}
.dropdown-trigger {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
background: $color-card;
border-radius: $radius-base;
padding: $spacing-2 $spacing-3;
transition: background $transition-fast;
&:active {
background: $color-muted;
}
}
.dropdown-label {
font-size: $font-size-sm;
color: $color-foreground;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.active {
color: $uv-primary;
font-weight: $font-weight-medium;
}
}
.dropdown-arrow {
width: 12rpx;
height: 12rpx;
border-right: 3rpx solid $uv-tips-color;
border-bottom: 3rpx solid $uv-tips-color;
transform: rotate(45deg);
flex-shrink: 0;
margin-left: $spacing-1;
}
/* ====== 列表区域 ====== */
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx $spacing-3;
}
.empty-icon {
position: relative;
width: 160rpx;
height: 160rpx;
margin-bottom: $spacing-4;
}
.empty-circle {
width: 120rpx;
height: 120rpx;
border: 6rpx solid $uv-border-color;
border-radius: 50%;
position: absolute;
top: 20rpx;
left: 20rpx;
}
.empty-line {
position: absolute;
background: $uv-border-color;
border-radius: 4rpx;
}
.line-1 {
width: 80rpx;
height: 8rpx;
top: 60rpx;
left: 40rpx;
}
.line-2 {
width: 60rpx;
height: 8rpx;
top: 80rpx;
left: 50rpx;
}
.empty-text {
font-size: $font-size-base;
color: $uv-tips-color;
}
/* 列表区域 */
.list-area {
flex: 1;
}
/* 列表容器 */
.list-container {
padding: $spacing-3;
padding-top: $spacing-2;
}
/* ====== 卡片 ====== */
.scenic-card {
background: $color-card;
border-radius: $radius-lg;
overflow: hidden;
box-shadow: $shadow-sm;
margin-bottom: $spacing-3;
transition: transform $transition-fast, box-shadow $transition-base;
&:active {
transform: scale(0.98);
box-shadow: $shadow-base;
}
}
/* 封面图 */
.card-cover {
position: relative;
width: 100%;
height: 320rpx;
background: $color-muted;
}
.cover-img {
width: 100%;
height: 100%;
}
.region-tag {
position: absolute;
top: $spacing-2;
left: $spacing-2;
display: flex;
align-items: center;
gap: 6rpx;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(8rpx);
border-radius: $radius-sm;
padding: 6rpx $spacing-2;
text {
font-size: $font-size-xs;
color: #fff;
}
}
.tag-dot {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: $color-card;
}
/* 等级标签 — 图片右上角 */
.level-tag {
position: absolute;
top: $spacing-2;
right: $spacing-2;
display: flex;
align-items: center;
justify-content: center;
padding: 6rpx $spacing-2;
border-radius: $radius-sm;
min-height: 36rpx;
text {
font-size: $font-size-xs;
color: #fff;
font-weight: $font-weight-medium;
}
&.level-5A { background: #e53935; }
&.level-4A { background: #f9ae3d; }
&.level-3A,
&.level-2A { background: $uv-success; }
}
/* 信息区 */
.card-body {
padding: $spacing-3;
}
/* 名称 + 挂牌价 同行 */
.name-price-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: $spacing-2;
}
.scenic-name {
font-size: $font-size-lg;
font-weight: $font-weight-medium;
color: $color-foreground;
flex: 1;
margin-right: $spacing-2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.price-group {
display: flex;
align-items: baseline;
flex-shrink: 0;
}
.price-symbol {
font-size: $font-size-sm;
color: $uv-error;
font-weight: $font-weight-medium;
}
.price-value {
font-size: $font-size-xl;
color: $uv-error;
font-weight: $font-weight-bold;
}
.price-free {
font-size: $font-size-base;
color: $uv-success;
font-weight: $font-weight-medium;
}
/* 营业时间 + 营业状态 同行 */
.hours-status-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: $spacing-2;
}
.time-block {
display: flex;
align-items: center;
}
.time-icon {
width: 22rpx;
height: 22rpx;
border: 2rpx solid $uv-tips-color;
border-radius: 50%;
margin-right: 8rpx;
position: relative;
&::before {
content: '';
position: absolute;
width: 2rpx;
height: 8rpx;
background: $uv-tips-color;
top: 4rpx;
left: 50%;
transform: translateX(-50%);
}
&::after {
content: '';
position: absolute;
width: 6rpx;
height: 2rpx;
background: $uv-tips-color;
top: 50%;
left: 50%;
transform: translateY(-50%);
}
}
.time-text {
font-size: $font-size-sm;
color: $uv-tips-color;
}
.status-block {
display: flex;
align-items: center;
}
.status-icon {
width: 14rpx;
height: 14rpx;
border-radius: 50%;
margin-right: 8rpx;
&.open { background: $uv-success; }
&.closed { background: $uv-error; }
}
.status-text {
font-size: $font-size-sm;
&.open { color: $uv-success; }
&.closed { color: $uv-error; }
}
/* 权益标签行:剩余次数 + 免票 + 预约 同行 */
.benefit-row {
display: flex;
align-items: center;
gap: $spacing-1;
margin-bottom: $spacing-2;
flex-wrap: wrap;
}
.remaining-tag {
display: flex;
align-items: center;
justify-content: center;
padding: 4rpx $spacing-2;
background: rgba($uv-success, 0.1);
border-radius: $radius-sm;
min-height: 36rpx;
text {
font-size: $font-size-xs;
color: $uv-success;
font-weight: $font-weight-medium;
white-space: nowrap;
}
}
.benefit-tag {
display: flex;
align-items: center;
justify-content: center;
padding: 4rpx $spacing-2;
border-radius: $radius-sm;
min-height: 36rpx;
text {
font-size: $font-size-xs;
color: #fff;
font-weight: $font-weight-medium;
white-space: nowrap;
}
&.primary {
background: $uv-primary;
margin-left: auto;
}
&.warning { background: $uv-warning; }
&.success { background: $uv-success; }
}
/* 温馨提示 */
.tips-bar {
display: flex;
align-items: flex-start;
background: rgba(249, 174, 61, 0.08);
border-radius: $radius-sm;
padding: $spacing-2;
}
.tips-icon {
width: 32rpx;
height: 32rpx;
background: $uv-warning;
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: $font-size-sm;
font-weight: $font-weight-bold;
margin-right: $spacing-2;
flex-shrink: 0;
}
.tips-text {
font-size: $font-size-sm;
color: $uv-content-color;
line-height: 1.5;
}
/* ====== uni-popup 弹窗内容 ====== */
.popup-sheet {
background: $color-card;
border-radius: $radius-xl $radius-xl 0 0;
overflow: hidden;
}
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: $spacing-3 $spacing-3;
border-bottom: $border-width solid rgba($uv-border-color, 0.5);
}
.popup-title {
font-size: $font-size-lg;
font-weight: $font-weight-medium;
color: $color-foreground;
}
.popup-reset {
padding: $spacing-1 $spacing-2;
border-radius: $radius-sm;
transition: background $transition-fast;
text {
font-size: $font-size-sm;
color: $uv-primary;
}
&:active {
background: rgba($uv-primary, 0.08);
}
}
.popup-scroll {
max-height: 560rpx;
}
.popup-option {
display: flex;
align-items: center;
justify-content: space-between;
padding: $spacing-3;
min-height: 96rpx;
border-bottom: $border-width solid rgba($uv-border-color, 0.25);
transition: background $transition-fast;
&:last-child {
border-bottom: none;
}
&:active {
background: $color-muted;
}
text {
font-size: $font-size-base;
color: $color-foreground;
}
&.active text {
color: $uv-primary;
font-weight: $font-weight-medium;
}
}
.check-mark {
width: 18rpx;
height: 32rpx;
border-right: 5rpx solid $uv-primary;
border-bottom: 5rpx solid $uv-primary;
transform: rotate(45deg);
margin-right: $spacing-1;
flex-shrink: 0;
}
.popup-footer {
display: flex;
align-items: center;
justify-content: center;
height: 96rpx;
background: $uv-primary;
transition: opacity $transition-fast;
text {
font-size: $font-size-lg;
font-weight: $font-weight-medium;
color: #fff;
letter-spacing: 2rpx;
}
&:active {
opacity: 0.8;
}
}
</style>