index.vue 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228
  1. <template>
  2. <div class="live">
  3. <!--顶部-->
  4. <div class="live-top">
  5. <div class="live-title">
  6. <div class="live-title-name">
  7. {{ roomInfo.course_name }} - {{ roomInfo.cs_item_name }} - {{ roomInfo.task_name }}
  8. </div>
  9. <div>
  10. <el-button v-if="!liveStat" @click="startLive">开启直播</el-button>
  11. <el-button v-if="liveStat" icon="el-icon-switch-button" @click="closeLiveRoom">
  12. 结束直播
  13. </el-button>
  14. <el-button v-if="liveStat" @click="reconnection">重连</el-button>
  15. <el-button @click="setDevice(true)">设置设备</el-button>
  16. </div>
  17. </div>
  18. </div>
  19. <!-- 主容器 -->
  20. <div class="live-container">
  21. <!-- 左侧 -->
  22. <div class="live-container-left">
  23. <div v-show="callLoading" class="loading">
  24. <div class="loading-wrapper">
  25. <el-avatar icon="el-icon-user" :src="connectStudent.student_image_url" />
  26. <p class="loading-title">
  27. 正在呼叫【{{ connectStudent.student_name }}】,等待对方接通...
  28. </p>
  29. <div>
  30. <el-button type="danger" circle @click="handsDown">
  31. <svg-icon icon-class="hang-up" />
  32. </el-button>
  33. </div>
  34. </div>
  35. </div>
  36. <div v-show="connect" class="student-parent">
  37. <div v-show="roomInfo.video_mode === 1 || remoteStreamType === 1" id="student"></div>
  38. <template v-if="remoteStreamType !== 1">
  39. <template v-if="roomInfo.video_mode === 1">
  40. <el-button type="danger" @click="handsDown">
  41. <svg-icon icon-class="hang-up" /> 挂断
  42. </el-button>
  43. </template>
  44. <template v-else>
  45. <div class="student-audio">
  46. <el-avatar icon="el-icon-user" :src="connectStudent.student_image_url" />
  47. <span class="connect-name">{{ connectStudent.student_name }}</span>
  48. <el-button type="danger" circle @click="handsDown">
  49. <svg-icon icon-class="hang-up" />
  50. </el-button>
  51. </div>
  52. </template>
  53. </template>
  54. </div>
  55. <div v-show="isDraw" id="draw-parent">
  56. <!-- 画笔设置 -->
  57. <div v-show="isDrawSetting" class="draw-setting">
  58. <span class="brush-shape" @click="drawChange('type', 2)">
  59. <svg-icon icon-class="brush-shape" />
  60. </span>
  61. <!-- 画笔颜色 -->
  62. <span
  63. v-for="item in drawColorList"
  64. :key="item"
  65. :class="['draw-color', item === curColor ? 'current' : '']"
  66. :style="{ 'background-color': item }"
  67. @click="drawChange('color', item)"
  68. />
  69. <span
  70. v-for="item in drawThicknessList"
  71. :key="item"
  72. class="draw-thickness"
  73. @click="drawChange('thickNess', item)"
  74. >
  75. <span :style="{ width: item * 2 + 'px', height: item * 2 + 'px' }"></span>
  76. </span>
  77. <span class="eraser" @click="drawChange('type', 9)">
  78. <svg-icon icon-class="back" />
  79. </span>
  80. <span class="eraser" @click="drawChange('type', 10)">
  81. <svg-icon icon-class="eraser" />
  82. </span>
  83. <span class="brush-clear" @click="drawChange('type', 0)">
  84. <svg-icon icon-class="clear" />
  85. </span>
  86. </div>
  87. </div>
  88. <div class="button-group">
  89. <div class="button-group-left">
  90. <span class="icon-button" @click="publishShareStream">
  91. <svg-icon :icon-class="remoteStreamType === 1 ? 'close' : 'share'" />
  92. </span>
  93. <span class="icon-button" @click="showDrawSetting">
  94. <svg-icon icon-class="draw" />
  95. </span>
  96. <span class="icon-button" @click="startGroup">
  97. <svg-icon icon-class="group" />
  98. </span>
  99. <span class="icon-button" @click="dialogVisible = true">
  100. <svg-icon icon-class="push" />
  101. </span>
  102. </div>
  103. <div class="button-group-right"></div>
  104. </div>
  105. </div>
  106. <!-- 右侧 -->
  107. <div class="live-container-right">
  108. <div class="live-teacher-lens">
  109. <div id="live" @mouseover="liveMenuShow = true" @mouseout="liveMenuShow = false"></div>
  110. <div :style="{ bottom: liveMenuShow ? '0' : '-40px' }" class="live-wrapper">
  111. <div class="live-wrapper-right">
  112. <span :style="{ color: netStatusColor }">{{ netStatus }}</span>
  113. </div>
  114. </div>
  115. <div class="live-operate">
  116. <div>{{ roomInfo.teacher_name }}</div>
  117. <div class="live-operate-icon">
  118. <svg-icon
  119. :icon-class="hasVideo ? 'camera-on-black' : 'camera-off-black'"
  120. @click="playOrPauseVideo"
  121. />
  122. <svg-icon
  123. :icon-class="hasAudio ? 'mike-on-black' : 'mike-off-black'"
  124. @click="playOrPauseAudio"
  125. />
  126. </div>
  127. </div>
  128. </div>
  129. <div class="live-container-right-chat">
  130. <div class="chat-top">
  131. <span>聊天</span>
  132. <label @click="chatBans">
  133. <input v-model="roomData.allow_chat" type="checkbox" class="allow-chat" />
  134. <span>禁言</span>
  135. </label>
  136. </div>
  137. <div class="chat-window">
  138. <ul ref="chat" class="chat-window-ul">
  139. <li v-for="(item, i) in chatList" :key="i">
  140. <div class="msg-normal">
  141. <span>{{ item.username }}: </span>
  142. <span>{{ item.msg }}</span>
  143. </div>
  144. </li>
  145. </ul>
  146. </div>
  147. <div class="chat-speak">
  148. <el-input
  149. v-model="msg"
  150. placeholder="输入发言"
  151. maxlength="400"
  152. @keydown.enter.native="sendMsg"
  153. >
  154. <el-button slot="append" @click="sendMsg">发送</el-button>
  155. </el-input>
  156. </div>
  157. </div>
  158. </div>
  159. </div>
  160. <!-- 学员列表 -->
  161. <div class="student-list">
  162. <div class="student-list-title">
  163. <span>学员列表({{ student_list.length }} / {{ roomInfo.student_count }})</span>
  164. </div>
  165. <div class="student-list-container">
  166. <ul>
  167. <li v-for="item in student_list" :key="item.room_user_id">
  168. <div class="li-top">
  169. <svg-icon
  170. v-if="item.is_mobile === 'true'"
  171. :icon-class="item.is_exit_page === 'true' ? 'mobile-close' : 'mobile'"
  172. />
  173. <el-avatar icon="el-icon-user" :size="40" :src="item.student_image_url" />
  174. </div>
  175. <div class="li-bottom">
  176. <div class="name">{{ item.student_name }}</div>
  177. <div class="li-bottom-operate">
  178. <svg-icon
  179. v-if="item.connection_mode === 1 && item.connection_status !== 0"
  180. icon-class="hang-up-black"
  181. @click="handsDown(item.room_user_id)"
  182. />
  183. <svg-icon
  184. v-else
  185. icon-class="video"
  186. @click="invite(item, 1, item.is_mobile === 'true', item.is_exit_page === 'true')"
  187. />
  188. <svg-icon
  189. v-if="item.connection_mode === 2 && item.connection_status !== 0"
  190. icon-class="hang-up-black"
  191. @click="handsDown(item.room_user_id)"
  192. />
  193. <svg-icon
  194. v-else
  195. icon-class="voice"
  196. @click="invite(item, 2, item.is_mobile === 'true', item.is_exit_page === 'true')"
  197. />
  198. </div>
  199. </div>
  200. </li>
  201. </ul>
  202. </div>
  203. </div>
  204. <!-- 推送资料 -->
  205. <select-material
  206. :dialog-visible="dialogVisible"
  207. :task-id="task_id"
  208. @dialogClose="dialogClose"
  209. @dialogPush="dialogPush"
  210. />
  211. <!-- 教师查看当前完成列表 -->
  212. <complete-list
  213. :task-id="task_id"
  214. :dialog-visible-complete="dialogVisibleComplete"
  215. @dialogCompleteClose="dialogCompleteClose"
  216. />
  217. <select-device
  218. :dialog-visible-device="dialogVisibleDevice"
  219. :device="device"
  220. @dialogDeviceClose="dialogDeviceClose"
  221. />
  222. <el-dialog
  223. title="分组讨论组数"
  224. top="30vh"
  225. width="300px"
  226. class="dialog-group"
  227. :visible.sync="dialogVisibleGroup"
  228. :close-on-click-modal="false"
  229. >
  230. <el-select v-model="group_count">
  231. <el-option v-for="i in groupNumList" :key="i" :label="i" :value="i" />
  232. </el-select>
  233. <span slot="footer">
  234. <el-button size="small" @click="closeGroup">取 消</el-button>
  235. <el-button size="small" type="primary" @click="dialogGroup">确 定</el-button>
  236. </span>
  237. </el-dialog>
  238. </div>
  239. </template>
  240. <script>
  241. import {
  242. GetLiveRoomStudentList,
  243. CloseLiveRoom,
  244. GetLiveRoomInfo,
  245. StudentExitLiveRoom,
  246. StartGroup,
  247. GetGroupStatus,
  248. DealStudentConnection,
  249. GetStudentInfo_Connection
  250. } from '@/api/live';
  251. import { app } from '@/store/mutation-types';
  252. import SelectMaterial from '@/components/live/SelectMaterial.vue';
  253. import CompleteList from './CompleteList.vue';
  254. import SelectDevice from '../SelectDevice.vue';
  255. import * as common from './live';
  256. export default {
  257. components: {
  258. SelectMaterial,
  259. CompleteList,
  260. SelectDevice
  261. },
  262. data() {
  263. return {
  264. task_id: this.$route.query.task_id,
  265. // 连麦
  266. connect: false,
  267. // 连线学员信息
  268. connectStudent: {},
  269. // 等待接通
  270. callLoading: false,
  271. dialogVisible: false,
  272. // 学员完成
  273. dialogVisibleComplete: false,
  274. // 定时器
  275. timer: null,
  276. remoteStreamType: -1,
  277. rtc: null,
  278. roomData: {
  279. desc: '直播间标题',
  280. name: '姓名',
  281. user: {
  282. id: '',
  283. name: '',
  284. role: 'talker',
  285. rommid: ''
  286. },
  287. max_users: 1,
  288. allow_chat: true,
  289. allow_audio: true,
  290. allow_speak: true
  291. },
  292. roomInfo: {
  293. room_id: '',
  294. video_mode: 1,
  295. task_name: '',
  296. cs_item_name: '',
  297. course_name: '',
  298. teacher_name: '',
  299. student_count: 0,
  300. student_connection_info: {}
  301. },
  302. loadedNumber: 0,
  303. speakData: {},
  304. roomContext: {},
  305. msg: '',
  306. chatList: [],
  307. isDrawSetting: false,
  308. curColor: '#343434',
  309. drawColorList: ['#FF4747', '#343434', '#628EFF', '#FFCA0E'],
  310. drawThicknessList: ['1', '3', '5'],
  311. // 直播间学员列表
  312. student_list: [],
  313. // 直播状态
  314. liveStat: false,
  315. liveMenuShow: false,
  316. // 分组讨论
  317. groupNumList: [],
  318. group_count: 1,
  319. // 网络整体情况
  320. netStatus: 0,
  321. dialogVisibleGroup: false,
  322. dialogVisibleDevice: false,
  323. isRecreate: false,
  324. device: {
  325. video: [],
  326. audio: []
  327. },
  328. // 本地视频流画面、声音
  329. hasVideo: false,
  330. hasAudio: false
  331. };
  332. },
  333. computed: {
  334. // 画板模式
  335. isDraw() {
  336. return !this.connect && !this.callLoading;
  337. },
  338. netStatusColor() {
  339. if (this.netStatus >= 1000) {
  340. return '#f00';
  341. }
  342. if (this.netStatus >= 500) {
  343. return '#d6e91a';
  344. }
  345. if (this.netStatus >= 200) {
  346. return '#6aee4c';
  347. }
  348. return '#38d514';
  349. },
  350. connectUid() {
  351. return 'room_user_id' in this.connectStudent ? this.connectStudent.room_user_id : '';
  352. }
  353. },
  354. watch: {
  355. loadedNumber(newVal) {
  356. if (newVal === 5) {
  357. common.createScript(
  358. 'https://class.csslcloud.net/static/SDK/docSDK/drawSdk_3.0.js'
  359. ).onload = () => {
  360. this.initSDK();
  361. this.$loading().close();
  362. };
  363. }
  364. },
  365. // 聊天列表滚动
  366. chatList() {
  367. common.chatRoll(this, false);
  368. }
  369. },
  370. created() {
  371. GetGroupStatus({ task_id: this.task_id }).then(({ is_enable_group }) => {
  372. if (is_enable_group === 'true') {
  373. this.$router.push({
  374. path: '/live/teacher/group',
  375. query: {
  376. task_id: this.task_id
  377. }
  378. });
  379. } else {
  380. this.$loading({
  381. text: '加载直播所需SDK中...',
  382. background: '#fff'
  383. });
  384. common.downloadWebSDK(this);
  385. this.getLiveRoomStudentList();
  386. this.getLiveRoomInfo();
  387. }
  388. });
  389. },
  390. mounted() {
  391. document.addEventListener(
  392. 'click',
  393. e => {
  394. let target = e.target;
  395. let isHasClass = false;
  396. do {
  397. if (target === null) {
  398. break;
  399. }
  400. if (target.className === 'draw-setting') {
  401. isHasClass = true;
  402. }
  403. target = target.parentElement;
  404. } while (!isHasClass);
  405. if (!isHasClass) {
  406. this.isDrawSetting = false;
  407. }
  408. },
  409. true
  410. );
  411. this.getLiveRoomStudentListPolling();
  412. },
  413. beforeDestroy() {
  414. clearInterval(this.timer);
  415. common.closeVideo('main');
  416. },
  417. methods: {
  418. initSDK() {
  419. const { live_room_sys_user_id, room_id, session_id } = this.$route.query;
  420. this.rtc = common.initSDK({
  421. userid: live_room_sys_user_id,
  422. roomid: room_id,
  423. sessionid: session_id
  424. });
  425. common.initListener(this); // 注册监听事件
  426. this.getLiveStat();
  427. },
  428. getLiveRoomInfo() {
  429. GetLiveRoomInfo({ task_id: this.task_id }).then(
  430. ({
  431. room_id,
  432. video_mode,
  433. task_name,
  434. cs_item_name,
  435. course_name,
  436. teacher_name,
  437. student_count,
  438. student_connection_info
  439. }) => {
  440. this.roomInfo = {
  441. room_id,
  442. video_mode,
  443. task_name,
  444. cs_item_name,
  445. course_name,
  446. teacher_name,
  447. student_count,
  448. student_connection_info
  449. };
  450. this.connectStudent = student_connection_info;
  451. if (student_connection_info.connection_status === 1) {
  452. this.callLoading = true;
  453. }
  454. if (student_connection_info.connection_status === 2) {
  455. this.connect = true;
  456. }
  457. }
  458. );
  459. },
  460. closeLiveRoom() {
  461. CloseLiveRoom({ task_id: this.task_id }).then(() => {
  462. this.$router.push('/');
  463. this.$message.success('关闭直播成功');
  464. });
  465. },
  466. startLive() {
  467. common.startLive();
  468. },
  469. getLiveStat() {
  470. common.getLiveStat({
  471. success: data => {
  472. this.liveStat = data.started;
  473. },
  474. fail: str => {
  475. this.liveStat = false;
  476. console.log('直播关闭状态或查询直播失败', str);
  477. }
  478. });
  479. },
  480. // 推送桌面共享
  481. publishShareStream() {
  482. if (this.remoteStreamType === 1) {
  483. common.unPubShareStream();
  484. } else {
  485. common.publishShareStream();
  486. }
  487. },
  488. reconnection() {
  489. common.reconnection();
  490. },
  491. // 老师邀请学生上麦
  492. invite(student, mode, is_mobile, is_exit_page) {
  493. if (is_mobile && is_exit_page) {
  494. this.$message.warning('手机端学员已离开直播视频界面,不能进行通话');
  495. return;
  496. }
  497. if (this.connect || this.callLoading) {
  498. this.$message.warning('正在连麦中');
  499. return;
  500. }
  501. this.callLoading = true;
  502. this.connectStudent = student;
  503. GetLiveRoomInfo({ task_id: this.task_id })
  504. .then(({ video_mode }) => {
  505. let uid = student.room_user_id;
  506. this.roomInfo.video_mode = mode;
  507. if (video_mode === mode) {
  508. this.inviteStudent(uid, student, mode);
  509. } else {
  510. common.roomUpdate({
  511. video_mode: mode,
  512. roomUpdateSuccess: data => {
  513. console.log(data, '连麦音视频模式更新请求成功!');
  514. this.inviteStudent(uid, student, mode);
  515. },
  516. roomUpdateFailed: data => {
  517. this.callLoading = false;
  518. this.$message.error('连麦音视频模式更新请求失败! 请稍后再试!');
  519. }
  520. });
  521. }
  522. })
  523. .catch(() => {
  524. this.callLoading = false;
  525. });
  526. },
  527. inviteStudent(uid, connectStudent, mode) {
  528. common.invite({
  529. uid,
  530. success: str => {
  531. console.log('邀请上麦成功', str);
  532. this.dealStudentConnection(uid, 1, mode);
  533. common.sendPublishMessage({
  534. type: 'inviteImage',
  535. connectStudent
  536. });
  537. },
  538. fail: data => {
  539. console.log('邀请上麦失败:', data);
  540. this.callLoading = false;
  541. this.$message.error(`邀请上麦失败:${data.errorMsg}`);
  542. }
  543. });
  544. },
  545. dealStudentConnection(room_user_id, deal_mode, connection_mode) {
  546. DealStudentConnection({
  547. task_id: this.task_id,
  548. room_user_id,
  549. deal_mode,
  550. connection_mode
  551. }).then(() => {
  552. this.getLiveRoomStudentList();
  553. });
  554. },
  555. handsDown(uid) {
  556. let connectUid = typeof uid === 'string' ? uid : this.connectUid;
  557. if (connectUid.length === 0) {
  558. GetStudentInfo_Connection({ task_id: this.task_id }).then(({ room_user_id }) =>
  559. this.handsDown_Live(room_user_id)
  560. );
  561. } else {
  562. this.handsDown_Live(connectUid);
  563. }
  564. },
  565. // 下麦
  566. handsDown_Live(uid) {
  567. common.handsDown({
  568. uid,
  569. success: str => {
  570. if (this.callLoading) {
  571. common.sendPublishMessage({
  572. type: 'handsDown-load',
  573. uid
  574. });
  575. }
  576. this.dealStudentConnection(uid, 0, this.connectStudent.connection_mode);
  577. this.callLoading = false;
  578. this.connect = false;
  579. common.updateMcResult('', 0);
  580. this.$message.success('下麦成功');
  581. this.connectStudent = {};
  582. },
  583. fail: data => {
  584. this.callLoading = false;
  585. this.connect = false;
  586. console.log('下麦失败', data);
  587. this.$message.warning(`下麦失败:${data.errorMsg}`);
  588. }
  589. });
  590. },
  591. // 本地流视频开启、关闭
  592. playOrPauseVideo() {
  593. if (this.device.video.length === 0) {
  594. return this.$message.warning('无视频设备');
  595. }
  596. if (this.hasVideo) {
  597. common.pauseVideo({
  598. streamName: 'main',
  599. success: () => {
  600. this.$message.success('关闭本地流视频画面成功');
  601. this.hasVideo = false;
  602. },
  603. fail: str => {
  604. this.$message.warning(str);
  605. }
  606. });
  607. } else {
  608. common.playVideo({
  609. streamName: 'main',
  610. success: () => {
  611. this.$message.success('开启本地流视频画面成功');
  612. this.hasVideo = true;
  613. },
  614. fail: str => {
  615. this.$message.warning(str);
  616. }
  617. });
  618. }
  619. },
  620. // 本地流音频开启、关闭
  621. playOrPauseAudio() {
  622. if (this.device.audio.length === 0) {
  623. return this.$message.warning('无音频设备');
  624. }
  625. if (this.hasAudio) {
  626. common.pauseAudio({
  627. streamName: 'main',
  628. success: () => {
  629. this.$message.success('关闭本地流声音成功');
  630. this.hasAudio = false;
  631. },
  632. fail: str => {
  633. this.$message.warning(str);
  634. }
  635. });
  636. } else {
  637. common.playAudio({
  638. streamName: 'main',
  639. success: () => {
  640. this.$message.success('开启本地流声音成功');
  641. this.hasAudio = true;
  642. },
  643. fail: str => {
  644. this.$message.warning(str);
  645. }
  646. });
  647. }
  648. },
  649. // 发消息
  650. sendMsg() {
  651. common.sendMsg(this.msg);
  652. this.msg = '';
  653. },
  654. chatBans() {
  655. common.roomUpdate({
  656. allow_chat: !this.roomData.allow_chat,
  657. roomUpdateSuccess(data) {
  658. console.log(data, '房间模板配置更新请求成功!');
  659. },
  660. roomUpdateFailed(data) {
  661. console.log(data, '房间模板配置更新请求失败! 请稍后再试!');
  662. }
  663. });
  664. },
  665. // 画笔变更
  666. drawChange(action, value) {
  667. if (action === 'color') {
  668. common.drawChange(action, parseInt(Number(value.replace('#', '0x')), 10));
  669. this.curColor = value;
  670. } else {
  671. common.drawChange(action, value);
  672. }
  673. this.isDrawSetting = false;
  674. },
  675. // 设置音视频设备
  676. setDevice(isRecreate) {
  677. this.isRecreate = isRecreate;
  678. this.dialogVisibleDevice = true;
  679. },
  680. getNetPoint() {
  681. common.getNetPoint();
  682. },
  683. dialogDeviceClose(device) {
  684. this.$store.commit(`app/${app.SET_DEVICE}`, device);
  685. this.dialogVisibleDevice = false;
  686. if (this.isRecreate) {
  687. common.closeVideoTeacher({
  688. streamName: 'main',
  689. success: () => {
  690. common.createLocalStream(this);
  691. },
  692. fail: str => {
  693. console.log(str);
  694. }
  695. });
  696. } else {
  697. common.createLocalStream(this);
  698. }
  699. },
  700. showDrawSetting() {
  701. this.isDrawSetting = !this.isDrawSetting;
  702. },
  703. getLiveRoomStudentList() {
  704. GetLiveRoomStudentList({ task_id: this.task_id }).then(({ student_list }) => {
  705. this.student_list = student_list;
  706. });
  707. },
  708. getLiveRoomStudentListPolling() {
  709. this.timer = setInterval(() => {
  710. this.getLiveRoomStudentList();
  711. }, 5000);
  712. },
  713. studentExitLiveRoom(room_user_id, is_exit_page = true) {
  714. StudentExitLiveRoom({ task_id: this.task_id, room_user_id, is_exit_page });
  715. },
  716. publishStream() {
  717. common.publishStream('main');
  718. },
  719. // 分组讨论
  720. startGroup() {
  721. for (let i = 1; i <= this.student_list.length; i++) {
  722. this.groupNumList.push(i);
  723. }
  724. this.dialogVisibleGroup = true;
  725. },
  726. // 弹出框方法
  727. dialogGroup() {
  728. const loading = this.$loading({
  729. text: '正在进行分组,请等待...'
  730. });
  731. // 开始分组讨论
  732. StartGroup({ task_id: this.task_id, group_count: this.group_count })
  733. .then(() => {
  734. this.$message.success('开启分组讨论成功');
  735. this.$router.push({
  736. path: '/live/teacher/group',
  737. query: {
  738. task_id: this.task_id
  739. }
  740. });
  741. })
  742. .finally(() => {
  743. loading.close();
  744. });
  745. },
  746. closeGroup() {
  747. this.dialogVisibleGroup = false;
  748. this.groupNumList = [];
  749. this.group_count = 1;
  750. },
  751. dialogClose() {
  752. this.dialogVisible = false;
  753. },
  754. dialogPush() {
  755. this.dialogVisible = false;
  756. this.dialogVisibleComplete = true;
  757. },
  758. dialogCompleteClose() {
  759. this.dialogVisibleComplete = false;
  760. }
  761. }
  762. };
  763. </script>
  764. <style lang="scss" scoped>
  765. @import '~@/styles/mixin.scss';
  766. $live-bc: #3d3938;
  767. $draw-h: 520px;
  768. .live {
  769. @include dialog;
  770. margin-bottom: 24px;
  771. min-width: 1440px;
  772. .dialog-group .el-dialog__body {
  773. height: 65px;
  774. }
  775. // 顶部
  776. &-top {
  777. background-color: #fff;
  778. padding: 24px 32px;
  779. border-top-left-radius: 8px;
  780. border-top-right-radius: 8px;
  781. border-bottom: 1px solid #ccc;
  782. box-shadow: 0 1px 1px #ccc;
  783. .live-title {
  784. display: flex;
  785. justify-content: space-between;
  786. &-name {
  787. font-size: 22px;
  788. }
  789. .el-button {
  790. border-radius: 4px;
  791. padding: 7px 12px;
  792. }
  793. }
  794. }
  795. // 主容器
  796. &-container {
  797. display: flex;
  798. &-left {
  799. flex: 1;
  800. background-color: #fff;
  801. border-radius: 8px;
  802. .loading {
  803. position: relative;
  804. width: 100%;
  805. height: $draw-h;
  806. color: #fff;
  807. background-color: #646464;
  808. display: flex;
  809. justify-content: center;
  810. align-items: center;
  811. &-wrapper {
  812. text-align: center;
  813. > .el-avatar {
  814. width: 96px;
  815. height: 96px;
  816. line-height: 96px;
  817. margin-bottom: 24px;
  818. > &--icon {
  819. font-size: 36px;
  820. }
  821. }
  822. .loading-title {
  823. margin-bottom: 24px;
  824. }
  825. }
  826. }
  827. .student-parent {
  828. position: relative;
  829. width: 100%;
  830. height: $draw-h;
  831. #student {
  832. width: 100%;
  833. height: $draw-h;
  834. position: relative;
  835. background-color: $live-bc;
  836. }
  837. > .el-button {
  838. position: absolute;
  839. bottom: 40px;
  840. left: calc(50% - 44px);
  841. }
  842. .student-audio {
  843. width: 100%;
  844. height: 100%;
  845. background-color: #646464;
  846. display: flex;
  847. flex-direction: column;
  848. justify-content: center;
  849. align-items: center;
  850. .connect-name {
  851. color: #fff;
  852. margin: 24px 0;
  853. }
  854. > .el-avatar {
  855. width: 96px;
  856. height: 96px;
  857. line-height: 96px;
  858. > &--icon {
  859. font-size: 36px;
  860. }
  861. }
  862. }
  863. }
  864. // 画板
  865. #draw-parent {
  866. height: $draw-h;
  867. width: 100%;
  868. position: relative;
  869. background-color: $live-bc;
  870. overflow: hidden;
  871. // 设置屏幕画笔
  872. .draw-setting {
  873. position: absolute;
  874. bottom: 13px;
  875. left: 22px;
  876. z-index: 9999;
  877. background-color: #a0a0a0;
  878. padding: 6px;
  879. border-radius: 40px;
  880. height: 40px;
  881. line-height: 28px;
  882. & > span {
  883. display: inline-block;
  884. text-align: center;
  885. margin-right: 10px;
  886. }
  887. & > span.brush-shape {
  888. width: 28px;
  889. height: 28px;
  890. border-radius: 50%;
  891. background-color: #fff;
  892. cursor: pointer;
  893. }
  894. .draw-color {
  895. position: relative;
  896. top: 5px;
  897. height: 18px;
  898. width: 18px;
  899. border-radius: 50%;
  900. cursor: pointer;
  901. }
  902. .current::after {
  903. content: '';
  904. position: absolute;
  905. bottom: -7px;
  906. left: 7px;
  907. width: 4px;
  908. height: 4px;
  909. border-radius: 50%;
  910. background-color: #292929;
  911. }
  912. .draw-thickness {
  913. height: 18px;
  914. width: 18px;
  915. cursor: pointer;
  916. display: inline-flex;
  917. flex-direction: column;
  918. justify-content: center;
  919. vertical-align: middle;
  920. align-items: center;
  921. span {
  922. border-radius: 50%;
  923. background-color: #000;
  924. }
  925. }
  926. & > .brush-clear {
  927. width: 28px;
  928. height: 28px;
  929. border-radius: 50%;
  930. background-color: #666;
  931. cursor: pointer;
  932. margin-right: 0;
  933. }
  934. & > .eraser {
  935. @extend .brush-clear;
  936. margin-right: 12px;
  937. }
  938. }
  939. }
  940. .button-group {
  941. display: flex;
  942. justify-content: space-between;
  943. height: 48px;
  944. background-color: #4d4d4d;
  945. padding: 0 15px;
  946. border-bottom-left-radius: 5px;
  947. .svg-icon {
  948. font-size: 20px;
  949. }
  950. &-left {
  951. .stop-group {
  952. color: #fff;
  953. }
  954. > .icon-button {
  955. display: inline-block;
  956. height: 100%;
  957. padding: 14px 16px;
  958. cursor: pointer;
  959. &:active,
  960. &:hover {
  961. background-color: #3d3d3d;
  962. }
  963. }
  964. }
  965. }
  966. }
  967. &-right {
  968. height: 568px;
  969. width: 485px;
  970. background-color: #fff;
  971. .live-teacher-lens {
  972. display: flex;
  973. position: relative;
  974. overflow: hidden;
  975. #live {
  976. width: 272px;
  977. height: 152px;
  978. background-color: $live-bc;
  979. }
  980. .live-wrapper {
  981. display: flex;
  982. position: absolute;
  983. height: 40px;
  984. width: 272px;
  985. background-color: #000;
  986. opacity: 0.7;
  987. color: #fff;
  988. line-height: 40px;
  989. padding: 0 16px;
  990. transition: all 300ms ease-in 0s;
  991. > div:first-child {
  992. flex: 6;
  993. }
  994. &-right {
  995. flex: 4;
  996. text-align: right;
  997. .svg-icon {
  998. cursor: pointer;
  999. margin-right: 18px;
  1000. }
  1001. }
  1002. }
  1003. .live-operate {
  1004. flex: 1;
  1005. display: flex;
  1006. flex-direction: column;
  1007. justify-content: center;
  1008. align-items: center;
  1009. &-icon {
  1010. display: flex;
  1011. padding-top: 18px;
  1012. > .svg-icon {
  1013. cursor: pointer;
  1014. margin-left: 24px;
  1015. }
  1016. }
  1017. }
  1018. }
  1019. // 聊天窗口
  1020. &-chat {
  1021. height: 417px;
  1022. border: 1px solid #ccc;
  1023. display: flex;
  1024. flex-direction: column;
  1025. justify-content: space-between;
  1026. .chat-top {
  1027. display: flex;
  1028. justify-content: space-between;
  1029. padding: 15px 15px 10px;
  1030. border-bottom: 1px solid #e6e6e6;
  1031. color: #959595;
  1032. label {
  1033. cursor: pointer;
  1034. }
  1035. .allow-chat {
  1036. margin-right: 12px;
  1037. }
  1038. }
  1039. .chat-window {
  1040. position: relative;
  1041. width: 100%;
  1042. height: 100%;
  1043. overflow: hidden;
  1044. &-ul {
  1045. position: absolute;
  1046. top: 0;
  1047. left: 0;
  1048. width: 100%;
  1049. height: 100%;
  1050. overflow: auto;
  1051. .msg-normal {
  1052. padding: 7px 16px;
  1053. }
  1054. }
  1055. }
  1056. .chat-speak {
  1057. padding: 16px;
  1058. }
  1059. }
  1060. }
  1061. }
  1062. // 学员列表
  1063. .student-list {
  1064. width: 100%;
  1065. font-size: 14px;
  1066. &-title {
  1067. padding: 24px 16px;
  1068. background-color: #fff;
  1069. }
  1070. &-container {
  1071. padding: 16px 20px;
  1072. background-color: #f2f2f2;
  1073. ul {
  1074. display: flex;
  1075. flex-wrap: wrap;
  1076. li {
  1077. display: flex;
  1078. flex-direction: column;
  1079. align-items: center;
  1080. width: 120px;
  1081. height: 144px;
  1082. background-color: #fff;
  1083. margin: 0 8px 8px 0;
  1084. .li-top {
  1085. position: relative;
  1086. flex: 1;
  1087. width: 100%;
  1088. background-color: #000;
  1089. min-height: 72px;
  1090. text-align: center;
  1091. .svg-icon {
  1092. position: absolute;
  1093. top: 10px;
  1094. right: 10px;
  1095. }
  1096. .el-avatar--icon {
  1097. margin-top: 16px;
  1098. }
  1099. }
  1100. .li-bottom {
  1101. flex: 1;
  1102. min-height: 72px;
  1103. padding: 16px 8px;
  1104. &-operate {
  1105. padding: 0 24px;
  1106. .svg-icon {
  1107. font-size: 18px;
  1108. cursor: pointer;
  1109. margin: 8px 4px 0;
  1110. }
  1111. }
  1112. }
  1113. }
  1114. }
  1115. }
  1116. }
  1117. }
  1118. </style>