完成图片、文件兼容修改语音icon 回复头像显示
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user