conversation-list.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. <template>
  2. <view class="TUI-conversation-list" @click="dialogID = ''">
  3. <view class="no-data-box" v-if="conversationList.length==0">
  4. <image src="https://cos.his.cdwjyyh.com/fs/20240423/cf4a86b913a04341bb44e34bb4d37aa2.png"></image>
  5. <view class="empty-title">暂无数据</view>
  6. </view>
  7. <view v-if="conversationList.length>0" v-for="(item, index) in conversationList" :key="index">
  8. <view
  9. class="TUI-conversation-item"
  10. :class="[
  11. dialogID === item.conversationID && 'selected',
  12. item.isPinned && 'pinned',
  13. ]"
  14. @click="handleGotoItem(item)"
  15. @longpress="handleItemLongpress(item)"
  16. >
  17. <aside class="left">
  18. <image mode="aspectFill" class="avatar" :src="handleItemAvator(item)" />
  19. <span
  20. class="num"
  21. v-if="
  22. item.unreadCount > 0 &&
  23. item.messageRemindType !== 'AcceptNotNotify'
  24. "
  25. >
  26. {{ item.unreadCount > 99 ? "99+" : item.unreadCount }}
  27. </span>
  28. <span
  29. class="num-notNotify"
  30. v-if="
  31. item.unreadCount > 0 &&
  32. item.messageRemindType === 'AcceptNotNotify'
  33. "
  34. ></span>
  35. </aside>
  36. <main class="content">
  37. <header class="content-header">
  38. <label>
  39. <p class="name">{{ handleItemName(item) }}</p>
  40. </label>
  41. </header>
  42. <footer class="content-footer">
  43. <span
  44. class="content-footer-unread"
  45. v-if="
  46. item.unreadCount > 0 &&
  47. item.messageRemindType === 'AcceptNotNotify'
  48. "
  49. >[{{ item.unreadCount > 99 ? "99+" : item.unreadCount }}条]</span
  50. >
  51. <span class="message-text">{{
  52. handleItemMessage(item.lastMessage)
  53. }}</span>
  54. <view class="conversation-line"></view>
  55. </footer>
  56. <view class="item-footer">
  57. <span class="time">{{
  58. handleItemTime(item.lastMessage.lastTime)
  59. }}</span>
  60. <image
  61. class="mute-icon"
  62. v-if="item.messageRemindType === 'AcceptNotNotify'"
  63. src="../../assets/icon/mute.svg"
  64. ></image>
  65. </view>
  66. </main>
  67. <view
  68. class="dialog-box dialog-item"
  69. v-if="item.conversationID === dialogID"
  70. >
  71. <view
  72. class="conversation-options"
  73. @click.stop="handleConversation('delete', dialogID)"
  74. >删除会话</view
  75. >
  76. <view
  77. class="conversation-options"
  78. v-if="!item.isPinned"
  79. @tap.stop="handleConversation('ispinned', dialogID)"
  80. >置顶会话</view
  81. >
  82. <view
  83. class="conversation-options"
  84. v-if="item.isPinned"
  85. @click.stop="handleConversation('dispinned', dialogID)"
  86. >取消置顶</view
  87. >
  88. <view
  89. class="conversation-options"
  90. v-if="
  91. item.messageRemindType === '' ||
  92. item.messageRemindType === 'AcceptAndNotify'
  93. "
  94. @click.stop="handleConversation('mute', dialogID)"
  95. >消息免打扰</view
  96. >
  97. <view
  98. class="conversation-options"
  99. v-if="item.messageRemindType === 'AcceptNotNotify'"
  100. @click.stop="handleConversation('notMute', dialogID)"
  101. >取消免打扰</view
  102. >
  103. </view>
  104. </view>
  105. </view>
  106. </view>
  107. </template>
  108. <script lang="ts">
  109. import { defineComponent, reactive, toRefs, watchEffect } from "vue";
  110. import { caculateTimeago } from "../../utils/date";
  111. const TUIConversationList = defineComponent({
  112. props: {
  113. conversationList: {
  114. type: Array,
  115. default: () => {
  116. return [];
  117. },
  118. },
  119. currentID: {
  120. type: String,
  121. default: () => {
  122. return "";
  123. },
  124. },
  125. },
  126. setup(props: any, ctx: any) {
  127. const obj = reactive({
  128. conversationList: [],
  129. currentID: "",
  130. isOpened: "none",
  131. currentConversation: {},
  132. dialogID: "",
  133. });
  134. watchEffect(() => {
  135. obj.conversationList = props.conversationList;
  136. obj.currentID = props.currentID;
  137. });
  138. // 处理头像
  139. const handleItemAvator = (item: any) => {
  140. let avatar = "";
  141. switch (item.type) {
  142. case uni.$TIM.TYPES.CONV_C2C:
  143. avatar =
  144. item?.userProfile?.avatar ||
  145. "https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png";
  146. break;
  147. case uni.$TIM.TYPES.CONV_GROUP:
  148. avatar =
  149. item?.groupProfile?.avatar ||
  150. "https://web.sdk.qcloud.com/component/TUIKit/assets/group_avatar.png";
  151. break;
  152. case uni.$TIM.TYPES.CONV_SYSTEM:
  153. avatar =
  154. item?.groupProfile?.avatar ||
  155. "https://web.sdk.qcloud.com/component/TUIKit/assets/group_avatar.png";
  156. break;
  157. }
  158. return avatar;
  159. };
  160. // 处理昵称
  161. const handleItemName = (item: any) => {
  162. let name = "";
  163. switch (item.type) {
  164. case uni.$TIM.TYPES.CONV_C2C:
  165. name = item?.userProfile.nick || item?.userProfile?.userID || "";
  166. break;
  167. case uni.$TIM.TYPES.CONV_GROUP:
  168. name = item.groupProfile.name || item?.groupProfile?.groupID || "";
  169. break;
  170. case uni.$TIM.TYPES.CONV_SYSTEM:
  171. name = "系统通知";
  172. break;
  173. }
  174. return name;
  175. };
  176. // 处理时间
  177. const handleItemTime = (time: number) => {
  178. if (time > 0) {
  179. return caculateTimeago(time * 1000);
  180. }
  181. return "";
  182. };
  183. // 处理lastMessage
  184. const handleItemMessage = (message: any) => {
  185. switch (message.type) {
  186. case uni.$TIM.TYPES.MSG_TEXT:
  187. return message.payload.text;
  188. default:
  189. return message.messageForShow;
  190. }
  191. };
  192. const handleGotoItem = (item: any) => {
  193. ctx.emit("handleGotoItem", item);
  194. };
  195. const handleItemLongpress = (item: any) => {
  196. //长按
  197. obj.currentConversation = item;
  198. obj.dialogID = item.conversationID;
  199. if (item.type === "C2C") {
  200. obj.currentuserID = item.userProfile.userID;
  201. } else if (item.type === "GROUP") {
  202. obj.currentuserID = item.groupProfile.groupID;
  203. }
  204. obj.conversationType = item.type;
  205. };
  206. // todo
  207. const handlerIsOpened = (item: any) => {
  208. if (item.conversationID === obj.doalogID) {
  209. return "right";
  210. } else {
  211. return "none";
  212. }
  213. };
  214. // 删除会话,后续TODO,置顶聊天,消息免打扰
  215. const handleConversation = (type: string) => {
  216. switch (type) {
  217. case "delete":
  218. uni.$TUIKit.TUIConversationServer.deleteConversation(
  219. obj.dialogID
  220. ).then((imResponse: any) => {
  221. const { conversationID } = imResponse.data;
  222. });
  223. obj.dialogID = "";
  224. break;
  225. case "ispinned":
  226. if (type === "ispinned") {
  227. const options: any = {
  228. conversationID: obj.dialogID,
  229. isPinned: true,
  230. };
  231. uni.$TUIKit.TUIConversationServer.pinConversation(options).then(
  232. (imResponse: any) => {
  233. console.log(imResponse);
  234. }
  235. );
  236. }
  237. obj.dialogID = "";
  238. break;
  239. case "dispinned":
  240. if (type === "dispinned") {
  241. const options: any = {
  242. conversationID: obj.dialogID,
  243. isPinned: false,
  244. };
  245. uni.$TUIKit.TUIConversationServer.pinConversation(options).then(
  246. (imResponse: any) => {}
  247. );
  248. }
  249. obj.dialogID = "";
  250. break;
  251. case "mute":
  252. if (type === "mute" && obj.conversationType === "C2C") {
  253. const options: any = {
  254. userIDList: [obj.currentuserID],
  255. messageRemindType: uni.$TIM.TYPES.MSG_REMIND_ACPT_NOT_NOTE,
  256. };
  257. uni.$TUIKit.TUIConversationServer.muteConversation(options).then(
  258. (imResponse: any) => {
  259. console.log(imResponse);
  260. }
  261. );
  262. } else if (type === "mute" && obj.conversationType === "GROUP") {
  263. const options: any = {
  264. groupID: obj.currentuserID,
  265. messageRemindType: uni.$TIM.TYPES.MSG_REMIND_ACPT_NOT_NOTE,
  266. };
  267. uni.$TUIKit.TUIConversationServer.muteConversation(options).then(
  268. (imResponse: any) => {
  269. console.log(imResponse);
  270. }
  271. );
  272. }
  273. obj.dialogID = "";
  274. break;
  275. case "notMute":
  276. if (type === "notMute" && obj.conversationType === "C2C") {
  277. const options: any = {
  278. userIDList: [obj.currentuserID],
  279. messageRemindType: uni.$TIM.TYPES.MSG_REMIND_ACPT_AND_NOTE,
  280. };
  281. uni.$TUIKit.TUIConversationServer.muteConversation(options).then(
  282. (imResponse: any) => {
  283. console.log(imResponse);
  284. }
  285. );
  286. } else if (type === "notMute" && obj.conversationType === "GROUP") {
  287. const options: any = {
  288. groupID: obj.currentuserID,
  289. messageRemindType: uni.$TIM.TYPES.MSG_REMIND_ACPT_AND_NOTE,
  290. };
  291. uni.$TUIKit.TUIConversationServer.muteConversation(options).then(
  292. (imResponse: any) => {
  293. console.log(imResponse);
  294. }
  295. );
  296. }
  297. obj.dialogID = "";
  298. break;
  299. }
  300. };
  301. return {
  302. ...toRefs(obj),
  303. handleGotoItem,
  304. handleItemAvator,
  305. handleItemTime,
  306. handleItemMessage,
  307. handleItemName,
  308. handleItemLongpress,
  309. handleConversation,
  310. handlerIsOpened,
  311. };
  312. },
  313. });
  314. export default TUIConversationList;
  315. </script>
  316. <style lang="scss" scoped>
  317. .TUI-conversation {
  318. font-family: PingFangSC-Regular;
  319. font-weight: 400;
  320. letter-spacing: 0;
  321. &-list {
  322. list-style: none;
  323. }
  324. &-item {
  325. position: relative;
  326. padding: 12px;
  327. display: flex;
  328. align-items: center;
  329. .left {
  330. position: relative;
  331. .num {
  332. font-family: PingFangSC-Regular;
  333. position: absolute;
  334. display: inline-block;
  335. right: -8px;
  336. top: -8px;
  337. background: red;
  338. width: 20px;
  339. height: 20px;
  340. font-size: 11px;
  341. text-align: center;
  342. line-height: 20px;
  343. border-radius: 50%;
  344. color: #ffffff;
  345. font-weight: 600;
  346. letter-spacing: 0;
  347. }
  348. .num-notNotify {
  349. position: absolute;
  350. display: inline-block;
  351. right: -4px;
  352. top: -4px;
  353. background: red;
  354. width: 11px;
  355. height: 11px;
  356. border-radius: 50%;
  357. color: #ffffff;
  358. }
  359. .avatar {
  360. width: 48px;
  361. height: 48px;
  362. border-radius: 6px;
  363. }
  364. }
  365. .content {
  366. flex: 1;
  367. padding-left: 10px;
  368. position: relative;
  369. p {
  370. width: 200px;
  371. overflow: hidden;
  372. text-overflow: ellipsis;
  373. white-space: nowrap;
  374. font-weight: 400;
  375. font-size: 14px;
  376. color: #999999;
  377. letter-spacing: 0;
  378. line-height: 19px;
  379. font-family: PingFangSC-Regular;
  380. }
  381. .conversation-line {
  382. position: absolute;
  383. display: block;
  384. left: 0;
  385. right: -12px;
  386. height: 0.5px;
  387. transform: scaleY(0.3);
  388. background: #b0b0b0;
  389. /* padding-top: 10px; */
  390. bottom: -15px;
  391. }
  392. .name {
  393. font-weight: 400;
  394. font-size: 16px;
  395. color: #000000;
  396. letter-spacing: 0;
  397. margin-bottom: 5px;
  398. font-family: PingFangSC-Regular;
  399. }
  400. &-header {
  401. display: flex;
  402. justify-content: space-between;
  403. align-items: center;
  404. label {
  405. flex: 1;
  406. font-size: 14px;
  407. color: #000000;
  408. .name {
  409. width: 110px;
  410. overflow: hidden;
  411. text-overflow: ellipsis;
  412. white-space: nowrap;
  413. }
  414. }
  415. .time {
  416. font-size: 12px;
  417. color: #b0b0b0;
  418. line-height: 16px;
  419. display: inline-block;
  420. max-width: 75px;
  421. font-weight: 400;
  422. }
  423. }
  424. &-footer {
  425. color: #999999;
  426. line-height: 16px;
  427. display: flex;
  428. .message-text {
  429. margin-left: 4px;
  430. display: block;
  431. width: 240px;
  432. overflow: hidden;
  433. text-overflow: ellipsis;
  434. white-space: nowrap;
  435. }
  436. }
  437. .item-footer {
  438. position: absolute;
  439. right: 0;
  440. top: 0;
  441. justify-items: center;
  442. display: flex;
  443. flex-direction: column;
  444. align-items: flex-end;
  445. .time {
  446. font-size: 12px;
  447. color: #b0b0b0;
  448. line-height: 16px;
  449. display: inline-block;
  450. max-width: 75px;
  451. font-weight: 400;
  452. margin-bottom: 5px;
  453. }
  454. .mute-icon {
  455. display: block;
  456. width: 20px;
  457. height: 20px;
  458. }
  459. }
  460. }
  461. .dialog-box {
  462. position: absolute;
  463. z-index: 5;
  464. background: #fff;
  465. border: 1px solid #dddddd;
  466. padding: 15px 20px;
  467. right: 15px;
  468. top: 30px;
  469. &-item {
  470. top: 60px;
  471. right: 40px;
  472. box-shadow: 0 11px 20px 0 rgb(0 0 0 / 30%);
  473. background: #ffffff;
  474. border: 1px solid #e0e0e0;
  475. box-shadow: 0 -4px 12px 0 rgb(0 0 0 / 6%);
  476. border-radius: 8px;
  477. }
  478. .conversation-options {
  479. height: 35px;
  480. font-family: PingFangSC-Regular;
  481. font-weight: 400;
  482. font-size: 14px;
  483. color: #4f4f4f;
  484. letter-spacing: 0;
  485. line-height: 35px;
  486. }
  487. }
  488. }
  489. }
  490. .selected {
  491. background: #edf0f5;
  492. }
  493. .pinned {
  494. background: #eef0f3;
  495. }
  496. </style>