mdm-client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/README.md +92 -0
  2. package/index.js +35 -0
  3. package/package.json +41 -0
  4. package/src/App.vue +246 -0
  5. package/src/assets/audio/moon_light.ogg +0 -0
  6. package/src/assets/font/DINPro-Medium.otf +0 -0
  7. package/src/assets/font/FZZhengHeiS-B-GB.ttf +0 -0
  8. package/src/assets/font/PingFang Regular.otf +0 -0
  9. package/src/assets/font/SourceHanSansCN-Regular.otf +0 -0
  10. package/src/assets/image/common/add_other_icon.png +0 -0
  11. package/src/assets/image/common/bottomFrame.png +0 -0
  12. package/src/assets/image/common/broadcastOn.png +0 -0
  13. package/src/assets/image/common/broadcastTask.png +0 -0
  14. package/src/assets/image/common/call.png +0 -0
  15. package/src/assets/image/common/call_user_mic_off_icon.png +0 -0
  16. package/src/assets/image/common/call_user_mic_on_icon.png +0 -0
  17. package/src/assets/image/common/cam-off.png +0 -0
  18. package/src/assets/image/common/cam-on.png +0 -0
  19. package/src/assets/image/common/cam_off_small.png +0 -0
  20. package/src/assets/image/common/camera.png +0 -0
  21. package/src/assets/image/common/cameraBtn1.png +0 -0
  22. package/src/assets/image/common/cameraBtn2.png +0 -0
  23. package/src/assets/image/common/cameraBtn3.png +0 -0
  24. package/src/assets/image/common/cameraBtn4.png +0 -0
  25. package/src/assets/image/common/cameraBtn5.png +0 -0
  26. package/src/assets/image/common/cameraBtn6.png +0 -0
  27. package/src/assets/image/common/cameraBtn7.png +0 -0
  28. package/src/assets/image/common/cameraClose.png +0 -0
  29. package/src/assets/image/common/cameraFull.png +0 -0
  30. package/src/assets/image/common/cameraOff.png +0 -0
  31. package/src/assets/image/common/cancel_icon.png +0 -0
  32. package/src/assets/image/common/card_blue.png +0 -0
  33. package/src/assets/image/common/card_grey.png +0 -0
  34. package/src/assets/image/common/chosen_icon.png +0 -0
  35. package/src/assets/image/common/chosen_icon_slided.png +0 -0
  36. package/src/assets/image/common/custom_layout_equipment_avatar.png +0 -0
  37. package/src/assets/image/common/custom_layout_user_avatar.png +0 -0
  38. package/src/assets/image/common/default_avatar.png +0 -0
  39. package/src/assets/image/common/default_avatar_mini.png +0 -0
  40. package/src/assets/image/common/delete-number.svg +3 -0
  41. package/src/assets/image/common/deviceIcon.png +0 -0
  42. package/src/assets/image/common/fourOff.png +0 -0
  43. package/src/assets/image/common/fourOn.png +0 -0
  44. package/src/assets/image/common/group.png +0 -0
  45. package/src/assets/image/common/groupManage.png +0 -0
  46. package/src/assets/image/common/histroy_meeting_icon.png +0 -0
  47. package/src/assets/image/common/icon-device.svg +12 -0
  48. package/src/assets/image/common/icon-monitor.svg +12 -0
  49. package/src/assets/image/common/icon-/345/260/217/347/250/213/345/272/217.svg +5 -0
  50. package/src/assets/image/common/icon-/345/272/224/347/255/224.svg +5 -0
  51. package/src/assets/image/common/icon-/350/247/206/351/242/221.svg +7 -0
  52. package/src/assets/image/common/input_search_icon.png +0 -0
  53. package/src/assets/image/common/launch_icon.png +0 -0
  54. package/src/assets/image/common/layout-active1.png +0 -0
  55. package/src/assets/image/common/layout-active2.png +0 -0
  56. package/src/assets/image/common/layout1.png +0 -0
  57. package/src/assets/image/common/layout2.png +0 -0
  58. package/src/assets/image/common/loading.png +0 -0
  59. package/src/assets/image/common/login/checked_icon.png +0 -0
  60. package/src/assets/image/common/login/login_bg.png +0 -0
  61. package/src/assets/image/common/login/login_form_left_icon.png +0 -0
  62. package/src/assets/image/common/login/pwd_icon.png +0 -0
  63. package/src/assets/image/common/login/user_icon.png +0 -0
  64. package/src/assets/image/common/login/verify_icon.png +0 -0
  65. package/src/assets/image/common/logo.png +0 -0
  66. package/src/assets/image/common/ltypeOff.png +0 -0
  67. package/src/assets/image/common/ltypeOn.png +0 -0
  68. package/src/assets/image/common/manager_cam_off_icon.png +0 -0
  69. package/src/assets/image/common/manager_mic_off_icon.png +0 -0
  70. package/src/assets/image/common/map-cicle-icon.svg +6 -0
  71. package/src/assets/image/common/map-icon-search-info.svg +11 -0
  72. package/src/assets/image/common/map-location.svg +37 -0
  73. package/src/assets/image/common/map.png +0 -0
  74. package/src/assets/image/common/mic-off.png +0 -0
  75. package/src/assets/image/common/mic-on.png +0 -0
  76. package/src/assets/image/common/mic_off_small.png +0 -0
  77. package/src/assets/image/common/minum_cam_off_icon.png +0 -0
  78. package/src/assets/image/common/minum_cam_on_icon.png +0 -0
  79. package/src/assets/image/common/minum_expand_icon.png +0 -0
  80. package/src/assets/image/common/minum_mic_off_icon.png +0 -0
  81. package/src/assets/image/common/minum_mic_on_icon.png +0 -0
  82. package/src/assets/image/common/minum_reset_icon.png +0 -0
  83. package/src/assets/image/common/minum_slide_icon.png +0 -0
  84. package/src/assets/image/common/more_icon.png +0 -0
  85. package/src/assets/image/common/mute.png +0 -0
  86. package/src/assets/image/common/newFolder.png +0 -0
  87. package/src/assets/image/common/newTask.png +0 -0
  88. package/src/assets/image/common/nineOff.png +0 -0
  89. package/src/assets/image/common/nineOn.png +0 -0
  90. package/src/assets/image/common/none.png +0 -0
  91. package/src/assets/image/common/offline.png +0 -0
  92. package/src/assets/image/common/oneOff.png +0 -0
  93. package/src/assets/image/common/oneOn.png +0 -0
  94. package/src/assets/image/common/online.png +0 -0
  95. package/src/assets/image/common/output_off_small.png +0 -0
  96. package/src/assets/image/common/playOff.png +0 -0
  97. package/src/assets/image/common/playOn.png +0 -0
  98. package/src/assets/image/common/playStop.png +0 -0
  99. package/src/assets/image/common/preview_icon.png +0 -0
  100. package/src/assets/image/common/preview_meet_icon.png +0 -0
  101. package/src/assets/image/common/scan-map.png +0 -0
  102. package/src/assets/image/common/screen_blue.png +0 -0
  103. package/src/assets/image/common/screen_gray.png +0 -0
  104. package/src/assets/image/common/screen_white.png +0 -0
  105. package/src/assets/image/common/select_item_check.png +0 -0
  106. package/src/assets/image/common/select_item_checked.png +0 -0
  107. package/src/assets/image/common/selector.png +0 -0
  108. package/src/assets/image/common/selectorOn.png +0 -0
  109. package/src/assets/image/common/share_icon.png +0 -0
  110. package/src/assets/image/common/signal_good.png +0 -0
  111. package/src/assets/image/common/signal_poor.png +0 -0
  112. package/src/assets/image/common/sixteenOff.png +0 -0
  113. package/src/assets/image/common/sixteenOn.png +0 -0
  114. package/src/assets/image/common/slide-bth-expand.png +0 -0
  115. package/src/assets/image/common/slide_btn.png +0 -0
  116. package/src/assets/image/common/speak.png +0 -0
  117. package/src/assets/image/common/speakOn.png +0 -0
  118. package/src/assets/image/common/speaker-off.png +0 -0
  119. package/src/assets/image/common/speaker-on.png +0 -0
  120. package/src/assets/image/common/speaking.png +0 -0
  121. package/src/assets/image/common/speed-left.svg +5 -0
  122. package/src/assets/image/common/speed-right.svg +5 -0
  123. package/src/assets/image/common/tree_checked_icon.png +0 -0
  124. package/src/assets/image/common/tree_expand_icon.png +0 -0
  125. package/src/assets/image/common/tree_slide_icon.png +0 -0
  126. package/src/assets/image/common/tree_uncheck_icon.png +0 -0
  127. package/src/assets/image/common/unchosen_icon.png +0 -0
  128. package/src/assets/image/common/up.png +0 -0
  129. package/src/assets/image/common/volume.png +0 -0
  130. package/src/assets/image/common/warning.png +0 -0
  131. package/src/assets/image/screenBlue/a1.png +0 -0
  132. package/src/assets/image/screenBlue/a2.png +0 -0
  133. package/src/assets/image/screenBlue/a3.png +0 -0
  134. package/src/assets/image/screenBlue/a4.png +0 -0
  135. package/src/assets/image/screenBlue/a5.png +0 -0
  136. package/src/assets/image/screenBlue/a6.png +0 -0
  137. package/src/assets/image/screenBlue/add.png +0 -0
  138. package/src/assets/image/screenBlue/add_group_icon.png +0 -0
  139. package/src/assets/image/screenBlue/add_to_group_icon.png +0 -0
  140. package/src/assets/image/screenBlue/arrow_icon.png +0 -0
  141. package/src/assets/image/screenBlue/audio_level_icon.png +0 -0
  142. package/src/assets/image/screenBlue/b1.png +0 -0
  143. package/src/assets/image/screenBlue/b2.png +0 -0
  144. package/src/assets/image/screenBlue/b3.png +0 -0
  145. package/src/assets/image/screenBlue/b4.png +0 -0
  146. package/src/assets/image/screenBlue/b5.png +0 -0
  147. package/src/assets/image/screenBlue/b6.png +0 -0
  148. package/src/assets/image/screenBlue/bottom_footer_bg.png +0 -0
  149. package/src/assets/image/screenBlue/call_clear_icon.png +0 -0
  150. package/src/assets/image/screenBlue/call_duration_icon.png +0 -0
  151. package/src/assets/image/screenBlue/call_fullscreen_icon.png +0 -0
  152. package/src/assets/image/screenBlue/call_mini_icon.png +0 -0
  153. package/src/assets/image/screenBlue/call_video_icon.png +0 -0
  154. package/src/assets/image/screenBlue/call_voice_icon.png +0 -0
  155. package/src/assets/image/screenBlue/cam_on_small.png +0 -0
  156. package/src/assets/image/screenBlue/close_icon.png +0 -0
  157. package/src/assets/image/screenBlue/copy-icon.png +0 -0
  158. package/src/assets/image/screenBlue/custom_layout_drag_icon.png +0 -0
  159. package/src/assets/image/screenBlue/custom_layout_grid16_active_icon.png +0 -0
  160. package/src/assets/image/screenBlue/custom_layout_grid16_icon.png +0 -0
  161. package/src/assets/image/screenBlue/custom_layout_grid4_active_icon.png +0 -0
  162. package/src/assets/image/screenBlue/custom_layout_grid4_icon.png +0 -0
  163. package/src/assets/image/screenBlue/custom_layout_grid9_active_icon.png +0 -0
  164. package/src/assets/image/screenBlue/custom_layout_grid9_icon.png +0 -0
  165. package/src/assets/image/screenBlue/custom_layout_header_icon.png +0 -0
  166. package/src/assets/image/screenBlue/custom_layout_placeholder_bg.png +0 -0
  167. package/src/assets/image/screenBlue/custom_layout_refresh_icon.png +0 -0
  168. package/src/assets/image/screenBlue/custom_layout_rightside_active_icon.png +0 -0
  169. package/src/assets/image/screenBlue/custom_layout_rightside_icon.png +0 -0
  170. package/src/assets/image/screenBlue/custom_layout_ring_active_icon.png +0 -0
  171. package/src/assets/image/screenBlue/custom_layout_ring_icon.png +0 -0
  172. package/src/assets/image/screenBlue/custom_layout_topside_active_icon.png +0 -0
  173. package/src/assets/image/screenBlue/custom_layout_topside_icon.png +0 -0
  174. package/src/assets/image/screenBlue/date_picker_icon.png +0 -0
  175. package/src/assets/image/screenBlue/dialog_check_icon.png +0 -0
  176. package/src/assets/image/screenBlue/emoji-logo.png +0 -0
  177. package/src/assets/image/screenBlue/header_alert_icon.png +0 -0
  178. package/src/assets/image/screenBlue/manager_cam_on_icon.png +0 -0
  179. package/src/assets/image/screenBlue/manager_mic_on_icon.png +0 -0
  180. package/src/assets/image/screenBlue/manager_more_icon.png +0 -0
  181. package/src/assets/image/screenBlue/meeting_board_bg.png +0 -0
  182. package/src/assets/image/screenBlue/meeting_chat_emoji_icon.png +0 -0
  183. package/src/assets/image/screenBlue/meeting_chat_icon.png +0 -0
  184. package/src/assets/image/screenBlue/meeting_chat_image_icon.png +0 -0
  185. package/src/assets/image/screenBlue/meeting_chat_logo.png +0 -0
  186. package/src/assets/image/screenBlue/meeting_copy_icon.png +0 -0
  187. package/src/assets/image/screenBlue/meeting_invite_icon.png +0 -0
  188. package/src/assets/image/screenBlue/meeting_layout_icon.png +0 -0
  189. package/src/assets/image/screenBlue/meeting_member_icon.png +0 -0
  190. package/src/assets/image/screenBlue/meeting_mode_icon.png +0 -0
  191. package/src/assets/image/screenBlue/meeting_record_icon.png +0 -0
  192. package/src/assets/image/screenBlue/meeting_setting_icon.png +0 -0
  193. package/src/assets/image/screenBlue/meeting_share_icon.png +0 -0
  194. package/src/assets/image/screenBlue/meeting_slide_small_icon.png +0 -0
  195. package/src/assets/image/screenBlue/mic_on_small.png +0 -0
  196. package/src/assets/image/screenBlue/module_bg1.png +0 -0
  197. package/src/assets/image/screenBlue/module_bg2.png +0 -0
  198. package/src/assets/image/screenBlue/monitor/circle data.png +0 -0
  199. package/src/assets/image/screenBlue/monitor/circle.png +0 -0
  200. package/src/assets/image/screenBlue/output_on_small.png +0 -0
  201. package/src/assets/image/screenBlue/page_bg.png +0 -0
  202. package/src/assets/image/screenBlue/pic-logo.png +0 -0
  203. package/src/assets/image/screenBlue/preview_date_icon.png +0 -0
  204. package/src/assets/image/screenBlue/preview_empty_icon.png +0 -0
  205. package/src/assets/image/screenBlue/preview_more_icon.png +0 -0
  206. package/src/assets/image/screenBlue/receive_hang_off_icon.png +0 -0
  207. package/src/assets/image/screenBlue/receive_hang_on_icon.png +0 -0
  208. package/src/assets/image/screenBlue/receive_video_icon.png +0 -0
  209. package/src/assets/image/screenBlue/receive_voice_icon.png +0 -0
  210. package/src/assets/image/screenBlue/send-logo.png +0 -0
  211. package/src/assets/image/screenBlue/slide_menu_bg.png +0 -0
  212. package/src/assets/image/screenBlue/top_header_bg.png +0 -0
  213. package/src/assets/image/screenBlue/yq.png +0 -0
  214. package/src/assets/json/emoji.json +222 -0
  215. package/src/assets/style/base.scss +217 -0
  216. package/src/assets/style/elForm.scss +80 -0
  217. package/src/assets/style/font.scss +16 -0
  218. package/src/assets/style/index.scss +5 -0
  219. package/src/assets/style/math.scss +11 -0
  220. package/src/assets/style/mixin.scss +69 -0
  221. package/src/components/LiveCallBoard/LiveCallBoard.vue +237 -0
  222. package/src/components/LiveInviteReceive/LiveInviteReceive.vue +150 -0
  223. package/src/components/LiveMulti/LiveMulti.vue +303 -0
  224. package/src/components/LiveMultipleMeeting/LiveMultipleMeeting.vue +4469 -0
  225. package/src/components/LiveMultipleMeeting/style/index.scss +337 -0
  226. package/src/components/LivePoint/LivePoint.vue +372 -0
  227. package/src/components/LivePointMeeting/LivePointMeeting.vue +1134 -0
  228. package/src/components/LivePointMeeting/style/index.scss +202 -0
  229. package/src/components/MeetingReadyDialog/MeetingReadyDialog.vue +583 -0
  230. package/src/components/MiniumVideoDialog/MiniumVideoDialog.vue +449 -0
  231. package/src/components/other/LayoutPlaceholder.vue +184 -0
  232. package/src/components/other/addressBook.vue +1121 -0
  233. package/src/components/other/appointDialog.vue +208 -0
  234. package/src/components/other/callBoard.vue +191 -0
  235. package/src/components/other/chatArea.vue +727 -0
  236. package/src/components/other/customGroupDialog.vue +180 -0
  237. package/src/components/other/customLayout.vue +1112 -0
  238. package/src/components/other/editGroupDialog.vue +290 -0
  239. package/src/components/other/inviteNonContactDialog.vue +160 -0
  240. package/src/components/other/layoutSwitch.vue +183 -0
  241. package/src/components/other/leaveOptionDialog.vue +90 -0
  242. package/src/components/other/memberManage.vue +502 -0
  243. package/src/components/other/moreOptionDialog.vue +291 -0
  244. package/src/components/other/screenShareBoard.vue +121 -0
  245. package/src/components/other/selectDialog.vue +279 -0
  246. package/src/components/other/selectSpecialDialog.vue +234 -0
  247. package/src/components/other/settingDialog.vue +756 -0
  248. package/src/components/other/themeDialog.vue +180 -0
  249. package/src/components/other/updateNameDialog.vue +162 -0
  250. package/src/directive/clickOutside.js +58 -0
  251. package/src/directive/drag.js +165 -0
  252. package/src/directive/scale.js +22 -0
  253. package/src/directive/throttle.js +77 -0
  254. package/src/main.js +21 -0
  255. package/src/request/index.js +27 -0
  256. package/src/utils/api.js +82 -0
  257. package/src/utils/index.js +4 -0
  258. package/src/utils/livekit/live-client-esm-old.js +1 -0
  259. package/src/utils/livekit/live-client-esm.js +1 -0
  260. package/src/utils/message.js +24 -0
  261. package/src/utils/mitt.js +4 -0
  262. package/src/utils/tool.js +154 -0
@@ -0,0 +1,4469 @@
1
+ <template>
2
+ <div class="meeting-room" id="multiple-meeting" ref="rootElm">
3
+ <!-- 会议顶部栏 -->
4
+ <div class="meeting-room-top-bar" v-drag="'multiple-meeting'">
5
+ <div class="bar-group">
6
+ <div class="meeting-theme">
7
+ <span style="white-space: nowrap">会议主题</span>
8
+ </div>
9
+ <div class="meeting-name" @click="themeDialogShow = !themeDialogShow">
10
+ <span class="meeting-name-text">{{ meetingName }}</span>
11
+ <div
12
+ :class="['meeting-name-icon', { 'meeting-name-icon-expanded': themeDialogShow }]"
13
+ ></div>
14
+ <ThemeDialog
15
+ v-if="themeDialogShow"
16
+ :meetingName="meetingName"
17
+ :meetingNum="meetingNum"
18
+ :meetingCreator="meetingHost.ownerName"
19
+ ></ThemeDialog>
20
+ </div>
21
+ </div>
22
+ <div class="meeting-duration">
23
+ <div class="meeting-duration-logo"></div>
24
+ <span>{{ meetingDuration }}</span>
25
+ </div>
26
+ <div class="bar-group">
27
+ <div
28
+ class="btn custom-layout"
29
+ v-if="judgeParticipantIsHost(localIdentity) || judgeParticipantIsCoHost(localIdentity)"
30
+ @click="customLayoutShow = !customLayoutShow"
31
+ ></div>
32
+ <span
33
+ v-if="judgeParticipantIsHost(localIdentity) || judgeParticipantIsCoHost(localIdentity)"
34
+ class="bar-group-text"
35
+ @click="customLayoutShow = !customLayoutShow"
36
+ >自定义布局</span
37
+ >
38
+ <div
39
+ :class="['btn', layoutSwitchShow ? 'layout-active' : 'layout']"
40
+ @click="layoutSwitchShow = !layoutSwitchShow"
41
+ v-if="judgeParticipantIsHost(localIdentity) || judgeParticipantIsCoHost(localIdentity)"
42
+ >
43
+ <LayoutSwitch
44
+ v-if="layoutSwitchShow"
45
+ :currentLayout="currentLayout"
46
+ :layoutList="layoutList"
47
+ :layoutLabels="layoutLabels"
48
+ :currentRoomMode="currentRoomMode"
49
+ @setLayout="manualSetLayout"
50
+ @mouseenter.native="layoutSwitchShow = true"
51
+ @mouseleave.native="layoutSwitchShow = false"
52
+ ></LayoutSwitch>
53
+ </div>
54
+ <span
55
+ class="bar-group-text"
56
+ v-if="judgeParticipantIsHost(localIdentity) || judgeParticipantIsCoHost(localIdentity)"
57
+ >布局</span
58
+ >
59
+ <div class="btn slide-small" @click="minumDialog"></div>
60
+ <div class="btn full-screen" @click="switchFullScreen($event)"></div>
61
+ <div class="bar-group-split"></div>
62
+ <div
63
+ class="btn close-meeting-icon"
64
+ @click="isInMeeting ? openLeaveOptionDialog2() : exitPage()"
65
+ ></div>
66
+ </div>
67
+ </div>
68
+
69
+ <!-- 会议主体内容 -->
70
+ <div class="meeting-room-contain">
71
+ <div
72
+ :class="[
73
+ 'meeting-room-contain-left',
74
+ { 'meeting-room-contain-left-slide': memberManageShow || chatShow },
75
+ ]"
76
+ v-drag="'multiple-meeting'"
77
+ >
78
+ <div
79
+ :class="['meeting', layout, { 'meeting-slide': pageFooterVisible }]"
80
+ id="room"
81
+ @mousemove="setPageFooterVisible(5, $event)"
82
+ >
83
+ <ScreenShareBoard
84
+ v-if="isScreenShareEnabled"
85
+ :screenShareCaptureOption="screenShareCaptureOption"
86
+ @screenShareOptionChange="screenShareOptionChange"
87
+ @stopScreenShare="stopScreenShare"
88
+ ></ScreenShareBoard>
89
+ </div>
90
+
91
+ <!-- 会议控制栏 -->
92
+ <div
93
+ :class="['meeting-control-bar', { 'meeting-control-bar-show': pageFooterVisible }]"
94
+ @mousemove="handleClick"
95
+ >
96
+ <!-- 控制按钮组 -->
97
+ <div class="bar-group">
98
+ <div class="custom-select">
99
+ <div
100
+ :class="['custom-select-btn', isMicrophoneEnabled ? 'mic-on' : 'mic-off']"
101
+ @click="changeLocalMicrophoneStatus"
102
+ ></div>
103
+ <div class="custom-select-right" @click="audioSelectShow = !audioSelectShow">
104
+ <div class="custom-select-right-text">麦克风</div>
105
+ <div class="arrow-icon"></div>
106
+ </div>
107
+ <SelectSpecialDialog
108
+ v-if="audioSelectShow"
109
+ dialogType="audio"
110
+ :audioDevices="audioDevicesRef"
111
+ :outputDevices="outputDevicesRef"
112
+ :activeDevice="activeDevice"
113
+ @chooseAudioInputDevice="chooseAudioInputDevice"
114
+ @chooseAudioOutputDevice="chooseAudioOutputDevice"
115
+ @detectMicroAndOutput="detectMicroAndOutput"
116
+ @openSettingDialog="openSettingDialog"
117
+ ></SelectSpecialDialog>
118
+ </div>
119
+ <div class="custom-select">
120
+ <div
121
+ :class="['custom-select-btn', isCameraEnabled ? 'cam-on' : 'cam-off']"
122
+ @click="changeLocalCameraStatus"
123
+ ></div>
124
+ <div class="custom-select-right" @click="videoSelectShow = !videoSelectShow">
125
+ <div class="custom-select-right-text">摄像头</div>
126
+ <div class="arrow-icon"></div>
127
+ </div>
128
+ <SelectSpecialDialog
129
+ v-if="videoSelectShow"
130
+ :videoDevices="videoDevicesRef"
131
+ :activeDevice="activeDevice"
132
+ dialogType="video"
133
+ @chooseVideoDevice="chooseVideoDevice"
134
+ @openSettingDialog="openSettingDialog"
135
+ >
136
+ </SelectSpecialDialog>
137
+ </div>
138
+ </div>
139
+
140
+ <!-- 功能按钮组 -->
141
+ <div class="bar-group">
142
+ <div
143
+ class="custom-select"
144
+ v-if="
145
+ judgeParticipantIsHost(localIdentity) || judgeParticipantIsCoHost(localIdentity)
146
+ "
147
+ >
148
+ <div class="custom-select-btn theme-icon"></div>
149
+ <div class="custom-select-right" @click="modeSelectShow = !modeSelectShow">
150
+ <div class="custom-select-right-text">会议模式</div>
151
+ <div class="arrow-icon"></div>
152
+ </div>
153
+ <SelectDialog
154
+ v-if="modeSelectShow"
155
+ :currentRoomMode="currentRoomMode"
156
+ dialogType="other"
157
+ @chooseOtherItem="chooseMeetingMode"
158
+ @close="modeSelectShow = false"
159
+ >
160
+ </SelectDialog>
161
+ </div>
162
+ <div
163
+ :class="['t-btn', { 't-btn-hide': isBtnTextHide }]"
164
+ v-if="!isScreenShareEnabled"
165
+ @click="changeScreenShareStatus"
166
+ >
167
+ <div class="t-btn-icon screen-share"></div>
168
+ <span>共享屏幕</span>
169
+ </div>
170
+ <div
171
+ :class="['t-btn', { 't-btn-hide': isBtnTextHide }]"
172
+ v-else
173
+ @click="changeScreenShareStatus"
174
+ >
175
+ <div class="t-btn-icon screen-share-off"></div>
176
+ <span>结束共享</span>
177
+ </div>
178
+ <div :class="['t-btn', { 't-btn-hide': isBtnTextHide }]" @click="chatShow = !chatShow">
179
+ <div class="t-btn-icon chat"></div>
180
+ <span>聊天</span>
181
+ </div>
182
+ <div
183
+ :class="['t-btn', { 't-btn-hide': isBtnTextHide }]"
184
+ v-if="!isRecording"
185
+ @click="startRecord"
186
+ >
187
+ <div class="t-btn-icon record"></div>
188
+ <span>录制</span>
189
+ </div>
190
+ <div :class="['t-btn', { 't-btn-hide': isBtnTextHide }]" v-else @click="stopRecord">
191
+ <div class="t-btn-icon record-off"></div>
192
+ <span>结束录制</span>
193
+ </div>
194
+ <template
195
+ v-if="
196
+ judgeParticipantIsHost(localIdentity) || judgeParticipantIsCoHost(localIdentity)
197
+ "
198
+ >
199
+ <div
200
+ :class="['t-btn', { 't-btn-hide': isBtnTextHide }]"
201
+ @click.self="inviteWaysSelectShow = !inviteWaysSelectShow"
202
+ >
203
+ <div
204
+ class="t-btn-icon invite"
205
+ @click.self="inviteWaysSelectShow = !inviteWaysSelectShow"
206
+ ></div>
207
+ <span @click.self="inviteWaysSelectShow = !inviteWaysSelectShow">邀请</span>
208
+ <SelectDialog
209
+ v-if="inviteWaysSelectShow"
210
+ :otherOptionList="inviteWaysList"
211
+ dialogType="other"
212
+ @chooseOtherItem="chooseInviteWay"
213
+ @close="inviteWaysSelectShow = false"
214
+ ></SelectDialog>
215
+ <CallBoard
216
+ v-if="callBoardShow"
217
+ :position="callBoardOffset"
218
+ @closeCallBoard="callBoardShow = false"
219
+ @phoneCall="phoneCall"
220
+ @miniCall="miniCall"
221
+ ></CallBoard>
222
+ </div>
223
+ </template>
224
+ <div
225
+ :class="['t-btn', { 't-btn-hide': isBtnTextHide }]"
226
+ @click="memberManageShow = !memberManageShow"
227
+ >
228
+ <div class="t-btn-icon member"></div>
229
+ <span>成员管理({{ participantNum }})</span>
230
+ </div>
231
+ <template
232
+ v-if="
233
+ judgeParticipantIsHost(localIdentity) || judgeParticipantIsCoHost(localIdentity)
234
+ "
235
+ >
236
+ <div
237
+ :class="['t-btn', { 't-btn-hide': isBtnTextHide }]"
238
+ @click="openSettingDialog('1')"
239
+ >
240
+ <div class="t-btn-icon setting"></div>
241
+ <span>设置</span>
242
+ </div>
243
+ </template>
244
+ </div>
245
+ <div class="exit-btn" @click="isInMeeting ? openLeaveOptionDialog($event) : exitPage()">
246
+ {{ judgeParticipantIsHost(localIdentity) ? "结束会议" : "离开会议" }}
247
+ </div>
248
+ </div>
249
+
250
+ <AppointDialog
251
+ v-if="appointDialogShow"
252
+ :participants="participants"
253
+ :meetingHost="meetingHost"
254
+ @appointHost="appointHost"
255
+ @closeDialog="appointDialogShow = false"
256
+ >
257
+ </AppointDialog>
258
+ </div>
259
+
260
+ <!-- 右侧边栏 -->
261
+ <div class="meeting-room-contain-right">
262
+ <MemberManage
263
+ ref="memeberManageRef"
264
+ v-if="memberManageShow"
265
+ :joinedList="joinedList"
266
+ :participants="participants"
267
+ :localIdentity="localIdentity"
268
+ :unJoinedList="unJoinedList"
269
+ :meetingNum="meetingNum"
270
+ :meetingName="meetingName"
271
+ :meetingHost="meetingHost"
272
+ :meetingCoHost="meetingCoHost"
273
+ @close="
274
+ memberManageShow = false;
275
+ moreDialogShow = false;
276
+ "
277
+ @closeMoreDialog="closeMoreDialog"
278
+ @participantCameraSwitch="switchParticipantCameraStatus"
279
+ @participantMicrophoneSwitch="switchParticipantMicrophoneStatus"
280
+ @allCameraClose="closeAllCamera"
281
+ @allMicrophoneClose="closeAllMicrophone"
282
+ @openMoreDialog="moreOptionClick"
283
+ @appendMemberInvite="appendMemberInvite"
284
+ @removeUnjoinItem="removeUnjoinItem"
285
+ @filterJoinedList="filterJoinedList"
286
+ ></MemberManage>
287
+ <ChatArea
288
+ ref="chatAreaRef"
289
+ v-if="chatShow"
290
+ :localIdentity="localIdentity"
291
+ :messageList="messageList"
292
+ :baseUrl="baseUrl"
293
+ :token="token"
294
+ @close="chatShow = false"
295
+ @messageSend="messageSend"
296
+ ></ChatArea>
297
+ </div>
298
+
299
+ <CustomLayout
300
+ v-if="customLayoutShow"
301
+ :participantList="participants"
302
+ :meetingNum="meetingNum"
303
+ @closeCustomLayout="customLayoutShow = false"
304
+ ></CustomLayout>
305
+ </div>
306
+
307
+ <!-- 弹窗组件 -->
308
+ <LeaveOptionDialog
309
+ v-if="leaveOptionShow"
310
+ :position="leaveOptionOffset"
311
+ @cancelMeeting="cancelMeeting"
312
+ @leaveMeeting="openAppointDialog"
313
+ @optionClose="leaveOptionShow = false"
314
+ ></LeaveOptionDialog>
315
+ <MoreOptionDialog
316
+ v-if="moreDialogShow"
317
+ :curBlur="curBlurIdentity"
318
+ :identity="optionIdentity"
319
+ :optionDialogOffset="optionDialogOffset"
320
+ :localIdentity="localIdentity"
321
+ :participants="participants"
322
+ :meetingHost="meetingHost"
323
+ :meetingCoHost="meetingCoHost"
324
+ @close="moreDialogShow = false"
325
+ @setToBlur="setMemberBlur"
326
+ @removeFromBlur="removeCurBlur"
327
+ @openMicro="openMicro"
328
+ @closeMicro="closeMicro"
329
+ @openCamera="openCamera"
330
+ @closeCamera="closeCamera"
331
+ @setToHost="setToHost"
332
+ @setToDeafness="setToDeafness"
333
+ @cancelDeafness="cancelDeafness"
334
+ @updateName="updateName"
335
+ @remove="removeMember"
336
+ @setToCoHost="setToCoHost"
337
+ @removeFromCoHost="removeFromCoHost"
338
+ >
339
+ </MoreOptionDialog>
340
+ <AddressBook
341
+ :inviteList="inviteList"
342
+ :baseUrl="baseUrl"
343
+ :token="token"
344
+ @appendInvitePeople="appendInvitePeople"
345
+ @appendInviteDevice="appendInviteDevice"
346
+ ></AddressBook>
347
+ <UpdateNameDialog @updateNameConfirm="updateNameConfirm"></UpdateNameDialog>
348
+ <SettingDialog
349
+ ref="settingDialogRef"
350
+ :activeDevice="activeDevice"
351
+ :audioDevices="audioDevicesRef"
352
+ :videoDevices="videoDevicesRef"
353
+ :outputDevices="outputDevicesRef"
354
+ ></SettingDialog>
355
+ </div>
356
+ </template>
357
+
358
+ <script>
359
+ import LiveClient from "@/utils/livekit/live-client-esm";
360
+ import { ShowMessage, calculateTime, mittBus } from "@/utils";
361
+ import dayjs from "dayjs";
362
+ import ThemeDialog from "../other/themeDialog.vue";
363
+ import LayoutSwitch from "../other/layoutSwitch.vue";
364
+ import SelectDialog from "../other/selectDialog.vue";
365
+ import MemberManage from "../other/memberManage.vue";
366
+ import ChatArea from "../other/chatArea.vue";
367
+ import LeaveOptionDialog from "../other/leaveOptionDialog.vue";
368
+ import AppointDialog from "../other/appointDialog.vue";
369
+ import MoreOptionDialog from "../other/moreOptionDialog.vue";
370
+ import CallBoard from "../other/callBoard.vue";
371
+ import AddressBook from "../other/addressBook.vue";
372
+ import UpdateNameDialog from "../other/updateNameDialog.vue";
373
+ import SelectSpecialDialog from "../other/selectSpecialDialog.vue";
374
+ import ScreenShareBoard from "../other/screenShareBoard.vue";
375
+ import SettingDialog from "../other/settingDialog.vue";
376
+ import CustomLayout from "../other/customLayout.vue";
377
+
378
+ export default {
379
+ name: "LiveMultipleMeeting",
380
+ components: {
381
+ ThemeDialog,
382
+ LayoutSwitch,
383
+ SelectDialog,
384
+ MemberManage,
385
+ ChatArea,
386
+ LeaveOptionDialog,
387
+ AppointDialog,
388
+ MoreOptionDialog,
389
+ CallBoard,
390
+ AddressBook,
391
+ UpdateNameDialog,
392
+ SelectSpecialDialog,
393
+ ScreenShareBoard,
394
+ SettingDialog,
395
+ CustomLayout,
396
+ },
397
+ props: {
398
+ tempMeetingName: {
399
+ type: String,
400
+ default: "",
401
+ },
402
+ tempMeetingNum: {
403
+ type: String,
404
+ default: "",
405
+ },
406
+ tempCameraStatus: {
407
+ type: Boolean,
408
+ },
409
+ tempMicroStatus: {
410
+ type: Boolean,
411
+ },
412
+ tempOutputStatus: {
413
+ type: Boolean,
414
+ },
415
+ tempActiveDevice: {
416
+ type: Object,
417
+ default: () => ({}),
418
+ },
419
+ tempInviteList: {
420
+ type: Array,
421
+ default: () => [],
422
+ },
423
+ deviceList: {
424
+ type: Array,
425
+ default: () => [],
426
+ },
427
+ userData: {
428
+ type: Object,
429
+ default: () => ({}),
430
+ },
431
+ org: {
432
+ type: String,
433
+ default: "default",
434
+ },
435
+ token: {
436
+ type: String,
437
+ default: "",
438
+ },
439
+ joinType: {
440
+ type: String,
441
+ default: "launch",
442
+ },
443
+ isCustomizeMiniInvitations: {
444
+ type: Boolean,
445
+ default: false,
446
+ },
447
+ miniPagePath: {
448
+ type: String,
449
+ default: "",
450
+ },
451
+ defaultInviteWay: {
452
+ type: String,
453
+ default: "identity",
454
+ },
455
+ isInMeeting: {
456
+ type: Boolean,
457
+ default: false,
458
+ },
459
+ baseUrl: {
460
+ type: String,
461
+ default: "",
462
+ },
463
+ },
464
+ data() {
465
+ return {
466
+ // 会议基础数据
467
+ meetingName: "",
468
+ meetingNum: "",
469
+ meetingHost: {
470
+ ownerId: "",
471
+ ownerName: "",
472
+ },
473
+ meetingCoHost: [],
474
+ roomMetadata: {},
475
+ localMetadata: {},
476
+ localIdentity: "",
477
+ localName: "",
478
+
479
+ // 参与者数据
480
+ inviteList: [],
481
+ tempInvitedList: [],
482
+ participants: [],
483
+ joinedList: [],
484
+ unJoinedList: [],
485
+ filterJoinVal: "",
486
+
487
+ // 设备数据
488
+ meetingQuality: "",
489
+ meetingTTL: "",
490
+ audioDevicesRef: [],
491
+ videoDevicesRef: [],
492
+ outputDevicesRef: [],
493
+ activeDevice: {
494
+ audioInputDevice: "",
495
+ audioOutputDevice: "",
496
+ videoDevice: "",
497
+ },
498
+
499
+ // 消息数据
500
+ messageList: [],
501
+ meetingWXLink: "",
502
+
503
+ // 布局数据
504
+ currentLayout: "grid",
505
+ currentRoomMode: "normal",
506
+ layoutList: ["grid", "rightSide"],
507
+ layoutLabels: ["宫格", "右侧边栏"],
508
+
509
+ // 状态数据
510
+ duration: 0,
511
+ memberManageShow: false,
512
+ chatShow: false,
513
+ themeDialogShow: false,
514
+ layoutSwitchShow: false,
515
+ audioSelectShow: false,
516
+ videoSelectShow: false,
517
+ modeSelectShow: false,
518
+ isMicrophoneEnabled: false,
519
+ isCameraEnabled: false,
520
+ isScreenShareEnabled: false,
521
+ isOutputEnabled: false,
522
+ isRecording: false,
523
+ recordStreamUrl: null,
524
+
525
+ // 技术数据
526
+ pageEventList: [],
527
+ trackList: [],
528
+ curBlurIdentity: "",
529
+ curHostIdentity: null,
530
+ moreDialogShow: false,
531
+ optionDialogOffset: {},
532
+ optionIdentity: "",
533
+ pageFooterVisible: false,
534
+ footerVisibleDuration: 3,
535
+ messageTemplate: "测试短信",
536
+ isDeleteRoom: false,
537
+ isLeaveRoom: false,
538
+ leaveOptionShow: false,
539
+ leaveOptionOffset: {},
540
+ appointDialogShow: false,
541
+ callBoardShow: false,
542
+ callBoardOffset: {},
543
+ isScreenShareChange: false,
544
+ screenShareChangeOldLayout: "",
545
+ isDurationCalc: true,
546
+ isFullScreen: false,
547
+ inviteWaysSelectShow: false,
548
+ inviteWaysList: [
549
+ {
550
+ label: "通讯录邀请",
551
+ value: "addressbook",
552
+ },
553
+ {
554
+ label: "电话呼叫",
555
+ value: "videoCall",
556
+ },
557
+ ],
558
+ screenShareCaptureOption: {
559
+ systemAudio: "exclude",
560
+ resolution: {
561
+ width: 1920,
562
+ height: 1080,
563
+ frameRate: 60,
564
+ },
565
+ },
566
+ screenShareChangeOldBlur: null,
567
+ isBatchUpdating: false,
568
+ isInitializing: false,
569
+ domOperationQueue: [],
570
+ watchEnabled: {
571
+ layout: true,
572
+ mode: true,
573
+ host: true,
574
+ blur: true,
575
+ },
576
+ customLayoutShow: false,
577
+ placeholderTemplate: `
578
+ <div class="board">
579
+ <div class="board-icon"></div>
580
+ </div>
581
+ <div class="describe">
582
+ <div class="identity">空缺席位</div>
583
+ </div>
584
+ `,
585
+ showMessage: null,
586
+ liveClient: null,
587
+ meetingInterval: null,
588
+ timeInterval: null,
589
+ durationInterval: null,
590
+ footerInterval: null,
591
+ unjoinParticipantInterval: null,
592
+ };
593
+ },
594
+ computed: {
595
+ meetingDuration() {
596
+ return calculateTime(this.duration);
597
+ },
598
+ participantNum() {
599
+ return this.participants.length;
600
+ },
601
+ participantIdentities() {
602
+ return this.participantNum > 0 ? this.participants.map((item) => item.identity) : [];
603
+ },
604
+ inviteNum() {
605
+ return this.inviteList.length;
606
+ },
607
+ deviceNum() {
608
+ return this.deviceList.length;
609
+ },
610
+ invitedNum() {
611
+ return this.tempInvitedList?.length || 0;
612
+ },
613
+ hasScreenShare() {
614
+ if (this.participantNum > 0) {
615
+ const index = this.participants.findIndex((item) => {
616
+ return item.isScreenShareEnabled;
617
+ });
618
+ return index;
619
+ } else {
620
+ return -1;
621
+ }
622
+ },
623
+ isBtnTextHide() {
624
+ return this.memberManageShow || this.chatShow;
625
+ },
626
+ layout() {
627
+ if (this.currentRoomMode === "pointTurn") {
628
+ return "point-turn";
629
+ } else {
630
+ if (this.currentLayout === "grid") {
631
+ if (this.participantNum <= 1) {
632
+ return "grid1";
633
+ } else if (this.participantNum <= 2) {
634
+ return "grid2";
635
+ } else if (this.participantNum <= 4) {
636
+ return "grid3";
637
+ } else if (this.participantNum <= 6) {
638
+ return "grid4";
639
+ } else if (this.participantNum <= 9) {
640
+ return "grid5";
641
+ } else if (this.participantNum <= 12) {
642
+ return "grid6";
643
+ } else if (this.participantNum <= 16) {
644
+ return "grid7";
645
+ } else {
646
+ return "grid8";
647
+ }
648
+ } else {
649
+ return this.currentLayout;
650
+ }
651
+ }
652
+ },
653
+ },
654
+ watch: {
655
+ footerVisibleDuration(newVal) {
656
+ if (newVal <= 0) {
657
+ this.pageFooterVisible = false;
658
+ if (this.footerInterval) {
659
+ clearInterval(this.footerInterval);
660
+ }
661
+ }
662
+ },
663
+ currentLayout: {
664
+ handler(newLayout, oldLayout) {
665
+ if (!this.watchEnabled.layout || this.isBatchUpdating) {
666
+ console.log("布局watch被禁用,跳过DOM操作");
667
+ return;
668
+ }
669
+ this.$nextTick(() => {
670
+ this.executeLayoutChange(newLayout, oldLayout);
671
+ });
672
+ },
673
+ },
674
+ curHostIdentity: {
675
+ handler(newVal, oldVal) {
676
+ if (!this.watchEnabled.host || this.isBatchUpdating) {
677
+ console.log("主持人watch被禁用,跳过DOM操作");
678
+ return;
679
+ }
680
+ this.$nextTick(() => {
681
+ this.executeHostChange(newVal, oldVal);
682
+ });
683
+ },
684
+ },
685
+ curBlurIdentity: {
686
+ handler(newVal, oldVal) {
687
+ if (!this.watchEnabled.blur || this.isBatchUpdating) {
688
+ console.log("焦点用户watch被禁用,跳过DOM操作");
689
+ return;
690
+ }
691
+ this.executeBlurChange(newVal, oldVal);
692
+ },
693
+ },
694
+ currentRoomMode: {
695
+ handler(newMode) {
696
+ if (!this.watchEnabled.mode || this.isBatchUpdating) {
697
+ console.log("模式watch被禁用,跳过DOM操作");
698
+ return;
699
+ }
700
+ this.$nextTick(() => {
701
+ this.executeModeChange(newMode);
702
+ });
703
+ },
704
+ },
705
+ },
706
+ created() {
707
+ this.showMessage = new ShowMessage();
708
+ this.liveClient = null;
709
+ },
710
+ async mounted() {
711
+ await this.getDeviceList();
712
+ this.initData();
713
+ if (this.joinType === "launch") {
714
+ this.initLiveClient();
715
+ this.liveClient.roomName = this.tempMeetingName;
716
+ await this.createRoom();
717
+ } else {
718
+ this.initLiveClient();
719
+ this.meetingNum = this.tempMeetingNum;
720
+ await this.joinRoom();
721
+ }
722
+ this.setPageFooterVisible(5);
723
+ this.initGlobleEvent();
724
+ },
725
+ beforeDestroy() {
726
+ this.stopUnjoinParticipantPolling();
727
+ this.dispatchLiveClientEvent();
728
+ this.removeGlobleEvent();
729
+ this.liveClient = null;
730
+ },
731
+ methods: {
732
+ // DOM操作管理函数
733
+ async batchUpdateStates(updateFn) {
734
+ this.isBatchUpdating = true;
735
+
736
+ // 暂停所有watch监听器的DOM操作
737
+ this.watchEnabled = {
738
+ layout: false,
739
+ mode: false,
740
+ host: false,
741
+ blur: false,
742
+ };
743
+
744
+ try {
745
+ // 执行状态更新
746
+ await updateFn();
747
+
748
+ this.$nextTick(async () => {
749
+ await this.processDOMQueue();
750
+ });
751
+ } finally {
752
+ // 重新启用watch监听器
753
+ this.watchEnabled = {
754
+ layout: true,
755
+ mode: true,
756
+ host: true,
757
+ blur: true,
758
+ };
759
+
760
+ this.isBatchUpdating = false;
761
+ }
762
+ },
763
+ // 处理DOM操作队列
764
+ async processDOMQueue() {
765
+ if (this.domOperationQueue.length === 0) return;
766
+
767
+ console.log("执行DOM操作队列,队列长度:", this.domOperationQueue.length);
768
+
769
+ // 根据操作类型和优先级排序
770
+ const sortedQueue = [...this.domOperationQueue].sort((a, b) => {
771
+ const priority = { mode: 1, layout: 2, host: 3, blur: 4 };
772
+ return priority[a.type] - priority[b.type];
773
+ });
774
+
775
+ // 清空队列
776
+ this.domOperationQueue = [];
777
+
778
+ // 依次执行DOM操作
779
+ for (const operation of sortedQueue) {
780
+ try {
781
+ console.log(`开始执行DOM操作: ${operation.type}`);
782
+ await operation.execute();
783
+ this.$nextTick(() => {
784
+ console.log(`DOM操作完成: ${operation.type}`);
785
+ });
786
+ } catch (error) {
787
+ console.error(`DOM操作执行失败:`, operation.type, error);
788
+ // 确保错误不会阻止后续操作
789
+ if (error.name === "NotFoundError" && error.message.includes("removeChild")) {
790
+ console.warn(`DOM元素已被移除,跳过此操作: ${operation.type}`);
791
+ } else {
792
+ console.error(`其他DOM操作错误:`, error);
793
+ }
794
+ }
795
+ }
796
+ },
797
+ // 添加DOM操作到队列
798
+ addDOMOperation(type, executeFn) {
799
+ // 移除同类型的旧操作,只保留最新的
800
+ this.domOperationQueue = this.domOperationQueue.filter((op) => op.type !== type);
801
+
802
+ // 添加操作到队列
803
+ this.domOperationQueue.push({
804
+ type,
805
+ execute: async () => {
806
+ try {
807
+ // 在执行前检查DOM容器是否存在
808
+ const container = document.getElementById("room");
809
+ if (!container) {
810
+ console.warn(`DOM容器不存在,跳过${type}操作`);
811
+ return;
812
+ }
813
+
814
+ // 执行实际的DOM操作
815
+ await executeFn();
816
+ } catch (error) {
817
+ console.error(`执行${type}DOM操作时发生错误:`, error);
818
+ throw error; // 重新抛出错误以便上层处理
819
+ }
820
+ },
821
+ timestamp: Date.now(),
822
+ });
823
+ },
824
+ // 动态等待与会者首次渲染完成
825
+ async waitForParticipantsRender() {
826
+ const minDelay = 1000; // 最小延迟1000ms,确保有时间开始渲染
827
+ const checkInterval = 100; // 每100ms检查一次
828
+ const stableTime = 300; // 稳定时间300ms,参与者数量不变则认为渲染完成
829
+ const maxWaitTime = 5000; // 最大等待5秒,避免无限等待
830
+
831
+ console.log(`开始动态等待,当前参与者数量: ${this.participantNum}`);
832
+
833
+ // 先等待最小延迟
834
+ await new Promise((resolve) => setTimeout(resolve, minDelay));
835
+
836
+ let lastParticipantNum = this.participantNum;
837
+ let stableStartTime = Date.now();
838
+ let totalStartTime = Date.now();
839
+
840
+ while (true) {
841
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
842
+
843
+ const currentParticipantNum = this.participantNum;
844
+ const currentTime = Date.now();
845
+
846
+ // 如果参与者数量发生变化,重置稳定计时器
847
+ if (currentParticipantNum !== lastParticipantNum) {
848
+ console.log(`参与者数量变化: ${lastParticipantNum} → ${currentParticipantNum}`);
849
+ lastParticipantNum = currentParticipantNum;
850
+ stableStartTime = currentTime;
851
+ }
852
+
853
+ // 检查是否达到稳定状态(参与者数量在stableTime内没有变化)
854
+ if (currentTime - stableStartTime >= stableTime) {
855
+ console.log(
856
+ `参与者数量稳定${stableTime}ms,当前数量: ${currentParticipantNum},认为首次渲染完成`
857
+ );
858
+ break;
859
+ }
860
+
861
+ // 检查是否超过最大等待时间
862
+ if (currentTime - totalStartTime >= maxWaitTime) {
863
+ console.log(
864
+ `达到最大等待时间${maxWaitTime}ms,强制结束等待,当前参与者数量: ${currentParticipantNum}`
865
+ );
866
+ break;
867
+ }
868
+ }
869
+
870
+ const totalWaitTime = Date.now() - totalStartTime;
871
+ console.log(
872
+ `动态等待完成,总等待时间: ${totalWaitTime}ms,最终参与者数量: ${this.participantNum}`
873
+ );
874
+ },
875
+ // 计算MoreOptionDialog选项数量的辅助函数
876
+ getOptionCount(identity) {
877
+ const participantItem = this.getUserItemByIdentity(identity);
878
+ if (!participantItem || !participantItem.metadata) return 8; // 默认最大选项数
879
+
880
+ const metadata = participantItem.metadata;
881
+ const isLocalParticipant = identity === this.localIdentity;
882
+
883
+ let optionCount = 3; // 基础选项:设为主屏、麦克风、摄像头
884
+
885
+ if (this.judgeParticipantIsHost(this.localIdentity)) {
886
+ // 本地为主持人
887
+ if (metadata?.platformID == 2 || metadata?.platformID == 5 || metadata?.platformID == 6) {
888
+ // 设备
889
+ optionCount += isLocalParticipant ? 1 : 2; // 修改名称 + 移出会议(非本地)
890
+ } else if (metadata?.platformID == 4) {
891
+ // volte手机端
892
+ optionCount += isLocalParticipant ? 1 : 3; // 置聋 + 修改名称 + 移出会议(非本地)
893
+ } else {
894
+ // app手机端
895
+ optionCount += isLocalParticipant ? 1 : 5; // 置聋 + 设为主持人 + 联席主持人 + 修改名称 + 移出会议(非本地)
896
+ }
897
+ } else if (this.judgeParticipantIsCoHost(this.localIdentity)) {
898
+ // 本地为联席主持人
899
+ if (metadata?.platformID == 2 || metadata?.platformID == 5 || metadata?.platformID == 6) {
900
+ optionCount += isLocalParticipant ? 1 : 2;
901
+ } else if (metadata?.platformID == 4) {
902
+ optionCount += isLocalParticipant ? 1 : 3;
903
+ } else {
904
+ optionCount += isLocalParticipant ? 1 : 4; // 置聋 + 联席主持人 + 修改名称 + 移出会议(非本地)
905
+ }
906
+ }
907
+
908
+ return Math.min(optionCount, 8); // 限制最大选项数
909
+ },
910
+ // 弹窗定位工具函数
911
+ calculateDialogPosition(event, options = {}) {
912
+ const {
913
+ dialogWidth = 200,
914
+ dialogHeight = 300,
915
+ containerId = "multiple-meeting",
916
+ offsetX = 0,
917
+ offsetY = 20,
918
+ preferredPosition = "bottom-left", // 'bottom-left', 'bottom-right', 'top-left', 'top-right'
919
+ optionCount = null, // 动态计算高度的选项数量
920
+ } = options;
921
+
922
+ // 如果提供了选项数量,动态计算弹窗高度
923
+ let actualDialogHeight = dialogHeight;
924
+ if (optionCount && optionCount > 0) {
925
+ // MoreOptionDialog 每个选项36px高度 + 6px间距 + 8px padding (上下各4px)
926
+ actualDialogHeight = optionCount * 36 + (optionCount - 1) * 6 + 8;
927
+ }
928
+
929
+ // 获取容器的位置信息
930
+ const container = document.getElementById(containerId);
931
+ const containerRect = container ? container.getBoundingClientRect() : { left: 0, top: 0 };
932
+
933
+ // 计算相对于容器的坐标
934
+ const relativeX = event.clientX - containerRect.left;
935
+ const relativeY = event.clientY - containerRect.top;
936
+
937
+ // 获取容器尺寸
938
+ const containerWidth = container ? container.clientWidth : window.innerWidth;
939
+ const containerHeight = container ? container.clientHeight : window.innerHeight;
940
+
941
+ let left, top;
942
+
943
+ // 根据首选位置计算初始位置
944
+ switch (preferredPosition) {
945
+ case "bottom-right":
946
+ left = relativeX + offsetX;
947
+ top = relativeY + offsetY;
948
+ break;
949
+ case "top-left":
950
+ left = relativeX - dialogWidth - offsetX;
951
+ top = relativeY - actualDialogHeight - offsetY;
952
+ break;
953
+ case "top-right":
954
+ left = relativeX + offsetX;
955
+ top = relativeY - actualDialogHeight - offsetY;
956
+ break;
957
+ case "bottom-left":
958
+ default:
959
+ left = relativeX - dialogWidth - offsetX;
960
+ top = relativeY + offsetY;
961
+ break;
962
+ }
963
+
964
+ // 边界检查和自动调整
965
+ const margin = 10; // 距离边界的最小距离
966
+
967
+ // 检查左右边界
968
+ if (left < margin) {
969
+ left = relativeX + offsetX; // 切换到右侧
970
+ } else if (left + dialogWidth > containerWidth - margin) {
971
+ left = relativeX - dialogWidth - offsetX; // 切换到左侧
972
+ if (left < margin) {
973
+ left = containerWidth - dialogWidth - margin; // 贴右边界
974
+ }
975
+ }
976
+
977
+ // 检查上下边界
978
+ if (top < margin) {
979
+ top = relativeY + offsetY; // 切换到下方
980
+ } else if (top + actualDialogHeight > containerHeight - margin) {
981
+ top = relativeY - actualDialogHeight - offsetY; // 切换到上方
982
+ if (top < margin) {
983
+ top = margin; // 贴上边界
984
+ }
985
+ }
986
+
987
+ // 确保位置不为负数
988
+ left = Math.max(margin, left);
989
+ top = Math.max(margin, top);
990
+
991
+ return {
992
+ left: left + "px",
993
+ top: top + "px",
994
+ };
995
+ },
996
+ // 取消屏幕共享自动还原的逻辑
997
+ cancelScreenShareAutoRestore(reason) {
998
+ if (this.isScreenShareChange) {
999
+ console.log(`检测到屏幕共享期间${reason},取消自动还原`);
1000
+ this.isScreenShareChange = false;
1001
+ this.screenShareChangeOldLayout = null;
1002
+ this.screenShareChangeOldBlur = null;
1003
+ }
1004
+ },
1005
+ // 手动切换布局(供LayoutSwitch组件调用)
1006
+ async manualSetLayout(layout) {
1007
+ // 仅主持人和联席主持人可以切换布局
1008
+ if (
1009
+ !this.judgeParticipantIsHost(this.localIdentity) &&
1010
+ !this.judgeParticipantIsCoHost(this.localIdentity)
1011
+ ) {
1012
+ this.showMessage.message("warning", "仅主持人和联席主持人可以切换布局");
1013
+ return;
1014
+ }
1015
+
1016
+ // 手动切换布局时取消自动还原
1017
+ this.cancelScreenShareAutoRestore("手动切换布局");
1018
+
1019
+ await this.setLayout(layout);
1020
+ },
1021
+ // 设置布局(内部使用,包括自动切换)
1022
+ async setLayout(layout) {
1023
+ // 仅主持人和联席主持人可以切换布局
1024
+ if (
1025
+ !this.judgeParticipantIsHost(this.localIdentity) &&
1026
+ !this.judgeParticipantIsCoHost(this.localIdentity)
1027
+ ) {
1028
+ this.showMessage.message("warning", "仅主持人和联席主持人可以切换布局");
1029
+ return;
1030
+ }
1031
+ await this.liveClient.setRoomLayout(layout);
1032
+ },
1033
+ // 判断与会者是否为主持人
1034
+ judgeParticipantIsHost(identity) {
1035
+ return this.meetingHost.ownerId === identity;
1036
+ },
1037
+ // 判断与会者是否为联席主持人
1038
+ judgeParticipantIsCoHost(identity) {
1039
+ if (this.meetingCoHost.length > 0) {
1040
+ return this.meetingCoHost.some((item) => item.coHostId === identity);
1041
+ } else {
1042
+ return false;
1043
+ }
1044
+ },
1045
+ // 判断是否已经在与会者列表中
1046
+ judgeParticipantInMeeting(identity) {
1047
+ if (this.participantNum > 0) {
1048
+ let index = this.participants.findIndex((item) => {
1049
+ return item.identity === identity;
1050
+ });
1051
+ if (index >= 0) {
1052
+ return index;
1053
+ } else {
1054
+ return -1;
1055
+ }
1056
+ } else {
1057
+ return -1;
1058
+ }
1059
+ },
1060
+ // 添加用户到与会者列表
1061
+ addToParticipantList(userItem) {
1062
+ if (this.participantNum <= 0) {
1063
+ this.participants.push(userItem);
1064
+ } else {
1065
+ const index = this.judgeParticipantInMeeting(userItem.identity);
1066
+ if (index !== -1) {
1067
+ // 更新数组中与会者
1068
+ this.participants.splice(index, 1, userItem);
1069
+ } else {
1070
+ this.participants.push(userItem);
1071
+ }
1072
+ }
1073
+ },
1074
+ removeFromParticipantList(identity) {
1075
+ if (this.participantNum > 0) {
1076
+ let index = this.judgeParticipantInMeeting(identity);
1077
+ if (index !== -1) {
1078
+ this.participants.splice(index, 1);
1079
+ }
1080
+ }
1081
+ },
1082
+ // 查找匹配搜索值的与会者列表
1083
+ filterParticipantList() {
1084
+ this.joinedList = [];
1085
+ this.participants.forEach((item) => {
1086
+ if (item.name.includes(this.filterJoinVal)) {
1087
+ this.joinedList.push({
1088
+ identity: item?.identity,
1089
+ name: item.name,
1090
+ audioLevel: item.audioLevel,
1091
+ isCameraEnabled: item.isCameraEnabled,
1092
+ isLocal: item.isLocal,
1093
+ isMicrophoneEnabled: item.isMicrophoneEnabled,
1094
+ isScreenShareEnabled: item.isScreenShareEnabled,
1095
+ isSpeaking: item.isSpeaking,
1096
+ sid: item.sid,
1097
+ metadata: item.metadata,
1098
+ });
1099
+ }
1100
+ });
1101
+ },
1102
+ // 将用户从邀请列表中移除
1103
+ removeFromInviteList(identity) {
1104
+ if (this.inviteNum > 0) {
1105
+ const index = this.inviteList.findIndex((item) => {
1106
+ if (item?.loginCode) {
1107
+ return item.loginCode === identity;
1108
+ } else {
1109
+ return item.phone === identity;
1110
+ }
1111
+ });
1112
+ if (index !== -1) {
1113
+ this.inviteList.splice(index, 1);
1114
+ }
1115
+ }
1116
+ },
1117
+ // 添加用户到邀请列表中
1118
+ addToInviteList(userItem) {
1119
+ if (this.inviteNum <= 0) {
1120
+ this.inviteList.push(userItem);
1121
+ } else {
1122
+ if (this.judgePersonIsInvited(userItem.id) < 0) {
1123
+ this.inviteList.push(userItem);
1124
+ }
1125
+ }
1126
+ },
1127
+ // 判断是否已经在邀请列表中
1128
+ judgePersonIsInvited(id) {
1129
+ if (this.inviteNum > 0) {
1130
+ let index = this.inviteList.findIndex((item) => {
1131
+ return item.id === id;
1132
+ });
1133
+ return index;
1134
+ } else {
1135
+ return -1;
1136
+ }
1137
+ },
1138
+ // 判断是否有除自身外其他与会者共享屏幕
1139
+ judgeOtherScreenShare(identity) {
1140
+ if (this.participantNum > 0) {
1141
+ const index = this.participants.findIndex((item) => {
1142
+ return item.isScreenShareEnabled && item.identity !== identity;
1143
+ });
1144
+ return index;
1145
+ } else {
1146
+ return -1;
1147
+ }
1148
+ },
1149
+ // 将消息添加到消息队列
1150
+ addToMessageList(message) {
1151
+ this.messageList.push(message);
1152
+ },
1153
+ // 根据与会者identity返回与会者
1154
+ getUserItemByIdentity(identity) {
1155
+ if (this.participantNum > 0) {
1156
+ const index = this.participants.findIndex((item) => {
1157
+ return item.identity === identity;
1158
+ });
1159
+ if (index > -1) {
1160
+ return this.participants[index];
1161
+ } else {
1162
+ return null;
1163
+ }
1164
+ } else {
1165
+ return null;
1166
+ }
1167
+ },
1168
+ // 返回本地与会者
1169
+ getLocalParticipant() {
1170
+ if (this.participantNum > 0) {
1171
+ const index = this.participants.findIndex((item) => {
1172
+ return item.isLocal;
1173
+ });
1174
+ if (index > -1) {
1175
+ return this.participants[index];
1176
+ } else {
1177
+ return null;
1178
+ }
1179
+ } else {
1180
+ return null;
1181
+ }
1182
+ },
1183
+
1184
+ // 基础方法
1185
+ async cancelMeeting() {
1186
+ if (this.isFullScreen) {
1187
+ this.isDeleteRoom = true;
1188
+ this.leaveRoom();
1189
+ } else {
1190
+ this.$confirm("点击结束会议后会议将被解散,您确定要这么做么?", "警告", {
1191
+ confirmButtonText: "确定",
1192
+ cancelButtonText: "取消",
1193
+ type: "warning",
1194
+ })
1195
+ .then(() => {
1196
+ this.isDeleteRoom = true;
1197
+ this.leaveRoom();
1198
+ })
1199
+ .catch(() => {
1200
+ void 0;
1201
+ });
1202
+ }
1203
+ },
1204
+
1205
+ openAppointDialog() {
1206
+ if (this.judgeParticipantIsHost(this.localIdentity)) {
1207
+ if (this.participantNum <= 1) {
1208
+ this.cancelMeeting();
1209
+ } else {
1210
+ const index = this.participants.findIndex((participant) => {
1211
+ return (
1212
+ (participant.metadata.platformID == 0 || participant.metadata.platformID == 1) &&
1213
+ participant.identity !== this.localIdentity
1214
+ );
1215
+ });
1216
+ if (index !== -1) {
1217
+ this.appointDialogShow = true;
1218
+ } else {
1219
+ this.$confirm("当前会议没有pc端和app端与会者, 无法指派主持人, 将直接解散会议", "警告", {
1220
+ confirmButtonText: "确定",
1221
+ cancelButtonText: "取消",
1222
+ type: "warning",
1223
+ })
1224
+ .then(() => {
1225
+ this.isDeleteRoom = true;
1226
+ this.leaveRoom();
1227
+ })
1228
+ .catch(() => {
1229
+ void 0;
1230
+ });
1231
+ }
1232
+ }
1233
+ }
1234
+ },
1235
+ initData() {
1236
+ this.participants = [];
1237
+ this.isCameraEnabled = this.tempCameraStatus;
1238
+ this.isMicrophoneEnabled = this.tempMicroStatus;
1239
+ this.isOutputEnabled = this.tempOutputStatus;
1240
+ },
1241
+
1242
+ async getDeviceList() {
1243
+ try {
1244
+ const { audioDevices, videoDevices, outputDevices } = await LiveClient.getDeviceList();
1245
+ this.audioDevicesRef = audioDevices.map((item) => {
1246
+ return {
1247
+ label: item.label,
1248
+ value: item.deviceId,
1249
+ };
1250
+ });
1251
+ this.videoDevicesRef = videoDevices.map((item) => {
1252
+ return {
1253
+ label: item.label,
1254
+ value: item.deviceId,
1255
+ };
1256
+ });
1257
+ this.outputDevicesRef = outputDevices.map((item) => {
1258
+ return {
1259
+ label: item.label,
1260
+ value: item.deviceId,
1261
+ };
1262
+ });
1263
+ } catch (err) {
1264
+ this.showMessage.message("error", "获取设备列表失败");
1265
+ }
1266
+ },
1267
+ // 保存liveClient监听的事件到数组中,方便离开页面时解除绑定
1268
+ addEventToList(eventName) {
1269
+ if (this.pageEventList.indexOf(eventName) < 0) {
1270
+ this.pageEventList.push(eventName);
1271
+ }
1272
+ },
1273
+ clearPageInterval() {
1274
+ if (this.meetingInterval) {
1275
+ clearInterval(this.meetingInterval);
1276
+ this.meetingInterval = null;
1277
+ }
1278
+ if (this.durationInterval) {
1279
+ clearInterval(this.durationInterval);
1280
+ this.durationInterval = null;
1281
+ }
1282
+ if (this.footerInterval) {
1283
+ clearInterval(this.footerInterval);
1284
+ this.footerInterval = null;
1285
+ }
1286
+ if (this.timeInterval) {
1287
+ clearInterval(this.timeInterval);
1288
+ this.timeInterval = null;
1289
+ }
1290
+ if (this.unjoinParticipantInterval) {
1291
+ clearInterval(this.unjoinParticipantInterval);
1292
+ this.unjoinParticipantInterval = null;
1293
+ }
1294
+ },
1295
+ clearAllTrack() {
1296
+ if (this.trackList.length > 0) {
1297
+ this.trackList.forEach((track) => {
1298
+ track?.detach();
1299
+ track?.stop();
1300
+ });
1301
+ }
1302
+ },
1303
+ initLiveClientEvent() {
1304
+ if (!this.liveClient) {
1305
+ this.showMessage.message("error", "liveClient初始化失败");
1306
+ return;
1307
+ }
1308
+ this.liveClient.on("errorAlert", (e) => {
1309
+ console.log("errorAlert", e);
1310
+ this.addEventToList("errorAlert");
1311
+ if (e.severity == "high" || e.severity == "critical") {
1312
+ if (e?.context?.originalMessage == "could not establish pc connection") {
1313
+ this.showMessage.message("error", "无法建立连接,请检查udp端口是否启用");
1314
+ } else {
1315
+ this.showMessage.message("error", e.message);
1316
+ }
1317
+ }
1318
+ });
1319
+ this.liveClient.on("roomCreatedSuccess", async (e) => {
1320
+ this.addEventToList("roomCreatedSuccess");
1321
+ this.showMessage.message("success", "会议房间创建成功");
1322
+ this.meetingNum = e;
1323
+ await this.joinRoom();
1324
+ });
1325
+ this.liveClient.on("joinRoomError", (e) => {
1326
+ this.addEventToList("joinRoomError");
1327
+ console.error("加入房间失败", e.message);
1328
+ this.$emit("meetingEnd");
1329
+ if (e.message.includes("unknown websocket error")) {
1330
+ this.showMessage.message("error", `加入房间失败: 请检查会议内websocket连接是否正常`);
1331
+ } else if (e.message.includes("could not establish pc connection")) {
1332
+ this.showMessage.message("error", `加入房间失败: 请检查udp端口是否启用`);
1333
+ } else {
1334
+ this.showMessage.message("error", `加入房间失败: ${e.message}`);
1335
+ }
1336
+ });
1337
+ this.liveClient.on("roomMetadataChanged", (e) => {
1338
+ const metadata = JSON.parse(e);
1339
+ this.roomMetadataChange(metadata);
1340
+ });
1341
+ this.liveClient.on("roomDataReceived", (e) => {
1342
+ this.addEventToList("roomDataReceived");
1343
+ console.log("roomDataReceived", e);
1344
+ this.handleRoomDataReceived(e);
1345
+ });
1346
+ this.liveClient.on("participantDataReceived", (e) => {
1347
+ this.addEventToList("participantDataReceived");
1348
+ console.log("participantDataReceived", e);
1349
+ });
1350
+ this.liveClient.on("roomConnected", async (e) => {
1351
+ this.addEventToList("roomConnected");
1352
+ if (this.liveClient.room) {
1353
+ const metadata = JSON.parse(this.liveClient.room.metadata);
1354
+ // 初始化部分会议信息
1355
+ this.initRoomMetadata(metadata);
1356
+ }
1357
+ if (this.userData?.username) {
1358
+ // 如果用户已经登录,则将入会名称修改为用户名
1359
+ this.liveClient.updateParticipantName(this.userData.userId, this.userData.username);
1360
+ }
1361
+ this.meetingInterval = setInterval(() => {
1362
+ this.meetingTTL = navigator.connection.rtt;
1363
+ }, 1000);
1364
+ console.log("joinType", this.joinType);
1365
+ // 判断当前是发起者还是加入者,是发起者则发送邀请信息
1366
+ this.$emit("meetingStart");
1367
+ if (this.joinType == "launch") {
1368
+ // 发送邀请信息
1369
+ this.sendInviteMessage([], this.defaultInviteWay == "mini" ? 3 : 0);
1370
+ // 拉取监控设备
1371
+ this.deviceNum > 0 &&
1372
+ this.deviceList.forEach((item) => {
1373
+ if (item.source == "监控") {
1374
+ this.pullMonitorDevice(item.monitorID, item.label);
1375
+ } else if (item.source == "设备") {
1376
+ this.pullMonitorDevice(item.equipmentID, item.label);
1377
+ }
1378
+ });
1379
+ }
1380
+ // 启动获取未入会和邀请人员轮询
1381
+ this.startUnjoinParticipantPolling();
1382
+ });
1383
+ this.liveClient.on("roomDisconnected", () => {
1384
+ this.addEventToList("roomDisconnected");
1385
+ this.showMessage.message("info", "房间链接已断开");
1386
+ this.$emit("meetingEnd");
1387
+ this.stopUnjoinParticipantPolling();
1388
+ this.clearPageInterval();
1389
+ this.clearAllTrack();
1390
+ if (this.isDeleteRoom) {
1391
+ this.liveClient
1392
+ .deleteRoom(this.meetingNum)
1393
+ .then((res) => {
1394
+ if (res.code == 200) {
1395
+ this.exitPage();
1396
+ } else {
1397
+ this.showMessage.message("error", "删除会议失败");
1398
+ }
1399
+ })
1400
+ .catch((err) => {
1401
+ this.showMessage.message("error", "删除会议失败");
1402
+ this.exitPage();
1403
+ });
1404
+ } else {
1405
+ this.exitPage();
1406
+ }
1407
+ });
1408
+ this.liveClient.on("localCameraChange", (e) => {
1409
+ this.addEventToList("localCameraChange");
1410
+ this.isCameraEnabled = e;
1411
+ if (this.isCameraEnabled) {
1412
+ this.showMessage.message("success", "摄像头已开启");
1413
+ } else {
1414
+ this.showMessage.message("success", "摄像头已关闭");
1415
+ }
1416
+ });
1417
+ this.liveClient.on("localMicrophoneChange", (e) => {
1418
+ this.addEventToList("localMicrophoneChange");
1419
+ this.isMicrophoneEnabled = e;
1420
+ if (this.isMicrophoneEnabled) {
1421
+ this.showMessage.message("success", "麦克风已开启");
1422
+ } else {
1423
+ this.showMessage.message("success", "麦克风已关闭");
1424
+ }
1425
+ });
1426
+ this.liveClient.on("localScreenShareChange", (e) => {
1427
+ this.addEventToList("localScreenShareChange");
1428
+ this.isScreenShareEnabled = e;
1429
+ if (this.isScreenShareEnabled) {
1430
+ this.showMessage.message("success", "屏幕共享已开启");
1431
+ } else {
1432
+ this.showMessage.message("success", "屏幕共享已关闭");
1433
+ }
1434
+ });
1435
+ this.liveClient.on("startRecording", (e) => {
1436
+ this.addEventToList("startRecording");
1437
+ this.isRecording = true;
1438
+ this.recordStreamUrl = e;
1439
+ this.showMessage.message("success", "开始录制");
1440
+ });
1441
+ this.liveClient.on("stopRecording", (e) => {
1442
+ this.addEventToList("stopRecording");
1443
+ this.isRecording = false;
1444
+ this.showMessage.message("success", "结束录制");
1445
+ });
1446
+ this.liveClient.on("mediaDeviceChange", (e) => {
1447
+ this.addEventToList("mediaDeviceChange");
1448
+ // showMessage.message("success", "设备切换成功");
1449
+ console.log("设备类型" + e.kind, e.deviceId);
1450
+ if (e.kind == "audioinput") {
1451
+ this.activeDevice.audioInputDevice = e.deviceId;
1452
+ } else if (e.kind == "videoinput") {
1453
+ this.activeDevice.videoDevice = e.deviceId;
1454
+ } else {
1455
+ this.activeDevice.audioOutputDevice = e.deviceId;
1456
+ }
1457
+ });
1458
+ this.liveClient.on("videoCallRender", (e) => {
1459
+ this.addEventToList("videoCallRender");
1460
+ this.renderVideoItem(e);
1461
+ });
1462
+ this.liveClient.on("activeSpeakerChange", (e) => {
1463
+ this.addEventToList("activeSpeakerChange");
1464
+ if (e.length > 0) {
1465
+ let maxAudioLevel = 0,
1466
+ maxAudioLevelIndex = -1;
1467
+ e.forEach((speaker, index) => {
1468
+ if (speaker.audioLevel > maxAudioLevel) {
1469
+ maxAudioLevel = speaker.audioLevel;
1470
+ maxAudioLevelIndex = index;
1471
+ }
1472
+ });
1473
+ if (maxAudioLevelIndex >= 0) {
1474
+ this.switchActiveSpeakerToFirst(e[maxAudioLevelIndex]);
1475
+ }
1476
+ }
1477
+ });
1478
+ },
1479
+ handleRoomDataReceived(e) {
1480
+ switch (e.topic) {
1481
+ case "MsgEvent":
1482
+ // 聊天消息
1483
+ this.handleChatMessage(e);
1484
+ break;
1485
+ case "MuteEvent":
1486
+ // 麦克风/摄像头/置聋/禁言消息
1487
+ this.handleMuteMessage(e);
1488
+ break;
1489
+ case "UpdateNameEvent":
1490
+ // 更新名称消息
1491
+ this.handleUpdateNameMessage(e);
1492
+ break;
1493
+ case "HostEvent":
1494
+ // 主持人/联席主持人消息
1495
+ this.handleHostMessage(e);
1496
+ break;
1497
+ default:
1498
+ break;
1499
+ }
1500
+ },
1501
+ handleUpdateNameMessage(e) {
1502
+ const message = JSON.parse(this.liveClient.decodeMessage(e.data));
1503
+ console.log("更新名称消息", message);
1504
+
1505
+ if (message.operationSource === "1") {
1506
+ this.showMessage.message("info", "主持人将您的名称更新为" + message.Name);
1507
+ } else if (message.operationSource === "2") {
1508
+ this.showMessage.message("info", "联席主持人将您的名称更新为" + message.Name);
1509
+ }
1510
+ },
1511
+ handleHostMessage(e) {
1512
+ const message = JSON.parse(this.liveClient.decodeMessage(e.data));
1513
+ console.log("主持人/联席主持人消息", message);
1514
+
1515
+ const operationMessages = {
1516
+ 1: {
1517
+ // 主持人
1518
+ 1: "您被设为主持人",
1519
+ 2: "您被移除主持人",
1520
+ },
1521
+ 2: {
1522
+ // 联席主持人
1523
+ 1: "您被设为联席主持人",
1524
+ 2: "您被移除联席主持人",
1525
+ },
1526
+ };
1527
+
1528
+ const messageText = operationMessages[message.input]?.[message.operationType];
1529
+
1530
+ if (messageText) {
1531
+ this.showMessage.message("info", messageText);
1532
+ }
1533
+ },
1534
+
1535
+ handleChatMessage(e) {
1536
+ const message = JSON.parse(this.liveClient.decodeMessage(e.data));
1537
+ // message.timestamp = dayjs().format("HH:mm");
1538
+ const messageHandled = {
1539
+ origin: message.sendID,
1540
+ fromName: message.senderNickname,
1541
+ content: message.contentType == 1 ? message.content.content : message.content.sourcePath,
1542
+ contentType: message.contentType,
1543
+ timestamp: dayjs(new Date(message.sendTime)).format("HH:mm"),
1544
+ srcList: message.contentType == 2 ? [message.content.sourcePath] : [],
1545
+ };
1546
+ console.log("聊天消息", messageHandled);
1547
+ this.addToMessageList(messageHandled);
1548
+ },
1549
+ handleMuteMessage(e) {
1550
+ const message = JSON.parse(this.liveClient.decodeMessage(e.data));
1551
+ console.log("麦克风/摄像头/置聋/禁言消息", message);
1552
+ // 定义操作类型和输入类型的映射
1553
+ const operationMessages = {
1554
+ 1: {
1555
+ // 主持人
1556
+ 1: {
1557
+ // 请求打开
1558
+ 1: "主持人请求打开您的麦克风",
1559
+ 2: "主持人请求打开您的摄像头",
1560
+ 3: "主持人将您取消置聋",
1561
+ 4: "主持人将您取消禁言",
1562
+ },
1563
+ 2: {
1564
+ // 关闭
1565
+ 1: "主持人关闭您的麦克风",
1566
+ 2: "主持人关闭您的摄像头",
1567
+ 3: "主持人将您置聋",
1568
+ 4: "主持人将您禁言",
1569
+ },
1570
+ },
1571
+ 2: {
1572
+ // 联席主持人
1573
+ 1: {
1574
+ // 请求打开
1575
+ 1: "联席主持人请求打开您的麦克风",
1576
+ 2: "联席主持人请求打开您的摄像头",
1577
+ 3: "联席主持人将您取消置聋",
1578
+ 4: "联席主持人将您取消禁言",
1579
+ },
1580
+ 2: {
1581
+ // 关闭
1582
+ 1: "联席主持人关闭您的麦克风",
1583
+ 2: "联席主持人关闭您的摄像头",
1584
+ 3: "联席主持人将您置聋",
1585
+ 4: "联席主持人将您禁言",
1586
+ },
1587
+ },
1588
+ };
1589
+
1590
+ const messageText =
1591
+ operationMessages[message.operationSource]?.[message.operationType]?.[message.input];
1592
+
1593
+ if (messageText) {
1594
+ this.showMessage.message("info", messageText);
1595
+ }
1596
+ },
1597
+ async initRoomMetadata(metadata) {
1598
+ console.log("roomMetadata", metadata);
1599
+
1600
+ // 标记为初始化状态
1601
+ this.isInitializing = true;
1602
+
1603
+ await this.batchUpdateStates(async () => {
1604
+ this.meetingName = metadata.roomTheme;
1605
+ this.meetingHost = {
1606
+ ownerId: metadata.ownerID,
1607
+ ownerName: metadata?.ownerName ?? "暂无",
1608
+ };
1609
+
1610
+ if (metadata.coHosts && metadata.coHosts.length > 0) {
1611
+ this.meetingCoHost = metadata.coHosts.map((item) => {
1612
+ return {
1613
+ coHostId: item.coHostID,
1614
+ };
1615
+ });
1616
+ } else {
1617
+ this.meetingCoHost = [];
1618
+ }
1619
+
1620
+ this.roomMetadata = metadata;
1621
+
1622
+ // 批量设置关键状态
1623
+ const newHostIdentity = this.meetingHost.ownerId;
1624
+ const newBlurIdentity = metadata.blurID;
1625
+ const newLayout = metadata.frontLayout || "grid";
1626
+ const newMode = metadata.roomMode || "normal";
1627
+
1628
+ // 记录变化,但先不触发watch
1629
+ const changes = {
1630
+ host: { old: this.curHostIdentity, new: newHostIdentity },
1631
+ blur: { old: this.curBlurIdentity, new: newBlurIdentity },
1632
+ layout: { old: this.currentLayout, new: newLayout },
1633
+ mode: { old: this.currentRoomMode, new: newMode },
1634
+ };
1635
+
1636
+ // 如果是加入会议,在设置新值前等待renderVideoItem首次渲染完成
1637
+ if (this.joinType !== "launch") {
1638
+ console.log("加入会议模式:动态等待renderVideoItem首次渲染完成,然后设置关键状态");
1639
+ await this.waitForParticipantsRender();
1640
+ }
1641
+
1642
+ // 设置新值
1643
+ this.curHostIdentity = newHostIdentity;
1644
+ this.curBlurIdentity = newBlurIdentity;
1645
+ this.currentLayout = newLayout;
1646
+ this.currentRoomMode = newMode;
1647
+
1648
+ // 添加需要的DOM操作到队列
1649
+ if (changes.mode.old !== changes.mode.new) {
1650
+ this.addDOMOperation("mode", () => this.executeModeChange(changes.mode.new));
1651
+ }
1652
+
1653
+ if (changes.layout.old !== changes.layout.new) {
1654
+ this.addDOMOperation("layout", () =>
1655
+ this.executeLayoutChange(changes.layout.new, changes.layout.old)
1656
+ );
1657
+ }
1658
+
1659
+ if (changes.host.old !== changes.host.new) {
1660
+ this.addDOMOperation("host", () =>
1661
+ this.executeHostChange(changes.host.new, changes.host.old)
1662
+ );
1663
+ }
1664
+
1665
+ if (changes.blur.old !== changes.blur.new) {
1666
+ this.addDOMOperation("blur", () =>
1667
+ this.executeBlurChange(changes.blur.new, changes.blur.old)
1668
+ );
1669
+ }
1670
+ });
1671
+
1672
+ this.isInitializing = false;
1673
+ this.initRoomDuration(metadata);
1674
+ },
1675
+ async roomMetadataChange(metadata) {
1676
+ console.log("roomMetadataChange", metadata);
1677
+
1678
+ await this.batchUpdateStates(async () => {
1679
+ this.meetingName = metadata.roomTheme;
1680
+ this.meetingHost = {
1681
+ ownerId: metadata.ownerID,
1682
+ ownerName: metadata?.ownerName ?? "暂无",
1683
+ };
1684
+
1685
+ if (metadata.coHosts && metadata.coHosts.length > 0) {
1686
+ this.meetingCoHost = metadata.coHosts.map((item) => {
1687
+ return {
1688
+ coHostId: item.coHostID,
1689
+ };
1690
+ });
1691
+ } else {
1692
+ this.meetingCoHost = [];
1693
+ }
1694
+
1695
+ this.roomMetadata = metadata;
1696
+
1697
+ // 批量设置关键状态
1698
+ const newHostIdentity = this.meetingHost.ownerId;
1699
+ const newBlurIdentity = metadata.blurID;
1700
+ const newLayout = metadata.frontLayout || "grid";
1701
+ const newMode = metadata.roomMode || "normal";
1702
+
1703
+ // 记录变化
1704
+ const changes = {
1705
+ host: { old: this.curHostIdentity, new: newHostIdentity },
1706
+ blur: { old: this.curBlurIdentity, new: newBlurIdentity },
1707
+ layout: { old: this.currentLayout, new: newLayout },
1708
+ mode: { old: this.currentRoomMode, new: newMode },
1709
+ };
1710
+
1711
+ // 设置新值
1712
+ this.curHostIdentity = newHostIdentity;
1713
+ this.curBlurIdentity = newBlurIdentity;
1714
+ this.currentLayout = newLayout;
1715
+ this.currentRoomMode = newMode;
1716
+
1717
+ // 添加需要的DOM操作到队列
1718
+ if (changes.mode.old !== changes.mode.new) {
1719
+ this.addDOMOperation("mode", () => this.executeModeChange(changes.mode.new));
1720
+ }
1721
+
1722
+ if (changes.layout.old !== changes.layout.new) {
1723
+ this.addDOMOperation("layout", () =>
1724
+ this.executeLayoutChange(changes.layout.new, changes.layout.old)
1725
+ );
1726
+ }
1727
+
1728
+ if (changes.host.old !== changes.host.new) {
1729
+ this.addDOMOperation("host", () =>
1730
+ this.executeHostChange(changes.host.new, changes.host.old)
1731
+ );
1732
+ }
1733
+
1734
+ if (changes.blur.old !== changes.blur.new) {
1735
+ this.addDOMOperation("blur", () =>
1736
+ this.executeBlurChange(changes.blur.new, changes.blur.old)
1737
+ );
1738
+ }
1739
+ });
1740
+
1741
+ this.initRoomDuration(metadata);
1742
+ },
1743
+ initRoomDuration(metadata) {
1744
+ if (this.isDurationCalc) {
1745
+ if (this.durationInterval) {
1746
+ clearInterval(this.durationInterval);
1747
+ this.durationInterval = null;
1748
+ }
1749
+ this.durationInterval = setInterval(() => {
1750
+ if (new Date(metadata.roomStartTime).getTime() > 0) {
1751
+ this.duration = Math.floor(
1752
+ (Date.now() - new Date(metadata.roomStartTime).getTime()) / 1000
1753
+ );
1754
+ this.isDurationCalc = false;
1755
+ } else {
1756
+ this.duration++;
1757
+ }
1758
+ }, 1000);
1759
+ }
1760
+ },
1761
+ startUnjoinParticipantPolling() {
1762
+ // 先清除可能存在的定时器
1763
+ if (this.unjoinParticipantInterval) {
1764
+ clearInterval(this.unjoinParticipantInterval);
1765
+ this.unjoinParticipantInterval = null;
1766
+ }
1767
+
1768
+ // 立即执行一次
1769
+ this.getUnjoinParticipant();
1770
+ this.queryAllInviteParticipant();
1771
+ // 设置5秒间隔的轮询
1772
+ this.unjoinParticipantInterval = setInterval(() => {
1773
+ this.getUnjoinParticipant();
1774
+ this.queryAllInviteParticipant();
1775
+ }, 6000);
1776
+ },
1777
+ initLiveClient() {
1778
+ this.liveClient = window["liveClient"];
1779
+ this.initLiveClientEvent();
1780
+ },
1781
+ sendInviteMessage(tempList = [], inviteWay = 0) {
1782
+ if (tempList.length <= 0) {
1783
+ if (this.tempInviteList.length > 0) {
1784
+ this.liveClient
1785
+ .inviteParticipant(
1786
+ this.meetingNum,
1787
+ this.tempInviteList.map((item) => {
1788
+ return {
1789
+ userName: item.label,
1790
+ identity: item?.loginCode ? item.loginCode : item.phone,
1791
+ phone: item.phone,
1792
+ };
1793
+ }),
1794
+ false,
1795
+ inviteWay
1796
+ )
1797
+ .then((res) => {
1798
+ if (res.code == 200) {
1799
+ this.showMessage.message("success", "成功发送邀请");
1800
+ this.tempInviteList.forEach((item) => {
1801
+ this.addToInviteList(item);
1802
+ });
1803
+ this.getUnjoinParticipant();
1804
+ this.queryAllInviteParticipant();
1805
+ } else {
1806
+ this.showMessage.message("error", res?.msg);
1807
+ }
1808
+ });
1809
+ }
1810
+ } else {
1811
+ this.liveClient
1812
+ .inviteParticipant(
1813
+ this.meetingNum,
1814
+ tempList.map((item) => {
1815
+ return {
1816
+ userName: item.label,
1817
+ identity: item?.loginCode ? item.loginCode : item.phone,
1818
+ phone: item.phone,
1819
+ };
1820
+ }),
1821
+ false,
1822
+ inviteWay
1823
+ )
1824
+ .then((res) => {
1825
+ if (res.code == 200) {
1826
+ this.showMessage.message("success", "成功发送邀请");
1827
+ this.getUnjoinParticipant();
1828
+ this.queryAllInviteParticipant();
1829
+ } else {
1830
+ this.showMessage.message("error", res?.msg);
1831
+ }
1832
+ });
1833
+ }
1834
+ },
1835
+ queryAllInviteParticipant() {
1836
+ this.liveClient.getAllInviteParticipant(this.meetingNum).then((res) => {
1837
+ if (res.code == 200) {
1838
+ this.tempInvitedList = res?.data || [];
1839
+ } else {
1840
+ this.showMessage.message("error", res?.msg);
1841
+ }
1842
+ });
1843
+ },
1844
+ async createRoom() {
1845
+ if (!this.liveClient) {
1846
+ return;
1847
+ }
1848
+ await this.liveClient.createRoom();
1849
+ },
1850
+
1851
+ async joinRoom() {
1852
+ let joinParam = {
1853
+ echoCancellation: true,
1854
+ noiseSuppression: true,
1855
+ roomNum: this.meetingNum,
1856
+ cameraStatus: this.isCameraEnabled,
1857
+ microphoneStatus: this.isMicrophoneEnabled,
1858
+ audioDeviceId: this.activeDevice.audioInputDevice,
1859
+ videoDeviceId: this.activeDevice.videoDevice,
1860
+ outputDeviceId: this.activeDevice.audioOutputDevice,
1861
+ };
1862
+ let tempActiveDevice = null;
1863
+ try {
1864
+ tempActiveDevice = await this.liveClient.joinRoom(joinParam);
1865
+ if (this.activeDevice) {
1866
+ this.activeDevice.audioInputDevice = tempActiveDevice?.audioInputDevice;
1867
+ this.activeDevice.audioOutputDevice = tempActiveDevice?.audioOutputDevice;
1868
+ this.activeDevice.videoDevice = tempActiveDevice?.videoInputDevice;
1869
+ console.log("activeDevice:", this.activeDevice);
1870
+ }
1871
+ } catch (e) {
1872
+ console.error("加入会议失败", e);
1873
+ }
1874
+ },
1875
+ switchActiveSpeakerToFirst(speaker) {
1876
+ if (speaker && speaker?.identity) {
1877
+ const speakerElm = document.getElementById(`participant-${speaker.identity}`);
1878
+ if (speakerElm) {
1879
+ if (this.currentRoomMode === "pointTurn") {
1880
+ // 当前为点调模式
1881
+ if (
1882
+ !(
1883
+ speaker.identity === this.curHostIdentity ||
1884
+ speaker.identity === this.curBlurIdentity
1885
+ )
1886
+ ) {
1887
+ // 仅当讲话人不为当前主持人和焦点用户时切换
1888
+ const pointTurnTop = document.querySelector("#room .point-turn-top");
1889
+ const pointTurnBottom = document.querySelector("#room .point-turn-bottom");
1890
+ const pointTurnOther = document.querySelector("#room .point-turn-other");
1891
+ if (!pointTurnTop) {
1892
+ return;
1893
+ }
1894
+ if (!pointTurnBottom) {
1895
+ return;
1896
+ }
1897
+ if (!pointTurnOther) {
1898
+ return;
1899
+ }
1900
+ if (speakerElm.parentElement) {
1901
+ if (speakerElm.parentElement.className === "point-turn-top") {
1902
+ if (speakerElm.id === pointTurnTop.firstElementChild.id) {
1903
+ return;
1904
+ }
1905
+ pointTurnTop.insertBefore(speakerElm, pointTurnTop.firstChild ?? null);
1906
+ } else if (speakerElm.parentElement.className === "point-turn-bottom") {
1907
+ if (document.querySelectorAll(".point-turn-top .participant").length >= 5) {
1908
+ pointTurnOther.insertBefore(
1909
+ pointTurnTop.lastElementChild,
1910
+ pointTurnOther.firstChild ?? null
1911
+ );
1912
+ pointTurnTop.insertBefore(speakerElm, pointTurnTop.firstChild ?? null);
1913
+ pointTurnBottom.insertBefore(
1914
+ pointTurnOther.firstChild,
1915
+ pointTurnBottom.firstChild ?? null
1916
+ );
1917
+ }
1918
+ } else {
1919
+ if (document.querySelectorAll(".point-turn-top .participant").length >= 5) {
1920
+ if (document.querySelectorAll(".point-turn-bottom .participant").length >= 5) {
1921
+ pointTurnOther.insertBefore(
1922
+ pointTurnBottom.lastElementChild,
1923
+ pointTurnOther.firstChild ?? null
1924
+ );
1925
+ pointTurnBottom.insertBefore(
1926
+ pointTurnTop.lastElementChild,
1927
+ pointTurnBottom.firstChild ?? null
1928
+ );
1929
+ pointTurnTop.insertBefore(speakerElm, pointTurnTop.firstChild ?? null);
1930
+ }
1931
+ }
1932
+ }
1933
+ }
1934
+ }
1935
+ } else {
1936
+ const container = document.getElementById("room");
1937
+ if (this.currentLayout === "grid") {
1938
+ // 当前为会议模式的常规布局
1939
+ if (speakerElm.id === container.firstElementChild.id) {
1940
+ return;
1941
+ } else {
1942
+ container.insertBefore(speakerElm, container.firstChild ?? null);
1943
+ }
1944
+ } else if (this.currentLayout === "rightSide") {
1945
+ // 当前为会议模式的焦点布局
1946
+ const layoutRightSideEle = document.querySelector("#room .layout-rightside");
1947
+ const layoutLeftSideEle = document.querySelector("#room .layout-leftside");
1948
+ if (!layoutLeftSideEle) {
1949
+ return;
1950
+ }
1951
+ if (!layoutRightSideEle) {
1952
+ return;
1953
+ }
1954
+ if (speaker.identity !== this.curBlurIdentity) {
1955
+ if (
1956
+ speakerElm.id === layoutLeftSideEle.firstElementChild.id ||
1957
+ speakerElm.id === layoutRightSideEle.firstElementChild.id
1958
+ ) {
1959
+ return;
1960
+ } else {
1961
+ layoutRightSideEle.insertBefore(
1962
+ speakerElm,
1963
+ layoutRightSideEle.firstElementChild ?? null
1964
+ );
1965
+ }
1966
+ }
1967
+ }
1968
+ }
1969
+ }
1970
+ } else {
1971
+ return;
1972
+ }
1973
+ },
1974
+ constructParticipantDom(videoItem) {
1975
+ let videoDom = document.createElement("div");
1976
+ videoDom.id = `participant-${videoItem.identity}`;
1977
+ videoDom.className = videoItem.isLocal ? "participant local" : "participant";
1978
+ if (videoItem.isScreenShareEnabled) {
1979
+ videoDom.innerHTML = `
1980
+ <video id="video-${
1981
+ videoItem.identity
1982
+ }" class="p-video" autoplay webkit-playsinline playsinline x5-video-player-type="h5"></video>
1983
+ <div id="screenshare-${videoItem.identity}" class="screen-share-suspension">
1984
+ ${videoItem.name} 正在共享屏幕
1985
+ </div>
1986
+ <div class="describe">
1987
+ <div id="microphone-${videoItem.identity}" class="microphone"></div>
1988
+ <div id="${videoItem.identity}" class="identity">${
1989
+ videoItem.isLocal ? videoItem.name + "(我)" : videoItem.name
1990
+ }</div>
1991
+ </div>
1992
+ `;
1993
+ } else if (videoItem.isCameraEnabled) {
1994
+ videoDom.innerHTML = `
1995
+ <video id="video-${
1996
+ videoItem.identity
1997
+ }" class="p-video" autoplay webkit-playsinline playsinline x5-video-player-type="h5"></video>
1998
+ <div id="signal-${videoItem.identity}" class="signal-icon signal-icon-good"></div>
1999
+ <div id="more-${videoItem.identity}" class="more-icon"></div>
2000
+ <div class="describe">
2001
+ <div id="microphone-${videoItem.identity}" class="microphone"></div>
2002
+ <div id="${videoItem.identity}" class="identity">${
2003
+ videoItem.isLocal ? videoItem.name + "(我)" : videoItem.name
2004
+ }</div>
2005
+ </div>
2006
+ `;
2007
+ } else {
2008
+ videoDom.innerHTML = `
2009
+ <video id="video-${
2010
+ videoItem.identity
2011
+ }" class="p-video" autoplay webkit-playsinline playsinline x5-video-player-type="h5"></video>
2012
+ <div id="board-${videoItem.identity}" class="board">
2013
+ <div class="board-icon"></div>
2014
+ </div>
2015
+ <div id="signal-${videoItem.identity}" class="signal-icon signal-icon-good"></div>
2016
+ <div id="more-${videoItem.identity}" class="more-icon"></div>
2017
+ <div class="describe">
2018
+ <div id="microphone-${videoItem.identity}" class="microphone"></div>
2019
+ <div id="${videoItem.identity}" class="identity">${
2020
+ videoItem.isLocal ? videoItem.name + "(我)" : videoItem.name
2021
+ }</div>
2022
+ </div>
2023
+ `;
2024
+ }
2025
+ return videoDom;
2026
+ },
2027
+ constructRoomLayout(container, videoDiv, videoItem) {
2028
+ if (this.currentRoomMode === "pointTurn") {
2029
+ // 点调模式下用户第一次入会
2030
+ let pointTurnTop = document.querySelector("#room .point-turn-top");
2031
+ let pointTurnCenter = document.querySelector("#room .point-turn-center");
2032
+ let pointTurnBottom = document.querySelector("#room .point-turn-bottom");
2033
+ let pointTurnOther = document.querySelector("#room .point-turn-other");
2034
+ if (!pointTurnTop) {
2035
+ pointTurnTop = document.createElement("div");
2036
+ pointTurnTop.className = "point-turn-top";
2037
+ container.insertBefore(pointTurnTop, container.firstChild ?? null);
2038
+ }
2039
+ if (!pointTurnCenter) {
2040
+ pointTurnCenter = document.createElement("div");
2041
+ pointTurnCenter.className = "point-turn-center";
2042
+ container.appendChild(pointTurnCenter);
2043
+ }
2044
+ if (!pointTurnBottom) {
2045
+ pointTurnBottom = document.createElement("div");
2046
+ pointTurnBottom.className = "point-turn-bottom";
2047
+ container.appendChild(pointTurnBottom);
2048
+ }
2049
+ if (!pointTurnOther) {
2050
+ pointTurnOther = document.createElement("div");
2051
+ pointTurnOther.className = "point-turn-other";
2052
+ container.appendChild(pointTurnOther);
2053
+ }
2054
+ if (this.curHostIdentity === videoItem.identity) {
2055
+ // 作为主持人入会
2056
+ if (pointTurnCenter.hasChildNodes()) {
2057
+ let placeHolderElms = document.querySelectorAll(".point-turn-center .participant");
2058
+ if (placeHolderElms.length > 0) {
2059
+ if (placeHolderElms.length === 1) {
2060
+ pointTurnCenter.insertBefore(videoDiv, pointTurnCenter.firstChild ?? null);
2061
+ } else {
2062
+ if (!placeHolderElms[0].id) {
2063
+ pointTurnCenter.removeChild(placeHolderElms[0]);
2064
+ pointTurnCenter.insertBefore(videoDiv, pointTurnCenter.firstChild ?? null);
2065
+ } else {
2066
+ if (document.querySelectorAll(".point-turn-top .participant").length >= 5) {
2067
+ if (document.querySelectorAll(".point-turn-bottom .participant").length >= 5) {
2068
+ pointTurnOther.appendChild(placeHolderElms[0]);
2069
+ } else {
2070
+ pointTurnBottom.appendChild(placeHolderElms[0]);
2071
+ }
2072
+ } else {
2073
+ pointTurnTop.appendChild(placeHolderElms[0]);
2074
+ }
2075
+ pointTurnCenter.insertBefore(videoDiv, pointTurnCenter.firstChild ?? null);
2076
+ }
2077
+ }
2078
+ } else {
2079
+ pointTurnCenter.insertBefore(videoDiv, pointTurnCenter.firstChild ?? null);
2080
+ }
2081
+ } else {
2082
+ pointTurnCenter.appendChild(videoDiv);
2083
+ }
2084
+ } else if (this.curBlurIdentity === videoItem.identity) {
2085
+ // 作为焦点视频用户入会
2086
+ if (pointTurnCenter.hasChildNodes()) {
2087
+ let placeHolderElms = document.querySelectorAll(".point-turn-center .participant");
2088
+ if (placeHolderElms.length > 0) {
2089
+ if (placeHolderElms.length === 1) {
2090
+ pointTurnCenter.appendChild(videoDiv);
2091
+ } else {
2092
+ if (!placeHolderElms[1].id) {
2093
+ pointTurnCenter.removeChild(placeHolderElms[1]);
2094
+ pointTurnCenter.appendChild(videoDiv);
2095
+ } else {
2096
+ if (document.querySelectorAll(".point-turn-top .participant").length >= 5) {
2097
+ if (document.querySelectorAll(".point-turn-bottom .participant").length >= 5) {
2098
+ pointTurnOther.appendChild(placeHolderElms[1]);
2099
+ } else {
2100
+ pointTurnBottom.appendChild(placeHolderElms[1]);
2101
+ }
2102
+ } else {
2103
+ pointTurnTop.appendChild(placeHolderElms[1]);
2104
+ }
2105
+ pointTurnCenter.appendChild(videoDiv);
2106
+ }
2107
+ }
2108
+ } else {
2109
+ pointTurnCenter.appendChild(videoDiv);
2110
+ }
2111
+ } else {
2112
+ pointTurnCenter.appendChild(videoDiv);
2113
+ }
2114
+ } else {
2115
+ // 作为普通与会者入会
2116
+ if (document.querySelectorAll(".point-turn-top .participant").length >= 5) {
2117
+ if (document.querySelectorAll(".point-turn-bottom .participant").length >= 5) {
2118
+ pointTurnOther.appendChild(videoDiv);
2119
+ } else {
2120
+ pointTurnBottom.appendChild(videoDiv);
2121
+ }
2122
+ } else {
2123
+ pointTurnTop.appendChild(videoDiv);
2124
+ }
2125
+ }
2126
+ } else {
2127
+ if (this.currentLayout === "grid") {
2128
+ // 宫格布局
2129
+ if (videoItem.isLocal) {
2130
+ // 本地与会者插入到最前面
2131
+ container.insertBefore(videoDiv, container.firstChild ?? null);
2132
+ } else {
2133
+ container.appendChild(videoDiv);
2134
+ }
2135
+ } else if (this.currentLayout === "rightSide") {
2136
+ // 右侧边栏布局
2137
+ // 1、初始化左右侧外层元素
2138
+ let layoutRightSideEle = document.querySelector("#room .layout-rightside");
2139
+ let layoutLeftSideEle = document.querySelector("#room .layout-leftside");
2140
+ if (!layoutLeftSideEle) {
2141
+ layoutLeftSideEle = document.createElement("div");
2142
+ layoutLeftSideEle.className = "layout-leftside";
2143
+ container.insertBefore(layoutLeftSideEle, container.firstChild ?? null);
2144
+ }
2145
+ if (!layoutRightSideEle) {
2146
+ layoutRightSideEle = document.createElement("div");
2147
+ layoutRightSideEle.className = "layout-rightside";
2148
+ container.appendChild(layoutRightSideEle);
2149
+ }
2150
+ // 2、添加元素到左右侧外层元素
2151
+ if (this.curBlurIdentity && this.curBlurIdentity === videoItem.identity) {
2152
+ if (layoutLeftSideEle.hasChildNodes()) {
2153
+ layoutRightSideEle.insertBefore(
2154
+ layoutLeftSideEle.firstChild,
2155
+ layoutRightSideEle.firstChild ?? null
2156
+ );
2157
+ layoutLeftSideEle.appendChild(videoDiv);
2158
+ }
2159
+ } else {
2160
+ if (videoItem.isLocal) {
2161
+ if (layoutLeftSideEle.hasChildNodes()) {
2162
+ layoutRightSideEle.insertBefore(
2163
+ layoutLeftSideEle.firstChild,
2164
+ layoutRightSideEle.firstChild ?? null
2165
+ );
2166
+ }
2167
+ layoutLeftSideEle.appendChild(videoDiv);
2168
+ } else {
2169
+ layoutRightSideEle.appendChild(videoDiv);
2170
+ }
2171
+ }
2172
+ }
2173
+ }
2174
+ return container;
2175
+ },
2176
+ renderVideoItem(videoItem) {
2177
+ console.log(`与会者渲染 ${videoItem.identity}`, videoItem);
2178
+ // 是否本人
2179
+ if (videoItem.isLocal) {
2180
+ this.localIdentity = videoItem.identity;
2181
+ this.localName = videoItem.name;
2182
+ this.localMetadata = videoItem.metadata;
2183
+ this.isCameraEnabled = videoItem.isCameraEnabled;
2184
+ this.isMicrophoneEnabled = videoItem.isMicrophoneEnabled;
2185
+ this.isScreenShareEnabled = videoItem.isScreenShareEnabled;
2186
+ }
2187
+ let container = document.getElementById("room");
2188
+ if (!container) {
2189
+ this.showMessage.message("error", "会议容器不存在");
2190
+ return;
2191
+ }
2192
+ let videoDiv = document.getElementById(`participant-${videoItem.identity}`);
2193
+ // 当与会者第一次入会
2194
+ if (!videoDiv && !videoItem.remove) {
2195
+ // 构建与会者dom元素
2196
+ videoDiv = this.constructParticipantDom(videoItem);
2197
+ // 构建会议室布局
2198
+ container = this.constructRoomLayout(container, videoDiv, videoItem);
2199
+ // 添加到participant数组
2200
+ this.addToParticipantList(videoItem);
2201
+ }
2202
+ // 与会者状态变更
2203
+ // 视频元素
2204
+ let videoElm = document.getElementById(`video-${videoItem.identity}`);
2205
+ // 摄像头关闭后展板元素
2206
+ let boardElm = document.getElementById(`board-${videoItem.identity}`);
2207
+ // 底部麦克风图标元素
2208
+ let microElm = document.getElementById(`microphone-${videoItem.identity}`);
2209
+ // 与会者网络状态图标元素
2210
+ let signalElm = document.getElementById(`signal-${videoItem.identity}`);
2211
+ // 与会者更多操作按钮元素
2212
+ let moreElm = document.getElementById(`more-${videoItem.identity}`);
2213
+ // 声明麦克风按钮点击事件回调
2214
+ const unableMicrophone = () => {
2215
+ this.liveClient.changeParticipantMicrophoneStatus(videoItem.identity, true);
2216
+ };
2217
+ const enableMicrophone = () => {
2218
+ this.liveClient.changeParticipantMicrophoneStatus(videoItem.identity, false);
2219
+ };
2220
+ // 声明与会者元素鼠标事件回调
2221
+ const signalAndMoreShow = () => {
2222
+ // 每次执行时重新获取最新的元素
2223
+ const currentSignalElm = document.getElementById(`signal-${videoItem.identity}`);
2224
+ const currentMoreElm = document.getElementById(`more-${videoItem.identity}`);
2225
+ currentSignalElm && (currentSignalElm.style.visibility = "visible");
2226
+ currentMoreElm && (currentMoreElm.style.visibility = "visible");
2227
+ };
2228
+ const signalAndMoreHide = () => {
2229
+ // 每次执行时重新获取最新的元素
2230
+ const currentSignalElm = document.getElementById(`signal-${videoItem.identity}`);
2231
+ const currentMoreElm = document.getElementById(`more-${videoItem.identity}`);
2232
+ currentSignalElm && (currentSignalElm.style.visibility = "hidden");
2233
+ currentMoreElm && (currentMoreElm.style.visibility = "hidden");
2234
+ };
2235
+ // 声明操作按钮元素点击事件回调
2236
+ const moreElmClick = (event) => {
2237
+ // 动态计算选项数量以获得精确的弹窗高度
2238
+ const optionCount = this.getOptionCount(videoItem.identity);
2239
+
2240
+ // 使用工具函数计算弹窗位置(使用真实的MoreOptionDialog尺寸)
2241
+ this.optionDialogOffset = this.calculateDialogPosition(event, {
2242
+ dialogWidth: 130,
2243
+ dialogHeight: 350, // 备用高度
2244
+ optionCount: optionCount, // 动态计算精确高度
2245
+ preferredPosition: "bottom-left",
2246
+ });
2247
+
2248
+ if (this.optionIdentity === videoItem.identity && this.moreDialogShow) {
2249
+ this.moreDialogShow = false;
2250
+ } else {
2251
+ this.moreDialogShow = false;
2252
+ this.optionIdentity = videoItem.identity;
2253
+ this.moreDialogShow = true;
2254
+ }
2255
+ };
2256
+ // 为麦克风元素绑定事件
2257
+ if (microElm && videoItem.isMicrophoneEnabled) {
2258
+ microElm.className = "microphone microphone-active";
2259
+ microElm.onclick = unableMicrophone;
2260
+ } else if (microElm && !videoItem.isMicrophoneEnabled) {
2261
+ microElm.className = "microphone microphone-inactive";
2262
+ microElm.onclick = enableMicrophone;
2263
+ }
2264
+ // 为与会者元素绑定事件
2265
+ if (videoDiv) {
2266
+ videoDiv.onmouseenter = signalAndMoreShow;
2267
+ videoDiv.onmouseleave = signalAndMoreHide;
2268
+ }
2269
+ // 根据视频宽高比设定元素样式
2270
+ // if(videoItem?.dimensions) {
2271
+ // const { width, height } = videoItem.dimensions;
2272
+ // const ratio = width / height;
2273
+ // if(ratio > (1 / 1) && videoItem?.source == "camera") {
2274
+ // videoElm && (videoElm.style.objectFit = "cover");
2275
+ // } else {
2276
+ // videoElm && (videoElm.style.objectFit = "contain");
2277
+ // }
2278
+ // }
2279
+ // 为操作按钮元素绑定事件
2280
+ if (moreElm) {
2281
+ moreElm.onclick = moreElmClick;
2282
+ }
2283
+ if (signalElm) {
2284
+ if (videoItem.connectionQuality === "excellent" || videoItem.connectionQuality === "good") {
2285
+ signalElm.className = "signal-icon signal-icon-good";
2286
+ } else {
2287
+ signalElm.className = "signal-icon signal-icon-poor";
2288
+ }
2289
+ }
2290
+ let screenShareElm = document.getElementById(`screenshare-${videoItem.identity}`);
2291
+ // 与会者dom元素内部样式重新渲染(摄像头或屏幕共享切换后)
2292
+ if (videoItem.isScreenShareEnabled) {
2293
+ if (!screenShareElm) {
2294
+ screenShareElm = document.createElement("div");
2295
+ screenShareElm.id = `screenshare-${videoItem.identity}`;
2296
+ screenShareElm.className = "screen-share-suspension";
2297
+ screenShareElm.innerHTML = `${videoItem.name} 正在共享屏幕`;
2298
+ videoDiv.appendChild(screenShareElm);
2299
+ }
2300
+ if (boardElm) {
2301
+ // 之前为关闭摄像头状态,此时需要移除board元素
2302
+ videoDiv.removeChild(boardElm);
2303
+ boardElm = null;
2304
+ }
2305
+ // 去除信号图标元素和操作按钮元素
2306
+ if (signalElm) {
2307
+ videoDiv.removeChild(signalElm);
2308
+ signalElm = null;
2309
+ }
2310
+ if (moreElm) {
2311
+ videoDiv.removeChild(moreElm);
2312
+ moreElm = null;
2313
+ }
2314
+ } else if (videoItem.isCameraEnabled) {
2315
+ if (screenShareElm) {
2316
+ // 之前为屏幕共享状态,此时需要移除screenshare元素
2317
+ videoDiv.removeChild(screenShareElm);
2318
+ screenShareElm = null;
2319
+ }
2320
+ if (boardElm) {
2321
+ // 之前为关闭摄像头状态,此时需要移除board元素
2322
+ videoDiv.removeChild(boardElm);
2323
+ boardElm = null;
2324
+ }
2325
+ if (!signalElm) {
2326
+ // 构建信号图标元素
2327
+ signalElm = document.createElement("div");
2328
+ signalElm.id = `signal-${videoItem.identity}`;
2329
+ if (
2330
+ videoItem.connectionQuality === "excellent" ||
2331
+ videoItem.connectionQuality === "good"
2332
+ ) {
2333
+ signalElm.className = "signal-icon signal-icon-good";
2334
+ } else {
2335
+ signalElm.className = "signal-icon signal-icon-poor";
2336
+ }
2337
+ videoDiv.appendChild(signalElm);
2338
+ }
2339
+ // if (!moreElm) {
2340
+ // // 构建更多操作按钮元素
2341
+ // moreElm = document.createElement("div");
2342
+ // moreElm.id = `more-${videoItem.identity}`;
2343
+ // moreElm.className = "more-icon";
2344
+ // videoDiv.appendChild(moreElm);
2345
+ // }
2346
+ if (this.localMetadata?.isOwner || this.localMetadata?.isCoHost) {
2347
+ if (!moreElm) {
2348
+ moreElm = document.createElement("div");
2349
+ moreElm.id = `more-${videoItem.identity}`;
2350
+ moreElm.className = "more-icon";
2351
+ videoDiv && videoDiv.appendChild(moreElm);
2352
+ moreElm.onclick = moreElmClick;
2353
+ }
2354
+ } else {
2355
+ if (moreElm) {
2356
+ videoDiv.removeChild(moreElm);
2357
+ moreElm = null;
2358
+ }
2359
+ }
2360
+ } else {
2361
+ if (!boardElm) {
2362
+ // 之前为屏幕共享状态或摄像头开启状态
2363
+ boardElm = document.createElement("div");
2364
+ boardElm.id = `board-${videoItem.identity}`;
2365
+ boardElm.className = "board";
2366
+ let avatarElm = document.createElement("div");
2367
+ avatarElm.className = "board-icon";
2368
+ boardElm.appendChild(avatarElm);
2369
+ if (videoDiv) {
2370
+ videoDiv?.appendChild(boardElm);
2371
+ }
2372
+ }
2373
+ if (screenShareElm) {
2374
+ videoDiv.removeChild(screenShareElm);
2375
+ screenShareElm = null;
2376
+ }
2377
+ }
2378
+ // 当与会者断开会议链接即remove为true
2379
+ if (videoItem.remove) {
2380
+ console.log("参会者离开会议:", videoItem.identity);
2381
+
2382
+ if (videoElm) {
2383
+ videoElm.srcObject = null;
2384
+ videoElm.src = "";
2385
+ videoElm.remove();
2386
+ }
2387
+ if (videoDiv) {
2388
+ videoDiv.remove();
2389
+ }
2390
+ this.removeFromParticipantList(videoItem.identity);
2391
+ this.filterParticipantList();
2392
+
2393
+ // 处理焦点用户离开的情况
2394
+ if (this.curBlurIdentity === videoItem.identity) {
2395
+ console.log("焦点用户离开会议:", videoItem.identity);
2396
+ // 调用房间焦点用户移除方法
2397
+ this.removeBlurParticipant();
2398
+ if (this.currentRoomMode === "normal") {
2399
+ // normal模式下,这里do nothing
2400
+ void 0;
2401
+ } else if (this.currentRoomMode === "pointTurn") {
2402
+ // pointTurn模式下,在原本焦点用户所在的中间右侧位置添加占位符
2403
+ // const pointTurnCenter = document.querySelector("#room .point-turn-center");
2404
+ // if (pointTurnCenter && pointTurnCenter.children.length < 2) {
2405
+ // const placeHolderElm = document.createElement("div");
2406
+ // placeHolderElm.className = "participant";
2407
+ // placeHolderElm.innerHTML = placeholderTemplate;
2408
+ // pointTurnCenter.appendChild(placeHolderElm);
2409
+ // }
2410
+ // do nothing
2411
+ void 0;
2412
+ }
2413
+ }
2414
+
2415
+ // 处理主持人离开的情况
2416
+ if (videoItem.identity === this.curHostIdentity) {
2417
+ this.curHostIdentity = null;
2418
+ // 主持人离开后具体的逻辑处理放到watch中处理
2419
+ }
2420
+ // pointTurn模式下,主持人离开处理
2421
+ // if (currentRoomMode.value === "pointTurn" && videoItem.identity === curHostIdentity.value) {
2422
+ // 当前离开与会者为会议主持人
2423
+ // const pointTurnCenter = document.querySelector("#room .point-turn-center");
2424
+ // if (pointTurnCenter) {
2425
+ // // 会议中没有其他主持人,显示默认占位元素
2426
+ // const placeHolderElm = document.createElement("div");
2427
+ // placeHolderElm.className = "participant";
2428
+ // placeHolderElm.innerHTML = placeholderTemplate;
2429
+ // pointTurnCenter.insertBefore(placeHolderElm, pointTurnCenter.firstChild ?? null);
2430
+ // }
2431
+ // }
2432
+ // pointTurn模式下的其他用户离开处理
2433
+ if (
2434
+ this.currentRoomMode === "pointTurn" &&
2435
+ videoItem.identity !== this.curHostIdentity &&
2436
+ videoItem.identity !== this.curBlurIdentity
2437
+ ) {
2438
+ // 当前离开为一般与会者,递补填充空位
2439
+ const pointTurnTop = document.querySelector("#room .point-turn-top");
2440
+ const pointTurnBottom = document.querySelector("#room .point-turn-bottom");
2441
+ const pointTurnOther = document.querySelector("#room .point-turn-other");
2442
+
2443
+ if (pointTurnTop && pointTurnBottom && pointTurnOther) {
2444
+ const pointTurnTopChildNum = pointTurnTop.children.length;
2445
+ const pointTurnBottomChildNum = pointTurnBottom.children.length;
2446
+ const pointTurnOtherChildNum = pointTurnOther.children.length;
2447
+
2448
+ if (pointTurnTopChildNum < 5) {
2449
+ if (pointTurnBottomChildNum > 0) {
2450
+ pointTurnTop.appendChild(pointTurnBottom.firstElementChild);
2451
+ } else if (pointTurnOtherChildNum > 0) {
2452
+ pointTurnTop.appendChild(pointTurnOther.firstChild);
2453
+ }
2454
+ } else if (pointTurnBottomChildNum < 5) {
2455
+ if (pointTurnOtherChildNum > 0) {
2456
+ pointTurnBottom.appendChild(pointTurnOther.firstChild);
2457
+ }
2458
+ }
2459
+ }
2460
+ }
2461
+
2462
+ // 音视频轨道绑定到video元素
2463
+ if (videoItem?.videoTrack) {
2464
+ videoItem.videoTrack.detach();
2465
+ }
2466
+ if (videoItem?.audioTrack) {
2467
+ videoItem.audioTrack.detach();
2468
+ }
2469
+ return;
2470
+ }
2471
+ // 与会者屏幕共享布局切换
2472
+ if (videoItem.isScreenShareEnabled) {
2473
+ if (this.currentRoomMode === "pointTurn") {
2474
+ // 点调模式下不做处理
2475
+ void 0;
2476
+ } else {
2477
+ // 当前模式为常规会议模式,且仅当前用户为主持人时才进行布局操作
2478
+ if (this.judgeParticipantIsHost(this.localIdentity)) {
2479
+ if (this.currentLayout === "grid") {
2480
+ if (this.judgeOtherScreenShare(videoItem.identity) !== -1) {
2481
+ // 会议中存在其他人正在共享屏幕, 什么都不做
2482
+ void 0;
2483
+ } else {
2484
+ // 会议中并无其他人共享屏幕,仅在未设置屏幕共享状态时保存布局状态
2485
+ if (!this.isScreenShareChange) {
2486
+ this.isScreenShareChange = true;
2487
+ // 保存屏幕共享前的布局和焦点用户状态
2488
+ this.screenShareChangeOldLayout = this.currentLayout;
2489
+ this.screenShareChangeOldBlur = this.curBlurIdentity;
2490
+ console.log("开始屏幕共享,保存状态:", {
2491
+ oldLayout: this.screenShareChangeOldLayout,
2492
+ oldBlur: this.screenShareChangeOldBlur,
2493
+ });
2494
+ }
2495
+ // 设置屏幕共享用户为焦点
2496
+ this.setMemberBlur(videoItem.identity);
2497
+ }
2498
+ } else if (this.currentLayout === "rightSide") {
2499
+ if (this.judgeOtherScreenShare(videoItem.identity) !== -1) {
2500
+ // 会议中存在其他人正在共享屏幕, 什么都不做
2501
+ void 0;
2502
+ } else {
2503
+ // 会议中并无其他人共享屏幕,仅在未设置屏幕共享状态时保存布局状态
2504
+ if (!this.isScreenShareChange) {
2505
+ this.isScreenShareChange = true;
2506
+ // 保存屏幕共享前的布局和焦点用户状态
2507
+ this.screenShareChangeOldLayout = this.currentLayout;
2508
+ this.screenShareChangeOldBlur = this.curBlurIdentity;
2509
+ console.log("开始屏幕共享,保存状态:", {
2510
+ oldLayout: this.screenShareChangeOldLayout,
2511
+ oldBlur: this.screenShareChangeOldBlur,
2512
+ });
2513
+ }
2514
+ // 设置屏幕共享用户为焦点
2515
+ this.setMemberBlur(videoItem.identity);
2516
+ }
2517
+ }
2518
+ }
2519
+ }
2520
+ }
2521
+ // 网络质量监测
2522
+ this.detectUserCommuniQuality();
2523
+ // 音视频轨道绑定到video元素
2524
+ if (videoItem?.videoTrack) {
2525
+ videoItem.videoTrack.attach(videoElm);
2526
+ }
2527
+ if (!videoItem.isLocal) {
2528
+ if (videoItem?.audioTrack) {
2529
+ videoItem.audioTrack.attach(videoElm);
2530
+ }
2531
+ }
2532
+ // 更新与会者名称
2533
+ let nameDom = document.getElementById(videoItem.identity);
2534
+ if (nameDom) {
2535
+ nameDom.innerHTML = videoItem.isLocal ? videoItem.name + "(我)" : videoItem.name;
2536
+ }
2537
+ // 更新与会者数组
2538
+ this.addToParticipantList(videoItem);
2539
+ this.filterParticipantList();
2540
+ // 与会者结束共享,切换回到之前布局(仅主持人执行)
2541
+ if (this.currentRoomMode === "normal" && this.judgeParticipantIsHost(this.localIdentity)) {
2542
+ console.log(
2543
+ "normal模式下,主持人结束屏幕共享,准备还原布局",
2544
+ this.hasScreenShare,
2545
+ this.isScreenShareChange
2546
+ );
2547
+ if (this.hasScreenShare === -1 && this.isScreenShareChange) {
2548
+ console.log("结束屏幕共享,准备还原布局:", {
2549
+ oldLayout: this.screenShareChangeOldLayout,
2550
+ oldBlur: this.screenShareChangeOldBlur,
2551
+ currentLayout: this.currentLayout,
2552
+ });
2553
+
2554
+ if (
2555
+ this.screenShareChangeOldLayout &&
2556
+ this.screenShareChangeOldLayout !== this.currentLayout
2557
+ ) {
2558
+ // 恢复到共享前的布局
2559
+ console.log("还原布局:", this.screenShareChangeOldLayout);
2560
+ this.setLayout(this.screenShareChangeOldLayout);
2561
+ }
2562
+ // 恢复到共享前的焦点用户
2563
+ if (this.screenShareChangeOldBlur) {
2564
+ console.log("还原焦点用户:", this.screenShareChangeOldBlur);
2565
+ this.setMemberBlur(this.screenShareChangeOldBlur);
2566
+ } else {
2567
+ // 如果共享前没有焦点用户,则移除焦点
2568
+ console.log("移除焦点用户");
2569
+ this.removeBlurParticipant();
2570
+ }
2571
+ // 重置状态
2572
+ console.log("重置屏幕共享状态");
2573
+ this.isScreenShareChange = false;
2574
+ this.screenShareChangeOldBlur = null;
2575
+ this.screenShareChangeOldLayout = null;
2576
+ }
2577
+ }
2578
+ },
2579
+ async leaveRoom() {
2580
+ if (!this.liveClient) {
2581
+ return;
2582
+ }
2583
+ await this.liveClient.leaveRoom();
2584
+ },
2585
+
2586
+ exitPage() {
2587
+ this.isLeaveRoom = true;
2588
+ this.$emit("multiMeetingClose");
2589
+ },
2590
+
2591
+ setPageFooterVisible(time = 3, e) {
2592
+ this.footerVisibleDuration = time;
2593
+ this.pageFooterVisible = true;
2594
+ if (this.footerInterval) {
2595
+ clearInterval(this.footerInterval);
2596
+ }
2597
+ this.footerInterval = setInterval(() => {
2598
+ if (this.footerVisibleDuration > 0) {
2599
+ this.footerVisibleDuration--;
2600
+ }
2601
+ }, 1000);
2602
+ },
2603
+
2604
+ // 暂时空实现,避免模板报错
2605
+ minumDialog() {
2606
+ if (!this.isInMeeting) {
2607
+ this.showMessage.message("error", "请先进入会议");
2608
+ return;
2609
+ }
2610
+ if (this.localIdentity) {
2611
+ const index = this.participants.findIndex((item) => item.identity === this.localIdentity);
2612
+ if (index < 0) {
2613
+ this.showMessage.message("error", "获取本地与会者视频流失败");
2614
+ return;
2615
+ }
2616
+ this.$emit("multiMeetingMinum", {
2617
+ identity: this.localIdentity,
2618
+ name: this.participants[index].name,
2619
+ videoTrack: this.participants[index]?.videoTrack,
2620
+ });
2621
+ this.$refs.rootElm.style.visibility = "hidden";
2622
+ } else {
2623
+ this.showMessage.message("error", "获取本地与会者视频流失败");
2624
+ }
2625
+ },
2626
+ resetDialog() {
2627
+ this.$refs.rootElm.style.visibility = "visible";
2628
+ },
2629
+ async executeLayoutChange(newLayout, oldLayout) {
2630
+ console.log("执行布局变化", newLayout, "->", oldLayout);
2631
+ console.log("新布局", newLayout);
2632
+ console.log("旧布局", oldLayout);
2633
+ this.$nextTick(() => {
2634
+ // 仅当会议模式下才可切换布局
2635
+ if (this.currentRoomMode == "normal") {
2636
+ let container = document.getElementById("room");
2637
+ if (!container) {
2638
+ this.showMessage.warning("会议容器不存在");
2639
+ return;
2640
+ }
2641
+
2642
+ if (oldLayout === "grid" && newLayout === "rightSide") {
2643
+ // 从宫格布局切换到焦点布局
2644
+
2645
+ // 先清理可能残留的布局容器
2646
+ const existingRightSide = document.querySelector("#room .layout-rightside");
2647
+ const existingLeftSide = document.querySelector("#room .layout-leftside");
2648
+ if (existingRightSide) {
2649
+ console.warn("发现残留的右侧布局容器,正在清理");
2650
+ existingRightSide.remove();
2651
+ }
2652
+ if (existingLeftSide) {
2653
+ console.warn("发现残留的左侧布局容器,正在清理");
2654
+ existingLeftSide.remove();
2655
+ }
2656
+
2657
+ let layoutRightSideEle = document.querySelector("#room .layout-rightside");
2658
+ let layoutLeftSideEle = document.querySelector("#room .layout-leftside");
2659
+
2660
+ // 创建布局容器
2661
+ if (!layoutLeftSideEle) {
2662
+ layoutLeftSideEle = document.createElement("div");
2663
+ layoutLeftSideEle.className = "layout-leftside";
2664
+ }
2665
+ if (!layoutRightSideEle) {
2666
+ layoutRightSideEle = document.createElement("div");
2667
+ layoutRightSideEle.className = "layout-rightside";
2668
+ }
2669
+
2670
+ let focusVideoItem = null;
2671
+ let blurDom = null;
2672
+
2673
+ // 确定焦点用户 - 优先级:当前焦点用户 > 主持人 > 本地用户
2674
+ if (this.curBlurIdentity) {
2675
+ focusVideoItem = this.getUserItemByIdentity(this.curBlurIdentity);
2676
+ if (focusVideoItem) {
2677
+ blurDom = document.getElementById(`participant-${focusVideoItem.identity}`);
2678
+ }
2679
+ }
2680
+
2681
+ // 如果没有有效的焦点用户,设置主持人为焦点用户
2682
+ if (!blurDom && this.curHostIdentity) {
2683
+ focusVideoItem = this.getUserItemByIdentity(this.curHostIdentity);
2684
+ if (focusVideoItem) {
2685
+ blurDom = document.getElementById(`participant-${focusVideoItem.identity}`);
2686
+ // 注意:这里不需要调用autoSetHostAsBlur(),因为curBlurIdentity的变化会触发watch监听器
2687
+ }
2688
+ }
2689
+
2690
+ // 如果主持人也不存在,则设置本地用户为焦点
2691
+ if (!blurDom) {
2692
+ focusVideoItem = this.getLocalParticipant();
2693
+ if (focusVideoItem) {
2694
+ blurDom = document.getElementById(`participant-${focusVideoItem.identity}`);
2695
+ }
2696
+ }
2697
+
2698
+ if (blurDom) {
2699
+ // 清空左侧容器并移动其子元素到右侧
2700
+ if (layoutLeftSideEle.hasChildNodes()) {
2701
+ let child;
2702
+ while ((child = layoutLeftSideEle.firstChild)) {
2703
+ if (child) {
2704
+ layoutRightSideEle.appendChild(child);
2705
+ }
2706
+ }
2707
+ }
2708
+
2709
+ // 将焦点用户放入左侧容器
2710
+ layoutLeftSideEle.appendChild(blurDom);
2711
+
2712
+ // 将容器中的其他元素移动到右侧容器
2713
+ if (container.hasChildNodes()) {
2714
+ let child;
2715
+ let maxIterations = container.children.length + 10; // 设置最大循环次数防止死循环
2716
+ let iterations = 0;
2717
+
2718
+ while ((child = container.firstElementChild) && iterations < maxIterations) {
2719
+ iterations++;
2720
+
2721
+ if (child && child !== layoutLeftSideEle && child !== layoutRightSideEle) {
2722
+ const prevFirstChild = container.firstElementChild;
2723
+ layoutRightSideEle.appendChild(child);
2724
+
2725
+ // 检查元素是否成功移动,如果没有移动则强制退出循环
2726
+ if (container.firstElementChild === prevFirstChild) {
2727
+ console.warn("元素移动失败,强制退出循环", child);
2728
+ break;
2729
+ }
2730
+ } else {
2731
+ // 如果遇到布局容器本身,说明有问题,强制移除
2732
+ console.warn("发现布局容器残留,强制移除", child);
2733
+ container.removeChild(child);
2734
+ }
2735
+ }
2736
+
2737
+ if (iterations >= maxIterations) {
2738
+ console.error("while循环达到最大次数,可能存在死循环");
2739
+ }
2740
+ }
2741
+
2742
+ // 将布局容器添加到主容器
2743
+ container.insertBefore(layoutLeftSideEle, container.firstChild ?? null);
2744
+ container.appendChild(layoutRightSideEle);
2745
+ } else {
2746
+ this.showMessage.warning("无法找到有效的焦点用户");
2747
+ }
2748
+ }
2749
+
2750
+ if (oldLayout === "grid" && newLayout === "ring") {
2751
+ // 从宫格布局切换到环状布局
2752
+ // TODO: 实现环状布局逻辑
2753
+ }
2754
+
2755
+ if (oldLayout === "grid" && newLayout === "downLSide") {
2756
+ // 从宫格布局切换到下L侧边栏布局
2757
+ // TODO: 实现下L侧边栏布局逻辑
2758
+ }
2759
+
2760
+ if (oldLayout === "rightSide" && newLayout === "grid") {
2761
+ // 从焦点布局切换到宫格布局
2762
+ let layoutRightSideEle = document.querySelector("#room .layout-rightside");
2763
+ let layoutLeftSideEle = document.querySelector("#room .layout-leftside");
2764
+ console.log("layoutSideEle", layoutRightSideEle, layoutLeftSideEle);
2765
+
2766
+ const fragment = document.createDocumentFragment();
2767
+
2768
+ // 处理右侧容器的元素
2769
+ if (layoutRightSideEle && layoutRightSideEle.hasChildNodes()) {
2770
+ let child;
2771
+ while ((child = layoutRightSideEle.firstChild)) {
2772
+ fragment.appendChild(child);
2773
+ }
2774
+ // 检查元素是否仍然是 container 的子元素,然后移除
2775
+ if (container.contains(layoutRightSideEle)) {
2776
+ container.removeChild(layoutRightSideEle);
2777
+ } else {
2778
+ console.warn("layoutRightSideEle is no longer a child of container");
2779
+ }
2780
+ }
2781
+
2782
+ // 处理左侧容器的元素(焦点用户)
2783
+ if (layoutLeftSideEle && layoutLeftSideEle.hasChildNodes()) {
2784
+ let child;
2785
+ while ((child = layoutLeftSideEle.firstChild)) {
2786
+ console.log("layoutLeftSideEleChild", layoutLeftSideEle, child);
2787
+ container.insertBefore(child, container.firstChild ?? null);
2788
+ }
2789
+ // 检查元素是否仍然是 container 的子元素,然后移除
2790
+ if (container.contains(layoutLeftSideEle)) {
2791
+ container.removeChild(layoutLeftSideEle);
2792
+ } else {
2793
+ console.warn("layoutLeftSideEle is no longer a child of container");
2794
+ }
2795
+ }
2796
+
2797
+ // 添加其他元素
2798
+ container.appendChild(fragment);
2799
+ }
2800
+ }
2801
+ });
2802
+ },
2803
+ async switchFullScreen() {
2804
+ if (
2805
+ document.fullscreenElement ||
2806
+ document.msFullscreenElement ||
2807
+ document.mozFullScreenElement ||
2808
+ document.webkitFullscreenElement
2809
+ ? true
2810
+ : false
2811
+ ) {
2812
+ if (document.exitFullscreen) {
2813
+ document.exitFullscreen();
2814
+ } else if (document.mozCancelFullScreen) {
2815
+ document.mozCancelFullScreen();
2816
+ } else if (document.webkitCancelFullScreen) {
2817
+ document.webkitCancelFullScreen();
2818
+ } else if (document.msExitFullscreen) {
2819
+ document.msExitFullscreen();
2820
+ }
2821
+ } else {
2822
+ this.$nextTick(() => {
2823
+ if (this.$refs.rootElm.requestFullscreen) {
2824
+ this.$refs.rootElm.requestFullscreen();
2825
+ }
2826
+ });
2827
+ }
2828
+ },
2829
+ openLeaveOptionDialog(event) {
2830
+ console.log('aaaaaaaaa');
2831
+
2832
+ if (this.judgeParticipantIsHost(this.localIdentity)) {
2833
+ if (this.leaveOptionShow) {
2834
+ this.leaveOptionShow = false;
2835
+ } else {
2836
+ this.leaveOptionOffset = {
2837
+ left: event.target.offsetLeft - 40 + "px",
2838
+ bottom: event.target.offsetTop + 60 + "px",
2839
+ };
2840
+ this.leaveOptionShow = true;
2841
+ }
2842
+ } else {
2843
+ this.isDeleteRoom = false;
2844
+ this.leaveRoom();
2845
+ }
2846
+ },
2847
+ openLeaveOptionDialog2(event) {
2848
+ if (this.judgeParticipantIsHost(this.localIdentity)) {
2849
+ if (this.leaveOptionShow) {
2850
+ this.leaveOptionShow = false;
2851
+ } else {
2852
+ this.leaveOptionOffset = {
2853
+ left: event.target.offsetLeft - 150 + "px",
2854
+ bottom: event.target.offsetTop + 60 + "px",
2855
+ };
2856
+ this.leaveOptionShow = true;
2857
+ }
2858
+ } else {
2859
+ this.isDeleteRoom = false;
2860
+ this.leaveRoom();
2861
+ }
2862
+ },
2863
+ closeMoreDialog() {
2864
+ this.moreDialogShow = false;
2865
+ },
2866
+ getUnjoinParticipant() {
2867
+ this.liveClient.getUnjoinParticipant(this.meetingNum).then((res) => {
2868
+ if (res.code == 200) {
2869
+ this.unJoinedList = res?.data || [];
2870
+ } else {
2871
+ this.showMessage.message("error", res?.msg);
2872
+ }
2873
+ });
2874
+ },
2875
+ moreOptionClick(e) {
2876
+ // 动态计算选项数量以获得精确的弹窗高度
2877
+ const optionCount = this.getOptionCount(e.identity);
2878
+
2879
+ // 使用工具函数计算弹窗位置(使用真实的MoreOptionDialog尺寸)
2880
+ this.optionDialogOffset = this.calculateDialogPosition(e, {
2881
+ dialogWidth: 130,
2882
+ dialogHeight: 350, // 备用高度
2883
+ optionCount: optionCount, // 动态计算精确高度
2884
+ preferredPosition: "bottom-left",
2885
+ });
2886
+
2887
+ if (this.optionIdentity === e.identity && this.moreDialogShow) {
2888
+ this.moreDialogShow = false;
2889
+ } else {
2890
+ this.moreDialogShow = false;
2891
+ this.optionIdentity = e.identity;
2892
+ this.moreDialogShow = true;
2893
+ }
2894
+ },
2895
+ removeUnjoinItem(item) {
2896
+ this.liveClient.deleteUnjoinParticipant(this.meetingNum, item.identity).then((res) => {
2897
+ if (res.code == 200) {
2898
+ this.showMessage.message("success", "成功删除未入会人员");
2899
+ this.getUnjoinParticipant();
2900
+ this.removeFromInviteList(item.identity);
2901
+ } else {
2902
+ this.showMessage.message("error", res?.msg);
2903
+ }
2904
+ });
2905
+ },
2906
+ switchParticipantCameraStatus(e) {
2907
+ if (this.liveClient) {
2908
+ this.liveClient.changeParticipantCameraStatus(e.identity, e.isMute);
2909
+ }
2910
+ },
2911
+ switchParticipantMicrophoneStatus(e) {
2912
+ if (this.liveClient) {
2913
+ this.liveClient.changeParticipantMicrophoneStatus(e.identity, e.isMute);
2914
+ }
2915
+ },
2916
+ async closeAllCamera() {
2917
+ if (this.liveClient) {
2918
+ await this.liveClient.muteAllParticipants("2");
2919
+ }
2920
+ },
2921
+ async closeAllMicrophone() {
2922
+ if (this.liveClient) {
2923
+ await this.liveClient.muteAllParticipants("1");
2924
+ }
2925
+ },
2926
+ appendMemberInvite(e) {
2927
+ this.sendAppendInviteMessage([e]);
2928
+ },
2929
+ async changeActiveDevice(kind, deviceId) {
2930
+ if (!deviceId) {
2931
+ this.showMessage.message("error", "设备id不存在");
2932
+ return;
2933
+ }
2934
+ if (kind == "audioinput") {
2935
+ await this.liveClient.changeAudioDevice(deviceId);
2936
+ } else if (kind == "audiooutput") {
2937
+ await this.liveClient.changeOutputDevice(deviceId);
2938
+ } else if (kind == "videoinput") {
2939
+ await this.liveClient.changeVideoDevice(deviceId);
2940
+ } else {
2941
+ return;
2942
+ }
2943
+ },
2944
+ filterJoinedList(e) {
2945
+ this.filterJoinVal = e;
2946
+ this.filterParticipantList();
2947
+ },
2948
+ messageSend(e) {
2949
+ const msg = {
2950
+ content: e.content,
2951
+ contentType: e.contentType,
2952
+ };
2953
+ this.liveClient.sendChatMessage(msg);
2954
+ },
2955
+ appointHost(e) {
2956
+ this.liveClient
2957
+ .setParticipantToHost(e.ownerId)
2958
+ .then((res) => {
2959
+ if (res.code == 200) {
2960
+ this.showMessage.message("success", `成功指派 ${e.ownerName} 为主持人`);
2961
+ this.isDeleteRoom = false;
2962
+ this.leaveRoom();
2963
+ } else {
2964
+ this.showMessage.message("error", res?.msg);
2965
+ }
2966
+ })
2967
+ .catch((err) => {
2968
+ this.showMessage.message("error", `指派 ${e.ownerName} 失败`);
2969
+ });
2970
+ },
2971
+ detectUserCommuniQuality() {
2972
+ if (!this.liveClient || !this.liveClient?.room) {
2973
+ return;
2974
+ }
2975
+ let quality = null;
2976
+ switch (this.liveClient.room?.localParticipant.connectionQuality) {
2977
+ case "excellent":
2978
+ case "good":
2979
+ quality = "良好";
2980
+ break;
2981
+ case "poor":
2982
+ quality = "较差";
2983
+ break;
2984
+ case "lost":
2985
+ case "unknown":
2986
+ quality = "极差";
2987
+ break;
2988
+ }
2989
+ this.meetingQuality = quality ?? "暂无";
2990
+ },
2991
+
2992
+ async setMemberBlur(e) {
2993
+ if (
2994
+ !this.judgeParticipantIsHost(this.localIdentity) &&
2995
+ !this.judgeParticipantIsCoHost(this.localIdentity)
2996
+ ) {
2997
+ // showMessage.message("warning", "仅主持人和联席主持人可以设置焦点");
2998
+ return;
2999
+ }
3000
+
3001
+ // 检查是否为手动设置焦点(非屏幕共享用户)且当前正在屏幕共享状态
3002
+ // if (isScreenShareChange.value) {
3003
+ // const targetUser = getUserItemByIdentity(e);
3004
+ // if (!targetUser || !targetUser.isScreenShareEnabled) {
3005
+ // // 当前正在屏幕共享状态,但设置的焦点用户不是屏幕共享用户,说明是手动设置,取消自动还原
3006
+ // cancelScreenShareAutoRestore("手动设置焦点用户");
3007
+ // }
3008
+ // }
3009
+
3010
+ if (this.currentRoomMode === "pointTurn") {
3011
+ // 当前为点调模式
3012
+ if (this.curHostIdentity && this.curHostIdentity === e) {
3013
+ this.showMessage.message("info", "当前与会者已被设置为主持人,暂无法设为焦点");
3014
+ return;
3015
+ }
3016
+ if (this.curBlurIdentity && this.curBlurIdentity === e) {
3017
+ this.showMessage.message("info", "当前与会者已被设置为焦点");
3018
+ return;
3019
+ }
3020
+
3021
+ // 只设置焦点用户,布局切换逻辑交给 watch 处理
3022
+ await this.liveClient.setBlurParticipant(e);
3023
+ } else {
3024
+ // 会议模式
3025
+ if (this.curBlurIdentity && this.curBlurIdentity === e) {
3026
+ this.showMessage.message("info", "当前与会者已被设置为焦点");
3027
+ return;
3028
+ }
3029
+
3030
+ // 只设置焦点用户,布局切换逻辑交给 watch 处理
3031
+ await this.liveClient.setBlurParticipant(e);
3032
+ }
3033
+ },
3034
+ removeCurBlur() {
3035
+ // 仅主持人和联席主持人可以取消设为焦点
3036
+ if (
3037
+ !this.judgeParticipantIsHost(this.localIdentity) &&
3038
+ !this.judgeParticipantIsCoHost(this.localIdentity)
3039
+ ) {
3040
+ this.showMessage.message("warning", "仅主持人和联席主持人可以取消设为焦点");
3041
+ return;
3042
+ }
3043
+
3044
+ if (!this.curBlurIdentity) {
3045
+ this.showMessage.warning("当前没有焦点用户");
3046
+ return;
3047
+ }
3048
+
3049
+ // 如果当前正在屏幕共享状态且手动取消焦点,取消自动还原
3050
+ this.cancelScreenShareAutoRestore("手动取消焦点用户");
3051
+
3052
+ const videoDiv = document.getElementById(`participant-${this.curBlurIdentity}`);
3053
+ if (!videoDiv) {
3054
+ this.showMessage.warning("焦点用户视频元素不存在:", this.curBlurIdentity);
3055
+ this.removeBlurParticipant();
3056
+ return;
3057
+ }
3058
+
3059
+ if (this.currentRoomMode === "pointTurn") {
3060
+ const pointTurnTop = document.querySelector("#room .point-turn-top");
3061
+ const pointTurnCenter = document.querySelector("#room .point-turn-center");
3062
+ const pointTurnBottom = document.querySelector("#room .point-turn-bottom");
3063
+ const pointTurnOther = document.querySelector("#room .point-turn-other");
3064
+
3065
+ if (!pointTurnTop || !pointTurnCenter || !pointTurnBottom || !pointTurnOther) {
3066
+ this.showMessage.warning("点调模式容器不存在");
3067
+ return;
3068
+ }
3069
+
3070
+ // 将焦点用户移动到合适的位置
3071
+ if (pointTurnTop.children.length < 5) {
3072
+ pointTurnTop.appendChild(videoDiv);
3073
+ } else if (pointTurnBottom.children.length < 5) {
3074
+ pointTurnBottom.appendChild(videoDiv);
3075
+ } else {
3076
+ pointTurnOther.appendChild(videoDiv);
3077
+ }
3078
+
3079
+ // 在中心区域添加占位符
3080
+ const placeHolderElm = document.createElement("div");
3081
+ placeHolderElm.className = "participant";
3082
+ placeHolderElm.innerHTML = this.placeholderTemplate;
3083
+ pointTurnCenter.appendChild(placeHolderElm);
3084
+ } else if (this.currentLayout === "rightSide") {
3085
+ const layoutRightSideEle = document.querySelector("#room .layout-rightside");
3086
+ const layoutLeftSideEle = document.querySelector("#room .layout-leftside");
3087
+
3088
+ if (!layoutLeftSideEle || !layoutRightSideEle) {
3089
+ this.showMessage.warning("焦点布局容器不存在");
3090
+ return;
3091
+ }
3092
+
3093
+ // 将左侧容器的元素(焦点用户)移动到右侧容器
3094
+ let child = null;
3095
+ while ((child = layoutLeftSideEle.firstChild)) {
3096
+ if (child) {
3097
+ layoutRightSideEle.appendChild(child);
3098
+ }
3099
+ }
3100
+ }
3101
+
3102
+ // 清空焦点用户标识
3103
+ this.removeBlurParticipant();
3104
+ },
3105
+ async removeBlurParticipant() {
3106
+ if (
3107
+ this.judgeParticipantIsHost(this.localIdentity) ||
3108
+ this.judgeParticipantIsCoHost(this.localIdentity)
3109
+ ) {
3110
+ try {
3111
+ await this.liveClient.removeBlurParticipant();
3112
+ } catch (err) {
3113
+ console.error("删除焦点失败", err);
3114
+ }
3115
+ }
3116
+ },
3117
+ async autoSetHostAsBlur() {
3118
+ if (!this.curBlurIdentity && this.curHostIdentity) {
3119
+ try {
3120
+ await this.liveClient.setBlurParticipant(this.curHostIdentity);
3121
+ console.log("自动设置主持人为焦点用户:", this.curHostIdentity);
3122
+ } catch (err) {
3123
+ console.error("自动设置主持人为焦点失败", err);
3124
+ }
3125
+ }
3126
+ },
3127
+ async openMicro(e) {
3128
+ if (this.liveClient) {
3129
+ await this.liveClient.changeParticipantMicrophoneStatus(e, false);
3130
+ }
3131
+ },
3132
+ async closeMicro(e) {
3133
+ if (this.liveClient) {
3134
+ await this.liveClient.changeParticipantMicrophoneStatus(e, true);
3135
+ }
3136
+ },
3137
+ async openCamera(e) {
3138
+ if (this.liveClient) {
3139
+ await this.liveClient.changeParticipantCameraStatus(e, true);
3140
+ }
3141
+ },
3142
+ async closeCamera(e) {
3143
+ if (this.liveClient) {
3144
+ await this.liveClient.changeParticipantCameraStatus(e, false);
3145
+ }
3146
+ },
3147
+ async setToHost(e) {
3148
+ if (this.judgeParticipantIsHost(e)) {
3149
+ return;
3150
+ } else {
3151
+ await this.liveClient.setParticipantToHost(e);
3152
+ }
3153
+ },
3154
+ async setToDeafness(e) {
3155
+ if (this.liveClient) {
3156
+ await this.liveClient.setParticipantDeafness(e);
3157
+ }
3158
+ },
3159
+ async cancelDeafness(e) {
3160
+ if (this.liveClient) {
3161
+ await this.liveClient.cancelParticipantDeafness(e);
3162
+ }
3163
+ },
3164
+ updateName(e) {
3165
+ mittBus.emit("updateName", {
3166
+ identity: e,
3167
+ });
3168
+ },
3169
+ async removeMember(e) {
3170
+ if (liveClient) {
3171
+ await liveClient.moveoutParticipant(e);
3172
+ }
3173
+ },
3174
+ async setToCoHost(e) {
3175
+ if (this.liveClient) {
3176
+ await this.liveClient.addCoHost(e);
3177
+ }
3178
+ },
3179
+ async removeFromCoHost(e) {
3180
+ if (this.liveClient) {
3181
+ await this.liveClient.removeCoHost(e);
3182
+ }
3183
+ },
3184
+ async appendInvitePeople(e) {
3185
+ console.log("追加邀请", e);
3186
+ if (e && e.length > 0) {
3187
+ e.forEach((item) => {
3188
+ this.addToInviteList(item);
3189
+ });
3190
+ }
3191
+ let tempList = [];
3192
+ let index = -1;
3193
+ this.inviteList.forEach((item) => {
3194
+ if (this.invitedNum > 0) {
3195
+ index = this.tempInvitedList.findIndex((ele) => {
3196
+ if (item?.loginCode) {
3197
+ return ele.identity === item.loginCode;
3198
+ } else {
3199
+ return ele.identity === item.phone;
3200
+ }
3201
+ });
3202
+ if (index < 0) {
3203
+ // 当前人员尚未被邀请
3204
+ tempList.push(item);
3205
+ }
3206
+ } else {
3207
+ tempList.push(item);
3208
+ }
3209
+ });
3210
+
3211
+ this.sendInviteMessage(tempList, this.defaultInviteWay == "mini" ? 3 : 0);
3212
+ },
3213
+ async appendInviteDevice(e) {
3214
+ console.log("通讯录邀请设备", e);
3215
+ if (e && e.length > 0) {
3216
+ e.forEach((item) => {
3217
+ this.pullMonitorDevice(item?.equipmentID || item?.monitorID, item.label);
3218
+ });
3219
+ }
3220
+ },
3221
+ async updateNameConfirm(e) {
3222
+ if (this.liveClient) {
3223
+ await this.liveClient.updateParticipantName(e.identity, e.name);
3224
+ }
3225
+ },
3226
+ async changeLocalMicrophoneStatus() {
3227
+ await this.liveClient.changeMicrophoneStatus();
3228
+ },
3229
+ async changeLocalCameraStatus() {
3230
+ await this.liveClient.changeCameraStatus();
3231
+ },
3232
+ async chooseAudioInputDevice(e) {
3233
+ await this.changeActiveDevice("audioinput", e);
3234
+ this.audioSelectShow = false;
3235
+ },
3236
+ async chooseAudioOutputDevice(e) {
3237
+ await this.changeActiveDevice("audiooutput", e);
3238
+ this.audioSelectShow = false;
3239
+ },
3240
+ detectMicroAndOutput() {
3241
+ this.$refs.settingDialogRef.open({
3242
+ type: "1",
3243
+ });
3244
+ },
3245
+ openSettingDialog(type) {
3246
+ this.$refs.settingDialogRef.open({
3247
+ type,
3248
+ });
3249
+ },
3250
+ async chooseVideoDevice(e) {
3251
+ await this.changeActiveDevice("videoinput", e);
3252
+ this.videoSelectShow = false;
3253
+ },
3254
+ async chooseMeetingMode(e) {
3255
+ // 仅主持人和联席主持人可以切换会议模式
3256
+ if (
3257
+ !this.judgeParticipantIsHost(this.localIdentity) &&
3258
+ !this.judgeParticipantIsCoHost(this.localIdentity)
3259
+ ) {
3260
+ this.showMessage.message("warning", "仅主持人和联席主持人可以切换会议模式");
3261
+ return;
3262
+ }
3263
+ await this.liveClient.setRoomMode(e);
3264
+ this.modeSelectShow = false;
3265
+ },
3266
+ async changeScreenShareStatus() {
3267
+ if (this.judgeOtherScreenShare(this.localIdentity) > -1) {
3268
+ this.showMessage.info("会议中存在其他人正在共享屏幕");
3269
+ return;
3270
+ }
3271
+ await this.liveClient.changeScreenShareStatus(this.screenShareCaptureOption);
3272
+ },
3273
+ screenShareOptionChange(e) {
3274
+ console.log("屏幕共享设置变更", e);
3275
+ },
3276
+ stopScreenShare() {
3277
+ this.changeScreenShareStatus(this.screenShareCaptureOption);
3278
+ },
3279
+ async startRecord() {
3280
+ await this.liveClient.startRecord();
3281
+ },
3282
+ async stopRecord() {
3283
+ await this.liveClient.stopRecord();
3284
+ },
3285
+ openCallBoard() {
3286
+ if (this.callBoardShow) {
3287
+ this.callBoardShow = false;
3288
+ } else {
3289
+ this.callBoardShow = true;
3290
+ // callBoardOffset.value = {
3291
+ // bottom: event.target.offsetTop + 50 + "px",
3292
+ // left: event.target.clientLeft - 122 + "px",
3293
+ // };
3294
+ }
3295
+ },
3296
+ chooseInviteWay(e) {
3297
+ if (e === "addressbook") {
3298
+ this.openAddressBook();
3299
+ } else if (e === "videoCall") {
3300
+ this.openCallBoard();
3301
+ }
3302
+ },
3303
+ // 打开通讯录
3304
+ openAddressBook() {
3305
+ mittBus.emit("getaddressBookUser", {
3306
+ isInner: true,
3307
+ });
3308
+ },
3309
+ appendInvite(e, inviteWay) {
3310
+ let tempList = [];
3311
+ let tempDeviceList = [];
3312
+ let index = -1;
3313
+ if (e && e.length > 0) {
3314
+ e.forEach((item) => {
3315
+ if (item.source == "人员") {
3316
+ if (this.invitedNum > 0) {
3317
+ index = this.tempInvitedList.findIndex((ele) => {
3318
+ if (item?.loginCode) {
3319
+ return ele.identity === item.loginCode;
3320
+ } else {
3321
+ return ele.identity === item.phone;
3322
+ }
3323
+ });
3324
+ if (index < 0) {
3325
+ // 当前人员尚未被邀请
3326
+ tempList.push(item);
3327
+ }
3328
+ } else {
3329
+ tempList.push(item);
3330
+ }
3331
+ this.addToInviteList(item);
3332
+ } else {
3333
+ tempDeviceList.push(item);
3334
+ }
3335
+ });
3336
+ console.log("本次追加邀请人员", tempList);
3337
+ console.log("本次追加邀请设备", tempDeviceList);
3338
+ this.sendInviteMessage(tempList, inviteWay == "mini" ? 3 : 0);
3339
+ if (tempDeviceList.length > 0) {
3340
+ tempDeviceList.forEach((item) => {
3341
+ if (item.source == "设备") {
3342
+ this.pullMonitorDevice(item.equipmentID, item.label);
3343
+ } else if (item.source == "监控") {
3344
+ this.pullMonitorDevice(item.monitorID, item.label);
3345
+ }
3346
+ });
3347
+ }
3348
+ }
3349
+ },
3350
+ pullMonitorDevice(monitorID, monitorName) {
3351
+ this.liveClient.judgeUserInMeeting(this.meetingNum, monitorID).then((res) => {
3352
+ if (res && res?.code == 200) {
3353
+ if (res.data == 1) {
3354
+ this.showMessage.message("error", "该监控设备已进入会议");
3355
+ return;
3356
+ } else {
3357
+ this.liveClient.makeCall(monitorName, monitorID, 1);
3358
+ }
3359
+ } else {
3360
+ this.showMessage.message("error", "获取监控设备进会状态失败");
3361
+ }
3362
+ });
3363
+ },
3364
+
3365
+ async sendAppendInviteMessage(uninviteList = []) {
3366
+ // 政协项目新增
3367
+ if (this.isCustomizeMiniInvitations) {
3368
+ this.$emit("inviteMessageSend", {
3369
+ inviteUserList: uninviteList,
3370
+ meetingName: this.meetingName,
3371
+ meetingNum: this.meetingNum,
3372
+ fromUser: this.userData.username,
3373
+ });
3374
+ } else {
3375
+ if (uninviteList.length > 0) {
3376
+ this.liveClient
3377
+ .inviteParticipant(
3378
+ this.meetingNum,
3379
+ uninviteList.map((item) => {
3380
+ return {
3381
+ userName: item.userName,
3382
+ identity: item.identity,
3383
+ phone: item.phone,
3384
+ };
3385
+ })
3386
+ )
3387
+ .then((res) => {
3388
+ if (res.code == 200) {
3389
+ this.showMessage.message("success", "成功发送邀请");
3390
+ this.getUnjoinParticipant();
3391
+ } else {
3392
+ this.showMessage.message("error", res?.msg);
3393
+ }
3394
+ });
3395
+ }
3396
+ }
3397
+ },
3398
+ phoneCall(num) {
3399
+ this.liveClient.makeCall(num, num);
3400
+ },
3401
+ async miniCall(num) {
3402
+ if (this.isCustomizeMiniInvitations) {
3403
+ this.$emit("miniInviteSend", {
3404
+ inviteMobile: num,
3405
+ fromUser: this.userData.username,
3406
+ meetingName: this.meetingName,
3407
+ meetingNum: this.meetingNum,
3408
+ });
3409
+ } else {
3410
+ this.showMessage.message("info", "当前功能暂未实现");
3411
+ }
3412
+ },
3413
+ sleep(waitTimeInMs) {
3414
+ return new Promise((resolve) => setTimeout(resolve, waitTimeInMs));
3415
+ },
3416
+
3417
+ handleClick() {
3418
+ console.log("点击", this.footerInterval);
3419
+ if (this.footerInterval) {
3420
+ clearInterval(this.footerInterval);
3421
+ }
3422
+ this.pageFooterVisible = true;
3423
+ },
3424
+ clickAway(event) {
3425
+ console.log("点击事件", event.target.className);
3426
+ if (!event.target.className.includes("meeting-name")) {
3427
+ this.themeDialogShow = false;
3428
+ }
3429
+ if (
3430
+ !(
3431
+ event.target.className.includes("exit-btn") ||
3432
+ event.target.className.includes("close-meeting-icon")
3433
+ )
3434
+ ) {
3435
+ this.leaveOptionShow = false;
3436
+ }
3437
+ if (
3438
+ !(
3439
+ event.target.className.includes("member-item") ||
3440
+ event.target.className.includes("appoint-dialog") ||
3441
+ event.target.className.includes("member-list") ||
3442
+ event.target.className.includes("control-list-item")
3443
+ )
3444
+ ) {
3445
+ this.appointDialogShow = false;
3446
+ }
3447
+ if (
3448
+ !(
3449
+ event.target.className.includes("icon-btn-option") ||
3450
+ event.target.className.includes("more-icon")
3451
+ )
3452
+ ) {
3453
+ this.moreDialogShow = false;
3454
+ }
3455
+ },
3456
+ verifyLeavePage(event) {
3457
+ console.log("beforeunload", event);
3458
+ if (!this.isLeaveRoom) {
3459
+ // 不是用户手动点击结束导致离开页面
3460
+ event.preventDefault();
3461
+ }
3462
+ },
3463
+ async executeHostChange(newVal, oldVal) {
3464
+ console.log("执行主持人变化", newVal, "->", oldVal);
3465
+ console.log("修改前主持人", oldVal);
3466
+ console.log("修改后主持人", newVal);
3467
+ if (!newVal) return;
3468
+
3469
+ // 如果新主持人与当前焦点用户相同,清空焦点用户
3470
+ if (this.curBlurIdentity === newVal) {
3471
+ console.log("新主持人与焦点用户相同,清空焦点用户");
3472
+ this.removeBlurParticipant();
3473
+ }
3474
+ const hostVideoElm = document.getElementById(`participant-${newVal}`);
3475
+
3476
+ if (!hostVideoElm) {
3477
+ console.error("主持人视频元素不存在:", newVal);
3478
+ return;
3479
+ }
3480
+
3481
+ if (this.currentRoomMode === "pointTurn") {
3482
+ const pointTurnTop = document.querySelector("#room .point-turn-top");
3483
+ const pointTurnCenter = document.querySelector("#room .point-turn-center");
3484
+ const pointTurnBottom = document.querySelector("#room .point-turn-bottom");
3485
+ const pointTurnOther = document.querySelector("#room .point-turn-other");
3486
+
3487
+ if (!pointTurnTop || !pointTurnCenter || !pointTurnBottom || !pointTurnOther) {
3488
+ this.showMessage.warning("点调模式容器元素不存在");
3489
+ return;
3490
+ }
3491
+
3492
+ let pointTurnTopChildNum = pointTurnTop.children.length;
3493
+ let pointTurnBottomChildNum = pointTurnBottom.children.length;
3494
+ let pointTurnOtherChildNum = pointTurnOther.children.length;
3495
+
3496
+ if (pointTurnCenter.hasChildNodes()) {
3497
+ let childNodes = pointTurnCenter.children;
3498
+ console.log("childNodes", childNodes);
3499
+
3500
+ // 处理中心区域的第一个元素(如果存在且有ID)
3501
+ if (childNodes[0] && childNodes[0].id) {
3502
+ // 将原有元素移动到合适位置
3503
+ if (pointTurnTopChildNum >= 5) {
3504
+ if (pointTurnBottomChildNum >= 5) {
3505
+ pointTurnOther.appendChild(childNodes[0]);
3506
+ } else {
3507
+ pointTurnBottom.appendChild(childNodes[0]);
3508
+ }
3509
+ } else {
3510
+ pointTurnTop.appendChild(childNodes[0]);
3511
+ }
3512
+ } else if (childNodes[0] && !childNodes[0].id) {
3513
+ // 移除占位符
3514
+ pointTurnCenter.removeChild(childNodes[0]);
3515
+ }
3516
+
3517
+ // 将新主持人移动到中心区域第一个位置
3518
+ pointTurnCenter.insertBefore(hostVideoElm, pointTurnCenter.firstElementChild ?? null);
3519
+
3520
+ // 如果新主持人不是焦点用户,需要添加占位符
3521
+ // if (newVal !== curBlurIdentity.value) {
3522
+ // let placeHolderElm = document.createElement("div");
3523
+ // placeHolderElm.className = "participant";
3524
+ // placeHolderElm.innerHTML = placeholderTemplate;
3525
+ // pointTurnCenter.appendChild(placeHolderElm);
3526
+ // }
3527
+ } else {
3528
+ // 中心区域为空,直接添加主持人
3529
+ pointTurnCenter.insertBefore(hostVideoElm, pointTurnCenter.firstElementChild ?? null);
3530
+
3531
+ // 如果新主持人不是焦点用户,需要添加占位符
3532
+ // if (newVal !== curBlurIdentity.value) {
3533
+ // let placeHolderElm = document.createElement("div");
3534
+ // placeHolderElm.className = "participant";
3535
+ // placeHolderElm.innerHTML = placeholderTemplate;
3536
+ // pointTurnCenter.appendChild(placeHolderElm);
3537
+ // }
3538
+ }
3539
+
3540
+ // 递补填充空位
3541
+ if (pointTurnTopChildNum < 5) {
3542
+ if (pointTurnBottomChildNum > 0) {
3543
+ pointTurnTop.appendChild(pointTurnBottom.firstElementChild);
3544
+ } else if (pointTurnOtherChildNum > 0) {
3545
+ pointTurnTop.appendChild(pointTurnOther.firstChild);
3546
+ }
3547
+ } else if (pointTurnBottomChildNum < 5) {
3548
+ if (pointTurnOtherChildNum > 0) {
3549
+ pointTurnBottom.appendChild(pointTurnOther.firstChild);
3550
+ }
3551
+ }
3552
+ }
3553
+ },
3554
+ async executeBlurChange(newVal, oldVal) {
3555
+ console.log("执行焦点用户变化", newVal, "->", oldVal);
3556
+ console.log("修改前焦点用户", oldVal);
3557
+ console.log("修改后焦点用户", newVal);
3558
+
3559
+ if (!newVal) {
3560
+ if (this.currentRoomMode === "pointTurn") {
3561
+ const pointTurnCenter = document.querySelector("#room .point-turn-center");
3562
+ if (pointTurnCenter) {
3563
+ if (pointTurnCenter.children.length < 2) {
3564
+ // 此时中心区域有空位,直接添加占位符
3565
+ const placeHolderElm = document.createElement("div");
3566
+ placeHolderElm.className = "participant";
3567
+ placeHolderElm.innerHTML = this.placeholderTemplate;
3568
+ pointTurnCenter.appendChild(placeHolderElm);
3569
+ } else {
3570
+ // 此时中心区域已满,需要替换
3571
+ const lastChild = pointTurnCenter.lastChild;
3572
+ if (lastChild && lastChild.id) {
3573
+ // 最后一个是真实用户,移动到其他位置,并使用占位符补位,优先移动到top,若top已满,则移动到bottom,若bottom已满,则移动到other
3574
+ const pointTurnTop = document.querySelector("#room .point-turn-top");
3575
+ const pointTurnBottom = document.querySelector("#room .point-turn-bottom");
3576
+ const pointTurnOther = document.querySelector("#room .point-turn-other");
3577
+ if (pointTurnTop && pointTurnTop.children.length < 5) {
3578
+ pointTurnTop.appendChild(lastChild);
3579
+ } else if (pointTurnBottom && pointTurnBottom.children.length < 5) {
3580
+ pointTurnBottom.appendChild(lastChild);
3581
+ } else if (pointTurnOther && pointTurnOther.children.length < 5) {
3582
+ pointTurnOther.appendChild(lastChild);
3583
+ }
3584
+ // 使用占位符补位
3585
+ const placeHolderElm = document.createElement("div");
3586
+ placeHolderElm.className = "participant";
3587
+ placeHolderElm.innerHTML = this.placeholderTemplate;
3588
+ pointTurnCenter.appendChild(placeHolderElm);
3589
+ } else if (lastChild && !lastChild.id) {
3590
+ // 最后一个是占位符,do nothing
3591
+ void 0;
3592
+ }
3593
+ }
3594
+ }
3595
+ } else {
3596
+ if (this.currentLayout === "grid") {
3597
+ // 宫格布局下移除焦点用户,则do nothing
3598
+ void 0;
3599
+ } else if (this.currentLayout === "rightSide") {
3600
+ // 焦点布局下移除焦点用户,则切换到宫格布局
3601
+ this.setLayout("grid");
3602
+ }
3603
+ }
3604
+ } else {
3605
+ // 在点调模式下的处理
3606
+ if (this.currentRoomMode === "pointTurn") {
3607
+ // 如果焦点用户和主持人是同一人,需要特殊处理
3608
+ if (this.curHostIdentity === newVal) {
3609
+ console.log("焦点用户和主持人为同一人,无需额外处理");
3610
+ return;
3611
+ }
3612
+
3613
+ const pointTurnCenter = document.querySelector("#room .point-turn-center");
3614
+ const videoDiv = document.getElementById(`participant-${newVal}`);
3615
+
3616
+ if (!pointTurnCenter) {
3617
+ this.showMessage.warning("点调模式中心容器不存在");
3618
+ return;
3619
+ }
3620
+
3621
+ if (!videoDiv) {
3622
+ this.showMessage.warning("焦点用户视频元素不存在:", newVal);
3623
+ return;
3624
+ }
3625
+
3626
+ let videoDivParentEle = videoDiv.parentElement;
3627
+ console.log("焦点元素的父元素", videoDivParentEle, videoDivParentEle?.className);
3628
+
3629
+ if (pointTurnCenter.children.length < 2) {
3630
+ // 中心区域有空位,直接添加
3631
+ pointTurnCenter.appendChild(videoDiv);
3632
+
3633
+ // 从其他区域补充元素到原位置
3634
+ if (videoDivParentEle && videoDivParentEle.className !== "point-turn-other") {
3635
+ const pointTurnOther = document.querySelector("#room .point-turn-other");
3636
+ if (pointTurnOther && pointTurnOther.children.length > 0) {
3637
+ videoDivParentEle.appendChild(pointTurnOther.firstChild);
3638
+ }
3639
+ }
3640
+ } else {
3641
+ // 中心区域已满,需要替换
3642
+ const lastChild = pointTurnCenter.lastChild;
3643
+ if (lastChild && lastChild.id) {
3644
+ // 最后一个是真实用户,移动到原位置
3645
+ if (videoDivParentEle) {
3646
+ videoDivParentEle.appendChild(lastChild);
3647
+ }
3648
+ pointTurnCenter.appendChild(videoDiv);
3649
+ } else if (lastChild && !lastChild.id) {
3650
+ // 最后一个是占位符,直接替换
3651
+ pointTurnCenter.removeChild(lastChild);
3652
+ pointTurnCenter.appendChild(videoDiv);
3653
+
3654
+ // 从其他区域补充元素
3655
+ if (videoDivParentEle && videoDivParentEle.className !== "point-turn-other") {
3656
+ const pointTurnOther = document.querySelector("#room .point-turn-other");
3657
+ if (pointTurnOther && pointTurnOther.children.length > 0) {
3658
+ videoDivParentEle.appendChild(pointTurnOther.firstChild);
3659
+ }
3660
+ }
3661
+ }
3662
+ }
3663
+ } else {
3664
+ // 会议模式下的处理
3665
+ console.log("11111");
3666
+ if (this.currentLayout === "grid") {
3667
+ console.log("222222");
3668
+ // 在宫格布局下设置焦点用户,自动切换到焦点布局
3669
+ console.log("在宫格布局下设置焦点用户,自动切换到焦点布局");
3670
+ this.setLayout("rightSide");
3671
+ } else if (this.currentLayout === "rightSide") {
3672
+ // 在焦点布局下切换焦点用户
3673
+ const layoutRightSideEle = document.querySelector("#room .layout-rightside");
3674
+ const layoutLeftSideEle = document.querySelector("#room .layout-leftside");
3675
+ const videoDiv = document.getElementById(`participant-${newVal}`);
3676
+
3677
+ if (!layoutLeftSideEle || !layoutRightSideEle) {
3678
+ this.showMessage.warning("焦点布局容器不存在");
3679
+ return;
3680
+ }
3681
+
3682
+ if (!videoDiv) {
3683
+ this.showMessage.warning("焦点用户视频元素不存在:", newVal);
3684
+ return;
3685
+ }
3686
+
3687
+ // 将左侧容器的元素移动到右侧
3688
+ let child = null;
3689
+ while ((child = layoutLeftSideEle.firstChild)) {
3690
+ if (child) {
3691
+ layoutRightSideEle.appendChild(child);
3692
+ }
3693
+ }
3694
+
3695
+ // 将新焦点用户放入左侧容器
3696
+ layoutLeftSideEle.appendChild(videoDiv);
3697
+ }
3698
+ }
3699
+ }
3700
+ },
3701
+ async executeModeChange(newMode) {
3702
+ console.log("执行模式变化", newMode);
3703
+ this.$nextTick(() => {
3704
+ let container = document.getElementById("room");
3705
+
3706
+ if (!container) {
3707
+ this.showMessage.warning("会议容器不存在");
3708
+ return;
3709
+ }
3710
+
3711
+ if (newMode === "pointTurn") {
3712
+ // 会议模式切换到点调模式
3713
+
3714
+ // 首先检查并清理已存在的点调容器,防止嵌套问题
3715
+ const existingPointTurnElements = container.querySelectorAll(
3716
+ ".point-turn-top, .point-turn-center, .point-turn-bottom, .point-turn-other"
3717
+ );
3718
+
3719
+ if (existingPointTurnElements.length > 0) {
3720
+ console.log(
3721
+ "发现已存在的点调容器,开始清理:",
3722
+ Array.from(existingPointTurnElements).map((el) => el.className)
3723
+ );
3724
+
3725
+ existingPointTurnElements.forEach((element) => {
3726
+ if (container.contains(element)) {
3727
+ console.log(`清理容器 ${element.className},子元素数量:`, element.children.length);
3728
+
3729
+ // 将现有点调容器中的子元素移回主容器
3730
+ const childrenToMove = Array.from(element.children);
3731
+ childrenToMove.forEach((child) => {
3732
+ if (child.id) {
3733
+ console.log(`移动元素 ${child.id || child.className} 回主容器`);
3734
+ container.appendChild(child);
3735
+ } else {
3736
+ console.log(`移除占位符元素 ${child.className}`);
3737
+ child.remove();
3738
+ }
3739
+ });
3740
+
3741
+ // 移除空的点调容器
3742
+ container.removeChild(element);
3743
+ console.log(`已移除容器 ${element.className}`);
3744
+ }
3745
+ });
3746
+
3747
+ console.log(
3748
+ "点调容器清理完成,当前主容器子元素:",
3749
+ Array.from(container.children).map((el) => el.className || el.id)
3750
+ );
3751
+ } else {
3752
+ console.log("未发现已存在的点调容器,继续创建新布局");
3753
+ }
3754
+
3755
+ // 初始化点调模式下元素
3756
+ let pointTurnTop = document.createElement("div");
3757
+ pointTurnTop.className = "point-turn-top";
3758
+ let pointTurnCenter = document.createElement("div");
3759
+ pointTurnCenter.className = "point-turn-center";
3760
+ let pointTurnBottom = document.createElement("div");
3761
+ pointTurnBottom.className = "point-turn-bottom";
3762
+ let pointTurnOther = document.createElement("div");
3763
+ pointTurnOther.className = "point-turn-other";
3764
+
3765
+ // 创建占位符元素
3766
+ let placeHolderElm1 = document.createElement("div");
3767
+ let placeHolderElm2 = document.createElement("div");
3768
+ placeHolderElm1.className = "participant";
3769
+ placeHolderElm1.innerHTML = this.placeholderTemplate;
3770
+ placeHolderElm2.className = "participant";
3771
+ placeHolderElm2.innerHTML = this.placeholderTemplate;
3772
+
3773
+ // 预先添加占位符
3774
+ pointTurnCenter.appendChild(placeHolderElm1);
3775
+ pointTurnCenter.appendChild(placeHolderElm2);
3776
+
3777
+ if (this.currentLayout === "grid") {
3778
+ // 从宫格布局到点调模式
3779
+ // 收集所有参与者元素,避免在循环中操作DOM结构导致的问题
3780
+ const participantElements = Array.from(container.children).filter(
3781
+ (child) =>
3782
+ child.classList.contains("participant") && child.id.startsWith("participant-")
3783
+ );
3784
+
3785
+ console.log(
3786
+ "找到的参与者元素:",
3787
+ participantElements.map((el) => el.id)
3788
+ );
3789
+
3790
+ // 处理参与者元素分配到各个位置
3791
+ participantElements.forEach((child) => {
3792
+ if (this.curHostIdentity && child.id === `participant-${this.curHostIdentity}`) {
3793
+ // 主持人放在中心区域第一个位置
3794
+ pointTurnCenter.removeChild(placeHolderElm1);
3795
+ pointTurnCenter.insertBefore(child, pointTurnCenter.firstChild ?? null);
3796
+ } else if (
3797
+ this.curBlurIdentity &&
3798
+ child.id === `participant-${this.curBlurIdentity}`
3799
+ ) {
3800
+ if (this.curBlurIdentity === this.curHostIdentity) {
3801
+ // 当焦点用户和主持人为同一人,什么都不做
3802
+ void 0;
3803
+ } else {
3804
+ // 焦点用户放在中心区域第二个位置
3805
+ pointTurnCenter.removeChild(placeHolderElm2);
3806
+ pointTurnCenter.appendChild(child);
3807
+ }
3808
+ } else {
3809
+ // 其他用户按顺序放入top、bottom、other区域
3810
+ if (pointTurnTop.children.length >= 5) {
3811
+ if (pointTurnBottom.children.length >= 5) {
3812
+ pointTurnOther.appendChild(child);
3813
+ } else {
3814
+ pointTurnBottom.appendChild(child);
3815
+ }
3816
+ } else {
3817
+ pointTurnTop.appendChild(child);
3818
+ }
3819
+ }
3820
+ });
3821
+ }
3822
+
3823
+ if (this.currentLayout === "rightSide") {
3824
+ // 从焦点布局到点调模式
3825
+ let layoutRightSideEle = document.querySelector("#room .layout-rightside");
3826
+ let layoutLeftSideEle = document.querySelector("#room .layout-leftside");
3827
+
3828
+ // 处理左侧容器(焦点用户)
3829
+ if (layoutLeftSideEle) {
3830
+ let child = null;
3831
+ while ((child = layoutLeftSideEle.firstChild)) {
3832
+ if (child) {
3833
+ if (this.curHostIdentity && child.id === `participant-${this.curHostIdentity}`) {
3834
+ // 主持人存在且当前与会者为主持人
3835
+ pointTurnCenter.removeChild(placeHolderElm1);
3836
+ pointTurnCenter.insertBefore(child, pointTurnCenter.firstChild ?? null);
3837
+ } else if (
3838
+ this.curBlurIdentity &&
3839
+ child.id === `participant-${this.curBlurIdentity}`
3840
+ ) {
3841
+ if (this.curBlurIdentity === this.curHostIdentity) {
3842
+ // 当焦点用户和主持人为同一人,什么都不做
3843
+ void 0;
3844
+ } else {
3845
+ pointTurnCenter.removeChild(placeHolderElm2);
3846
+ pointTurnCenter.appendChild(child);
3847
+ }
3848
+ } else {
3849
+ // 分配到其他区域
3850
+ if (pointTurnTop.children.length >= 5) {
3851
+ if (pointTurnBottom.children.length >= 5) {
3852
+ pointTurnOther.appendChild(child);
3853
+ } else {
3854
+ pointTurnBottom.appendChild(child);
3855
+ }
3856
+ } else {
3857
+ pointTurnTop.appendChild(child);
3858
+ }
3859
+ }
3860
+ }
3861
+ }
3862
+ // 检查元素是否仍然是 container 的子元素,然后移除
3863
+ if (container.contains(layoutLeftSideEle)) {
3864
+ container.removeChild(layoutLeftSideEle);
3865
+ } else {
3866
+ console.warn("layoutLeftSideEle is no longer a child of container");
3867
+ }
3868
+ }
3869
+
3870
+ // 处理右侧容器
3871
+ if (layoutRightSideEle) {
3872
+ let child = null;
3873
+ while ((child = layoutRightSideEle.firstChild)) {
3874
+ if (child) {
3875
+ if (this.curHostIdentity && child.id === `participant-${this.curHostIdentity}`) {
3876
+ pointTurnCenter.removeChild(placeHolderElm1);
3877
+ pointTurnCenter.insertBefore(child, pointTurnCenter.firstChild ?? null);
3878
+ } else if (
3879
+ this.curBlurIdentity &&
3880
+ child.id === `participant-${this.curBlurIdentity}`
3881
+ ) {
3882
+ if (this.curBlurIdentity === this.curHostIdentity) {
3883
+ // 当焦点用户和主持人为同一人,什么都不做
3884
+ void 0;
3885
+ } else {
3886
+ pointTurnCenter.removeChild(placeHolderElm2);
3887
+ pointTurnCenter.appendChild(child);
3888
+ }
3889
+ } else {
3890
+ // 分配到其他区域
3891
+ if (pointTurnTop.children.length >= 5) {
3892
+ if (pointTurnBottom.children.length >= 5) {
3893
+ pointTurnOther.appendChild(child);
3894
+ } else {
3895
+ pointTurnBottom.appendChild(child);
3896
+ }
3897
+ } else {
3898
+ pointTurnTop.appendChild(child);
3899
+ }
3900
+ }
3901
+ }
3902
+ }
3903
+ // 检查元素是否仍然是 container 的子元素,然后移除
3904
+ if (container.contains(layoutRightSideEle)) {
3905
+ container.removeChild(layoutRightSideEle);
3906
+ } else {
3907
+ console.warn("layoutRightSideEle is no longer a child of container");
3908
+ }
3909
+ }
3910
+ }
3911
+
3912
+ // 添加点调容器之前的最终检查
3913
+ const remainingLayoutElements = container.querySelectorAll(
3914
+ ".layout-leftside, .layout-rightside, .point-turn-top, .point-turn-center, .point-turn-bottom, .point-turn-other"
3915
+ );
3916
+ if (remainingLayoutElements.length > 0) {
3917
+ console.warn(
3918
+ "发现遗留的布局元素,强制清理:",
3919
+ Array.from(remainingLayoutElements).map((el) => el.className)
3920
+ );
3921
+ remainingLayoutElements.forEach((element) => {
3922
+ if (container.contains(element)) {
3923
+ // 将子元素移回主容器
3924
+ Array.from(element.children).forEach((child) => {
3925
+ if (child.classList.contains("participant")) {
3926
+ container.appendChild(child);
3927
+ }
3928
+ });
3929
+ container.removeChild(element);
3930
+ }
3931
+ });
3932
+ }
3933
+
3934
+ // 点调模式下元素添加到container下
3935
+ console.log(
3936
+ "添加点调容器到主容器,当前container子元素:",
3937
+ Array.from(container.children).map((el) => el.className || el.tagName)
3938
+ );
3939
+
3940
+ container.insertBefore(pointTurnTop, container.firstChild ?? null);
3941
+ container.appendChild(pointTurnCenter);
3942
+ container.appendChild(pointTurnBottom);
3943
+ container.appendChild(pointTurnOther);
3944
+
3945
+ console.log("点调容器添加完成,最终结构:", {
3946
+ containerChildren: Array.from(container.children).map((el) => el.className),
3947
+ pointTurnTopChildren: Array.from(pointTurnTop.children).map(
3948
+ (el) => el.className || el.id
3949
+ ),
3950
+ pointTurnCenterChildren: Array.from(pointTurnCenter.children).map(
3951
+ (el) => el.className || el.id
3952
+ ),
3953
+ pointTurnBottomChildren: Array.from(pointTurnBottom.children).map(
3954
+ (el) => el.className || el.id
3955
+ ),
3956
+ pointTurnOtherChildren: Array.from(pointTurnOther.children).map(
3957
+ (el) => el.className || el.id
3958
+ ),
3959
+ });
3960
+ } else {
3961
+ // 点调模式切换到会议模式
3962
+ let pointTurnTop = document.querySelector("#room .point-turn-top");
3963
+ let pointTurnCenter = document.querySelector("#room .point-turn-center");
3964
+ let pointTurnBottom = document.querySelector("#room .point-turn-bottom");
3965
+ let pointTurnOther = document.querySelector("#room .point-turn-other");
3966
+
3967
+ console.log("currentLayout", this.currentLayout);
3968
+
3969
+ if (this.currentLayout === "grid") {
3970
+ // 从点调模式到宫格布局
3971
+ const processContainer = (containerEle) => {
3972
+ if (!containerEle) {
3973
+ console.warn("processContainer: containerEle is null");
3974
+ return;
3975
+ }
3976
+
3977
+ // 检查元素是否真的是 container 的子元素
3978
+ if (!container.contains(containerEle)) {
3979
+ console.warn("processContainer: containerEle is not a child of container");
3980
+ return;
3981
+ }
3982
+
3983
+ if (containerEle.children.length > 0) {
3984
+ let child = null;
3985
+ while ((child = containerEle.firstChild)) {
3986
+ if (child) {
3987
+ if (child.id) {
3988
+ // 有ID的是真实用户元素
3989
+ container.appendChild(child);
3990
+ } else {
3991
+ // 无ID的是占位符,直接移除
3992
+ child.remove();
3993
+ }
3994
+ }
3995
+ }
3996
+ }
3997
+
3998
+ // 再次检查元素是否仍然是 container 的子元素,然后移除
3999
+ if (container.contains(containerEle)) {
4000
+ container.removeChild(containerEle);
4001
+ } else {
4002
+ console.warn("processContainer: containerEle is no longer a child of container");
4003
+ }
4004
+ };
4005
+
4006
+ processContainer(pointTurnTop);
4007
+ processContainer(pointTurnCenter);
4008
+ processContainer(pointTurnBottom);
4009
+ processContainer(pointTurnOther);
4010
+ }
4011
+
4012
+ if (this.currentLayout === "rightSide") {
4013
+ // 从点调模式到焦点布局
4014
+ let layoutLeftSideEle = document.createElement("div");
4015
+ layoutLeftSideEle.className = "layout-leftside";
4016
+ let layoutRightSideEle = document.createElement("div");
4017
+ layoutRightSideEle.className = "layout-rightside";
4018
+
4019
+ const processContainerForRightSide = (containerEle) => {
4020
+ if (!containerEle) {
4021
+ console.warn("processContainerForRightSide: containerEle is null");
4022
+ return;
4023
+ }
4024
+
4025
+ // 检查元素是否真的是 container 的子元素
4026
+ if (!container.contains(containerEle)) {
4027
+ console.warn(
4028
+ "processContainerForRightSide: containerEle is not a child of container"
4029
+ );
4030
+ return;
4031
+ }
4032
+
4033
+ // 处理容器中的子元素(如果有的话)
4034
+ if (containerEle.children.length > 0) {
4035
+ let child = null;
4036
+ while ((child = containerEle.firstChild)) {
4037
+ if (child) {
4038
+ if (child.id) {
4039
+ // 有ID的是真实用户元素
4040
+ if (
4041
+ this.curBlurIdentity &&
4042
+ child.id === `participant-${this.curBlurIdentity}`
4043
+ ) {
4044
+ layoutLeftSideEle.appendChild(child);
4045
+ } else if (
4046
+ this.localIdentity &&
4047
+ child.id === `participant-${this.localIdentity}`
4048
+ ) {
4049
+ layoutLeftSideEle.appendChild(child);
4050
+ } else {
4051
+ layoutRightSideEle.appendChild(child);
4052
+ }
4053
+ } else {
4054
+ // 无ID的是占位符,直接移除
4055
+ child.remove();
4056
+ }
4057
+ }
4058
+ }
4059
+ }
4060
+
4061
+ // 再次检查元素是否仍然是 container 的子元素,然后移除
4062
+ if (container.contains(containerEle)) {
4063
+ container.removeChild(containerEle);
4064
+ } else {
4065
+ console.warn(
4066
+ "processContainerForRightSide: containerEle is no longer a child of container"
4067
+ );
4068
+ }
4069
+ };
4070
+
4071
+ processContainerForRightSide(pointTurnTop);
4072
+ processContainerForRightSide(pointTurnCenter);
4073
+ processContainerForRightSide(pointTurnBottom);
4074
+ processContainerForRightSide(pointTurnOther);
4075
+
4076
+ // 添加布局容器
4077
+ container.insertBefore(layoutLeftSideEle, container.firstChild ?? null);
4078
+ container.appendChild(layoutRightSideEle);
4079
+ }
4080
+ }
4081
+ });
4082
+ },
4083
+
4084
+ initGlobleEvent() {
4085
+ window.addEventListener("click", this.clickAway);
4086
+ window.addEventListener("beforeunload", this.verifyLeavePage);
4087
+ document.addEventListener("fullscreenchange", this.fullscreenChangeHandler);
4088
+ },
4089
+ removeGlobleEvent() {
4090
+ window.removeEventListener("click", this.clickAway);
4091
+ document.removeEventListener("fullscreenchange", this.fullscreenChangeHandler);
4092
+ },
4093
+ fullscreenChangeHandler() {
4094
+ if (document.fullscreenElement) {
4095
+ this.isFullScreen = true;
4096
+ } else {
4097
+ this.isFullScreen = false;
4098
+ }
4099
+ },
4100
+
4101
+ stopUnjoinParticipantPolling() {
4102
+ if (this.unjoinParticipantInterval) {
4103
+ clearInterval(this.unjoinParticipantInterval);
4104
+ this.unjoinParticipantInterval = null;
4105
+ }
4106
+ },
4107
+ dispatchLiveClientEvent() {
4108
+ if (this.liveClient) {
4109
+ if (this.pageEventList.length > 0) {
4110
+ this.pageEventList.forEach((eventName) => {
4111
+ console.log("解除绑定事件:", eventName);
4112
+ this.liveClient.off(eventName);
4113
+ });
4114
+ }
4115
+ }
4116
+ },
4117
+ },
4118
+ };
4119
+ </script>
4120
+
4121
+ <style lang="scss" scoped>
4122
+ .meeting-room {
4123
+ width: 1354px;
4124
+ height: 916px;
4125
+ position: fixed;
4126
+ inset: 0;
4127
+ margin: auto;
4128
+ z-index: 2000;
4129
+ overflow: hidden;
4130
+ border-radius: 6px;
4131
+
4132
+ &-top-bar {
4133
+ width: 100%;
4134
+ height: 48px;
4135
+ display: flex;
4136
+ justify-content: space-between;
4137
+ align-items: center;
4138
+ background: var(--dialog-title-bg);
4139
+ padding: 0 20px;
4140
+ border-bottom: 1px solid var(--dialog-border-color);
4141
+
4142
+ .bar-group {
4143
+ display: flex;
4144
+ align-items: center;
4145
+ flex-wrap: nowrap;
4146
+
4147
+ .meeting-theme {
4148
+ position: relative;
4149
+ flex-shrink: 0;
4150
+ text-align: center;
4151
+ width: 80px;
4152
+ height: 28px;
4153
+ background: var(--theme-color);
4154
+ border-radius: 4px;
4155
+ display: flex;
4156
+ align-items: center;
4157
+ justify-content: center;
4158
+ font-weight: 400;
4159
+ font-size: 14px;
4160
+ margin-right: 20px;
4161
+ color: var(--theme-font-color);
4162
+ }
4163
+
4164
+ .meeting-name {
4165
+ cursor: pointer;
4166
+ flex-shrink: 0;
4167
+ display: flex;
4168
+ flex-wrap: nowrap;
4169
+ align-items: center;
4170
+ font-weight: 400;
4171
+ font-size: 16px;
4172
+ color: var(--theme-font-color);
4173
+ position: relative;
4174
+
4175
+ &-text {
4176
+ flex-shrink: 0;
4177
+ white-space: nowrap;
4178
+ margin-right: 12px;
4179
+ }
4180
+
4181
+ &-icon {
4182
+ flex-shrink: 0;
4183
+ width: 10px;
4184
+ height: 5px;
4185
+ background: var(--ready-arrow-icon) no-repeat center / 140% 140%;
4186
+ transition: transform 0.2s ease;
4187
+ &-expanded {
4188
+ transform: rotate(180deg);
4189
+ }
4190
+ }
4191
+ }
4192
+
4193
+ .btn {
4194
+ width: 16px;
4195
+ height: 16px;
4196
+ cursor: pointer;
4197
+ position: relative;
4198
+ }
4199
+
4200
+ .layout {
4201
+ background: var(--meeting-layout-icon) no-repeat center / 100% 100%;
4202
+
4203
+ &-active {
4204
+ background: var(--meeting-layout-icon) no-repeat center / 100% 100%;
4205
+ }
4206
+ }
4207
+ .custom-layout {
4208
+ background: var(--custom-layout-header-icon) no-repeat center / 100% 100%;
4209
+ }
4210
+ .slide-small {
4211
+ background: var(--meeting-slide-small-icon) no-repeat center / 100% 100%;
4212
+ margin-right: 12px;
4213
+ }
4214
+ .full-screen {
4215
+ background: var(--call-fullscreen-icon) no-repeat center / 100% 100%;
4216
+ }
4217
+
4218
+ .close-meeting-icon {
4219
+ background: var(--close-icon) no-repeat center / 100% 100%;
4220
+ }
4221
+
4222
+ &-text {
4223
+ cursor: pointer;
4224
+ font-weight: 400;
4225
+ font-size: 12px;
4226
+ color: var(--theme-font-color);
4227
+ margin: 0 12px 0 6px;
4228
+ }
4229
+ &-split {
4230
+ width: 1px;
4231
+ height: 20px;
4232
+ background: var(--dialog-border-color);
4233
+ margin: 0 20px;
4234
+ }
4235
+ }
4236
+
4237
+ .meeting-duration {
4238
+ font-weight: 500;
4239
+ font-size: 16px;
4240
+ color: #ffffff;
4241
+ display: flex;
4242
+ align-items: center;
4243
+ justify-content: center;
4244
+ flex-wrap: nowrap;
4245
+
4246
+ &-logo {
4247
+ width: 16px;
4248
+ height: 16px;
4249
+ background: var(--call-duration-icon) no-repeat center / 100% 100%;
4250
+ margin-right: 7px;
4251
+ }
4252
+
4253
+ span {
4254
+ white-space: nowrap;
4255
+ }
4256
+ }
4257
+ }
4258
+
4259
+ &-contain {
4260
+ width: 100%;
4261
+ height: calc(100% - 48px);
4262
+ display: flex;
4263
+ align-items: flex-start;
4264
+ flex-wrap: nowrap;
4265
+ position: relative;
4266
+
4267
+ &-left {
4268
+ flex-shrink: 0;
4269
+ width: 100%;
4270
+ height: 100%;
4271
+ position: relative;
4272
+
4273
+ &-slide {
4274
+ width: calc(100% - 347px);
4275
+ }
4276
+
4277
+ .meeting {
4278
+ width: 100%;
4279
+ height: 100%;
4280
+ background: var(--meeting-bg);
4281
+ padding: 5px;
4282
+ overflow-y: auto;
4283
+ position: relative;
4284
+
4285
+ &::-webkit-scrollbar {
4286
+ display: none;
4287
+ }
4288
+ &-slide {
4289
+ height: calc(100% - 66px);
4290
+ }
4291
+ }
4292
+
4293
+ .meeting-control-bar {
4294
+ width: 100%;
4295
+ height: 66px;
4296
+ padding: 10px 20px 20px;
4297
+ background: var(--dialog-bg);
4298
+ display: flex;
4299
+ align-items: center;
4300
+ justify-content: space-between;
4301
+ position: absolute;
4302
+ z-index: 100;
4303
+ bottom: -66px;
4304
+ left: 0;
4305
+ transition: all 0.2s ease;
4306
+
4307
+ &-show {
4308
+ bottom: 0;
4309
+ }
4310
+
4311
+ .bar-group {
4312
+ display: flex;
4313
+ align-items: center;
4314
+ flex-wrap: nowrap;
4315
+
4316
+ .custom-select {
4317
+ position: relative;
4318
+ background: var(--ready-dialog-select-bg);
4319
+ border-radius: 6px;
4320
+ height: 36px;
4321
+ display: flex;
4322
+ flex-wrap: nowrap;
4323
+ align-items: center;
4324
+ padding: 0 12px;
4325
+ cursor: pointer;
4326
+ &:not(:last-child) {
4327
+ margin-right: 12px;
4328
+ }
4329
+
4330
+ &-btn {
4331
+ width: 20px;
4332
+ height: 20px;
4333
+ margin-right: 6px;
4334
+ position: relative;
4335
+ }
4336
+
4337
+ &-right {
4338
+ display: flex;
4339
+ align-items: center;
4340
+ flex-wrap: nowrap;
4341
+ position: relative;
4342
+ font-weight: 400;
4343
+ font-size: 14px;
4344
+ color: var(--theme-font-color);
4345
+ cursor: pointer;
4346
+ &-text {
4347
+ white-space: nowrap;
4348
+ flex: 1 0;
4349
+ margin-right: 12px;
4350
+ }
4351
+ .arrow-icon {
4352
+ width: 8px;
4353
+ height: 4px;
4354
+ flex: 1 0;
4355
+ background: var(--ready-arrow-icon) no-repeat center / 100% 100%;
4356
+ }
4357
+ }
4358
+
4359
+ .mic-on {
4360
+ background: var(--ready-mic-on-icon) no-repeat center / 100% 100%;
4361
+ }
4362
+
4363
+ .mic-off {
4364
+ background: url("../../assets/image/common/mic_off_small.png") no-repeat center / 100%
4365
+ 100%;
4366
+ }
4367
+
4368
+ .cam-on {
4369
+ background: var(--ready-cam-on-icon) no-repeat center / 100% 100%;
4370
+ }
4371
+
4372
+ .cam-off {
4373
+ background: url("../../assets/image/common/cam_off_small.png") no-repeat center / 100%
4374
+ 100%;
4375
+ }
4376
+
4377
+ .theme-icon {
4378
+ background: var(--meeting-mode-icon) no-repeat center / 100% 100%;
4379
+ }
4380
+ }
4381
+
4382
+ .t-btn {
4383
+ position: relative;
4384
+ cursor: pointer;
4385
+ display: flex;
4386
+ flex-wrap: nowrap;
4387
+ align-items: center;
4388
+ padding: 0 12px;
4389
+ height: 36px;
4390
+ background: var(--ready-dialog-select-bg);
4391
+ border-radius: 6px;
4392
+ font-weight: 400;
4393
+ font-size: 14px;
4394
+ color: var(--theme-font-color);
4395
+
4396
+ &:not(:last-child) {
4397
+ margin-right: 12px;
4398
+ }
4399
+
4400
+ span {
4401
+ white-space: nowrap;
4402
+ }
4403
+
4404
+ &-icon {
4405
+ width: 20px;
4406
+ height: 20px;
4407
+ margin-right: 6px;
4408
+ }
4409
+ &-hide {
4410
+ .t-btn-icon {
4411
+ margin-right: 0;
4412
+ }
4413
+ span {
4414
+ display: none;
4415
+ }
4416
+ }
4417
+ .screen-share {
4418
+ background: var(--meeting-share-icon) no-repeat center / 100% 100%;
4419
+ }
4420
+ .screen-share-off {
4421
+ background: var(--meeting-share-icon) no-repeat center / 100% 100%;
4422
+ }
4423
+ .chat {
4424
+ background: var(--meeting-chat-icon) no-repeat center / 100% 100%;
4425
+ }
4426
+ .record {
4427
+ background: var(--meeting-record-icon) no-repeat center / 100% 100%;
4428
+ }
4429
+ .record-off {
4430
+ background: var(--meeting-record-icon) no-repeat center / 100% 100%;
4431
+ }
4432
+ .invite {
4433
+ background: var(--meeting-invite-icon) no-repeat center / 100% 100%;
4434
+ }
4435
+ .setting {
4436
+ background: var(--meeting-setting-icon) no-repeat center / 100% 100%;
4437
+ }
4438
+ .member {
4439
+ background: var(--meeting-member-icon) no-repeat center / 100% 100%;
4440
+ }
4441
+ }
4442
+ }
4443
+
4444
+ .exit-btn {
4445
+ cursor: pointer;
4446
+ width: 112px;
4447
+ height: 36px;
4448
+ line-height: 36px;
4449
+ background: var(--btn-danger-bg);
4450
+ border-radius: 6px;
4451
+ text-align: center;
4452
+ font-weight: 500;
4453
+ font-size: 16px;
4454
+ color: #ffffff;
4455
+ border: 1px solid var(--btn-danger-bg);
4456
+ }
4457
+ }
4458
+ }
4459
+
4460
+ &-right {
4461
+ flex-shrink: 0;
4462
+ height: 100%;
4463
+ }
4464
+ }
4465
+ }
4466
+ </style>
4467
+ <style lang="scss">
4468
+ @import "./style/index.scss";
4469
+ </style>