<template>
<div class="app-container">
<!-- 搜索区域 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
label-width="68px">
<el-form-item label="接收人" prop="receiverId">
<el-select v-model="queryParams.receiverId" placeholder="请选择" clearable filterable style="width: 150px">
<el-option v-for="user in userList" :key="user.id" :label="user.nickName" :value="user.id" />
</el-select>
</el-form-item>
<el-form-item label="发送时间" prop="createdAt">
<el-date-picker v-model="queryParams.createdAt" type="daterange" range-separator="至"
start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['mod:im_mes:add']">发起聊天</el-button>
</el-form-item>
</el-form>
<div class="chat-container">
<!-- 联系人列表 -->
<div class="contact-list">
<!-- 当前用户信息 -->
<div class="current-user">
<img :src="currentUser.avatar || options.img" class="avatar" />
<div class="user-info">
<span class="dept-name">{{ currentUser.deptName }}</span>
<span class="user-name">{{ currentUser.nickName }}</span>
</div>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</div>
<div class="contact-header">会话列表</div>
<div v-for="contact in contactList" :key="contact.id" class="contact-item"
:class="{ active: activeContact === contact.id }" @click="selectContact(contact)">
<img v-if="contact.avatar" :src="getFullAvatarUrl(contact.avatar)" class="avatar-img" />
<div v-else class="avatar">{{ contact.nickName ? contact.nickName.substring(0, 1) : '' }}</div>
<div class="contact-info">
<div class="name">{{ contact.nickName }}({{ currentUser.dept.deptName }})</div>
<div class="last-msg">{{ contact.lastMsg }}</div>
</div>
<!-- <div class="time">{{ contact.time }}</div>-->
</div>
</div>
<!-- 聊天区域 -->
<div class="chat-area" v-if="activeContact">
<div class="chat-header">
<div class="current-contact">
<div class="avatar">{{ currentContact.nickName ? currentContact.nickName.substring(0, 1) : '' }}
</div>
<div class="name">{{ currentContact.nickName }}</div>
</div>
</div>
<div class="message-container" ref="messageContainer">
<div v-for="(message, index) in filteredMessages" :key="index"
:class="['message', message.senderId === currentUser.userId ? 'sent' : 'received']">
<div class="avatar" v-if="message.senderId !== currentUser.userId">
{{ getContactName(message.senderId).substring(0, 1) }}
</div>
<div class="message-content">
<!-- 文本消息 -->
<div v-if="message.mediaType === 1" class="message-text">{{ message.content }}</div>
<!-- 图片消息 -->
<div v-else-if="message.mediaType === 2" class="message-image">
<el-image :src="getFileUrl(message.content)"
:preview-src-list="[getFileUrl(message.content)]" fit="cover"
style="max-width: 200px; max-height: 200px; border-radius: 4px;"></el-image>
</div>
<!-- 视频消息 -->
<div v-else-if="message.mediaType === 3" class="message-video">
<video :src="getFileUrl(message.content)" controls
style="max-width: 200px; max-height: 200px; border-radius: 4px;">您的浏览器不支持视频播放</video>
</div>
<!-- 文件消息 -->
<div v-else-if="message.mediaType === 4" class="message-file">
<div class="file-item">
<i class="el-icon-document" style="font-size: 24px; margin-right: 8px;"></i>
<div class="file-info">
<div class="file-name">{{ getFileName(message.content) }}</div>
<div class="file-size">{{ formatFileSize(message.fileSize) }}</div>
</div>
<el-button type="text" @click="downloadFile(message.content)"
style="margin-left: 10px;">下载</el-button>
</div>
</div>
<!-- 其他类型消息 -->
<div v-else class="message-text">{{ message.content }}</div>
<div class="message-time">{{ formatTime(message.createdAt) }}</div>
</div>
<div class="avatar" v-if="message.senderId === currentUser.userId">
{{ currentUser.nickName.substring(0, 1) }}
</div>
</div>
</div>
<div class="input-area">
<!-- 文件上传区域 -->
<div class="upload-actions">
<el-upload :action="uploadFileUrl" :before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess" :show-file-list="false" :headers="headers"
:data="{ type: 'image' }">
<el-button size="mini" icon="el-icon-picture" title="上传图片"></el-button>
</el-upload>
<el-upload :action="uploadFileUrl" :before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess" :show-file-list="false" :headers="headers"
:data="{ type: 'video' }">
<el-button size="mini" icon="el-icon-video-camera" title="上传视频"></el-button>
</el-upload>
<el-upload :action="uploadFileUrl" :before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess" :show-file-list="false" :headers="headers"
:data="{ type: 'file' }">
<el-button size="mini" icon="el-icon-document" title="上传文件"></el-button>
</el-upload>
</div>
<el-input type="textarea" :rows="3" placeholder="请输入消息..." v-model="newMessage"
@keydown.enter.native="sendMessage" resize="none">
</el-input>
<div class="input-actions">
<el-button type="primary" size="small" @click="sendMessage">发送</el-button>
</div>
</div>
</div>
<div class="no-chat" v-else>
<div class="no-chat-tip">
<i class="el-icon-chat-line-round"></i>
<p>请选择左侧聊天/或在组织中选择人员聊天</p>
</div>
</div>
</div>
<!-- 发起聊天对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="接收人" prop="receiverId">
<el-select v-model="form.receiverId" placeholder="请选择接收人">
<el-option v-for="user in userList" :key="user.userId" :label="user.nickName"
:value="user.userId"></el-option>
</el-select>
</el-form-item>
<el-form-item label="内容" prop="content">
<el-input v-model="form.content" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">发送</el-button>
<el-button @click="cancel">取消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listIm_mes, addIm_mes, } from "@/im/sys/api/im_mes"
import { getUserProfile, listUser } from "@/api/system/user"
import { getToken } from "@/utils/auth"
import store from "@/store"
import dayjs from 'dayjs'
export default {
name: "Im_mes",
data() {
return {
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: false,
// 总条数
total: 0,
// 消息记录表格数据
im_mesList: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 100,
receiverId: null,
senderId: null,
createdAt: null,
content: null,
},
options: {
img: store.getters.avatar,
},
// 表单参数
form: {},
// 表单校验
rules: {
receiverId: [{ required: true, message: "接收人不能为空", trigger: "blur" }],
content: [{ required: true, message: "内容不能为空", trigger: "blur" }]
},
// 聊天相关数据
userList: [], // 所有用户列表
contactList: [], // 联系人列表
activeContact: null, // 当前选中的联系人ID
currentContact: {}, // 当前联系人信息
currentUser: {
userId: store.getters.userId,
nickName: store.getters.nickName,
avatar: store.getters.avatar,
deptName: store.getters.dept ? store.getters.dept.deptName : '未知部门'
}, // 当前登录用户
newMessage: "", // 新消息内容
pollInterval: null, // 轮询定时器
// 文件上传相关
uploadFileUrl: process.env.VUE_APP_BASE_API + "/common/upload",
headers: {
Authorization: "Bearer " + getToken(),
},
uploadingFile: null, // 正在上传的文件信息
}
},
computed: {
filteredMessages() {
if (!this.activeContact || !this.currentUser.userId) return [];
return this.im_mesList
.filter(msg => {
// 添加空值检查
if (!msg || !msg.senderId || !msg.receiverId) return false;
return (
(msg.senderId === this.activeContact && msg.receiverId === this.currentUser.userId) ||
(msg.receiverId === this.activeContact && msg.senderId === this.currentUser.userId)
);
})
.sort((a, b) => new Date(a.sendTime || a.createdAt) - new Date(b.sendTime || b.createdAt));
}
},
created() {
this.getCurrentUser();
this.getUserList();
this.getList();
// 启动轮询,每5秒获取一次新消息
this.pollInterval = setInterval(() => {
this.getList();
}, 500000);
},
beforeDestroy() {
// 组件销毁前清除定时器
if (this.pollInterval) {
clearInterval(this.pollInterval);
}
},
watch: {
// 当消息列表更新时,自动滚动到底部
filteredMessages: {
handler() {
this.$nextTick(() => {
this.scrollToBottom();
});
},
deep: true
}
},
methods: {
/** 时间格式处理 */
formatTime(time) {
return dayjs(time).format('YYYY-MM-DD HH:mm:ss')
},
/** 格式化显示时间(用于联系人列表) */
formatDisplayTime(time) {
if (!time) return '';
const now = dayjs();
const messageTime = dayjs(time);
const diffDays = now.diff(messageTime, 'day');
if (diffDays === 0) {
return messageTime.format('HH:mm');
} else if (diffDays === 1) {
return '昨天';
} else if (diffDays < 7) {
return `${diffDays}天前`;
} else {
return messageTime.format('MM-DD');
}
},
/** 格式化文件大小 */
formatFileSize(size) {
if (!size) return '0 B';
const units = ['B', 'KB', 'MB', 'GB'];
let index = 0;
let fileSize = size;
while (fileSize >= 1024 && index < units.length - 1) {
fileSize /= 1024;
index++;
}
return `${fileSize.toFixed(2)} ${units[index]}`;
},
/** 获取文件URL - 完整版本 */
getFileUrl(path) {
if (!path) return '';
// 如果已经是完整URL,直接返回
if (path.startsWith('http') || path.startsWith('blob:')) return path;
// 获取基础API URL(可能只是路径部分)
const baseApi = process.env.VUE_APP_BASE_API || '';
// 如果baseApi已经是完整URL,直接拼接
if (baseApi.startsWith('http')) {
const normalizedPath = path.startsWith('/') ? path : '/' + path;
return baseApi + normalizedPath;
}
// 如果baseApi只是路径部分,需要拼接当前域名
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port ? `:${window.location.port}` : '';
// 构建完整的基础URL
const fullBaseUrl = `${protocol}//${hostname}${port}${baseApi}`;
// 确保路径格式正确
const normalizedPath = path.startsWith('/') ? path : '/' + path;
return fullBaseUrl + normalizedPath;
},
/** 获取文件名 */
getFileName(path) {
if (!path) return '未知文件';
return path.substring(path.lastIndexOf('/') + 1);
},
/** 下载文件 */
downloadFile(path) {
const link = document.createElement('a');
link.href = this.getFileUrl(path);
link.download = this.getFileName(path);
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
},
/** 上传前校验 */
handleBeforeUpload(file) {
const fileType = file.type;
const isImage = fileType.includes('image');
const isVideo = fileType.includes('video');
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
this.$modal.msgError('上传文件大小不能超过 10MB!');
return false;
}
// 保存当前上传的文件信息
this.uploadingFile = {
name: file.name,
size: file.size,
type: isImage ? 2 : isVideo ? 3 : 4
};
return true;
},
/** 上传成功处理 */
handleUploadSuccess(res, file) {
if (res.code === 200) {
// 自动发送文件消息
this.sendFileMessage(res.fileName, this.uploadingFile.type, this.uploadingFile.size);
} else {
this.$modal.msgError(res.msg || '上传失败');
}
this.uploadingFile = null;
},
/** 发送文件消息 */
sendFileMessage(fileName, mediaType, fileSize) {
if (!this.activeContact) {
this.$modal.msgWarning('请先选择联系人');
return;
}
const newMsg = {
receiverId: this.activeContact,
senderId: this.currentUser.userId,
content: fileName,
room: "default",
type: 1,
mediaType: mediaType, // 2:图片, 3:视频, 4:文件
fileSize: fileSize,
status: 1,
createdAt: dayjs().format('YYYY-MM-DD HH:mm:ss'),
};
addIm_mes(newMsg).then(response => {
try {
let responseData;
if (response && typeof response === 'object') {
responseData = response.data || response.result || response;
}
let messageToAdd;
if (responseData && typeof responseData === 'object') {
messageToAdd = {
id: responseData.id || `temp-${Date.now()}`,
receiverId: responseData.receiverId || this.activeContact,
senderId: responseData.senderId || this.currentUser.userId,
content: responseData.content || fileName,
mediaType: responseData.mediaType || mediaType,
fileSize: responseData.fileSize || fileSize,
createdAt: responseData.createdAt || responseData.sendTime || newMsg.createdAt,
...responseData
};
} else {
messageToAdd = {
id: `temp-${Date.now()}`,
...newMsg,
isLocal: true
};
}
this.im_mesList.push(messageToAdd);
// 更新联系人最后一条消息
const contact = this.contactList.find(c => c.id === this.activeContact);
if (contact) {
let lastMsg = '';
if (mediaType === 2) {
lastMsg = '[图片]';
} else if (mediaType === 3) {
lastMsg = '[视频]';
} else {
lastMsg = '[文件] ' + this.getFileName(fileName);
}
contact.lastMsg = lastMsg;
contact.time = this.formatDisplayTime(messageToAdd.createdAt);
}
this.$nextTick(() => {
this.scrollToBottom();
});
} catch (error) {
console.error('处理响应时出错:', error);
this.$modal.msgError('处理消息时出错');
}
}).catch(error => {
console.error("发送失败:", error);
this.$modal.msgError("发送失败");
});
},
/** 获取头像地址 */
getFullAvatarUrl(avatarPath) {
if (!avatarPath) return '';
// 如果已经是完整URL,直接返回
if (avatarPath.startsWith('http')) return avatarPath;
// 拼接基础URL(根据您的实际后端地址配置)
return process.env.VUE_APP_BASE_API + avatarPath;
},
/** 获取当前登录用户信息 */
getCurrentUser() {
// 从store获取基本信息
this.currentUser = {
userId: store.getters.userId,
nickName: store.getters.nickName,
avatar: store.getters.avatar,
deptName: store.getters.dept ? store.getters.dept.deptName : '未知部门'
};
// 如果需要更详细信息,可以调用API
getUserProfile().then(response => {
if (response.data) {
this.currentUser = {
...this.currentUser,
...response.data,
deptName: response.data.dept ? response.data.dept.deptName : '未知部门'
};
}
}).catch(error => {
console.error('获取用户信息失败', error);
});
},
/** 获取用户列表 */
getUserList() {
listUser().then(response => {
this.userList = response.rows;
// 初始化联系人列表
this.initContactList();
});
},
/** 初始化联系人列表 */
initContactList() {
// 从消息记录中提取联系人
const contactMap = {};
this.im_mesList.forEach(msg => {
// 对方是发送人
if (msg.senderId !== this.currentUser.userId) {
if (!contactMap[msg.senderId]) {
const user = this.userList.find(u => u.userId === msg.senderId);
contactMap[msg.senderId] = {
id: msg.senderId,
nickName: user ? user.nickName : '用户' + msg.senderId,
avatar: user ? user.avatar : '',
lastMsg: this.getLastMessagePreview(msg),
time: this.formatTime(msg.createdAt, true)
};
}
}
// 对方是接收人
if (msg.receiverId !== this.currentUser.userId) {
if (!contactMap[msg.receiverId]) {
const user = this.userList.find(u => u.userId === msg.receiverId);
contactMap[msg.receiverId] = {
id: msg.receiverId,
nickName: user ? user.nickName : '用户' + msg.receiverId,
avatar: user ? user.avatar : '',
lastMsg: this.getLastMessagePreview(msg),
time: this.formatTime(msg.createdAt, true)
};
}
}
});
this.contactList = Object.values(contactMap);
},
/** 获取最后一条消息预览 */
getLastMessagePreview(msg) {
if (msg.mediaType === 2) {
return '[图片]';
} else if (msg.mediaType === 3) {
return '[视频]';
} else if (msg.mediaType === 4) {
return '[文件] ' + this.getFileName(msg.content);
} else {
return msg.content;
}
},
/** 获取消息记录列表 - 优化版本 */
getList() {
this.loading = true;
listIm_mes(this.queryParams).then(response => {
// 处理消息列表,确保文件路径正确
this.im_mesList = response.rows.map(msg => {
// 如果是文件类型的消息,确保content字段是完整URL
if (msg.mediaType && msg.mediaType !== "text" && msg.content) {
console.log('原始content:', msg.content);
console.log('VUE_APP_BASE_API:', process.env.VUE_APP_BASE_API);
// 如果content不是完整URL,则转换为完整URL
if (!msg.content.startsWith('http')) {
msg.content = this.getFileUrl(msg.content);
console.log('转换后content:', msg.content);
}
}
return msg;
});
this.total = response.total;
this.loading = false;
// 更新联系人列表
this.initContactList();
}).catch(() => {
this.loading = false;
});
},
// 取消按钮
cancel() {
this.open = false;
this.reset();
},
// 表单重置
reset() {
this.form = {
id: null,
receiverId: null,
senderId: this.currentUser.userId,
content: null,
createdAt: null,
room: "default",
type: 1,
mediaType: 1,
status: 1
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id);
this.multiple = !selection.length;
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "发起聊天";
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
this.form.senderId = this.currentUser.userId;
this.form.createdAt = dayjs().format('YYYY-MM-DD HH:mm:ss');
addIm_mes(this.form).then(response => {
this.$modal.msgSuccess("发送成功");
this.open = false;
this.getList();
// 如果这是一个新的联系人,添加到联系人列表
if (!this.contactList.find(c => c.id === this.form.receiverId)) {
const user = this.userList.find(u => u.userId === this.form.receiverId);
if (user) {
this.contactList.push({
id: user.userId,
nickName: user.nickName,
lastMsg: this.form.content,
time: this.formatTime(new Date())
});
}
}
});
}
});
},
// 聊天相关方法
selectContact(contact) {
this.activeContact = contact.id;
this.currentContact = contact;
},
sendMessage() {
if (!this.newMessage.trim() || !this.activeContact) return;
const newMsg = {
receiverId: this.activeContact,
senderId: this.currentUser.userId,
content: this.newMessage,
room: "default",
type: 1,
mediaType: 1,
fileSize: this.newMessage.length,
status: 1,
createdAt: dayjs().format('YYYY-MM-DD HH:mm:ss'),
};
// 保存原始消息内容,以防需要回退
const originalMessage = this.newMessage;
addIm_mes(newMsg).then(response => {
try {
// 处理API响应
let responseData;
if (response && typeof response === 'object') {
responseData = response.data || response.result || response;
}
let messageToAdd;
if (responseData && typeof responseData === 'object') {
// 使用服务器返回的数据
messageToAdd = {
id: responseData.id || `temp-${Date.now()}`,
receiverId: responseData.receiverId || this.activeContact,
senderId: responseData.senderId || this.currentUser.userId,
content: responseData.content || originalMessage,
createdAt: responseData.createdAt || responseData.sendTime || newMsg.createdAt,
...responseData
};
} else {
// 创建本地消息
messageToAdd = {
id: `temp-${Date.now()}`,
...newMsg,
isLocal: true
};
}
// 添加到消息列表
this.im_mesList.push(messageToAdd);
// 清空输入框
this.newMessage = "";
// 更新联系人最后一条消息
const contact = this.contactList.find(c => c.id === this.activeContact);
if (contact) {
contact.lastMsg = messageToAdd.content;
contact.time = this.formatDisplayTime(messageToAdd.createdAt);
}
// 重新排序消息列表
this.im_mesList = [...this.im_mesList].sort((a, b) =>
new Date(a.createdAt || 0) - new Date(b.createdAt || 0)
);
// 滚动到底部
this.$nextTick(() => {
this.scrollToBottom();
});
} catch (error) {
console.error('处理响应时出错:', error);
this.$modal.msgError('处理消息时出错');
}
}).catch(error => {
console.error("发送失败:", error);
this.$modal.msgError("发送失败");
// 即使API失败,也在本地显示消息
const localMessage = {
id: `temp-${Date.now()}`,
...newMsg,
isLocal: true,
sendStatus: 'failed'
};
this.im_mesList.push(localMessage);
this.newMessage = "";
});
},
getContactName(contactId) {
if (contactId === this.currentUser.userId) return this.currentUser.nickName;
const contact = this.userList.find(c => c.userId === contactId);
return contact ? contact.nickName : '用户' + contactId;
},
scrollToBottom() {
const container = this.$refs.messageContainer;
if (container) {
container.scrollTop = container.scrollHeight;
}
}
}
}
</script>
以上若依前端代码只要getList后,消息窗口图片就显示为http://127.0.0.1/dev-api/profile/upload/2025/09/12/CN-wp6_20250912112412A010.jpg,正常上传图片或附件都能正常显示,是什么问题,请给出修改后的完整代码
最新发布