From db1df052ebf16d1bfeae01a2ddc401ede561bed1 Mon Sep 17 00:00:00 2001 From: Jafeng <2998840497@qq.com> Date: Tue, 26 May 2026 16:27:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=B8=B8=E7=94=A8?= =?UTF-8?q?=E8=AF=AD=E5=8F=91=E9=80=81=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=8F=91=E9=80=81=E5=9B=BE=E7=89=87=E5=B9=B6=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E7=9B=B8=E5=85=B3=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/message/common-phrases.vue | 144 +++++++++++++++++------- pages/message/components/chat-input.vue | 57 ++++++++++ pages/message/index.vue | 20 +++- 3 files changed, 176 insertions(+), 45 deletions(-) diff --git a/pages/message/common-phrases.vue b/pages/message/common-phrases.vue index d3e8e84..28dafc7 100644 --- a/pages/message/common-phrases.vue +++ b/pages/message/common-phrases.vue @@ -35,7 +35,7 @@ class="delete-badge" @click.stop="deleteCategory(category)" > - × + @@ -56,14 +56,21 @@ {{ currentCategoryName }}({{ currentPhrases.length }}) - + - × + @@ -75,12 +82,6 @@ > {{ phrase.content }} - - 已收藏 - - + 发送 @@ -115,7 +116,12 @@ :class="{ collected: isFavorite(phrase) }" @click.stop="toggleFavorite(phrase)" > - {{ isFavorite(phrase) ? "★" : "☆" }} + {{ isFavorite(phrase) ? "已收藏" : "收藏" }} @@ -135,7 +141,7 @@ - + + 添加快捷回复 @@ -150,7 +156,7 @@ {{ editingPhrase ? "编辑常用语" : "添加常用语" }} - × + @@ -164,7 +170,7 @@ > {{ selectedPhraseCategoryName }} - + 可选择保存到指定目录 @@ -200,7 +206,7 @@ 收藏常用语 - × + @@ -214,7 +220,7 @@ > {{ selectedCollectCategoryName }} - + 收藏后将添加到所选目录 @@ -247,7 +253,7 @@ {{ editingCategory ? "编辑分类" : "新建分类" }} - × + { return myCategories.value[0]; }); +const getFavoriteKey = (id) => (id ? String(id) : ""); + const favoriteMap = computed(() => { return myPhrases.value.reduce((map, item) => { - if (item.sourceCommonWordId) map[item.sourceCommonWordId] = item; + const key = getFavoriteKey(item.sourceCommonWordId); + if (key) map[key] = item; return map; }, {}); }); @@ -418,7 +427,7 @@ const getAccountParams = () => { return { corpId, userId }; }; -const isFavorite = (phrase) => Boolean(favoriteMap.value[phrase.id]); +const isFavorite = (phrase) => Boolean(favoriteMap.value[getFavoriteKey(phrase?.id)]); const switchTab = (tab) => { if (activeTab.value === tab) return; @@ -443,12 +452,38 @@ const handleCategoryClick = (category) => { currentCategory.value = category.id; }; -const sendPhrase = (phrase) => { +const confirmSendImages = (count) => { + return new Promise((resolve) => { + uni.showModal({ + title: "提示", + content: `该常用语包含${count}张图片,是否一并发送?`, + cancelText: "不发送图片", + confirmText: "发送图片", + success: (res) => resolve(Boolean(res.confirm)), + fail: () => resolve(false), + }); + }); +}; + +const sendPhrase = async (phrase) => { const pages = getCurrentPages(); const prevPage = pages[pages.length - 2]; + const files = normalizeFiles(phrase.files); + const shouldSendImages = files.length > 0 ? await confirmSendImages(files.length) : false; + const phraseToSend = { + ...phrase, + files: shouldSendImages ? files : [], + }; + + if (!phraseToSend.content?.trim() && phraseToSend.files.length === 0) { + return; + } if (prevPage) { - prevPage.$vm.sendCommonPhrase(phrase); + const result = prevPage.$vm.sendCommonPhrase(phraseToSend); + if (result && typeof result.then === "function") { + await result; + } } uni.navigateBack(); @@ -589,7 +624,7 @@ const toggleFavorite = async (phrase) => { } try { - const favorite = favoriteMap.value[phrase.id]; + const favorite = favoriteMap.value[getFavoriteKey(phrase.id)]; if (favorite) { const result = await removeMyPhrase(favorite); if (result.success) { @@ -650,12 +685,35 @@ const confirmFavorite = async () => { files: normalizeFiles(phrase.files), }); - if (result.success && result.data) { - myPhrases.value.unshift(normalizePhrase(result.data, "user")); - uni.showToast({ title: "收藏成功", icon: "success" }); - closeCollectPopup(); - } else if (result.success) { - await loadMyPhrases(); + if (result.success) { + const collectedData = result.data || {}; + const nextPhrase = normalizePhrase({ + ...collectedData, + _id: collectedData._id || collectedData.id, + cateId: collectedData.cateId || collectForm.value.cateId, + content: collectedData.content || phrase.content, + files: collectedData.files || normalizeFiles(phrase.files), + sourceType: collectedData.sourceType || "common-words", + sourceCommonWordId: collectedData.sourceCommonWordId || phrase.id, + sourceCateId: collectedData.sourceCateId || phrase.cateId, + collectClient: collectedData.collectClient || "wxapp", + collectTime: collectedData.collectTime || Date.now(), + }, "user"); + + if (nextPhrase.id) { + const favoriteKey = getFavoriteKey(phrase.id); + const index = myPhrases.value.findIndex((item) => { + return item.id === nextPhrase.id || getFavoriteKey(item.sourceCommonWordId) === favoriteKey; + }); + if (index > -1) { + myPhrases.value.splice(index, 1, nextPhrase); + } else { + myPhrases.value.unshift(nextPhrase); + } + } else { + await loadMyPhrases(); + } + uni.showToast({ title: "收藏成功", icon: "success" }); closeCollectPopup(); } else { @@ -1095,25 +1153,18 @@ $border-color: #edf0f5; white-space: pre-wrap; } - .collected-badge { - flex-shrink: 0; - padding: 4rpx 10rpx; - color: $primary-color; - font-size: 24rpx; - border-radius: 6rpx; - background: $primary-light; - } - .phrase-actions { display: flex; align-items: center; - gap: 28rpx; + justify-content: space-between; + gap: 0; margin-top: 20rpx; padding-top: 16rpx; border-top: 1px solid $border-color; } .action-btn { + flex: 1; display: flex; align-items: center; gap: 6rpx; @@ -1130,6 +1181,19 @@ $border-color: #edf0f5; color: $primary-color; } + &.send { + justify-content: flex-start; + } + + &.edit { + justify-content: center; + } + + &.delete, + &.collect { + justify-content: flex-end; + } + &.delete { color: #ff3b30; } diff --git a/pages/message/components/chat-input.vue b/pages/message/components/chat-input.vue index 57d52b2..1c55ac3 100644 --- a/pages/message/components/chat-input.vue +++ b/pages/message/components/chat-input.vue @@ -201,6 +201,62 @@ const sendTextMessageFromPhrase = async (content) => { }); }; +const normalizePhraseImageFile = (file) => { + return new Promise((resolve) => { + const imageUrl = + typeof file === "string" + ? file + : file?.url || file?.URL || file?.download_url || file?.tempFilePath || file?.path; + + if (!imageUrl) { + resolve(null); + return; + } + + const makeImageFile = (path, size = 0) => ({ + type: "image", + tempFilePaths: [path], + tempFiles: [ + { + tempFilePath: path, + path, + size, + fileType: "image", + type: "image", + }, + ], + tempFilePath: path, + }); + + if (/^https?:\/\//.test(imageUrl)) { + uni.downloadFile({ + url: imageUrl, + success: (res) => { + if (res.statusCode === 200 && res.tempFilePath) { + resolve(makeImageFile(res.tempFilePath, file?.size || 0)); + } else { + resolve(null); + } + }, + fail: () => resolve(null), + }); + return; + } + + resolve(makeImageFile(imageUrl, file?.size || 0)); + }); +}; + +const sendImageMessageFromPhrase = async (file) => { + const imageFile = await normalizePhraseImageFile(file); + if (!imageFile) { + uni.showToast({ title: "图片发送失败", icon: "none" }); + return; + } + + await sendImageMessage(imageFile); +}; + // 设置输入框文本(覆盖原内容) const setInputText = (text) => { inputText.value = text; @@ -214,6 +270,7 @@ const clearInputText = () => { // 暴露方法给父组件调用 defineExpose({ sendTextMessageFromPhrase, + sendImageMessageFromPhrase, appendStreamText, setInputText, clearInputText, diff --git a/pages/message/index.vue b/pages/message/index.vue index 67e16a5..a006b52 100644 --- a/pages/message/index.vue +++ b/pages/message/index.vue @@ -992,11 +992,21 @@ onHide(() => { console.log("✓ 页面隐藏,已清空当前会话ID"); }); -const sendCommonPhrase = (phraseOrContent) => { - const content = typeof phraseOrContent === "string" ? phraseOrContent : phraseOrContent?.content || ""; - if (chatInputRef.value) { - // 覆盖输入框内容,而不是直接发送 - chatInputRef.value.setInputText(content); +const sendCommonPhrase = async (phraseOrContent) => { + if (!chatInputRef.value) return; + + const content = + typeof phraseOrContent === "string" + ? phraseOrContent + : phraseOrContent?.content || ""; + const files = Array.isArray(phraseOrContent?.files) ? phraseOrContent.files : []; + + if (content.trim()) { + await chatInputRef.value.sendTextMessageFromPhrase(content); + } + + for (const file of files) { + await chatInputRef.value.sendImageMessageFromPhrase(file); } };