feat: 首页Hero改造Banner轮播 + 公告栏优化

- Hero: 替换为运营Banner图, swiper轮播结构, 移除文字遮罩, 点击跳转卡列表
- 公告栏: 卡片化圆角阴影, 分类标签(重要/景区/福利), 按类型变色脉冲圆点,
  触摸暂停轮播, 右侧更多按钮, 修复橙色圆点被拉伸bug(.promo类名冲突)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
jacobxu666
2026-06-25 12:22:59 +08:00
co-authored by Claude
parent 31a9abda37
commit e3628369a1
+99 -65
View File
@@ -1,31 +1,47 @@
<template> <template>
<view class="page"> <view class="page">
<!-- Hero: mountain landscape photo --> <!-- Hero: 运营Banner轮播 -->
<view class="hero animate-fade-in-up"> <view class="hero animate-fade-in-up" @click="goCardDetail()">
<image <swiper class="hero-swiper" autoplay circular :interval="5000">
class="hero-bg" <swiper-item>
src="https://images.pexels.com/photos/28594448/pexels-photo-28594448.jpeg?auto=compress&cs=tinysrgb&w=750" <image
mode="aspectFill" class="hero-bg"
/> src="https://aliyunoss.hntgov.net/image/2026/04/14/d2bf03167e484ba59f7c2d62879ad07f.jpg"
<view class="hero-overlay" /> mode="aspectFill"
<view class="hero-inner"> />
<text class="hero-title">爱上萍乡</text> </swiper-item>
<text class="hero-sub">赣湘之间 · 发现旅途美好</text> </swiper>
</view>
</view> </view>
<scroll-view scroll-y class="main-scroll"> <scroll-view scroll-y class="main-scroll">
<!-- 公告栏 --> <!-- 公告栏 -->
<view v-if="announcements.length" class="notice animate-fade-in-up stagger-1"> <view v-if="announcements.length" class="notice animate-fade-in-up stagger-1">
<view class="notice-tag"> <view class="notice-tag">
<view class="notice-pip animate-pulse" /> <view class="notice-pip animate-pulse" :class="`type-${currentNoticeType}`" />
<text class="notice-label">公告</text> <text class="notice-label">公告</text>
</view> </view>
<swiper class="notice-swiper" vertical autoplay circular :interval="3000" :duration="500"> <swiper
class="notice-swiper"
vertical
:autoplay="swiperAutoplay"
circular
:interval="3000"
:duration="500"
@change="onNoticeChange"
@touchstart="onNoticeTouchStart"
@touchend="onNoticeTouchEnd"
>
<swiper-item v-for="item in announcements" :key="item.id"> <swiper-item v-for="item in announcements" :key="item.id">
<text class="notice-text">{{ item.content }}</text> <view class="notice-item">
<text class="notice-type-tag" :class="`type-${item.type}`">{{ item.typeLabel }}</text>
<text class="notice-text">{{ item.content }}</text>
</view>
</swiper-item> </swiper-item>
</swiper> </swiper>
<view class="notice-more" @click="goMoreNotice">
<text class="notice-more-text">更多</text>
<view class="chevron" />
</view>
</view> </view>
<!-- 旅游卡 — 大字报风格 --> <!-- 旅游卡 — 大字报风格 -->
@@ -102,17 +118,27 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref, computed } from 'vue'
import { scenicSpots } from '../../data/scenic-spots' import { scenicSpots } from '../../data/scenic-spots'
import type { ScenicSpotItem } from '../../data/scenic-spots' import type { ScenicSpotItem } from '../../data/scenic-spots'
/** 公告数据(mock,后续接 API) */ /** 公告数据(mock,后续接 API) */
const announcements = ref([ const announcements = ref([
{ id: 1, content: '赣湘全域旅游年卡限时优惠,工会会员专享¥300/套卡!' }, { id: 1, type: 'important', typeLabel: '重要', content: '赣湘全域旅游年卡限时优惠,工会会员专享¥300/套卡!' },
{ id: 2, content: '超级玩家卡全新上线,40家景区免门票,仅¥198!' }, { id: 2, type: 'scenic', typeLabel: '景区', content: '武功山金顶景区已开放,持卡可免费入园1次。' },
{ id: 3, content: '武功山金顶景区已开放,持卡可免费入园1次。' }, { id: 3, type: 'promo', typeLabel: '福利', content: '超级玩家卡全新上线,40家景区免门票,仅¥198!' },
]) ])
/** 公告轮播状态 */
const swiperAutoplay = ref(true)
const currentNoticeIndex = ref(0)
const currentNoticeType = computed(() => announcements.value[currentNoticeIndex.value]?.type || 'scenic')
function onNoticeChange(e: any) { currentNoticeIndex.value = e.detail.current }
function onNoticeTouchStart() { swiperAutoplay.value = false }
function onNoticeTouchEnd() { swiperAutoplay.value = true }
function goMoreNotice() { uni.showToast({ title: '全部公告开发中', icon: 'none' }) }
/** 景点山水风景图 */ /** 景点山水风景图 */
const spotImages: Record<number, string> = { const spotImages: Record<number, string> = {
1: 'https://images.unsplash.com/photo-1513415564515-763d91423bdd?fm=jpg&q=80&w=400&h=250&fit=crop', 1: 'https://images.unsplash.com/photo-1513415564515-763d91423bdd?fm=jpg&q=80&w=400&h=250&fit=crop',
@@ -152,7 +178,7 @@ function goMore() {
} }
/* ============================================ /* ============================================
Hero — mountain landscape photo Hero — 运营Banner轮播
============================================ */ ============================================ */
.hero { .hero {
position: relative; position: relative;
@@ -160,51 +186,14 @@ function goMore() {
overflow: hidden; overflow: hidden;
} }
.hero-swiper {
width: 100%;
height: 340rpx;
}
.hero-bg { .hero-bg {
position: absolute;
top: 0;
left: 0;
width: 100%; width: 100%;
height: 100%; height: 340rpx;
}
.hero-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
180deg,
rgba(0, 0, 0, 0.45) 0%,
rgba(0, 0, 0, 0.3) 40%,
rgba(0, 0, 0, 0.65) 100%
);
}
.hero-inner {
position: relative;
z-index: 2;
padding: $spacing-16 $spacing-4 $spacing-4;
display: flex;
flex-direction: column;
}
.hero-title {
font-family: $font-family-display;
font-size: $font-size-3xl;
font-weight: $font-weight-bold;
color: #fff;
line-height: $line-height-tight;
margin-bottom: $spacing-1;
text-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.4);
}
.hero-sub {
font-size: $font-size-sm;
color: rgba(255, 255, 255, 0.9);
letter-spacing: 4rpx;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
} }
/* ============================================ /* ============================================
@@ -213,9 +202,15 @@ function goMore() {
.notice-pip { .notice-pip {
width: 12rpx; width: 12rpx;
height: 12rpx; height: 12rpx;
min-height: 12rpx;
max-height: 12rpx;
border-radius: 50%; border-radius: 50%;
background: $color-accent;
flex-shrink: 0; flex-shrink: 0;
background: $color-accent;
&.type-important { background: #EF4444; }
&.type-scenic { background: #0052FF; }
&.type-promo { background: #F59E0B; }
} }
.chevron { .chevron {
@@ -251,7 +246,9 @@ function goMore() {
align-items: center; align-items: center;
background: $color-card; background: $color-card;
padding: $spacing-2 $spacing-3; padding: $spacing-2 $spacing-3;
border-bottom: $border-width solid $color-border; margin: $spacing-2 $spacing-3 0;
border-radius: 16rpx;
box-shadow: $shadow-sm;
} }
.notice-tag { .notice-tag {
@@ -260,6 +257,7 @@ function goMore() {
margin-right: $spacing-2; margin-right: $spacing-2;
flex-shrink: 0; flex-shrink: 0;
gap: $spacing-1; gap: $spacing-1;
height: 40rpx;
} }
.notice-label { .notice-label {
@@ -273,6 +271,28 @@ function goMore() {
height: 40rpx; height: 40rpx;
} }
.notice-item {
display: flex;
align-items: center;
gap: $spacing-1;
height: 40rpx;
}
.notice-type-tag {
font-size: 20rpx;
font-weight: $font-weight-medium;
padding: 0 10rpx;
border-radius: 6rpx;
flex-shrink: 0;
height: 32rpx;
line-height: 32rpx;
box-sizing: border-box;
&.type-important { color: #EF4444; background: rgba(239, 68, 68, 0.08); }
&.type-scenic { color: #0052FF; background: rgba(0, 82, 255, 0.08); }
&.type-promo { color: #F59E0B; background: rgba(245, 158, 11, 0.08); }
}
.notice-text { .notice-text {
font-size: $font-size-sm; font-size: $font-size-sm;
color: $color-muted-foreground; color: $color-muted-foreground;
@@ -281,6 +301,20 @@ function goMore() {
-webkit-line-clamp: 1; -webkit-line-clamp: 1;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
flex: 1;
}
.notice-more {
display: flex;
align-items: center;
gap: $spacing-1;
flex-shrink: 0;
margin-left: $spacing-2;
}
.notice-more-text {
font-size: $font-size-xs;
color: $color-muted-foreground;
} }
/* ============================================ /* ============================================