2026-07-08 16:17:50 +08:00

145 lines
8.4 KiB
Vue

<!-- 未来社区 - 活动详情 -->
<template>
<view class="ad-page">
<view class="fc-nav" :style="{ paddingTop: statusBarTop + 'px' }">
<view class="fc-nav-bar">
<view class="fc-back" @tap="goBack"><u-icon name="arrow-left" color="#1a1a1a" size="38"></u-icon></view>
<text class="fc-nav-title">活动详情</text>
<view class="ad-star" @tap="onFollow" v-if="act"><u-icon :name="followed ? 'star-fill' : 'star'" :color="followed ? '#ff9a12' : '#9098a0'" size="40"></u-icon></view>
</view>
</view>
<view class="ad-body" v-if="act">
<!-- 封面 -->
<view class="ad-banner">
<image v-if="act.image" :src="act.image" class="ad-banner-img" mode="aspectFill" />
<view v-else class="ad-banner-img" :style="{ background: sceneBg(act.scene) }"></view>
<text class="ad-badge" :style="statusStyle(act.status)">{{ statusLabel(act.status) }}</text>
</view>
<!-- 摘要 -->
<view class="ad-summary">
<text class="ad-title">{{ act.title }}</text>
<view class="ad-sum-row">
<view class="ad-dist"><u-icon name="map" color="#9098a0" size="24"></u-icon><text>{{ act.distance || '社区内' }}</text></view>
<view class="ad-call" v-if="act.phone" @tap="onCall"><u-icon name="phone" color="#e9272d" size="24"></u-icon><text>联系电话</text></view>
</view>
<text class="ad-addr">{{ act.address || act.place }}</text>
</view>
<!-- 活动说明 -->
<view class="ad-card">
<text class="ad-card-tt">活动说明</text>
<view class="ad-info-row"><u-icon name="account" color="#9098a0" size="26"></u-icon><text class="ad-lb">报名人数</text><text class="ad-vl">{{ act.enrolled || act.quota }}</text></view>
<view class="ad-info-row"><u-icon name="clock" color="#9098a0" size="26"></u-icon><text class="ad-lb">报名时间</text><text class="ad-vl">{{ act.signupRange || act.deadline }}</text></view>
<view class="ad-info-row"><u-icon name="calendar" color="#9098a0" size="26"></u-icon><text class="ad-lb">活动时间</text><text class="ad-vl">{{ act.time }}</text></view>
<view class="ad-info-row"><u-icon name="integral" color="#9098a0" size="26"></u-icon><text class="ad-lb">报名费用</text><text class="ad-vl">{{ act.fee || '免费' }}</text></view>
<view class="ad-info-row"><u-icon name="server-man" color="#9098a0" size="26"></u-icon><text class="ad-lb">组织方</text><text class="ad-vl">{{ act.organizer || '九九社区' }}</text></view>
<view class="ad-info-row"><u-icon name="phone" color="#9098a0" size="26"></u-icon><text class="ad-lb">联系电话</text><text class="ad-vl">{{ act.phone || '暂无' }}</text></view>
</view>
<!-- 活动介绍 -->
<view class="ad-card">
<text class="ad-card-tt">活动介绍</text>
<rich-text class="ad-content" :nodes="act.content"></rich-text>
<view class="ad-points" v-if="act.points && act.points.length">
<view class="ad-point" v-for="(p, i) in act.points" :key="i"><text class="ad-dot">•</text><text class="ad-pt">{{ p }}</text></view>
</view>
</view>
<!-- 参与须知 -->
<view class="ad-card">
<text class="ad-card-tt">参与须知</text>
<view class="ad-point" v-for="(p, i) in ACTIVITY_REGISTER_NOTICE" :key="i"><text class="ad-dot"></text><text class="ad-pt">{{ p }}</text></view>
</view>
<view class="fc-spacer"></view>
</view>
<view v-else class="fc-empty">活动不存在</view>
<view class="ad-foot" v-if="act">
<view class="ad-foot-fee"><text class="ad-foot-lb">报名费用</text><text class="ad-foot-vl">{{ act.fee || '免费' }}</text></view>
<text class="ad-join" :class="{ done: joined, ended: act.status === 'ended' }" @tap="onJoin">{{ joinLabel }}</text>
</view>
</view>
</template>
<script>
import { findActivity, ACTIVITY_STATUS_META, ACTIVITY_SCENE_BG, ACTIVITY_REGISTER_NOTICE, isActivityJoined, toggleActivityJoin, isActivityFollowed, toggleActivityFollow } from '../data.js'
export default {
data() { return { statusBarTop: 20, act: null, joined: false, followed: false, ACTIVITY_REGISTER_NOTICE } },
onLoad(options) {
const sys = uni.getSystemInfoSync()
this.statusBarTop = sys.statusBarHeight || 20
this.act = findActivity(options && options.id) || null
if (this.act) { this.joined = isActivityJoined(this.act.id); this.followed = isActivityFollowed(this.act.id) }
},
computed: {
joinLabel() {
if (!this.act) return ''
if (this.act.status === 'ended') return '活动已结束'
return this.joined ? '已报名 · 点击取消' : '立即报名'
}
},
methods: {
statusLabel(s) { return (ACTIVITY_STATUS_META[s] || {}).label || '' },
statusStyle(s) { const m = ACTIVITY_STATUS_META[s] || {}; return { color: m.color, background: m.bg } },
sceneBg(s) { return ACTIVITY_SCENE_BG[s] || ACTIVITY_SCENE_BG.handicraft },
goBack() { uni.navigateBack({ delta: 1 }) },
onCall() { uni.showToast({ title: '联系电话:' + this.act.phone, icon: 'none' }) },
onFollow() { this.followed = toggleActivityFollow(this.act.id); uni.showToast({ title: this.followed ? '已关注活动' : '已取消关注', icon: 'none' }) },
onJoin() {
if (this.act.status === 'ended') { uni.showToast({ title: '活动已结束', icon: 'none' }); return }
this.joined = toggleActivityJoin(this.act.id)
uni.showToast({ title: this.joined ? '报名成功' : '已取消报名', icon: 'none' })
}
}
}
</script>
<style scoped>
page { background: #f5f6f7; }
.ad-page { min-height: 100vh; background: #f5f6f7; padding-bottom: 140rpx; }
.fc-nav { position: sticky; top: 0; z-index: 100; background: #fff; padding: 0 24rpx; }
.fc-nav-bar { position: relative; display: flex; align-items: center; justify-content: center; height: 88rpx; }
.fc-back { position: absolute; left: 0; top: 0; height: 88rpx; display: flex; align-items: center; }
.fc-nav-title { font-size: 34rpx; font-weight: 700; color: #1a1a1a; }
.ad-star { position: absolute; right: 0; top: 0; height: 88rpx; display: flex; align-items: center; }
.ad-body { padding: 20rpx; }
.ad-banner { position: relative; border-radius: 20rpx; overflow: hidden; }
.ad-banner-img { width: 100%; height: 360rpx; display: block; }
.ad-badge { position: absolute; left: 20rpx; top: 20rpx; font-size: 22rpx; padding: 4rpx 16rpx; border-radius: 6rpx; }
.ad-summary { background: #fff; border-radius: 20rpx; padding: 26rpx; margin-top: 16rpx; }
.ad-title { font-size: 38rpx; font-weight: 800; color: #1a1a1a; }
.ad-sum-row { display: flex; align-items: center; justify-content: space-between; margin-top: 16rpx; }
.ad-dist { display: flex; align-items: center; font-size: 24rpx; color: #6b7078; }
.ad-dist text { margin-left: 8rpx; }
.ad-call { display: flex; align-items: center; font-size: 24rpx; color: #e9272d; }
.ad-call text { margin-left: 6rpx; }
.ad-addr { display: block; margin-top: 14rpx; font-size: 24rpx; color: #9098a0; }
.ad-card { background: #fff; border-radius: 20rpx; padding: 26rpx; margin-top: 16rpx; }
.ad-card-tt { display: block; font-size: 30rpx; font-weight: 700; color: #1a1a1a; margin-bottom: 18rpx; }
.ad-info-row { display: flex; align-items: center; padding: 14rpx 0; }
.ad-lb { width: 130rpx; margin-left: 14rpx; font-size: 26rpx; color: #9098a0; }
.ad-vl { flex: 1; font-size: 26rpx; color: #333; }
.ad-content { display: block; font-size: 28rpx; color: #333; line-height: 1.8; }
.ad-points { margin-top: 12rpx; }
.ad-point { display: flex; margin-top: 12rpx; }
.ad-dot { color: #e9272d; margin-right: 12rpx; font-size: 28rpx; line-height: 1.6; }
.ad-pt { flex: 1; font-size: 26rpx; color: #555; line-height: 1.6; }
.ad-foot { position: fixed; left: 0; right: 0; bottom: 0; background: #fff; display: flex; align-items: center; padding: 14rpx 24rpx calc(14rpx + env(safe-area-inset-bottom)); box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.04); }
.ad-foot-fee { flex: 1; display: flex; flex-direction: column; }
.ad-foot-lb { font-size: 20rpx; color: #9098a0; }
.ad-foot-vl { font-size: 34rpx; color: #e9272d; font-weight: 700; }
.ad-join { flex-shrink: 0; background: linear-gradient(90deg, #ff5b5b 0%, #ef111b 100%); color: #fff; font-size: 30rpx; font-weight: 700; padding: 20rpx 70rpx; border-radius: 44rpx; }
.ad-join.done { background: #fff0ee; color: #ef111b; }
.ad-join.ended { background: #eef0f3; color: #9aa0a8; }
.fc-empty { text-align: center; color: #9aa0a8; font-size: 26rpx; padding: 120rpx 0; }
.fc-spacer { height: 20rpx; }
</style>