1033 lines
37 KiB
Vue
1033 lines
37 KiB
Vue
<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>
|