Files
property-resident-side/property-uniapp-project/pageSubPack/activity-list/activityListDetails.vue
2026-06-09 18:06:38 +08:00

889 lines
20 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="container">
<view class="status_bar"></view>
<!-- 顶部导航栏 -->
<view class="nav-bar">
<view class="nav-left" @tap="goBack">
<uni-icons type="left" size="24" color="#000"></uni-icons>
</view>
<view class="nav-title">{{navTitle}}</view>
<view class="nav-right"></view>
</view>
<!-- 内容区域 -->
<scroll-view class="scroll-content" scroll-y="true">
<!-- 选项卡 -->
<view class="tabs">
<view
class="tab-item"
:class="{ 'active': activeTab === 'detail' }"
@tap="switchTab('detail')"
>
活动详情
</view>
<view
class="tab-item"
:class="{ 'active': activeTab === 'status' }"
@tap="switchTab('status')"
>
活动实况
</view>
</view>
<!-- 活动详情内容 -->
<view v-if="activeTab === 'detail'" class="detail-content">
<!-- 基本信息 -->
<view class="info-section">
<view class="info-item">
<view class="info-label">报名截止时间</view>
<view class="info-value">{{formatTime(activityConent.applyDeadline)}}</view>
</view>
<view class="info-item">
<view class="info-label">活动时间</view>
<view class="info-value">
<text>{{formatTime(activityConent.startTime)}} {{formatTime(activityConent.endTime)}}</text>
</view>
</view>
<view class="info-item">
<view class="info-label">签到时间</view>
<view class="info-value">
<text>{{formatTime(activityConent.signinStartTime)}} {{formatTime(activityConent.signinEndTime)}}</text>
</view>
</view>
<view class="info-item">
<view class="info-label">活动地点</view>
<view class="info-value">{{activityConent.address}}</view>
</view>
<view class="info-item">
<view class="info-label">活动名额</view>
<view class="info-value">{{activityConent.quota}}</view>
</view>
<view class="info-item">
<view class="info-label">主办方</view>
<view class="info-value">{{activityConent.organizer}}</view>
</view>
<view class="info-item">
<view class="info-label">联系人</view>
<view class="info-value">{{activityConent.contactName}}</view>
</view>
<view class="info-item">
<view class="info-label">联系电话</view>
<view class="info-value">{{activityConent.contactPhone}}</view>
</view>
<!-- <view class="info-item">
<view class="info-label">积分奖励</view>
<view class="info-value">{{activityConent.pointsReward}}</view>
</view> -->
</view>
<!-- 招募要求 -->
<view class="section">
<view class="section-title">招募要求</view>
<view class="section-content">
{{activityConent.requirements}}
</view>
</view>
<!-- 活动详情 -->
<view class="section" style="margin-bottom: 40rpx;">
<view class="section-title">活动详情</view>
<view class="section-content">
<view v-html="activityConent.content"></view>
<view></view>
</view>
</view>
</view>
<!-- 活动实况内容 -->
<view v-else class="status-content">
<view class="status-header">
<view class="status-title">
<image src="http://47.104.199.163:9090/images/activity/liveScene.png" style="width: 38rpx;height: 23rpx;"></image>
现场实况
</view>
<view class="status-count">{{ liveRecords.length }}</view>
</view>
<!-- 实况列表 -->
<view class="record-list">
<view v-for="(record, index) in liveRecords" :key="index" class="record-item">
<view class="record-user">
<view class="user-avatar">
<uni-icons type="person" size="20" color="#999"></uni-icons>
</view>
<view class="user-info">
<view class="user-name">微信用户</view>
<view class="record-time">{{ record.time }}</view>
<view class="record-content">{{ record.content }}</view>
<!-- 图片展示 -->
<view v-if="record.images && record.images.length > 0" class="record-images">
<view
v-for="(img, imgIndex) in record.images"
:key="imgIndex"
class="image-item"
:style="{ backgroundImage: `url(${img})` }"
>
<image :src="img" style="width: 100%;height: 100%;"></image>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<!-- 未报名状态显示报名按钮 -->
<button
v-if="!hasSigned && activeTab === 'detail'"
class="action-btn signup-btn"
@tap="handleSignup"
>
报名
</button>
<!-- 已报名状态显示取消报名按钮 -->
<button
v-if="hasSigned && activeTab === 'detail'"
class="action-btn cancel-btn"
@tap="handleCancelSignup"
>
取消报名
</button>
<!-- 活动实况页显示添加实况按钮 -->
<button class="action-btn add-status-btn" v-if="hasSigned &&activeTab === 'status'" @click="liveClick">添加实况</button>
</view>
<!-- 报名确认模态框 -->
<view class="modal" :class="{ 'show': showSignupModal }">
<view class="modal-content">
<view class="modal-header">报名确认</view>
<view class="modal-body">
<view>{{navTitle}}</view>
<view class="modal-tip">报名后请按时参加活动</view>
</view>
<view class="modal-footer">
<view class="modal-btn cancel" @tap="hideSignupModal">取消</view>
<view class="modal-btn confirm" @tap="confirmSignup">确定</view>
</view>
</view>
</view>
<!-- 取消报名确认模态框 -->
<view class="modal" :class="{ 'show': showCancelModal }">
<view class="modal-content">
<view class="modal-header">取消报名</view>
<view class="modal-body">
<view>{{navTitle}}</view>
<view class="modal-tip">取消后如需参加需重新报名</view>
</view>
<view class="modal-footer">
<view class="modal-btn cancel" @tap="hideCancelModal">再想想</view>
<view class="modal-btn confirm" @tap="confirmCancel">确定取消</view>
</view>
</view>
</view>
<!-- 提示信息 -->
<view class="toast" :class="{ 'show': showToast }">
{{ toastMessage }}
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import {onLoad,onUnload} from '@dcloudio/uni-app'
import moment from 'moment'
import {getActivityDetails,confirmResident,cancelActivity,getActivityLive} from '../api/api.js'
// 响应式数据
const activeTab = ref('detail') // 'detail' 或 'status'
const hasSigned = ref(false) // 是否已报名
const showSignupModal = ref(false)
const showCancelModal = ref(false)
const showToast = ref(false)
const toastMessage = ref('')
const newStatusContent = ref('')
const navTitle = ref('')
const curActivityId = ref('') // 当前活动的id
const activityConent = ref({}) // 获取详情数据
// 实况记录数据
const liveRecords = ref([
// {
// id: 1,
// user: '微信用户',
// time: '2023-08-20 16:00:02',
// content: '参与活动打卡',
// images: []
// },
// {
// id: 2,
// user: '微信用户',
// time: '2023-08-20 16:20:02',
// content: '目前现场的互动氛围很好',
// images: ['http://47.104.199.163:9090/images/logo.png', 'http://47.104.199.163:9090/images/repair/house1.png']
// }
])
// ========方法=====
// 时间格式化函数
const formatTime = (timeStr) => {
if (!timeStr) return ''
return moment(timeStr).format('YYYY-MM-DD HH:mm')
}
// ---接收参数---
onLoad((option) =>{
navTitle.value = option?.title
curActivityId.value = option?.id
// 处理applied参数可能是字符串类型需要转布尔
hasSigned.value = option?.applied === 'true' || option?.applied === true
// console.log('hasSigned.value',hasSigned.value)
// 2. 初始化数据
if (curActivityId.value) {
getActivityDetailsData(curActivityId.value) // 获取活动详情
getLiveActivityList() // 获取实况列表
}
// 3. 监听实况列表刷新事件
uni.$on('refreshLiveList', () => {
getLiveActivityList()
})
})
// 获取列表详情
const getActivityDetailsData = async (id) =>{
try{
const res = await getActivityDetails(id)
if(res.code === 200){
activityConent.value = res.data || {}
}else{
activityConent.value = {}
uni.showToast({
title: res.msg ||"详情获取失败",
icon:"error"
})
}
}catch(err){
uni.showToast({
title: "网络异常",
icon:"none"
})
// console.error('获取活动详情失败:', err)
}
}
// 切换选项卡
const switchTab = (tab) => {
activeTab.value = tab
}
// 返回上一页
const goBack = () => {
uni.navigateBack()
}
// 处理报名
const handleSignup = () => {
showSignupModal.value = true
}
// 隐藏报名模态框
const hideSignupModal = () => {
showSignupModal.value = false
}
// 确认报名
const confirmSignup = () => {
confirmRegist()
hideSignupModal()
}
// 确认报名接口
const confirmRegist = async () =>{
try{
const res = await confirmResident(curActivityId.value)
if(res.code === 200){
hasSigned.value = true // 报名成功后更新状态
uni.showToast({
title: '报名成功',
icon:"success"
})
setTimeout(() =>{
uni.$emit('refreshActivityList', {
activityId: curActivityId.value,
isSigned: true
})
uni.navigateBack()
},1500)
}else{
uni.showToast({
title: res.msg || "报名失败",
icon:"error"
})
}
}catch(err){
uni.showToast({
title: "网络异常",
icon:"none"
})
// console.error('报名失败:', err)
}
}
// 处理取消报名
const handleCancelSignup = () => {
showCancelModal.value = true
}
// 隐藏取消报名模态框
const hideCancelModal = () => {
showCancelModal.value = false
}
// 确认取消报名
const confirmCancel = () => {
hasSigned.value = false // 取消报名后更新状态
cancelActivityData()
hideCancelModal()
// showToastMessage('已取消报名')
}
// 确认取消报名接口
const cancelActivityData = async () =>{
try{
const res = await cancelActivity(curActivityId.value)
if(res.code === 200){
hasSigned.value = false // 报名成功后更新状态
uni.showToast({
title: '取消报名成功',
icon:"success"
})
setTimeout(() =>{
uni.$emit('refreshActivityList', {
activityId: curActivityId.value,
isSigned: false
})
uni.navigateBack()
},1500)
}else{
uni.showToast({
title: res.msg || "取消报名失败",
icon:"error"
})
}
}catch(err){
uni.showToast({
title: "网络异常",
icon:"none"
})
}
}
// -----获取活动实况列表----
const getLiveActivityList = async () =>{
try{
const res = await getActivityLive(curActivityId.value)
if(res.code === 200){
liveRecords.value = res.rows || []
}else{
liveRecords.value = []
uni.showToast({
title: res.msg ||"实况列表获取失败",
icon:"error"
})
}
}catch(err){
uni.showToast({
title: "网络异常",
icon:"none"
})
}
}
// 添加实况跳转
const liveClick = () =>{
uni.navigateTo({
url:`/pageSubPack/activity-list/live-add?id=${curActivityId.value}`
})
}
// 监听实况列表刷新
// ---------
onUnload(() => {
uni.$off('refreshLiveList')
})
// 显示提示信息
const showToastMessage = (message) => {
toastMessage.value = message
showToast.value = true
setTimeout(() => {
showToast.value = false
}, 2000)
}
</script>
<style scoped>
/* 页面容器 */
.container {
height: 100vh;
display: flex;
flex-direction: column;
background: linear-gradient(to bottom left, #E2F0FF 0%, #ffffff 50%);
overflow: hidden;
}
/* 状态栏 */
.status_bar {
height: 46px;
width: 100%;
flex-shrink: 0;
}
/* 顶部导航栏 */
.nav-bar {
height: 90rpx;
display: flex;
align-items: center;
padding: 0 30rpx;
color: #fff;
position: sticky;
top: 44px;
z-index: 100;
flex-shrink: 0;
}
.nav-left {
width: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.nav-title {
flex: 1;
text-align: left;
font-size: 32rpx;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 20rpx;
color: #000;
}
.nav-right {
width: 60rpx;
}
/* 滚动内容区域 */
.scroll-content {
flex: 1;
width: 100%;
box-sizing: border-box;
overflow-y: auto;
}
/* 选项卡 */
.tabs {
display: flex;
border-bottom: 1rpx solid #e9ecef;
}
.tab-item {
text-align: center;
padding: 30rpx 35rpx;
font-size: 32rpx;
color: #666;
position: relative;
transition: all 0.3s;
}
.tab-item.active {
color: #007AFF;
font-weight: 600;
}
/* 活动详情内容 */
.detail-content {
padding: 0;
}
/* 基本信息区域 */
.info-section {
border-radius: 16rpx;
padding: 30rpx 40rpx;
}
.info-item {
display: flex;
margin-bottom: 30rpx;
padding-bottom: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.info-item:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.info-label {
width: 220rpx;
font-size: 28rpx;
color: #222;
flex-shrink: 0;
}
.info-value {
flex: 1;
font-size: 28rpx;
color: #222;
font-weight: 500;
line-height: 1.4;
text-align: right;
}
/* 区块样式 */
.section {
border-radius: 16rpx;
padding: 30rpx 40rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.section-title {
font-size: 32rpx;
color: #333;
font-weight: 600;
margin-bottom: 20rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.section-content {
font-size: 28rpx;
color: #666;
line-height: 1.6;
}
.section-content view {
margin-bottom: 20rpx;
}
.section-content view:last-child {
margin-bottom: 0;
}
/* 活动实况内容 */
.status-content {
padding: 30rpx;
}
/* 实况头部 */
.status-header {
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 30rpx;
}
.status-title {
font-size: 32rpx;
color: #333;
font-weight: 600;
border-bottom: 1rpx solid #f0f0f0;
}
.status-count {
padding: 8rpx 20rpx;
color: #666;
font-size: 28rpx;
border-radius: 20rpx;
font-weight: 500;
}
/* 实况列表 */
.record-list {
border-radius: 16rpx;
overflow: hidden;
}
.record-item {
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.record-user {
display: flex;
align-items: flex-start;
justify-content: flex-start;
margin-bottom: 20rpx;
}
.user-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background-color: #f8f9fa;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
border: 1rpx solid #e9ecef;
}
.user-info {
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.user-name {
font-size: 28rpx;
color: #333;
font-weight: 500;
margin-bottom: 8rpx;
}
.record-time {
font-size: 24rpx;
color: #999;
}
.record-content {
font-size: 28rpx;
color: #333;
line-height: 1.5;
margin: 25rpx 0rpx;
}
/* 实况图片 */
.record-images {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.image-item {
width: 150rpx;
height: 150rpx;
border-radius: 12rpx;
background-color: #f8f9fa;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
border: 1rpx solid #e9ecef;
}
/* 底部操作栏 */
.bottom-bar {
padding: 20rpx 30rpx 40rpx;
background-color: #fff;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
flex-shrink: 0;
}
.action-btn {
height: 100rpx;
line-height: 100rpx;
border-radius: 12rpx;
font-size: 34rpx;
font-weight: 600;
width: 100%;
border: none;
transition: all 0.3s;
}
.signup-btn {
background: linear-gradient(135deg, #007AFF 0%, #0056cc 100%);
color: #fff;
box-shadow: 0 8rpx 30rpx rgba(0, 122, 255, 0.3);
}
.signup-btn:active {
background: linear-gradient(135deg, #0056cc 0%, #004199 100%);
transform: translateY(2rpx);
box-shadow: 0 4rpx 20rpx rgba(0, 122, 255, 0.2);
}
.cancel-btn {
background: linear-gradient(135deg, #fff 0%, #f8f9fa 100%);
color: #666;
border: 1rpx solid #e9ecef;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.cancel-btn:active {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
transform: translateY(2rpx);
}
.add-status-btn {
background: linear-gradient(135deg, #007AFF 0%, #0056cc 100%);
color: #fff;
box-shadow: 0 8rpx 30rpx rgba(0, 122, 255, 0.3);
}
.add-status-btn:active {
background: linear-gradient(135deg, #0056cc 0%, #004199 100%);
transform: translateY(2rpx);
box-shadow: 0 4rpx 20rpx rgba(0, 122, 255, 0.2);
}
/* 模态框 */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
align-items: center;
justify-content: center;
backdrop-filter: blur(5rpx);
}
.modal.show {
display: flex;
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.modal-content {
background-color: #fff;
border-radius: 20rpx;
width: 600rpx;
overflow: hidden;
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.2);
animation: slideUp 0.3s;
}
@keyframes slideUp {
from { transform: translateY(100rpx); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.modal-header {
padding: 40rpx 30rpx 20rpx;
text-align: center;
font-size: 36rpx;
font-weight: 600;
color: #333;
border-bottom: 1rpx solid #f0f0f0;
}
.modal-body {
padding: 40rpx 30rpx;
text-align: center;
font-size: 30rpx;
color: #666;
line-height: 1.5;
}
.modal-tip {
font-size: 24rpx;
color: #999;
margin-top: 20rpx;
}
.modal-footer {
display: flex;
border-top: 1rpx solid #f0f0f0;
}
.modal-btn {
flex: 1;
padding: 30rpx 0;
text-align: center;
font-size: 32rpx;
font-weight: 500;
transition: all 0.2s;
}
.modal-btn.cancel {
color: #666;
border-right: 1rpx solid #f0f0f0;
}
.modal-btn.cancel:active {
background-color: #f8f9fa;
}
.modal-btn.confirm {
color: #007AFF;
font-weight: 600;
}
.modal-btn.confirm:active {
background-color: #f0f7ff;
}
/* 提示信息 */
.toast {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.8);
color: #fff;
padding: 20rpx 40rpx;
border-radius: 10rpx;
font-size: 28rpx;
z-index: 1100;
display: none;
backdrop-filter: blur(10rpx);
}
.toast.show {
display: block;
animation: toastFade 2s;
}
@keyframes toastFade {
0% { opacity: 0; transform: translate(-50%, -40%); }
20% { opacity: 1; transform: translate(-50%, -50%); }
80% { opacity: 1; transform: translate(-50%, -50%); }
100% { opacity: 0; transform: translate(-50%, -60%); }
}
/* 适配不同屏幕 */
@media (min-width: 768px) {
.container {
max-width: 500px;
margin: 0 auto;
}
}
</style>