mdm-client 1.0.3 → 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 -67
- 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/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 +27 -6
- package/src/components/LiveMultipleMeeting/LiveMultipleMeeting.vue +1063 -98
- 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 +137 -20
- package/src/components/other/appointDialog.vue +1 -1
- package/src/components/other/customLayout.vue +368 -202
- 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/moreOptionDialog.vue +17 -1
- package/src/components/other/selectDialog.vue +1 -1
- package/src/components/other/selectSpecialDialog.vue +1 -1
- package/src/utils/api.js +19 -0
- package/src/utils/livekit/live-client-esm.js +1 -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: () => ({}),
|
|
@@ -453,6 +472,21 @@ export default {
|
|
|
453
472
|
type: String,
|
|
454
473
|
default: "",
|
|
455
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
|
+
},
|
|
456
490
|
},
|
|
457
491
|
data() {
|
|
458
492
|
return {
|
|
@@ -496,9 +530,9 @@ export default {
|
|
|
496
530
|
// 布局数据
|
|
497
531
|
currentLayout: "grid",
|
|
498
532
|
currentRoomMode: "normal",
|
|
499
|
-
layoutList: ["grid", "rightSide"],
|
|
500
|
-
layoutLabels: ["宫格", "右侧边栏"],
|
|
501
|
-
|
|
533
|
+
layoutList: ["grid", "rightSide", "ring"],
|
|
534
|
+
layoutLabels: ["宫格", "右侧边栏", "环形"],
|
|
535
|
+
currentGridSize: 9,
|
|
502
536
|
// 状态数据
|
|
503
537
|
duration: 0,
|
|
504
538
|
memberManageShow: false,
|
|
@@ -582,6 +616,15 @@ export default {
|
|
|
582
616
|
durationInterval: null,
|
|
583
617
|
footerInterval: null,
|
|
584
618
|
unjoinParticipantInterval: null,
|
|
619
|
+
// 保存与会者画面旋转状态
|
|
620
|
+
rotateDegreeMap: new Map(),
|
|
621
|
+
// 当前聚焦画面
|
|
622
|
+
leaderShipFocus: ""
|
|
623
|
+
};
|
|
624
|
+
},
|
|
625
|
+
provide() {
|
|
626
|
+
return {
|
|
627
|
+
rotateDegreeMap: this.rotateDegreeMap,
|
|
585
628
|
};
|
|
586
629
|
},
|
|
587
630
|
computed: {
|
|
@@ -594,12 +637,33 @@ export default {
|
|
|
594
637
|
participantIdentities() {
|
|
595
638
|
return this.participantNum > 0 ? this.participants.map((item) => item.identity) : [];
|
|
596
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
|
+
},
|
|
597
658
|
inviteNum() {
|
|
598
659
|
return this.inviteList.length;
|
|
599
660
|
},
|
|
600
661
|
deviceNum() {
|
|
601
662
|
return this.deviceList.length;
|
|
602
663
|
},
|
|
664
|
+
terminalNum() {
|
|
665
|
+
return this.meetingTerminals.length;
|
|
666
|
+
},
|
|
603
667
|
invitedNum() {
|
|
604
668
|
return this.tempInvitedList?.length || 0;
|
|
605
669
|
},
|
|
@@ -621,22 +685,55 @@ export default {
|
|
|
621
685
|
return "point-turn";
|
|
622
686
|
} else {
|
|
623
687
|
if (this.currentLayout === "grid") {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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
|
+
}
|
|
640
737
|
}
|
|
641
738
|
} else {
|
|
642
739
|
return this.currentLayout;
|
|
@@ -714,14 +811,148 @@ export default {
|
|
|
714
811
|
}
|
|
715
812
|
this.setPageFooterVisible(5);
|
|
716
813
|
this.initGlobleEvent();
|
|
814
|
+
// 观察#room尺寸变化,动态同步环形布局顶部高度
|
|
815
|
+
this.observeRoomResizeForRing();
|
|
816
|
+
// 首次挂载后尝试同步一次高度
|
|
817
|
+
requestAnimationFrame(() => this.updateRingTopHeight());
|
|
717
818
|
},
|
|
718
819
|
beforeDestroy() {
|
|
719
820
|
this.stopUnjoinParticipantPolling();
|
|
720
821
|
this.dispatchLiveClientEvent();
|
|
721
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
|
+
}
|
|
722
830
|
this.liveClient = null;
|
|
723
831
|
},
|
|
724
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
|
+
},
|
|
725
956
|
// DOM操作管理函数
|
|
726
957
|
async batchUpdateStates(updateFn) {
|
|
727
958
|
this.isBatchUpdating = true;
|
|
@@ -868,12 +1099,12 @@ export default {
|
|
|
868
1099
|
// 计算MoreOptionDialog选项数量的辅助函数
|
|
869
1100
|
getOptionCount(identity) {
|
|
870
1101
|
const participantItem = this.getUserItemByIdentity(identity);
|
|
871
|
-
if (!participantItem || !participantItem.metadata) return
|
|
1102
|
+
if (!participantItem || !participantItem.metadata) return 9; // 默认最大选项数
|
|
872
1103
|
|
|
873
1104
|
const metadata = participantItem.metadata;
|
|
874
1105
|
const isLocalParticipant = identity === this.localIdentity;
|
|
875
1106
|
|
|
876
|
-
let optionCount =
|
|
1107
|
+
let optionCount = 4; // 基础选项:设为主屏、麦克风、摄像头、聚焦画面
|
|
877
1108
|
|
|
878
1109
|
if (this.judgeParticipantIsHost(this.localIdentity)) {
|
|
879
1110
|
// 本地为主持人
|
|
@@ -898,7 +1129,7 @@ export default {
|
|
|
898
1129
|
}
|
|
899
1130
|
}
|
|
900
1131
|
|
|
901
|
-
return Math.min(optionCount,
|
|
1132
|
+
return Math.min(optionCount, 9); // 限制最大选项数
|
|
902
1133
|
},
|
|
903
1134
|
// 弹窗定位工具函数
|
|
904
1135
|
calculateDialogPosition(event, options = {}) {
|
|
@@ -1366,12 +1597,12 @@ export default {
|
|
|
1366
1597
|
const deviceCalls = this.deviceList
|
|
1367
1598
|
.map(item => {
|
|
1368
1599
|
const id = item?.source === '监控' ? item?.monitorID : item?.equipmentID;
|
|
1369
|
-
return id ? { dnis: id, name: item.label } : null;
|
|
1600
|
+
return id ? { dnis: id, name: item.label, type: this.handleInviteType(item?.integrationType) } : null;
|
|
1370
1601
|
})
|
|
1371
1602
|
.filter(Boolean);
|
|
1372
1603
|
|
|
1373
1604
|
if (deviceCalls.length > 0) {
|
|
1374
|
-
this.liveClient.makeBatchCall(deviceCalls
|
|
1605
|
+
this.liveClient.makeBatchCall(deviceCalls)
|
|
1375
1606
|
.then(() => {
|
|
1376
1607
|
this.showMessage.message('success', '监控设备批量外呼已发起');
|
|
1377
1608
|
})
|
|
@@ -1380,6 +1611,24 @@ export default {
|
|
|
1380
1611
|
});
|
|
1381
1612
|
}
|
|
1382
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
|
+
}
|
|
1383
1632
|
}
|
|
1384
1633
|
// 启动获取未入会和邀请人员轮询
|
|
1385
1634
|
this.startUnjoinParticipantPolling();
|
|
@@ -1475,18 +1724,44 @@ export default {
|
|
|
1475
1724
|
}
|
|
1476
1725
|
});
|
|
1477
1726
|
if (maxAudioLevelIndex >= 0) {
|
|
1478
|
-
this.
|
|
1727
|
+
this.highlightCurrentSpeaker(e[maxAudioLevelIndex])
|
|
1728
|
+
if (this.isVoiceMotivationOpen) {
|
|
1729
|
+
this.switchActiveSpeakerToFirst(e[maxAudioLevelIndex])
|
|
1730
|
+
}
|
|
1731
|
+
} else {
|
|
1732
|
+
this.highlightCurrentSpeaker(-1)
|
|
1479
1733
|
}
|
|
1734
|
+
} else {
|
|
1735
|
+
this.highlightCurrentSpeaker(-1)
|
|
1480
1736
|
}
|
|
1481
1737
|
});
|
|
1482
1738
|
this.liveClient.on("resMeetingRefresh", this.handleResMeetingRefresh);
|
|
1483
1739
|
},
|
|
1484
|
-
|
|
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) {
|
|
1485
1760
|
console.log('resMeetingRefresh事件触发', e);
|
|
1486
|
-
|
|
1487
|
-
if(e.includes("queryAllInvite") || e.includes("queryUnjoined")) {
|
|
1761
|
+
|
|
1762
|
+
if (e.includes("queryAllInvite") || e.includes("queryUnjoined")) {
|
|
1488
1763
|
console.log('this', this, this.getUnjoinParticipant);
|
|
1489
|
-
|
|
1764
|
+
|
|
1490
1765
|
this.getUnjoinParticipant();
|
|
1491
1766
|
this.queryAllInviteParticipant();
|
|
1492
1767
|
this.startUnjoinParticipantPolling();
|
|
@@ -1632,7 +1907,7 @@ export default {
|
|
|
1632
1907
|
} else {
|
|
1633
1908
|
this.meetingCoHost = [];
|
|
1634
1909
|
}
|
|
1635
|
-
|
|
1910
|
+
this.leaderShipFocus = metadata.leaderShipFocus;
|
|
1636
1911
|
this.roomMetadata = metadata;
|
|
1637
1912
|
|
|
1638
1913
|
// 批量设置关键状态
|
|
@@ -1708,6 +1983,7 @@ export default {
|
|
|
1708
1983
|
this.meetingCoHost = [];
|
|
1709
1984
|
}
|
|
1710
1985
|
|
|
1986
|
+
this.leaderShipFocus = metadata.leaderShipFocus;
|
|
1711
1987
|
this.roomMetadata = metadata;
|
|
1712
1988
|
|
|
1713
1989
|
// 批量设置关键状态
|
|
@@ -1955,7 +2231,8 @@ export default {
|
|
|
1955
2231
|
// 构建批量外呼数据
|
|
1956
2232
|
const volteCalls = volteList.map(item => ({
|
|
1957
2233
|
dnis: item.phone,
|
|
1958
|
-
name: item.label
|
|
2234
|
+
name: item.label,
|
|
2235
|
+
type: 0
|
|
1959
2236
|
}));
|
|
1960
2237
|
|
|
1961
2238
|
promises.push(
|
|
@@ -1997,9 +2274,9 @@ export default {
|
|
|
1997
2274
|
roomNum: this.meetingNum,
|
|
1998
2275
|
cameraStatus: this.isCameraEnabled,
|
|
1999
2276
|
microphoneStatus: this.isMicrophoneEnabled,
|
|
2000
|
-
audioDeviceId: this.
|
|
2001
|
-
videoDeviceId: this.
|
|
2002
|
-
outputDeviceId: this.
|
|
2277
|
+
audioDeviceId: this.tempActiveDevice.audioInputDevice,
|
|
2278
|
+
videoDeviceId: this.tempActiveDevice.videoDevice,
|
|
2279
|
+
outputDeviceId: this.tempActiveDevice.audioOutputDevice,
|
|
2003
2280
|
};
|
|
2004
2281
|
let tempActiveDevice = null;
|
|
2005
2282
|
try {
|
|
@@ -2133,36 +2410,83 @@ export default {
|
|
|
2133
2410
|
</div>
|
|
2134
2411
|
`;
|
|
2135
2412
|
} else if (videoItem.isCameraEnabled) {
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
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
|
+
}
|
|
2149
2448
|
} else {
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
<div class="
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
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
|
+
}
|
|
2166
2490
|
}
|
|
2167
2491
|
return videoDom;
|
|
2168
2492
|
},
|
|
@@ -2311,6 +2635,57 @@ export default {
|
|
|
2311
2635
|
layoutRightSideEle.appendChild(videoDiv);
|
|
2312
2636
|
}
|
|
2313
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
|
+
}
|
|
2314
2689
|
}
|
|
2315
2690
|
}
|
|
2316
2691
|
return container;
|
|
@@ -2338,6 +2713,25 @@ export default {
|
|
|
2338
2713
|
videoDiv = this.constructParticipantDom(videoItem);
|
|
2339
2714
|
// 构建会议室布局
|
|
2340
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
|
+
}
|
|
2341
2735
|
// 添加到participant数组
|
|
2342
2736
|
this.addToParticipantList(videoItem);
|
|
2343
2737
|
}
|
|
@@ -2352,6 +2746,8 @@ export default {
|
|
|
2352
2746
|
let signalElm = document.getElementById(`signal-${videoItem.identity}`);
|
|
2353
2747
|
// 与会者更多操作按钮元素
|
|
2354
2748
|
let moreElm = document.getElementById(`more-${videoItem.identity}`);
|
|
2749
|
+
// 与会者画面旋转按钮元素
|
|
2750
|
+
let rotateElm = document.getElementById(`rotate-${videoItem.identity}`)
|
|
2355
2751
|
// 声明麦克风按钮点击事件回调
|
|
2356
2752
|
const unableMicrophone = () => {
|
|
2357
2753
|
this.liveClient.changeParticipantMicrophoneStatus(videoItem.identity, true);
|
|
@@ -2366,6 +2762,8 @@ export default {
|
|
|
2366
2762
|
const currentMoreElm = document.getElementById(`more-${videoItem.identity}`);
|
|
2367
2763
|
currentSignalElm && (currentSignalElm.style.visibility = "visible");
|
|
2368
2764
|
currentMoreElm && (currentMoreElm.style.visibility = "visible");
|
|
2765
|
+
const currentRotateElm = document.getElementById(`rotate-${videoItem.identity}`);
|
|
2766
|
+
currentRotateElm && (currentRotateElm.style.visibility = "visible");
|
|
2369
2767
|
};
|
|
2370
2768
|
const signalAndMoreHide = () => {
|
|
2371
2769
|
// 每次执行时重新获取最新的元素
|
|
@@ -2373,6 +2771,8 @@ export default {
|
|
|
2373
2771
|
const currentMoreElm = document.getElementById(`more-${videoItem.identity}`);
|
|
2374
2772
|
currentSignalElm && (currentSignalElm.style.visibility = "hidden");
|
|
2375
2773
|
currentMoreElm && (currentMoreElm.style.visibility = "hidden");
|
|
2774
|
+
const currentRotateElm = document.getElementById(`rotate-${videoItem.identity}`);
|
|
2775
|
+
currentRotateElm && (currentRotateElm.style.visibility = "hidden");
|
|
2376
2776
|
};
|
|
2377
2777
|
// 声明操作按钮元素点击事件回调
|
|
2378
2778
|
const moreElmClick = (event) => {
|
|
@@ -2395,6 +2795,13 @@ export default {
|
|
|
2395
2795
|
this.moreDialogShow = true;
|
|
2396
2796
|
}
|
|
2397
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
|
+
};
|
|
2398
2805
|
// 为麦克风元素绑定事件
|
|
2399
2806
|
if (microElm && videoItem.isMicrophoneEnabled) {
|
|
2400
2807
|
microElm.className = "microphone microphone-active";
|
|
@@ -2422,12 +2829,47 @@ export default {
|
|
|
2422
2829
|
if (moreElm) {
|
|
2423
2830
|
moreElm.onclick = moreElmClick;
|
|
2424
2831
|
}
|
|
2832
|
+
if (rotateElm) {
|
|
2833
|
+
rotateElm.onclick = rotateElmClick;
|
|
2834
|
+
}
|
|
2425
2835
|
if (signalElm) {
|
|
2426
2836
|
if (videoItem.connectionQuality === "excellent" || videoItem.connectionQuality === "good") {
|
|
2427
2837
|
signalElm.className = "signal-icon signal-icon-good";
|
|
2428
2838
|
} else {
|
|
2429
2839
|
signalElm.className = "signal-icon signal-icon-poor";
|
|
2430
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
|
+
}
|
|
2431
2873
|
}
|
|
2432
2874
|
let screenShareElm = document.getElementById(`screenshare-${videoItem.identity}`);
|
|
2433
2875
|
// 与会者dom元素内部样式重新渲染(摄像头或屏幕共享切换后)
|
|
@@ -2477,6 +2919,15 @@ export default {
|
|
|
2477
2919
|
signalElm.className = "signal-icon signal-icon-poor";
|
|
2478
2920
|
}
|
|
2479
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
|
+
}
|
|
2480
2931
|
}
|
|
2481
2932
|
// if (!moreElm) {
|
|
2482
2933
|
// // 构建更多操作按钮元素
|
|
@@ -2499,6 +2950,28 @@ export default {
|
|
|
2499
2950
|
moreElm = null;
|
|
2500
2951
|
}
|
|
2501
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
|
+
}
|
|
2502
2975
|
} else {
|
|
2503
2976
|
if (!boardElm) {
|
|
2504
2977
|
// 之前为屏幕共享状态或摄像头开启状态
|
|
@@ -2532,6 +3005,11 @@ export default {
|
|
|
2532
3005
|
this.removeFromParticipantList(videoItem.identity);
|
|
2533
3006
|
this.filterParticipantList();
|
|
2534
3007
|
|
|
3008
|
+
// 清理可能存在的旋转状态
|
|
3009
|
+
if (this.rotateDegreeMap.has(videoItem.identity)) {
|
|
3010
|
+
this.rotateDegreeMap.delete(videoItem.identity);
|
|
3011
|
+
}
|
|
3012
|
+
|
|
2535
3013
|
// 处理焦点用户离开的情况
|
|
2536
3014
|
if (this.curBlurIdentity === videoItem.identity) {
|
|
2537
3015
|
console.log("焦点用户离开会议:", videoItem.identity);
|
|
@@ -2559,18 +3037,6 @@ export default {
|
|
|
2559
3037
|
this.curHostIdentity = null;
|
|
2560
3038
|
// 主持人离开后具体的逻辑处理放到watch中处理
|
|
2561
3039
|
}
|
|
2562
|
-
// pointTurn模式下,主持人离开处理
|
|
2563
|
-
// if (currentRoomMode.value === "pointTurn" && videoItem.identity === curHostIdentity.value) {
|
|
2564
|
-
// 当前离开与会者为会议主持人
|
|
2565
|
-
// const pointTurnCenter = document.querySelector("#room .point-turn-center");
|
|
2566
|
-
// if (pointTurnCenter) {
|
|
2567
|
-
// // 会议中没有其他主持人,显示默认占位元素
|
|
2568
|
-
// const placeHolderElm = document.createElement("div");
|
|
2569
|
-
// placeHolderElm.className = "participant";
|
|
2570
|
-
// placeHolderElm.innerHTML = placeholderTemplate;
|
|
2571
|
-
// pointTurnCenter.insertBefore(placeHolderElm, pointTurnCenter.firstChild ?? null);
|
|
2572
|
-
// }
|
|
2573
|
-
// }
|
|
2574
3040
|
// pointTurn模式下的其他用户离开处理
|
|
2575
3041
|
if (
|
|
2576
3042
|
this.currentRoomMode === "pointTurn" &&
|
|
@@ -2679,6 +3145,10 @@ export default {
|
|
|
2679
3145
|
// 更新与会者数组
|
|
2680
3146
|
this.addToParticipantList(videoItem);
|
|
2681
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
|
+
}
|
|
2682
3152
|
// 与会者结束共享,切换回到之前布局(仅主持人执行)
|
|
2683
3153
|
if (this.currentRoomMode === "normal" && this.judgeParticipantIsHost(this.localIdentity)) {
|
|
2684
3154
|
console.log(
|
|
@@ -2891,7 +3361,73 @@ export default {
|
|
|
2891
3361
|
|
|
2892
3362
|
if (oldLayout === "grid" && newLayout === "ring") {
|
|
2893
3363
|
// 从宫格布局切换到环状布局
|
|
2894
|
-
//
|
|
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());
|
|
2895
3431
|
}
|
|
2896
3432
|
|
|
2897
3433
|
if (oldLayout === "grid" && newLayout === "downLSide") {
|
|
@@ -2939,6 +3475,178 @@ export default {
|
|
|
2939
3475
|
// 添加其他元素
|
|
2940
3476
|
container.appendChild(fragment);
|
|
2941
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
|
+
}
|
|
2942
3650
|
}
|
|
2943
3651
|
});
|
|
2944
3652
|
},
|
|
@@ -3038,7 +3746,6 @@ export default {
|
|
|
3038
3746
|
this.liveClient.deleteUnjoinParticipant(this.meetingNum, item.identity).then((res) => {
|
|
3039
3747
|
if (res.code == 200) {
|
|
3040
3748
|
this.showMessage.message("success", "成功删除未入会人员");
|
|
3041
|
-
// this.getUnjoinParticipant();
|
|
3042
3749
|
this.removeFromInviteList(item.identity);
|
|
3043
3750
|
} else {
|
|
3044
3751
|
this.showMessage.message("error", res?.msg);
|
|
@@ -3278,12 +3985,12 @@ export default {
|
|
|
3278
3985
|
},
|
|
3279
3986
|
async openCamera(e) {
|
|
3280
3987
|
if (this.liveClient) {
|
|
3281
|
-
await this.liveClient.changeParticipantCameraStatus(e,
|
|
3988
|
+
await this.liveClient.changeParticipantCameraStatus(e, false);
|
|
3282
3989
|
}
|
|
3283
3990
|
},
|
|
3284
3991
|
async closeCamera(e) {
|
|
3285
3992
|
if (this.liveClient) {
|
|
3286
|
-
await this.liveClient.changeParticipantCameraStatus(e,
|
|
3993
|
+
await this.liveClient.changeParticipantCameraStatus(e, true);
|
|
3287
3994
|
}
|
|
3288
3995
|
},
|
|
3289
3996
|
async setToHost(e) {
|
|
@@ -3335,9 +4042,43 @@ export default {
|
|
|
3335
4042
|
async appendInviteDevice(e) {
|
|
3336
4043
|
console.log("通讯录邀请设备", e);
|
|
3337
4044
|
if (e && e.length > 0) {
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
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
|
+
}
|
|
3341
4082
|
}
|
|
3342
4083
|
},
|
|
3343
4084
|
async updateNameConfirm(e) {
|
|
@@ -3369,7 +4110,30 @@ export default {
|
|
|
3369
4110
|
type,
|
|
3370
4111
|
});
|
|
3371
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
|
+
},
|
|
3372
4134
|
async chooseVideoDevice(e) {
|
|
4135
|
+
this.dispatchLocalTrack('video')
|
|
4136
|
+
await this.sleep(100)
|
|
3373
4137
|
await this.changeActiveDevice("videoinput", e);
|
|
3374
4138
|
this.videoSelectShow = false;
|
|
3375
4139
|
},
|
|
@@ -3431,6 +4195,7 @@ export default {
|
|
|
3431
4195
|
appendInvite(e, inviteWay) {
|
|
3432
4196
|
let tempList = [];
|
|
3433
4197
|
let tempDeviceList = []
|
|
4198
|
+
let tempTerminalList = []
|
|
3434
4199
|
let index = -1;
|
|
3435
4200
|
if (e && e.length > 0) {
|
|
3436
4201
|
e.forEach((item) => {
|
|
@@ -3451,12 +4216,15 @@ export default {
|
|
|
3451
4216
|
tempList.push(item);
|
|
3452
4217
|
}
|
|
3453
4218
|
this.addToInviteList(item);
|
|
3454
|
-
} else {
|
|
4219
|
+
} else if (item.source == '设备' || item.source == '监控') {
|
|
3455
4220
|
tempDeviceList.push(item)
|
|
4221
|
+
} else if (item.source == '会议终端') {
|
|
4222
|
+
tempTerminalList.push(item)
|
|
3456
4223
|
}
|
|
3457
4224
|
});
|
|
3458
4225
|
console.log("本次追加邀请人员", tempList);
|
|
3459
4226
|
console.log("本次追加邀请设备", tempDeviceList);
|
|
4227
|
+
console.log("本次追加邀请会议终端", tempTerminalList);
|
|
3460
4228
|
// 追加邀请人员
|
|
3461
4229
|
if(tempList.length > 0) {
|
|
3462
4230
|
const promises = [];
|
|
@@ -3485,7 +4253,8 @@ export default {
|
|
|
3485
4253
|
const volteCalls = tempList.map(item => {
|
|
3486
4254
|
return {
|
|
3487
4255
|
dnis: item.phone,
|
|
3488
|
-
name: item?.label || item?.userName || '未知用户'
|
|
4256
|
+
name: item?.label || item?.userName || '未知用户',
|
|
4257
|
+
type: 0,
|
|
3489
4258
|
}
|
|
3490
4259
|
})
|
|
3491
4260
|
promises.push(
|
|
@@ -3524,25 +4293,52 @@ export default {
|
|
|
3524
4293
|
});
|
|
3525
4294
|
}
|
|
3526
4295
|
// 追加邀请设备
|
|
3527
|
-
if(tempDeviceList.length > 0) {
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
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
|
+
}
|
|
3535
4331
|
}
|
|
3536
4332
|
}
|
|
3537
4333
|
},
|
|
3538
|
-
pullMonitorDevice(monitorID, monitorName) {
|
|
4334
|
+
pullMonitorDevice(monitorID, monitorName, integrationType = 0) {
|
|
3539
4335
|
this.liveClient.judgeUserInMeeting(this.meetingNum, monitorID).then((res) => {
|
|
3540
4336
|
if (res && res?.code == 200) {
|
|
3541
4337
|
if (res.data == 1) {
|
|
3542
4338
|
this.showMessage.message("error", "该监控设备已进入会议");
|
|
3543
4339
|
return;
|
|
3544
4340
|
} else {
|
|
3545
|
-
this.liveClient.makeCall(monitorName, monitorID,
|
|
4341
|
+
this.liveClient.makeCall(monitorName, monitorID, this.handleInviteType(integrationType));
|
|
3546
4342
|
}
|
|
3547
4343
|
} else {
|
|
3548
4344
|
this.showMessage.message("error", "获取监控设备进会状态失败");
|
|
@@ -3557,7 +4353,8 @@ export default {
|
|
|
3557
4353
|
const platformGroup = {
|
|
3558
4354
|
'1_2': [], // platformID 为 1 或 2
|
|
3559
4355
|
'7': [], // platformID 为 7
|
|
3560
|
-
'4': [] // platformID 为 4
|
|
4356
|
+
'4': [], // platformID 为 4
|
|
4357
|
+
'8': [] // platformID 为 8
|
|
3561
4358
|
};
|
|
3562
4359
|
|
|
3563
4360
|
uninviteList.forEach(item => {
|
|
@@ -3569,6 +4366,8 @@ export default {
|
|
|
3569
4366
|
platformGroup['7'].push({ userName, identity, phone });
|
|
3570
4367
|
} else if (platformID === 4) {
|
|
3571
4368
|
platformGroup['4'].push({ userName, identity, phone });
|
|
4369
|
+
} else if (platformID === 8) {
|
|
4370
|
+
platformGroup['8'].push({ userName, identity, phone });
|
|
3572
4371
|
}
|
|
3573
4372
|
});
|
|
3574
4373
|
|
|
@@ -3619,7 +4418,8 @@ export default {
|
|
|
3619
4418
|
// 构建批量外呼数据
|
|
3620
4419
|
const volteCalls = platformGroup['4'].map(item => ({
|
|
3621
4420
|
dnis: item.phone,
|
|
3622
|
-
name: item.userName
|
|
4421
|
+
name: item.userName,
|
|
4422
|
+
type: 0
|
|
3623
4423
|
}));
|
|
3624
4424
|
|
|
3625
4425
|
promises.push(
|
|
@@ -3636,6 +4436,27 @@ export default {
|
|
|
3636
4436
|
);
|
|
3637
4437
|
}
|
|
3638
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
|
+
|
|
3639
4460
|
// 等待所有邀请完成后更新列表
|
|
3640
4461
|
Promise.allSettled(promises).then(() => {
|
|
3641
4462
|
// 可以在这里添加统一的后续处理,如刷新邀请列表
|
|
@@ -4159,6 +4980,64 @@ export default {
|
|
|
4159
4980
|
}
|
|
4160
4981
|
}
|
|
4161
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
|
+
}
|
|
4162
5041
|
// 添加点调容器之前的最终检查
|
|
4163
5042
|
const remainingLayoutElements = container.querySelectorAll(
|
|
4164
5043
|
".layout-leftside, .layout-rightside, .point-turn-top, .point-turn-center, .point-turn-bottom, .point-turn-other"
|
|
@@ -4327,6 +5206,92 @@ export default {
|
|
|
4327
5206
|
container.insertBefore(layoutLeftSideEle, container.firstChild ?? null);
|
|
4328
5207
|
container.appendChild(layoutRightSideEle);
|
|
4329
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
|
+
}
|
|
4330
5295
|
}
|
|
4331
5296
|
});
|
|
4332
5297
|
},
|
|
@@ -4550,7 +5515,7 @@ export default {
|
|
|
4550
5515
|
align-items: center;
|
|
4551
5516
|
justify-content: space-between;
|
|
4552
5517
|
position: absolute;
|
|
4553
|
-
z-index:
|
|
5518
|
+
z-index: 2100;
|
|
4554
5519
|
bottom: -66px;
|
|
4555
5520
|
left: 0;
|
|
4556
5521
|
transition: all 0.2s ease;
|