Files
urbanLifeline/urbanLifelineWeb/packages/workcase_wechat/pages/meeting/meetingCard/MeetingCard.uvue
2025-12-28 10:51:39 +08:00

296 lines
7.8 KiB
Plaintext
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 v-if="loading" class="meeting-card meeting-card--loading">
<text class="meeting-card-loading">加载中...</text>
</view>
<view v-else-if="meeting" :class="['meeting-card', meeting.status ? `meeting-card--${meeting.status}` : '']">
<view class="meeting-card-header">
<view class="meeting-card-title">{{ meeting.meetingName || '未命名会议' }}</view>
<view class="meeting-card-status">
<text v-if="isMeetingEnded" class="status-badge status-ended">已结束</text>
<text v-else-if="meeting.status === 'ongoing'" class="status-badge status-ongoing">进行中</text>
<text v-else-if="meeting.status === 'scheduled'" class="status-badge status-scheduled">预定</text>
</view>
</view>
<view class="meeting-card-time">
<text>开始时间:{{ formatDateTime(meeting.startTime) }}</text>
<text>结束时间:{{ formatDateTime(meeting.endTime) }}</text>
<text v-if="meeting.advance">提前入会:{{ meeting.advance }}分钟</text>
</view>
<view v-if="meeting.description" class="meeting-card-content">
<text class="meeting-card-desc">{{ meeting.description }}</text>
</view>
<view class="meeting-card-action">
<text class="meeting-card-countdown">{{ countdownText }}</text>
<button
type="primary"
:disabled="!canJoinMeeting"
@tap="handleJoinMeeting"
size="mini"
>
<text>{{ buttonText }}</text>
</button>
</view>
</view>
<view v-else class="meeting-card meeting-card--error">
<text class="meeting-card-error">会议信息加载失败</text>
</view>
</template>
<script setup lang="ts">
import type { VideoMeetingVO } from '../../../types/workcase/chatRoom'
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { workcaseChatAPI } from '../../../api/workcase'
interface Props {
meetingId: string
}
const props = defineProps<Props>()
const emit = defineEmits<{
join: [meetingId: string]
}>()
// 会议详情从API获取的实时数据
const meeting = ref<VideoMeetingVO | null>(null)
const loading = ref(false)
// 当前时间,每秒更新
const currentTime = ref(Date.now())
let timeTimer: ReturnType<typeof setInterval> | null = null
let refreshTimer: ReturnType<typeof setInterval> | null = null
// 获取会议详情
async function fetchMeetingInfo() {
if (!props.meetingId) return
try {
loading.value = true
const result = await workcaseChatAPI.getMeetingInfo(props.meetingId)
if (result.success && result.data) {
meeting.value = result.data
console.log('[MeetingCard] 获取会议信息成功:', result.data)
} else {
console.error('[MeetingCard] 获取会议信息失败:', result.message)
}
} catch (error) {
console.error('[MeetingCard] 获取会议信息异常:', error)
} finally {
loading.value = false
}
}
onMounted(() => {
// 初始加载会议信息
fetchMeetingInfo()
// 每秒更新当前时间
timeTimer = setInterval(() => {
currentTime.value = Date.now()
}, 1000)
// 每30秒刷新会议状态
refreshTimer = setInterval(() => {
fetchMeetingInfo()
}, 30000)
})
onUnmounted(() => {
if (timeTimer !== null) {
clearInterval(timeTimer)
timeTimer = null
}
if (refreshTimer !== null) {
clearInterval(refreshTimer)
refreshTimer = null
}
})
/**
* 格式化日期时间
*/
function formatDateTime(dateStr?: string): string {
if (!dateStr) return ''
// iOS和小程序兼容性处理将空格替换为T
const iosCompatibleTime = dateStr.replace(' ', 'T')
const date = new Date(iosCompatibleTime)
if (isNaN(date.getTime())) return dateStr // 如果解析失败,返回原字符串
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hour = String(date.getHours()).padStart(2, '0')
const minute = String(date.getMinutes()).padStart(2, '0')
return `${month}-${day} ${hour}:${minute}`
}
/**
* 判断会议是否已结束
* 满足以下任一条件即为已结束:
* 1. status是ended状态
* 2. 超过了endTime
* 3. actualEndTime存在
*/
const isMeetingEnded = computed((): boolean => {
if (!meeting.value) {
return false
}
// 条件1: status是ended状态
if (meeting.value.status === 'ended') {
return true
}
// 条件3: actualEndTime存在
if (meeting.value.actualEndTime) {
return true
}
// 条件2: 超过了endTime
if (meeting.value.endTime) {
const endTime = new Date(meeting.value.endTime.replace(' ', 'T')).getTime()
if (!isNaN(endTime) && currentTime.value > endTime) {
return true
}
}
return false
})
/**
* 计算倒计时文本
*/
const countdownText = computed((): string => {
if (!meeting.value || !meeting.value.startTime || !meeting.value.endTime) {
return ''
}
// 统一判断会议是否已结束
if (isMeetingEnded.value) {
return '会议已结束'
}
// advance 默认为 0
const advanceMinutes = meeting.value.advance || 0
const now = currentTime.value
// iOS和小程序兼容性处理
const startTime = new Date(meeting.value.startTime.replace(' ', 'T')).getTime()
const endTime = new Date(meeting.value.endTime.replace(' ', 'T')).getTime()
// 检查时间解析是否有效
if (isNaN(startTime) || isNaN(endTime)) {
return ''
}
const advanceTime = startTime - advanceMinutes * 60 * 1000
if (now < advanceTime) {
// 未到提前入会时间
const leftMs = advanceTime - now
const leftMinutes = Math.floor(leftMs / 60000)
const leftSeconds = Math.floor((leftMs % 60000) / 1000)
if (leftMinutes >= 60) {
const hours = Math.floor(leftMinutes / 60)
const mins = leftMinutes % 60
return `距离入会:${hours}小时${mins}分钟`
} else if (leftMinutes > 0) {
return `距离入会:${leftMinutes}分${leftSeconds}秒`
} else {
return `距离入会:${leftSeconds}秒`
}
} else if (now < startTime) {
// 在提前入会时间窗口内,但未到开始时间
return '可以入会'
} else if (now < endTime) {
// 会议进行中
if (meeting.value.status === 'ongoing') {
return '会议进行中'
} else {
return '可以入会'
}
} else {
// 已超过结束时间
return '会议已超时'
}
})
/**
* 是否可以加入会议
*/
const canJoinMeeting = computed((): boolean => {
if (!meeting.value || !meeting.value.startTime || !meeting.value.endTime) {
return false
}
// 统一判断会议是否已结束
if (isMeetingEnded.value) {
return false
}
const advanceMinutes = meeting.value.advance || 0
const now = currentTime.value
// iOS和小程序兼容性处理
const startTime = new Date(meeting.value.startTime.replace(' ', 'T')).getTime()
const endTime = new Date(meeting.value.endTime.replace(' ', 'T')).getTime()
// 检查时间解析是否有效
if (isNaN(startTime) || isNaN(endTime)) {
return false
}
const advanceTime = startTime - advanceMinutes * 60 * 1000
// 在允许入会的时间窗口内(提前入会时间 ~ 结束时间)
return now >= advanceTime && now <= endTime
})
/**
* 按钮文本
*/
const buttonText = computed((): string => {
if (!meeting.value) {
return '加载中'
}
// 统一判断会议是否已结束
if (isMeetingEnded.value) {
return '会议已结束'
}
if (!canJoinMeeting.value) {
return '未到入会时间'
}
return '加入会议'
})
/**
* 加入会议
*/
function handleJoinMeeting() {
console.log('[MeetingCard] handleJoinMeeting 被点击', {
meetingId: props.meetingId,
canJoin: canJoinMeeting.value,
meeting: meeting.value
})
if (!props.meetingId) {
console.error('[MeetingCard] 会议ID不存在')
uni.showToast({
title: '会议ID不存在',
icon: 'none'
})
return
}
if (!canJoinMeeting.value) {
console.warn('[MeetingCard] 不允许加入会议', {
status: meeting.value?.status,
canJoin: canJoinMeeting.value
})
return
}
console.log('[MeetingCard] 触发join事件, meetingId:', props.meetingId)
emit('join', props.meetingId)
}
</script>
<style scoped lang="scss">
@import './MeetingCard.scss';
</style>