| New file |
| | |
| | | <template> |
| | | <view class="activity-container"> |
| | | |
| | | <!-- 顶部 Tab 导航 --> |
| | | <view class="tab-nav"> |
| | | <view |
| | | v-for="(tab, index) in tabs" |
| | | :key="index" |
| | | class="tab-item" |
| | | :class="{active: currentTab === index}" |
| | | @click="switchTab(index)" |
| | | > |
| | | {{tab}} |
| | | <view class="tab-indicator" v-if="currentTab === index"></view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 活动列表 --> |
| | | <view class="activity-list"> |
| | | <!-- 已报名活动 --> |
| | | <view v-if="currentTab === 0"> |
| | | <view v-if="signedActivities.length > 0"> |
| | | <view |
| | | v-for="(item, idx) in signedActivities" |
| | | :key="idx" |
| | | class="activity-item card" |
| | | > |
| | | <!-- 封面区域 --> |
| | | <view class="cover-container"> |
| | | <block v-if="item.coverType === '图片' || item.coverType === '视频'"> |
| | | <image :src="getUrl(item.cover)" mode="aspectFill" class="activity-cover" /> |
| | | </block> |
| | | <block v-if="item.coverType === '文字'"> |
| | | <view class="activity-cover text-cover">{{ item.cover }}</view> |
| | | </block> |
| | | </view> |
| | | |
| | | <!-- 活动信息 --> |
| | | <view class="activity-info"> |
| | | <view class="info-header"> |
| | | <view class="activity-title">{{ item.activityName }}</view> |
| | | <view class="activity-status signed">已报名</view> |
| | | </view> |
| | | |
| | | <view class="activity-meta"> |
| | | <view class="meta-item"> |
| | | <u-icon name="calendar" size="16" color="#999"></u-icon> |
| | | <text class="activity-time">{{ item.startTime }} - {{ item.endTime }}</text> |
| | | </view> |
| | | <view class="meta-item"> |
| | | <u-icon name="map" size="16" color="#999"></u-icon> |
| | | <text class="activity-location">{{ item.activityLocation || '待定' }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="action-container"> |
| | | <button |
| | | class="cancel-btn" |
| | | @click="handleActivityCancel(item.id)" |
| | | hover-class="cancel-btn-hover" |
| | | > |
| | | 取消报名 |
| | | </button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else class="empty-state"> |
| | | |
| | | <text class="empty-text">暂无已报名活动</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 已结束活动 --> |
| | | <view v-if="currentTab === 1"> |
| | | <view v-if="endedActivities.length > 0"> |
| | | <view |
| | | v-for="(item, idx) in endedActivities" |
| | | :key="idx" |
| | | class="activity-item card" |
| | | > |
| | | <view class="cover-container"> |
| | | <block v-if="item.coverType === '图片' || item.coverType === '视频'"> |
| | | <image :src="getUrl(item.cover)" mode="aspectFill" class="activity-cover" /> |
| | | </block> |
| | | <block v-if="item.coverType === '文字'"> |
| | | <view class="activity-cover text-cover">{{ item.cover }}</view> |
| | | </block> |
| | | </view> |
| | | |
| | | <!-- 活动信息 --> |
| | | <view class="activity-info"> |
| | | <view class="info-header"> |
| | | <view class="activity-title">{{ item.activityName }}</view> |
| | | <view class="activity-status ended">已结束</view> |
| | | </view> |
| | | |
| | | <view class="activity-meta"> |
| | | <view class="meta-item"> |
| | | <u-icon name="calendar" size="16" color="#999"></u-icon> |
| | | <text class="activity-time">{{ item.startTime }} - {{ item.endTime }}</text> |
| | | </view> |
| | | <view class="meta-item"> |
| | | <u-icon name="map" size="16" color="#999"></u-icon> |
| | | <text class="activity-location">{{ item.activityLocation || '待定' }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else class="empty-state"> |
| | | |
| | | <text class="empty-text">暂无已结束活动</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 已取消活动 --> |
| | | <view v-if="currentTab === 2"> |
| | | <view v-if="canceledActivities.length > 0"> |
| | | <view |
| | | v-for="(item, idx) in canceledActivities" |
| | | :key="idx" |
| | | class="activity-item card" |
| | | > |
| | | <view class="cover-container"> |
| | | <block v-if="item.coverType === '图片' || item.coverType === '视频'"> |
| | | <image :src="getUrl(item.cover)" mode="aspectFill" class="activity-cover" /> |
| | | </block> |
| | | <block v-if="item.coverType === '文字'"> |
| | | <view class="activity-cover text-cover">{{ item.cover }}</view> |
| | | </block> |
| | | </view> |
| | | |
| | | <!-- 活动信息 --> |
| | | <view class="activity-info"> |
| | | <view class="info-header"> |
| | | <view class="activity-title">{{ item.activityName }}</view> |
| | | <view class="activity-status canceled">已取消</view> |
| | | </view> |
| | | |
| | | <view class="activity-meta"> |
| | | <view class="meta-item"> |
| | | <u-icon name="calendar" size="16" color="#999"></u-icon> |
| | | <text class="activity-time">{{ item.startTime }} - {{ item.endTime }}</text> |
| | | </view> |
| | | <view class="meta-item"> |
| | | <u-icon name="map" size="16" color="#999"></u-icon> |
| | | <text class="activity-location">{{ item.activityLocation || '待定' }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else class="empty-state"> |
| | | <text class="empty-text">暂无已取消活动</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import {getMyActivityList,collectCancel,activityCancel} from '@/api/activity.js' |
| | | import {getFilePreviewUrl} from '@/api/common.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | currentTab: 0, // 当前选中的tab索引 |
| | | tabs: ['已报名', '已结束', '已取消'], |
| | | signedActivities: [], // 已报名列表 |
| | | endedActivities: [],// 已结束列表 |
| | | canceledActivities: [],//已取消列表 |
| | | query:{ |
| | | status:'', |
| | | cancel:false, |
| | | }, |
| | | } |
| | | }, |
| | | onLoad(){ |
| | | this.currentTab = 0; |
| | | this.getMyActivityList(this.currentTab); |
| | | }, |
| | | methods: { |
| | | handleActivityCancel(activityId){ |
| | | activityCancel(activityId).then(res =>{ |
| | | if(res.statusCode == 200){ |
| | | uni.showToast({ |
| | | title: res.data.msg, |
| | | icon: 'success', |
| | | mask: true, |
| | | duration: 2000, |
| | | success: () => { |
| | | setTimeout(() => { |
| | | this.getMyActivityList(this.currentTab); |
| | | }, 2000); |
| | | } |
| | | }); |
| | | } |
| | | }) |
| | | }, |
| | | getUrl(params){ |
| | | getFilePreviewUrl(params).then(res =>{ |
| | | return res.data.data |
| | | }) |
| | | }, |
| | | switchTab(index) { |
| | | if (this.currentTab !== index) { |
| | | this.currentTab = index; |
| | | this.getMyActivityList(index) |
| | | } |
| | | }, |
| | | getMyActivityList(index){ |
| | | uni.showLoading({ |
| | | title: '加载中' |
| | | }); |
| | | if(index === 0){ |
| | | this.query.cancel = false; |
| | | getMyActivityList(this.query).then(res =>{ |
| | | uni.hideLoading(); |
| | | if(res.statusCode === 200){ |
| | | this.signedActivities = res.data.data |
| | | } |
| | | }) |
| | | }else if(index === 1){ |
| | | this.query.status = '已结束'; |
| | | this.query.cancel = false; |
| | | getMyActivityList(this.query).then(res =>{ |
| | | uni.hideLoading(); |
| | | if(res.statusCode === 200){ |
| | | this.endedActivities = res.data.data |
| | | } |
| | | }) |
| | | }else if(index === 2){ |
| | | this.query.cancel = true; |
| | | getMyActivityList(this.query).then(res =>{ |
| | | uni.hideLoading(); |
| | | if(res.statusCode === 200){ |
| | | this.canceledActivities = res.data.data |
| | | } |
| | | }) |
| | | } |
| | | this.query.status = ''; |
| | | this.query.cancel= false; |
| | | }, |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | .activity-container { |
| | | padding: 0; |
| | | background-color: #f7f8fa; |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | /* Tab 导航样式 */ |
| | | .tab-nav { |
| | | display: flex; |
| | | background-color: #fff; |
| | | margin-bottom: 16rpx; |
| | | position: sticky; |
| | | top: 0; |
| | | z-index: 10; |
| | | box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04); |
| | | } |
| | | |
| | | .tab-item { |
| | | flex: 1; |
| | | text-align: center; |
| | | padding: 28rpx 0; |
| | | font-size: 30rpx; |
| | | color: #666; |
| | | position: relative; |
| | | transition: all 0.3s ease; |
| | | |
| | | &.active { |
| | | color: #2979ff; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .tab-indicator { |
| | | position: absolute; |
| | | bottom: 0; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | width: 80rpx; |
| | | height: 6rpx; |
| | | background-color: #2979ff; |
| | | border-radius: 3rpx; |
| | | animation: scaleIn 0.3s ease; |
| | | } |
| | | } |
| | | |
| | | @keyframes scaleIn { |
| | | from { transform: translateX(-50%) scaleX(0); } |
| | | to { transform: translateX(-50%) scaleX(1); } |
| | | } |
| | | |
| | | /* 活动列表样式 */ |
| | | .activity-list { |
| | | padding: 20rpx 24rpx; |
| | | } |
| | | |
| | | .card { |
| | | background: #fff; |
| | | border-radius: 16rpx; |
| | | margin-bottom: 24rpx; |
| | | overflow: hidden; |
| | | box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04); |
| | | transition: transform 0.2s ease, box-shadow 0.2s ease; |
| | | |
| | | &:active { |
| | | transform: scale(0.98); |
| | | box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08); |
| | | } |
| | | } |
| | | |
| | | .activity-item { |
| | | display: flex; |
| | | padding: 24rpx; |
| | | } |
| | | |
| | | .cover-container { |
| | | position: relative; |
| | | width: 200rpx; |
| | | height: 200rpx; |
| | | border-radius: 12rpx; |
| | | overflow: hidden; |
| | | margin-right: 24rpx; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .activity-cover { |
| | | width: 100%; |
| | | height: 100%; |
| | | border-radius: 12rpx; |
| | | } |
| | | |
| | | .text-cover { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); |
| | | color: #fff; |
| | | font-size: 28rpx; |
| | | padding: 16rpx; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | .activity-info { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .info-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | margin-bottom: 16rpx; |
| | | } |
| | | |
| | | .activity-title { |
| | | flex: 1; |
| | | font-size: 32rpx; |
| | | color: #333; |
| | | font-weight: 500; |
| | | margin-right: 16rpx; |
| | | display: -webkit-box; |
| | | -webkit-box-orient: vertical; |
| | | -webkit-line-clamp: 2; |
| | | overflow: hidden; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | .activity-meta { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | margin-bottom: 16rpx; |
| | | } |
| | | |
| | | .meta-item { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 12rpx; |
| | | |
| | | .u-icon { |
| | | margin-right: 8rpx; |
| | | } |
| | | } |
| | | |
| | | .activity-status { |
| | | font-size: 24rpx; |
| | | padding: 6rpx 16rpx; |
| | | border-radius: 20rpx; |
| | | flex-shrink: 0; |
| | | |
| | | &.signed { |
| | | color: #2979ff; |
| | | background-color: rgba(41, 121, 255, 0.1); |
| | | } |
| | | |
| | | &.ended { |
| | | color: #909399; |
| | | background-color: rgba(144, 147, 153, 0.1); |
| | | } |
| | | |
| | | &.canceled { |
| | | color: #fa3534; |
| | | background-color: rgba(250, 53, 52, 0.1); |
| | | } |
| | | } |
| | | |
| | | /* 操作按钮 */ |
| | | .action-container { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin-top: auto; |
| | | padding-top: 16rpx; |
| | | } |
| | | |
| | | .cancel-btn { |
| | | background: #fef0f0; |
| | | color: #fa3534; |
| | | border: none; |
| | | border-radius: 40rpx; |
| | | padding: 0 32rpx; |
| | | height: 64rpx; |
| | | line-height: 64rpx; |
| | | font-size: 26rpx; |
| | | transition: all 0.3s ease; |
| | | |
| | | &::after { |
| | | border: none; |
| | | } |
| | | |
| | | &.cancel-btn-hover { |
| | | background: #fde2e2; |
| | | transform: scale(0.98); |
| | | } |
| | | } |
| | | |
| | | /* 空状态 */ |
| | | .empty-state { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 100rpx 0; |
| | | text-align: center; |
| | | |
| | | .empty-image { |
| | | width: 300rpx; |
| | | height: 300rpx; |
| | | opacity: 0.6; |
| | | margin-bottom: 40rpx; |
| | | } |
| | | |
| | | .empty-text { |
| | | font-size: 28rpx; |
| | | color: #999; |
| | | margin-top: 20rpx; |
| | | } |
| | | } |
| | | </style> |