feat: 新增景区列表页和景区详情页

- 新增 pages/scenic-list/scenic-list.vue 全部合作景区列表页
  - 动态导航栏适配胶囊按钮高度
  - 搜索框支持实时模糊检索
  - uni-popup 底部弹窗筛选(地区/等级),支持多条件叠加
  - 单列卡片布局:封面图+等级标签、名称+挂牌价、营业状态+时间、权益标签行、温馨提示
  - 42个景区完整数据内置
- 新增 pages/scenic-detail/scenic-detail.vue 景区详情页
- 首页「查看全部」跳转至景区列表页
- pages.json 注册两个新页面
This commit is contained in:
jacobxu666
2026-06-25 15:50:20 +08:00
parent aa9f024094
commit 78cb3e00bd
4 changed files with 1635 additions and 1 deletions
+14
View File
@@ -29,6 +29,20 @@
"style": {
"navigationBarTitleText": "个人中心"
}
},
{
"path": "pages/scenic-list/scenic-list",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": ""
}
},
{
"path": "pages/scenic-detail/scenic-detail",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": ""
}
}
],
"subPackages": [
+1 -1
View File
@@ -184,7 +184,7 @@ function goSpot(spot: ScenicSpotItem) {
}
function goMore() {
uni.showToast({ title: '景点列表开发中', icon: 'none' })
uni.navigateTo({ url: '/pages/scenic-list/scenic-list' })
}
</script>
+632
View File
@@ -0,0 +1,632 @@
<template>
<view class="page">
<!-- 自定义导航栏 -->
<view class="nav-bar">
<view class="nav-back" @click="goBack">
<view class="back-arrow" />
</view>
<text class="nav-title">{{ scenic?.name || '景区详情' }}</text>
<view class="nav-placeholder" />
</view>
<!-- 内容区 -->
<scroll-view scroll-y class="detail-scroll" v-if="scenic">
<!-- 封面图 -->
<view class="cover-section">
<image class="cover-img" :src="scenic.image" mode="aspectFill" />
<view class="region-tag">
<view class="tag-dot" />
<text>萍乡</text>
</view>
</view>
<!-- 信息卡片 -->
<view class="info-card">
<!-- 标题行 -->
<view class="title-row">
<text class="scenic-name">{{ scenic.name }}</text>
<view v-if="scenic.level" class="level-tag" :class="`level-${scenic.level}`">
<text>{{ scenic.level }}</text>
</view>
</view>
<!-- 营业状态 -->
<view class="info-row">
<view class="status-icon" :class="scenic.status === '正常营业' ? 'open' : 'closed'" />
<text class="status-text" :class="scenic.status === '正常营业' ? 'open' : 'closed'">
{{ scenic.status }}
</text>
</view>
<!-- 营业时间 -->
<view class="info-row">
<view class="time-icon" />
<text class="time-text">{{ scenic.hours }}</text>
</view>
<!-- 价格区域 -->
<view class="price-section">
<view class="price-label">挂牌价</view>
<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="benefit-section">
<view class="benefit-card">
<text class="benefit-title">持卡权益</text>
<text class="benefit-desc">
{{ scenic.freeCount > 0 ? `全年免${scenic.freeCount}次首道门票` : '免费入园' }}
</text>
</view>
<view class="benefit-card" :class="scenic.needReservation ? 'warning' : 'success'">
<text class="benefit-title">预约要求</text>
<text class="benefit-desc">{{ scenic.needReservation ? '需提前预约' : '无需预约' }}</text>
</view>
</view>
<!-- 温馨提示 -->
<view v-if="scenic.tips" class="tips-section">
<view class="section-title">温馨提示</view>
<view class="tips-content">
<view class="tips-icon">!</view>
<text class="tips-text">{{ scenic.tips }}</text>
</view>
</view>
<!-- 景区类型 -->
<view class="type-section">
<view class="section-title">景区类型</view>
<view class="type-tag">
<text>{{ scenic.type }}</text>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="action-btn primary" @click="onBookClick">
<text>立即预约</text>
</view>
<view class="action-btn secondary" @click="onCallClick">
<text>咨询客服</text>
</view>
</view>
</scroll-view>
<!-- 加载中 -->
<view v-else class="loading-state">
<view class="loading-spinner" />
<text class="loading-text">加载中...</text>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
interface ScenicItem {
id: number
name: string
price: number
freeCount: number
level: '5A' | '4A' | '3A' | '2A' | 'A' | ''
status: '正常营业' | '暂停营业'
hours: string
needReservation: boolean
tips: string
type: string
image: string
}
const scenicList: ScenicItem[] = [
{ id: 1, name: '武功山金顶景区', price: 70, freeCount: 1, level: '5A', status: '正常营业', hours: '08:00-18:00', needReservation: true, tips: '建议提前1天预约,山顶温差大请备好外套', type: '自然风光', image: 'https://images.unsplash.com/photo-1513415564515-763d91423bdd?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 2, name: '杨岐山孽龙洞景区', price: 68, freeCount: 6, level: '4A', status: '正常营业', hours: '08:30-17:30', needReservation: false, tips: '洞内温度恒定18℃,建议携带薄外套', type: '自然风光', image: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 3, name: '仙凤三宝景区', price: 50, freeCount: 6, level: '4A', status: '正常营业', hours: '09:00-17:00', needReservation: false, tips: '适合亲子游玩,园内有小动物喂养体验', type: '乡村田园', image: 'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 4, name: '武功山君澜温泉', price: 228, freeCount: 6, level: '4A', status: '正常营业', hours: '10:00-22:00', needReservation: true, tips: '需提前预约,请自备泳衣泳帽', type: '休闲娱乐', image: 'https://images.unsplash.com/photo-1433086966358-54859d0ed716?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 5, name: '凯光新天地生态旅游区', price: 90, freeCount: 6, level: '4A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '园区较大,建议穿舒适运动鞋', type: '休闲娱乐', image: 'https://images.unsplash.com/photo-1441974231531-c6227db76b6e?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 6, name: '和平农场·哎马乐园', price: 79, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-17:30', needReservation: false, tips: '亲子乐园,周末人流较多建议错峰', type: '乡村田园', image: 'https://images.unsplash.com/photo-1501854140801-50d01698950b?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 7, name: '武功山滑草场', price: 60, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-17:00', needReservation: false, tips: '雨天暂停开放,请关注天气', type: '休闲娱乐', image: 'https://images.unsplash.com/photo-1551632811-561732d1e306?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 8, name: '芦溪仙凤三宝农业休闲观光园', price: 50, freeCount: 6, level: '3A', status: '正常营业', hours: '08:30-17:30', needReservation: false, tips: '可体验采摘,季节性水果品种丰富', type: '乡村田园', image: 'https://images.unsplash.com/photo-1500382017468-9049fed747ef?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 9, name: '麻山幸福村', price: 40, freeCount: 6, level: '3A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '乡村旅游景区,可体验农家生活', type: '乡村田园', image: 'https://images.unsplash.com/photo-1523712999610-f77fbcfc3843?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 10, name: '东桥双月湾', price: 38, freeCount: 6, level: '3A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '临水景区,请注意安全', type: '自然风光', image: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 11, name: '上栗花炮文化博览园', price: 45, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-17:00', needReservation: false, tips: '了解花炮历史文化,适合研学', type: '文化古迹', image: 'https://images.unsplash.com/photo-1533174072545-7a4b6ad7a6c3?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 12, name: '杨岐普通寺', price: 30, freeCount: 6, level: '3A', status: '正常营业', hours: '08:00-17:00', needReservation: false, tips: '佛教圣地,请保持安静', type: '文化古迹', image: 'https://images.unsplash.com/photo-1518241353330-0f7941c2d9b5?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 13, name: '孽龙洞漂流', price: 88, freeCount: 6, level: '3A', status: '暂停营业', hours: '暂停开放', needReservation: false, tips: '季节性项目,预计6月开放', type: '休闲娱乐', image: 'https://images.unsplash.com/photo-1504280390367-361c6d9f38f4?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 14, name: '安源路矿工人运动纪念馆', price: 0, freeCount: 0, level: '4A', status: '正常营业', hours: '09:00-16:30', needReservation: false, tips: '红色教育基地,周一闭馆', type: '红色教育', image: 'https://images.unsplash.com/photo-1565008576549-57569a49371d?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 15, name: '莲花荷博园', price: 35, freeCount: 6, level: '3A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '夏季荷花盛开时最佳观赏期', type: '自然风光', image: 'https://images.unsplash.com/photo-1490750967868-88aa4486c946?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 16, name: '山口岩水利风景区', price: 25, freeCount: 6, level: '3A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '水利工程景观,可了解水利知识', type: '自然风光', image: 'https://images.unsplash.com/photo-1439066615861-d1af74d74000?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 17, name: '宣风三宝水上乐园', price: 65, freeCount: 6, level: '3A', status: '暂停营业', hours: '暂停开放', needReservation: false, tips: '夏季开放,请关注开园公告', type: '休闲娱乐', image: 'https://images.unsplash.com/photo-1519750013994-3d27cd3f7f49?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 18, name: '万龙山温泉', price: 168, freeCount: 6, level: '4A', status: '正常营业', hours: '10:00-22:00', needReservation: true, tips: '需提前预约,温泉养生好去处', type: '休闲娱乐', image: 'https://images.unsplash.com/photo-1545569341-9eb8b30979d9?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 19, name: '杨岐山野生动物科普园', price: 42, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-17:00', needReservation: false, tips: '适合亲子科普教育', type: '乡村田园', image: 'https://images.unsplash.com/photo-1474511320723-9a56873571b7?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 20, name: '鸡冠山森林步道', price: 0, freeCount: 0, level: '3A', status: '正常营业', hours: '全天开放', needReservation: false, tips: '森林氧吧,适合徒步健身', type: '自然风光', image: 'https://images.unsplash.com/photo-1551632811-561732d1e306?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 21, name: '赤山杨岐山民宿集群配套景区', price: 28, freeCount: 6, level: '2A', status: '正常营业', hours: '08:00-20:00', needReservation: false, tips: '可体验民宿住宿,感受乡村风情', type: '乡村田园', image: 'https://images.unsplash.com/photo-1510798831971-661eb04b3739?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 22, name: '湘东傩文化园', price: 32, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-17:00', needReservation: false, tips: '了解傩文化,感受非遗魅力', type: '文化古迹', image: 'https://images.unsplash.com/photo-1533174072545-7a4b6ad7a6c3?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 23, name: '广寒寨四季花海', price: 36, freeCount: 6, level: '3A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '四季花卉轮番绽放,拍照打卡好去处', type: '自然风光', image: 'https://images.unsplash.com/photo-1490750967868-88aa4486c946?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 24, name: '上栗泉水湾度假村', price: 58, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-18:00', needReservation: false, tips: '休闲度假好去处,可垂钓烧烤', type: '休闲娱乐', image: 'https://images.unsplash.com/photo-1571896349842-33c89424de2d?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 25, name: '莲花棋盘山', price: 22, freeCount: 6, level: '2A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '登山步道完善,适合休闲健身', type: '自然风光', image: 'https://images.unsplash.com/photo-1444464666168-49d633b86797?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 26, name: '芦溪东阳古村', price: 26, freeCount: 6, level: '3A', status: '正常营业', hours: '08:30-17:30', needReservation: false, tips: '古建筑群保存完好,可感受传统村落文化', type: '文化古迹', image: 'https://images.unsplash.com/photo-1528181304800-259b08848526?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 27, name: '安源欢乐谷', price: 72, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-18:00', needReservation: false, tips: '游乐设施丰富,适合亲子游玩', type: '休闲娱乐', image: 'https://images.unsplash.com/photo-1513407030348-c9836fb1966d?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 28, name: '湘东太阳广场游乐区', price: 48, freeCount: 6, level: '2A', status: '正常营业', hours: '09:00-21:00', needReservation: false, tips: '夜间灯光秀值得一看', type: '休闲娱乐', image: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 29, name: '长平养生谷', price: 52, freeCount: 6, level: '3A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '养生休闲好去处,空气清新', type: '自然风光', image: 'https://images.unsplash.com/photo-1518241353330-0f7941c2d9b5?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 30, name: '坊楼红色文化园', price: 0, freeCount: 0, level: '3A', status: '正常营业', hours: '09:00-17:00', needReservation: false, tips: '红色教育基地,免费参观', type: '红色教育', image: 'https://images.unsplash.com/photo-1565008576549-57569a49371d?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 31, name: '高坑生态采摘园', price: 33, freeCount: 6, level: '2A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '季节性水果采摘,体验田园乐趣', type: '乡村田园', image: 'https://images.unsplash.com/photo-1500382017468-9049fed747ef?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 32, name: '源南田园综合体', price: 39, freeCount: 6, level: '3A', status: '正常营业', hours: '08:30-17:30', needReservation: false, tips: '田园风光优美,可体验农耕文化', type: '乡村田园', image: 'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 33, name: '下埠陶瓷文化小镇', price: 29, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-17:00', needReservation: false, tips: '可体验陶瓷制作,了解陶瓷文化', type: '文化古迹', image: 'https://images.unsplash.com/photo-1528181304800-259b08848526?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 34, name: '桐木水上乐园', price: 66, freeCount: 6, level: '3A', status: '暂停营业', hours: '暂停开放', needReservation: false, tips: '夏季开放,请关注开园时间', type: '休闲娱乐', image: 'https://images.unsplash.com/photo-1519750013994-3d27cd3f7f49?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 35, name: '东源温泉度假村', price: 138, freeCount: 6, level: '4A', status: '正常营业', hours: '10:00-22:00', needReservation: true, tips: '需提前预约,温泉品质优良', type: '休闲娱乐', image: 'https://images.unsplash.com/photo-1545569341-9eb8b30979d9?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 36, name: '长丰山水景区', price: 24, freeCount: 6, level: '2A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '山水相依,适合休闲散步', type: '自然风光', image: 'https://images.unsplash.com/photo-1439066615861-d1af74d74000?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 37, name: '白竺青草湖', price: 27, freeCount: 6, level: '3A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '湖光山色,可垂钓泛舟', type: '自然风光', image: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 38, name: '荷尧古渡口', price: 0, freeCount: 0, level: '2A', status: '正常营业', hours: '全天开放', needReservation: false, tips: '古渡口遗址,感受历史文化', type: '文化古迹', image: 'https://images.unsplash.com/photo-1504280390367-361c6d9f38f4?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 39, name: '新泉红色研学基地', price: 31, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-17:00', needReservation: true, tips: '研学活动需提前预约', type: '红色教育', image: 'https://images.unsplash.com/photo-1565008576549-57569a49371d?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 40, name: '张佳坊山地乐园', price: 55, freeCount: 6, level: '3A', status: '正常营业', hours: '09:00-17:30', needReservation: false, tips: '山地运动项目丰富,注意安全', type: '休闲娱乐', image: 'https://images.unsplash.com/photo-1551632811-561732d1e306?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 41, name: '南坑户外拓展基地', price: 44, freeCount: 6, level: '3A', status: '正常营业', hours: '08:30-18:00', needReservation: true, tips: '团队拓展需提前预约', type: '休闲娱乐', image: 'https://images.unsplash.com/photo-1501854140801-50d01698950b?fm=jpg&q=80&w=1200&h=600&fit=crop' },
{ id: 42, name: '老关荷花景区', price: 34, freeCount: 6, level: '2A', status: '正常营业', hours: '08:00-18:00', needReservation: false, tips: '夏季荷花盛开,景色宜人', type: '自然风光', image: 'https://images.unsplash.com/photo-1490750967868-88aa4486c946?fm=jpg&q=80&w=1200&h=600&fit=crop' },
]
const scenic = ref<ScenicItem | null>(null)
onLoad((options) => {
const id = parseInt(options?.id || '0')
scenic.value = scenicList.find(s => s.id === id) || null
})
function goBack() {
uni.navigateBack()
}
function onBookClick() {
uni.showToast({
title: '预约功能开发中',
icon: 'none',
})
}
function onCallClick() {
uni.showToast({
title: '客服功能开发中',
icon: 'none',
})
}
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: $color-background;
display: flex;
flex-direction: column;
}
/* 导航栏 */
.nav-bar {
height: 88rpx;
background: $color-card;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 $spacing-3;
box-shadow: $shadow-sm;
position: sticky;
top: 0;
z-index: 10;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: $radius-base;
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 {
font-size: $font-size-lg;
font-weight: $font-weight-semibold;
color: $color-foreground;
}
.nav-placeholder {
width: 60rpx;
}
/* 滚动区域 */
.detail-scroll {
flex: 1;
}
/* 封面图 */
.cover-section {
position: relative;
width: 100%;
height: 480rpx;
background: $color-muted;
}
.cover-img {
width: 100%;
height: 100%;
}
.region-tag {
position: absolute;
top: $spacing-3;
left: $spacing-3;
display: flex;
align-items: center;
gap: 8rpx;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(8rpx);
border-radius: $radius-sm;
padding: 8rpx $spacing-2;
text {
font-size: $font-size-sm;
color: #fff;
}
}
.tag-dot {
width: 10rpx;
height: 10rpx;
border-radius: 50%;
background: #fff;
}
/* 信息卡片 */
.info-card {
background: $color-card;
margin: -60rpx $spacing-3 $spacing-3;
padding: $spacing-3;
border-radius: $radius-lg;
box-shadow: $shadow-base;
position: relative;
z-index: 5;
}
/* 标题行 */
.title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: $spacing-3;
}
.scenic-name {
font-size: $font-size-xl;
font-weight: $font-weight-bold;
color: $color-foreground;
flex: 1;
margin-right: $spacing-2;
}
.level-tag {
padding: 6rpx $spacing-2;
border-radius: $radius-sm;
flex-shrink: 0;
text {
font-size: $font-size-sm;
color: #fff;
font-weight: $font-weight-semibold;
}
&.level-5A {
background: #EF4444;
}
&.level-4A {
background: #F59E0B;
}
&.level-3A,
&.level-2A,
&.level-A {
background: $uv-success;
}
}
/* 信息行 */
.info-row {
display: flex;
align-items: center;
margin-bottom: $spacing-2;
}
.status-icon {
width: 20rpx;
height: 20rpx;
border-radius: 50%;
margin-right: $spacing-2;
&.open {
background: $uv-success;
}
&.closed {
background: $uv-error;
}
}
.status-text {
font-size: $font-size-base;
font-weight: $font-weight-medium;
&.open {
color: $uv-success;
}
&.closed {
color: $uv-error;
}
}
.time-icon {
width: 24rpx;
height: 24rpx;
border: 3rpx solid $uv-tips-color;
border-radius: 50%;
margin-right: $spacing-2;
position: relative;
&::before {
content: '';
position: absolute;
width: 3rpx;
height: 10rpx;
background: $uv-tips-color;
top: 5rpx;
left: 50%;
transform: translateX(-50%);
}
&::after {
content: '';
position: absolute;
width: 8rpx;
height: 3rpx;
background: $uv-tips-color;
top: 50%;
left: 50%;
transform: translateY(-50%);
}
}
.time-text {
font-size: $font-size-base;
color: $uv-content-color;
}
/* 价格区域 */
.price-section {
background: $uv-warning-light;
border-radius: $radius-base;
padding: $spacing-3;
margin-bottom: $spacing-3;
}
.price-label {
font-size: $font-size-sm;
color: $uv-content-color;
margin-bottom: $spacing-1;
}
.price-group {
display: flex;
align-items: baseline;
}
.price-symbol {
font-size: $font-size-lg;
color: $uv-warning-dark;
font-weight: $font-weight-bold;
}
.price-value {
font-size: $font-size-3xl;
color: $uv-warning-dark;
font-weight: $font-weight-bold;
line-height: 1;
}
.price-free {
font-size: $font-size-xl;
color: $uv-success;
font-weight: $font-weight-bold;
}
/* 权益信息 */
.benefit-section {
display: flex;
gap: $spacing-2;
margin-bottom: $spacing-3;
}
.benefit-card {
flex: 1;
background: $uv-primary-light;
border-radius: $radius-base;
padding: $spacing-2;
&.warning {
background: $uv-warning-light;
}
&.success {
background: $uv-success-light;
}
}
.benefit-title {
font-size: $font-size-sm;
color: $uv-content-color;
margin-bottom: $spacing-1;
display: block;
}
.benefit-desc {
font-size: $font-size-base;
color: $color-foreground;
font-weight: $font-weight-semibold;
display: block;
}
/* 分区标题 */
.section-title {
font-size: $font-size-base;
font-weight: $font-weight-semibold;
color: $color-foreground;
margin-bottom: $spacing-2;
}
/* 温馨提示 */
.tips-section {
margin-bottom: $spacing-3;
}
.tips-content {
display: flex;
align-items: flex-start;
background: rgba(249, 174, 61, 0.08);
border-radius: $radius-base;
padding: $spacing-2;
}
.tips-icon {
width: 36rpx;
height: 36rpx;
background: $uv-warning;
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: $font-size-base;
font-weight: $font-weight-bold;
margin-right: $spacing-2;
flex-shrink: 0;
}
.tips-text {
font-size: $font-size-base;
color: $uv-content-color;
line-height: 1.6;
}
/* 景区类型 */
.type-section {
margin-bottom: 120rpx;
}
.type-tag {
display: inline-flex;
padding: $spacing-1 $spacing-3;
background: $color-muted;
border-radius: $radius-full;
text {
font-size: $font-size-base;
color: $uv-content-color;
}
}
/* 底部操作栏 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: $color-card;
padding: $spacing-3;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
display: flex;
gap: $spacing-2;
z-index: 20;
}
.action-btn {
flex: 1;
height: 88rpx;
border-radius: $radius-full;
display: flex;
align-items: center;
justify-content: center;
transition: transform $transition-fast;
text {
font-size: $font-size-base;
font-weight: $font-weight-semibold;
}
&:active {
transform: scale(0.98);
}
&.primary {
background: $uv-primary;
text {
color: #fff;
}
}
&.secondary {
background: $color-muted;
text {
color: $uv-content-color;
}
}
}
/* 加载中 */
.loading-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.loading-spinner {
width: 64rpx;
height: 64rpx;
border: 6rpx solid $color-muted;
border-top-color: $uv-primary;
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin-bottom: $spacing-3;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.loading-text {
font-size: $font-size-base;
color: $uv-tips-color;
}
</style>
+988
View File
@@ -0,0 +1,988 @@
<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>
</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
}
// ========== 导航栏动态高度 ==========
const statusBarHeight = ref(20)
const navContentHeight = ref(44)
const navTotalHeight = ref(64)
const stickyHeaderHeight = ref(96) // 预估,onMounted 后精确测量
// 页面加载时获取系统信息
;(function initNavBar() {
try {
const sysInfo = uni.getSystemInfoSync()
statusBarHeight.value = sysInfo.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(() => {
const query = uni.createSelectorQuery().in(getCurrentInstance())
query.select('.sticky-header').boundingClientRect((rect: any) => {
if (rect) stickyHeaderHeight.value = Math.ceil(rect.height)
}).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-1519750013994-3d27cd3f7f49?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-1474511320723-9a56873571b7?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-1513407030348-c9836fb1966d?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-1519750013994-3d27cd3f7f49?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' },
]
// ========== 搜索筛选 ==========
const searchText = ref('')
const selectedRegion = ref('')
const selectedLevel = ref('')
// uni-popup 单实例引用
const filterPopup = ref()
// 当前弹窗类型 & 临时选中值
const activePickerType = ref<'region' | 'level'>('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 popupTitle = computed(() =>
activePickerType.value === 'region' ? '选择地区' : '选择等级'
)
const currentOptions = computed(() =>
activePickerType.value === 'region' ? regionOptions : levelOptions
)
function openPicker(type: 'region' | 'level') {
activePickerType.value = type
tempValue.value = type === 'region' ? selectedRegion.value : selectedLevel.value
filterPopup.value?.open()
}
function onOptionTap(value: string) {
tempValue.value = value
}
function confirmFilter() {
if (activePickerType.value === 'region') {
selectedRegion.value = tempValue.value
} else {
selectedLevel.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
}
return true
})
})
function goBack() {
uni.navigateBack()
}
function onCardClick(scenic: ScenicItem) {
uni.navigateTo({
url: `/pages/scenic-detail/scenic-detail?id=${scenic.id}`,
})
}
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: $color-background;
position: relative;
}
/* ====== 固定导航栏 ====== */
.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-semibold;
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: #fff;
}
/* 等级标签 — 图片右上角 */
.level-tag {
position: absolute;
top: $spacing-2;
right: $spacing-2;
padding: 6rpx $spacing-2;
border-radius: $radius-sm;
text {
font-size: $font-size-xs;
color: #fff;
font-weight: $font-weight-semibold;
}
&.level-5A { background: #EF4444; }
&.level-4A { background: #F59E0B; }
&.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-semibold;
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-warning;
font-weight: $font-weight-semibold;
}
.price-value {
font-size: $font-size-xl;
color: $uv-warning;
font-weight: $font-weight-bold;
}
.price-free {
font-size: $font-size-base;
color: $uv-success;
font-weight: $font-weight-semibold;
}
/* 营业时间 + 营业状态 同行 */
.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 {
padding: 4rpx $spacing-2;
background: rgba($uv-success, 0.1);
border-radius: $radius-sm;
text {
font-size: $font-size-xs;
color: $uv-success;
font-weight: $font-weight-medium;
white-space: nowrap;
}
}
.benefit-tag {
padding: 4rpx $spacing-2;
border-radius: $radius-sm;
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-semibold;
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-semibold;
}
}
.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-semibold;
color: #fff;
letter-spacing: 2rpx;
}
&:active {
opacity: 0.8;
}
}
</style>