feat: 优化常用语发送功能,支持发送图片并重构相关逻辑

This commit is contained in:
Jafeng 2026-05-26 16:27:52 +08:00
parent 66f28a9c8e
commit db1df052eb
3 changed files with 176 additions and 45 deletions

View File

@ -35,7 +35,7 @@
class="delete-badge"
@click.stop="deleteCategory(category)"
>
<text class="delete-icon">×</text>
<uni-icons class="delete-icon" type="closeempty" size="12" color="#fff" />
</view>
</view>
</view>
@ -56,14 +56,21 @@
{{ currentCategoryName }}{{ currentPhrases.length }}
</view>
<view class="search-box">
<text class="search-icon"></text>
<uni-icons class="search-icon" type="search" size="16" color="#98a2b3" />
<input
v-model="searchKeyword"
class="search-input"
placeholder="搜索常用语"
confirm-type="search"
/>
<text v-if="searchKeyword" class="clear-search" @click="searchKeyword = ''">×</text>
<uni-icons
v-if="searchKeyword"
class="clear-search"
type="closeempty"
size="18"
color="#98a2b3"
@click="searchKeyword = ''"
/>
</view>
</view>
@ -75,12 +82,6 @@
>
<view class="phrase-top">
<view class="phrase-content">{{ phrase.content }}</view>
<view
v-if="activeTab === 'mine' && phrase.sourceCommonWordId"
class="collected-badge"
>
已收藏
</view>
</view>
<view v-if="phrase.files && phrase.files.length" class="phrase-images">
<image
@ -94,17 +95,17 @@
</view>
<view class="phrase-actions">
<view class="action-btn send" @click.stop="sendPhrase(phrase)">
<text class="action-icon"></text>
<uni-icons class="action-icon" type="paperplane" size="18" color="#1f5cff" />
<text>发送</text>
</view>
<template v-if="activeTab === 'mine'">
<view class="action-btn edit" @click.stop="editPhrase(phrase)">
<text class="action-icon"></text>
<uni-icons class="action-icon" type="compose" size="18" color="#475467" />
<text>编辑</text>
</view>
<view class="action-btn delete" @click.stop="deletePhrase(phrase)">
<text class="action-icon"></text>
<uni-icons class="action-icon" type="trash" size="18" color="#ff3b30" />
<text>删除</text>
</view>
</template>
@ -115,7 +116,12 @@
:class="{ collected: isFavorite(phrase) }"
@click.stop="toggleFavorite(phrase)"
>
<text class="action-icon">{{ isFavorite(phrase) ? "★" : "☆" }}</text>
<uni-icons
class="action-icon"
:type="isFavorite(phrase) ? 'star-filled' : 'star'"
size="18"
:color="isFavorite(phrase) ? '#1f5cff' : '#667085'"
/>
<text>{{ isFavorite(phrase) ? "已收藏" : "收藏" }}</text>
</view>
</view>
@ -135,7 +141,7 @@
<view v-if="activeTab === 'mine'" class="footer-action-bar">
<view class="add-phrase-btn" @click="showAddPhraseDialog">
<text class="add-icon">+</text>
<uni-icons class="add-icon" type="plusempty" size="18" color="#fff" />
<text class="add-text">添加快捷回复</text>
</view>
<view class="edit-btn" @click="toggleEditMode">
@ -150,7 +156,7 @@
<view class="popup-header">
<text class="popup-title">{{ editingPhrase ? "编辑常用语" : "添加常用语" }}</text>
<view class="popup-close" @click="closePopup">
<text class="close-icon">×</text>
<uni-icons class="close-icon" type="closeempty" size="24" color="#98a2b3" />
</view>
</view>
@ -164,7 +170,7 @@
>
<view class="category-picker">
<text>{{ selectedPhraseCategoryName }}</text>
<text class="picker-arrow"></text>
<uni-icons class="picker-arrow" type="arrowdown" size="16" color="#667085" />
</view>
</picker>
<view class="form-hint">可选择保存到指定目录</view>
@ -200,7 +206,7 @@
<view class="popup-header">
<text class="popup-title">收藏常用语</text>
<view class="popup-close" @click="closeCollectPopup">
<text class="close-icon">×</text>
<uni-icons class="close-icon" type="closeempty" size="24" color="#98a2b3" />
</view>
</view>
@ -214,7 +220,7 @@
>
<view class="category-picker">
<text>{{ selectedCollectCategoryName }}</text>
<text class="picker-arrow"></text>
<uni-icons class="picker-arrow" type="arrowdown" size="16" color="#667085" />
</view>
</picker>
<view class="form-hint">收藏后将添加到所选目录</view>
@ -247,7 +253,7 @@
<view class="popup-header">
<text class="popup-title">{{ editingCategory ? "编辑分类" : "新建分类" }}</text>
<view class="popup-close" @click="closeCategoryPopup">
<text class="close-icon">×</text>
<uni-icons class="close-icon" type="closeempty" size="24" color="#98a2b3" />
</view>
</view>
<input
@ -318,9 +324,12 @@ const firstMyCategory = computed(() => {
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;
}

View File

@ -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,

View File

@ -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);
}
};