You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
626 lines
16 KiB
626 lines
16 KiB
<template>
|
|
<view class="activity-detail">
|
|
<image class="activity-img" :src="detail.coverImage" 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: config.LIB_CODE,
|
|
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: config.LIB_CODE,
|
|
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>
|