|
|
@@ -0,0 +1,2086 @@
|
|
|
+import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
|
|
|
+import { CommonModule } from '@angular/common';
|
|
|
+import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
|
|
+import { Subscription } from 'rxjs';
|
|
|
+import { UploadSuccessModalComponent } from '../upload-success-modal/upload-success-modal.component';
|
|
|
+import { GlobalPromptComponent } from '../global-prompt/global-prompt.component';
|
|
|
+import { ColorAnalysisService, ColorAnalysisResult } from '../../services/color-analysis.service';
|
|
|
+import { FullReportOverlayComponent } from '../full-report-overlay/full-report-overlay.component';
|
|
|
+import { CadAnalysisService, CadAnalysisResult } from '../../services/cad-analysis.service';
|
|
|
+import { RequirementMappingService } from '../../../services/requirement-mapping.service';
|
|
|
+import { RequirementMapping, SceneTemplate } from '../../../models/requirement-mapping.interface';
|
|
|
+
|
|
|
+// 素材文件接口
|
|
|
+interface MaterialFile {
|
|
|
+ id: string;
|
|
|
+ name: string;
|
|
|
+ type: 'text' | 'image' | 'cad';
|
|
|
+ url?: string;
|
|
|
+ content?: string;
|
|
|
+ size?: string;
|
|
|
+ uploadTime: Date;
|
|
|
+ analysis?: MaterialAnalysis;
|
|
|
+}
|
|
|
+
|
|
|
+// 素材解析结果接口
|
|
|
+interface MaterialAnalysis {
|
|
|
+ // 文本类解析
|
|
|
+ atmosphere?: { description: string; rgb?: string; colorTemp?: string };
|
|
|
+ residents?: { type: string; details: string };
|
|
|
+ scenes?: { preference: string; requirements: string };
|
|
|
+ keywords?: { positive: string[]; negative: string[] };
|
|
|
+
|
|
|
+ // 图片类解析
|
|
|
+ mainColors?: { rgb: string; percentage: number }[];
|
|
|
+ colorTemperature?: string;
|
|
|
+ materialRatio?: { material: string; percentage: number }[];
|
|
|
+ spaceRatio?: number;
|
|
|
+
|
|
|
+ // CAD类解析
|
|
|
+ structuralElements?: { type: string; position: string; changeable: boolean }[];
|
|
|
+ spaceMetrics?: { room: string; ratio: string; width: string }[];
|
|
|
+ flowMetrics?: { area: string; width: string; compliance: boolean }[];
|
|
|
+
|
|
|
+ // 增强色彩分析
|
|
|
+ enhancedColorAnalysis?: {
|
|
|
+ colorWheel?: {
|
|
|
+ primaryHue: number;
|
|
|
+ saturation: number;
|
|
|
+ brightness: number;
|
|
|
+ dominantColors: Array<{ hue: number; saturation: number; brightness: number; percentage: number }>;
|
|
|
+ colorDistribution: string;
|
|
|
+ };
|
|
|
+ colorHarmony?: {
|
|
|
+ harmonyType: string;
|
|
|
+ harmonyScore: number;
|
|
|
+ complementaryColors: string[];
|
|
|
+ suggestions: string[];
|
|
|
+ };
|
|
|
+ colorTemperatureAnalysis?: {
|
|
|
+ kelvin: number;
|
|
|
+ warmCoolBalance: number;
|
|
|
+ description: string;
|
|
|
+ lightingRecommendations: string[];
|
|
|
+ };
|
|
|
+ colorPsychology?: {
|
|
|
+ primaryMood: string;
|
|
|
+ atmosphere: string;
|
|
|
+ psychologicalEffects: string[];
|
|
|
+ suitableSpaces: string[];
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
+ // 形体分析
|
|
|
+ formAnalysis?: {
|
|
|
+ lineAnalysis?: {
|
|
|
+ dominantLines: Array<{ type: string; percentage: number; characteristics: string[] }>;
|
|
|
+ lineQuality: { smoothness: number; precision: number; expressiveness: number };
|
|
|
+ visualFlow: { direction: string; rhythm: string; continuity: number };
|
|
|
+ };
|
|
|
+ geometryAnalysis?: {
|
|
|
+ primaryShapes: Array<{ shape: string; percentage: number; characteristics: string[] }>;
|
|
|
+ complexity: string;
|
|
|
+ symmetry: { type: string; score: number };
|
|
|
+ balance: { visual: number; compositional: number };
|
|
|
+ };
|
|
|
+ proportionAnalysis?: {
|
|
|
+ aspectRatio: number;
|
|
|
+ goldenRatio: number;
|
|
|
+ proportionHarmony: number;
|
|
|
+ scaleRelationships: Array<{ element: string; scale: string; relationship: string }>;
|
|
|
+ };
|
|
|
+ overallAssessment?: {
|
|
|
+ formComplexity: number;
|
|
|
+ visualImpact: number;
|
|
|
+ functionalSuitability: number;
|
|
|
+ aestheticAppeal: number;
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
+ // 质感分析
|
|
|
+ textureAnalysis?: {
|
|
|
+ surfaceProperties?: {
|
|
|
+ roughness: { level: string; value: number; description: string };
|
|
|
+ glossiness: { level: string; value: number; reflectivity: number };
|
|
|
+ transparency: { level: string; value: number };
|
|
|
+ porosity: { level: string; value: number };
|
|
|
+ };
|
|
|
+ tactilePredict?: {
|
|
|
+ temperature: { perceived: string; thermalConductivity: number };
|
|
|
+ hardness: { level: string; value: number };
|
|
|
+ flexibility: { level: string; value: number };
|
|
|
+ weight: { perceived: string; density: number };
|
|
|
+ comfort: { tactileComfort: number; ergonomicSuitability: number };
|
|
|
+ };
|
|
|
+ materialClassification?: {
|
|
|
+ primaryMaterial: { category: string; confidence: number; subcategory?: string };
|
|
|
+ secondaryMaterials: Array<{ category: string; percentage: number; confidence: number }>;
|
|
|
+ materialProperties: { durability: number; maintenance: string; sustainability: number; costLevel: string };
|
|
|
+ };
|
|
|
+ textureQuality?: {
|
|
|
+ overallQuality: number;
|
|
|
+ consistency: number;
|
|
|
+ authenticity: number;
|
|
|
+ visualAppeal: number;
|
|
|
+ functionalSuitability: number;
|
|
|
+ ageingCharacteristics: { wearResistance: number; patinaPotential: number; maintenanceNeeds: string[] };
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
+ // 纹理图案分析
|
|
|
+ patternAnalysis?: {
|
|
|
+ patternRecognition?: {
|
|
|
+ primaryPatterns: Array<{ type: string; confidence: number; coverage: number; characteristics: string[] }>;
|
|
|
+ patternComplexity: { level: string; score: number; elements: number };
|
|
|
+ patternScale: { size: string; uniformity: number; variation: number };
|
|
|
+ };
|
|
|
+ repetitionAnalysis?: {
|
|
|
+ repetitionType: { primary: string; pattern: string; consistency: number };
|
|
|
+ spacing: { horizontal: number; vertical: number; uniformity: number; rhythm: string };
|
|
|
+ symmetry: { type: string; strength: number; axes: number };
|
|
|
+ tiling: { seamless: boolean; quality: number; edgeHandling: string };
|
|
|
+ };
|
|
|
+ textureClassification?: {
|
|
|
+ textureFamily: { primary: string; subcategory: string; confidence: number };
|
|
|
+ surfaceCharacter: { tactileQuality: string; visualDepth: number; dimensionality: string };
|
|
|
+ materialSuggestion: { likelyMaterials: string[]; fabricationMethod: string[]; applicationSuitability: string[] };
|
|
|
+ };
|
|
|
+ visualRhythm?: {
|
|
|
+ rhythmType: { primary: string; intensity: number; tempo: string };
|
|
|
+ movement: { direction: string; flow: number; energy: number };
|
|
|
+ emphasis: { focalPoints: number; contrast: number; hierarchy: number };
|
|
|
+ harmony: { overall: number; balance: number; unity: number };
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
+ // 灯光分析
|
|
|
+ lightingAnalysis?: {
|
|
|
+ lightSourceIdentification?: {
|
|
|
+ primarySources: Array<{
|
|
|
+ type: string;
|
|
|
+ subtype: string;
|
|
|
+ position: { direction: string; angle: number; distance: string };
|
|
|
+ intensity: number;
|
|
|
+ confidence: number;
|
|
|
+ }>;
|
|
|
+ lightingSetup: { complexity: string; sourceCount: number; dominantSource: string; lightingStyle: string };
|
|
|
+ };
|
|
|
+ illuminationAnalysis?: {
|
|
|
+ brightness: { overall: number; distribution: string; dynamicRange: number; exposure: string };
|
|
|
+ contrast: { level: number; type: string; areas: { highlights: number; midtones: number; shadows: number } };
|
|
|
+ colorTemperature: { kelvin: number; warmth: string; consistency: number; mixedLighting: boolean };
|
|
|
+ lightQuality: { softness: number; diffusion: number; directionality: number; evenness: number };
|
|
|
+ };
|
|
|
+ shadowAnalysis?: {
|
|
|
+ shadowPresence: { coverage: number; intensity: number; sharpness: string; definition: number };
|
|
|
+ shadowCharacteristics: { direction: string; length: string; density: number; falloff: string };
|
|
|
+ shadowTypes: Array<{ type: 'cast' | 'form' | 'occlusion' | 'contact'; prominence: number; contribution: string }>;
|
|
|
+ dimensionalEffect: { depth: number; volume: number; threedimensionality: number };
|
|
|
+ };
|
|
|
+ ambientAnalysis?: {
|
|
|
+ ambientLevel: { strength: number; uniformity: number; contribution: number };
|
|
|
+ lightingMood: { primary: string; intensity: number; emotional_impact: string[] };
|
|
|
+ atmosphericEffects: { haze: number; glare: number; reflections: number; transparency: number };
|
|
|
+ spatialPerception: { depth_enhancement: number; space_definition: number; focal_guidance: number };
|
|
|
+ };
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+// 需求指标接口
|
|
|
+interface RequirementMetric {
|
|
|
+ id: string;
|
|
|
+ category: 'color' | 'space' | 'material';
|
|
|
+ name: string;
|
|
|
+ value: any;
|
|
|
+ unit?: string;
|
|
|
+ range?: { min: number; max: number };
|
|
|
+ description?: string;
|
|
|
+}
|
|
|
+
|
|
|
+// 协作评论接口
|
|
|
+interface CollaborationComment {
|
|
|
+ id: string;
|
|
|
+ author: string;
|
|
|
+ role: 'customer-service' | 'designer' | 'client';
|
|
|
+ content: string;
|
|
|
+ timestamp: Date;
|
|
|
+ requirementId?: string;
|
|
|
+ status: 'pending' | 'resolved';
|
|
|
+}
|
|
|
+
|
|
|
+// 需求项接口
|
|
|
+interface RequirementItem {
|
|
|
+ id: string;
|
|
|
+ title: string;
|
|
|
+ description: string;
|
|
|
+ priority: 'high' | 'medium' | 'low';
|
|
|
+ status: 'pending' | 'confirmed' | 'rejected';
|
|
|
+ tags?: string[];
|
|
|
+ metrics?: RequirementMetric[];
|
|
|
+ comments?: CollaborationComment[];
|
|
|
+ lastUpdated: Date;
|
|
|
+ showComments?: boolean;
|
|
|
+}
|
|
|
+
|
|
|
+// 上传文件接口
|
|
|
+interface UploadedFile {
|
|
|
+ id: string;
|
|
|
+ name: string;
|
|
|
+ url: string;
|
|
|
+ size?: number;
|
|
|
+ type: 'image' | 'cad' | 'text';
|
|
|
+ preview?: string;
|
|
|
+}
|
|
|
+
|
|
|
+@Component({
|
|
|
+ selector: 'app-requirements-confirm-card',
|
|
|
+ standalone: true,
|
|
|
+ imports: [CommonModule, FormsModule, ReactiveFormsModule, UploadSuccessModalComponent, GlobalPromptComponent, FullReportOverlayComponent],
|
|
|
+ providers: [CadAnalysisService],
|
|
|
+ templateUrl: './requirements-confirm-card.html',
|
|
|
+ styleUrls: ['./requirements-confirm-card.scss'],
|
|
|
+ changeDetection: ChangeDetectionStrategy.Default // 确保使用默认变更检测策略
|
|
|
+})
|
|
|
+export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
|
|
|
+ @Input() projectId?: string;
|
|
|
+ @Output() requirementConfirmed = new EventEmitter<RequirementItem>();
|
|
|
+ @Output() progressUpdated = new EventEmitter<number>();
|
|
|
+ @Output() stageCompleted = new EventEmitter<{ stage: string; allStagesCompleted: boolean }>();
|
|
|
+ @Output() dataUpdated = new EventEmitter<any>(); // 新增:实时数据更新事件
|
|
|
+
|
|
|
+ // 表单
|
|
|
+ materialForm!: FormGroup;
|
|
|
+ commentForm!: FormGroup;
|
|
|
+ materialUploadForm!: FormGroup;
|
|
|
+
|
|
|
+ // 数据
|
|
|
+ materialFiles: MaterialFile[] = [];
|
|
|
+ materials: MaterialFile[] = [];
|
|
|
+ requirementMetrics: RequirementMetric[] = [];
|
|
|
+ collaborationComments: CollaborationComment[] = [];
|
|
|
+ requirementItems: RequirementItem[] = [];
|
|
|
+
|
|
|
+ // 状态
|
|
|
+ activeTab: 'materials' | 'mapping' | 'collaboration' | 'progress' = 'materials';
|
|
|
+ isUploading = false;
|
|
|
+ isAnalyzing = false;
|
|
|
+ dragOver = false;
|
|
|
+ showConsistencyWarning = false;
|
|
|
+ warningMessage = '';
|
|
|
+
|
|
|
+ // 自动保存相关状态
|
|
|
+ autoSaveEnabled = true;
|
|
|
+ lastSaveTime?: Date;
|
|
|
+ hasUnsavedChanges = false;
|
|
|
+ isSaving = false;
|
|
|
+ saveStatus: 'saved' | 'saving' | 'error' | 'unsaved' = 'saved';
|
|
|
+
|
|
|
+ // 流程状态
|
|
|
+ stageCompletionStatus = {
|
|
|
+ materialAnalysis: false,
|
|
|
+ requirementMapping: false,
|
|
|
+ collaboration: false,
|
|
|
+ progressReview: false
|
|
|
+ };
|
|
|
+
|
|
|
+ // 自动保存定时器
|
|
|
+ private autoSaveTimer?: any;
|
|
|
+
|
|
|
+ // 需求指标
|
|
|
+ colorIndicators = {
|
|
|
+ mainColor: { r: 255, g: 230, b: 180 },
|
|
|
+ colorTemperature: 2700,
|
|
|
+ colorRange: '暖色调',
|
|
|
+ saturation: 70,
|
|
|
+ brightness: 80,
|
|
|
+ contrast: 60
|
|
|
+ };
|
|
|
+
|
|
|
+ spaceIndicators = {
|
|
|
+ lineRatio: 60,
|
|
|
+ blankRatio: 30,
|
|
|
+ flowWidth: 0.9,
|
|
|
+ aspectRatio: 1.6,
|
|
|
+ ceilingHeight: 2.8,
|
|
|
+ lightingLevel: 300
|
|
|
+ };
|
|
|
+
|
|
|
+ materialIndicators = {
|
|
|
+ fabricRatio: 50,
|
|
|
+ woodRatio: 30,
|
|
|
+ metalRatio: 20,
|
|
|
+ smoothness: 7,
|
|
|
+ glossiness: 4,
|
|
|
+ texture: 6
|
|
|
+ };
|
|
|
+
|
|
|
+ // 指标范围配置
|
|
|
+ indicatorRanges = {
|
|
|
+ color: {
|
|
|
+ r: { min: 0, max: 255 },
|
|
|
+ g: { min: 0, max: 255 },
|
|
|
+ b: { min: 0, max: 255 },
|
|
|
+ temperature: { min: 2000, max: 6500 },
|
|
|
+ saturation: { min: 0, max: 100 },
|
|
|
+ brightness: { min: 0, max: 100 },
|
|
|
+ contrast: { min: 0, max: 100 }
|
|
|
+ },
|
|
|
+ space: {
|
|
|
+ lineRatio: { min: 0, max: 100 },
|
|
|
+ blankRatio: { min: 10, max: 80 },
|
|
|
+ flowWidth: { min: 0.6, max: 1.5 },
|
|
|
+ aspectRatio: { min: 1.0, max: 3.0 },
|
|
|
+ ceilingHeight: { min: 2.4, max: 4.0 },
|
|
|
+ lightingLevel: { min: 100, max: 800 }
|
|
|
+ },
|
|
|
+ material: {
|
|
|
+ fabricRatio: { min: 0, max: 100 },
|
|
|
+ woodRatio: { min: 0, max: 100 },
|
|
|
+ metalRatio: { min: 0, max: 100 },
|
|
|
+ smoothness: { min: 1, max: 10 },
|
|
|
+ glossiness: { min: 1, max: 10 },
|
|
|
+ texture: { min: 1, max: 10 }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 预设数据
|
|
|
+ presetAtmospheres = [
|
|
|
+ { name: '温馨暖调', rgb: '255,230,180', colorTemp: '2700-3000K', materials: ['木质', '布艺'] },
|
|
|
+ { name: '现代冷调', rgb: '200,220,240', colorTemp: '5000-6000K', materials: ['金属', '玻璃'] },
|
|
|
+ { name: '自然清新', rgb: '220,240,220', colorTemp: '4000-4500K', materials: ['竹木', '石材'] }
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 一致性检查
|
|
|
+ consistencyWarnings: string[] = [];
|
|
|
+ historyStates: any[] = [];
|
|
|
+
|
|
|
+ // 上传成功弹窗相关
|
|
|
+ showUploadSuccessModal = false;
|
|
|
+ uploadedFiles: { id: string; name: string; url: string; size?: number; type?: 'image' | 'cad' | 'text'; preview?: string }[] = [];
|
|
|
+ uploadType: 'image' | 'document' | 'mixed' = 'image';
|
|
|
+ colorAnalysisResult?: ColorAnalysisResult;
|
|
|
+ cadAnalysisResult?: CadAnalysisResult;
|
|
|
+ isAnalyzingColors = false; // 新增:色彩分析状态
|
|
|
+
|
|
|
+ // 全局提示组件状态
|
|
|
+ showGlobalPrompt = false;
|
|
|
+ promptMode: 'fullscreen' | 'corner' = 'corner';
|
|
|
+ promptPosition: 'top-right' | 'bottom-right' = 'bottom-right';
|
|
|
+ promptTitle = '上传成功!';
|
|
|
+ promptMessage = '';
|
|
|
+ // 全屏报告覆盖层
|
|
|
+ showFullReportOverlay = false;
|
|
|
+
|
|
|
+ // 需求映射测试相关属性
|
|
|
+ testSteps = [
|
|
|
+ { id: 'upload', name: '图片上传', status: 'pending' as 'pending' | 'in-progress' | 'completed' | 'error' },
|
|
|
+ { id: 'analysis', name: '图片分析', status: 'pending' as 'pending' | 'in-progress' | 'completed' | 'error' },
|
|
|
+ { id: 'mapping', name: '需求映射', status: 'pending' as 'pending' | 'in-progress' | 'completed' | 'error' },
|
|
|
+ { id: 'preview', name: '氛围预览', status: 'pending' as 'pending' | 'in-progress' | 'completed' | 'error' }
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 需求映射相关状态
|
|
|
+ isGeneratingMapping = false;
|
|
|
+ requirementMapping: RequirementMapping | null = null;
|
|
|
+ mappingError: string | null = null;
|
|
|
+ analysisError: string | null = null;
|
|
|
+ analysisResult: ColorAnalysisResult | undefined = undefined;
|
|
|
+
|
|
|
+ // 订阅管理
|
|
|
+ private subscriptions: Subscription[] = [];
|
|
|
+
|
|
|
+ constructor(
|
|
|
+ private fb: FormBuilder,
|
|
|
+ private cdr: ChangeDetectorRef,
|
|
|
+ private colorAnalysisService: ColorAnalysisService,
|
|
|
+ private cadAnalysisService: CadAnalysisService,
|
|
|
+ private requirementMappingService: RequirementMappingService
|
|
|
+ ) {}
|
|
|
+
|
|
|
+ ngOnInit() {
|
|
|
+ this.initializeForms();
|
|
|
+ this.initializeRequirements();
|
|
|
+ this.loadPresetMetrics();
|
|
|
+
|
|
|
+ // 启用自动保存
|
|
|
+ this.autoSaveEnabled = true;
|
|
|
+
|
|
|
+ // 定期检查阶段完成状态
|
|
|
+ setInterval(() => {
|
|
|
+ this.checkStageCompletion();
|
|
|
+ }, 5000);
|
|
|
+ }
|
|
|
+
|
|
|
+ ngOnDestroy(): void {
|
|
|
+ // 清理自动保存定时器
|
|
|
+ if (this.autoSaveTimer) {
|
|
|
+ clearTimeout(this.autoSaveTimer);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清理订阅
|
|
|
+ this.subscriptions.forEach(sub => sub.unsubscribe());
|
|
|
+
|
|
|
+ // 清理对象URL
|
|
|
+ this.uploadedFiles.forEach(file => {
|
|
|
+ if (file.url.startsWith('blob:')) {
|
|
|
+ URL.revokeObjectURL(file.url);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private initializeForms() {
|
|
|
+ this.materialForm = this.fb.group({
|
|
|
+ textContent: ['', Validators.required],
|
|
|
+ atmosphereDescription: [''],
|
|
|
+ residentInfo: [''],
|
|
|
+ scenePreferences: [''],
|
|
|
+ title: [''],
|
|
|
+ content: ['']
|
|
|
+ });
|
|
|
+
|
|
|
+ this.commentForm = this.fb.group({
|
|
|
+ content: ['', Validators.required],
|
|
|
+ requirementId: ['']
|
|
|
+ });
|
|
|
+
|
|
|
+ this.materialUploadForm = this.fb.group({
|
|
|
+ file: [''],
|
|
|
+ textContent: ['', Validators.required]
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private initializeRequirements() {
|
|
|
+ this.requirementItems = [
|
|
|
+ {
|
|
|
+ id: 'color-atmosphere',
|
|
|
+ title: '色彩氛围',
|
|
|
+ description: '整体色调和氛围营造',
|
|
|
+ priority: 'high',
|
|
|
+ status: 'pending',
|
|
|
+ lastUpdated: new Date()
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'space-layout',
|
|
|
+ title: '空间布局',
|
|
|
+ description: '功能分区和动线设计',
|
|
|
+ priority: 'high',
|
|
|
+ status: 'pending',
|
|
|
+ lastUpdated: new Date()
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'material-selection',
|
|
|
+ title: '材质选择',
|
|
|
+ description: '主要材质和质感要求',
|
|
|
+ priority: 'medium',
|
|
|
+ status: 'pending',
|
|
|
+ lastUpdated: new Date()
|
|
|
+ }
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ private loadPresetMetrics() {
|
|
|
+ this.requirementMetrics = [
|
|
|
+ {
|
|
|
+ id: 'main-color',
|
|
|
+ category: 'color',
|
|
|
+ name: '主色调',
|
|
|
+ value: { r: 255, g: 230, b: 180 },
|
|
|
+ description: '空间主要色彩'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'color-temp',
|
|
|
+ category: 'color',
|
|
|
+ name: '色温',
|
|
|
+ value: 3000,
|
|
|
+ unit: 'K',
|
|
|
+ range: { min: 2700, max: 6500 },
|
|
|
+ description: '照明色温范围'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'wood-ratio',
|
|
|
+ category: 'material',
|
|
|
+ name: '木质占比',
|
|
|
+ value: 60,
|
|
|
+ unit: '%',
|
|
|
+ range: { min: 0, max: 100 },
|
|
|
+ description: '木质材料使用比例'
|
|
|
+ }
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 素材上传功能
|
|
|
+ onFileSelected(event: Event, type: 'text' | 'image' | 'cad') {
|
|
|
+ const input = event.target as HTMLInputElement;
|
|
|
+ if (input.files) {
|
|
|
+ Array.from(input.files).forEach(file => {
|
|
|
+ this.uploadFile(file, type);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ onFileDrop(event: DragEvent, type: 'text' | 'image' | 'cad') {
|
|
|
+ event.preventDefault();
|
|
|
+ this.dragOver = false;
|
|
|
+
|
|
|
+ if (event.dataTransfer?.files) {
|
|
|
+ Array.from(event.dataTransfer.files).forEach(file => {
|
|
|
+ this.uploadFile(file, type);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ onDragOver(event: DragEvent) {
|
|
|
+ event.preventDefault();
|
|
|
+ this.dragOver = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ onDragLeave(event: DragEvent) {
|
|
|
+ event.preventDefault();
|
|
|
+ this.dragOver = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private uploadFile(file: File, type: 'text' | 'image' | 'cad') {
|
|
|
+ this.isUploading = true;
|
|
|
+
|
|
|
+ // 模拟文件上传
|
|
|
+ setTimeout(() => {
|
|
|
+ const materialFile: MaterialFile = {
|
|
|
+ id: this.generateId(),
|
|
|
+ name: file.name,
|
|
|
+ type: type,
|
|
|
+ url: URL.createObjectURL(file),
|
|
|
+ size: this.formatFileSize(file.size),
|
|
|
+ uploadTime: new Date()
|
|
|
+ };
|
|
|
+
|
|
|
+ this.materialFiles.push(materialFile);
|
|
|
+ this.materials.push(materialFile);
|
|
|
+ this.isUploading = false;
|
|
|
+
|
|
|
+ // 显示全局提示(角落模式,无遮罩,不遮挡操作)
|
|
|
+ this.promptTitle = '上传成功!';
|
|
|
+ this.promptMessage = `已上传文件:${file.name}`;
|
|
|
+ this.promptMode = 'corner';
|
|
|
+ this.promptPosition = 'bottom-right';
|
|
|
+ this.showGlobalPrompt = true;
|
|
|
+
|
|
|
+ // 显示上传成功弹窗
|
|
|
+ this.showUploadSuccessModal = true;
|
|
|
+ this.uploadedFiles = [{
|
|
|
+ id: materialFile.id,
|
|
|
+ name: file.name,
|
|
|
+ url: materialFile.url || '',
|
|
|
+ size: file.size,
|
|
|
+ type: type === 'text' ? 'text' : type === 'image' ? 'image' : 'cad'
|
|
|
+ }];
|
|
|
+ this.uploadType = type === 'text' ? 'document' : type === 'image' ? 'image' : 'mixed';
|
|
|
+
|
|
|
+ // 自动解析
|
|
|
+ this.analyzeMaterial(materialFile);
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 文本提交
|
|
|
+ onTextSubmit(): void {
|
|
|
+ if (this.materialUploadForm.valid) {
|
|
|
+ const formValue = this.materialUploadForm.value;
|
|
|
+ const textMaterial: MaterialFile = {
|
|
|
+ id: this.generateId(),
|
|
|
+ name: '文本需求',
|
|
|
+ type: 'text',
|
|
|
+ content: formValue.textContent,
|
|
|
+ uploadTime: new Date(),
|
|
|
+ size: this.formatFileSize(formValue.textContent?.length || 0)
|
|
|
+ };
|
|
|
+
|
|
|
+ this.materialFiles.push(textMaterial);
|
|
|
+ this.materials.push(textMaterial);
|
|
|
+ this.analyzeMaterial(textMaterial);
|
|
|
+ this.materialUploadForm.reset();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 素材解析功能
|
|
|
+ private analyzeMaterial(material: MaterialFile): void {
|
|
|
+ this.isAnalyzing = true;
|
|
|
+
|
|
|
+ // CAD采用服务分析,其它类型保持模拟
|
|
|
+ if (material.type === 'cad') {
|
|
|
+ const fileUrl = material.url || '';
|
|
|
+ this.cadAnalysisService.analyzeByUrl(fileUrl, material.name).subscribe({
|
|
|
+ next: (result) => {
|
|
|
+ this.cadAnalysisResult = result;
|
|
|
+ const analysis: MaterialAnalysis = {
|
|
|
+ structuralElements: result.structuralElements,
|
|
|
+ spaceMetrics: result.spaceMetrics,
|
|
|
+ flowMetrics: result.flowMetrics
|
|
|
+ };
|
|
|
+ material.analysis = analysis;
|
|
|
+ this.isAnalyzing = false;
|
|
|
+ this.updateRequirementsFromAnalysis(analysis);
|
|
|
+ },
|
|
|
+ error: () => {
|
|
|
+ this.cadAnalysisService.simulateCadAnalysis(material.name).subscribe({
|
|
|
+ next: (mock) => {
|
|
|
+ this.cadAnalysisResult = mock;
|
|
|
+ const analysis: MaterialAnalysis = {
|
|
|
+ structuralElements: mock.structuralElements,
|
|
|
+ spaceMetrics: mock.spaceMetrics,
|
|
|
+ flowMetrics: mock.flowMetrics
|
|
|
+ };
|
|
|
+ material.analysis = analysis;
|
|
|
+ this.isAnalyzing = false;
|
|
|
+ this.updateRequirementsFromAnalysis(analysis);
|
|
|
+ },
|
|
|
+ error: () => {
|
|
|
+ this.isAnalyzing = false;
|
|
|
+ console.error('CAD分析失败');
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 非CAD类型模拟
|
|
|
+ setTimeout(() => {
|
|
|
+ let analysis: MaterialAnalysis = {};
|
|
|
+ switch (material.type) {
|
|
|
+ case 'text':
|
|
|
+ analysis = this.analyzeTextMaterial(material);
|
|
|
+ break;
|
|
|
+ case 'image':
|
|
|
+ analysis = this.analyzeImageMaterial(material);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ material.analysis = analysis;
|
|
|
+ this.isAnalyzing = false;
|
|
|
+ this.updateRequirementsFromAnalysis(analysis);
|
|
|
+ }, 1200);
|
|
|
+ }
|
|
|
+
|
|
|
+ private analyzeTextMaterial(material: MaterialFile): MaterialAnalysis {
|
|
|
+ return {
|
|
|
+ atmosphere: {
|
|
|
+ description: '温馨暖调',
|
|
|
+ rgb: '255,230,180',
|
|
|
+ colorTemp: '2700-3000K'
|
|
|
+ },
|
|
|
+ residents: {
|
|
|
+ type: '亲子家庭',
|
|
|
+ details: '孩子年龄3-6岁'
|
|
|
+ },
|
|
|
+ scenes: {
|
|
|
+ preference: '瑜伽爱好者',
|
|
|
+ requirements: '需要瑜伽区收纳'
|
|
|
+ },
|
|
|
+ keywords: {
|
|
|
+ positive: ['科技感', '温馨', '实用'],
|
|
|
+ negative: ['复杂线条', '冷色调']
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private analyzeImageMaterial(material: MaterialFile): MaterialAnalysis {
|
|
|
+ return {
|
|
|
+ mainColors: [
|
|
|
+ { rgb: '255,230,180', percentage: 45 },
|
|
|
+ { rgb: '200,180,160', percentage: 30 },
|
|
|
+ { rgb: '180,160,140', percentage: 25 }
|
|
|
+ ],
|
|
|
+ colorTemperature: '3000K',
|
|
|
+ materialRatio: [
|
|
|
+ { material: '木质', percentage: 60 },
|
|
|
+ { material: '布艺', percentage: 25 },
|
|
|
+ { material: '金属', percentage: 15 }
|
|
|
+ ],
|
|
|
+ spaceRatio: 35
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private analyzeCADMaterial(material: MaterialFile): MaterialAnalysis {
|
|
|
+ return {
|
|
|
+ structuralElements: [
|
|
|
+ { type: '承重柱', position: '客厅中央', changeable: false },
|
|
|
+ { type: '门窗', position: '南墙', changeable: false }
|
|
|
+ ],
|
|
|
+ spaceMetrics: [
|
|
|
+ { room: '客厅', ratio: '16:9', width: '4.2m' },
|
|
|
+ { room: '卧室', ratio: '4:3', width: '3.6m' }
|
|
|
+ ],
|
|
|
+ flowMetrics: [
|
|
|
+ { area: '主通道', width: '1.2m', compliance: true },
|
|
|
+ { area: '次通道', width: '0.8m', compliance: false }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private updateRequirementsFromAnalysis(analysis: MaterialAnalysis) {
|
|
|
+ if (analysis.atmosphere?.rgb) {
|
|
|
+ const colorMetric = this.requirementMetrics.find(m => m.id === 'main-color');
|
|
|
+ if (colorMetric) {
|
|
|
+ const [r, g, b] = analysis.atmosphere.rgb.split(',').map(Number);
|
|
|
+ colorMetric.value = { r, g, b };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (analysis.materialRatio) {
|
|
|
+ const woodRatio = analysis.materialRatio.find(m => m.material === '木质');
|
|
|
+ if (woodRatio) {
|
|
|
+ const woodMetric = this.requirementMetrics.find(m => m.id === 'wood-ratio');
|
|
|
+ if (woodMetric) {
|
|
|
+ woodMetric.value = woodRatio.percentage;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 需求确认功能
|
|
|
+ confirmRequirement(requirementId: string) {
|
|
|
+ // 检查是否可以进行需求确认操作
|
|
|
+ if (!this.canProceedToNextStage('materialAnalysis')) {
|
|
|
+ alert('请先完成素材分析阶段的所有必要操作');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const requirement = this.requirementItems.find(r => r.id === requirementId);
|
|
|
+ if (requirement) {
|
|
|
+ requirement.status = 'confirmed';
|
|
|
+ requirement.lastUpdated = new Date();
|
|
|
+ this.requirementConfirmed.emit(requirement);
|
|
|
+ this.updateProgress();
|
|
|
+ this.triggerAutoSave();
|
|
|
+
|
|
|
+ // 检查阶段完成状态
|
|
|
+ this.checkStageCompletion();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ rejectRequirement(requirementId: string, reason?: string) {
|
|
|
+ // 检查是否可以进行需求拒绝操作
|
|
|
+ if (!this.canProceedToNextStage('materialAnalysis')) {
|
|
|
+ alert('请先完成素材分析阶段的所有必要操作');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const requirement = this.requirementItems.find(r => r.id === requirementId);
|
|
|
+ if (requirement) {
|
|
|
+ requirement.status = 'rejected';
|
|
|
+ requirement.lastUpdated = new Date();
|
|
|
+
|
|
|
+ if (reason) {
|
|
|
+ this.addCommentToRequirement(requirementId, reason, 'system');
|
|
|
+ }
|
|
|
+
|
|
|
+ this.updateProgress();
|
|
|
+ this.triggerAutoSave();
|
|
|
+
|
|
|
+ // 检查阶段完成状态
|
|
|
+ this.checkStageCompletion();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 协作功能
|
|
|
+ addComment() {
|
|
|
+ if (this.commentForm.valid) {
|
|
|
+ const comment: CollaborationComment = {
|
|
|
+ id: this.generateId(),
|
|
|
+ author: '当前用户',
|
|
|
+ role: 'designer',
|
|
|
+ content: this.commentForm.value.content,
|
|
|
+ timestamp: new Date(),
|
|
|
+ requirementId: this.commentForm.value.requirementId,
|
|
|
+ status: 'pending'
|
|
|
+ };
|
|
|
+
|
|
|
+ this.collaborationComments.push(comment);
|
|
|
+ this.commentForm.reset();
|
|
|
+ this.triggerAutoSave();
|
|
|
+
|
|
|
+ // 检查阶段完成状态
|
|
|
+ this.checkStageCompletion();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ resolveComment(commentId: string) {
|
|
|
+ const comment = this.collaborationComments.find(c => c.id === commentId);
|
|
|
+ if (comment) {
|
|
|
+ comment.status = 'resolved';
|
|
|
+ this.triggerAutoSave();
|
|
|
+
|
|
|
+ // 检查阶段完成状态
|
|
|
+ this.checkStageCompletion();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 进度管理
|
|
|
+ private updateProgress() {
|
|
|
+ const totalRequirements = this.requirementItems.length;
|
|
|
+ const confirmedRequirements = this.requirementItems.filter(r => r.status === 'confirmed').length;
|
|
|
+ const progress = totalRequirements > 0 ? (confirmedRequirements / totalRequirements) * 100 : 0;
|
|
|
+
|
|
|
+ // 实时更新进度条
|
|
|
+ this.updateProgressBar(progress);
|
|
|
+ this.progressUpdated.emit(progress);
|
|
|
+
|
|
|
+ // 检查是否完成所有需求
|
|
|
+ if (progress === 100) {
|
|
|
+ this.onRequirementCommunicationComplete();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private updateProgressBar(progress: number): void {
|
|
|
+ // 更新进度条显示
|
|
|
+ const progressBar = document.querySelector('.progress-bar-fill') as HTMLElement;
|
|
|
+ if (progressBar) {
|
|
|
+ progressBar.style.width = `${progress}%`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新进度文本
|
|
|
+ const progressText = document.querySelector('.progress-text') as HTMLElement;
|
|
|
+ if (progressText) {
|
|
|
+ progressText.textContent = `${Math.round(progress)}%`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加进度动画效果
|
|
|
+ this.animateProgressUpdate(progress);
|
|
|
+ }
|
|
|
+
|
|
|
+ private animateProgressUpdate(progress: number): void {
|
|
|
+ // 添加进度更新的视觉反馈
|
|
|
+ const progressContainer = document.querySelector('.progress-container') as HTMLElement;
|
|
|
+ if (progressContainer) {
|
|
|
+ progressContainer.classList.add('progress-updated');
|
|
|
+ setTimeout(() => {
|
|
|
+ progressContainer.classList.remove('progress-updated');
|
|
|
+ }, 500);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 需求沟通阶段完成处理
|
|
|
+ private onRequirementCommunicationComplete(): void {
|
|
|
+ console.log('需求沟通阶段完成,开始同步关键信息');
|
|
|
+ this.syncKeyInfoToSolutionConfirmation();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 同步关键信息到方案确认阶段
|
|
|
+ private syncKeyInfoToSolutionConfirmation(): void {
|
|
|
+ const keyInfo = {
|
|
|
+ colorAtmosphere: {
|
|
|
+ mainColor: this.colorIndicators.mainColor,
|
|
|
+ colorTemperature: this.colorIndicators.colorTemperature,
|
|
|
+ saturation: this.colorIndicators.saturation,
|
|
|
+ brightness: this.colorIndicators.brightness
|
|
|
+ },
|
|
|
+ spaceStructure: {
|
|
|
+ lineRatio: this.spaceIndicators.lineRatio,
|
|
|
+ blankRatio: this.spaceIndicators.blankRatio,
|
|
|
+ flowWidth: this.spaceIndicators.flowWidth
|
|
|
+ },
|
|
|
+ materialWeights: {
|
|
|
+ fabricRatio: this.materialIndicators.fabricRatio,
|
|
|
+ woodRatio: this.materialIndicators.woodRatio,
|
|
|
+ metalRatio: this.materialIndicators.metalRatio,
|
|
|
+ smoothness: this.materialIndicators.smoothness
|
|
|
+ },
|
|
|
+ presetAtmosphere: this.getSelectedPresetAtmosphere()
|
|
|
+ };
|
|
|
+
|
|
|
+ // 触发同步事件
|
|
|
+ this.requirementConfirmed.emit({
|
|
|
+ id: 'requirement-sync',
|
|
|
+ title: '需求沟通完成',
|
|
|
+ description: '关键信息已同步至方案确认阶段',
|
|
|
+ priority: 'high',
|
|
|
+ status: 'confirmed',
|
|
|
+ lastUpdated: new Date(),
|
|
|
+ metrics: this.convertToMetrics(keyInfo)
|
|
|
+ });
|
|
|
+
|
|
|
+ // 启动交付执行流程
|
|
|
+ setTimeout(() => {
|
|
|
+ this.startDeliveryExecution();
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ private getSelectedPresetAtmosphere(): any {
|
|
|
+ // 根据当前颜色指标找到最匹配的预设氛围
|
|
|
+ const currentRgb = `${this.colorIndicators.mainColor.r},${this.colorIndicators.mainColor.g},${this.colorIndicators.mainColor.b}`;
|
|
|
+ return this.presetAtmospheres.find(preset => preset.rgb === currentRgb) || this.presetAtmospheres[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ private convertToMetrics(keyInfo: any): RequirementMetric[] {
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ id: 'color-sync',
|
|
|
+ category: 'color',
|
|
|
+ name: '色彩氛围',
|
|
|
+ value: keyInfo.colorAtmosphere
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'space-sync',
|
|
|
+ category: 'space',
|
|
|
+ name: '空间结构',
|
|
|
+ value: keyInfo.spaceStructure
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'material-sync',
|
|
|
+ category: 'material',
|
|
|
+ name: '材质权重',
|
|
|
+ value: keyInfo.materialWeights
|
|
|
+ }
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 启动交付执行流程
|
|
|
+ private startDeliveryExecution(): void {
|
|
|
+ console.log('启动交付执行流程');
|
|
|
+
|
|
|
+ // 显示执行启动提示
|
|
|
+ this.showExecutionStartNotification();
|
|
|
+
|
|
|
+ // 这里可以触发路由跳转或其他业务逻辑
|
|
|
+ // 例如:this.router.navigate(['/project', this.projectId, 'execution']);
|
|
|
+ }
|
|
|
+
|
|
|
+ private showExecutionStartNotification(): void {
|
|
|
+ // 显示执行流程启动的通知
|
|
|
+ const notification = document.createElement('div');
|
|
|
+ notification.className = 'execution-notification';
|
|
|
+ notification.innerHTML = `
|
|
|
+ <div class="notification-content">
|
|
|
+ <svg viewBox="0 0 24 24" fill="currentColor">
|
|
|
+ <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
|
|
|
+ </svg>
|
|
|
+ <span>交付执行流程已启动</span>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+
|
|
|
+ document.body.appendChild(notification);
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ notification.remove();
|
|
|
+ }, 3000);
|
|
|
+ }
|
|
|
+
|
|
|
+ getProgressPercentage(): number {
|
|
|
+ if (this.requirementItems.length === 0) return 0;
|
|
|
+ const confirmedCount = this.requirementItems.filter(r => r.status === 'confirmed').length;
|
|
|
+ return Math.round((confirmedCount / this.requirementItems.length) * 100);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理优先级更新
|
|
|
+ onPriorityChange(requirementId: string, event: Event): void {
|
|
|
+ const target = event.target as HTMLSelectElement;
|
|
|
+ this.updateRequirementPriority(requirementId, target.value as 'high' | 'medium' | 'low');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查需求是否有待处理评论
|
|
|
+ hasUnreadComments(requirementId: string): boolean {
|
|
|
+ return this.getCommentsForRequirement(requirementId).some(c => c.status === 'pending');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理历史状态选择
|
|
|
+ onHistoryStateChange(event: Event): void {
|
|
|
+ const target = event.target as HTMLSelectElement;
|
|
|
+ const index = +target.value;
|
|
|
+ if (index >= 0) {
|
|
|
+ this.restoreHistoryState(index);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取指定状态的需求数量
|
|
|
+ getRequirementCountByStatus(status: 'confirmed' | 'pending' | 'rejected'): number {
|
|
|
+ return (this.requirementItems || []).filter(r => r.status === status).length;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取状态百分比
|
|
|
+ getStatusPercentage(status: 'confirmed' | 'pending' | 'rejected'): number {
|
|
|
+ const total = this.requirementItems.length;
|
|
|
+ if (total === 0) return 0;
|
|
|
+
|
|
|
+ const statusCount = this.getRequirementCountByStatus(status);
|
|
|
+ return Math.round((statusCount / total) * 100);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 指标映射功能
|
|
|
+ updateMetricValue(metricId: string, value: any) {
|
|
|
+ const metric = this.requirementMetrics.find(m => m.id === metricId);
|
|
|
+ if (metric) {
|
|
|
+ metric.value = value;
|
|
|
+ this.checkConsistency();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 滑动条数值同步方法
|
|
|
+ onSliderChange(key: string, value: number): void {
|
|
|
+ console.log(`滑动条变化: ${key} = ${value}`);
|
|
|
+ console.log('变化前的mainColor:', JSON.stringify(this.colorIndicators.mainColor));
|
|
|
+
|
|
|
+ // 直接更新数据,不需要额外的updateIndicatorValue调用
|
|
|
+ // 因为使用了双向绑定,数据已经自动更新
|
|
|
+ if (key === 'r' || key === 'g' || key === 'b') {
|
|
|
+ // 确保RGB值在有效范围内
|
|
|
+ const range = this.getIndicatorRange(key);
|
|
|
+ if (range) {
|
|
|
+ const clampedValue = Math.max(range.min, Math.min(range.max, value));
|
|
|
+ this.colorIndicators.mainColor[key as keyof typeof this.colorIndicators.mainColor] = clampedValue;
|
|
|
+ console.log(`RGB值已更新: ${key} = ${clampedValue}`);
|
|
|
+ console.log('变化后的mainColor:', JSON.stringify(this.colorIndicators.mainColor));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 触发颜色指标更新
|
|
|
+ this.updateColorIndicator('mainColor', this.colorIndicators.mainColor);
|
|
|
+ } else {
|
|
|
+ // 处理其他颜色指标
|
|
|
+ this.updateIndicatorValue(key, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.triggerAutoSave(); // 触发自动保存
|
|
|
+ this.checkStageCompletion(); // 检查阶段完成状态
|
|
|
+
|
|
|
+ // 发射实时数据更新事件
|
|
|
+ this.emitDataUpdate();
|
|
|
+
|
|
|
+ // 强制触发变更检测
|
|
|
+ this.cdr.detectChanges();
|
|
|
+ console.log('滑动条变更检测已触发');
|
|
|
+ }
|
|
|
+
|
|
|
+ onInputChange(key: string, value: number): void {
|
|
|
+ console.log(`输入框变化: ${key} = ${value}`);
|
|
|
+ console.log('变化前的mainColor:', JSON.stringify(this.colorIndicators.mainColor));
|
|
|
+
|
|
|
+ // 直接更新数据,不需要额外的updateIndicatorValue调用
|
|
|
+ // 因为使用了双向绑定,数据已经自动更新
|
|
|
+ if (key === 'r' || key === 'g' || key === 'b') {
|
|
|
+ // 确保RGB值在有效范围内
|
|
|
+ const range = this.getIndicatorRange(key);
|
|
|
+ if (range) {
|
|
|
+ const clampedValue = Math.max(range.min, Math.min(range.max, value));
|
|
|
+ this.colorIndicators.mainColor[key as keyof typeof this.colorIndicators.mainColor] = clampedValue;
|
|
|
+ console.log(`RGB值已更新: ${key} = ${clampedValue}`);
|
|
|
+ console.log('变化后的mainColor:', JSON.stringify(this.colorIndicators.mainColor));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 触发颜色指标更新
|
|
|
+ this.updateColorIndicator('mainColor', this.colorIndicators.mainColor);
|
|
|
+ } else {
|
|
|
+ // 处理其他颜色指标
|
|
|
+ this.updateIndicatorValue(key, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.triggerAutoSave();
|
|
|
+
|
|
|
+ // 发射实时数据更新事件
|
|
|
+ this.emitDataUpdate();
|
|
|
+
|
|
|
+ // 强制触发变更检测
|
|
|
+ this.cdr.detectChanges();
|
|
|
+ console.log('输入框变更检测已触发');
|
|
|
+ }
|
|
|
+
|
|
|
+ validateInput(key: string, value: string): void {
|
|
|
+ const numValue = parseFloat(value);
|
|
|
+ if (isNaN(numValue)) {
|
|
|
+ // 如果输入无效,恢复原值
|
|
|
+ this.restoreIndicatorValue(key);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const range = this.getIndicatorRange(key);
|
|
|
+ if (range) {
|
|
|
+ const clampedValue = Math.max(range.min, Math.min(range.max, numValue));
|
|
|
+ this.updateIndicatorValue(key, clampedValue);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private updateIndicatorValue(key: string, value: number): void {
|
|
|
+ console.log(`updateIndicatorValue调用: ${key} = ${value}`);
|
|
|
+
|
|
|
+ // 更新颜色指标
|
|
|
+ if (key in this.colorIndicators) {
|
|
|
+ if (key === 'r' || key === 'g' || key === 'b') {
|
|
|
+ // 创建新的mainColor对象以确保变更检测
|
|
|
+ const oldColor = { ...this.colorIndicators.mainColor };
|
|
|
+ this.colorIndicators.mainColor = {
|
|
|
+ ...this.colorIndicators.mainColor,
|
|
|
+ [key]: value
|
|
|
+ };
|
|
|
+ console.log(`RGB更新: ${key}从${oldColor[key as keyof typeof oldColor]}变为${value}`,
|
|
|
+ '新mainColor:', this.colorIndicators.mainColor);
|
|
|
+ this.updateColorIndicator('mainColor', this.colorIndicators.mainColor);
|
|
|
+ } else {
|
|
|
+ (this.colorIndicators as any)[key] = value;
|
|
|
+ this.updateColorIndicator(key, value);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新空间指标
|
|
|
+ if (key in this.spaceIndicators) {
|
|
|
+ (this.spaceIndicators as any)[key] = value;
|
|
|
+ this.updateSpaceIndicator(key, value);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新材质指标
|
|
|
+ if (key in this.materialIndicators) {
|
|
|
+ (this.materialIndicators as any)[key] = value;
|
|
|
+ this.updateMaterialIndicator(key, value);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private restoreIndicatorValue(key: string): void {
|
|
|
+ // 这里可以从历史状态或默认值恢复
|
|
|
+ console.log(`恢复指标值: ${key}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ private getIndicatorRange(key: string): { min: number; max: number } | null {
|
|
|
+ // 检查颜色范围
|
|
|
+ if (key === 'r' || key === 'g' || key === 'b') {
|
|
|
+ return this.indicatorRanges.color[key as keyof typeof this.indicatorRanges.color];
|
|
|
+ }
|
|
|
+ if (key in this.indicatorRanges.color) {
|
|
|
+ return (this.indicatorRanges.color as any)[key];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查空间范围
|
|
|
+ if (key in this.indicatorRanges.space) {
|
|
|
+ return (this.indicatorRanges.space as any)[key];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查材质范围
|
|
|
+ if (key in this.indicatorRanges.material) {
|
|
|
+ return (this.indicatorRanges.material as any)[key];
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 指标更新方法
|
|
|
+ updateColorIndicator(type: string, value?: any): void {
|
|
|
+ switch (type) {
|
|
|
+ case 'mainColor':
|
|
|
+ if (value && typeof value === 'object' && 'r' in value) {
|
|
|
+ this.colorIndicators.mainColor = { ...value }; // 创建新对象以触发变更检测
|
|
|
+ console.log('mainColor更新后:', this.colorIndicators.mainColor);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'colorTemperature':
|
|
|
+ // 色温更新时不需要改变颜色值,只是触发一致性检查
|
|
|
+ break;
|
|
|
+ case 'saturation':
|
|
|
+ // 饱和度更新时不需要改变颜色值,只是触发一致性检查
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`更新颜色指示器 ${type}:`, value, '当前RGB:', this.getRgbString());
|
|
|
+ this.checkConsistency();
|
|
|
+ this.updatePreview();
|
|
|
+ // 强制触发变更检测
|
|
|
+ this.cdr.detectChanges();
|
|
|
+ console.log('变更检测已触发');
|
|
|
+ }
|
|
|
+
|
|
|
+ updateSpaceIndicator(key: string, value: number) {
|
|
|
+ if (key in this.spaceIndicators) {
|
|
|
+ (this.spaceIndicators as any)[key] = value;
|
|
|
+ this.checkConsistency();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ updateMaterialIndicator(key: string, value: number) {
|
|
|
+ if (key in this.materialIndicators) {
|
|
|
+ (this.materialIndicators as any)[key] = value;
|
|
|
+ this.checkConsistency();
|
|
|
+ this.normalizeMaterialRatios();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 材质比例归一化
|
|
|
+ private normalizeMaterialRatios() {
|
|
|
+ const total = this.materialIndicators.fabricRatio +
|
|
|
+ this.materialIndicators.woodRatio +
|
|
|
+ this.materialIndicators.metalRatio;
|
|
|
+
|
|
|
+ if (total > 100) {
|
|
|
+ const scale = 100 / total;
|
|
|
+ this.materialIndicators.fabricRatio = Math.round(this.materialIndicators.fabricRatio * scale);
|
|
|
+ this.materialIndicators.woodRatio = Math.round(this.materialIndicators.woodRatio * scale);
|
|
|
+ this.materialIndicators.metalRatio = 100 - this.materialIndicators.fabricRatio - this.materialIndicators.woodRatio;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 预览更新
|
|
|
+ private updatePreview() {
|
|
|
+ // 移除直接DOM操作,依赖Angular的属性绑定
|
|
|
+ console.log('updatePreview调用,当前RGB:', this.getRgbString());
|
|
|
+ // 不再直接操作DOM,让Angular的变更检测处理
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取RGB字符串
|
|
|
+ getRgbString(): string {
|
|
|
+ const { r, g, b } = this.colorIndicators.mainColor;
|
|
|
+ const rgbString = `rgb(${r}, ${g}, ${b})`;
|
|
|
+ console.log('getRgbString调用:', rgbString, '完整mainColor对象:', this.colorIndicators.mainColor);
|
|
|
+ return rgbString;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取色温描述
|
|
|
+ getColorTemperatureDescription(): string {
|
|
|
+ const temp = this.colorIndicators.colorTemperature;
|
|
|
+ if (temp < 3000) return '暖色调';
|
|
|
+ if (temp < 4000) return '中性色调';
|
|
|
+ if (temp < 5000) return '自然色调';
|
|
|
+ return '冷色调';
|
|
|
+ }
|
|
|
+
|
|
|
+ applyPresetAtmosphere(preset: any) {
|
|
|
+ const rgbMatch = preset.rgb.match(/(\d+),(\d+),(\d+)/);
|
|
|
+ if (rgbMatch) {
|
|
|
+ this.colorIndicators.mainColor = {
|
|
|
+ r: parseInt(rgbMatch[1]),
|
|
|
+ g: parseInt(rgbMatch[2]),
|
|
|
+ b: parseInt(rgbMatch[3])
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ const tempMatch = preset.colorTemp.match(/(\d+)/);
|
|
|
+ if (tempMatch) {
|
|
|
+ this.colorIndicators.colorTemperature = parseInt(tempMatch[1]);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (preset.materials.includes('木质')) {
|
|
|
+ this.materialIndicators.woodRatio = 60;
|
|
|
+ this.materialIndicators.fabricRatio = 30;
|
|
|
+ this.materialIndicators.metalRatio = 10;
|
|
|
+ } else if (preset.materials.includes('金属')) {
|
|
|
+ this.materialIndicators.metalRatio = 50;
|
|
|
+ this.materialIndicators.woodRatio = 20;
|
|
|
+ this.materialIndicators.fabricRatio = 30;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.updatePreview();
|
|
|
+ this.checkConsistency();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 一致性检查
|
|
|
+ private checkConsistency() {
|
|
|
+ this.consistencyWarnings = [];
|
|
|
+
|
|
|
+ if (this.colorIndicators.colorTemperature < 3000 &&
|
|
|
+ this.colorIndicators.mainColor.r > 200 &&
|
|
|
+ this.colorIndicators.mainColor.g < 150) {
|
|
|
+ this.consistencyWarnings.push('暖调氛围与冷色调RGB值存在矛盾');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.materialIndicators.fabricRatio > 70 && this.colorIndicators.colorTemperature > 5000) {
|
|
|
+ this.consistencyWarnings.push('高布艺占比与冷色调可能不协调');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.spaceIndicators.lineRatio > 80 && this.materialIndicators.woodRatio > 60) {
|
|
|
+ this.consistencyWarnings.push('高直线条占比与高木质占比可能过于刚硬');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 协作批注功能
|
|
|
+ addCommentToRequirement(requirementId: string, content: string, author: string = '当前用户'): void {
|
|
|
+ const comment: CollaborationComment = {
|
|
|
+ id: this.generateId(),
|
|
|
+ author,
|
|
|
+ role: 'designer',
|
|
|
+ content,
|
|
|
+ timestamp: new Date(),
|
|
|
+ requirementId,
|
|
|
+ status: 'pending'
|
|
|
+ };
|
|
|
+
|
|
|
+ this.collaborationComments.push(comment);
|
|
|
+ this.updateProgress();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 优先级排序功能
|
|
|
+ updateRequirementPriority(requirementId: string, priority: 'high' | 'medium' | 'low'): void {
|
|
|
+ const requirement = this.requirementItems.find(r => r.id === requirementId);
|
|
|
+ if (requirement) {
|
|
|
+ requirement.priority = priority;
|
|
|
+ requirement.lastUpdated = new Date();
|
|
|
+ this.sortRequirementsByPriority();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ sortRequirementsByPriority(): void {
|
|
|
+ const priorityOrder = { 'high': 3, 'medium': 2, 'low': 1 };
|
|
|
+ this.requirementItems.sort((a, b) => {
|
|
|
+ return priorityOrder[b.priority] - priorityOrder[a.priority];
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 历史记录功能
|
|
|
+ saveCurrentState(): void {
|
|
|
+ const currentState = {
|
|
|
+ timestamp: new Date(),
|
|
|
+ colorIndicators: { ...this.colorIndicators },
|
|
|
+ spaceIndicators: { ...this.spaceIndicators },
|
|
|
+ materialIndicators: { ...this.materialIndicators },
|
|
|
+ requirementItems: this.requirementItems.map((r: RequirementItem) => ({ ...r })),
|
|
|
+ author: '当前用户'
|
|
|
+ };
|
|
|
+
|
|
|
+ this.historyStates.push(currentState);
|
|
|
+
|
|
|
+ if (this.historyStates.length > 10) {
|
|
|
+ this.historyStates.shift();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ restoreHistoryState(index: number): void {
|
|
|
+ if (index >= 0 && index < this.historyStates.length) {
|
|
|
+ const state = this.historyStates[index];
|
|
|
+ this.colorIndicators = { ...state.colorIndicators };
|
|
|
+ this.spaceIndicators = { ...state.spaceIndicators };
|
|
|
+ this.materialIndicators = { ...state.materialIndicators };
|
|
|
+ this.requirementItems = state.requirementItems.map((r: RequirementItem) => ({ ...r }));
|
|
|
+
|
|
|
+ this.updatePreview();
|
|
|
+ this.checkConsistency();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取未解决的评论数量
|
|
|
+ getUnresolvedCommentsCount(): number {
|
|
|
+ return this.collaborationComments.filter(c => c.status === 'pending').length;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取需求的评论
|
|
|
+ getCommentsForRequirement(requirementId: string): CollaborationComment[] {
|
|
|
+ return this.collaborationComments.filter(comment => comment.requirementId === requirementId) || [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取状态样式类
|
|
|
+ getStatusClass(status: string): string {
|
|
|
+ return status;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 工具方法
|
|
|
+ private generateId(): string {
|
|
|
+ return Math.random().toString(36).substr(2, 9);
|
|
|
+ }
|
|
|
+
|
|
|
+ private formatFileSize(bytes: number): string {
|
|
|
+ if (bytes === 0) return '0 Bytes';
|
|
|
+ const k = 1024;
|
|
|
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
|
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除素材
|
|
|
+ removeMaterial(materialId: string) {
|
|
|
+ const index = this.materialFiles.findIndex(m => m.id === materialId);
|
|
|
+ if (index > -1) {
|
|
|
+ const material = this.materialFiles[index];
|
|
|
+ if (material.url) {
|
|
|
+ URL.revokeObjectURL(material.url);
|
|
|
+ }
|
|
|
+ this.materialFiles.splice(index, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ const materialsIndex = this.materials.findIndex(m => m.id === materialId);
|
|
|
+ if (materialsIndex > -1) {
|
|
|
+ this.materials.splice(materialsIndex, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换标签页
|
|
|
+ switchTab(tab: 'materials' | 'mapping' | 'collaboration' | 'progress') {
|
|
|
+ this.activeTab = tab;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取需求状态文本
|
|
|
+ getRequirementStatusText(status: string): string {
|
|
|
+ const statusMap: { [key: string]: string } = {
|
|
|
+ 'pending': '待确认',
|
|
|
+ 'confirmed': '已确认',
|
|
|
+ 'rejected': '已拒绝'
|
|
|
+ };
|
|
|
+ return statusMap[status] || status;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取优先级文本
|
|
|
+ getPriorityText(priority: string): string {
|
|
|
+ const priorityMap: { [key: string]: string } = {
|
|
|
+ 'high': '高',
|
|
|
+ 'medium': '中',
|
|
|
+ 'low': '低'
|
|
|
+ };
|
|
|
+ return priorityMap[priority] || priority;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 刷新进度(保留用于手动刷新,但不再强制要求)
|
|
|
+ refreshProgress(): void {
|
|
|
+ // 重新计算进度
|
|
|
+ this.updateProgress();
|
|
|
+
|
|
|
+ // 更新进度条显示
|
|
|
+ const progress = this.getProgressPercentage();
|
|
|
+ this.updateProgressBar(progress);
|
|
|
+
|
|
|
+ // 检查阶段完成状态
|
|
|
+ this.checkStageCompletion();
|
|
|
+
|
|
|
+ // 触发进度更新事件
|
|
|
+ this.progressUpdated.emit(progress);
|
|
|
+
|
|
|
+ console.log('进度已手动刷新:', progress + '%');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 自动保存功能
|
|
|
+ private triggerAutoSave(): void {
|
|
|
+ if (!this.autoSaveEnabled) return;
|
|
|
+
|
|
|
+ this.hasUnsavedChanges = true;
|
|
|
+ this.saveStatus = 'unsaved';
|
|
|
+
|
|
|
+ // 清除之前的定时器
|
|
|
+ if (this.autoSaveTimer) {
|
|
|
+ clearTimeout(this.autoSaveTimer);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置新的自动保存定时器(延迟1秒)
|
|
|
+ this.autoSaveTimer = setTimeout(() => {
|
|
|
+ this.performAutoSave();
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ private async performAutoSave(): Promise<void> {
|
|
|
+ if (this.isSaving) return;
|
|
|
+
|
|
|
+ this.isSaving = true;
|
|
|
+ this.saveStatus = 'saving';
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 收集所有需要保存的数据
|
|
|
+ const saveData = this.collectSaveData();
|
|
|
+
|
|
|
+ // 模拟保存操作(实际项目中这里会调用API)
|
|
|
+ await this.saveToServer(saveData);
|
|
|
+
|
|
|
+ this.hasUnsavedChanges = false;
|
|
|
+ this.saveStatus = 'saved';
|
|
|
+ this.lastSaveTime = new Date();
|
|
|
+
|
|
|
+ console.log('自动保存成功:', new Date().toLocaleTimeString());
|
|
|
+ } catch (error) {
|
|
|
+ this.saveStatus = 'error';
|
|
|
+ console.error('自动保存失败:', error);
|
|
|
+ } finally {
|
|
|
+ this.isSaving = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 手动保存
|
|
|
+ async manualSave(): Promise<void> {
|
|
|
+ if (this.isSaving) return;
|
|
|
+
|
|
|
+ // 清除自动保存定时器
|
|
|
+ if (this.autoSaveTimer) {
|
|
|
+ clearTimeout(this.autoSaveTimer);
|
|
|
+ }
|
|
|
+
|
|
|
+ await this.performAutoSave();
|
|
|
+ }
|
|
|
+
|
|
|
+ private collectSaveData(): any {
|
|
|
+ return {
|
|
|
+ projectId: this.projectId,
|
|
|
+ colorIndicators: this.colorIndicators,
|
|
|
+ spaceIndicators: this.spaceIndicators,
|
|
|
+ materialIndicators: this.materialIndicators,
|
|
|
+ requirementItems: this.requirementItems,
|
|
|
+ requirementMetrics: this.requirementMetrics,
|
|
|
+ materialFiles: this.materialFiles,
|
|
|
+ collaborationComments: this.collaborationComments,
|
|
|
+ stageCompletionStatus: this.stageCompletionStatus,
|
|
|
+ lastUpdated: new Date()
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private async saveToServer(data: any): Promise<void> {
|
|
|
+ // 模拟API调用延迟
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ setTimeout(() => {
|
|
|
+ // 模拟90%成功率
|
|
|
+ if (Math.random() > 0.1) {
|
|
|
+ resolve();
|
|
|
+ } else {
|
|
|
+ reject(new Error('网络错误'));
|
|
|
+ }
|
|
|
+ }, 500);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查阶段完成状态
|
|
|
+ private checkStageCompletion(): void {
|
|
|
+ // 检查素材分析阶段 - 需要有素材文件且都已分析
|
|
|
+ this.stageCompletionStatus.materialAnalysis = this.materialFiles.length > 0 &&
|
|
|
+ this.materialFiles.every(m => m.analysis);
|
|
|
+
|
|
|
+ // 检查需求映射阶段 - 需要有确认的需求项,或者滑动条数据已调整且保存
|
|
|
+ const hasConfirmedRequirements = this.requirementItems.length > 0 &&
|
|
|
+ this.requirementItems.some(r => r.status === 'confirmed');
|
|
|
+ const hasAdjustedIndicators = this.hasIndicatorChanges();
|
|
|
+ this.stageCompletionStatus.requirementMapping = hasConfirmedRequirements ||
|
|
|
+ (hasAdjustedIndicators && this.saveStatus === 'saved');
|
|
|
+
|
|
|
+ // 检查协作验证阶段 - 需要有协作评论或需求评论
|
|
|
+ this.stageCompletionStatus.collaboration = this.collaborationComments.length > 0 ||
|
|
|
+ this.requirementItems.some(r => r.comments && r.comments.length > 0);
|
|
|
+
|
|
|
+ // 检查进度审查阶段 - 需要进度达到80%以上
|
|
|
+ this.stageCompletionStatus.progressReview = this.getProgressPercentage() >= 80;
|
|
|
+
|
|
|
+ console.log('阶段完成状态更新:', this.stageCompletionStatus);
|
|
|
+
|
|
|
+ // 检查是否所有阶段都已完成
|
|
|
+ const allStagesCompleted = Object.values(this.stageCompletionStatus).every(status => status);
|
|
|
+
|
|
|
+ // 发射阶段完成事件
|
|
|
+ if (allStagesCompleted) {
|
|
|
+ this.stageCompleted.emit({ stage: 'requirements-communication', allStagesCompleted: true });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查指示器是否有变化
|
|
|
+ private hasIndicatorChanges(): boolean {
|
|
|
+ // 检查颜色指示器是否有变化(与默认值比较)
|
|
|
+ const defaultColorIndicators = {
|
|
|
+ mainColor: { r: 255, g: 230, b: 180 },
|
|
|
+ colorTemperature: 2700,
|
|
|
+ colorRange: '暖色调',
|
|
|
+ saturation: 70,
|
|
|
+ brightness: 80,
|
|
|
+ contrast: 60
|
|
|
+ };
|
|
|
+
|
|
|
+ const defaultSpaceIndicators = {
|
|
|
+ lineRatio: 60,
|
|
|
+ blankRatio: 30,
|
|
|
+ flowWidth: 0.9,
|
|
|
+ aspectRatio: 1.6,
|
|
|
+ ceilingHeight: 2.8,
|
|
|
+ lightingLevel: 300
|
|
|
+ };
|
|
|
+
|
|
|
+ const defaultMaterialIndicators = {
|
|
|
+ fabricRatio: 50,
|
|
|
+ woodRatio: 30,
|
|
|
+ metalRatio: 20,
|
|
|
+ smoothness: 7,
|
|
|
+ glossiness: 4,
|
|
|
+ texture: 6
|
|
|
+ };
|
|
|
+
|
|
|
+ // 检查颜色指示器变化
|
|
|
+ const colorChanged = JSON.stringify(this.colorIndicators) !== JSON.stringify(defaultColorIndicators);
|
|
|
+
|
|
|
+ // 检查空间指示器变化
|
|
|
+ const spaceChanged = JSON.stringify(this.spaceIndicators) !== JSON.stringify(defaultSpaceIndicators);
|
|
|
+
|
|
|
+ // 检查材质指示器变化
|
|
|
+ const materialChanged = JSON.stringify(this.materialIndicators) !== JSON.stringify(defaultMaterialIndicators);
|
|
|
+
|
|
|
+ return colorChanged || spaceChanged || materialChanged;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取阶段状态文本
|
|
|
+ getStageStatusText(stage: keyof typeof this.stageCompletionStatus): string {
|
|
|
+ if (this.stageCompletionStatus[stage]) {
|
|
|
+ return '已完成';
|
|
|
+ } else {
|
|
|
+ // 检查是否为未开始状态
|
|
|
+ const stages = ['materialAnalysis', 'requirementMapping', 'collaboration', 'progressReview'];
|
|
|
+ const currentIndex = stages.indexOf(stage);
|
|
|
+
|
|
|
+ // 检查前面的阶段是否都已完成
|
|
|
+ let allPreviousCompleted = true;
|
|
|
+ for (let i = 0; i < currentIndex; i++) {
|
|
|
+ if (!this.stageCompletionStatus[stages[i] as keyof typeof this.stageCompletionStatus]) {
|
|
|
+ allPreviousCompleted = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return allPreviousCompleted ? '进行中' : '未进行';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取阶段状态类名
|
|
|
+ getStageStatusClass(stage: keyof typeof this.stageCompletionStatus): string {
|
|
|
+ if (this.stageCompletionStatus[stage]) {
|
|
|
+ return 'stage-completed';
|
|
|
+ } else {
|
|
|
+ // 检查是否为未开始状态
|
|
|
+ const stages = ['materialAnalysis', 'requirementMapping', 'collaboration', 'progressReview'];
|
|
|
+ const currentIndex = stages.indexOf(stage);
|
|
|
+
|
|
|
+ // 检查前面的阶段是否都已完成
|
|
|
+ let allPreviousCompleted = true;
|
|
|
+ for (let i = 0; i < currentIndex; i++) {
|
|
|
+ if (!this.stageCompletionStatus[stages[i] as keyof typeof this.stageCompletionStatus]) {
|
|
|
+ allPreviousCompleted = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return allPreviousCompleted ? 'stage-in-progress' : 'stage-pending';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否可以进入下一阶段
|
|
|
+ canProceedToNextStage(currentStage: keyof typeof this.stageCompletionStatus): boolean {
|
|
|
+ const stages = Object.keys(this.stageCompletionStatus) as (keyof typeof this.stageCompletionStatus)[];
|
|
|
+ const currentIndex = stages.indexOf(currentStage);
|
|
|
+
|
|
|
+ // 检查当前阶段之前的所有阶段是否都已完成
|
|
|
+ for (let i = 0; i <= currentIndex; i++) {
|
|
|
+ if (!this.stageCompletionStatus[stages[i]]) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取保存状态文本
|
|
|
+ getSaveStatusText(): string {
|
|
|
+ switch (this.saveStatus) {
|
|
|
+ case 'saved': return this.lastSaveTime ? `已保存 ${this.lastSaveTime.toLocaleTimeString()}` : '已保存';
|
|
|
+ case 'saving': return '保存中...';
|
|
|
+ case 'error': return '保存失败';
|
|
|
+ case 'unsaved': return '有未保存的更改';
|
|
|
+ default: return '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取保存状态图标
|
|
|
+ getSaveStatusIcon(): string {
|
|
|
+ switch (this.saveStatus) {
|
|
|
+ case 'saved': return '✓';
|
|
|
+ case 'saving': return '⏳';
|
|
|
+ case 'error': return '⚠';
|
|
|
+ case 'unsaved': return '●';
|
|
|
+ default: return '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 上传成功弹窗相关方法
|
|
|
+ onModalClose(): void {
|
|
|
+ this.showUploadSuccessModal = false;
|
|
|
+ this.uploadedFiles = [];
|
|
|
+ this.colorAnalysisResult = undefined;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 全局提示关闭
|
|
|
+ onPromptClose(): void {
|
|
|
+ this.showGlobalPrompt = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ onAnalyzeColors(): void {
|
|
|
+ if (this.uploadedFiles.length > 0 && this.uploadType === 'image') {
|
|
|
+ const imageFile = this.uploadedFiles[0];
|
|
|
+
|
|
|
+ // 设置分析状态为true
|
|
|
+ this.isAnalyzingColors = true;
|
|
|
+
|
|
|
+ // 从URL获取文件数据并创建File对象
|
|
|
+ fetch(imageFile.url)
|
|
|
+ .then(response => {
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(`HTTP error! status: ${response.status}`);
|
|
|
+ }
|
|
|
+ return response.blob();
|
|
|
+ })
|
|
|
+ .then(blob => {
|
|
|
+ // 创建包含实际文件数据的File对象
|
|
|
+ const file = new File([blob], imageFile.name, { type: imageFile.type || 'image/jpeg' });
|
|
|
+
|
|
|
+ // 使用模拟分析(在实际应用中应该调用真实的API)
|
|
|
+ this.colorAnalysisService.simulateAnalysis(file).subscribe({
|
|
|
+ next: (result) => {
|
|
|
+ this.colorAnalysisResult = result;
|
|
|
+ this.isAnalyzingColors = false; // 分析完成,设置状态为false
|
|
|
+
|
|
|
+ // 可以在这里更新颜色指标
|
|
|
+ if (result.colors.length > 0) {
|
|
|
+ const mainColor = result.colors[0];
|
|
|
+ this.updateColorIndicator('mainColor', mainColor.rgb);
|
|
|
+ }
|
|
|
+ // 新增:分析完成后向父级发射数据更新,包含色彩分析结果
|
|
|
+ this.emitDataUpdate();
|
|
|
+ },
|
|
|
+ error: (error) => {
|
|
|
+ console.error('颜色分析失败:', error);
|
|
|
+ this.isAnalyzingColors = false; // 分析失败,也要重置状态
|
|
|
+ }
|
|
|
+ });
|
|
|
+ })
|
|
|
+ .catch(error => {
|
|
|
+ console.error('获取文件数据失败:', error);
|
|
|
+ this.isAnalyzingColors = false; // 获取文件失败,重置状态
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ onViewReport(): void {
|
|
|
+ console.log('onViewReport被调用,当前colorAnalysisResult:', this.colorAnalysisResult);
|
|
|
+ // 打开全屏报告覆盖层
|
|
|
+ this.showFullReportOverlay = true;
|
|
|
+
|
|
|
+ // 确保色彩分析结果传递给父组件用于右侧展示
|
|
|
+ if (this.colorAnalysisResult) {
|
|
|
+ console.log('调用emitDataUpdate传递色彩分析结果');
|
|
|
+ this.emitDataUpdate();
|
|
|
+ } else {
|
|
|
+ console.log('没有色彩分析结果可传递');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ onCloseFullReport(): void {
|
|
|
+ this.showFullReportOverlay = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 新增:发射实时数据更新事件的方法
|
|
|
+ private emitDataUpdate(): void {
|
|
|
+ // 收集所有材料的详细分析数据
|
|
|
+ const materialAnalysisData = this.materials.map(material => ({
|
|
|
+ id: material.id,
|
|
|
+ name: material.name,
|
|
|
+ type: material.type,
|
|
|
+ analysis: material.analysis || {}
|
|
|
+ }));
|
|
|
+
|
|
|
+ const currentData = {
|
|
|
+ colorIndicators: this.colorIndicators || [],
|
|
|
+ spaceIndicators: this.spaceIndicators || [],
|
|
|
+ materialIndicators: this.materialIndicators || [],
|
|
|
+ requirementItems: this.requirementItems || [],
|
|
|
+ materials: this.materials || [],
|
|
|
+ collaborationComments: this.collaborationComments || '',
|
|
|
+ stageCompletionStatus: this.stageCompletionStatus || {},
|
|
|
+ // 传递色彩分析结果到父级用于右侧展示
|
|
|
+ colorAnalysisResult: this.colorAnalysisResult,
|
|
|
+ // 新增:传递完整的材料分析数据,包含色彩、形体、质感、纹理、灯光分析
|
|
|
+ materialAnalysisData: materialAnalysisData,
|
|
|
+ // 新增:传递详细的分析结果
|
|
|
+ detailedAnalysis: {
|
|
|
+ enhancedColorAnalysis: this.extractEnhancedColorAnalysis(),
|
|
|
+ formAnalysis: this.extractFormAnalysis(),
|
|
|
+ textureAnalysis: this.extractTextureAnalysis(),
|
|
|
+ patternAnalysis: this.extractPatternAnalysis(),
|
|
|
+ lightingAnalysis: this.extractLightingAnalysis()
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ console.log('emitDataUpdate被调用,准备发射数据:', {
|
|
|
+ colorAnalysisResult: this.colorAnalysisResult,
|
|
|
+ materials: this.materials,
|
|
|
+ requirementItems: this.requirementItems,
|
|
|
+ materialAnalysisData: materialAnalysisData,
|
|
|
+ detailedAnalysis: currentData.detailedAnalysis
|
|
|
+ });
|
|
|
+
|
|
|
+ this.dataUpdated.emit(currentData);
|
|
|
+ console.log('实时数据更新事件已发射:', currentData);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取增强色彩分析数据
|
|
|
+ private extractEnhancedColorAnalysis(): any {
|
|
|
+ const colorAnalyses = this.materials
|
|
|
+ .filter(m => m.analysis?.enhancedColorAnalysis)
|
|
|
+ .map(m => m.analysis!.enhancedColorAnalysis);
|
|
|
+
|
|
|
+ if (colorAnalyses.length === 0) return null;
|
|
|
+
|
|
|
+ // 合并所有色彩分析数据
|
|
|
+ return colorAnalyses.reduce((merged, current) => ({
|
|
|
+ ...merged,
|
|
|
+ ...current
|
|
|
+ }), {});
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取形体分析数据
|
|
|
+ private extractFormAnalysis(): any {
|
|
|
+ const formAnalyses = this.materials
|
|
|
+ .filter(m => m.analysis?.formAnalysis)
|
|
|
+ .map(m => m.analysis!.formAnalysis);
|
|
|
+
|
|
|
+ if (formAnalyses.length === 0) return null;
|
|
|
+
|
|
|
+ return formAnalyses.reduce((merged, current) => ({
|
|
|
+ ...merged,
|
|
|
+ ...current
|
|
|
+ }), {});
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取质感分析数据
|
|
|
+ private extractTextureAnalysis(): any {
|
|
|
+ const textureAnalyses = this.materials
|
|
|
+ .filter(m => m.analysis?.textureAnalysis)
|
|
|
+ .map(m => m.analysis!.textureAnalysis);
|
|
|
+
|
|
|
+ if (textureAnalyses.length === 0) return null;
|
|
|
+
|
|
|
+ return textureAnalyses.reduce((merged, current) => ({
|
|
|
+ ...merged,
|
|
|
+ ...current
|
|
|
+ }), {});
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取纹理分析数据
|
|
|
+ private extractPatternAnalysis(): any {
|
|
|
+ const patternAnalyses = this.materials
|
|
|
+ .filter(m => m.analysis?.patternAnalysis)
|
|
|
+ .map(m => m.analysis!.patternAnalysis);
|
|
|
+
|
|
|
+ if (patternAnalyses.length === 0) return null;
|
|
|
+
|
|
|
+ return patternAnalyses.reduce((merged, current) => ({
|
|
|
+ ...merged,
|
|
|
+ ...current
|
|
|
+ }), {});
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取灯光分析数据
|
|
|
+ private extractLightingAnalysis(): any {
|
|
|
+ const lightingAnalyses = this.materials
|
|
|
+ .filter(m => m.analysis?.lightingAnalysis)
|
|
|
+ .map(m => m.analysis!.lightingAnalysis);
|
|
|
+
|
|
|
+ if (lightingAnalyses.length === 0) return null;
|
|
|
+
|
|
|
+ return lightingAnalyses.reduce((merged, current) => ({
|
|
|
+ ...merged,
|
|
|
+ ...current
|
|
|
+ }), {});
|
|
|
+ }
|
|
|
+
|
|
|
+ // 新增:图片与CAD文件预览方法
|
|
|
+ previewImage(url?: string): void {
|
|
|
+ if (!url) { return; }
|
|
|
+ try {
|
|
|
+ window.open(url, '_blank');
|
|
|
+ } catch (e) {
|
|
|
+ console.error('预览图片失败:', e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ previewCad(url?: string): void {
|
|
|
+ if (!url) { return; }
|
|
|
+ try {
|
|
|
+ window.open(url, '_blank');
|
|
|
+ } catch (e) {
|
|
|
+ console.error('预览CAD失败:', e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 需求映射测试相关方法
|
|
|
+ onFileSelected(event: Event): void {
|
|
|
+ const input = event.target as HTMLInputElement;
|
|
|
+ if (!input.files || input.files.length === 0) return;
|
|
|
+
|
|
|
+ this.updateStepStatus('upload', 'in-progress');
|
|
|
+ this.isUploading = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const files = Array.from(input.files);
|
|
|
+ this.uploadedFiles = files.map(file => ({
|
|
|
+ id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
|
|
|
+ name: file.name,
|
|
|
+ url: URL.createObjectURL(file),
|
|
|
+ size: file.size,
|
|
|
+ type: 'image' as const,
|
|
|
+ preview: URL.createObjectURL(file)
|
|
|
+ }));
|
|
|
+
|
|
|
+ // 模拟上传延迟
|
|
|
+ setTimeout(() => {
|
|
|
+ this.isUploading = false;
|
|
|
+ this.updateStepStatus('upload', 'completed');
|
|
|
+ this.showUploadModal = true;
|
|
|
+
|
|
|
+ // 自动开始分析
|
|
|
+ this.startAnalysis();
|
|
|
+ }, 1000);
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('文件上传失败:', error);
|
|
|
+ this.isUploading = false;
|
|
|
+ this.updateStepStatus('upload', 'error');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 开始分析
|
|
|
+ startAnalysis(): void {
|
|
|
+ if (this.uploadedFiles.length === 0) return;
|
|
|
+
|
|
|
+ this.updateStepStatus('analysis', 'in-progress');
|
|
|
+ this.isAnalyzing = true;
|
|
|
+ this.analysisError = null;
|
|
|
+
|
|
|
+ // 使用第一个文件进行分析
|
|
|
+ const firstFile = this.uploadedFiles[0];
|
|
|
+
|
|
|
+ // 添加null检查和错误处理
|
|
|
+ if (!firstFile) {
|
|
|
+ this.analysisError = '未找到有效的文件';
|
|
|
+ this.isAnalyzing = false;
|
|
|
+ this.updateStepStatus('analysis', 'error');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const analysisSubscription = this.colorAnalysisService.analyzeImage(firstFile).subscribe({
|
|
|
+ next: (result: ColorAnalysisResult) => {
|
|
|
+ if (result) {
|
|
|
+ this.analysisResult = result;
|
|
|
+ this.isAnalyzing = false;
|
|
|
+ this.updateStepStatus('analysis', 'completed');
|
|
|
+
|
|
|
+ // 自动开始需求映射
|
|
|
+ this.startRequirementMapping();
|
|
|
+ } else {
|
|
|
+ this.analysisError = '分析结果为空';
|
|
|
+ this.isAnalyzing = false;
|
|
|
+ this.updateStepStatus('analysis', 'error');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ error: (error: any) => {
|
|
|
+ console.error('分析失败:', error);
|
|
|
+ this.analysisError = '图片分析失败,请重试';
|
|
|
+ this.isAnalyzing = false;
|
|
|
+ this.updateStepStatus('analysis', 'error');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ this.subscriptions.push(analysisSubscription);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('启动分析失败:', error);
|
|
|
+ this.analysisError = '启动分析失败,请重试';
|
|
|
+ this.isAnalyzing = false;
|
|
|
+ this.updateStepStatus('analysis', 'error');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 开始需求映射
|
|
|
+ startRequirementMapping(): void {
|
|
|
+ if (!this.analysisResult) {
|
|
|
+ console.warn('分析结果为空,无法开始需求映射');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.updateStepStatus('mapping', 'in-progress');
|
|
|
+ this.isGeneratingMapping = true;
|
|
|
+ this.mappingError = null;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const mappingSubscription = this.requirementMappingService.generateRequirementMapping(
|
|
|
+ this.analysisResult,
|
|
|
+ SceneTemplate.LIVING_ROOM_MODERN
|
|
|
+ ).subscribe({
|
|
|
+ next: (mapping) => {
|
|
|
+ if (mapping) {
|
|
|
+ this.requirementMapping = mapping;
|
|
|
+ this.isGeneratingMapping = false;
|
|
|
+ this.updateStepStatus('mapping', 'completed');
|
|
|
+ this.updateStepStatus('preview', 'completed');
|
|
|
+
|
|
|
+ console.log('=== 需求映射测试完成 ===');
|
|
|
+ console.log('映射结果:', this.requirementMapping);
|
|
|
+ } else {
|
|
|
+ this.mappingError = '需求映射结果为空';
|
|
|
+ this.isGeneratingMapping = false;
|
|
|
+ this.updateStepStatus('mapping', 'error');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ error: (error) => {
|
|
|
+ console.error('需求映射生成失败:', error);
|
|
|
+ this.mappingError = '需求映射生成失败,请重试';
|
|
|
+ this.isGeneratingMapping = false;
|
|
|
+ this.updateStepStatus('mapping', 'error');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ this.subscriptions.push(mappingSubscription);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('启动需求映射失败:', error);
|
|
|
+ this.mappingError = '启动需求映射失败,请重试';
|
|
|
+ this.isGeneratingMapping = false;
|
|
|
+ this.updateStepStatus('mapping', 'error');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新步骤状态
|
|
|
+ private updateStepStatus(stepId: string, status: 'pending' | 'in-progress' | 'completed' | 'error'): void {
|
|
|
+ const step = this.testSteps.find(s => s.id === stepId);
|
|
|
+ if (step) {
|
|
|
+ step.status = status;
|
|
|
+ } else {
|
|
|
+ console.warn(`未找到步骤: ${stepId}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 辅助方法:获取步骤图标
|
|
|
+ getStepIcon(status: string): string {
|
|
|
+ switch (status) {
|
|
|
+ case 'completed': return '✅';
|
|
|
+ case 'in-progress': return '⏳';
|
|
|
+ case 'error': return '❌';
|
|
|
+ default: return '⭕';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取步骤状态类名
|
|
|
+ getStepClass(status: string): string {
|
|
|
+ return `step-${status}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 手动重试分析
|
|
|
+ retryAnalysis(): void {
|
|
|
+ this.analysisError = null;
|
|
|
+ this.startAnalysis();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 手动重试映射
|
|
|
+ retryMapping(): void {
|
|
|
+ this.mappingError = null;
|
|
|
+ this.startRequirementMapping();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 下载测试结果
|
|
|
+ downloadTestResult(): void {
|
|
|
+ if (!this.requirementMapping) return;
|
|
|
+
|
|
|
+ const testResult = {
|
|
|
+ timestamp: new Date().toISOString(),
|
|
|
+ uploadedFiles: this.uploadedFiles.map(f => ({
|
|
|
+ name: f.name,
|
|
|
+ size: f.size,
|
|
|
+ type: f.type
|
|
|
+ })),
|
|
|
+ analysisResult: this.analysisResult,
|
|
|
+ requirementMapping: this.requirementMapping,
|
|
|
+ testSteps: this.testSteps
|
|
|
+ };
|
|
|
+
|
|
|
+ const blob = new Blob([JSON.stringify(testResult, null, 2)], { type: 'application/json' });
|
|
|
+ const url = URL.createObjectURL(blob);
|
|
|
+ const link = document.createElement('a');
|
|
|
+ link.href = url;
|
|
|
+ link.download = `requirement-mapping-test-${Date.now()}.json`;
|
|
|
+ link.click();
|
|
|
+ URL.revokeObjectURL(url);
|
|
|
+ }
|
|
|
+}
|