From 4ce77ececcd75515bb21257e32be62d7e31087cf Mon Sep 17 00:00:00 2001 From: Gsh <15170702455@163.com> Date: Mon, 19 Jan 2026 22:15:01 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AF=B9=E8=AF=9D=E6=A1=86=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E7=B2=98=E8=B4=B4=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Yi.Ai.Vue3/.claude/settings.local.json | 5 +- .../pages/chat/layouts/chatDefaul/index.vue | 181 +++++++++- .../pages/chat/layouts/chatWithId/index.vue | 337 +++++++++++++++++- 3 files changed, 520 insertions(+), 3 deletions(-) diff --git a/Yi.Ai.Vue3/.claude/settings.local.json b/Yi.Ai.Vue3/.claude/settings.local.json index 13e6d318..776c8df4 100644 --- a/Yi.Ai.Vue3/.claude/settings.local.json +++ b/Yi.Ai.Vue3/.claude/settings.local.json @@ -3,7 +3,10 @@ "allow": [ "Bash(npx vue-tsc --noEmit)", "Bash(timeout 60 npx vue-tsc:*)", - "Bash(npm run dev:*)" + "Bash(npm run dev:*)", + "Bash(taskkill:*)", + "Bash(timeout /t 5 /nobreak)", + "Bash(git checkout:*)" ], "deny": [], "ask": [] diff --git a/Yi.Ai.Vue3/src/pages/chat/layouts/chatDefaul/index.vue b/Yi.Ai.Vue3/src/pages/chat/layouts/chatDefaul/index.vue index faa0ef01..3eefcc93 100644 --- a/Yi.Ai.Vue3/src/pages/chat/layouts/chatDefaul/index.vue +++ b/Yi.Ai.Vue3/src/pages/chat/layouts/chatDefaul/index.vue @@ -4,7 +4,7 @@ import type { FilesCardProps } from 'vue-element-plus-x/types/FilesCard'; import { ArrowLeftBold, ArrowRightBold, Loading } from '@element-plus/icons-vue'; import { useDebounceFn } from '@vueuse/core'; import { ElMessage } from 'element-plus'; -import { computed, nextTick, ref, watch } from 'vue'; +import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'; import ModelSelect from '@/components/ModelSelect/index.vue'; import WelecomeText from '@/components/WelecomeText/index.vue'; import Collapse from '@/layouts/components/Header/components/Collapse.vue'; @@ -26,6 +26,10 @@ const senderValue = ref(''); // 输入框内容 const senderRef = ref(); // Sender 组件引用 const isSending = ref(false); // 发送状态标志 +// 文件处理相关常量 +const MAX_FILE_SIZE = 3 * 1024 * 1024; +const MAX_TOTAL_CONTENT_LENGTH = 150000; + /** * 防抖发送消息函数 */ @@ -107,6 +111,181 @@ watch( }); }, ); + +/** + * 压缩图片 + */ +function compressImage(file: File, maxWidth = 1024, maxHeight = 1024, quality = 0.8): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = (e) => { + const img = new Image(); + img.onload = () => { + const canvas = document.createElement('canvas'); + let width = img.width; + let height = img.height; + + if (width > maxWidth || height > maxHeight) { + const ratio = Math.min(maxWidth / width, maxHeight / height); + width = width * ratio; + height = height * ratio; + } + + canvas.width = width; + canvas.height = height; + + const ctx = canvas.getContext('2d')!; + ctx.drawImage(img, 0, 0, width, height); + + canvas.toBlob( + (blob) => { + if (blob) { + resolve(blob); + } + else { + reject(new Error('压缩失败')); + } + }, + file.type, + quality, + ); + }; + img.onerror = reject; + img.src = e.target?.result as string; + }; + reader.onerror = reject; + reader.readAsDataURL(file); + }); +} + +/** + * 将 Blob 转换为 base64 + */ +function blobToBase64(blob: Blob): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + resolve(reader.result as string); + }; + reader.onerror = reject; + reader.readAsDataURL(blob); + }); +} + +/** + * 处理粘贴事件 + */ +async function handlePaste(event: ClipboardEvent) { + const items = event.clipboardData?.items; + if (!items) + return; + + const files: File[] = []; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (item.kind === 'file') { + const file = item.getAsFile(); + if (file) { + files.push(file); + } + } + } + + if (files.length === 0) + return; + + event.preventDefault(); + + // 计算已有文件的总内容长度 + let totalContentLength = 0; + filesStore.filesList.forEach((f) => { + if (f.fileType === 'text' && f.fileContent) { + totalContentLength += f.fileContent.length; + } + if (f.fileType === 'image' && f.base64) { + totalContentLength += Math.floor(f.base64.length * 0.5); + } + }); + + const arr: any[] = []; + + for (const file of files) { + // 验证文件大小 + if (file.size > MAX_FILE_SIZE) { + ElMessage.error(`文件 ${file.name} 超过 3MB 限制,已跳过`); + continue; + } + + const isImage = file.type.startsWith('image/'); + + if (isImage) { + try { + const compressionLevels = [ + { maxWidth: 800, maxHeight: 800, quality: 0.6 }, + { maxWidth: 600, maxHeight: 600, quality: 0.5 }, + { maxWidth: 400, maxHeight: 400, quality: 0.4 }, + ]; + + let compressedBlob: Blob | null = null; + let base64 = ''; + + for (const level of compressionLevels) { + compressedBlob = await compressImage(file, level.maxWidth, level.maxHeight, level.quality); + base64 = await blobToBase64(compressedBlob); + + const estimatedLength = Math.floor(base64.length * 0.5); + if (totalContentLength + estimatedLength <= MAX_TOTAL_CONTENT_LENGTH) { + totalContentLength += estimatedLength; + break; + } + + compressedBlob = null; + } + + if (!compressedBlob) { + ElMessage.error(`${file.name} 图片内容过大,请压缩后上传`); + continue; + } + + arr.push({ + uid: crypto.randomUUID(), + name: file.name, + fileSize: file.size, + file, + maxWidth: '200px', + showDelIcon: true, + imgPreview: true, + imgVariant: 'square', + url: base64, + isUploaded: true, + base64, + fileType: 'image', + }); + } + catch (error) { + console.error('处理图片失败:', error); + ElMessage.error(`${file.name} 处理失败`); + } + } + else { + ElMessage.warning(`${file.name} 不支持粘贴,请使用上传按钮`); + } + } + + if (arr.length > 0) { + filesStore.setFilesList([...filesStore.filesList, ...arr]); + ElMessage.success(`已添加 ${arr.length} 个文件`); + } +} + +// 监听粘贴事件 +onMounted(() => { + document.addEventListener('paste', handlePaste); +}); + +onUnmounted(() => { + document.removeEventListener('paste', handlePaste); +});