feat: UI redesign with uv-ui color system and landscape imagery

- Replace all colors with uv-ui design tokens (primary #3c9cff, warning #f9ae3d, etc.)
- Hero section: Wugong Mountain landscape photo with dark overlay
- Super Player card: photo background, large typography, warning-color price/CTA, value comparison (¥198 vs ¥3800+)
- Spots section: landscape thumbnails instead of CSS gradients, warning-color prices
- Profile page: landscape hero header, CSS menu icons, floating card layout
- Card list page: landscape hero header, filter pills, empty state design
- TabBar: rename '入园码' to '旅游卡', Lucide-style PNG icons (home/card/profile)
- Typography: corrected font weights (semibold for prices, medium for labels)
- Remove broken font-loader (lxgw-wen-kai CDN 404)
- Skip wx.getPrivacySetting in devtools to avoid framework timeout
- Replace all inline SVG with CSS-only icons for mini-program compatibility
- Add design system files (styles/, components/, animations)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
jacobxu666
2026-06-25 01:07:21 +08:00
co-authored by Claude
parent c062dee3e6
commit 5f1ab737e9
25 changed files with 2110 additions and 390 deletions
+497 -232
View File
@@ -1,92 +1,98 @@
<template>
<view class="page">
<!-- 头部区域:背景图 + 渐变遮罩 + 标题 + 搜索栏 -->
<view class="hero">
<!-- Hero: mountain landscape photo -->
<view class="hero animate-fade-in-up">
<image
class="hero-bg"
src="https://images.pexels.com/photos/28594448/pexels-photo-28594448.jpeg?auto=compress&cs=tinysrgb&w=750"
mode="aspectFill"
/>
<view class="hero-overlay" />
<view class="hero-content">
<view class="hero-inner">
<text class="hero-title">爱上萍乡</text>
<text class="hero-subtitle">赣湘之间 · 发现旅途美好</text>
<view class="search-bar" @click="goSearch">
<view class="search-icon-wrap">
<view class="search-icon-circle" />
</view>
<text class="search-placeholder">搜索景区、活动...</text>
</view>
<text class="hero-sub">赣湘之间 · 发现旅途美好</text>
</view>
</view>
<scroll-view scroll-y class="scroll-content">
<scroll-view scroll-y class="main-scroll">
<!-- 公告栏 -->
<view class="notice-bar" v-if="announcements.length">
<view class="notice-label">
<view class="notice-dot" />
<text class="notice-text">公告</text>
<view v-if="announcements.length" class="notice animate-fade-in-up stagger-1">
<view class="notice-tag">
<view class="notice-pip animate-pulse" />
<text class="notice-label">公告</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>
<text class="notice-text">{{ item.content }}</text>
</swiper-item>
</swiper>
</view>
<!-- 卡片销售宣传 -->
<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">
<view class="promo-card-feature"><view class="feature-dot" /><text>22家景区免门票</text></view>
<view class="promo-card-feature"><view class="feature-dot" /><text>武功山·方特·君澜温泉</text></view>
<view class="promo-card-feature"><view class="feature-dot" /><text>覆盖萍乡/长沙/株洲/湘潭</text></view>
</view>
<view class="promo-card-btn">
<text class="promo-card-btn-text">立即抢购</text>
<view class="btn-arrow" />
</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">
<view class="promo-card-feature"><view class="feature-dot" /><text>40家景区免门票</text></view>
<view class="promo-card-feature"><view class="feature-dot" /><text>武功山·方特·韶山·滴水洞</text></view>
<view class="promo-card-feature"><view class="feature-dot" /><text>含外骨骼登山·青年旅社等</text></view>
</view>
<view class="promo-card-btn">
<text class="promo-card-btn-text">立即抢购</text>
<view class="btn-arrow" />
<!-- 旅游卡 — 大字报风格 -->
<view class="promo-wrap animate-fade-in-up stagger-2">
<view class="promo" @click="goCardDetail()">
<image
class="promo-bg"
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?fm=jpg&q=80&w=750&h=400&fit=crop"
mode="aspectFill"
/>
<view class="promo-overlay" />
<view class="promo-content">
<view class="promo-badge">大学生·市民专属</view>
<text class="promo-name">超级玩家卡</text>
<view class="promo-price-row">
<view class="promo-price-block">
<text class="promo-currency">¥</text>
<text class="promo-price">198</text>
</view>
<view class="promo-value-block">
<text class="promo-value-label">景区总价值</text>
<text class="promo-value">¥3800+</text>
</view>
</view>
<text class="promo-desc">40家景区免门票 · 武功山·方特·韶山·滴水洞</text>
<view class="promo-cta">
<text class="cta-label">立即抢购</text>
<view class="cta-arrow" />
</view>
</view>
</view>
</view>
<!-- 热门景点列表 -->
<!-- 热门景点 -->
<view class="spots-section">
<view class="spots-header">
<view class="spots-head animate-fade-in-up stagger-3">
<text class="spots-title">热门景点</text>
<view class="spots-more" @click="goMore">
<text>查看全部</text>
<view class="chevron-right" />
<text class="spots-more-text">查看全部</text>
<view class="chevron" />
</view>
</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">{{ spot.name }}</text>
<view class="spot-meta">
<text class="spot-price">¥{{ spot.price }}</text>
<view class="spot-free-tag">持卡免门票</view>
<view class="spots-grid">
<view
v-for="(spot, idx) in scenicSpots.slice(0, 6)"
:key="spot.id"
class="spot animate-fade-in-up"
:class="`stagger-${Math.min(idx + 4, 10)}`"
@click="goSpot(spot)"
>
<view class="spot-cover">
<image class="spot-img" :src="getSpotImage(spot.id)" mode="aspectFill" />
<view class="spot-region">
<view class="pin-dot" />
<text>{{ spot.region }}</text>
</view>
</view>
<view class="spot-body">
<text class="spot-name">{{ spot.name }}</text>
<view class="spot-meta">
<text class="spot-price">¥{{ spot.price }}</text>
<view class="spot-free">
<view class="free-dot" />
<text>免门票</text>
</view>
</view>
</view>
</view>
</view>
@@ -107,202 +113,461 @@ const announcements = ref([
{ id: 3, content: '武功山金顶景区已开放,持卡可免费入园1次。' },
])
/** 区域对应的渐变色(景点图片占位) */
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 spotImages: Record<number, string> = {
1: 'https://images.unsplash.com/photo-1513415564515-763d91423bdd?fm=jpg&q=80&w=400&h=250&fit=crop',
2: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?fm=jpg&q=80&w=400&h=250&fit=crop',
3: 'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?fm=jpg&q=80&w=400&h=250&fit=crop',
4: 'https://images.unsplash.com/photo-1433086966358-54859d0ed716?fm=jpg&q=80&w=400&h=250&fit=crop',
5: 'https://images.unsplash.com/photo-1441974231531-c6227db76b6e?fm=jpg&q=80&w=400&h=250&fit=crop',
6: 'https://images.unsplash.com/photo-1501854140801-50d01698950b?fm=jpg&q=80&w=400&h=250&fit=crop',
}
function getGradient(region: string): string {
return regionGradients[region] || regionGradients['萍乡']
function getSpotImage(id: number): string {
return spotImages[id] || `https://images.unsplash.com/photo-1506905925346-21bda4d32df4?fm=jpg&q=80&w=400&h=250&fit=crop`
}
/** 跳转搜索(暂不实现) */
function goSearch() {
uni.showToast({ title: '搜索功能开发中', icon: 'none' })
}
/** 跳转卡片详情 */
function goCardDetail(type: 'gx' | 'super') {
function goCardDetail() {
uni.navigateTo({ url: '/packages/user/pages/card/list' })
}
/** 跳转景点详情 */
function goSpot(spot: ScenicSpotItem) {
uni.showToast({ title: `${spot.name} 详情开发中`, icon: 'none' })
}
/** 查看全部景点 */
function goMore() {
uni.showToast({ title: '景点列表开发中', icon: 'none' })
}
</script>
<style scoped>
.page { display: flex; flex-direction: column; min-height: 100vh; background: #f5f5f5; }
<style lang="scss" scoped>
/* ============================================
Page
============================================ */
.page {
display: flex;
flex-direction: column;
min-height: 100vh;
background: $color-background;
}
/* === 头部区域 === */
.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;
min-height: 88rpx;
transition: opacity 0.2s;
}
.search-bar:active { opacity: 0.7; }
.search-icon-wrap {
width: 36rpx; height: 36rpx; margin-right: 12rpx;
display: flex; align-items: center; justify-content: center;
}
.search-icon-circle {
width: 24rpx; height: 24rpx;
border: 3rpx solid #999; border-radius: 50%;
/* ============================================
Hero — mountain landscape photo
============================================ */
.hero {
position: relative;
}
.search-icon-circle::after {
content: ''; position: absolute;
width: 3rpx; height: 10rpx;
background: #999; border-radius: 2rpx;
bottom: -10rpx; right: -4rpx;
transform: rotate(-45deg);
}
.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-dot {
width: 14rpx; height: 14rpx; border-radius: 50%;
background: #E74C3C; margin-right: 8rpx;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.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;
height: 340rpx;
overflow: hidden;
transition: transform 0.15s ease;
}
.promo-card:active { transform: scale(0.98); }
.promo-card-gx {
background: linear-gradient(135deg, #1a237e 0%, #283593 40%, #ffd54f 100%);
.hero-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.promo-card-super {
background: linear-gradient(135deg, #e65100 0%, #bf360c 40%, #7b1fa2 100%);
.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%
);
}
.promo-card-badge {
.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);
}
/* ============================================
CSS-only icons
============================================ */
.notice-pip {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: $color-accent;
flex-shrink: 0;
}
.chevron {
width: 10rpx;
height: 10rpx;
border-top: 2rpx solid $color-muted-foreground;
border-right: 2rpx solid $color-muted-foreground;
transform: rotate(45deg);
flex-shrink: 0;
}
.pin-dot {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: #fff;
flex-shrink: 0;
}
.free-dot {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: $color-accent;
flex-shrink: 0;
}
/* ============================================
Notice bar
============================================ */
.notice {
display: flex;
align-items: center;
background: $color-card;
padding: $spacing-2 $spacing-3;
border-bottom: $border-width solid $color-border;
}
.notice-tag {
display: flex;
align-items: center;
margin-right: $spacing-2;
flex-shrink: 0;
gap: $spacing-1;
}
.notice-label {
font-size: $font-size-xs;
color: $color-accent;
font-weight: $font-weight-medium;
}
.notice-swiper {
flex: 1;
height: 40rpx;
}
.notice-text {
font-size: $font-size-sm;
color: $color-muted-foreground;
line-height: 40rpx;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* ============================================
Promo card — landscape photo hero card
============================================ */
.promo-wrap {
padding: $spacing-3;
}
.promo {
position: relative;
border-radius: $radius-xl;
overflow: hidden;
min-height: 400rpx;
transition: transform $transition-fast;
&:active {
transform: scale(0.98);
}
}
.promo-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.promo-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
160deg,
rgba($color-foreground, 0.88) 0%,
rgba($color-foreground, 0.6) 55%,
rgba($color-foreground, 0.3) 100%
);
}
.promo-content {
position: relative;
z-index: 2;
padding: $spacing-5 $spacing-4;
display: flex;
flex-direction: column;
}
.promo-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: 10rpx; margin-bottom: 24rpx; }
.promo-card-feature {
display: flex; align-items: center; gap: 10rpx;
font-size: 24rpx; color: rgba(255,255,255,0.9);
}
.feature-dot {
width: 8rpx; height: 8rpx; border-radius: 50%;
background: rgba(255,255,255,0.7); flex-shrink: 0;
}
.promo-card-btn {
display: flex; align-items: center; justify-content: center; gap: 8rpx;
background: #fff; border-radius: 44rpx;
padding: 22rpx 0;
min-height: 88rpx;
transition: opacity 0.2s;
}
.promo-card-btn:active { opacity: 0.8; }
.promo-card-btn-text { font-size: 28rpx; font-weight: bold; color: #333; }
.btn-arrow {
width: 12rpx; height: 12rpx;
border-top: 3rpx solid #333;
border-right: 3rpx solid #333;
transform: rotate(45deg);
align-self: flex-start;
background: rgba($color-accent, 0.2);
border: $border-width solid rgba($color-accent, 0.4);
border-radius: $radius-full;
padding: 6rpx $spacing-3;
font-size: $font-size-sm;
color: rgba(255, 255, 255, 0.95);
font-weight: $font-weight-semibold;
margin-bottom: $spacing-3;
}
/* === 热门景点列表 === */
.spots-section { padding: 24rpx; }
.spots-header {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 20rpx;
.promo-name {
font-family: $font-family-display;
font-size: 56rpx;
font-weight: $font-weight-bold;
color: #fff;
display: block;
margin-bottom: $spacing-3;
text-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.4);
letter-spacing: 2rpx;
}
.spots-title { font-size: 34rpx; font-weight: bold; color: #333; }
.promo-price-row {
display: flex;
align-items: flex-end;
gap: $spacing-4;
margin-bottom: $spacing-3;
}
.promo-price-block {
display: flex;
align-items: baseline;
}
.promo-currency {
font-size: $font-size-2xl;
font-weight: $font-weight-bold;
color: $uv-warning;
}
.promo-price {
font-size: 96rpx;
font-weight: $font-weight-bold;
color: $uv-warning;
line-height: 0.85;
letter-spacing: -4rpx;
}
.promo-value-block {
display: flex;
flex-direction: column;
padding-bottom: $spacing-2;
}
.promo-value-label {
font-size: $font-size-xs;
color: rgba(255, 255, 255, 0.5);
margin-bottom: 4rpx;
}
.promo-value {
font-size: $font-size-xl;
color: rgba(255, 255, 255, 0.45);
text-decoration: line-through;
font-weight: $font-weight-medium;
}
.promo-desc {
font-size: $font-size-base;
color: rgba(255, 255, 255, 0.8);
display: block;
margin-bottom: $spacing-4;
line-height: $line-height-relaxed;
}
.promo-cta {
display: flex;
align-items: center;
justify-content: center;
gap: $spacing-2;
align-self: flex-start;
padding: $spacing-2 $spacing-8;
border-radius: $radius-full;
background: $uv-warning;
transition: transform $transition-fast;
&:active {
transform: scale(0.96);
}
.cta-label {
font-size: $font-size-base;
font-weight: $font-weight-bold;
color: $uv-main-color;
}
.cta-arrow {
width: 10rpx;
height: 10rpx;
border-top: 3rpx solid $uv-main-color;
border-right: 3rpx solid $uv-main-color;
transform: rotate(45deg);
}
}
/* ============================================
Spots section
============================================ */
.spots-section {
padding: $spacing-3;
}
.spots-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $spacing-3;
}
.spots-title {
font-family: $font-family-display;
font-size: $font-size-xl;
font-weight: $font-weight-bold;
color: $color-foreground;
}
.spots-more {
display: flex; align-items: center; gap: 4rpx;
font-size: 24rpx; color: #999;
}
.chevron-right {
width: 10rpx; height: 10rpx;
border-top: 2rpx solid #999;
border-right: 2rpx solid #999;
transform: rotate(45deg);
display: flex;
align-items: center;
gap: $spacing-1;
}
.spot-card {
background: #fff; border-radius: 20rpx; overflow: hidden;
margin-bottom: 20rpx;
transition: transform 0.15s ease;
}
.spot-card:active { transform: scale(0.98); }
.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: 6rpx 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: 6rpx 16rpx;
font-size: 22rpx; color: #2E7D32; font-weight: 500;
.spots-more-text {
font-size: $font-size-sm;
color: $color-muted-foreground;
}
/* === 滚动区域 === */
.scroll-content { flex: 1; }
/* Spots grid — 2 column */
.spots-grid {
display: flex;
flex-wrap: wrap;
gap: $spacing-2;
}
.spot {
width: calc(50% - #{$spacing-1});
background: $color-card;
border-radius: $radius-lg;
overflow: hidden;
box-shadow: $shadow-sm;
transition: transform $transition-fast, box-shadow $transition-base;
&:active {
transform: scale(0.97);
box-shadow: $shadow-base;
}
}
.spot-cover {
width: 100%;
height: 220rpx;
position: relative;
background: $color-muted;
}
.spot-img {
width: 100%;
height: 100%;
}
.spot-region {
position: absolute;
top: $spacing-2;
left: $spacing-2;
display: flex;
align-items: center;
gap: 4rpx;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(8rpx);
border-radius: $radius-sm;
padding: 4rpx $spacing-2;
text {
font-size: $font-size-xs;
color: #fff;
}
}
.spot-body {
padding: $spacing-2;
}
.spot-name {
font-size: $font-size-sm;
font-weight: $font-weight-semibold;
color: $color-foreground;
display: block;
margin-bottom: $spacing-1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.spot-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.spot-price {
font-size: $font-size-base;
color: $uv-warning;
font-weight: $font-weight-semibold;
}
.spot-free {
display: flex;
align-items: center;
gap: 4rpx;
background: rgba($color-accent, 0.08);
border-radius: $radius-sm;
padding: 4rpx $spacing-1;
text {
font-size: $font-size-xs;
color: $color-accent;
font-weight: $font-weight-medium;
}
}
/* ============================================
Scroll area
============================================ */
.main-scroll {
flex: 1;
}
</style>