list.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. <!-- 聊天消息中心列表页面 -->
  2. <template>
  3. <view class="message_center">
  4. <!-- 消息类型 start -->
  5. <view class="message_type">
  6. <view class="message_type_con">
  7. <view class="message_type_pre" v-for="(item,index) in noticeList" :key="index"
  8. @click="linkTo(item.tplTypeCode,item.msgNum)">
  9. <image :src="system_news" mode="" v-if="item.tplTypeCode == 'system_news'"></image>
  10. <image :src="order_news" mode="" v-if="item.tplTypeCode == 'order_news'"></image>
  11. <image :src="assets_news" mode="" v-if="item.tplTypeCode == 'assets_news'"></image>
  12. <image :src="appointment_news" mode="" v-if="item.tplTypeCode == 'appointment_news'"></image>
  13. <image :src="after_sale_news" mode="" v-if="item.tplTypeCode == 'after_sale_news'"></image>
  14. <text>{{item.msgName}}</text>
  15. <text class="message_type_nums message_type_nums_9"
  16. v-if="item.msgNum>0">{{item.msgNum>=99?'99+':item.msgNum}}</text>
  17. </view>
  18. <view class="message_type_pre" @click="goNoticeSet">
  19. <image :src="setting_icon" mode=""></image>
  20. <text>{{$L('接收设置')}}</text>
  21. </view>
  22. </view>
  23. </view>
  24. <!-- 消息类型 end -->
  25. <!-- 消息列表 start -->
  26. <view class="message_list">
  27. <view class="message_list_pre" @touchmove="drawMove" @toucheend="drawEnd"
  28. :style="{right: (optBtn&&_index==index) ? '240rpx' : '0rpx'}" v-for="(item,index) in chatList" :key="index"
  29. :data-index="index" @touchstart="drawStart" @click="toDetail(item)">
  30. <view class="list_pre_left">
  31. <image :src="item.vendorAvatar" mode="aspectFill"></image>
  32. </view>
  33. <view class="list_pre_con">
  34. <view class="list_pre_top">
  35. <text class="pre_name">{{item.storeName}}</text>
  36. <text class="list_pre_time">{{item.addTime}}</text>
  37. </view>
  38. <view class="list_pre_bottom">
  39. <text class="pre_des">{{item.showContent}}</text>
  40. <text v-if="item.receiveMsgNumber"
  41. class="list_pre_nums9 list_pre_nums">{{item.receiveMsgNumber>=99?'99+':item.receiveMsgNumber}}</text>
  42. </view>
  43. </view>
  44. <view class="list_pre_btn">
  45. <text class="list_btn_read" @click.stop="msgReadDone(item.storeId)">{{$L('已读')}}</text>
  46. <text class="list_btn_del" @click.stop="msgDelete(item.storeId)">{{$L('删除')}}</text>
  47. </view>
  48. </view>
  49. <loadingState v-if="loadingState == 'first_loading'||chatList.length > 0" :state='loadingState' />
  50. <view class="empty_data" v-if="!chatList.length>0">
  51. <image :src="imgUrl+'empty_msg.png'" mode=""></image>
  52. <text>暂无消息记录</text>
  53. </view>
  54. </view>
  55. <!-- 消息列表 end -->
  56. </view>
  57. </template>
  58. <script>
  59. import loadingState from "@/components/loading-state.vue";
  60. import {
  61. mapState,
  62. mapMutations
  63. } from 'vuex';
  64. import io from '@hyoga/uni-socket.io';
  65. export default {
  66. components: {
  67. loadingState
  68. },
  69. data() {
  70. return {
  71. connectBaseData: {}, //每次通信必传信息
  72. imgUrl: getApp().globalData.imgUrl,
  73. assets_news: getApp().globalData.imgUrl + 'member/icon10.png',
  74. order_news: getApp().globalData.imgUrl + 'member/icon20.png',
  75. after_sale_news: getApp().globalData.imgUrl + 'member/icon30.png',
  76. system_news: getApp().globalData.imgUrl + 'member/icon60.png',
  77. appointment_news: getApp().globalData.imgUrl + 'member/icon70.png',
  78. setting_icon: getApp().globalData.imgUrl + 'member/receive_settings.png',
  79. startX: '',
  80. optBtn: false, //操作按钮是否显示(已读,删除)
  81. current: 1,
  82. hasMore: true, //是否还有数据
  83. pageSize: 10,
  84. loadingState: 'first_loading',
  85. chatList: [],
  86. noticeList: [],
  87. minMsgId: '', //当前消息的最小id
  88. socketInfo: '', //socket连接成功返回的房间信息
  89. _index: '',
  90. showState: false
  91. };
  92. },
  93. async onLoad() {
  94. await this.getChatList();
  95. this.initSocket();
  96. },
  97. /**
  98. * 生命周期函数--监听页面卸载
  99. */
  100. onUnload: function() {
  101. if (this.socket) {
  102. this.closeSocket();
  103. }
  104. },
  105. computed: {
  106. ...mapState(['userInfo', 'userCenterData'])
  107. },
  108. onBackPress() {
  109. this.closeSocket();
  110. },
  111. onShow() {
  112. this.getNoticeNum();
  113. if (this.showState) {
  114. this.initSocket();
  115. }
  116. },
  117. methods: {
  118. ...mapMutations(['saveChatBaseInfo']),
  119. initSocket() {
  120. if (this.socket) {
  121. this.closeSocket();
  122. }
  123. this.connectBaseData = {
  124. userId: this.userCenterData.memberId,
  125. role: 1
  126. };
  127. this.socket = io(getApp().globalData.chatUrl, {
  128. reconnection: true,
  129. jsonp: true,
  130. transports: ['websocket', 'polling'],
  131. timeout: 5000,
  132. });
  133. this.socket.on("connect", () => {
  134. //需要向服务端发送店铺id,方便加入房间
  135. if (this.chatList.length > 0) {
  136. this.sendStoreIds();
  137. }
  138. //监听最近联系人列表
  139. this.socket.on("contact_change", e => {
  140. let tmp_data = this.chatList.filter(item => item.storeId != e.storeId)
  141. tmp_data.unshift(e);
  142. this.chatList = tmp_data;
  143. this.formatMsgContent();
  144. });
  145. //监听未读数
  146. this.socket.on("unread_num_change", e => {
  147. console.log(e, 'asdasdasdaswesss')
  148. let tmp_data = this.chatList.filter(item => item.storeId == e.storeId);
  149. if (tmp_data.length == 1) {
  150. tmp_data[0].receiveMsgNumber = e.unreadNum;
  151. }
  152. });
  153. });
  154. },
  155. // 发送当前列表的所有店铺id
  156. sendStoreIds() {
  157. let tmpStoreIdArray = [];
  158. this.chatList.map(item => {
  159. tmpStoreIdArray.push(item.storeId);
  160. });
  161. this.socket.emit("send_store_ids", {
  162. storeIds: tmpStoreIdArray.join(','),
  163. ...this.connectBaseData
  164. });
  165. this.socket.emit("connect_success", {
  166. storeId: '',
  167. ...this.connectBaseData
  168. });
  169. },
  170. //关闭socket
  171. closeSocket() {
  172. if (this.socket) {
  173. this.socket.close();
  174. }
  175. },
  176. //开始触摸滑动
  177. drawStart(e) {
  178. let index = e.currentTarget.dataset.index;
  179. this._index = index;
  180. let touch = e.touches[0];
  181. this.startX = touch.clientX;
  182. },
  183. //触摸滑动
  184. drawMove(e) {
  185. let touch = e.touches[0];
  186. let dixX = this.startX - touch.clientX;
  187. if (dixX >= 20) {
  188. this.optBtn = true;
  189. } else {
  190. this.optBtn = false;
  191. }
  192. },
  193. //触摸滑动结束
  194. drawEnd(e) {
  195. this.optBtn = false;
  196. },
  197. //去消息设置页面
  198. goNoticeSet() {
  199. uni.navigateTo({
  200. url: '/pages/notice/receivingSet'
  201. })
  202. },
  203. //导航栏跳转页面
  204. linkTo(tplTypeCode, msgNum) {
  205. uni.navigateTo({
  206. url: '/pages/notice/notice?tplType=' + tplTypeCode
  207. })
  208. },
  209. //聊天标记为已读,将未读数置为0
  210. msgReadDone(storeId) {
  211. let _this = this;
  212. this.socket.emit("member_read_all", {
  213. ...this.connectBaseData
  214. });
  215. let tmpData = _this.chatList.filter(item => item.storeId == storeId)[0];
  216. tmpData.receiveMsgNumber = 0;
  217. _this.chatList = _this.chatList;
  218. this.optBtn = false
  219. },
  220. //删除聊天
  221. msgDelete(storeId) {
  222. let _this = this;
  223. uni.showModal({
  224. title: '提示',
  225. content: '是否删除该聊天?',
  226. success: res => {
  227. if (res.confirm) {
  228. _this.socket.emit("member_remove_contact", {
  229. storeId: storeId,
  230. ...this.connectBaseData
  231. }, () => {
  232. _this.chatList = _this.chatList.filter(item => item.storeId !=
  233. storeId);
  234. });
  235. _this.optBtn = false;
  236. _this._index = ''
  237. }else{
  238. _this.optBtn = false;
  239. }
  240. }
  241. })
  242. },
  243. //获取消息未读数
  244. getNoticeNum() {
  245. let param = {}
  246. param.url = 'v3/msg/front/msg/msgListNum'
  247. param.method = 'GET'
  248. this.$request(param).then(res => {
  249. if (res.state == 200) {
  250. this.noticeList = res.data;
  251. } else {
  252. this.$api.msg(res.msg)
  253. }
  254. })
  255. },
  256. //获取聊天列表
  257. async getChatList() {
  258. let params = {}
  259. params.url = 'v3/helpdesk/front/chat/storeList',
  260. params.method = 'GET';
  261. params.data = {};
  262. params.data.pageSize = this.pageSize;
  263. params.data.current = this.current;
  264. if (this.minMsgId) {
  265. params.data.msgId = this.minMsgId;
  266. }
  267. this.loadingState = this.loadingState == 'first_loading' ? this.loadingState : 'loading';
  268. await this.$request(params).then(res => {
  269. if (res.state == 200) {
  270. if (this.current == 1) {
  271. this.chatList = res.data
  272. } else {
  273. this.chatList = this.chatList.concat(res.data);
  274. }
  275. if (this.minMsgId) {
  276. this.sendStoreIds();
  277. }
  278. if (this.chatList.length > 0) {
  279. this.minMsgId = this.chatList[this.chatList.length - 1].msgId;
  280. }
  281. if (res.data.length < this.pageSize) {
  282. this.hasMore = false;
  283. }
  284. if (this.hasMore) {
  285. this.current++;
  286. this.loadingState = 'allow_loading_more';
  287. } else {
  288. this.loadingState = 'no_more_data';
  289. }
  290. this.formatMsgContent();
  291. } else {
  292. this.$api.msg(res.msg);
  293. }
  294. })
  295. },
  296. //格式化聊天内容,方便列表展示
  297. formatMsgContent() {
  298. let reg = /<img [^>]*src=['"]([^'"]+)[^>]*>/g
  299. if (this.chatList.length > 0) {
  300. this.chatList.map(item => {
  301. if (typeof item.msgContent == 'string') {
  302. item.msgContent = JSON.parse(item.msgContent)
  303. }
  304. //1.text(文本) 2.img(图片) 3.goods(商品) 4.order(订单)用户
  305. if (item.msgType == 1) {
  306. if (reg.test(item.msgContent.content)) {
  307. item.showContent = item.msgContent.content.replace(reg, '[表情]')
  308. } else {
  309. item.showContent = item.msgContent.content;
  310. }
  311. } else if (item.msgType == 2) {
  312. item.showContent = '[图片]';
  313. } else if (item.msgType == 3) {
  314. item.showContent = '[商品]';
  315. } else if (item.msgType == 4) {
  316. item.showContent = '[订单]';
  317. }
  318. })
  319. }
  320. },
  321. onReachBottom() {
  322. if (this.hasMore) {
  323. getChatList()
  324. }
  325. },
  326. //前往聊天界面
  327. toDetail(item) {
  328. let chatBaseInfo = {};
  329. chatBaseInfo.memberId = item.memberId;
  330. chatBaseInfo.memberName = item.memberName;
  331. chatBaseInfo.memberNickName = item.memberName;
  332. chatBaseInfo.memberAvatar = item.memberAvatar;
  333. chatBaseInfo.storeId = item.storeId;
  334. chatBaseInfo.storeLogo = item.vendorAvatar;
  335. chatBaseInfo.storeName = item.storeName;
  336. chatBaseInfo.source = 'chat_list';
  337. chatBaseInfo.showData = {}
  338. this.saveChatBaseInfo(chatBaseInfo);
  339. this.showState = true
  340. uni.navigateTo({
  341. url: '/pages/chat/detail?vid=' + item.storeId
  342. })
  343. }
  344. },
  345. }
  346. </script>
  347. <style lang='scss'>
  348. page {
  349. background: #FFFFFF;
  350. }
  351. .message_center {
  352. width: 750rpx;
  353. margin-left: calc((100vw - 750rpx) / 2);
  354. /* 消息类型 start */
  355. .message_type {
  356. padding: 32rpx 20rpx 20rpx;
  357. border-top: 1rpx solid #F2F2F2;
  358. display: flex;
  359. overflow-x: scroll;
  360. .message_type_con {
  361. padding-right: 20rpx;
  362. display: flex;
  363. .message_type_pre {
  364. margin-right: 40rpx;
  365. display: flex;
  366. flex-direction: column;
  367. align-items: center;
  368. position: relative;
  369. image {
  370. width: 70rpx;
  371. height: 70rpx;
  372. }
  373. text {
  374. font-size: 28rpx;
  375. font-family: PingFang SC;
  376. font-weight: 500;
  377. color: #2D2D2D;
  378. line-height: 32rpx;
  379. margin-top: 20rpx;
  380. white-space: nowrap;
  381. }
  382. .message_type_nums {
  383. position: absolute;
  384. right: 18rpx;
  385. top: -28rpx;
  386. min-width: 22rpx;
  387. height: 22rpx;
  388. background: #FF0000;
  389. border-radius: 13rpx;
  390. font-size: 20rpx;
  391. font-family: PingFang SC;
  392. font-weight: 400;
  393. color: #FFFFFF;
  394. line-height: 22rpx;
  395. text-align: center;
  396. }
  397. .message_type_nums_9 {
  398. padding: 0 5rpx;
  399. /* height: 22rpx; */
  400. }
  401. }
  402. .message_type_pre:nth-last-of-type(1) {
  403. margin-right: 0;
  404. }
  405. }
  406. }
  407. /* 消息类型 end */
  408. /* 消息列表 start */
  409. .message_list {
  410. border-top: 20rpx solid #F5F5F5;
  411. overflow-x: hidden;
  412. .message_list_pre {
  413. display: flex;
  414. align-items: center;
  415. margin: 0 20rpx;
  416. border-bottom: 1rpx solid #F2F2F2;
  417. height: 150rpx;
  418. position: relative;
  419. transition: all 0.3s;
  420. .message_list_pre:last-child {
  421. border: none;
  422. }
  423. .list_pre_left {
  424. width: 80rpx;
  425. height: 80rpx;
  426. border-radius: 50%;
  427. margin-right: 30rpx;
  428. image {
  429. width: 80rpx;
  430. height: 80rpx;
  431. border-radius: 50%;
  432. }
  433. }
  434. .list_pre_con {
  435. display: flex;
  436. flex-direction: column;
  437. justify-content: space-between;
  438. align-items: space-between;
  439. width: 620rpx;
  440. padding: 40rpx 0;
  441. height: 150rpx;
  442. box-sizing: border-box;
  443. .list_pre_top {
  444. display: flex;
  445. justify-content: space-between;
  446. .pre_name {
  447. font-size: 30rpx;
  448. font-family: PingFang SC;
  449. font-weight: 500;
  450. color: #333333;
  451. line-height: 30rpx;
  452. overflow: hidden;
  453. text-overflow: ellipsis;
  454. white-space: nowrap;
  455. }
  456. .list_pre_time {
  457. font-size: 22rpx;
  458. font-family: PingFang SC;
  459. font-weight: 500;
  460. color: #999999;
  461. line-height: 32rpx;
  462. }
  463. }
  464. .list_pre_bottom {
  465. display: flex;
  466. justify-content: space-between;
  467. .pre_des {
  468. width: 484rpx;
  469. font-size: 26rpx;
  470. font-family: PingFang SC;
  471. font-weight: 500;
  472. color: #999999;
  473. line-height: 28rpx;
  474. overflow: hidden;
  475. text-overflow: ellipsis;
  476. white-space: nowrap;
  477. height: 30rpx;
  478. }
  479. .list_pre_nums {
  480. height: 22rpx;
  481. background: #FF0000;
  482. border-radius: 10rpx;
  483. text-align: center;
  484. line-height: 22rpx;
  485. font-size: 20rpx;
  486. font-family: PingFang SC;
  487. font-weight: 500;
  488. color: #FFFFFF;
  489. line-height: 22rpx;
  490. }
  491. .list_pre_nums9 {
  492. position: absolute;
  493. left: 48rpx;
  494. top: 24rpx;
  495. padding: 0 4rpx;
  496. min-width: 22rpx;
  497. }
  498. }
  499. }
  500. .list_pre_btn {
  501. display: flex;
  502. align-items: center;
  503. position: absolute;
  504. top: 0;
  505. right: -260rpx;
  506. .list_btn_read {
  507. width: 120rpx;
  508. height: 150rpx;
  509. background: #EEEEEE;
  510. font-size: 28rpx;
  511. font-family: PingFang SC;
  512. font-weight: 500;
  513. color: #333333;
  514. line-height: 150rpx;
  515. text-align: center;
  516. }
  517. .list_btn_del {
  518. width: 120rpx;
  519. height: 150rpx;
  520. background: #FF0000;
  521. font-size: 28rpx;
  522. font-family: PingFang SC;
  523. font-weight: 500;
  524. color: #FFFFFF;
  525. line-height: 150rpx;
  526. text-align: center;
  527. }
  528. }
  529. }
  530. }
  531. /* 消息列表 end */
  532. }
  533. .empty_data {
  534. height: 750rpx;
  535. display: flex;
  536. justify-content: center;
  537. flex-direction: column;
  538. align-items: center;
  539. image {
  540. width: 200rpx;
  541. height: 200rpx;
  542. }
  543. text {
  544. margin-top: 20rpx;
  545. font-size: 28rpx;
  546. color: #999999;
  547. }
  548. }
  549. </style>