chat-room.vue 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  1. <template>
  2. <div class="chat-room common-container">
  3. <!-- 顶部 -->
  4. <div class="chat-room-head">
  5. <div class="head-title">
  6. <span>监控中心</span>
  7. </div>
  8. <div class="head-btn parent">
  9. <el-button
  10. type="danger"
  11. class="vertical-center exitBtn"
  12. size="medium"
  13. @click="goBack()"
  14. >退出</el-button>
  15. <!--<el-switch
  16. class="vertical-center head-switch"
  17. v-model="busy"
  18. active-color="#13ce66"
  19. inactive-color="#ccc"
  20. active-text="示忙"
  21. ></el-switch>-->
  22. </div>
  23. </div>
  24. <!-- 主体内容部分 -->
  25. <div class="chat-room-body">
  26. <!-- 聊天框 -->
  27. <div class="body-centre">
  28. <div class="centre-top">
  29. <div class="centre-top-title">{{name}}-<span v-if="dataTable == 1">问诊</span><span v-else>出诊</span></div>
  30. <el-scrollbar style="background-color:#fff; height: 400px;" ref="scrollbar">
  31. <div class="centre-top-content clearfix">
  32. <div
  33. class="chat-records"
  34. v-for="(item,index) in chatList"
  35. :key="index"
  36. :class="item.fromUser === customerServiceId ? 'reverse' : 'row' "
  37. >
  38. <div class="chat-records-icon">
  39. <el-image
  40. :src="item.avatarUrl | pathPipe"
  41. style="width:35px;height:35px"
  42. fit="cover"
  43. ></el-image>
  44. </div>
  45. <div class="chat-records-content" v-if="(item.msg.substr(item.msg.lastIndexOf('.')+1)!='jpg') && (item.msg.substr(item.msg.lastIndexOf('.')+1)!='png')">{{item.msg}}</div>
  46. <div class="chat-records-content" v-else>
  47. <span @click="imgOpen(item.msg)" style="cursor:pointer;">
  48. <el-image :src="item.msg | pathPipe" style="width:100px;height:100px;">
  49. </el-image>
  50. </span>
  51. </div>
  52. </div>
  53. </div>
  54. </el-scrollbar>
  55. </div>
  56. <div class="centre-bottom">
  57. <div class="bottom-top">
  58. <!-- <i class="el-icon-setting"></i>
  59. <i class="el-icon-warning-outline"></i>
  60. <i class="el-icon-remove-outline"></i>
  61. <i class="el-icon-circle-check"></i> -->
  62. </div>
  63. <div class="bottom-text clearfix">
  64. <el-input
  65. type="textarea"
  66. resize="none"
  67. placeholder="请输入内容..."
  68. v-model="content"
  69. ></el-input>
  70. <el-button
  71. type="success"
  72. class="fr send"
  73. size="medium"
  74. @click="sendMessage()"
  75. >发送</el-button>
  76. </div>
  77. </div>
  78. </div>
  79. <!-- 右边聊天成员击专家成员-->
  80. <div class="body-rignt">
  81. <div class="member">
  82. <div class="member-head">聊天成员({{this.memberList.length || 0}}人)</div>
  83. <div class="member-box">
  84. <el-scrollbar>
  85. <div
  86. class="membei-list"
  87. v-for="(item,index) in memberList"
  88. :key="index"
  89. >
  90. <!-- <div class="circle" :class="item.flag === true?'active':'inactive'"></div> -->
  91. <div class="membei-icon">
  92. <el-image
  93. :src="item.avatarUrl | pathPipe"
  94. style="width:43px;height:43px"
  95. fit="cover"
  96. ></el-image>
  97. </div>
  98. <div class="member-people">
  99. <div class="people-name">{{item.name}}</div>
  100. <div class="people-time">{{item.createTime | formatTimePipe}}</div>
  101. </div>
  102. </div>
  103. </el-scrollbar>
  104. </div>
  105. </div>
  106. <div class="expert-list-bigbox">
  107. <div class="expert-title">专家列表</div>
  108. <el-scrollbar class="expert-scrollbar">
  109. <div class="expert-list">
  110. <el-scrollbar>
  111. <div
  112. class="membei-list pointer"
  113. v-for="(item,index) in expertList"
  114. :key="index"
  115. >
  116. <div class="left-side">
  117. <div style="margin-top:20px;">
  118. <i
  119. class="el-icon-success"
  120. :class="item.userId === checkId?'check-active':'check-inactive'"
  121. @click="selectExpert(item)"
  122. ></i>
  123. </div>
  124. <div class="membei-icon">
  125. <el-image
  126. :src="item.avatarUrl | pathPipe"
  127. style="width:43px;height:43px"
  128. fit="cover"
  129. ></el-image>
  130. </div>
  131. <div class="member-people">
  132. <div class="people-name txt-ovehid">
  133. <span>{{item.name}}</span>
  134. <span class="expert-label">
  135. <span v-if="item.platformInvitedFlag !== 0">普通专家</span>
  136. <span v-if="item.platformInvitedFlag === 0">特邀专家</span>
  137. </span>
  138. <span class="expert-level">
  139. <span v-if="item.expertLevel === 1">一级</span>
  140. <span v-if="item.expertLevel === 2">二级</span>
  141. <span v-if="item.expertLevel === 3">三级</span>
  142. <span v-if="item.expertLevel === 4">四级</span>
  143. <span v-if="item.expertLevel === 5">五级</span>
  144. </span>
  145. </div>
  146. <div
  147. class="people-time"
  148. >{{item.createTime | formatTimePipe}}</div>
  149. <div class="people-phone">{{item.mobile}}</div>
  150. </div>
  151. </div>
  152. <div class="member-people-btn">
  153. <el-button
  154. type="primary"
  155. class
  156. size="mini"
  157. disabled
  158. @click="callExpert(item)"
  159. >呼叫</el-button>
  160. </div>
  161. </div>
  162. </el-scrollbar>
  163. </div>
  164. </el-scrollbar>
  165. <div class="Invite-experts clearfix">
  166. <el-button
  167. type="success"
  168. class="fr Invite-experts-btn"
  169. size="medium"
  170. @click="inviteExpert()"
  171. >邀请专家</el-button>
  172. </div>
  173. </div>
  174. </div>
  175. </div>
  176. </div>
  177. </template>
  178. <script>
  179. import { webSocketUrl } from '../../../../../config';
  180. import { queryLiftCasesList, queryChatHistoryList, invitationExpert } from '@/apps/mobile/api/monitoring-center/index';
  181. import { queryAllExpert } from '@/apps/mobile/api/expert/index';
  182. import { isArray, chatUnique, chatUnique2 } from '@/apps//mobile/utils/util';
  183. export default {
  184. data() {
  185. return {
  186. id: null, // 诊单id
  187. sessionId: null, // 房间id
  188. customerServiceId: null, // 平台客服id
  189. expertId: null, // 专家id
  190. statuz: null, // 诊单状态
  191. busy: false,
  192. masterList: [],
  193. memberList: [],
  194. expertList: [],
  195. chatList: [],
  196. content: '',
  197. page: {
  198. pageNum: 1,
  199. pageSize: 500,
  200. sort: {
  201. order: 'desc',
  202. orderBy: 'create_time',
  203. },
  204. },
  205. chatPage: {
  206. pageNum: 1,
  207. pageSize: 500,
  208. },
  209. checkId: -1,
  210. name: null,
  211. dataTable: null,
  212. $ws: null,
  213. timer: '',
  214. };
  215. },
  216. created() {
  217. this.id = this.$route.query.id;
  218. this.name = this.$route.query.name;
  219. this.sessionId = this.$route.query.sessionId;
  220. this.customerServiceId = this.$route.query.customerServiceId;
  221. this.dataTable = this.$route.query.dataTable;
  222. this.queryChatHistoryData();
  223. this.queryExpertData();
  224. if (!this.id) {
  225. this.queryLiftCaseData();
  226. console.log('NoId...');
  227. }
  228. },
  229. mounted() {
  230. this.initWebSocket();
  231. },
  232. beforeDestroy() {
  233. clearInterval(this.timer);
  234. },
  235. methods: {
  236. imgOpen(item) {
  237. window.open(item);
  238. },
  239. // 诊单---------------------------------------------------------
  240. queryLiftCaseData() {
  241. let obj = {
  242. ...this.page,
  243. type: 'BACKQUERY',
  244. sessionId: this.sessionId,
  245. // customerServiceId: this.customerServiceId,
  246. };
  247. queryLiftCasesList(obj)
  248. .then(res => {
  249. if (res.data && res.data.records && res.data.records.length > 0) {
  250. this.id = res.data.records[0].id;
  251. }
  252. })
  253. .catch();
  254. },
  255. // 聊天开始-------------------------------------------------------
  256. initWebSocket() {
  257. if (window.WebSocket) {
  258. this.$ws = new WebSocket(webSocketUrl);
  259. this.$ws.onopen = this.wsOpen;
  260. this.$ws.onclose = this.wsClose;
  261. this.$ws.onmessage = this.wsMessage;
  262. this.$ws.onerror = this.wsError;
  263. } else {
  264. this.$message.warning('浏览器暂不支持WebSocket服务');
  265. }
  266. },
  267. wsOpen(e) {
  268. console.log('WebSocket连接成功...........');
  269. this.initSeeionLogin();
  270. this.initLogin();
  271. this.timer = setInterval(this.initLoopLogin, 10000);
  272. },
  273. wsClose: function(e) {
  274. console.log('WebSocket连接关闭...........');
  275. clearInterval(this.timer);
  276. },
  277. wsError: function(e) {
  278. console.log('WebSocket连接出现错误............');
  279. },
  280. wsMessage: function(e) {
  281. let msgData = JSON.parse(e.data);
  282. console.log('...消息...', msgData);
  283. if (msgData && msgData.cmd === 'INITROOM') {
  284. let msgs = JSON.parse(msgData.msg);
  285. if (msgs && msgs.length > 0) {
  286. this.chatList = msgs;
  287. }
  288. } else if (msgData && msgData.cmd === 'CHAT') {
  289. this.content = '';
  290. this.chatList.push(msgData);
  291. if (this.chatList && this.chatList.length > 0) {
  292. this.keepChattingTop();
  293. }
  294. } else {
  295. }
  296. },
  297. initLogin() {
  298. let obj = {};
  299. // obj.cmd = 'LOGINALL';
  300. obj.cmd = 'INITROOM';
  301. obj.type = 1;
  302. obj.avatarUrl = JSON.parse(sessionStorage.getItem('avatarUrl'));
  303. obj.time = new Date().getTime();
  304. obj.fromUser = this.customerServiceId;
  305. obj.userId = this.customerServiceId;
  306. obj.name = '管理员';
  307. obj.sessionid = this.sessionId;
  308. obj.msg = '管理员进入该房间了!';
  309. this.$ws.send(JSON.stringify(obj));
  310. },
  311. initSeeionLogin() {
  312. let obj = {};
  313. obj.cmd = 'LOGINALL';
  314. obj.type = 1;
  315. obj.avatarUrl = JSON.parse(sessionStorage.getItem('avatarUrl'));
  316. obj.time = new Date().getTime();
  317. obj.fromUser = this.customerServiceId;
  318. obj.userId = this.customerServiceId;
  319. obj.name = '管理员';
  320. obj.sessionid = this.sessionId;
  321. obj.msg = '管理员进入该房间了!';
  322. this.$ws.send(JSON.stringify(obj));
  323. },
  324. initLoopLogin() {
  325. let obj = {};
  326. obj.cmd = 'LOOP';
  327. obj.userId = this.customerServiceId;
  328. this.$ws.send(JSON.stringify(obj));
  329. },
  330. sendMessage() {
  331. if (!window.WebSocket) {
  332. return;
  333. }
  334. if (this.content.replace(/\s/gi, '') == '') {
  335. return;
  336. }
  337. let obj = {};
  338. obj.cmd = 'CHAT';
  339. obj.type = 1;
  340. obj.avatarUrl = JSON.parse(sessionStorage.getItem('avatarUrl'));
  341. obj.time = new Date().getTime();
  342. obj.fromUser = this.customerServiceId;
  343. obj.userId = this.customerServiceId;
  344. obj.name = '管理员';
  345. obj.sessionid = this.sessionId;
  346. obj.msg = this.content;
  347. this.$ws.send(JSON.stringify(obj));
  348. },
  349. // 聊天结束-------------------------------------------------------
  350. queryChatHistoryData() {
  351. let obj = {
  352. ...this.chatPage,
  353. sessionid: this.sessionId,
  354. };
  355. queryChatHistoryList(obj)
  356. .then(res => {
  357. // console.log('聊天历史记录', res);
  358. if (res.data && res.data.records && res.data.records.length > 0) {
  359. this.chatList = res.data.records;
  360. let chatData = [...res.data.records];
  361. this.chatList = chatData.reverse();
  362. if (this.chatList && this.chatList.length > 0) {
  363. this.keepChattingTop();
  364. }
  365. }
  366. })
  367. .catch();
  368. },
  369. // 专家 -------------------------------------------------
  370. queryExpertData() {
  371. let obj = {
  372. sort: {
  373. order: 'desc',
  374. orderBy: 'platform_invited_flag',
  375. }
  376. };
  377. queryAllExpert(obj)
  378. .then(res => {
  379. console.log('专家列表', res);
  380. this.expertList = res.data;
  381. })
  382. .catch();
  383. },
  384. selectExpert(data) {
  385. console.log(data);
  386. this.checkId = data.userId;
  387. this.expertId = data.userId;
  388. this.statuz = data.statuz;
  389. },
  390. inviteExpert() {
  391. if (!this.expertId) {
  392. this.$message({
  393. type: 'warning',
  394. message: '请先选择一个专家',
  395. });
  396. return false;
  397. }
  398. //问诊
  399. if(this.statuz == 1 || this.statuz == 8){
  400. let obj = {
  401. id: this.id,
  402. chargerId: this.expertId,
  403. };
  404. invitationExpert(obj)
  405. .then(res => {
  406. if(res.statusCode==="1"){
  407. this.$message.success('已邀请此专家,请耐心等候!');
  408. this.queryLiftCaseData();
  409. this.checkId = -1;
  410. this.expertId = null;
  411. }
  412. else{
  413. this.$message.error(res.message);
  414. }
  415. })
  416. .catch();
  417. }
  418. //出诊
  419. else if(this.statuz == 0 || this.statuz == 8){
  420. let obj = {
  421. id: this.id,
  422. chargerId: this.expertId,
  423. };
  424. invitationExpert(obj)
  425. .then(res => {
  426. if(res.statusCode==="1"){
  427. this.$message.success('已邀请此专家,请耐心等候!');
  428. this.queryLiftCaseData();
  429. this.checkId = -1;
  430. this.expertId = null;
  431. }
  432. else{
  433. this.$message.error(data.message);
  434. }
  435. })
  436. .catch();
  437. }
  438. else{
  439. this.$message.warning('该专家已接单,请选择其他专家!');
  440. }
  441. },
  442. callExpert(data) {
  443. // console.log(data);
  444. },
  445. goBack() {
  446. this.$router.go(-1);
  447. },
  448. // 聊天消息置顶
  449. keepChattingTop() {
  450. this.$nextTick(() => {
  451. let scrollRef = this.$refs['scrollbar'].$refs['wrap'];
  452. if (scrollRef && scrollRef.scrollHeight) {
  453. scrollRef.scrollTop = scrollRef.scrollHeight;
  454. }
  455. });
  456. },
  457. },
  458. destroyed() {
  459. this.$ws.onclose();
  460. },
  461. };
  462. </script>
  463. <style lang="stylus" scoped>
  464. .chat-room {
  465. width: 100%;
  466. margin: 0 auto;
  467. }
  468. .chat-room-head {
  469. display: flex;
  470. height: 64px;
  471. .head-title {
  472. background-color: rgba(0, 40, 77, 1);
  473. span {
  474. display: block;
  475. width: 256px;
  476. height: 64px;
  477. line-height: 64px;
  478. text-align: center;
  479. font-weight: 700;
  480. font-style: normal;
  481. font-size: 20px;
  482. color: rgba(255, 255, 255, 0.847058823529412);
  483. }
  484. }
  485. .head-btn {
  486. width: calc(100% - 256px);
  487. .exitBtn {
  488. left: 80px;
  489. font-size: 16px;
  490. }
  491. .head-switch {
  492. right: 20px;
  493. }
  494. }
  495. }
  496. .chat-room-body {
  497. display: flex;
  498. justify-content: space-around;
  499. padding: 20px 10px;
  500. background-color: rgba(240, 242, 245, 1);
  501. .body-centre {
  502. width: 700px;
  503. .centre-top {
  504. .centre-top-title {
  505. height: 20px;
  506. line-height: 20px;
  507. font-size: 16px;
  508. color: #000000;
  509. text-indent: 10px;
  510. background-color: #f5f5f5;
  511. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12);
  512. }
  513. .centre-top-content {
  514. width: 100%;
  515. background-color: #fff;
  516. .left-side {
  517. width: 350px;
  518. border: 1px solid green;
  519. padding-left: 8px;
  520. }
  521. .left-item {
  522. width: 100%;
  523. display: flex;
  524. margin-bottom: 10px;
  525. }
  526. .left-chat-content {
  527. max-width: 160px;
  528. padding: 8px;
  529. margin-top: 4px;
  530. border-radius: 5px;
  531. background-color: #f5f5f5;
  532. height: auto;
  533. }
  534. .right-side {
  535. display: flex;
  536. width: 350px;
  537. border: 1px solid yellow;
  538. padding-right: 8px;
  539. }
  540. .right-item {
  541. width: 100%;
  542. display: flex;
  543. margin-bottom: 10px;
  544. flex-direction: row-reverse;
  545. }
  546. .right-chat-content {
  547. max-width: 160px;
  548. padding: 8px;
  549. margin-top: 4px;
  550. border-radius: 5px;
  551. background-color: #f5f5f5;
  552. height: auto;
  553. }
  554. .chat-records {
  555. display: flex;
  556. min-height: 76px;
  557. padding: 20px;
  558. .chat-records-icon {
  559. margin: 0 5px;
  560. width: 35px;
  561. height: 35px;
  562. .el-image {
  563. border-radius: 50%;
  564. }
  565. }
  566. .chat-records-content {
  567. max-width: 160px;
  568. padding: 8px;
  569. margin-top: 3px;
  570. border-radius: 5px;
  571. background-color: #f5f5f5;
  572. }
  573. }
  574. .row {
  575. flex-direction: row;
  576. }
  577. .reverse {
  578. flex-direction: row-reverse;
  579. }
  580. }
  581. }
  582. .centre-bottom {
  583. .bottom-top {
  584. height: 30px;
  585. padding-left: 10px;
  586. i {
  587. margin-right: 5px;
  588. height: 30px;
  589. line-height: 30px;
  590. }
  591. i:before {
  592. font-size: 16px;
  593. }
  594. }
  595. .bottom-text {
  596. background-color: #fff;
  597. padding-bottom: 20px;
  598. .send {
  599. margin-right: 10px;
  600. }
  601. }
  602. }
  603. }
  604. .body-rignt {
  605. width: 350px;
  606. background-color: #fff;
  607. .member {
  608. padding: 0 20px;
  609. .member-head {
  610. height: 40px;
  611. line-height: 40px;
  612. font-size: 16px;
  613. font-weight: 700;
  614. }
  615. .member-box {
  616. height: 180px;
  617. }
  618. .membei-list {
  619. display: flex;
  620. position: relative;
  621. padding: 2px 0;
  622. border-bottom: 1px solid #f2f2f2;
  623. margin-bottom: 10px;
  624. .membei-icon {
  625. padding: 5px;
  626. .el-image {
  627. border-radius: 50%;
  628. }
  629. }
  630. .member-people {
  631. div {
  632. height: 25px;
  633. line-height: 25px;
  634. font-size: 14px;
  635. }
  636. .people-name {
  637. color: rgba(24, 210, 94, 1);
  638. font-size: 16px;
  639. }
  640. .people-time {
  641. color: #7f7f7f;
  642. }
  643. .people-phone {
  644. color: rgba(24, 210, 94, 1);
  645. }
  646. }
  647. .member-people-btn {
  648. position: absolute;
  649. top: 10px;
  650. right: 10px;
  651. }
  652. .circle {
  653. width: 8px;
  654. height: 8px;
  655. border-radius: 50%;
  656. background-color: red;
  657. }
  658. .active {
  659. background-color: #67C23A;
  660. }
  661. .inactive {
  662. background-color: #ccc;
  663. }
  664. }
  665. }
  666. .expert-list-bigbox {
  667. .expert-title {
  668. text-indent: 20px;
  669. font-size: 16px;
  670. font-weight: 700;
  671. height: 40px;
  672. line-height: 40px;
  673. background-color: #f0f2f5;
  674. }
  675. .expert-scrollbar {
  676. height: 100%;
  677. }
  678. .expert-list {
  679. padding: 20px;
  680. height: 370px;
  681. }
  682. .el-icon-success {
  683. font-size: 18px;
  684. }
  685. .check-active {
  686. color: #85ce61;
  687. }
  688. .check-inactive {
  689. color: #ccc;
  690. }
  691. .membei-list {
  692. display: flex;
  693. position: relative;
  694. padding: 2px 0;
  695. border-bottom: 1px solid #f2f2f2;
  696. margin-bottom: 10px;
  697. .left-side {
  698. display: flex;
  699. }
  700. .membei-icon {
  701. padding: 5px;
  702. .el-image {
  703. border-radius: 50%;
  704. }
  705. }
  706. .member-people {
  707. div {
  708. height: 25px;
  709. line-height: 25px;
  710. font-size: 14px;
  711. }
  712. .people-name {
  713. color: rgba(24, 210, 94, 1);
  714. font-size: 16px;
  715. }
  716. .people-time {
  717. color: #7f7f7f;
  718. }
  719. .people-phone {
  720. color: rgba(24, 210, 94, 1);
  721. }
  722. .expert-label {
  723. margin-left: 4px;
  724. font-size: 10px;
  725. color: #E6A23C;
  726. }
  727. .expert-level {
  728. margin-left: 4px;
  729. font-size: 8px;
  730. color: #409EFF;
  731. }
  732. }
  733. .member-people-btn {
  734. position: absolute;
  735. top: 10px;
  736. right: 10px;
  737. }
  738. }
  739. .Invite-experts {
  740. .Invite-experts-btn {
  741. margin-right: 10px;
  742. }
  743. }
  744. }
  745. }
  746. }
  747. ::v-deep .el-switch__label {
  748. width: 30px;
  749. }
  750. ::v-deep .el-button {
  751. border: none;
  752. }
  753. .el-scrollbar {
  754. height: 100%;
  755. }
  756. ::v-deep .el-scrollbar__wrap {
  757. overflow-x: hidden;
  758. background-color: #fff;
  759. }
  760. ::v-deep .el-textarea__inner {
  761. height: 130px;
  762. }
  763. </style>