完成图片、文件兼容修改语音icon 回复头像显示

This commit is contained in:
2025-07-05 14:25:53 +08:00
parent ad33895b6d
commit 179005822d
21 changed files with 2715 additions and 73 deletions

View File

@@ -94,13 +94,80 @@
:closeOnClickOverlay="true" :closeOnClickAction="true" :show="showActionSheet"
@close="showActionSheet = false">
</u-action-sheet>
<!-- Emoji 选择面板 -->
<!-- Emoji 选择面板 -->
<view v-if="showEmojiPicker">
<view class="emoji-mask" @touchstart="showEmojiPicker = false" style="position:fixed;left:0;top:0;width:100vw;height:100vh;z-index:998;"></view>
<view class="emoji-picker" style="position:relative;z-index:999;">
<EmojiPicker @select="onEmojiSelect" />
</view>
</view>
<!-- 文件选择弹窗 -->
<view v-if="showFileSelectModal" class="file-select-mask" @touchstart="showFileSelectModal = false">
<view class="file-select-container" @touchstart.stop>
<view class="file-select-header">
<text class="file-select-title">选择文件</text>
<text class="file-select-close" @click="showFileSelectModal = false">×</text>
</view>
<view class="file-select-tabs">
<view
class="tab-item"
:class="{ active: currentFileTab === 'chat' }"
@click="currentFileTab = 'chat'"
>
聊天文件
</view>
<view
class="tab-item"
:class="{ active: currentFileTab === 'system' }"
@click="currentFileTab = 'system'"
>
系统文件
</view>
</view>
<view class="file-select-content">
<view v-if="currentFileTab === 'chat'" class="chat-files">
<view v-if="chatFiles.length === 0" class="empty-state">
<text class="empty-text">暂无聊天文件</text>
</view>
<view v-else class="file-list">
<view
v-for="file in chatFiles"
:key="file.clientMsgID"
class="file-item"
@click="selectChatFile(file)"
>
<image :src="getFileIcon(file)" class="file-icon" mode="aspectFit" />
<view class="file-info">
<text class="file-name">{{ getFileName(file) }}</text>
<text class="file-size">{{ getFileSize(file) }}</text>
</view>
</view>
</view>
</view>
<view v-else-if="currentFileTab === 'system'" class="system-files">
<view class="file-options">
<view class="option-item" @click="chooseSystemFile('document')">
<image src="/static/images/file_message/file_pdf.png" class="option-icon" mode="aspectFit" />
<text class="option-text">文档</text>
</view>
<view class="option-item" @click="chooseSystemFile('image')">
<image src="/static/images/file_message/file_image.png" class="option-icon" mode="aspectFit" />
<text class="option-text">图片</text>
</view>
<view class="option-item" @click="chooseSystemFile('video')">
<image src="/static/images/file_message/file_unknown.png" class="option-icon" mode="aspectFit" />
<text class="option-text">视频</text>
</view>
<view class="option-item" @click="chooseSystemFile('audio')">
<image src="/static/images/file_message/file_unknown.png" class="option-icon" mode="aspectFit" />
<text class="option-text">音频</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
@@ -169,6 +236,9 @@ export default {
recordingTime: 0,
recordingTimer: null,
showEmojiPicker: false,
showFileSelectModal: false,
currentFileTab: 'chat',
chatFiles: [],
recorderManager: null,
recordFilePath: '',
recordFileDuration: 0,
@@ -188,12 +258,21 @@ export default {
footerOutsideFlag(newVal) {
this.onClickActionBarOutside();
},
showFileSelectModal(newVal) {
if (newVal) {
// 当弹窗显示时,获取聊天文件列表
this.getChatFiles();
}
},
},
mounted() {
this.setKeyboardListener();
document.addEventListener('plusready', function() {
// plus 相关代码
});
// 只在浏览器环境下添加事件监听
if (typeof document !== 'undefined') {
document.addEventListener('plusready', function() {
// plus 相关代码
});
}
},
beforeDestroy() {
this.disposeKeyboardListener();
@@ -344,38 +423,275 @@ export default {
}
},
async chooseFileAndSend() {
if (!uni.chooseMessageFile) {
return;
// 显示文件选择弹窗,类似微信的文件选择
this.showFileSelectModal = true;
},
// 获取聊天文件列表
getChatFiles() {
// 从store中获取当前会话的消息列表筛选出文件消息
const messages = this.$store.getters.storeHistoryMessageList || [];
this.chatFiles = messages.filter(msg => {
return msg.contentType === 105; // 文件消息类型
}).slice(-20); // 只显示最近20个文件
},
// 选择聊天文件
selectChatFile(file) {
// 直接转发这个文件消息
this.forwardFileMessage(file);
this.showFileSelectModal = false;
},
// 转发文件消息
async forwardFileMessage(file) {
try {
const uuid = IMSDK.uuid();
const fileElem = file.fileElem;
// 创建新的文件消息
const message = await IMSDK.asyncApi('createFileMessageByURL', IMSDK.uuid(), {
filePath: fileElem.filePath || '',
fileName: fileElem.fileName,
uuid: uuid,
sourceUrl: fileElem.sourceUrl,
fileSize: fileElem.fileSize,
fileType: fileElem.fileType,
});
// 发送消息
this.sendMessage(message);
} catch (e) {
console.log('转发文件失败:', e);
uni.showToast({
title: '转发文件失败',
icon: 'none'
});
}
uni.chooseMessageFile({
count: 1,
type: 'file',
success: async (res) => {
const file = res.tempFiles[0];
try {
const nameIdx = file.name.lastIndexOf("/") + 1;
const fileName = file.name.slice(nameIdx);
const typeIdx = file.name.lastIndexOf(".") + 1;
const fileType = file.name.slice(typeIdx);
const { data: { url } } = await IMSDK.asyncApi(IMMethods.UploadFile, IMSDK.uuid(), {
filepath: file.path,
name: fileName,
contentType: fileType,
uuid: IMSDK.uuid(),
});
// 创建文件消息
const message = await IMSDK.asyncApi(IMMethods.CreateFileMessage, IMSDK.uuid(), {
fileName: fileName,
fileSize: file.size,
sourceUrl: url,
});
this.sendMessage(message);
} catch (e) {
},
// 获取文件图标
getFileIcon(file) {
const fileName = file.fileElem?.fileName?.toLowerCase() || '';
if (fileName.endsWith('.pdf')) {
return '/static/images/file_message/file_pdf.png';
} else if (fileName.endsWith('.doc') || fileName.endsWith('.docx')) {
return '/static/images/file_message/file_word.png';
} else if (fileName.endsWith('.xls') || fileName.endsWith('.xlsx')) {
return '/static/images/file_message/file_excel.png';
} else if (fileName.endsWith('.ppt') || fileName.endsWith('.pptx')) {
return '/static/images/file_message/file_ppt.png';
} else if (fileName.endsWith('.zip') || fileName.endsWith('.rar') || fileName.endsWith('.7z')) {
return '/static/images/file_message/file_zip.png';
} else if (fileName.endsWith('.jpg') || fileName.endsWith('.jpeg') || fileName.endsWith('.png') || fileName.endsWith('.gif')) {
return '/static/images/file_message/file_image.png';
} else {
return '/static/images/file_message/file_unknown.png';
}
},
// 获取文件名
getFileName(file) {
return file.fileElem?.fileName || '未知文件';
},
// 获取文件大小
getFileSize(file) {
const size = file.fileElem?.fileSize;
if (!size) return '';
if (size < 1024) {
return size + ' B';
} else if (size < 1024 * 1024) {
return (size / 1024).toFixed(1) + ' KB';
} else {
return (size / (1024 * 1024)).toFixed(1) + ' MB';
}
},
// 选择系统文件
chooseSystemFile(type) {
switch (type) {
case 'document':
// 选择文档
uni.chooseMessageFile({
count: 1,
type: 'file',
success: async (res) => {
const file = res.tempFiles[0];
await this.processFileAndSend(file);
},
fail: () => {
uni.showToast({
title: '请从聊天记录中选择文档',
icon: 'none'
});
}
});
break;
case 'image':
// 选择图片
uni.chooseImage({
count: 1,
sourceType: ['album'],
success: async (res) => {
const file = {
name: 'image.jpg',
size: 0,
path: res.tempFilePaths[0],
type: 'image'
};
await this.processFileAndSend(file);
}
});
break;
case 'video':
// 选择视频
uni.chooseVideo({
sourceType: ['album'],
success: async (res) => {
const file = {
name: 'video.mp4',
size: res.size || 0,
path: res.tempFilePath,
type: 'video'
};
await this.processFileAndSend(file);
}
});
break;
case 'audio':
// 选择音频
uni.chooseMessageFile({
count: 1,
type: 'file',
success: async (res) => {
const file = res.tempFiles[0];
await this.processFileAndSend(file);
},
fail: () => {
uni.showToast({
title: '请从聊天记录中选择音频文件',
icon: 'none'
});
}
});
break;
}
this.showFileSelectModal = false;
},
async processFileAndSend(file) {
try {
// 处理文件名
let fileName = file.name;
if (!fileName) {
const pathParts = file.path.split('/');
fileName = pathParts[pathParts.length - 1] || 'unknown';
}
// 处理文件类型
let fileType = file.type;
if (!fileType) {
const nameIdx = fileName.lastIndexOf(".") + 1;
fileType = fileName.slice(nameIdx) || 'unknown';
}
const uuid = IMSDK.uuid();
// 如果是图片类型,直接创建图片消息
if (fileType.toLowerCase().includes('image') || fileType.toLowerCase().includes('jpg') || fileType.toLowerCase().includes('jpeg') || fileType.toLowerCase().includes('png') || fileType.toLowerCase().includes('gif')) {
// 创建图片消息
const message = await IMSDK.asyncApi(
IMMethods.CreateImageMessageFromFullPath,
IMSDK.uuid(),
file.path
);
this.sendMessage(message);
return;
}
// 先保存到本地
uni.saveFile({
tempFilePath: file.path,
success: (saveRes) => {
let savedFilePath = saveRes.savedFilePath;
// 用 plus.io 转换为原生绝对路径
if (typeof plus !== 'undefined') {
plus.io.resolveLocalFileSystemURL(savedFilePath, (entry) => {
IMSDK.asyncApi(IMMethods.UploadFile, uuid, {
name: fileName,
contentType: fileType,
uuid: uuid,
filepath: entry.fullPath
})
.then(({ data }) => {
let sourceUrl = data.url;
IMSDK.asyncApi('createFileMessageByURL', IMSDK.uuid(), {
filePath: entry.fullPath,
fileName: fileName,
uuid: uuid,
sourceUrl: sourceUrl,
fileSize: file.size || 0,
fileType: fileType,
})
.then((message) => {
IMSDK.asyncApi('sendMessageNotOss', IMSDK.uuid(), {
recvID: this.storeCurrentConversation.userID,
groupID: this.storeCurrentConversation.groupID,
message: message,
offlinePushInfo: {
title: '你有新消息',
desc: '新消息',
ex: '',
iOSPushSound: '+1',
iOSBadgeCount: true,
}
})
.then(({ data }) => {
// 1. 本地新增消息
this.pushNewMessage(message);
// 2. 更新消息状态
this.updateOneMessage({
message: data,
isSuccess: true,
});
this.$emit("scrollToBottom");
})
.catch(({ errCode, errMsg }) => {
// 发送失败时也要更新消息状态为失败
this.updateOneMessage({
message: message,
type: UpdateMessageTypes.KeyWords,
keyWords: [
{ key: "status", value: MessageStatus.Failed },
{ key: "errCode", value: errCode },
],
});
});
})
.catch(() => {
// 忽略错误提示
});
})
.catch(() => {
// 忽略错误提示
});
}, () => {
// 忽略路径转换失败
});
}
},
fail: () => {
// 忽略保存失败
}
},
fail: function (err) {
},
});
});
} catch (e) {
console.log('处理文件失败:', e);
}
},
// from comp
@@ -648,4 +964,177 @@ export default {
position: relative;
z-index: 999;
}
.file-select-mask {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.6);
z-index: 998;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(4px);
}
.file-select-container {
background: #fff;
border-radius: 16px;
width: 90%;
max-width: 500px;
max-height: 80vh;
overflow: hidden;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
}
.file-select-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px;
border-bottom: 1px solid #f0f0f0;
}
.file-select-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.file-select-close {
font-size: 24px;
color: #999;
cursor: pointer;
padding: 4px;
}
.file-select-tabs {
display: flex;
border-bottom: 1px solid #f0f0f0;
}
.tab-item {
flex: 1;
text-align: center;
padding: 16px;
font-size: 16px;
color: #666;
cursor: pointer;
transition: all 0.3s ease;
border-bottom: 2px solid transparent;
}
.tab-item.active {
color: #4a9cfc;
border-bottom-color: #4a9cfc;
background: rgba(74, 156, 252, 0.05);
}
.file-select-content {
max-height: 400px;
overflow-y: auto;
}
.chat-files {
padding: 16px;
}
.empty-state {
text-align: center;
padding: 40px 20px;
}
.empty-text {
color: #999;
font-size: 14px;
}
.file-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.file-item {
display: flex;
align-items: center;
padding: 12px;
border-radius: 8px;
background: #f8f9fa;
cursor: pointer;
transition: all 0.3s ease;
}
.file-item:hover {
background: #e9ecef;
}
.file-icon {
width: 40px;
height: 40px;
margin-right: 12px;
}
.file-info {
flex: 1;
display: flex;
flex-direction: column;
}
.file-name {
font-size: 14px;
color: #333;
font-weight: 500;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-size {
font-size: 12px;
color: #999;
}
.system-files {
padding: 24px;
}
.file-options {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.option-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
border-radius: 12px;
background: #f8f9fa;
cursor: pointer;
transition: all 0.3s ease;
}
.option-item:hover {
background: #e9ecef;
transform: translateY(-2px);
}
.option-icon {
width: 48px;
height: 48px;
margin-bottom: 8px;
}
.option-text {
font-size: 14px;
color: #333;
font-weight: 500;
}
</style>