|
|
@@ -233,6 +233,12 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
@ViewChild('chatMessagesWrapper') chatMessagesWrapper!: ElementRef;
|
|
|
@ViewChild('chatInput') chatInputElement!: ElementRef;
|
|
|
|
|
|
+ // 图片预览
|
|
|
+ imagePreviewVisible = false;
|
|
|
+ imagePreviewUrl = '';
|
|
|
+ imagePreviewIndex = 0;
|
|
|
+ imagePreviewList: string[] = [];
|
|
|
+
|
|
|
// AI分析状态
|
|
|
aiAnalyzing: boolean = false;
|
|
|
aiAnalyzingImages: boolean = false;
|
|
|
@@ -704,24 +710,39 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 判断是否为CAD文件
|
|
|
+ * 判断是否为CAD文件(宽松模式)
|
|
|
*/
|
|
|
private isCADFile(file: File, fileName: string): boolean {
|
|
|
+ const lowerFileName = fileName.toLowerCase();
|
|
|
+
|
|
|
const cadExtensions = ['.dwg', '.dxf', '.rvt', '.ifc', '.step', '.stp', '.iges', '.igs', '.pdf'];
|
|
|
const cadMimeTypes = [
|
|
|
'application/vnd.autodesk.autocad.drawing',
|
|
|
'application/vnd.autodesk.autocad.drawing.macroenabled',
|
|
|
'application/pdf',
|
|
|
- 'application/x-pdf'
|
|
|
+ 'application/x-pdf',
|
|
|
+ 'application/acad',
|
|
|
+ 'application/x-acad',
|
|
|
+ 'application/autocad_dwg',
|
|
|
+ 'image/x-dwg',
|
|
|
+ 'image/vnd.dwg',
|
|
|
+ 'drawing/x-dwg'
|
|
|
];
|
|
|
|
|
|
- // 检查文件扩展名
|
|
|
- const hasCADExtension = cadExtensions.some(ext => fileName.endsWith(ext));
|
|
|
+ // 1. 检查文件扩展名
|
|
|
+ const hasCADExtension = cadExtensions.some(ext => lowerFileName.endsWith(ext));
|
|
|
+
|
|
|
+ // 2. 检查MIME类型(可能为空)
|
|
|
+ const hasCADMimeType = file.type && cadMimeTypes.includes(file.type);
|
|
|
|
|
|
- // 检查MIME类型
|
|
|
- const hasCADMimeType = cadMimeTypes.includes(file.type);
|
|
|
+ // 3. 检查文件名中是否包含CAD相关关键词(作为辅助判断)
|
|
|
+ const hasCADKeyword = lowerFileName.includes('cad') ||
|
|
|
+ lowerFileName.includes('dwg') ||
|
|
|
+ lowerFileName.includes('dxf') ||
|
|
|
+ lowerFileName.includes('drawing');
|
|
|
|
|
|
- return hasCADExtension || hasCADMimeType;
|
|
|
+ // 满足任一条件即认为是CAD文件
|
|
|
+ return hasCADExtension || hasCADMimeType || hasCADKeyword;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -896,7 +917,7 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
material: '数字设计',
|
|
|
texture: '线条清晰',
|
|
|
quality: '高精度',
|
|
|
- form: '几何精确',
|
|
|
+ form: '几何线条为主',
|
|
|
structure: '标准建筑结构',
|
|
|
colorComposition: '黑白线条'
|
|
|
},
|
|
|
@@ -1358,7 +1379,7 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
|
|
|
// 保存到服务器
|
|
|
await this.project.save();
|
|
|
-
|
|
|
+
|
|
|
console.log(`✅ 空间 ${spaceId} 的特殊需求已保存:`, requirements);
|
|
|
} catch (error) {
|
|
|
console.error('保存特殊需求失败:', error);
|
|
|
@@ -1376,7 +1397,6 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
this.uploading = true;
|
|
|
const targetProductId = productId || this.activeProductId;
|
|
|
const targetProjectId = this.projectId || this.project?.id;
|
|
|
- const finalImageType = imageType || 'other';
|
|
|
|
|
|
if (!targetProjectId) {
|
|
|
console.error('未找到项目ID,无法上传文件');
|
|
|
@@ -1406,7 +1426,7 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
targetProductId,
|
|
|
'requirements',
|
|
|
{
|
|
|
- imageType: finalImageType,
|
|
|
+ imageType: imageType,
|
|
|
uploadedFor: 'requirements_analysis',
|
|
|
spaceId: targetProductId,
|
|
|
deliveryType: 'requirements_reference',
|
|
|
@@ -1424,8 +1444,9 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
spaceId: targetProductId,
|
|
|
deliveryType: 'requirements_reference',
|
|
|
uploadedFor: 'requirements_analysis',
|
|
|
- imageType: finalImageType,
|
|
|
+ imageType: imageType,
|
|
|
analysis: {
|
|
|
+ // 预留AI分析结果字段
|
|
|
ai: null,
|
|
|
manual: null,
|
|
|
lastAnalyzedAt: null
|
|
|
@@ -1438,7 +1459,7 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
id: projectFile.id || '',
|
|
|
url: projectFile.get('fileUrl') || '',
|
|
|
name: projectFile.get('fileName') || file.name,
|
|
|
- type: finalImageType,
|
|
|
+ type: imageType,
|
|
|
uploadTime: projectFile.createdAt || new Date(),
|
|
|
spaceId: targetProductId,
|
|
|
tags: [],
|
|
|
@@ -1602,14 +1623,37 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
console.log('🔍 [企业微信拖拽] dataTransfer.types:', dataTransfer.types);
|
|
|
console.log('🔍 [企业微信拖拽] files.length:', dataTransfer.files.length);
|
|
|
|
|
|
- // 1. 提取图片文件
|
|
|
+ // 1. 提取所有文件(图片、CAD等)- 使用更宽松的判断逻辑
|
|
|
const images: File[] = [];
|
|
|
if (dataTransfer.files && dataTransfer.files.length > 0) {
|
|
|
for (let i = 0; i < dataTransfer.files.length; i++) {
|
|
|
const file = dataTransfer.files[i];
|
|
|
- if (file.type.startsWith('image/')) {
|
|
|
+ const fileName = file.name.toLowerCase();
|
|
|
+
|
|
|
+ // 图片扩展名列表
|
|
|
+ const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg', '.heic', '.heif'];
|
|
|
+ // CAD扩展名列表
|
|
|
+ const cadExtensions = ['.dwg', '.dxf', '.rvt', '.ifc', '.step', '.stp', '.iges', '.igs', '.pdf'];
|
|
|
+
|
|
|
+ // 检查是否为图片(MIME类型 或 扩展名)
|
|
|
+ const isImageByMime = file.type.startsWith('image/');
|
|
|
+ const isImageByExt = imageExtensions.some(ext => fileName.endsWith(ext));
|
|
|
+
|
|
|
+ // 检查是否为CAD文件
|
|
|
+ const isCADByExt = cadExtensions.some(ext => fileName.endsWith(ext));
|
|
|
+ const isCADByMime = [
|
|
|
+ 'application/vnd.autodesk.autocad.drawing',
|
|
|
+ 'application/pdf',
|
|
|
+ 'application/x-pdf'
|
|
|
+ ].includes(file.type);
|
|
|
+
|
|
|
+ // 只要满足任一条件就接受该文件
|
|
|
+ if (isImageByMime || isImageByExt || isCADByExt || isCADByMime) {
|
|
|
images.push(file);
|
|
|
- console.log(`📸 [图片文件] ${file.name} (${(file.size/1024).toFixed(2)}KB)`);
|
|
|
+ const fileType = (isCADByExt || isCADByMime) ? 'CAD文件' : '图片文件';
|
|
|
+ console.log(`📎 [${fileType}] ${file.name} (${(file.size/1024).toFixed(2)}KB, type: ${file.type || '未知'})`);
|
|
|
+ } else {
|
|
|
+ console.warn(`⚠️ [不支持的文件] ${file.name} (type: ${file.type})`);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -1669,14 +1713,6 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
return urls;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 检测是否为企业微信环境
|
|
|
- */
|
|
|
- isWeChatWorkEnv(): boolean {
|
|
|
- const ua = window.navigator.userAgent.toLowerCase();
|
|
|
- return ua.includes('wxwork') || ua.includes('qywechat');
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* 从URL下载并上传图片
|
|
|
*/
|
|
|
@@ -1712,31 +1748,6 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 判断是否为图片URL
|
|
|
- */
|
|
|
- private isImageUrl(url: string): boolean {
|
|
|
- const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg'];
|
|
|
- const lowerUrl = url.toLowerCase();
|
|
|
- return imageExts.some(ext => lowerUrl.includes(ext)) ||
|
|
|
- lowerUrl.includes('image') ||
|
|
|
- lowerUrl.includes('photo');
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 从URL提取文件名
|
|
|
- */
|
|
|
- private extractFileNameFromUrl(url: string): string | null {
|
|
|
- try {
|
|
|
- const urlObj = new URL(url);
|
|
|
- const pathname = urlObj.pathname;
|
|
|
- const segments = pathname.split('/');
|
|
|
- return segments[segments.length - 1] || null;
|
|
|
- } catch {
|
|
|
- return null;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* 清理文件名:移除特殊字符,保留扩展名
|
|
|
*/
|
|
|
@@ -1846,141 +1857,6 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
window?.fmode?.alert?.(message);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- // ==================== End: 企业微信拖拽支持 ====================
|
|
|
-
|
|
|
- // ==================== AI对话拖拽支持 ====================
|
|
|
-
|
|
|
- /**
|
|
|
- * 详细打印拖拽数据结构(用于调试)
|
|
|
- */
|
|
|
- private logDragDataStructure(event: DragEvent, context: string): void {
|
|
|
- console.log(`\n========== [${context}] 拖拽数据结构分析 ==========`);
|
|
|
-
|
|
|
- const dt = event.dataTransfer;
|
|
|
- if (!dt) {
|
|
|
- console.log('❌ dataTransfer 为空');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // 1. 基本信息
|
|
|
- console.log('\n📋 基本信息:');
|
|
|
- console.log(' dropEffect:', dt.dropEffect);
|
|
|
- console.log(' effectAllowed:', dt.effectAllowed);
|
|
|
- console.log(' types:', Array.from(dt.types));
|
|
|
-
|
|
|
- // 2. Files
|
|
|
- console.log('\n📁 Files 对象:');
|
|
|
- console.log(' files.length:', dt.files?.length || 0);
|
|
|
- if (dt.files && dt.files.length > 0) {
|
|
|
- for (let i = 0; i < dt.files.length; i++) {
|
|
|
- const file = dt.files[i];
|
|
|
- console.log(` [${i}] File对象:`, {
|
|
|
- name: file.name,
|
|
|
- size: file.size,
|
|
|
- type: file.type,
|
|
|
- lastModified: new Date(file.lastModified).toLocaleString()
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 3. Items
|
|
|
- console.log('\n📦 Items 对象:');
|
|
|
- console.log(' items.length:', dt.items?.length || 0);
|
|
|
- if (dt.items && dt.items.length > 0) {
|
|
|
- for (let i = 0; i < dt.items.length; i++) {
|
|
|
- const item = dt.items[i];
|
|
|
- console.log(` [${i}] DataTransferItem:`, {
|
|
|
- kind: item.kind,
|
|
|
- type: item.type
|
|
|
- });
|
|
|
-
|
|
|
- // 尝试获取item的内容
|
|
|
- if (item.kind === 'string') {
|
|
|
- item.getAsString((str) => {
|
|
|
- console.log(` → 字符串内容 (${item.type}):`, str.substring(0, 200));
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 4. getData测试
|
|
|
- console.log('\n📝 getData() 测试:');
|
|
|
- const commonTypes = ['text/plain', 'text/html', 'text/uri-list'];
|
|
|
-
|
|
|
- for (const type of commonTypes) {
|
|
|
- try {
|
|
|
- const data = dt.getData(type);
|
|
|
- if (data) {
|
|
|
- const preview = data.length > 200 ? data.substring(0, 200) + '...' : data;
|
|
|
- console.log(` ${type}:`, preview);
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- // 某些类型可能不可访问
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- console.log('\n========== 数据结构分析结束 ==========\n');
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 提取拖拽内容(支持企业微信特殊格式)
|
|
|
- */
|
|
|
- private extractDragContent(event: DragEvent): {
|
|
|
- files: File[];
|
|
|
- images: File[];
|
|
|
- text: string;
|
|
|
- html: string;
|
|
|
- urls: string[];
|
|
|
- hasContent: boolean;
|
|
|
- } {
|
|
|
- const dt = event.dataTransfer;
|
|
|
- if (!dt) return { files: [], images: [], text: '', html: '', urls: [], hasContent: false };
|
|
|
-
|
|
|
- // 提取文件
|
|
|
- const files: File[] = dt.files ? Array.from(dt.files) : [];
|
|
|
- const images = files.filter(f => f.type.startsWith('image/'));
|
|
|
-
|
|
|
- // 提取文字(智能处理企业微信格式)
|
|
|
- let text = dt.getData('text/plain') || '';
|
|
|
-
|
|
|
- // 🔥 企业微信特殊处理:检测文件名模式(如 4e370f418f0671be8a4fc68674266f3c.jpg)
|
|
|
- const wechatFilePattern = /\b[a-f0-9]{32}\.(jpg|jpeg|png|gif|webp)\b/gi;
|
|
|
- const matches = text.match(wechatFilePattern);
|
|
|
-
|
|
|
- if (matches && matches.length > 0) {
|
|
|
- console.log('🔍 检测到企业微信文件名格式:', matches);
|
|
|
- // 如果文本主要是文件名,清空文本内容(避免显示为文件名)
|
|
|
- if (text.replace(wechatFilePattern, '').trim().length < 20) {
|
|
|
- text = '';
|
|
|
- } else {
|
|
|
- // 否则只移除文件名部分
|
|
|
- text = text.replace(wechatFilePattern, '').replace(/\[图片\]/g, '').trim();
|
|
|
- }
|
|
|
- } else {
|
|
|
- // 普通处理:移除[图片]占位符
|
|
|
- text = text.replace(/\[图片\]/g, '').replace(/\[文件\]/g, '').trim();
|
|
|
- }
|
|
|
-
|
|
|
- // 提取HTML
|
|
|
- const html = dt.getData('text/html') || '';
|
|
|
-
|
|
|
- // 提取URL(过滤掉文件名形式的假URL)
|
|
|
- const uriList = dt.getData('text/uri-list') || '';
|
|
|
- const urls = uriList.split('\n')
|
|
|
- .map(url => url.trim())
|
|
|
- .filter(url => {
|
|
|
- // 过滤条件:必须是有效URL且不是#开头
|
|
|
- if (!url || url.startsWith('#')) return false;
|
|
|
- // 过滤掉纯文件名(没有协议头的)
|
|
|
- if (!url.includes('://')) return false;
|
|
|
- return true;
|
|
|
- });
|
|
|
-
|
|
|
- const hasContent = files.length > 0 || text.length > 0 || urls.length > 0;
|
|
|
-
|
|
|
- return { files, images, text, html, urls, hasContent };
|
|
|
- }
|
|
|
|
|
|
/**
|
|
|
* AI文件拖拽悬停
|
|
|
@@ -1988,7 +1864,12 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
onAIFileDragOver(event: DragEvent): void {
|
|
|
event.preventDefault();
|
|
|
event.stopPropagation();
|
|
|
- this.aiDesignDragOver = true;
|
|
|
+
|
|
|
+ // 🔥 只在状态改变时更新,避免频繁触发
|
|
|
+ if (!this.aiDesignDragOver) {
|
|
|
+ this.aiDesignDragOver = true;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -1997,11 +1878,20 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
onAIFileDragLeave(event: DragEvent): void {
|
|
|
event.preventDefault();
|
|
|
event.stopPropagation();
|
|
|
- this.aiDesignDragOver = false;
|
|
|
+
|
|
|
+ // 🔥 检查是否真的离开了拖拽区域(避免子元素触发)
|
|
|
+ const target = event.target as HTMLElement;
|
|
|
+ const relatedTarget = event.relatedTarget as HTMLElement;
|
|
|
+
|
|
|
+ // 如果relatedTarget不是拖拽区域的子元素,才重置状态
|
|
|
+ if (!relatedTarget || !target.contains(relatedTarget)) {
|
|
|
+ this.aiDesignDragOver = false;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * AI文件拖拽放下(增强版 - 支持企业微信)
|
|
|
+ * AI文件拖拽放下(增强版 - 支持企业微信多选群聊内容)
|
|
|
*/
|
|
|
async onAIFileDrop(event: DragEvent): Promise<void> {
|
|
|
event.preventDefault();
|
|
|
@@ -2009,59 +1899,68 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
|
|
|
// 🔥 立即重置拖拽状态,确保可以连续拖拽
|
|
|
this.aiDesignDragOver = false;
|
|
|
+ this.cdr.markForCheck();
|
|
|
|
|
|
- console.log('📥 [AI对话拖拽] 放下');
|
|
|
+ console.log('\n========== 📥 AI对话拖拽开始 ==========');
|
|
|
console.log('📥 [AI对话拖拽] 环境:', this.isWeChatWorkEnv() ? '企业微信' : '普通浏览器');
|
|
|
|
|
|
// 🔍 详细打印数据结构(调试用)
|
|
|
this.logDragDataStructure(event, 'AI对话区域');
|
|
|
|
|
|
- // 📊 提取内容
|
|
|
+ // 📊 提取内容(支持多选群聊内容)
|
|
|
const content = this.extractDragContent(event);
|
|
|
console.log('📊 [AI对话拖拽] 提取的内容:', content);
|
|
|
|
|
|
if (!content.hasContent) {
|
|
|
console.warn('⚠️ [AI对话拖拽] 未检测到有效内容');
|
|
|
- // 🔥 确保状态正确重置
|
|
|
- this.cdr.markForCheck();
|
|
|
+ console.log('========== ❌ AI对话拖拽结束 ==========\n');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
- this.aiDesignUploading = true;
|
|
|
+ // 🔥 合并所有文件(图片 + 其他文件)
|
|
|
+ const allFiles: File[] = [];
|
|
|
|
|
|
- // 1. 处理图片文件
|
|
|
+ // 1. 添加图片文件
|
|
|
if (content.images.length > 0) {
|
|
|
- console.log(`📸 [AI对话拖拽] 处理${content.images.length}张图片`);
|
|
|
- await this.processAIFiles(content.images);
|
|
|
+ console.log(`📸 [AI对话拖拽] 发现${content.images.length}张图片`);
|
|
|
+ allFiles.push(...content.images);
|
|
|
}
|
|
|
|
|
|
- // 2. 处理其他文件(PDF/CAD)
|
|
|
+ // 2. 添加其他文件(PDF/CAD)
|
|
|
const otherFiles = content.files.filter(f => !f.type.startsWith('image/'));
|
|
|
if (otherFiles.length > 0) {
|
|
|
- console.log(`📄 [AI对话拖拽] 处理${otherFiles.length}个文件`);
|
|
|
- await this.processAIFiles(otherFiles);
|
|
|
+ console.log(`📄 [AI对话拖拽] 发现${otherFiles.length}个文件`);
|
|
|
+ allFiles.push(...otherFiles);
|
|
|
}
|
|
|
|
|
|
- // 3. 处理图片URL(下载后添加)
|
|
|
+ // 3. 🔥 使用统一的handleAIFileUpload处理所有文件(确保数据结构一致)
|
|
|
+ if (allFiles.length > 0) {
|
|
|
+ console.log(`🔄 [AI对话拖拽] 开始处理${allFiles.length}个文件...`);
|
|
|
+ await this.handleAIFileUpload(allFiles);
|
|
|
+ console.log(`✅ [AI对话拖拽] 文件处理完成`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 处理图片URL(下载后添加)
|
|
|
if (content.urls.length > 0) {
|
|
|
console.log(`🔗 [AI对话拖拽] 处理${content.urls.length}个URL`);
|
|
|
await this.processImageUrls(content.urls);
|
|
|
}
|
|
|
|
|
|
- // 4. 处理文字内容(添加到AI对话输入框)
|
|
|
+ // 5. 处理文字内容(添加到AI对话输入框)
|
|
|
if (content.text) {
|
|
|
console.log(`📝 [AI对话拖拽] 处理文字内容: ${content.text.substring(0, 50)}...`);
|
|
|
this.appendTextToAIInput(content.text);
|
|
|
}
|
|
|
|
|
|
+ console.log('========== ✅ AI对话拖拽成功 ==========\n');
|
|
|
this.cdr.markForCheck();
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('❌ [AI对话拖拽] 处理失败:', error);
|
|
|
+ console.log('========== ❌ AI对话拖拽失败 ==========\n');
|
|
|
window?.fmode?.alert?.('处理拖拽内容失败,请重试');
|
|
|
} finally {
|
|
|
- this.aiDesignUploading = false;
|
|
|
// 🔥 确保拖拽状态完全重置,支持连续拖拽
|
|
|
this.aiDesignDragOver = false;
|
|
|
this.cdr.markForCheck();
|
|
|
@@ -2069,41 +1968,237 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 处理AI文件(图片和文档)
|
|
|
+ * 打印拖拽数据结构(调试用)
|
|
|
*/
|
|
|
- private async processAIFiles(files: FileList | File[]): Promise<void> {
|
|
|
- if (this.aiDesignUploadedFiles.length + files.length > 3) {
|
|
|
- window?.fmode?.alert('最多只能上传3个参考文件');
|
|
|
- return;
|
|
|
+ private logDragDataStructure(event: DragEvent, area: string): void {
|
|
|
+ const dt = event.dataTransfer;
|
|
|
+ if (!dt) return;
|
|
|
+
|
|
|
+ console.log(`🔍 [${area}] 数据传输对象:`, {
|
|
|
+ types: dt.types,
|
|
|
+ files: dt.files.length,
|
|
|
+ items: dt.items.length
|
|
|
+ });
|
|
|
+
|
|
|
+ // 打印所有可用的数据格式
|
|
|
+ for (let i = 0; i < dt.types.length; i++) {
|
|
|
+ const type = dt.types[i];
|
|
|
+ console.log(` 📋 类型${i + 1}: ${type}`);
|
|
|
}
|
|
|
+
|
|
|
+ // 打印文件信息
|
|
|
+ if (dt.files.length > 0) {
|
|
|
+ console.log(` 📁 文件列表 (${dt.files.length}个):`);
|
|
|
+ Array.from(dt.files).forEach((file, idx) => {
|
|
|
+ console.log(` ${idx + 1}. ${file.name} (${file.type}, ${(file.size / 1024).toFixed(2)}KB)`);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打印items信息
|
|
|
+ if (dt.items.length > 0) {
|
|
|
+ console.log(` 📦 Items列表 (${dt.items.length}个):`);
|
|
|
+ Array.from(dt.items).forEach((item, idx) => {
|
|
|
+ console.log(` ${idx + 1}. kind: ${item.kind}, type: ${item.type}`);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 提取拖拽内容(支持企业微信多选群聊内容)
|
|
|
+ */
|
|
|
+ private extractDragContent(event: DragEvent): {
|
|
|
+ files: File[];
|
|
|
+ images: File[];
|
|
|
+ text: string;
|
|
|
+ html: string;
|
|
|
+ urls: string[];
|
|
|
+ hasContent: boolean;
|
|
|
+ } {
|
|
|
+ const dt = event.dataTransfer;
|
|
|
+ if (!dt) {
|
|
|
+ return { files: [], images: [], text: '', html: '', urls: [], hasContent: false };
|
|
|
+ }
|
|
|
+
|
|
|
+ const result = {
|
|
|
+ files: [] as File[],
|
|
|
+ images: [] as File[],
|
|
|
+ text: '',
|
|
|
+ html: '',
|
|
|
+ urls: [] as string[],
|
|
|
+ hasContent: false
|
|
|
+ };
|
|
|
+
|
|
|
+ // 提取文件
|
|
|
+ if (dt.files.length > 0) {
|
|
|
+ result.files = Array.from(dt.files);
|
|
|
+ result.images = result.files.filter(f => f.type.startsWith('image/'));
|
|
|
+ console.log(`📁 [提取] 文件: ${result.files.length}个, 图片: ${result.images.length}个`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取文字(智能处理企业微信格式)
|
|
|
+ let text = dt.getData('text/plain') || '';
|
|
|
+
|
|
|
+ console.log('📝 [文字清理] 原始文本:', text.substring(0, 200));
|
|
|
+
|
|
|
+ // 🔥 企业微信特殊处理:移除发送人名字和时间戳
|
|
|
+ // 格式示例:"脑控徐福静 12/2 14:22:42\r\n[文件:]\r\n\r\n脑控徐福静 12/2 14:22:45"
|
|
|
+
|
|
|
+ // 1. 移除发送人名字 + 时间戳模式(如:脑控徐福静 12/2 14:22:42)
|
|
|
+ // 匹配模式:任意字符 + 空格 + 日期时间
|
|
|
+ const senderTimePattern = /^.+?\s+\d{1,2}\/\d{1,2}\s+\d{1,2}:\d{2}(:\d{2})?$/gm;
|
|
|
+ text = text.replace(senderTimePattern, '');
|
|
|
|
|
|
- for (let i = 0; i < files.length; i++) {
|
|
|
- const file = files[i];
|
|
|
- const extension = file.name.split('.').pop()?.toLowerCase() || '';
|
|
|
- const isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(extension);
|
|
|
+ // 2. 移除[文件:]、[图片]等占位符(增强版,匹配更多变体)
|
|
|
+ // 匹配模式:[文件]、[文件:]、[文件:xxx]、[图片]、文件、文件,文件等
|
|
|
+ text = text.replace(/\[文件[::]?[^\]]*\]/g, ''); // 匹配 [文件]、[文件:]、[文件:xxx]
|
|
|
+ text = text.replace(/\[图片[::]?[^\]]*\]/g, ''); // 匹配 [图片]、[图片:]、[图片:xxx]
|
|
|
+ text = text.replace(/^文件[,,、\s]*文件$/gm, ''); // 匹配独立行的"文件,文件"、"文件 文件"等
|
|
|
+ text = text.replace(/^文件[,,、\s]*$/gm, ''); // 匹配独立行的"文件"、"文件,"等
|
|
|
+ text = text.replace(/\s+文件\s+/g, ' '); // 匹配被空白包围的"文件"
|
|
|
+
|
|
|
+ // 3. 移除企业微信文件名模式(如 4e370f418f0671be8a4fc68674266f3c.jpg)
|
|
|
+ const wechatFilePattern = /\b[a-f0-9]{32}\.(jpg|jpeg|png|gif|webp|pdf|dwg|dxf)\b/gi;
|
|
|
+ const matches = text.match(wechatFilePattern);
|
|
|
+ if (matches && matches.length > 0) {
|
|
|
+ console.log('🔍 检测到企业微信文件名格式:', matches);
|
|
|
+ text = text.replace(wechatFilePattern, '');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 清理多余的空行和空白字符
|
|
|
+ text = text
|
|
|
+ .split(/\r?\n/) // 按行分割
|
|
|
+ .map(line => line.trim()) // 清理每行的首尾空白
|
|
|
+ .filter(line => line.length > 0) // 移除空行
|
|
|
+ .join('\n'); // 重新组合
|
|
|
+
|
|
|
+ console.log('✨ [文字清理] 清理后文本:', text.substring(0, 200));
|
|
|
+
|
|
|
+ result.text = text;
|
|
|
+
|
|
|
+ // 提取HTML
|
|
|
+ result.html = dt.getData('text/html') || '';
|
|
|
+ if (result.html) {
|
|
|
+ console.log(`📄 [提取] HTML: ${result.html.substring(0, 100)}...`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取URL
|
|
|
+ const urlData = dt.getData('text/uri-list') || '';
|
|
|
+ if (urlData) {
|
|
|
+ result.urls = urlData.split('\n').filter(url => url.trim().length > 0);
|
|
|
+ console.log(`🔗 [提取] URL: ${result.urls.length}个`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断是否有内容
|
|
|
+ result.hasContent = result.files.length > 0 ||
|
|
|
+ result.text.length > 0 ||
|
|
|
+ result.urls.length > 0;
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检测是否为企业微信环境
|
|
|
+ */
|
|
|
+ private isWeChatWorkEnv(): boolean {
|
|
|
+ const ua = window.navigator.userAgent.toLowerCase();
|
|
|
+ return ua.includes('wxwork') || ua.includes('qywechat');
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查是否为图片URL(宽松模式)
|
|
|
+ * 对于无扩展名的URL(如微信/企业微信的图片URL),默认尝试当作图片处理
|
|
|
+ */
|
|
|
+ private isImageUrl(url: string): boolean {
|
|
|
+ const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg', '.heic', '.heif'];
|
|
|
+ const lowerUrl = url.toLowerCase();
|
|
|
+
|
|
|
+ // 1. 检查是否包含图片扩展名
|
|
|
+ const hasImageExt = imageExtensions.some(ext => lowerUrl.includes(ext));
|
|
|
+
|
|
|
+ // 2. 检查URL中是否包含图片相关关键词
|
|
|
+ const hasImageKeyword = lowerUrl.includes('image') ||
|
|
|
+ lowerUrl.includes('photo') ||
|
|
|
+ lowerUrl.includes('img') ||
|
|
|
+ lowerUrl.includes('pic');
|
|
|
+
|
|
|
+ // 3. 检查是否为常见的图片CDN域名
|
|
|
+ const isImageCDN = lowerUrl.includes('qpic.cn') || // 腾讯图片CDN
|
|
|
+ lowerUrl.includes('file-cloud.fmode.cn') || // 项目CDN
|
|
|
+ (lowerUrl.includes('cdn') && /\.(jpg|jpeg|png|gif|webp)/i.test(lowerUrl));
|
|
|
+
|
|
|
+ // 4. 对于微信/企业微信的无扩展名图片(纯哈希值),也尝试当作图片
|
|
|
+ // 如果URL不包含明显的非图片扩展名,就当作图片尝试
|
|
|
+ const hasNonImageExt = ['.txt', '.doc', '.docx', '.xls', '.xlsx', '.pdf', '.zip', '.rar'].some(ext => lowerUrl.endsWith(ext));
|
|
|
+ const isHashUrl = /[a-f0-9]{32}/i.test(url); // 检测32位哈希值(微信图片常见格式)
|
|
|
+
|
|
|
+ // 满足任一条件即认为是图片URL
|
|
|
+ return hasImageExt || hasImageKeyword || isImageCDN || (!hasNonImageExt && isHashUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从URL提取文件名(增强版)
|
|
|
+ * 对于无扩展名的URL(如微信图片),自动添加.jpg扩展名
|
|
|
+ */
|
|
|
+ private extractFileNameFromUrl(url: string): string {
|
|
|
+ try {
|
|
|
+ const urlObj = new URL(url);
|
|
|
+ const pathname = urlObj.pathname;
|
|
|
+ const parts = pathname.split('/');
|
|
|
+ let fileName = parts[parts.length - 1] || '';
|
|
|
+
|
|
|
+ // 移除查询参数
|
|
|
+ fileName = fileName.split('?')[0];
|
|
|
|
|
|
- let url = '';
|
|
|
- if (isImage) {
|
|
|
- // 读取为DataURL用于预览
|
|
|
- url = await this.readFileAsDataURL(file);
|
|
|
+ // 如果文件名为空或只包含数字和字母(哈希值),生成新名称
|
|
|
+ if (!fileName || /^[a-f0-9]{32}$/i.test(fileName)) {
|
|
|
+ const timestamp = Date.now();
|
|
|
+ const random = Math.random().toString(36).substring(2, 8);
|
|
|
+ fileName = `image_${timestamp}_${random}.jpg`;
|
|
|
+ console.log(`📝 [生成文件名] ${fileName}`);
|
|
|
+ return fileName;
|
|
|
}
|
|
|
|
|
|
- this.aiDesignUploadedFiles.push({
|
|
|
- file: file,
|
|
|
- name: file.name,
|
|
|
- size: file.size,
|
|
|
- extension: extension,
|
|
|
- url: url
|
|
|
- });
|
|
|
+ // 检查是否有扩展名
|
|
|
+ const hasExtension = /\.[a-z0-9]{2,4}$/i.test(fileName);
|
|
|
|
|
|
- if (isImage) {
|
|
|
- this.aiDesignUploadedImages.push(url);
|
|
|
+ // 如果没有扩展名,根据URL判断并添加
|
|
|
+ if (!hasExtension) {
|
|
|
+ const lowerUrl = url.toLowerCase();
|
|
|
+
|
|
|
+ // 检查是否为CAD文件URL
|
|
|
+ if (lowerUrl.includes('dwg') || lowerUrl.includes('cad')) {
|
|
|
+ fileName += '.dwg';
|
|
|
+ } else if (lowerUrl.includes('dxf')) {
|
|
|
+ fileName += '.dxf';
|
|
|
+ } else if (lowerUrl.includes('pdf')) {
|
|
|
+ fileName += '.pdf';
|
|
|
+ } else {
|
|
|
+ // 默认为图片
|
|
|
+ fileName += '.jpg';
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`📝 [添加扩展名] ${fileName}`);
|
|
|
}
|
|
|
|
|
|
- console.log(`✅ [AI对话拖拽] 文件已添加: ${file.name}`);
|
|
|
+ return fileName;
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('⚠️ [文件名提取失败] 使用默认名称', error);
|
|
|
+ const timestamp = Date.now();
|
|
|
+ return `image_${timestamp}.jpg`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 处理AI文件(图片和文档)
|
|
|
+ * @deprecated 已废弃,请使用 handleAIFileUpload 方法
|
|
|
+ * 保留此方法仅为兼容性,内部调用 handleAIFileUpload
|
|
|
+ */
|
|
|
+ private async processAIFiles(files: FileList | File[]): Promise<void> {
|
|
|
+ console.warn('⚠️ processAIFiles已废弃,自动转发到handleAIFileUpload');
|
|
|
+ const fileArray = Array.from(files);
|
|
|
+ await this.handleAIFileUpload(fileArray);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 读取文件为DataURL
|
|
|
*/
|
|
|
@@ -2155,6 +2250,12 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
* 将文字内容添加到AI输入框
|
|
|
*/
|
|
|
private appendTextToAIInput(text: string): void {
|
|
|
+ // 只添加有实际内容的文字
|
|
|
+ if (!text || text.trim().length === 0) {
|
|
|
+ console.log('⚠️ [AI对话拖拽] 文字为空,跳过添加');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
// 如果有AI对话输入框,添加文字
|
|
|
if (this.aiChatInput) {
|
|
|
this.aiChatInput = this.aiChatInput
|
|
|
@@ -2165,10 +2266,149 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
|
|
|
|
console.log(`✅ [AI对话拖拽] 文字已添加到输入框: ${text.substring(0, 50)}...`);
|
|
|
+ this.cdr.markForCheck();
|
|
|
}
|
|
|
|
|
|
// ==================== End: AI对话拖拽支持 ====================
|
|
|
|
|
|
+ /**
|
|
|
+ * 预览图片
|
|
|
+ */
|
|
|
+ previewImage(url: string, index?: number): void {
|
|
|
+ this.imagePreviewUrl = url;
|
|
|
+ this.imagePreviewVisible = true;
|
|
|
+
|
|
|
+ // 构建预览列表(所有已上传的图片)
|
|
|
+ this.imagePreviewList = this.aiDesignUploadedFiles
|
|
|
+ .filter(f => f.isImage && f.url)
|
|
|
+ .map(f => f.url);
|
|
|
+
|
|
|
+ // 设置当前索引
|
|
|
+ if (index !== undefined) {
|
|
|
+ this.imagePreviewIndex = index;
|
|
|
+ } else {
|
|
|
+ this.imagePreviewIndex = this.imagePreviewList.indexOf(url);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ console.log('🖼️ 打开图片预览:', url.substring(0, 50));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 关闭图片预览
|
|
|
+ */
|
|
|
+ closeImagePreview(): void {
|
|
|
+ this.imagePreviewVisible = false;
|
|
|
+ this.imagePreviewUrl = '';
|
|
|
+ this.imagePreviewList = [];
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 上一张图片
|
|
|
+ */
|
|
|
+ previousImage(event?: Event): void {
|
|
|
+ if (event) {
|
|
|
+ event.stopPropagation();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.imagePreviewList.length === 0) return;
|
|
|
+
|
|
|
+ this.imagePreviewIndex = (this.imagePreviewIndex - 1 + this.imagePreviewList.length) % this.imagePreviewList.length;
|
|
|
+ this.imagePreviewUrl = this.imagePreviewList[this.imagePreviewIndex];
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 下一张图片
|
|
|
+ */
|
|
|
+ nextImage(event?: Event): void {
|
|
|
+ if (event) {
|
|
|
+ event.stopPropagation();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.imagePreviewList.length === 0) return;
|
|
|
+
|
|
|
+ this.imagePreviewIndex = (this.imagePreviewIndex + 1) % this.imagePreviewList.length;
|
|
|
+ this.imagePreviewUrl = this.imagePreviewList[this.imagePreviewIndex];
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 预览文件(PDF、CAD等)
|
|
|
+ */
|
|
|
+ previewFile(file: any): void {
|
|
|
+ if (!file || !file.url) {
|
|
|
+ console.warn('⚠️ 文件URL不存在');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('📄 预览文件:', file.name, file.category);
|
|
|
+
|
|
|
+ // 对于base64文件,需要转换为Blob URL或直接在新窗口打开
|
|
|
+ if (file.isBase64 && file.url.startsWith('data:')) {
|
|
|
+ // 将base64转换为Blob URL
|
|
|
+ const base64Data = file.url.split(',')[1];
|
|
|
+ const byteCharacters = atob(base64Data);
|
|
|
+ const byteNumbers = new Array(byteCharacters.length);
|
|
|
+ for (let i = 0; i < byteCharacters.length; i++) {
|
|
|
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
|
|
|
+ }
|
|
|
+ const byteArray = new Uint8Array(byteNumbers);
|
|
|
+ const blob = new Blob([byteArray], { type: file.type });
|
|
|
+ const blobUrl = URL.createObjectURL(blob);
|
|
|
+
|
|
|
+ // 在新窗口打开
|
|
|
+ window.open(blobUrl, '_blank');
|
|
|
+
|
|
|
+ // 5秒后释放URL
|
|
|
+ setTimeout(() => URL.revokeObjectURL(blobUrl), 5000);
|
|
|
+ } else {
|
|
|
+ // 普通URL直接在新窗口打开
|
|
|
+ window.open(file.url, '_blank');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 删除AI对话区域的图片
|
|
|
+ */
|
|
|
+ removeAIDialogImage(index: number): void {
|
|
|
+ if (index < 0 || index >= this.aiDesignUploadedFiles.length) return;
|
|
|
+
|
|
|
+ const removedFile = this.aiDesignUploadedFiles[index];
|
|
|
+ this.aiDesignUploadedFiles.splice(index, 1);
|
|
|
+
|
|
|
+ // 如果是图片,同时从图片数组中移除
|
|
|
+ if (removedFile.isImage && removedFile.url) {
|
|
|
+ const imgIndex = this.aiDesignUploadedImages.indexOf(removedFile.url);
|
|
|
+ if (imgIndex !== -1) {
|
|
|
+ this.aiDesignUploadedImages.splice(imgIndex, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('🗑️ 已移除文件:', removedFile.name);
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 触发文件选择器
|
|
|
+ */
|
|
|
+ triggerAIDialogFileInput(): void {
|
|
|
+ const input = document.createElement('input');
|
|
|
+ input.type = 'file';
|
|
|
+ input.multiple = true;
|
|
|
+ input.accept = 'image/*,.pdf,.dwg,.dxf,.dwf';
|
|
|
+
|
|
|
+ input.onchange = async (e: any) => {
|
|
|
+ const files = Array.from(e.target.files || []) as File[];
|
|
|
+ if (files.length > 0) {
|
|
|
+ await this.handleAIFileUpload(files);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ input.click();
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 删除参考图片 - 同时删除服务器文件
|
|
|
*/
|
|
|
@@ -2269,7 +2509,7 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
const projectFile = await this.projectFileService.uploadProjectFileWithRecord(
|
|
|
file,
|
|
|
targetProjectId,
|
|
|
- 'cad_drawing',
|
|
|
+ 'cad_file',
|
|
|
targetProductId,
|
|
|
'requirements', // stage参数
|
|
|
{
|
|
|
@@ -2740,6 +2980,9 @@ CAD文件分析:${JSON.stringify(this.aiAnalysisResults.cadAnalysis, null, 2)}
|
|
|
});
|
|
|
|
|
|
this.cdr.markForCheck();
|
|
|
+
|
|
|
+ // 保存对话历史到数据库
|
|
|
+ await this.saveChatHistory();
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('AI聊天失败:', error);
|
|
|
@@ -2829,7 +3072,7 @@ ${context}
|
|
|
const cadFiles = await this.projectFileService.getProjectFiles(
|
|
|
targetProjectId,
|
|
|
{
|
|
|
- fileType: 'cad_drawing',
|
|
|
+ fileType: 'cad_file',
|
|
|
stage: 'requirements'
|
|
|
}
|
|
|
);
|
|
|
@@ -3112,6 +3355,7 @@ ${context}
|
|
|
globalRequirements: this.globalRequirements,
|
|
|
spaceRequirements: this.spaceRequirements,
|
|
|
crossSpaceRequirements: this.crossSpaceRequirements,
|
|
|
+ spaceSpecialRequirements: this.spaceSpecialRequirements, // 🔥 保存特殊需求
|
|
|
referenceImages: this.referenceImages.map(img => ({
|
|
|
id: img.id,
|
|
|
url: img.url,
|
|
|
@@ -3130,6 +3374,22 @@ ${context}
|
|
|
aiAnalysisResults: this.aiAnalysisResults,
|
|
|
confirmedAt: new Date().toISOString()
|
|
|
};
|
|
|
+
|
|
|
+ // 🔥 保存AI对话历史(如果有选择的空间)
|
|
|
+ if (this.aiDesignCurrentSpace?.id && this.aiChatMessages.length > 0) {
|
|
|
+ if (!data.aiChatHistory) {
|
|
|
+ data.aiChatHistory = {};
|
|
|
+ }
|
|
|
+ data.aiChatHistory[this.aiDesignCurrentSpace.id] = {
|
|
|
+ messages: this.aiChatMessages.map(m => ({
|
|
|
+ role: m.role,
|
|
|
+ content: m.content,
|
|
|
+ timestamp: m.timestamp.toISOString(),
|
|
|
+ images: m.images
|
|
|
+ })),
|
|
|
+ lastUpdated: new Date().toISOString()
|
|
|
+ };
|
|
|
+ }
|
|
|
|
|
|
// 重新生成阶段截止时间 (基于当前确认时间与项目交付日期)
|
|
|
this.rebuildPhaseDeadlines(data, confirmedAt);
|
|
|
@@ -3827,16 +4087,6 @@ ${context}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 触发AI对话框文件选择
|
|
|
- */
|
|
|
- triggerAIDialogFileInput(): void {
|
|
|
- const element = document.getElementById('aiDesignFileInput') as HTMLInputElement;
|
|
|
- if (element) {
|
|
|
- element.click();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* 处理AI对话框文件选择
|
|
|
*/
|
|
|
@@ -3853,16 +4103,6 @@ ${context}
|
|
|
input.value = '';
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 移除AI对话框中的图片
|
|
|
- */
|
|
|
- removeAIDialogImage(index: number): void {
|
|
|
- this.aiDesignUploadedImages.splice(index, 1);
|
|
|
- this.aiDesignUploadedFiles.splice(index, 1);
|
|
|
- this.cdr.markForCheck();
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
/**
|
|
|
* 处理AI文件上传(统一处理点击和拖拽)
|
|
|
* 🔥 扩展支持:图片、PDF、CAD文件
|