upload-success-modal.component.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ChangeDetectionStrategy, HostListener } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { ColorAnalysisService, AnalysisProgress } from '../../services/color-analysis.service';
  4. import { Subscription } from 'rxjs';
  5. import { modalAnimations } from './upload-success-modal.animations';
  6. export interface UploadedFile {
  7. id: string;
  8. name: string;
  9. url: string;
  10. size?: number;
  11. type?: 'image' | 'cad' | 'text';
  12. preview?: string;
  13. }
  14. export interface ColorAnalysisResult {
  15. colors: Array<{ hex: string; rgb: { r: number; g: number; b: number }; percentage: number }>;
  16. reportUrl?: string;
  17. mosaicUrl?: string;
  18. }
  19. @Component({
  20. selector: 'app-upload-success-modal',
  21. standalone: true,
  22. imports: [CommonModule],
  23. templateUrl: './upload-success-modal.component.html',
  24. styleUrls: ['./upload-success-modal.component.scss'],
  25. changeDetection: ChangeDetectionStrategy.OnPush,
  26. animations: modalAnimations
  27. })
  28. export class UploadSuccessModalComponent implements OnInit, OnDestroy {
  29. @Input() isVisible: boolean = false;
  30. @Input() uploadedFiles: UploadedFile[] = [];
  31. @Input() uploadType: 'image' | 'document' | 'mixed' = 'image';
  32. @Input() analysisResult?: ColorAnalysisResult;
  33. @Output() closeModal = new EventEmitter<void>();
  34. @Output() analyzeColors = new EventEmitter<UploadedFile[]>();
  35. @Output() viewReport = new EventEmitter<ColorAnalysisResult>();
  36. // 颜色分析状态
  37. isAnalyzing = false;
  38. analysisProgress: AnalysisProgress | null = null;
  39. analysisError: string | null = null;
  40. // 响应式状态
  41. isMobile = false;
  42. isTablet = false;
  43. // 动画状态
  44. animationState = 'idle';
  45. buttonHoverState = 'normal';
  46. private progressSubscription?: Subscription;
  47. private resizeSubscription?: Subscription;
  48. constructor(private colorAnalysisService: ColorAnalysisService) {}
  49. ngOnInit() {
  50. this.checkScreenSize();
  51. this.setupResizeListener();
  52. }
  53. ngOnDestroy() {
  54. this.progressSubscription?.unsubscribe();
  55. this.resizeSubscription?.unsubscribe();
  56. }
  57. // 响应式布局检测
  58. @HostListener('window:resize', ['$event'])
  59. onResize(event: any) {
  60. this.checkScreenSize();
  61. }
  62. @HostListener('document:keydown', ['$event'])
  63. onKeyDown(event: KeyboardEvent) {
  64. if (event.key === 'Escape' && this.isVisible) {
  65. this.onClose();
  66. }
  67. }
  68. // 开始颜色分析
  69. async startColorAnalysis() {
  70. if (this.uploadedFiles.length === 0 || this.uploadType !== 'image') {
  71. return;
  72. }
  73. this.isAnalyzing = true;
  74. this.analysisError = null;
  75. try {
  76. // 发射分析事件给父组件处理
  77. this.analyzeColors.emit(this.uploadedFiles);
  78. // 模拟分析过程(实际应该由父组件处理并返回结果)
  79. await this.simulateAnalysis();
  80. } catch (error) {
  81. this.analysisError = '颜色分析失败,请重试';
  82. console.error('Color analysis error:', error);
  83. } finally {
  84. this.isAnalyzing = false;
  85. }
  86. }
  87. // 模拟分析过程
  88. private async simulateAnalysis(): Promise<void> {
  89. return new Promise((resolve) => {
  90. setTimeout(() => {
  91. // 模拟分析结果
  92. this.analysisResult = {
  93. colors: [
  94. { hex: '#8B4513', rgb: { r: 139, g: 69, b: 19 }, percentage: 35.2 },
  95. { hex: '#A0522D', rgb: { r: 160, g: 82, b: 45 }, percentage: 27.1 },
  96. { hex: '#D2B48C', rgb: { r: 210, g: 180, b: 140 }, percentage: 22.4 },
  97. { hex: '#DEB887', rgb: { r: 222, g: 184, b: 135 }, percentage: 12.5 },
  98. { hex: '#F5F5DC', rgb: { r: 245, g: 245, b: 220 }, percentage: 2.8 }
  99. ],
  100. reportUrl: '/assets/reports/color-analysis-report.html',
  101. mosaicUrl: '/assets/reports/mosaic-image.png'
  102. };
  103. resolve();
  104. }, 2000);
  105. });
  106. }
  107. // 事件处理方法
  108. onClose() {
  109. this.closeModal.emit();
  110. }
  111. onBackdropClick(event: Event) {
  112. // 点击背景遮罩关闭弹窗
  113. this.onClose();
  114. }
  115. onAnalyzeColorsClick() {
  116. if (this.isAnalyzing || this.uploadedFiles.length === 0) {
  117. return;
  118. }
  119. this.animationState = 'loading';
  120. this.analyzeColors.emit(this.uploadedFiles);
  121. }
  122. onViewReportClick() {
  123. if (this.analysisResult) {
  124. this.viewReport.emit(this.analysisResult);
  125. }
  126. }
  127. // 工具方法
  128. shouldShowColorAnalysis(): boolean {
  129. return this.uploadType === 'image' || this.hasImageFiles();
  130. }
  131. formatFileSize(bytes: number): string {
  132. if (bytes === 0) return '0 Bytes';
  133. const k = 1024;
  134. const sizes = ['Bytes', 'KB', 'MB', 'GB'];
  135. const i = Math.floor(Math.log(bytes) / Math.log(k));
  136. return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  137. }
  138. getFileTypeIcon(file: UploadedFile): string {
  139. if (file.type?.startsWith('image/')) {
  140. return 'image';
  141. } else if (file.type?.includes('pdf')) {
  142. return 'pdf';
  143. } else if (file.type?.includes('word') || file.type?.includes('doc')) {
  144. return 'document';
  145. } else {
  146. return 'file';
  147. }
  148. }
  149. hasImageFiles(): boolean {
  150. return this.uploadedFiles.some(file => file.type?.startsWith('image/'));
  151. }
  152. private checkScreenSize() {
  153. const width = window.innerWidth;
  154. this.isMobile = width < 768;
  155. this.isTablet = width >= 768 && width < 1024;
  156. }
  157. private setupResizeListener() {
  158. // 可以添加更复杂的响应式逻辑
  159. }
  160. }