电话咨询
This commit is contained in:
@@ -49,6 +49,7 @@
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="!replaceChatWithScreenShare"
|
||||
class="btn btn-screen"
|
||||
:class="{ active: screenSharing, loading: screenLoading }"
|
||||
:disabled="screenLoading || screenDisabled"
|
||||
@@ -127,7 +128,36 @@
|
||||
<text class="label">成员</text>
|
||||
</button>
|
||||
|
||||
<button class="btn" :disabled="!allowChat" @click="emitIf('chat')">
|
||||
<button
|
||||
v-if="replaceChatWithScreenShare"
|
||||
class="btn btn-screen-share-slot"
|
||||
:class="{ active: screenSharing, loading: screenLoading }"
|
||||
:disabled="screenLoading || screenDisabled"
|
||||
:title="screenDisabledReason || ''"
|
||||
@click="emitIf('screen-toggle')"
|
||||
>
|
||||
<view class="icon">
|
||||
<svg v-if="screenSharing" viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
|
||||
<line x1="8" y1="21" x2="16" y2="21"></line>
|
||||
<line x1="12" y1="17" x2="12" y2="21"></line>
|
||||
<line x1="6" y1="7" x2="18" y2="13"></line>
|
||||
<line x1="18" y1="7" x2="6" y2="13"></line>
|
||||
</svg>
|
||||
<svg v-else viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
|
||||
<line x1="8" y1="21" x2="16" y2="21"></line>
|
||||
<line x1="12" y1="17" x2="12" y2="21"></line>
|
||||
<polyline points="9 10 12 7 15 10"></polyline>
|
||||
<line x1="12" y1="7" x2="12" y2="14"></line>
|
||||
</svg>
|
||||
</view>
|
||||
<text class="label">
|
||||
<text class="label-desktop">{{ screenSharing ? '停止共享' : '共享屏幕' }}</text>
|
||||
<text class="label-mobile">{{ screenSharing ? '停止' : '共享' }}</text>
|
||||
</text>
|
||||
</button>
|
||||
<button v-else class="btn" :disabled="!allowChat" @click="emitIf('chat')">
|
||||
<view class="icon">
|
||||
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
||||
@@ -173,7 +203,9 @@ const props = defineProps({
|
||||
/** Phase B 录制:当前是否在录制中 */
|
||||
recording: { type: Boolean, default: false },
|
||||
/** Phase B 录制:REST 调用 / 上传中 loading 态,禁用按钮 */
|
||||
recordingLoading: { type: Boolean, default: false }
|
||||
recordingLoading: { type: Boolean, default: false },
|
||||
/** 云律律师端:用「共享屏幕」替换「聊天」按钮(移动端工具栏不隐藏屏幕共享) */
|
||||
replaceChatWithScreenShare: { type: Boolean, default: false }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['mic-toggle', 'cam-toggle', 'screen-toggle', 'recording-toggle', 'invite', 'members', 'chat', 'leave'])
|
||||
@@ -336,6 +368,10 @@ const emitIf = (name) => {
|
||||
.btn-screen {
|
||||
display: none;
|
||||
}
|
||||
/* 律师端:共享屏幕占原「聊天」槽位,移动端仍需显示 */
|
||||
.btn-screen-share-slot {
|
||||
display: flex;
|
||||
}
|
||||
.icon {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
|
||||
@@ -96,7 +96,17 @@
|
||||
</view>
|
||||
|
||||
<view class="controls-row controls-row-bottom">
|
||||
<view class="control-item" @click="takeSnapshot" @tap="takeSnapshot">
|
||||
<!-- 律师端:底部左侧为共享屏幕(替代通用会议页「聊天」能力) -->
|
||||
<view v-if="isLawRole" class="control-item" @click="onScreenToggle" @tap="onScreenToggle">
|
||||
<view
|
||||
class="control-btn dark-btn text-icon-btn"
|
||||
:class="{ active: screenSharingByMe, loading: screenLoading, disabled: screenDisabled && !screenSharingByMe }"
|
||||
>
|
||||
<text class="text-icon">{{ screenSharingByMe ? '停' : '屏' }}</text>
|
||||
</view>
|
||||
<text class="control-label">{{ screenSharingByMe ? '停止共享' : '共享屏幕' }}</text>
|
||||
</view>
|
||||
<view v-else class="control-item" @click="takeSnapshot" @tap="takeSnapshot">
|
||||
<view class="control-btn dark-btn text-icon-btn">
|
||||
<image v-if="!captureIconError" class="control-image" :src="captureIcon" mode="aspectFit" @error="captureIconError = true"></image>
|
||||
<text v-else class="text-icon">截</text>
|
||||
@@ -177,6 +187,7 @@ const joining = ref(false)
|
||||
const joinError = ref('')
|
||||
const audioLoading = ref(false)
|
||||
const videoLoading = ref(false)
|
||||
const screenLoading = ref(false)
|
||||
const leaving = ref(false)
|
||||
const startedAt = ref(0)
|
||||
const nowTs = ref(Date.now())
|
||||
@@ -244,7 +255,56 @@ const participants = computed(() => {
|
||||
|
||||
const localTrackVersion = computed(() => {
|
||||
const producers = meetingStore.localProducers || {}
|
||||
return `${producers.audio || ''}-${producers.video || ''}`
|
||||
return `${producers.audio || ''}-${producers.video || ''}-${producers.screen || ''}`
|
||||
})
|
||||
|
||||
const screenSharingActive = computed(() => !!meetingStore.screenShare?.ownerUserId)
|
||||
const screenSharingByMe = computed(() => {
|
||||
const owner = meetingStore.screenShare?.ownerUserId
|
||||
return !!owner && sameUser(owner, myUserId.value)
|
||||
})
|
||||
const screenSupported = computed(() => {
|
||||
// #ifdef H5
|
||||
return typeof navigator !== 'undefined' &&
|
||||
!!navigator.mediaDevices &&
|
||||
typeof navigator.mediaDevices.getDisplayMedia === 'function'
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
return false
|
||||
// #endif
|
||||
})
|
||||
const screenDisabled = computed(() => {
|
||||
if (!screenSupported.value) return true
|
||||
if (screenSharingActive.value && !screenSharingByMe.value) return true
|
||||
return false
|
||||
})
|
||||
const screenDisabledReason = computed(() => {
|
||||
if (!screenSupported.value) return '当前环境不支持屏幕共享'
|
||||
if (screenSharingActive.value && !screenSharingByMe.value) return '已有成员正在共享屏幕'
|
||||
return ''
|
||||
})
|
||||
|
||||
const screenTile = computed(() => {
|
||||
const owner = meetingStore.screenShare?.ownerUserId
|
||||
if (!owner) return null
|
||||
localTrackVersion.value
|
||||
remoteTrackVersion.value
|
||||
const isLocal = sameUser(owner, myUserId.value)
|
||||
const track = isLocal
|
||||
? meetingStore.getLocalTrack('screen')
|
||||
: meetingStore.getRemoteTrack(owner, 'screen')
|
||||
if (!track) return null
|
||||
return {
|
||||
key: `screen_${owner}`,
|
||||
userId: owner,
|
||||
name: isLocal ? '你' : nameOf(owner),
|
||||
isLocal,
|
||||
isHost: sameUser(owner, hostId.value),
|
||||
audioEnabled: false,
|
||||
videoEnabled: true,
|
||||
audioTrack: null,
|
||||
videoTrack: track
|
||||
}
|
||||
})
|
||||
|
||||
const remoteTrackVersion = computed(() => {
|
||||
@@ -252,7 +312,7 @@ const remoteTrackVersion = computed(() => {
|
||||
const map = meetingStore.remoteConsumers || {}
|
||||
Object.keys(map).forEach(uid => {
|
||||
const slot = map[uid]
|
||||
parts.push(`${uid}:${slot?.audio?.id || ''}:${slot?.video?.id || ''}`)
|
||||
parts.push(`${uid}:${slot?.audio?.id || ''}:${slot?.video?.id || ''}:${slot?.screen?.id || ''}`)
|
||||
})
|
||||
return parts.join('|')
|
||||
})
|
||||
@@ -315,6 +375,7 @@ const tiles = computed(() => {
|
||||
const selfTile = computed(() => tiles.value.find(t => t.isLocal) || null)
|
||||
const remoteTile = computed(() => tiles.value.find(t => !t.isLocal && t.videoTrack) || tiles.value.find(t => !t.isLocal) || null)
|
||||
const primaryTile = computed(() => {
|
||||
if (screenTile.value) return screenTile.value
|
||||
if (preferSelfMain.value && selfTile.value) return selfTile.value
|
||||
return remoteTile.value || selfTile.value
|
||||
})
|
||||
@@ -650,6 +711,28 @@ const flipCamera = () => {
|
||||
flipCameraByCommand()
|
||||
}
|
||||
|
||||
const onScreenToggle = async () => {
|
||||
if (screenLoading.value) return
|
||||
if (screenDisabled.value && !screenSharingByMe.value) {
|
||||
uni.showToast({ title: screenDisabledReason.value || '无法共享屏幕', icon: 'none' })
|
||||
return
|
||||
}
|
||||
screenLoading.value = true
|
||||
try {
|
||||
if (screenSharingByMe.value) {
|
||||
await meetingStore.stopScreenShare()
|
||||
} else {
|
||||
await meetingStore.startScreenShare()
|
||||
}
|
||||
} catch (err) {
|
||||
if (err?.name !== 'NotAllowedError') {
|
||||
uni.showToast({ title: err?.message || '屏幕共享失败', icon: 'none' })
|
||||
}
|
||||
} finally {
|
||||
screenLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const executeCloudLawCommand = async (cmd = {}) => {
|
||||
const command = String(cmd.command || cmd.type || '').toLowerCase()
|
||||
const value = cmd.value
|
||||
@@ -893,6 +976,13 @@ onLoad(query => {
|
||||
cloudLawApiBase.value = query?.cloudLawApiBase || query?.cloud_law_api_base || ''
|
||||
cloudLawToken.value = query?.cloudLawToken || query?.cloud_law_token || ''
|
||||
cloudLawRole.value = query?.cloudLawRole || query?.cloud_law_role || ''
|
||||
// #ifdef H5
|
||||
if (String(cloudLawRole.value).toLowerCase() === 'law') {
|
||||
try {
|
||||
window.sessionStorage.setItem('echo_cloud_law_meeting_role', 'law')
|
||||
} catch (e) {}
|
||||
}
|
||||
// #endif
|
||||
const mode = String(query?.mode || query?.callType || query?.type || '').toLowerCase()
|
||||
callMode.value = mode === 'voice' || mode === 'audio' ? 'voice' : 'video'
|
||||
joinMeeting()
|
||||
@@ -1233,6 +1323,10 @@ onUnload(() => {
|
||||
.control-btn.disabled {
|
||||
opacity: 0.94;
|
||||
}
|
||||
.control-btn.active {
|
||||
background: linear-gradient(180deg, #4f8dff, #2563eb);
|
||||
border-color: rgba(79, 141, 255, 0.55);
|
||||
}
|
||||
.hangup-btn {
|
||||
width: 150rpx;
|
||||
height: 150rpx;
|
||||
|
||||
@@ -123,6 +123,7 @@ const mode = ref('create')
|
||||
const joinCode = ref('')
|
||||
const joinPassword = ref('')
|
||||
const autoJoin = ref(false)
|
||||
const cloudLawRole = ref('')
|
||||
const initializing = ref(true)
|
||||
// autoJoin 失败/超时的可见错误(替代无限"正在进入会议…"转圈)
|
||||
const autoJoinError = ref('')
|
||||
@@ -467,7 +468,8 @@ const onJoin = async (options = {}) => {
|
||||
await meetingStore.joinAndEnter(joinCode.value, joinPassword.value, prefs)
|
||||
}
|
||||
const roomCode = meetingStore.currentRoom?.room_code
|
||||
uni.redirectTo({ url: `/pages/meeting/room${roomCode ? `?code=${roomCode}` : ''}` })
|
||||
const roleQ = cloudLawRole.value ? `&cloudLawRole=${encodeURIComponent(cloudLawRole.value)}` : ''
|
||||
uni.redirectTo({ url: `/pages/meeting/room${roomCode ? `?code=${roomCode}${roleQ}` : (roleQ ? `?${roleQ.slice(1)}` : '')}` })
|
||||
} catch (err) {
|
||||
const msg = err?.message || JSON.stringify(err)
|
||||
// autoJoin 模式下把错误显式呈现在页面(toast 一闪而过,真机很难看清)
|
||||
@@ -491,7 +493,15 @@ onLoad(async (query) => {
|
||||
try {
|
||||
mode.value = query?.mode === 'join' ? 'join' : 'create'
|
||||
joinCode.value = query?.code || ''
|
||||
cloudLawRole.value = query?.cloudLawRole || query?.cloud_law_role || ''
|
||||
autoJoin.value = query?.autoJoin === '1' || query?.autoJoin === 'true' || query?.autoJoin === true
|
||||
// #ifdef H5
|
||||
if (String(cloudLawRole.value).toLowerCase() === 'law') {
|
||||
try {
|
||||
window.sessionStorage.setItem('echo_cloud_law_meeting_role', 'law')
|
||||
} catch (e) {}
|
||||
}
|
||||
// #endif
|
||||
console.log('[Preview] onLoad params:', {
|
||||
query,
|
||||
mode: mode.value,
|
||||
|
||||
@@ -145,6 +145,7 @@
|
||||
:show-recording="isHost"
|
||||
:recording="isRecording"
|
||||
:recording-loading="recordingLoading"
|
||||
:replace-chat-with-screen-share="replaceChatWithScreenShare"
|
||||
@mic-toggle="onMicToggle"
|
||||
@cam-toggle="onCamToggle"
|
||||
@screen-toggle="onScreenToggle"
|
||||
@@ -230,6 +231,10 @@ import SelfVideoFloat from '@/components/meeting/SelfVideoFloat.vue'
|
||||
const meetingStore = useMeetingStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
/** 云律律师端:工具栏用共享屏幕替换聊天 */
|
||||
const cloudLawRole = ref('')
|
||||
const replaceChatWithScreenShare = computed(() => String(cloudLawRole.value).toLowerCase() === 'law')
|
||||
|
||||
const memberVisible = ref(false)
|
||||
const inviteVisible = ref(false)
|
||||
const chatVisible = ref(false)
|
||||
@@ -839,6 +844,14 @@ let stateStopWatch = null
|
||||
let redirectingToJoin = false
|
||||
|
||||
onLoad((query) => {
|
||||
cloudLawRole.value = query?.cloudLawRole || query?.cloud_law_role || ''
|
||||
if (!cloudLawRole.value) {
|
||||
// #ifdef H5
|
||||
try {
|
||||
cloudLawRole.value = window.sessionStorage.getItem('echo_cloud_law_meeting_role') || ''
|
||||
} catch (e) {}
|
||||
// #endif
|
||||
}
|
||||
// 刷新页面 / 直链访问 room 时 store 为空,回跳 join 避免白屏
|
||||
if (!meetingStore.isInMeeting) {
|
||||
const paramCode = (query?.code || '').replace(/\D/g, '')
|
||||
|
||||
Reference in New Issue
Block a user