仲裁视频会议H5

room.vue 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685
  1. <template>
  2. <div class="page">
  3. <div class="roompage" @mouseover="mouseHover" v-if="modileFlag">
  4. <div class="txtContent">
  5. <el-tag type="danger" @click="txtContent">会议内容</el-tag>
  6. </div>
  7. <div :class="userClass" :style="{ height: userHeight }" id="localStream">
  8. <div class="userName">{{ userId }}</div>
  9. </div>
  10. <div :class="userClass" :style="{ height: userHeight }" v-for="(item, index) in userList" :key="index" :id="item">
  11. <div class="userName">{{ item }}</div>
  12. </div>
  13. <div class="footer">
  14. <roomFooter @exitRoom="exitRoom" :roomId="roomId"></roomFooter>
  15. </div>
  16. </div>
  17. <div class="roomPhone" v-if="!modileFlag">
  18. <div class="header">
  19. <roomFooterPhone @exitRoom="exitRoom"></roomFooterPhone>
  20. </div>
  21. <div class="bodyVideo">
  22. <div :class="userClassPhone" id="localStream">
  23. <div class="userNamePhone">{{ userId }}</div>
  24. </div>
  25. <div :class="userClassPhone" v-for="(item, index) in userList" :key="index" :id="item">
  26. <div class="userNamePhone">{{ item }}</div>
  27. </div>
  28. </div>
  29. </div>
  30. <!-- 语音转文字弹窗 -->
  31. <el-drawer title="会议内容" :visible.sync="textVisible" :modal="false">
  32. <div style="margin-left: 20px; margin-bottom: 10px">
  33. <div style="
  34. width: 100%;
  35. display: flex;
  36. justify-content: space-around;
  37. margin-bottom: 10px;
  38. ">
  39. <el-upload ref="upload" :limit="1" action="https://api.xayunmei.com/tiaojieapi/video/upload"
  40. :headers="headers" :data="filedata" :on-change="beforeUpload" :on-success="handlSuccess"
  41. :file-list="fileList" v-if="appFlag">
  42. <el-button slot="trigger" size="small" type="primary">申请人上传证据</el-button>
  43. </el-upload>
  44. <el-upload v-if="resFlag" ref="upload1" :limit="1" action="https://api.xayunmei.com/tiaojieapi/video/upload"
  45. :headers="headers1" :data="filedata1" :on-change="beforeUpload1" :on-success="handlSuccess1"
  46. :file-list="fileList1">
  47. <el-button slot="trigger" size="small" type="primary">被申请人上传证据</el-button>
  48. </el-upload>
  49. <el-upload v-if="editFlag" ref="upload3" :limit="1" action="https://api.xayunmei.com/tiaojieapi/video/upload"
  50. :headers="headers3" :data="filedata3" :on-change="beforeUpload3" :on-success="handlSuccess3"
  51. :file-list="fileList3" accept=".doc,.docx">
  52. <el-button slot="trigger" size="small" type="primary">上传调解书</el-button>
  53. </el-upload>
  54. </div>
  55. <div class="list">
  56. <div class="applicant" v-if="applicantFile.length > 0">
  57. <div>申请人证据</div>
  58. <div style="color: #104fad; cursor: pointer" v-for="(item, index) in applicantFile" :key="index"
  59. @click="preview(item, 0)">
  60. {{ item.annexName }}
  61. </div>
  62. </div>
  63. <div class="res" v-if="resFile.length > 0">
  64. <div>被申请人证据</div>
  65. <div style="color: #104fad; cursor: pointer" v-for="(item, index) in resFile" :key="index"
  66. @click="preview(item, 0)">
  67. {{ item.annexName }}
  68. </div>
  69. </div>
  70. <div class="mediate" v-if="mediateFile.length > 0">
  71. <div>调解书</div>
  72. <div style="color: #104fad; cursor: pointer" v-for="(item, index) in mediateFile" :key="index"
  73. @click="preview(item, 1)">
  74. {{ item.annexName }}
  75. </div>
  76. </div>
  77. </div>
  78. </div>
  79. <quill-editor ref="myQuillEditor" v-model="contentValue" :options="editorOption" @blur="onEditorBlur($event)"
  80. @focus="onEditorFocus($event)" @ready="onEditorReady($event)"></quill-editor>
  81. <el-button class="updataBtn" @click="updataClick" type="primary" :disabled="!updataFlag">确认修改内容</el-button>
  82. </el-drawer>
  83. </div>
  84. </template>
  85. <script>
  86. import { getUsersig, reserveConferenceList } from "@/api/home";
  87. import {
  88. secretaryRoleByUserId,
  89. htmlToPDF,
  90. selectById,
  91. selectRoleMenuByCaseId,
  92. getMenuPermsByUser,
  93. } from "@/api/room";
  94. import { getWidth, getHeight, getWidthPhone, getModile } from "@/utils/utils";
  95. import roomFooter from "./components/roomFooter.vue";
  96. import roomFooterPhone from "./components/footerPhone.vue";
  97. import ASR from "../utils/asr.esm.js";
  98. import TRTC from "trtc-sdk-v5";
  99. let trtc = null;
  100. import "quill/dist/quill.core.css";
  101. import "quill/dist/quill.snow.css";
  102. import "quill/dist/quill.bubble.css";
  103. import { quillEditor } from "vue-quill-editor";
  104. export default {
  105. name: "App",
  106. components: {
  107. roomFooter,
  108. roomFooterPhone,
  109. quillEditor,
  110. },
  111. data() {
  112. return {
  113. resFlag: null,
  114. appFlag: null,
  115. userClass: "userVideo5",
  116. userClassPhone: "userVideoPhone",
  117. userList: [],
  118. userHeight: "100%",
  119. userPhoneHeight: "92%",
  120. userSign: "",
  121. roomId: "",
  122. userId: null,
  123. id: null,
  124. showFlag: false,
  125. modileFlag: false,
  126. localStreamAsr: null,
  127. contentValue: "",
  128. textVisible: false,
  129. editorOption: {
  130. // Some Quill options...
  131. },
  132. asrList: {},
  133. updataFlag: true,
  134. token: "",
  135. applicantFile: [],
  136. resFile: [],
  137. mediateFile: [],
  138. headers: {
  139. // Authorization: "Bearer " + token,
  140. Authorization: "",
  141. },
  142. filedata: {
  143. annexType: 2,
  144. officeFlag: 0,
  145. caseId: null,
  146. },
  147. fileList: [],
  148. headers1: {
  149. // Authorization: "Bearer " + token,
  150. Authorization: "",
  151. },
  152. filedata1: {
  153. annexType: 12,
  154. officeFlag: 0,
  155. caseId: null,
  156. },
  157. fileList1: [],
  158. headers3: {
  159. // Authorization: "Bearer " + token,
  160. Authorization: "",
  161. },
  162. filedata3: {
  163. annexType: 7,
  164. officeFlag: 0,
  165. caseId: null,
  166. },
  167. fileList3: [],
  168. editFlag: false,
  169. };
  170. },
  171. methods: {
  172. /**获取当前用户操作权限 */
  173. getMenuPermsByUserFn() {
  174. getMenuPermsByUser().then((res) => {
  175. // console.log(res.perms, "KKKKKKKKKKKKKKKKKKKKKKKKKKKKKK");
  176. this.editFlag = res.perms.includes("caseManagement:list:editOffice");
  177. });
  178. },
  179. beforeUpload(file, fileList) {
  180. this.fileList = fileList;
  181. if (file.name.indexOf("docx") == -1) {
  182. this.filedata.officeFlag = 0;
  183. } else {
  184. this.filedata.officeFlag = 1;
  185. }
  186. },
  187. // 文件上传成功
  188. handlSuccess(res, file) {
  189. this.$message({
  190. message: "上传成功",
  191. type: "success",
  192. });
  193. this.$refs.upload.clearFiles();
  194. this.selectByIdFn(this.caseId);
  195. },
  196. beforeUpload1(file, fileList) {
  197. this.fileList1 = fileList;
  198. if (file.name.indexOf("docx") == -1) {
  199. this.filedata1.officeFlag = 0;
  200. } else {
  201. this.filedata1.officeFlag = 1;
  202. }
  203. },
  204. // 文件上传成功
  205. handlSuccess1(res, file) {
  206. this.$message({
  207. message: "上传成功",
  208. type: "success",
  209. });
  210. this.$refs.upload1.clearFiles();
  211. this.selectByIdFn(this.caseId);
  212. },
  213. beforeUpload3(file, fileList) {
  214. this.fileList3 = fileList;
  215. this.filedata3.officeFlag = 1;
  216. },
  217. // 文件上传成功
  218. handlSuccess3(res, file) {
  219. this.$message({
  220. message: "上传成功",
  221. type: "success",
  222. });
  223. this.$refs.upload3.clearFiles();
  224. this.selectByIdFn(this.caseId);
  225. },
  226. preview(item, flag) {
  227. if (item.onlyOfficeFileId) {
  228. this.$router.push({
  229. path: "/onlyoffice",
  230. query: { id: item.onlyOfficeFileId, flag: flag },
  231. });
  232. } else {
  233. window.open(
  234. "https://api.xayunmei.com/tiaojieapi" + item.annexPath,
  235. "_black"
  236. );
  237. }
  238. },
  239. // 点击提交修改后的内容
  240. updataClick() {
  241. this.contentValue = this.contentValue.replace(/<br>/g, "");
  242. htmlToPDF({
  243. caseId: this.caseId,
  244. htmlContent: this.contentValue,
  245. }).then((res) => {
  246. if (res.code == 200) {
  247. this.$message({
  248. message: "提交修改成功",
  249. type: "success",
  250. });
  251. } else {
  252. this.$message({
  253. message: res.msg,
  254. type: "error",
  255. });
  256. }
  257. });
  258. },
  259. onEditorBlur(quill) {
  260. console.log("editor blur!", this.content, quill);
  261. },
  262. onEditorFocus(quill) {
  263. if (!this.updataFlag) {
  264. quill.enable(false);
  265. } else {
  266. quill.enable(true);
  267. }
  268. console.log("editor focus!", quill);
  269. },
  270. onEditorReady(quill) {
  271. console.log("editor ready!", quill);
  272. },
  273. onEditorChange({ quill, html, text }) {
  274. console.log("editor change!", quill, html, text);
  275. this.content = html;
  276. },
  277. // 点击显示修改的文本框
  278. txtContent() {
  279. this.textVisible = true;
  280. setInterval(() => {
  281. this.selectByIdFn(this.caseId);
  282. }, 8000);
  283. this.selectRoleMenuByCaseIdFn(this.caseId);
  284. this.getMenuPermsByUserFn();
  285. },
  286. /** 获取证据列表 */
  287. selectByIdFn(id) {
  288. selectById(id).then((res) => {
  289. this.applicantFile = [];
  290. this.resFile = [];
  291. this.mediateFile = [];
  292. let fileList = res.data.caseAttachList;
  293. fileList.forEach((item) => {
  294. if (item.annexType == 2) {
  295. this.applicantFile.push(item);
  296. } else if (item.annexType == 12) {
  297. this.resFile.push(item);
  298. } else if (item.annexType == 7) {
  299. this.mediateFile.push(item)
  300. }
  301. });
  302. });
  303. },
  304. // 鼠标滑过显示操作栏
  305. mouseHover() {
  306. this.showFlag = true;
  307. setTimeout(() => {
  308. this.showFlag = false;
  309. }, 4000);
  310. },
  311. async exitRoom() {
  312. // // 关闭识别
  313. Object.keys(this.asrList).forEach((key) => {
  314. this.asrList[key].stop();
  315. });
  316. this.localStreamAsr.stop();
  317. await trtc.exitRoom();
  318. await trtc.updateLocalVideo({ publish: false });
  319. await trtc.updateLocalAudio({ publish: false });
  320. await trtc.destroy();
  321. this.$router.push({
  322. name: "Home",
  323. });
  324. },
  325. // 获取拉流信息
  326. getPushVideo() {
  327. trtc.on(TRTC.EVENT.REMOTE_VIDEO_AVAILABLE, (event) => {
  328. const userId = event.userId;
  329. const streamType = event.streamType;
  330. this.userList.push(userId);
  331. setTimeout(() => {
  332. let aoido = trtc.getAudioTrack(userId);
  333. this.asrList[this.userList[this.userList.length - 1]] = new ASR({
  334. secretKey: "INDrIXcT8YmomZBcsy0oNirnU0LTN4X7",
  335. secretId: "AKID3xfHgroY4MQHvLXUXMwIQL1UjmbBX1Tv",
  336. appId: 1304001529,
  337. engine_model_type: "16k_zh",
  338. voice_format: 1,
  339. needvad: 1,
  340. audioTrack: aoido,
  341. });
  342. this.asrList[this.userList[this.userList.length - 1]].start();
  343. // 开始识别
  344. this.asrList[
  345. this.userList[this.userList.length - 1]
  346. ].OnRecognitionStart = (res) => {
  347. console.log("远端流:开始识别", res);
  348. };
  349. this.asrList[this.userList[this.userList.length - 1]].OnError = (
  350. res
  351. ) => {
  352. console.log("远端流:识别失败", res);
  353. };
  354. // 一句话结束
  355. this.asrList[this.userList[this.userList.length - 1]].OnSentenceEnd =
  356. (res) => {
  357. console.log("远端流:一句话结束", res);
  358. this.contentValue =
  359. this.contentValue +
  360. `<h2>${userId}</h2>` +
  361. `<span>${res.result.voice_text_str}</span>`;
  362. };
  363. }, 2000);
  364. if (this.modileFlag) {
  365. this.userClass = getWidth(this.userList);
  366. this.userHeight = getHeight(this.userList);
  367. } else {
  368. this.userClassPhone = getWidthPhone(this.userList);
  369. }
  370. setTimeout(() => {
  371. trtc.startRemoteVideo({ userId, streamType, view: `${userId}` });
  372. });
  373. // })
  374. });
  375. },
  376. // 删除退出会议人员列表
  377. deletePushVideo() {
  378. trtc.on(TRTC.EVENT.REMOTE_VIDEO_UNAVAILABLE, (event) => {
  379. const userId = event.userId;
  380. if (this.hostId == userId) {
  381. alert("主持人已经解散会议");
  382. this.$router.push({
  383. name: "Home",
  384. });
  385. this.userList = [];
  386. return;
  387. }
  388. let deleteIndex = this.userList.indexOf(userId);
  389. this.userList = this.userList.filter((item) => item !== userId);
  390. if (deleteIndex !== -1) {
  391. this.userList.splice(deleteIndex, 1);
  392. }
  393. if (this.modileFlag) {
  394. this.userClass = getWidth(this.userList);
  395. this.userHeight = getHeight(this.userList);
  396. } else {
  397. this.userClassPhone = getWidthPhone(this.userList);
  398. }
  399. Object.keys(this.asrList).forEach((key) => {
  400. this.asrList[userId].stop();
  401. });
  402. });
  403. },
  404. // 根据caseId查询房间相关信息
  405. reserveConferenceListFn(data) {
  406. reserveConferenceList(data).then((res) => {
  407. this.hostId = res.data[0].userName;
  408. });
  409. },
  410. // 根据userid查询是否是秘书角色
  411. secretaryRoleByUserIdFn(data, data1) {
  412. secretaryRoleByUserId(data, data1).then((res) => {
  413. this.updataFlag = res.data.isSecretaryRole;
  414. });
  415. },
  416. /**获取申请人被申请人权限 */
  417. selectRoleMenuByCaseIdFn(caseId) {
  418. selectRoleMenuByCaseId(caseId).then((res) => {
  419. if (res.appFlag && res.appFlag == "1") {
  420. this.appFlag = true;
  421. this.resFlag = false;
  422. } else if (res.resFlag && res.resFlag == "1") {
  423. this.resFlag = true;
  424. this.appFlag = false;
  425. }
  426. });
  427. },
  428. },
  429. computed: {
  430. editor() {
  431. return this.$refs.myQuillEditor.quill;
  432. },
  433. },
  434. async mounted() {
  435. // 判断设备类型
  436. this.modileFlag = getModile();
  437. let roomId = this.$route.query.roomId;
  438. this.roomId = this.$route.query.roomId;
  439. let userId = this.$route.query.userId;
  440. this.userId = this.$route.query.userId;
  441. this.caseId = this.$route.query.caseId;
  442. this.id = this.$route.query.id;
  443. this.token = this.$route.query.token;
  444. this.headers.Authorization = "Bearer " + this.token;
  445. this.headers1.Authorization = "Bearer " + this.token;
  446. this.headers3.Authorization = "Bearer " + this.token;
  447. this.filedata.caseId = this.caseId;
  448. this.filedata1.caseId = this.caseId;
  449. this.filedata3.caseId = this.caseId;
  450. window.sessionStorage.setItem("token", this.token);
  451. window.sessionStorage.setItem("userId", this.id);
  452. this.secretaryRoleByUserIdFn(this.id, this.caseId);
  453. // 获取主持人的userId
  454. this.reserveConferenceListFn(this.caseId);
  455. const sdkAppId = 1600011167;
  456. // 获取usersign
  457. await getUsersig(userId).then((res) => {
  458. this.userSign = res.msg;
  459. });
  460. this.getPushVideo();
  461. this.deletePushVideo();
  462. try {
  463. await trtc.enterRoom({
  464. roomId: Number(roomId),
  465. scene: "rtc",
  466. sdkAppId,
  467. userId,
  468. userSig: this.userSign,
  469. });
  470. await trtc.startLocalVideo({
  471. view: document.getElementById("localStream"), // 在 DOM 中的 elementId 为 localStream 的标签上预览视频。
  472. });
  473. await trtc.startLocalAudio();
  474. console.log("进房成功");
  475. this.$message({
  476. message: "进房成功",
  477. type: "success",
  478. });
  479. } catch (error) {
  480. console.error("进房失败 " + error);
  481. this.$message({
  482. message: "进房失败",
  483. type: "error",
  484. });
  485. // this.$router.push({
  486. // name: 'Home'
  487. // })
  488. }
  489. this.localStreamAsr = new ASR({
  490. secretKey: "INDrIXcT8YmomZBcsy0oNirnU0LTN4X7",
  491. secretId: "AKID3xfHgroY4MQHvLXUXMwIQL1UjmbBX1Tv",
  492. appId: 1304001529,
  493. engine_model_type: "16k_zh",
  494. voice_format: 1,
  495. needvad: 1,
  496. audioTrack: trtc.getAudioTrack(),
  497. });
  498. this.localStreamAsr.start();
  499. // 开始识别
  500. this.localStreamAsr.OnRecognitionStart = (res) => {
  501. console.log("本地流:开始识别", res);
  502. };
  503. this.localStreamAsr.OnError = (res) => {
  504. console.log("本地流:识别失败", res);
  505. };
  506. // 一句话结束
  507. this.localStreamAsr.OnSentenceEnd = (res) => {
  508. console.log("本地流:一句话结束", res);
  509. // this.contentValue = `<h2>${this.userId}</h2>` + `<span>${res.result.voice_text_str}</span>`;
  510. this.contentValue =
  511. this.contentValue +
  512. `<h2>${this.userId}</h2>` +
  513. `<span>${res.result.voice_text_str}</span>`;
  514. };
  515. },
  516. created() {
  517. trtc = TRTC.create();
  518. },
  519. };
  520. </script>
  521. <style scoped>
  522. .roompage {
  523. width: 100%;
  524. height: 100vh;
  525. display: flex;
  526. align-items: center;
  527. justify-content: space-around;
  528. flex-wrap: wrap;
  529. position: relative;
  530. }
  531. .txtContent {
  532. width: 100px;
  533. height: 50px;
  534. position: absolute;
  535. right: 5px;
  536. top: 20px;
  537. z-index: 10;
  538. cursor: pointer;
  539. }
  540. .userVideo,
  541. .userVideo1,
  542. .userVideo2,
  543. .userVideo3,
  544. .userVideo4,
  545. .userVideo5 {
  546. position: relative;
  547. }
  548. .userVideo5 {
  549. width: 100%;
  550. }
  551. .userVideo {
  552. width: 48%;
  553. }
  554. .userVideo1 {
  555. width: 33%;
  556. }
  557. .userVideo2 {
  558. width: 33%;
  559. }
  560. .userVideo3 {
  561. width: 33%;
  562. }
  563. .userVideo4 {
  564. width: 100%;
  565. }
  566. .footer {
  567. width: 100%;
  568. height: 10%;
  569. background: rgb(46 43 43 / 90%);
  570. position: absolute;
  571. bottom: 0;
  572. z-index: 10;
  573. }
  574. .userName {
  575. border-radius: 10px;
  576. width: 90px;
  577. height: 40px;
  578. text-align: center;
  579. line-height: 40px;
  580. background-color: black;
  581. position: absolute;
  582. right: 0;
  583. bottom: 0;
  584. z-index: 9;
  585. color: #ffffff;
  586. }
  587. .userNamePhone {
  588. border-radius: 10px;
  589. min-width: 20%;
  590. height: 30px;
  591. line-height: 30px;
  592. text-align: center;
  593. background-color: black;
  594. font-size: 12px;
  595. position: absolute;
  596. right: 0;
  597. bottom: 0;
  598. z-index: 9;
  599. color: #ffffff;
  600. }
  601. .header {
  602. width: 100%;
  603. height: 8%;
  604. background-color: #716c6c;
  605. display: flex;
  606. flex-direction: row-reverse;
  607. align-items: center;
  608. }
  609. .roomPhone {
  610. width: 100%;
  611. height: 100vh;
  612. }
  613. .bodyVideo {
  614. width: 100%;
  615. height: 92%;
  616. display: flex;
  617. flex-wrap: wrap;
  618. /* justify-content: space-between; */
  619. }
  620. .userVideoPhone {
  621. width: 100%;
  622. height: 100%;
  623. background-color: yellow;
  624. position: relative;
  625. }
  626. .userVideoPhone1 {
  627. width: 50%;
  628. flex-basis: calc(33.333% - 3px);
  629. max-width: calc(33.333% - 3px);
  630. height: 200px;
  631. /* height: 0; */
  632. /* padding-bottom: calc(33.333% - 3px); */
  633. margin-bottom: 3px;
  634. position: relative;
  635. }
  636. .quill-editor {
  637. height: 70%;
  638. }
  639. .updataBtn {
  640. width: 100%;
  641. position: absolute;
  642. bottom: 8px;
  643. }
  644. .fileList {
  645. /* max-height: 300px; */
  646. /* overflow-y: scroll; */
  647. }
  648. .list {
  649. display: flex;
  650. flex-wrap: wrap;
  651. }
  652. .applicant,
  653. .res,
  654. .mediate {
  655. width: 100%;
  656. margin-left: 20px;
  657. margin-bottom: 10px;
  658. color: #38393b;
  659. }
  660. </style>