feat: redesign homepage with hero, announcements, card promos, scenic spots
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+202
-96
@@ -1,49 +1,85 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="header">
|
||||
<!-- 头部区域:背景图 + 渐变遮罩 + 标题 + 搜索栏 -->
|
||||
<view class="hero">
|
||||
<view class="hero-overlay" />
|
||||
<view class="hero-content">
|
||||
<text class="hero-title">爱上萍乡</text>
|
||||
<text class="hero-subtitle">赣湘之间 · 发现旅途美好</text>
|
||||
<view class="search-bar" @click="goSearch">
|
||||
<text class="search-icon">🔍</text>
|
||||
<text class="search-placeholder">搜索景区、活动...</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="scroll-content">
|
||||
<swiper class="banner-swiper" autoplay circular indicator-dots indicator-color="rgba(255,255,255,0.5)" indicator-active-color="#fff">
|
||||
<swiper-item v-for="item in banners" :key="item.id" @click="goDetail(item)">
|
||||
<image class="banner-img" :src="item.image" mode="aspectFill" />
|
||||
<!-- 公告栏 -->
|
||||
<view class="notice-bar" v-if="announcements.length">
|
||||
<view class="notice-label">
|
||||
<text class="notice-icon">📢</text>
|
||||
<text class="notice-text">公告</text>
|
||||
</view>
|
||||
<swiper class="notice-swiper" vertical autoplay circular :interval="3000" :duration="500">
|
||||
<swiper-item v-for="item in announcements" :key="item.id">
|
||||
<text class="notice-content">{{ item.content }}</text>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">热门景区</text>
|
||||
<text class="section-more" @click="goMore">查看更多 ›</text>
|
||||
</view>
|
||||
<view class="spot-list">
|
||||
<view class="spot-card" v-for="item in hotSpots" :key="item.id" @click="goSpot(item)">
|
||||
<image class="spot-img" :src="item.image" mode="aspectFill" />
|
||||
|
||||
<!-- 卡片销售宣传 -->
|
||||
<view class="card-section">
|
||||
<view class="promo-card promo-card-gx" @click="goCardDetail('gx')">
|
||||
<view class="promo-card-badge">工会会员专属</view>
|
||||
<text class="promo-card-name">赣湘全域旅游年卡</text>
|
||||
<view class="promo-card-price-row">
|
||||
<text class="promo-card-currency">¥</text>
|
||||
<text class="promo-card-price">300</text>
|
||||
<text class="promo-card-unit">/套卡(2张)</text>
|
||||
</view>
|
||||
<view class="promo-card-features">
|
||||
<text class="promo-card-feature">✦ 22家景区免门票</text>
|
||||
<text class="promo-card-feature">✦ 武功山·方特·君澜温泉</text>
|
||||
<text class="promo-card-feature">✦ 覆盖萍乡/长沙/株洲/湘潭</text>
|
||||
</view>
|
||||
<view class="promo-card-btn">
|
||||
<text class="promo-card-btn-text">立即抢购 →</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="promo-card promo-card-super" @click="goCardDetail('super')">
|
||||
<view class="promo-card-badge">大学生·市民专属</view>
|
||||
<text class="promo-card-name">超级玩家卡</text>
|
||||
<view class="promo-card-price-row">
|
||||
<text class="promo-card-currency">¥</text>
|
||||
<text class="promo-card-price">198</text>
|
||||
</view>
|
||||
<view class="promo-card-features">
|
||||
<text class="promo-card-feature">✦ 40家景区免门票</text>
|
||||
<text class="promo-card-feature">✦ 武功山·方特·韶山·滴水洞</text>
|
||||
<text class="promo-card-feature">✦ 含外骨骼登山·青年旅社等</text>
|
||||
</view>
|
||||
<view class="promo-card-btn">
|
||||
<text class="promo-card-btn-text">立即抢购 →</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 热门景点列表 -->
|
||||
<view class="spots-section">
|
||||
<view class="spots-header">
|
||||
<text class="spots-title">热门景点</text>
|
||||
<text class="spots-more">查看全部 ></text>
|
||||
</view>
|
||||
<view class="spot-card" v-for="spot in scenicSpots" :key="spot.id" @click="goSpot(spot)">
|
||||
<view class="spot-image" :style="{ background: getGradient(spot.region) }">
|
||||
<view class="spot-region-tag">{{ spot.region }}</view>
|
||||
</view>
|
||||
<view class="spot-info">
|
||||
<text class="spot-name">{{ item.name }}</text>
|
||||
<text class="spot-name">{{ spot.name }}</text>
|
||||
<view class="spot-meta">
|
||||
<text class="spot-price">¥{{ item.price }}<text class="spot-price-unit">/人</text></text>
|
||||
<text class="spot-sales">已售{{ item.sales }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">精选活动</text>
|
||||
</view>
|
||||
<view class="activity-list">
|
||||
<view class="activity-card" v-for="item in activities" :key="item.id" @click="goActivity(item)">
|
||||
<image class="activity-img" :src="item.image" mode="aspectFill" />
|
||||
<view class="activity-info">
|
||||
<text class="activity-name">{{ item.name }}</text>
|
||||
<text class="activity-desc">{{ item.desc }}</text>
|
||||
<text class="activity-price" v-if="item.price">¥{{ item.price }}起</text>
|
||||
<text class="spot-price">¥{{ spot.price }}</text>
|
||||
<view class="spot-free-tag">持卡免门票</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -53,81 +89,151 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useUserStore } from '../../store/user'
|
||||
import { ref } from 'vue'
|
||||
import { scenicSpots } from '../../data/scenic-spots'
|
||||
import type { ScenicSpotItem } from '../../data/scenic-spots'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
/** 轮播图数据 */
|
||||
const banners = ref([
|
||||
{ id: 1, image: 'https://picsum.photos/750/340?random=1', link: '' },
|
||||
{ id: 2, image: 'https://picsum.photos/750/340?random=2', link: '' }
|
||||
/** 公告数据(mock,后续接 API) */
|
||||
const announcements = ref([
|
||||
{ id: 1, content: '赣湘全域旅游年卡限时优惠,工会会员专享¥300/套卡!' },
|
||||
{ id: 2, content: '超级玩家卡全新上线,40家景区免门票,仅¥198!' },
|
||||
{ id: 3, content: '武功山金顶景区已开放,持卡可免费入园1次。' },
|
||||
])
|
||||
|
||||
/** 热门景区 */
|
||||
const hotSpots = ref([
|
||||
{ id: 1, name: '张家界国家森林公园', image: 'https://picsum.photos/200/200?random=3', price: 225, sales: 12580 },
|
||||
{ id: 2, name: '天门山景区', image: 'https://picsum.photos/200/200?random=4', price: 278, sales: 8960 },
|
||||
{ id: 3, name: '凤凰古城', image: 'https://picsum.photos/200/200?random=5', price: 0, sales: 25600 },
|
||||
{ id: 4, name: '岳麓山景区', image: 'https://picsum.photos/200/200?random=6', price: 0, sales: 18200 }
|
||||
])
|
||||
/** 区域对应的渐变色(景点图片占位) */
|
||||
const regionGradients: Record<string, string> = {
|
||||
'萍乡': 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
'长沙': 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
|
||||
'株洲': 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
|
||||
'湘潭': 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)',
|
||||
}
|
||||
|
||||
/** 精选活动 */
|
||||
const activities = ref([
|
||||
{ id: 1, name: '周末亲子游', desc: '探索自然,快乐成长', image: 'https://picsum.photos/300/200?random=7', price: 199 },
|
||||
{ id: 2, name: '夜游湘江', desc: '璀璨夜景,浪漫之旅', image: 'https://picsum.photos/300/200?random=8', price: 128 }
|
||||
])
|
||||
function getGradient(region: string): string {
|
||||
return regionGradients[region] || regionGradients['萍乡']
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 未登录时跳转登录页
|
||||
if (!userStore.isLogin) {
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
}
|
||||
})
|
||||
/** 跳转搜索(暂不实现) */
|
||||
function goSearch() {
|
||||
uni.showToast({ title: '搜索功能开发中', icon: 'none' })
|
||||
}
|
||||
|
||||
/** 跳转搜索 */
|
||||
function goSearch() { uni.navigateTo({ url: '/pages/search/search' }) }
|
||||
/** 跳转卡片详情 */
|
||||
function goCardDetail(type: 'gx' | 'super') {
|
||||
uni.navigateTo({ url: '/packages/user/pages/card/list' })
|
||||
}
|
||||
|
||||
/** 跳转详情 */
|
||||
function goDetail(item: any) { /* TODO */ }
|
||||
|
||||
/** 跳转景区详情 */
|
||||
function goSpot(item: any) { uni.navigateTo({ url: `/pages/spot/detail?id=${item.id}` }) }
|
||||
|
||||
/** 查看更多 */
|
||||
function goMore() { uni.navigateTo({ url: '/pages/spot/list' }) }
|
||||
|
||||
/** 跳转活动 */
|
||||
function goActivity(item: any) { /* TODO */ }
|
||||
/** 跳转景点详情 */
|
||||
function goSpot(spot: ScenicSpotItem) {
|
||||
uni.showToast({ title: `${spot.name} 详情开发中`, icon: 'none' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { display: flex; flex-direction: column; height: 100vh; background: #f5f5f5; }
|
||||
.header { background: linear-gradient(135deg, #4A90D9, #357ABD); padding: 20rpx 30rpx; padding-top: 80rpx; }
|
||||
.search-bar { display: flex; align-items: center; background: rgba(255,255,255,0.9); border-radius: 40rpx; padding: 16rpx 24rpx; }
|
||||
.page { display: flex; flex-direction: column; min-height: 100vh; background: #f5f5f5; }
|
||||
|
||||
/* === 头部区域 === */
|
||||
.hero { position: relative; height: 360rpx; overflow: hidden; }
|
||||
.hero-overlay {
|
||||
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: linear-gradient(180deg, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0.6) 100%);
|
||||
z-index: 1;
|
||||
}
|
||||
.hero::before {
|
||||
content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: linear-gradient(135deg, #1a237e 0%, #4a148c 50%, #e65100 100%);
|
||||
}
|
||||
.hero-content {
|
||||
position: relative; z-index: 2;
|
||||
padding: 80rpx 30rpx 30rpx;
|
||||
display: flex; flex-direction: column;
|
||||
}
|
||||
.hero-title { font-size: 42rpx; font-weight: bold; color: #fff; margin-bottom: 8rpx; }
|
||||
.hero-subtitle { font-size: 24rpx; color: rgba(255,255,255,0.8); margin-bottom: 24rpx; }
|
||||
.search-bar {
|
||||
display: flex; align-items: center;
|
||||
background: rgba(255,255,255,0.95); border-radius: 40rpx;
|
||||
padding: 18rpx 24rpx;
|
||||
}
|
||||
.search-icon { font-size: 28rpx; margin-right: 12rpx; }
|
||||
.search-placeholder { font-size: 26rpx; color: #999; }
|
||||
|
||||
/* === 公告栏 === */
|
||||
.notice-bar {
|
||||
display: flex; align-items: center;
|
||||
background: #fff; padding: 20rpx 24rpx; margin: 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.notice-label { display: flex; align-items: center; margin-right: 16rpx; flex-shrink: 0; }
|
||||
.notice-icon { font-size: 28rpx; margin-right: 6rpx; }
|
||||
.notice-text { font-size: 24rpx; color: #E74C3C; font-weight: 500; }
|
||||
.notice-swiper { flex: 1; height: 40rpx; }
|
||||
.notice-content { font-size: 24rpx; color: #666; line-height: 40rpx; }
|
||||
|
||||
/* === 卡片销售宣传 === */
|
||||
.card-section { padding: 24rpx; display: flex; flex-direction: column; gap: 24rpx; }
|
||||
|
||||
.promo-card {
|
||||
position: relative; border-radius: 24rpx; padding: 40rpx 32rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.promo-card-gx {
|
||||
background: linear-gradient(135deg, #1a237e 0%, #283593 40%, #ffd54f 100%);
|
||||
}
|
||||
.promo-card-super {
|
||||
background: linear-gradient(135deg, #e65100 0%, #bf360c 40%, #7b1fa2 100%);
|
||||
}
|
||||
.promo-card-badge {
|
||||
display: inline-block;
|
||||
background: rgba(255,255,255,0.2); border-radius: 20rpx;
|
||||
padding: 6rpx 16rpx; margin-bottom: 16rpx;
|
||||
font-size: 22rpx; color: rgba(255,255,255,0.9);
|
||||
}
|
||||
.promo-card-name { font-size: 34rpx; font-weight: bold; color: #fff; display: block; margin-bottom: 12rpx; }
|
||||
.promo-card-price-row { display: flex; align-items: baseline; margin-bottom: 20rpx; }
|
||||
.promo-card-currency { font-size: 32rpx; color: #fff; font-weight: bold; }
|
||||
.promo-card-price { font-size: 64rpx; color: #fff; font-weight: bold; line-height: 1; }
|
||||
.promo-card-unit { font-size: 24rpx; color: rgba(255,255,255,0.8); margin-left: 8rpx; }
|
||||
.promo-card-features { display: flex; flex-direction: column; gap: 8rpx; margin-bottom: 24rpx; }
|
||||
.promo-card-feature { font-size: 24rpx; color: rgba(255,255,255,0.9); }
|
||||
.promo-card-btn {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: #fff; border-radius: 40rpx;
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
.promo-card-btn-text { font-size: 28rpx; font-weight: bold; color: #333; }
|
||||
|
||||
/* === 热门景点列表 === */
|
||||
.spots-section { padding: 24rpx; }
|
||||
.spots-header {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.spots-title { font-size: 34rpx; font-weight: bold; color: #333; }
|
||||
.spots-more { font-size: 24rpx; color: #999; }
|
||||
|
||||
.spot-card {
|
||||
background: #fff; border-radius: 20rpx; overflow: hidden;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.spot-image {
|
||||
width: 100%; height: 320rpx; position: relative;
|
||||
}
|
||||
.spot-region-tag {
|
||||
position: absolute; top: 16rpx; left: 16rpx;
|
||||
background: rgba(0,0,0,0.5); border-radius: 16rpx;
|
||||
padding: 4rpx 16rpx;
|
||||
font-size: 22rpx; color: #fff;
|
||||
}
|
||||
.spot-info { padding: 20rpx 24rpx; }
|
||||
.spot-name { font-size: 30rpx; font-weight: 600; color: #333; margin-bottom: 12rpx; display: block; }
|
||||
.spot-meta { display: flex; justify-content: space-between; align-items: center; }
|
||||
.spot-price { font-size: 32rpx; color: #E74C3C; font-weight: bold; }
|
||||
.spot-free-tag {
|
||||
background: #E8F5E9; border-radius: 16rpx;
|
||||
padding: 4rpx 16rpx;
|
||||
font-size: 22rpx; color: #2E7D32; font-weight: 500;
|
||||
}
|
||||
|
||||
/* === 滚动区域 === */
|
||||
.scroll-content { flex: 1; }
|
||||
.banner-swiper { height: 340rpx; }
|
||||
.banner-img { width: 100%; height: 100%; }
|
||||
.section { padding: 20rpx; }
|
||||
.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20rpx; }
|
||||
.section-title { font-size: 32rpx; font-weight: bold; color: #333; }
|
||||
.section-more { font-size: 24rpx; color: #999; }
|
||||
.spot-list { display: flex; flex-wrap: wrap; gap: 16rpx; }
|
||||
.spot-card { width: calc(50% - 8rpx); background: #fff; border-radius: 16rpx; overflow: hidden; }
|
||||
.spot-img { width: 100%; height: 200rpx; }
|
||||
.spot-info { padding: 16rpx; }
|
||||
.spot-name { font-size: 26rpx; font-weight: 500; color: #333; display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.spot-meta { display: flex; justify-content: space-between; align-items: baseline; margin-top: 8rpx; }
|
||||
.spot-price { font-size: 30rpx; color: #E74C3C; font-weight: bold; }
|
||||
.spot-price-unit { font-size: 20rpx; font-weight: normal; }
|
||||
.spot-sales { font-size: 22rpx; color: #999; }
|
||||
.activity-list { display: flex; flex-direction: column; gap: 16rpx; }
|
||||
.activity-card { display: flex; background: #fff; border-radius: 16rpx; overflow: hidden; }
|
||||
.activity-img { width: 240rpx; height: 160rpx; flex-shrink: 0; }
|
||||
.activity-info { flex: 1; padding: 16rpx; display: flex; flex-direction: column; justify-content: center; }
|
||||
.activity-name { font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 8rpx; }
|
||||
.activity-desc { font-size: 24rpx; color: #999; margin-bottom: 8rpx; }
|
||||
.activity-price { font-size: 28rpx; color: #E74C3C; font-weight: bold; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user