realTime.vue 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393
  1. <!-- -->
  2. <template>
  3. <div class="realTime_box">
  4. <!-- 搜索 -->
  5. <el-row>
  6. <el-col>
  7. <!-- 组织搜索 -->
  8. <div class="search_box">
  9. <el-input
  10. size="mini"
  11. placeholder="请输入用户姓名"
  12. v-model.trim="nameVal"
  13. @change="searchData"
  14. clearable
  15. >
  16. </el-input>
  17. <el-input
  18. size="mini"
  19. placeholder="请输入手机号"
  20. v-model.trim="phoneVal"
  21. @change="searchData"
  22. clearable
  23. >
  24. </el-input>
  25. <div class="btn_box">
  26. <el-button type="info" size="mini" @click="searchData"
  27. >搜索</el-button
  28. >
  29. </div>
  30. </div>
  31. </el-col>
  32. </el-row>
  33. <el-card style="margin-top: 15px">
  34. <div class="card_box" style="margin-top: 15px">
  35. <!-- 组织 -->
  36. <div
  37. class="userManger_left"
  38. :style="'height:' + 26 * 25 + 'px;' + 'overflow-y: auto'"
  39. >
  40. <el-tree
  41. :data="data"
  42. :props="defaultProps"
  43. v-loading="loading2"
  44. @node-click="handleNodeClick"
  45. ></el-tree>
  46. </div>
  47. <!-- 表格 -->
  48. <div class="userManger_right">
  49. <el-table
  50. v-loading="loading"
  51. :data="tableData"
  52. stripe
  53. :height="48 * 13"
  54. style="width: 100%; overflow-y: auto"
  55. >
  56. <el-table-column prop="ind" label="序号" width="180">
  57. <template slot-scope="scope">
  58. <span>{{ (page - 1) * 20 + scope.row.ind }}</span>
  59. </template>
  60. </el-table-column>
  61. <el-table-column prop="real_name" label="组织成员" width="330">
  62. </el-table-column>
  63. <el-table-column prop="mobile" label="手机号" width="250">
  64. <template slot-scope="scope">
  65. <span>{{ scope.row.mobile || "无" }}</span>
  66. </template>
  67. </el-table-column>
  68. <el-table-column
  69. :show-overflow-tooltip="true"
  70. prop="address"
  71. label="职位"
  72. width="320"
  73. >
  74. <template slot-scope="scope">
  75. <span v-for="(item, index) in scope.row.org_list" :key="index"
  76. >{{ item.org_name }}
  77. <span v-if="index !== scope.row.org_list.length - 1">、</span>
  78. </span>
  79. </template>
  80. </el-table-column>
  81. <el-table-column label="操作" width="200" fixed="right">
  82. <template slot-scope="scope">
  83. <span
  84. style="
  85. height: 30px;
  86. display: inline-block;
  87. margin: 0 0 0 0;
  88. position: absolute;
  89. left: 0;
  90. top: 11px;
  91. "
  92. >
  93. <el-badge
  94. v-if="scope.row.unread"
  95. is-dot
  96. class="item"
  97. >
  98. <img
  99. @click="msgAxios(scope.row)"
  100. src="../../assets/images/realTime/xiaoxi.png"
  101. alt=""
  102. />
  103. </el-badge>
  104. <img
  105. v-else
  106. @click="msgAxios(scope.row)"
  107. src="../../assets/images/realTime/xiaoxi.png"
  108. alt=""
  109. />
  110. </span>
  111. <span style="position: absolute; top: 12px; left: 60px">
  112. <img
  113. @click="videoAxios(scope.row)"
  114. src="../../assets/images/realTime/shipin.png"
  115. alt=""
  116. />
  117. </span>
  118. </template>
  119. </el-table-column>
  120. </el-table>
  121. <!-- 分页 -->
  122. <el-pagination
  123. v-if="tableData.length !== 0"
  124. @current-change="changeList"
  125. background
  126. layout="prev, pager, next, jumper"
  127. :current-page="page"
  128. :total="pageSum"
  129. :page-size="20"
  130. >
  131. </el-pagination>
  132. </div>
  133. </div>
  134. </el-card>
  135. <!-- 视频通话 -->
  136. <el-dialog
  137. :close-on-click-modal="false"
  138. :close-on-press-escape="false"
  139. :before-close="handleClose"
  140. :modal="false"
  141. v-dialogDrag
  142. :fullscreen="zoomShow"
  143. :title="videoTle"
  144. :visible.sync="videoVisible"
  145. width="600px"
  146. >
  147. <!-- <div class="video_box"> -->
  148. <div :class="zoomShow == false ? 'video_box' : 'video_box-big'">
  149. <!-- 最大化、最小化 -->
  150. <div
  151. style="
  152. position: absolute;
  153. right: 42px;
  154. color: #9fa2a7;
  155. top: -28px;
  156. font-size: 13px;
  157. "
  158. >
  159. <!-- 最小化 -->
  160. <span
  161. style="font-size: 25px; cursor: pointer"
  162. class="iconfont icon-zuixiaohua"
  163. @click="zoomOperate('small')"
  164. ></span>
  165. <!-- 最大化 -->
  166. <span
  167. style="font-size: 25px; cursor: pointer"
  168. class="iconfont icon-zuidahua"
  169. @click="zoomOperate('big')"
  170. ></span>
  171. </div>
  172. <!-- 最大化、最小化 -->
  173. <div class="host_video" v-if="videoShow">
  174. <img
  175. src="../../assets/images/realTime/8.png"
  176. alt=""
  177. class="video_img"
  178. />
  179. <!-- <img
  180. v-if="audioShow"
  181. src="../../assets/images/realTime/8.png"
  182. alt=""
  183. class=""
  184. style="margin: 120px atuo;"
  185. />
  186. <img
  187. v-else
  188. src="../../assets/images/realTime/13.png"
  189. alt=""
  190. class=""
  191. /> -->
  192. </div>
  193. <div class="host_video" v-else>
  194. <!-- <video
  195. style="width: 100%; height: 100%"
  196. ref="localVideo"
  197. autoplay
  198. playsinline
  199. :muted="true"
  200. ></video> -->
  201. <!-- <video
  202. style="width: 100%; height: 100%"
  203. :ref="idName1"
  204. autoplay
  205. playsinline
  206. :muted="true"
  207. ></video> -->
  208. <video
  209. style="width: 100%; height: 100%"
  210. :ref="idName"
  211. autoplay
  212. playsinline
  213. :muted="true"
  214. ></video>
  215. </div>
  216. <!-- 副视频画面 -->
  217. <div v-if="audioShow" @click="switchWindow" class="deputy_video" v-drag>
  218. <!-- <video
  219. style="width: 100%; height: 100%"
  220. ref="remoteVideo"
  221. autoplay
  222. playsinline
  223. :muted="true"
  224. ></video> -->
  225. <!-- <video
  226. style="width: 100%; height: 100%"
  227. :ref="idName"
  228. autoplay
  229. playsinline
  230. :muted="true"
  231. ></video> -->
  232. <video
  233. style="width: 100%; height: 100%"
  234. :ref="idName1"
  235. autoplay
  236. playsinline
  237. :muted="true"
  238. ></video>
  239. </div>
  240. <!-- 操作 -->
  241. <div class="operate">
  242. <!-- <div style="margin: 0 10px 0 0">
  243. <img
  244. v-if="microphone"
  245. src="../../assets/images/realTime/10.png"
  246. alt=""
  247. />
  248. <img v-else src="../../assets/images/realTime/9.png" alt="" />
  249. </div> -->
  250. <div v-if="audioShow" style="margin: 0 10px 0 0">
  251. <img
  252. v-if="camera"
  253. @click="notPlugFlow"
  254. src="../../assets/images/realTime/12.png"
  255. alt=""
  256. />
  257. <img
  258. @click="openVideo"
  259. v-else
  260. src="../../assets/images/realTime/11.png"
  261. alt=""
  262. />
  263. </div>
  264. <el-button
  265. type="danger"
  266. style="padding: 2px 10px; margin: -2px 10px 0 0"
  267. @click="notLogin"
  268. size="mini"
  269. >挂断</el-button
  270. >
  271. </div>
  272. </div>
  273. </el-dialog>
  274. <!-- 文本聊天框 -->
  275. <el-dialog
  276. custom-class="msgFrame"
  277. :title="tltData"
  278. v-dialogDrag
  279. :close-on-click-modal="false"
  280. :modal="false"
  281. :close-on-press-escape="false"
  282. :visible.sync="dialogVisible"
  283. width="500px"
  284. >
  285. <!-- 聊天内容 -->
  286. <ul class="ul_list test-5" id="msgBox">
  287. <!-- 查看更多 -->
  288. <!-- <li class="">
  289. <div
  290. v-if="iconShow"
  291. style="color: #0400ff; cursor: pointer; text-align: center"
  292. @click="moreMsg"
  293. >
  294. 查看更多消息
  295. </div>
  296. </li> -->
  297. <li class="list" v-for="(item, index) in msgList.msg_list" :key="index">
  298. <!-- me -->
  299. <div class="list_msgBox1" v-if="item.is_right == true">
  300. <div
  301. style="
  302. background: #60fba5;
  303. font-weight: 550;
  304. color: #000;
  305. padding: 12px 15px 0 15px;
  306. line-height: 25px;
  307. border-radius: 8px;
  308. "
  309. >
  310. {{ item.msg_info }}
  311. </div>
  312. <div
  313. style="
  314. width: 0;
  315. height: 0;
  316. border-top: 6px solid transparent;
  317. border-left: 10px solid #60fba5;
  318. border-bottom: 5px solid transparent;
  319. margin: 23px 0 0 0;
  320. "
  321. ></div>
  322. <div
  323. style="
  324. width: 45px;
  325. height: 45px;
  326. border-radius: 10px;
  327. line-height: 45px;
  328. text-align: center;
  329. font-size: 16px;
  330. background-image: linear-gradient(#a7fbc6, #60fba5);
  331. color: #fff;
  332. "
  333. >
  334. </div>
  335. </div>
  336. <!-- you -->
  337. <div v-else-if="item.is_right == false" class="list_msgBox2">
  338. <div
  339. style="
  340. width: 45px;
  341. height: 45px;
  342. border-radius: 10px;
  343. line-height: 45px;
  344. text-align: center;
  345. font-size: 16px;
  346. background-image: linear-gradient(#addbff, #5cacff);
  347. color: #fff;
  348. "
  349. >
  350. {{ getUserObj.real_name.slice(0, 1) }}
  351. </div>
  352. <div
  353. style="
  354. width: 0;
  355. height: 0;
  356. border-top: 5px solid transparent;
  357. border-right: 10px solid #eae9eb;
  358. border-bottom: 6px solid transparent;
  359. margin: 24px 0 0 0;
  360. "
  361. ></div>
  362. <div
  363. style="
  364. background: #eae9eb;
  365. padding: 12px 15px 0 15px;
  366. line-height: 25px;
  367. border-radius: 8px;
  368. font-weight: 550;
  369. color: #000;
  370. "
  371. >
  372. {{ item.msg_info }}
  373. </div>
  374. </div>
  375. </li>
  376. </ul>
  377. <!-- 发送框 -->
  378. <el-input
  379. style="border: 0"
  380. resize="none"
  381. type="textarea"
  382. v-model="input"
  383. @keyup.enter.native="submit()"
  384. @keydown.native="pushKeyword($event)"
  385. maxlength="30"
  386. show-word-limit
  387. placeholder="按下enter按键发送消息"
  388. ></el-input>
  389. </el-dialog>
  390. </div>
  391. </template>
  392. <script>
  393. import { color } from "highcharts";
  394. import { ZegoExpressEngine } from "zego-express-engine-webrtc";
  395. export default {
  396. //import引入的组件需要注入到对象中才能使用
  397. components: {},
  398. data() {
  399. //这里存放数据
  400. return {
  401. fullHeight: document.documentElement.clientHeight - 116, //
  402. // 搜索
  403. nameVal: "",
  404. phoneVal: "",
  405. // 表格
  406. tableData: [],
  407. websockMsgList: [], // websocket传递来的数组数据
  408. loading: false, // 加载
  409. org_id: "", //组织id
  410. // 树形图
  411. data: [],
  412. loading2: false,
  413. defaultProps: {
  414. children: "childrens",
  415. label: "org_name",
  416. },
  417. // 分页
  418. pageSum: 0,
  419. page: 1,
  420. // 视频通话弹框
  421. idName: "remoteVideo", // ref 副视频
  422. idName1: "localVideo", // ref 主视频
  423. videoVisible: false,
  424. videoShow: true, //主视频是否展示画面
  425. audioShow: true, // 判断当前是语音通话还是视频通话 true为视频 false为语音
  426. microphone: true, // 是否打开麦克风
  427. camera: true, // 是否打开摄像头
  428. appID: 2672645646, //项目唯一标识 AppID
  429. // server: "1e8344b2a193220e5e96338ba53c3dcc", // 接入服务器地址Server
  430. videoUrl: "wss://webliveroom2672645646-api.imzego.com/ws", // 请求
  431. zg: null,
  432. // UserID: "sample" + Math.floor(Math.random() * 10000000000000).toString(),
  433. UserID: "user00002",
  434. // UserID: "168",
  435. // StreamID: "web-4796754531236",
  436. StreamID: "web-" + Math.floor(Math.random() * 10000000000000).toString(),
  437. localStream: null,
  438. Token: "",
  439. RoomID: "",
  440. videoTle: "正在和云飞-卢万里视频通话",
  441. timer: null, // 接听
  442. // 文本聊天框
  443. tltData: "",
  444. dialogVisible: false,
  445. input: "", //发送框
  446. iconShow: true,
  447. // 文本消息功能
  448. url:
  449. // "ws://192.168.1.17:12345/api/api_gateway?method=control_center.real_time.im_message",
  450. // "ws" +
  451. // "https" +
  452. this.$wsUrl +
  453. "/api/api_gateway?method=control_center.real_time.im_message",
  454. websock: null,
  455. getUserObj: {}, // 获取到当前点击的行数据
  456. msgList: [], //当前点击的账号消息列表
  457. userName: "",
  458. // 心跳、重连机制
  459. ws: null, //建立的连接
  460. lockReconnect: false, //是否真正建立连接
  461. timeout: 5 * 1000, //30秒一次心跳
  462. timeoutObj: null, //心跳心跳倒计时
  463. serverTimeoutObj: null, //心跳倒计时
  464. timeoutnum: null, //断开 重连倒计时
  465. // 视频通话窗口缩放
  466. zoomShow: false, // 默认最小化
  467. };
  468. },
  469. //监听属性 类似于data概念
  470. computed: {},
  471. //监控data中的数据变化
  472. watch: {
  473. fullHeight(val) {
  474. //监控浏览器高度变化
  475. if (!this.timer) {
  476. this.fullHeight = val;
  477. this.timer = true;
  478. let that = this;
  479. setTimeout(function () {
  480. //防止过度调用监测事件,导致卡顿
  481. that.timer = false;
  482. }, 400);
  483. }
  484. },
  485. // 聊天列表
  486. msgList(val) {
  487. // 实现打开聊天框后滚动条定位到最下方
  488. this.$nextTick(() => {
  489. var div = document.getElementById("msgBox");
  490. div.scrollTop = div.scrollHeight;
  491. });
  492. },
  493. // 聊天框
  494. dialogVisible(val) {
  495. if (val == false) {
  496. this.msgList = [];
  497. }
  498. },
  499. // 音视频弹框
  500. videoVisible(val) {
  501. if (val == false) {
  502. this.notPlugFlow(); // 停止推流
  503. this.notTensile(); // 停止拉流
  504. this.notLogin(); //退出房间
  505. }
  506. },
  507. // 给表格文本消息增加提示,数据从webscoket中获取
  508. tableData(val) {
  509. var that = this
  510. for (var i = 0; i < val.length; i++) {
  511. let sdhkj = val[i];
  512. for (var k = 0; k < this.websockMsgList.length; k++) {
  513. let sdjaks = this.websockMsgList[k];
  514. if (sdhkj.real_name == sdjaks.real_name && sdjaks.unread !== 0 && sdjaks.unread) {
  515. this.tableData[i]['unread'] = sdjaks.unread
  516. }
  517. }
  518. }
  519. },
  520. deep: true,
  521. // websocket列表变化
  522. websockMsgList(val) {
  523. // var that = this
  524. // for (var i = 0; i < this.tableData.length; i++) {
  525. // let sdhkj = this.tableData[i];
  526. // for (var k = 0; k < val.length; k++) {
  527. // let sdjaks = val[k];
  528. // if (sdhkj.real_name == sdjaks.real_name && sdjaks.unread !== 0 && sdjaks.unread) {
  529. // this.tableData[i]['unread'] = sdjaks.unread
  530. // }
  531. // }
  532. // }
  533. this.userListData(); // 获取右侧用户列表
  534. },
  535. },
  536. //方法集合
  537. methods: {
  538. //动态获取浏览器高度
  539. get_boderHeight() {
  540. const that = this;
  541. window.onresize = () => {
  542. return (() => {
  543. window.fullHeight = document.documentElement.clientHeight;
  544. that.fullHeight = window.fullHeight;
  545. })();
  546. };
  547. },
  548. // 树形图
  549. handleNodeClick(data) {
  550. console.log(data);
  551. this.org_id = data.id;
  552. this.userListData();
  553. },
  554. // 搜索
  555. searchData() {
  556. this.page = 1;
  557. this.userListData();
  558. },
  559. // 分页
  560. changeList(page) {
  561. this.page = page;
  562. this.loading = true;
  563. this.tableData = [];
  564. this.userListData();
  565. },
  566. // 获取左侧组织列表
  567. organizationData() {
  568. this.$axios({
  569. method: "POST",
  570. url: "/api/api_gateway?method=sysmenage.usermanager.org_list",
  571. data: this.qs.stringify({
  572. page: this.page,
  573. page_item: "100000000",
  574. org_name: "",
  575. }),
  576. })
  577. .then((res) => {
  578. if (res.data.data.page_list.length !== 0) {
  579. var obj = {
  580. org_name: "全部",
  581. id: "",
  582. };
  583. var data = res.data.data.page_list;
  584. this.data = [obj, ...data]; // 左侧组织列表
  585. }
  586. this.loading2 = false;
  587. })
  588. .catch((err) => {
  589. this.loading2 = false;
  590. });
  591. },
  592. // 获取右侧用户列表
  593. userListData() {
  594. this.$axios({
  595. method: "POST",
  596. url: "/api/api_gateway?method=sysmenage.usermanager.user_list",
  597. data: this.qs.stringify({
  598. page: this.page,
  599. page_item: "20",
  600. real_name: this.nameVal, //用户名称
  601. mobile: this.phoneVal, // 电话
  602. org_id: this.org_id,
  603. }),
  604. })
  605. .then((res) => {
  606. if (res.data.data.total_item !== 0) {
  607. var data = res.data.data.page_list;
  608. var list = [];
  609. data.forEach((item, index) => {
  610. item.ind = index + 1;
  611. list.push(item);
  612. });
  613. this.tableData = list;
  614. this.pageSum = res.data.data.total_item;
  615. this.loading = false;
  616. }
  617. })
  618. .catch((err) => {
  619. this.loading = false;
  620. });
  621. },
  622. // 文本消息
  623. msgAxios(data) {
  624. this.getUserObj = data;
  625. this.redtf = data
  626. this.userName = localStorage.getItem("usernme");
  627. console.log(this.userName);
  628. this.tltData = "与" + data.real_name + "的对话";
  629. var obj = {};
  630. obj = {
  631. action: "list",
  632. recv_user_id: data.user_id,
  633. data: {
  634. msg_status: false,
  635. msg_info: "",
  636. },
  637. };
  638. this.websock.send(JSON.stringify(obj)); // 获取聊天记录
  639. // var readObj = {};
  640. // readObj = {
  641. // action: "read",
  642. // send_user_id: data.user_id,
  643. // data: {
  644. // msg_status: false,
  645. // msg_info: "",
  646. // },
  647. // };
  648. // this.websock.send(JSON.stringify(readObj)); // 消息已读
  649. this.dialogVisible = true;
  650. },
  651. // 视频消息
  652. videoAxios(data) {
  653. this.getUserObj = data;
  654. this.userName = localStorage.getItem("username");
  655. this.videoTle = "正在和" + data.real_name + "视频通话";
  656. this.UserID = localStorage.getItem("userID");
  657. // 先获取当前用户的房间号和登录所需的Token
  658. var obj = {};
  659. obj = {
  660. action: "send_video",
  661. recv_user_id: data.user_id,
  662. data: {
  663. msg_status: false,
  664. msg_info: "",
  665. },
  666. };
  667. this.websock.send(JSON.stringify(obj));
  668. // // 登录房间
  669. // this.zg.loginRoom(
  670. // this.RoomID,
  671. // this.Token,
  672. // { userID: this.UserID, userName: this.UserID },
  673. // { userUpdate: true }
  674. // ).then((result) => {
  675. // if (result == true) {
  676. // this.plugFlow(); //推流
  677. // this.videoVisible = true
  678. // }
  679. // });
  680. },
  681. // 打开视频画面
  682. openVideo() {
  683. this.plugFlow();
  684. },
  685. // 音视频推流
  686. async plugFlow() {
  687. this.videoShow = false;
  688. this.camera = true;
  689. const localStream = await this.zg.createStream();
  690. // stream 为 MediaStream 对象,开发者可通过赋值给 video 或 audio 的 srcObject 属性进行渲染
  691. this.$refs["localVideo"].srcObject = localStream;
  692. this.localStream = localStream;
  693. // localStream 为创建流获取的 MediaStream 对象
  694. this.zg.startPublishingStream(this.StreamID, localStream);
  695. },
  696. // 音视频停止推流
  697. notPlugFlow() {
  698. this.videoShow = true;
  699. this.camera = false;
  700. this.zg.stopPublishingStream(this.StreamID);
  701. this.zg.destroyStream(this.localStream);
  702. },
  703. // 拉流
  704. async tensile(streamID) {
  705. const remoteStream = await this.zg.startPlayingStream(streamID);
  706. // remoteVideo 为本地 <video> 或 <audio> 对象
  707. this.$refs["remoteVideo"].srcObject = remoteStream;
  708. },
  709. notTensile(streamID) {
  710. this.zg.stopPlayingStream(streamID);
  711. },
  712. // 切换视频窗口
  713. switchWindow() {
  714. // 副视频画面 remoteVideo
  715. // 主视频画面 localVideo
  716. this.idName = this.idName == "remoteVideo" ? "localVideo" : "remoteVideo";
  717. this.idName =
  718. this.idName1 == "remoteVideo" ? "localVideo" : "remoteVideo";
  719. },
  720. // 退出房间
  721. notLogin() {
  722. clearTimeout(this.timer);
  723. this.zg.logoutRoom(this.RoomID);
  724. this.videoVisible = false;
  725. },
  726. // 当音视频通话关闭时的回调
  727. handleClose(done) {
  728. var that = this;
  729. this.$confirm("目前正在音视频通话中,确认关闭?")
  730. .then((_) => {
  731. done();
  732. that.notLogin(); // 退出房间
  733. })
  734. .catch((_) => {});
  735. },
  736. // 发送消息
  737. async submit() {
  738. if (this.input.split(" ").join("").length == 0) {
  739. if (document.getElementsByClassName("el-message").length == 0) {
  740. this.$message({
  741. message: "不能发送空白消息!",
  742. type: "warning",
  743. duration: 1500,
  744. });
  745. }
  746. this.input = "";
  747. } else {
  748. try {
  749. var obj = {};
  750. obj = {
  751. action: "send",
  752. recv_user_id: this.getUserObj.user_id,
  753. data: {
  754. msg_status: false,
  755. msg_info: this.input,
  756. },
  757. };
  758. var v = JSON.stringify(obj);
  759. this.websock.send(v);
  760. this.input = "";
  761. } catch (error) {
  762. console.log(">>> sendMsg, error: ", error);
  763. }
  764. }
  765. },
  766. // 取消回车换行行为
  767. pushKeyword(event) {
  768. if (event.keyCode === 13) {
  769. event.preventDefault(); // 阻止浏览器默认换行操作
  770. this.keyword = "";
  771. return false;
  772. }
  773. },
  774. // 更多消息
  775. moreMsg() {},
  776. // 文本消息功能初始化
  777. async msgInit() {
  778. var that = this;
  779. if (typeof WebSocket === "undefined") {
  780. alert("您的浏览器不支持socket!");
  781. } else {
  782. this.websock = new window.WebSocket(
  783. this.url + "&token=" + localStorage.getItem("session")
  784. );
  785. this.websock.onopen = (event) => {
  786. console.log("WebSocket:已连接");
  787. console.log(event);
  788. // 发送消息 - 获取对话列表
  789. var obj1 = {};
  790. obj1 = {
  791. action: "list",
  792. recv_user_id: "",
  793. data: {
  794. msg_status: false,
  795. msg_info: "",
  796. },
  797. };
  798. this.websock.send(JSON.stringify(obj1));
  799. this.start(); //开启心跳
  800. };
  801. this.websock.onmessage = (event) => {
  802. var data = JSON.parse(event.data);
  803. console.log("WebSocket:消息---------------------------", data);
  804. if (data.action == "none") {
  805. // 获取聊天记录
  806. var datArr = data.data; // 总数据
  807. this.websockMsgList = datArr; // 把数据定义在data中
  808. if (datArr.length !== 0) {
  809. for (var i = 0; i < datArr.length; i++) {
  810. if (this.getUserObj.user_id == datArr[i].user_id) {
  811. data.data[i].msg_list = data.data[i].msg_list.reverse();
  812. that.msgList = data.data[i];
  813. console.log(data.data[i]);
  814. }
  815. }
  816. }
  817. // if (data.data[0] !== undefined) {
  818. // data.data[0].msg_list = data.data[0].msg_list.reverse();
  819. // this.msgList = data.data[0];
  820. // }
  821. } else if (data.action == "list") {
  822. // 返回list为发送消息成功后需要再次调用聊天列表
  823. var obj = {};
  824. obj = {
  825. action: "list",
  826. // recv_user_id: this.getUserObj.user_id,
  827. recv_user_id: "",
  828. data: {
  829. msg_status: false,
  830. msg_info: "",
  831. },
  832. };
  833. this.websock.send(JSON.stringify(obj));
  834. } else if (data.action == "recv_video") {
  835. // 获取当前点击用户的房间号以及登录房间所需的Token
  836. // console.log(JSON.parse(event.data));
  837. console.log(data);
  838. var data = JSON.parse(event.data);
  839. this.RoomID = data.data.room_id; // 房间号
  840. this.Token = data.data.room_token; // Token
  841. // 登录房间
  842. this.zg
  843. .loginRoom(
  844. this.RoomID,
  845. this.Token,
  846. { userID: this.UserID, userName: this.UserID },
  847. { userUpdate: true }
  848. )
  849. .then((result) => {
  850. if (result == true) {
  851. console.log("0000000000000000000000000000000000000登录成功");
  852. this.plugFlow(); //推流
  853. this.videoVisible = true;
  854. }
  855. });
  856. } else if (data.action == "ok") {
  857. // 心跳机制 - 当前状态是websocket连接没有问题
  858. this.start(); // 心跳机制
  859. }
  860. };
  861. this.websock.onerror = (event) => {
  862. console.log("WebSocket:发生错误 ");
  863. console.log(event);
  864. if (that.timeoutnum !== null) {
  865. setTimeout(() => {
  866. that.websock.close(); // 先关闭
  867. clearTimeout(that.timeoutObj); //清除时间
  868. that.reconnect(); // 重连
  869. }, 5000);
  870. }
  871. };
  872. this.websock.onclose = (event) => {
  873. console.log("WebSocket:已关闭");
  874. console.log(event);
  875. console.log(that.timeoutnum);
  876. setTimeout(() => {
  877. // that.websock.close(); // 先关闭
  878. clearTimeout(that.timeoutObj); //清除时间
  879. that.reconnect(); // 重连
  880. }, 5000);
  881. };
  882. }
  883. },
  884. // 音视频消息功能初始化
  885. videoInit() {
  886. this.zg = new ZegoExpressEngine(this.appID, this.videoUrl);
  887. this.soundOn(); // 监听房间
  888. this.detection(); // 检测是否兼容当前浏览器
  889. },
  890. // 监听房间
  891. soundOn() {
  892. // 房间状态更新回调
  893. this.zg.on(
  894. "roomStateUpdate",
  895. (roomID, state, errorCode, extendedData) => {
  896. if (state == "CONNECTED") {
  897. // 与房间连接成功,只有当房间状态是连接成功时,才能进行推流、拉流等操作。
  898. // 接下来的“预览并推流”的代码写在这里
  899. console.log("房间连接成功");
  900. //定时器
  901. this.timer = setTimeout(() => {
  902. if (document.getElementsByClassName("el-message").length == 0) {
  903. this.$message({
  904. message: "没人接听,请稍后重试",
  905. type: "warning",
  906. duration: 2500,
  907. });
  908. }
  909. this.videoVisible = false;
  910. // }, 5000);
  911. }, 30000);
  912. }
  913. if (state == "DISCONNECTED") {
  914. // 与房间断开了连接
  915. console.log("与房间断开连接");
  916. }
  917. if (state == "CONNECTING") {
  918. // 与房间尝试连接中
  919. console.log("与房间尝试连接中");
  920. }
  921. }
  922. );
  923. this.zg.on("roomUserUpdate", (roomID, updateType, userList) => {
  924. // 其他用户进出房间的通知
  925. console.log(updateType);
  926. console.log(userList);
  927. console.log("有其他用户进出房间");
  928. if (updateType == "DELETE") {
  929. if (document.getElementsByClassName("el-message").length == 0) {
  930. this.$message({
  931. message: "对方已挂断,结束通话!",
  932. type: "warning",
  933. duration: 1500,
  934. });
  935. }
  936. setTimeout(() => {
  937. this.notLogin(); // 退出房间
  938. }, 2000);
  939. } else if (updateType == "ADD") {
  940. clearTimeout(this.timer); // 关闭无人接听回调
  941. }
  942. });
  943. this.zg.on(
  944. "roomStreamUpdate",
  945. async (roomID, updateType, streamList, extendedData) => {
  946. console.log(roomID);
  947. console.log(updateType);
  948. console.log(streamList);
  949. console.log(extendedData);
  950. // 房间内其他用户音视频流变化的通知
  951. console.log("有其他用户开启或关闭音频");
  952. if (updateType == "ADD") {
  953. // 流新增,开始拉流
  954. this.tensile(streamList[0].streamID);
  955. } else if (updateType == "DELETE") {
  956. // 流删除,停止拉流
  957. this.notTensile(streamList[0].streamID);
  958. }
  959. }
  960. );
  961. this.zg.on("publisherStateUpdate", (result) => {
  962. // 推流状态更新回调
  963. console.log("推流状态更新");
  964. });
  965. this.zg.on("publishQualityUpdate", (streamID, stats) => {
  966. // 推流质量回调
  967. console.log("推流质量更新");
  968. });
  969. },
  970. // 检测是否兼容当前浏览器
  971. async detection() {
  972. const result = await this.zg.checkSystemRequirements();
  973. // 返回的 result 为兼容性检测结果。 webRTC 为 true 时表示支持 webRTC,其他属性含义可以参考接口 API 文档
  974. console.log(result);
  975. if (result.webRTC == true) {
  976. console.log("兼容");
  977. } else {
  978. console.log("不兼容");
  979. }
  980. },
  981. // 心跳机制
  982. start() {
  983. //开启心跳
  984. var self = this;
  985. self.timeoutObj && clearTimeout(self.timeoutObj);
  986. self.serverTimeoutObj && clearTimeout(self.serverTimeoutObj);
  987. self.timeoutObj = setTimeout(function () {
  988. //这里发送一个心跳,后端收到后,返回一个心跳消息,
  989. console.log("心跳中。。。", self.websock.readyState);
  990. if (self.websock.readyState == 1) {
  991. //如果连接正常
  992. var obj1 = {};
  993. obj1 = {
  994. action: "keepalive",
  995. recv_user_id: "",
  996. data: {
  997. msg_status: false,
  998. msg_info: "",
  999. },
  1000. };
  1001. self.websock.send(JSON.stringify(obj1));
  1002. } else {
  1003. //否则重连
  1004. self.reconnect();
  1005. }
  1006. self.serverTimeoutObj = setTimeout(function () {
  1007. //超时关闭
  1008. console.log("又进入了?");
  1009. self.websock.close();
  1010. self.reconnect(); // 重连
  1011. }, self.timeout);
  1012. }, self.timeout);
  1013. },
  1014. reset() {
  1015. //重置心跳
  1016. var that = this;
  1017. clearTimeout(that.timeoutObj); //清除时间
  1018. clearTimeout(that.serverTimeoutObj); //清除时间
  1019. that.start(); //重启心跳
  1020. },
  1021. // 重连机制
  1022. reconnect() {
  1023. var that = this;
  1024. console.log("进入重连了");
  1025. clearTimeout(that.timeoutObj); //清除时间
  1026. if (that.lockReconnect) {
  1027. return;
  1028. }
  1029. that.lockReconnect = true;
  1030. // 没连接上会一直重连,设置延迟避免请求过多
  1031. that.timeoutnum && clearTimeout(that.timeoutnum);
  1032. that.timeoutnum = setTimeout(function () {
  1033. that.msgInit(); //新连接
  1034. that.lockReconnect = false;
  1035. }, 5000);
  1036. },
  1037. // 视频通话窗口最小化、最大化
  1038. zoomOperate(ref) {
  1039. if (ref == "small") {
  1040. // 最小化
  1041. this.zoomShow = false;
  1042. } else if (ref == "big") {
  1043. // 最大化
  1044. this.zoomShow = true;
  1045. }
  1046. },
  1047. },
  1048. //生命周期 - 创建完成(可以访问当前this实例)
  1049. created() {
  1050. this.loading = true;
  1051. this.loading2 = true;
  1052. this.organizationData(); //获取左侧组织列表
  1053. this.userListData(); // 获取右侧用户列表
  1054. },
  1055. //生命周期 - 挂载完成(可以访问DOM元素)
  1056. mounted() {
  1057. // this.loading = true;
  1058. // this.loading2 = true;
  1059. // this.organizationData(); //获取左侧组织列表
  1060. // this.userListData(); // 获取右侧用户列表
  1061. // console.log("---------------------------");
  1062. // console.log(this.websock);
  1063. // console.log("---------------------------");
  1064. if (this.websock == null) {
  1065. this.msgInit(); // 文本消息功能初始化
  1066. }
  1067. this.videoInit(); // 视频消息功能初始化
  1068. },
  1069. beforeCreate() {}, //生命周期 - 创建之前
  1070. beforeMount() {}, //生命周期 - 挂载之前
  1071. beforeUpdate() {}, //生命周期 - 更新之前
  1072. updated() {}, //生命周期 - 更新之后
  1073. beforeDestroy() {
  1074. var that = this;
  1075. clearTimeout(that.timeoutnum); // 清除重连
  1076. clearTimeout(that.timeoutObj); // 清除心跳
  1077. that.websock.close(); // 关闭websocket
  1078. }, //生命周期 - 销毁之前
  1079. destroyed() {}, //生命周期 - 销毁完成
  1080. activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
  1081. };
  1082. </script>
  1083. <style lang="less" scoped>
  1084. .realTime_box {
  1085. // 搜索
  1086. .search_box {
  1087. display: flex;
  1088. /deep/.el-input {
  1089. width: 20%;
  1090. margin: 0 15px 0 0;
  1091. }
  1092. .btn_box {
  1093. display: flex;
  1094. justify-content: start;
  1095. }
  1096. }
  1097. .card_box {
  1098. display: flex;
  1099. width: 100%;
  1100. // 树形图
  1101. .userManger_left {
  1102. width: 19%;
  1103. margin: 0 15px 0 0;
  1104. padding: 5px;
  1105. border: 1px solid #eeeeee;
  1106. border-radius: 5px;
  1107. overflow: hidden;
  1108. overflow-y: auto;
  1109. }
  1110. // 搜索和表格
  1111. .userManger_right {
  1112. width: 80%;
  1113. // border: 1px solid red;
  1114. a {
  1115. text-decoration: none;
  1116. }
  1117. .reset {
  1118. color: #1890ff;
  1119. }
  1120. .delete {
  1121. color: #f56c6c;
  1122. }
  1123. .line {
  1124. display: inline-block;
  1125. width: 1px;
  1126. background: rgba(0, 0, 0, 0.09);
  1127. margin: 0 11px;
  1128. height: 14px;
  1129. }
  1130. }
  1131. }
  1132. /deep/.el-select {
  1133. width: 80%;
  1134. }
  1135. /deep/.el-cascader {
  1136. width: 80%;
  1137. }
  1138. // 视频通话 - 最小化
  1139. .video_box {
  1140. border: 1px solid #000;
  1141. background: #2c2c2c;
  1142. height: 325px;
  1143. position: relative;
  1144. // 主视频画面
  1145. .host_video {
  1146. height: 326px;
  1147. vertical-align: middle;
  1148. text-align: center;
  1149. position: relative;
  1150. .video_img {
  1151. margin: 120px auto;
  1152. position: absolute;
  1153. top: 0;
  1154. right: 0;
  1155. bottom: 0;
  1156. left: 0;
  1157. margin: auto;
  1158. }
  1159. }
  1160. // 副视频画面
  1161. .deputy_video {
  1162. width: 90px;
  1163. height: 90px;
  1164. position: absolute;
  1165. top: 0;
  1166. right: 0;
  1167. background: #fff;
  1168. }
  1169. // 操作
  1170. .operate {
  1171. height: 25px;
  1172. width: 100%;
  1173. background: #000;
  1174. padding: 5px 0 5px 0;
  1175. margin: -13px 0 0 0px;
  1176. position: absolute;
  1177. bottom: 0;
  1178. left: 0;
  1179. display: flex;
  1180. justify-content: flex-end;
  1181. }
  1182. }
  1183. // 视频通话 - 最大化
  1184. .video_box-big {
  1185. border: 1px solid #000;
  1186. background: #2c2c2c;
  1187. height: 100%;
  1188. position: relative;
  1189. // 主视频画面
  1190. .host_video {
  1191. height: 100%;
  1192. vertical-align: middle;
  1193. text-align: center;
  1194. position: relative;
  1195. .video_img {
  1196. margin: 120px auto;
  1197. position: absolute;
  1198. top: 0;
  1199. right: 0;
  1200. bottom: 0;
  1201. left: 0;
  1202. margin: auto;
  1203. }
  1204. }
  1205. // 副视频画面
  1206. .deputy_video {
  1207. width: 190px;
  1208. height: 190px;
  1209. position: absolute;
  1210. top: 0;
  1211. right: 0;
  1212. background: #fff;
  1213. }
  1214. // 操作
  1215. .operate {
  1216. height: 25px;
  1217. width: 100%;
  1218. background: #000;
  1219. padding: 5px 0 5px 0;
  1220. margin: -13px 0 0 0px;
  1221. position: absolute;
  1222. bottom: 3%;
  1223. left: 0;
  1224. display: flex;
  1225. justify-content: flex-end;
  1226. }
  1227. }
  1228. /* 聊天内容 */
  1229. .ul_list {
  1230. height: 350px;
  1231. // margin-left: -41px;
  1232. overflow: hidden;
  1233. overflow-y: auto;
  1234. }
  1235. .list_msgBox1 {
  1236. display: flex;
  1237. margin-top: 22px;
  1238. justify-content: flex-end;
  1239. }
  1240. .list_msgBox2 {
  1241. display: flex;
  1242. margin-top: 22px;
  1243. }
  1244. .test-5::-webkit-scrollbar {
  1245. /*滚动条整体样式*/
  1246. width: 10px; /*高宽分别对应横竖滚动条的尺寸*/
  1247. height: 1px;
  1248. }
  1249. .test-5::-webkit-scrollbar-thumb {
  1250. /*滚动条里面小方块*/
  1251. border-radius: 10px;
  1252. background-color: skyblue;
  1253. background-image: -webkit-linear-gradient(
  1254. 45deg,
  1255. rgba(255, 255, 255, 0.2) 25%,
  1256. transparent 25%,
  1257. transparent 50%,
  1258. rgba(255, 255, 255, 0.2) 50%,
  1259. rgba(255, 255, 255, 0.2) 75%,
  1260. transparent 75%,
  1261. transparent
  1262. );
  1263. }
  1264. .test-5::-webkit-scrollbar-track {
  1265. /*滚动条里面轨道*/
  1266. box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
  1267. background: #ededed;
  1268. border-radius: 10px;
  1269. }
  1270. .list {
  1271. /* border: 1px solid red; */
  1272. width: 100%;
  1273. height: 100px;
  1274. }
  1275. /* ElementUI 样式 */
  1276. // 聊天框
  1277. /deep/.el-dialog {
  1278. overflow: hidden;
  1279. }
  1280. /deep/.el-dialog__header {
  1281. background: #f2f2f2;
  1282. border-bottom: 1px solid #cacaca;
  1283. }
  1284. /deep/.el-dialog__title {
  1285. font-weight: 550;
  1286. font-size: 15px;
  1287. line-height: 0;
  1288. float: left;
  1289. }
  1290. /deep/.el-dialog__headerbtn {
  1291. top: 10px;
  1292. right: 10px;
  1293. }
  1294. /deep/.el-dialog__headerbtn .el-dialog__close {
  1295. font-size: 20px;
  1296. line-height: 15px;
  1297. }
  1298. /deep/.el-dialog__body {
  1299. padding: 0;
  1300. // margin-top: -15px;
  1301. height: 100%;
  1302. }
  1303. /deep/.el-textarea__inner {
  1304. border: 0;
  1305. border-top: 1px solid #dcdfe6;
  1306. border-radius: 0;
  1307. height: 95px;
  1308. }
  1309. /deep/.el-card {
  1310. overflow: hidden;
  1311. overflow-y: auto;
  1312. }
  1313. }
  1314. /deep/.el-button--info {
  1315. background-color: #409eff;
  1316. border-color: #409eff;
  1317. }
  1318. </style>