ChatingList.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. <template>
  2. <scroll-view
  3. :scroll-with-animation="withAnimation"
  4. @click="click"
  5. class="scroll_view"
  6. id="scroll_view"
  7. :style="{
  8. height: '100%',
  9. backgroundImage: `url(${bgUrl})`,
  10. backgroundSize: `100% 100%`
  11. }"
  12. @scroll="throttleScroll"
  13. :scroll-top="scrollTop"
  14. scroll-y
  15. :scroll-into-view="scrollIntoView"
  16. upper-threshold="250"
  17. @scrolltoupper="scrolltoupper">
  18. <view id="scroll_wrap" style="padding-top: 10px">
  19. <u-loadmore nomoreText=" " :status="loadMoreStatus" marginTop="0" />
  20. <view v-for="(item, index) in storeHistoryMessageList" :key="item.clientMsgID">
  21. <view v-if="getTimeLine" class="time_gap_line">
  22. {{ getTimeLine(item, storeHistoryMessageList[index - 1]) }}
  23. </view>
  24. <message-item-render
  25. :mutipleCheckVisible="mutipleCheckVisible"
  26. :menuOutsideFlag="menuOutsideFlag"
  27. @messageItemRender="messageItemRender"
  28. :source="item"
  29. :isSender="item.sendID === storeCurrentUserID"
  30. @closeMenu="closeMenu"
  31. />
  32. <view v-if="sendFailedDesc" class="time_gap_line send_failed_tip">
  33. {{ sendFailedDesc(item) }}
  34. </view>
  35. </view>
  36. <view style="visibility: hidden; height: 12px" id="auchormessage_bottom_item"></view>
  37. </view>
  38. </scroll-view>
  39. </template>
  40. <script>
  41. import { mapGetters, mapActions } from 'vuex';
  42. import dayjs from 'dayjs';
  43. import { SendMessageFailedType } from '../../../../constant';
  44. import MessageItemRender from './MessageItem/index.vue';
  45. import { MessageStatus } from 'openim-uniapp-polyfill';
  46. export default {
  47. name: '',
  48. components: {
  49. MessageItemRender
  50. },
  51. props: {
  52. menuOutsideFlag: Number,
  53. mutipleCheckVisible: Boolean
  54. },
  55. data() {
  56. return {
  57. scrollIntoView: '',
  58. scrollWithAnimation: false,
  59. scrollTop: 0,
  60. old: {
  61. scrollTop: 0
  62. },
  63. initFlag: true,
  64. isOverflow: false,
  65. needScoll: true,
  66. withAnimation: false,
  67. messageLoadState: {
  68. loading: false
  69. },
  70. bgUrl: '',
  71. bgHeight: ''
  72. };
  73. },
  74. computed: {
  75. ...mapGetters(['storeCurrentConversation', 'storeHistoryMessageList', 'storeHasMoreMessage', 'storeCurrentUserID', 'storeSelfInfo']),
  76. loadMoreStatus() {
  77. if (!this.storeHasMoreMessage) {
  78. return 'nomore';
  79. }
  80. return this.messageLoadState.loading ? 'loading' : 'loadmore';
  81. },
  82. getNewMesageCount() {
  83. return this.storeHistoryMessageList.filter((message) => message.isAppend).length;
  84. },
  85. sendFailedDesc() {
  86. return (message) => {
  87. if (message.status === MessageStatus.Failed && message.errCode === SendMessageFailedType.Blacked) {
  88. return '消息已发出,但被对方拒收了';
  89. }
  90. if (message.status === MessageStatus.Failed && message.errCode === SendMessageFailedType.NotFriend) {
  91. return '对方开启了好友验证,你还不是他(她)好友。请先发送好友验证,对方验证通过后,才能聊天。';
  92. }
  93. return '';
  94. };
  95. },
  96. getTimeLine() {
  97. return (message, preMessage) => {
  98. const sendTime = message.sendTime;
  99. const preSendTime = preMessage?.sendTime;
  100. if (preSendTime && sendTime - preSendTime > 600000) {
  101. const fromNowStr = dayjs(sendTime).fromNow();
  102. if (fromNowStr.includes('秒')) {
  103. return '刚刚';
  104. }
  105. return dayjs(sendTime).calendar();
  106. }
  107. return null;
  108. };
  109. }
  110. },
  111. beforeMount() {
  112. this.updateBgUrl();
  113. },
  114. mounted() {
  115. this.loadMessageList();
  116. },
  117. methods: {
  118. ...mapActions('message', ['getHistoryMesageList']),
  119. messageItemRender(clientMsgID) {
  120. if (this.initFlag && clientMsgID === this.storeHistoryMessageList[this.storeHistoryMessageList.length - 1].clientMsgID) {
  121. this.initFlag = false;
  122. setTimeout(() => this.scrollToBottom(true), 200);
  123. // setTimeout(() => this.scrollToAnchor(`auchor${clientMsgID}`, false, true), 200)
  124. // this.checkInitHeight();
  125. // this.scrollToAnchor('message_bottom_item',true)
  126. }
  127. },
  128. async loadMessageList(isLoadMore = false) {
  129. this.messageLoadState.loading = true;
  130. const lastMsgID = this.storeHistoryMessageList[0]?.clientMsgID;
  131. const options = {
  132. conversationID: this.storeCurrentConversation.conversationID,
  133. count: 20,
  134. startClientMsgID: this.storeHistoryMessageList[0]?.clientMsgID ?? '',
  135. viewType: 0
  136. };
  137. try {
  138. const { emptyFlag } = await this.getHistoryMesageList(options);
  139. if (emptyFlag) {
  140. this.$emit('initSuccess');
  141. }
  142. } catch (e) {
  143. console.log(e);
  144. }
  145. this.$nextTick(function () {
  146. if (isLoadMore && lastMsgID) {
  147. this.scrollToAnchor(`auchor${lastMsgID}`);
  148. }
  149. this.messageLoadState.loading = false;
  150. });
  151. },
  152. click(e) {
  153. this.$emit('click', e);
  154. },
  155. onScroll(event) {
  156. const { scrollHeight, scrollTop } = event.target;
  157. this.old.scrollTop = scrollTop;
  158. this.needScoll = scrollHeight - scrollTop < uni.getWindowInfo().windowHeight * 1.2;
  159. this.$emit('closeMenu', event);
  160. },
  161. throttleScroll(event) {
  162. uni.$u.throttle(() => this.onScroll(event), 150);
  163. },
  164. scrolltoupper() {
  165. if (!this.messageLoadState.loading && this.storeHasMoreMessage) {
  166. this.loadMessageList(true);
  167. }
  168. },
  169. scrollToBottom(isInit = false, isRecv = false) {
  170. if (isRecv && !this.needScoll) {
  171. return;
  172. }
  173. if (!isInit) {
  174. this.withAnimation = true;
  175. setTimeout(() => (this.withAnimation = false), 100);
  176. }
  177. this.$nextTick(() => {
  178. uni.createSelectorQuery()
  179. .in(this)
  180. .select('#scroll_wrap')
  181. .boundingClientRect((res) => {
  182. // let top = res.height - this.scrollViewHeight;
  183. // if (top > 0) {
  184. this.scrollTop = this.old.scrollTop;
  185. this.$nextTick(() => (this.scrollTop = res.height));
  186. if (isInit) {
  187. this.$emit('initSuccess');
  188. }
  189. // }
  190. })
  191. .exec();
  192. });
  193. },
  194. scrollToAnchor(auchor) {
  195. this.$nextTick(function () {
  196. this.scrollIntoView = auchor;
  197. });
  198. },
  199. checkInitHeight() {
  200. this.getEl('#scroll_view').then(({ height }) => {
  201. this.bgHeight = `${height}px`;
  202. });
  203. },
  204. getEl(el) {
  205. return new Promise((resolve) => {
  206. const query = uni.createSelectorQuery().in(this);
  207. query
  208. .select(el)
  209. .boundingClientRect((data) => {
  210. resolve(data);
  211. })
  212. .exec();
  213. });
  214. },
  215. closeMenu() {
  216. this.$emit('closeMenu');
  217. },
  218. updateBgUrl() {
  219. const bgMap = uni.getStorageSync('IMBgMap') || {};
  220. this.bgUrl = bgMap[this.$store.getters.storeCurrentConversation.conversationID] || '';
  221. }
  222. }
  223. };
  224. </script>
  225. <style scoped>
  226. .scroll_view {
  227. flex: 1;
  228. background-repeat: no-repeat;
  229. position: relative;
  230. }
  231. .watermark-view {
  232. width: 100%;
  233. height: 100%;
  234. position: fixed;
  235. }
  236. .watermark {
  237. font-size: 16px; /* 水印文字大小 */
  238. color: #f0f2f6; /* 水印文字颜色,使用透明度控制可见度 */
  239. position: absolute; /* 水印相对定位 */
  240. transform: rotate(-45deg);
  241. pointer-events: none; /* 防止水印文字干扰交互 */
  242. }
  243. .uni-scroll-view {
  244. position: relative;
  245. }
  246. .new_message_flag {
  247. position: sticky;
  248. background: #ffffff;
  249. box-shadow: 0px 3px 8px 0px rgba(0, 0, 0, 0.1);
  250. border-radius: 14px;
  251. padding: 4px 8px;
  252. display: flex;
  253. justify-content: center;
  254. align-items: center;
  255. bottom: 12px;
  256. left: 50%;
  257. transform: translateX(-50%);
  258. width: fit-content;
  259. font-size: 24rpx;
  260. color: #006aff;
  261. }
  262. .time_gap_line {
  263. position: relative;
  264. padding: 0 10vw 12rpx;
  265. text-align: center;
  266. // font-size: 24rpx;
  267. font-size: 0.93rem;
  268. color: #999;
  269. }
  270. .fade-leave,
  271. .fade-enter-to {
  272. opacity: 1;
  273. }
  274. .fade-leave-active,
  275. .fade-enter-active {
  276. transition: all 0.3s;
  277. }
  278. .fade-leave-to,
  279. .fade-enter {
  280. opacity: 0;
  281. }
  282. </style>