mifei 1 month ago
parent
commit
ad65e19d8b

+ 137 - 3
index.html

@@ -7,6 +7,9 @@
7 7
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8 8
   <script src="/js/leaderline.js"></script>
9 9
   <script src="/config.js"></script>
10
+  <link type="text/css" rel="stylesheet" href="./player.css" />
11
+  <script src="./ueapp.js"></script>
12
+  <script src="./webRtcPlayer.js"></script>
10 13
   <title>智慧教室IOC三维系统</title>
11 14
   <style>
12 15
     * {
@@ -15,10 +18,141 @@
15 18
   </style>
16 19
 </head>
17 20
 
18
-<body>
19
-  <div id="app"></div>
20
-  <script type="module" src="/src/main.js"></script>
21
+<body style="width: 100%; height: 100%" onload="load()">
22
+  <div id="overlay" class="overlay text-light bg-dark" style="display: none">
23
+    <div>
24
+      <div id="qualityStatus" class="greyStatus"></div>
25
+      <div id="overlayButton">+</div>
26
+    </div>
27
+    <div id="overlaySettings">
28
+      <div id="kickOthers">
29
+        <div class="settings-text">Kick all other players</div>
30
+        <label class="btn-overlay">
31
+          <input type="button" id="kick-other-players-button" class="overlay-button btn-flat" value="Kick" />
32
+        </label>
33
+      </div>
34
+      <div id="fillWindow">
35
+        <div class="settings-text">Enlarge Display to Fill Window</div>
36
+        <label class="tgl-switch">
37
+          <input type="checkbox" id="enlarge-display-to-fill-window-tgl" class="tgl tgl-flat" />
38
+          <div class="tgl-slider"></div>
39
+        </label>
40
+      </div>
41
+      <div id="qualityControlOwnership">
42
+        <div class="settings-text">Quality control ownership</div>
43
+        <label class="tgl-switch">
44
+          <input type="checkbox" id="quality-control-ownership-tgl" class="tgl tgl-flat" />
45
+          <div class="tgl-slider"></div>
46
+        </label>
47
+      </div>
48
+      <br />
49
+
50
+      <section id="encoderSettings">
51
+        <div class="settings-text">Encoder Settings</div>
52
+        <div id="encoderParamsContainer" class="collapse">
53
+          <div class="form-group">
54
+            <label for="encoder-rate-control" class="settings-text">Rate Control</label>
55
+            <select id="encoder-rate-control">
56
+              <option value="CBR" selected>CBR</option>
57
+              <option value="VBR">VBR</option>
58
+              <option value="ConstQP">ConstQP</option>
59
+            </select>
60
+            <label for="encoder-target-bitrate-text">Target Bitrate (kbps)</label>
61
+            <input
62
+              type="number"
63
+              class="form-control"
64
+              id="encoder-target-bitrate-text"
65
+              value="0"
66
+              min="0"
67
+              max="100000" />
68
+            <label for="encoder-max-bitrate-text">Max Bitrate (kbps)</label>
69
+            <input type="number" class="form-control" id="encoder-max-bitrate-text" value="0" min="0" max="100000" />
70
+            <label for="encoder-min-qp-text">Min QP</label>
71
+            <input type="number" class="form-control" id="encoder-min-qp-text" value="0" min="0" max="999" />
72
+            <label for="encoder-max-qp-text">Max QP</label>
73
+            <input type="number" class="form-control" id="encoder-max-qp-text" value="0" min="0" max="999" />
74
+            <label for="encoder-multipass" class="settings-text">Multipass</label>
75
+            <select id="encoder-multipass">
76
+              <option value="DISABLED" selected>DISABLED</option>
77
+              <option value="QUARTER">QUARTER</option>
78
+              <option value="FULL">FULL</option>
79
+            </select>
80
+            <div class="settings-text">Filler Data</div>
81
+            <label class="tgl-switch">
82
+              <input type="checkbox" id="encoder-filler-data-tgl" class="tgl tgl-flat" />
83
+              <div class="tgl-slider"></div>
84
+            </label>
85
+          </div>
86
+          <input id="encoder-params-submit" class="btn btn-primary btn-lg mt-3" type="button" value="Apply" />
87
+        </div>
88
+        <br />
89
+      </section>
90
+
91
+      <section id="webRTCSettings">
92
+        <div class="settings-text">WebRTC Settings</div>
93
+        <div id="webrtcParamsContainer" class="collapse">
94
+          <div class="form-group">
95
+            <label for="webrtc-degradation-pref">Degradation Pref</label>
96
+            <select id="webrtc-degradation-pref">
97
+              <option value="BALANCED">BALANCED</option>
98
+              <option value="MAINTAIN_FRAMERATE">MAINTAIN_FRAMERATE</option>
99
+              <option value="MAINTAIN_RESOLUTION">MAINTAIN_RESOLUTION</option>
100
+            </select>
101
+            <label for="webrtc-max-fps-text">Max FPS</label>
102
+            <input type="number" class="form-control" id="webrtc-max-fps-text" value="1" min="1" max="999" />
103
+            <label for="webrtc-min-bitrate-text">Min Bitrate (kbps)</label>
104
+            <input type="number" class="form-control" id="webrtc-min-bitrate-text" value="0" min="0" max="100000" />
105
+            <label for="webrtc-max-bitrate-text">Max Bitrate (kbps)</label>
106
+            <input type="number" class="form-control" id="webrtc-max-bitrate-text" value="0" min="0" max="100000" />
107
+            <label for="webrtc-low-qp-text">Low QP Threshold</label>
108
+            <input type="number" class="form-control" id="webrtc-low-qp-text" value="0" min="0" max="999" />
109
+            <label for="webrtc-high-qp-text">High QP Threshold</label>
110
+            <input type="number" class="form-control" id="webrtc-high-qp-text" value="0" min="0" max="999" />
111
+          </div>
112
+          <input id="webrtc-params-submit" class="btn btn-primary btn-lg mt-3" type="button" value="Apply" />
113
+        </div>
114
+      </section>
115
+      <br />
21 116
 
117
+      <div id="showFPS">
118
+        <div class="settings-text">Show FPS</div>
119
+        <label class="btn-overlay">
120
+          <input type="button" id="show-fps-button" class="overlay-button btn-flat" value="Toggle" />
121
+        </label>
122
+      </div>
123
+      <div id="matchViewportResolution">
124
+        <div class="settings-text">Match Viewport Resolution</div>
125
+        <label class="tgl-switch">
126
+          <input type="checkbox" id="match-viewport-res-tgl" class="tgl tgl-flat" />
127
+          <div class="tgl-slider"></div>
128
+        </label>
129
+      </div>
130
+
131
+      <div id="statsPanel">
132
+        <div class="settings-text">Show Stats</div>
133
+        <label class="tgl-switch">
134
+          <input type="checkbox" id="show-stats-tgl" class="tgl tgl-flat" checked />
135
+          <div class="tgl-slider"></div>
136
+        </label>
137
+        <div id="statsContainer" class="statsContainer">
138
+          <div id="stats" class="stats"></div>
139
+        </div>
140
+        <br />
141
+      </div>
142
+
143
+      <div id="latencyTest">
144
+        <div class="settings-text">Latency Stats</div>
145
+        <label class="btn-overlay">
146
+          <input type="button" id="test-latency-button" class="overlay-button btn-flat" value="Test Latency" />
147
+        </label>
148
+        <div id="latencyStatsContainer" class="statsContainer">
149
+          <div id="LatencyStats" class="stats">No stats yet...</div>
150
+        </div>
151
+      </div>
152
+    </div>
153
+  </div>
154
+  <div id="app" style="width: 100%; height: 100vh; overflow: hidden"></div>
155
+  <script type="module" src="/src/main.js"></script>
22 156
 </body>
23 157
 <style>
24 158
   *{

File diff suppressed because it is too large
+ 792 - 23
package-lock.json


+ 5 - 0
package.json

@@ -12,12 +12,17 @@
12 12
     "axios": "^1.4.0",
13 13
     "echarts": "^5.4.3",
14 14
     "element-plus": "^2.3.9",
15
+    "hls.js": "^1.4.12",
15 16
     "leader-line": "^1.0.7",
16 17
     "postcss-pxtorem": "^6.0.0",
18
+    "terser": "^5.29.1",
19
+    "video.js": "^8.6.1",
20
+    "videojs-contrib-hls": "^5.15.0",
17 21
     "vue": "^3.2.25",
18 22
     "vue-router": "^4.2.5",
19 23
     "vue3-count-to": "^1.1.2",
20 24
     "vue3-number-roll-plus": "^0.1.3",
25
+    "vue3-seamless-scroll": "^3.0.2",
21 26
     "vue3-video-play": "^1.3.1-beta.6"
22 27
   },
23 28
   "devDependencies": {

+ 14 - 2
public/config.js

@@ -1,7 +1,19 @@
1
+/*
2
+ * @Author: zy1125 1515706227@qq.com
3
+ * @Date: 2023-11-08 20:40:07
4
+ * @LastEditors: 半生瓜 1515706227@qq.com
5
+ * @LastEditTime: 2024-01-16 19:55:33
6
+ * @FilePath: \v3_yyz\public\config.js
7
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
8
+ */
1 9
 window.g = {
2 10
     // 获取数据请求地址
11
+    // BASE_API: "https://weizhi.huanghuai.edu.cn/yyzioc-server/",//prod
12
+    BASE_API: "https://782y5867q3.vicp.fun/ioc-server/",//dev
3 13
     // BASE_API: "https://test.lqkj.top//cmioc3-server",
14
+
4 15
     // BASE_API: "http://192.168.4.219:12397/ioc-server",
5
-    BASE_API: "https://782y5867q3.vicp.fun/ioc-server/",
6
-    UE_IP: "http://192.168.4.159"
16
+    // UE_IP: "http://192.168.4.159"
17
+    // UE_IP: "http://127.0.0.1:80"
18
+    UE_IP: "ws://192.168.4.152:81"
7 19
 };

BIN
public/img/bg.png


BIN
public/img/l1.png


BIN
public/img/l2.png


BIN
public/img/l3.png


BIN
public/img/l4.png


BIN
public/img/logo.png


BIN
public/img/r1.png


BIN
public/img/r2.png


BIN
public/img/r3.png


BIN
public/img/r4.png


BIN
public/img/r5.png


BIN
public/img/r6.png


BIN
public/img/swiper-left.png


BIN
public/img/swiper-right.png


BIN
public/img/video-border.png


+ 390 - 0
public/player.css

@@ -0,0 +1,390 @@
1
+/*Copyright Epic Games, Inc. All Rights Reserved.*/
2
+
3
+:root {
4
+	/*Using colour scheme https://color.adobe.com/TD-Colors---Option-3-color-theme-10394433/*/
5
+	--colour1:#2B3A42;
6
+	--colour2:#3F5765;
7
+	--colour3:#BDD4DE;
8
+	--colour4:#EFEFEF;
9
+	--colour5:#FF5035;
10
+	
11
+	--buttonFont:Helvetica;
12
+	--inputFont:Helvetica;
13
+}
14
+
15
+body{
16
+    margin: 0px;
17
+    background-color: black;
18
+}	
19
+
20
+#playerUI {
21
+	width: 100%;
22
+	position: absolute;
23
+	/*top: 0;
24
+	left: 0;*/
25
+	z-index: 10;
26
+}
27
+
28
+.statsContainer {
29
+	background-color: rgba(0,0,0,0.8);
30
+	text-align: left;
31
+	display: block;
32
+	margin-top: 5px;
33
+}
34
+
35
+.stats {
36
+	font-size: 14px;
37
+	font-weight: bold;
38
+	padding: 6px;
39
+	color: lime;
40
+}
41
+
42
+canvas{
43
+    image-rendering: crisp-edges;
44
+    position: absolute;
45
+}
46
+
47
+video{
48
+	position: absolute;
49
+	width: 100%;
50
+	height: 100%;
51
+}
52
+
53
+#player{
54
+
55
+}
56
+
57
+#overlay{
58
+	-moz-border-radius-bottomright: 5px;
59
+	-moz-border-radius-bottomleft: 5px;
60
+	-webkit-border-bottom-right-radius: 5px;
61
+	-webkit-border-bottom-left-radius: 5px;
62
+	border-bottom-right-radius: 5px; /* future proofing */
63
+	border-bottom-left-radius: 5px; /* future proofing */
64
+	-khtml-border-bottom-right-radius: 5px; /* for old Konqueror browsers */
65
+	-khtml-border-bottom-left-radius: 5px; /* for old Konqueror browsers */
66
+	
67
+	-webkit-touch-callout: none; /* iOS Safari */
68
+    -webkit-user-select: none; /* Safari */
69
+     -khtml-user-select: none; /* Konqueror HTML */
70
+       -moz-user-select: none; /* Firefox */
71
+        -ms-user-select: none; /* Internet Explorer/Edge */
72
+            user-select: none; /* Non-prefixed version, currently
73
+                                  supported by Chrome and Opera */
74
+	
75
+	position: absolute;
76
+	padding: 4px;
77
+	top: 0;
78
+	right: 2%;
79
+	z-index: 100;
80
+	border: 2px solid var(--colour4);
81
+	border-top-width: 0px;
82
+}
83
+
84
+.overlay {
85
+    background-color: var(--colour2);
86
+	font-family: var(--buttonFont);
87
+	font-weight: lighter;
88
+	color: var(--colour4);
89
+}
90
+
91
+.overlay-shown > #overlaySettings {
92
+	padding: 50px 4px 4px 4px;
93
+	display: block;
94
+}
95
+
96
+.overlay-shown > div > #overlayButton {
97
+	transform: rotate(-135deg);
98
+	-webkit-transform: rotate(-135deg); /* Safari */
99
+	-moz-transform: rotate(-135deg); /* Firefox */
100
+	-ms-transform: rotate(-135deg); /* IE */
101
+	-o-transform: rotate(-135deg); /* Opera */
102
+}
103
+
104
+#overlayButton:hover{
105
+	cursor: pointer;
106
+}
107
+
108
+#overlayButton{
109
+	transition-duration: 250ms;
110
+	float: right;
111
+	text-align: right;
112
+	font-size: 40px;
113
+}
114
+
115
+#qualityStatus{
116
+	float: left;
117
+	font-size: 37px;
118
+	padding-right: 4px;
119
+}
120
+
121
+#overlaySettings{
122
+	width: 400px;
123
+	display: none;
124
+}
125
+
126
+.greyStatus {
127
+	color: grey;
128
+}
129
+
130
+.limeStatus {
131
+	color: lime;
132
+}
133
+
134
+.orangeStatus {
135
+	color: orange;
136
+}
137
+
138
+.redStatus {
139
+	color: red;
140
+}
141
+
142
+#videoMessageOverlay{
143
+	z-index: 20;
144
+	color: var(--colour4);;
145
+	font-size: 1.8em;
146
+	position: absolute;
147
+	margin: auto;
148
+	font-family: var(--inputFont);;
149
+	width: 100%;
150
+}
151
+
152
+#videoPlayOverlay{
153
+	z-index: 30;
154
+	position: absolute;
155
+	color: var(--colour4);
156
+	font-size: 1.8em;
157
+	font-family: var(--inputFont);
158
+	width: 100%;
159
+	height: 100%;
160
+	background-color: rgba(100, 100, 100, 0.7);
161
+}
162
+
163
+/* State for element to be clickable */
164
+.clickableState{
165
+	align-items: center;
166
+	justify-content: center;
167
+	display: flex;
168
+	cursor: pointer;
169
+}
170
+
171
+/* State for element to show text, this is for informational use*/
172
+.textDisplayState{
173
+	display: flex;
174
+}
175
+
176
+/* State to hide overlay, WebRTC communication is in progress and or is playing */
177
+.hiddenState{
178
+	display: none;
179
+}
180
+
181
+#playButton{
182
+	display: inline-block;
183
+	height: auto;
184
+}
185
+
186
+img#playButton{
187
+	max-width: 241px;
188
+	width: 10%;
189
+}
190
+
191
+#UIInteraction{
192
+	position: fixed;
193
+}
194
+
195
+#UIInteractionButtonBoundary{
196
+	padding: 2px;
197
+}
198
+
199
+#UIInteractionButton{
200
+	cursor: pointer;
201
+}
202
+
203
+#hiddenInput{
204
+	position: absolute;
205
+	left: -10%;   /* Although invisible, push off-screen to prevent user interaction. */
206
+	width: 0px;
207
+	opacity: 0;
208
+}
209
+
210
+#editTextButton{
211
+	position: absolute;
212
+	height: 40px;
213
+	width: 40px;
214
+}
215
+
216
+.settings-text{
217
+	color: var(--colour4);
218
+	vertical-align: middle;
219
+	font-size: 18px;
220
+	font-weight: normal;
221
+	display: inline-block;
222
+}
223
+
224
+.overlay-button{
225
+	line-height: 1.1;
226
+	padding: 1px 6px;
227
+}
228
+
229
+.btn-overlay{
230
+	float: right;
231
+	vertical-align: middle;
232
+	display: inline-block;
233
+}
234
+
235
+.btn-flat{
236
+	background: var(--colour4);
237
+	border: 2px solid var(--colour5);
238
+	font-weight: bold;
239
+	cursor: pointer;
240
+	font-family: var(--buttonFont);
241
+	font-size: 10px;
242
+	color: var(--colour5);
243
+	border-radius: 5px;
244
+	height: 20px;
245
+}
246
+
247
+.btn-flat:disabled{
248
+	background: var(--colour4);
249
+	border-color: var(--colour3);
250
+	color: var(--colour3);
251
+	cursor: default;
252
+}
253
+
254
+.btn-flat:active{
255
+	border-color: var(--colour2);
256
+	color: var(--colour2);
257
+}
258
+
259
+.btn-flat:focus{
260
+	outline: none;
261
+}
262
+/*** Toggle Switch styles ***/
263
+.tgl-switch {
264
+  float: right;
265
+  vertical-align: middle;
266
+  display: inline-block;
267
+}
268
+
269
+.tgl-switch .tgl {
270
+	display:none;
271
+}
272
+
273
+.tgl, .tgl:after, .tgl:before, .tgl *, .tgl *:after, .tgl *:before, .tgl + .tgl-slider {
274
+  -webkit-box-sizing: border-box;
275
+          box-sizing: border-box;
276
+}
277
+.tgl::-moz-selection, .tgl:after::-moz-selection, .tgl:before::-moz-selection, .tgl *::-moz-selection, .tgl *:after::-moz-selection, .tgl *:before::-moz-selection, .tgl + .tgl-slider::-moz-selection {
278
+  background: none;
279
+}
280
+.tgl::selection, .tgl:after::selection, .tgl:before::selection, .tgl *::selection, .tgl *:after::selection, .tgl *:before::selection, .tgl + .tgl-slider::selection {
281
+  background: none;
282
+}
283
+
284
+.tgl-slider {
285
+	float:right;
286
+}
287
+
288
+.tgl + .tgl-slider {
289
+  outline: 0;
290
+  display: block;
291
+  width: 40px;
292
+  height: 18px;
293
+  position: relative;
294
+  cursor: pointer;
295
+  -webkit-user-select: none;
296
+     -moz-user-select: none;
297
+      -ms-user-select: none;
298
+          user-select: none;
299
+}
300
+
301
+.tgl + .tgl-slider:after, .tgl + .tgl-slider:before {
302
+  position: relative;
303
+  display: block;
304
+  content: "";
305
+  width: 50%;
306
+  height: 100%;
307
+}
308
+.tgl + .tgl-slider:after {
309
+  left: 0;
310
+}
311
+.tgl + .tgl-slider:before {
312
+  display: none;
313
+}
314
+
315
+.tgl-flat + .tgl-slider {
316
+  padding: 2px;
317
+  -webkit-transition: all .2s ease;
318
+  transition: all .2s ease;
319
+  background: #fff;
320
+  border: 3px solid var(--colour4);
321
+  border-radius: 2em;
322
+}
323
+
324
+.tgl-flat + .tgl-slider:after {
325
+  -webkit-transition: all .2s ease;
326
+  transition: all .2s ease;
327
+  background: var(--colour4);
328
+  content: "";
329
+  border-radius: 1em;
330
+}
331
+
332
+.tgl-flat:checked + .tgl-slider {
333
+  border: 3px solid var(--colour5);
334
+}
335
+
336
+.tgl-flat:checked + .tgl-slider:after {
337
+  left: 50%;
338
+  background: var(--colour5);
339
+}
340
+/*** Toggle Switch styles ***/
341
+
342
+#encoderSettings, #webRTCSettings {
343
+	margin: 10px 0px;
344
+}
345
+
346
+#encoderParamsContainer, #webrtcParamsContainer {
347
+	padding-left: 5%;
348
+}
349
+
350
+#encoder-params-submit {
351
+	float: right;
352
+}
353
+
354
+#webrtc-params-submit {
355
+	float: right;
356
+}
357
+
358
+select {
359
+	text-align: right;
360
+}
361
+
362
+
363
+
364
+.form-group {
365
+	display: grid;
366
+	grid-template-columns: 50% 50%;
367
+}
368
+
369
+.form-group label {
370
+	color: var(--colour4);
371
+	vertical-align: middle;
372
+	font-size: 18px;
373
+	font-weight: normal;
374
+}
375
+
376
+#latencyTest {
377
+	display: block;
378
+}
379
+
380
+#latencyTest button {
381
+	margin: 30px 0px;
382
+}
383
+
384
+#freezeFrameOverlay {
385
+	background-color: transparent;
386
+}
387
+
388
+.freezeframeBackground {
389
+	background-color: #000 !important;
390
+}

File diff suppressed because it is too large
+ 186 - 503
src/webrtcVideo.js


+ 537 - 0
public/webRtcPlayer.js

@@ -0,0 +1,537 @@
1
+// Copyright Epic Games, Inc. All Rights Reserved.
2
+// universal module definition - read https://www.davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd/
3
+
4
+(function (root, factory) {
5
+    if (typeof define === 'function' && define.amd) {
6
+        // AMD. Register as an anonymous module.
7
+        define(["./adapter"], factory);
8
+    } else if (typeof exports === 'object') {
9
+        // Node. Does not work with strict CommonJS, but
10
+        // only CommonJS-like environments that support module.exports,
11
+        // like Node.
12
+        module.exports = factory(require("./adapter"));
13
+    } else {
14
+        // Browser globals (root is window)
15
+
16
+
17
+        
18
+        root.webRtcPlayer = factory(root.adapter);
19
+    }
20
+}(this, function (adapter) {
21
+
22
+    function webRtcPlayer(parOptions) {
23
+    	parOptions = typeof parOptions !== 'undefined' ? parOptions : {};
24
+    	
25
+        var self = this;
26
+
27
+        //**********************
28
+        //Config setup
29
+        //**********************
30
+		this.cfg = typeof parOptions.peerConnectionOptions !== 'undefined' ? parOptions.peerConnectionOptions : {};
31
+		this.cfg.sdpSemantics = 'unified-plan';
32
+        // this.cfg.rtcAudioJitterBufferMaxPackets = 10;
33
+        // this.cfg.rtcAudioJitterBufferFastAccelerate = true;
34
+        // this.cfg.rtcAudioJitterBufferMinDelayMs = 0;
35
+
36
+		// If this is true in Chrome 89+ SDP is sent that is incompatible with UE Pixel Streaming 4.26 and below.
37
+        // However 4.27 Pixel Streaming does not need this set to false as it supports `offerExtmapAllowMixed`.
38
+        // tdlr; uncomment this line for older versions of Pixel Streaming that need Chrome 89+.
39
+        this.cfg.offerExtmapAllowMixed = false;
40
+
41
+        //**********************
42
+        //Variables
43
+        //**********************
44
+        this.pcClient = null;
45
+        this.dcClient = null;
46
+        this.tnClient = null;
47
+
48
+        this.sdpConstraints = {
49
+          offerToReceiveAudio: 1, //Note: if you don't need audio you can get improved latency by turning this off.
50
+          offerToReceiveVideo: 1,
51
+          voiceActivityDetection: false
52
+        };
53
+
54
+        // See https://www.w3.org/TR/webrtc/#dom-rtcdatachannelinit for values (this is needed for Firefox to be consistent with Chrome.)
55
+        this.dataChannelOptions = {ordered: true};
56
+
57
+        // This is useful if the video/audio needs to autoplay (without user input) as browsers do not allow autoplay non-muted of sound sources without user interaction.
58
+        this.startVideoMuted = typeof parOptions.startVideoMuted !== 'undefined' ? parOptions.startVideoMuted : false;
59
+        this.autoPlayAudio = typeof parOptions.autoPlayAudio !== 'undefined' ? parOptions.autoPlayAudio : true;
60
+
61
+        // To enable mic in browser use SSL/localhost and have ?useMic in the query string.
62
+        const urlParams = new URLSearchParams(window.location.search);
63
+        this.useMic = urlParams.has('useMic');
64
+        if(!this.useMic)
65
+        {
66
+            console.log("Microphone access is not enabled. Pass ?useMic in the url to enable it.");
67
+        }
68
+
69
+        // When ?useMic check for SSL or localhost
70
+        let isLocalhostConnection = location.hostname === "localhost" || location.hostname === "127.0.0.1";
71
+        let isHttpsConnection = location.protocol === 'https:';
72
+        if(this.useMic && !isLocalhostConnection && !isHttpsConnection)
73
+        {
74
+            this.useMic = false;
75
+            console.error("Microphone access in the browser will not work if you are not on HTTPS or localhost. Disabling mic access.");
76
+            console.error("For testing you can enable HTTP microphone access Chrome by visiting chrome://flags/ and enabling 'unsafely-treat-insecure-origin-as-secure'");
77
+        }
78
+
79
+        // Latency tester
80
+        this.latencyTestTimings = 
81
+        {
82
+            TestStartTimeMs: null,
83
+            UEReceiptTimeMs: null,
84
+            UEPreCaptureTimeMs: null,
85
+            UEPostCaptureTimeMs: null,
86
+            UEPreEncodeTimeMs: null,
87
+            UEPostEncodeTimeMs: null,
88
+            UETransmissionTimeMs: null,
89
+            BrowserReceiptTimeMs: null,
90
+            FrameDisplayDeltaTimeMs: null,
91
+            Reset: function()
92
+            {
93
+                this.TestStartTimeMs = null;
94
+                this.UEReceiptTimeMs = null;
95
+                this.UEPreCaptureTimeMs = null;
96
+                this.UEPostCaptureTimeMs = null;
97
+                this.UEPreEncodeTimeMs = null;
98
+                this.UEPostEncodeTimeMs = null;
99
+                this.UETransmissionTimeMs = null;
100
+                this.BrowserReceiptTimeMs = null;
101
+                this.FrameDisplayDeltaTimeMs = null;
102
+            },
103
+            SetUETimings: function(UETimings)
104
+            {
105
+                this.UEReceiptTimeMs = UETimings.ReceiptTimeMs;
106
+                this.UEPreCaptureTimeMs = UETimings.PreCaptureTimeMs;
107
+                this.UEPostCaptureTimeMs = UETimings.PostCaptureTimeMs;
108
+                this.UEPreEncodeTimeMs = UETimings.PreEncodeTimeMs;
109
+                this.UEPostEncodeTimeMs = UETimings.PostEncodeTimeMs;
110
+                this.UETransmissionTimeMs = UETimings.TransmissionTimeMs;
111
+                this.BrowserReceiptTimeMs = Date.now();
112
+                this.OnAllLatencyTimingsReady(this);
113
+            },
114
+            SetFrameDisplayDeltaTime: function(DeltaTimeMs)
115
+            {
116
+                if(this.FrameDisplayDeltaTimeMs == null)
117
+                {
118
+                    this.FrameDisplayDeltaTimeMs = Math.round(DeltaTimeMs);
119
+                    this.OnAllLatencyTimingsReady(this);
120
+                }
121
+            },
122
+            OnAllLatencyTimingsReady: function(Timings){}
123
+        }
124
+
125
+        //**********************
126
+        //Functions
127
+        //**********************
128
+
129
+        //Create Video element and expose that as a parameter
130
+        this.createWebRtcVideo = function() {
131
+            var video = document.createElement('video');
132
+
133
+            video.id = "streamingVideo";
134
+            video.playsInline = true;
135
+            video.disablepictureinpicture = true;
136
+            // video.muted = self.startVideoMuted;
137
+            video.muted = true;
138
+			
139
+            video.addEventListener('loadedmetadata', function(e){
140
+                if(self.onVideoInitialised){
141
+                    self.onVideoInitialised();
142
+                }
143
+            }, true);
144
+			
145
+			// Check if request video frame callback is supported
146
+			if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
147
+				// The API is supported! 
148
+				
149
+				const onVideoFrameReady = (now, metadata) => {
150
+					
151
+					if(metadata.receiveTime && metadata.expectedDisplayTime)
152
+					{
153
+						const receiveToCompositeMs = metadata.presentationTime - metadata.receiveTime;
154
+						self.aggregatedStats.receiveToCompositeMs = receiveToCompositeMs;
155
+					}
156
+					
157
+				  
158
+					// Re-register the callback to be notified about the next frame.
159
+					video.requestVideoFrameCallback(onVideoFrameReady);
160
+				};
161
+				
162
+				// Initially register the callback to be notified about the first frame.
163
+				video.requestVideoFrameCallback(onVideoFrameReady);
164
+			}
165
+			
166
+            return video;
167
+        }
168
+
169
+        this.video = this.createWebRtcVideo();
170
+
171
+        onsignalingstatechange = function(state) {
172
+            console.info('signaling state change:', state)
173
+        };
174
+
175
+        oniceconnectionstatechange = function(state) {
176
+            console.info('ice connection state change:', state)
177
+        };
178
+
179
+        onicegatheringstatechange = function(state) {
180
+            console.info('ice gathering state change:', state)
181
+        };
182
+
183
+        handleOnTrack = function(e) {
184
+            console.log('handleOnTrack', e.streams);
185
+			
186
+			if (e.track)
187
+			{
188
+				console.log('Got track - ' + e.track.kind + ' id=' + e.track.id + ' readyState=' + e.track.readyState); 
189
+			}
190
+			
191
+			if(e.track.kind == "audio")
192
+			{
193
+                handleOnAudioTrack(e.streams[0]);
194
+                return;
195
+			}
196
+            else(e.track.kind == "video" && self.video.srcObject !== e.streams[0])
197
+            {
198
+                self.video.srcObject = e.streams[0];
199
+				console.log('Set video source from video track ontrack.');
200
+                return;
201
+            }
202
+			
203
+        };
204
+
205
+        handleOnAudioTrack = function(audioMediaStream)
206
+        {
207
+            // do nothing the video has the same media stream as the audio track we have here (they are linked)
208
+            if(self.video.srcObject == audioMediaStream)
209
+            {
210
+                return;
211
+            }
212
+            // video element has some other media stream that is not associated with this audio track
213
+            else if(self.video.srcObject && self.video.srcObject !== audioMediaStream)
214
+            {
215
+                // create a new audio element
216
+                let audioElem = document.createElement("Audio");
217
+                audioElem.srcObject = audioMediaStream;
218
+
219
+                // there is no way to autoplay audio (even muted), so we defer audio until first click
220
+                if(!self.autoPlayAudio) {
221
+
222
+                    let clickToPlayAudio = function() {
223
+                        audioElem.play();
224
+                        self.video.removeEventListener("click", clickToPlayAudio);
225
+                    };
226
+
227
+                    self.video.addEventListener("click", clickToPlayAudio);
228
+                }
229
+                // we assume the user has clicked somewhere on the page and autoplaying audio will work
230
+                else {
231
+                    audioElem.play();
232
+                }
233
+                console.log('Created new audio element to play seperate audio stream.');
234
+            }
235
+
236
+        }
237
+
238
+        setupDataChannel = function(pc, label, options) {
239
+            try {
240
+                let datachannel = pc.createDataChannel(label, options);
241
+                console.log(`Created datachannel (${label})`)
242
+
243
+                // Inform browser we would like binary data as an ArrayBuffer (FF chooses Blob by default!)
244
+                datachannel.binaryType = "arraybuffer";
245
+                
246
+                datachannel.onopen = function (e) {
247
+                  console.log(`data channel (${label}) connect`)
248
+                  if(self.onDataChannelConnected){
249
+                    self.onDataChannelConnected();
250
+                  }
251
+                }
252
+
253
+                datachannel.onclose = function (e) {
254
+                  console.log(`data channel (${label}) closed`)
255
+                }
256
+
257
+                datachannel.onmessage = function (e) {
258
+                  //console.log(`Got message (${label})`, e.data)
259
+                  if (self.onDataChannelMessage)
260
+                    self.onDataChannelMessage(e.data);
261
+                }
262
+
263
+                return datachannel;
264
+            } catch (e) { 
265
+                console.warn('No data channel', e);
266
+                return null;
267
+            }
268
+        }
269
+
270
+        onicecandidate = function (e) {
271
+			console.log('ICE candidate', e)
272
+			if (e.candidate && e.candidate.candidate) {
273
+                self.onWebRtcCandidate(e.candidate);
274
+            }
275
+        };
276
+
277
+        handleCreateOffer = function (pc) {
278
+            pc.createOffer(self.sdpConstraints).then(function (offer) {
279
+                
280
+                // Munging is where we modifying the sdp string to set parameters that are not exposed to the browser's WebRTC API
281
+                mungeSDPOffer(offer);
282
+
283
+                // Set our munged SDP on the local peer connection so it is "set" and will be send across
284
+            	pc.setLocalDescription(offer);
285
+            	if (self.onWebRtcOffer) {
286
+            		self.onWebRtcOffer(offer);
287
+                }
288
+            },
289
+            function () { console.warn("Couldn't create offer") });
290
+        }
291
+
292
+        mungeSDPOffer = function (offer) {
293
+
294
+            // turn off video-timing sdp sent from browser
295
+            //offer.sdp = offer.sdp.replace("http://www.webrtc.org/experiments/rtp-hdrext/playout-delay", "");
296
+
297
+            // this indicate we support stereo (Chrome needs this)
298
+            offer.sdp = offer.sdp.replace('useinbandfec=1', 'useinbandfec=1;stereo=1;sprop-maxcapturerate=48000');
299
+
300
+        }
301
+        
302
+        setupPeerConnection = function (pc) {
303
+        	if (pc.SetBitrate)
304
+        		console.log("Hurray! there's RTCPeerConnection.SetBitrate function");
305
+
306
+            //Setup peerConnection events
307
+            pc.onsignalingstatechange = onsignalingstatechange;
308
+            pc.oniceconnectionstatechange = oniceconnectionstatechange;
309
+            pc.onicegatheringstatechange = onicegatheringstatechange;
310
+
311
+            pc.ontrack = handleOnTrack;
312
+            pc.onicecandidate = onicecandidate;
313
+        };
314
+
315
+        generateAggregatedStatsFunction = function(){
316
+            if(!self.aggregatedStats)
317
+                self.aggregatedStats = {};
318
+
319
+            return function(stats){
320
+                //console.log('Printing Stats');
321
+
322
+                let newStat = {};
323
+                
324
+                stats.forEach(stat => {
325
+//                    console.log(JSON.stringify(stat, undefined, 4));
326
+                    if (stat.type == 'inbound-rtp' 
327
+                        && !stat.isRemote 
328
+                        && (stat.mediaType == 'video' || stat.id.toLowerCase().includes('video'))) {
329
+
330
+                        newStat.timestamp = stat.timestamp;
331
+                        newStat.bytesReceived = stat.bytesReceived;
332
+                        newStat.framesDecoded = stat.framesDecoded;
333
+                        newStat.packetsLost = stat.packetsLost;
334
+                        newStat.bytesReceivedStart = self.aggregatedStats && self.aggregatedStats.bytesReceivedStart ? self.aggregatedStats.bytesReceivedStart : stat.bytesReceived;
335
+                        newStat.framesDecodedStart = self.aggregatedStats && self.aggregatedStats.framesDecodedStart ? self.aggregatedStats.framesDecodedStart : stat.framesDecoded;
336
+                        newStat.timestampStart = self.aggregatedStats && self.aggregatedStats.timestampStart ? self.aggregatedStats.timestampStart : stat.timestamp;
337
+
338
+                        if(self.aggregatedStats && self.aggregatedStats.timestamp){
339
+                            if(self.aggregatedStats.bytesReceived){
340
+                                // bitrate = bits received since last time / number of ms since last time
341
+                                //This is automatically in kbits (where k=1000) since time is in ms and stat we want is in seconds (so a '* 1000' then a '/ 1000' would negate each other)
342
+                                newStat.bitrate = 8 * (newStat.bytesReceived - self.aggregatedStats.bytesReceived) / (newStat.timestamp - self.aggregatedStats.timestamp);
343
+                                newStat.bitrate = Math.floor(newStat.bitrate);
344
+                                newStat.lowBitrate = self.aggregatedStats.lowBitrate && self.aggregatedStats.lowBitrate < newStat.bitrate ? self.aggregatedStats.lowBitrate : newStat.bitrate
345
+                                newStat.highBitrate = self.aggregatedStats.highBitrate && self.aggregatedStats.highBitrate > newStat.bitrate ? self.aggregatedStats.highBitrate : newStat.bitrate
346
+                            }
347
+
348
+                            if(self.aggregatedStats.bytesReceivedStart){
349
+                                newStat.avgBitrate = 8 * (newStat.bytesReceived - self.aggregatedStats.bytesReceivedStart) / (newStat.timestamp - self.aggregatedStats.timestampStart);
350
+                                newStat.avgBitrate = Math.floor(newStat.avgBitrate);
351
+                            }
352
+
353
+                            if(self.aggregatedStats.framesDecoded){
354
+                                // framerate = frames decoded since last time / number of seconds since last time
355
+                                newStat.framerate = (newStat.framesDecoded - self.aggregatedStats.framesDecoded) / ((newStat.timestamp - self.aggregatedStats.timestamp) / 1000);
356
+                                newStat.framerate = Math.floor(newStat.framerate);
357
+                                newStat.lowFramerate = self.aggregatedStats.lowFramerate && self.aggregatedStats.lowFramerate < newStat.framerate ? self.aggregatedStats.lowFramerate : newStat.framerate
358
+                                newStat.highFramerate = self.aggregatedStats.highFramerate && self.aggregatedStats.highFramerate > newStat.framerate ? self.aggregatedStats.highFramerate : newStat.framerate
359
+                            }
360
+
361
+                            if(self.aggregatedStats.framesDecodedStart){
362
+                                newStat.avgframerate = (newStat.framesDecoded - self.aggregatedStats.framesDecodedStart) / ((newStat.timestamp - self.aggregatedStats.timestampStart) / 1000);
363
+                                newStat.avgframerate = Math.floor(newStat.avgframerate);
364
+                            }
365
+                        }
366
+                    }
367
+
368
+                    //Read video track stats
369
+                    if(stat.type == 'track' && (stat.trackIdentifier == 'video_label' || stat.kind == 'video')) {
370
+                        newStat.framesDropped = stat.framesDropped;
371
+                        newStat.framesReceived = stat.framesReceived;
372
+                        newStat.framesDroppedPercentage = stat.framesDropped / stat.framesReceived * 100;
373
+                        newStat.frameHeight = stat.frameHeight;
374
+                        newStat.frameWidth = stat.frameWidth;
375
+                        newStat.frameHeightStart = self.aggregatedStats && self.aggregatedStats.frameHeightStart ? self.aggregatedStats.frameHeightStart : stat.frameHeight;
376
+                        newStat.frameWidthStart = self.aggregatedStats && self.aggregatedStats.frameWidthStart ? self.aggregatedStats.frameWidthStart : stat.frameWidth;
377
+                    }
378
+
379
+                    if(stat.type =='candidate-pair' && stat.hasOwnProperty('currentRoundTripTime') && stat.currentRoundTripTime != 0){
380
+                        newStat.currentRoundTripTime = stat.currentRoundTripTime;
381
+                    }
382
+                });
383
+
384
+				
385
+				if(self.aggregatedStats.receiveToCompositeMs)
386
+				{
387
+					newStat.receiveToCompositeMs = self.aggregatedStats.receiveToCompositeMs;
388
+                    self.latencyTestTimings.SetFrameDisplayDeltaTime(self.aggregatedStats.receiveToCompositeMs);
389
+				}
390
+				
391
+                self.aggregatedStats = newStat;
392
+
393
+                if(self.onAggregatedStats)
394
+                    self.onAggregatedStats(newStat)
395
+            }
396
+        };
397
+
398
+        setupTracksToSendAsync = async function(pc){
399
+            
400
+            // Setup a transceiver for getting UE video
401
+            pc.addTransceiver("video", { direction: "recvonly" });
402
+
403
+            // Setup a transceiver for sending mic audio to UE and receiving audio from UE
404
+            if(!self.useMic)
405
+            {
406
+                pc.addTransceiver("audio", { direction: "recvonly" });
407
+            }
408
+            else
409
+            {
410
+                let audioSendOptions = self.useMic ? 
411
+                {
412
+                    autoGainControl: false,
413
+                    channelCount: 1,
414
+                    echoCancellation: false,
415
+                    latency: 0,
416
+                    noiseSuppression: false,
417
+                    sampleRate: 16000,
418
+                    volume: 1.0
419
+                } : false;
420
+
421
+                // Note using mic on android chrome requires SSL or chrome://flags/ "unsafely-treat-insecure-origin-as-secure"
422
+                const stream = await navigator.mediaDevices.getUserMedia({video: false, audio: audioSendOptions});
423
+                if(stream)
424
+                {
425
+                    for (const track of stream.getTracks()) {
426
+                        if(track.kind && track.kind == "audio")
427
+                        {
428
+                            pc.addTransceiver(track, { direction: "sendrecv" });
429
+                        }
430
+                    }
431
+                }
432
+                else
433
+                {
434
+                    pc.addTransceiver("audio", { direction: "recvonly" });
435
+                }
436
+            }
437
+        };
438
+
439
+
440
+        //**********************
441
+        //Public functions
442
+        //**********************
443
+
444
+        this.setVideoEnabled = function(enabled) {
445
+            self.video.srcObject.getTracks().forEach(track => track.enabled = enabled);
446
+        }
447
+
448
+        this.startLatencyTest = function(onTestStarted) {
449
+            // Can't start latency test without a video element
450
+            if(!self.video)
451
+            {
452
+                return;
453
+            }
454
+
455
+            self.latencyTestTimings.Reset();
456
+            self.latencyTestTimings.TestStartTimeMs = Date.now();
457
+            onTestStarted(self.latencyTestTimings.TestStartTimeMs);            
458
+        }
459
+
460
+        //This is called when revceiving new ice candidates individually instead of part of the offer
461
+        //This is currently not used but would be called externally from this class
462
+        this.handleCandidateFromServer = function(iceCandidate) {
463
+            console.log("ICE candidate: ", iceCandidate);
464
+            let candidate = new RTCIceCandidate(iceCandidate);
465
+            self.pcClient.addIceCandidate(candidate).then(_=>{
466
+                console.log('ICE candidate successfully added');
467
+            });
468
+        };
469
+
470
+        //Called externaly to create an offer for the server
471
+        this.createOffer = function() {
472
+            if(self.pcClient){
473
+                console.log("Closing existing PeerConnection")
474
+                self.pcClient.close();
475
+                self.pcClient = null;
476
+            }
477
+            self.pcClient = new RTCPeerConnection(self.cfg);
478
+
479
+            setupTracksToSendAsync(self.pcClient).finally(function()
480
+            {
481
+                setupPeerConnection(self.pcClient);
482
+                self.dcClient = setupDataChannel(self.pcClient, 'cirrus', self.dataChannelOptions);
483
+                handleCreateOffer(self.pcClient);
484
+            });
485
+            
486
+        };
487
+
488
+        //Called externaly when an answer is received from the server
489
+        this.receiveAnswer = function(answer) {
490
+            console.log('Received answer:');
491
+            console.log(answer);
492
+            var answerDesc = new RTCSessionDescription(answer);
493
+            self.pcClient.setRemoteDescription(answerDesc);
494
+
495
+            let receivers = self.pcClient.getReceivers();
496
+            for(let receiver of receivers)
497
+            {
498
+                receiver.playoutDelayHint = 0;
499
+            }
500
+        };
501
+
502
+        this.close = function(){
503
+            if(self.pcClient){
504
+                console.log("Closing existing peerClient")
505
+                self.pcClient.close();
506
+                self.pcClient = null;
507
+            }
508
+            if(self.aggregateStatsIntervalId)
509
+                clearInterval(self.aggregateStatsIntervalId);
510
+        }
511
+
512
+        //Sends data across the datachannel
513
+        this.send = function(data){
514
+            if(self.dcClient && self.dcClient.readyState == 'open'){
515
+                //console.log('Sending data on dataconnection', self.dcClient)
516
+                self.dcClient.send(data);
517
+            }
518
+        };
519
+
520
+        this.getStats = function(onStats){
521
+            if(self.pcClient && onStats){
522
+                self.pcClient.getStats(null).then((stats) => { 
523
+                    onStats(stats); 
524
+                });
525
+            }
526
+        }
527
+
528
+        this.aggregateStats = function(checkInterval){
529
+            let calcAggregatedStats = generateAggregatedStatsFunction();
530
+            let printAggregatedStats = () => { self.getStats(calcAggregatedStats); }
531
+            self.aggregateStatsIntervalId = setInterval(printAggregatedStats, checkInterval);
532
+        }
533
+    };
534
+
535
+    return webRtcPlayer;
536
+  
537
+}));

+ 33 - 3
src/App.vue

@@ -1,10 +1,40 @@
1
+<!--
2
+ * @Author: zy1125 1515706227@qq.com
3
+ * @Date: 2023-10-18 10:46:30
4
+ * @LastEditors: zy1125 1515706227@qq.com
5
+ * @LastEditTime: 2023-12-22 14:46:51
6
+ * @FilePath: \v3_yyz\src\App.vue
7
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
8
+-->
1 9
 
2 10
 <template >
3
-      <router-view />
11
+    <router-view />
4 12
     <!-- <UeVideo /> -->
5 13
 </template>
6
-<script setup>
7
-import UeVideo from './components/UeVideo.vue'
14
+<script >
15
+import { ref, onBeforeMount } from "vue";
16
+import { useRouter } from 'vue-router'
17
+
18
+
19
+
20
+// import login from '/''
21
+export default ({
22
+    setup() {
23
+        //首先在setup中定义
24
+        const router = useRouter()
25
+
26
+     
27
+
28
+        return {
29
+
30
+        }
31
+    }
32
+})
33
+
34
+
35
+
36
+
37
+
8 38
 </script>
9 39
 <style scoped>
10 40
 #app {

+ 39 - 53
src/assets/css/home.scss

@@ -7,39 +7,20 @@
7 7
     background-position: center !important;
8 8
     background-size: cover !important;
9 9
     position: relative;
10
-    
11 10
 
12
-    // .header {
13
-    //     position: absolute;
14
-    //     z-index: 555;
15
-    //     margin-bottom: 20px;
16
-    //     height: 70px;
17
-    //     width: 100%;
18
-    //     top: 10px;
11
+    .header {
12
+        position: absolute;
13
+        z-index: 555;
14
+        margin-bottom: 20px;
15
+        height: 70px;
16
+        width: 100%;
17
+        // top: 10px;
19 18
 
20
-    //     img {
21
-    //         width: 100%;
22
-    //         height: 100%;
23
-    //     }
24
-    //     .weather{
25
-    //         position: absolute;
26
-    //         right: 10px;
27
-    //         top: 50%;
28
-    //         transform: translateY(-50%);
29
-    //         display: flex;
30
-    //         align-items: center;
31
-    //         .right-weatherInfo{
32
-    //             display: flex;
33
-    //             align-items: center;
34
-    //             .info{
35
-    //                 flex: 1;
36
-    //                 .temp{
37
-                       
38
-    //                 }
39
-    //             }
40
-    //         }
41
-    //     }
42
-    // }
19
+        img {
20
+            width: 100%;
21
+            height: 100%;
22
+        }
23
+    }
43 24
 }
44 25
 
45 26
 .el-container {
@@ -60,13 +41,13 @@
60 41
     backdrop-filter: blur(30.5px);
61 42
     z-index: 2007;
62 43
 
63
-    .model-detail2{
64
-            border-top-left-radius: 10px;
65
-    border-top-right-radius: 10px;
66
-    height: 155px;
67
-    overflow: hidden;
44
+    .model-detail2 {
45
+        border-top-left-radius: 10px;
46
+        border-top-right-radius: 10px;
47
+        height: 155px;
48
+        overflow: hidden;
68 49
 
69
-            .listbox2 {
50
+        .listbox2 {
70 51
             width: 180px;
71 52
             text-align: center;
72 53
             height: 30px;
@@ -77,20 +58,20 @@
77 58
             font-size: 12px;
78 59
             font-family: Microsoft YaHei UI;
79 60
             font-weight: 700;
80
-       
81
-            .listtitlebox{
82
-             
83
-                
61
+
62
+            .listtitlebox {
63
+
64
+
84 65
                 background-color: rgba(255, 255, 255, 0.18);
85 66
                 padding-bottom: 20px;
86 67
             }
87 68
 
88 69
         }
89
-  
90
-        .listtitle{
91
-           
70
+
71
+        .listtitle {
72
+
92 73
             width: 100%;
93
-      
74
+
94 75
             display: flex;
95 76
             align-items: center;
96 77
 
@@ -141,7 +122,7 @@
141 122
 
142 123
         .listtitle {
143 124
             width: 100%;
144
-    
125
+
145 126
             display: flex;
146 127
             align-items: center;
147 128
 
@@ -202,10 +183,15 @@
202 183
     // }
203 184
 }
204 185
 
205
-// .asd  .list:first-child {
206
-//     /* Your styles here */
207
-//     // border-radius: 10px;
208
-//     border-top-left-radius: 10px;
209
-//     border-top-right-radius: 10px;
210
-//     // background-color: aqua;
211
-//   }
186
+
187
+
188
+
189
+
190
+// .deviceDialog {
191
+//     background-color: red;
192
+
193
+//     .el-dialog__header {
194
+//         background-color: red !important;
195
+//     }
196
+// }
197
+

+ 77 - 51
src/assets/css/left.scss

@@ -1,9 +1,10 @@
1 1
 .left {
2 2
     position: absolute;
3 3
     width: 412px;
4
-    height: 86vh;
4
+    height: 92vh;
5 5
     top: 8vh;
6
-    z-index: 9;
6
+    // top: 0px;
7
+    z-index: 999;
7 8
     flex-shrink: 0;
8 9
     border-radius: 2px;
9 10
     // background: rgba(125, 125, 125, 0.17);
@@ -156,8 +157,50 @@
156 157
             padding-top: 0;
157 158
             height: 100%;
158 159
             overflow: hidden;
159
-            padding-left: 20px;
160
-            padding-right: 20px;
160
+        }
161
+
162
+        .table {
163
+            width: 100%;
164
+
165
+            .th {
166
+                font-weight: 700;
167
+                display: flex;
168
+                width: 100%;
169
+
170
+                .long {
171
+                    width: 500px;
172
+                }
173
+            }
174
+
175
+            ul {
176
+                display: flex;
177
+                list-style: none;
178
+                height: 48px;
179
+                line-height: 40px;
180
+                font-size: 12px;
181
+                border-bottom: 1px dotted #fff;
182
+                padding: 0;
183
+
184
+                &:last-child {
185
+                    margin-bottom: 10px;
186
+                }
187
+
188
+                li {
189
+                    display: flex;
190
+                    width: 100%;
191
+                    align-items: center;
192
+                    justify-content: space-around;
193
+                    word-wrap: break-word;
194
+                }
195
+            }
196
+
197
+            .el-button {
198
+                background-color: #5F7BDC;
199
+                color: #fff;
200
+                border: none;
201
+            }
202
+
203
+
161 204
         }
162 205
 
163 206
         .title {
@@ -266,7 +309,7 @@
266 309
 
267 310
         .table {
268 311
             width: 100%;
269
-           
312
+
270 313
             .dropdown-container {
271 314
                 display: flex;
272 315
                 justify-content: space-between;
@@ -288,14 +331,9 @@
288 331
 
289 332
             }
290 333
 
291
-            .th {
292
-                font-weight: 700;
293
-                display: flex;
294
-                width: 100%;
295
-
296
-                .long {
297
-                    width: 500px;
298
-                }
334
+            .vue3-seamless-scroll {
335
+                overflow: scroll;
336
+                // height: 342px !important;
299 337
             }
300 338
 
301 339
             .list-item {
@@ -317,6 +355,7 @@
317 355
 
318 356
                     .left-class-status {
319 357
                         display: flex;
358
+                        align-items: center;
320 359
                     }
321 360
 
322 361
                     .right-btn {
@@ -327,6 +366,7 @@
327 366
                         gap: 10px;
328 367
                         border-radius: 2px;
329 368
                         background: #0082D3;
369
+                        cursor: pointer;
330 370
 
331 371
 
332 372
 
@@ -342,44 +382,26 @@
342 382
                     }
343 383
                 }
344 384
 
345
-                .position-type-container {
385
+                .item-content {
346 386
                     display: flex;
347
-                    justify-content: space-between;
348
-                    margin-top: 20px;
387
+                    line-height: 22px;
349 388
 
350
-                    div {
351
-                        flex: 1;
352
-                    }
353
-                }
354
-
355
-                .course-person-rate-container {
356
-                    display: flex;
357
-                    margin-top: 8px;
389
+                    .item-content-left {
390
+                        flex: 6;
358 391
 
359
-                    .course {
360
-                        flex: 1;
361 392
                     }
362 393
 
363
-                    .person-rate-container {
364
-                        flex: 2;
365
-                    }
394
+                    .item-content-right {
395
+                        margin-left: 10px;
396
+                        flex: 7;
366 397
 
367
-                    .person-rate-container {
368
-                        display: flex;
398
+                        // margin-left: 20px;s
399
+                        .person-rate-container {
400
+                            display: flex;
401
+                        }
369 402
                     }
370 403
                 }
371 404
 
372
-                // &:last-child {
373
-                //     margin-bottom: 10px;
374
-                // }
375
-
376
-                // li {
377
-                //     display: flex;
378
-                //     width: 100%;
379
-                //     align-items: center;
380
-                //     justify-content: space-around;
381
-                //     word-wrap: break-word;
382
-                // }
383 405
             }
384 406
 
385 407
             .el-button {
@@ -396,15 +418,7 @@
396 418
 
397 419
 // 通用标题
398 420
 .title {
399
-    //  width: 380px;
400
-    //  height: 32px;
401
-    //  flex-shrink: 0;
402
-    //  background: linear-gradient(90deg, rgba(255, 255, 255, 0.17) 2.49%, rgba(255, 255, 255, 0.00) 98.69%);
403
-    //  ;
404
-    //  line-height: 32px;
405
-    //  margin: 11px 22px 0 10px;
406
-    //    max-width: 100%;
407
-    //    height: auto;
421
+
408 422
     width: 439px;
409 423
     height: 40px;
410 424
     flex-shrink: 0;
@@ -425,11 +439,18 @@
425 439
 }
426 440
 
427 441
 // 通用content
442
+// 通用content
428 443
 .content {
429 444
     display: flex;
430 445
     width: 100%;
431 446
     //  overflow: hidden;
432 447
     justify-content: space-evenly;
448
+    padding: 24px 0px;
449
+
450
+    #myChart {
451
+        width: 140px;
452
+        height: 140px;
453
+    }
433 454
 
434 455
 }
435 456
 
@@ -510,4 +531,9 @@
510 531
 :deep(.el-tooltip__trigger:focus-visible) {
511 532
     outline: unset;
512 533
 
534
+}
535
+
536
+::-webkit-scrollbar {
537
+    width: 0px;
538
+    height: 0px;
513 539
 }

+ 269 - 109
src/assets/css/right.scss

@@ -1,4 +1,4 @@
1
-::v-deep .el-carousel__indicators{
1
+::v-deep .el-carousel__indicators {
2 2
     display: none;
3 3
     // width: 100%;
4 4
     // display: flex;
@@ -8,40 +8,35 @@
8 8
     // transform: translateY(10%);
9 9
 
10 10
 }
11
+
11 12
 .vue3VideoPlay {
12 13
     pointer-events: none;
13 14
 }
14
-::v-deep .el-carousel__arrow{
15
+
16
+::v-deep .el-carousel__arrow {
15 17
     // display: none;
16 18
     display: block;
17 19
 }
18 20
 
19
-::v-deep  .el-carousel__arrow--left{
21
+::v-deep .el-carousel__arrow--left {
20 22
 
21 23
     left: -7px;
22 24
     top: 55%;
23 25
 }
24
-::v-deep  .el-carousel__arrow--right{
26
+
27
+::v-deep .el-carousel__arrow--right {
25 28
 
26 29
     right: -7px;
27 30
     top: 55%;
28 31
 }
32
+
29 33
 ::v-deep .el-carousel {
30 34
 
31
-    --el-carousel-arrow-size:0.1375rem
35
+    --el-carousel-arrow-size: 0.1375rem
32 36
 }
33 37
 
34 38
 // 通用标题
35 39
 .title {
36
-    //  width: 380px;
37
-    //  height: 32px;
38
-    //  flex-shrink: 0;
39
-    //  background: linear-gradient(90deg, rgba(255, 255, 255, 0.17) 2.49%, rgba(255, 255, 255, 0.00) 98.69%);
40
-    //  ;
41
-    //  line-height: 32px;
42
-    //  margin: 11px 22px 0 10px;
43
-    //    max-width: 100%;
44
-    //    height: auto;
45 40
     width: 439px;
46 41
     height: 40px;
47 42
     flex-shrink: 0;
@@ -60,9 +55,6 @@
60 55
 
61 56
     }
62 57
 }
63
-.title-mt{
64
-    margin-top: 42px;
65
-}
66 58
 
67 59
 // 通用content
68 60
 .content {
@@ -79,22 +71,22 @@
79 71
     width: 412px;
80 72
 
81 73
     top: 8vh;
82
-    z-index: 9;
74
+    z-index: 999;
83 75
     flex-shrink: 0;
84 76
     border-radius: 2px;
85 77
     // background: rgba(125, 125, 125, 0.17);
86 78
     // backdrop-filter: blur(30.5px);
87 79
     color: #FFF;
88 80
     font-size: 14px;
89
-    // overflow: hidden;
81
+    overflow: hidden;
90 82
     right: 25px;
91 83
     // border: 2px solid salmon;
92 84
     box-sizing: border-box;
93 85
     display: flex;
94 86
     flex-direction: column;
95
-    height: 86vh;
96
-    overflow: hidden;
97
-    padding: 11px 10px 15px 10px;
87
+    height: 90vh;
88
+    // overflow: hidden;
89
+    padding: 11px 10px 0px 10px;
98 90
 
99 91
 }
100 92
 
@@ -205,28 +197,56 @@
205 197
 
206 198
 // 物联设备类型统计
207 199
 .right_center {
208
-    height: 33%;
200
+    height: 45%;
209 201
     // border: 3px solid darkcyan;
210 202
     box-sizing: border-box;
211 203
     // overflow: hidden;
204
+    display: flex;
205
+    flex-direction: column;
212 206
 
213 207
     .contentwrap {
214 208
         display: flex;
215 209
         flex-wrap: wrap;
216
-
217
-        height: 90%;
218
-        overflow: hidden;
219
-        align-content: center;
210
+        flex: 1;
211
+        // height: 50%;
212
+        // overflow: hidden;
213
+        // align-content: center;
214
+        align-items: center;
215
+        // justify-content: space-around;
220 216
         // border: 2px solid sandybrown;
221 217
 
222 218
         .count {
223 219
             display: flex;
224 220
             flex-direction: column;
225 221
             align-items: center;
226
-            padding: 6px;
222
+            // padding: 6px;
227 223
             text-align: center;
228 224
             // border: 2px solid darkkhaki;
229
-            width: 110px;
225
+            flex: 1 0 33.33%;
226
+            /* 每个元素占据容器的三分之一宽度 */
227
+            box-sizing: border-box;
228
+            /* 包含padding和border在内计算宽度 */
229
+            padding: 10px;
230
+            // border: 1px solid #000;
231
+            text-align: center;
232
+            /* 文本居中 */
233
+
234
+            .num {
235
+                color: #FFF;
236
+
237
+                /* text/pc/07-85-bold */
238
+                font-family: "Alibaba PuHuiTi 2.0";
239
+                font-size: 18px;
240
+                font-style: normal;
241
+                font-weight: 700;
242
+                line-height: normal;
243
+            }
244
+
245
+            img {
246
+                width: 100%;
247
+                height: 94px;
248
+                // transform: scale(0.5);
249
+            }
230 250
         }
231 251
     }
232 252
 }
@@ -234,101 +254,241 @@
234 254
 
235 255
 // 智慧教室实时监控     
236 256
 .right_bottom {
237
-    flex: 1;
257
+    // flex: 1;
238 258
     box-sizing: border-box;
239 259
     // border: 3px solid darkcyan;
240 260
     height: 33%;
261
+    display: flex;
262
+    flex-direction: column;
241 263
 
242 264
     .monitorContent {
243
-        margin-top: -6px;
244
-
245
-        .monitor {
246
-            box-sizing: border-box;
247
-            display: flex;
248
-            flex-direction: column;
265
+        // height: 210px;
266
+        flex: 1;
267
+        margin-top: 20px;
268
+        position: relative;
269
+
270
+        padding:3px  8px 10px 8px;
271
+        box-sizing: border-box;
272
+        .change-left{
273
+            position: absolute;
274
+            left: 5px;
275
+            top: 50%;
276
+            transform: translateY(-50%);
277
+            z-index:99;
278
+            cursor: pointer;
279
+        }
280
+        .change-right{
281
+            position: absolute;
282
+            right: 5px;
283
+            top: 50%;
284
+            transform: translateY(-50%);
285
+            z-index:999999;
286
+            cursor: pointer;
287
+        }
288
+        // position: relative;
289
+        // background-size: contain !important;
290
+        // background-repeat: no-repeat !important;
291
+        .video-border {
292
+            position: absolute;
293
+            top: 0;
294
+            left: 0;
295
+            width: 100%;
296
+            height: 100%;
297
+            z-index: -1;
298
+            // height: 210px;
299
+            // transform: scale(1);
300
+        }
301
+        .swiper-container {
302
+            // margin-top: 10px;
303
+            // width: 388px;
304
+            height: 100% !important;
305
+            background-size: cover !important;
306
+            // padding: 6px;
307
+            box-sizing: border-box !important;
308
+            position: relative;
249 309
             
250
-            background: rgba(0, 0, 0, 0.2);
251
-            padding: 10px;
252
-            width: 364px;
253
-            height: 25vh;
254
-            margin: 2vh auto;
255
-    
256
-
257
-
258
-            // border: 1px solid red;
259
-
260
-            .interactclass {
261
-                display: flex;
262
-                justify-content: space-between;
263
-                width: 93.5%;
264
-                margin-left: 4.5%;
265
-
266
-                .room {
267
-
268
-           
269
-                    position: relative;
270
-                    width: 155px;
271
-                    height: 8vh;
272
-                    padding: 0;
273
-                    margin: 0;
274
-                    // background: saddlebrown;
275
-                    // margin-top: 10px;
276
-                    cursor: pointer;
277
-
278
-                    // &:first-child {
279
-                    //     margin-right: 10px;
280
-                    // }
281
-
282
-                    span {
283
-                        position: absolute;
284
-                        top: 0;
310
+            p {
311
+                position: absolute;
312
+                top: 10px;
313
+                left: 10px;
314
+                z-index: 999999999999;
315
+                background: linear-gradient(90deg, rgba(5, 94, 143, 0.65) 47.49%, rgba(0, 66, 128, 0.00) 100%);
316
+                color: #FFF;
317
+
318
+                font-family: 江城斜黑体;
319
+                font-size: 12px;
320
+                font-style: normal;
321
+                font-weight: 700;
322
+                line-height: normal;
323
+                text-transform: uppercase;
324
+            }
325
+
326
+            .video-container {
327
+                width: 100%;
328
+                height: 100%;
329
+                position: relative;
330
+                // background-color: red;
285 331
                 
286
-                        font-size: 11px;
287
-                    
288
-                        text-indent: 5px;
289
-                        padding-right: 5px;
290
-                        height: 18px;
291
-                        line-height: 17px;
292
-                        background-color: rgba(0, 0, 0, 0.38);
293
-                    }
332
+                // -webkit-clip-path: polygon(4% 0, 96% 0, 100% 4%, 100% 80%, 100% 99%, 0 100%, 0% 80%, 0 4%);
333
+                // clip-path: polygon(4% 0, 96% 0, 100% 4%, 100% 80%, 100% 99%, 0 100%, 0% 80%, 0 4%);
334
+                // box-sizing: border-box;
335
+                // overflow: hidden;
336
+                &::after {
337
+                    content: '';
338
+                    position: absolute;
339
+                    top: 0;
340
+                    left: 0;
341
+                    width: 100%;
342
+                    height: 100%;
343
+                    border: 5px solid #1FC6FF;
344
+                    /* 设置边框 */
345
+                    z-index: -1;
346
+                    box-sizing: border-box;
347
+                    /* 确保边框在内容之下 */
294 348
                 }
295
-            }
296
-        }
297 349
 
298
-        .el-carousel__indicators--horizontal {
299
-            display: none;
300
-            bottom: 6%;
301
-        }
350
+                .video {
351
+                    width: 100%;
352
+                    // position: absolute;
353
+                    // top: 0px;
354
+                    // left: 0px;
302 355
 
303
-        // .el-carousel__container {
304
-        //     height: 248px;
305
-        // }
356
+                    height: 210px;
357
+                }
306 358
 
307
-        // .el-carousel__indicators .el-carousel__indicators--horizontal {
308
-        //     display: none;
309
-        // }
359
+               
360
+            }
361
+            
310 362
 
311
-        .el-pagination {
312
-            justify-content: center;
313
-            --el-pagination-bg-color: 'tranparent';
314
-            --el-pagination-text-color: '#fff'
315
-        }
363
+            // padding: 10px;
364
+            // overflow: hidden !important;
365
+            .swiper-slide {
366
+                // width: 388px;
367
+                height: 210px;
368
+                // overflow: hidden !important;
369
+                position: relative;
316 370
 
317
-        .el-pagination button {
318
-            color: #fff;
319
-        }
371
+                // padding: 50px;
320 372
 
321
-        .el-pagination button:disabled {
322
-            background: none;
323
-            color: '#fff';
324
-        }
373
+            }
325 374
 
326
-        .el-pagination button:hover {
327
-            color: '#fff';
328
-        }
375
+            .swiper-left {
376
+                position: absolute;
377
+                width: 34px;
378
+                height: 34px;
379
+                left: 0px;
380
+                top: 50%;
381
+                z-index: 999;
382
+                transform: translateY(-50%);
383
+            }
384
+
385
+            .swiper-right {
386
+                position: absolute;
387
+                width: 34px;
388
+                height: 34px;
389
+                right: 0px;
390
+                top: 50%;
391
+                z-index: 999;
392
+                transform: translateY(-50%);
393
+            }
329 394
 
330
-        .el-pager li {
331
-            color: #fff;
332 395
         }
333 396
     }
334
-}
397
+
398
+
399
+    // .monitorContent {
400
+    //     margin-top: -6px;
401
+
402
+    //     .monitor {
403
+    //         box-sizing: border-box;
404
+    //         display: flex;
405
+    //         flex-direction: column;
406
+
407
+    //         background: rgba(0, 0, 0, 0.2);
408
+    //         padding: 10px;
409
+    //         width: 364px;
410
+    //         height: 25vh;
411
+    //         margin: 2vh auto;
412
+
413
+
414
+
415
+    //         // border: 1px solid red;
416
+
417
+    //         .interactclass {
418
+    //             display: flex;
419
+    //             justify-content: space-between;
420
+    //             width: 93.5%;
421
+    //             margin-left: 4.5%;
422
+
423
+    //             .room {
424
+
425
+
426
+    //                 position: relative;
427
+    //                 width: 155px;
428
+    //                 height: 8vh;
429
+    //                 padding: 0;
430
+    //                 margin: 0;
431
+    //                 // background: saddlebrown;
432
+    //                 // margin-top: 10px;
433
+    //                 cursor: pointer;
434
+
435
+    //                 // &:first-child {
436
+    //                 //     margin-right: 10px;
437
+    //                 // }
438
+
439
+    //                 span {
440
+    //                     position: absolute;
441
+    //                     top: 0;
442
+
443
+    //                     font-size: 11px;
444
+
445
+    //                     text-indent: 5px;
446
+    //                     padding-right: 5px;
447
+    //                     height: 18px;
448
+    //                     line-height: 17px;
449
+    //                     background-color: rgba(0, 0, 0, 0.38);
450
+    //                 }
451
+    //             }
452
+    //         }
453
+    //     }
454
+
455
+    //     .el-carousel__indicators--horizontal {
456
+    //         display: none;
457
+    //         bottom: 6%;
458
+    //     }
459
+
460
+    //     // .el-carousel__container {
461
+    //     //     height: 248px;
462
+    //     // }
463
+
464
+    //     // .el-carousel__indicators .el-carousel__indicators--horizontal {
465
+    //     //     display: none;
466
+    //     // }
467
+
468
+    //     .el-pagination {
469
+    //         justify-content: center;
470
+    //         --el-pagination-bg-color: 'tranparent';
471
+    //         --el-pagination-text-color: '#fff'
472
+    //     }
473
+
474
+    //     .el-pagination button {
475
+    //         color: #fff;
476
+    //     }
477
+
478
+    //     .el-pagination button:disabled {
479
+    //         background: none;
480
+    //         color: '#fff';
481
+    //     }
482
+
483
+    //     .el-pagination button:hover {
484
+    //         color: '#fff';
485
+    //     }
486
+
487
+    //     .el-pager li {
488
+    //         color: #fff;
489
+    //     }
490
+    // }
491
+}
492
+:deep(.el-carousel__container){
493
+    height: 100%;
494
+}

+ 171 - 147
src/components/CircleProgress.vue

@@ -1,209 +1,233 @@
1 1
 <template>
2
-    <div>
3
-        <div class="number" v-if="textPosition == 'top'">
4
-            <ul class="flex">
5
-                <!-- <ScrollNum v-for="(num, idx) of numArr" :key="idx" as="li" :i="num" :delay="idx + 1" /> -->
6
-                <vue-number-roll-plus
2
+  <div>
3
+    <div class="number" v-if="textPosition == 'top'">
4
+      <ul class="flex">
5
+        <!-- <ScrollNum v-for="(num, idx) of numArr" :key="idx" as="li" :i="num" :delay="idx + 1" /> -->
6
+        <!-- <vue-number-roll-plus
7 7
                     :number="targetValue"
8 8
                     background="transparent"
9 9
                     speed="1.4"
10 10
                     >
11
-                </vue-number-roll-plus>
12
-                <!-- <count-to :useEasing="true" style="font-size: 0.08rem;"  :startVal='1' :endVal='targetValue' :duration='2000'></count-to> -->
13
-                <span>个</span>
14
-            </ul>
15
-        </div>
11
+                </vue-number-roll-plus> -->
12
+        <countTo
13
+          :startVal="0"
14
+          :endVal="targetValue"
15
+          :decimals="0"
16
+          :duration="3000"
17
+        ></countTo>
18
+
19
+        <!-- <count-to :useEasing="true" style="font-size: 0.08rem;"  :startVal='1' :endVal='targetValue' :duration='2000'></count-to> -->
20
+        <span>个</span>
21
+      </ul>
16 22
     </div>
17
-    <div class="progress" :style="{ width, height }">
18
-        <svg viewBox="0 0 96 96" class="svg-circle-progress">
23
+  </div>
24
+  <div class="progress">
25
+    <!-- <svg viewBox="0 0 96 96" class="svg-circle-progress">
19 26
             <circle r="38" cx="48" cy="48" fill="none" stroke-miterlimit="20" stroke-width="4" class="svg-progress"
20 27
                 style="stroke-dasharray: 275, 279.602; stroke: rgba(255, 255, 255, 0.19)"></circle>
21 28
             <circle r="38" transform="rotate(180 48 48)" cx="48" cy="48" fill="none" stroke-miterlimit="20" stroke-width="4"
22 29
                 class="svg-progress" :style="`stroke-dasharray: 240, 279.602;stroke:${color};`"></circle>
23
-        </svg>
24
-        <div class="mask">
30
+        </svg> -->
31
+    <!-- <div class="mask">
25 32
             <slot></slot>
26
-        </div>
27
-    </div>
28
-
29
-    <div class="bottomNumber" v-if="textPosition == 'bottom'">
30
-        <ul class="flex" style="width: 70px;display: flex;align-items: center;justify-content: center;">
31
-            <!-- <ScrollNum v-for="(num, idx) of numArr" :key="idx" as="li" :i="num" :delay="idx + 1" /> -->
32
-            <div style="color: #fff;font-weight: 600;  ">
33
-                <vue-number-roll-plus
33
+        </div> -->
34
+    <img :src="l1" v-if="index == 0" alt="" srcset="" />
35
+    <img :src="l2" v-if="index == 1" alt="" srcset="" />
36
+    <img :src="l3" v-if="index == 2" alt="" srcset="" />
37
+    <img :src="l4" v-if="index == 3" alt="" srcset="" />
38
+  </div>
39
+
40
+  <div class="bottomNumber" v-if="textPosition == 'bottom'">
41
+    <ul class="flex">
42
+      <!-- <ScrollNum v-for="(num, idx) of numArr" :key="idx" as="li" :i="num" :delay="idx + 1" /> -->
43
+      <div style="color: #fff; font-weight: 600">
44
+        <!-- <vue-number-roll-plus
34 45
                     :number="targetValue"
35 46
                     background="transparent"
36 47
                     speed="1.4"
48
+            
49
+                    
37 50
                     >
38
-                </vue-number-roll-plus>
39
-
40
-                <!-- <count-to :useEasing="true" style="font-size: 0.08rem;"  :startVal='1' :endVal='targetValue' :duration='2000'></count-to> -->
41
-            </div>
42
-            <span style="color: antiquewhite;"   v-if="text.includes('课程')">(节)</span>
43
-            <span style="color: antiquewhite;" v-else>(人)</span>
44
-        </ul>
45
-
46
-    </div>
51
+                </vue-number-roll-plus> -->
52
+        <countTo
53
+          :startVal="0"
54
+          :endVal="targetValue"
55
+          :decimals="0"
56
+          :duration="3000"
57
+        ></countTo>
58
+
59
+        <!-- <count-to :useEasing="true" style="font-size: 0.08rem;"  :startVal='1' :endVal='targetValue' :duration='2000'></count-to> -->
60
+      </div>
61
+      <span style="color: antiquewhite" v-if="text.includes('课程')">(节)</span>
62
+      <span style="color: antiquewhite" v-else>(人)</span>
63
+    </ul>
64
+  </div>
47 65
 </template>
48
-<script setup lang="ts">
66
+<script setup >
67
+import VueNumberRollPlus from "vue3-number-roll-plus";
68
+import "vue3-number-roll-plus/main.css";
49 69
 
50
-import VueNumberRollPlus from "vue3-number-roll-plus"
51
-import "vue3-number-roll-plus/main.css"
52
-
53
-import { ref, computed, toRefs, watch, onMounted,reactive,  } from 'vue';
70
+import { ref, computed, toRefs, watch, onMounted, reactive } from "vue";
54 71
 // import ScrollNum from './roll.vue';
55
-import { CountTo } from 'vue3-count-to';
56
-
57
-
72
+import { CountTo } from "vue3-count-to";
58 73
 
59 74
 const props = defineProps({
60
-    targetValue: {
61
-        type: Number,
62
-        require: false,
63
-        default: -1,
64
-    },
65
-    color: {
66
-        type: String,
67
-        default: '#4c7cee',
68
-    },
69
-    text: {
70
-        type: String,
71
-        default: -1,
72
-    },
73
-    width: {
74
-        type: String,
75
-        default: '54.6px',
76
-    },
77
-    height: {
78
-        type: String,
79
-        default: '54.6px',
80
-    },
81
-    textPosition: {
82
-        type: String,
83
-        require: false,
84
-        default: 'top',
85
-    }
75
+  targetValue: {
76
+    type: Number,
77
+    require: false,
78
+    default: -1,
79
+  },
80
+  color: {
81
+    type: String,
82
+    default: "#4c7cee",
83
+  },
84
+  text: {
85
+    type: String,
86
+    default: '',
87
+  },
88
+  width: {
89
+    type: String,
90
+    default: "54.6px",
91
+  },
92
+  height: {
93
+    type: String,
94
+    default: "54.6px",
95
+  },
96
+  textPosition: {
97
+    type: String,
98
+    require: false,
99
+    default: "top",
100
+  },
101
+  index: {
102
+    type: Number,
103
+    require: false,
104
+    default: 0,
105
+  },
86 106
 });
87
-const { height, width, color, targetValue, textPosition,text } = toRefs(props);
107
+const { height, width, color, targetValue, textPosition, text } = toRefs(props);
88 108
 
89 109
 // let showProgress = ref<number>(0);
90 110
 const numArr = computed(() => {
91
-    const str = String(targetValue.value)
92
-    let arr = <any>[]
93
-    for (let i = 0; i < str.length; i++) {
94
-        arr.push(parseInt(str[i]))
95
-    }
96
-    return arr
97
-})
111
+  const str = String(targetValue.value);
112
+  let arr = [];
113
+  for (let i = 0; i < str.length; i++) {
114
+    arr.push(parseInt(str[i]));
115
+  }
116
+  return arr;
117
+});
98 118
 
99
-const progressValue = ref<number>(0);
100
-let timer = ref<any>(null)
119
+const progressValue = ref(0);
120
+let timer = ref(null);
101 121
 const circleValue = () => {
102
-
103
-
104
-    if (textPosition.value == "top") {
105
-        if (progressValue.value = 360) {
106
-            timer.value = null
107
-            return
108
-        }
109
-        (timer as any) = setInterval(() => {
110
-            progressValue.value += 5
111
-        }, 3000)
112
-    } else {
113
-        if (progressValue.value = 120) {
114
-            timer.value = null
115
-            return
116
-        }
117
-        (timer as any) = setInterval(() => {
118
-            progressValue.value += 5
119
-        }, 3000)
122
+  if (textPosition.value == "top") {
123
+    if ((progressValue.value = 360)) {
124
+      timer.value = null;
125
+      return;
120 126
     }
121
-}
122
-
123
-
127
+    (timer ) = setInterval(() => {
128
+      progressValue.value += 5;
129
+    }, 3000);
130
+  } else {
131
+    if ((progressValue.value = 120)) {
132
+      timer.value = null;
133
+      return;
134
+    }
135
+    (timer ) = setInterval(() => {
136
+      progressValue.value += 5;
137
+    }, 3000);
138
+  }
139
+};
124 140
 
125 141
 onMounted(() => {
126
-    circleValue()
127
-
128
- 
129
-})
142
+  circleValue();
143
+});
130 144
 
131 145
 // const progressValue = ref(0)
132 146
 watch(textPosition, (newValue) => {
133
-    console.log("监听一下现在的位置", newValue)
134
-    // progressValue.value = 360;
147
+  console.log("监听一下现在的位置", newValue);
148
+  // progressValue.value = 360;
135 149
 });
136
-
137
-
150
+const l1 = ref("./img/l1.png");
151
+const l2 = ref("./img/l2.png");
152
+const l3 = ref("./img/l3.png");
153
+const l4 = ref("./img/l4.png");
138 154
 </script>
139 155
 <style lang="scss" scoped>
140 156
 .progress {
141
-
157
+  display: flex;
158
+  flex-direction: column;
159
+
160
+  align-items: center;
161
+  justify-content: center;
162
+  text-align: center;
163
+  width: 56px;
164
+  height: 56px;
165
+  img {
166
+    width: 54px;
167
+    height: 54px;
168
+  }
169
+}
170
+.bottomNumber {
171
+  .flex {
172
+    width: 70px;
142 173
     display: flex;
143
-    flex-direction: column;
144
-
145 174
     align-items: center;
146 175
     justify-content: center;
147
-    text-align: center;
176
+  }
148 177
 }
149 178
 
150 179
 .svg-circle-progress {
151
-    position: relative;
152
-    transform: rotate(-90deg);
153
-
180
+  position: relative;
181
+  transform: rotate(-90deg);
154 182
 }
155 183
 
156 184
 .svg-progress {
157
-    stroke: #2196f3;
158
-    stroke-linecap: round;
159
-    transition: all 0.3s linear;
185
+  stroke: #2196f3;
186
+  stroke-linecap: round;
187
+  transition: all 0.3s linear;
160 188
 }
161 189
 
162 190
 .mask {
163
-    position: absolute;
164
-    margin-top: 5px;
191
+  position: absolute;
192
+  margin-top: 5px;
165 193
 }
166 194
 
167 195
 .number {
168
-    font-size: 14px;
169
-    font-weight: bold;
170
-    height: 28px;
171
-    // background-color: #2196f3;
172
-    margin-top: -8px;
173
-
174
-    span {
175
-        font-size: 12px;
176
-    }
196
+  font-size: 14px;
197
+  font-weight: bold;
198
+  height: 28px;
199
+  // background-color: #2196f3;
200
+  margin-top: -8px;
201
+
202
+  span {
203
+    font-size: 12px;
204
+  }
177 205
 }
178 206
 
179
-
180
-
181 207
 ul {
182
-    padding: 0;
183
-    margin: 0
208
+  padding: 0;
209
+  margin: 0;
184 210
 }
185 211
 
186 212
 .flex {
187
-    display: flex;
188
-    // border: 1px solid darkblue;
189
-    box-sizing: border-box;
190
-    // width: 100px;
191
-    align-items: center;
192
-    text-align: center;
193
-
194
-    span {
195
-        font-size: 12px;
196
-        margin-left: 4px;
197
-    }
213
+  display: flex;
214
+  // border: 1px solid darkblue;
215
+  box-sizing: border-box;
216
+  // width: 100px;
217
+  align-items: center;
218
+  text-align: center;
219
+
220
+  span {
221
+    font-size: 12px;
222
+    margin-left: 4px;
223
+  }
198 224
 }
199
-
200 225
 </style>
201 226
 <style lang="scss">
202
-.real-time-num{
203
-    // background-color: rgb(137, 80, 5) !important;
204
-    width: 9px!important;
205
-    height: 33px !important;
206
-    margin-left: 0px !important;
207
-
227
+.real-time-num {
228
+  // background-color: rgb(137, 80, 5) !important;
229
+  width: 9px !important;
230
+  height: 33px !important;
231
+  margin-left: 0px !important;
208 232
 }
209 233
 </style>

+ 17 - 12
src/components/Header.vue

@@ -18,7 +18,7 @@
18 18
         <img class="weather-icon" :src="sun" alt="" />
19 19
         <div class="info">
20 20
           <div class="text">{{ weatherInfo.text }}</div>
21
-          <div class="temp">{{ weatherInfo.temp }} <span>°C</span></div>
21
+          <div class="temp">{{ weatherInfo.temperature }} <span>°C</span></div>
22 22
         </div>
23 23
       </div>
24 24
     </div>
@@ -35,7 +35,7 @@ onMounted(() => {
35 35
   starttimeInterval();
36 36
 
37 37
   // 获取天气信息
38
-  // getWeatherInfo();
38
+  getWeatherInfo();
39 39
 });
40 40
 
41 41
 // 获取当前年月日计算属性
@@ -60,15 +60,13 @@ const starttimeInterval = () => {
60 60
 let weatherInfo = ref({});
61 61
 
62 62
 const getWeatherInfo = (latitude, Longitude) => {
63
-  const key = "UFwchof57SfCeYaiWXj1F2XBx0w8hKtB";
64
-  //https://api.map.baidu.com/weather/v1/?location=${latitude},${Longitude}&data_type=all&ak=${key}
63
+  const key = "SBMShMRLbnu49svK0";
65 64
   axios
66 65
     .get(
67
-      `https://api.map.baidu.com/weather/v1/?district_id=222405&data_type=all&ak=${key}`
66
+      `https://api.seniverse.com/v3/weather/now.json?key=${key}&location=zhumadian&language=zh-Hans&unit=c`
68 67
     )
69 68
     .then((res) => {
70
-      weatherInfo.value = res.data.result.now;
71
-      console.log("天气信息", res.data.result.now);
69
+      weatherInfo.value = res.data.results[0].now;
72 70
     });
73 71
 };
74 72
 
@@ -94,6 +92,8 @@ const sun = ref("./img/sun.png");
94 92
   background-size: cover !important;
95 93
   background-position: center !important;
96 94
   background-repeat: no-repeat !important;
95
+  padding: 5px;
96
+  box-sizing: border-box;
97 97
   .logo {
98 98
     width: 162px;
99 99
     height: 50px;
@@ -118,15 +118,16 @@ const sun = ref("./img/sun.png");
118 118
 
119 119
   .weather {
120 120
     position: absolute;
121
-    right: 10px;
121
+    right: 40px;
122 122
     top: 50%;
123 123
     transform: translateY(-50%);
124 124
     display: flex;
125 125
     align-items: center;
126
-    height: 38px;
126
+    // height: 100%;
127
+    
127 128
     .left-time {
128 129
         margin-right: 12px;
129
-        height: 38px;
130
+        // height: 100%;
130 131
       .time {
131 132
         color: #bfdfff;
132 133
         text-shadow: 0px 1px 3px rgba(5, 12, 25, 0.54);
@@ -150,7 +151,7 @@ const sun = ref("./img/sun.png");
150 151
     .right-weatherInfo {
151 152
       display: flex;
152 153
       align-items: center;
153
-      height: 38px;
154
+      // height: 100%;
154 155
       .weather-icon {
155 156
         width: 30px;
156 157
         height: 30px;
@@ -159,6 +160,10 @@ const sun = ref("./img/sun.png");
159 160
         flex: 1;
160 161
         margin-left: 12px;
161 162
         height: 38px;
163
+        display: flex;
164
+        height: 100%;
165
+        flex-direction: column;
166
+        justify-content: space-between;
162 167
         .text {
163 168
           color: #bfdfff;
164 169
 
@@ -177,7 +182,7 @@ const sun = ref("./img/sun.png");
177 182
           font-family: PangMenZhengDao;
178 183
           font-size: 16px;
179 184
           font-style: normal;
180
-          font-weight: 400;
185
+          font-weight: 600;
181 186
           line-height: normal;
182 187
           span {
183 188
             color: #bfdfff;

+ 21 - 11
src/components/Login.vue

@@ -1,11 +1,20 @@
1
+<!--
2
+ * @Author: zy1125 1515706227@qq.com
3
+ * @Date: 2023-10-18 14:19:17
4
+ * @LastEditors: zy1125 1515706227@qq.com
5
+ * @LastEditTime: 2023-12-20 18:49:48
6
+ * @FilePath: \v3_yyz\src\components\Login.vue
7
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
8
+-->
1 9
 <template>
2 10
     <div class="bg">
3
-      <div style="width: 100%;height: 50px;position: relative;top: 85vh ;z-index: 999;display: flex;align-items: center;justify-content: center;">
4
-        <div class="loginbutton"  @click="login">
5
-            <img :src="loginimg" alt="" style="">
6
-            <span> 登录</span>
11
+        <div
12
+            style="width: 100%;height: 50px;position: relative;top: 85vh ;z-index: 999;display: flex;align-items: center;justify-content: center;">
13
+            <div class="loginbutton" @click="login">
14
+                <img :src="loginimg" alt="" style="">
15
+                <span> 登录</span>
16
+            </div>
7 17
         </div>
8
-      </div>
9 18
 
10 19
     </div>
11 20
 </template>
@@ -17,15 +26,16 @@ import { useRouter } from 'vue-router'
17 26
 // import login from '/''
18 27
 export default ({
19 28
     setup() {
20
-         //首先在setup中定义
29
+        //首先在setup中定义
21 30
         const router = useRouter()
22 31
 
23 32
         const loginimg = ref("./img/login.png")
24 33
         const login = function () {
25
-            // window.open("https://cas.huanghuai.edu.cn/cas/")
26
-            //跳转路由到首页
27
-            router.push('/')
28
-            
34
+            // 跳转到指定的 URL 地址
35
+            location.href = "https://cas.huanghuai.edu.cn/cas/?service=https://weizhi.huanghuai.edu.cn/xyyzioc/#/"
36
+           
37
+    
38
+
29 39
         }
30 40
         return {
31 41
             loginimg,
@@ -38,7 +48,7 @@ export default ({
38 48
 
39 49
 <style scoped lang="scss">
40 50
 .bg {
41
- 
51
+
42 52
     position: absolute;
43 53
     box-sizing: border-box;
44 54
     // height: 200vw;

+ 227 - 0
src/components/ScrollView.vue

@@ -0,0 +1,227 @@
1
+<template>
2
+  <div
3
+    ref="scrollBox"
4
+    :class="`infinite-scroll-component-box-${direction}`"
5
+    :style="{
6
+      '--speed-': `${speed}s`,
7
+      mask: mask? `linear-gradient(${degList[direction]}, #000 70%, transparent)`: '',
8
+    }"
9
+  >
10
+    <div class="scroll-content" ref="scrollContent">
11
+      <template v-if="Array.isArray(content)">
12
+        <div
13
+          v-for="(item, index) in content"
14
+          :key="'arrayFirst' + index"
15
+          class="loop-item"
16
+        >
17
+          <slot :item="item" :index="index"></slot>
18
+        </div>
19
+        <template v-if="loop">
20
+          <div
21
+            v-for="(item, index) in content"
22
+            class="loop-item"
23
+            :key="'arrayCopy' + index"
24
+          >
25
+            <slot :item="item" :index="index"></slot>
26
+          </div>
27
+        </template>
28
+      </template>
29
+      <template v-else>
30
+        <slot></slot>
31
+        <slot v-if="loop"></slot>
32
+      </template>
33
+    </div>
34
+  </div>
35
+</template>
36
+
37
+<script setup>
38
+import { onMounted, ref, nextTick, defineProps,watch } from 'vue'
39
+
40
+const props = defineProps({
41
+  direction: {
42
+    type: String,
43
+    default: 'left',
44
+    validate: (value) =>
45
+      ['left', 'top', 'right', 'bottom'].findIndex(value) !== -1,
46
+  },
47
+
48
+  content: {
49
+    type: [String, Array],
50
+    default: '',
51
+  },
52
+
53
+  mask: {
54
+    type: Boolean,
55
+    default: true,
56
+  },
57
+
58
+  // 速率倍速,越大越快,默认值20
59
+  speedRate: {
60
+    type: Number,
61
+    default: 20,
62
+  },
63
+})
64
+
65
+onMounted(() => {
66
+  init()
67
+})
68
+
69
+let degList = {
70
+  left: '90deg',
71
+  top: '180deg',
72
+  right: '270deg',
73
+  bottom: '0deg',
74
+}
75
+// 滚动速度
76
+let speed = ref(0)
77
+// 是否需要无限滚动
78
+let loop = ref(false)
79
+// 容器ref
80
+let scrollBox = ref()
81
+
82
+watch(
83
+  () => props.content,
84
+  () => {
85
+    loop.value = false
86
+    init()
87
+  },
88
+  { deep: true }
89
+)
90
+
91
+// 初始化速度,以及是否需要无限滚动
92
+const init = async () => {
93
+  await nextTick()
94
+  if (props.direction === 'left' || props.direction === 'right') {
95
+    // 可视区域的宽度
96
+    const boxWidth = scrollBox.value.offsetWidth
97
+    // 滚动内容的宽度
98
+    const itemWidth =
99
+      scrollBox.value.getElementsByClassName('scroll-content')[0].offsetWidth
100
+    if (itemWidth >= boxWidth) {
101
+      loop.value = true
102
+      speed.value = itemWidth / props.speedRate
103
+    } else {
104
+      speed.value = 0
105
+      scrollBox.value.getElementsByClassName(
106
+        'scroll-content'
107
+      )[0].style.transform = 'translateX(0)'
108
+      loop.value = false
109
+    }
110
+  } else {
111
+    const boxHeight = scrollBox.value.offsetHeight
112
+    const itemHeight =
113
+      scrollBox.value.getElementsByClassName('scroll-content')[0].offsetHeight
114
+    if (itemHeight >= boxHeight) {
115
+      loop.value = true
116
+      speed.value = itemHeight / props.speedRate
117
+    } else {
118
+      speed.value = 0
119
+      scrollBox.value.getElementsByClassName(
120
+        'scroll-content'
121
+      )[0].style.transform = 'translateY(0)'
122
+      loop.value = false
123
+    }
124
+  }
125
+}
126
+</script>
127
+
128
+<style lang="scss" scoped>
129
+.infinite-scroll-component-box-left,
130
+.infinite-scroll-component-box-right {
131
+  width: 100%;
132
+  height: 100%;
133
+  overflow: scroll;
134
+  position: relative;
135
+
136
+  .scroll-content {
137
+    position: absolute;
138
+    left: 0;
139
+    display: flex;
140
+    align-items: center;
141
+    justify-content: center;
142
+
143
+    &:hover {
144
+      animation-play-state: paused;
145
+    }
146
+  }
147
+}
148
+.infinite-scroll-component-box-top,
149
+.infinite-scroll-component-box-bottom {
150
+  width: 100%;
151
+  height: 100%;
152
+  overflow: scroll;
153
+  position: relative;
154
+
155
+  .scroll-content {
156
+    width: 100%;
157
+    position: absolute;
158
+    left: 0;
159
+    display: flex;
160
+    align-items: center;
161
+    justify-content: center;
162
+
163
+    &:hover {
164
+      animation-play-state: paused;
165
+    }
166
+
167
+    .loop-item {
168
+      width: 100%;
169
+    }
170
+  }
171
+}
172
+.infinite-scroll-component-box-left .scroll-content {
173
+  flex-direction: row;
174
+  animation: var(--speed-) move-left linear infinite;
175
+
176
+  @keyframes move-left {
177
+    0% {
178
+      transform: translateX(0);
179
+    }
180
+    100% {
181
+      transform: translateX(-50%);
182
+    }
183
+  }
184
+}
185
+
186
+.infinite-scroll-component-box-right .scroll-content {
187
+  flex-direction: row-reverse;
188
+  animation: var(--speed-) move-right linear infinite;
189
+
190
+  @keyframes move-right {
191
+    0% {
192
+      transform: translateX(-50%);
193
+    }
194
+    100% {
195
+      transform: translateX(0);
196
+    }
197
+  }
198
+}
199
+
200
+.infinite-scroll-component-box-top .scroll-content {
201
+  flex-direction: column;
202
+  animation: var(--speed-) move-top linear infinite;
203
+
204
+  @keyframes move-top {
205
+    0% {
206
+      transform: translateY(0);
207
+    }
208
+    100% {
209
+      transform: translateY(-50%);
210
+    }
211
+  }
212
+}
213
+
214
+.infinite-scroll-component-box-bottom .scroll-content {
215
+  flex-direction: column-reverse;
216
+  animation: var(--speed-) move-bottom linear infinite;
217
+
218
+  @keyframes move-bottom {
219
+    0% {
220
+      transform: translateY(-50%);
221
+    }
222
+    100% {
223
+      transform: translateY(0);
224
+    }
225
+  }
226
+}
227
+</style>

+ 39 - 0
src/components/Tag.vue

@@ -0,0 +1,39 @@
1
+<template>
2
+  <div class="tag" :class="{ free: text == -1, useing: text == 1 }">
3
+    {{ text == 1?'在用':'空闲' }}
4
+  </div>
5
+</template>
6
+
7
+<script setup>
8
+const props = defineProps({
9
+  text: {
10
+    type: Number,
11
+    default: 1,
12
+  },
13
+});
14
+</script>
15
+
16
+<style lang="scss" scoped>
17
+.tag {
18
+  padding: 0px 6px;
19
+  font-size: 12px;
20
+  font-style: normal;
21
+  font-weight: 400;
22
+  height: 18px;
23
+  line-height: 18px;
24
+}
25
+.free {
26
+  color: #e5be20;
27
+  border-radius: 2px;
28
+  border: 1px solid #e5be20;
29
+  background: rgba(229, 190, 32, 0.17);
30
+}
31
+.useing {
32
+  color: #1BCDFF;
33
+  border-radius: 2px;
34
+  border: 1px solid #1bcdff;
35
+  background: rgba(27, 205, 255, 0.17);
36
+ 
37
+  
38
+}
39
+</style>

+ 17 - 16
src/components/UeVideo.vue

@@ -9,9 +9,9 @@
9 9
 
10 10
 <script>
11 11
 import { onMounted, ref } from "vue";
12
-import {
13
-  initLoad
14
-} from "../webrtcVideo.js";
12
+// import {
13
+//   initLoad
14
+// } from "../webrtcVideo.js";
15 15
 import { ElButton, ElInput } from "element-plus";
16 16
 import Home from './home.vue'
17 17
 export default {
@@ -35,18 +35,18 @@ export default {
35 35
     }
36 36
     onMounted(() => {
37 37
 
38
-      videoInstance = initLoad({
39
-        context,
40
-        serverUrl: window.g.UE_IP,
41
-        autoConnection: true,
42
-        showPlayOverlay: true,
43
-        qualityControl: true,
44
-        inputOptions: {
45
-          controlScheme: 1, // 鼠标:0是锁定,1是滑过
46
-          suppressBrowserKeys: false,
47
-        },
48
-      });
49
-      console.log("video.videoInstance", videoInstance);
38
+      // videoInstance = initLoad({
39
+      //   context,
40
+      //   serverUrl: window.g.UE_IP,
41
+      //   autoConnection: true,
42
+      //   showPlayOverlay: true,
43
+      //   qualityControl: true,
44
+      //   inputOptions: {
45
+      //     controlScheme: 1, // 鼠标:0是锁定,1是滑过
46
+      //     suppressBrowserKeys: false,
47
+      //   },
48
+      // });
49
+      // console.log("video.videoInstance", videoInstance);
50 50
 
51 51
       // let search = window.location.href.split("?")[1];
52 52
 
@@ -86,10 +86,11 @@ export default {
86 86
 </script>
87 87
 <style scoped>
88 88
 #player {
89
-  width: 100%;
89
+   width: 100%;
90 90
   height: 100%;
91 91
   position: absolute;
92 92
   overflow: hidden;
93
+  /* background: url("./img/test_bg.png"); */
93 94
 }
94 95
 
95 96
 .hidden {

+ 120 - 0
src/components/VideoJs.vue

@@ -0,0 +1,120 @@
1
+<template>
2
+  <div class="videoPlay">
3
+    <video ref="m3u8_video" preload="auto" class="video-js  vjs-big-play-centered" controls>
4
+      <source :src="shiping2" />
5
+    </video>
6
+  </div>
7
+</template>
8
+<script lang="ts" setup>
9
+import { getDevice, getVideoUrl,getvideo } from '../request/api';
10
+import { nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
11
+import videojs, { VideoJsPlayer } from "video.js";
12
+import "video.js/dist/video-js.css";
13
+import zh from "video.js/dist/lang/zh-CN.json";
14
+const props = withDefaults(
15
+  defineProps<{
16
+    videoSrc: string;
17
+    autoPlay?: boolean;
18
+  }>(),
19
+  { autoPlay: false }
20
+);
21
+
22
+const shiping2=ref('')
23
+
24
+const m3u8_video = ref();
25
+let player: VideoJsPlayer;
26
+const initPlay = async () => {
27
+  videojs.addLanguage("zh-CN", zh);
28
+  await nextTick();
29
+  const options = {
30
+    muted: true,
31
+    controls: false,
32
+    autoplay: true,
33
+    loop: true,
34
+    language: "zh-CN",
35
+    techOrder: ["html5"],
36
+  };
37
+
38
+     //视频转码
39
+     getvideo({
40
+          "rtsp": props.videoSrc,
41
+          }).then((res)=>{
42
+              console.log('sadnuq21',res.data);
43
+              console.log('sadnuq2121',res.data.httpFlv);
44
+              // shiping2.value='http://kbs-dokdo.gscdn.com/dokdo_300/_definst_/dokdo_300.stream/playlist.m3u8'
45
+              // shiping2.value=res.data.httpFlv
46
+              shiping2.value="https://weizhi.huanghuai.edu.cn/jk/hls/c7a1e790-b3bc-4092-a975-40d0e35d54c2.m3u8"
47
+            
48
+
49
+              setTimeout(() => {
50
+            player = videojs(m3u8_video.value, options, () => {
51
+    videojs.log("播放器已经准备好了!");
52
+    if (props.autoPlay && props.videoSrc) {
53
+      player.play();
54
+    }
55
+    player.on("ended", () => {
56
+      videojs.log("播放结束了!");
57
+    });
58
+    player.on("error", () => {
59
+      videojs.log("播放器解析出错!");
60
+    });
61
+  });
62
+          }, 300);
63
+
64
+          console.log('asiohdahsiod',shiping2.value);
65
+          })
66
+
67
+        
68
+          
69
+
70
+};
71
+onMounted(() => {
72
+  console.log('shidaodx', shiping2.value);
73
+
74
+
75
+       
76
+
77
+  initPlay();
78
+});
79
+//直接改变路径测试
80
+watch(
81
+  () => props.videoSrc,
82
+  () => {
83
+    player.pause();
84
+    player.src(props.videoSrc);
85
+    player.load();
86
+    if (props.videoSrc) {
87
+      player.play();
88
+    }
89
+  }
90
+);
91
+
92
+
93
+
94
+
95
+
96
+onBeforeUnmount(() => {
97
+  player?.dispose();
98
+});
99
+
100
+
101
+
102
+</script>
103
+<style lang="scss" scoped>
104
+.videoPlay {
105
+  width: 100%;
106
+  height: 100%;
107
+
108
+  .video-js {
109
+    height: 100%;
110
+    width: 100%;
111
+    // object-fit: fill;
112
+  }
113
+}
114
+
115
+:deep(.vjs-tech) {
116
+  object-fit: fill;
117
+}
118
+</style>
119
+     
120
+  

+ 527 - 429
src/components/home.vue

@@ -1,449 +1,547 @@
1 1
 <template>
2
-    <div class="container" :style="{background: `url(${backgroundImg})` }">
3
-        <img src="bgShadow" alt="" style="position: absolute;top: 0px;left: 0px;width: 100%;height: 100%;">
4
-        
5
-        <Header />
6
-        <el-container>
7
-            <Left ref="childRef" @childMethod="childMethod"></Left>
8
-            <right></right>
9
-            <div ref="linedomRef" style="width:1px;height:1px;position: absolute;top:0px;left:0px;z-index: 228;background:#ccc;">
10
-            </div>
11
-
12
-        </el-container>
13
-        <div ref="popoverRef" id="popoverRef" class="modelInfo">
14
-            <div v-if="orientation=='Left' || orientation=='Right'" class="model-detail"  >
15
-                <div v-for="item in classDeviceInfo" class="listbox" >
16
-                    
17
-                    <div class="list">
18
-                    {{ item[0].value }}
19
-                    </div>
20
-                    <div   v-for="item2 in item" class="listtitle" >
21
-                        
22
-                        <div  v-if="item2.name!=='设备名称'" style="height: 30px;display: flex;align-items: center;">
23
-                            <span class="name"> {{ item2.name }} :</span>
24
-                       <span v-if="item2.value!='在线'" class="value">{{ item2.value }}</span>
25
-                       <span v-else style=" font-family: 100; margin-left: 5px;color: #10C383;">{{ item2.value }}</span>
26
-                        </div>
27
-                    </div>
28
-                </div>
29
-            </div>
30
-            <div v-else class="model-detail2"  >
31
-                <div style="display: flex;">
32
-                    <div v-for="item in classDeviceInfo" class="listbox2"  >
33
-                        
34
-                        <div class="list" style="text-align: center;width: 100%;">
35
-                        {{ item[0].value }}
36
-                        </div>
37
-                        
38
-                        <div class="listtitlebox">
39
-                                <div   v-for="item2 in item" class="listtitle" >
40
-                            
41
-                                
42
-                            
43
-                                <div v-if="item2.name!=='设备名称'">
44
-                                    <span class="name"> {{ item2.name }} :</span>
45
-                                <span v-if="item2.value!='在线'" class="value" style="font-weight: 400;">{{ item2.value }}</span>
46
-                                <span v-else style=" font-family: 100; margin-left: 5px;color: #10C383;">{{ item2.value }}</span>
47
-                                </div>
48
-                         
49
-                            </div>
50
-                        </div>
51
-                        
52
-                    </div>
53
-                </div>
2
+  <!-- <div class="container" > -->
3
+  <Header />
4
+  <!-- <el-container> -->
5
+  <Left ref="childRef" @childMethod="childMethod"></Left>
6
+  <right></right>
7
+  <div
8
+    ref="linedomRef"
9
+    style="
10
+      width: 1px;
11
+      height: 1px;
12
+      position: absolute;
13
+      z-index: 228;
14
+      background: #ccc;
15
+    "
16
+  ></div>
17
+
18
+  <!-- </el-container> -->
19
+  <div ref="popoverRef" id="popoverRef" class="modelInfo">
20
+    <div
21
+      v-if="orientation == 'Left' || orientation == 'Right'"
22
+      class="model-detail"
23
+    >
24
+      <div v-for="item in classDeviceInfo" class="listbox">
25
+        <div class="list">
26
+          {{ item[0].value }}
27
+        </div>
28
+        <div v-for="item2 in item" class="listtitle">
29
+          <div
30
+            v-if="item2.name !== '设备名称'"
31
+            style="height: 30px; display: flex; align-items: center"
32
+          >
33
+            <span class="name"> {{ item2.name }} :</span>
34
+            <span v-if="item2.value != '在线'" class="value">{{
35
+              item2.value
36
+            }}</span>
37
+            <span
38
+              v-else
39
+              style="font-family: 100; margin-left: 5px; color: #10c383"
40
+              >{{ item2.value }}</span
41
+            >
42
+          </div>
43
+        </div>
44
+      </div>
45
+    </div>
46
+    <div v-else class="model-detail2">
47
+      <div style="display: flex">
48
+        <div v-for="item in classDeviceInfo" class="listbox2">
49
+          <div class="list" style="text-align: center; width: 100%">
50
+            {{ item[0].value }}
51
+          </div>
52
+
53
+          <div class="listtitlebox">
54
+            <div v-for="item2 in item" class="listtitle">
55
+              <div v-if="item2.name !== '设备名称'">
56
+                <span class="name"> {{ item2.name }} :</span>
57
+                <span
58
+                  v-if="item2.value != '在线'"
59
+                  class="value"
60
+                  style="font-weight: 400"
61
+                  >{{ item2.value }}</span
62
+                >
63
+                <span
64
+                  v-else
65
+                  style="font-family: 100; margin-left: 5px; color: #10c383"
66
+                  >{{ item2.value }}</span
67
+                >
68
+              </div>
54 69
             </div>
70
+          </div>
55 71
         </div>
56
-
57
-
58
-        <!-- 视频播放弹框 -->
59
-        <el-dialog class="videoDialog" v-model="videoDialog" :title="titleDialog" width="62%"  align="center" top="7%">
60
-            <videoPlay v-bind="videoData" @play="onPlay" />
61
-        </el-dialog>
72
+      </div>
62 73
     </div>
74
+  </div>
75
+
76
+  <!-- 视频播放弹框 -->
77
+  <el-dialog
78
+    class="videoDialog"
79
+    v-model="videoDialog"
80
+    :title="titleDialog"
81
+    width="62%"
82
+    align="center"
83
+    top="7%"
84
+    style="padding: 0"
85
+  >
86
+    <videoPlay v-bind="videoData" @play="onPlay" />
87
+  </el-dialog>
88
+
89
+  <el-dialog
90
+    class="deviceDialog"
91
+    v-model="deviceDialog"
92
+    title="设备明细"
93
+    width="40%"
94
+    align="left"
95
+    top="30vh"
96
+  
97
+    style="z-index: 2015;background: rgba(0, 0, 0, 0) ;padding:0px;"
98
+  >
99
+    <el-table
100
+      :data="deviceDetailsList"
101
+      border
102
+      height="350px"
103
+      style="width: 100%; background: rgba(0, 0, 0, 0)"
104
+    >
105
+      <el-table-column
106
+        align="center"
107
+        type="index"
108
+        label="序号"
109
+      ></el-table-column>
110
+      <el-table-column
111
+        align="center"
112
+        prop="device_name"
113
+        label="设备名称"
114
+      ></el-table-column>
115
+      <el-table-column align="center" prop="heart_status" label="设备状态">
116
+        <template #default="scope">
117
+          <div
118
+            :class="{
119
+              normal: scope.row.status == '正常',
120
+              error: scope.row.status == '异常',
121
+            }"
122
+          >
123
+            <div>{{ scope.row.heart_status }}</div>
124
+          </div>
125
+        </template>
126
+      </el-table-column>
127
+      <el-table-column label="操作" align="center">
128
+        <template #default="scope">
129
+          <el-button type="primary" size="mini" @click="lookDetails(scope.row)"
130
+            >详情</el-button
131
+          >
132
+        </template>
133
+      </el-table-column>
134
+    </el-table>
135
+    <el-dialog
136
+      custom-class="deviceDetailsDialog"
137
+      v-model="isShowDeviceDetails"
138
+      title="详情"
139
+      width="15%"
140
+      align="left"
141
+      top="40vh"
142
+      style="z-index: 2019; background: rgba(0, 0, 0, 0);padding:0px"
143
+      :modal="false"
144
+    >
145
+      <div class="details-container">
146
+        <div>设备名称:{{ deviceDetails.device_name }}</div>
147
+        <div>设备状态:<span :class="{ online: true }">在线</span></div>
148
+        <div>位置:{{ deviceDetails.teach_name }}</div>
149
+        <div>型号:{{ deviceDetails.device_type_name }}</div>
150
+        <!-- <div>质保期:5年</div> -->
151
+      </div>
152
+    </el-dialog>
153
+  </el-dialog>
154
+  <!-- </div> -->
63 155
 </template>
64 156
 
65 157
 <script>
66
-import { reactive, onMounted, ref, toRefs, onBeforeUnmount, getCurrentInstance, nextTick,computed,onUnmounted } from 'vue';
67
-import { ElScrollbar, ElPagination, ElDialog, ElCarousel, ElCarouselItem, ElMessage } from "element-plus";
158
+import {
159
+  reactive,
160
+  onMounted,
161
+  ref,
162
+  toRefs,
163
+  onBeforeUnmount,
164
+  getCurrentInstance,
165
+  nextTick,
166
+} from "vue";
167
+import {
168
+  ElScrollbar,
169
+  ElPagination,
170
+  ElDialog,
171
+  ElCarousel,
172
+  ElCarouselItem,
173
+  ElMessage,
174
+} from "element-plus";
68 175
 import "vue3-video-play/dist/style.css";
69
-import { addResponseEventListener } from "../webrtcVideo.js";
176
+// import { addResponseEventListener } from "../webrtcVideo.js";
70 177
 import { videoPlay } from "vue3-video-play";
71
-import CircleProgress from './CircleProgress.vue';
72
-import Left from './left.vue'
73
-import Right from './right.vue'
74
-import Login from './Login.vue'
75
-import LeaderLine from "../../public/js/leaderline.js"
76
-import { getClassDetail } from '../request/api.js'
77
-import {
78
-    callUIInteraction,
79
-} from "../webrtcVideo";
80
-
81
-import { getVideoUrl } from '../request/api';
82
-
83
-import Header from './Header.vue';
84
-
85
-
86
-export default ({
87
-    name: 'Histogram',
88
-    components: { Login, Left, Right, ElScrollbar, ElPagination, ElDialog, videoPlay, ElCarousel, ElCarouselItem, CircleProgress,Header },
89
-  
90
-    setup() {
91
-        const titleDialog = ref("")
92
-        const orientation = ref("")
93
-        // 视频数据
94
-        const videoData = reactive({
95
-            width: "100%", //播放器高度
96
-            height: "66.5vh", //播放器高度
97
-            color: "red", //主题色
98
-            title: "互动教室", //视频名称
99
-            // src: "https://cdn.jsdelivr.net/gh/xdlumia/files/video-play/IronMan.mp4", //视频源
100
-            type: 'm3u8',
101
-            src: '',
102
-            muted: false, //静音
103
-            webFullScreen: false,
104
-            // speedRate: ["0.75", "1.0", "1.25", "1.5", "2.0"], //播放倍速
105
-            autoPlay: false, //自动播放
106
-            loop: false, //循环播放
107
-            mirror: false, //镜像画面
108
-            ligthOff: false, //关灯模式
109
-            volume: 0.3, //默认音量大小
110
-            control: true, //是否显示控制
111
-            currentTime: 0,//跳转到固定播放时间
112
-            controlBtns: [
113
-                "audioTrack",
114
-                "quality",
115
-                "speedRate",
116
-                "volume",
117
-                "setting",
118
-                "pip",
119
-                "pageFullScreen",
120
-                "fullScreen",
121
-            ], //显示所有按钮,
122
-        });
123
-        // 物联设备类型统计
124
-        const interDevice = ref([])
125
-        // 位置弹框
126
-        let centerDialogVisible = ref(false)
127
-        // 获取点击点数据
128
-        let clickData = reactive({})
129
-        const triggerRef = ref({
130
-            getBoundingClientRect() {
131
-                // console.log("positon----方法返回元素的大小及其相对于视口的位置", position.value)
132
-                return position.value
133
-            },
134
-        })
135
-        const position = ref({
136
-            top: 0,
137
-            left: 0,
138
-            bottom: 0,
139
-            right: 0,
140
-        })
141
-        // 点击某个模型跟随移动
142
-        const mousemoveHandler = (x, y) => {
143
-            position.value = DOMRect.fromRect({
144
-                width: 0,
145
-                height: 0,
146
-                x: x,
147
-                y: y,
148
-            })
178
+import CircleProgress from "./CircleProgress.vue";
179
+import Left from "./left.vue";
180
+import Right from "./right.vue";
181
+import Login from "./Login.vue";
182
+import LeaderLine from "../../public/js/leaderline.js";
183
+import { getClassDetail } from "../request/api.js";
184
+import Header from "./Header.vue";
185
+// import {
186
+//     callUIInteraction,
187
+// } from "../webrtcVideo";
188
+
189
+import { getVideoUrl, equipmentDetailsApi } from "../request/api";
190
+
191
+export default {
192
+  name: "Histogram",
193
+  components: {
194
+    Login,
195
+    Left,
196
+    Right,
197
+    ElScrollbar,
198
+    ElPagination,
199
+    ElDialog,
200
+    videoPlay,
201
+    ElCarousel,
202
+    ElCarouselItem,
203
+    CircleProgress,
204
+    Header,
205
+  },
206
+
207
+  setup() {
208
+    const titleDialog = ref("");
209
+    const orientation = ref("");
210
+    // 视频数据
211
+    const videoData = reactive({
212
+      width: "100%", //播放器高度
213
+      height: "66.5vh", //播放器高度
214
+      color: "red", //主题色
215
+      title: "互动教室", //视频名称
216
+      // src: "https://cdn.jsdelivr.net/gh/xdlumia/files/video-play/IronMan.mp4", //视频源
217
+      type: "m3u8",
218
+      src: "",
219
+      muted: false, //静音
220
+      webFullScreen: false,
221
+      // speedRate: ["0.75", "1.0", "1.25", "1.5", "2.0"], //播放倍速
222
+      autoPlay: false, //自动播放
223
+      loop: false, //循环播放
224
+      mirror: false, //镜像画面
225
+      ligthOff: false, //关灯模式
226
+      volume: 0.3, //默认音量大小
227
+      control: true, //是否显示控制
228
+      currentTime: 0, //跳转到固定播放时间
229
+      controlBtns: [
230
+        "audioTrack",
231
+        "quality",
232
+        "speedRate",
233
+        "volume",
234
+        "setting",
235
+        "pip",
236
+        "pageFullScreen",
237
+        "fullScreen",
238
+      ], //显示所有按钮,
239
+    });
240
+    // 物联设备类型统计
241
+    const interDevice = ref([]);
242
+    // 位置弹框
243
+    let centerDialogVisible = ref(false);
244
+    // 获取点击点数据
245
+    let clickData = reactive({});
246
+    const triggerRef = ref({
247
+      getBoundingClientRect() {
248
+        // console.log("positon----方法返回元素的大小及其相对于视口的位置", position.value)
249
+        return position.value;
250
+      },
251
+    });
252
+    const position = ref({
253
+      top: 0,
254
+      left: 0,
255
+      bottom: 0,
256
+      right: 0,
257
+    });
258
+    // 点击某个模型跟随移动
259
+    const mousemoveHandler = (x, y) => {
260
+      position.value = DOMRect.fromRect({
261
+        width: 0,
262
+        height: 0,
263
+        x: x,
264
+        y: y,
265
+      });
266
+    };
267
+
268
+    //视频播放弹框
269
+    const videoDialog = ref(false);
270
+
271
+    // 播放视频
272
+    const lookVideo = function () {
273
+      videoDialog.value = true;
274
+      console.log("点击了播放视频", 12312312);
275
+    };
276
+    // 播放视频
277
+    const onPlay = function () {
278
+      console.log("播放视频");
279
+    };
280
+
281
+    const linedomRef = ref(null);
282
+    let lineContainer = ref(null);
283
+    const popoverRef = ref(null);
284
+    const lineStyleOption = ref({
285
+      startPlug: "disc",
286
+      endPlug: "disc",
287
+      dash: true,
288
+      color: "#2AC367",
289
+      startPlugColor: "#fff",
290
+      startPlugOutlineColor: "#2AC367",
291
+      endPlugColor: "#fff", // translucent
292
+      endPlugOutlineColor: "#2AC367",
293
+      size: 3,
294
+      startPlugSize: 1.5,
295
+      startPlugOutlineSize: 2,
296
+      endPlugSize: 1.5,
297
+      endPlugOutlineSize: 2,
298
+    });
299
+    const classDeviceInfo = ref([]);
300
+    const classDataLoaded = ref(false);
301
+    // const classDevice = async function (deviceCode) {
302
+    //     let res = await getClassDetail(deviceCode)
303
+    //     console.log("获取到了当前点击物体得详情", res)
304
+    //     classDeviceInfo.value = res.data;
305
+    //     classDataLoaded.value = true
306
+
307
+    // }
308
+    const clickData1 = ref({});
309
+    const mouseClick = function (e) {
310
+      // console.log("屏幕点击位置看", e)
311
+      clickData1.value.x = e.clientX;
312
+      clickData1.value.y = e.clientY;
313
+    };
314
+
315
+    //消失线的方法
316
+    const childMethod = () => {
317
+      if (lineContainer.value) {
318
+        lineContainer.value.remove();
319
+        lineContainer.value = null;
320
+      }
321
+    };
322
+
323
+    const childRef = ref(null);
324
+    let isShowDeviceDetails = ref(false);
325
+    let deviceDialog = ref(false);
326
+    let deviceDetailsList = ref([
327
+      {
328
+        name: "多媒体设备",
329
+        status: "正常",
330
+      },
331
+      {
332
+        name: "多媒体设备",
333
+        status: "异常",
334
+      },
335
+      
336
+    ]);
337
+    let deviceDetails = ref({});
338
+
339
+    const lookDetails = (data) => {
340
+      console.log("darta", data);
341
+      isShowDeviceDetails.value = true;
342
+      deviceDetails.value = data;
343
+    };
344
+
345
+    const handleResponseFunction = (data) => {
346
+      console.log("从UE返回过来的值", data);
347
+
348
+      popoverRef.value.style.display = "none";
349
+      if (lineContainer.value) {
350
+        lineContainer.value.remove();
351
+        lineContainer.value = null;
352
+      }
353
+
354
+      //如果退出教室
355
+      if (data == "quit_class") {
356
+        //消失线
357
+        if (lineContainer.value) {
358
+          lineContainer.value.remove();
359
+          lineContainer.value = null;
149 360
         }
150
-
151
-
152
-        //视频播放弹框
153
-        const videoDialog = ref(false)
154
-
155
-
156
-        // 播放视频
157
-        const lookVideo = function () {
158
-            videoDialog.value = true;
159
-            console.log("点击了播放视频", 12312312)
160
-        }
161
-        // 播放视频
162
-        const onPlay = function () {
163
-            console.log("播放视频")
164
-        }
165
-
166
-        const linedomRef = ref(null)
167
-        let lineContainer = ref(null)
168
-        const popoverRef = ref(null)
169
-        const lineStyleOption = ref({
170
-            startPlug: 'disc',
171
-            endPlug: 'disc',
172
-            dash: true,
173
-            color: '#2AC367',
174
-            startPlugColor: '#fff',
175
-            startPlugOutlineColor: '#2AC367',
176
-            endPlugColor: '#fff', // translucent
177
-            endPlugOutlineColor: '#2AC367',
178
-            size: 3,
179
-            startPlugSize: 1.5,
180
-            startPlugOutlineSize: 2,
181
-            endPlugSize: 1.5,
182
-            endPlugOutlineSize: 2
183
-        })
184
-        const classDeviceInfo = ref([])
185
-        const classDataLoaded = ref(false)
186
-        // const classDevice = async function (deviceCode) {
187
-        //     let res = await getClassDetail(deviceCode)
188
-        //     console.log("获取到了当前点击物体得详情", res)
189
-        //     classDeviceInfo.value = res.data;
190
-        //     classDataLoaded.value = true
191
-
192
-        // }
193
-        const clickData1 = ref({})
194
-        const mouseClick = function (e) {
195
-            // console.log("屏幕点击位置看", e)
196
-            clickData1.value.x = e.clientX;
197
-            clickData1.value.y = e.clientY;
198
-        }
199
-
200
-
201
-        //消失线的方法
202
-        const childMethod =()=>{
203
-            
204
-            if (lineContainer.value) {
205
-                    lineContainer.value.remove()
206
-                    lineContainer.value = null
207
-                }
361
+        //消失点击详情
362
+        popoverRef.value.style.display = "none";
363
+        //消失面板
364
+        if (childRef.value) {
365
+          childRef.value.clearPanel();
208 366
         }
209 367
 
368
+        return;
369
+      }
210 370
 
211
-        const childRef = ref(null);
212
-        const handleResponseFunction = (data) => {
213
-            console.log("从UE返回过来的值", data)
214
-
215
-            popoverRef.value.style.display = "none"
216
-            if (lineContainer.value) {
217
-                    lineContainer.value.remove()
218
-                    lineContainer.value = null
219
-                }
220
-
221
-            //如果退出教室
222
-            if(data=='quit_class'){
223
-
224
-                //消失线
225
-                if (lineContainer.value) {
226
-                    lineContainer.value.remove()
227
-                    lineContainer.value = null
228
-                }
229
-                //消失点击详情
230
-                popoverRef.value.style.display = "none"
231
-                //消失面板
232
-                if (childRef.value) {
233
-                    childRef.value.clearPanel();
234
-                }
235
-
236
-               return 
237
-            }
238
-
239
-
240
-
241
-            if (data == "{isrotate}") {
242
-                // console.log("旋转场景了", lineContainer, lineContainer.value);
243
-                if (lineContainer.value) {
244
-                    lineContainer.value.remove()
245
-                    lineContainer.value = null
246
-                }
247
-                popoverRef.value.style.display = "none"
248
-                return
249
-            }
250
-            let json = eval("(" + data + ")");//转对象
251
-            console.log("clickData---点击的数据", json)
252
-            if (json.url) {
253
-                videoDialog.value = true;
254
-                titleDialog.value = json.name
255
-                getVideoUrl({ "rtsp": json.url }).then(res => {
256
-                    videoData.src = res.data.httpFlv;
257
-                    console.log("32424", videoData.src)
258
-                })
259
-
260
-                return
261
-            }
262
-            clickData.value = json;
263
-
264
-
265
-            orientation.value=clickData.value.direction
266
-
267
-            console.log('看看这个',clickData.value.direction);
268
-            if (json.deviceCode) {
269
-               
270
-              
271
-                console.log('sadasd',json.deviceCode);
272
-              
273
-                let replacedStr = json.deviceCode.replace(/\//g, 'A');
274
-                // let replacedStr = replacedStr2.replace("/", "A");
275
-
276
-                console.log('replacedStr',replacedStr);
277
-                getClassDetail(replacedStr).then(res => {
278
-                    console.log('sad21312',res);
279
-                    classDeviceInfo.value = res.data;
280
-
281
-                    console.log('asd2121',classDeviceInfo.value);
282
-                    // classDeviceInfo.value.map(i => {
283
-                    //     if (i.value == "在线") {
284
-                    //         i.online = true
285
-                    //     }
286
-                    //     if (i.value == "离线") {
287
-                    //         i.online = false
288
-                    //     }
289
-                    // })
290
-                    console.log("json.y ", json,);
291
-                    linedomRef.value.style.top = (clickData.value.y) + "px";
292
-                    linedomRef.value.style.left = (clickData.value.x) + "px";
293
-                    console.log("可以开始移动")
294
-                    console.log("看一下---小圆点的值--top", linedomRef.value.style.top)
295
-                    console.log("看一下---小圆点的值--left", linedomRef.value.style.left)
296
-
297
-               
298
-                     // 弹出框位置
299
-                    popoverRef.value.style.display = "block"
300
-                    switch (json.direction) {
301
-                        case "Left":
302
-                            popoverRef.value.style.top = json.y - 30 + 'px'
303
-                            popoverRef.value.style.left = json.x - 320 + 'px'
304
-                            break;
305
-                        case "Up":
306
-                            popoverRef.value.style.top = (json.y - 300) + 'px'
307
-                            popoverRef.value.style.left = (json.x - 120) + 'px'
308
-                            break;
309
-                        case "Right":
310
-                            popoverRef.value.style.top = (Number(json.y)) -150+ 'px'
311
-                            popoverRef.value.style.left = Number(json.x) + 100 + 'px'
312
-
313
-                            break;
314
-                        case "Down":
315
-                            popoverRef.value.style.top = (Number(json.y) + 100) + 'px'
316
-                            popoverRef.value.style.left = Number(json.x) + 'px'
317
-                            break;
318
-                        default:
319
-                            popoverRef.value.style.top = json.y - 60 + 'px'
320
-                            popoverRef.value.style.left = json.x + 'px'
321
-                            break;
322
-                    }
323
-         
324
-
325
-                    if (lineContainer.value) {
326
-                        lineContainer.value.remove()
327
-
328
-                    }
329
-
330
-                    lineContainer.value = new LeaderLine(linedomRef.value, popoverRef.value, lineStyleOption.value)
331
-
332
-
333
-                    lineContainer.value.show('draw', {
334
-                        duration: 100, //持续时长
335
-                        timing: 'ease-in' // 动画函数
336
-                    })
337
-                    lineContainer.value.endPlugOutline = true;
338
-                    lineContainer.value.startPlugOutline = true;
339
-                    lineContainer.value.position();
340
-
341
-
342
-
343
-                })
344
-
345
-            }
346
-
347
-
348
-        }
349
-        const windowSize = function () {
350
-            // console.log("监听一下窗口是不是改了--click", lineContainer);
351
-            callUIInteraction("windowSize")
371
+      if (data == "{isrotate}") {
372
+        // console.log("旋转场景了", lineContainer, lineContainer.value);
373
+        if (lineContainer.value) {
374
+          lineContainer.value.remove();
375
+          lineContainer.value = null;
352 376
         }
353
-  
377
+        popoverRef.value.style.display = "none";
378
+        return;
379
+      }
380
+      let json = eval("(" + data + ")"); //转对象
381
+      console.log("clickData---点击的数据", json);
382
+      if (json.url) {
383
+        videoDialog.value = true;
384
+        titleDialog.value = json.name;
385
+        getVideoUrl({ rtsp: json.url }).then((res) => {
386
+          videoData.src = res.data.httpFlv;
387
+          console.log("32424", videoData.src);
388
+        });
354 389
 
355
-        onMounted(() => {
356
-            addResponseEventListener("444", handleResponseFunction);
357
-            window.addEventListener('resize', windowSize);
358
-            // window.addEventListener('click', windowSize);
359
-            // addResponseEventListener("widowSizeChange", windoChangeFunction);
360
-        })
361
-        // // 获取当前年月日计算属性
362
-
363
-        // const getCurrentYMD =  computed(()=>{
364
-        //     const now = new Date();
365
-        //     const year = now.getFullYear();
366
-        //     const month = String(now.getMonth() + 1).padStart(2, '0');
367
-        //     const day = String(now.getDate()).padStart(2, '0');
368
-        //     return `${year}-${month}-${day}`;
369
-        // })
370
-
371
-        // // 获取当前时间   umbRQyjXTsyAsbzT4zLI3GNgnPrTBYGD
372
-        // let currentTime = ref(new Date().toLocaleTimeString());
373
-        // let timeInterval =ref(null)
374
-        // const starttimeInterval =()=>{
375
-        //     clearTimeout(timeInterval.value)
376
-        //     timeInterval.value = setInterval(() => {
377
-        //     currentTime.value = new Date().toLocaleTimeString();
378
-        // }, 1000);
379
-        // }
380
-
381
-        // let weatherInfo =ref({});
382
-        
383
-        // const getWeatherInfo =(latitude,Longitude)=>{
384
-        //     const key ="UFwchof57SfCeYaiWXj1F2XBx0w8hKtB";
385
-        //     //https://api.map.baidu.com/weather/v1/?location=${latitude},${Longitude}&data_type=all&ak=${key}
386
-        //     axios.get(`https://api.map.baidu.com/weather/v1/?district_id=222405&data_type=all&ak=${key}`).then(res=>{
387
-        //         weatherInfo.value = res.data.result.now;
388
-        //         console.log("天气信息",res.data.result.now)
389
-        //     })
390
-        // }
391
-        
392
-        
393
-
394
-
395
-
396
-
397
-
398
-
399
-        // onBeforeUnmount(() => {
400
-        //     clearTimeout(timeInterval.value)
401
-        // })
402
-        // onUnmounted(() => {
403
-        //     clearTimeout(timeInterval.value)
404
-        // })
405
-
406
-
407
-
408
-
409
-        
410
-
411
-        
412
-
413
-    
414
-        // const headImg = ref('./img/head.png')
415
-        const backgroundImg = ref('./img/test_bg.png')
416
-        const bgShadow = ref('./img/bg-shadow.png')
417
-        return {
418
-            centerDialogVisible,
419
-            interDevice,
420
-            clickData,
421
-
422
-            triggerRef,
423
-            videoData,
424
-            onPlay,
425
-            videoDialog,
426
-            lookVideo,
427
-            linedomRef,
428
-            popoverRef,
429
-            lineContainer,
430
-            classDeviceInfo,
431
-            mouseClick,
432
-            titleDialog,
433
-            orientation,
434
-            childMethod,
435
-            childRef,
436
-            // getCurrentYMD,
437
-            // currentTime,
438
-            // weatherInfo,
439
-            backgroundImg,
440
-            bgShadow
441
-        }
442
-    },
443
-})
390
+        return;
391
+      }
392
+      clickData.value = json;
393
+
394
+      orientation.value = clickData.value.direction;
395
+
396
+      console.log("看看这个", clickData.value.direction);
397
+      if (json.deviceCode) {
398
+        console.log("sadasd", json.deviceCode);
399
+
400
+        let replacedStr = json.deviceCode.replace(/\//g, "A");
401
+        // let replacedStr = replacedStr2.replace("/", "A");
402
+
403
+        console.log("replacedStr", replacedStr);
404
+        getClassDetail(replacedStr).then((res) => {
405
+          console.log("sad21312", res);
406
+          classDeviceInfo.value = res.data;
407
+
408
+          console.log("asd2121", classDeviceInfo.value);
409
+          // classDeviceInfo.value.map(i => {
410
+          //     if (i.value == "在线") {
411
+          //         i.online = true
412
+          //     }
413
+          //     if (i.value == "离线") {
414
+          //         i.online = false
415
+          //     }
416
+          // })
417
+          console.log("json.y ", json);
418
+          linedomRef.value.style.top = clickData.value.y + "px";
419
+          linedomRef.value.style.left = clickData.value.x + "px";
420
+          console.log("可以开始移动");
421
+          console.log("看一下---小圆点的值--top", linedomRef.value.style.top);
422
+          console.log("看一下---小圆点的值--left", linedomRef.value.style.left);
423
+
424
+          // 弹出框位置
425
+          popoverRef.value.style.display = "block";
426
+          switch (json.direction) {
427
+            case "Left":
428
+              popoverRef.value.style.top = json.y - 30 + "px";
429
+              popoverRef.value.style.left = json.x - 320 + "px";
430
+              break;
431
+            case "Up":
432
+              popoverRef.value.style.top = json.y - 300 + "px";
433
+              popoverRef.value.style.left = json.x - 120 + "px";
434
+              break;
435
+            case "Right":
436
+              popoverRef.value.style.top = Number(json.y) - 150 + "px";
437
+              popoverRef.value.style.left = Number(json.x) + 100 + "px";
438
+
439
+              break;
440
+            case "Down":
441
+              popoverRef.value.style.top = Number(json.y) + 100 + "px";
442
+              popoverRef.value.style.left = Number(json.x) + "px";
443
+              break;
444
+            default:
445
+              popoverRef.value.style.top = json.y - 60 + "px";
446
+              popoverRef.value.style.left = json.x + "px";
447
+              break;
448
+          }
449
+
450
+          if (lineContainer.value) {
451
+            lineContainer.value.remove();
452
+          }
453
+
454
+          lineContainer.value = new LeaderLine(
455
+            linedomRef.value,
456
+            popoverRef.value,
457
+            lineStyleOption.value
458
+          );
459
+
460
+          lineContainer.value.show("draw", {
461
+            duration: 100, //持续时长
462
+            timing: "ease-in", // 动画函数
463
+          });
464
+          lineContainer.value.endPlugOutline = true;
465
+          lineContainer.value.startPlugOutline = true;
466
+          lineContainer.value.position();
467
+        });
468
+      }
469
+      console.log("data---------------------", JSON.parse(data));
470
+
471
+      if (JSON.parse(data).MainServiceName == "JianKong") {
472
+        getVideoUrl({ rtsp: JSON.parse(data).MonitorURL }).then((res) => {
473
+          //   videoData.src = res.data.httpFlv;
474
+          videoData.src =
475
+            "https://weizhi.huanghuai.edu.cn/jk/hls/9159eba9-b4c6-4a4b-a8ff-a1dd94c88bae.m3u8";
476
+          videoDialog.value = true;
477
+          console.log("32424", videoData.src);
478
+        });
479
+      }
480
+      if (JSON.parse(data).MainServiceName == "SheBeiLieBiao") {
481
+        equipmentDetailsApi(JSON.parse(data).ClassroomCode).then((res) => {
482
+          deviceDialog.value = true;
483
+          console.log("res", res);
444 484
 
485
+          deviceDetailsList.value = res.data;
486
+        });
487
+      }
488
+    };
489
+    const windowSize = function () {
490
+      // console.log("监听一下窗口是不是改了--click", lineContainer);
491
+      callUIInteraction("windowSize");
492
+    };
493
+
494
+    onMounted(() => {
495
+      addResponseEventListener("444", handleResponseFunction);
496
+
497
+      // window.addEventListener("resize", windowSize);
498
+      //   addResponseEventListener("jiankong", openVideo);
499
+
500
+      // window.addEventListener('click', windowSize);
501
+      // addResponseEventListener("widowSizeChange", windoChangeFunction);
502
+    });
503
+
504
+    const headImg = ref("./img/head.png");
505
+    const bgShadow = ref("./img/bg-shadow.png");
506
+    return {
507
+      centerDialogVisible,
508
+      interDevice,
509
+      clickData,
510
+
511
+      triggerRef,
512
+      videoData,
513
+      onPlay,
514
+      videoDialog,
515
+      lookVideo,
516
+      linedomRef,
517
+      popoverRef,
518
+      lineContainer,
519
+      classDeviceInfo,
520
+      mouseClick,
521
+      headImg,
522
+      titleDialog,
523
+      orientation,
524
+      childMethod,
525
+      childRef,
526
+      bgShadow,
527
+      isShowDeviceDetails,
528
+      lookDetails,
529
+      deviceDetailsList,
530
+      deviceDialog,
531
+      deviceDetails
532
+    };
533
+  },
534
+};
445 535
 </script>
446 536
 
447 537
 <style scoped lang="scss">
448
-@import '../assets/css/home.scss';
538
+@import "../assets/css/home.scss";
539
+
540
+:deep(.el-dialog__header) {
541
+    height: 49px;
542
+    line-height: 49px;
543
+    padding: 0;
544
+    background-color: #1b67d9;
545
+    margin-right: 0;
546
+}
449 547
 </style>

+ 430 - 177
src/components/left.vue

@@ -1,20 +1,27 @@
1 1
 <template>
2 2
   <el-aside class="left">
3 3
     <div class="left_top">
4
-      <!-- <div class="title">
5
-                <div class="text">教室分类统计</div>
6
-            </div> -->
7 4
       <div class="title" :style="{ background: `url(${frame})` }">
8 5
         <div class="text">教室分类统计</div>
9 6
       </div>
10
-
11 7
       <div class="content">
12
-        <div
13
-          id="myChart"
14
-          style="width: 140px; height: 140px; margin: 0px 0 30px 18px"
15
-        ></div>
8
+        <div id="myChart"></div>
16 9
         <div class="list">
17
-          <div class="item" v-for="(item, index) in classRoomCount">
10
+          <div
11
+            class="item"
12
+            v-for="(item, index) in classRoomCount"
13
+            :key="index"
14
+            :style="{
15
+              borderRight:
16
+                index == 0
17
+                  ? '2px solid #0BF'
18
+                  : index == 1
19
+                  ? '2px solid #00EB7D'
20
+                  : index == 2
21
+                  ? '2px solid #FB0'
22
+                  : '',
23
+            }"
24
+          >
18 25
             <!-- <i :style="{ backgroundColor: item.color }"></i> -->
19 26
             <svg
20 27
               xmlns="http://www.w3.org/2000/svg"
@@ -73,6 +80,7 @@
73 80
             :target-value="item.number"
74 81
             :color="item.color"
75 82
             :text="item.text"
83
+            :index="index"
76 84
             text-position="bottom"
77 85
           >
78 86
             <img :src="leftimg + item.imgnumber + '.png'" alt="" />
@@ -89,136 +97,176 @@
89 97
       <div class="usering-free-container">
90 98
         <div class="usering-free">
91 99
           <div class="usering-box">
92
-            <div class="text">使用中72.56% <span class="num">657间</span></div>
100
+            <div class="text">
101
+              使用中{{ getPercentage || 0 }}%
102
+              <span class="num">{{ occupiedClassroomsCount || 0 }}间</span>
103
+            </div>
93 104
           </div>
94 105
           <div class="free-box">
95 106
             <div class="text">
96
-              空闲中:28.44% <span class="num">657间</span>
107
+              空闲中:{{ (100 - getPercentage).toFixed(2) || 0 }}%
108
+              <span class="num"
109
+                >{{ classRoom.length - occupiedClassroomsCount || 0 }}间</span
110
+              >
97 111
             </div>
98 112
           </div>
99 113
         </div>
100
-        <el-progress :percentage="72.56" :show-text="false" color="#1BCEFF" />
114
+        <el-progress
115
+          :percentage="getPercentage"
116
+          :show-text="false"
117
+          color="#1BCEFF"
118
+        />
101 119
       </div>
102 120
       <div class="search-container">
103
-        <el-input v-model="searchData" placeholder="请输入大楼名称、教室名称" />
121
+        <el-input
122
+          v-model="searchData"
123
+          clearable
124
+          placeholder="请输入大楼名称、教室名称"
125
+        />
104 126
         <el-button type="primary" :icon="Search" @click="searchHandel" />
105 127
       </div>
128
+
106 129
       <div class="content" style="height: 100%; overflow: hidden">
107 130
         <div class="table">
108 131
           <div class="dropdown-container">
109
-            <el-dropdown trigger="click" @command="clickBuildingsropdown" >
132
+            <el-dropdown trigger="click" @command="clickBuildingsropdown">
110 133
               <span class="dropdown-name">
111
-                全部大楼
112
-                <Edit style="width: 1em; height: 1em; margin-right: 8px" />
134
+                {{ cureetBuild }}
135
+                <!-- <Edit style="width: 1em; height: 1em; margin-right: 8px" /> -->
113 136
               </span>
114 137
               <template #dropdown>
115 138
                 <el-dropdown-menu>
116
-                  <!-- <el-dropdown-item>一号大楼</el-dropdown-item>
117
-                  <el-dropdown-item>二号大楼</el-dropdown-item>
118
-                  <el-dropdown-item>三号大楼</el-dropdown-item> -->
119
-                  <el-dropdown-item :command="item.gis_building_code" v-for="(item,index) in buildings" :key="index">{{ item.building_name }}</el-dropdown-item>
120
-                  
139
+                  <el-dropdown-item
140
+                    :command="item.gis_building_code"
141
+                    v-for="(item, index) in buildings"
142
+                    :key="index"
143
+                    >{{ item.building_name }}</el-dropdown-item
144
+                  >
121 145
                 </el-dropdown-menu>
122 146
               </template>
123 147
             </el-dropdown>
124 148
             <el-dropdown trigger="click" @command="clickCategoryTypedropdown">
125 149
               <span class="dropdown-name">
126
-                全部类型
150
+                {{ category || "全部类型" }}
127 151
                 <el-icon>
128 152
                   <arrow-down />
129 153
                 </el-icon>
130 154
               </span>
131 155
               <template #dropdown>
132 156
                 <el-dropdown-menu>
133
-                  <el-dropdown-item :command="item.category" v-for="(item,index) in categoryType" :key="index">{{ item.category }}</el-dropdown-item>
134
-
157
+                  <el-dropdown-item
158
+                    :command="item.category"
159
+                    v-for="(item, index) in categoryType"
160
+                    :key="index"
161
+                    >{{ item.category }}</el-dropdown-item
162
+                  >
135 163
                 </el-dropdown-menu>
136 164
               </template>
137 165
             </el-dropdown>
138 166
             <el-dropdown trigger="click" @command="clickLeafsdropdown">
139 167
               <span class="dropdown-name">
140
-                全部楼层
168
+                {{ cureetLeaf }}
141 169
                 <el-icon>
142 170
                   <arrow-down />
143 171
                 </el-icon>
144 172
               </span>
145 173
               <template #dropdown>
146 174
                 <el-dropdown-menu>
147
-                  <el-dropdown-item :command="item.leaf" v-for="(item,index) in leafs" :key="index">{{ item.leaf_name }}</el-dropdown-item>
148
-
175
+                  <el-dropdown-item
176
+                    :command="item.leaf"
177
+                    v-for="(item, index) in leafs"
178
+                    :key="index"
179
+                    >{{ item.leaf_name }}</el-dropdown-item
180
+                  >
149 181
                 </el-dropdown-menu>
150 182
               </template>
151 183
             </el-dropdown>
152 184
             <el-dropdown trigger="click" @command="clickStatusesdropdown">
153 185
               <span class="dropdown-name">
154
-                全部状态
186
+                {{ cureetStatus }}
155 187
                 <el-icon>
156 188
                   <arrow-down />
157 189
                 </el-icon>
158 190
               </span>
159 191
               <template #dropdown>
160 192
                 <el-dropdown-menu>
161
-                  <el-dropdown-item  :command="item.status_code" v-for="(item,index) in statuses" :key="index">{{ item.status_name }}</el-dropdown-item>
162
-
193
+                  <el-dropdown-item
194
+                    :command="item.status_code"
195
+                    v-for="(item, index) in statuses"
196
+                    :key="index"
197
+                    >{{ item.status_name }}</el-dropdown-item
198
+                  >
163 199
                 </el-dropdown-menu>
164 200
               </template>
165 201
             </el-dropdown>
166 202
           </div>
167
-
168
-          <!-- <ul class="th">
169
-            <li>教室</li>
170
-            <li>使用状态</li>
171
-            <li class="long">实到/应到人数</li>
172
-            <li>到课率</li>
173
-            <li>操作</li>
174
-          </ul> -->
175
-          <div
203
+          <!-- <div
176 204
             ref="testMain"
177
-            style="height: 342px; overflow: scroll; margin-top: 8px"
205
+            style="height: 442px; overflow: scroll; padding-top: 6px"
178 206
             class="scroll"
207
+          > -->
208
+          <!-- <div v-if="classRoomList.length > 0" style="width: 100%; height: 100%"> -->
209
+          <div
210
+            style="height: 342px; overflow: hidden; padding-top: 10px"
211
+            v-if="classRoomList.length > 0"
179 212
           >
180
-            <div
181
-              v-for="item in classRoom"
182
-              @mouseenter="testMove"
183
-              @mouseleave="testMend"
184
-              class="list-item"
213
+            <scroll
214
+             
215
+              :content="classRoomList"
216
+              class="vue3-seamless-scroll"
217
+              :hover="true"
218
+              :wheel="true"
219
+              direction="top"
220
+              ref="scrollRef"
221
+              :mask="false"
222
+            
185 223
             >
186
-              <div class="class-status-btn-container">
187
-                <div class="left-class-status">
188
-                  <div>{{ item.class }}</div>
189
-                </div>
190
-                <div class="right-btn" @click.prevent="handleRoom(item)">
191
-                  查看
192
-                </div>
193
-              </div>
194
-              <div class="position-type-container">
195
-                <div>位置:1号教学楼A201</div>
196
-                <div>类型:智慧教室</div>
197
-              </div>
198
-              <div class="course-person-rate-container">
199
-                <div class="course">课程:软件测试</div>
200
-                <div class="person-rate-container">
201
-                  <div>实到/应到:40/48</div>
202
-                  <div>到课率:90%</div>
224
+              <template #default="{item}" >
225
+                <div  class="list-item" >
226
+                  <div class="class-status-btn-container">
227
+                    <div class="left-class-status">
228
+                      <div style="margin-right: 16px">
229
+                        {{ item.classroom_name }}
230
+                      </div>
231
+                      <Tag :text="item.status_code"></Tag>
232
+                    </div>
233
+                    <div class="right-btn" @click.prevent="handleRoom(item)">
234
+                      查看
235
+                    </div>
236
+                  </div>
237
+
238
+                  <div class="item-content">
239
+                    <div class="item-content-left">
240
+                      <div class="position">位置:{{ item.location }}</div>
241
+                      <div class="course" v-if="item.course_name">
242
+                        课程:{{ item.course_name }}
243
+                      </div>
244
+                    </div>
245
+                    <div class="item-content-right">
246
+                      <div>类型:{{ item.category }}</div>
247
+                      <div class="person-rate-container">
248
+                        <div v-if="item.actual != null">
249
+                          实到/应到:{{ item.actual }}/{{ item.expected }}
250
+                        </div>
251
+                        <div
252
+                          style="margin-left: 8px"
253
+                          v-if="item.actual != null"
254
+                        >
255
+                          到课率:{{
256
+                            (item.actual / item.expected).toFixed(2) * 100 || 0
257
+                          }}%
258
+                        </div>
259
+                      </div>
260
+                    </div>
261
+                  </div>
203 262
                 </div>
204
-              </div>
205
-              <!-- <div class="jiaos">{{ item.class }}</div>
206
-              <div>{{ item?.status }}</div>
207
-              <div>{{ item.people || "—" }}</div>
208
-              <div>{{ item.percentage ? item.percentage : "—" }}</div>
209
-
210
-              <div>
211
-                <el-button
212
-                  size="small"
213
-                  v-if="item.leaf"
214
-                  class="look"
215
-                  @click.prevent="handleRoom(item)"
216
-                  >查看</el-button
217
-                >
218
-              </div> -->
219
-            </div>
263
+              </template>
264
+            </scroll>
220 265
           </div>
266
+
267
+          
221 268
         </div>
269
+        <!-- </div> -->
222 270
       </div>
223 271
     </div>
224 272
   </el-aside>
@@ -241,7 +289,7 @@
241 289
       <span
242 290
         ><span class="title">教室状态: </span>
243 291
         <span :class="classInfo.online ? 'green' : 'red'">{{
244
-          classInfo && classInfo?.status
292
+          classInfo && classInfo.status
245 293
         }}</span>
246 294
       </span>
247 295
 
@@ -289,6 +337,7 @@ import {
289 337
   onUnmounted,
290 338
   nextTick,
291 339
   getCurrentInstance,
340
+  computed,
292 341
 } from "vue";
293 342
 import {
294 343
   ElScrollbar,
@@ -299,10 +348,19 @@ import {
299 348
   ElIcon,
300 349
 } from "element-plus";
301 350
 import { Search, ArrowDown } from "@element-plus/icons-vue";
302
-
303
-import { getClass,classSelections,queryClassroom } from "../request/api";
351
+import { getClass, classSelections, queryClassroom } from "../request/api";
304 352
 import CircleProgress from "./CircleProgress.vue";
305
-import { callUIInteraction } from "../webrtcVideo";
353
+
354
+import {
355
+  Vue3SeamlessScroll,
356
+  VerticalScroll,
357
+  HorizontalScroll,
358
+} from "vue3-seamless-scroll";
359
+import Scroll from "./ScrollView.vue";
360
+
361
+import vueScrolling from "vue-scrolling";
362
+// import { callUIInteraction } from "../webrtcVideo";
363
+import Tag from "./Tag.vue";
306 364
 
307 365
 export default {
308 366
   name: "Histogram",
@@ -313,63 +371,199 @@ export default {
313 371
     ElCarousel,
314 372
     ElCarouselItem,
315 373
     CircleProgress,
374
+    Tag,
375
+    Vue3SeamlessScroll,
376
+    VerticalScroll,
377
+    vueScrolling,
378
+    Scroll
316 379
   },
317 380
   setup(_, { emit }) {
318 381
     // 教室分类统计
319 382
     const classRoomCount = ref([]);
383
+    const generateData = (totalNum, bigvalue, smallvalue, color) => {
384
+      let dataArr = [];
385
+      for (var i = 0; i < totalNum; i++) {
386
+        if (i % 2 === 0) {
387
+          dataArr.push({
388
+            name: (i + 1).toString(),
389
+            value: bigvalue,
390
+            itemStyle: {
391
+              normal: {
392
+                color: color,
393
+                borderWidth: 0,
394
+              },
395
+            },
396
+          });
397
+        } else {
398
+          dataArr.push({
399
+            name: (i + 1).toString(),
400
+            value: smallvalue,
401
+            itemStyle: {
402
+              normal: {
403
+                color: "rgba(0,0,0,0)",
404
+                borderWidth: 0,
405
+              },
406
+            },
407
+          });
408
+        }
409
+      }
410
+      return dataArr;
411
+    };
412
+    let dolitData = generateData(60, 25, 20, "rgb(126,190,255)");
413
+
414
+    const fontSize = (res) => {
415
+      let clientWidth =
416
+        window.innerWidth ||
417
+        document.documentElement.clientWidth ||
418
+        document.body.clientWidth;
419
+      if (!clientWidth) return;
420
+      // 此处的3840 为设计稿的宽度,记得修改!
421
+      let fontSize = clientWidth / 1920;
422
+      return res * fontSize;
423
+    };
320 424
 
321 425
     const classPie = reactive({
322 426
       option: {
323
-        color: ["#4ED2E4", "#5E91F6", "#67F0A8"],
427
+        color: ["#00bbff", "#00eb7d", "#ffbb00"],
324 428
         tooltip: {
325 429
           trigger: "item",
326 430
           position: "right",
327 431
         },
328
-
329 432
         series: [
330 433
           {
331 434
             type: "pie",
332
-            radius: ["60%", "90%"],
435
+            radius: ["85%", "100%"],
333 436
             avoidLabelOverlap: false,
437
+            zlevel: 9999,
438
+            labelLine: {},
334 439
             label: {
335 440
               show: true,
336 441
               position: "center",
337 442
               color: "#fff",
443
+              fontSize: fontSize(14),
338 444
               formatter: () => {
339 445
                 // 格式化要展示的文本
340
-                return `总计\n\n${roomtotle.value}间`;
446
+                return `{a|总数}\n{b|${roomtotle.value}}{c|间}`;
447
+              },
448
+              rich: {
449
+                a: {
450
+                  color: "rgba(255, 255, 255, 0.80)",
451
+                  lineHeight: fontSize(24),
452
+                  fontSize: fontSize(12),
453
+                },
454
+                b: {
455
+                  color: "#fff",
456
+                  fontSize: fontSize(20),
457
+                  fontWeight: 700,
458
+                },
459
+                c: {
460
+                  color: "rgba(255, 255, 255, 0.80)",
461
+
462
+                  fontSize: fontSize(12),
463
+                },
341 464
               },
342
-
343
-              // formatter: '{total|' + 200 + '}' + '\n\r' + '{active|共发布活动}',
344
-              // 等着获取了数据在给样式
345
-              // rich: {
346
-              //     total: {
347
-              //         fontSize: 35,
348
-              //         fontFamily: "微软雅黑",
349
-              //         color: '#454c5c'
350
-              //     },
351
-              //     active: {
352
-              //         fontFamily: "微软雅黑",
353
-              //         fontSize: 16,
354
-              //         color: '#6c7a89',
355
-              //         lineHeight: 30,
356
-              //     },
357
-              // }
358 465
             },
359 466
             labelLine: {
360 467
               show: false,
361 468
             },
362 469
             data: classRoomCount,
363 470
           },
471
+
472
+          {
473
+            name: "虚线",
474
+            type: "pie",
475
+            zlevel: 10,
476
+            silent: true,
477
+            radius: ["58%", "56%"],
478
+            label: {
479
+              normal: {
480
+                show: false,
481
+              },
482
+            },
483
+            labelLine: {
484
+              normal: {
485
+                show: false,
486
+              },
487
+            },
488
+            data: dolitData,
489
+          },
490
+          {
491
+            name: "阴影圈",
492
+            type: "pie",
493
+            radius: ["100%", "70%"],
494
+            center: ["50%", "50%"],
495
+            emphasis: {
496
+              scale: false,
497
+            },
498
+            tooltip: {
499
+              show: false,
500
+            },
501
+            itemStyle: {
502
+              // color: "rgba(255,225,255, 0.08)",
503
+              color: "rgba(34, 81, 113,0.8)",
504
+            },
505
+            zlevel: 4,
506
+            labelLine: {
507
+              show: false,
508
+            },
509
+            data: [100],
510
+          },
511
+          {
512
+            name: "阴影圈2",
513
+            type: "pie",
514
+            radius: ["70%", "52%"],
515
+            center: ["50%", "50%"],
516
+            emphasis: {
517
+              scale: false,
518
+            },
519
+            tooltip: {
520
+              show: false,
521
+            },
522
+            itemStyle: {
523
+              // color: "rgba(255,225,255, 0.08)",
524
+              color: "rgba(36, 66, 88,0.8)",
525
+            },
526
+            zlevel: 4,
527
+            labelLine: {
528
+              show: false,
529
+            },
530
+            data: [100],
531
+          },
532
+          {
533
+            name: "中间圆",
534
+            type: "pie",
535
+            radius: ["0", "50%"],
536
+            center: ["50%", "50%"],
537
+            emphasis: {
538
+              scale: false,
539
+            },
540
+            tooltip: {
541
+              show: false,
542
+            },
543
+            itemStyle: {
544
+              // color: "rgba(204,225,255, 0.08)",
545
+              color: "rgba(34, 81, 113,0.5)",
546
+            },
547
+            zlevel: 4,
548
+            labelLine: {
549
+              show: false,
550
+            },
551
+            data: [100],
552
+          },
364 553
         ],
365 554
       },
366 555
     });
556
+    let myChart = ref(null);
557
+
367 558
     const initeCharts = () => {
368
-      let myChart = echarts.init(document.getElementById("myChart"));
559
+      myChart.value = echarts.init(document.getElementById("myChart"));
369 560
       // 绘制图表
370 561
       console.log("绘制图表", classPie.option);
371
-      myChart.setOption(classPie.option);
562
+      myChart.value.setOption(classPie.option);
372 563
     };
564
+    window.addEventListener("resize", function () {
565
+      myChart.value.resize();
566
+    });
373 567
 
374 568
     // 物联设备类型统计
375 569
     const interDevice = ref([]);
@@ -406,29 +600,35 @@ export default {
406 600
     let classInfo = ref({});
407 601
     const handleRoom = function (item) {
408 602
       //查看前隐藏
409
-      let a1 = document.getElementById("popoverRef");
410
-      a1.style.display = "none";
603
+      // let a1 = document.getElementById("popoverRef");
604
+      // a1.style.display = "none";
411 605
 
412
-      emit("childMethod");
606
+      // emit("childMethod");
413 607
 
414
-      console.log("kankanitem", item);
415
-      setTimeout(() => {
416
-        roomVisible.value = true;
417
-        classInfo.value = item;
608
+      // console.log("kankanitem", item);
609
+      // setTimeout(() => {
610
+      //   roomVisible.value = true;
611
+      //   classInfo.value = item;
418 612
 
419
-        classInfo.value.online =
420
-          classInfo.value?.status == "在用" ? true : false;
421
-      }, 2400);
613
+      //   classInfo.value.online =
614
+      //     classInfo.value.status == "在用" ? true : false;
615
+      // }, 2400);
422 616
 
423
-      mousemoveHandler(1000, 60);
617
+      // mousemoveHandler(1000, 60);
424 618
 
425
-      let meg = item;
619
+      // let meg = item;
426 620
 
427
-      // let a2 =`roomName=${item.class}`
621
+      // // let a2 =`roomName=${item.class}`
428 622
 
429
-      console.log("meg是多少", meg);
623
+      // console.log("meg是多少", meg);
624
+      //发消息给UE
625
+      let data = {
626
+        MainServiceName: "JiaoShiShiNei",
627
+        ClassroomName: item.classroom_name,
628
+      };
629
+      console.log("data", JSON.stringify(data));
430 630
 
431
-      callUIInteraction(meg);
631
+      emitUIInteraction(data);
432 632
     };
433 633
 
434 634
     // 本周课程统计数据
@@ -439,7 +639,7 @@ export default {
439 639
 
440 640
     const getClassData = async () => {
441 641
       let res = await getClass();
442
-
642
+      console.log("res----教室分类统计", res);
443 643
       let { course, classroomDetail, category } = res.data;
444 644
       classRoomCount.value = category;
445 645
 
@@ -448,15 +648,14 @@ export default {
448 648
         (accumulator, currentValue) => accumulator + currentValue.value,
449 649
         0
450 650
       );
651
+      console.log("categorycategory", roomtotle);
451 652
 
452 653
       if (classRoomCount.value) {
453 654
         initeCharts();
454 655
       }
455 656
       classCount.value = course;
456
-
657
+      console.log("看一下颜色", classCount.value);
457 658
       classRoom.value = classroomDetail;
458
-      // 开启教师统计数据刷新
459
-      starttimeInterval();
460 659
     };
461 660
 
462 661
     let timer = ref(null);
@@ -465,18 +664,16 @@ export default {
465 664
       getClassData();
466 665
       start();
467 666
       // 获取教师下拉选择数据
468
-      getClassSelections()
667
+      getClassSelections();
668
+      //获取教室列表数据
669
+      getClassRoomList();
469 670
     });
470 671
 
471 672
     onBeforeUnmount(() => {
472 673
       clearTimeout(timer.value);
473
-      //清除教师统计数据刷新定时器
474
-      clearTimeout(classRoomInterval.value);
475 674
     });
476 675
     onUnmounted(() => {
477 676
       clearTimeout(timer.value);
478
-      //清除教师统计数据刷新定时器
479
-      clearTimeout(classRoomInterval.value);
480 677
     });
481 678
 
482 679
     function testMove() {
@@ -495,6 +692,7 @@ export default {
495 692
       timer.value = setInterval(MarqueeTest, speed.value);
496 693
     }
497 694
     function MarqueeTest() {
695
+      return;
498 696
       let test1 = testMain.value;
499 697
 
500 698
       //判断组件是否渲染完成
@@ -502,7 +700,7 @@ export default {
502 700
         test1 = testMain.value;
503 701
       } else {
504 702
         //如果列表数量过少不进行滚动
505
-        // console.log("test1", test1.scrollTop)
703
+        // console.log("test1", test1.childNodes)
506 704
         if (test1.childNodes.length < 6) {
507 705
           clearTimeout(timer.value);
508 706
           return;
@@ -531,57 +729,105 @@ export default {
531 729
       }, 5000);
532 730
     };
533 731
 
732
+    // 占用教室数量
733
+    const occupiedClassroomsCount = computed(() => {
734
+      return classRoom.value.filter((classroom) => classroom.status === "在用")
735
+        .length;
736
+    });
737
+
738
+    // 计算属性:占用百分比
739
+    const getPercentage = computed(() => {
740
+      const total = classRoom.value.length;
741
+      const occupied = occupiedClassroomsCount.value;
742
+      return ((occupied / total) * 100).toFixed(2) || 0;
743
+    });
534 744
 
535 745
     // 获取教师下拉选择
536
-    let buildings =ref({})
537
-    let categoryType =ref({})
538
-    let leafs =ref({})
539
-    let statuses =ref({})
540
-    const getClassSelections = async()=>{
746
+    let buildings = ref({});
747
+    let categoryType = ref({});
748
+    let leafs = ref({});
749
+    let statuses = ref({});
750
+    const getClassSelections = async () => {
541 751
       let res = await classSelections();
542
-      console.log("教师下拉选择",res.data);
543
-      if(res.code==200){
544
-        buildings.value=res.data.buildings
545
-        categoryType.value=res.data.categoryType
546
-        leafs.value=res.data.leafs
547
-        statuses.value=res.data.statuses
752
+      if (res.code == 200) {
753
+        buildings.value = res.data.buildings;
754
+        categoryType.value = res.data.categoryType;
755
+        leafs.value = res.data.leafs;
756
+        statuses.value = res.data.statuses;
548 757
       }
549
-    }
550
-    const clickBuildingsropdown =(e)=>{
551
-      console.log("e",e);
552
-      gisBuildingCode.value=e
553
-      searchHandel()
554
-    }
555
-    const clickCategoryTypedropdown =(e)=>{
556
-      console.log("e",e);
557
-      category.value=e
558
-      searchHandel()
559
-    }
560
-    const clickLeafsdropdown =(e)=>{
561
-      console.log("e",e);
562
-      gisLeaf.value=e
563
-      searchHandel()
564
-    }
565
-    const clickStatusesdropdown =(e)=>{
566
-      console.log("e",e);
567
-      statusCode.value=e
568
-      searchHandel()
569
-    }
758
+    };
759
+    let cureetBuild = ref("全部大楼");
760
+    const clickBuildingsropdown = (e) => {
761
+      console.log("e选择大楼", e);
762
+      gisBuildingCode.value = e;
763
+      let build = buildings.value.find((item) => {
764
+        return item.gis_building_code == e;
765
+      });
766
+      cureetBuild.value = build.building_name;
767
+      searchHandel();
768
+    };
769
+
770
+    const clickCategoryTypedropdown = (e) => {
771
+      console.log("e", e);
772
+      category.value = e;
773
+
774
+      searchHandel();
775
+    };
776
+    let cureetLeaf = ref("全部楼层");
777
+    const clickLeafsdropdown = (e) => {
778
+      console.log("e", e);
779
+      gisLeaf.value = e;
780
+      let leaf = leafs.value.find((item) => {
781
+        return item.leaf == e;
782
+      });
783
+      cureetLeaf.value = leaf.leaf_name;
784
+      searchHandel();
785
+    };
786
+
787
+    let cureetStatus = ref("全部状态");
788
+    const clickStatusesdropdown = (e) => {
789
+      console.log("e", e);
790
+      statusCode.value = e;
791
+      let status = statuses.value.find((item) => {
792
+        return item.status_code == e;
793
+      });
794
+      cureetStatus.value = status.status_name;
795
+      searchHandel();
796
+    };
570 797
 
571 798
     // 搜索
572 799
     let searchData = ref("");
573
-    let category =ref("")
574
-    let gisBuildingCode =ref("")
575
-    let gisLeaf =ref("")
576
-    let statusCode =ref("")
577
-    const searchHandel = async() => {
578
-      let res =await  queryClassroom({category: category.value,gisBuildingCode:gisBuildingCode.value,statusCode:statusCode.value,gisLeaf:gisLeaf.value})
579
-      console.log("搜索",res.data);
580
-      classRoom.value = res.data;
800
+    let category = ref("");
801
+    let gisBuildingCode = ref("");
802
+    let gisLeaf = ref("");
803
+    let statusCode = ref("");
804
+    let scrollRef = ref(null);
805
+    const offset = () => {
806
+      console.log("滚动完成一次");
581 807
       
582 808
     };
583
-
584
-
809
+    const searchHandel = async () => {
810
+      getClassRoomList();
811
+    };
812
+    let classRoomList = ref([]);
813
+    const getClassRoomList = async () => {
814
+      let res = await queryClassroom({
815
+        classRoomName: searchData.value,
816
+        category: category.value,
817
+        gisBuildingCode: gisBuildingCode.value,
818
+        statusCode: statusCode.value,
819
+        gisLeaf: gisLeaf.value,
820
+      });
821
+      classRoomList.value = [];
822
+      setTimeout(() => {
823
+        classRoomList.value = res.data;
824
+        // scrollRef.value.reset()
825
+      }, 0);
826
+    };
827
+    let deviceDialog = ref(true);
828
+    const showDeviceDialog = () => {
829
+      deviceDialog.value = true;
830
+    };
585 831
     const leftimg = ref("./img/");
586 832
     const frame = ref("./img/frame.png");
587 833
     return {
@@ -600,6 +846,7 @@ export default {
600 846
       leftimg,
601 847
       clearPanel,
602 848
       frame,
849
+      ElIcon,
603 850
       Search,
604 851
       ArrowDown,
605 852
       ElIcon,
@@ -614,11 +861,21 @@ export default {
614 861
       clickBuildingsropdown,
615 862
       clickLeafsdropdown,
616 863
       clickStatusesdropdown,
617
-      start,
864
+      // start,
618 865
       category,
619 866
       gisBuildingCode,
620 867
       gisLeaf,
621
-      statusCode
868
+      statusCode,
869
+      getPercentage,
870
+      occupiedClassroomsCount,
871
+      classRoomList,
872
+      deviceDialog,
873
+      showDeviceDialog,
874
+      cureetBuild,
875
+      cureetLeaf,
876
+      cureetStatus,
877
+      scrollRef,
878
+      offset,
622 879
     };
623 880
   },
624 881
 };
@@ -643,8 +900,4 @@ export default {
643 900
 .el-popper.is-light {
644 901
   border-radius: 10px;
645 902
 }
646
-input[aria-hidden=true]{
647
-    display: none !important;
648
-}
649
-
650 903
 </style>

+ 413 - 157
src/components/right.vue

@@ -1,7 +1,6 @@
1 1
 <template>
2 2
   <el-aside class="right">
3 3
     <div class="right_top">
4
-
5 4
       <div class="title" :style="{ background: `url(${frame})` }">
6 5
         <div class="text">物联设备统计</div>
7 6
       </div>
@@ -37,111 +36,80 @@
37 36
     </div>
38 37
 
39 38
     <div class="right_center">
40
-      <div class="title title-mt " :style="{ background: `url(${frame})` }">
39
+      <div class="title" :style="{ background: `url(${frame})` }">
41 40
         <div class="text">物联设备类型统计</div>
42 41
       </div>
43 42
       <div class="contentwrap">
44 43
         <div class="count" v-for="(item, index) in interDevice" :key="index">
45
-          <CircleProgress
46
-            :target-value="item.number"
47
-            :color="item.color"
48
-            text-position="top"
49
-          >
50
-            <img :src="deviceImg + item.icon + '.png'" alt="" />
51
-            <!-- <img :src="'src/assets/img/' + item.icon + '.png'" alt=""> -->
52
-          </CircleProgress>
53
-
44
+          <countTo
45
+            :startVal="0"
46
+            :endVal="item.number"
47
+            :decimals="2"
48
+            :duration="3000"
49
+          ></countTo>
50
+          <img :src="deviceImg + item.icon + '.png'" alt="" />
54 51
           <div class="text">{{ item?.text }}</div>
55
-
56
-          <!-- <el-tooltip placement="bottom">
57
-                        <template    #content> multiple lines<br />second line </template>
58
-                        <el-text style="width: 100px;" class="w-100px mb-2" truncated>
59
-                            测试测试测试
60
-                        </el-text>
61
-                    </el-tooltip> -->
62 52
         </div>
63 53
       </div>
64 54
     </div>
65 55
 
66 56
     <div class="right_bottom">
67
-      <div class="title title-mt" :style="{ background: `url(${frame})` }">
57
+      <div class="title" :style="{ background: `url(${frame})` }">
68 58
         <div class="text">教室实时监控</div>
69 59
       </div>
70 60
 
61
+      <!-- <div class="monitorContent" :style="{ background: `url(${videoBorder})` }"> -->
71 62
       <div class="monitorContent">
72
-        <el-carousel class="monitor">
73
-          <el-carousel-item v-for="(item, index) in carouselData" :key="index">
63
+        <img
64
+          @click="swiperRef.prev()"
65
+          class="change-left"
66
+          :src="swiperLeft"
67
+          alt=""
68
+          srcset=""
69
+        />
70
+        <el-carousel
71
+          class="swiper-container"
72
+          ref="swiperRef"
73
+          :autoplay="false"
74
+          indicator-position="none"
75
+          @change="monitorchange"
76
+          arrow="never"
77
+        >
78
+          <!-- <img src="swiper-left" alt="" srcset=""> -->
79
+
80
+          <el-carousel-item
81
+            v-for="(item, index) in carouselData"
82
+            :key="index"
83
+            @click="lookVideo(item)"
84
+            style="height: 100%"
85
+          >
74 86
             <p style="margin-left: 12px; margin-bottom: 3px; font-size: 14px">
75
-              {{ item.activeMonitor?.title }}
76
-            </p>
77
-            <div class="interactclass">
78
-              <div
79
-                class="room"
80
-                v-for="(item, index) in item.activeMonitor?.array"
81
-                :key="index"
82
-              >
83
-                <span style="z-index: 99">{{ item?.title }}</span>
84
-                <!-- <img :src="deviceImg + 'rb2.png'" alt="" @click="lookVideo(item)"> -->
85
-                <div
86
-                  v-if="item.src"
87
-                  style="width: 100%; height: 100%"
88
-                  @click="lookVideo(item)"
89
-                >
90
-                  <div class="vue3VideoPlay">
91
-                    <vue3VideoPlay
92
-                      width="100%"
93
-                      height="8.8vh"
94
-                      style="object-fit: fill"
95
-                      :src="item.src"
96
-                      :type="videoData.type"
97
-                      :autoPlay="true"
98
-                      :control="false"
99
-                      :preload="meta"
100
-                    />
101
-                  </div>
102
-                </div>
103
-              </div>
104
-            </div>
105
-
106
-            <p
107
-              style="
108
-                margin-top: 18px;
109
-                margin-left: 12px;
110
-                margin-bottom: 3px;
111
-                font-size: 14px;
112
-              "
113
-            >
114
-              {{ item.activeMonitor?.title1 }}
87
+              {{ item.title }}
115 88
             </p>
116
-            <div class="interactclass">
117
-              <div
118
-                class="room"
119
-                v-for="(item, index) in item.activeMonitor?.array1"
120
-                :key="index"
121
-              >
122
-                <span style="z-index: 99">{{ item?.title }}</span>
123
-                <!-- <br>
124
-                                <div>
125
-                                    {{ item.src }}
126
-                                </div> -->
127
-                <div style="width: 100%; height: 100%" @click="lookVideo(item)">
128
-                  <div class="vue3VideoPlay">
129
-                    <vue3VideoPlay
130
-                      width="100%"
131
-                      height="9vh"
132
-                      style="object-fit: fill"
133
-                      :src="item.src"
134
-                      :type="videoData.type"
135
-                      :autoPlay="true"
136
-                      :control="false"
137
-                      :preload="meta"
138
-                    />
139
-                  </div>
140
-                </div>
141
-              </div>
89
+            <div class="video-container" v-if="videoindex == index">
90
+              <VideoJs
91
+                style="height: 100%"
92
+                class="video"
93
+                :videoSrc="item.src"
94
+                autoPlay
95
+              />
142 96
             </div>
143 97
           </el-carousel-item>
144 98
         </el-carousel>
99
+        <img
100
+          class="video-border"
101
+          style="height: 100%;width: 100%"
102
+          :src="videoBorder"
103
+          alt=""
104
+          srcset=""
105
+        />
106
+        <img
107
+          @click="swiperRef.next()"
108
+          class="change-right"
109
+          :src="swiperRight"
110
+          alt=""
111
+          srcset=""
112
+        />
145 113
       </div>
146 114
     </div>
147 115
   </el-aside>
@@ -154,26 +122,114 @@
154 122
     width="62%"
155 123
     align="center"
156 124
     top="8%"
157
-    style="z-index: 2015"
125
+    style="z-index: 2015; padding: 0"
158 126
   >
159
-    <videoPlay v-bind="videoData" />
127
+    <videoPlay  v-bind="videoData" />
160 128
 
161 129
     <!-- {{ videoData.src }} -->
162 130
   </el-dialog>
131
+
132
+  <el-dialog
133
+    custom-class="deviceDialog"
134
+    v-model="deviceDialog"
135
+    title="设备明细"
136
+    width="40%"
137
+    align="left"
138
+    top="30vh"
139
+    style="z-index: 2015; background: rgba(0, 0, 0, 0)"
140
+  >
141
+    <el-table
142
+      :data="deviceDetailsList"
143
+      border
144
+      height="100%"
145
+      style="width: 100%; background: rgba(0, 0, 0, 0)"
146
+    >
147
+      <el-table-column
148
+        align="center"
149
+        type="index"
150
+        label="序号"
151
+      ></el-table-column>
152
+      <el-table-column
153
+        align="center"
154
+        prop="name"
155
+        label="设备名称"
156
+      ></el-table-column>
157
+      <el-table-column align="center" prop="status" label="设备状态">
158
+        <template #default="scope">
159
+          <div
160
+            :class="{
161
+              normal: scope.row.status == '正常',
162
+              error: scope.row.status == '异常',
163
+            }"
164
+          >
165
+            <div>{{ scope.row.status }}</div>
166
+          </div>
167
+        </template>
168
+      </el-table-column>
169
+      <el-table-column label="操作" align="center">
170
+        <template #default="scope">
171
+          <el-button type="primary" size="mini" @click="lookDetails(scope.row)"
172
+            >详情</el-button
173
+          >
174
+        </template>
175
+      </el-table-column>
176
+    </el-table>
177
+    <el-dialog
178
+      custom-class="deviceDetailsDialog"
179
+      v-model="isShowDeviceDetails"
180
+      title="详情"
181
+      width="15%"
182
+      align="left"
183
+      top="40vh"
184
+      style="z-index: 2019; background: rgba(0, 0, 0, 0)"
185
+      :modal="false"
186
+    >
187
+      <div class="details-container">
188
+        <div>设备名称:多媒体设备</div>
189
+        <div>设备状态:<span :class="{ online: true }">在线</span></div>
190
+        <div>位置:一号教学楼A101</div>
191
+        <div>型号:教学设备</div>
192
+        <div>质保期:5年</div>
193
+      </div>
194
+    </el-dialog>
195
+  </el-dialog>
163 196
 </template>
164 197
 
165 198
 <script >
166
-import { reactive, onMounted, ref, nextTick, onBeforeUnmount } from "vue";
199
+import { reactive, onBeforeMount, ref, nextTick, onMounted } from "vue";
167 200
 import { ElCarousel, ElCarouselItem } from "element-plus";
168 201
 import "vue3-video-play/dist/style.css";
169 202
 import { videoPlay } from "vue3-video-play";
170 203
 // import CircleProgress from './CircleProgress.vue';
171 204
 import { getDevice, getVideoUrl, getvideo } from "../request/api";
205
+//找到你的组件地址引入进来
206
+import VideoJs from "./VideoJs.vue";
207
+import { CountTo } from "vue3-count-to";
208
+import { Virtual } from "swiper/modules";
209
+import { useSwiper } from "swiper/vue";
210
+import { Swiper, SwiperSlide } from "swiper/vue";
211
+import "swiper/css";
172 212
 
173 213
 export default {
174 214
   name: "Histogram",
175
-  components: { videoPlay, ElCarousel, ElCarouselItem },
215
+  components: {
216
+    videoPlay,
217
+    ElCarousel,
218
+    ElCarouselItem,
219
+    CountTo,
220
+    Swiper,
221
+    SwiperSlide,
222
+    VideoJs,
223
+  },
176 224
   setup() {
225
+    const swiper = useSwiper();
226
+    console.log("swiper", swiper);
227
+    onMounted(() => {});
228
+
229
+    const data = reactive({
230
+      videoSrc:
231
+        "http://kbs-dokdo.gscdn.com/dokdo_300/_definst_/dokdo_300.stream/playlist.m3u8",
232
+    });
177 233
     // 视频数据
178 234
     const videoData = reactive({
179 235
       width: "100%", //播放器高度
@@ -181,7 +237,7 @@ export default {
181 237
       color: "red", //主题色
182 238
       // title: "互动教室", //视频名称
183 239
       // src: "https://cdn.jsdelivr.net/gh/xdlumia/files/video-play/IronMan.mp4", //视频源
184
-      src: "",
240
+      src: "https://scpull04.scjtonline.cn/scgro4/C436715B032077D498730AC93826DE29.m3u8?t=67c9399e&k=fe0359a57220382aa6a8bc859e918ade",
185 241
       type: "m3u8",
186 242
       muted: true, //静音
187 243
       webFullScreen: false,
@@ -206,26 +262,55 @@ export default {
206 262
     });
207 263
     //视频播放弹框
208 264
     const videoDialog = ref(false);
265
+    const asd = ref(false);
266
+
267
+    //播放视频索引
268
+    let videoindex = ref(0);
269
+    let monitorchange = function (val) {
270
+      // let videos = document.querySelectorAll('.interactclass video');
271
+
272
+      // setTimeout(() => {
273
+      //     for (let video of videos) {
274
+      //     console.log("sbdadbdbdb",video);
275
+      // video.pause();
276
+
277
+      // video.removeAttribute('src'); // empty source
278
+      // video.load();
279
+      // // video.remove();
280
+      // video.destroy();
281
+      // video.load();
282
+      // video.scr='';
283
+
284
+      // video.style.display = 'none';
285
+
286
+      // video=null;
287
+      // // video.dispose();
288
+      // }
289
+      // }, 500);
290
+
291
+      setTimeout(() => {
292
+        videoindex.value = val;
293
+      }, 500);
294
+    };
209 295
 
210 296
     // const  videoshow = ref(true)
211 297
     const dialogTitle = ref("");
212 298
     // 播放视频
213 299
     const lookVideo = function (item) {
300
+      console.log("item", item);
301
+
214 302
       videoDialog.value = true;
215 303
       dialogTitle.value = item.title;
216
-      let copysrc = JSON.parse(JSON.stringify(item));
217
-      console.log("sadi2", copysrc);
218
-      // videoData.src = item.scr
219
-      videoData.src = copysrc.src;
220
-      console.log("kankna222", videoData.src);
221 304
 
222
-      // getVideoUrl({ "rtsp": item.src }).then((res) => {
223
-      //     console.log("视频处理", res);
224
-      //     console.log("视频处理2", res.data.httpFlv);
305
+      // getVideoUrl({ rtsp: item.src }).then((res) => {
306
+      //   console.log("视频处理", res);
307
+      //   console.log("视频处理2", res.data.httpFlv);
225 308
 
226
-      //     videoData.src = res.data.httpFlv
227
-      // })
228
-      // videoData.src = src
309
+      //   videoData.src = res.data.httpFlv;
310
+      // });
311
+      // videoData.src = src;
312
+      videoData.src =
313
+        "https://weizhi.huanghuai.edu.cn/jk/hls/c7a1e790-b3bc-4092-a975-40d0e35d54c2.m3u8";
229 314
     };
230 315
     // 播放视频
231 316
     const onPlay = function () {
@@ -235,6 +320,7 @@ export default {
235 320
     let equipmentTotal = ref({});
236 321
     // 物联设备类型统计
237 322
     let interDevice = ref([]);
323
+
238 324
     // 智慧教室
239 325
     let carouselData = ref([]);
240 326
 
@@ -248,7 +334,6 @@ export default {
248 334
       // })
249 335
 
250 336
       let res = await getDevice();
251
-      console.log("s12312123", res);
252 337
       if (res.code !== 200) {
253 338
         ElMessage.error("数据请求出错");
254 339
       }
@@ -260,70 +345,80 @@ export default {
260 345
       //截取数组前6项
261 346
       interDevice.value = category;
262 347
 
348
+      // monitor.map((res) => {
349
+      // console.log('sadhi2', res.activeMonitor);
350
+      // if(res.activeMonitor.array){
351
+      // res.activeMonitor.array.map(array => {
352
+      // //视频转码
353
+      // getvideo({
354
+      // "rtsp": array.src,
355
+      // }).then((res) => {
356
+      // // array.src = 'http://kbs-dokdo.gscdn.com/dokdo_300/_definst_/dokdo_300.stream/playlist.m3u8'
357
+      // array.src=res.data.httpFlv
358
+      // })
359
+      // })
360
+      // }
361
+      //     if ( res.activeMonitor.array1) {
362
+      // res.activeMonitor.array1.map(array1 => {
363
+      // //视频转码
364
+      // getvideo({
365
+      // "rtsp": array1.src,
366
+      // }).then((res) => {
367
+      // // array1.src = 'http://kbs-dokdo.gscdn.com/dokdo_300/_definst_/dokdo_300.stream/playlist.m3u8'
368
+      // array1.src=res.data.httpFlv
369
+      // })
370
+      // })
371
+      // }
372
+
373
+      // })
263 374
       // 互动教室
264 375
       carouselData.value = monitor;
265 376
 
266
-      carouselData.value.map((res) => {
267
-        console.log("sadhi2", res.activeMonitor);
268
-
269
-        res.activeMonitor?.array.map((array) => {
270
-          //视频转码
271
-          getvideo({
272
-            rtsp: array.src,
273
-          }).then((res) => {
274
-            console.log("sadnuq21", res.data);
275
-            console.log("sadnuq2121", res.data.httpFlv);
276
-            // array.src='http://kbs-dokdo.gscdn.com/dokdo_300/_definst_/dokdo_300.stream/playlist.m3u8'
277
-            array.src = res.data.httpFlv;
278
-          });
279
-        });
280
-        res.activeMonitor?.array1.map((array1) => {
281
-          console.log("asdui1231", array1);
282
-          //视频转码
283
-          getvideo({
284
-            rtsp: array1.src,
285
-          }).then((res) => {
286
-            console.log("sadnuq212123s2", res.data);
287
-            // array1.src='http://kbs-dokdo.gscdn.com/dokdo_300/_definst_/dokdo_300.stream/playlist.m3u8'
288
-            array1.src = res.data.httpFlv;
289
-          });
290
-        });
291
-      });
292
-
293
-      //开启物联网统计数据定时刷新
294
-      startdeviceInterval();
295
-
296
-      // setTimeout(() => {
297
-      //     console.log('处理后监控视频数据===',carouselData.value);
298
-      // }, 2000);
377
+      console.log("hshshhs", carouselData.value);
299 378
     };
300 379
 
301
-    // 定时刷新物联网统计数据
302
-
303
-    let deviceInterval = ref(null);
304
-    const startdeviceInterval = () => {
305
-      //清除物联网统计数据刷新定时器
306
-      clearTimeout(deviceInterval.value);
307
-      deviceInterval.value = setInterval(() => {
308
-        getDeviceData();
309
-      }, 5000);
310
-    };
311
-
312
-    onMounted(() => {
380
+    onBeforeMount(() => {
313 381
       getDeviceData();
314
-    });
315 382
 
316
-    onBeforeUnmount(() => {
317
-      //清除物联网统计数据刷新定时器
318
-      clearTimeout(deviceInterval.value);
383
+      setTimeout(() => {
384
+        asd.value = true;
385
+      }, 1000);
319 386
     });
320
-
387
+    let deviceDialog = ref(false);
388
+    const showDeviceDialog = () => {
389
+      deviceDialog.value = true;
390
+    };
391
+    let deviceDetailsList = ref([
392
+      {
393
+        name: "多媒体设备",
394
+        status: "正常",
395
+      },
396
+      {
397
+        name: "多媒体设备",
398
+        status: "异常",
399
+      },
400
+    ]);
401
+    let isShowDeviceDetails = ref(false);
402
+    const lookDetails = (data) => {
403
+      console.log("darta", data);
404
+      isShowDeviceDetails.value = true;
405
+    };
406
+    const swiperOptions = ref({
407
+      autoplay: true,
408
+      renderExternal: (data) => {
409
+        console.log("data", data);
410
+      },
411
+    });
412
+    let swiperRef = ref(null);
321 413
     const d1img = ref("./img/d1_1.png");
322 414
     const d12img = ref("./img/d1_2.png");
323 415
     const d2img = ref("./img/d2.png");
324 416
     const d3img = ref("./img/d3.png");
325 417
     const deviceImg = ref("./img/");
326 418
     const frame = ref("./img/frame.png");
419
+    const videoBorder = ref("./img/video-border.png");
420
+    const swiperLeft = ref("./img/swiper-left.png");
421
+    const swiperRight = ref("./img/swiper-right.png");
327 422
 
328 423
     return {
329 424
       interDevice,
@@ -339,8 +434,22 @@ export default {
339 434
       d3img,
340 435
       deviceImg,
341 436
       dialogTitle,
342
-      startdeviceInterval,
343
-      frame
437
+      monitorchange,
438
+      videoindex,
439
+      data,
440
+      asd,
441
+      frame,
442
+      videoBorder,
443
+      deviceDialog,
444
+      deviceDetailsList,
445
+      lookDetails,
446
+      isShowDeviceDetails,
447
+      Virtual,
448
+      swiperLeft,
449
+      swiperRight,
450
+      swiper,
451
+      swiperOptions,
452
+      swiperRef,
344 453
     };
345 454
   },
346 455
 };
@@ -349,7 +458,7 @@ export default {
349 458
 <style scoped lang="scss">
350 459
 @import "../assets/css/right.scss";
351 460
 </style>
352
-<style lang="scss">
461
+<style lang="scss" >
353 462
 .videoDialog {
354 463
   .el-dialog__header {
355 464
     height: 49px;
@@ -378,4 +487,151 @@ export default {
378 487
     padding: 0 !important;
379 488
   }
380 489
 }
490
+.deviceDialog {
491
+
492
+  .el-dialog__header {
493
+    height: 39px;
494
+    line-height: 39px;
495
+    padding: 0;
496
+    background-color: #1b67d9;
497
+    margin-right: 0;
498
+      text-indent: 14px;
499
+
500
+    .el-dialog__title {
501
+      color: #fff;
502
+      font-family: Inter;
503
+    }
504
+
505
+    .el-dialog__headerbtn {
506
+      top: 0px;
507
+
508
+      i {
509
+        color: #fff;
510
+        font-size: 20px;
511
+        font-weight: bold;
512
+      }
513
+    }
514
+  }
515
+
516
+  .el-dialog__body {
517
+    // padding: 10px !important;
518
+    background-color: rgba(0, 0, 0, 0.5) !important;
519
+    .el-table {
520
+      --el-table-border-color: #253f48;
521
+      --el-table-row-hover-bg-color: rgba(0, 0, 0, 0.7);
522
+      .normal {
523
+        text-align: center;
524
+        /* text/pc/02-55-Regular */
525
+        font-family: "Alibaba PuHuiTi 2.0";
526
+        font-size: 14px;
527
+        font-style: normal;
528
+        font-weight: 400;
529
+        line-height: normal;
530
+        display: flex;
531
+        justify-content: center;
532
+        align-items: center;
533
+        div {
534
+          width: 50px;
535
+          height: 26px;
536
+          background-color: #0b4935;
537
+          // line-height: 30px;
538
+          line-height: 26px;
539
+          color: #13eb7f;
540
+          box-sizing: border-box;
541
+          border-radius: 4px;
542
+          // padding: 4px 6px;
543
+        }
544
+      }
545
+      .error {
546
+        text-align: center;
547
+        /* text/pc/02-55-Regular */
548
+        font-family: "Alibaba PuHuiTi 2.0";
549
+        font-size: 14px;
550
+        font-style: normal;
551
+        font-weight: 400;
552
+        line-height: normal;
553
+        display: flex;
554
+        justify-content: center;
555
+        align-items: center;
556
+        div {
557
+          width: 50px;
558
+          height: 26px;
559
+          background-color: #442124;
560
+          // line-height: 30px;
561
+          line-height: 26px;
562
+          color: #ff352e;
563
+          box-sizing: border-box;
564
+          border-radius: 4px;
565
+          // padding: 4px 6px;
566
+        }
567
+      }
568
+    }
569
+    .el-table th.el-table__cell {
570
+      background-color: rgba(0, 0, 0, 0.5);
571
+    }
572
+    .el-table tr {
573
+      background-color: rgba(0, 0, 0, 0.5);
574
+    }
575
+    .el-table td.el-table__cell,
576
+    .el-table th.el-table__cell.is-leaf {
577
+      border-color: #253f48;
578
+    }
579
+  }
580
+}
581
+
582
+.deviceDetailsDialog {
583
+
584
+
585
+    .el-dialog__header {
586
+        height: 49px;
587
+        line-height: 49px;
588
+        padding: 0px;
589
+        background-color: #1b67d9;
590
+        margin-right: 0;
591
+        text-indent: 10px;
592
+
593
+        .el-dialog__title {
594
+            color: #fff;
595
+            font-family: Inter;
596
+        }
597
+
598
+        .el-dialog__headerbtn {
599
+            top: 0px;
600
+
601
+            i {
602
+                color: #fff;
603
+                font-size: 20px;
604
+                font-weight: bold;
605
+            }
606
+        }
607
+    }
608
+
609
+    .el-dialog__body {
610
+        padding: 10px !important;
611
+        background-color: rgba(0, 0, 0, 0.8) !important;
612
+
613
+        .details-container {
614
+            color: #fff;
615
+
616
+            /* text/pc/03-55-Regular */
617
+            font-family: "Alibaba PuHuiTi 2.0";
618
+            font-size: 12px;
619
+            font-style: normal;
620
+            font-weight: 400;
621
+            line-height: normal;
622
+            line-height: 30px;
623
+
624
+            .online {
625
+                color: #30fcaa;
626
+            }
627
+        }
628
+    }
629
+}
630
+
631
+.el-tag .el-tag--success {
632
+    --el-tag-text-color: #000;
633
+}
634
+.d-player-video{
635
+  display: flex;
636
+}
381 637
 </style>

+ 6 - 0
src/main.js

@@ -8,6 +8,11 @@ import 'element-plus/dist/index.css'
8 8
 
9 9
 import vue3videoPlay from 'vue3-video-play' // 引入组件
10 10
 import 'vue3-video-play/dist/style.css' // 引入css
11
+import videojs from "video.js";
12
+import "video.js/dist/video-js.css";
13
+
14
+import vueScrolling from "vue-scrolling";
15
+// Vue.prototype.$video = videojs;
11 16
 
12 17
 
13 18
 
@@ -15,6 +20,7 @@ const app = createApp(App)
15 20
 console.log("全局的app", app)
16 21
 app.use(router)
17 22
 app.use(vue3videoPlay)
23
+app.use(vueScrolling)
18 24
 // app.config.globalProperties.$message = ElMessage;
19 25
 // app.use(ElMessage)
20 26
 app.mount("#app")

+ 18 - 2
src/request/api.js

@@ -1,3 +1,11 @@
1
+/*
2
+ * @Author: zy1125 1515706227@qq.com
3
+ * @Date: 2023-10-18 10:46:30
4
+ * @LastEditors: zy1125 1515706227@qq.com
5
+ * @LastEditTime: 2023-12-22 14:06:54
6
+ * @FilePath: \v3_yyz\src\request\api.js
7
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
8
+ */
1 9
 
2 10
 import { service, post } from './http'
3 11
 
@@ -21,8 +29,16 @@ export const getClassDetail = (deviceCode) => service.get(`/wisdomClass/deviceDe
21 29
 
22 30
 export const getVideoUrl = (data) => service.post(`/camera/getUrl`, data)
23 31
 
32
+//单点登录ticket校验
33
+export const getvalidateLogin = (params) => service.get(`/wisdomClass/validateLogin`, {params})
34
+
24 35
 //教室下拉选择数据
25
- export const classSelections = () => service.get("/wisdomClass/selections")
36
+export const classSelections = () => service.get("/wisdomClass/selections")
26 37
 
27 38
 //教室下搜索
28
- export const queryClassroom = (data) => service.post("/wisdomClass/queryClassroom",data)
39
+export const queryClassroom = (data) => service.post(`/wisdomClass/queryClassroom?category=${data.category}&gisBuildingCode=${data.gisBuildingCode}&gisLeaf=${data.gisLeaf}&statusCode=${data.statusCode}&classRoomName=${data.classRoomName}`)
40
+
41
+
42
+export const equipmentDetailsApi = (data) => service.get(`/wisdomClass/equipmentDetails?classroomCode=${data}`)
43
+
44
+

+ 19 - 0
src/request/api2.js

@@ -0,0 +1,19 @@
1
+/*
2
+ * @Author: zy1125 1515706227@qq.com
3
+ * @Date: 2023-10-18 10:46:30
4
+ * @LastEditors: zy1125 1515706227@qq.com
5
+ * @LastEditTime: 2023-12-25 10:40:18
6
+ * @FilePath: \v3_yyz\src\request\api.js
7
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
8
+ */
9
+
10
+import { service, post } from './http2'
11
+
12
+
13
+
14
+
15
+//单点登录ticket校验
16
+export const getvalidateLogin = (params) => service.get(`/center/cas/v1/getUser`, {params})
17
+
18
+
19
+

+ 8 - 0
src/request/http.js

@@ -1,3 +1,11 @@
1
+/*
2
+ * @Author: 半生瓜 1515706227@qq.com
3
+ * @Date: 2023-10-18 10:46:30
4
+ * @LastEditors: 半生瓜 1515706227@qq.com
5
+ * @LastEditTime: 2024-01-09 09:12:35
6
+ * @FilePath: \v3_yyz\src\request\http.js
7
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
8
+ */
1 9
 import axios from 'axios'
2 10
 import { ElMessage } from 'element-plus';
3 11
 

+ 90 - 0
src/request/http2.js

@@ -0,0 +1,90 @@
1
+/*
2
+ * @Author: zy1125 1515706227@qq.com
3
+ * @Date: 2023-12-25 10:38:37
4
+ * @LastEditors: zy1125 1515706227@qq.com
5
+ * @LastEditTime: 2023-12-25 10:39:45
6
+ * @FilePath: \v3_yyz\src\request\http2.js
7
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
8
+ */
9
+import axios from 'axios'
10
+import { ElMessage } from 'element-plus';
11
+
12
+
13
+export const service = axios.create({
14
+    baseURL: 'https://weizhi.huanghuai.edu.cn/cmccr-server',
15
+    // withCredentials: true, // 跨域请求时发送 cookies
16
+    timeout: 10000
17
+
18
+})
19
+
20
+
21
+
22
+
23
+// 请求拦截器
24
+service.interceptors.request.use(
25
+    config => {
26
+        // const token = session.get('Token');
27
+        // token && (config.headers.Authorization = token);
28
+        if (config.method.toUpperCase() === "POST") {
29
+            config.headers['Content-Type'] = 'application/json;charset=utf-8';
30
+        }
31
+        return config
32
+    },
33
+    error => {
34
+        return Promise.error(error)
35
+    }
36
+)
37
+// 响应请求拦截器
38
+
39
+service.interceptors.response.use(
40
+    response => {
41
+        // console.log("response", response)
42
+        if (response.status === 200) {
43
+            return Promise.resolve(response.data)
44
+        } else {
45
+            return Promise.reject(response)
46
+        }
47
+    },
48
+    error => {
49
+        if (error.response.status) {
50
+            switch (error.response.status) {
51
+                case 500:
52
+                    console.error("网络错误,请稍后再试");
53
+                    break;
54
+                case 404:
55
+                    console.error("请求路径出错");
56
+                    break;
57
+
58
+                default:
59
+                    console.error(error.response.data.console)
60
+            }
61
+            return Promise.reject(error.response)
62
+        }
63
+    }
64
+)
65
+
66
+
67
+
68
+export function get(url, params) {
69
+    return new Promise((resolve, reject) => {
70
+        service.get(url, {
71
+            params
72
+        }).then(res => {
73
+            resolve(res)
74
+        }).catch(err => {
75
+            reject(err)
76
+        })
77
+    })
78
+}
79
+
80
+export function post(url, params) {
81
+    return new Promise((resolve, reject) => {
82
+        service.post(url,
83
+            params
84
+        ).then(res => {
85
+            resolve(res)
86
+        }).catch(err => {
87
+            reject(err)
88
+        })
89
+    })
90
+}

+ 52 - 18
src/router/index.js

@@ -1,18 +1,28 @@
1
+/*
2
+ * @Author: zy1125 1515706227@qq.com
3
+ * @Date: 2023-11-09 09:07:06
4
+ * @LastEditors: zy1125 1515706227@qq.com
5
+ * @LastEditTime: 2023-12-25 13:59:36
6
+ * @FilePath: \v3_yyz\src\router\index.js
7
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
8
+ */
1 9
 import { createRouter, createWebHashHistory } from 'vue-router'
2 10
 import Home from '../components/UeVideo.vue'
3 11
 import Login from '../components/Login.vue'
12
+import { getvalidateLogin } from '../request/api2'
4 13
 
5 14
 const routes = [
6
-    {
7
-        path: '/login',
8
-        name: 'Login',
9
-        component: Login
10
-      },
11
-    {
12
-        path: '/',
13
-        name: 'Home',
14
-        component: Home
15
-    }
15
+
16
+  {
17
+    path: '/',
18
+    name: 'Home',
19
+    component: Home
20
+  },
21
+  {
22
+    path: '/login',
23
+    name: 'Login',
24
+    component: Login
25
+  }
16 26
 ]
17 27
 
18 28
 const router = createRouter({
@@ -21,13 +31,37 @@ const router = createRouter({
21 31
 })
22 32
 
23 33
 
24
-// router.beforeEach(function(to, from, next) {
25
-//   if (!localStorage.getItem("myAdminToken")) {
26
-//       if (to.path !== '/login') {
27
-//           return next('/login')
28
-//       }
29
-//   }
30
-//   next()
31
-// })
34
+router.beforeEach(function (to, from, next) {
35
+
36
+  let urlA = location.href.indexOf('ticket')
37
+  if (urlA == -1) {
38
+    // if (to.path == '/') {
39
+    //   return next('/login')
40
+    // }
41
+  } else {
42
+    const url = window.location.href;
43
+    const paramStr = url.split("?")[1].split("#")[0];
44
+    // console.log('sadasd' ,paramStr);
45
+    let query = {
46
+      casLoginUrl: "https://cas.huanghuai.edu.cn/cas/serviceValidate?" + paramStr + '&service=https://weizhi.huanghuai.edu.cn/xyyzioc/#/'
47
+    }
48
+    console.log('aslddddd',query);
49
+    getvalidateLogin(query).then(res => {
50
+
51
+      
52
+      console.log('yzzzzzzz',res);
53
+      let resjson=res
54
+      console.log('asdjbasbd',typeof(resjson));
55
+
56
+      if (resjson.indexOf('INVALID_TICKET')!==-1) {
57
+        console.log('校验失效了');
58
+        window.location.href='https://cas.huanghuai.edu.cn/cas/?service=https://weizhi.huanghuai.edu.cn/xyyzioc/#/'
59
+      }
60
+
61
+    })
62
+
63
+  }
64
+  next()
65
+})
32 66
 
33 67
 export default router

+ 0 - 600
src/webRtcPlayer.js

@@ -1,600 +0,0 @@
1
-// Copyright Epic Games, Inc. All Rights Reserved.
2
-
3
-function webRtcPlayer(parOptions) {
4
-    parOptions = typeof parOptions !== 'undefined' ? parOptions : {};
5
-
6
-    var self = this;
7
-    const urlParams = new URLSearchParams(window.location.search);
8
-
9
-    //**********************
10
-    //Config setup
11
-    //**********************
12
-    this.cfg = typeof parOptions.peerConnectionOptions !== 'undefined' ? parOptions.peerConnectionOptions : {};
13
-    this.cfg.sdpSemantics = 'unified-plan';
14
-    // this.cfg.rtcAudioJitterBufferMaxPackets = 10;
15
-    // this.cfg.rtcAudioJitterBufferFastAccelerate = true;
16
-    // this.cfg.rtcAudioJitterBufferMinDelayMs = 0;
17
-
18
-    // If this is true in Chrome 89+ SDP is sent that is incompatible with UE Pixel Streaming 4.26 and below.
19
-    // However 4.27 Pixel Streaming does not need this set to false as it supports `offerExtmapAllowMixed`.
20
-    // tdlr; uncomment this line for older versions of Pixel Streaming that need Chrome 89+.
21
-    this.cfg.offerExtmapAllowMixed = false;
22
-
23
-    this.forceTURN = urlParams.has('ForceTURN');
24
-    if (this.forceTURN) {
25
-        console.log("Forcing TURN usage by setting ICE Transport Policy in peer connection config.");
26
-        this.cfg.iceTransportPolicy = "relay";
27
-    }
28
-
29
-    this.cfg.bundlePolicy = "balanced";
30
-    this.forceMaxBundle = urlParams.has('ForceMaxBundle');
31
-    if (this.forceMaxBundle) {
32
-        this.cfg.bundlePolicy = "max-bundle";
33
-    }
34
-
35
-    //**********************
36
-    //Variables
37
-    //**********************
38
-    this.pcClient = null;
39
-    this.dcClient = null;
40
-    this.tnClient = null;
41
-
42
-    this.sdpConstraints = {
43
-        offerToReceiveAudio: 1, //Note: if you don't need audio you can get improved latency by turning this off.
44
-        offerToReceiveVideo: 1,
45
-        voiceActivityDetection: false
46
-    };
47
-
48
-    // See https://www.w3.org/TR/webrtc/#dom-rtcdatachannelinit for values (this is needed for Firefox to be consistent with Chrome.)
49
-    this.dataChannelOptions = { ordered: true };
50
-
51
-    // This is useful if the video/audio needs to autoplay (without user input) as browsers do not allow autoplay non-muted of sound sources without user interaction.
52
-    this.startVideoMuted = typeof parOptions.startVideoMuted !== 'undefined' ? parOptions.startVideoMuted : false;
53
-    this.autoPlayAudio = typeof parOptions.autoPlayAudio !== 'undefined' ? parOptions.autoPlayAudio : true;
54
-
55
-    // To enable mic in browser use SSL/localhost and have ?useMic in the query string.
56
-    this.useMic = urlParams.has('useMic');
57
-    if (!this.useMic) {
58
-        console.log("Microphone access is not enabled. Pass ?useMic in the url to enable it.");
59
-    }
60
-
61
-    // When ?useMic check for SSL or localhost
62
-    let isLocalhostConnection = location.hostname === "localhost" || location.hostname === "127.0.0.1";
63
-    let isHttpsConnection = location.protocol === 'https:';
64
-    if (this.useMic && !isLocalhostConnection && !isHttpsConnection) {
65
-        this.useMic = false;
66
-        console.error("Microphone access in the browser will not work if you are not on HTTPS or localhost. Disabling mic access.");
67
-        console.error("For testing you can enable HTTP microphone access Chrome by visiting chrome://flags/ and enabling 'unsafely-treat-insecure-origin-as-secure'");
68
-    }
69
-
70
-    // Prefer SFU or P2P connection
71
-    this.preferSFU = urlParams.has('preferSFU');
72
-    console.log(this.preferSFU ?
73
-        "The browser will signal it would prefer an SFU connection. Remove ?preferSFU from the url to signal for P2P usage." :
74
-        "The browser will signal for a P2P connection. Pass ?preferSFU in the url to signal for SFU usage.");
75
-
76
-    // Latency tester
77
-    this.latencyTestTimings =
78
-    {
79
-        TestStartTimeMs: null,
80
-        UEReceiptTimeMs: null,
81
-        UEEncodeMs: null,
82
-        UECaptureToSendMs: null,
83
-        UETransmissionTimeMs: null,
84
-        BrowserReceiptTimeMs: null,
85
-        FrameDisplayDeltaTimeMs: null,
86
-        Reset: function () {
87
-            this.TestStartTimeMs = null;
88
-            this.UEReceiptTimeMs = null;
89
-            this.UEEncodeMs = null,
90
-                this.UECaptureToSendMs = null,
91
-                this.UETransmissionTimeMs = null;
92
-            this.BrowserReceiptTimeMs = null;
93
-            this.FrameDisplayDeltaTimeMs = null;
94
-        },
95
-        SetUETimings: function (UETimings) {
96
-            this.UEReceiptTimeMs = UETimings.ReceiptTimeMs;
97
-            this.UEEncodeMs = UETimings.EncodeMs,
98
-                this.UECaptureToSendMs = UETimings.CaptureToSendMs,
99
-                this.UETransmissionTimeMs = UETimings.TransmissionTimeMs;
100
-            this.BrowserReceiptTimeMs = Date.now();
101
-            this.OnAllLatencyTimingsReady(this);
102
-        },
103
-        SetFrameDisplayDeltaTime: function (DeltaTimeMs) {
104
-            if (this.FrameDisplayDeltaTimeMs == null) {
105
-                this.FrameDisplayDeltaTimeMs = Math.round(DeltaTimeMs);
106
-                this.OnAllLatencyTimingsReady(this);
107
-            }
108
-        },
109
-        OnAllLatencyTimingsReady: function (Timings) { }
110
-    }
111
-
112
-    //**********************
113
-    //Functions
114
-    //**********************
115
-
116
-    //Create Video element and expose that as a parameter
117
-    this.createWebRtcVideo = function () {
118
-        var video = document.createElement('video');
119
-
120
-        video.id = "streamingVideo";
121
-        video.playsInline = true;
122
-        video.disablepictureinpicture = true;
123
-        // video.muted = self.startVideoMuted;
124
-        // 音频
125
-        video.muted = true;
126
-        // 开启全屏
127
-        video.style.width = "100%"
128
-        video.style.height = "100%"
129
-        video.style.objectFit = "fill"
130
-        video.style.margin = 0;
131
-        video.style.padding = 0;
132
-        video.style.top = 0;
133
-        video.style.left = 0;
134
-        video.style.position = "relative";
135
-        // video.style.zIndex = 0;
136
-        video.style.cursor = "pointer";
137
-        // video.style.overflow="hidden";
138
-
139
-
140
-        video.addEventListener('loadedmetadata', function (e) {
141
-            if (self.onVideoInitialised) {
142
-                self.onVideoInitialised();
143
-            }
144
-        }, true);
145
-
146
-        // Check if request video frame callback is supported
147
-        if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
148
-            // The API is supported! 
149
-
150
-            const onVideoFrameReady = (now, metadata) => {
151
-
152
-                if (metadata.receiveTime && metadata.expectedDisplayTime) {
153
-                    const receiveToCompositeMs = metadata.presentationTime - metadata.receiveTime;
154
-                    self.aggregatedStats.receiveToCompositeMs = receiveToCompositeMs;
155
-                }
156
-
157
-
158
-                // Re-register the callback to be notified about the next frame.
159
-                video.requestVideoFrameCallback(onVideoFrameReady);
160
-            };
161
-
162
-            // Initially register the callback to be notified about the first frame.
163
-            video.requestVideoFrameCallback(onVideoFrameReady);
164
-        }
165
-
166
-        return video;
167
-    }
168
-
169
-    this.video = this.createWebRtcVideo();
170
-    this.availableVideoStreams = new Map();
171
-
172
-    function onsignalingstatechange(state) {
173
-        console.info('Signaling state change. |', state.srcElement.signalingState, "|")
174
-    };
175
-
176
-    function oniceconnectionstatechange(state) {
177
-        console.info('Browser ICE connection |', state.srcElement.iceConnectionState, '|')
178
-    };
179
-
180
-    function onicegatheringstatechange(state) {
181
-        console.info('Browser ICE gathering |', state.srcElement.iceGatheringState, '|')
182
-    };
183
-
184
-    function handleOnTrack(e) {
185
-        if (e.track) {
186
-            console.log('Got track. | Kind=' + e.track.kind + ' | Id=' + e.track.id + ' | readyState=' + e.track.readyState + ' |');
187
-        }
188
-
189
-        if (e.track.kind == "audio") {
190
-            handleOnAudioTrack(e.streams[0]);
191
-            return;
192
-        }
193
-        else (e.track.kind == "video")
194
-        {
195
-            for (const s of e.streams) {
196
-                if (!self.availableVideoStreams.has(s.id)) {
197
-                    self.availableVideoStreams.set(s.id, s);
198
-                }
199
-            }
200
-
201
-            self.video.srcObject = e.streams[0];
202
-
203
-            // All tracks are added "muted" by WebRTC/browser and become unmuted when media is being sent
204
-            e.track.onunmute = () => {
205
-                self.video.srcObject = e.streams[0];
206
-                self.onNewVideoTrack(e.streams);
207
-            }
208
-        }
209
-    };
210
-
211
-    function handleOnAudioTrack(audioMediaStream) {
212
-        // do nothing the video has the same media stream as the audio track we have here (they are linked)
213
-        if (self.video.srcObject == audioMediaStream) {
214
-            return;
215
-        }
216
-        // video element has some other media stream that is not associated with this audio track
217
-        else if (self.video.srcObject && self.video.srcObject !== audioMediaStream) {
218
-            // create a new audio element
219
-            let audioElem = document.createElement("Audio");
220
-            audioElem.srcObject = audioMediaStream;
221
-
222
-            // there is no way to autoplay audio (even muted), so we defer audio until first click
223
-            if (!self.autoPlayAudio) {
224
-
225
-                let clickToPlayAudio = function () {
226
-                    audioElem.play();
227
-                    self.video.removeEventListener("click", clickToPlayAudio);
228
-                };
229
-
230
-                self.video.addEventListener("click", clickToPlayAudio);
231
-            }
232
-            // we assume the user has clicked somewhere on the page and autoplaying audio will work
233
-            else {
234
-                audioElem.play();
235
-            }
236
-            console.log('Created new audio element to play seperate audio stream.');
237
-        }
238
-
239
-    }
240
-
241
-    function onDataChannel(dataChannelEvent) {
242
-        // This is the primary data channel code path when we are "receiving"
243
-        console.log("Data channel created for us by browser as we are a receiving peer.");
244
-        self.dcClient = dataChannelEvent.channel;
245
-        setupDataChannelCallbacks(self.dcClient);
246
-    }
247
-
248
-    function createDataChannel(pc, label, options) {
249
-        // This is the primary data channel code path when we are "offering"
250
-        let datachannel = pc.createDataChannel(label, options);
251
-        console.log(`Created datachannel (${label})`);
252
-        setupDataChannelCallbacks(datachannel);
253
-        return datachannel;
254
-    }
255
-
256
-    function setupDataChannelCallbacks(datachannel) {
257
-        try {
258
-            // Inform browser we would like binary data as an ArrayBuffer (FF chooses Blob by default!)
259
-            datachannel.binaryType = "arraybuffer";
260
-
261
-            datachannel.onopen = function (e) {
262
-                console.log("Data channel connected");
263
-                if (self.onDataChannelConnected) {
264
-                    self.onDataChannelConnected();
265
-                }
266
-            }
267
-
268
-            datachannel.onclose = function (e) {
269
-                console.log("Data channel connected", e);
270
-            }
271
-
272
-            datachannel.onmessage = function (e) {
273
-                if (self.onDataChannelMessage) {
274
-                    self.onDataChannelMessage(e.data);
275
-                }
276
-            }
277
-
278
-            datachannel.onerror = function (e) {
279
-                console.error("Data channel error", e);
280
-            }
281
-
282
-            return datachannel;
283
-        } catch (e) {
284
-            console.warn('No data channel', e);
285
-            return null;
286
-        }
287
-    }
288
-
289
-    function onicecandidate(e) {
290
-        let candidate = e.candidate;
291
-        if (candidate && candidate.candidate) {
292
-            console.log("%c[Browser ICE candidate]", "background: violet; color: black", "| Type=", candidate.type, "| Protocol=", candidate.protocol, "| Address=", candidate.address, "| Port=", candidate.port, "|");
293
-            self.onWebRtcCandidate(candidate);
294
-        }
295
-    };
296
-
297
-    function handleCreateOffer(pc) {
298
-        pc.createOffer(self.sdpConstraints).then(function (offer) {
299
-
300
-            // Munging is where we modifying the sdp string to set parameters that are not exposed to the browser's WebRTC API
301
-            mungeSDPOffer(offer);
302
-
303
-            // Set our munged SDP on the local peer connection so it is "set" and will be send across
304
-            pc.setLocalDescription(offer);
305
-            if (self.onWebRtcOffer) {
306
-                self.onWebRtcOffer(offer);
307
-            }
308
-        },
309
-            function () { console.warn("Couldn't create offer") });
310
-    }
311
-
312
-    function mungeSDPOffer(offer) {
313
-
314
-        // turn off video-timing sdp sent from browser
315
-        //offer.sdp = offer.sdp.replace("http://www.webrtc.org/experiments/rtp-hdrext/playout-delay", "");
316
-
317
-        // this indicate we support stereo (Chrome needs this)
318
-        offer.sdp = offer.sdp.replace('useinbandfec=1', 'useinbandfec=1;stereo=1;sprop-maxcapturerate=48000');
319
-
320
-    }
321
-
322
-    function setupPeerConnection(pc) {
323
-        //Setup peerConnection events
324
-        pc.onsignalingstatechange = onsignalingstatechange;
325
-        pc.oniceconnectionstatechange = oniceconnectionstatechange;
326
-        pc.onicegatheringstatechange = onicegatheringstatechange;
327
-
328
-        pc.ontrack = handleOnTrack;
329
-        pc.onicecandidate = onicecandidate;
330
-        pc.ondatachannel = onDataChannel;
331
-    };
332
-
333
-    function generateAggregatedStatsFunction() {
334
-        if (!self.aggregatedStats)
335
-            self.aggregatedStats = {};
336
-
337
-        return function (stats) {
338
-            //console.log('Printing Stats');
339
-
340
-            let newStat = {};
341
-
342
-            stats.forEach(stat => {
343
-                //                    console.log(JSON.stringify(stat, undefined, 4));
344
-                if (stat.type == 'inbound-rtp'
345
-                    && !stat.isRemote
346
-                    && (stat.mediaType == 'video' || stat.id.toLowerCase().includes('video'))) {
347
-
348
-                    newStat.timestamp = stat.timestamp;
349
-                    newStat.bytesReceived = stat.bytesReceived;
350
-                    newStat.framesDecoded = stat.framesDecoded;
351
-                    newStat.packetsLost = stat.packetsLost;
352
-                    newStat.bytesReceivedStart = self.aggregatedStats && self.aggregatedStats.bytesReceivedStart ? self.aggregatedStats.bytesReceivedStart : stat.bytesReceived;
353
-                    newStat.framesDecodedStart = self.aggregatedStats && self.aggregatedStats.framesDecodedStart ? self.aggregatedStats.framesDecodedStart : stat.framesDecoded;
354
-                    newStat.timestampStart = self.aggregatedStats && self.aggregatedStats.timestampStart ? self.aggregatedStats.timestampStart : stat.timestamp;
355
-
356
-                    if (self.aggregatedStats && self.aggregatedStats.timestamp) {
357
-                        if (self.aggregatedStats.bytesReceived) {
358
-                            // bitrate = bits received since last time / number of ms since last time
359
-                            //This is automatically in kbits (where k=1000) since time is in ms and stat we want is in seconds (so a '* 1000' then a '/ 1000' would negate each other)
360
-                            newStat.bitrate = 8 * (newStat.bytesReceived - self.aggregatedStats.bytesReceived) / (newStat.timestamp - self.aggregatedStats.timestamp);
361
-                            newStat.bitrate = Math.floor(newStat.bitrate);
362
-                            newStat.lowBitrate = self.aggregatedStats.lowBitrate && self.aggregatedStats.lowBitrate < newStat.bitrate ? self.aggregatedStats.lowBitrate : newStat.bitrate
363
-                            newStat.highBitrate = self.aggregatedStats.highBitrate && self.aggregatedStats.highBitrate > newStat.bitrate ? self.aggregatedStats.highBitrate : newStat.bitrate
364
-                        }
365
-
366
-                        if (self.aggregatedStats.bytesReceivedStart) {
367
-                            newStat.avgBitrate = 8 * (newStat.bytesReceived - self.aggregatedStats.bytesReceivedStart) / (newStat.timestamp - self.aggregatedStats.timestampStart);
368
-                            newStat.avgBitrate = Math.floor(newStat.avgBitrate);
369
-                        }
370
-
371
-                        if (self.aggregatedStats.framesDecoded) {
372
-                            // framerate = frames decoded since last time / number of seconds since last time
373
-                            newStat.framerate = (newStat.framesDecoded - self.aggregatedStats.framesDecoded) / ((newStat.timestamp - self.aggregatedStats.timestamp) / 1000);
374
-                            newStat.framerate = Math.floor(newStat.framerate);
375
-                            newStat.lowFramerate = self.aggregatedStats.lowFramerate && self.aggregatedStats.lowFramerate < newStat.framerate ? self.aggregatedStats.lowFramerate : newStat.framerate
376
-                            newStat.highFramerate = self.aggregatedStats.highFramerate && self.aggregatedStats.highFramerate > newStat.framerate ? self.aggregatedStats.highFramerate : newStat.framerate
377
-                        }
378
-
379
-                        if (self.aggregatedStats.framesDecodedStart) {
380
-                            newStat.avgframerate = (newStat.framesDecoded - self.aggregatedStats.framesDecodedStart) / ((newStat.timestamp - self.aggregatedStats.timestampStart) / 1000);
381
-                            newStat.avgframerate = Math.floor(newStat.avgframerate);
382
-                        }
383
-                    }
384
-                }
385
-
386
-                //Read video track stats
387
-                if (stat.type == 'track' && (stat.trackIdentifier == 'video_label' || stat.kind == 'video')) {
388
-                    newStat.framesDropped = stat.framesDropped;
389
-                    newStat.framesReceived = stat.framesReceived;
390
-                    newStat.framesDroppedPercentage = stat.framesDropped / stat.framesReceived * 100;
391
-                    newStat.frameHeight = stat.frameHeight;
392
-                    newStat.frameWidth = stat.frameWidth;
393
-                    newStat.frameHeightStart = self.aggregatedStats && self.aggregatedStats.frameHeightStart ? self.aggregatedStats.frameHeightStart : stat.frameHeight;
394
-                    newStat.frameWidthStart = self.aggregatedStats && self.aggregatedStats.frameWidthStart ? self.aggregatedStats.frameWidthStart : stat.frameWidth;
395
-                }
396
-
397
-                if (stat.type == 'candidate-pair' && stat.hasOwnProperty('currentRoundTripTime') && stat.currentRoundTripTime != 0) {
398
-                    newStat.currentRoundTripTime = stat.currentRoundTripTime;
399
-                }
400
-            });
401
-
402
-
403
-            if (self.aggregatedStats.receiveToCompositeMs) {
404
-                newStat.receiveToCompositeMs = self.aggregatedStats.receiveToCompositeMs;
405
-                self.latencyTestTimings.SetFrameDisplayDeltaTime(self.aggregatedStats.receiveToCompositeMs);
406
-            }
407
-
408
-            self.aggregatedStats = newStat;
409
-
410
-            if (self.onAggregatedStats)
411
-                self.onAggregatedStats(newStat)
412
-        }
413
-    };
414
-
415
-    let setupTransceiversAsync = async function (pc) {
416
-
417
-        let hasTransceivers = pc.getTransceivers().length > 0;
418
-
419
-        // Setup a transceiver for getting UE video
420
-        pc.addTransceiver("video", { direction: "recvonly" });
421
-
422
-        // Setup a transceiver for sending mic audio to UE and receiving audio from UE
423
-        if (!self.useMic) {
424
-            pc.addTransceiver("audio", { direction: "recvonly" });
425
-        }
426
-        else {
427
-            let audioSendOptions = self.useMic ?
428
-                {
429
-                    autoGainControl: false,
430
-                    channelCount: 1,
431
-                    echoCancellation: false,
432
-                    latency: 0,
433
-                    noiseSuppression: false,
434
-                    sampleRate: 48000,
435
-                    volume: 1.0
436
-                } : false;
437
-
438
-            // Note using mic on android chrome requires SSL or chrome://flags/ "unsafely-treat-insecure-origin-as-secure"
439
-            const stream = await navigator.mediaDevices.getUserMedia({ video: false, audio: audioSendOptions });
440
-            if (stream) {
441
-                if (hasTransceivers) {
442
-                    for (let transceiver of pc.getTransceivers()) {
443
-                        if (transceiver && transceiver.receiver && transceiver.receiver.track && transceiver.receiver.track.kind === "audio") {
444
-                            for (const track of stream.getTracks()) {
445
-                                if (track.kind && track.kind == "audio") {
446
-                                    transceiver.sender.replaceTrack(track);
447
-                                    transceiver.direction = "sendrecv";
448
-                                }
449
-                            }
450
-                        }
451
-                    }
452
-                }
453
-                else {
454
-                    for (const track of stream.getTracks()) {
455
-                        if (track.kind && track.kind == "audio") {
456
-                            pc.addTransceiver(track, { direction: "sendrecv" });
457
-                        }
458
-                    }
459
-                }
460
-            }
461
-            else {
462
-                pc.addTransceiver("audio", { direction: "recvonly" });
463
-            }
464
-        }
465
-    };
466
-
467
-
468
-    //**********************
469
-    //Public functions
470
-    //**********************
471
-
472
-    this.setVideoEnabled = function (enabled) {
473
-        self.video.srcObject.getTracks().forEach(track => track.enabled = enabled);
474
-    }
475
-
476
-    this.startLatencyTest = function (onTestStarted) {
477
-        // Can't start latency test without a video element
478
-        if (!self.video) {
479
-            return;
480
-        }
481
-
482
-        self.latencyTestTimings.Reset();
483
-        self.latencyTestTimings.TestStartTimeMs = Date.now();
484
-        onTestStarted(self.latencyTestTimings.TestStartTimeMs);
485
-    }
486
-
487
-    //This is called when revceiving new ice candidates individually instead of part of the offer
488
-    this.handleCandidateFromServer = function (iceCandidate) {
489
-        let candidate = new RTCIceCandidate(iceCandidate);
490
-
491
-        console.log("%c[Unreal ICE candidate]", "background: pink; color: black", "| Type=", candidate.type, "| Protocol=", candidate.protocol, "| Address=", candidate.address, "| Port=", candidate.port, "|");
492
-
493
-        // if forcing TURN, reject any candidates not relay
494
-        if (self.forceTURN) {
495
-            // check if no relay address is found, if so, we are assuming it means no TURN server
496
-            if (candidate.candidate.indexOf("relay") < 0) {
497
-                console.warn("Dropping candidate because it was not TURN relay.", "| Type=", candidate.type, "| Protocol=", candidate.protocol, "| Address=", candidate.address, "| Port=", candidate.port, "|")
498
-                return;
499
-            }
500
-        }
501
-
502
-        self.pcClient.addIceCandidate(candidate).catch(function (e) {
503
-            console.error("Failed to add ICE candidate", e);
504
-        });
505
-    };
506
-
507
-    //Called externaly to create an offer for the server
508
-    this.createOffer = function () {
509
-        if (self.pcClient) {
510
-            console.log("Closing existing PeerConnection")
511
-            self.pcClient.close();
512
-            self.pcClient = null;
513
-        }
514
-        self.pcClient = new RTCPeerConnection(self.cfg);
515
-        setupPeerConnection(self.pcClient);
516
-
517
-        setupTransceiversAsync(self.pcClient).finally(function () {
518
-            self.dcClient = createDataChannel(self.pcClient, 'cirrus', self.dataChannelOptions);
519
-            handleCreateOffer(self.pcClient);
520
-        });
521
-
522
-    };
523
-
524
-    //Called externaly when an offer is received from the server
525
-    this.receiveOffer = function (offer) {
526
-        var offerDesc = new RTCSessionDescription(offer);
527
-
528
-        if (!self.pcClient) {
529
-            console.log("Creating a new PeerConnection in the browser.")
530
-            self.pcClient = new RTCPeerConnection(self.cfg);
531
-            setupPeerConnection(self.pcClient);
532
-
533
-            // Put things here that happen post transceiver setup
534
-            self.pcClient.setRemoteDescription(offerDesc)
535
-                .then(() => {
536
-                    setupTransceiversAsync(self.pcClient).finally(function () {
537
-                        self.pcClient.createAnswer()
538
-                            .then(answer => self.pcClient.setLocalDescription(answer))
539
-                            .then(() => {
540
-                                if (self.onWebRtcAnswer) {
541
-                                    self.onWebRtcAnswer(self.pcClient.currentLocalDescription);
542
-                                }
543
-                            })
544
-                            .then(() => {
545
-                                let receivers = self.pcClient.getReceivers();
546
-                                for (let receiver of receivers) {
547
-                                    receiver.playoutDelayHint = 0;
548
-                                }
549
-                            })
550
-                            .catch((error) => console.error("createAnswer() failed:", error));
551
-                    });
552
-                });
553
-        }
554
-    };
555
-
556
-    //Called externaly when an answer is received from the server
557
-    this.receiveAnswer = function (answer) {
558
-        var answerDesc = new RTCSessionDescription(answer);
559
-        self.pcClient.setRemoteDescription(answerDesc);
560
-
561
-        let receivers = self.pcClient.getReceivers();
562
-        for (let receiver of receivers) {
563
-            receiver.playoutDelayHint = 0;
564
-        }
565
-    };
566
-
567
-    this.close = function () {
568
-        if (self.pcClient) {
569
-            console.log("Closing existing peerClient")
570
-            self.pcClient.close();
571
-            self.pcClient = null;
572
-        }
573
-        if (self.aggregateStatsIntervalId)
574
-            clearInterval(self.aggregateStatsIntervalId);
575
-    }
576
-
577
-    //Sends data across the datachannel
578
-    this.send = function (data) {
579
-        if (self.dcClient && self.dcClient.readyState == 'open') {
580
-            //console.log('Sending data on dataconnection', self.dcClient)
581
-            self.dcClient.send(data);
582
-        }
583
-    };
584
-
585
-    this.getStats = function (onStats) {
586
-        if (self.pcClient && onStats) {
587
-            self.pcClient.getStats(null).then((stats) => {
588
-                onStats(stats);
589
-            });
590
-        }
591
-    }
592
-
593
-    this.aggregateStats = function (checkInterval) {
594
-        let calcAggregatedStats = generateAggregatedStatsFunction();
595
-        let printAggregatedStats = () => { self.getStats(calcAggregatedStats); }
596
-        self.aggregateStatsIntervalId = setInterval(printAggregatedStats, checkInterval);
597
-    }
598
-}
599
-
600
-export default webRtcPlayer

+ 22 - 1
vite.config.js

@@ -1,3 +1,11 @@
1
+/*
2
+ * @Author: 半生瓜 1515706227@qq.com
3
+ * @Date: 2023-10-18 10:46:30
4
+ * @LastEditors: 半生瓜 1515706227@qq.com
5
+ * @LastEditTime: 2024-03-11 19:07:53
6
+ * @FilePath: \v3_yyz\vite.config.js
7
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
8
+ */
1 9
 import { defineConfig } from 'vite'
2 10
 import vue from '@vitejs/plugin-vue'
3 11
 import AutoImport from 'unplugin-auto-import/vite'
@@ -7,6 +15,19 @@ import postCssPxToRem from "postcss-pxtorem";
7 15
 
8 16
 export default defineConfig({
9 17
   base: './',//相对路径
18
+  build: {
19
+    minify: 'terser',
20
+    terserOptions: {
21
+        compress: {
22
+            //生产环境时移除console
23
+            drop_console: true,
24
+            drop_debugger: true,
25
+        }
26
+    }
27
+  },
28
+  server:{
29
+    host:'0.0.0.0',
30
+  },
10 31
   plugins: [
11 32
     vue(),
12 33
     Components({
@@ -28,7 +49,7 @@ export default defineConfig({
28 49
         })]
29 50
     }
30 51
   },
31
-  configureWebpack: (config) => {
52
+    configureWebpack: (config) => {
32 53
     console.log("config");
33 54
     config.module.rules.push(
34 55
       {

+ 540 - 24
yarn.lock

@@ -7,20 +7,47 @@
7 7
   resolved "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.5.tgz"
8 8
   integrity sha512-dlR6LdS+0SzOAPx/TPRhnoi7hE251OVeT2Snw0RguNbBSbjUHdWr0l3vcUUDg26rEysT89kCbtw1lVorBXLLCg==
9 9
 
10
-"@babel/parser@^7.15.8", "@babel/parser@^7.16.4":
11
-  version "7.18.5"
12
-  resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.18.5.tgz"
13
-  integrity sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw==
10
+"@babel/helper-string-parser@^7.25.9":
11
+  version "7.25.9"
12
+  resolved "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz"
13
+  integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
14
+
15
+"@babel/helper-validator-identifier@^7.25.9":
16
+  version "7.25.9"
17
+  resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz"
18
+  integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
19
+
20
+"@babel/parser@^7.15.8", "@babel/parser@^7.16.4", "@babel/parser@^7.23.5":
21
+  version "7.26.9"
22
+  resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.26.9.tgz"
23
+  integrity sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==
24
+  dependencies:
25
+    "@babel/types" "^7.26.9"
26
+
27
+"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5":
28
+  version "7.23.5"
29
+  resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz"
30
+  integrity sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==
31
+  dependencies:
32
+    regenerator-runtime "^0.14.0"
33
+
34
+"@babel/types@^7.26.9":
35
+  version "7.26.9"
36
+  resolved "https://registry.npmmirror.com/@babel/types/-/types-7.26.9.tgz"
37
+  integrity sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==
38
+  dependencies:
39
+    "@babel/helper-string-parser" "^7.25.9"
40
+    "@babel/helper-validator-identifier" "^7.25.9"
14 41
 
15 42
 "@ctrl/tinycolor@^3.4.1":
16 43
   version "3.6.0"
17 44
   resolved "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz"
18 45
   integrity sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ==
19 46
 
20
-"@element-plus/icons-vue@^2.0.6":
21
-  version "2.1.0"
22
-  resolved "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.1.0.tgz"
23
-  integrity sha512-PSBn3elNoanENc1vnCfh+3WA9fimRC7n+fWkf3rE5jvv+aBohNHABC/KAR5KWPecxWxDTVT1ERpRbOMRcOV/vA==
47
+"@element-plus/icons-vue@^2.3.1":
48
+  version "2.3.1"
49
+  resolved "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz"
50
+  integrity sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==
24 51
 
25 52
 "@floating-ui/core@^1.4.1":
26 53
   version "1.4.1"
@@ -42,11 +69,46 @@
42 69
   resolved "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.1.1.tgz"
43 70
   integrity sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==
44 71
 
45
-"@jridgewell/sourcemap-codec@^1.4.15":
72
+"@jridgewell/gen-mapping@^0.3.0":
73
+  version "0.3.5"
74
+  resolved "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz"
75
+  integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==
76
+  dependencies:
77
+    "@jridgewell/set-array" "^1.2.1"
78
+    "@jridgewell/sourcemap-codec" "^1.4.10"
79
+    "@jridgewell/trace-mapping" "^0.3.24"
80
+
81
+"@jridgewell/resolve-uri@^3.1.0":
82
+  version "3.1.2"
83
+  resolved "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz"
84
+  integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
85
+
86
+"@jridgewell/set-array@^1.2.1":
87
+  version "1.2.1"
88
+  resolved "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.2.1.tgz"
89
+  integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
90
+
91
+"@jridgewell/source-map@^0.3.3":
92
+  version "0.3.5"
93
+  resolved "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.5.tgz"
94
+  integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==
95
+  dependencies:
96
+    "@jridgewell/gen-mapping" "^0.3.0"
97
+    "@jridgewell/trace-mapping" "^0.3.9"
98
+
99
+"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15":
46 100
   version "1.4.15"
47 101
   resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz"
48 102
   integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
49 103
 
104
+"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.9":
105
+  version "0.3.25"
106
+  resolved "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz"
107
+  integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
108
+  dependencies:
109
+    "@jridgewell/resolve-uri" "^3.1.0"
110
+    "@jridgewell/sourcemap-codec" "^1.4.14"
111
+
50 112
 "@nodelib/fs.scandir@2.1.5":
51 113
   version "2.1.5"
52 114
   resolved "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
@@ -104,6 +166,47 @@
104 166
   resolved "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz"
105 167
   integrity sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==
106 168
 
169
+"@videojs/http-streaming@3.7.0":
170
+  version "3.7.0"
171
+  resolved "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.7.0.tgz"
172
+  integrity sha512-5uLFKBL8CvD56dxxJyuxqB5CY0tdoa4SE9KbXakeiAy6iFBUEPvTr2YGLKEWvQ8Lojs1wl+FQndLdv+GO7t9Fw==
173
+  dependencies:
174
+    "@babel/runtime" "^7.12.5"
175
+    "@videojs/vhs-utils" "4.0.0"
176
+    aes-decrypter "4.0.1"
177
+    global "^4.4.0"
178
+    m3u8-parser "^7.1.0"
179
+    mpd-parser "^1.2.2"
180
+    mux.js "7.0.1"
181
+    video.js "^7 || ^8"
182
+
183
+"@videojs/vhs-utils@^3.0.5":
184
+  version "3.0.5"
185
+  resolved "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz"
186
+  integrity sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==
187
+  dependencies:
188
+    "@babel/runtime" "^7.12.5"
189
+    global "^4.4.0"
190
+    url-toolkit "^2.2.1"
191
+
192
+"@videojs/vhs-utils@^4.0.0", "@videojs/vhs-utils@4.0.0":
193
+  version "4.0.0"
194
+  resolved "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.0.0.tgz"
195
+  integrity sha512-xJp7Yd4jMLwje2vHCUmi8MOUU76nxiwII3z4Eg3Ucb+6rrkFVGosrXlMgGnaLjq724j3wzNElRZ71D/CKrTtxg==
196
+  dependencies:
197
+    "@babel/runtime" "^7.12.5"
198
+    global "^4.4.0"
199
+    url-toolkit "^2.2.1"
200
+
201
+"@videojs/xhr@2.6.0":
202
+  version "2.6.0"
203
+  resolved "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.6.0.tgz"
204
+  integrity sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==
205
+  dependencies:
206
+    "@babel/runtime" "^7.5.5"
207
+    global "~4.4.0"
208
+    is-function "^1.0.1"
209
+
107 210
 "@vitejs/plugin-vue@^2.3.3":
108 211
   version "2.3.3"
109 212
   resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-2.3.3.tgz"
@@ -127,6 +230,17 @@
127 230
     "@vue/compiler-core" "3.2.37"
128 231
     "@vue/shared" "3.2.37"
129 232
 
233
+"@vue/compiler-sfc@2.7.16":
234
+  version "2.7.16"
235
+  resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz"
236
+  integrity sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==
237
+  dependencies:
238
+    "@babel/parser" "^7.23.5"
239
+    postcss "^8.4.14"
240
+    source-map "^0.6.1"
241
+  optionalDependencies:
242
+    prettier "^1.18.2 || ^2.0.0"
243
+
130 244
 "@vue/compiler-sfc@3.2.37":
131 245
   version "3.2.37"
132 246
   resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz"
@@ -226,11 +340,33 @@
226 340
   dependencies:
227 341
     vue-demi "*"
228 342
 
229
-acorn@^8.10.0, acorn@^8.9.0:
343
+"@xmldom/xmldom@^0.8.3":
344
+  version "0.8.10"
345
+  resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz"
346
+  integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==
347
+
348
+acorn@^8.10.0, acorn@^8.8.2, acorn@^8.9.0:
230 349
   version "8.10.0"
231 350
   resolved "https://registry.npmmirror.com/acorn/-/acorn-8.10.0.tgz"
232 351
   integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
233 352
 
353
+aes-decrypter@^4.0.1, aes-decrypter@4.0.1:
354
+  version "4.0.1"
355
+  resolved "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-4.0.1.tgz"
356
+  integrity sha512-H1nh/P9VZXUf17AA5NQfJML88CFjVBDuGkp5zDHa7oEhYN9TTpNLJknRY1ie0iSKWlDf6JRnJKaZVDSQdPy6Cg==
357
+  dependencies:
358
+    "@babel/runtime" "^7.12.5"
359
+    "@videojs/vhs-utils" "^3.0.5"
360
+    global "^4.4.0"
361
+    pkcs7 "^1.0.4"
362
+
363
+aes-decrypter@1.0.3:
364
+  version "1.0.3"
365
+  resolved "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-1.0.3.tgz"
366
+  integrity sha512-rsx8pfx7wJsn+ziYbpJ8XA5c93hKAtBCrfydxJqJCMT+qfjipd/B5wC2xHtBcoxyvlqJcpeAo3K55t0lXOn9yQ==
367
+  dependencies:
368
+    pkcs7 "^0.2.3"
369
+
234 370
 amfe-flexible@^2.2.1:
235 371
   version "2.2.1"
236 372
   resolved "https://registry.npmmirror.com/amfe-flexible/-/amfe-flexible-2.2.1.tgz"
@@ -263,6 +399,14 @@ axios@^1.4.0:
263 399
     form-data "^4.0.0"
264 400
     proxy-from-env "^1.1.0"
265 401
 
402
+babel-runtime@^6.9.2:
403
+  version "6.26.0"
404
+  resolved "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz"
405
+  integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==
406
+  dependencies:
407
+    core-js "^2.4.0"
408
+    regenerator-runtime "^0.11.0"
409
+
266 410
 balanced-match@^1.0.0:
267 411
   version "1.0.2"
268 412
   resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz"
@@ -292,6 +436,11 @@ braces@^3.0.2, braces@~3.0.2:
292 436
   dependencies:
293 437
     fill-range "^7.0.1"
294 438
 
439
+buffer-from@^1.0.0:
440
+  version "1.1.2"
441
+  resolved "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz"
442
+  integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
443
+
295 444
 chokidar@^3.5.3, "chokidar@>=3.0.0 <4.0.0":
296 445
   version "3.5.3"
297 446
   resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz"
@@ -314,7 +463,17 @@ combined-stream@^1.0.8:
314 463
   dependencies:
315 464
     delayed-stream "~1.0.0"
316 465
 
317
-core-js@^3.8.1:
466
+commander@^2.20.0:
467
+  version "2.20.3"
468
+  resolved "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz"
469
+  integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
470
+
471
+core-js@^2.4.0:
472
+  version "2.6.12"
473
+  resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz"
474
+  integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
475
+
476
+core-js@^3.8.1, core-js@^3.8.3:
318 477
   version "3.33.0"
319 478
   resolved "https://registry.npmjs.org/core-js/-/core-js-3.33.0.tgz"
320 479
   integrity sha512-HoZr92+ZjFEKar5HS6MC776gYslNOKHt75mEBKWKnPeFDpZ6nH5OeF3S6HFT1mUAUZKrzkez05VboaX8myjSuw==
@@ -324,10 +483,15 @@ csstype@^2.6.8:
324 483
   resolved "https://registry.npmmirror.com/csstype/-/csstype-2.6.20.tgz"
325 484
   integrity sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==
326 485
 
327
-dayjs@^1.11.3:
328
-  version "1.11.9"
329
-  resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.9.tgz"
330
-  integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==
486
+csstype@^3.1.0:
487
+  version "3.1.3"
488
+  resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz"
489
+  integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
490
+
491
+dayjs@^1.11.13:
492
+  version "1.11.13"
493
+  resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz"
494
+  integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
331 495
 
332 496
 debug@^4.3.4:
333 497
   version "4.3.4"
@@ -341,6 +505,11 @@ delayed-stream@~1.0.0:
341 505
   resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz"
342 506
   integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
343 507
 
508
+dom-walk@^0.1.0:
509
+  version "0.1.2"
510
+  resolved "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz"
511
+  integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==
512
+
344 513
 echarts@^5.4.3:
345 514
   version "5.4.3"
346 515
   resolved "https://registry.npmmirror.com/echarts/-/echarts-5.4.3.tgz"
@@ -349,20 +518,20 @@ echarts@^5.4.3:
349 518
     tslib "2.3.0"
350 519
     zrender "5.4.4"
351 520
 
352
-element-plus@^2.3.9:
353
-  version "2.3.9"
354
-  resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.3.9.tgz"
355
-  integrity sha512-TIOLnPl4cnoCPXqK3QYh+jpkthUBQnAM21O7o3Lhbse8v9pfrRXRTaBJtoEKnYNa8GZ4lZptUfH0PeZgDCNLUg==
521
+element-plus@^2.3.9, element-plus@^2.9.3:
522
+  version "2.9.6"
523
+  resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.6.tgz"
524
+  integrity sha512-D9zU28Ce0s/9O/Vp3ewemikxzFVA6gdZyMwmWijHijo+t5/9H3sHRTIm1WlfeNpFW2Yq0y8nHXD0fU5YxU6qlQ==
356 525
   dependencies:
357 526
     "@ctrl/tinycolor" "^3.4.1"
358
-    "@element-plus/icons-vue" "^2.0.6"
527
+    "@element-plus/icons-vue" "^2.3.1"
359 528
     "@floating-ui/dom" "^1.0.1"
360 529
     "@popperjs/core" "npm:@sxzz/popperjs-es@^2.11.7"
361 530
     "@types/lodash" "^4.14.182"
362 531
     "@types/lodash-es" "^4.17.6"
363 532
     "@vueuse/core" "^9.1.0"
364 533
     async-validator "^4.2.5"
365
-    dayjs "^1.11.3"
534
+    dayjs "^1.11.13"
366 535
     escape-html "^1.0.3"
367 536
     lodash "^4.17.21"
368 537
     lodash-es "^4.17.21"
@@ -375,6 +544,11 @@ emojis-list@^3.0.0:
375 544
   resolved "https://registry.npmmirror.com/emojis-list/-/emojis-list-3.0.0.tgz"
376 545
   integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
377 546
 
547
+es5-shim@^4.5.1:
548
+  version "4.6.7"
549
+  resolved "https://registry.npmjs.org/es5-shim/-/es5-shim-4.6.7.tgz"
550
+  integrity sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ==
551
+
378 552
 esbuild-windows-64@0.14.47:
379 553
   version "0.14.47"
380 554
   resolved "https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.14.47.tgz"
@@ -472,6 +646,30 @@ glob-parent@^5.1.2, glob-parent@~5.1.2:
472 646
   dependencies:
473 647
     is-glob "^4.0.1"
474 648
 
649
+global@^4.3.0, global@^4.3.1, global@^4.4.0, global@~4.4.0, global@4.4.0:
650
+  version "4.4.0"
651
+  resolved "https://registry.npmjs.org/global/-/global-4.4.0.tgz"
652
+  integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==
653
+  dependencies:
654
+    min-document "^2.19.0"
655
+    process "^0.11.10"
656
+
657
+global@~4.3.0:
658
+  version "4.3.2"
659
+  resolved "https://registry.npmjs.org/global/-/global-4.3.2.tgz"
660
+  integrity sha512-/4AybdwIDU4HkCUbJkZdWpe4P6vuw/CUtu+0I1YlLIPe7OlUO7KNJ+q/rO70CW2/NW6Jc6I62++Hzsf5Alu6rQ==
661
+  dependencies:
662
+    min-document "^2.19.0"
663
+    process "~0.5.1"
664
+
665
+global@4.3.2:
666
+  version "4.3.2"
667
+  resolved "https://registry.npmjs.org/global/-/global-4.3.2.tgz"
668
+  integrity sha512-/4AybdwIDU4HkCUbJkZdWpe4P6vuw/CUtu+0I1YlLIPe7OlUO7KNJ+q/rO70CW2/NW6Jc6I62++Hzsf5Alu6rQ==
669
+  dependencies:
670
+    min-document "^2.19.0"
671
+    process "~0.5.1"
672
+
475 673
 has@^1.0.3:
476 674
   version "1.0.3"
477 675
   resolved "https://registry.npmmirror.com/has/-/has-1.0.3.tgz"
@@ -479,7 +677,7 @@ has@^1.0.3:
479 677
   dependencies:
480 678
     function-bind "^1.1.1"
481 679
 
482
-hls.js@^1.0.10:
680
+hls.js@^1.0.10, hls.js@^1.4.12:
483 681
   version "1.4.12"
484 682
   resolved "https://registry.npmjs.org/hls.js/-/hls.js-1.4.12.tgz"
485 683
   integrity sha512-1RBpx2VihibzE3WE9kGoVCtrhhDWTzydzElk/kyRbEOLnb1WIE+3ZabM/L8BqKFTCL3pUy4QzhXgD1Q6Igr1JA==
@@ -489,6 +687,11 @@ immutable@^4.0.0:
489 687
   resolved "https://registry.npmmirror.com/immutable/-/immutable-4.3.2.tgz"
490 688
   integrity sha512-oGXzbEDem9OOpDWZu88jGiYCvIsLHMvGw+8OXlpsvTFvIQplQbjg1B1cvKg8f7Hoch6+NGjpPsH1Fr+Mc2D1aA==
491 689
 
690
+individual@^2.0.0:
691
+  version "2.0.0"
692
+  resolved "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz"
693
+  integrity sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g==
694
+
492 695
 is-binary-path@~2.1.0:
493 696
   version "2.1.0"
494 697
   resolved "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz"
@@ -515,6 +718,11 @@ is-extglob@^2.1.1:
515 718
   resolved "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz"
516 719
   integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
517 720
 
721
+is-function@^1.0.1:
722
+  version "1.0.2"
723
+  resolved "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz"
724
+  integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==
725
+
518 726
 is-glob@^4.0.1, is-glob@~4.0.1:
519 727
   version "4.0.3"
520 728
   resolved "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz"
@@ -539,6 +747,11 @@ jsonc-parser@^3.2.0:
539 747
   resolved "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz"
540 748
   integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==
541 749
 
750
+keycode@2.2.0:
751
+  version "2.2.0"
752
+  resolved "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz"
753
+  integrity sha512-ps3I9jAdNtRpJrbBvQjpzyFbss/skHqzS+eu4RxKLaEAtFqkjZaB6TZMSivPbLxf4K7VI4SjR0P5mRCX5+Q25A==
754
+
542 755
 leader-line@^1.0.7:
543 756
   version "1.0.7"
544 757
   resolved "https://registry.npmmirror.com/leader-line/-/leader-line-1.0.7.tgz"
@@ -573,6 +786,29 @@ lodash@*, lodash@^4.17.21:
573 786
   resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz"
574 787
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
575 788
 
789
+m3u8-parser@^6.0.0:
790
+  version "6.2.0"
791
+  resolved "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-6.2.0.tgz"
792
+  integrity sha512-qlC00JTxYOxawcqg+RB8jbyNwL3foY/nCY61kyWP+RCuJE9APLeqB/nSlTjb4Mg0yRmyERgjswpdQxMvkeoDrg==
793
+  dependencies:
794
+    "@babel/runtime" "^7.12.5"
795
+    "@videojs/vhs-utils" "^3.0.5"
796
+    global "^4.4.0"
797
+
798
+m3u8-parser@^7.1.0:
799
+  version "7.1.0"
800
+  resolved "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-7.1.0.tgz"
801
+  integrity sha512-7N+pk79EH4oLKPEYdgRXgAsKDyA/VCo0qCHlUwacttQA0WqsjZQYmNfywMvjlY9MpEBVZEt0jKFd73Kv15EBYQ==
802
+  dependencies:
803
+    "@babel/runtime" "^7.12.5"
804
+    "@videojs/vhs-utils" "^3.0.5"
805
+    global "^4.4.0"
806
+
807
+m3u8-parser@2.1.0:
808
+  version "2.1.0"
809
+  resolved "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-2.1.0.tgz"
810
+  integrity sha512-WbEpQ2FUaNGbJ0YanSeyj9D9ruu4FUvz+ZvebIzI2bSME+PUwcPXO1kKXZkjcPUAFruDikoOI5fWQNIA6JCCOQ==
811
+
576 812
 magic-string@^0.25.7:
577 813
   version "0.25.9"
578 814
   resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz"
@@ -631,6 +867,13 @@ mime-types@^2.1.12:
631 867
   dependencies:
632 868
     mime-db "1.52.0"
633 869
 
870
+min-document@^2.19.0:
871
+  version "2.19.0"
872
+  resolved "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz"
873
+  integrity sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==
874
+  dependencies:
875
+    dom-walk "^0.1.0"
876
+
634 877
 minimatch@^9.0.1, minimatch@^9.0.2:
635 878
   version "9.0.3"
636 879
   resolved "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.3.tgz"
@@ -653,11 +896,42 @@ mlly@^1.2.0, mlly@^1.4.0:
653 896
     pkg-types "^1.0.3"
654 897
     ufo "^1.1.2"
655 898
 
899
+mpd-parser@^1.0.1, mpd-parser@^1.2.2:
900
+  version "1.2.2"
901
+  resolved "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.2.2.tgz"
902
+  integrity sha512-QCfB1koOoZw6E5La1cx+W/Yd0EZlRhHMqMr4TAJez0eRTuPDzPM5FWoiOqjyo37W+ISPLzmfJACSbJFEBjbL4Q==
903
+  dependencies:
904
+    "@babel/runtime" "^7.12.5"
905
+    "@videojs/vhs-utils" "^3.0.5"
906
+    "@xmldom/xmldom" "^0.8.3"
907
+    global "^4.4.0"
908
+
656 909
 ms@2.1.2:
657 910
   version "2.1.2"
658 911
   resolved "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz"
659 912
   integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
660 913
 
914
+mux.js@^7.0.1:
915
+  version "7.0.2"
916
+  resolved "https://registry.npmjs.org/mux.js/-/mux.js-7.0.2.tgz"
917
+  integrity sha512-CM6+QuyDbc0qW1OfEjkd2+jVKzTXF+z5VOKH0eZxtZtnrG/ilkW/U7l7IXGtBNLASF9sKZMcK1u669cq50Qq0A==
918
+  dependencies:
919
+    "@babel/runtime" "^7.11.2"
920
+    global "^4.4.0"
921
+
922
+mux.js@4.3.2:
923
+  version "4.3.2"
924
+  resolved "https://registry.npmjs.org/mux.js/-/mux.js-4.3.2.tgz"
925
+  integrity sha512-g0q6DPdvb3yYcoK7ElBGobdSSrhY/RjPt19U7uUc733aqvc5bCS/aCvL9z+448y+IoCZnYDwyZfQBBXMSmGOaQ==
926
+
927
+mux.js@7.0.1:
928
+  version "7.0.1"
929
+  resolved "https://registry.npmjs.org/mux.js/-/mux.js-7.0.1.tgz"
930
+  integrity sha512-Omz79uHqYpMP1V80JlvEdCiOW1hiw4mBvDh9gaZEpxvB+7WYb2soZSzfuSRrK2Kh9Pm6eugQNrIpY/Bnyhk4hw==
931
+  dependencies:
932
+    "@babel/runtime" "^7.11.2"
933
+    global "^4.4.0"
934
+
661 935
 nanoid@^3.3.4:
662 936
   version "3.3.4"
663 937
   resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz"
@@ -673,6 +947,11 @@ normalize-wheel-es@^1.2.0:
673 947
   resolved "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz"
674 948
   integrity sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==
675 949
 
950
+parse-headers@^2.0.0:
951
+  version "2.0.5"
952
+  resolved "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz"
953
+  integrity sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==
954
+
676 955
 path-parse@^1.0.7:
677 956
   version "1.0.7"
678 957
   resolved "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz"
@@ -693,6 +972,18 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
693 972
   resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz"
694 973
   integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
695 974
 
975
+pkcs7@^0.2.3:
976
+  version "0.2.3"
977
+  resolved "https://registry.npmjs.org/pkcs7/-/pkcs7-0.2.3.tgz"
978
+  integrity sha512-kJRwmADEQUg+qJyRgWLtpEL9q9cFjZschejTEK3GRjKvnsU9G5WWoe/wKqRgbBoqWdVSeTUKP6vIA3Y72M3rWA==
979
+
980
+pkcs7@^1.0.4:
981
+  version "1.0.4"
982
+  resolved "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz"
983
+  integrity sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==
984
+  dependencies:
985
+    "@babel/runtime" "^7.5.5"
986
+
696 987
 pkg-types@^1.0.3:
697 988
   version "1.0.3"
698 989
   resolved "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.0.3.tgz"
@@ -707,7 +998,7 @@ postcss-pxtorem@^6.0.0:
707 998
   resolved "https://registry.npmmirror.com/postcss-pxtorem/-/postcss-pxtorem-6.0.0.tgz"
708 999
   integrity sha512-ZRXrD7MLLjLk2RNGV6UA4f5Y7gy+a/j1EqjAfp9NdcNYVjUMvg5HTYduTjSkKBkRkfqbg/iKrjMO70V4g1LZeg==
709 1000
 
710
-postcss@^8.0.0, postcss@^8.1.10, postcss@^8.4.13:
1001
+postcss@^8.0.0, postcss@^8.1.10, postcss@^8.4.13, postcss@^8.4.14:
711 1002
   version "8.4.14"
712 1003
   resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.14.tgz"
713 1004
   integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==
@@ -716,6 +1007,21 @@ postcss@^8.0.0, postcss@^8.1.10, postcss@^8.4.13:
716 1007
     picocolors "^1.0.0"
717 1008
     source-map-js "^1.0.2"
718 1009
 
1010
+"prettier@^1.18.2 || ^2.0.0":
1011
+  version "2.8.8"
1012
+  resolved "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz"
1013
+  integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
1014
+
1015
+process@^0.11.10:
1016
+  version "0.11.10"
1017
+  resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz"
1018
+  integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
1019
+
1020
+process@~0.5.1:
1021
+  version "0.5.2"
1022
+  resolved "https://registry.npmjs.org/process/-/process-0.5.2.tgz"
1023
+  integrity sha512-oNpcutj+nYX2FjdEW7PGltWhXulAnFlM0My/k48L90hARCOJtvBbQXc/6itV2jDvU5xAAtonP+r6wmQgCcbAUA==
1024
+
719 1025
 proxy-from-env@^1.1.0:
720 1026
   version "1.1.0"
721 1027
   resolved "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
@@ -733,6 +1039,16 @@ readdirp@~3.6.0:
733 1039
   dependencies:
734 1040
     picomatch "^2.2.1"
735 1041
 
1042
+regenerator-runtime@^0.11.0:
1043
+  version "0.11.1"
1044
+  resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz"
1045
+  integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
1046
+
1047
+regenerator-runtime@^0.14.0:
1048
+  version "0.14.0"
1049
+  resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz"
1050
+  integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==
1051
+
736 1052
 resolve@^1.22.0:
737 1053
   version "1.22.1"
738 1054
   resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.1.tgz"
@@ -770,6 +1086,20 @@ run-parallel@^1.1.9:
770 1086
   dependencies:
771 1087
     queue-microtask "^1.2.2"
772 1088
 
1089
+rust-result@^1.0.0:
1090
+  version "1.0.0"
1091
+  resolved "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz"
1092
+  integrity sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==
1093
+  dependencies:
1094
+    individual "^2.0.0"
1095
+
1096
+safe-json-parse@4.0.0:
1097
+  version "4.0.0"
1098
+  resolved "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-4.0.0.tgz"
1099
+  integrity sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==
1100
+  dependencies:
1101
+    rust-result "^1.0.0"
1102
+
773 1103
 sass@*, sass@^1.64.2:
774 1104
   version "1.64.2"
775 1105
   resolved "https://registry.npmmirror.com/sass/-/sass-1.64.2.tgz"
@@ -796,7 +1126,15 @@ source-map-js@^1.0.2, "source-map-js@>=0.6.2 <2.0.0":
796 1126
   resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz"
797 1127
   integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
798 1128
 
799
-source-map@^0.6.1:
1129
+source-map-support@~0.5.20:
1130
+  version "0.5.21"
1131
+  resolved "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz"
1132
+  integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
1133
+  dependencies:
1134
+    buffer-from "^1.0.0"
1135
+    source-map "^0.6.0"
1136
+
1137
+source-map@^0.6.0, source-map@^0.6.1:
800 1138
   version "0.6.1"
801 1139
   resolved "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz"
802 1140
   integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
@@ -818,6 +1156,21 @@ supports-preserve-symlinks-flag@^1.0.0:
818 1156
   resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
819 1157
   integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
820 1158
 
1159
+swiper@^11.2.5:
1160
+  version "11.2.5"
1161
+  resolved "https://registry.npmmirror.com/swiper/-/swiper-11.2.5.tgz"
1162
+  integrity sha512-nG0kbIyBfeE2BPFt9nPUX03qUBF75o6+enzjIT/DfCmbh8ORlwhc4eZz1+4H/yseAgb3H+OoEYzmb64i0tYNnQ==
1163
+
1164
+terser@^5.29.1:
1165
+  version "5.29.1"
1166
+  resolved "https://registry.npmmirror.com/terser/-/terser-5.29.1.tgz"
1167
+  integrity sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==
1168
+  dependencies:
1169
+    "@jridgewell/source-map" "^0.3.3"
1170
+    acorn "^8.8.2"
1171
+    commander "^2.20.0"
1172
+    source-map-support "~0.5.20"
1173
+
821 1174
 throttle-debounce@^3.0.1:
822 1175
   version "3.0.1"
823 1176
   resolved "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz"
@@ -835,6 +1188,11 @@ tslib@2.3.0:
835 1188
   resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz"
836 1189
   integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
837 1190
 
1191
+tsml@1.0.1:
1192
+  version "1.0.1"
1193
+  resolved "https://registry.npmjs.org/tsml/-/tsml-1.0.1.tgz"
1194
+  integrity sha512-3KmepnH9SUsoOVtg013CRrL7c+AK7ECaquAsJdvu4288EDJuraqBlP4PDXT/rLEJ9YDn4jqLAzRJsnFPx+V6lg==
1195
+
838 1196
 ufo@^1.1.2:
839 1197
   version "1.2.0"
840 1198
   resolved "https://registry.npmmirror.com/ufo/-/ufo-1.2.0.tgz"
@@ -897,6 +1255,121 @@ unplugin@^1.3.1, unplugin@^1.3.2, unplugin@^1.4.0:
897 1255
     webpack-sources "^3.2.3"
898 1256
     webpack-virtual-modules "^0.5.0"
899 1257
 
1258
+url-toolkit@^2.1.3, url-toolkit@^2.2.1:
1259
+  version "2.2.5"
1260
+  resolved "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz"
1261
+  integrity sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==
1262
+
1263
+"video.js@^5.17.0 || ^6.2.0":
1264
+  version "6.13.0"
1265
+  resolved "https://registry.npmjs.org/video.js/-/video.js-6.13.0.tgz"
1266
+  integrity sha512-36/JR/GhPQSZj0o+GNbhcEYv/b0SkV9SQsjlodAnzMQYN0TA7VhmqrKPYMCi1NGRYu7S9W3OaFCFoUxkYfSVlg==
1267
+  dependencies:
1268
+    babel-runtime "^6.9.2"
1269
+    global "4.3.2"
1270
+    safe-json-parse "4.0.0"
1271
+    tsml "1.0.1"
1272
+    videojs-font "2.1.0"
1273
+    videojs-ie8 "1.1.2"
1274
+    videojs-vtt.js "0.12.6"
1275
+    xhr "2.4.0"
1276
+
1277
+"video.js@^5.19.1 || ^6.2.0":
1278
+  version "6.13.0"
1279
+  resolved "https://registry.npmjs.org/video.js/-/video.js-6.13.0.tgz"
1280
+  integrity sha512-36/JR/GhPQSZj0o+GNbhcEYv/b0SkV9SQsjlodAnzMQYN0TA7VhmqrKPYMCi1NGRYu7S9W3OaFCFoUxkYfSVlg==
1281
+  dependencies:
1282
+    babel-runtime "^6.9.2"
1283
+    global "4.3.2"
1284
+    safe-json-parse "4.0.0"
1285
+    tsml "1.0.1"
1286
+    videojs-font "2.1.0"
1287
+    videojs-ie8 "1.1.2"
1288
+    videojs-vtt.js "0.12.6"
1289
+    xhr "2.4.0"
1290
+
1291
+"video.js@^7 || ^8", video.js@^8, video.js@^8.6.1:
1292
+  version "8.6.1"
1293
+  resolved "https://registry.npmjs.org/video.js/-/video.js-8.6.1.tgz"
1294
+  integrity sha512-CNYVJ5WWIZ7bOhbkkfcKqLGoc6WsE3Ft2RfS1lXdQTWk8UiSsPW2Ssk2JzPCA8qnIlUG9os/faCFsYWjyu4JcA==
1295
+  dependencies:
1296
+    "@babel/runtime" "^7.12.5"
1297
+    "@videojs/http-streaming" "3.7.0"
1298
+    "@videojs/vhs-utils" "^4.0.0"
1299
+    "@videojs/xhr" "2.6.0"
1300
+    aes-decrypter "^4.0.1"
1301
+    global "4.4.0"
1302
+    keycode "2.2.0"
1303
+    m3u8-parser "^6.0.0"
1304
+    mpd-parser "^1.0.1"
1305
+    mux.js "^7.0.1"
1306
+    safe-json-parse "4.0.0"
1307
+    videojs-contrib-quality-levels "4.0.0"
1308
+    videojs-font "4.1.0"
1309
+    videojs-vtt.js "0.15.5"
1310
+
1311
+videojs-contrib-hls@^5.15.0:
1312
+  version "5.15.0"
1313
+  resolved "https://registry.npmjs.org/videojs-contrib-hls/-/videojs-contrib-hls-5.15.0.tgz"
1314
+  integrity sha512-18zbMYZ0XRBKTPEayA9bFTWWrqhT9b4G8+zf0czJLD7Epe5PcK1I/3dflTHQeQ5rwlWir+/XnFU3sMg/B2MMcw==
1315
+  dependencies:
1316
+    aes-decrypter "1.0.3"
1317
+    global "^4.3.0"
1318
+    m3u8-parser "2.1.0"
1319
+    mux.js "4.3.2"
1320
+    url-toolkit "^2.1.3"
1321
+    video.js "^5.19.1 || ^6.2.0"
1322
+    videojs-contrib-media-sources "4.7.2"
1323
+    webwackify "0.1.6"
1324
+
1325
+videojs-contrib-media-sources@4.7.2:
1326
+  version "4.7.2"
1327
+  resolved "https://registry.npmjs.org/videojs-contrib-media-sources/-/videojs-contrib-media-sources-4.7.2.tgz"
1328
+  integrity sha512-e6iCHWBFuV05EGo7v+pS9iepObXnJ9joms467gzi8ZjpKVb3ifha9M0Ja24Rd8JfvYpzjltsgDVtGFDvIg4hQQ==
1329
+  dependencies:
1330
+    global "^4.3.0"
1331
+    mux.js "4.3.2"
1332
+    video.js "^5.17.0 || ^6.2.0"
1333
+    webwackify "0.1.6"
1334
+
1335
+videojs-contrib-quality-levels@4.0.0:
1336
+  version "4.0.0"
1337
+  resolved "https://registry.npmjs.org/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-4.0.0.tgz"
1338
+  integrity sha512-u5rmd8BjLwANp7XwuQ0Q/me34bMe6zg9PQdHfTS7aXgiVRbNTb4djcmfG7aeSrkpZjg+XCLezFNenlJaCjBHKw==
1339
+  dependencies:
1340
+    global "^4.4.0"
1341
+
1342
+videojs-font@2.1.0:
1343
+  version "2.1.0"
1344
+  resolved "https://registry.npmjs.org/videojs-font/-/videojs-font-2.1.0.tgz"
1345
+  integrity sha512-zFqWpLrXf1q8NtYx5qtZhMC6SLUFScDmR6j+UGPogobxR21lvXShhnzcNNMdOxJUuFLiToJ/BPpFUQwX4xhpvA==
1346
+
1347
+videojs-font@4.1.0:
1348
+  version "4.1.0"
1349
+  resolved "https://registry.npmjs.org/videojs-font/-/videojs-font-4.1.0.tgz"
1350
+  integrity sha512-X1LuPfLZPisPLrANIAKCknZbZu5obVM/ylfd1CN+SsCmPZQ3UMDPcvLTpPBJxcBuTpHQq2MO1QCFt7p8spnZ/w==
1351
+
1352
+videojs-ie8@1.1.2:
1353
+  version "1.1.2"
1354
+  resolved "https://registry.npmjs.org/videojs-ie8/-/videojs-ie8-1.1.2.tgz"
1355
+  integrity sha512-0Zb2T4MLkpfZbeGMK/Z93b8Lrepr+rLFoHgQV1CoDeFqXvH7b+Vsd/VHoILGxQrgCSHFQ7mAODR6oyMjuiD4/g==
1356
+  dependencies:
1357
+    es5-shim "^4.5.1"
1358
+
1359
+videojs-vtt.js@0.12.6:
1360
+  version "0.12.6"
1361
+  resolved "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.12.6.tgz"
1362
+  integrity sha512-XFXeGBQiljnElMhwCcZst0RDbZn2n8LU7ZScXryd3a00OaZsHAjdZu/7/RdSr7Z1jHphd45FnOvOQkGK4YrWCQ==
1363
+  dependencies:
1364
+    global "^4.3.1"
1365
+
1366
+videojs-vtt.js@0.15.5:
1367
+  version "0.15.5"
1368
+  resolved "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz"
1369
+  integrity sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==
1370
+  dependencies:
1371
+    global "^4.3.1"
1372
+
900 1373
 vite@^2.5.10, vite@^2.9.9:
901 1374
   version "2.9.12"
902 1375
   resolved "https://registry.npmmirror.com/vite/-/vite-2.9.12.tgz"
@@ -926,6 +1399,22 @@ vue-router@^4.2.5:
926 1399
   dependencies:
927 1400
     "@vue/devtools-api" "^6.5.0"
928 1401
 
1402
+vue-scrolling@^1.0.1:
1403
+  version "1.0.1"
1404
+  resolved "https://registry.npmmirror.com/vue-scrolling/-/vue-scrolling-1.0.1.tgz"
1405
+  integrity sha512-E9USTNpvNm033pE3kSvs9RUlPlyVXcutziNn1nyDjLDWrLG32jCjEdGhqy6/nOeGfls5Lg8wafPLgDJ/Ul8oow==
1406
+  dependencies:
1407
+    core-js "^3.8.3"
1408
+    vue "^2.6.14"
1409
+
1410
+vue@^2.6.14:
1411
+  version "2.7.16"
1412
+  resolved "https://registry.npmmirror.com/vue/-/vue-2.7.16.tgz"
1413
+  integrity sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==
1414
+  dependencies:
1415
+    "@vue/compiler-sfc" "2.7.16"
1416
+    csstype "^3.1.0"
1417
+
929 1418
 "vue@^3.0.0-0 || ^2.6.0", vue@^3.2.0, vue@^3.2.2, vue@^3.2.25, "vue@>= 3 < 4", "vue@2 || 3", vue@3.2.37:
930 1419
   version "3.2.37"
931 1420
   resolved "https://registry.npmmirror.com/vue/-/vue-3.2.37.tgz"
@@ -950,6 +1439,13 @@ vue3-number-roll-plus@^0.1.3:
950 1439
   resolved "https://registry.npmjs.org/vue3-number-roll-plus/-/vue3-number-roll-plus-0.1.3.tgz"
951 1440
   integrity sha512-DngEDTyEIrDPIefl3ZEH5ZFtnTw0xNOb71DELLOW6PeQxep41jQ1ntJPWugj2csEIzMW7jlLNSBAASBGR81Ykw==
952 1441
 
1442
+vue3-seamless-scroll@^3.0.2:
1443
+  version "3.0.2"
1444
+  resolved "https://registry.npmmirror.com/vue3-seamless-scroll/-/vue3-seamless-scroll-3.0.2.tgz"
1445
+  integrity sha512-LpKoL1ht71MASabUBsoSqbhLqcuKSrD+u01dgHac+/cthPAShKvcdM7dSGtaxjVntSFqdeViqJssjcwy/KqRSA==
1446
+  dependencies:
1447
+    element-plus "^2.9.3"
1448
+
953 1449
 vue3-video-play@^1.3.1-beta.6:
954 1450
   version "1.3.1-beta.6"
955 1451
   resolved "https://registry.npmjs.org/vue3-video-play/-/vue3-video-play-1.3.1-beta.6.tgz"
@@ -969,6 +1465,26 @@ webpack-virtual-modules@^0.5.0:
969 1465
   resolved "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz"
970 1466
   integrity sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==
971 1467
 
1468
+webwackify@0.1.6:
1469
+  version "0.1.6"
1470
+  resolved "https://registry.npmjs.org/webwackify/-/webwackify-0.1.6.tgz"
1471
+  integrity sha512-pGcw1T3HpNnM/UTRQqqRkkkzythSLts05mB+7Gr00B+0VbL0m39dFL5g20rSIEUt9Wrpw+/8k+snxRlUFHhcqA==
1472
+
1473
+xhr@2.4.0:
1474
+  version "2.4.0"
1475
+  resolved "https://registry.npmjs.org/xhr/-/xhr-2.4.0.tgz"
1476
+  integrity sha512-TUbBsdAuJbX8olk9hsDwGK8P1ri1XlV+PdEWkYw+HQQbpkiBR8PLgD1F3kQDPBs9l4Px34hP9rCYAZOCCAENbw==
1477
+  dependencies:
1478
+    global "~4.3.0"
1479
+    is-function "^1.0.1"
1480
+    parse-headers "^2.0.0"
1481
+    xtend "^4.0.0"
1482
+
1483
+xtend@^4.0.0:
1484
+  version "4.0.2"
1485
+  resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz"
1486
+  integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
1487
+
972 1488
 zrender@5.4.4:
973 1489
   version "5.4.4"
974 1490
   resolved "https://registry.npmmirror.com/zrender/-/zrender-5.4.4.tgz"