PageSourceSearch

https://assets.uooconline.com/index/js/directives/chapterVideo-30845d05db.js

js uooconline.com collected 2026-10-08 09:15:24 UTC 26,242 bytes, 814 lines download raw bytes

1const apiUrlConfig = {
2  dev: "http://192.168.1.103:8004",
3  uat: "https://chatuat.uoocuniversity.com/backend-api",
4  prod: "https://chat.uoocuniversity.com/backend-api",
5};
6const env = location.host.startsWith("210")
7  ? "dev"
8  : location.host.startsWith("beta")
9  ? "uat"
10  : "prod";
11const apiUrl = apiUrlConfig[env];
12
13// 预览视频
14app.directive("chapterVideo", [
15  "courseService",
16  "$rootScope",
17  "$filter",
18  "$compile",
19  "$timeout",
20  function (courseService, $rootScope, $filter, $compile, $timeout) {
21    return {
22      restrict: "AE",
23      scope: {
24        source: "=",
25      },
26      require: "?^sourceManage",
27      templateUrl: "/tpl/teacher/chapter-4c2837f37b.source.video.html", // 页面内部模板
28      link: function (scope, element, attrs, sourceManage) {
29        scope.updateResource = sourceManage.updateResource;
30        scope.visibleShow = sourceManage.visibleShow;
31        scope.sourceView = sourceManage.sourceView;
32        scope.updateResTask = sourceManage.updateResTask;
33        scope.staticRoot = window.staticRoot;
34
35        // 仅为预览
36        scope.justview = angular.isDefined(attrs.justview);
37
38        // 定义了opened则默认设置为打开
39        scope.source.visibleShow = angular.isDefined(attrs.opened);
40
41        // 阻止冒泡
42        scope.stopBubble = function ($event) {
43          $event.stopPropagation(); // 阻止冒泡
44        };
45
46        // 从库中选择文件替换
47        scope.replaceFile = function (source) {
48          scope.$emit("replaceFile", source);
49        };
50
51        scope.$watch("source.uri", function () {
52          scope.sourceView(scope.source);
53        });
54
55        /**
56         * 跳转视频指定时间
57         * @param {*} sliceTime 单位:秒
58         */
59        scope.jumpSlice = function (sliceTime) {
60          if (scope.player && !scope.isDragging) {
61            scope.player.currentTime(sliceTime);
62          }
63        };
64
65        let manualScrollingLock = false;
66        let manualLockTimer = null;
67
68        function lockManualScroll() {
69          manualScrollingLock = true;
70          clearTimeout(manualLockTimer);
71          manualLockTimer = setTimeout(
72            () => (manualScrollingLock = false),
73            3000
74          );
75        }
76        // 视频切片左划/右划
77        scope.moveSlice = function (direction) {
78          const el = document.querySelector(
79            `.video_slice_content_center_${scope.source.id}`
80          );
81          if(!el) return
82          const itemWidth = el.querySelector(
83            `.video_slice_content_center_${scope.source.id} .video_slice_content_center_item`
84          ).offsetWidth;
85          lockManualScroll();
86          el.scrollTo({
87            left: el.scrollLeft + itemWidth * direction,
88            behavior: "smooth",
89          });
90        };
91
92        // 视频切片数据
93        scope.sliceData = {
94          startM: "",
95          startS: "",
96          endM: "",
97          endS: "",
98          pointName: "",
99          pointId: "",
100        };
101
102        // 切片任务状态:PENDING-待处理,PROCESSING-处理中,COMPLETED-已完成,FAILED-失败
103        scope.sliceStatus = "";
104
105        // AI视频切片列表
106        scope.slices = [];
107        // 是否开启AI视频切片权限
108        scope.aiSliceAuthority = false;
109        // 是否开启课程图谱权限
110        scope.aiGraphAuthority = false;
111
112        // 选择知识点弹窗数据
113        scope.pointData = {
114          name: "",
115          id: "",
116        };
117
118        scope.point = {
119          searchValue: "", // 搜索知识点
120        };
121        // 知识点列表
122        scope.pointList = [];
123        // 筛选后的知识点列表
124        scope.filteredPointList = [];
125
126        // 搜索知识点
127        scope.searchPoint = function (searchPoint) {
128          scope.filteredPointList = scope.pointList.filter((point) => {
129            return point.name.includes(searchPoint);
130          });
131        };
132
133        // 获取知识点列表
134        scope.getPointList = function () {
135          const uooc_auth = document.cookie.match(/uooc_auth=(.*?);/)[1];
136          $.get({
137            url: `${location.origin}/teacher/course/getAiToken?course_id=${$rootScope.courseId}`,
138            headers: {
139              Authorization: "Bearer " + uooc_auth,
140            },
141            success: function (res) {
142              const aiToken = res.data.Authorization;
143              $.get({
144                url: `${apiUrl}/knowledge_graph/getPointAndRelationList?courseId=${$rootScope.courseId}&packageCourseId=${$rootScope.parent}`,
145                headers: {
146                  Authorization: aiToken,
147                },
148                success: function (res) {
149                  // 测试数据
150                  // res.data.pointVOList = JSON.parse(
151                  //   localStorage.getItem("testPoints")
152                  // );
153                  // 测试数据
154                  scope.pointList = res.data?.pointVOList || [];
155                },
156              });
157            },
158          });
159        };
160
161        scope.getPointList();
162
163        // 将秒数转换为 00:00 格式
164        scope.toMinuteString = (seconds = 0) => {
165          const m = Math.floor(seconds / 60);
166          const s = seconds % 60;
167          return `${m.toString().padStart(2, "0")}:${s
168            .toString()
169            .padStart(2, "0")}`;
170        };
171
172        // 获取当前视频时间
173        scope.getVideoTime = function () {
174          var vtime = parseInt(scope.source.viewOpts.currentTime) || 0; // 视频时间秒
175          var timerObj = $filter("timerObj")(vtime); // 转成时分秒对象
176          scope.sliceData.startM = timerObj.hours * 60 + timerObj.minutes;
177          scope.sliceData.startS = timerObj.seconds;
178        };
179
180        // 重新生成切片
181        scope.retrySlice = function () {
182            courseService.buildCourseSliceResApi({
183                cid: $rootScope.courseId,
184                rid: scope.source.id
185            }).then(function () {
186                layer.msg('重试成功');
187                scope.sliceStatus = 'PROCESSING';
188            })
189        };
190
191        // 打开知识点列表
192        scope.openPointList = () => {
193          scope.point.searchValue = "";
194          scope.searchPoint("");
195          $(`#video_slice_point_list_${scope.source.id}`).toggle();
196        };
197
198        // 点击选择知识点
199        scope.selectPoint = function (point) {
200          scope.pointData = {
201            name: point.name,
202            id: point.id,
203          };
204          $(`#video_slice_point_list_${scope.source.id}`).hide();
205        };
206
207        // 监听修改切片名称
208        scope.onPointNameInput = function (pointName) {
209          const point = scope.pointList.find((item) => item.name == pointName);
210          scope.pointData = {
211            name: point?.name || "",
212            id: point?.id || "",
213          };
214          scope.sliceData = {
215            ...scope.sliceData,
216            pointId: scope.pointData.id,
217          };
218        };
219
220        // 打开选择知识点弹窗
221        scope.openAddPoint = function () {
222          const point =
223            scope.pointList.find(
224              (item) => item.id == scope.sliceData?.pointId
225            ) || {};
226
227          scope.pointData = {
228            name: point?.name || "",
229            id: point?.id || "",
230          };
231          $(`#video_slice_point_list_${scope.source.id}`).hide();
232          layer.open({
233            type: 1,
234            title: "选择知识点",
235            area: ["400px", "400px"],
236            content: $(`#video_slice_point_${scope.source.id}`),
237            btn: ["确认", "取消"],
238            yes: function (index) {
239              if (scope.pointData.name === "") return layer.msg("请选择知识点");
240              scope.sliceData = {
241                ...scope.sliceData,
242                pointName: scope.pointData.name,
243                pointId: scope.pointData.id,
244              };
245              scope.$digest();
246              layer.close(index); // 关闭弹窗
247            },
248            btn2: function (index) {},
249          });
250        };
251
252        // 新增切片
253        scope.addSlice = ($event) => {
254          scope.sliceData = {
255            startM: "",
256            startS: "",
257            endM: "",
258            endS: "",
259            pointName: "",
260            pointId: "",
261          };
262          $event.stopPropagation();
263          scope.openAddSlice();
264        };
265
266        // 编辑切片
267        scope.editSlice = ($event, sliceItem) => {
268          scope.sliceData = {
269            startM: Math.floor(sliceItem.startTime / 60),
270            startS: sliceItem.startTime % 60,
271            endM: Math.floor(sliceItem.endTime / 60),
272            endS: sliceItem.endTime % 60,
273            pointName: sliceItem.knowledgePointName,
274            pointId: sliceItem.knowledgePointId,
275            slice_index: sliceItem.sliceIndex,
276          };
277          $event.stopPropagation();
278          scope.openAddSlice();
279        };
280
281        // 打开新增/编辑切片弹窗
282        scope.openAddSlice = () => {
283          layer.open({
284            type: 1,
285            title: "编辑内容",
286            area: ["580px", "320px"],
287            btn: ["确认", "取消"],
288            content: $(`#video_slice_edit_${scope.source.id}`),
289            yes: function (index) {
290              const sd = scope.sliceData;
291
292              // ======== 必填校验(0 视为合法)========
293              if (
294                sd.startM === null ||
295                sd.startM === undefined ||
296                sd.startM === "" ||
297                sd.endM === null ||
298                sd.endM === undefined ||
299                sd.endM === "" ||
300                sd.startS === null ||
301                sd.startS === undefined ||
302                sd.startS === "" ||
303                sd.endS === null ||
304                sd.endS === undefined ||
305                sd.endS === ""
306              ) {
307                return layer.msg("请输入完整的时间");
308              }
309
310              // ======== 秒数范围 0-59 ========
311              if (
312                sd.startS < 0 ||
313                sd.startS > 59 ||
314                sd.endS < 0 ||
315                sd.endS > 59
316              ) {
317                return layer.msg("秒数必须在 0-59 之间");
318              }
319
320              // ======== 开始时间 ≤ 结束时间 ========
321              const startTotal = Number(sd.startM) * 60 + Number(sd.startS);
322              const endTotal = Number(sd.endM) * 60 + Number(sd.endS);
323
324              if (startTotal > endTotal) {
325                return layer.msg("开始时间不能大于结束时间");
326              }
327
328              // 结束时间不能超过视频时长
329              if (endTotal > scope.player.duration()) {
330                return layer.msg("结束时间不能超过视频时长");
331              }
332
333              // ======== 知识点必填 ========
334              if (!sd.pointName) return layer.msg("请输入知识点");
335
336              const api = !sd.slice_index && sd.slice_index != 0 ? courseService.addCourseSliceDataApi : courseService.upCourseSliceDataApi;
337
338              api({
339                  cid: $rootScope.courseId,
340                  rid: scope.source.id,
341                  slice_index: sd.slice_index,
342                  point_id: sd.pointId,
343                  point_name: sd.pointName,
344                  start_time: startTotal,
345                  end_time: endTotal,
346                })
347                .then((res) => {
348                  layer.msg("保存成功");
349                  scope.getSliceList();
350                })
351                .finally(() => {
352                  layer.close(index);
353                });
354            },
355
356            btn2: function (index) {},
357          });
358        };
359
360        // 删除切片
361        scope.removeSlice = ($event, sliceItem) => {
362          $event.stopPropagation();
363          layer.confirm(
364            `<div>
365              <div style="font-weight: bold">确认删除该切片?</div>
366              <div style="color: #999;margin-top: 6px;">删除后不可恢复</div>
367            </div>`,
368            {
369              title: "提示",
370              icon: 3,
371              btn: ["确认", "取消"],
372              success: function (layero, index) {},
373            },
374            function (index) {
375              courseService
376                .delCourseSliceDataApi({
377                  cid: $rootScope.courseId,
378                  rid: scope.source.id,
379                  slice_index: sliceItem.sliceIndex,
380                })
381                .then((res) => {
382                  layer.msg("删除成功");
383                  scope.getSliceList();
384                })
385                .finally(() => {
386                  layer.close(index);
387                });
388            },
389            function (index) {}
390          );
391        };
392
393        // 获取切片列表
394        scope.getSliceList = () => {
395          courseService
396            .getCourseSliceDataTeacherApi({
397              cid: scope.source.cid,
398              rid: scope.source.id,
399            })
400            .then((res) => {
401              // 测试数据
402              // res.data = JSON.parse(localStorage.getItem("testSlices"));
403              // 测试数据
404              scope.aiSliceAuthority = !JSON.parse(
405                localStorage.getItem("aiConfig") || "{}"
406              )["1011"];
407              scope.aiGraphAuthority = !JSON.parse(
408                localStorage.getItem("aiConfig") || "{}"
409              )["1005"]
410              scope.sliceStatus = res.data.status;
411              // 切片以开始时间排序
412              scope.slices = res.data?.slices?.sort((a, b) => a.startTime - b.startTime) || [];
413              scope.onMousemove();
414            });
415        };
416
417        // 监听鼠标滑动切片
418        scope.onMousemove = function (e) {
419          $timeout(() => {
420            // 监听鼠标滑动AI视频切片
421            const el = document.querySelector(
422              `.video_slice_content_center_${scope.source.id}`
423            );
424
425            if (!el) return;
426
427            let isDown = false;
428            let startX;
429            let scrollLeft;
430            scope.isDragging = false;
431
432            el.addEventListener("mousedown", (e) => {
433              isDown = true;
434              el.classList.add("dragging");
435              startX = e.pageX - el.offsetLeft;
436              scrollLeft = el.scrollLeft;
437              scope.isDragging = false;
438            });
439            el.addEventListener("mouseleave", () => {
440              isDown = false;
441              el.classList.remove("dragging");
442            });
443            el.addEventListener("mouseup", () => {
444              isDown = false;
445              el.classList.remove("dragging");
446            });
447            el.addEventListener("mousemove", (e) => {
448              if (!isDown) return;
449              e.preventDefault();
450              lockManualScroll();
451              const x = e.pageX - el.offsetLeft;
452              const walk = x - startX;
453              if (Math.abs(walk) > 5) {
454                scope.isDragging = true;
455              }
456              el.scrollLeft = scrollLeft - walk;
457            });
458          }, 0);
459        };
460
461        scope.$on("playerInit", function (e, player) {
462          scope.player = player;
463
464          scope.getSliceList();
465
466          player.on("play", function () {
467            // courseService.updateVideoLength({
468            //     'cid': $rootScope.courseId,
469            //     'hidemsg_': true,
470            //     'resource_id': scope.source.id,
471            //     'video_length': player.duration().toFixed(2)
472            // });
473          });
474          player.on("timeupdate", function (e) {
475            // 找到当前播放的视频切片
476            if (scope.slices?.length) {
477              let targetId = "";
478              scope.slices.forEach((item) => {
479                if (
480                  scope.source.viewOpts.currentTime >= item.startTime &&
481                  scope.source.viewOpts.currentTime <= item.endTime
482                ) {
483                  targetId = `${item.sliceIndex}_${item.startTime}_slice`;
484                }
485              });
486              // if (!manualScrollingLock && targetId) {
487              //   let target = document.getElementById(targetId);
488              //   if (target) {
489              //     target.scrollIntoView({
490              //       behavior: "smooth",
491              //     });
492              //   }
493              // }
494            }
495          });
496        });
497
498        // 如果默认存在opened, 则预览
499        if (scope.source.opened) {
500          scope.source.visibleShow = true; // 预览
501          scope.sourceView(scope.source);
502
503          setTimeout(function () {
504            var offsetTop = angular.element(element).offset().top;
505            $("html,body").scrollTop(offsetTop);
506          }, 30);
507        }
508
509        // 上传字幕
510        scope.uploadTrack = function (source) {
511          scope.$emit("uploadTrack", source);
512        };
513
514        /**
515         * 添加更新小测
516         */
517
518        // 小测列表
519        scope.quizList = [];
520
521        // 获取小测列表
522        scope.getQuizlist = function () {
523          scope.loadding = true;
524          courseService
525            .quizlist({
526              cid: $rootScope.courseId,
527              resource_id: scope.source.id,
528              load: false,
529            })
530            .then(function (rdata) {
531              scope.loadding = false;
532              var quizList_ = rdata.data || [];
533              scope.quizList = [];
534
535              angular.forEach(quizList_, function (quiz) {
536                var timerObj = $filter("timerObj")(quiz.set_time);
537                quiz.minutes = timerObj.hours * 60 + timerObj.minutes;
538                quiz.seconds = timerObj.seconds;
539                scope.quizList.push(quiz);
540              });
541            });
542        };
543
544        // 监听是否为展开
545        scope.$watch("source.visibleShow", function (e, visibleShow) {
546          if (scope.source.visibleShow) {
547            scope.getQuizlist();
548          }
549        });
550
551        // 添加新的小测
552        scope.addEmptyQuiz = function (type) {
553          var cdata = {
554            id: 0,
555            type: type,
556            pattern: 20,
557            set_time: parseInt(scope.source.viewOpts.currentTime) || 0,
558            question: "",
559            minutes: 0,
560            seconds: 0,
561            options: JSON.stringify({
562              A: type == 20 ? "正确" : "",
563              B: type == 20 ? "错误" : "",
564            }),
565          };
566          scope.applyVideoTime(cdata);
567          scope.layerQuiz(cdata);
568        };
569
570        // 编辑小测
571        scope.layerQuiz = function (quizItem) {
572          scope.curTestSource = angular.copy(quizItem);
573          scope.curTestSource.old_ = quizItem;
574          var ele = angular.element("#testLayer");
575
576          ele
577            .find(".questionSub")
578            .html(
579              '<div ng-if="curTestSource" question-common question-object qsource="curTestSource" pattern="20"></div>'
580            );
581          $compile(ele.contents())(scope);
582
583          layer.open({
584            type: 1,
585            zIndex: 99,
586            move: false,
587            scrollbar: false,
588            title: quizItem.id ? "更新小测" : "添加小测",
589            area: ["600px", "540px"],
590            content: ele,
591          });
592        };
593
594        scope.quizListLib = []; // 库中的小测
595
596        // 库中小测筛选及分页数据
597        scope.quizSearch = {
598          cid: $rootScope.courseId,
599          keyword: "",
600          type: 0,
601          page: 1,
602          pagesize: 7,
603        };
604
605        // 库中添加小测计时器
606        scope.libTimer = {
607          set_time: 0,
608          minutes: 0,
609          seconds: 0,
610        };
611        // 获取库中小测列表
612        scope.getLibQuizlist = function (page) {
613          scope.applyVideoTime(scope.libTimer);
614          scope.quizSearch.page = page || scope.quizSearch.page;
615          courseService
616            .quizQuestionList(scope.quizSearch)
617            .then(function (rdata) {
618              scope.quizListLib = rdata.data.data;
619              scope.quizSearch.pages = rdata.data.page.pages;
620            });
621        };
622
623        // 筛选题型
624        scope.typeSearch = function (type) {
625          scope.quizSearch.type = type || 0;
626          scope.getLibQuizlist(1);
627        };
628
629        // 获取库中测验列表
630        scope.getQuizFromLib = function () {
631          scope.applyVideoTime(scope.libTimer);
632          layer.open({
633            type: 1,
634            move: false,
635            scrollbar: false,
636            title: "题库选题",
637            area: ["860px", "600px"],
638            content: angular.element(element).find("#quizlib"),
639          });
640          scope.quizSearch.type = 0;
641          scope.quizSearch.keyword = "";
642          scope.getLibQuizlist();
643        };
644
645        // 关键字搜索
646        // scope.keywordSearch = function(type){
647        //     scope.quizSearch.type = type || 0;
648        // };
649
650        // 添加小测从库
651        scope.addQuizFromLib = function (qsource) {
652          qsource.minutes = parseInt(scope.libTimer.minutes) || 0;
653          qsource.seconds = parseInt(scope.libTimer.seconds) || 0;
654          qsource.set_time = qsource.minutes * 60 + qsource.seconds;
655
656          courseService
657            .pullQuiz({
658              id: 0,
659              qid: qsource.id,
660              cid: $rootScope.courseId,
661              resource_id: scope.source.id,
662              set_time: qsource.set_time,
663            })
664            .then(function (rdata) {
665              layer.closeAll();
666              layer.msg("添加成功");
667
668              qsource.id = rdata.data.id;
669              scope.quizList.push(qsource);
670            });
671        };
672
673        // 应用当前视频的播放时间
674        scope.applyVideoTime = function (qsource) {
675          var vtime = parseInt(scope.source.viewOpts.currentTime) || 0; // 视频时间秒
676          var timerObj = $filter("timerObj")(vtime); // 转成时分秒对象
677          qsource.set_time = vtime;
678          qsource.minutes = timerObj.hours * 60 + timerObj.minutes;
679          qsource.seconds = timerObj.seconds;
680        };
681
682        // 不能为字母与其他特殊字符
683        scope.onlyNumber = function (e) {
684          var ss = window.event || e;
685          var kcode = ss.keyCode;
686          if (
687            (kcode > 64 && kcode < 91) ||
688            (kcode > 105 && kcode < 108) ||
689            (kcode > 108 && kcode < 112) ||
690            (kcode >= 186 && kcode <= 222)
691          ) {
692            ss.preventDefault();
693          }
694        };
695
696        // 点击确定添加小测
697        scope.$on("sureQuestion", function (e, qsource, qscope) {
698          var isUpdate = qsource.id; // 如果不存在id则为更新, 否则为新增
699
700          if (
701            isNaN(qsource.minutes) ||
702            isNaN(qsource.minutes) ||
703            qsource.seconds > 60
704          ) {
705            layer.msg("请输入合法的时间");
706            return;
707          }
708
709          qsource.minutes = parseInt(qsource.minutes) || 0;
710          qsource.seconds = parseInt(qsource.seconds) || 0;
711          qsource.set_time = qsource.minutes * 60 + qsource.seconds;
712
713          if (qsource.set_time > 4 * 3600) {
714            layer.msg("小测允许最大时长为4小时(240分钟)");
715            return false;
716          }
717
718          // 解析answer
719          var answer_;
720          if (qsource.type == 10 || qsource.type == 20) {
721            // 单选
722            answer_ = [qsource.eanswer];
723          } else if (qsource.type == 11) {
724            // 多选题
725            answer_ = qsource.answer.split(",");
726            qsource.answer = answer_;
727          }
728
729          var options_ = {
730            id: qsource.id || 0,
731            cid: $rootScope.courseId,
732            resource_id: scope.source.id,
733            set_time: qsource.set_time,
734            minutes: qsource.minutes,
735            seconds: qsource.seconds,
736            question: qsource.question,
737            type: qsource.type,
738            options: JSON.stringify(qsource.voptions),
739            answer: JSON.stringify(answer_),
740          };
741
742          // 添加quiz
743          courseService.addQuiz(options_).then(function (rdata) {
744            qscope.updateView(qsource);
745            layer.closeAll();
746            if (isUpdate) {
747              layer.msg("更新成功");
748              qsource.id = qsource.id || rdata.data.id;
749            } else {
750              layer.msg("添加成功");
751              options_.id = rdata.data.id;
752              scope.quizList.push(options_);
753            }
754          });
755        });
756
757        scope.$on("delQuestion", function (e, qsource) {
758          if (qsource.id) {
759            // 如果存在id则添加询问
760            layer.confirm(
761              "确定删除当前小测?",
762              {
763                btn: ["确定", "取消"], //按钮
764              },
765              function () {
766                courseService
767                  .delQuiz({
768                    id: qsource.id,
769                  })
770                  .then(function () {
771                    qsource.hidesource = true;
772                    layer.closeAll();
773                  });
774              },
775              function () {
776                layer.closeAll();
777              }
778            );
779          } else {
780            qsource.hidesource = true;
781          }
782        });
783
784        scope.$on("editQuestion", function (e, qsource) {
785          scope.layerQuiz(qsource);
786        });
787
788        // 删除资源
789        scope.delSource = function (curSource) {
790          var options = curSource.options || {},
791            data = curSource.data || {};
792          layer.confirm(
793            "确定删除当前字幕?",
794            {
795              area: ["260px", "150px"],
796              btn: ["确定", "取消"], //按钮
797            },
798            function () {
799              courseService
800                .delResource({
801                  cid: $rootScope.courseId,
802                  id: curSource.id || options.id || data.id,
803                })
804                .then(function () {
805                  layer.closeAll();
806                  curSource.hide = true;
807                });
808            }
809          );
810        };
811      },
812    };
813  },
814]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.