|
|
@@ -0,0 +1,549 @@
|
|
|
+<template>
|
|
|
+ <view class="activity-detail-page">
|
|
|
+ <!-- 自定义导航栏 -->
|
|
|
+ <view class="custom-navbar">
|
|
|
+ <view class="navbar-left" @click="goBack">
|
|
|
+ <text class="back-icon">←</text>
|
|
|
+ </view>
|
|
|
+ <view class="navbar-title">活动详情</view>
|
|
|
+ <view class="navbar-right"></view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 活动封面 -->
|
|
|
+ <view class="activity-cover">
|
|
|
+ <image :src="activity.coverImage || activity.cover_image" class="cover-image" mode="aspectFill"></image>
|
|
|
+ <view class="cover-mask">
|
|
|
+ <view class="activity-tag" v-if="activity.isRecommended">⭐ 推荐活动</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 活动信息 -->
|
|
|
+ <view class="activity-content">
|
|
|
+ <view class="activity-title">{{ activity.name }}</view>
|
|
|
+
|
|
|
+ <view class="info-section">
|
|
|
+ <view class="info-item">
|
|
|
+ <text class="info-icon">⏰</text>
|
|
|
+ <text class="info-label">活动时间:</text>
|
|
|
+ <text class="info-value">{{ formatActivityTime(activity.startTime || activity.start_time, activity.endTime || activity.end_time) }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="info-item">
|
|
|
+ <text class="info-icon">📍</text>
|
|
|
+ <text class="info-label">活动地点:</text>
|
|
|
+ <text class="info-value">{{ activity.location || '待定' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="info-item">
|
|
|
+ <text class="info-icon">👥</text>
|
|
|
+ <text class="info-label">报名人数:</text>
|
|
|
+ <text class="info-value">{{ activity.participants || 0 }} / {{ activity.capacity || '不限' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="info-item">
|
|
|
+ <text class="info-icon">💎</text>
|
|
|
+ <text class="info-label">所需积分:</text>
|
|
|
+ <text class="info-value points">{{ activity.points || 0 }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="divider"></view>
|
|
|
+
|
|
|
+ <view class="description-section">
|
|
|
+ <view class="section-title">活动介绍</view>
|
|
|
+ <view class="description-text">{{ activity.description || '暂无介绍' }}</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="divider"></view>
|
|
|
+
|
|
|
+ <view class="content-section" v-if="activity.content">
|
|
|
+ <view class="section-title">活动详情</view>
|
|
|
+ <view class="content-text">{{ activity.content }}</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 底部兑换按钮 -->
|
|
|
+ <view class="bottom-bar" v-if="!hasExchanged">
|
|
|
+ <view class="points-info">
|
|
|
+ <text class="points-label">所需积分</text>
|
|
|
+ <view class="points-value">
|
|
|
+ <text class="points-symbol">💎</text>
|
|
|
+ <text class="points-num">{{ activity.points || 0 }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="exchange-btn"
|
|
|
+ :class="{ 'btn-disabled': currentPoints < (activity.points || 0) }"
|
|
|
+ @click="handleExchange">
|
|
|
+ <text class="btn-text">{{ currentPoints < (activity.points || 0) ? '积分不足' : '积分兑换报名' }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 已报名状态 -->
|
|
|
+ <view class="bottom-bar" v-else>
|
|
|
+ <view class="registered-status">
|
|
|
+ <text class="status-icon">✓</text>
|
|
|
+ <text class="status-text">已报名</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import api from '../../utils/api.js'
|
|
|
+
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ activityId: null,
|
|
|
+ activity: {
|
|
|
+ name: '加载中...',
|
|
|
+ coverImage: 'https://images.unsplash.com/photo-1511671782779-c97d3d27a1d4?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60',
|
|
|
+ points: 0
|
|
|
+ },
|
|
|
+ currentPoints: 0,
|
|
|
+ makerId: null,
|
|
|
+ hasExchanged: false
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ onLoad(options) {
|
|
|
+ if (options.id) {
|
|
|
+ this.activityId = options.id
|
|
|
+ this.initData()
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ methods: {
|
|
|
+ // 初始化数据
|
|
|
+ async initData() {
|
|
|
+ const userInfo = uni.getStorageSync('userInfo')
|
|
|
+ // 兼容多种字段名
|
|
|
+ this.makerId = userInfo && (userInfo.matchmakerId || userInfo.makerId || userInfo.matchmaker_id)
|
|
|
+
|
|
|
+ // 如果没有makerId,尝试通过userId获取
|
|
|
+ if (!this.makerId && userInfo && userInfo.userId) {
|
|
|
+ try {
|
|
|
+ const res = await api.matchmaker.getByUserId(userInfo.userId)
|
|
|
+ let matchmaker = res
|
|
|
+ if (res && res.data) {
|
|
|
+ matchmaker = res.data
|
|
|
+ }
|
|
|
+ if (matchmaker) {
|
|
|
+ this.makerId = matchmaker.matchmakerId || matchmaker.matchmaker_id
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('获取红娘信息失败:', e)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ await this.loadMakerInfo()
|
|
|
+ await this.loadActivityDetail()
|
|
|
+ await this.checkExchangeStatus()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载红娘信息(包括积分)
|
|
|
+ async loadMakerInfo() {
|
|
|
+ try {
|
|
|
+ const res = await api.matchmaker.getDetail(this.makerId)
|
|
|
+ console.log('红娘信息返回:', res)
|
|
|
+ // 兼容不同的返回格式
|
|
|
+ let makerInfo = res
|
|
|
+ if (res && res.data) {
|
|
|
+ makerInfo = res.data
|
|
|
+ }
|
|
|
+ if (makerInfo) {
|
|
|
+ // 兼容 snake_case 和 camelCase
|
|
|
+ this.currentPoints = makerInfo.points ?? makerInfo.total_points ?? 0
|
|
|
+ console.log('当前积分:', this.currentPoints)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载红娘信息失败:', error)
|
|
|
+ this.currentPoints = 0
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载活动详情
|
|
|
+ async loadActivityDetail() {
|
|
|
+ try {
|
|
|
+ const data = await api.matchmakerActivity.getDetail(this.activityId)
|
|
|
+ if (data) {
|
|
|
+ this.activity = data
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载活动详情失败:', error)
|
|
|
+ uni.showToast({
|
|
|
+ title: '加载失败',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 检查是否已兑换
|
|
|
+ async checkExchangeStatus() {
|
|
|
+ try {
|
|
|
+ console.log('检查兑换状态, makerId:', this.makerId, 'activityId:', this.activityId)
|
|
|
+ const res = await api.matchmakerActivity.getPurchasedList(this.makerId)
|
|
|
+ console.log('已兑换活动列表返回:', res)
|
|
|
+ // 兼容不同的返回格式
|
|
|
+ let list = res
|
|
|
+ if (res && res.data) {
|
|
|
+ list = res.data
|
|
|
+ }
|
|
|
+ if (Array.isArray(list) && list.length > 0) {
|
|
|
+ this.hasExchanged = list.some(item => {
|
|
|
+ const itemActivityId = item.activity_id || item.activityId
|
|
|
+ console.log('比较活动ID:', itemActivityId, '==', this.activityId)
|
|
|
+ return itemActivityId == this.activityId
|
|
|
+ })
|
|
|
+ }
|
|
|
+ console.log('是否已兑换:', this.hasExchanged)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('检查兑换状态失败:', error)
|
|
|
+ this.hasExchanged = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 格式化活动时间
|
|
|
+ formatActivityTime(startTime, endTime) {
|
|
|
+ if (!startTime) return '待定'
|
|
|
+
|
|
|
+ const start = new Date(startTime)
|
|
|
+ const end = endTime ? new Date(endTime) : null
|
|
|
+
|
|
|
+ const startStr = start.toLocaleDateString('zh-CN') + ' ' +
|
|
|
+ start.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
|
|
+
|
|
|
+ if (end) {
|
|
|
+ // 判断是否同一天
|
|
|
+ const isSameDay = start.toDateString() === end.toDateString()
|
|
|
+ let endStr
|
|
|
+ if (isSameDay) {
|
|
|
+ // 同一天只显示时间
|
|
|
+ endStr = end.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
|
|
+ } else {
|
|
|
+ // 不同天显示完整日期时间
|
|
|
+ endStr = end.toLocaleDateString('zh-CN') + ' ' +
|
|
|
+ end.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
|
|
+ }
|
|
|
+ return `${startStr} - ${endStr}`
|
|
|
+ }
|
|
|
+
|
|
|
+ return startStr
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理兑换
|
|
|
+ async handleExchange() {
|
|
|
+ if (this.currentPoints < (this.activity.points || 0)) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '积分不足',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ uni.showModal({
|
|
|
+ title: '确认兑换',
|
|
|
+ content: `是否用 ${this.activity.points} 积分兑换"${this.activity.name}"活动?`,
|
|
|
+ confirmText: '确认',
|
|
|
+ cancelText: '取消',
|
|
|
+ success: (res) => {
|
|
|
+ if (res.confirm) {
|
|
|
+ this.exchangeActivity()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 兑换活动
|
|
|
+ async exchangeActivity() {
|
|
|
+ try {
|
|
|
+ const result = await api.matchmakerActivity.exchange({
|
|
|
+ makerId: this.makerId,
|
|
|
+ activityId: this.activityId,
|
|
|
+ points: this.activity.points
|
|
|
+ })
|
|
|
+
|
|
|
+ if (result) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '兑换成功',
|
|
|
+ icon: 'success'
|
|
|
+ })
|
|
|
+ this.currentPoints -= this.activity.points
|
|
|
+ this.hasExchanged = true
|
|
|
+
|
|
|
+ // 更新参与人数
|
|
|
+ if (this.activity.participants) {
|
|
|
+ this.activity.participants++
|
|
|
+ } else {
|
|
|
+ this.activity.participants = 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('兑换失败:', error)
|
|
|
+ uni.showToast({
|
|
|
+ title: error.message || '兑换失败',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 返回
|
|
|
+ goBack() {
|
|
|
+ uni.navigateBack()
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+ .activity-detail-page {
|
|
|
+ min-height: 100vh;
|
|
|
+ background-color: #FFF9F9;
|
|
|
+ padding-top: 90rpx;
|
|
|
+ padding-bottom: 120rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 自定义导航栏 */
|
|
|
+ .custom-navbar {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ height: 90rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 0 20rpx;
|
|
|
+ background: linear-gradient(135deg, #FCE4EC 0%, #F8BBD0 100%);
|
|
|
+ z-index: 999;
|
|
|
+
|
|
|
+ .navbar-left,
|
|
|
+ .navbar-right {
|
|
|
+ width: 80rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .back-icon {
|
|
|
+ font-size: 40rpx;
|
|
|
+ color: #333333;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ .navbar-title {
|
|
|
+ flex: 1;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 活动封面 */
|
|
|
+ .activity-cover {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 400rpx;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .cover-image {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background-color: #F5F5F5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .cover-mask {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ justify-content: flex-start;
|
|
|
+ padding: 20rpx;
|
|
|
+ background: linear-gradient(180deg, rgba(0, 0, 0, 0.3) 0%, transparent 100%);
|
|
|
+
|
|
|
+ .activity-tag {
|
|
|
+ background-color: rgba(255, 215, 0, 0.9);
|
|
|
+ color: #333333;
|
|
|
+ padding: 8rpx 16rpx;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 活动内容 */
|
|
|
+ .activity-content {
|
|
|
+ padding: 30rpx 20rpx;
|
|
|
+
|
|
|
+ .activity-title {
|
|
|
+ font-size: 36rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333333;
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-section {
|
|
|
+ background-color: #FFFFFF;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ padding: 20rpx;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+
|
|
|
+ .info-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-icon {
|
|
|
+ font-size: 28rpx;
|
|
|
+ margin-right: 12rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-label {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #666666;
|
|
|
+ min-width: 120rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-value {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #333333;
|
|
|
+ flex: 1;
|
|
|
+
|
|
|
+ &.points {
|
|
|
+ color: #FF6B8A;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .divider {
|
|
|
+ height: 1rpx;
|
|
|
+ background-color: #EEEEEE;
|
|
|
+ margin: 20rpx 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .description-section,
|
|
|
+ .content-section {
|
|
|
+ background-color: #FFFFFF;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ padding: 20rpx;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+
|
|
|
+ .section-title {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333333;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .description-text,
|
|
|
+ .content-text {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #666666;
|
|
|
+ line-height: 1.6;
|
|
|
+ white-space: pre-wrap;
|
|
|
+ word-break: break-word;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 底部栏 */
|
|
|
+ .bottom-bar {
|
|
|
+ position: fixed;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ height: 120rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 20rpx;
|
|
|
+ background-color: #FFFFFF;
|
|
|
+ border-top: 1rpx solid #EEEEEE;
|
|
|
+ z-index: 100;
|
|
|
+
|
|
|
+ .points-info {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+
|
|
|
+ .points-label {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #999999;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .points-value {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8rpx;
|
|
|
+
|
|
|
+ .points-symbol {
|
|
|
+ font-size: 28rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .points-num {
|
|
|
+ font-size: 32rpx;
|
|
|
+ color: #FF6B8A;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .exchange-btn {
|
|
|
+ flex: 1;
|
|
|
+ height: 80rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: linear-gradient(135deg, #9C27B0 0%, #7B1FA2 100%);
|
|
|
+ color: #FFFFFF;
|
|
|
+ border-radius: 40rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ margin-left: 20rpx;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ transform: scale(0.95);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.btn-disabled {
|
|
|
+ background: #CCCCCC;
|
|
|
+ color: #999999;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-text {
|
|
|
+ color: inherit;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .registered-status {
|
|
|
+ flex: 1;
|
|
|
+ height: 80rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background-color: #E8F5E9;
|
|
|
+ border-radius: 40rpx;
|
|
|
+ margin-left: 20rpx;
|
|
|
+
|
|
|
+ .status-icon {
|
|
|
+ font-size: 40rpx;
|
|
|
+ color: #4CAF50;
|
|
|
+ margin-right: 12rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-text {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #4CAF50;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|