feat: 优化常用语发送功能,支持发送图片并重构相关逻辑
This commit is contained in:
parent
66f28a9c8e
commit
db1df052eb
@ -35,7 +35,7 @@
|
|||||||
class="delete-badge"
|
class="delete-badge"
|
||||||
@click.stop="deleteCategory(category)"
|
@click.stop="deleteCategory(category)"
|
||||||
>
|
>
|
||||||
<text class="delete-icon">×</text>
|
<uni-icons class="delete-icon" type="closeempty" size="12" color="#fff" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -56,14 +56,21 @@
|
|||||||
{{ currentCategoryName }}({{ currentPhrases.length }})
|
{{ currentCategoryName }}({{ currentPhrases.length }})
|
||||||
</view>
|
</view>
|
||||||
<view class="search-box">
|
<view class="search-box">
|
||||||
<text class="search-icon">⌕</text>
|
<uni-icons class="search-icon" type="search" size="16" color="#98a2b3" />
|
||||||
<input
|
<input
|
||||||
v-model="searchKeyword"
|
v-model="searchKeyword"
|
||||||
class="search-input"
|
class="search-input"
|
||||||
placeholder="搜索常用语"
|
placeholder="搜索常用语"
|
||||||
confirm-type="search"
|
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>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -75,12 +82,6 @@
|
|||||||
>
|
>
|
||||||
<view class="phrase-top">
|
<view class="phrase-top">
|
||||||
<view class="phrase-content">{{ phrase.content }}</view>
|
<view class="phrase-content">{{ phrase.content }}</view>
|
||||||
<view
|
|
||||||
v-if="activeTab === 'mine' && phrase.sourceCommonWordId"
|
|
||||||
class="collected-badge"
|
|
||||||
>
|
|
||||||
已收藏
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
<view v-if="phrase.files && phrase.files.length" class="phrase-images">
|
<view v-if="phrase.files && phrase.files.length" class="phrase-images">
|
||||||
<image
|
<image
|
||||||
@ -94,17 +95,17 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="phrase-actions">
|
<view class="phrase-actions">
|
||||||
<view class="action-btn send" @click.stop="sendPhrase(phrase)">
|
<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>
|
<text>发送</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<template v-if="activeTab === 'mine'">
|
<template v-if="activeTab === 'mine'">
|
||||||
<view class="action-btn edit" @click.stop="editPhrase(phrase)">
|
<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>
|
<text>编辑</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="action-btn delete" @click.stop="deletePhrase(phrase)">
|
<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>
|
<text>删除</text>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@ -115,7 +116,12 @@
|
|||||||
:class="{ collected: isFavorite(phrase) }"
|
:class="{ collected: isFavorite(phrase) }"
|
||||||
@click.stop="toggleFavorite(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>
|
<text>{{ isFavorite(phrase) ? "已收藏" : "收藏" }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -135,7 +141,7 @@
|
|||||||
|
|
||||||
<view v-if="activeTab === 'mine'" class="footer-action-bar">
|
<view v-if="activeTab === 'mine'" class="footer-action-bar">
|
||||||
<view class="add-phrase-btn" @click="showAddPhraseDialog">
|
<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>
|
<text class="add-text">添加快捷回复</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="edit-btn" @click="toggleEditMode">
|
<view class="edit-btn" @click="toggleEditMode">
|
||||||
@ -150,7 +156,7 @@
|
|||||||
<view class="popup-header">
|
<view class="popup-header">
|
||||||
<text class="popup-title">{{ editingPhrase ? "编辑常用语" : "添加常用语" }}</text>
|
<text class="popup-title">{{ editingPhrase ? "编辑常用语" : "添加常用语" }}</text>
|
||||||
<view class="popup-close" @click="closePopup">
|
<view class="popup-close" @click="closePopup">
|
||||||
<text class="close-icon">×</text>
|
<uni-icons class="close-icon" type="closeempty" size="24" color="#98a2b3" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -164,7 +170,7 @@
|
|||||||
>
|
>
|
||||||
<view class="category-picker">
|
<view class="category-picker">
|
||||||
<text>{{ selectedPhraseCategoryName }}</text>
|
<text>{{ selectedPhraseCategoryName }}</text>
|
||||||
<text class="picker-arrow">⌄</text>
|
<uni-icons class="picker-arrow" type="arrowdown" size="16" color="#667085" />
|
||||||
</view>
|
</view>
|
||||||
</picker>
|
</picker>
|
||||||
<view class="form-hint">可选择保存到指定目录</view>
|
<view class="form-hint">可选择保存到指定目录</view>
|
||||||
@ -200,7 +206,7 @@
|
|||||||
<view class="popup-header">
|
<view class="popup-header">
|
||||||
<text class="popup-title">收藏常用语</text>
|
<text class="popup-title">收藏常用语</text>
|
||||||
<view class="popup-close" @click="closeCollectPopup">
|
<view class="popup-close" @click="closeCollectPopup">
|
||||||
<text class="close-icon">×</text>
|
<uni-icons class="close-icon" type="closeempty" size="24" color="#98a2b3" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -214,7 +220,7 @@
|
|||||||
>
|
>
|
||||||
<view class="category-picker">
|
<view class="category-picker">
|
||||||
<text>{{ selectedCollectCategoryName }}</text>
|
<text>{{ selectedCollectCategoryName }}</text>
|
||||||
<text class="picker-arrow">⌄</text>
|
<uni-icons class="picker-arrow" type="arrowdown" size="16" color="#667085" />
|
||||||
</view>
|
</view>
|
||||||
</picker>
|
</picker>
|
||||||
<view class="form-hint">收藏后将添加到所选目录</view>
|
<view class="form-hint">收藏后将添加到所选目录</view>
|
||||||
@ -247,7 +253,7 @@
|
|||||||
<view class="popup-header">
|
<view class="popup-header">
|
||||||
<text class="popup-title">{{ editingCategory ? "编辑分类" : "新建分类" }}</text>
|
<text class="popup-title">{{ editingCategory ? "编辑分类" : "新建分类" }}</text>
|
||||||
<view class="popup-close" @click="closeCategoryPopup">
|
<view class="popup-close" @click="closeCategoryPopup">
|
||||||
<text class="close-icon">×</text>
|
<uni-icons class="close-icon" type="closeempty" size="24" color="#98a2b3" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<input
|
<input
|
||||||
@ -318,9 +324,12 @@ const firstMyCategory = computed(() => {
|
|||||||
return myCategories.value[0];
|
return myCategories.value[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getFavoriteKey = (id) => (id ? String(id) : "");
|
||||||
|
|
||||||
const favoriteMap = computed(() => {
|
const favoriteMap = computed(() => {
|
||||||
return myPhrases.value.reduce((map, item) => {
|
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;
|
return map;
|
||||||
}, {});
|
}, {});
|
||||||
});
|
});
|
||||||
@ -418,7 +427,7 @@ const getAccountParams = () => {
|
|||||||
return { corpId, userId };
|
return { corpId, userId };
|
||||||
};
|
};
|
||||||
|
|
||||||
const isFavorite = (phrase) => Boolean(favoriteMap.value[phrase.id]);
|
const isFavorite = (phrase) => Boolean(favoriteMap.value[getFavoriteKey(phrase?.id)]);
|
||||||
|
|
||||||
const switchTab = (tab) => {
|
const switchTab = (tab) => {
|
||||||
if (activeTab.value === tab) return;
|
if (activeTab.value === tab) return;
|
||||||
@ -443,12 +452,38 @@ const handleCategoryClick = (category) => {
|
|||||||
currentCategory.value = category.id;
|
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 pages = getCurrentPages();
|
||||||
const prevPage = pages[pages.length - 2];
|
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) {
|
if (prevPage) {
|
||||||
prevPage.$vm.sendCommonPhrase(phrase);
|
const result = prevPage.$vm.sendCommonPhrase(phraseToSend);
|
||||||
|
if (result && typeof result.then === "function") {
|
||||||
|
await result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.navigateBack();
|
uni.navigateBack();
|
||||||
@ -589,7 +624,7 @@ const toggleFavorite = async (phrase) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const favorite = favoriteMap.value[phrase.id];
|
const favorite = favoriteMap.value[getFavoriteKey(phrase.id)];
|
||||||
if (favorite) {
|
if (favorite) {
|
||||||
const result = await removeMyPhrase(favorite);
|
const result = await removeMyPhrase(favorite);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@ -650,12 +685,35 @@ const confirmFavorite = async () => {
|
|||||||
files: normalizeFiles(phrase.files),
|
files: normalizeFiles(phrase.files),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.success && result.data) {
|
if (result.success) {
|
||||||
myPhrases.value.unshift(normalizePhrase(result.data, "user"));
|
const collectedData = result.data || {};
|
||||||
uni.showToast({ title: "收藏成功", icon: "success" });
|
const nextPhrase = normalizePhrase({
|
||||||
closeCollectPopup();
|
...collectedData,
|
||||||
} else if (result.success) {
|
_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();
|
await loadMyPhrases();
|
||||||
|
}
|
||||||
|
|
||||||
uni.showToast({ title: "收藏成功", icon: "success" });
|
uni.showToast({ title: "收藏成功", icon: "success" });
|
||||||
closeCollectPopup();
|
closeCollectPopup();
|
||||||
} else {
|
} else {
|
||||||
@ -1095,25 +1153,18 @@ $border-color: #edf0f5;
|
|||||||
white-space: pre-wrap;
|
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 {
|
.phrase-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 28rpx;
|
justify-content: space-between;
|
||||||
|
gap: 0;
|
||||||
margin-top: 20rpx;
|
margin-top: 20rpx;
|
||||||
padding-top: 16rpx;
|
padding-top: 16rpx;
|
||||||
border-top: 1px solid $border-color;
|
border-top: 1px solid $border-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6rpx;
|
gap: 6rpx;
|
||||||
@ -1130,6 +1181,19 @@ $border-color: #edf0f5;
|
|||||||
color: $primary-color;
|
color: $primary-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.send {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.edit {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.delete,
|
||||||
|
&.collect {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
&.delete {
|
&.delete {
|
||||||
color: #ff3b30;
|
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) => {
|
const setInputText = (text) => {
|
||||||
inputText.value = text;
|
inputText.value = text;
|
||||||
@ -214,6 +270,7 @@ const clearInputText = () => {
|
|||||||
// 暴露方法给父组件调用
|
// 暴露方法给父组件调用
|
||||||
defineExpose({
|
defineExpose({
|
||||||
sendTextMessageFromPhrase,
|
sendTextMessageFromPhrase,
|
||||||
|
sendImageMessageFromPhrase,
|
||||||
appendStreamText,
|
appendStreamText,
|
||||||
setInputText,
|
setInputText,
|
||||||
clearInputText,
|
clearInputText,
|
||||||
|
|||||||
@ -992,11 +992,21 @@ onHide(() => {
|
|||||||
console.log("✓ 页面隐藏,已清空当前会话ID");
|
console.log("✓ 页面隐藏,已清空当前会话ID");
|
||||||
});
|
});
|
||||||
|
|
||||||
const sendCommonPhrase = (phraseOrContent) => {
|
const sendCommonPhrase = async (phraseOrContent) => {
|
||||||
const content = typeof phraseOrContent === "string" ? phraseOrContent : phraseOrContent?.content || "";
|
if (!chatInputRef.value) return;
|
||||||
if (chatInputRef.value) {
|
|
||||||
// 覆盖输入框内容,而不是直接发送
|
const content =
|
||||||
chatInputRef.value.setInputText(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