2025-08-01 09:34:21 +08:00

578 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="activity-detail-container">
<!-- 顶部图片轮播 -->
<swiper class="activity-banner" :autoplay="false" indicator-dots indicator-color="rgba(255,255,255,0.5)"
indicator-active-color="#5b9cf8">
<swiper-item v-for="(img, index) in bannerImages" :key="index">
<image :src="img" mode="aspectFill" class="w-full h-full" @click="previewImage(index)" />
</swiper-item>
</swiper>
<!-- 活动基本信息 -->
<view class="activity-base-info">
<view class="title-box">
<text class="title">{{activityInfo.title}}</text>
<view class="status-tag active" v-if="activityInfo.status === 2">
{{'未开始'}}
</view>
<view class="status-tag active" v-else-if="activityInfo.status === 3">
{{'进行中'}}
</view>
<view class="status-tag active" v-else="activityInfo.status === 4">
{{'已结束'}}
</view>
</view>
<view class="info-grid">
<view class="info-item">
<u-icon name="star" size="24" color="#5b9cf8"></u-icon>
<view class="info-text"><text>活动类型:</text>{{activityInfo.categoryName || '待确认'}} </view>
</view>
<view class="info-item">
<u-icon name="map" size="24" color="#5b9cf8"></u-icon>
<view class="info-text"><text>活动地址:</text>{{activityInfo.address || '待定'}}</view>
</view>
<view class="info-item">
<u-icon name="clock" size="24" color="#5b9cf8"></u-icon>
<view class="info-text">{{ activityInfo.openAt }}</view>
</view>
<!-- <view class="info-item">
<u-icon name="account" size="18" color="#36CFC9"></u-icon>
<text class="info-text">已报名 {{activityInfo.participants}}人</text>
</view> -->
</view>
</view>
<!-- 活动详情卡片 -->
<view class="detail-card">
<!-- <view class="card-header">
<view class="header-line"></view>
<text class="header-title">活动详情</text>
</view> -->
<view class="detail-section">
<view class="section-title">
<view class="title-icon"></view>
<text>活动详情</text>
</view>
<view class="section-content">
<view v-html="activityInfo.content"></view>
<image src="/static/route-map.jpg" mode="widthFix" class="route-map"
@click="previewImage(bannerImages.length)"></image>
</view>
</view>
<!-- <view class="detail-section">
<view class="section-title">
<view class="title-icon"></view>
<text>活动流程</text>
</view>
<view class="timeline">
<view class="timeline-item" v-for="(step, index) in timeline" :key="index">
<view class="timeline-dot" :style="{backgroundColor: dotColors[index]}"></view>
<view class="timeline-content">
<text class="timeline-time">{{step.time}}</text>
<text class="timeline-desc">{{step.desc}}</text>
</view>
</view>
</view>
</view> -->
<!-- <view class="detail-section">
<view class="section-title">
<view class="title-icon"></view>
<text>积分规则</text>
</view>
<view class="point-rules">
<view class="rule-item" v-for="(rule, index) in pointRules" :key="index">
<view class="rule-icon">
<text>{{index + 1}}</text>
</view>
<text class="rule-text">{{rule}}</text>
</view>
</view>
</view> -->
</view>
<!-- 温馨提示 -->
<!-- <view class="tips-card">
<view class="card-header">
<view class="header-line"></view>
<text class="header-title">温馨提示</text>
</view>
<view class="tips-content">
<text class="tip-item" v-for="(tip, index) in tips" :key="index">
{{tip}}
</text>
</view>
</view> -->
<!-- 底部操作栏 -->
<view class="action-bar">
<button class="share-btn" open-type="share" >
<u-icon name="share" size="36" color="#fff"></u-icon>
<text class="share-text">分享</text>
</button>
<button class="signup-btn" @click="handleSignUp">立即报名</button>
</view>
</view>
</template>
<script>
import {
get,
post
} from '@/utils/request';
import {
IMAGE_BASE_URL,
BASE_URL
} from '@/utils/config';
import {
formatTime,
formatRelativeTime
} from '@/utils/timeFormat';
export default {
data() {
return {
formatTime,
IMAGE_BASE_URL,
Id: null,
isFavorite: false,
isSignedUp: false,
bannerImages: [],
activityInfo: {},
// timeline: [
// {time: '19:00-19:20', desc: '签到领取装备'},
// {time: '19:20-19:30', desc: '热身运动'},
// {time: '19:30-20:15', desc: '集体夜跑'},
// {time: '20:15-20:30', desc: '拉伸放松'}
// ],
// pointRules: [
// '完成全程跑步可获得50积分',
// '连续参加4次额外奖励100积分',
// '邀请好友参加每人奖励30积分',
// '积分每月1日清零请及时兑换'
// ],
// tips: [
// '建议穿着舒适运动服装和跑鞋',
// '活动前1小时请勿大量进食',
// '请自带水杯,现场提供饮用水',
// '如遇雨天,活动将延期举行',
// '未成年人需家长陪同参加'
// ],
dotColors: ['#36CFC9', '#5AC8FA', '#FF9500', '#FF5A5F']
}
},
onLoad(options) {
if (options && options.Id) {
this.Id = options.Id;
}
},
mounted() {
this.getActivitiesDetail();
},
methods: {
toggleFavorite() {
this.isFavorite = !this.isFavorite
uni.showToast({
title: this.isFavorite ? '已收藏' : '已取消收藏',
icon: 'none'
})
},
async handleSignUp() {
try {
const res = await get(`/api/v1/app_auth/activities/${this.Id}`);
if (!res.success) {
uni.showToast({
title: res.msg || '报名失败',
icon: 'error'
})
} else {
uni.showToast({
title: '报名成功!',
icon: 'success'
})
}
} catch (err) {
console.error('获取详情失败:', err);
}
},
onShareAppMessage() {
return {
title: this.activityInfo.title,
path: `/pages/activity/detail?id=${this.Id}`,
imageUrl: this.bannerImages[0] || '/static/logo.png'
}
},
previewImage(index) {
uni.previewImage({
current: index,
urls: [...this.bannerImages, '/static/route-map.jpg']
})
},
async getActivitiesDetail() {
try {
const res = await get(`/api/v1/apps/home/activities/${this.Id}`);
if (!res || !res.success) {
throw new Error('获取详情失败');
}
let imgs = res.data.images;
imgs.forEach(item => {
if (!item.startsWith('http') && !item.startsWith('https')) {
item = IMAGE_BASE_URL + item;
this.bannerImages.push(item);
}
});
this.activityInfo = {
...res.data
};
} catch (err) {
console.error('获取详情失败:', err);
}
}
}
}
</script>
<style lang="scss" scoped>
$primary-color: #5b9cf8;
$secondary-color: #F7F8FA;
$text-color: #333;
$light-text: #666;
$border-color: #eee;
$border-radius: 16rpx;
$shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
.activity-detail-container {
padding-bottom: 120rpx;
background-color: $secondary-color;
.activity-banner {
width: 100%;
height: 420rpx;
image {
width: 100%;
height: 100%;
}
}
.activity-base-info {
padding: 30rpx;
background-color: #fff;
margin-bottom: 20rpx;
box-shadow: $shadow;
.title-box {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.title {
font-size: 36rpx;
font-weight: bold;
color: $text-color;
margin-right: 20rpx;
}
.status-tag {
padding: 6rpx 16rpx;
border-radius: 30rpx;
font-size: 24rpx;
&.active {
background-color: rgba($primary-color, 0.1);
color: $primary-color;
}
}
}
.info-grid {
display: grid;
grid-template-columns: repeat(1, 1fr);
gap: 20rpx;
.info-item {
display: flex;
align-items: center;
.info-text {
font-size: 26rpx;
color: $light-text;
margin-left: 10rpx;
}
}
}
}
.detail-card,
.tips-card {
background-color: #fff;
border-radius: $border-radius;
padding: 30rpx;
margin: 0 20rpx 20rpx;
box-shadow: $shadow;
.card-header {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.header-line {
width: 6rpx;
height: 32rpx;
background-color: $primary-color;
border-radius: 4rpx;
margin-right: 16rpx;
}
.header-title {
font-size: 32rpx;
font-weight: bold;
color: $text-color;
}
}
.detail-section {
margin-bottom: 40rpx;
&:last-child {
margin-bottom: 0;
}
.section-title {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.title-icon {
width: 8rpx;
height: 28rpx;
background-color: $primary-color;
border-radius: 4rpx;
margin-right: 12rpx;
}
text {
font-size: 30rpx;
font-weight: bold;
color: $text-color;
}
}
.section-content {
font-size: 28rpx;
color: $light-text;
line-height: 1.6;
.route-map {
width: 100%;
margin-top: 20rpx;
border-radius: $border-radius;
}
}
.timeline {
position: relative;
padding-left: 40rpx;
&::before {
content: '';
position: absolute;
left: 15rpx;
top: 10rpx;
bottom: 10rpx;
width: 2rpx;
background-color: $border-color;
}
.timeline-item {
position: relative;
padding-bottom: 30rpx;
&:last-child {
padding-bottom: 0;
}
.timeline-dot {
position: absolute;
left: -40rpx;
top: 4rpx;
width: 30rpx;
height: 30rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
&::after {
content: '';
width: 10rpx;
height: 10rpx;
background-color: #fff;
border-radius: 50%;
}
}
.timeline-content {
.timeline-time {
display: block;
font-size: 26rpx;
color: $primary-color;
font-weight: bold;
margin-bottom: 6rpx;
}
.timeline-desc {
font-size: 28rpx;
color: $light-text;
}
}
}
}
.point-rules {
.rule-item {
display: flex;
align-items: flex-start;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.rule-icon {
width: 36rpx;
height: 36rpx;
background-color: $primary-color;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16rpx;
flex-shrink: 0;
text {
color: #fff;
font-size: 22rpx;
font-weight: bold;
}
}
.rule-text {
font-size: 28rpx;
color: $light-text;
line-height: 1.5;
}
}
}
}
.tips-content {
.tip-item {
display: block;
position: relative;
padding-left: 30rpx;
font-size: 28rpx;
color: $light-text;
line-height: 1.6;
margin-bottom: 16rpx;
&::before {
content: '•';
position: absolute;
left: 0;
color: $primary-color;
}
}
}
}
.action-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
background-color: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30rpx;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08);
z-index: 100;
.share-btn {
display: flex;
align-items: center;
justify-content: center;
height: 80rpx;
padding: 0 30rpx;
background: linear-gradient(135deg, #6e8cfa, #5b9cf8);
border-radius: 40rpx;
box-shadow: 0 4rpx 12rpx rgba(91, 156, 248, 0.3);
color: #fff;
font-size: 28rpx;
margin-right: 20rpx;
border: none;
.share-text {
color: #fff;
font-size: 28rpx;
margin-left: 10rpx;
font-weight: 500;
}
&.btn-hover {
opacity: 0.9;
transform: translateY(2rpx);
box-shadow: 0 2rpx 6rpx rgba(91, 156, 248, 0.4);
}
}
.share-btn::after {
border: none;
}
.signup-btn {
flex: 1;
height: 80rpx;
margin-left: 30rpx;
background: linear-gradient(135deg, #ff7e5f, #ff5a5f);
color: #fff;
border-radius: 40rpx;
font-size: 32rpx;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 12rpx rgba(255, 90, 95, 0.3);
transition: all 0.3s ease;
border: none;
&::after {
border: none;
}
&:active {
opacity: 0.9;
transform: translateY(2rpx);
box-shadow: 0 2rpx 6rpx rgba(255, 90, 95, 0.4);
}
}
}
}
@media (min-width: 768px) {
.activity-detail-container {
max-width: 750px;
margin: 0 auto;
}
}
</style>