完成供需消息对话

dev
lynn 4 months ago
parent 15115d6a85
commit 71593c5df8

@ -42,6 +42,9 @@ let apiApp = {
supplyDemandSave: '/api/mobile/supply-demand/save',
supplyDemandList: '/api/mobile/supply-demand/index',
supplyDemandDetail: '/api/mobile/supply-demand/detail',
supplyDemandMessageList: '/api/mobile/supply-demand/message-list',
supplyDemandSendMessage: '/api/mobile/supply-demand/send-message',
supplyDemandDialogues: '/api/mobile/supply-demand/dialogues',
// 图书
bookIndex: '/api/mobile/book/index',
@ -99,6 +102,9 @@ const install = (Vue, vm) => {
let supplyDemandSave = (params = {}) => vm.$u.post(apiApp.supplyDemandSave, params);
let supplyDemandList = (params = {}) => vm.$u.get(apiApp.supplyDemandList, params);
let supplyDemandDetail = (params = {}) => vm.$u.get(apiApp.supplyDemandDetail, params);
let supplyDemandMessageList = (params = {}) => vm.$u.get(apiApp.supplyDemandMessageList, params);
let supplyDemandSendMessage = (params = {}) => vm.$u.post(apiApp.supplyDemandSendMessage, params);
let supplyDemandDialogues = (params = {}) => vm.$u.get(apiApp.supplyDemandDialogues, params);
// 图书
let bookIndex = (params = {}) => vm.$u.get(apiApp.bookIndex, params);
@ -148,6 +154,9 @@ const install = (Vue, vm) => {
supplyDemandSave,
supplyDemandList,
supplyDemandDetail,
supplyDemandMessageList,
supplyDemandSendMessage,
supplyDemandDialogues,
// 图书
bookIndex,

@ -1,13 +1,32 @@
<template>
<view class="chat-window">
<scroll-view :scroll-y="true" class="message-list" :scroll-into-view="lastMessageId">
<view v-for="message in messages" :key="message.id" :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>{{ message.content }}</text>
<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>
<u-avatar v-if="message.from === 'me'" :src="myAvatar" size="80"></u-avatar>
</view>
</scroll-view>
@ -22,64 +41,208 @@
export default {
data() {
return {
myAvatar: 'https://via.placeholder.com/80',
theirAvatar: 'https://via.placeholder.com/80',
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 ? decodeURIComponent(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: `${userId} 的对话`
title: title
});
this.fetchMessages(userId);
this.fetchMessages(supplyDemandId);
},
methods: {
fetchMessages(userId) {
//
this.messages = [{
id: 1,
from: 'them',
content: '你好!很高兴认识你。'
},
{
id: 2,
from: 'me',
content: '你好!我也是。'
},
{
id: 3,
from: 'them',
content: '我们下周一开会讨论一下那个项目吧,你觉得怎么样?'
},
{
id: 4,
from: 'me',
content: '好啊,没问题。'
},
];
this.scrollToBottom();
},
fetchMessages(supplyDemandId, page = 1, append = false) {
if (this.loading || (!append && !this.hasMore)) return;
this.loading = true;
// 使API
this.$u.api.supplyDemandMessageList({
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: this.messages.length + 1,
id: tempId,
from: 'me',
content: this.newMessage.trim()
content: messageContent,
timestamp: new Date().toISOString(),
user: this.$store.state.vuex_user,
to_user: null
};
//
this.messages.push(newMsg);
this.newMessage = '';
this.scrollToBottom();
// API
this.$u.api.supplyDemandSendMessage({
supply_demand_id: this.supplyDemandId,
content: messageContent,
to_user_id: this.theirUserId
}).then(res => {
console.log('发送消息成功:', res);
// IDID
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);
}
}
}
}
@ -133,6 +296,47 @@
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 {

@ -1,7 +1,10 @@
<template>
<view class="chat-list-container">
<view v-for="chat in chatList" :key="chat.id" class="chat-item" @click="goToChat(chat.user.id)">
<u-avatar :src="chat.user.avatar" size="90"></u-avatar>
<view v-if="chatList.length === 0" class="empty-state">
<u-empty text="暂无聊天记录" mode="chat"></u-empty>
</view>
<view v-else v-for="chat in chatList" :key="chat.id" class="chat-item" @click="goToChat(chat.id)">
<u-avatar :src="chat.user.avatar" size="90" loading-icon="account"></u-avatar>
<view class="chat-content">
<view class="content-header">
<text class="user-name">{{ chat.user.name }}</text>
@ -20,53 +23,86 @@
export default {
data() {
return {
chatList: [{
id: 1,
user: {
id: 'user001',
name: '张云',
avatar: 'https://via.placeholder.com/90'
},
lastMessage: {
content: '好的,我们下周一开会讨论一下这个项目。',
time: '昨天'
},
unreadCount: 1
},
{
id: 2,
user: {
id: 'user002',
name: '李经理',
avatar: 'https://via.placeholder.com/90'
},
lastMessage: {
content: '资料已经发你邮箱了,请查收。',
time: '14:28'
},
unreadCount: 0
},
{
id: 3,
user: {
id: 'user003',
name: '王顾问',
avatar: 'https://via.placeholder.com/90'
},
lastMessage: {
content: '[图片]',
time: '周一'
},
unreadCount: 3
}
]
chatList: []
}
},
onLoad() {
this.fetchDialogues();
},
methods: {
goToChat(userId) {
uni.navigateTo({
url: `/packages/chat/chatWindow?userId=${userId}`
})
fetchDialogues() {
this.$u.api.supplyDemandDialogues().then(res => {
console.log('获取会话列表成功:', res);
if (res.dialogue && res.dialogue.data) {
this.chatList = res.dialogue.data.map(item => {
//
const currentUserId = this.$store.state.vuex_user.id;
const otherUser = item.user_id === currentUserId ? item.to_user : item.user;
return {
id: item.id,
user: {
id: otherUser.id,
name: otherUser.nickname || otherUser.name || otherUser.username || `用户${otherUser.id}`,
avatar: otherUser.headimgurl || ''
},
lastMessage: {
content: item.last_content || '暂无消息',
time: this.formatTime(item.last_datetime || item.updated_at)
},
unreadCount: 0, // 0
supplyDemandId: item.supply_demand_id,
supplyDemandTitle: item.supply_demand?.title || ''
};
});
}
}).catch(err => {
console.error('获取会话列表失败:', err);
});
},
goToChat(id) {
//
const chat = this.chatList.find(item => item.id === id);
if (chat) {
uni.navigateTo({
url: `/packages/chat/chatWindow?userId=${chat.user.id}&userName=${chat.user.name}&supplyDemandId=${chat.supplyDemandId}`
});
}
},
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'
})}`;
}
//
if (diff < 7 * 24 * 60 * 60 * 1000) {
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
return weekdays[date.getDay()];
}
//
return date.toLocaleDateString('zh-CN', {
month: '2-digit',
day: '2-digit'
});
}
}
}
@ -120,4 +156,11 @@
overflow: hidden;
text-overflow: ellipsis;
}
.empty-state {
padding: 100rpx 0;
display: flex;
justify-content: center;
align-items: center;
}
</style>

@ -18,8 +18,8 @@
<u-line color="#e8e8e8" margin="20rpx 0" />
<view class="item-footer">
<view class="user-info">
<u-avatar :src="item.user_avatar || 'https://via.placeholder.com/60'" size="60"></u-avatar>
<text class="user-name">{{ item.user_name || '匿名用户' }}</text>
<u-avatar :src="item.user.headimgurl || ''" size="60"></u-avatar>
<text class="user-name">{{ item.user.name || '匿名用户' }}</text>
</view>
<view class="actions">
<view class="view-button view-button-check" @click="goToDetail(item.id)">
@ -169,7 +169,7 @@
goToChat(item) {
//
uni.navigateTo({
url: `/packages/chat/chatWindow?userId=${item.user_id}&userName=${item.user_name}`
url: `/packages/chat/chatWindow?userId=${item.user_id}&userName=${item.user.name}&supplyDemandId=${item.id}`
})
}
}

Loading…
Cancel
Save