| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- <template>
- <!-- 使用mescroll组件替代原生scroll-view -->
- <mescroll-uni
- ref="mescrollRef"
- @init="mescrollInit"
- @down="downCallback"
- @up="upCallback"
- :height="scrollHeight"
- :down="downOption"
- :up="upOption"
- :fixed="false"
- id="scroll_view"
- :style="{
- backgroundImage: `url(${bgUrl})`,
- backgroundSize: `100% 100%`
- }"
- >
- <view id="scroll_wrap">
- <!-- 消息列表 -->
- <view
- v-for="(item, index) in storeHistoryMessageList"
- :key="item.clientMsgID"
- :id="`msg_${item.clientMsgID}`"
- :data-index="index"
- class="message-wrapper">
-
- <view v-if="shouldShowTimeLine(item, index)" class="time_gap_line">
- {{ formatMessageTime(item, storeHistoryMessageList[index - 1]) }}
- </view>
-
- <message-item-render
- :mutipleCheckVisible="mutipleCheckVisible"
- :menuOutsideFlag="menuOutsideFlag"
- @messageItemRender="messageItemRender"
- :source="item"
- :isSender="item.sendID === storeCurrentUserID"
- @closeMenu="closeMenu"/>
-
- <view v-if="getFailedMessage(item)" class="time_gap_line send_failed_tip">
- {{ getFailedMessage(item) }}
- </view>
- </view>
-
- <!-- 底部占位 -->
- <view style="height: 20px;" id="bottom_anchor"></view>
- </view>
- </mescroll-uni>
- </template>
- <script>
- import { mapGetters, mapActions } from 'vuex';
- import dayjs from 'dayjs';
- import { SendMessageFailedType } from '../../../../constant';
- import MessageItemRender from './MessageItem/index.vue';
- import { MessageStatus } from 'openim-uniapp-polyfill';
- import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
- import MescrollMoreItemMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more-item.js";
- export default {
- mixins: [ MescrollMixin,MescrollMoreItemMixin ],
- name: 'ChatingList',
- components: {
- MessageItemRender,
-
- },
- props: {
- menuOutsideFlag: Number,
- mutipleCheckVisible: Boolean
- },
- data() {
- return {
- scrollHeight: "100vh",
- bgUrl: '',
- mescroll: null,
- downOption: {
- auto: false, // 不自动下拉刷新
- textInOffset: '下拉刷新',
- textOutOffset: '释放更新',
- textLoading: '加载中...',
- offset: 80
- },
- upOption: {
- auto: false, // 不自动上拉加载
- page: {
- num: 0,
- size: 20
- },
- noMoreSize: 5,
- textNoMore: '-- 没有更多消息了 --',
- empty: {
- tip: '暂无消息'
- }
- },
- // 滚动位置管理
- scrollPosition: {
- lastScrollTop: 0,
- lastScrollTime: 0
- },
-
- // 初始化状态
- isInitialized: false
- };
- },
-
- computed: {
- ...mapGetters([
- 'storeCurrentConversation',
- 'storeHistoryMessageList',
- 'storeHasMoreMessage',
- 'storeCurrentUserID'
- ]),
- },
-
- mounted() {
- this.initializeList();
- this.updateBgUrl();
-
- // 计算滚动区域高度
- this.calculateScrollHeight();
-
- // 监听窗口变化
- uni.onWindowResize(this.calculateScrollHeight);
- },
-
- beforeDestroy() {
- if (this.mescroll) {
- //this.mescroll.destroy();
- }
- },
-
- methods: {
- ...mapActions('message', ['getHistoryMesageList']),
-
- // 计算滚动区域高度
- calculateScrollHeight() {
- const systemInfo = uni.getSystemInfoSync();
- const windowHeight = systemInfo.windowHeight;
-
- uni.createSelectorQuery().in(this)
- .select('#scroll_view')
- .boundingClientRect(res => {
- if (res) {
- const top = res.top;
- this.scrollHeight = `${windowHeight - top}px`;
- }
- })
- .exec();
- },
-
- // Mescroll初始化
- mescrollInit(mescroll) {
- this.mescroll = mescroll;
- },
-
- // 下拉刷新回调
- downCallback() {
- // 聊天界面通常不需要下拉刷新
- this.mescroll.endSuccess();
- },
-
- // 上拉加载回调
- async upCallback() {
- if (!this.storeHasMoreMessage) {
- this.mescroll.endSuccess(0, false);
- return;
- }
-
- try {
- const options = {
- conversationID: this.storeCurrentConversation.conversationID,
- count: this.upOption.page.size,
- startClientMsgID: this.storeHistoryMessageList[0]?.clientMsgID ?? '',
- viewType: 0
- };
-
- // 记录当前第一条消息ID和位置
- const firstMsgId = this.storeHistoryMessageList[0]?.clientMsgID;
- let firstMsgTop = 0;
-
- if (firstMsgId) {
- const rect = await this.getElementPosition(`#msg_${firstMsgId}`);
- if (rect) {
- firstMsgTop = rect.top;
- }
- }
-
- // 加载历史消息
- await this.getHistoryMesageList(options);
-
- // 等待DOM更新
- this.$nextTick(() => {
- if (firstMsgId) {
- // 计算新位置
- this.getElementPosition(`#msg_${firstMsgId}`).then(newRect => {
- if (newRect) {
- // 计算滚动偏移量
- const offset = newRect.top - firstMsgTop;
-
- // 平滑滚动到原位置
- if (offset !== 0) {
- this.mescroll.scrollTo(this.mescroll.getScrollTop() + offset, 300);
- }
- }
-
- // 结束加载
- this.mescroll.endSuccess(this.storeHasMoreMessage ? this.upOption.page.size : 0, this.storeHasMoreMessage);
- });
- } else {
- this.mescroll.endSuccess(this.storeHasMoreMessage ? this.upOption.page.size : 0, this.storeHasMoreMessage);
- }
- });
- } catch (error) {
- console.error('加载历史消息失败:', error);
- this.mescroll.endErr();
- }
- },
-
- // 获取元素位置
- getElementPosition(selector) {
- return new Promise(resolve => {
- uni.createSelectorQuery().in(this)
- .select(selector)
- .boundingClientRect(res => resolve(res))
- .exec();
- });
- },
-
- // 初始化列表
- async initializeList() {
- try {
- const options = {
- conversationID: this.storeCurrentConversation.conversationID,
- count: this.upOption.page.size,
- startClientMsgID: '',
- viewType: 0
- };
-
- await this.getHistoryMesageList(options);
-
- this.$nextTick(() => {
- this.scrollToBottom(true);
- this.isInitialized = true;
- this.$emit('initSuccess');
-
- // 初始化mescroll
- if (this.mescroll) {
- this.mescroll.endSuccess(this.upOption.page.size, this.storeHasMoreMessage);
- }
- });
- } catch (error) {
- console.error('初始化消息列表失败:', error);
- }
- },
-
- // 消息项渲染完成
- messageItemRender(clientMsgID) {
- if (!this.isInitialized && clientMsgID === this.storeHistoryMessageList[this.storeHistoryMessageList.length - 1]?.clientMsgID) {
- this.$nextTick(() => {
- this.scrollToBottom(true);
- this.isInitialized = true;
- this.$emit('initSuccess');
- });
- }
- },
-
- // 滚动到底部
- scrollToBottom(instantly = false) {
- if (this.mescroll) {
- if (instantly) {
- this.mescroll.scrollTo(999999, 0);
- } else {
- this.mescroll.scrollToBottom(300);
- }
- }
- },
-
- // 时间线相关方法
- shouldShowTimeLine(message, index) {
- if (index === 0) return false;
-
- const preMessage = this.storeHistoryMessageList[index - 1];
- return this.formatMessageTime(message, preMessage) !== null;
- },
-
- formatMessageTime(message, preMessage) {
- if (!preMessage) return null;
-
- const sendTime = message.sendTime;
- const preSendTime = preMessage.sendTime;
-
- // 10分钟内不显示时间
- if (sendTime - preSendTime <= 600000) return null;
-
- const messageDate = dayjs(sendTime);
- const now = dayjs();
-
- if (messageDate.isSame(now, 'day')) {
- return messageDate.format('HH:mm');
- } else if (messageDate.isSame(now.subtract(1, 'day'), 'day')) {
- return '昨天 ' + messageDate.format('HH:mm');
- } else if (messageDate.isSame(now, 'year')) {
- return messageDate.format('MM-DD HH:mm');
- } else {
- return messageDate.format('YYYY-MM-DD HH:mm');
- }
- },
-
- // 失败消息处理
- getFailedMessage(message) {
- if (message.status === MessageStatus.Failed) {
- if (message.errCode === SendMessageFailedType.Blacked) {
- return '消息已发出,但被对方拒收了';
- }
- if (message.errCode === SendMessageFailedType.NotFriend) {
- return '对方开启了好友验证,你还不是他(她)好友。请先发送好友验证,对方验证通过后,才能聊天。';
- }
- }
- return '';
- },
-
- // 点击事件
- click(e) {
- this.$emit('click', e);
- },
-
- // 关闭菜单
- closeMenu() {
- this.$emit('closeMenu');
- },
-
- // 更新背景
- updateBgUrl() {
- const bgMap = uni.getStorageSync('IMBgMap') || {};
- this.bgUrl = bgMap[this.$store.getters.storeCurrentConversation.conversationID] || '';
- },
-
- // 外部接口:滚动到底部
- scrollToBottomExternal(isRecv = false) {
- if (this.mescroll) {
- // 如果用户已经向上滚动较多,接收新消息时不自动滚动到底部
- const scrollTop = this.mescroll.getScrollTop();
- const scrollHeight = this.mescroll.getScrollHeight();
- if (isRecv && scrollTop < (scrollHeight * 0.8)) {
- return;
- }
-
- this.scrollToBottom();
- }
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- #scroll_view {
- flex: 1;
- background-repeat: no-repeat;
- position: relative;
- overflow: hidden;
- }
- #scroll_wrap {
- min-height: 100%;
- position: relative;
- padding-bottom: 20rpx;
- }
- .message-wrapper {
- position: relative;
- }
- .time_gap_line {
- padding: 16rpx 0;
- text-align: center;
- font-size: 16px;
- color: #999;
- background: transparent;
- }
- .send_failed_tip {
- color: #ff6b6b;
- font-size: 16px;
- padding: 8rpx 0;
- text-align: center;
- }
- /* 自定义mescroll样式 */
- ::v-deep .mescroll-downwarp .downwarp-content {
- padding: 16rpx 0;
- font-size: 26rpx;
- color: #888;
- }
- ::v-deep .mescroll-upwarp {
- padding: 20rpx 0;
- .upwarp-progress {
- width: 40rpx;
- height: 40rpx;
- border-color: #07C160;
- }
-
- .upwarp-nodata {
- font-size: 28rpx;
- color: #999;
- }
- }
- /* 隐藏原生滚动条 */
- ::-webkit-scrollbar {
- display: none;
- }
- </style>
|