视频会议
This commit is contained in:
@@ -1,5 +1,12 @@
|
|||||||
<template>
|
<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 v-if="!isReady" class="entry-state">
|
||||||
<view class="entry-avatar">{{ entryInitial }}</view>
|
<view class="entry-avatar">{{ entryInitial }}</view>
|
||||||
<text class="entry-title">{{ entryTitle }}</text>
|
<text class="entry-title">{{ entryTitle }}</text>
|
||||||
@@ -89,6 +96,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="isDesktopWindow" class="window-resizer" @mousedown="startWindowResize"></view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -128,9 +136,13 @@ const startedAt = ref(0)
|
|||||||
const nowTs = ref(Date.now())
|
const nowTs = ref(Date.now())
|
||||||
const ended = ref(false)
|
const ended = ref(false)
|
||||||
const preferSelfMain = 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 timerHandle = null
|
||||||
let stateStopWatch = null
|
let stateStopWatch = null
|
||||||
let videoRetryHandle = null
|
let videoRetryHandle = null
|
||||||
|
let dragState = null
|
||||||
|
let resizeState = null
|
||||||
|
|
||||||
const digitsOf = (value) => String(value || '').replace(/\D/g, '').slice(0, 9)
|
const digitsOf = (value) => String(value || '').replace(/\D/g, '').slice(0, 9)
|
||||||
const sameUser = (a, b) => String(a || '') === String(b || '')
|
const sameUser = (a, b) => String(a || '') === String(b || '')
|
||||||
@@ -150,6 +162,15 @@ const speakingMap = computed(() => meetingStore.speakingMap || {})
|
|||||||
const isVoiceMode = computed(() => callMode.value === 'voice')
|
const isVoiceMode = computed(() => callMode.value === 'voice')
|
||||||
const cameraIcon = computed(() => videoEnabled.value ? videoOnIcon : videoOffIcon)
|
const cameraIcon = computed(() => videoEnabled.value ? videoOnIcon : videoOffIcon)
|
||||||
const micIcon = computed(() => audioEnabled.value ? voiceOnIcon : voiceOffIcon)
|
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 entryInitial = computed(() => {
|
||||||
const name = userStore.userInfo?.nickname || userStore.userInfo?.username || '在'
|
const name = userStore.userInfo?.nickname || userStore.userInfo?.username || '在'
|
||||||
@@ -433,6 +454,80 @@ const swapPrimaryTile = () => {
|
|||||||
preferSelfMain.value = !preferSelfMain.value
|
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 () => {
|
const onMicToggle = async () => {
|
||||||
if (audioLoading.value || leaving.value) return
|
if (audioLoading.value || leaving.value) return
|
||||||
audioLoading.value = true
|
audioLoading.value = true
|
||||||
@@ -494,6 +589,10 @@ onLoad(query => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
isDesktopWindow.value = window.innerWidth >= 900
|
||||||
|
if (isDesktopWindow.value) resetWindow()
|
||||||
|
}
|
||||||
stateStopWatch = watch(() => meetingStore.localState, s => {
|
stateStopWatch = watch(() => meetingStore.localState, s => {
|
||||||
if (s === MEETING_LOCAL_STATE_CONNECTED) {
|
if (s === MEETING_LOCAL_STATE_CONNECTED) {
|
||||||
startTimer()
|
startTimer()
|
||||||
@@ -521,6 +620,9 @@ onBeforeUnmount(() => {
|
|||||||
clearTimeout(videoRetryHandle)
|
clearTimeout(videoRetryHandle)
|
||||||
videoRetryHandle = null
|
videoRetryHandle = null
|
||||||
}
|
}
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
stopWindowMove()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnload(() => {
|
onUnload(() => {
|
||||||
@@ -541,6 +643,70 @@ onUnload(() => {
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
z-index: 1000;
|
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 {
|
.entry-state {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user