feat: 添加图片附件功能到常用语,支持上传和预览
This commit is contained in:
parent
957ccf8b47
commit
b95f57f1c1
@ -82,6 +82,16 @@
|
||||
已收藏
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="phrase.files && phrase.files.length" class="phrase-images">
|
||||
<image
|
||||
v-for="(file, fileIndex) in phrase.files"
|
||||
:key="file.url || fileIndex"
|
||||
class="phrase-image"
|
||||
:src="file.url"
|
||||
mode="aspectFill"
|
||||
@click.stop="previewFiles(phrase.files, fileIndex)"
|
||||
/>
|
||||
</view>
|
||||
<view class="phrase-actions">
|
||||
<view class="action-btn send" @click.stop="sendPhrase(phrase)">
|
||||
<text class="action-icon">✈</text>
|
||||
@ -168,6 +178,23 @@
|
||||
:auto-height="false"
|
||||
></textarea>
|
||||
<view class="char-count">{{ phraseForm.content.length }}/500</view>
|
||||
|
||||
<view class="form-label content-label">图片附件</view>
|
||||
<view class="image-grid">
|
||||
<view
|
||||
v-for="(file, index) in phraseForm.files"
|
||||
:key="file.url || index"
|
||||
class="image-item"
|
||||
@click="previewFiles(phraseForm.files, index)"
|
||||
>
|
||||
<image class="image-thumb" :src="file.url" mode="aspectFill" />
|
||||
<view class="image-remove" @click.stop="removePhraseImage(index)">×</view>
|
||||
</view>
|
||||
<view v-if="phraseForm.files.length < 9" class="image-add" @click="addPhraseImage">
|
||||
<text class="image-add-icon">+</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="popup-actions">
|
||||
<button class="cancel-btn" @click="closePopup">取消</button>
|
||||
<button class="confirm-btn" @click="savePhrase">保存</button>
|
||||
@ -200,6 +227,16 @@
|
||||
<view class="form-hint">收藏后将添加到所选目录</view>
|
||||
|
||||
<view class="collect-preview">{{ collectingPhrase?.content || "" }}</view>
|
||||
<view v-if="collectingPhrase?.files && collectingPhrase.files.length" class="phrase-images collect-images">
|
||||
<image
|
||||
v-for="(file, fileIndex) in collectingPhrase.files"
|
||||
:key="file.url || fileIndex"
|
||||
class="phrase-image"
|
||||
:src="file.url"
|
||||
mode="aspectFill"
|
||||
@click.stop="previewFiles(collectingPhrase.files, fileIndex)"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="popup-actions">
|
||||
<button class="cancel-btn" @click="closeCollectPopup">取消</button>
|
||||
@ -239,6 +276,7 @@ import { ref, computed, onMounted } from "vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import api from "@/utils/api";
|
||||
import useAccountStore from "@/store/account";
|
||||
import { chooseAndUploadImage, normalizeFileUrl } from "@/utils/file";
|
||||
|
||||
const { doctorInfo } = storeToRefs(useAccountStore());
|
||||
|
||||
@ -262,6 +300,7 @@ const collectingPhrase = ref(null);
|
||||
const phraseForm = ref({
|
||||
content: "",
|
||||
cateId: "",
|
||||
files: [],
|
||||
});
|
||||
|
||||
const collectForm = ref({
|
||||
@ -348,12 +387,29 @@ const normalizeCategory = (item, categoryType) => ({
|
||||
deletable: categoryType === "user",
|
||||
});
|
||||
|
||||
const normalizeFiles = (files) => {
|
||||
if (!Array.isArray(files)) return [];
|
||||
return files
|
||||
.map((item) => {
|
||||
if (typeof item === "string") return { type: "image", url: normalizeFileUrl(item) };
|
||||
const url = item?.url || item?.URL || item?.download_url;
|
||||
if (!url) return null;
|
||||
return {
|
||||
type: item.type || "image",
|
||||
url: normalizeFileUrl(url),
|
||||
name: item.name || item.fileName || "",
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
const normalizePhrase = (item, phraseType) => ({
|
||||
id: item._id || item.id,
|
||||
cateId: item.cateId || item.categoryId,
|
||||
content: item.content || "",
|
||||
createTime: item.createTime,
|
||||
updateTime: item.updateTime,
|
||||
files: normalizeFiles(item.files),
|
||||
sourceType: item.sourceType,
|
||||
sourceCommonWordId: item.sourceCommonWordId,
|
||||
sourceCateId: item.sourceCateId,
|
||||
@ -398,7 +454,7 @@ const sendPhrase = (phrase) => {
|
||||
const prevPage = pages[pages.length - 2];
|
||||
|
||||
if (prevPage) {
|
||||
prevPage.$vm.sendCommonPhrase(phrase.content);
|
||||
prevPage.$vm.sendCommonPhrase(phrase);
|
||||
}
|
||||
|
||||
uni.navigateBack();
|
||||
@ -409,6 +465,7 @@ const showAddPhraseDialog = () => {
|
||||
phraseForm.value = {
|
||||
content: "",
|
||||
cateId: currentCategory.value || firstMyCategory.value?.id || "",
|
||||
files: [],
|
||||
};
|
||||
showPhrasePopup.value = true;
|
||||
};
|
||||
@ -418,6 +475,7 @@ const editPhrase = (phrase) => {
|
||||
phraseForm.value = {
|
||||
content: phrase.content,
|
||||
cateId: phrase.cateId || firstMyCategory.value?.id || "",
|
||||
files: normalizeFiles(phrase.files),
|
||||
};
|
||||
showPhrasePopup.value = true;
|
||||
};
|
||||
@ -427,6 +485,26 @@ const handlePhraseCategoryChange = (event) => {
|
||||
phraseForm.value.cateId = myCategories.value[index]?.id || "";
|
||||
};
|
||||
|
||||
const previewFiles = (files, index = 0) => {
|
||||
const urls = normalizeFiles(files).map((item) => item.url).filter(Boolean);
|
||||
if (!urls.length) return;
|
||||
uni.previewImage({ urls, current: urls[index] || urls[0] });
|
||||
};
|
||||
|
||||
const addPhraseImage = async () => {
|
||||
if (phraseForm.value.files.length >= 9) {
|
||||
uni.showToast({ title: "最多上传9张图片", icon: "none" });
|
||||
return;
|
||||
}
|
||||
const url = await chooseAndUploadImage({ count: 1 });
|
||||
if (!url) return;
|
||||
phraseForm.value.files.push({ type: "image", url: normalizeFileUrl(url), name: "图片" });
|
||||
};
|
||||
|
||||
const removePhraseImage = (index) => {
|
||||
phraseForm.value.files.splice(index, 1);
|
||||
};
|
||||
|
||||
const savePhrase = async () => {
|
||||
if (!phraseForm.value.cateId) {
|
||||
uni.showToast({ title: "请选择目录", icon: "none" });
|
||||
@ -448,6 +526,7 @@ const savePhrase = async () => {
|
||||
id: editingPhrase.value?.id,
|
||||
cateId: phraseForm.value.cateId,
|
||||
content: phraseForm.value.content,
|
||||
files: normalizeFiles(phraseForm.value.files),
|
||||
corpId,
|
||||
userId,
|
||||
});
|
||||
@ -459,6 +538,7 @@ const savePhrase = async () => {
|
||||
_id: editingPhrase.value?.id || result.data?._id,
|
||||
cateId: phraseForm.value.cateId,
|
||||
content: phraseForm.value.content.trim(),
|
||||
files: normalizeFiles(phraseForm.value.files),
|
||||
}, "user");
|
||||
|
||||
if (editingPhrase.value) {
|
||||
@ -583,6 +663,7 @@ const confirmFavorite = async () => {
|
||||
sourceCommonWordId: phrase.id,
|
||||
sourceCateId: phrase.cateId,
|
||||
collectClient: "wxapp",
|
||||
files: normalizeFiles(phrase.files),
|
||||
});
|
||||
|
||||
if (result.success && result.data) {
|
||||
@ -1071,6 +1152,20 @@ $border-color: #edf0f5;
|
||||
}
|
||||
}
|
||||
|
||||
.phrase-images {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.phrase-image {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 10rpx;
|
||||
background: #f2f4f7;
|
||||
}
|
||||
|
||||
.collect-tip {
|
||||
padding: 8rpx 0 28rpx;
|
||||
color: #98a2b3;
|
||||
@ -1235,6 +1330,60 @@ $border-color: #edf0f5;
|
||||
border: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.collect-images {
|
||||
margin: -14rpx 0 32rpx;
|
||||
}
|
||||
|
||||
.image-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.image-item,
|
||||
.image-add {
|
||||
position: relative;
|
||||
width: 142rpx;
|
||||
height: 142rpx;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
background: #f7f8fb;
|
||||
}
|
||||
|
||||
.image-thumb {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.image-remove {
|
||||
position: absolute;
|
||||
top: 6rpx;
|
||||
right: 6rpx;
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
line-height: 34rpx;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
.image-add {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #98a2b3;
|
||||
border: 1px dashed #cfd3dc;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.image-add-icon {
|
||||
font-size: 52rpx;
|
||||
line-height: 52rpx;
|
||||
}
|
||||
|
||||
.phrase-textarea {
|
||||
width: 100%;
|
||||
height: 220rpx;
|
||||
|
||||
@ -992,12 +992,13 @@ onHide(() => {
|
||||
console.log("✓ 页面隐藏,已清空当前会话ID");
|
||||
});
|
||||
|
||||
const sendCommonPhrase = (content) => {
|
||||
if (chatInputRef.value) {
|
||||
// 覆盖输入框内容,而不是直接发送
|
||||
chatInputRef.value.setInputText(content);
|
||||
}
|
||||
};
|
||||
const sendCommonPhrase = (phraseOrContent) => {
|
||||
const content = typeof phraseOrContent === "string" ? phraseOrContent : phraseOrContent?.content || "";
|
||||
if (chatInputRef.value) {
|
||||
// 覆盖输入框内容,而不是直接发送
|
||||
chatInputRef.value.setInputText(content);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理流式文本输入
|
||||
const handleStreamText = (char) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user