You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

363 lines
9.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="chat-window">
<scroll-view
:scroll-y="true"
class="message-list"
:scroll-into-view="lastMessageId"
@scrolltoupper="onScrollToUpper"
:scroll-with-animation="false"
:upper-threshold="50">
<!-- 加载更多提示 -->
<view v-if="loading" class="loading-more">
<u-loading mode="circle" size="20"></u-loading>
<text class="loading-text">加载中...</text>
</view>
<view v-for="(message, index) in messages" :key="message.id">
<!-- 时间分割线 -->
<view v-if="shouldShowTimeDivider(message, index)" class="time-divider">
<text class="time-text">{{ formatTime(message.timestamp) }}</text>
</view>
<!-- 消息内容 -->
<view :id="'msg-' + message.id"
:class="['message-item', message.from === 'me' ? 'my-message' : 'their-message']">
<u-avatar v-if="message.from !== 'me'" :src="theirAvatar" size="80"></u-avatar>
<view class="message-content">
<text class="message-text">{{ message.content }}</text>
</view>
<u-avatar v-if="message.from === 'me'" :src="myAvatar" size="80"></u-avatar>
</view>
</view>
</scroll-view>
<view class="input-area">
<u-input v-model="newMessage" placeholder="输入消息..." class="input-field" :border="false" />
<u-button type="primary" size="medium" @click="sendMessage" class="send-button"></u-button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
myAvatar: '',
theirAvatar: '',
newMessage: '',
messages: [],
lastMessageId: '',
supplyDemandId: '',
theirUserId: '',
loading: false,
currentPage: 1,
hasMore: true,
pageSize: 20
}
},
onLoad(options) {
const userId = options.userId;
const userName = options.userName;
const supplyDemandId = options.supplyDemandId;
const supplyDemandTitle = options.supplyDemandTitle;
// 保存供应需求ID
this.supplyDemandId = supplyDemandId;
this.theirUserId = userId;
// 调试:输出当前用户信息
console.log('当前用户信息:', this.$store.state.vuex_user);
// 动态设置导航栏标题
const title = supplyDemandTitle ? `${supplyDemandTitle} - ${userName || userId}` : `${userName || userId} 的对话`;
uni.setNavigationBarTitle({
title: title
});
this.fetchMessages(supplyDemandId);
},
methods: {
fetchMessages(supplyDemandId, page = 1, append = false) {
if (this.loading || (!append && !this.hasMore)) return;
this.loading = true;
// 使用正确的API调用方式
this.$u.api.supplyDemandMessageList({
to_user_id: this.theirUserId,
supply_demand_id: supplyDemandId,
page: page,
per_page: this.pageSize
}).then(res => {
console.log('获取消息列表成功:', res);
// 处理返回的消息数据,数据在 res.message.data 中
if (res.message && res.message.data) {
const newMessages = res.message.data.map((item, index) => ({
id: item.id,
from: item.user_id === this.$store.state.vuex_user.id ? 'me' : 'them',
content: item.content,
timestamp: item.created_at,
user: item.user,
to_user: item.to_user
}));
if (append) {
// 上拉加载更多,老消息插入到前面
this.messages.unshift(...newMessages);
} else {
// 首次加载或刷新,按时间正序排列(老消息在前,新消息在后)
this.messages = newMessages.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
}
// 检查是否还有更多数据
this.hasMore = res.message.current_page < res.message.last_page;
this.currentPage = res.message.current_page;
// 设置头像
if (this.messages.length > 0) {
const firstMessage = this.messages[0];
if (firstMessage.from === 'me') {
this.myAvatar = firstMessage.user?.headimgurl || '';
this.theirAvatar = firstMessage.to_user?.headimgurl || '';
} else {
this.myAvatar = firstMessage.to_user?.headimgurl || '';
this.theirAvatar = firstMessage.user?.headimgurl || '';
}
// 更新导航栏标题,使用实际的用户名
const theirUser = firstMessage.from === 'me' ? firstMessage.to_user : firstMessage.user;
if (theirUser) {
const displayName = theirUser.nickname || theirUser.name || `用户${theirUser.id}`;
uni.setNavigationBarTitle({
title: `${displayName} 的对话`
});
}
}
if (!append) {
this.scrollToBottom();
}
}
}).catch(err => {
console.error('获取消息列表失败:', err);
}).finally(() => {
this.loading = false;
});
},
sendMessage() {
if (this.newMessage.trim() === '') return;
const messageContent = this.newMessage.trim();
this.newMessage = '';
// 生成临时ID使用时间戳确保唯一性
const tempId = Date.now();
// 先添加到本地消息列表
const newMsg = {
id: tempId,
from: 'me',
content: messageContent,
timestamp: new Date().toISOString(),
user: this.$store.state.vuex_user,
to_user: null
};
// 新消息添加到列表末尾(最新消息)
this.messages.push(newMsg);
this.scrollToBottom();
// 调用发送消息API
this.$u.api.supplyDemandSendMessage({
supply_demand_id: this.supplyDemandId,
content: messageContent,
to_user_id: this.theirUserId
}).then(res => {
console.log('发送消息成功:', res);
// 发送成功后更新临时消息的ID为服务器返回的真实ID
if (res && res.id) {
const lastMessage = this.messages[this.messages.length - 1];
if (lastMessage.id === tempId) {
lastMessage.id = res.id;
}
}
}).catch(err => {
console.error('发送消息失败:', err);
// 发送失败时,可以从消息列表中移除临时消息
this.messages = this.messages.filter(msg => msg.id !== tempId);
// 可选:提示用户发送失败
uni.showToast({
title: '发送失败,请重试',
icon: 'none'
});
});
},
scrollToBottom() {
this.$nextTick(() => {
if (this.messages.length > 0) {
// 滚动到最新消息(列表末尾)
this.lastMessageId = `msg-${this.messages[this.messages.length - 1].id}`;
}
})
},
formatTime(timestamp) {
if (!timestamp) return '';
const date = new Date(timestamp);
const now = new Date();
const diff = now - date;
// 今天的消息只显示时间
if (diff < 24 * 60 * 60 * 1000 && date.getDate() === now.getDate()) {
return date.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
});
}
// 昨天的消息显示"昨天 + 时间"
if (diff < 48 * 60 * 60 * 1000 && date.getDate() === now.getDate() - 1) {
return `昨天 ${date.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
})}`;
}
// 其他显示完整日期和时间
return date.toLocaleString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
},
shouldShowTimeDivider(message, index) {
// 第一条消息总是显示时间
if (index === 0) return true;
const currentTime = new Date(message.timestamp);
const prevTime = new Date(this.messages[index - 1].timestamp);
// 如果时间间隔超过5分钟显示时间分割线
const timeDiff = Math.abs(currentTime - prevTime);
return timeDiff > 5 * 60 * 1000; // 5分钟
},
onScrollToUpper() {
// 上拉加载更多历史消息
if (this.hasMore && !this.loading) {
this.fetchMessages(this.supplyDemandId, this.currentPage + 1, true);
}
}
}
}
</script>
<style lang="scss" scoped>
.chat-window {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
}
.message-list {
flex: 1;
padding: 20rpx;
overflow-y: auto;
box-sizing: border-box;
}
.message-item {
display: flex;
margin-bottom: 30rpx;
align-items: flex-start;
}
.my-message {
justify-content: flex-end;
.message-content {
background-color: #409eff;
color: #fff;
border-radius: 20rpx 20rpx 4rpx 20rpx;
margin-right: 20rpx;
}
}
.their-message {
justify-content: flex-start;
.message-content {
background-color: #fff;
color: #333;
border-radius: 20rpx 20rpx 20rpx 4rpx;
margin-left: 20rpx;
}
}
.message-content {
padding: 20rpx 25rpx;
max-width: 70%;
font-size: 30rpx;
line-height: 1.6;
display: flex;
flex-direction: column;
}
.message-text {
margin-bottom: 8rpx;
}
.message-time {
font-size: 22rpx;
color: #999;
opacity: 0.8;
}
.time-divider {
display: flex;
justify-content: center;
align-items: center;
margin: 30rpx 0 20rpx 0;
}
.time-text {
background-color: rgba(0, 0, 0, 0.1);
color: #999;
font-size: 24rpx;
padding: 8rpx 16rpx;
border-radius: 20rpx;
text-align: center;
}
.loading-more {
display: flex;
justify-content: center;
align-items: center;
padding: 20rpx 0;
}
.loading-text {
font-size: 24rpx;
color: #999;
margin-left: 10rpx;
}
.input-area {
display: flex;
align-items: center;
padding: 20rpx;
background-color: #fff;
border-top: 1rpx solid #e0e0e0;
}
.input-field {
flex: 1;
margin-right: 20rpx;
background-color: #f5f5f5;
border-radius: 12rpx;
padding: 0 20rpx;
}
.send-button {
height: 70rpx;
line-height: 70rpx;
}
</style>