feat: 优化常用语发送功能,支持发送图片并重构相关逻辑
This commit is contained in:
parent
66f28a9c8e
commit
db1df052eb
@ -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;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user