|
|
@@ -255,6 +255,9 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
generating: boolean = false;
|
|
|
saving: boolean = false;
|
|
|
|
|
|
+ // NovaStorage实例(用于直接上传)
|
|
|
+ private storage: any = null;
|
|
|
+
|
|
|
// 模板引用变量
|
|
|
@ViewChild('chatMessages') chatMessagesContainer!: ElementRef;
|
|
|
|
|
|
@@ -746,12 +749,13 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 上传CAD文件
|
|
|
+ * 上传CAD文件(使用两步式上传)
|
|
|
*/
|
|
|
async uploadCADFiles(files: File[], spaceId: string): Promise<void> {
|
|
|
try {
|
|
|
this.uploading = true;
|
|
|
const targetProjectId = this.projectId || this.project?.id;
|
|
|
+ const cid = this.cid;
|
|
|
|
|
|
if (!targetProjectId) {
|
|
|
console.error('未找到项目ID,无法上传文件');
|
|
|
@@ -759,56 +763,58 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
|
|
|
|
for (const file of files) {
|
|
|
- // 文件大小验证 (50MB for CAD)
|
|
|
+ // 文件大小验证 (50MB)
|
|
|
if (file.size > 50 * 1024 * 1024) {
|
|
|
console.warn(`CAD文件 ${file.name} 超过50MB限制,跳过`);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
- // 上传CAD文件
|
|
|
- const projectFile = await this.projectFileService.uploadProjectFileWithRecord(
|
|
|
+ console.log(`📤 [CAD上传] 开始上传: ${file.name}, 大小: ${(file.size / 1024 / 1024).toFixed(2)}MB`);
|
|
|
+
|
|
|
+ // 🔥 使用ProjectFileService的uploadProjectFileWithRecord方法(有自动fallback机制)
|
|
|
+ const projectFileRecord = await this.projectFileService.uploadProjectFileWithRecord(
|
|
|
file,
|
|
|
targetProjectId,
|
|
|
'cad_file',
|
|
|
spaceId,
|
|
|
'requirements',
|
|
|
{
|
|
|
- uploadedFor: 'requirements_cad',
|
|
|
- spaceId: spaceId,
|
|
|
- uploadStage: 'requirements'
|
|
|
+ cadType: file.name.toLowerCase().endsWith('.dwg') ? 'dwg' : 'dxf',
|
|
|
+ uploadTime: new Date(),
|
|
|
+ uploader: this.currentUser?.get('name') || '未知用户'
|
|
|
},
|
|
|
- (progress) => {
|
|
|
- console.log(`CAD文件上传进度: ${progress}%`);
|
|
|
+ (progress: number) => {
|
|
|
+ console.log(`📊 CAD ${file.name} 上传进度: ${progress.toFixed(2)}%`);
|
|
|
}
|
|
|
);
|
|
|
|
|
|
+ console.log(`✅ [CAD上传] 文件上传并记录创建成功: ${file.name}`);
|
|
|
+ console.log(`🔗 URL: ${projectFileRecord.get('fileUrl')}`);
|
|
|
+
|
|
|
// 创建CAD文件记录
|
|
|
const uploadedCAD = {
|
|
|
- id: projectFile.id || '',
|
|
|
- url: projectFile.get('fileUrl') || '',
|
|
|
- name: projectFile.get('fileName') || file.name,
|
|
|
- uploadTime: projectFile.createdAt || new Date(),
|
|
|
- spaceId: spaceId,
|
|
|
+ id: projectFileRecord.id,
|
|
|
+ url: projectFileRecord.get('fileUrl'),
|
|
|
+ name: file.name,
|
|
|
+ uploadTime: new Date(),
|
|
|
size: file.size,
|
|
|
- projectFile: projectFile
|
|
|
+ projectFile: projectFileRecord
|
|
|
};
|
|
|
|
|
|
- if (uploadedCAD.id) {
|
|
|
- this.cadFiles.push(uploadedCAD);
|
|
|
- console.log(`✅ CAD文件上传成功: ${uploadedCAD.name}`);
|
|
|
-
|
|
|
- // 对CAD文件进行AI分析
|
|
|
- await this.analyzeCADFileWithAI(uploadedCAD, spaceId);
|
|
|
- }
|
|
|
+ this.cadFiles.push(uploadedCAD);
|
|
|
+ console.log(`✅ CAD文件上传成功: ${uploadedCAD.name}`);
|
|
|
+
|
|
|
+ // 对CAD文件进行AI分析
|
|
|
+ await this.analyzeCADFileWithAI(uploadedCAD, spaceId);
|
|
|
} catch (error) {
|
|
|
- console.error(`CAD文件上传失败: ${file.name}`, error);
|
|
|
+ console.error(`❌ [CAD上传] CAD文件上传失败: ${file.name}`, error);
|
|
|
+ window?.fmode?.alert(`CAD文件上传失败: ${file.name}`);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- this.cdr.markForCheck();
|
|
|
} catch (error) {
|
|
|
- console.error('CAD文件上传失败:', error);
|
|
|
+ console.error('❌ [CAD上传] CAD文件上传失败:', error);
|
|
|
window?.fmode?.alert('CAD文件上传失败,请重试');
|
|
|
} finally {
|
|
|
this.uploading = false;
|
|
|
@@ -816,12 +822,13 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 上传并分析图片
|
|
|
+ * 上传并分析图片(使用两步式上传)
|
|
|
*/
|
|
|
async uploadAndAnalyzeImages(files: File[], spaceId: string): Promise<void> {
|
|
|
try {
|
|
|
this.uploading = true;
|
|
|
const targetProjectId = this.projectId || this.project?.id;
|
|
|
+ const cid = this.cid;
|
|
|
|
|
|
if (!targetProjectId) {
|
|
|
console.error('未找到项目ID,无法上传文件');
|
|
|
@@ -835,48 +842,57 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- // 上传文件
|
|
|
- const projectFile = await this.projectFileService.uploadProjectFileWithRecord(
|
|
|
- file,
|
|
|
- targetProjectId,
|
|
|
- 'reference_image',
|
|
|
- spaceId,
|
|
|
- 'requirements',
|
|
|
- {
|
|
|
- uploadedFor: 'requirements_analysis',
|
|
|
- spaceId: spaceId,
|
|
|
- uploadStage: 'requirements'
|
|
|
- },
|
|
|
- (progress) => {
|
|
|
- console.log(`上传进度: ${progress}%`);
|
|
|
- }
|
|
|
- );
|
|
|
+ try {
|
|
|
+ console.log(`📤 [拖拽上传] 开始上传: ${file.name}, 大小: ${(file.size / 1024 / 1024).toFixed(2)}MB`);
|
|
|
|
|
|
- // 创建参考图片记录
|
|
|
- const uploadedFile = {
|
|
|
- id: projectFile.id || '',
|
|
|
- url: projectFile.get('fileUrl') || '',
|
|
|
- name: projectFile.get('fileName') || file.name,
|
|
|
- type: 'other', // 默认类型,AI分析后会更新
|
|
|
- uploadTime: projectFile.createdAt || new Date(),
|
|
|
- spaceId: spaceId,
|
|
|
- tags: [],
|
|
|
- projectFile: projectFile
|
|
|
- };
|
|
|
+ // 🔥 使用ProjectFileService的uploadProjectFileWithRecord方法(有自动fallback机制)
|
|
|
+ const projectFileRecord = await this.projectFileService.uploadProjectFileWithRecord(
|
|
|
+ file,
|
|
|
+ targetProjectId,
|
|
|
+ 'reference_image',
|
|
|
+ spaceId,
|
|
|
+ 'requirements',
|
|
|
+ {
|
|
|
+ imageType: 'other',
|
|
|
+ uploadTime: new Date(),
|
|
|
+ uploader: this.currentUser?.get('name') || '未知用户'
|
|
|
+ },
|
|
|
+ (progress: number) => {
|
|
|
+ console.log(`📊 ${file.name} 上传进度: ${progress.toFixed(2)}%`);
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ console.log(`✅ [拖拽上传] 文件上传并记录创建成功: ${file.name}`);
|
|
|
+ console.log(`🔗 URL: ${projectFileRecord.get('fileUrl')}`);
|
|
|
+ console.log(`💾 ProjectFile ID: ${projectFileRecord.id}`);
|
|
|
+
|
|
|
+ // 创建参考图片记录
|
|
|
+ const uploadedFile = {
|
|
|
+ id: projectFileRecord.id,
|
|
|
+ url: projectFileRecord.get('fileUrl'),
|
|
|
+ name: file.name,
|
|
|
+ type: 'other', // 默认类型,AI分析后会更新
|
|
|
+ uploadTime: new Date(),
|
|
|
+ spaceId: spaceId,
|
|
|
+ tags: [],
|
|
|
+ projectFile: projectFileRecord
|
|
|
+ };
|
|
|
|
|
|
- if (uploadedFile.id) {
|
|
|
this.analysisImageMap[uploadedFile.id] = uploadedFile;
|
|
|
this.referenceImages.push(uploadedFile);
|
|
|
|
|
|
// 触发AI分析
|
|
|
await this.analyzeImageWithAI(uploadedFile, spaceId);
|
|
|
+ } catch (fileError) {
|
|
|
+ console.error(`❌ [拖拽上传] 文件 ${file.name} 上传失败:`, fileError);
|
|
|
+ // 继续处理下一个文件
|
|
|
}
|
|
|
}
|
|
|
|
|
|
this.cdr.markForCheck();
|
|
|
|
|
|
} catch (error) {
|
|
|
- console.error('上传失败:', error);
|
|
|
+ console.error('❌ [拖拽上传] 上传失败:', error);
|
|
|
window?.fmode?.alert('文件上传失败,请重试');
|
|
|
} finally {
|
|
|
this.uploading = false;
|
|
|
@@ -1397,6 +1413,7 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
this.uploading = true;
|
|
|
const targetProductId = productId || this.activeProductId;
|
|
|
const targetProjectId = this.projectId || this.project?.id;
|
|
|
+ const cid = this.cid;
|
|
|
|
|
|
if (!targetProjectId) {
|
|
|
console.error('未找到项目ID,无法上传文件');
|
|
|
@@ -1418,65 +1435,55 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- // 使用ProjectFileService上传到服务器
|
|
|
- const projectFile = await this.projectFileService.uploadProjectFileWithRecord(
|
|
|
- file,
|
|
|
- targetProjectId,
|
|
|
- 'reference_image',
|
|
|
- targetProductId,
|
|
|
- 'requirements',
|
|
|
- {
|
|
|
- imageType: imageType,
|
|
|
- uploadedFor: 'requirements_analysis',
|
|
|
- spaceId: targetProductId,
|
|
|
- deliveryType: 'requirements_reference',
|
|
|
- uploadStage: 'requirements'
|
|
|
- },
|
|
|
- (progress) => {
|
|
|
- console.log(`上传进度: ${progress}%`);
|
|
|
- }
|
|
|
- );
|
|
|
+ try {
|
|
|
+ console.log(`📤 [需求阶段] 开始上传: ${file.name}, 大小: ${(file.size / 1024 / 1024).toFixed(2)}MB`);
|
|
|
|
|
|
- // 为ProjectFile添加扩展数据字段
|
|
|
- const existingData = projectFile.get('data') || {};
|
|
|
- projectFile.set('data', {
|
|
|
- ...existingData,
|
|
|
- spaceId: targetProductId,
|
|
|
- deliveryType: 'requirements_reference',
|
|
|
- uploadedFor: 'requirements_analysis',
|
|
|
- imageType: imageType,
|
|
|
- analysis: {
|
|
|
- // 预留AI分析结果字段
|
|
|
- ai: null,
|
|
|
- manual: null,
|
|
|
- lastAnalyzedAt: null
|
|
|
- }
|
|
|
- });
|
|
|
- await projectFile.save();
|
|
|
-
|
|
|
- // 创建参考图片记录
|
|
|
- const uploadedFile = {
|
|
|
- id: projectFile.id || '',
|
|
|
- url: projectFile.get('fileUrl') || '',
|
|
|
- name: projectFile.get('fileName') || file.name,
|
|
|
- type: imageType,
|
|
|
- uploadTime: projectFile.createdAt || new Date(),
|
|
|
- spaceId: targetProductId,
|
|
|
- tags: [],
|
|
|
- projectFile: projectFile
|
|
|
- };
|
|
|
+ // 🔥 使用ProjectFileService的uploadProjectFileWithRecord方法(有自动fallback机制)
|
|
|
+ const projectFileRecord = await this.projectFileService.uploadProjectFileWithRecord(
|
|
|
+ file,
|
|
|
+ targetProjectId,
|
|
|
+ 'reference_image',
|
|
|
+ targetProductId,
|
|
|
+ 'requirements',
|
|
|
+ {
|
|
|
+ imageType: imageType || 'other',
|
|
|
+ uploadTime: new Date(),
|
|
|
+ uploader: this.currentUser?.get('name') || '未知用户'
|
|
|
+ },
|
|
|
+ (progress: number) => {
|
|
|
+ console.log(`📊 ${file.name} 上传进度: ${progress.toFixed(2)}%`);
|
|
|
+ }
|
|
|
+ );
|
|
|
|
|
|
- // 添加到参考图片列表
|
|
|
- if (uploadedFile.id) {
|
|
|
+ console.log(`✅ [需求阶段] 文件上传并记录创建成功: ${file.name}`);
|
|
|
+ console.log(`🔗 URL: ${projectFileRecord.get('fileUrl')}`);
|
|
|
+ console.log(`💾 ProjectFile ID: ${projectFileRecord.id}`);
|
|
|
+
|
|
|
+ // 创建参考图片记录
|
|
|
+ const uploadedFile = {
|
|
|
+ id: projectFileRecord.id,
|
|
|
+ url: projectFileRecord.get('fileUrl'),
|
|
|
+ name: file.name,
|
|
|
+ type: imageType || 'other',
|
|
|
+ uploadTime: new Date(),
|
|
|
+ spaceId: targetProductId,
|
|
|
+ tags: [],
|
|
|
+ projectFile: projectFileRecord
|
|
|
+ };
|
|
|
+
|
|
|
+ // 添加到参考图片列表
|
|
|
this.analysisImageMap[uploadedFile.id] = uploadedFile;
|
|
|
this.referenceImages.push(uploadedFile);
|
|
|
+ } catch (fileError) {
|
|
|
+ console.error(`❌ [需求阶段] 文件 ${file.name} 上传失败:`, fileError);
|
|
|
+ // 继续处理下一个文件
|
|
|
}
|
|
|
}
|
|
|
|
|
|
this.cdr.markForCheck();
|
|
|
|
|
|
} catch (error) {
|
|
|
- console.error('上传失败:', error);
|
|
|
+ console.error('❌ [需求阶段] 上传失败:', error);
|
|
|
window?.fmode?.alert('文件上传失败,请重试');
|
|
|
} finally {
|
|
|
this.uploading = false;
|
|
|
@@ -1484,7 +1491,102 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 上传参考图片 - 使用ProjectFileService实际存储
|
|
|
+ * 创建CAD文件的ProjectFile记录
|
|
|
+ */
|
|
|
+ private async createCADProjectFileRecord(
|
|
|
+ fileUrl: string,
|
|
|
+ fileKey: string,
|
|
|
+ fileName: string,
|
|
|
+ fileSize: number,
|
|
|
+ projectId: string,
|
|
|
+ productId: string
|
|
|
+ ): Promise<any> {
|
|
|
+ const Parse = FmodeParse.with('nova');
|
|
|
+ const ProjectFile = Parse.Object.extend('ProjectFile');
|
|
|
+ const projectFile = new ProjectFile();
|
|
|
+
|
|
|
+ // 设置基本字段
|
|
|
+ projectFile.set('project', {
|
|
|
+ __type: 'Pointer',
|
|
|
+ className: 'Project',
|
|
|
+ objectId: projectId
|
|
|
+ });
|
|
|
+ projectFile.set('fileUrl', fileUrl);
|
|
|
+ projectFile.set('fileName', fileName);
|
|
|
+ projectFile.set('fileSize', fileSize);
|
|
|
+ projectFile.set('fileType', 'cad_file');
|
|
|
+ projectFile.set('stage', 'requirements');
|
|
|
+
|
|
|
+ // 设置数据字段
|
|
|
+ projectFile.set('data', {
|
|
|
+ spaceId: productId,
|
|
|
+ uploadedFor: 'requirements_cad',
|
|
|
+ uploadStage: 'requirements',
|
|
|
+ key: fileKey,
|
|
|
+ uploadedAt: new Date()
|
|
|
+ });
|
|
|
+
|
|
|
+ // 设置上传人
|
|
|
+ if (this.currentUser) {
|
|
|
+ projectFile.set('uploadedBy', this.currentUser);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存到数据库
|
|
|
+ await projectFile.save();
|
|
|
+ return projectFile;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建ProjectFile记录(用于持久化)- 需求阶段
|
|
|
+ */
|
|
|
+ private async createRequirementsProjectFileRecord(
|
|
|
+ fileUrl: string,
|
|
|
+ fileKey: string,
|
|
|
+ fileName: string,
|
|
|
+ fileSize: number,
|
|
|
+ projectId: string,
|
|
|
+ productId: string,
|
|
|
+ imageType: string
|
|
|
+ ): Promise<any> {
|
|
|
+ const Parse = FmodeParse.with('nova');
|
|
|
+ const ProjectFile = Parse.Object.extend('ProjectFile');
|
|
|
+ const projectFile = new ProjectFile();
|
|
|
+
|
|
|
+ // 设置基本字段
|
|
|
+ projectFile.set('project', {
|
|
|
+ __type: 'Pointer',
|
|
|
+ className: 'Project',
|
|
|
+ objectId: projectId
|
|
|
+ });
|
|
|
+ projectFile.set('fileUrl', fileUrl);
|
|
|
+ projectFile.set('fileName', fileName);
|
|
|
+ projectFile.set('fileSize', fileSize);
|
|
|
+ projectFile.set('fileType', 'reference_image');
|
|
|
+ projectFile.set('stage', 'requirements');
|
|
|
+
|
|
|
+ // 设置数据字段
|
|
|
+ projectFile.set('data', {
|
|
|
+ spaceId: productId,
|
|
|
+ imageType: imageType,
|
|
|
+ uploadedFor: 'requirements_analysis',
|
|
|
+ deliveryType: 'requirements_reference',
|
|
|
+ uploadStage: 'requirements',
|
|
|
+ key: fileKey,
|
|
|
+ uploadedAt: new Date()
|
|
|
+ });
|
|
|
+
|
|
|
+ // 设置上传人
|
|
|
+ if (this.currentUser) {
|
|
|
+ projectFile.set('uploadedBy', this.currentUser);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存到数据库
|
|
|
+ await projectFile.save();
|
|
|
+ return projectFile;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 上传参考图片 - 使用两步式上传(NovaStorage)
|
|
|
*/
|
|
|
async uploadReferenceImage(event: any, productId?: string): Promise<void> {
|
|
|
const files = event.target.files;
|
|
|
@@ -1494,6 +1596,7 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
this.uploading = true;
|
|
|
const targetProductId = productId || this.activeProductId;
|
|
|
const targetProjectId = this.projectId || this.project?.id;
|
|
|
+ const cid = this.cid;
|
|
|
|
|
|
if (!targetProjectId) {
|
|
|
console.error('未找到项目ID,无法上传文件');
|
|
|
@@ -1515,65 +1618,58 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- // 使用ProjectFileService上传到服务器
|
|
|
- const projectFile = await this.projectFileService.uploadProjectFileWithRecord(
|
|
|
- file,
|
|
|
- targetProjectId,
|
|
|
- 'reference_image',
|
|
|
- targetProductId,
|
|
|
- 'requirements', // stage参数
|
|
|
- {
|
|
|
- imageType: 'style',
|
|
|
- uploadedFor: 'requirements_analysis',
|
|
|
- // 补充:添加关联空间ID和交付类型标识
|
|
|
- spaceId: targetProductId,
|
|
|
- deliveryType: 'requirements_reference', // 需求阶段参考图片
|
|
|
- uploadStage: 'requirements'
|
|
|
- },
|
|
|
- (progress) => {
|
|
|
- console.log(`上传进度: ${progress}%`);
|
|
|
- }
|
|
|
- );
|
|
|
+ try {
|
|
|
+ console.log(`📤 [参考图片上传] 开始上传: ${file.name}`);
|
|
|
|
|
|
- // 补充:为ProjectFile添加扩展数据字段
|
|
|
- const existingData = projectFile.get('data') || {};
|
|
|
- projectFile.set('data', {
|
|
|
- ...existingData,
|
|
|
- spaceId: targetProductId,
|
|
|
- deliveryType: 'requirements_reference',
|
|
|
- uploadedFor: 'requirements_analysis',
|
|
|
- analysis: {
|
|
|
- // 预留AI分析结果字段
|
|
|
- ai: null,
|
|
|
- manual: null,
|
|
|
- lastAnalyzedAt: null
|
|
|
- }
|
|
|
- });
|
|
|
- await projectFile.save();
|
|
|
-
|
|
|
- // 创建参考图片记录
|
|
|
- const uploadedFile = {
|
|
|
- id: projectFile.id || '',
|
|
|
- url: projectFile.get('fileUrl') || '',
|
|
|
- name: projectFile.get('fileName') || file.name,
|
|
|
- type: 'style',
|
|
|
- uploadTime: projectFile.createdAt || new Date(),
|
|
|
- spaceId: targetProductId,
|
|
|
- tags: [],
|
|
|
- projectFile: projectFile // 保存ProjectFile对象引用
|
|
|
- };
|
|
|
+ // 🔥 使用ProjectFileService的uploadProjectFileWithRecord方法(有自动fallback机制)
|
|
|
+ const projectFileRecord = await this.projectFileService.uploadProjectFileWithRecord(
|
|
|
+ file,
|
|
|
+ targetProjectId,
|
|
|
+ 'reference_image',
|
|
|
+ targetProductId,
|
|
|
+ 'requirements',
|
|
|
+ {
|
|
|
+ imageType: 'style',
|
|
|
+ uploadTime: new Date(),
|
|
|
+ uploader: this.currentUser?.get('name') || '未知用户',
|
|
|
+ analysis: {
|
|
|
+ ai: null,
|
|
|
+ manual: null,
|
|
|
+ lastAnalyzedAt: null
|
|
|
+ }
|
|
|
+ },
|
|
|
+ (progress: number) => {
|
|
|
+ console.log(`📊 上传进度: ${progress.toFixed(2)}%`);
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ console.log(`✅ [参考图片上传] 文件上传并记录创建成功: ${file.name}`);
|
|
|
+ console.log(`🔗 URL: ${projectFileRecord.get('fileUrl')}`);
|
|
|
|
|
|
- // 添加到参考图片列表
|
|
|
- if (uploadedFile.id) {
|
|
|
+ // 创建参考图片记录
|
|
|
+ const uploadedFile = {
|
|
|
+ id: projectFileRecord.id,
|
|
|
+ url: projectFileRecord.get('fileUrl'),
|
|
|
+ name: file.name,
|
|
|
+ type: 'style',
|
|
|
+ uploadTime: new Date(),
|
|
|
+ spaceId: targetProductId,
|
|
|
+ tags: [],
|
|
|
+ projectFile: projectFileRecord
|
|
|
+ };
|
|
|
+
|
|
|
+ // 添加到参考图片列表
|
|
|
this.analysisImageMap[uploadedFile.id] = uploadedFile;
|
|
|
this.referenceImages.push(uploadedFile);
|
|
|
+ } catch (fileError) {
|
|
|
+ console.error(`❌ [参考图片上传] 文件上传失败: ${file.name}`, fileError);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
this.cdr.markForCheck();
|
|
|
|
|
|
} catch (error) {
|
|
|
- console.error('上传失败:', error);
|
|
|
+ console.error('❌ [参考图片上传] 上传失败:', error);
|
|
|
window?.fmode?.alert('文件上传失败,请重试');
|
|
|
} finally {
|
|
|
this.uploading = false;
|
|
|
@@ -1765,7 +1861,7 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 带重试的文件上传
|
|
|
+ * 带重试的文件上传(使用ProjectFileService,有自动fallback机制)
|
|
|
*/
|
|
|
private async uploadFileWithRetry(
|
|
|
file: File,
|
|
|
@@ -1774,47 +1870,29 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
fileName: string,
|
|
|
maxRetries: number = 3
|
|
|
): Promise<any> {
|
|
|
- let lastError: any = null;
|
|
|
+ console.log(`📤 开始上传: ${fileName} (ProjectFileService方法已内置重试和fallback)`);
|
|
|
|
|
|
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
|
- try {
|
|
|
- console.log(`📤 上传尝试 ${attempt}/${maxRetries}: ${fileName}`);
|
|
|
-
|
|
|
- const projectFile = await this.projectFileService.uploadProjectFileWithRecord(
|
|
|
- file,
|
|
|
- projectId,
|
|
|
- 'reference_image',
|
|
|
- spaceId,
|
|
|
- 'requirements',
|
|
|
- {
|
|
|
- uploadedFor: 'requirements_analysis',
|
|
|
- spaceId: spaceId,
|
|
|
- uploadStage: 'requirements',
|
|
|
- fileName: fileName
|
|
|
- },
|
|
|
- (progress) => {
|
|
|
- console.log(`📊 上传进度: ${progress}% [${fileName}]`);
|
|
|
- }
|
|
|
- );
|
|
|
-
|
|
|
- console.log(`✅ 上传成功 (尝试 ${attempt}): ${fileName}`);
|
|
|
- return projectFile;
|
|
|
-
|
|
|
- } catch (error: any) {
|
|
|
- lastError = error;
|
|
|
- console.error(`❌ 上传失败 (尝试 ${attempt}/${maxRetries}):`, error);
|
|
|
-
|
|
|
- // 如果是631错误且还有重试次数,等待后重试
|
|
|
- if (attempt < maxRetries) {
|
|
|
- const waitTime = attempt * 1000; // 递增等待时间
|
|
|
- console.log(`⏳ 等待 ${waitTime}ms 后重试...`);
|
|
|
- await this.delay(waitTime);
|
|
|
- }
|
|
|
+ // 🔥 直接使用ProjectFileService的uploadProjectFileWithRecord
|
|
|
+ // 该方法已经内置了重试机制和631错误的fallback(切换到Parse File)
|
|
|
+ const projectFileRecord = await this.projectFileService.uploadProjectFileWithRecord(
|
|
|
+ file,
|
|
|
+ projectId,
|
|
|
+ 'reference_image',
|
|
|
+ spaceId,
|
|
|
+ 'requirements',
|
|
|
+ {
|
|
|
+ imageType: 'other',
|
|
|
+ uploadTime: new Date(),
|
|
|
+ uploader: this.currentUser?.get('name') || '未知用户',
|
|
|
+ originalFileName: fileName
|
|
|
+ },
|
|
|
+ (progress: number) => {
|
|
|
+ console.log(`📊 ${fileName} 上传进度: ${progress.toFixed(2)}%`);
|
|
|
}
|
|
|
- }
|
|
|
+ );
|
|
|
|
|
|
- // 所有重试都失败
|
|
|
- throw new Error(`上传失败(已重试${maxRetries}次): ${lastError?.message || '未知错误'}`);
|
|
|
+ console.log(`✅ 文件上传并记录创建成功: ${fileName}`);
|
|
|
+ return projectFileRecord;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -2482,6 +2560,7 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
this.uploading = true;
|
|
|
const targetProductId = productId || this.activeProductId;
|
|
|
const targetProjectId = this.projectId || this.project?.id;
|
|
|
+ const cid = this.cid;
|
|
|
|
|
|
if (!targetProjectId) {
|
|
|
console.error('未找到项目ID,无法上传文件');
|
|
|
@@ -2505,62 +2584,54 @@ export class StageRequirementsComponent implements OnInit, OnDestroy {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- // 使用ProjectFileService上传到服务器
|
|
|
- const projectFile = await this.projectFileService.uploadProjectFileWithRecord(
|
|
|
- file,
|
|
|
- targetProjectId,
|
|
|
- 'cad_file',
|
|
|
- targetProductId,
|
|
|
- 'requirements', // stage参数
|
|
|
- {
|
|
|
- cadFormat: fileExtension.replace('.', ''),
|
|
|
- uploadedFor: 'requirements_analysis',
|
|
|
- // 补充:添加关联空间ID和交付类型标识
|
|
|
- spaceId: targetProductId,
|
|
|
- deliveryType: 'requirements_cad',
|
|
|
- uploadStage: 'requirements'
|
|
|
- },
|
|
|
- (progress) => {
|
|
|
- console.log(`上传进度: ${progress}%`);
|
|
|
- }
|
|
|
- );
|
|
|
+ try {
|
|
|
+ console.log(`📤 [uploadCAD] 开始上传: ${file.name}`);
|
|
|
|
|
|
- // 补充:为CAD文件ProjectFile添加扩展数据字段
|
|
|
- const existingData = projectFile.get('data') || {};
|
|
|
- projectFile.set('data', {
|
|
|
- ...existingData,
|
|
|
- spaceId: targetProductId,
|
|
|
- deliveryType: 'requirements_cad',
|
|
|
- uploadedFor: 'requirements_analysis',
|
|
|
- cadFormat: fileExtension.replace('.', ''),
|
|
|
- analysis: {
|
|
|
- // 预留CAD分析结果字段
|
|
|
- ai: null,
|
|
|
- manual: null,
|
|
|
- lastAnalyzedAt: null,
|
|
|
- spaceStructure: null,
|
|
|
- dimensions: null,
|
|
|
- constraints: [],
|
|
|
- opportunities: []
|
|
|
- }
|
|
|
- });
|
|
|
- await projectFile.save();
|
|
|
-
|
|
|
- // 创建CAD文件记录
|
|
|
- const uploadedFile = {
|
|
|
- id: projectFile.id || '',
|
|
|
- url: projectFile.get('fileUrl') || '',
|
|
|
- name: projectFile.get('fileName') || file.name,
|
|
|
- uploadTime: projectFile.createdAt || new Date(),
|
|
|
- size: projectFile.get('fileSize') || file.size,
|
|
|
- spaceId: targetProductId,
|
|
|
- projectFile: projectFile // 保存ProjectFile对象引用
|
|
|
- };
|
|
|
+ // 🔥 使用ProjectFileService的uploadProjectFileWithRecord方法(有自动fallback机制)
|
|
|
+ const projectFileRecord = await this.projectFileService.uploadProjectFileWithRecord(
|
|
|
+ file,
|
|
|
+ targetProjectId,
|
|
|
+ 'cad_file',
|
|
|
+ targetProductId,
|
|
|
+ 'requirements',
|
|
|
+ {
|
|
|
+ cadFormat: fileExtension.replace('.', ''),
|
|
|
+ uploadTime: new Date(),
|
|
|
+ uploader: this.currentUser?.get('name') || '未知用户',
|
|
|
+ analysis: {
|
|
|
+ ai: null,
|
|
|
+ manual: null,
|
|
|
+ lastAnalyzedAt: null,
|
|
|
+ spaceStructure: null,
|
|
|
+ dimensions: null,
|
|
|
+ constraints: [],
|
|
|
+ opportunities: []
|
|
|
+ }
|
|
|
+ },
|
|
|
+ (progress: number) => {
|
|
|
+ console.log(`📊 CAD上传进度: ${progress.toFixed(2)}%`);
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ console.log(`✅ [uploadCAD] 文件上传并记录创建成功: ${file.name}`);
|
|
|
+ console.log(`🔗 URL: ${projectFileRecord.get('fileUrl')}`);
|
|
|
|
|
|
- // 添加到CAD文件列表
|
|
|
- if (uploadedFile.id) {
|
|
|
+ // 创建CAD文件记录
|
|
|
+ const uploadedFile = {
|
|
|
+ id: projectFileRecord.id,
|
|
|
+ url: projectFileRecord.get('fileUrl'),
|
|
|
+ name: file.name,
|
|
|
+ uploadTime: new Date(),
|
|
|
+ size: file.size,
|
|
|
+ spaceId: targetProductId,
|
|
|
+ projectFile: projectFileRecord
|
|
|
+ };
|
|
|
+
|
|
|
+ // 添加到CAD文件列表
|
|
|
this.analysisFileMap[uploadedFile.id] = uploadedFile;
|
|
|
this.cadFiles.push(uploadedFile);
|
|
|
+ } catch (fileError) {
|
|
|
+ console.error(`❌ [uploadCAD] 文件上传失败: ${file.name}`, fileError);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -4199,12 +4270,11 @@ ${context}
|
|
|
console.log(`📤 准备处理${fileCategory}文件: ${file.name}, 大小: ${(file.size / 1024 / 1024).toFixed(2)}MB`);
|
|
|
|
|
|
let processedFile = file;
|
|
|
- let base64 = '';
|
|
|
+ let uploadedUrl = '';
|
|
|
|
|
|
try {
|
|
|
- // 🔥 根据文件类型进行不同处理
|
|
|
+ // 🔥 图片压缩处理(仅图片类型)
|
|
|
if (fileCategory === 'image') {
|
|
|
- // 图片处理:可能需要压缩
|
|
|
if (file.size > fileConfig.compressThreshold) {
|
|
|
console.log(`🔄 图片较大,尝试压缩...`);
|
|
|
try {
|
|
|
@@ -4214,69 +4284,74 @@ ${context}
|
|
|
console.warn('⚠️ 压缩失败,使用原文件:', compressError);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- // 转换为base64
|
|
|
- console.log(`🔄 将图片转换为base64格式...`);
|
|
|
- base64 = await this.fileToBase64(processedFile);
|
|
|
- console.log(`✅ 图片已转换为base64,大小: ${(base64.length / 1024).toFixed(2)}KB`);
|
|
|
-
|
|
|
- // 保存到图片数组(用于AI分析)
|
|
|
- this.aiDesignUploadedImages.push(base64);
|
|
|
-
|
|
|
- } else if (fileCategory === 'pdf' || fileCategory === 'cad') {
|
|
|
- // PDF/CAD文件处理:直接转base64
|
|
|
- console.log(`🔄 将${fileCategory.toUpperCase()}文件转换为base64格式...`);
|
|
|
- base64 = await this.fileToBase64(file);
|
|
|
- console.log(`✅ ${fileCategory.toUpperCase()}已转换为base64,大小: ${(base64.length / 1024).toFixed(2)}KB`);
|
|
|
-
|
|
|
- // 🔥 打印文件详细信息
|
|
|
- console.log(`\n📋 ${fileCategory.toUpperCase()}文件详情:`);
|
|
|
- console.log(` ├─ 原始大小: ${(file.size / 1024 / 1024).toFixed(2)}MB`);
|
|
|
- console.log(` ├─ Base64大小: ${(base64.length / 1024 / 1024).toFixed(2)}MB`);
|
|
|
- console.log(` ├─ 数据URL前缀: ${base64.substring(0, 50)}...`);
|
|
|
- console.log(` └─ 可用于: AI分析、文件存储、预览`);
|
|
|
}
|
|
|
|
|
|
- // 🔥 保存文件信息到统一数组
|
|
|
+ // 🔥 使用标准云存储上传(符合 storage.md 规范)
|
|
|
+ console.log(`☁️ 上传${fileCategory}文件到云存储: ${processedFile.name}`);
|
|
|
+
|
|
|
+ const categoryMap = {
|
|
|
+ 'image': 'ai_design_reference',
|
|
|
+ 'pdf': 'ai_document_reference',
|
|
|
+ 'cad': 'ai_cad_reference'
|
|
|
+ };
|
|
|
+
|
|
|
+ const projectFile = await this.projectFileService.uploadProjectFileWithRecord(
|
|
|
+ processedFile,
|
|
|
+ this.projectId!,
|
|
|
+ categoryMap[fileCategory] || 'ai_file_reference',
|
|
|
+ this.aiDesignCurrentSpace?.id,
|
|
|
+ 'requirements',
|
|
|
+ {
|
|
|
+ source: 'ai_design_analysis',
|
|
|
+ category: fileCategory,
|
|
|
+ uploadedFor: 'ai_design_analysis',
|
|
|
+ analysisReady: true
|
|
|
+ },
|
|
|
+ (progress) => {
|
|
|
+ console.log(`📊 上传进度: ${progress.toFixed(1)}%`);
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ // 获取上传后的URL
|
|
|
+ const attachment = projectFile.get('attach');
|
|
|
+ uploadedUrl = attachment?.url || attachment?.get?.('url') || '';
|
|
|
+
|
|
|
+ console.log(`✅ ${fileCategory}文件已上传到云存储:`, uploadedUrl);
|
|
|
+
|
|
|
+ // 🔥 保存文件信息到统一数组(用于AI分析)
|
|
|
this.aiDesignUploadedFiles.push({
|
|
|
- url: base64,
|
|
|
- name: file.name,
|
|
|
- type: file.type || `application/${fileExt}`,
|
|
|
- size: file.size,
|
|
|
+ url: uploadedUrl,
|
|
|
+ name: processedFile.name,
|
|
|
+ type: processedFile.type || `application/${fileExt}`,
|
|
|
+ size: processedFile.size,
|
|
|
extension: fileExt,
|
|
|
category: fileCategory,
|
|
|
- isBase64: true,
|
|
|
+ isBase64: false, // ✅ 不再使用base64
|
|
|
isImage: fileCategory === 'image',
|
|
|
isPDF: fileCategory === 'pdf',
|
|
|
isCAD: fileCategory === 'cad',
|
|
|
- uploadedAt: new Date().toISOString()
|
|
|
+ uploadedAt: new Date().toISOString(),
|
|
|
+ projectFileId: projectFile.id
|
|
|
});
|
|
|
|
|
|
- console.log(`💾 已保存${fileCategory}文件: ${file.name}`);
|
|
|
-
|
|
|
- // 🔥 保存到ProjectFile表(所有类型文件都保存)
|
|
|
- try {
|
|
|
- const categoryMap = {
|
|
|
- 'image': 'ai_design_reference',
|
|
|
- 'pdf': 'ai_document_reference',
|
|
|
- 'cad': 'ai_cad_reference'
|
|
|
- };
|
|
|
- await this.saveFileToProjectFile(processedFile, base64, categoryMap[fileCategory] || 'ai_file_reference');
|
|
|
- console.log(`✅ 文件已持久化存储到ProjectFile表`);
|
|
|
- } catch (saveError) {
|
|
|
- console.warn('⚠️ 保存到ProjectFile表失败,但不影响AI分析:', saveError);
|
|
|
+ // 如果是图片,还需要添加到图片数组(用于AI分析)
|
|
|
+ if (fileCategory === 'image') {
|
|
|
+ this.aiDesignUploadedImages.push(uploadedUrl);
|
|
|
}
|
|
|
|
|
|
- } catch (convertError: any) {
|
|
|
- console.error(`❌ 处理${fileCategory}文件失败: ${file.name}`, convertError);
|
|
|
- window?.fmode?.alert(`处理文件失败: ${file.name}\n${convertError?.message || '未知错误'}`);
|
|
|
+ console.log(`💾 已保存${fileCategory}文件到云存储: ${processedFile.name}`);
|
|
|
+ console.log(`🔗 访问URL: ${uploadedUrl}`);
|
|
|
+
|
|
|
+ } catch (uploadError: any) {
|
|
|
+ console.error(`❌ 上传${fileCategory}文件失败: ${file.name}`, uploadError);
|
|
|
+ window?.fmode?.alert(`上传文件失败: ${file.name}\n${uploadError?.message || '未知错误'}`);
|
|
|
continue;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
this.cdr.markForCheck();
|
|
|
- console.log(`✅ 已处理${this.aiDesignUploadedImages.length}个文件`);
|
|
|
- console.log(`🎯 所有图片已转为base64,可直接进行AI分析`);
|
|
|
+ console.log(`✅ 已成功上传${this.aiDesignUploadedFiles.length}个文件到云存储`);
|
|
|
+ console.log(`🎯 所有文件已上传完成,可直接进行AI分析`);
|
|
|
|
|
|
} catch (error: any) {
|
|
|
console.error('❌ 处理文件失败:', error);
|
|
|
@@ -4287,100 +4362,8 @@ ${context}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 🔥 将文件转换为base64格式
|
|
|
- */
|
|
|
- private async fileToBase64(file: File): Promise<string> {
|
|
|
- return new Promise<string>((resolve, reject) => {
|
|
|
- const reader = new FileReader();
|
|
|
- reader.onloadend = () => {
|
|
|
- const result = reader.result as string;
|
|
|
- resolve(result);
|
|
|
- };
|
|
|
- reader.onerror = () => {
|
|
|
- reject(new Error('文件读取失败'));
|
|
|
- };
|
|
|
- reader.readAsDataURL(file);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 🔥 保存文件到ProjectFile表(支持图片、PDF、CAD等)
|
|
|
- */
|
|
|
- private async saveFileToProjectFile(file: File, base64: string, fileCategory: string = 'ai_design_reference'): Promise<void> {
|
|
|
- try {
|
|
|
- if (!this.projectId) {
|
|
|
- console.warn('⚠️ 没有项目ID,无法保存到ProjectFile表');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- console.log(`💾 [ProjectFile] 开始保存文件到数据库: ${file.name},类型: ${file.type}`);
|
|
|
-
|
|
|
- // 创建Parse File(使用base64)
|
|
|
- const base64Data = base64.split(',')[1]; // 移除data URL前缀
|
|
|
- const parseFile = new Parse.File(file.name, { base64: base64Data });
|
|
|
-
|
|
|
- // 🔥 修复:使用正确的save方法(类型断言确保方法存在)
|
|
|
- const savedFile = await (parseFile as any).save();
|
|
|
- const fileUrl = savedFile ? savedFile.url() : '';
|
|
|
-
|
|
|
- console.log(`✅ [ProjectFile] Parse File保存成功:`, fileUrl);
|
|
|
-
|
|
|
- // 创建ProjectFile记录
|
|
|
- const ProjectFile = Parse.Object.extend('ProjectFile');
|
|
|
- const projectFile = new ProjectFile();
|
|
|
-
|
|
|
- // 设置项目关联
|
|
|
- projectFile.set('project', {
|
|
|
- __type: 'Pointer',
|
|
|
- className: 'Project',
|
|
|
- objectId: this.projectId
|
|
|
- });
|
|
|
-
|
|
|
- // 设置文件信息(使用attach字段)
|
|
|
- projectFile.set('attach', {
|
|
|
- name: file.name,
|
|
|
- originalName: file.name,
|
|
|
- url: fileUrl,
|
|
|
- mime: file.type,
|
|
|
- size: file.size,
|
|
|
- source: 'ai_design_analysis',
|
|
|
- description: 'AI设计分析参考图'
|
|
|
- });
|
|
|
-
|
|
|
- // 设置其他字段
|
|
|
- projectFile.set('key', `ai-design-analysis/${this.projectId}/${file.name}`);
|
|
|
- projectFile.set('uploadedAt', new Date());
|
|
|
- projectFile.set('category', 'ai_design_reference');
|
|
|
- projectFile.set('fileType', 'reference_image');
|
|
|
- projectFile.set('stage', 'requirements');
|
|
|
-
|
|
|
- // 如果有选择空间,保存关联
|
|
|
- if (this.aiDesignCurrentSpace?.id) {
|
|
|
- projectFile.set('product', {
|
|
|
- __type: 'Pointer',
|
|
|
- className: 'Product',
|
|
|
- objectId: this.aiDesignCurrentSpace.id
|
|
|
- });
|
|
|
-
|
|
|
- projectFile.set('data', {
|
|
|
- spaceId: this.aiDesignCurrentSpace.id,
|
|
|
- spaceName: this.aiDesignCurrentSpace.name || this.aiDesignCurrentSpace.productName,
|
|
|
- uploadedFor: 'ai_design_analysis',
|
|
|
- timestamp: new Date().toISOString()
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- // 保存到数据库
|
|
|
- await projectFile.save();
|
|
|
- console.log(`✅ [ProjectFile] 记录已创建:`, projectFile.id);
|
|
|
- console.log(`📂 [ProjectFile] 文件已保存到ProjectFile表,可在文件管理中查看`);
|
|
|
-
|
|
|
- } catch (error: any) {
|
|
|
- console.error('❌ [ProjectFile] 保存失败:', error);
|
|
|
- // 不抛出错误,允许继续AI分析
|
|
|
- }
|
|
|
- }
|
|
|
+ // ✅ 已移除 fileToBase64 方法 - 不再使用base64存储
|
|
|
+ // ✅ 已移除 saveFileToProjectFile 方法 - 统一使用 projectFileService.uploadProjectFileWithRecord()
|
|
|
|
|
|
/**
|
|
|
* 开始AI分析(直接调用AI进行真实分析)
|