| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- <template>
- <view class="section">
- <!-- 头部 -->
- <view class="section-header">
- <view class="section-header-left">
- <view class="section-icon-wrap">
- <text class="section-icon">{{ dimensionIcon }}</text>
- </view>
- <text class="section-title">推荐活动</text>
- </view>
- <view class="section-more" @click="$emit('moreActivities')">
- <text class="section-more-text">更多</text>
- <text class="section-more-arrow">›</text>
- </view>
- </view>
- <!-- 空状态 -->
- <view v-if="!displayActivities || displayActivities.length === 0" class="empty-state">
- <text class="empty-icon">{{ dimensionIcon }}</text>
- <text class="empty-tip">{{ isLoggedIn ? '暂无相关活动' : '登录后查看更多活动' }}</text>
- </view>
- <!-- 活动卡片轮播(行布局) -->
- <view class="activity-carousel" v-if="displayActivities && displayActivities.length > 0">
- <swiper
- class="activity-swiper"
- :indicator-dots="displayActivities.length > 1"
- :active-index="swiperIndex"
- @change="onSwiperChange"
- autoplay
- interval="3000"
- duration="500"
- circular
- >
- <swiper-item v-for="(act, index) in displayActivities" :key="act.id">
- <view class="activity-card" @click.stop="$emit('activityClick', act)">
- <!-- 封面图(占满卡片高度) -->
- <image
- class="card-cover-img"
- :src="act.coverImage || '/static/default-activity.png'"
- mode="aspectFill"
- />
- <!-- 右侧信息区 -->
- <view class="card-info">
- <text class="card-title line-clamp-1">{{ act.title }}</text>
- <view class="card-meta-row">
- <view class="card-time">
- <text class="meta-icon">🕐</text>
- <text class="meta-text">{{ act._timeText }}</text>
- </view>
- <view class="card-badges" v-if="act._badges && act._badges.length">
- <text
- class="card-badge"
- v-for="badge in act._badges"
- :key="badge.name"
- :style="{ color: badge.color, borderColor: badge.color + '40' }"
- >
- {{ badge.name }}
- </text>
- </view>
- </view>
- <view class="card-bottom">
- <text class="card-status" :class="'status-' + (act.status || 'upcoming')">
- {{ act._statusText }}
- </text>
- <text class="card-price" v-if="act.priceLabel">{{ act.priceLabel }}</text>
- <text class="card-price" v-else-if="act.price && act.price > 0">{{ act._priceDisplay }}</text>
- <text class="card-price-free" v-else>免费</text>
- </view>
- </view>
- </view>
- </swiper-item>
- </swiper>
- <!-- 轮播指示点 -->
- <view v-if="displayActivities.length > 1" class="swiper-dots">
- <view
- class="dot"
- v-for="(dot, index) in displayActivities"
- :key="index"
- :class="{ active: swiperIndex === index }"
- ></view>
- </view>
- </view>
- </view>
- </template>
- <script>
- var DIMENSION_MAP = {
- body: { name: '身', color: '#FF8C42', icon: '🌏' },
- mind: { name: '心', color: '#FF6B9D', icon: '🔥' },
- wisdom: { name: '智', color: '#6366F1', icon: '⚡' },
- action: { name: '行', color: '#10B981', icon: '🌿' },
- wealth: { name: '富', color: '#F59E0B', icon: '💧' }
- }
- export default {
- props: {
- dimensionCode: { type: String, default: '' },
- activities: { type: Array, default: function() { return [] } },
- isLoggedIn: { type: Boolean, default: false }
- },
- data: function() {
- return {
- swiperIndex: 0
- }
- },
- computed: {
- displayActivities: function() {
- var self = this
- return (this.activities || []).slice(0, 3).map(function(item) {
- return {
- id: item.id,
- title: item.title,
- coverImage: item.coverImage,
- startTime: item.startTime,
- status: item.status,
- price: item.price,
- priceLabel: item.priceLabel,
- dimensionWeights: item.dimensionWeights,
- _badges: self.parseWeights(item.dimensionWeights),
- _timeText: self.formatTime(item.startTime),
- _statusText: self.statusText(item.status),
- _priceDisplay: self.formatPriceWithSymbol(item.price)
- }
- })
- },
- dimensionIcon: function() {
- var map = { body: '🌏', mind: '🔥', wisdom: '⚡', action: '🌿', wealth: '💧' }
- return map[this.dimensionCode] || '🎯'
- }
- },
- methods: {
- onSwiperChange: function(e) {
- this.swiperIndex = e.detail.current
- },
- parseWeights: function(str) {
- if (!str) return []
- try {
- var obj = JSON.parse(str)
- var result = []
- for (var key in obj) {
- if (obj[key] > 0 && DIMENSION_MAP[key]) {
- result.push({ name: DIMENSION_MAP[key].name, color: DIMENSION_MAP[key].color, weight: obj[key] })
- }
- }
- return result
- } catch (e) {
- return []
- }
- },
- statusText: function(status) {
- var map = { upcoming: '即将开始', ongoing: '进行中', ended: '已结束', cancelled: '已取消' }
- return map[status] || '即将开始'
- },
- formatTime: function(timeStr) {
- if (!timeStr) return '待定'
- var d = this._parseDate(timeStr)
- if (!d || isNaN(d.getTime())) return timeStr
- var month = d.getMonth() + 1
- var day = d.getDate()
- var hour = d.getHours()
- var minute = d.getMinutes() < 10 ? '0' + d.getMinutes() : d.getMinutes()
- return month + '月' + day + '日 ' + hour + ':' + minute
- },
- // iOS 不兼容 Java "EEE MMM dd HH:mm:ss zzz yyyy" 格式,需提前归一化
- _parseDate: function(timeStr) {
- if (!timeStr) return null
- // Java 格式: "Sat Aug 22 14:00:00 CST 2026" — iOS 不支持,必须先于 new Date() 处理
- var javaRe = /^\w{3}\s+(\w{3})\s+(\d{1,2})\s+(\d{2}:\d{2}:\d{2})\s+\w{3}\s+(\d{4})$/
- var m = timeStr.match(javaRe)
- if (m) {
- var months = {Jan:'01',Feb:'02',Mar:'03',Apr:'04',May:'05',Jun:'06',Jul:'07',Aug:'08',Sep:'09',Oct:'10',Nov:'11',Dec:'12'}
- var iso = m[4] + '/' + (months[m[1]] || '01') + '/' + (m[2].length === 1 ? '0' + m[2] : m[2]) + ' ' + m[3]
- return new Date(iso)
- }
- // 标准格式尝试(iOS 支持 yyyy/MM/dd、yyyy-MM-dd、ISO 8601 等)
- var d = new Date(timeStr)
- if (!isNaN(d.getTime())) return d
- return null
- },
- formatPriceWithSymbol: function(price) {
- if (price == null) return '免费'
- return '¥' + (Number(price) / 100).toFixed(0)
- }
- }
- }
- </script>
- <style scoped>
- .section {
- margin: 16rpx 20rpx;
- }
- /* ===== 头部 ===== */
- .section-header {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 16rpx;
- }
- .section-header-left {
- display: flex;
- flex-direction: row;
- align-items: center;
- }
- .section-icon-wrap {
- width: 44rpx;
- height: 44rpx;
- border-radius: 12rpx;
- background: linear-gradient(135deg, #FF6B9D, #FF8FB1);
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 12rpx;
- }
- .section-icon {
- font-size: 24rpx;
- }
- .section-title {
- font-size: 32rpx;
- font-weight: 700;
- color: #1E293B;
- letter-spacing: 1rpx;
- }
- .section-more {
- display: flex;
- flex-direction: row;
- align-items: center;
- padding: 8rpx 20rpx;
- background: #F1F5F9;
- border-radius: 24rpx;
- }
- .section-more-text {
- font-size: 24rpx;
- color: #64748B;
- margin-right: 4rpx;
- }
- .section-more-arrow {
- font-size: 20rpx;
- color: #94A3B8;
- }
- /* ===== 空状态 ===== */
- .empty-state {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 30rpx 0;
- }
- .empty-icon {
- font-size: 64rpx;
- margin-bottom: 16rpx;
- }
- .empty-tip {
- font-size: 26rpx;
- color: #94A3B8;
- }
- /* ===== 活动轮播 ===== */
- .activity-carousel {
- width: 100%;
- margin: 0 -20rpx;
- }
- .activity-swiper {
- width: 100%;
- height: 160rpx;
- }
- .activity-swiper .swiper-slide {
- width: 100%;
- height: 100%;
- }
- /* 轮播指示点 */
- .swiper-dots {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 8rpx;
- margin-top: 12rpx;
- }
- .dot {
- width: 8rpx;
- height: 8rpx;
- border-radius: 50%;
- background: #D1D5DB;
- transition: all 0.3s ease;
- }
- .dot.active {
- width: 32rpx;
- border-radius: 4rpx;
- background: #F97316;
- }
- /* ===== 活动卡片(行布局) ===== */
- .activity-card {
- background: #FFFFFF;
- border-radius: 16rpx;
- overflow: hidden;
- box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
- display: flex;
- flex-direction: row;
- height: 100%;
- }
- .activity-card:active {
- opacity: 0.8;
- }
- /* 封面图 — 占满卡片高度 */
- .card-cover-img {
- width: 200rpx;
- height: 100%;
- flex-shrink: 0;
- background: #F1F5F9;
- }
- /* 右侧信息区 */
- .card-info {
- flex: 1;
- padding: 16rpx;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- min-width: 0;
- }
- /* 标题 */
- .card-title {
- font-size: 28rpx;
- font-weight: bold;
- color: #333;
- margin-bottom: 8rpx;
- }
- .line-clamp-1 {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- /* 时间和维度标签 */
- .card-meta-row {
- display: flex;
- flex-direction: row;
- align-items: center;
- margin-bottom: 8rpx;
- }
- .card-time {
- display: flex;
- flex-direction: row;
- align-items: center;
- margin-right: 16rpx;
- }
- .meta-icon {
- font-size: 22rpx;
- margin-right: 6rpx;
- }
- .meta-text {
- font-size: 24rpx;
- color: #64748B;
- }
- .card-badges {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- gap: 8rpx;
- }
- .card-badge {
- font-size: 20rpx;
- padding: 4rpx 12rpx;
- border-radius: 20rpx;
- border: 1rpx solid;
- font-weight: 500;
- }
- /* 底部:状态 + 价格 */
- .card-bottom {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 12rpx;
- }
- .card-status {
- font-size: 20rpx;
- padding: 2rpx 10rpx;
- border-radius: 8rpx;
- background: #E8F5E9;
- color: #2E7D32;
- }
- .card-status.status-upcoming {
- background: #E3F2FD;
- color: #1565C0;
- }
- .card-status.status-ended {
- background: #F5F5F5;
- color: #999;
- }
- .card-price {
- font-size: 24rpx;
- font-weight: bold;
- color: #F97316;
- }
- .card-price-free {
- font-size: 24rpx;
- font-weight: 600;
- color: #22C55E;
- }
- </style>
|