288 lines
6.7 KiB
Vue
288 lines
6.7 KiB
Vue
<template>
|
||
<view class="reservation-item reservation-box">
|
||
<view class="column reservation-inner" @click="openReservationPopup()">
|
||
<view class="row title title-row mt-10">
|
||
<view class="title">{{ reservation.title || '麻将预约' }}</view>
|
||
<view class="status-text" :style="getStatusStyle(reservation.status)">
|
||
{{ getStatusText(reservation.status) }}
|
||
</view>
|
||
</view>
|
||
|
||
<view class="row row-text row-center mt-10">
|
||
<text class="ml-20">{{ formatTimeRange(reservation.start_time, reservation.end_time) }}</text>
|
||
</view>
|
||
|
||
<view class="row row-center mt-10">
|
||
<text class="row-text ml-20">{{ reservation.room_name || '' }}</text>
|
||
</view>
|
||
|
||
<view class="row row-center mt-10" style="display: flex;">
|
||
<view style="width: 75%;">
|
||
<text class="row-text ml-20">已约牌友:</text>
|
||
<image v-for="(participant, pIndex) in reservation.participants" :key="pIndex"
|
||
:src="participant.avatarImage || '/static/default-avatar.png'" class="avatar"
|
||
mode="aspectFill" />
|
||
</view>
|
||
<view>
|
||
<view class="center evaluate-btn" v-if="reservation.status == 3" @click.stop="onEvaluate()">
|
||
<text class="evaluate-btn-text">牌友评价</text>
|
||
</view>
|
||
<view class="center evaluate-btn" v-else-if="isQianDaoVisible" @click.stop="onQianDao()">
|
||
<text class="evaluate-btn-text">签到</text>
|
||
</view>
|
||
|
||
<button v-else-if="reservation.status == 0" @click.stop open-type="share"
|
||
style=" background-color:#00AC4E;" class="center evaluate-btn" :data-item="reservation">
|
||
<text class="evaluate-btn-text">分享</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
<view class="mt-20" style="height: 10rpx;"></view>
|
||
</view>
|
||
|
||
<!-- 评价弹窗由父页面控制 -->
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
// 仅负责展示与触发评价事件
|
||
import { inject, computed } from 'vue'
|
||
const props = defineProps({
|
||
reservation: {
|
||
type: Object,
|
||
required: true
|
||
}
|
||
})
|
||
const emit = defineEmits(['evaluate', 'click'])
|
||
|
||
const onEvaluate = () => {
|
||
emit('evaluate', props.reservation)
|
||
// openEvaluatePop?.(props.reservation)
|
||
}
|
||
const onQianDao = () => {
|
||
console.log(" props.reservation", props.reservation);
|
||
emit('qianDao', props.reservation)
|
||
// TODO: 实现签到逻辑
|
||
}
|
||
const onfengxiao = () => {
|
||
console.log(" props.onfengxiao", props.reservation);
|
||
return false;
|
||
// TODO: 实现签到逻辑
|
||
}
|
||
// 计算属性:判断是否显示签到按钮
|
||
const isQianDaoVisible = computed(() => {
|
||
const item = props.reservation;
|
||
console.log("计算签到按钮显示状态", item);
|
||
|
||
// 检查预约状态:1-待开始,2-进行中
|
||
if (item.status !== 1) {
|
||
return false;
|
||
}
|
||
|
||
// 检查用户权限:role为1表示有权限
|
||
if (item.role !== 1) {
|
||
return false;
|
||
}
|
||
|
||
// 检查开始时间是否存在
|
||
if (!item.start_time) {
|
||
return false;
|
||
}
|
||
// 检查结束时间是否存在
|
||
if (!item.end_time) {
|
||
return false;
|
||
}
|
||
|
||
const now = new Date();
|
||
const startTime = new Date(item.start_time);
|
||
const endTime = new Date(item.end_time);
|
||
const tenMinutes = 10 * 60 * 1000; // 10分钟的毫秒数
|
||
|
||
// 计算时间差
|
||
const timeToStart = startTime.getTime() - now.getTime();
|
||
const timeToEnd = endTime.getTime() - now.getTime();
|
||
|
||
// 签到条件:
|
||
// 1. 开始时间前10分钟内可以签到
|
||
// 2. 在结束时间内也可以签到
|
||
// 3. 结束时间外无法签到
|
||
const canSignBeforeStart = timeToStart <= tenMinutes && timeToStart > 0; // 开始前10分钟内
|
||
const canSignDuringEvent = timeToStart <= 0 && timeToEnd > 0; // 活动进行中(开始后,结束前)
|
||
|
||
return canSignBeforeStart || canSignDuringEvent;
|
||
})
|
||
|
||
const formatTimeRange = (startTime, endTime) => {
|
||
if (!startTime || !endTime) return '时间未设置'
|
||
const start = new Date(startTime)
|
||
const end = new Date(endTime)
|
||
const startStr = `${start.getFullYear()}-${String(start.getMonth() + 1).padStart(2, '0')}-${String(start.getDate()).padStart(2, '0')} ${String(start.getHours()).padStart(2, '0')}:${String(start.getMinutes()).padStart(2, '0')}`
|
||
const endStr = `${end.getFullYear()}-${String(end.getMonth() + 1).padStart(2, '0')}-${String(end.getDate()).padStart(2, '0')} ${String(end.getHours()).padStart(2, '0')}:${String(end.getMinutes()).padStart(2, '0')}`
|
||
return `${startStr} ~ ${endStr}`
|
||
}
|
||
|
||
const getStatusText = (status) => {
|
||
const map = { 0: '组局中', 1: '待开始', 2: '进行中', 3: "已结束", 4: "已取消" }
|
||
return map[status] || '未知状态'
|
||
}
|
||
const getStatusStyle = (status) => {
|
||
const map = { 0: 'color: #999', 1: 'color: #1989FA', 2: 'color: #1989FA', 3: 'color: #999', 4: 'color: #999' }
|
||
return map[status] || 'color: #999'
|
||
}
|
||
const openReservationPopup = () => {
|
||
emit('click', props.reservation)
|
||
// openReservationPopupf?.(props.reservation)
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.reservation-item {
|
||
background: linear-gradient(180deg, #D7F0DD 0%, #FFFFFF 100%);
|
||
box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(0, 0, 0, 0.25);
|
||
border-radius: 46rpx 46rpx 46rpx 46rpx;
|
||
transition: transform 0.2s;
|
||
|
||
&:active {
|
||
transform: scale(0.98);
|
||
}
|
||
}
|
||
|
||
.reservation-box {
|
||
width: 100%;
|
||
border-radius: 25rpx;
|
||
background-color: #F2F3F5;
|
||
}
|
||
|
||
.reservation-inner {
|
||
width: 95%;
|
||
margin: 20rpx auto 20rpx;
|
||
}
|
||
|
||
.title {
|
||
font-family: PingFang SC, PingFang SC;
|
||
font-weight: 500;
|
||
font-size: 35rpx;
|
||
color: #322A2A;
|
||
text-align: left;
|
||
font-style: normal;
|
||
text-transform: none;
|
||
}
|
||
|
||
.title-row {
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.row-text {
|
||
font-family: PingFang SC, PingFang SC;
|
||
font-weight: 400;
|
||
font-size: 30rpx;
|
||
color: #575757;
|
||
text-align: left;
|
||
font-style: normal;
|
||
text-transform: none;
|
||
}
|
||
|
||
.row-center {
|
||
align-items: center;
|
||
}
|
||
|
||
.mt-20 {
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.mt-10 {
|
||
margin-top: 10rpx;
|
||
}
|
||
|
||
|
||
.ml-20 {
|
||
margin-left: 20rpx;
|
||
}
|
||
|
||
.avatar {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
border-radius: 50%;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.status-text {
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.evaluate-btn {
|
||
width: 120rpx;
|
||
height: 30px;
|
||
background-color: #1989FA;
|
||
border-radius: 10rpx;
|
||
margin-left: auto;
|
||
}
|
||
|
||
.evaluate-btn-text {
|
||
font-size: 24rpx;
|
||
color: #FFFFFF;
|
||
}
|
||
|
||
.popup-container {
|
||
width: 650rpx;
|
||
background-color: #FFFFFF;
|
||
border-radius: 10rpx;
|
||
}
|
||
|
||
.popup-inner {
|
||
width: 90%;
|
||
margin: 0 auto 0;
|
||
}
|
||
|
||
.popup-title {
|
||
font-size: 26rpx;
|
||
margin-top: 30rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.popup-desc {
|
||
font-size: 24rpx;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.panel-box {
|
||
width: 100%;
|
||
background-color: #F2F3F5;
|
||
border-radius: 10rpx;
|
||
padding-top: 20rpx;
|
||
padding-bottom: 20rpx;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.down-icon {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
margin-left: auto;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.font-24 {
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.action-row {
|
||
margin-top: 50rpx;
|
||
height: 80rpx;
|
||
color: #FFFFFF;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.action-btn {
|
||
flex: 1;
|
||
background-color: #1989FA;
|
||
border-radius: 10rpx;
|
||
}
|
||
|
||
.info-tip {
|
||
font-size: 24rpx;
|
||
text-align: center;
|
||
margin-top: 20rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
</style>
|