文件:ai-design-analysis.component.ts (第306-318行)
修改前:
// Final update
const aiMsgIndex = this.aiChatMessages.findIndex(m => m.id === aiMsgId);
if (aiMsgIndex !== -1) {
this.aiChatMessages[aiMsgIndex].isLoading = false;
this.aiChatMessages[aiMsgIndex].isStreaming = false;
// ❌ 问题:直接覆盖,导致流式输出的内容丢失
this.aiChatMessages[aiMsgIndex].content = result.formattedContent || result.rawContent || '分析完成...';
}
修改后:
// 🔥 Final update:仅标记完成,不覆盖内容(内容已通过流式输出显示)
const aiMsgIndex = this.aiChatMessages.findIndex(m => m.id === aiMsgId);
if (aiMsgIndex !== -1) {
this.aiChatMessages[aiMsgIndex].isLoading = false;
this.aiChatMessages[aiMsgIndex].isStreaming = false;
// 🔥 如果流式输出的内容为空或太短,才使用完整内容
if (!this.aiChatMessages[aiMsgIndex].content || this.aiChatMessages[aiMsgIndex].content.length < 100) {
console.log('⚠️ 流式输出内容不足,使用完整内容');
this.aiChatMessages[aiMsgIndex].content = result.formattedContent || result.rawContent || '分析完成,请查看下方详细结果。';
} else {
console.log('✅ 保留流式输出的完整内容,长度:', this.aiChatMessages[aiMsgIndex].content.length);
}
}
效果:
文件:design-analysis-ai.service.ts (第218-225行)
修改前:
// 解析JSON结果
const analysisData = this.parseJSONAnalysis(analysisResult);
// ❌ 问题:无条件发送,导致重复输出
if (options.onContentStream && analysisData.formattedContent) {
console.log('📤 发送最终格式化内容到UI...');
options.onContentStream(analysisData.formattedContent);
}
resolve(analysisData);
修改后:
// 解析JSON结果
const analysisData = this.parseJSONAnalysis(analysisResult);
// 🔥 修复:不在这里重复发送内容,流式输出已经发送过了
// 如果需要确保内容完整,可以检查streamContent长度
if (options.onContentStream && (!streamContent || streamContent.length < 100) && analysisData.formattedContent) {
console.log('⚠️ 流式内容不足,补充发送最终格式化内容...');
options.onContentStream(analysisData.formattedContent);
} else {
console.log('✅ 流式内容已完整,跳过重复发送');
}
resolve(analysisData);
效果:
文件:ai-design-analysis.component.html (第32-36行)
修改前:
<!-- 已上传的文件 -->
@if (aiDesignUploadedFiles.length > 0) {
<div class="uploaded-files">
<!-- ❌ 问题:没有绑定拖拽事件 -->
@for (file of aiDesignUploadedFiles; track file.url; let i = $index) {
...
}
</div>
}
修改后:
<!-- 已上传的文件 -->
@if (aiDesignUploadedFiles.length > 0) {
<div class="uploaded-files"
(drop)="onAIFileDrop($event)"
(dragover)="onAIFileDragOver($event)"
(dragleave)="onAIFileDragLeave($event)"
[class.drag-over]="aiDesignDragOver">
<!-- ✅ 现在可以继续拖拽文件 -->
@for (file of aiDesignUploadedFiles; track file.url; let i = $index) {
...
}
</div>
}
效果:
文件:ai-design-analysis.component.ts (第116-183行)
新增功能:
async onAIFileDrop(event: DragEvent) {
event.preventDefault();
event.stopPropagation();
this.aiDesignDragOver = false;
// 🔥 打印拖拽事件的完整结构(用于调试企业微信)
console.log('📥 [拖拽事件] 完整dataTransfer对象:', {
types: event.dataTransfer?.types,
items: Array.from(event.dataTransfer?.items || []).map((item, i) => ({
index: i,
kind: item.kind, // 'file' 或 'string'
type: item.type, // MIME类型
item: item
})),
files: Array.from(event.dataTransfer?.files || []).map((file, i) => ({
index: i,
name: file.name,
size: file.size,
type: file.type,
lastModified: file.lastModified
})),
effectAllowed: event.dataTransfer?.effectAllowed,
dropEffect: event.dataTransfer?.dropEffect
});
const files = event.dataTransfer?.files;
if (files && files.length > 0) {
console.log('✅ [拖拽事件] 检测到文件,数量:', files.length);
await this.processAIFiles(files);
} else {
console.warn('⚠️ [拖拽事件] 未检测到文件,尝试从items获取...');
// 🔥 企业微信可能将图片放在items中而非files中
const items = event.dataTransfer?.items;
if (items && items.length > 0) {
const fileList: File[] = [];
for (let i = 0; i < items.length; i++) {
const item = items[i];
console.log(`🔍 [拖拽事件] Item ${i}:`, {
kind: item.kind,
type: item.type
});
if (item.kind === 'file') {
const file = item.getAsFile();
if (file) {
console.log(`✅ [拖拽事件] 从Item ${i}获取到文件:`, file.name);
fileList.push(file);
}
} else if (item.kind === 'string') {
// 企业微信可能以字符串形式传递URL或base64
item.getAsString((str) => {
console.log(`📝 [拖拽事件] Item ${i}字符串内容:`, str.substring(0, 200));
});
}
}
if (fileList.length > 0) {
console.log('✅ [拖拽事件] 从items中获取到文件,数量:', fileList.length);
await this.processAIFiles(fileList);
} else {
console.error('❌ [拖拽事件] 无法从items中提取文件');
}
} else {
console.error('❌ [拖拽事件] dataTransfer中既无files也无items');
}
}
}
日志输出示例:
📥 [拖拽事件] 完整dataTransfer对象: {
types: ['Files'],
items: [
{ index: 0, kind: 'file', type: 'image/jpeg', item: DataTransferItem }
],
files: [
{
index: 0,
name: '4e370f418f06671be8a4fc6867.jpg',
size: 762800,
type: 'image/jpeg',
lastModified: 1701234567890
}
],
effectAllowed: 'all',
dropEffect: 'copy'
}
✅ [拖拽事件] 检测到文件,数量: 1
效果:
AI分析开始
↓
流式callback: 更新UI (显示内容A)
↓
分析完成
↓
手动更新: 覆盖UI (显示内容B) ❌ 重复输出
↓
用户看到内容闪烁
AI分析开始
↓
流式callback: 更新UI (显示内容A)
↓
分析完成
↓
检查: 内容A长度 >= 100?
├─ 是 → 保留内容A ✅ 不覆盖
└─ 否 → 使用内容B ✅ 备用方案
↓
用户看到完整内容,无闪烁
用户拖拽第1张图片 → 上传成功 ✅
用户拖拽第2张图片 → 无响应 ❌ (已上传区域没有绑定事件)
用户拖拽第1张图片 → 上传成功 ✅
用户拖拽第2张图片 → 上传成功 ✅ (已上传区域也支持拖拽)
用户拖拽第3张图片 → 上传成功 ✅
预期结果:
📥 AI流式响应: ...
🎨 开始格式化JSON对象...
✅ 格式化完成,长度: 2341
✅ 流式内容已完整,跳过重复发送
✅ 保留流式输出的完整内容,长度: 2341
验证点:
预期结果:
📥 [拖拽事件] 完整dataTransfer对象: { types: ['Files'], items: [...], files: [...] }
✅ [拖拽事件] 检测到文件,数量: 1
✅ [拖拽事件] 从items中获取到文件,数量: 1
验证点:
测试场景:
预期控制台输出:
场景1:图片消息
📥 [拖拽事件] 完整dataTransfer对象: {
types: ['Files'],
items: [
{ index: 0, kind: 'file', type: 'image/jpeg', item: DataTransferItem }
],
files: [
{ index: 0, name: '4e370f418f06671be8a4fc6867.jpg', size: 762800, type: 'image/jpeg' }
]
}
场景2:文件消息
📥 [拖拽事件] 完整dataTransfer对象: {
types: ['Files'],
items: [
{ index: 0, kind: 'file', type: 'application/pdf', item: DataTransferItem }
],
files: [
{ index: 0, name: 'design.pdf', size: 1024000, type: 'application/pdf' }
]
}
场景3:特殊格式(如果企业微信使用字符串传递)
📥 [拖拽事件] 完整dataTransfer对象: {
types: ['text/uri-list', 'text/plain'],
items: [
{ index: 0, kind: 'string', type: 'text/uri-list', item: DataTransferItem },
{ index: 1, kind: 'string', type: 'text/plain', item: DataTransferItem }
],
files: []
}
🔍 [拖拽事件] Item 0: { kind: 'string', type: 'text/uri-list' }
📝 [拖拽事件] Item 0字符串内容: https://file-cloud.fmode.cn/...
| 文件 | 修改内容 | 行数 |
|---|---|---|
ai-design-analysis.component.ts |
避免覆盖流式输出内容 | 306-318 |
ai-design-analysis.component.ts |
详细打印拖拽事件结构 | 116-183 |
ai-design-analysis.component.html |
已上传区域支持继续拖拽 | 32-36 |
design-analysis-ai.service.ts |
避免Service层重复发送内容 | 218-225 |
AI-DESIGN-ANALYSIS-FIXES.md |
修复总结文档(本文档) | 新建 |
如果控制台显示企业微信使用字符串传递URL:
if (item.kind === 'string' && item.type.includes('uri-list')) {
item.getAsString(async (urlString) => {
// 下载URL指向的图片
const response = await fetch(urlString);
const blob = await response.blob();
const file = new File([blob], 'image.jpg', { type: blob.type });
await this.processAIFiles([file]);
});
}
在SCSS中添加拖拽悬停样式:
.uploaded-files {
&.drag-over {
border: 2px dashed #1890ff;
background: rgba(24, 144, 255, 0.05);
.add-more {
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
}
}
}
如果内容很长,可以限制更新频率:
let lastUpdateTime = 0;
const updateInterval = 100; // 100ms更新一次
if (Date.now() - lastUpdateTime > updateInterval) {
options.onContentStream(displayText);
lastUpdateTime = Date.now();
}
现在可以在企业微信端测试拖拽功能了! 🎉