dusenyao 2 年 前
コミット
182ad9254f

+ 35 - 36
package-lock.json

@@ -13,7 +13,7 @@
         "awe-dnd": "^0.3.4",
         "axios": "^0.27.2",
         "book-ui": "file:../book-ui-0.3.24.tgz",
-        "core-js": "^3.27.1",
+        "core-js": "^3.27.2",
         "dayjs": "^1.11.7",
         "element-ui": "^2.15.12",
         "gcls-book-question-ui": "file:../gcls-book-question-ui-0.1.0.tgz",
@@ -44,18 +44,18 @@
         "@vue/cli-service": "~5.0.8",
         "@vue/eslint-config-prettier": "^7.0.0",
         "@vue/preload-webpack-plugin": "^2.0.0",
-        "@vue/test-utils": "^1.3.3",
+        "@vue/test-utils": "^1.3.4",
         "babel-jest": "^27.5.1",
         "babel-plugin-dynamic-import-node": "^2.3.3",
         "compression-webpack-plugin": "^6.1.1",
         "eslint": "^7.32.0",
         "eslint-plugin-prettier": "^4.2.1",
-        "eslint-plugin-vue": "^9.8.0",
+        "eslint-plugin-vue": "^9.9.0",
         "html-webpack-plugin": "^5.5.0",
         "postcss": "^8.4.21",
         "postcss-html": "^1.5.0",
-        "prettier": "2.8.2",
-        "sass": "^1.57.1",
+        "prettier": "2.8.3",
+        "sass": "^1.58.0",
         "sass-loader": "^10.4.1",
         "script-ext-html-webpack-plugin": "^2.1.5",
         "stylelint": "^14.16.1",
@@ -4434,11 +4434,10 @@
       }
     },
     "node_modules/@vue/test-utils": {
-      "version": "1.3.3",
-      "resolved": "https://repo.huaweicloud.com/repository/npm/@vue/test-utils/-/test-utils-1.3.3.tgz",
-      "integrity": "sha512-DmZkKrH5/MSkrU0hhHhv5+aOXcEJSaOhutKMOh2viuiLiMaFeOLPiTEvtegLunO3rXBagzHO681qW1sNMaB1sQ==",
+      "version": "1.3.4",
+      "resolved": "https://registry.npmmirror.com/@vue/test-utils/-/test-utils-1.3.4.tgz",
+      "integrity": "sha512-yh2sbosCxk5FfwjXYXdY9rUffaJqYEFjsod5sCD4oosRn2x8LfBLEzQH0scdo5n7z8VkBUThpYzbkI6DVAWimA==",
       "dev": true,
-      "license": "MIT",
       "dependencies": {
         "dom-event-types": "^1.0.0",
         "lodash": "^4.17.15",
@@ -6956,9 +6955,9 @@
       }
     },
     "node_modules/core-js": {
-      "version": "3.27.1",
-      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.27.1.tgz",
-      "integrity": "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==",
+      "version": "3.27.2",
+      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.27.2.tgz",
+      "integrity": "sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w==",
       "hasInstallScript": true
     },
     "node_modules/core-js-compat": {
@@ -8481,9 +8480,9 @@
       }
     },
     "node_modules/eslint-plugin-vue": {
-      "version": "9.8.0",
-      "resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-9.8.0.tgz",
-      "integrity": "sha512-E/AXwcTzunyzM83C2QqDHxepMzvI2y6x+mmeYHbVDQlKFqmKYvRrhaVixEeeG27uI44p9oKDFiyCRw4XxgtfHA==",
+      "version": "9.9.0",
+      "resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-9.9.0.tgz",
+      "integrity": "sha512-YbubS7eK0J7DCf0U2LxvVP7LMfs6rC6UltihIgval3azO3gyDwEGVgsCMe1TmDiEkl6GdMKfRpaME6QxIYtzDQ==",
       "dev": true,
       "dependencies": {
         "eslint-utils": "^3.0.0",
@@ -17620,9 +17619,9 @@
       }
     },
     "node_modules/prettier": {
-      "version": "2.8.2",
-      "resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.8.2.tgz",
-      "integrity": "sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw==",
+      "version": "2.8.3",
+      "resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.8.3.tgz",
+      "integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==",
       "dev": true,
       "bin": {
         "prettier": "bin-prettier.js"
@@ -18617,9 +18616,9 @@
       "license": "MIT"
     },
     "node_modules/sass": {
-      "version": "1.57.1",
-      "resolved": "https://registry.npmmirror.com/sass/-/sass-1.57.1.tgz",
-      "integrity": "sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw==",
+      "version": "1.58.0",
+      "resolved": "https://registry.npmmirror.com/sass/-/sass-1.58.0.tgz",
+      "integrity": "sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==",
       "dev": true,
       "dependencies": {
         "chokidar": ">=3.0.0 <4.0.0",
@@ -26676,9 +26675,9 @@
       "dev": true
     },
     "@vue/test-utils": {
-      "version": "1.3.3",
-      "resolved": "https://repo.huaweicloud.com/repository/npm/@vue/test-utils/-/test-utils-1.3.3.tgz",
-      "integrity": "sha512-DmZkKrH5/MSkrU0hhHhv5+aOXcEJSaOhutKMOh2viuiLiMaFeOLPiTEvtegLunO3rXBagzHO681qW1sNMaB1sQ==",
+      "version": "1.3.4",
+      "resolved": "https://registry.npmmirror.com/@vue/test-utils/-/test-utils-1.3.4.tgz",
+      "integrity": "sha512-yh2sbosCxk5FfwjXYXdY9rUffaJqYEFjsod5sCD4oosRn2x8LfBLEzQH0scdo5n7z8VkBUThpYzbkI6DVAWimA==",
       "dev": true,
       "requires": {
         "dom-event-types": "^1.0.0",
@@ -28492,9 +28491,9 @@
       }
     },
     "core-js": {
-      "version": "3.27.1",
-      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.27.1.tgz",
-      "integrity": "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww=="
+      "version": "3.27.2",
+      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.27.2.tgz",
+      "integrity": "sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w=="
     },
     "core-js-compat": {
       "version": "3.24.0",
@@ -29658,9 +29657,9 @@
       }
     },
     "eslint-plugin-vue": {
-      "version": "9.8.0",
-      "resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-9.8.0.tgz",
-      "integrity": "sha512-E/AXwcTzunyzM83C2QqDHxepMzvI2y6x+mmeYHbVDQlKFqmKYvRrhaVixEeeG27uI44p9oKDFiyCRw4XxgtfHA==",
+      "version": "9.9.0",
+      "resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-9.9.0.tgz",
+      "integrity": "sha512-YbubS7eK0J7DCf0U2LxvVP7LMfs6rC6UltihIgval3azO3gyDwEGVgsCMe1TmDiEkl6GdMKfRpaME6QxIYtzDQ==",
       "dev": true,
       "requires": {
         "eslint-utils": "^3.0.0",
@@ -35874,9 +35873,9 @@
       "dev": true
     },
     "prettier": {
-      "version": "2.8.2",
-      "resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.8.2.tgz",
-      "integrity": "sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw==",
+      "version": "2.8.3",
+      "resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.8.3.tgz",
+      "integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==",
       "dev": true
     },
     "prettier-linter-helpers": {
@@ -36572,9 +36571,9 @@
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
     },
     "sass": {
-      "version": "1.57.1",
-      "resolved": "https://registry.npmmirror.com/sass/-/sass-1.57.1.tgz",
-      "integrity": "sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw==",
+      "version": "1.58.0",
+      "resolved": "https://registry.npmmirror.com/sass/-/sass-1.58.0.tgz",
+      "integrity": "sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==",
       "dev": true,
       "requires": {
         "chokidar": ">=3.0.0 <4.0.0",

+ 5 - 5
package.json

@@ -18,7 +18,7 @@
     "awe-dnd": "^0.3.4",
     "axios": "^0.27.2",
     "book-ui": "file:../book-ui-0.3.24.tgz",
-    "core-js": "^3.27.1",
+    "core-js": "^3.27.2",
     "dayjs": "^1.11.7",
     "element-ui": "^2.15.12",
     "gcls-book-question-ui": "file:../gcls-book-question-ui-0.1.0.tgz",
@@ -49,18 +49,18 @@
     "@vue/cli-service": "~5.0.8",
     "@vue/eslint-config-prettier": "^7.0.0",
     "@vue/preload-webpack-plugin": "^2.0.0",
-    "@vue/test-utils": "^1.3.3",
+    "@vue/test-utils": "^1.3.4",
     "babel-jest": "^27.5.1",
     "babel-plugin-dynamic-import-node": "^2.3.3",
     "compression-webpack-plugin": "^6.1.1",
     "eslint": "^7.32.0",
     "eslint-plugin-prettier": "^4.2.1",
-    "eslint-plugin-vue": "^9.8.0",
+    "eslint-plugin-vue": "^9.9.0",
     "html-webpack-plugin": "^5.5.0",
     "postcss": "^8.4.21",
     "postcss-html": "^1.5.0",
-    "prettier": "2.8.2",
-    "sass": "^1.57.1",
+    "prettier": "2.8.3",
+    "sass": "^1.58.0",
     "sass-loader": "^10.4.1",
     "script-ext-html-webpack-plugin": "^2.1.5",
     "stylelint": "^14.16.1",

BIN
src/assets/live/communication.png


BIN
src/assets/live/every-user.png


BIN
src/assets/live/folder-upload.png


BIN
src/assets/live/laptop-computer.png


BIN
src/assets/live/monitor-off.png


BIN
src/assets/live/monitor.png


BIN
src/assets/live/peoples.png


BIN
src/assets/live/recording.png


BIN
src/assets/live/stop-recording.png


BIN
src/assets/live/voice-off.png


BIN
src/assets/live/voice.png


+ 23 - 5
src/router/index.js

@@ -74,13 +74,13 @@ const routes = [
     children: [
       // 分步表单 -> 第一步
       {
-        path: '/create_course_step_table/course_info',
+        path: 'course_info',
         component: () =>
           import(/* webpackChunkName: 'create_course'*/ '@/views/teacher/create_course/step_one/CourseInfo.vue')
       },
       // 分步表单 -> 第二步
       {
-        path: '/create_course_step_table/select_book/:id',
+        path: 'select_book/:id',
         name: 'SelectBook',
         component: () =>
           import(/* webpackChunkName: 'create_course'*/ '@/views/teacher/create_course/step_two/SelectBook.vue')
@@ -88,7 +88,7 @@ const routes = [
       /* 旧创建课节 开始 */
       // 分步表单 -> 第三步
       {
-        path: '/create_course_step_table/old_create_task/:id',
+        path: 'old_create_task/:id',
         component: () =>
           import(
             /* webpackChunkName: 'old_create_course'*/ '@/views/teacher/create_course/step_three_old/CreateTask.vue'
@@ -96,7 +96,7 @@ const routes = [
       },
       // 分步表单 -> 第四步 -> 新建课节任务
       {
-        path: '/create_course_step_table/new_task/:time_type/:id/:curItemID',
+        path: 'new_task/:time_type/:id/:curItemID',
         component: () =>
           import(/* webpackChunkName: 'old_create_course'*/ '@/views/teacher/create_course/step_four/NewTask.vue')
       },
@@ -105,7 +105,7 @@ const routes = [
       /* 新创建课节 */
       // 分步表单 -> 第三步
       {
-        path: '/create_course_step_table/create_task/:id',
+        path: 'create_task/:id',
         component: () =>
           import(/* webpackChunkName: 'create_course'*/ '@/views/teacher/create_course/step_three/index.vue')
       }
@@ -194,6 +194,24 @@ const routes = [
       }
     ]
   },
+  {
+    path: '/new_live',
+    component: Layout,
+    redirect: '/new_live/teacher',
+    children: [
+      {
+        path: 'teacher',
+        component: () => import(/* webpackChunkName: 'new_live' */ '@/views/new_live/teacher/index.vue')
+      },
+      {
+        path: 'student',
+        component: () => import(/* webpackChunkName: 'new_live' */ '@/views/new_live/student/index.vue')
+      },
+      {
+        path: 'group'
+      }
+    ]
+  },
   // 课程详情
   {
     path: '/course_details',

+ 4 - 1
src/views/main/components/TaskList.js

@@ -161,6 +161,7 @@ export function useTaskItem() {
  */
 export function useTaskLink(router) {
   const $t = inject('$t');
+  const task_mode = store.state.app.config.task_mode;
 
   function taskLink(teaching_type, task_id) {
     const userType = store.state.user.user_type;
@@ -187,7 +188,9 @@ export function useTaskLink(router) {
               });
             }
             router.push({
-              path: `/live/${userType === 'TEACHER' ? 'teacher' : 'student'}`,
+              path: `/${task_mode === taskModeList[0] ? 'live' : 'new_live'}/${
+                userType === 'TEACHER' ? 'teacher' : 'student'
+              }`,
               query: { live_room_sys_user_id, room_id, session_id, task_id, room_user_id }
             });
           }

+ 325 - 0
src/views/new_live/index.js

@@ -0,0 +1,325 @@
+import store from '@/store';
+import { ref, watch, reactive } from 'vue';
+import { Message } from 'element-ui';
+import i18n from '@/locales/i18n';
+
+export let rtc = ref({});
+export function useLive() {
+  function init(data) {
+    rtc.value = new Rtc(data);
+  }
+
+  /**
+   * 查询直播是否开启
+   */
+  function getLiveStat(obj) {
+    rtc.value.getLiveStat(obj);
+  }
+
+  /**
+   * 下麦操作
+   * @param { String } uid
+   */
+  function handsDown(Object) {
+    rtc.value.handsDown(Object);
+  }
+
+  /**
+   * 上麦更新
+   */
+  function updateMcResult(stid, pid) {
+    // 上麦更新是在两种情况下执行
+    // 1.推流成功之后将自己的麦序更新为3;系统会广播 speak_context 事件,其他用户监听该事件后更新对应的业务展示状态;
+    // 2.不能创建本地流或者推流失败,更新自己的麦序为0。
+    rtc.value.updateMcResult({
+      pid,
+      stid,
+      success(data) {
+        console.log('更新上麦结果请求成功,此处可处理应用层逻辑', data);
+      },
+      fail(data) {
+        console.log(`更新上麦结果请求失败:${JSON.stringify(data)}`);
+      }
+    });
+  }
+
+  /**
+   * 推送本地流
+   */
+  function publishStream(streamName) {
+    rtc.value.publish({
+      streamName,
+      // 推流成功,更新上麦结果
+      success: (stream) => {
+        console.log('推流成功', stream);
+        updateMcResult(stream.id(), 1);
+      },
+      fail: (str) => {
+        // 推流失败,更新上麦结果
+        console.log('推流失败,更新上麦结果', str);
+        updateMcResult('', 0);
+      }
+    });
+  }
+
+  /**
+   * 得到创建本地流参数对象
+   */
+  function createData() {
+    const device = store.state.app.liveDevice;
+    const video = device.video.length > 0 ? { device: 'camera', resolution: 'sif', deviceId: device.video } : false;
+    const audio = device.audio.length > 0 ? { deviceId: device.audio } : false;
+
+    return {
+      video,
+      audio
+    };
+  }
+
+  /**
+   * 创建本地流
+   */
+  function createLocalStream(streamName) {
+    rtc.value.createLocalStream({
+      streamName,
+      createData: createData(),
+      success(stream) {
+        console.log('创建本地流成功', stream);
+        // 创建本地流成功,将流展示到id为 live 的dom元素盒子中
+        stream.show('live');
+        publishStream(streamName); // 如果需要立即推流,执行 publish 方法
+      },
+      fail(data) {
+        // 创建本地流失败,应用层处理
+        Message({
+          type: 'error',
+          message: `${i18n.t('Key441')}:${data}`
+        });
+      }
+    });
+  }
+
+  /**
+   * 结束本地流
+   */
+  function closeVideo(streamName) {
+    rtc.value.closeVideo({
+      streamName,
+      success() {
+        console.log('结束本地流成功');
+      },
+      fail(str) {
+        console.log(str);
+      }
+    });
+  }
+
+  // 重连
+  function reconnection() {
+    rtc.value.closeVideo({
+      streamName: 'main',
+      success() {
+        createLocalStream('main');
+      },
+      fail(str) {
+        console.log(str);
+      }
+    });
+  }
+
+  /**
+   * 关闭本地流声音
+   */
+  function pauseAudio(options) {
+    rtc.value.pauseAudio(options);
+  }
+
+  /**
+   * 开启本地流声音
+   */
+  function playAudio(options) {
+    rtc.value.playAudio(options);
+  }
+
+  /**
+   * 关闭本地流视频画面
+   */
+  function pauseVideo(options) {
+    rtc.value.pauseVideo(options);
+  }
+
+  /**
+   * 开启本地流视频画面
+   */
+  function playVideo(options) {
+    rtc.value.playVideo(options);
+  }
+
+  /**
+   * 获取设备列表
+   */
+  function getDevice(options) {
+    rtc.value.getDevice(options);
+  }
+
+  // 查询网络节点
+  function getNetPoint() {
+    rtc.value.getNetPoint({
+      success(data) {
+        console.log('获取网络节点成功', data);
+      }
+    });
+  }
+
+  /**
+   * 获取历史记录
+   * 用于获取当前直播中房间的文档翻页,画笔,聊天的历史记录
+   */
+  function getHistory(options) {
+    rtc.value.getHistory(options);
+  }
+
+  /**
+   * 房间配置项更新
+   * @param {Object} option 房间配置项 (具体看2.0 https://doc.bokecc.com/class/developer/web/chat.html),以键值对的形式传
+   */
+  function roomUpdate(option) {
+    rtc.value.roomUpdate(option);
+  }
+
+  // 自定义消息发送事件
+  function sendPublishMessage(data) {
+    rtc.value.sendPublishMessage(data);
+  }
+
+  return {
+    rtc,
+    init,
+    getLiveStat,
+    handsDown,
+    closeVideo,
+    reconnection,
+    pauseAudio,
+    playAudio,
+    pauseVideo,
+    playVideo,
+    getDevice,
+    getNetPoint,
+    getHistory,
+    roomUpdate,
+    sendPublishMessage,
+    createLocalStream,
+    publishStream,
+    updateMcResult
+  };
+}
+
+/**
+ * 聊天
+ * @param {Object} rtc rtc实例
+ */
+export function useChat(rtc) {
+  let msg = ref(''); // length不能超过 400
+  let chatList = ref([]);
+  let chatShow = ref(false);
+
+  function toggle() {
+    chatShow.value = !chatShow.value;
+  }
+
+  /**
+   * 发送聊天
+   */
+  function sendMsg() {
+    rtc.sendMsg(msg);
+    msg.value = '';
+  }
+
+  return {
+    msg,
+    chatList,
+    chatShow,
+    toggle,
+    sendMsg
+  };
+}
+
+/**
+ * 成员列表
+ */
+export function useMemberList() {
+  let memberShow = ref(false);
+
+  function toggle() {
+    memberShow.value = !memberShow.value;
+  }
+
+  return {
+    memberShow,
+    toggle
+  };
+}
+
+/**
+ * 变更画笔
+ */
+export function drawChange(action, value) {
+  rtc.value.drawChange({
+    action,
+    value
+  });
+}
+
+/**
+ * 加载直播 SDK
+ */
+export function useDownloadSDK() {
+  function createScript(url) {
+    const script = document.createElement('script');
+    script.type = 'text/javascript';
+    script.src = url;
+    document.getElementsByTagName('body')[0].appendChild(script);
+    return script;
+  }
+
+  function createLink(url) {
+    const link = document.createElement('link');
+    link.rel = 'stylesheet';
+    link.href = url;
+    document.getElementsByTagName('body')[0].appendChild(link);
+    return link;
+  }
+
+  // 加载直播所需 SDK,加载完成后才能初始化
+  function downloadWebSDK(fn) {
+    let loadedNumber = ref(0);
+    watch(loadedNumber, (newVal) => {
+      if (newVal === 5) {
+        createScript('https://class.csslcloud.net/static/SDK/docSDK/drawSdk_3.0.js').onload = () => {
+          fn();
+        };
+      }
+    });
+
+    createLink('https://class.csslcloud.net/static/SDK/docSDK/draw.css').onload = () => {
+      loadedNumber.value += 1;
+    };
+
+    createScript('https://class.csslcloud.net/sdk/websdk/hdRtc-6.7.2.js').onload = () => {
+      [
+        'https://class.csslcloud.net/static/dist/js/classMode.js',
+        'https://class.csslcloud.net/static/dist/js/classUpdateChat.js',
+        'https://image.csslcloud.net/js/dpc.js',
+        'https://class.csslcloud.net/static/js/jquery/jquery.min.js'
+      ].forEach((item) => {
+        createScript(item).onload = () => {
+          loadedNumber.value += 1;
+        };
+      });
+    };
+  }
+
+  return {
+    downloadWebSDK
+  };
+}

+ 53 - 0
src/views/new_live/index.vue

@@ -0,0 +1,53 @@
+<template>
+  <div class="live">
+    <div class="top">{{ roomInfo.course_name }} - {{ roomInfo.cs_item_name }} - {{ roomInfo.task_name }}</div>
+    <div class="live-main">
+      <!-- <TeacherLive
+        v-if="isTeacher"
+        :rtc="rtc"
+        :room-info="roomInfo"
+        :student-list="student_list"
+        :live-stat="liveStat"
+      />
+      <StudentLive v-else :room-info="roomInfo" :student-list="student_list" /> -->
+      <slot></slot>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'BaseLive'
+};
+</script>
+
+<script setup>
+defineProps({
+  roomInfo: {
+    type: Object,
+    required: true
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+.live {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+
+  .top {
+    height: 32px;
+    font-weight: 400;
+    line-height: 32px;
+    text-align: center;
+    background-color: #f3f3f3;
+    border-bottom: 1px solid #ebebeb;
+  }
+
+  .live-main {
+    flex: 1;
+    background-color: #fff;
+  }
+}
+</style>

+ 168 - 0
src/views/new_live/student/index.vue

@@ -0,0 +1,168 @@
+<template>
+  <div class="student">
+    <div class="middle">
+      <!-- 教师学员列表 -->
+      <div :class="['live-list', { 'only-teacher': studentList.length <= 0 }]">
+        <div class="live-list-item">
+          <div class="portrait">
+            <span>{{ roomInfo.teacher_name }}</span>
+          </div>
+          <span class="teacher-card">教师</span>
+          <span class="name">
+            {{ roomInfo.teacher_name }}
+          </span>
+        </div>
+        <div
+          v-for="{ student_id, student_name, student_image_url } in studentList"
+          :key="student_id"
+          class="live-list-item"
+        >
+          <el-avatar :size="122" :src="student_image_url" />
+          <span class="student-card">学生</span>
+          <span class="name">
+            {{ student_name }}
+          </span>
+        </div>
+      </div>
+    </div>
+    <div class="bottom">
+      <div class="operation">
+        <div class="operation-item">
+          <img src="@/assets/live/communication.png" alt="" />
+          <span>聊天</span>
+        </div>
+        <div class="operation-item">
+          <img src="@/assets/live/peoples.png" alt="" />
+          <span>成员列表</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'StudentLive'
+};
+</script>
+
+<script setup>
+import { inject, onBeforeUnmount } from 'vue';
+import { StudentExitLiveRoom } from '@/api/live';
+import { useRoute } from 'vue-router/composables';
+
+defineProps({
+  roomInfo: {
+    type: Object,
+    required: true
+  },
+  studentList: {
+    type: Array,
+    required: true
+  }
+});
+
+const route = useRoute();
+
+const room_user_id = route.query.room_user_id;
+const task_id = inject('task_id');
+
+onBeforeUnmount(() => {
+  StudentExitLiveRoom(task_id, room_user_id);
+});
+</script>
+
+<style lang="scss" scoped>
+.student {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+
+  .middle {
+    flex: 1;
+
+    .live-list {
+      display: flex;
+      gap: 40px;
+      height: 100%;
+      padding: 53px 65px 32px;
+
+      &.only-teacher {
+        align-items: center;
+        justify-content: center;
+      }
+
+      &-item {
+        position: relative;
+        display: flex;
+        flex-direction: column;
+        row-gap: 16px;
+
+        .portrait {
+          width: 122px;
+          height: 122px;
+          line-height: 122px;
+          color: #fff;
+          text-align: center;
+          background-color: #7a9fff;
+          border-radius: 50%;
+        }
+        %card,
+        .teacher-card {
+          position: absolute;
+          top: 105px;
+          left: 34px;
+          padding: 4px 8px;
+          background-color: #ffb342;
+          border-radius: 4px;
+        }
+
+        .student-card {
+          @extend %card;
+
+          background-color: #c2ff42;
+        }
+
+        .name {
+          text-align: center;
+        }
+      }
+    }
+  }
+
+  .bottom {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 96px;
+    padding: 12px 16px;
+    background-color: #f8f8f8;
+
+    .operation {
+      display: flex;
+      column-gap: 16px;
+
+      &-item {
+        display: flex;
+        flex-direction: column;
+        row-gap: 4px;
+        align-items: center;
+        width: 80px;
+        height: 70px;
+        padding: 8px;
+        cursor: pointer;
+
+        &:active {
+          background-color: #e5e5e5;
+          border-radius: 8px;
+        }
+
+        img {
+          width: 32px;
+          height: 32px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 0 - 0
src/views/new_live/student/live.js


+ 546 - 0
src/views/new_live/teacher/index.vue

@@ -0,0 +1,546 @@
+<template>
+  <BaseLive>
+    <div class="teacher">
+      <div class="middle">
+        <!-- 教师学员列表 -->
+        <div :class="['live-list', { 'only-teacher': studentList.length <= 0 }]">
+          <div class="live-list-item">
+            <div class="portrait">
+              <span>{{ roomInfo.teacher_name }}</span>
+            </div>
+            <span class="teacher-card">教师</span>
+            <span class="name">
+              {{ roomInfo.teacher_name }}
+            </span>
+          </div>
+          <div
+            v-for="{ student_id, student_name, student_image_url } in studentList"
+            :key="student_id"
+            class="live-list-item"
+          >
+            <el-avatar :size="122" :src="student_image_url" />
+            <span class="student-card">学生</span>
+            <span class="name">
+              {{ student_name }}
+            </span>
+          </div>
+        </div>
+
+        <!-- 直播视频 -->
+        <div id="live" class=""></div>
+
+        <!-- 聊天 -->
+        <div v-show="chatShow" class="chat">
+          <div class="title">聊天</div>
+          <div class="chat-main"></div>
+          <div class="msg"></div>
+        </div>
+
+        <!-- 成员列表 -->
+        <div v-show="memberShow" class="member-list">
+          <div class="title">成员管理</div>
+          <div
+            v-for="{ student_id, student_name, student_image_url } in studentList"
+            :key="student_id"
+            :title="student_image_url"
+          >
+            <span>{{ student_name }}</span>
+          </div>
+        </div>
+      </div>
+      <div class="bottom">
+        <div class="operation">
+          <div class="operation-item">
+            <img src="@/assets/live/voice-off.png" alt="" />
+            <span>解除静音</span>
+          </div>
+          <div class="operation-item">
+            <img src="@/assets/live/monitor-off.png" alt="" />
+            <span>开启视频</span>
+          </div>
+        </div>
+        <div class="operation">
+          <div class="operation-item">
+            <img src="@/assets/live/laptop-computer.png" alt="" />
+            <span>共享屏幕</span>
+          </div>
+          <div class="operation-item">
+            <img src="@/assets/live/folder-upload.png" alt="" />
+            <span>课件推送</span>
+          </div>
+          <div class="operation-item">
+            <img src="@/assets/live/every-user.png" alt="" />
+            <span>分组讨论</span>
+          </div>
+
+          <span class="line"></span>
+
+          <div class="operation-item" @click="toggle">
+            <img src="@/assets/live/communication.png" alt="" />
+            <span>聊天</span>
+          </div>
+          <div class="operation-item" @click="memberToggle">
+            <img src="@/assets/live/peoples.png" alt="" />
+            <span>成员列表</span>
+          </div>
+          <div class="operation-item">
+            <img src="@/assets/live/recording.png" alt="" />
+            <span>录制</span>
+          </div>
+        </div>
+        <div class="operation">
+          <button class="live-button" @click="() => (liveStat ? closeLiveRoom() : startLive())">
+            {{ liveStat ? '结束直播' : '开始直播' }}
+          </button>
+        </div>
+      </div>
+    </div>
+  </BaseLive>
+</template>
+
+<script>
+export default {
+  name: 'TeacherLive'
+};
+</script>
+
+<script setup>
+import {
+  CloseLiveRoom,
+  StudentExitLiveRoom,
+  StartGroup,
+  GetGroupStatus,
+  DealStudentConnection,
+  GetStudentInfo_Connection,
+  GetLiveRoomData_DRTD,
+  GetLiveRoomInfo
+} from '@/api/live';
+import { ref, provide, inject, onMounted, onBeforeUnmount } from 'vue';
+import { Loading, Message, MessageBox } from 'element-ui';
+import { useRoute, useRouter } from 'vue-router/composables';
+import { useChat, useMemberList, useDownloadSDK, useLive, rtc } from '../index';
+import { useTeacherLive } from './live';
+import store from '@/store';
+
+import BaseLive from '../index.vue';
+
+const router = useRouter();
+const route = useRoute();
+const task_id = route.query.task_id;
+provide('task_id', task_id);
+const $t = inject('$t');
+
+const { msg, chatShow, sendMsg, toggle } = useChat(rtc);
+const { memberShow, toggle: memberToggle } = useMemberList(rtc);
+const { startLive, stopLive } = useTeacherLive(rtc);
+
+const isTeacher = store.getters.isTeacher;
+
+// 连麦
+let connect = ref(false);
+// 连线学员信息
+let connectStudent = ref({});
+// 等待接通
+let callLoading = ref(false);
+let dialogVisible = ref(false);
+// 学员完成
+let dialogVisibleComplete = ref(false);
+// 定时器
+let timer = ref(null);
+let remoteStreamType = ref(-1);
+let roomData = ref({
+  desc: '直播间标题',
+  name: '姓名',
+  user: {
+    id: '',
+    name: '',
+    role: 'talker',
+    rommid: ''
+  },
+  max_users: 1,
+  allow_chat: true,
+  allow_audio: true,
+  allow_speak: true
+});
+let roomInfo = ref({
+  room_id: '',
+  video_mode: 1,
+  task_name: '',
+  cs_item_name: '',
+  course_name: '',
+  teacher_name: '',
+  student_count: 0,
+  student_connection_info: {}
+});
+let loadedNumber = ref(0);
+let speakData = ref({});
+let roomContext = ref({});
+let chatList = ref([]);
+let isDrawSetting = ref(false);
+let curColor = ref('#343434');
+let drawColorList = ['#FF4747', '#343434', '#628EFF', '#FFCA0E'];
+let drawThicknessList = ['1', '3', '5'];
+// 直播间学员列表
+let student_list = ref([]);
+// 直播状态
+let liveStat = ref(false);
+let liveMenuShow = ref(false);
+// 分组讨论
+let groupNumList = ref([]);
+let group_count = ref(1);
+// 网络整体情况
+let netStatus = ref(0);
+let dialogVisibleGroup = ref(false);
+let dialogVisibleDevice = ref(false);
+let isRecreate = ref(false);
+let device = ref({
+  video: [],
+  audio: []
+});
+// 本地视频流画面、声音
+let hasVideo = ref(false);
+let hasAudio = ref(false);
+let material_list = ref([]);
+let materialId = ref('');
+let materialType = ref('');
+let docListInfo = ref(null);
+
+const { downloadWebSDK } = useDownloadSDK();
+
+const { init, getLiveStat, closeVideo, pauseVideo, playVideo, pauseAudio, playAudio } = useLive();
+
+onMounted(() => {
+  getLiveRoomData_DRTDPolling();
+});
+
+onBeforeUnmount(() => {
+  clearInterval(timer.value);
+  closeVideo('main');
+});
+
+/**
+ * 得到分组状态
+ */
+let loadingInstance = null;
+GetGroupStatus({ task_id }).then(({ is_enable_group }) => {
+  if (is_enable_group === 'true') {
+    router.push({
+      path: '/live/teacher/group',
+      query: {
+        task_id
+      }
+    });
+  } else {
+    loadingInstance = Loading.service({
+      text: $t('Key425'),
+      background: '#fff'
+    });
+    loadingInstance;
+    downloadWebSDK(initSDK);
+    getLiveRoomData_DRTD();
+    getLiveRoomInfo();
+  }
+});
+
+function initSDK() {
+  const { live_room_sys_user_id, room_id, session_id } = route.query;
+  init({
+    userid: live_room_sys_user_id,
+    roomid: room_id,
+    sessionid: session_id
+  });
+  // common.initListener(this); // 注册监听事件
+  getLiveStat({
+    success: (data) => {
+      liveStat.value = data.started;
+    },
+    fail: (str) => {
+      liveStat.value = false;
+      console.log('直播关闭状态或查询直播失败', str);
+    }
+  });
+  loadingInstance.close();
+}
+
+/**
+ * 获取直播间学员列表
+ */
+function getLiveRoomData_DRTD() {
+  GetLiveRoomData_DRTD({ task_id }).then(({ student_list: sList, material_list: mList }) => {
+    student_list.value = sList;
+    material_list.value = mList;
+  });
+}
+
+function getLiveRoomData_DRTDPolling() {
+  timer.value = setInterval(() => {
+    getLiveRoomData_DRTD();
+  }, 5000);
+}
+
+/**
+ * 获取直播间信息
+ */
+function getLiveRoomInfo() {
+  GetLiveRoomInfo({ task_id }).then(
+    ({
+      room_id,
+      video_mode,
+      task_name,
+      cs_item_name,
+      course_name,
+      teacher_name,
+      student_count,
+      student_connection_info
+    }) => {
+      roomInfo.value = {
+        room_id,
+        video_mode,
+        task_name,
+        cs_item_name,
+        course_name,
+        teacher_name,
+        student_count,
+        student_connection_info
+      };
+      connectStudent.value = student_connection_info;
+      if (student_connection_info.connection_status === 1) {
+        callLoading.value = true;
+      }
+      if (student_connection_info.connection_status === 2) {
+        connect.value = true;
+      }
+    }
+  );
+}
+
+/**
+ * 本地流视频开启、关闭
+ */
+function playOrPauseVideo() {
+  if (device.value.video.length === 0) {
+    return Message.warning($t('Key399'));
+  }
+
+  if (hasVideo.value) {
+    pauseVideo({
+      streamName: 'main',
+      success: () => {
+        Message.success($t('Key433'));
+        hasVideo.value = false;
+      },
+      fail: (str) => {
+        Message.warning(str);
+      }
+    });
+  } else {
+    playVideo({
+      streamName: 'main',
+      success: () => {
+        Message.success($t('Key434'));
+        hasVideo.value = true;
+      },
+      fail: (str) => {
+        Message.warning(str);
+      }
+    });
+  }
+}
+
+/**
+ * 本地流音频开启、关闭
+ */
+function playOrPauseAudio() {
+  if (device.value.audio.length === 0) {
+    return Message.warning($t('Key401'));
+  }
+
+  if (hasAudio.value) {
+    pauseAudio({
+      streamName: 'main',
+      success: () => {
+        Message.success($t('Key435'));
+        hasAudio.value = false;
+      },
+      fail: (str) => {
+        Message.warning(str);
+      }
+    });
+  } else {
+    playAudio({
+      streamName: 'main',
+      success: () => {
+        Message.success($t('Key436'));
+        hasAudio.value = true;
+      },
+      fail: (str) => {
+        Message.warning(str);
+      }
+    });
+  }
+}
+/**
+ * 关闭直播间
+ */
+function closeLiveRoom() {
+  MessageBox.confirm('是否关闭当前直播?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(() => {
+    CloseLiveRoom({ task_id }).then(() => {
+      router.push('/');
+      Message.success($t('Key426'));
+    });
+    stopLive();
+  });
+}
+</script>
+
+<style lang="scss" scoped>
+.teacher {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+
+  .middle {
+    flex: 1;
+
+    .live-list {
+      display: flex;
+      gap: 40px;
+      height: 100%;
+      padding: 53px 65px 32px;
+
+      &.only-teacher {
+        align-items: center;
+        justify-content: center;
+      }
+
+      &-item {
+        position: relative;
+        display: flex;
+        flex-direction: column;
+        row-gap: 16px;
+
+        .portrait {
+          width: 122px;
+          height: 122px;
+          line-height: 122px;
+          color: #fff;
+          text-align: center;
+          background-color: #7a9fff;
+          border-radius: 50%;
+        }
+        %card,
+        .teacher-card {
+          position: absolute;
+          top: 105px;
+          left: 34px;
+          padding: 4px 8px;
+          background-color: #ffb342;
+          border-radius: 4px;
+        }
+
+        .student-card {
+          @extend %card;
+
+          background-color: #c2ff42;
+        }
+
+        .name {
+          text-align: center;
+        }
+      }
+    }
+
+    %right-fixed {
+      position: fixed;
+      top: 96px;
+      right: 0;
+      width: 227px;
+      height: calc(100% - 192px);
+      background-color: #f3f3f3;
+      border-left: 1px solid #e5e5e5;
+
+      .title {
+        height: 38px;
+        padding: 8px;
+        font-size: 14px;
+        font-weight: 400;
+        line-height: 22px;
+        border-bottom: 1px solid #e5e5e5;
+      }
+    }
+
+    .chat {
+      @extend %right-fixed;
+
+      &-main {
+        height: calc(100% - 156px);
+        background-color: #fff;
+      }
+
+      .msg {
+        height: 118px;
+      }
+    }
+
+    .member-list {
+      @extend %right-fixed;
+    }
+  }
+
+  .bottom {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    height: 96px;
+    padding: 12px 16px;
+    background-color: #f8f8f8;
+
+    .operation {
+      display: flex;
+      column-gap: 16px;
+
+      &-item {
+        display: flex;
+        flex-direction: column;
+        row-gap: 4px;
+        align-items: center;
+        width: 80px;
+        height: 70px;
+        padding: 8px;
+        cursor: pointer;
+
+        &:active {
+          background-color: #e5e5e5;
+          border-radius: 8px;
+        }
+
+        img {
+          width: 32px;
+          height: 32px;
+        }
+      }
+
+      .line {
+        width: 2px;
+        height: 70px;
+        background-color: #d0d0d0;
+      }
+
+      .live-button {
+        height: 48px;
+        padding: 8px 24px;
+        font-size: 20px;
+        font-weight: 500;
+        color: #e52e2e;
+        cursor: pointer;
+        border: 2px solid #e52e2e;
+        border-radius: 4px;
+      }
+    }
+  }
+}
+</style>

+ 437 - 0
src/views/new_live/teacher/live.js

@@ -0,0 +1,437 @@
+import { ref } from 'vue';
+import { Message } from 'element-ui';
+import i18n from '@/locales/i18n';
+import { useLive } from '../index';
+import store from '@/store';
+
+const { publishStream, getHistory, getDevice, createLocalStream } = useLive();
+
+export function useTeacherLive(rtc) {
+  /**
+   * 开启直播
+   */
+  function startLive() {
+    rtc.startLive({
+      success(data) {
+        console.log(data);
+        publishStream('main'); // 如果需要立即推流,执行 publish 方法
+        Message({
+          message: i18n.t('Key452'),
+          type: 'success'
+        });
+      },
+      fail(data) {
+        Message({
+          message: `${i18n.t('Key453')}:${data}`,
+          type: 'warning'
+        });
+      }
+    });
+  }
+
+  /**
+   * 结束直播
+   */
+  function stopLive() {
+    rtc.stopLive({
+      success() {
+        // Message({
+        //   type: 'success',
+        //   message: i18n.t('Key454')
+        // });
+        console.log('结束直播成功');
+      },
+      fail(data) {
+        // Message({
+        //   type: 'error',
+        //   message: `${i18n.t('Key455')}:${JSON.stringify(data)}`
+        // });
+        console.log('结束直播失败', data);
+      }
+    });
+  }
+
+  /**
+   * 推送桌面共享
+   */
+  function publishShareStream() {
+    rtc.publishShareStream({
+      success: (stream) => {
+        console.log('推送桌面共享成功', stream);
+      },
+      fail: (str) => {
+        console.log(str);
+      }
+    });
+  }
+
+  /**
+   * 关闭桌面共享
+   */
+  function unPubShareStream() {
+    rtc.unPubShareStream();
+  }
+
+  /**
+   * 开启、结束、暂停、恢复录制
+   * @param { String } status: 'start' 开启, 'end' 结束, 'pause' 暂停, 'resume' 恢复
+   */
+  function liveRecord(status) {
+    rtc.liveRecord({
+      status,
+      success(data) {
+        console.log('成功', data);
+      },
+      fail(str) {
+        console.log(str);
+      }
+    });
+  }
+
+  // 连麦
+
+  /**
+   * 老师端发起邀请,邀请学生上麦。(举手模式)
+   * @param {Object} object
+   */
+  function invite(object) {
+    rtc.invite(object);
+  }
+
+  // 文档
+
+  // 获取文档列表
+  function getInstructionAllDocument(success, failed) {
+    rtc.getInstructionAllDocument({
+      getInstructionAllDocumentSuccess: success,
+      getInstructionAllDocumentFailed: failed
+    });
+  }
+
+  // 获取单个文档详情
+  function getSingleDocument(docId, callback) {
+    rtc.getSingleDocument({
+      docId,
+      getSingleDocumentCallback: callback
+    });
+  }
+
+  function docChange(obj) {
+    rtc.docChange(obj);
+  }
+
+  return {
+    startLive,
+    stopLive,
+    publishShareStream,
+    unPubShareStream,
+    liveRecord,
+    invite,
+    getInstructionAllDocument,
+    getSingleDocument,
+    docChange
+  };
+}
+
+/**
+ * 初始化监听事件
+ */
+export function initListener(rtc) {
+  let roomData = ref({
+    desc: '直播间标题',
+    name: '姓名',
+    user: {
+      id: '',
+      name: '',
+      role: 'talker',
+      rommid: ''
+    },
+    max_users: 1,
+    allow_chat: true,
+    allow_audio: true,
+    allow_speak: true
+  });
+  rtc.on('login_success', (data) => {
+    console.log('登录成功', data);
+    roomData.value = data;
+    // 初始化画板需要的数据
+    const canvasInitData = {
+      allowDraw: true, // 是否具有书写画笔权限(讲师权限) true / false
+      id: 'draw-parent',
+      pptDisplay: 1, // 文档展示方式。默认0,按窗口  1,按宽度
+      liveId: data.live.status === 1 ? data.live.id : '' // 如果直播已经开始,需将直播 id 传入 sdk 中
+    };
+    // 初始化画板
+    rtc.canvasInit(canvasInitData);
+  });
+
+  rtc.on('login_failed', (data) => {
+    console.log('登录失败', data);
+    Message({
+      message: `${i18n.t('Key443')}:${JSON.stringify(data)}`,
+      type: 'warning'
+    });
+  });
+
+  // 教师 必须在加入房间成功的事件回调里创建本地流
+  rtc.on('conference_join', () => {
+    console.log('加入房间成功');
+    getHistory({
+      success(data) {
+        const chatLog = data.datas.meta.chatLog.map(({ content, userName }) => {
+          return { msg: content, username: userName };
+        });
+        vue.chatList = chatLog;
+      },
+      fail(str) {
+        console.log(str);
+      }
+    });
+
+    getDevice({
+      success(data) {
+        console.log('获取音视频设备成功', data);
+        const device = store.state.app.liveDevice;
+        vue.device = data;
+
+        if (data.video.length === 0 && data.audio.length === 0) {
+          Message({
+            type: 'warning',
+            message: i18n.t('Key444')
+          });
+        }
+
+        const isVideo = device.video.length > 0 && data.video.some((item) => item.deviceId === device.video);
+        const isAudio = device.audio.length > 0 && data.audio.some((item) => item.deviceId === device.audio);
+
+        if (!isVideo && !isAudio) {
+          // TODO: 无音视频设备
+          // vue.setDevice(false);
+        } else {
+          createLocalStream('main');
+        }
+      },
+      fail(str) {
+        console.log('直播关闭状态或查询直播失败: ', str);
+        Message({
+          type: 'error',
+          message: `${i18n.t('Key445')}: ${str}`
+        });
+      }
+    });
+  });
+
+  rtc.on('conference_join_failed', (err) => {
+    // 加入房间失败  err为错误原因
+    console.log('加入房间失败', err);
+    Message({
+      message: `${i18n.t('Key446')}:${err}`,
+      type: 'warning'
+    });
+  });
+
+  /**
+   * 新增订阅流事件
+   */
+  let remoteStreamType = ref(-1);
+  rtc.on('allow_sub', (tryStream) => {
+    if (tryStream.isMixed()) {
+      console.log('是混合流,不订阅');
+    } else {
+      // 订阅远程流
+      rtc.trySubscribeStream({
+        tryStream,
+        success(stream) {
+          // 订阅流成功
+          const streamType = stream.streamType();
+          remoteStreamType.value = streamType;
+          console.log('订阅流成功', streamType);
+          stream.show('student', 'contain'); // 将流显示到指定 id 的盒子中
+
+          if (streamType === 1 || streamType === 10) {
+            vue.connect = true;
+            vue.callLoading = false;
+          }
+          if (streamType === 10) {
+            vue.dealStudentConnection(vue.connectUid, 2, vue.roomInfo.video_mode);
+          }
+        },
+        fail(err) {
+          console.log('订阅流失败', err);
+        }
+      });
+    }
+  });
+
+  // 直播未开始,不能推流
+  rtc.on('not_live', () => {
+    console.log('直播未开始,不能推流');
+    Message({
+      message: i18n.t('Key402'),
+      type: 'warning'
+    });
+  });
+
+  // 推流前查询直播状态失败,导致没有推流
+  rtc.on('local_stream_publish_failed', () => {
+    console.log('推流前查询直播状态失败,导致没有推流');
+    Message({
+      message: i18n.t('Key403'),
+      type: 'warning'
+    });
+  });
+
+  // 房间全量信息事件(人员进出时广播)
+  rtc.on('room_context', (roomData) => {
+    vue.roomContext = JSON.parse(roomData);
+    vue.getLiveRoomData_DRTD();
+    console.log('房间全量信息事件(人员进出时广播)', JSON.parse(roomData));
+  });
+
+  rtc.on('publish_stream', (str) => {
+    console.log('直播已开启', str);
+    vue.liveStat = true;
+  });
+
+  rtc.on('end_stream', (str) => {
+    console.log('直播已关闭', str);
+    vue.liveStat = false;
+  });
+
+  rtc.on('switch_user_settings', (settingData) => {
+    // 单个用户配置监听
+    console.log(settingData);
+  });
+
+  // 人员列表事件(人员麦序变化时广播)
+  rtc.on('speak_context', (speakData) => {
+    vue.speakData = JSON.parse(speakData);
+    console.log('人员列表事件(人员麦序变化时广播)', JSON.parse(speakData));
+  });
+
+  rtc.on('switch_settings', (data) => {
+    console.log('房间设置事件', data); // 房间设置事件
+  });
+
+  // 网络整体状态事件
+  rtc.on('netStatus', (data) => {
+    vue.netStatus = data.netStatus;
+  });
+
+  // 单条流状态通知事件
+  rtc.on('streamStatus', (data) => {
+    console.log(data);
+  });
+
+  // 推流异常断开事件
+  rtc.on('publishStreamErr', (data) => {
+    // 直播开启状态下,尝试重推这条流
+    console.log(`推流意外终止:${data.streamName}`);
+    publishStream('main');
+  });
+
+  // 视频无法自动播放
+  rtc.on('playError', (data) => {
+    console.log('视频无法自动播放', data);
+  });
+
+  // 监听通知移除流事件
+  rtc.on('stream_removed', (stream) => {
+    console.log('监听通知移除流事件', stream);
+    if ('room_user_id' in vue.connectStudent && stream.streamType() === 10 && vue.connect) {
+      vue.handsDown();
+    }
+    vue.connect = false;
+    vue.remoteStreamType = -1;
+  });
+
+  // 停止订阅流
+  rtc.on('unSub', (unSubStream) => {
+    console.log('停止订阅流', unSubStream);
+    rtc.unSubscribeStream({
+      unSubStream,
+      success(stream) {
+        console.log('取消订阅流成功', stream.id());
+      },
+
+      fail(str) {
+        console.log(str);
+      }
+    });
+  });
+
+  // 用户退出房间通知其他人员事件
+  rtc.on('exit_room_user', (data) => {
+    console.log('用户退出房间通知其他人员事件', data);
+    vue.studentExitLiveRoom(data.id, false);
+  });
+
+  /**
+   * 排麦监听事件
+   */
+
+  // 监听自己被邀请事件
+  rtc.on('inviteUp', (uid) => {
+    console.log('监听自己被邀请事件', uid);
+    rtc.inviteAccept({
+      success(str) {
+        console.log('接受邀请成功', str);
+      },
+      fail(data) {
+        console.log('接受邀请失败', data);
+      }
+    });
+  });
+
+  /**
+   * 监听聊天事件
+   */
+  rtc.on('chat_message', (data) => {
+    const dat = JSON.parse(data);
+    console.log(dat);
+    // 敏感词过滤:如果发送的聊天消息被系统判定包含敏感词,则只有发送者能收到本条消息,房间内其他人都不会收到这条聊天消息。
+    // 如果返回消息中有 isFilterChat 字段(消息不包含敏感词返回数据中无isFilterChat字段),且isFilterChat的值为1,则说明该消息包含敏感字,除发送者外其他人不会收到这条消息。
+    if (dat.isFilterChat && dat.isFilterChat === 1) {
+      return;
+    }
+    vue.chatList.push(dat);
+  });
+
+  rtc.on('allowChatChange', ({ settings }) => {
+    Message({
+      type: 'success',
+      message: settings.allow_chat ? i18n.t('Key633') : i18n.t('Key632')
+    });
+  });
+
+  // 画笔数据事件
+  rtc.on('draw', (data) => {
+    const drawData = JSON.parse(data);
+    console.log('画笔数据事件', drawData);
+  });
+
+  // 接收自定义消息
+  rtc.on('publish_message', (data) => {
+    // 连接中途下麦
+    if (data.type === 'handsDown-load-student' && data.uid === vue.connectUid) {
+      vue.callLoading = false;
+      Message({
+        type: 'warning',
+        message: i18n.t('Key449')
+      });
+    }
+
+    // 无对应设备
+    if (data.type === 'no-device') {
+      vue.callLoading = false;
+      Message({
+        type: 'warning',
+        message: data.video_mode === 1 ? i18n.t('Key451') : i18n.t('Key450')
+      });
+    }
+  });
+
+  return {
+    roomData
+  };
+}

+ 3 - 3
src/views/teacher/create_course/step_three_old/CreateTask.vue

@@ -504,12 +504,12 @@ function newTask(time_type) {
   });
 }
 
-function handleTask({ id, type, time_type }) {
+function handleTask({ id: task_id, type, time_type }) {
   if (type === 'edit') {
     router.push({
       path: `/create_course_step_table/new_task/${time_type}/${id}/${curItemID.value}?is_template=${is_template}`,
       query: {
-        task_id: id
+        task_id
       }
     });
   }
@@ -519,7 +519,7 @@ function handleTask({ id, type, time_type }) {
       cancelButtonText: $t('Key83'),
       type: 'warning'
     }).then(() => {
-      DeleteTask({ id }).then(() => {
+      DeleteTask({ id: task_id }).then(() => {
         Message.success($t('Key366'));
         getCSItemInfoBox();
       });