From 179005822df0e4046f726acf62f3ac8b595d681d Mon Sep 17 00:00:00 2001 From: 86156 <823267011@qq.com> Date: Sat, 5 Jul 2025 14:25:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=9B=BE=E7=89=87=E3=80=81?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=85=BC=E5=AE=B9=E4=BF=AE=E6=94=B9=E8=AF=AD?= =?UTF-8?q?=E9=9F=B3icon=20=E5=9B=9E=E5=A4=8D=E5=A4=B4=E5=83=8F=E6=98=BE?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App.vue | 2 + components/MyAvatar/index.vue | 9 +- manifest.json | 11 +- pages/contact/friendList/index.vue | 1 + .../components/ChatingFooter/index.vue | 557 ++++++++++++++- .../MessageItem/FileMessageRender.vue | 194 ++++++ .../MessageItem/MediaMessageRender.vue | 108 ++- .../MessageItem/TextMessageRender.vue | 4 +- .../MessageItem/VoiceMessageRender.vue | 10 +- .../chating/components/MessageItem/index.vue | 27 +- .../components/ConversationItem.vue | 1 - pages/login/index.vue | 24 +- static/logo.png | Bin 4023 -> 12998 bytes uni_modules/uni-file-picker/changelog.md | 81 +++ .../uni-file-picker/choose-and-upload-file.js | 287 ++++++++ .../uni-file-picker/uni-file-picker.vue | 658 ++++++++++++++++++ .../uni-file-picker/upload-file.vue | 325 +++++++++ .../uni-file-picker/upload-image.vue | 282 ++++++++ .../components/uni-file-picker/utils.js | 110 +++ uni_modules/uni-file-picker/package.json | 86 +++ uni_modules/uni-file-picker/readme.md | 11 + 21 files changed, 2715 insertions(+), 73 deletions(-) create mode 100644 pages/conversation/chating/components/MessageItem/FileMessageRender.vue create mode 100644 uni_modules/uni-file-picker/changelog.md create mode 100644 uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js create mode 100644 uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue create mode 100644 uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue create mode 100644 uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue create mode 100644 uni_modules/uni-file-picker/components/uni-file-picker/utils.js create mode 100644 uni_modules/uni-file-picker/package.json create mode 100644 uni_modules/uni-file-picker/readme.md diff --git a/App.vue b/App.vue index 424dcbb..eb5fe74 100644 --- a/App.vue +++ b/App.vue @@ -344,6 +344,8 @@ export default { this.$store.dispatch("user/getSelfInfo"); this.$store.dispatch("conversation/getConversationList"); this.$store.dispatch("conversation/getUnReadCount"); + this.$store.dispatch("contact/getFriendList"); + this.$store.dispatch("contact/getGrouplist"); this.$store.dispatch("contact/getBlacklist"); this.$store.dispatch("contact/getRecvFriendApplications"); this.$store.dispatch("contact/getSentFriendApplications"); diff --git a/components/MyAvatar/index.vue b/components/MyAvatar/index.vue index 486a94f..9e0a9a9 100644 --- a/components/MyAvatar/index.vue +++ b/components/MyAvatar/index.vue @@ -49,7 +49,7 @@ export default { computed: { getAvatarUrl() { if (this.src) { - return this.src; + return this.getFixedSourceUrl(this.src); } if (this.isGroup) { return defaultGroupIcon; @@ -65,6 +65,13 @@ export default { }, }, methods: { + getFixedSourceUrl(url) { + // 如果 url 以 http://47.117.71.33/api/object/ 开头,则替换为带端口的 + if (typeof url === 'string' && url.startsWith('http://47.117.71.33/api/object/')) { + return url.replace('http://47.117.71.33/api/object/', 'http://47.117.71.33:15219/api/object/'); + } + return url; + }, errorHandle() { this.avatarText = this.desc ? this.desc.slice(0, 1) : "未知"; }, diff --git a/manifest.json b/manifest.json index 81e6c7b..f28ed37 100644 --- a/manifest.json +++ b/manifest.json @@ -2,8 +2,8 @@ "name" : "德讯", "appid" : "__UNI__D705A34", "description" : "", - "versionName" : "toc_base_open 3.3.4", - "versionCode" : 334, + "versionName" : "fad-im 3.4.5", + "versionCode" : 345, "transformPx" : false, "app-plus" : { "bounce" : "none", @@ -11,9 +11,9 @@ "nvueStyleCompiler" : "uni-app", "compilerVersion" : 3, "splashscreen" : { - "alwaysShowBeforeRender" : false, + "alwaysShowBeforeRender" : true, "waiting" : true, - "autoclose" : false, + "autoclose" : true, "delay" : 0 }, "modules" : { @@ -21,8 +21,7 @@ "Camera" : {}, "Record" : {}, "Geolocation" : {}, - "Maps" : {}, - "Push" : {} + "Maps" : {} }, "distribute" : { "android" : { diff --git a/pages/contact/friendList/index.vue b/pages/contact/friendList/index.vue index a04efe8..ab76164 100644 --- a/pages/contact/friendList/index.vue +++ b/pages/contact/friendList/index.vue @@ -41,6 +41,7 @@ export default { computed: { ...mapGetters(["storeFriendList"]), getIndexData() { + return formatChooseData(this.storeFriendList); }, }, diff --git a/pages/conversation/chating/components/ChatingFooter/index.vue b/pages/conversation/chating/components/ChatingFooter/index.vue index 16806d7..f876380 100644 --- a/pages/conversation/chating/components/ChatingFooter/index.vue +++ b/pages/conversation/chating/components/ChatingFooter/index.vue @@ -94,13 +94,80 @@ :closeOnClickOverlay="true" :closeOnClickAction="true" :show="showActionSheet" @close="showActionSheet = false"> - + + + + + + + 选择文件 + × + + + + 聊天文件 + + + 系统文件 + + + + + + 暂无聊天文件 + + + + + + {{ getFileName(file) }} + {{ getFileSize(file) }} + + + + + + + + + 文档 + + + + 图片 + + + + 视频 + + + + 音频 + + + + + + @@ -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; +} + + \ No newline at end of file diff --git a/pages/conversation/chating/components/MessageItem/FileMessageRender.vue b/pages/conversation/chating/components/MessageItem/FileMessageRender.vue new file mode 100644 index 0000000..749da8b --- /dev/null +++ b/pages/conversation/chating/components/MessageItem/FileMessageRender.vue @@ -0,0 +1,194 @@ + + + + + \ No newline at end of file diff --git a/pages/conversation/chating/components/MessageItem/MediaMessageRender.vue b/pages/conversation/chating/components/MessageItem/MediaMessageRender.vue index 1e6d638..e8249a9 100644 --- a/pages/conversation/chating/components/MessageItem/MediaMessageRender.vue +++ b/pages/conversation/chating/components/MessageItem/MediaMessageRender.vue @@ -10,7 +10,16 @@ @click="clickMediaItem" > @@ -28,13 +37,22 @@ export default { }; }, computed: { - getImgUrl() { + + + getImgUrl() { + if (!this.message || !this.message.pictureElem) { + return ''; + } return ( - this.message.pictureElem.snapshotPicture?.url ?? - this.message.pictureElem.sourcePath - ); + this.message.pictureElem.snapshotPicture && this.message.pictureElem.snapshotPicture.url ? + this.getFixedSourceUrl(this.message.pictureElem.snapshotPicture.url) : + this.getFixedSourceUrl(this.message.pictureElem.sourcePicture.url) + ); }, maxHeight() { + if (!this.message || !this.message.pictureElem || !this.message.pictureElem.sourcePicture) { + return 120; + } const imageHeight = this.message.pictureElem.sourcePicture.height; const imageWidth = this.message.pictureElem.sourcePicture.width; const aspectRatio = imageHeight / imageWidth; @@ -42,10 +60,20 @@ export default { }, }, methods: { + getFixedSourceUrl(url) { + if (typeof url === 'string' && url.startsWith('http://47.117.71.33/api/object/')) { + return url.replace('http://47.117.71.33/api/object/', 'http://47.117.71.33:15219/api/object/'); + } + return url; + }, + clickMediaItem() { + if (!this.message || !this.message.pictureElem || !this.message.pictureElem.sourcePicture) { + return; + } uni.previewImage({ current: 0, - urls: [this.message.pictureElem.sourcePicture.url], + urls: [this.getFixedSourceUrl(this.message.pictureElem.sourcePicture.url)], indicator: "none", }); }, @@ -61,6 +89,7 @@ export default { position: relative; border-radius: 16rpx; overflow: hidden; + display: inline-block; .play_icon { width: 48px; @@ -78,4 +107,71 @@ export default { color: #fff; } } + +.custom-loading { + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(4px); + border-radius: 16rpx; + box-sizing: border-box; +} + +.loading-content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.loading-spinner { + position: relative; + width: 40px; + height: 40px; + margin-bottom: 8px; +} + +.spinner-ring { + position: absolute; + width: 100%; + height: 100%; + border: 3px solid transparent; + border-top: 3px solid #4a9cfc; + border-radius: 50%; + animation: spin 1.2s linear infinite; + + &:nth-child(2) { + width: 80%; + height: 80%; + top: 10%; + left: 10%; + border-top-color: #6bb6ff; + animation-delay: -0.4s; + } + + &:nth-child(3) { + width: 60%; + height: 60%; + top: 20%; + left: 20%; + border-top-color: #8cc8ff; + animation-delay: -0.8s; + } +} + +.loading-text { + font-size: 12px; + color: #666; + font-weight: 500; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/pages/conversation/chating/components/MessageItem/TextMessageRender.vue b/pages/conversation/chating/components/MessageItem/TextMessageRender.vue index 8ecaeda..fe57b50 100644 --- a/pages/conversation/chating/components/MessageItem/TextMessageRender.vue +++ b/pages/conversation/chating/components/MessageItem/TextMessageRender.vue @@ -20,8 +20,8 @@ export default { }, computed: { getContent() { - console.log(this.message); - return parseBr(this.message.textElem.content); + return this.message && this.message.textElem && this.message.textElem.content ? + parseBr(this.message.textElem.content) : ''; }, }, data() { diff --git a/pages/conversation/chating/components/MessageItem/VoiceMessageRender.vue b/pages/conversation/chating/components/MessageItem/VoiceMessageRender.vue index e6be4a1..1767a5d 100644 --- a/pages/conversation/chating/components/MessageItem/VoiceMessageRender.vue +++ b/pages/conversation/chating/components/MessageItem/VoiceMessageRender.vue @@ -2,7 +2,7 @@ {{ durationText }} @@ -15,11 +15,15 @@ export default { source: { type: Object, required: true + }, + isSender: { + type: Boolean, + default: false } }, computed: { durationText() { - const d = this.source.soundElem && this.source.soundElem.duration; + const d = this.source && this.source.soundElem && this.source.soundElem.duration; return d ? `${d}''` : ''; } }, @@ -46,7 +50,7 @@ export default { this.innerAudioContext = null; } this.innerAudioContext = uni.createInnerAudioContext(); - const rawUrl = this.source.soundElem && this.source.soundElem.sourceUrl || ''; + const rawUrl = this.source && this.source.soundElem && this.source.soundElem.sourceUrl || ''; this.innerAudioContext.src = this.getFixedSourceUrl(rawUrl); this.isPlaying = true; this.innerAudioContext.play(); diff --git a/pages/conversation/chating/components/MessageItem/index.vue b/pages/conversation/chating/components/MessageItem/index.vue index 19aaabb..2d05631 100644 --- a/pages/conversation/chating/components/MessageItem/index.vue +++ b/pages/conversation/chating/components/MessageItem/index.vue @@ -41,16 +41,16 @@