|
|
<template> <view class="activity-detail"> <image class="activity-img" :src="detail.coverImage||detail.cover_image" mode="aspectFill"></image> <view class="activity-base"> <text class="title">{{ detail.title }}</text> <view class="time"> <uni-icons class="detail-icon" custom-prefix="iconfont" type="icon-shijian" size="15"></uni-icons> <text>{{ detail.startTime || formatTime(detail.start_time) }} - {{ detail.endTime || formatTime(detail.end_time) }}</text> </view> <view class="location"> <uni-icons class="detail-icon" type="location" size="20"></uni-icons> <text>{{ detail.activityLocation || detail.activity_location }}</text> </view> </view> <view class="rich-content"> <text class="content-title">活动介绍/回顾</text> <div v-if="detail.localImg" style="text-align:center; margin:12px 0;"> <image :src="require(`@/static/${detail.localImg}`)" mode="widthFix" style="width:90%; border-radius:8px;"></image> </div> <rich-text class="rich-text" :nodes="contentNodes"></rich-text> </view>
<view class="detail-bottom"> <view class="detail-left"> <button open-type="share" class="handle-btn"> <uni-icons custom-prefix="iconfont" type="icon-fenxiang01" size="20"></uni-icons> <text>分享</text> </button> </view> <button class="join-btn" :class="getJoinBtnClass()" @click="handleJoin" > {{ getJoinBtnText() }} </button> </view>
<view class="modal-mask" v-if="showJoinModal" @click="closeJoinModal"> <view class="modal-content" @click.stop> <view class="modal-header"> <text class="modal-title">报名信息</text> <uni-icons class="close-icon" type="close" size="24" @click="closeJoinModal"></uni-icons> </view> <view class="modal-body"> <view class="form-item"> <text class="form-label">姓名</text> <input class="form-input" v-model="joinForm.name" placeholder="请输入您的姓名" /> </view> <view class="form-item"> <text class="form-label">联系方式</text> <view style="display:flex; justify-content: space-between; align-items:center;"> <input class="form-input" v-model="joinForm.phone" placeholder="请输入手机号码" type="number" maxlength="11" /> <button class="get-phone-btn" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber"> 一键获取 </button> </view> </view> <view class="form-item"> <text class="form-label">参加人数</text> <view class="select-wrapper"> <picker mode="selector" :range="rangeText" :value="joinForm.personCount - 1" @change="onPickerChange" class="picker-select" > <view class="picker-display"> {{ joinForm.personCountText || '请选择参加人数' }} </view> </picker> </view> </view> </view> <view class="modal-footer"> <button class="cancel-btn" @click="closeJoinModal">取消</button> <button class="submit-btn" @click="submitJoin">确认报名</button> </view> </view> </view> </view></template>
<script>// import config from '@/utils/config';
// import { getOpenId } from '@/utils/storage';
import { FetchSessionKey, FetchDecryptPhone } from '@/api/user.js';import { FetchSignActivity, FetchCancelSignActivity } from '@/api/activity.js';
export default { data() { return { detail: {}, activityId: "", contentNodes: "", showJoinModal: false, joinForm: { name: "", phone: "", personCount: 1, personCountText: "" }, personOptions: [ { value: 1, text: "1人" }, { value: 2, text: "2人" }, { value: 3, text: "3人" }, { value: 4, text: "4人" }, { value: 5, text: "5人" }, ], rangeText: [], sessionKey: "", openid: "" }; }, onLoad(options) { console.log(options); const item = JSON.parse(decodeURIComponent(options.item)); this.detail = item; console.log('this.detail',this.detail); this.activityId = item.id || item.activityId; this.formatContent();
this.rangeText = this.personOptions.map(item => item.text); this.joinForm.personCount = this.personOptions[0].value; this.joinForm.personCountText = this.personOptions[0].text;
this.getSessionKey(); },
methods: { formatContent() { let content = this.detail.activityContent || this.detail.activity_content || ""; if (!content) { this.contentNodes = ""; return; } content = content.replace(/&/g, '&');
const imgReg = /<img\s+[^>]*src\s*=\s*["']?([^"'> ]+)["']?[^>]*>/gi; content = content.replace(imgReg, (match, src) => { return `<div><img src="${src}" style="width:100%;max-width:750px;display:block;margin:0 auto;"></div>`; });
// ========== p标签:原有样式保留,缺少line-height自动补充,统一margin ==========
const pHasStyleReg = /<p(\s+[^>]*?)style\s*=\s*["']([^"']+)["']([^>]*)>/gi; content = content.replace(pHasStyleReg, (match, before, oldStyleStr, after) => { const hasLineHeight = /line-height\s*:/.test(oldStyleStr); let append = 'margin:15px 0;'; if (!hasLineHeight) { append = 'line-height:1.5;margin:15px 0;'; } const newStyle = oldStyleStr + ';' + append; return `<p${before}style="${newStyle}"${after}>`; }); const pNoStyleReg = /<p(?!\s+style\s*=)([^>]*)>/gi; content = content.replace(pNoStyleReg, '<p$1 style="line-height:1.5;margin:15px 0;">');
// ========== blockquote 引用样式 ==========
content = content.replace(/<blockquote([^>]*)>/gi, `<blockquote$1 style="background:#f5f7fa;border-left:8px solid #01a4fe;display:block;font-size:100%;line-height:1.5;margin:10px 0;padding:10px;">`);
// ========== 表格基础样式 ==========
content = content.replace(/<table([^>]*)>/gi, `<table$1 style="width:100% !important;border-collapse:collapse;margin:10px 0;">`); content = content.replace(/<th([^>]*)>/gi, `<th$1 style="border:1px solid #dcdfe6;padding:8px 12px;text-align:center;font-size:14px;">`); content = content.replace(/<td([^>]*)>/gi, `<td$1 style="border:1px solid #dcdfe6;padding:8px 12px;text-align:center;font-size:14px;">`);
this.contentNodes = content; }, onPickerChange(e) { const index = e.detail.value; this.joinForm.personCount = this.personOptions[index].value; this.joinForm.personCountText = this.personOptions[index].text; }, async getSessionKey() { try { uni.login({ success: async (loginRes) => { console.log('loginRes',loginRes) if (loginRes.code) { const res = await FetchSessionKey({ libcode: getApp().globalData.libcode, code: loginRes.code }); if (res.code === 200) { this.sessionKey = res.data.session_key; this.openid = res.data.openid; } } } }); } catch (error) { console.error('获取sessionKey失败:', error); } }, getPhoneNumber(e) { if (e.detail.errMsg !== 'getPhoneNumber:ok') { uni.showToast({ title: '取消授权', icon: 'none' }); return; } if (!this.sessionKey) { uni.showToast({ title: '请稍后再试', icon: 'none' }); return; } FetchDecryptPhone({ sessionKey: this.sessionKey, encryptedData: e.detail.encryptedData, iv: e.detail.iv }).then(res => { if (res.code === 200) { this.joinForm.phone = res.data.phoneNumber || ''; uni.showToast({ title: '获取成功', icon: 'success' }); } else { uni.showToast({ title: res.message || '获取失败', icon: 'none' }); } }).catch(() => { uni.showToast({ title: '获取手机号失败', icon: 'none' }); }); }, getJoinBtnClass() { const d = this.detail; // 已结束 / 已取消 禁用
if (d.status === '已结束' || d.status === '已取消') { return 'disabled-btn' } return '' }, // 获取按钮文字
getJoinBtnText() { const d = this.detail; if(d.status === '已结束') return '活动结束' if(d.status === '已取消') return '活动取消' // 未开始,有预约记录,没有取消,显示取消预约
if(d.status === '未开始' && d.isCancel === false && !!d.signId){ return '取消预约' } return '我要参加' }, handleJoin() { const d = this.detail // 已结束、已取消直接拦截
if (['已结束', '已取消'].includes(d.status)) { return; } // 未开始、有预约记录、未取消:执行取消报名
if(d.status === '未开始' && d.isCancel === false && !!d.signId){ uni.showModal({ title: '提示', content: `确定要取消活动【${d.title}】的预约吗?`, success: async (res) => { if(res.confirm){ try { const apiRes = await FetchCancelSignActivity({ id: d.signId }) if(apiRes.code === 200){ uni.showToast({ title: '取消成功', icon: 'success' }) // 发送全局事件,告诉预约列表页需要刷新
uni.$emit('refreshMySignList') setTimeout(() => { uni.navigateBack() }, 700) }else{ uni.showToast({ title: apiRes.message || '取消失败', icon: 'none' }) } } catch (err) { console.error('取消报名异常', err) uni.showToast({ title: '网络异常,取消失败', icon: 'none' }) } } } }) return } //打开报名弹窗
this.openJoinModal(); }, closeJoinModal() { this.showJoinModal = false; }, onPersonChange(e) { this.joinForm.personCount = e.detail.value + 1; }, openJoinModal() { // 每次打开弹窗完整重置表单,包含picker显示文本
this.joinForm = { name: "", phone: "", personCount: 1, personCountText: "1人" } this.showJoinModal = true }, submitJoin() { if (!this.joinForm.name) { uni.showToast({ title: '请输入姓名', icon: 'none' }); return; } if (!this.joinForm.phone) { uni.showToast({ title: '请输入联系方式', icon: 'none' }); return; } const phoneReg = /^1[3-9]\d{9}$/; if (!phoneReg.test(this.joinForm.phone)) { uni.showToast({ title: '请输入正确的手机号码', icon: 'none' }); return; } const params = { // libcode: getApp().globalData.libcode,
activityId: this.activityId, signName: this.joinForm.name, signPhone: this.joinForm.phone, signNum: this.joinForm.personCount, openid: this.openid, // sessionKey: this.sessionKey
} console.log('params',params) FetchSignActivity(params).then(res => { console.log('FetchSignActivity',res) if (res.code === 200) { uni.showToast({ title: '报名成功', icon: 'success' }); uni.$emit('refreshMySignList') this.closeJoinModal(); } else { console.log('error',res) uni.showToast({ title: res.message || '报名失败', icon: 'none' }); } }).catch((err) => { console.log('catch err', err) if(err){ uni.showToast({ title: err, icon: 'none' }) }else{ uni.showToast({ title: '报名失败', icon: 'none' }); } }); }, formatTime(timestamp) { if (!timestamp) return ''; const date = new Date(timestamp); const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, '0'); const d = String(date.getDate()).padStart(2, '0'); const h = String(date.getHours()).padStart(2, '0'); const min = String(date.getMinutes()).padStart(2, '0'); return `${y}-${m}-${d} ${h}:${min}`; }, onShareAppMessage() { return { title: this.detail.title, path: "/subpkg/pages/activity-detail/activity-detail?item=" + encodeURIComponent(JSON.stringify(this.detail)), imageUrl: this.detail.coverImage }; } }};</script>
<style lang="scss" scoped>.activity-detail { padding-bottom: 60px; background: #f5f5f5;}.activity-img { width: 100%; height: 180px;}.activity-base { background: #fff; padding: 15px; margin-bottom: 10px; .title { font-size: 16px; font-weight: bold; padding-bottom: 10px; } .time, .location { display: flex; align-items: center; font-size: 12px; color: #999; padding-top: 10px; .detail-icon { padding-right: 4px; } } .time{ padding-left: 3px; }}
.rich-content { background: #fff; padding: 10px; .content-title{ position: relative; font-size: 16px; font-weight: bold; color: #333; padding-left: 12px; margin-bottom: 20px; &::before{ position: absolute; left: 0; top: 50%; margin-top: -9px; content: ""; width: 4px; height: 18px; background-color: #01a4fe; } } }.rich-text{ ::v-deep p{ line-height: 1.5; margin: 15px 0; }}
.detail-bottom { position: fixed; bottom: 0; left: 0; right: 0; height: 50px; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 15px; box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.05);}.detail-left { display: flex; gap: 10px;}.handle-btn { display: flex; align-items: center; gap: 4px; background: #f5f5f5; border-radius: 30px; padding: 0 12px; height: 36px; font-size: 14px;}.join-btn { background: #01a4fe; color: #fff; border-radius: 30px; padding: 0 20px; height: 36px;}.disabled-btn { background: #ccc !important;}
.modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 1000;}
.modal-content { width: 85%; background: #fff; border-radius: 8px; overflow: hidden;}
.modal-header { display: flex; align-items: center; justify-content: space-between; padding: 15px; border-bottom: 1px solid #f0f0f0; .modal-title { font-size: 16px; font-weight: bold; color: #333; } .close-icon { color: #999; }}
.modal-body { padding: 15px;}
.form-item { margin-bottom: 15px; .form-label { display: block; font-size: 14px; color: #333; margin-bottom: 8px; } .form-input { width: calc(100% - 25px); height: 40px; background: #f8f8f8; border-radius: 8px; padding: 0 12px; font-size: 14px; } .select-wrapper { width: calc(100% - 25px); height: 40px; background: #f8f8f8; border-radius: 8px; display: flex; align-items: center; padding: 0 12px; font-size: 14px; ::v-deep .picker-select{ width: 100%; } .select-value { font-size: 14px; color: #333; width: 100%; display: flex; justify-content: space-between; align-items: center; &::after { content: ''; width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 5px solid #999; } } }}
.modal-footer { display: flex; border-top: 1px solid #f0f0f0; .cancel-btn, .submit-btn { flex: 1; height: 44px; line-height: 44px; text-align: center; font-size: 15px; border-radius: 0; border: none; margin: 0; padding: 0; &::after { border: none; border-radius: 0; } } .cancel-btn { background: #f5f5f5; color: #666; // border-right: 1px solid #f0f0f0;
} .submit-btn { background: #01a4fe; color: #fff; }}
.get-phone-btn { font-size: 12px; color: #fff; background-color: #01a4fe; width: 90px; border-radius: 20px; padding: 10px; border: none; line-height: 1.2; margin-left: 10px; &::after { border: none; }}</style>
|