mdm-client 1.0.2 → 1.0.4
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.
- package/package.json +1 -1
- package/src/App.vue +72 -61
- package/src/assets/image/common/layout-active16.png +0 -0
- package/src/assets/image/common/layout-active25.png +0 -0
- package/src/assets/image/common/layout-active3.png +0 -0
- package/src/assets/image/common/layout-active9.png +0 -0
- package/src/assets/image/common/layout16.png +0 -0
- package/src/assets/image/common/layout25.png +0 -0
- package/src/assets/image/common/layout3.png +0 -0
- package/src/assets/image/common/layout9.png +0 -0
- package/src/assets/image/common/mirror.png +0 -0
- package/src/assets/image/common/platform_app_icon.png +0 -0
- package/src/assets/image/common/platform_mini_icon.png +0 -0
- package/src/assets/image/common/platform_pc_icon.png +0 -0
- package/src/assets/image/common/platform_volte_icon.png +0 -0
- package/src/assets/image/common/rotate_icon1.png +0 -0
- package/src/assets/image/common/rotate_icon2.png +0 -0
- package/src/assets/image/common/rotate_icon3.png +0 -0
- package/src/assets/image/common/rotate_icon4.png +0 -0
- package/src/assets/style/base.scss +5 -0
- package/src/components/LiveMulti/LiveMulti.vue +26 -15
- package/src/components/LiveMultipleMeeting/LiveMultipleMeeting.vue +1443 -228
- package/src/components/LiveMultipleMeeting/style/index.scss +145 -14
- package/src/components/LivePoint/LivePoint.vue +49 -211
- package/src/components/LivePointMeeting/LivePointMeeting.vue +159 -10
- package/src/components/LivePointMeeting/style/index.scss +35 -0
- package/src/components/MeetingReadyDialog/MeetingReadyDialog.vue +96 -14
- package/src/components/other/addressBook.vue +274 -37
- package/src/components/other/appointDialog.vue +1 -1
- package/src/components/other/customGroupDialog.vue +2 -1
- package/src/components/other/customLayout.vue +368 -202
- package/src/components/other/editGroupDialog.vue +2 -0
- package/src/components/other/layoutSwitch.vue +253 -37
- package/src/components/other/leadershipFocus.vue +422 -0
- package/src/components/other/leaveOptionDialog.vue +1 -1
- package/src/components/other/memberManage.vue +61 -4
- package/src/components/other/moreOptionDialog.vue +17 -1
- package/src/components/other/selectDialog.vue +1 -1
- package/src/components/other/selectSpecialDialog.vue +1 -1
- package/src/main.js +4 -4
- package/src/utils/api.js +28 -0
- package/src/utils/livekit/live-client-esm.js +1 -1
- package/src/utils/livekit/live-client-esm-old.js +0 -1
|
@@ -43,10 +43,12 @@
|
|
|
43
43
|
<LayoutSwitch
|
|
44
44
|
v-if="layoutSwitchShow"
|
|
45
45
|
:currentLayout="currentLayout"
|
|
46
|
+
:current-grid-size="currentGridSize"
|
|
46
47
|
:layoutList="layoutList"
|
|
47
48
|
:layoutLabels="layoutLabels"
|
|
48
49
|
:currentRoomMode="currentRoomMode"
|
|
49
50
|
@setLayout="manualSetLayout"
|
|
51
|
+
@setGridSize="setGridSize"
|
|
50
52
|
@mouseenter.native="layoutSwitchShow = true"
|
|
51
53
|
@mouseleave.native="layoutSwitchShow = false"
|
|
52
54
|
></LayoutSwitch>
|
|
@@ -86,6 +88,12 @@
|
|
|
86
88
|
@screenShareOptionChange="screenShareOptionChange"
|
|
87
89
|
@stopScreenShare="stopScreenShare"
|
|
88
90
|
></ScreenShareBoard>
|
|
91
|
+
<LeadershipFocus
|
|
92
|
+
v-if="isLeaderShipFocusShow"
|
|
93
|
+
:participant="curLeadershipFocus"
|
|
94
|
+
:local-metadata="localMetadata"
|
|
95
|
+
@moreClick="leaderShipMoreClick"
|
|
96
|
+
@microphoneClick="leaderShipMicroClick"></LeadershipFocus>
|
|
89
97
|
</div>
|
|
90
98
|
|
|
91
99
|
<!-- 会议控制栏 -->
|
|
@@ -321,6 +329,7 @@
|
|
|
321
329
|
:participants="participants"
|
|
322
330
|
:meetingHost="meetingHost"
|
|
323
331
|
:meetingCoHost="meetingCoHost"
|
|
332
|
+
:leaderShipFocus="leaderShipFocus"
|
|
324
333
|
@close="moreDialogShow = false"
|
|
325
334
|
@setToBlur="setMemberBlur"
|
|
326
335
|
@removeFromBlur="removeCurBlur"
|
|
@@ -335,14 +344,17 @@
|
|
|
335
344
|
@remove="removeMember"
|
|
336
345
|
@setToCoHost="setToCoHost"
|
|
337
346
|
@removeFromCoHost="removeFromCoHost"
|
|
347
|
+
@openLeadershipFocus="openLeadershipFocus"
|
|
348
|
+
@closeLeadershipFocus="closeLeadershipFocus"
|
|
338
349
|
>
|
|
339
350
|
</MoreOptionDialog>
|
|
340
351
|
<AddressBook
|
|
341
|
-
:inviteList="inviteList"
|
|
342
352
|
:baseUrl="baseUrl"
|
|
343
353
|
:token="token"
|
|
354
|
+
:tab-list="addressBookTabList"
|
|
344
355
|
@appendInvitePeople="appendInvitePeople"
|
|
345
356
|
@appendInviteDevice="appendInviteDevice"
|
|
357
|
+
@appendInviteTerminal="appendInviteTerminal"
|
|
346
358
|
></AddressBook>
|
|
347
359
|
<UpdateNameDialog @updateNameConfirm="updateNameConfirm"></UpdateNameDialog>
|
|
348
360
|
<SettingDialog
|
|
@@ -375,7 +387,9 @@ import SelectSpecialDialog from "../other/selectSpecialDialog.vue";
|
|
|
375
387
|
import ScreenShareBoard from "../other/screenShareBoard.vue";
|
|
376
388
|
import SettingDialog from "../other/settingDialog.vue";
|
|
377
389
|
import CustomLayout from "../other/customLayout.vue";
|
|
390
|
+
import LeadershipFocus from "../other/leadershipFocus.vue";
|
|
378
391
|
|
|
392
|
+
let ringResizeObserver = null;
|
|
379
393
|
export default {
|
|
380
394
|
name: "LiveMultipleMeeting",
|
|
381
395
|
components: {
|
|
@@ -394,6 +408,7 @@ export default {
|
|
|
394
408
|
ScreenShareBoard,
|
|
395
409
|
SettingDialog,
|
|
396
410
|
CustomLayout,
|
|
411
|
+
LeadershipFocus
|
|
397
412
|
},
|
|
398
413
|
props: {
|
|
399
414
|
tempMeetingName: {
|
|
@@ -425,6 +440,10 @@ export default {
|
|
|
425
440
|
type: Array,
|
|
426
441
|
default: () => [],
|
|
427
442
|
},
|
|
443
|
+
meetingTerminals: {
|
|
444
|
+
type: Array,
|
|
445
|
+
default: () => [],
|
|
446
|
+
},
|
|
428
447
|
userData: {
|
|
429
448
|
type: Object,
|
|
430
449
|
default: () => ({}),
|
|
@@ -441,18 +460,10 @@ export default {
|
|
|
441
460
|
type: String,
|
|
442
461
|
default: "launch",
|
|
443
462
|
},
|
|
444
|
-
isCustomizeMiniInvitations: {
|
|
445
|
-
type: Boolean,
|
|
446
|
-
default: false,
|
|
447
|
-
},
|
|
448
463
|
miniPagePath: {
|
|
449
464
|
type: String,
|
|
450
465
|
default: "",
|
|
451
466
|
},
|
|
452
|
-
defaultInviteWay: {
|
|
453
|
-
type: String,
|
|
454
|
-
default: "identity",
|
|
455
|
-
},
|
|
456
467
|
isInMeeting: {
|
|
457
468
|
type: Boolean,
|
|
458
469
|
default: false,
|
|
@@ -461,6 +472,21 @@ export default {
|
|
|
461
472
|
type: String,
|
|
462
473
|
default: "",
|
|
463
474
|
},
|
|
475
|
+
// 通讯录可见的标签页配置
|
|
476
|
+
addressBookTabList: {
|
|
477
|
+
type: Array,
|
|
478
|
+
default: () => ["组织架构", "人员", "会议终端", "volte", "设备", "监控", "常用分组"],
|
|
479
|
+
},
|
|
480
|
+
// 语音激励开关
|
|
481
|
+
isVoiceMotivationOpen: {
|
|
482
|
+
type: Boolean,
|
|
483
|
+
default: false,
|
|
484
|
+
},
|
|
485
|
+
// 镜像开关
|
|
486
|
+
isMirror: {
|
|
487
|
+
type: Boolean,
|
|
488
|
+
default: false,
|
|
489
|
+
},
|
|
464
490
|
},
|
|
465
491
|
data() {
|
|
466
492
|
return {
|
|
@@ -504,9 +530,9 @@ export default {
|
|
|
504
530
|
// 布局数据
|
|
505
531
|
currentLayout: "grid",
|
|
506
532
|
currentRoomMode: "normal",
|
|
507
|
-
layoutList: ["grid", "rightSide"],
|
|
508
|
-
layoutLabels: ["宫格", "右侧边栏"],
|
|
509
|
-
|
|
533
|
+
layoutList: ["grid", "rightSide", "ring"],
|
|
534
|
+
layoutLabels: ["宫格", "右侧边栏", "环形"],
|
|
535
|
+
currentGridSize: 9,
|
|
510
536
|
// 状态数据
|
|
511
537
|
duration: 0,
|
|
512
538
|
memberManageShow: false,
|
|
@@ -590,6 +616,15 @@ export default {
|
|
|
590
616
|
durationInterval: null,
|
|
591
617
|
footerInterval: null,
|
|
592
618
|
unjoinParticipantInterval: null,
|
|
619
|
+
// 保存与会者画面旋转状态
|
|
620
|
+
rotateDegreeMap: new Map(),
|
|
621
|
+
// 当前聚焦画面
|
|
622
|
+
leaderShipFocus: ""
|
|
623
|
+
};
|
|
624
|
+
},
|
|
625
|
+
provide() {
|
|
626
|
+
return {
|
|
627
|
+
rotateDegreeMap: this.rotateDegreeMap,
|
|
593
628
|
};
|
|
594
629
|
},
|
|
595
630
|
computed: {
|
|
@@ -602,12 +637,33 @@ export default {
|
|
|
602
637
|
participantIdentities() {
|
|
603
638
|
return this.participantNum > 0 ? this.participants.map((item) => item.identity) : [];
|
|
604
639
|
},
|
|
640
|
+
isLeaderShipFocusShow() {
|
|
641
|
+
if (!this.leaderShipFocus) {
|
|
642
|
+
return false
|
|
643
|
+
}
|
|
644
|
+
if (this.participantNum <= 0) {
|
|
645
|
+
return false
|
|
646
|
+
}
|
|
647
|
+
if (this.participantIdentities.includes(this.leaderShipFocus)) {
|
|
648
|
+
return true
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
curLeadershipFocus() {
|
|
652
|
+
if (this.participantNum <= 0 || !this.leaderShipFocus) {
|
|
653
|
+
return {}
|
|
654
|
+
} else {
|
|
655
|
+
return this.participants.find(item => item.identity === this.leaderShipFocus)
|
|
656
|
+
}
|
|
657
|
+
},
|
|
605
658
|
inviteNum() {
|
|
606
659
|
return this.inviteList.length;
|
|
607
660
|
},
|
|
608
661
|
deviceNum() {
|
|
609
662
|
return this.deviceList.length;
|
|
610
663
|
},
|
|
664
|
+
terminalNum() {
|
|
665
|
+
return this.meetingTerminals.length;
|
|
666
|
+
},
|
|
611
667
|
invitedNum() {
|
|
612
668
|
return this.tempInvitedList?.length || 0;
|
|
613
669
|
},
|
|
@@ -629,22 +685,55 @@ export default {
|
|
|
629
685
|
return "point-turn";
|
|
630
686
|
} else {
|
|
631
687
|
if (this.currentLayout === "grid") {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
688
|
+
// 当前为宫格布局时, 不同与会者数量对应不同样式
|
|
689
|
+
if (this.currentGridSize === 9) {
|
|
690
|
+
if (this.participantNum <= 1) {
|
|
691
|
+
return 'grid1'
|
|
692
|
+
} else if (this.participantNum <= 2) {
|
|
693
|
+
return 'grid2'
|
|
694
|
+
} else if (this.participantNum <= 4) {
|
|
695
|
+
return 'grid3'
|
|
696
|
+
} else if (this.participantNum <= 6) {
|
|
697
|
+
return 'grid4'
|
|
698
|
+
} else if (this.participantNum <= 9) {
|
|
699
|
+
return 'grid5'
|
|
700
|
+
} else {
|
|
701
|
+
return 'grid5'
|
|
702
|
+
}
|
|
703
|
+
} else if (this.currentGridSize === 16) {
|
|
704
|
+
if (this.participantNum <= 1) {
|
|
705
|
+
return 'grid1'
|
|
706
|
+
} else if (this.participantNum <= 2) {
|
|
707
|
+
return 'grid2'
|
|
708
|
+
} else if (this.participantNum <= 4) {
|
|
709
|
+
return 'grid3'
|
|
710
|
+
} else if (this.participantNum <= 6) {
|
|
711
|
+
return 'grid4'
|
|
712
|
+
} else if (this.participantNum <= 9) {
|
|
713
|
+
return 'grid5'
|
|
714
|
+
} else if (this.participantNum <= 16) {
|
|
715
|
+
return 'grid6'
|
|
716
|
+
} else {
|
|
717
|
+
return 'grid6'
|
|
718
|
+
}
|
|
719
|
+
} else if (this.currentGridSize === 25) {
|
|
720
|
+
if (this.participantNum <= 1) {
|
|
721
|
+
return 'grid1'
|
|
722
|
+
} else if (this.participantNum <= 2) {
|
|
723
|
+
return 'grid2'
|
|
724
|
+
} else if (this.participantNum <= 4) {
|
|
725
|
+
return 'grid3'
|
|
726
|
+
} else if (this.participantNum <= 6) {
|
|
727
|
+
return 'grid4'
|
|
728
|
+
} else if (this.participantNum <= 9) {
|
|
729
|
+
return 'grid5'
|
|
730
|
+
} else if (this.participantNum <= 16) {
|
|
731
|
+
return 'grid6'
|
|
732
|
+
} else if (this.participantNum <= 25) {
|
|
733
|
+
return 'grid7'
|
|
734
|
+
} else {
|
|
735
|
+
return 'grid7'
|
|
736
|
+
}
|
|
648
737
|
}
|
|
649
738
|
} else {
|
|
650
739
|
return this.currentLayout;
|
|
@@ -722,14 +811,148 @@ export default {
|
|
|
722
811
|
}
|
|
723
812
|
this.setPageFooterVisible(5);
|
|
724
813
|
this.initGlobleEvent();
|
|
814
|
+
// 观察#room尺寸变化,动态同步环形布局顶部高度
|
|
815
|
+
this.observeRoomResizeForRing();
|
|
816
|
+
// 首次挂载后尝试同步一次高度
|
|
817
|
+
requestAnimationFrame(() => this.updateRingTopHeight());
|
|
725
818
|
},
|
|
726
819
|
beforeDestroy() {
|
|
727
820
|
this.stopUnjoinParticipantPolling();
|
|
728
821
|
this.dispatchLiveClientEvent();
|
|
729
822
|
this.removeGlobleEvent();
|
|
823
|
+
// 清理observer或事件监听
|
|
824
|
+
if (ringResizeObserver) {
|
|
825
|
+
try { ringResizeObserver.disconnect(); } catch (e) {}
|
|
826
|
+
ringResizeObserver = null;
|
|
827
|
+
} else {
|
|
828
|
+
window.removeEventListener('resize', this.updateRingTopHeight);
|
|
829
|
+
}
|
|
730
830
|
this.liveClient = null;
|
|
731
831
|
},
|
|
732
832
|
methods: {
|
|
833
|
+
handleInviteType(integrationType = 0) {
|
|
834
|
+
// 根据integrationType返回对应的makeCall的type参数
|
|
835
|
+
switch (Number(integrationType)) {
|
|
836
|
+
case 0:
|
|
837
|
+
return 1
|
|
838
|
+
case 1:
|
|
839
|
+
return 2
|
|
840
|
+
case 2:
|
|
841
|
+
return 3
|
|
842
|
+
default:
|
|
843
|
+
return 1
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
leaderShipMoreClick(e) {
|
|
847
|
+
// 动态计算选项数量以获得精确的弹窗高度
|
|
848
|
+
const optionCount = this.getOptionCount(e.identity)
|
|
849
|
+
|
|
850
|
+
// 使用工具函数计算弹窗位置(使用真实的MoreOptionDialog尺寸)
|
|
851
|
+
this.optionDialogOffset = this.calculateDialogPosition(e.event, {
|
|
852
|
+
dialogWidth: 130,
|
|
853
|
+
dialogHeight: 350, // 备用高度
|
|
854
|
+
optionCount: optionCount, // 动态计算精确高度
|
|
855
|
+
preferredPosition: 'bottom-left',
|
|
856
|
+
})
|
|
857
|
+
|
|
858
|
+
if (this.optionIdentity === e.identity && this.moreDialogShow) {
|
|
859
|
+
this.moreDialogShow = false
|
|
860
|
+
} else {
|
|
861
|
+
this.moreDialogShow = false
|
|
862
|
+
this.optionIdentity = e.identity
|
|
863
|
+
this.moreDialogShow = true
|
|
864
|
+
}
|
|
865
|
+
},
|
|
866
|
+
async leaderShipMicroClick() {
|
|
867
|
+
await this.liveClient.changeParticipantMicrophoneStatus(e.identity, e.isMuted)
|
|
868
|
+
},
|
|
869
|
+
// 聚焦画面
|
|
870
|
+
async openLeadershipFocus(identity) {
|
|
871
|
+
await this.liveClient.openLeadershipFocus(identity)
|
|
872
|
+
},
|
|
873
|
+
// 取消聚焦画面
|
|
874
|
+
async closeLeadershipFocus(identity) {
|
|
875
|
+
await this.liveClient.closeLeadershipFocus(identity)
|
|
876
|
+
},
|
|
877
|
+
updateRingTopHeight() {
|
|
878
|
+
try {
|
|
879
|
+
const room = document.getElementById('room');
|
|
880
|
+
if (!room) return;
|
|
881
|
+
const ringTop = room.querySelector('.layout-ring-top');
|
|
882
|
+
if (!ringTop) return;
|
|
883
|
+
// 使用clientHeight以获得包含内边距的可见高度
|
|
884
|
+
const roomHeight = room.clientHeight;
|
|
885
|
+
// 设置明确像素高度,覆盖CSS百分比或flex计算值
|
|
886
|
+
ringTop.style.height = roomHeight + 'px';
|
|
887
|
+
} catch (e) {
|
|
888
|
+
console.warn('updateRingTopHeight error:', e);
|
|
889
|
+
}
|
|
890
|
+
},
|
|
891
|
+
observeRoomResizeForRing() {
|
|
892
|
+
const room = document.getElementById('room');
|
|
893
|
+
if (!room) return;
|
|
894
|
+
// 优先使用ResizeObserver,实时响应容器尺寸变化
|
|
895
|
+
if ('ResizeObserver' in window) {
|
|
896
|
+
ringResizeObserver = new ResizeObserver(() => {
|
|
897
|
+
this.updateRingTopHeight();
|
|
898
|
+
});
|
|
899
|
+
ringResizeObserver.observe(room);
|
|
900
|
+
} else {
|
|
901
|
+
// 回退到window resize
|
|
902
|
+
window.addEventListener('resize', this.updateRingTopHeight);
|
|
903
|
+
}
|
|
904
|
+
},
|
|
905
|
+
setGridSize(size) {
|
|
906
|
+
this.currentGridSize = size;
|
|
907
|
+
},
|
|
908
|
+
// 根据identity和度数应用到video元素和旋转按钮的样式/属性
|
|
909
|
+
applyRotation(identity, degree) {
|
|
910
|
+
try {
|
|
911
|
+
const norm = ((degree % 360) + 360) % 360; // 归一化到0-359
|
|
912
|
+
// 更新video元素的transform和objectFit
|
|
913
|
+
const videoElm = document.getElementById(`video-${identity}`);
|
|
914
|
+
if (videoElm) {
|
|
915
|
+
videoElm.style.transformOrigin = 'center center';
|
|
916
|
+
// 针对90/270度,居中并以高度为基准适配,避免布局“怪异”
|
|
917
|
+
if (norm === 90 || norm === 270) {
|
|
918
|
+
const parent = videoElm.parentElement;
|
|
919
|
+
if (parent) {
|
|
920
|
+
const rect = parent.getBoundingClientRect();
|
|
921
|
+
// 旋转后使可视区域与父容器宽高贴合:宽高对调为父容器的高/宽
|
|
922
|
+
videoElm.style.top = '50%';
|
|
923
|
+
videoElm.style.left = '50%';
|
|
924
|
+
videoElm.style.width = `${rect.height}px`;
|
|
925
|
+
videoElm.style.height = `${rect.width}px`;
|
|
926
|
+
videoElm.style.transform = `translate(-50%, -50%) rotate(${norm}deg)`;
|
|
927
|
+
// 使用cover确保充满父容器(必要时会稍作裁剪)
|
|
928
|
+
videoElm.style.objectFit = 'cover';
|
|
929
|
+
}
|
|
930
|
+
} else {
|
|
931
|
+
// 0/180度直接占满容器
|
|
932
|
+
videoElm.style.top = '0';
|
|
933
|
+
videoElm.style.left = '0';
|
|
934
|
+
videoElm.style.width = '100%';
|
|
935
|
+
videoElm.style.height = '100%';
|
|
936
|
+
videoElm.style.transform = `rotate(${norm}deg)`;
|
|
937
|
+
videoElm.style.objectFit = 'contain';
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// 更新旋转按钮的class以反映当前角度(1..4)
|
|
942
|
+
const rotateElm = document.getElementById(`rotate-${identity}`);
|
|
943
|
+
if (rotateElm) {
|
|
944
|
+
let clsIndex = 1;
|
|
945
|
+
if (norm === 0) clsIndex = 1;
|
|
946
|
+
else if (norm === 90) clsIndex = 2;
|
|
947
|
+
else if (norm === 180) clsIndex = 3;
|
|
948
|
+
else if (norm === 270) clsIndex = 4;
|
|
949
|
+
rotateElm.className = `rotate-icon rotate-icon${clsIndex}`;
|
|
950
|
+
}
|
|
951
|
+
} catch (err) {
|
|
952
|
+
// 忽略DOM异常
|
|
953
|
+
console.error('applyRotation error', err);
|
|
954
|
+
}
|
|
955
|
+
},
|
|
733
956
|
// DOM操作管理函数
|
|
734
957
|
async batchUpdateStates(updateFn) {
|
|
735
958
|
this.isBatchUpdating = true;
|
|
@@ -876,12 +1099,12 @@ export default {
|
|
|
876
1099
|
// 计算MoreOptionDialog选项数量的辅助函数
|
|
877
1100
|
getOptionCount(identity) {
|
|
878
1101
|
const participantItem = this.getUserItemByIdentity(identity);
|
|
879
|
-
if (!participantItem || !participantItem.metadata) return
|
|
1102
|
+
if (!participantItem || !participantItem.metadata) return 9; // 默认最大选项数
|
|
880
1103
|
|
|
881
1104
|
const metadata = participantItem.metadata;
|
|
882
1105
|
const isLocalParticipant = identity === this.localIdentity;
|
|
883
1106
|
|
|
884
|
-
let optionCount =
|
|
1107
|
+
let optionCount = 4; // 基础选项:设为主屏、麦克风、摄像头、聚焦画面
|
|
885
1108
|
|
|
886
1109
|
if (this.judgeParticipantIsHost(this.localIdentity)) {
|
|
887
1110
|
// 本地为主持人
|
|
@@ -906,7 +1129,7 @@ export default {
|
|
|
906
1129
|
}
|
|
907
1130
|
}
|
|
908
1131
|
|
|
909
|
-
return Math.min(optionCount,
|
|
1132
|
+
return Math.min(optionCount, 9); // 限制最大选项数
|
|
910
1133
|
},
|
|
911
1134
|
// 弹窗定位工具函数
|
|
912
1135
|
calculateDialogPosition(event, options = {}) {
|
|
@@ -1120,16 +1343,16 @@ export default {
|
|
|
1120
1343
|
if (this.inviteNum <= 0) {
|
|
1121
1344
|
this.inviteList.push(userItem);
|
|
1122
1345
|
} else {
|
|
1123
|
-
if (this.judgePersonIsInvited(userItem.
|
|
1346
|
+
if (this.judgePersonIsInvited(userItem.phone) < 0) {
|
|
1124
1347
|
this.inviteList.push(userItem);
|
|
1125
1348
|
}
|
|
1126
1349
|
}
|
|
1127
1350
|
},
|
|
1128
1351
|
// 判断是否已经在邀请列表中
|
|
1129
|
-
judgePersonIsInvited(
|
|
1352
|
+
judgePersonIsInvited(phone) {
|
|
1130
1353
|
if (this.inviteNum > 0) {
|
|
1131
1354
|
let index = this.inviteList.findIndex((item) => {
|
|
1132
|
-
return item.
|
|
1355
|
+
return item.phone === phone;
|
|
1133
1356
|
});
|
|
1134
1357
|
return index;
|
|
1135
1358
|
} else {
|
|
@@ -1367,16 +1590,45 @@ export default {
|
|
|
1367
1590
|
this.$emit("meetingStart");
|
|
1368
1591
|
if (this.joinType == "launch") {
|
|
1369
1592
|
// 发送邀请信息
|
|
1370
|
-
this.sendInviteMessage([]
|
|
1593
|
+
this.sendInviteMessage([]);
|
|
1371
1594
|
// 拉取监控设备
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1595
|
+
// 使用批量外呼替换逐条外呼
|
|
1596
|
+
if (this.deviceNum > 0 && this.deviceList) {
|
|
1597
|
+
const deviceCalls = this.deviceList
|
|
1598
|
+
.map(item => {
|
|
1599
|
+
const id = item?.source === '监控' ? item?.monitorID : item?.equipmentID;
|
|
1600
|
+
return id ? { dnis: id, name: item.label, type: this.handleInviteType(item?.integrationType) } : null;
|
|
1601
|
+
})
|
|
1602
|
+
.filter(Boolean);
|
|
1603
|
+
|
|
1604
|
+
if (deviceCalls.length > 0) {
|
|
1605
|
+
this.liveClient.makeBatchCall(deviceCalls)
|
|
1606
|
+
.then(() => {
|
|
1607
|
+
this.showMessage.message('success', '监控设备批量外呼已发起');
|
|
1608
|
+
})
|
|
1609
|
+
.catch(err => {
|
|
1610
|
+
this.showMessage.message('error', `批量拉取监控设备失败: ${err?.message || err}`);
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
// 拉取会议终端
|
|
1615
|
+
if (this.terminalNum > 0 && this.meetingTerminals) {
|
|
1616
|
+
const terminalCalls = this.meetingTerminals
|
|
1617
|
+
.map(item => {
|
|
1618
|
+
return item?.id ? { dnis: item.id, name: item.label, type: 4 } : null
|
|
1619
|
+
})
|
|
1620
|
+
.filter(Boolean)
|
|
1621
|
+
if (terminalCalls.length > 0) {
|
|
1622
|
+
this.liveClient
|
|
1623
|
+
.makeBatchCall(terminalCalls)
|
|
1624
|
+
.then(() => {
|
|
1625
|
+
this.showMessage.message('success', '会议终端批量外呼已发起')
|
|
1626
|
+
})
|
|
1627
|
+
.catch(err => {
|
|
1628
|
+
this.showMessage.message('error', `批量拉取会议终端失败: ${err?.message || err}`)
|
|
1629
|
+
})
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1380
1632
|
}
|
|
1381
1633
|
// 启动获取未入会和邀请人员轮询
|
|
1382
1634
|
this.startUnjoinParticipantPolling();
|
|
@@ -1472,10 +1724,48 @@ export default {
|
|
|
1472
1724
|
}
|
|
1473
1725
|
});
|
|
1474
1726
|
if (maxAudioLevelIndex >= 0) {
|
|
1475
|
-
this.
|
|
1727
|
+
this.highlightCurrentSpeaker(e[maxAudioLevelIndex])
|
|
1728
|
+
if (this.isVoiceMotivationOpen) {
|
|
1729
|
+
this.switchActiveSpeakerToFirst(e[maxAudioLevelIndex])
|
|
1730
|
+
}
|
|
1731
|
+
} else {
|
|
1732
|
+
this.highlightCurrentSpeaker(-1)
|
|
1476
1733
|
}
|
|
1734
|
+
} else {
|
|
1735
|
+
this.highlightCurrentSpeaker(-1)
|
|
1477
1736
|
}
|
|
1478
1737
|
});
|
|
1738
|
+
this.liveClient.on("resMeetingRefresh", this.handleResMeetingRefresh);
|
|
1739
|
+
},
|
|
1740
|
+
// 高亮当前与会者
|
|
1741
|
+
highlightCurrentSpeaker(speaker) {
|
|
1742
|
+
const participantElms = document.querySelectorAll('#room .participant')
|
|
1743
|
+
if (speaker === -1) {
|
|
1744
|
+
if (participantElms.length > 0) {
|
|
1745
|
+
participantElms.forEach(elm => {
|
|
1746
|
+
elm.style.border = 'none'
|
|
1747
|
+
})
|
|
1748
|
+
}
|
|
1749
|
+
} else if (speaker && speaker.identity) {
|
|
1750
|
+
const speakerElm = document.getElementById(`participant-${speaker.identity}`)
|
|
1751
|
+
if (participantElms.length > 0) {
|
|
1752
|
+
participantElms.forEach(elm => {
|
|
1753
|
+
elm.style.border = 'none'
|
|
1754
|
+
})
|
|
1755
|
+
}
|
|
1756
|
+
speakerElm && (speakerElm.style.border = '2px solid #95ec69')
|
|
1757
|
+
}
|
|
1758
|
+
},
|
|
1759
|
+
handleResMeetingRefresh(e) {
|
|
1760
|
+
console.log('resMeetingRefresh事件触发', e);
|
|
1761
|
+
|
|
1762
|
+
if (e.includes("queryAllInvite") || e.includes("queryUnjoined")) {
|
|
1763
|
+
console.log('this', this, this.getUnjoinParticipant);
|
|
1764
|
+
|
|
1765
|
+
this.getUnjoinParticipant();
|
|
1766
|
+
this.queryAllInviteParticipant();
|
|
1767
|
+
this.startUnjoinParticipantPolling();
|
|
1768
|
+
}
|
|
1479
1769
|
},
|
|
1480
1770
|
handleRoomDataReceived(e) {
|
|
1481
1771
|
switch (e.topic) {
|
|
@@ -1617,7 +1907,7 @@ export default {
|
|
|
1617
1907
|
} else {
|
|
1618
1908
|
this.meetingCoHost = [];
|
|
1619
1909
|
}
|
|
1620
|
-
|
|
1910
|
+
this.leaderShipFocus = metadata.leaderShipFocus;
|
|
1621
1911
|
this.roomMetadata = metadata;
|
|
1622
1912
|
|
|
1623
1913
|
// 批量设置关键状态
|
|
@@ -1693,6 +1983,7 @@ export default {
|
|
|
1693
1983
|
this.meetingCoHost = [];
|
|
1694
1984
|
}
|
|
1695
1985
|
|
|
1986
|
+
this.leaderShipFocus = metadata.leaderShipFocus;
|
|
1696
1987
|
this.roomMetadata = metadata;
|
|
1697
1988
|
|
|
1698
1989
|
// 批量设置关键状态
|
|
@@ -1765,73 +2056,200 @@ export default {
|
|
|
1765
2056
|
clearInterval(this.unjoinParticipantInterval);
|
|
1766
2057
|
this.unjoinParticipantInterval = null;
|
|
1767
2058
|
}
|
|
1768
|
-
|
|
1769
|
-
// 立即执行一次
|
|
1770
|
-
this.getUnjoinParticipant();
|
|
1771
|
-
this.queryAllInviteParticipant();
|
|
1772
2059
|
// 设置5秒间隔的轮询
|
|
1773
2060
|
this.unjoinParticipantInterval = setInterval(() => {
|
|
1774
2061
|
this.getUnjoinParticipant();
|
|
1775
2062
|
this.queryAllInviteParticipant();
|
|
1776
|
-
},
|
|
2063
|
+
}, 30000);
|
|
1777
2064
|
},
|
|
1778
2065
|
initLiveClient() {
|
|
1779
2066
|
this.liveClient = window["liveClient"];
|
|
1780
2067
|
this.initLiveClientEvent();
|
|
1781
2068
|
},
|
|
1782
|
-
sendInviteMessage(tempList = []
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
2069
|
+
sendInviteMessage(tempList = []) {
|
|
2070
|
+
// 确定使用的邀请列表
|
|
2071
|
+
const inviteList = tempList.length <= 0 ? this.tempInviteList : tempList;
|
|
2072
|
+
|
|
2073
|
+
if (!inviteList || inviteList.length <= 0) {
|
|
2074
|
+
console.log('没有邀请人员');
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
// 去重处理:按 phone 字段去重,按优先级保留
|
|
2079
|
+
const deduplicatedList = [];
|
|
2080
|
+
const phoneMap = new Map();
|
|
2081
|
+
|
|
2082
|
+
// 定义优先级:人员 > 组织架构/外部 > volte > 常用分组 > 未知
|
|
2083
|
+
const getPriority = (source) => {
|
|
2084
|
+
if (source === '人员') return 1;
|
|
2085
|
+
if (source === '组织架构' || source === '外部') return 2;
|
|
2086
|
+
if (source === 'volte') return 3;
|
|
2087
|
+
if (source === '常用分组') return 4;
|
|
2088
|
+
return 5; // 未知类型优先级最低
|
|
2089
|
+
};
|
|
2090
|
+
|
|
2091
|
+
// 第一轮:处理非常用分组的项目,建立 phoneMap
|
|
2092
|
+
inviteList.forEach((item, index) => {
|
|
2093
|
+
const phone = item.phone;
|
|
2094
|
+
if (!phone) {
|
|
2095
|
+
// 没有 phone 字段的直接加入
|
|
2096
|
+
deduplicatedList.push(item);
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
// 如果是常用分组,先跳过,后续单独处理
|
|
2101
|
+
if (item.source === '常用分组') {
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
const existing = phoneMap.get(phone);
|
|
2106
|
+
if (!existing) {
|
|
2107
|
+
// 第一次遇到这个 phone
|
|
2108
|
+
phoneMap.set(phone, { item, index });
|
|
2109
|
+
} else {
|
|
2110
|
+
// 已存在相同 phone,比较优先级
|
|
2111
|
+
const existingPriority = getPriority(existing.item.source);
|
|
2112
|
+
const currentPriority = getPriority(item.source);
|
|
2113
|
+
|
|
2114
|
+
if (currentPriority < existingPriority) {
|
|
2115
|
+
// 当前项优先级更高,替换
|
|
2116
|
+
phoneMap.set(phone, { item, index });
|
|
2117
|
+
} else if (currentPriority === existingPriority && index < existing.index) {
|
|
2118
|
+
// 优先级相同,保留顺序靠前的
|
|
2119
|
+
phoneMap.set(phone, { item, index });
|
|
2120
|
+
}
|
|
2121
|
+
// 否则保持原有的
|
|
2122
|
+
}
|
|
2123
|
+
});
|
|
2124
|
+
|
|
2125
|
+
// 第二轮:处理常用分组项目,根据 original 字段分类
|
|
2126
|
+
const commonGroupItems = inviteList.filter(item => item.source === '常用分组');
|
|
2127
|
+
commonGroupItems.forEach((item, index) => {
|
|
2128
|
+
const phone = item.phone;
|
|
2129
|
+
if (!phone) {
|
|
2130
|
+
// 没有 phone 字段的直接加入
|
|
2131
|
+
deduplicatedList.push(item);
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
// 如果 phone 已经在 phoneMap 中,跳过(已有更高优先级的项)
|
|
2136
|
+
if (phoneMap.has(phone)) {
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
// 根据 original 字段确定实际的 source
|
|
2141
|
+
let actualSource = item.original;
|
|
2142
|
+
|
|
2143
|
+
// 如果 original 字段无效,默认为组织架构
|
|
2144
|
+
if (!actualSource || !['人员', '组织架构', 'volte', '外部'].includes(actualSource)) {
|
|
2145
|
+
actualSource = '组织架构';
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
// 创建新的项目,使用 original 作为实际 source
|
|
2149
|
+
const processedItem = {
|
|
2150
|
+
...item,
|
|
2151
|
+
source: actualSource
|
|
2152
|
+
};
|
|
2153
|
+
|
|
2154
|
+
// 添加到 phoneMap
|
|
2155
|
+
phoneMap.set(phone, { item: processedItem, index: index + inviteList.length });
|
|
2156
|
+
});
|
|
2157
|
+
|
|
2158
|
+
// 将去重后的项添加到结果列表
|
|
2159
|
+
phoneMap.forEach(({ item }) => {
|
|
2160
|
+
if (!deduplicatedList.find(existing => existing.phone === item.phone)) {
|
|
2161
|
+
deduplicatedList.push(item);
|
|
2162
|
+
}
|
|
2163
|
+
});
|
|
2164
|
+
|
|
2165
|
+
// 按 source 分组处理
|
|
2166
|
+
const personList = [];
|
|
2167
|
+
const orgAndExternalList = [];
|
|
2168
|
+
const volteList = [];
|
|
2169
|
+
|
|
2170
|
+
deduplicatedList.forEach(item => {
|
|
2171
|
+
if (item.source === '人员') {
|
|
2172
|
+
personList.push(item);
|
|
2173
|
+
} else if (item.source === '组织架构' || item.source === '外部') {
|
|
2174
|
+
orgAndExternalList.push(item);
|
|
2175
|
+
} else if (item.source === 'volte') {
|
|
2176
|
+
volteList.push(item);
|
|
2177
|
+
}
|
|
2178
|
+
});
|
|
2179
|
+
|
|
2180
|
+
// 发送邀请
|
|
2181
|
+
const promises = [];
|
|
2182
|
+
|
|
2183
|
+
// 处理人员邀请(invitationMethod = 0)
|
|
2184
|
+
if (personList.length > 0) {
|
|
2185
|
+
const personInviteData = personList.map(item => ({
|
|
2186
|
+
userName: item.label,
|
|
2187
|
+
identity: item?.loginCode ? item.loginCode : item.phone,
|
|
2188
|
+
phone: item.phone
|
|
2189
|
+
}));
|
|
2190
|
+
|
|
2191
|
+
promises.push(
|
|
2192
|
+
this.liveClient.inviteParticipant(this.meetingNum, personInviteData, false, 0)
|
|
2193
|
+
.then(res => {
|
|
2194
|
+
if (res.code === 200) {
|
|
2195
|
+
personList.forEach(item => this.addToInviteList(item));
|
|
1806
2196
|
} else {
|
|
1807
|
-
this.showMessage.message("error", res?.msg);
|
|
2197
|
+
this.showMessage.message("error", `邀请人员失败: ${res?.msg}`);
|
|
1808
2198
|
}
|
|
1809
|
-
})
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
.
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
}
|
|
1833
|
-
|
|
2199
|
+
})
|
|
2200
|
+
.catch(err => {
|
|
2201
|
+
this.showMessage.message("error", `邀请人员失败: ${err.message}`);
|
|
2202
|
+
})
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
// 处理组织架构和外部邀请(invitationMethod = 3)
|
|
2207
|
+
if (orgAndExternalList.length > 0) {
|
|
2208
|
+
const orgInviteData = orgAndExternalList.map(item => ({
|
|
2209
|
+
userName: item.label,
|
|
2210
|
+
identity: item?.loginCode ? item.loginCode : item.phone,
|
|
2211
|
+
phone: item.phone
|
|
2212
|
+
}));
|
|
2213
|
+
|
|
2214
|
+
promises.push(
|
|
2215
|
+
this.liveClient.inviteParticipant(this.meetingNum, orgInviteData, false, 3)
|
|
2216
|
+
.then(res => {
|
|
2217
|
+
if (res.code === 200) {
|
|
2218
|
+
orgAndExternalList.forEach(item => this.addToInviteList(item));
|
|
2219
|
+
} else {
|
|
2220
|
+
this.showMessage.message("error", `邀请组织/外部人员失败: ${res?.msg}`);
|
|
2221
|
+
}
|
|
2222
|
+
})
|
|
2223
|
+
.catch(err => {
|
|
2224
|
+
this.showMessage.message("error", `邀请组织/外部人员失败: ${err.message}`);
|
|
2225
|
+
})
|
|
2226
|
+
);
|
|
1834
2227
|
}
|
|
2228
|
+
|
|
2229
|
+
// 处理 volte 呼叫
|
|
2230
|
+
if (volteList.length > 0) {
|
|
2231
|
+
// 构建批量外呼数据
|
|
2232
|
+
const volteCalls = volteList.map(item => ({
|
|
2233
|
+
dnis: item.phone,
|
|
2234
|
+
name: item.label,
|
|
2235
|
+
type: 0
|
|
2236
|
+
}));
|
|
2237
|
+
|
|
2238
|
+
promises.push(
|
|
2239
|
+
this.liveClient.makeBatchCall(volteCalls)
|
|
2240
|
+
.then(() => {
|
|
2241
|
+
volteList.forEach(item => this.addToInviteList(item));
|
|
2242
|
+
})
|
|
2243
|
+
.catch(err => {
|
|
2244
|
+
this.showMessage.message("error", `批量呼叫失败: ${err.message}`);
|
|
2245
|
+
})
|
|
2246
|
+
);
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
// 等待所有邀请完成后更新列表
|
|
2250
|
+
Promise.allSettled(promises).then(() => {
|
|
2251
|
+
// 可以在这里添加统一的后续处理,如刷新邀请列表
|
|
2252
|
+
});
|
|
1835
2253
|
},
|
|
1836
2254
|
queryAllInviteParticipant() {
|
|
1837
2255
|
this.liveClient.getAllInviteParticipant(this.meetingNum).then((res) => {
|
|
@@ -1856,9 +2274,9 @@ export default {
|
|
|
1856
2274
|
roomNum: this.meetingNum,
|
|
1857
2275
|
cameraStatus: this.isCameraEnabled,
|
|
1858
2276
|
microphoneStatus: this.isMicrophoneEnabled,
|
|
1859
|
-
audioDeviceId: this.
|
|
1860
|
-
videoDeviceId: this.
|
|
1861
|
-
outputDeviceId: this.
|
|
2277
|
+
audioDeviceId: this.tempActiveDevice.audioInputDevice,
|
|
2278
|
+
videoDeviceId: this.tempActiveDevice.videoDevice,
|
|
2279
|
+
outputDeviceId: this.tempActiveDevice.audioOutputDevice,
|
|
1862
2280
|
};
|
|
1863
2281
|
let tempActiveDevice = null;
|
|
1864
2282
|
try {
|
|
@@ -1992,36 +2410,83 @@ export default {
|
|
|
1992
2410
|
</div>
|
|
1993
2411
|
`;
|
|
1994
2412
|
} else if (videoItem.isCameraEnabled) {
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2413
|
+
if (videoItem.metadata?.platformID == 5) {
|
|
2414
|
+
videoDom.innerHTML = `
|
|
2415
|
+
<video id="video-${
|
|
2416
|
+
videoItem.identity
|
|
2417
|
+
}" class="p-video" autoplay webkit-playsinline playsinline x5-video-player-type="h5"></video>
|
|
2418
|
+
<div id="loadingIndicator-${videoItem.identity}" class="loadingIndicator">
|
|
2419
|
+
<div class="loading-icon"></div>
|
|
2420
|
+
<div class="loading-text">视频流加载中...</div>
|
|
2421
|
+
</div>
|
|
2422
|
+
<div id="signal-${videoItem.identity}" class="signal-icon signal-icon-good"></div>
|
|
2423
|
+
<div id="more-${videoItem.identity}" class="more-icon"></div>
|
|
2424
|
+
<div id="bitrate-${videoItem.identity}" class="bitrate-indicator"></div>
|
|
2425
|
+
<div class="describe">
|
|
2426
|
+
<div id="microphone-${videoItem.identity}" class="microphone"></div>
|
|
2427
|
+
<div id="${videoItem.identity}" class="identity">${
|
|
2428
|
+
videoItem.isLocal ? videoItem.name + "(我)" : videoItem.name
|
|
2429
|
+
}</div>
|
|
2430
|
+
</div>
|
|
2431
|
+
`;
|
|
2432
|
+
} else {
|
|
2433
|
+
videoDom.innerHTML = `
|
|
2434
|
+
<video id="video-${
|
|
2435
|
+
videoItem.identity
|
|
2436
|
+
}" class="p-video" autoplay webkit-playsinline playsinline x5-video-player-type="h5"></video>
|
|
2437
|
+
<div id="signal-${videoItem.identity}" class="signal-icon signal-icon-good"></div>
|
|
2438
|
+
<div id="more-${videoItem.identity}" class="more-icon"></div>
|
|
2439
|
+
<div id="bitrate-${videoItem.identity}" class="bitrate-indicator"></div>
|
|
2440
|
+
<div class="describe">
|
|
2441
|
+
<div id="microphone-${videoItem.identity}" class="microphone"></div>
|
|
2442
|
+
<div id="${videoItem.identity}" class="identity">${
|
|
2443
|
+
videoItem.isLocal ? videoItem.name + '(我)' : videoItem.name
|
|
2444
|
+
}</div>
|
|
2445
|
+
</div>
|
|
2446
|
+
`;
|
|
2447
|
+
}
|
|
2008
2448
|
} else {
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
<div class="
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2449
|
+
if (videoItem.metadata?.platformID == 5) {
|
|
2450
|
+
videoDom.innerHTML = `
|
|
2451
|
+
<video id="video-${
|
|
2452
|
+
videoItem.identity
|
|
2453
|
+
}" class="p-video" autoplay webkit-playsinline playsinline x5-video-player-type="h5"></video>
|
|
2454
|
+
<div id="loadingIndicator-${videoItem.identity}" class="loadingIndicator">
|
|
2455
|
+
<div class="loading-icon"></div>
|
|
2456
|
+
<div class="loading-text">视频流加载中...</div>
|
|
2457
|
+
</div>
|
|
2458
|
+
<div id="board-${videoItem.identity}" class="board">
|
|
2459
|
+
<div class="board-icon"></div>
|
|
2460
|
+
</div>
|
|
2461
|
+
<div id="signal-${videoItem.identity}" class="signal-icon signal-icon-good"></div>
|
|
2462
|
+
<div id="more-${videoItem.identity}" class="more-icon"></div>
|
|
2463
|
+
<div id="bitrate-${videoItem.identity}" class="bitrate-indicator"></div>
|
|
2464
|
+
<div class="describe">
|
|
2465
|
+
<div id="microphone-${videoItem.identity}" class="microphone"></div>
|
|
2466
|
+
<div id="${videoItem.identity}" class="identity">${
|
|
2467
|
+
videoItem.isLocal ? videoItem.name + '(我)' : videoItem.name
|
|
2468
|
+
}</div>
|
|
2469
|
+
</div>
|
|
2470
|
+
`
|
|
2471
|
+
} else {
|
|
2472
|
+
videoDom.innerHTML = `
|
|
2473
|
+
<video id="video-${
|
|
2474
|
+
videoItem.identity
|
|
2475
|
+
}" class="p-video" autoplay webkit-playsinline playsinline x5-video-player-type="h5"></video>
|
|
2476
|
+
<div id="board-${videoItem.identity}" class="board">
|
|
2477
|
+
<div class="board-icon"></div>
|
|
2478
|
+
</div>
|
|
2479
|
+
<div id="signal-${videoItem.identity}" class="signal-icon signal-icon-good"></div>
|
|
2480
|
+
<div id="more-${videoItem.identity}" class="more-icon"></div>
|
|
2481
|
+
<div id="bitrate-${videoItem.identity}" class="bitrate-indicator"></div>
|
|
2482
|
+
<div class="describe">
|
|
2483
|
+
<div id="microphone-${videoItem.identity}" class="microphone"></div>
|
|
2484
|
+
<div id="${videoItem.identity}" class="identity">${
|
|
2485
|
+
videoItem.isLocal ? videoItem.name + '(我)' : videoItem.name
|
|
2486
|
+
}</div>
|
|
2487
|
+
</div>
|
|
2488
|
+
`
|
|
2489
|
+
}
|
|
2025
2490
|
}
|
|
2026
2491
|
return videoDom;
|
|
2027
2492
|
},
|
|
@@ -2170,6 +2635,57 @@ export default {
|
|
|
2170
2635
|
layoutRightSideEle.appendChild(videoDiv);
|
|
2171
2636
|
}
|
|
2172
2637
|
}
|
|
2638
|
+
} else if (this.currentLayout === 'ring') {
|
|
2639
|
+
// 环形布局:确保容器存在并将与会者放入合适位置
|
|
2640
|
+
let layoutRing = document.querySelector('#room .layout-ring');
|
|
2641
|
+
let layoutRingTop = layoutRing?.querySelector('.layout-ring-top');
|
|
2642
|
+
let layoutRingBottom = layoutRing?.querySelector('.layout-ring-bottom');
|
|
2643
|
+
let ringCenter = layoutRing?.querySelector('.ring-center');
|
|
2644
|
+
|
|
2645
|
+
// 如果容器不存在(可能由于时序问题),创建它
|
|
2646
|
+
if (!layoutRing || !layoutRingTop || !layoutRingBottom || !ringCenter) {
|
|
2647
|
+
layoutRing = document.createElement('div');
|
|
2648
|
+
layoutRing.className = 'layout-ring';
|
|
2649
|
+
layoutRingTop = document.createElement('div');
|
|
2650
|
+
layoutRingTop.className = 'layout-ring-top';
|
|
2651
|
+
layoutRingBottom = document.createElement('div');
|
|
2652
|
+
layoutRingBottom.className = 'layout-ring-bottom';
|
|
2653
|
+
// 创建12个环槽位
|
|
2654
|
+
for (let i = 0; i < 12; i++) {
|
|
2655
|
+
const slot = document.createElement('div');
|
|
2656
|
+
slot.className = 'ring-slot';
|
|
2657
|
+
layoutRingTop.appendChild(slot);
|
|
2658
|
+
}
|
|
2659
|
+
ringCenter = document.createElement('div');
|
|
2660
|
+
ringCenter.className = 'ring-center';
|
|
2661
|
+
layoutRingTop.appendChild(ringCenter);
|
|
2662
|
+
layoutRing.appendChild(layoutRingTop);
|
|
2663
|
+
layoutRing.appendChild(layoutRingBottom);
|
|
2664
|
+
container.insertBefore(layoutRing, container.firstChild ?? null);
|
|
2665
|
+
// 确保环形顶部高度与#room一致
|
|
2666
|
+
requestAnimationFrame(() => this.updateRingTopHeight());
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
const appendToFirstEmptySlot = (el) => {
|
|
2670
|
+
const slots = layoutRingTop.querySelectorAll('.ring-slot');
|
|
2671
|
+
for (let i = 0; i < slots.length; i++) {
|
|
2672
|
+
if (!slots[i].firstChild) {
|
|
2673
|
+
slots[i].appendChild(el);
|
|
2674
|
+
return true;
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
return false;
|
|
2678
|
+
};
|
|
2679
|
+
|
|
2680
|
+
// 主持人优先放置在中心;否则进入第一个空槽;都满了则放到底部
|
|
2681
|
+
if (this.curHostIdentity && videoItem.identity === this.curHostIdentity) {
|
|
2682
|
+
ringCenter.appendChild(videoDiv);
|
|
2683
|
+
} else {
|
|
2684
|
+
const placed = appendToFirstEmptySlot(videoDiv);
|
|
2685
|
+
if (!placed) {
|
|
2686
|
+
layoutRingBottom.appendChild(videoDiv);
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2173
2689
|
}
|
|
2174
2690
|
}
|
|
2175
2691
|
return container;
|
|
@@ -2197,6 +2713,25 @@ export default {
|
|
|
2197
2713
|
videoDiv = this.constructParticipantDom(videoItem);
|
|
2198
2714
|
// 构建会议室布局
|
|
2199
2715
|
container = this.constructRoomLayout(container, videoDiv, videoItem);
|
|
2716
|
+
|
|
2717
|
+
// 显示与会者画面加载中状态
|
|
2718
|
+
let videoElm = document.getElementById(`video-${videoItem.identity}`)
|
|
2719
|
+
let loadingIndicatorElm = document.getElementById(`loadingIndicator-${videoItem.identity}`)
|
|
2720
|
+
if (videoItem.metadata?.platformID == 5 && videoElm && loadingIndicatorElm) {
|
|
2721
|
+
setTimeout(() => {
|
|
2722
|
+
loadingIndicatorElm.style.display = 'none'
|
|
2723
|
+
}, 2000)
|
|
2724
|
+
}
|
|
2725
|
+
if (videoItem.metadata?.platformID == 4 && videoElm && loadingIndicatorElm) {
|
|
2726
|
+
setTimeout(() => {
|
|
2727
|
+
loadingIndicatorElm.style.display = 'none'
|
|
2728
|
+
}, 4000)
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
// 本地与会者镜像处理
|
|
2732
|
+
if (videoItem.isLocal) {
|
|
2733
|
+
videoElm && this.isMirror && (videoElm.style.transform = 'rotateY(180deg)')
|
|
2734
|
+
}
|
|
2200
2735
|
// 添加到participant数组
|
|
2201
2736
|
this.addToParticipantList(videoItem);
|
|
2202
2737
|
}
|
|
@@ -2211,6 +2746,8 @@ export default {
|
|
|
2211
2746
|
let signalElm = document.getElementById(`signal-${videoItem.identity}`);
|
|
2212
2747
|
// 与会者更多操作按钮元素
|
|
2213
2748
|
let moreElm = document.getElementById(`more-${videoItem.identity}`);
|
|
2749
|
+
// 与会者画面旋转按钮元素
|
|
2750
|
+
let rotateElm = document.getElementById(`rotate-${videoItem.identity}`)
|
|
2214
2751
|
// 声明麦克风按钮点击事件回调
|
|
2215
2752
|
const unableMicrophone = () => {
|
|
2216
2753
|
this.liveClient.changeParticipantMicrophoneStatus(videoItem.identity, true);
|
|
@@ -2225,6 +2762,8 @@ export default {
|
|
|
2225
2762
|
const currentMoreElm = document.getElementById(`more-${videoItem.identity}`);
|
|
2226
2763
|
currentSignalElm && (currentSignalElm.style.visibility = "visible");
|
|
2227
2764
|
currentMoreElm && (currentMoreElm.style.visibility = "visible");
|
|
2765
|
+
const currentRotateElm = document.getElementById(`rotate-${videoItem.identity}`);
|
|
2766
|
+
currentRotateElm && (currentRotateElm.style.visibility = "visible");
|
|
2228
2767
|
};
|
|
2229
2768
|
const signalAndMoreHide = () => {
|
|
2230
2769
|
// 每次执行时重新获取最新的元素
|
|
@@ -2232,6 +2771,8 @@ export default {
|
|
|
2232
2771
|
const currentMoreElm = document.getElementById(`more-${videoItem.identity}`);
|
|
2233
2772
|
currentSignalElm && (currentSignalElm.style.visibility = "hidden");
|
|
2234
2773
|
currentMoreElm && (currentMoreElm.style.visibility = "hidden");
|
|
2774
|
+
const currentRotateElm = document.getElementById(`rotate-${videoItem.identity}`);
|
|
2775
|
+
currentRotateElm && (currentRotateElm.style.visibility = "hidden");
|
|
2235
2776
|
};
|
|
2236
2777
|
// 声明操作按钮元素点击事件回调
|
|
2237
2778
|
const moreElmClick = (event) => {
|
|
@@ -2254,6 +2795,13 @@ export default {
|
|
|
2254
2795
|
this.moreDialogShow = true;
|
|
2255
2796
|
}
|
|
2256
2797
|
};
|
|
2798
|
+
// 旋转按钮点击事件:顺时针旋转90度(并居中适配)
|
|
2799
|
+
const rotateElmClick = () => {
|
|
2800
|
+
const current = this.rotateDegreeMap.get(videoItem.identity) || 0;
|
|
2801
|
+
const next = (current + 90) % 360;
|
|
2802
|
+
this.rotateDegreeMap.set(videoItem.identity, next);
|
|
2803
|
+
this.applyRotation(videoItem.identity, next);
|
|
2804
|
+
};
|
|
2257
2805
|
// 为麦克风元素绑定事件
|
|
2258
2806
|
if (microElm && videoItem.isMicrophoneEnabled) {
|
|
2259
2807
|
microElm.className = "microphone microphone-active";
|
|
@@ -2281,12 +2829,47 @@ export default {
|
|
|
2281
2829
|
if (moreElm) {
|
|
2282
2830
|
moreElm.onclick = moreElmClick;
|
|
2283
2831
|
}
|
|
2832
|
+
if (rotateElm) {
|
|
2833
|
+
rotateElm.onclick = rotateElmClick;
|
|
2834
|
+
}
|
|
2284
2835
|
if (signalElm) {
|
|
2285
2836
|
if (videoItem.connectionQuality === "excellent" || videoItem.connectionQuality === "good") {
|
|
2286
2837
|
signalElm.className = "signal-icon signal-icon-good";
|
|
2287
2838
|
} else {
|
|
2288
2839
|
signalElm.className = "signal-icon signal-icon-poor";
|
|
2289
2840
|
}
|
|
2841
|
+
// 为信号图标绑定鼠标悬停事件,显示码率信息
|
|
2842
|
+
signalElm.onmouseenter = () => {
|
|
2843
|
+
const bitrateElm = document.getElementById(`bitrate-${videoItem.identity}`)
|
|
2844
|
+
if (bitrateElm) {
|
|
2845
|
+
const bitrate = this.liveClient.getParticipantBitrate(videoItem.identity)
|
|
2846
|
+
console.log('bitrate', bitrate)
|
|
2847
|
+
if (bitrate !== null) {
|
|
2848
|
+
bitrateElm.innerText = `${bitrate} kbps`
|
|
2849
|
+
bitrateElm.style.display = 'block'
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
signalElm.onmouseleave = () => {
|
|
2854
|
+
const bitrateElm = document.getElementById(`bitrate-${videoItem.identity}`)
|
|
2855
|
+
if (bitrateElm) {
|
|
2856
|
+
bitrateElm.style.display = 'none'
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
// 根据platformID设定元素样式
|
|
2861
|
+
if (
|
|
2862
|
+
videoItem.metadata?.platformID == 1 ||
|
|
2863
|
+
videoItem.metadata?.platformID == 4 ||
|
|
2864
|
+
videoItem.metadata?.platformID == 7
|
|
2865
|
+
) {
|
|
2866
|
+
videoElm && (videoElm.style.objectFit = 'contain')
|
|
2867
|
+
} else {
|
|
2868
|
+
if (videoItem?.source == 'camera') {
|
|
2869
|
+
videoElm && (videoElm.style.objectFit = 'cover')
|
|
2870
|
+
} else {
|
|
2871
|
+
videoElm && (videoElm.style.objectFit = 'contain')
|
|
2872
|
+
}
|
|
2290
2873
|
}
|
|
2291
2874
|
let screenShareElm = document.getElementById(`screenshare-${videoItem.identity}`);
|
|
2292
2875
|
// 与会者dom元素内部样式重新渲染(摄像头或屏幕共享切换后)
|
|
@@ -2336,6 +2919,15 @@ export default {
|
|
|
2336
2919
|
signalElm.className = "signal-icon signal-icon-poor";
|
|
2337
2920
|
}
|
|
2338
2921
|
videoDiv.appendChild(signalElm);
|
|
2922
|
+
|
|
2923
|
+
// 构建码率显示元素
|
|
2924
|
+
let bitrateElm = document.getElementById(`bitrate-${identity}`)
|
|
2925
|
+
if (!bitrateElm) {
|
|
2926
|
+
bitrateElm = document.createElement('div')
|
|
2927
|
+
bitrateElm.id = `bitrate-${videoItem.identity}`
|
|
2928
|
+
bitrateElm.className = 'bitrate-indicator'
|
|
2929
|
+
videoDiv.appendChild(bitrateElm)
|
|
2930
|
+
}
|
|
2339
2931
|
}
|
|
2340
2932
|
// if (!moreElm) {
|
|
2341
2933
|
// // 构建更多操作按钮元素
|
|
@@ -2358,6 +2950,28 @@ export default {
|
|
|
2358
2950
|
moreElm = null;
|
|
2359
2951
|
}
|
|
2360
2952
|
}
|
|
2953
|
+
// 仅在platformID为4展示旋转按钮(对所有与会者可见)
|
|
2954
|
+
if (videoItem.metadata?.platformID === 4) {
|
|
2955
|
+
if (!rotateElm) {
|
|
2956
|
+
rotateElm = document.createElement('div');
|
|
2957
|
+
rotateElm.id = `rotate-${videoItem.identity}`;
|
|
2958
|
+
rotateElm.className = 'rotate-icon rotate-icon1';
|
|
2959
|
+
videoDiv && videoDiv.appendChild(rotateElm);
|
|
2960
|
+
rotateElm.onclick = rotateElmClick;
|
|
2961
|
+
}
|
|
2962
|
+
// 初始化map中度数(如果不存在)并应用到元素
|
|
2963
|
+
if (!this.rotateDegreeMap.has(videoItem.identity)) {
|
|
2964
|
+
this.rotateDegreeMap.set(videoItem.identity, 0);
|
|
2965
|
+
}
|
|
2966
|
+
this.applyRotation(videoItem.identity, this.rotateDegreeMap.get(videoItem.identity));
|
|
2967
|
+
} else {
|
|
2968
|
+
// 如果不是platformID 4,移除旋转按钮及map记录
|
|
2969
|
+
if (rotateElm) {
|
|
2970
|
+
try { videoDiv.removeChild(rotateElm); } catch(e) {}
|
|
2971
|
+
rotateElm = null;
|
|
2972
|
+
}
|
|
2973
|
+
this.rotateDegreeMap.delete(videoItem.identity);
|
|
2974
|
+
}
|
|
2361
2975
|
} else {
|
|
2362
2976
|
if (!boardElm) {
|
|
2363
2977
|
// 之前为屏幕共享状态或摄像头开启状态
|
|
@@ -2391,6 +3005,11 @@ export default {
|
|
|
2391
3005
|
this.removeFromParticipantList(videoItem.identity);
|
|
2392
3006
|
this.filterParticipantList();
|
|
2393
3007
|
|
|
3008
|
+
// 清理可能存在的旋转状态
|
|
3009
|
+
if (this.rotateDegreeMap.has(videoItem.identity)) {
|
|
3010
|
+
this.rotateDegreeMap.delete(videoItem.identity);
|
|
3011
|
+
}
|
|
3012
|
+
|
|
2394
3013
|
// 处理焦点用户离开的情况
|
|
2395
3014
|
if (this.curBlurIdentity === videoItem.identity) {
|
|
2396
3015
|
console.log("焦点用户离开会议:", videoItem.identity);
|
|
@@ -2418,18 +3037,6 @@ export default {
|
|
|
2418
3037
|
this.curHostIdentity = null;
|
|
2419
3038
|
// 主持人离开后具体的逻辑处理放到watch中处理
|
|
2420
3039
|
}
|
|
2421
|
-
// pointTurn模式下,主持人离开处理
|
|
2422
|
-
// if (currentRoomMode.value === "pointTurn" && videoItem.identity === curHostIdentity.value) {
|
|
2423
|
-
// 当前离开与会者为会议主持人
|
|
2424
|
-
// const pointTurnCenter = document.querySelector("#room .point-turn-center");
|
|
2425
|
-
// if (pointTurnCenter) {
|
|
2426
|
-
// // 会议中没有其他主持人,显示默认占位元素
|
|
2427
|
-
// const placeHolderElm = document.createElement("div");
|
|
2428
|
-
// placeHolderElm.className = "participant";
|
|
2429
|
-
// placeHolderElm.innerHTML = placeholderTemplate;
|
|
2430
|
-
// pointTurnCenter.insertBefore(placeHolderElm, pointTurnCenter.firstChild ?? null);
|
|
2431
|
-
// }
|
|
2432
|
-
// }
|
|
2433
3040
|
// pointTurn模式下的其他用户离开处理
|
|
2434
3041
|
if (
|
|
2435
3042
|
this.currentRoomMode === "pointTurn" &&
|
|
@@ -2538,6 +3145,10 @@ export default {
|
|
|
2538
3145
|
// 更新与会者数组
|
|
2539
3146
|
this.addToParticipantList(videoItem);
|
|
2540
3147
|
this.filterParticipantList();
|
|
3148
|
+
// 每次render时,如果该用户在rotateDegreeMap中,则应用其旋转到video元素(确保DOM更新后生效)
|
|
3149
|
+
if (this.rotateDegreeMap.has(videoItem.identity)) {
|
|
3150
|
+
this.applyRotation(videoItem.identity, this.rotateDegreeMap.get(videoItem.identity));
|
|
3151
|
+
}
|
|
2541
3152
|
// 与会者结束共享,切换回到之前布局(仅主持人执行)
|
|
2542
3153
|
if (this.currentRoomMode === "normal" && this.judgeParticipantIsHost(this.localIdentity)) {
|
|
2543
3154
|
console.log(
|
|
@@ -2750,7 +3361,73 @@ export default {
|
|
|
2750
3361
|
|
|
2751
3362
|
if (oldLayout === "grid" && newLayout === "ring") {
|
|
2752
3363
|
// 从宫格布局切换到环状布局
|
|
2753
|
-
//
|
|
3364
|
+
// 实现环形布局:上半部分为4x4网格(中心2x2为主持人独占),其余12个位置按从上到下、从左到右顺序填充;
|
|
3365
|
+
// 超过13人的用户放到下半部分按4x4顺序排列
|
|
3366
|
+
|
|
3367
|
+
// 清理可能残留的环形容器
|
|
3368
|
+
const existingRing = document.querySelector('#room .layout-ring');
|
|
3369
|
+
if (existingRing) {
|
|
3370
|
+
console.warn('发现残留的环形布局容器,正在清理');
|
|
3371
|
+
existingRing.remove();
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
// 创建环形布局容器
|
|
3375
|
+
const layoutRing = document.createElement('div');
|
|
3376
|
+
layoutRing.className = 'layout-ring';
|
|
3377
|
+
|
|
3378
|
+
const layoutRingTop = document.createElement('div');
|
|
3379
|
+
layoutRingTop.className = 'layout-ring-top';
|
|
3380
|
+
|
|
3381
|
+
const layoutRingBottom = document.createElement('div');
|
|
3382
|
+
layoutRingBottom.className = 'layout-ring-bottom';
|
|
3383
|
+
|
|
3384
|
+
// 按照4x4网格的顺序,但排除中心2x2的位置(索引 5,6,9,10),插入12个slot
|
|
3385
|
+
const slotOrder = [0,1,2,3,4,7,8,11,12,13,14,15];
|
|
3386
|
+
slotOrder.forEach((pos, idx) => {
|
|
3387
|
+
const slot = document.createElement('div');
|
|
3388
|
+
slot.className = 'ring-slot';
|
|
3389
|
+
slot.dataset.pos = String(pos);
|
|
3390
|
+
layoutRingTop.appendChild(slot);
|
|
3391
|
+
});
|
|
3392
|
+
|
|
3393
|
+
// 中央占位,用于主持人占据2x2
|
|
3394
|
+
const ringCenter = document.createElement('div');
|
|
3395
|
+
ringCenter.className = 'ring-center';
|
|
3396
|
+
layoutRingTop.appendChild(ringCenter);
|
|
3397
|
+
|
|
3398
|
+
// 将元素从主容器中收集并按规则分配到环形布局
|
|
3399
|
+
const participantElements = Array.from(container.children).filter(child => child.classList && child.classList.contains('participant') && child.id && child.id.startsWith('participant-'));
|
|
3400
|
+
|
|
3401
|
+
// 将主持人放到center,其余依序填充top slots,溢出进入bottom
|
|
3402
|
+
let filledCount = 0;
|
|
3403
|
+
participantElements.forEach((child) => {
|
|
3404
|
+
try {
|
|
3405
|
+
const id = child.id.replace('participant-', '');
|
|
3406
|
+
if (this.curHostIdentity && id === this.curHostIdentity) {
|
|
3407
|
+
ringCenter.appendChild(child);
|
|
3408
|
+
} else {
|
|
3409
|
+
if (filledCount < 12) {
|
|
3410
|
+
const slot = layoutRingTop.querySelectorAll('.ring-slot')[filledCount];
|
|
3411
|
+
slot && slot.appendChild(child);
|
|
3412
|
+
filledCount++;
|
|
3413
|
+
} else {
|
|
3414
|
+
layoutRingBottom.appendChild(child);
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
} catch (err) {
|
|
3418
|
+
console.error('分配参与者到环形布局时出错', err);
|
|
3419
|
+
}
|
|
3420
|
+
});
|
|
3421
|
+
|
|
3422
|
+
layoutRing.appendChild(layoutRingTop);
|
|
3423
|
+
layoutRing.appendChild(layoutRingBottom);
|
|
3424
|
+
|
|
3425
|
+
// 清理主容器中可能存在的非participant占位元素(避免重复)
|
|
3426
|
+
// 移除原有非布局容器元素
|
|
3427
|
+
// 插入新的环形布局
|
|
3428
|
+
container.insertBefore(layoutRing, container.firstChild ?? null);
|
|
3429
|
+
// 切换到环形后,同步一次顶部高度
|
|
3430
|
+
requestAnimationFrame(() => this.updateRingTopHeight());
|
|
2754
3431
|
}
|
|
2755
3432
|
|
|
2756
3433
|
if (oldLayout === "grid" && newLayout === "downLSide") {
|
|
@@ -2798,6 +3475,178 @@ export default {
|
|
|
2798
3475
|
// 添加其他元素
|
|
2799
3476
|
container.appendChild(fragment);
|
|
2800
3477
|
}
|
|
3478
|
+
// 从焦点布局切换到环形布局
|
|
3479
|
+
if (oldLayout === 'rightSide' && newLayout === 'ring') {
|
|
3480
|
+
// 将右侧/左侧容器拆解回主容器,然后重用 grid->ring 的逻辑
|
|
3481
|
+
let layoutRightSideEle = document.querySelector('#room .layout-rightside');
|
|
3482
|
+
let layoutLeftSideEle = document.querySelector('#room .layout-leftside');
|
|
3483
|
+
|
|
3484
|
+
// 把左右容器内的元素移动回主容器顺序(左侧优先)
|
|
3485
|
+
const moveChildrenToContainer = (ele) => {
|
|
3486
|
+
if (!ele) return;
|
|
3487
|
+
let child = null;
|
|
3488
|
+
while ((child = ele.firstChild)) {
|
|
3489
|
+
container.appendChild(child);
|
|
3490
|
+
}
|
|
3491
|
+
if (container.contains(ele)) container.removeChild(ele);
|
|
3492
|
+
};
|
|
3493
|
+
moveChildrenToContainer(layoutLeftSideEle);
|
|
3494
|
+
moveChildrenToContainer(layoutRightSideEle);
|
|
3495
|
+
|
|
3496
|
+
// 触发自身来走 grid->ring 分支,通过重新调用 executeLayoutChange 从 container 状态转换
|
|
3497
|
+
// 为避免递归/竞态,我们直接调用 the grid->ring block by setting variables here.
|
|
3498
|
+
// 简化做法:复制 grid->ring behavior here
|
|
3499
|
+
const existingRing = document.querySelector('#room .layout-ring');
|
|
3500
|
+
if (existingRing) existingRing.remove();
|
|
3501
|
+
const layoutRing = document.createElement('div');
|
|
3502
|
+
layoutRing.className = 'layout-ring';
|
|
3503
|
+
const layoutRingTop = document.createElement('div');
|
|
3504
|
+
layoutRingTop.className = 'layout-ring-top';
|
|
3505
|
+
const layoutRingBottom = document.createElement('div');
|
|
3506
|
+
layoutRingBottom.className = 'layout-ring-bottom';
|
|
3507
|
+
const slotOrder = [0,1,2,3,4,7,8,11,12,13,14,15];
|
|
3508
|
+
slotOrder.forEach((pos) => {
|
|
3509
|
+
const slot = document.createElement('div');
|
|
3510
|
+
slot.className = 'ring-slot';
|
|
3511
|
+
slot.dataset.pos = String(pos);
|
|
3512
|
+
layoutRingTop.appendChild(slot);
|
|
3513
|
+
});
|
|
3514
|
+
const ringCenter = document.createElement('div');
|
|
3515
|
+
ringCenter.className = 'ring-center';
|
|
3516
|
+
layoutRingTop.appendChild(ringCenter);
|
|
3517
|
+
const participantElements = Array.from(container.children).filter(child => child.classList && child.classList.contains('participant') && child.id && child.id.startsWith('participant-'));
|
|
3518
|
+
let filledCount = 0;
|
|
3519
|
+
participantElements.forEach((child) => {
|
|
3520
|
+
const id = child.id.replace('participant-', '');
|
|
3521
|
+
if (this.curHostIdentity && id === this.curHostIdentity) {
|
|
3522
|
+
ringCenter.appendChild(child);
|
|
3523
|
+
} else {
|
|
3524
|
+
if (filledCount < 12) {
|
|
3525
|
+
const slot = layoutRingTop.querySelectorAll('.ring-slot')[filledCount];
|
|
3526
|
+
slot && slot.appendChild(child);
|
|
3527
|
+
filledCount++;
|
|
3528
|
+
} else {
|
|
3529
|
+
layoutRingBottom.appendChild(child);
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
});
|
|
3533
|
+
layoutRing.appendChild(layoutRingTop);
|
|
3534
|
+
layoutRing.appendChild(layoutRingBottom);
|
|
3535
|
+
container.insertBefore(layoutRing, container.firstChild ?? null);
|
|
3536
|
+
// 右侧边栏 -> 环形:创建后同步高度
|
|
3537
|
+
requestAnimationFrame(() => this.updateRingTopHeight());
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
// 从环形布局切换到宫格布局
|
|
3541
|
+
if (oldLayout === 'ring' && newLayout === 'grid') {
|
|
3542
|
+
const layoutRing = document.querySelector('#room .layout-ring');
|
|
3543
|
+
if (layoutRing) {
|
|
3544
|
+
const top = layoutRing.querySelector('.layout-ring-top');
|
|
3545
|
+
const bottom = layoutRing.querySelector('.layout-ring-bottom');
|
|
3546
|
+
// 将top中的slot里的参与者按slot顺序移动回主容器
|
|
3547
|
+
if (top) {
|
|
3548
|
+
const slots = top.querySelectorAll('.ring-slot');
|
|
3549
|
+
slots.forEach(slot => {
|
|
3550
|
+
while (slot.firstChild) {
|
|
3551
|
+
container.appendChild(slot.firstChild);
|
|
3552
|
+
}
|
|
3553
|
+
slot.remove();
|
|
3554
|
+
});
|
|
3555
|
+
const center = top.querySelector('.ring-center');
|
|
3556
|
+
if (center) {
|
|
3557
|
+
while (center.firstChild) {
|
|
3558
|
+
container.appendChild(center.firstChild);
|
|
3559
|
+
}
|
|
3560
|
+
center.remove();
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
if (bottom) {
|
|
3564
|
+
while (bottom.firstChild) {
|
|
3565
|
+
container.appendChild(bottom.firstChild);
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
layoutRing.remove();
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
|
|
3572
|
+
// 从环形布局切换到焦点布局
|
|
3573
|
+
if (oldLayout === 'ring' && newLayout === 'rightSide') {
|
|
3574
|
+
// 先把ring拆平回主容器,再复用 rightSide creation logic
|
|
3575
|
+
const layoutRing = document.querySelector('#room .layout-ring');
|
|
3576
|
+
if (layoutRing) {
|
|
3577
|
+
const top = layoutRing.querySelector('.layout-ring-top');
|
|
3578
|
+
const bottom = layoutRing.querySelector('.layout-ring-bottom');
|
|
3579
|
+
if (top) {
|
|
3580
|
+
const slots = top.querySelectorAll('.ring-slot');
|
|
3581
|
+
slots.forEach(slot => {
|
|
3582
|
+
while (slot.firstChild) {
|
|
3583
|
+
container.appendChild(slot.firstChild);
|
|
3584
|
+
}
|
|
3585
|
+
});
|
|
3586
|
+
const center = top.querySelector('.ring-center');
|
|
3587
|
+
if (center) {
|
|
3588
|
+
while (center.firstChild) {
|
|
3589
|
+
container.appendChild(center.firstChild);
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
if (bottom) {
|
|
3594
|
+
while (bottom.firstChild) {
|
|
3595
|
+
container.appendChild(bottom.firstChild);
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
layoutRing.remove();
|
|
3599
|
+
}
|
|
3600
|
+
|
|
3601
|
+
// 现在容器已被平铺,复用已有 grid->rightSide 代码 path by creating left/right containers
|
|
3602
|
+
let layoutRightSideEle = document.createElement('div');
|
|
3603
|
+
layoutRightSideEle.className = 'layout-rightside';
|
|
3604
|
+
let layoutLeftSideEle = document.createElement('div');
|
|
3605
|
+
layoutLeftSideEle.className = 'layout-leftside';
|
|
3606
|
+
|
|
3607
|
+
// 确定焦点用户 - 优先级:当前焦点用户 > 主持人 > 本地用户
|
|
3608
|
+
let focusVideoItem = null;
|
|
3609
|
+
let blurDom = null;
|
|
3610
|
+
if (this.curBlurIdentity) {
|
|
3611
|
+
focusVideoItem = getUserItemByIdentity(this.curBlurIdentity);
|
|
3612
|
+
if (focusVideoItem) blurDom = document.getElementById(`participant-${focusVideoItem.identity}`);
|
|
3613
|
+
}
|
|
3614
|
+
if (!blurDom && this.curHostIdentity) {
|
|
3615
|
+
focusVideoItem = getUserItemByIdentity(this.curHostIdentity);
|
|
3616
|
+
if (focusVideoItem) blurDom = document.getElementById(`participant-${focusVideoItem.identity}`);
|
|
3617
|
+
}
|
|
3618
|
+
if (!blurDom) {
|
|
3619
|
+
focusVideoItem = getLocalParticipant();
|
|
3620
|
+
if (focusVideoItem) blurDom = document.getElementById(`participant-${focusVideoItem.identity}`);
|
|
3621
|
+
}
|
|
3622
|
+
|
|
3623
|
+
if (blurDom) {
|
|
3624
|
+
if (layoutLeftSideEle.hasChildNodes()) {
|
|
3625
|
+
let child;
|
|
3626
|
+
while ((child = layoutLeftSideEle.firstChild)) {
|
|
3627
|
+
layoutRightSideEle.appendChild(child);
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
layoutLeftSideEle.appendChild(blurDom);
|
|
3631
|
+
|
|
3632
|
+
// 将剩余元素移动到右侧
|
|
3633
|
+
if (container.hasChildNodes()) {
|
|
3634
|
+
let child;
|
|
3635
|
+
while ((child = container.firstElementChild)) {
|
|
3636
|
+
if (child && child !== layoutLeftSideEle && child !== layoutRightSideEle) {
|
|
3637
|
+
layoutRightSideEle.appendChild(child);
|
|
3638
|
+
} else {
|
|
3639
|
+
if (child) container.removeChild(child);
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3644
|
+
container.insertBefore(layoutLeftSideEle, container.firstChild ?? null);
|
|
3645
|
+
container.appendChild(layoutRightSideEle);
|
|
3646
|
+
} else {
|
|
3647
|
+
this.showMessage.warning('无法找到有效的焦点用户');
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
2801
3650
|
}
|
|
2802
3651
|
});
|
|
2803
3652
|
},
|
|
@@ -2897,7 +3746,6 @@ export default {
|
|
|
2897
3746
|
this.liveClient.deleteUnjoinParticipant(this.meetingNum, item.identity).then((res) => {
|
|
2898
3747
|
if (res.code == 200) {
|
|
2899
3748
|
this.showMessage.message("success", "成功删除未入会人员");
|
|
2900
|
-
this.getUnjoinParticipant();
|
|
2901
3749
|
this.removeFromInviteList(item.identity);
|
|
2902
3750
|
} else {
|
|
2903
3751
|
this.showMessage.message("error", res?.msg);
|
|
@@ -3137,12 +3985,12 @@ export default {
|
|
|
3137
3985
|
},
|
|
3138
3986
|
async openCamera(e) {
|
|
3139
3987
|
if (this.liveClient) {
|
|
3140
|
-
await this.liveClient.changeParticipantCameraStatus(e,
|
|
3988
|
+
await this.liveClient.changeParticipantCameraStatus(e, false);
|
|
3141
3989
|
}
|
|
3142
3990
|
},
|
|
3143
3991
|
async closeCamera(e) {
|
|
3144
3992
|
if (this.liveClient) {
|
|
3145
|
-
await this.liveClient.changeParticipantCameraStatus(e,
|
|
3993
|
+
await this.liveClient.changeParticipantCameraStatus(e, true);
|
|
3146
3994
|
}
|
|
3147
3995
|
},
|
|
3148
3996
|
async setToHost(e) {
|
|
@@ -3188,35 +4036,49 @@ export default {
|
|
|
3188
4036
|
e.forEach((item) => {
|
|
3189
4037
|
this.addToInviteList(item);
|
|
3190
4038
|
});
|
|
4039
|
+
this.sendInviteMessage(e)
|
|
3191
4040
|
}
|
|
3192
|
-
let tempList = [];
|
|
3193
|
-
let index = -1;
|
|
3194
|
-
this.inviteList.forEach((item) => {
|
|
3195
|
-
if (this.invitedNum > 0) {
|
|
3196
|
-
index = this.tempInvitedList.findIndex((ele) => {
|
|
3197
|
-
if (item?.loginCode) {
|
|
3198
|
-
return ele.identity === item.loginCode;
|
|
3199
|
-
} else {
|
|
3200
|
-
return ele.identity === item.phone;
|
|
3201
|
-
}
|
|
3202
|
-
});
|
|
3203
|
-
if (index < 0) {
|
|
3204
|
-
// 当前人员尚未被邀请
|
|
3205
|
-
tempList.push(item);
|
|
3206
|
-
}
|
|
3207
|
-
} else {
|
|
3208
|
-
tempList.push(item);
|
|
3209
|
-
}
|
|
3210
|
-
});
|
|
3211
|
-
|
|
3212
|
-
this.sendInviteMessage(tempList, this.defaultInviteWay == "mini" ? 3 : 0);
|
|
3213
4041
|
},
|
|
3214
4042
|
async appendInviteDevice(e) {
|
|
3215
4043
|
console.log("通讯录邀请设备", e);
|
|
3216
4044
|
if (e && e.length > 0) {
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
4045
|
+
// 构建批量外呼参数并一次性发起
|
|
4046
|
+
const deviceCalls = e
|
|
4047
|
+
.map(item => {
|
|
4048
|
+
const id = item?.equipmentID || item?.monitorID
|
|
4049
|
+
return id ? { dnis: id, name: item.label, type: this.handleInviteType(item?.integrationType) } : null
|
|
4050
|
+
})
|
|
4051
|
+
.filter(Boolean)
|
|
4052
|
+
|
|
4053
|
+
if (deviceCalls.length > 0) {
|
|
4054
|
+
try {
|
|
4055
|
+
await this.liveClient.makeBatchCall(deviceCalls)
|
|
4056
|
+
this.showMessage.message('success', '已发起通讯录设备批量外呼')
|
|
4057
|
+
} catch (err) {
|
|
4058
|
+
this.showMessage.message('error', `通讯录设备批量外呼失败: ${err?.message || err}`)
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
},
|
|
4063
|
+
async appendInviteTerminal(e) {
|
|
4064
|
+
console.log('通讯录邀请会议终端', e)
|
|
4065
|
+
if (e && e.length > 0) {
|
|
4066
|
+
// 构建批量外呼参数并一次性发起
|
|
4067
|
+
const terminalCalls = e
|
|
4068
|
+
.map(item => {
|
|
4069
|
+
const id = item?.id
|
|
4070
|
+
return id ? { dnis: id, name: item.label, type: 4 } : null
|
|
4071
|
+
})
|
|
4072
|
+
.filter(Boolean)
|
|
4073
|
+
|
|
4074
|
+
if (terminalCalls.length > 0) {
|
|
4075
|
+
try {
|
|
4076
|
+
await this.liveClient.makeBatchCall(terminalCalls)
|
|
4077
|
+
this.showMessage.message('success', '已发起会议终端批量外呼')
|
|
4078
|
+
} catch (err) {
|
|
4079
|
+
this.showMessage.message('error', `会议终端批量外呼失败: ${err?.message || err}`)
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
3220
4082
|
}
|
|
3221
4083
|
},
|
|
3222
4084
|
async updateNameConfirm(e) {
|
|
@@ -3248,7 +4110,30 @@ export default {
|
|
|
3248
4110
|
type,
|
|
3249
4111
|
});
|
|
3250
4112
|
},
|
|
4113
|
+
dispatchLocalTrack(kind = '') {
|
|
4114
|
+
if (this.participantNum > 0) {
|
|
4115
|
+
let index = this.participants.findIndex(item => item.isLocal)
|
|
4116
|
+
if (index !== -1) {
|
|
4117
|
+
let localVideoTrack = this.participants[index]?.videoTrack
|
|
4118
|
+
let localAudioTrack = this.participants[index]?.audioTrack
|
|
4119
|
+
if (!kind) {
|
|
4120
|
+
localVideoTrack?.detach()
|
|
4121
|
+
localVideoTrack?.stop()
|
|
4122
|
+
localAudioTrack?.detach()
|
|
4123
|
+
localAudioTrack?.stop()
|
|
4124
|
+
} else if (kind === 'video') {
|
|
4125
|
+
localVideoTrack?.detach()
|
|
4126
|
+
localVideoTrack?.stop()
|
|
4127
|
+
} else if (kind === 'audio') {
|
|
4128
|
+
localAudioTrack?.detach()
|
|
4129
|
+
localAudioTrack?.stop()
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
},
|
|
3251
4134
|
async chooseVideoDevice(e) {
|
|
4135
|
+
this.dispatchLocalTrack('video')
|
|
4136
|
+
await this.sleep(100)
|
|
3252
4137
|
await this.changeActiveDevice("videoinput", e);
|
|
3253
4138
|
this.videoSelectShow = false;
|
|
3254
4139
|
},
|
|
@@ -3309,17 +4194,18 @@ export default {
|
|
|
3309
4194
|
},
|
|
3310
4195
|
appendInvite(e, inviteWay) {
|
|
3311
4196
|
let tempList = [];
|
|
3312
|
-
let tempDeviceList = []
|
|
4197
|
+
let tempDeviceList = []
|
|
4198
|
+
let tempTerminalList = []
|
|
3313
4199
|
let index = -1;
|
|
3314
4200
|
if (e && e.length > 0) {
|
|
3315
4201
|
e.forEach((item) => {
|
|
3316
|
-
if
|
|
4202
|
+
if(item.source == "人员") {
|
|
3317
4203
|
if (this.invitedNum > 0) {
|
|
3318
4204
|
index = this.tempInvitedList.findIndex((ele) => {
|
|
3319
|
-
if
|
|
3320
|
-
return ele.identity === item.loginCode
|
|
4205
|
+
if(item?.loginCode) {
|
|
4206
|
+
return ele.identity === item.loginCode
|
|
3321
4207
|
} else {
|
|
3322
|
-
return ele.identity === item.phone
|
|
4208
|
+
return ele.identity === item.phone
|
|
3323
4209
|
}
|
|
3324
4210
|
});
|
|
3325
4211
|
if (index < 0) {
|
|
@@ -3330,32 +4216,129 @@ export default {
|
|
|
3330
4216
|
tempList.push(item);
|
|
3331
4217
|
}
|
|
3332
4218
|
this.addToInviteList(item);
|
|
3333
|
-
} else {
|
|
3334
|
-
tempDeviceList.push(item)
|
|
4219
|
+
} else if (item.source == '设备' || item.source == '监控') {
|
|
4220
|
+
tempDeviceList.push(item)
|
|
4221
|
+
} else if (item.source == '会议终端') {
|
|
4222
|
+
tempTerminalList.push(item)
|
|
3335
4223
|
}
|
|
3336
4224
|
});
|
|
3337
4225
|
console.log("本次追加邀请人员", tempList);
|
|
3338
4226
|
console.log("本次追加邀请设备", tempDeviceList);
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
4227
|
+
console.log("本次追加邀请会议终端", tempTerminalList);
|
|
4228
|
+
// 追加邀请人员
|
|
4229
|
+
if(tempList.length > 0) {
|
|
4230
|
+
const promises = [];
|
|
4231
|
+
if(inviteWay == "identity") {
|
|
4232
|
+
const applicationInviteData = tempList.map(item => {
|
|
4233
|
+
return {
|
|
4234
|
+
userName: item?.label || item?.userName || '未知用户',
|
|
4235
|
+
identity: item?.loginCode || item.phone,
|
|
4236
|
+
phone: item.phone,
|
|
4237
|
+
}
|
|
4238
|
+
})
|
|
4239
|
+
promises.push(
|
|
4240
|
+
this.liveClient.inviteParticipant(this.meetingNum, applicationInviteData, false, 0)
|
|
4241
|
+
.then(res => {
|
|
4242
|
+
if (res.code === 200) {
|
|
4243
|
+
console.log("邀请用户成功", applicationInviteData);
|
|
4244
|
+
} else {
|
|
4245
|
+
this.showMessage.message("error", `邀请用户失败: ${res?.msg}`);
|
|
4246
|
+
}
|
|
4247
|
+
})
|
|
4248
|
+
.catch(err => {
|
|
4249
|
+
this.showMessage.message("error", `邀请用户失败: ${err.message}`);
|
|
4250
|
+
})
|
|
4251
|
+
);
|
|
4252
|
+
} else if(inviteWay == "volte") {
|
|
4253
|
+
const volteCalls = tempList.map(item => {
|
|
4254
|
+
return {
|
|
4255
|
+
dnis: item.phone,
|
|
4256
|
+
name: item?.label || item?.userName || '未知用户',
|
|
4257
|
+
type: 0,
|
|
4258
|
+
}
|
|
4259
|
+
})
|
|
4260
|
+
promises.push(
|
|
4261
|
+
this.liveClient.makeBatchCall(volteCalls)
|
|
4262
|
+
.then(() => {
|
|
4263
|
+
console.log("批量呼叫成功", volteCalls);
|
|
4264
|
+
})
|
|
4265
|
+
.catch(err => {
|
|
4266
|
+
this.showMessage.message("error", `批量呼叫失败: ${err.message}`);
|
|
4267
|
+
})
|
|
4268
|
+
);
|
|
4269
|
+
} else if(inviteWay == "mini") {
|
|
4270
|
+
const miniInviteData = tempList.map(item => {
|
|
4271
|
+
return {
|
|
4272
|
+
userName: item?.label || item?.userName || '未知用户',
|
|
4273
|
+
phone: item.phone,
|
|
4274
|
+
identity: item?.loginCode || item.phone,
|
|
4275
|
+
}
|
|
4276
|
+
})
|
|
4277
|
+
promises.push(
|
|
4278
|
+
this.liveClient.inviteParticipant(this.meetingNum, miniInviteData, false, 3)
|
|
4279
|
+
.then(res => {
|
|
4280
|
+
if (res.code === 200) {
|
|
4281
|
+
console.log("邀请用户成功", miniInviteData);
|
|
4282
|
+
} else {
|
|
4283
|
+
this.showMessage.message("error", `邀请用户失败: ${res?.msg}`);
|
|
4284
|
+
}
|
|
4285
|
+
})
|
|
4286
|
+
.catch(err => {
|
|
4287
|
+
this.showMessage.message("error", `邀请用户失败: ${err.message}`);
|
|
4288
|
+
})
|
|
4289
|
+
)
|
|
4290
|
+
}
|
|
4291
|
+
Promise.allSettled(promises).then(() => {
|
|
4292
|
+
// 添加邀请成功后统一的后续处理
|
|
3347
4293
|
});
|
|
3348
4294
|
}
|
|
4295
|
+
// 追加邀请设备
|
|
4296
|
+
if (tempDeviceList && tempDeviceList.length > 0) {
|
|
4297
|
+
const deviceCalls = tempDeviceList
|
|
4298
|
+
.map(item => {
|
|
4299
|
+
const id = item?.source === '监控' ? item?.monitorID : item?.equipmentID;
|
|
4300
|
+
return id ? { dnis: id, name: item.label, type: this.handleInviteType(item?.integrationType) } : null;
|
|
4301
|
+
})
|
|
4302
|
+
.filter(Boolean);
|
|
4303
|
+
|
|
4304
|
+
if (deviceCalls.length > 0) {
|
|
4305
|
+
this.liveClient.makeBatchCall(deviceCalls)
|
|
4306
|
+
.then(() => {
|
|
4307
|
+
this.showMessage.message('success', '监控设备批量外呼已发起');
|
|
4308
|
+
})
|
|
4309
|
+
.catch(err => {
|
|
4310
|
+
this.showMessage.message('error', `批量拉取监控设备失败: ${err?.message || err}`);
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
4314
|
+
// 追加邀请终端
|
|
4315
|
+
if (tempTerminalList.length > 0) {
|
|
4316
|
+
const terminalCalls = tempTerminalList
|
|
4317
|
+
.map(item => {
|
|
4318
|
+
return item?.id ? { dnis: item.id, name: item.label, type: 4 } : null
|
|
4319
|
+
})
|
|
4320
|
+
.filter(Boolean)
|
|
4321
|
+
if (terminalCalls.length > 0) {
|
|
4322
|
+
this.liveClient
|
|
4323
|
+
.makeBatchCall(terminalCalls)
|
|
4324
|
+
.then(() => {
|
|
4325
|
+
this.showMessage.message('success', '会议终端批量外呼已发起')
|
|
4326
|
+
})
|
|
4327
|
+
.catch(err => {
|
|
4328
|
+
this.showMessage.message('error', `批量拉取会议终端失败: ${err?.message || err}`)
|
|
4329
|
+
})
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
3349
4332
|
}
|
|
3350
4333
|
},
|
|
3351
|
-
pullMonitorDevice(monitorID, monitorName) {
|
|
4334
|
+
pullMonitorDevice(monitorID, monitorName, integrationType = 0) {
|
|
3352
4335
|
this.liveClient.judgeUserInMeeting(this.meetingNum, monitorID).then((res) => {
|
|
3353
4336
|
if (res && res?.code == 200) {
|
|
3354
4337
|
if (res.data == 1) {
|
|
3355
4338
|
this.showMessage.message("error", "该监控设备已进入会议");
|
|
3356
4339
|
return;
|
|
3357
4340
|
} else {
|
|
3358
|
-
this.liveClient.makeCall(monitorName, monitorID,
|
|
4341
|
+
this.liveClient.makeCall(monitorName, monitorID, this.handleInviteType(integrationType));
|
|
3359
4342
|
}
|
|
3360
4343
|
} else {
|
|
3361
4344
|
this.showMessage.message("error", "获取监控设备进会状态失败");
|
|
@@ -3365,51 +4348,138 @@ export default {
|
|
|
3365
4348
|
|
|
3366
4349
|
async sendAppendInviteMessage(uninviteList = []) {
|
|
3367
4350
|
// 政协项目新增
|
|
3368
|
-
if (
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
4351
|
+
if (uninviteList.length > 0) {
|
|
4352
|
+
// 按 platformID 分组处理
|
|
4353
|
+
const platformGroup = {
|
|
4354
|
+
'1_2': [], // platformID 为 1 或 2
|
|
4355
|
+
'7': [], // platformID 为 7
|
|
4356
|
+
'4': [], // platformID 为 4
|
|
4357
|
+
'8': [] // platformID 为 8
|
|
4358
|
+
};
|
|
4359
|
+
|
|
4360
|
+
uninviteList.forEach(item => {
|
|
4361
|
+
const { userName, identity, phone, platformID } = item;
|
|
4362
|
+
|
|
4363
|
+
if (platformID === 30) {
|
|
4364
|
+
platformGroup['1_2'].push({ userName, identity, phone });
|
|
4365
|
+
} else if (platformID === 7) {
|
|
4366
|
+
platformGroup['7'].push({ userName, identity, phone });
|
|
4367
|
+
} else if (platformID === 4) {
|
|
4368
|
+
platformGroup['4'].push({ userName, identity, phone });
|
|
4369
|
+
} else if (platformID === 8) {
|
|
4370
|
+
platformGroup['8'].push({ userName, identity, phone });
|
|
4371
|
+
}
|
|
3374
4372
|
});
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
4373
|
+
|
|
4374
|
+
const promises = [];
|
|
4375
|
+
|
|
4376
|
+
// 处理 platformID 为 1 或 2 的邀请(invitationMethod = 0)
|
|
4377
|
+
if (platformGroup['1_2'].length > 0) {
|
|
4378
|
+
promises.push(
|
|
4379
|
+
this.liveClient.inviteParticipant(this.meetingNum, platformGroup['1_2'], false, 0)
|
|
4380
|
+
.then(res => {
|
|
4381
|
+
if (res.code === 200) {
|
|
4382
|
+
platformGroup['1_2'].forEach(item => {
|
|
4383
|
+
const inviteItem = uninviteList.find(u => u.identity === item.identity);
|
|
4384
|
+
if (inviteItem) this.addToInviteList(inviteItem);
|
|
4385
|
+
});
|
|
4386
|
+
} else {
|
|
4387
|
+
this.showMessage.message("error", `邀请用户失败 (platformID 1/2): ${res?.msg}`);
|
|
4388
|
+
}
|
|
3386
4389
|
})
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
4390
|
+
.catch(err => {
|
|
4391
|
+
this.showMessage.message("error", `邀请用户失败 (platformID 1/2): ${err.message}`);
|
|
4392
|
+
})
|
|
4393
|
+
);
|
|
4394
|
+
}
|
|
4395
|
+
|
|
4396
|
+
// 处理 platformID 为 7 的邀请(invitationMethod = 3)
|
|
4397
|
+
if (platformGroup['7'].length > 0) {
|
|
4398
|
+
promises.push(
|
|
4399
|
+
this.liveClient.inviteParticipant(this.meetingNum, platformGroup['7'], false, 3)
|
|
4400
|
+
.then(res => {
|
|
4401
|
+
if (res.code === 200) {
|
|
4402
|
+
platformGroup['7'].forEach(item => {
|
|
4403
|
+
const inviteItem = uninviteList.find(u => u.identity === item.identity);
|
|
4404
|
+
if (inviteItem) this.addToInviteList(inviteItem);
|
|
4405
|
+
});
|
|
4406
|
+
} else {
|
|
4407
|
+
this.showMessage.message("error", `邀请用户失败 (platformID 7): ${res?.msg}`);
|
|
4408
|
+
}
|
|
4409
|
+
})
|
|
4410
|
+
.catch(err => {
|
|
4411
|
+
this.showMessage.message("error", `邀请用户失败 (platformID 7): ${err.message}`);
|
|
4412
|
+
})
|
|
4413
|
+
);
|
|
4414
|
+
}
|
|
4415
|
+
|
|
4416
|
+
// 处理 platformID 为 4 的呼叫
|
|
4417
|
+
if (platformGroup['4'].length > 0) {
|
|
4418
|
+
// 构建批量外呼数据
|
|
4419
|
+
const volteCalls = platformGroup['4'].map(item => ({
|
|
4420
|
+
dnis: item.phone,
|
|
4421
|
+
name: item.userName,
|
|
4422
|
+
type: 0
|
|
4423
|
+
}));
|
|
4424
|
+
|
|
4425
|
+
promises.push(
|
|
4426
|
+
this.liveClient.makeBatchCall(volteCalls)
|
|
4427
|
+
.then(() => {
|
|
4428
|
+
platformGroup['4'].forEach(item => {
|
|
4429
|
+
const inviteItem = uninviteList.find(u => u.identity === item.identity);
|
|
4430
|
+
if (inviteItem) this.addToInviteList(inviteItem);
|
|
4431
|
+
});
|
|
4432
|
+
})
|
|
4433
|
+
.catch(err => {
|
|
4434
|
+
this.showMessage.message("error", `批量呼叫失败 (platformID 4): ${err.message}`);
|
|
4435
|
+
})
|
|
4436
|
+
);
|
|
3396
4437
|
}
|
|
4438
|
+
|
|
4439
|
+
// 处理 platformID 为 8 的呼叫
|
|
4440
|
+
if (platformGroup['8'].length > 0) {
|
|
4441
|
+
// 构建批量外呼数据
|
|
4442
|
+
const terminalCalls = platformGroup['8'].map(item => ({
|
|
4443
|
+
dnis: item.phone,
|
|
4444
|
+
name: item.userName,
|
|
4445
|
+
type: 4
|
|
4446
|
+
}))
|
|
4447
|
+
|
|
4448
|
+
promises.push(
|
|
4449
|
+
this.liveClient
|
|
4450
|
+
.makeBatchCall(terminalCalls)
|
|
4451
|
+
.then(() => {
|
|
4452
|
+
this.showMessage.message('success', '已发起终端批量外呼')
|
|
4453
|
+
})
|
|
4454
|
+
.catch(err => {
|
|
4455
|
+
this.showMessage.message('error', `批量呼叫失败 (platformID 8): ${err.message}`)
|
|
4456
|
+
})
|
|
4457
|
+
)
|
|
4458
|
+
}
|
|
4459
|
+
|
|
4460
|
+
// 等待所有邀请完成后更新列表
|
|
4461
|
+
Promise.allSettled(promises).then(() => {
|
|
4462
|
+
// 可以在这里添加统一的后续处理,如刷新邀请列表
|
|
4463
|
+
});
|
|
3397
4464
|
}
|
|
3398
4465
|
},
|
|
3399
4466
|
phoneCall(num) {
|
|
3400
4467
|
this.liveClient.makeCall(num, num);
|
|
3401
4468
|
},
|
|
3402
4469
|
async miniCall(num) {
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
4470
|
+
this.liveClient.inviteParticipant(this.meetingNum, [{
|
|
4471
|
+
userName: num,
|
|
4472
|
+
phone: num,
|
|
4473
|
+
identity: num,
|
|
4474
|
+
}], false, 3).then(res => {
|
|
4475
|
+
if (res.code === 200) {
|
|
4476
|
+
this.showMessage.message("success", "成功邀请小程序用户");
|
|
4477
|
+
} else {
|
|
4478
|
+
this.showMessage.message("error", `邀请小程序用户失败: ${res?.msg}`);
|
|
4479
|
+
}
|
|
4480
|
+
}).catch(err => {
|
|
4481
|
+
this.showMessage.message("error", `邀请小程序用户失败: ${err.message}`);
|
|
4482
|
+
});
|
|
3413
4483
|
},
|
|
3414
4484
|
sleep(waitTimeInMs) {
|
|
3415
4485
|
return new Promise((resolve) => setTimeout(resolve, waitTimeInMs));
|
|
@@ -3910,6 +4980,64 @@ export default {
|
|
|
3910
4980
|
}
|
|
3911
4981
|
}
|
|
3912
4982
|
|
|
4983
|
+
if (this.currentLayout === 'ring') {
|
|
4984
|
+
// 从环形布局到点调模式:先拆解环形容器,将参与者恢复到主容器,然后按照点调规则分配
|
|
4985
|
+
const layoutRing = document.querySelector('#room .layout-ring');
|
|
4986
|
+
if (layoutRing) {
|
|
4987
|
+
const ringTop = layoutRing.querySelector('.layout-ring-top');
|
|
4988
|
+
const ringBottom = layoutRing.querySelector('.layout-ring-bottom');
|
|
4989
|
+
const ringCenter = layoutRing.querySelector('.ring-center');
|
|
4990
|
+
// 先将center、slots、bottom中的参与者移动回主容器
|
|
4991
|
+
if (ringTop) {
|
|
4992
|
+
const slots = ringTop.querySelectorAll('.ring-slot');
|
|
4993
|
+
slots.forEach(slot => {
|
|
4994
|
+
while (slot.firstChild) {
|
|
4995
|
+
container.appendChild(slot.firstChild);
|
|
4996
|
+
}
|
|
4997
|
+
});
|
|
4998
|
+
if (ringCenter) {
|
|
4999
|
+
while (ringCenter.firstChild) {
|
|
5000
|
+
container.appendChild(ringCenter.firstChild);
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
if (ringBottom) {
|
|
5005
|
+
while (ringBottom.firstChild) {
|
|
5006
|
+
container.appendChild(ringBottom.firstChild);
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
layoutRing.remove();
|
|
5010
|
+
}
|
|
5011
|
+
|
|
5012
|
+
// 收集参与者元素
|
|
5013
|
+
const participantElements = Array.from(container.children).filter(child =>
|
|
5014
|
+
child.classList && child.classList.contains('participant') && child.id && child.id.startsWith('participant-')
|
|
5015
|
+
);
|
|
5016
|
+
|
|
5017
|
+
// 分配到点调容器:主持人优先到center[0],焦点到center[1](如果存在且不同),其余进入top/bottom/other
|
|
5018
|
+
// 注意:pointTurnCenter已有两个占位符 placeHolderElm1, placeHolderElm2
|
|
5019
|
+
participantElements.forEach(child => {
|
|
5020
|
+
if (this.curHostIdentity && child.id === `participant-${this.curHostIdentity}`) {
|
|
5021
|
+
// 主持人放在中心第一个位置
|
|
5022
|
+
pointTurnCenter.removeChild(placeHolderElm1);
|
|
5023
|
+
pointTurnCenter.insertBefore(child, pointTurnCenter.firstChild ?? null);
|
|
5024
|
+
} else if (this.curBlurIdentity && child.id === `participant-${this.curBlurIdentity}` && this.curBlurIdentity !== this.curHostIdentity) {
|
|
5025
|
+
// 焦点用户放在中心第二个位置(若不同于主持人)
|
|
5026
|
+
pointTurnCenter.removeChild(placeHolderElm2);
|
|
5027
|
+
pointTurnCenter.appendChild(child);
|
|
5028
|
+
} else {
|
|
5029
|
+
if (pointTurnTop.children.length >= 5) {
|
|
5030
|
+
if (pointTurnBottom.children.length >= 5) {
|
|
5031
|
+
pointTurnOther.appendChild(child);
|
|
5032
|
+
} else {
|
|
5033
|
+
pointTurnBottom.appendChild(child);
|
|
5034
|
+
}
|
|
5035
|
+
} else {
|
|
5036
|
+
pointTurnTop.appendChild(child);
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5039
|
+
});
|
|
5040
|
+
}
|
|
3913
5041
|
// 添加点调容器之前的最终检查
|
|
3914
5042
|
const remainingLayoutElements = container.querySelectorAll(
|
|
3915
5043
|
".layout-leftside, .layout-rightside, .point-turn-top, .point-turn-center, .point-turn-bottom, .point-turn-other"
|
|
@@ -4078,6 +5206,92 @@ export default {
|
|
|
4078
5206
|
container.insertBefore(layoutLeftSideEle, container.firstChild ?? null);
|
|
4079
5207
|
container.appendChild(layoutRightSideEle);
|
|
4080
5208
|
}
|
|
5209
|
+
|
|
5210
|
+
if (this.currentLayout === 'ring') {
|
|
5211
|
+
// 从点调模式到环形布局:创建环形容器,将与会者分配到center/slots/bottom
|
|
5212
|
+
const existingRing = document.querySelector('#room .layout-ring');
|
|
5213
|
+
if (existingRing) {
|
|
5214
|
+
existingRing.remove();
|
|
5215
|
+
}
|
|
5216
|
+
const layoutRing = document.createElement('div');
|
|
5217
|
+
layoutRing.className = 'layout-ring';
|
|
5218
|
+
const layoutRingTop = document.createElement('div');
|
|
5219
|
+
layoutRingTop.className = 'layout-ring-top';
|
|
5220
|
+
const layoutRingBottom = document.createElement('div');
|
|
5221
|
+
layoutRingBottom.className = 'layout-ring-bottom';
|
|
5222
|
+
// 创建12个槽位
|
|
5223
|
+
for (let i = 0; i < 12; i++) {
|
|
5224
|
+
const slot = document.createElement('div');
|
|
5225
|
+
slot.className = 'ring-slot';
|
|
5226
|
+
layoutRingTop.appendChild(slot);
|
|
5227
|
+
}
|
|
5228
|
+
const ringCenter = document.createElement('div');
|
|
5229
|
+
ringCenter.className = 'ring-center';
|
|
5230
|
+
layoutRingTop.appendChild(ringCenter);
|
|
5231
|
+
|
|
5232
|
+
// 将点调容器中的参与者按顺序分配:center(主持人 -> 焦点)、再从top、bottom、other依序填充slots;超出放到bottom
|
|
5233
|
+
const collect = (ele) => (ele ? Array.from(ele.children).filter(c => c.id && c.classList.contains('participant')) : []);
|
|
5234
|
+
const topList = collect(pointTurnTop);
|
|
5235
|
+
const centerList = collect(pointTurnCenter);
|
|
5236
|
+
const bottomList = collect(pointTurnBottom);
|
|
5237
|
+
const otherList = collect(pointTurnOther);
|
|
5238
|
+
|
|
5239
|
+
// 先处理中心:主持人在center首位,若有焦点且不同于主持人,作为第二位
|
|
5240
|
+
let hostEl = centerList.find(c => this.curHostIdentity && c.id === `participant-${this.curHostIdentity}`);
|
|
5241
|
+
if (!hostEl) {
|
|
5242
|
+
// 尝试从其它列表找到主持人
|
|
5243
|
+
hostEl = [...topList, ...bottomList, ...otherList].find(c => this.curHostIdentity && c.id === `participant-${this.curHostIdentity}`);
|
|
5244
|
+
if (hostEl && hostEl.parentElement) hostEl.parentElement.removeChild(hostEl);
|
|
5245
|
+
}
|
|
5246
|
+
if (hostEl) {
|
|
5247
|
+
ringCenter.appendChild(hostEl);
|
|
5248
|
+
}
|
|
5249
|
+
let blurEl = null;
|
|
5250
|
+
if (this.curBlurIdentity && this.curBlurIdentity !== this.curHostIdentity) {
|
|
5251
|
+
blurEl = centerList.find(c => c.id === `participant-${this.curBlurIdentity}`)
|
|
5252
|
+
|| topList.find(c => c.id === `participant-${this.curBlurIdentity}`)
|
|
5253
|
+
|| bottomList.find(c => c.id === `participant-${this.curBlurIdentity}`)
|
|
5254
|
+
|| otherList.find(c => c.id === `participant-${this.curBlurIdentity}`);
|
|
5255
|
+
if (blurEl && blurEl.parentElement) {
|
|
5256
|
+
blurEl.parentElement.removeChild(blurEl);
|
|
5257
|
+
// 注意:ring 只在中心留主持人,焦点不会占用中心,因此将其回到 slots 序列,不放中心
|
|
5258
|
+
}
|
|
5259
|
+
}
|
|
5260
|
+
|
|
5261
|
+
// 构建顺序序列:top -> bottom -> other(去除已被拿走的host/blur)
|
|
5262
|
+
const rest = [...topList, ...bottomList, ...otherList].filter(el => el !== hostEl && el !== blurEl);
|
|
5263
|
+
const slots = layoutRingTop.querySelectorAll('.ring-slot');
|
|
5264
|
+
let filled = 0;
|
|
5265
|
+
// 先将可能存在的焦点用户放在第一个可用槽(如果存在)
|
|
5266
|
+
if (blurEl) {
|
|
5267
|
+
if (filled < slots.length) {
|
|
5268
|
+
slots[filled++].appendChild(blurEl);
|
|
5269
|
+
} else {
|
|
5270
|
+
layoutRingBottom.appendChild(blurEl);
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
5273
|
+
// 填充剩余slots
|
|
5274
|
+
for (const el of rest) {
|
|
5275
|
+
if (filled < slots.length) {
|
|
5276
|
+
slots[filled++].appendChild(el);
|
|
5277
|
+
} else {
|
|
5278
|
+
layoutRingBottom.appendChild(el);
|
|
5279
|
+
}
|
|
5280
|
+
}
|
|
5281
|
+
|
|
5282
|
+
// 清理点调容器并挂载环容器
|
|
5283
|
+
const removeIfChild = (ele) => { if (ele && container.contains(ele)) container.removeChild(ele); };
|
|
5284
|
+
removeIfChild(pointTurnTop);
|
|
5285
|
+
removeIfChild(pointTurnCenter);
|
|
5286
|
+
removeIfChild(pointTurnBottom);
|
|
5287
|
+
removeIfChild(pointTurnOther);
|
|
5288
|
+
|
|
5289
|
+
layoutRing.appendChild(layoutRingTop);
|
|
5290
|
+
layoutRing.appendChild(layoutRingBottom);
|
|
5291
|
+
container.insertBefore(layoutRing, container.firstChild ?? null);
|
|
5292
|
+
// 点调 -> 环形:创建后同步高度
|
|
5293
|
+
requestAnimationFrame(() => this.updateRingTopHeight());
|
|
5294
|
+
}
|
|
4081
5295
|
}
|
|
4082
5296
|
});
|
|
4083
5297
|
},
|
|
@@ -4113,10 +5327,11 @@ export default {
|
|
|
4113
5327
|
this.liveClient.off(eventName);
|
|
4114
5328
|
});
|
|
4115
5329
|
}
|
|
5330
|
+
this.liveClient.off("resMeetingRefresh", this.handleResMeetingRefresh);
|
|
4116
5331
|
}
|
|
4117
|
-
}
|
|
5332
|
+
}
|
|
4118
5333
|
},
|
|
4119
|
-
}
|
|
5334
|
+
}
|
|
4120
5335
|
</script>
|
|
4121
5336
|
|
|
4122
5337
|
<style lang="scss" scoped>
|
|
@@ -4300,7 +5515,7 @@ export default {
|
|
|
4300
5515
|
align-items: center;
|
|
4301
5516
|
justify-content: space-between;
|
|
4302
5517
|
position: absolute;
|
|
4303
|
-
z-index:
|
|
5518
|
+
z-index: 2100;
|
|
4304
5519
|
bottom: -66px;
|
|
4305
5520
|
left: 0;
|
|
4306
5521
|
transition: all 0.2s ease;
|