视频会议
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<view class="cloud-law-call">
|
||||
<view class="cloud-law-call" :class="{ 'desktop-window': isDesktopWindow }" :style="windowStyle">
|
||||
<view v-if="isDesktopWindow" class="window-titlebar" @mousedown="startWindowDrag">
|
||||
<text class="window-title">视频咨询</text>
|
||||
<view class="window-actions">
|
||||
<text class="window-action" @click.stop="resetWindow">还原</text>
|
||||
<text class="window-action danger" @click.stop="hangup">挂断</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="!isReady" class="entry-state">
|
||||
<view class="entry-avatar">{{ entryInitial }}</view>
|
||||
<text class="entry-title">{{ entryTitle }}</text>
|
||||
@@ -89,6 +96,7 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="isDesktopWindow" class="window-resizer" @mousedown="startWindowResize"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -128,9 +136,13 @@ const startedAt = ref(0)
|
||||
const nowTs = ref(Date.now())
|
||||
const ended = ref(false)
|
||||
const preferSelfMain = ref(false)
|
||||
const isDesktopWindow = ref(false)
|
||||
const windowRect = ref({ left: 120, top: 80, width: 960, height: 540 })
|
||||
let timerHandle = null
|
||||
let stateStopWatch = null
|
||||
let videoRetryHandle = null
|
||||
let dragState = null
|
||||
let resizeState = null
|
||||
|
||||
const digitsOf = (value) => String(value || '').replace(/\D/g, '').slice(0, 9)
|
||||
const sameUser = (a, b) => String(a || '') === String(b || '')
|
||||
@@ -150,6 +162,15 @@ const speakingMap = computed(() => meetingStore.speakingMap || {})
|
||||
const isVoiceMode = computed(() => callMode.value === 'voice')
|
||||
const cameraIcon = computed(() => videoEnabled.value ? videoOnIcon : videoOffIcon)
|
||||
const micIcon = computed(() => audioEnabled.value ? voiceOnIcon : voiceOffIcon)
|
||||
const windowStyle = computed(() => {
|
||||
if (!isDesktopWindow.value) return {}
|
||||
return {
|
||||
left: `${windowRect.value.left}px`,
|
||||
top: `${windowRect.value.top}px`,
|
||||
width: `${windowRect.value.width}px`,
|
||||
height: `${windowRect.value.height}px`
|
||||
}
|
||||
})
|
||||
|
||||
const entryInitial = computed(() => {
|
||||
const name = userStore.userInfo?.nickname || userStore.userInfo?.username || '在'
|
||||
@@ -433,6 +454,80 @@ const swapPrimaryTile = () => {
|
||||
preferSelfMain.value = !preferSelfMain.value
|
||||
}
|
||||
|
||||
const clampWindowRect = (rect) => {
|
||||
const viewportWidth = window.innerWidth || 1200
|
||||
const viewportHeight = window.innerHeight || 800
|
||||
const width = Math.min(Math.max(rect.width, 420), viewportWidth)
|
||||
const height = Math.min(Math.max(rect.height, 260), viewportHeight)
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
left: Math.min(Math.max(rect.left, 0), Math.max(0, viewportWidth - width)),
|
||||
top: Math.min(Math.max(rect.top, 0), Math.max(0, viewportHeight - height))
|
||||
}
|
||||
}
|
||||
|
||||
const resetWindow = () => {
|
||||
if (typeof window === 'undefined') return
|
||||
const width = Math.min(960, Math.max(420, Math.floor(window.innerWidth * 0.82)))
|
||||
const height = Math.min(540, Math.max(260, Math.floor(window.innerHeight * 0.72)))
|
||||
windowRect.value = clampWindowRect({
|
||||
width,
|
||||
height,
|
||||
left: Math.floor((window.innerWidth - width) / 2),
|
||||
top: Math.floor((window.innerHeight - height) / 2)
|
||||
})
|
||||
}
|
||||
|
||||
const startWindowDrag = (event) => {
|
||||
if (!isDesktopWindow.value || event.button !== 0) return
|
||||
dragState = {
|
||||
startX: event.clientX,
|
||||
startY: event.clientY,
|
||||
rect: { ...windowRect.value }
|
||||
}
|
||||
window.addEventListener('mousemove', onWindowDrag)
|
||||
window.addEventListener('mouseup', stopWindowMove)
|
||||
}
|
||||
|
||||
const onWindowDrag = (event) => {
|
||||
if (!dragState) return
|
||||
windowRect.value = clampWindowRect({
|
||||
...dragState.rect,
|
||||
left: dragState.rect.left + event.clientX - dragState.startX,
|
||||
top: dragState.rect.top + event.clientY - dragState.startY
|
||||
})
|
||||
}
|
||||
|
||||
const startWindowResize = (event) => {
|
||||
if (!isDesktopWindow.value || event.button !== 0) return
|
||||
event.stopPropagation()
|
||||
resizeState = {
|
||||
startX: event.clientX,
|
||||
startY: event.clientY,
|
||||
rect: { ...windowRect.value }
|
||||
}
|
||||
window.addEventListener('mousemove', onWindowResize)
|
||||
window.addEventListener('mouseup', stopWindowMove)
|
||||
}
|
||||
|
||||
const onWindowResize = (event) => {
|
||||
if (!resizeState) return
|
||||
windowRect.value = clampWindowRect({
|
||||
...resizeState.rect,
|
||||
width: resizeState.rect.width + event.clientX - resizeState.startX,
|
||||
height: resizeState.rect.height + event.clientY - resizeState.startY
|
||||
})
|
||||
}
|
||||
|
||||
const stopWindowMove = () => {
|
||||
dragState = null
|
||||
resizeState = null
|
||||
window.removeEventListener('mousemove', onWindowDrag)
|
||||
window.removeEventListener('mousemove', onWindowResize)
|
||||
window.removeEventListener('mouseup', stopWindowMove)
|
||||
}
|
||||
|
||||
const onMicToggle = async () => {
|
||||
if (audioLoading.value || leaving.value) return
|
||||
audioLoading.value = true
|
||||
@@ -494,6 +589,10 @@ onLoad(query => {
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
isDesktopWindow.value = window.innerWidth >= 900
|
||||
if (isDesktopWindow.value) resetWindow()
|
||||
}
|
||||
stateStopWatch = watch(() => meetingStore.localState, s => {
|
||||
if (s === MEETING_LOCAL_STATE_CONNECTED) {
|
||||
startTimer()
|
||||
@@ -521,6 +620,9 @@ onBeforeUnmount(() => {
|
||||
clearTimeout(videoRetryHandle)
|
||||
videoRetryHandle = null
|
||||
}
|
||||
if (typeof window !== 'undefined') {
|
||||
stopWindowMove()
|
||||
}
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
@@ -541,6 +643,70 @@ onUnload(() => {
|
||||
color: #ffffff;
|
||||
z-index: 1000;
|
||||
}
|
||||
.cloud-law-call.desktop-window {
|
||||
inset: auto;
|
||||
border-radius: 10rpx;
|
||||
box-shadow: 0 12rpx 48rpx rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
.desktop-window .call-shell,
|
||||
.desktop-window .entry-state {
|
||||
height: calc(100% - 64rpx);
|
||||
margin-top: 64rpx;
|
||||
}
|
||||
.window-titlebar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 64rpx;
|
||||
padding: 0 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: rgba(7, 16, 37, 0.96);
|
||||
border-bottom: 1rpx solid rgba(255, 255, 255, 0.08);
|
||||
box-sizing: border-box;
|
||||
cursor: move;
|
||||
z-index: 20;
|
||||
user-select: none;
|
||||
}
|
||||
.window-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
}
|
||||
.window-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
.window-action {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.78);
|
||||
cursor: pointer;
|
||||
}
|
||||
.window-action.danger {
|
||||
color: #ff6b75;
|
||||
}
|
||||
.window-resizer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
cursor: nwse-resize;
|
||||
z-index: 30;
|
||||
}
|
||||
.window-resizer::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 8rpx;
|
||||
bottom: 8rpx;
|
||||
width: 18rpx;
|
||||
height: 18rpx;
|
||||
border-right: 3rpx solid rgba(255, 255, 255, 0.55);
|
||||
border-bottom: 3rpx solid rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
.entry-state {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user