This commit is contained in:
2025-12-27 15:36:40 +08:00
parent 7c6fbc5ebe
commit 55801fa0ec
17 changed files with 1728 additions and 229 deletions

View File

@@ -92,8 +92,6 @@
:room-id="currentRoomId"
:workcase-id="currentWorkcaseId"
:room-name="currentRoom?.roomName"
:meeting-url="currentMeetingUrl"
:show-meeting="showMeetingIframe"
:file-download-url="FILE_DOWNLOAD_URL"
:has-more="hasMore"
:loading-more="loadingMore"
@@ -145,8 +143,13 @@
title="工单详情"
width="800px"
class="workcase-dialog"
destroy-on-close
>
<WorkcaseDetail :workcase-id="currentWorkcaseId" />
<WorkcaseDetail
mode="view"
:workcase-id="currentWorkcaseId"
@cancel="showWorkcaseDetail = false"
/>
</ElDialog>
<!-- 工单创建对话框 -->
@@ -155,9 +158,14 @@
title="创建工单"
width="800px"
class="workcase-dialog"
destroy-on-close
>
<!-- TODO: 添加工单创建组件 -->
<div>工单创建表单</div>
<WorkcaseDetail
mode="create"
:room-id="currentRoomId!"
@cancel="showWorkcaseCreator = false"
@created="onWorkcaseCreated"
/>
</ElDialog>
</div>
</template>
@@ -247,11 +255,6 @@ const showWorkcaseDetail = ref(false)
// 工单创建对话框
const showWorkcaseCreator = ref(false)
// Jitsi Meet会议相关
const currentMeetingUrl = ref('')
const showMeetingIframe = ref(false)
const currentMeetingId = ref<string | null>(null)
// ChatRoom组件引用
const chatRoomRef = ref<InstanceType<typeof ChatRoom> | null>(null)
@@ -508,13 +511,15 @@ const onWorkcaseCreated = (workcaseId: string) => {
if (currentRoom.value) {
currentRoom.value.workcaseId = workcaseId
}
// 刷新聊天室列表
fetchChatRooms()
ElMessage.success('工单创建成功')
}
// 发起会议
const startMeeting = async () => {
if (!currentRoomId.value) return
try {
// 先检查是否有活跃会议
const activeResult = await workcaseChatAPI.getActiveMeeting(currentRoomId.value)
@@ -523,31 +528,33 @@ const startMeeting = async () => {
currentMeetingId.value = activeResult.data.meetingId!
const joinResult = await workcaseChatAPI.joinVideoMeeting(currentMeetingId.value!)
if (joinResult.success && joinResult.data?.iframeUrl) {
currentMeetingUrl.value = joinResult.data.iframeUrl
showMeetingIframe.value = true
// 使用router跳转到JitsiMeetingView页面附加roomId参数用于返回
const meetingUrl = joinResult.data.iframeUrl + `&roomId=${currentRoomId.value}`
router.push(meetingUrl)
} else {
ElMessage.error(joinResult.message || '加入会议失败')
}
return
}
// 没有活跃会议,创建新会议
const createResult = await workcaseChatAPI.createVideoMeeting({
roomId: currentRoomId.value,
meetingName: currentRoom.value?.roomName || '视频会议'
})
if (createResult.success && createResult.data) {
currentMeetingId.value = createResult.data.meetingId!
// 开始会议
await workcaseChatAPI.startVideoMeeting(currentMeetingId.value!)
// 加入会议获取iframe URL
// 加入会议获取会议页面URL
const joinResult = await workcaseChatAPI.joinVideoMeeting(currentMeetingId.value!)
if (joinResult.success && joinResult.data?.iframeUrl) {
currentMeetingUrl.value = joinResult.data.iframeUrl
showMeetingIframe.value = true
// 使用router跳转到JitsiMeetingView页面附加roomId参数用于返回
const meetingUrl = joinResult.data.iframeUrl + `&roomId=${currentRoomId.value}`
router.push(meetingUrl)
ElMessage.success('会议已创建')
} else {
ElMessage.error(joinResult.message || '获取会议链接失败')

View File

@@ -79,11 +79,11 @@
<!-- 发起会议按钮 -->
<button
class="action-btn"
:disabled="meetingLoading || showMeeting"
:disabled="meetingLoading"
@click="handleStartMeeting"
>
<Video :size="18" />
{{ showMeeting ? '会议进行中' : '发起会议' }}
发起会议
</button>
<!-- 额外的操作按钮插槽 -->
@@ -137,48 +137,20 @@
:workcase-id="workcaseId || ''"
@success="handleMeetingCreated"
/>
<!-- 视频会议弹窗 -->
<Teleport to="body">
<div v-if="showMeeting && meetingUrl" class="meeting-modal-mask">
<div class="meeting-modal">
<div class="meeting-modal-header">
<span class="meeting-modal-title">
<Video :size="18" />
视频会议进行中
</span>
<div class="meeting-modal-actions">
<button class="minimize-btn" @click="minimizeMeeting" title="最小化">
<Minus :size="18" />
</button>
<button class="close-meeting-btn" @click="handleEndMeeting" title="结束会议">
<X :size="18" />
</button>
</div>
</div>
<div class="meeting-modal-body">
<IframeView :url="meetingUrl" class="meeting-iframe" />
</div>
</div>
</div>
</Teleport>
<!-- 最小化的会议悬浮按钮 -->
<div v-if="meetingMinimized && meetingUrl" class="meeting-float-btn" @click="restoreMeeting">
<Video :size="20" />
<span>返回会议</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, nextTick, onMounted } from 'vue'
import { FileText, Video, Paperclip, Send, X, Minus } from 'lucide-vue-next'
import IframeView from 'shared/components/iframe/IframeView.vue'
import { ref, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { FileText, Video, Paperclip, Send } from 'lucide-vue-next'
import MeetingCreate from '../MeetingCreate/MeetingCreate.vue'
import MeetingCard from '../MeetingCard/MeetingCard.vue'
import type { ChatRoomMessageVO, VideoMeetingVO } from '@/types/workcase'
import { workcaseChatAPI } from '@/api/workcase'
import { ElMessage } from 'element-plus'
const router = useRouter()
interface Props {
messages: ChatRoomMessageVO[]
@@ -204,37 +176,17 @@ const emit = defineEmits<{
'send-message': [content: string, files: File[]]
'download-file': [fileId: string]
'load-more': []
'start-meeting': []
}>()
// 会议相关状态
const showMeeting = ref(false)
const meetingUrl = ref('')
const currentMeetingId = ref('')
const meetingLoading = ref(false)
const showMeetingCreate = ref(false)
const meetingMinimized = ref(false)
// 最小化会议
const minimizeMeeting = () => {
meetingMinimized.value = true
showMeeting.value = false
}
// 恢复会议窗口
const restoreMeeting = () => {
meetingMinimized.value = false
showMeeting.value = true
}
// 打开创建会议对话框
// 打开创建会议对话框或直接emit事件给父组件处理
const handleStartMeeting = () => {
// 先检查是否有活跃会议
checkActiveMeeting().then(() => {
if (!showMeeting.value) {
// 没有活跃会议,打开创建对话框
showMeetingCreate.value = true
}
})
// emit事件给父组件让父组件处理会议逻辑
emit('start-meeting')
}
// 会议创建成功回调
@@ -245,40 +197,6 @@ const handleMeetingCreated = async (meetingId: string) => {
// 会议消息会通过后端发送到聊天室,用户可以点击消息卡片加入
}
// 结束会议
const handleEndMeeting = async () => {
if (!currentMeetingId.value) return
try {
await workcaseChatAPI.endVideoMeeting(currentMeetingId.value)
showMeeting.value = false
meetingUrl.value = ''
currentMeetingId.value = ''
meetingMinimized.value = false
} catch (error) {
console.error('结束会议失败:', error)
}
}
// 检查是否有活跃会议
const checkActiveMeeting = async () => {
try {
const res = await workcaseChatAPI.getActiveMeeting(props.roomId)
if (res.code === 0 && res.data) {
currentMeetingId.value = res.data.meetingId
meetingUrl.value = res.data.iframeUrl
showMeeting.value = true
}
} catch (error) {
console.log('无活跃会议')
}
}
// 组件挂载时检查是否有活跃会议
onMounted(() => {
checkActiveMeeting()
})
// 滚动到顶部加载更多
const handleScroll = (e: Event) => {
const target = e.target as HTMLElement
@@ -370,34 +288,36 @@ const formatTime = (time?: string) => {
// 处理从MeetingCard发出的加入会议事件
const handleJoinMeeting = async (meetingId: string) => {
try {
// 调用加入会议接口获取iframe URL
meetingLoading.value = true
// 调用加入会议接口获取会议页面URL
const joinRes = await workcaseChatAPI.joinVideoMeeting(meetingId)
if (joinRes.success && joinRes.data) {
// 检查会议状态
const meetingData = joinRes.data
if (meetingData.status === 'ended') {
// 会议已结束,提示用户
alert('该会议已结束')
ElMessage.warning('该会议已结束')
return
}
if (!meetingData.iframeUrl) {
console.error('加入会议失败: 未获取到会议地址')
alert('加入会议失败:未获取到会议地址')
ElMessage.error('加入会议失败:未获取到会议地址')
return
}
currentMeetingId.value = meetingId
meetingUrl.value = meetingData.iframeUrl
showMeeting.value = true
meetingMinimized.value = false
// 使用router跳转到JitsiMeetingView页面附加roomId参数用于返回
const meetingUrl = meetingData.iframeUrl + `&roomId=${props.roomId}`
router.push(meetingUrl)
} else {
console.error('加入会议失败:', joinRes.message)
alert(joinRes.message || '加入会议失败')
ElMessage.error(joinRes.message || '加入会议失败')
}
} catch (error) {
console.error('加入会议失败:', error)
alert('加入会议失败,请稍后重试')
ElMessage.error('加入会议失败,请稍后重试')
} finally {
meetingLoading.value = false
}
}
@@ -457,8 +377,7 @@ const renderMarkdown = (text: string): string => {
// 暴露方法给父组件
defineExpose({
scrollToBottom,
handleStartMeeting,
handleEndMeeting
handleStartMeeting
})
</script>