case-detail-panel.component.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import { Component, Input, Output, EventEmitter } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. export interface Case {
  4. id: string;
  5. name: string;
  6. coverImage: string;
  7. images?: string[];
  8. projectType: '工装' | '家装' | string;
  9. spaceType: '平层' | '复式' | '别墅' | '自建房' | string;
  10. roomType?: string; // 户型
  11. renderingLevel: '高端' | '中端' | '低端' | string;
  12. designer: string;
  13. designerId?: string;
  14. team: string;
  15. teamId?: string;
  16. area: number;
  17. totalPrice?: number; // 项目总额
  18. completionDate?: Date | string; // 完成时间
  19. tag?: string[]; // 标签数组(替代styleTags)
  20. styleTags?: string[]; // 兼容旧字段
  21. customerReview?: string;
  22. viewCount?: number;
  23. shareCount?: number;
  24. favoriteCount?: number;
  25. isFavorite?: boolean;
  26. isExcellent?: boolean;
  27. isPublished?: boolean;
  28. createdAt: Date | string;
  29. updatedAt?: Date | string;
  30. projectId?: string;
  31. data?: any;
  32. info?: {
  33. area?: number;
  34. projectType?: '工装' | '家装';
  35. roomType?: string;
  36. spaceType?: string;
  37. renderingLevel?: string;
  38. };
  39. }
  40. @Component({
  41. selector: 'app-case-detail-panel',
  42. standalone: true,
  43. imports: [CommonModule],
  44. templateUrl: './case-detail-panel.component.html',
  45. styleUrls: ['./case-detail-panel.component.scss']
  46. })
  47. export class CaseDetailPanelComponent {
  48. @Input() case!: Case;
  49. @Input() isInternalUser: boolean = false;
  50. @Output() close = new EventEmitter<void>();
  51. @Output() toggleFavorite = new EventEmitter<Case>();
  52. @Output() share = new EventEmitter<Case>();
  53. /**
  54. * 统一计算封面图:
  55. * 1) 优先交付执行阶段上传的真实图片(来自 images 数组)
  56. * 2) 再看 coverImage 是否为真实上传
  57. * 3) 否则回退到本地默认图
  58. * 同时过滤需求阶段(stage/requirements)和占位/默认图。
  59. */
  60. coverImageOf(c: Case | null | undefined): string {
  61. if (!c) return '/assets/presets/家装图片.jpg';
  62. const images = (c as any)?.images || [];
  63. const uploadedImage = images.find((img: string) =>
  64. img &&
  65. img.startsWith('http') &&
  66. !img.includes('placeholder') &&
  67. !img.includes('unsplash.com') &&
  68. !img.endsWith('.svg') &&
  69. !img.includes('/stage/requirements/') &&
  70. (img.includes('file-cloud.fmode.cn') || img.includes('storage'))
  71. );
  72. if (uploadedImage) return uploadedImage;
  73. const coverUrl: string = c.coverImage || '';
  74. const isRealUpload = !!(
  75. coverUrl &&
  76. coverUrl.startsWith('http') &&
  77. !coverUrl.includes('placeholder') &&
  78. !coverUrl.includes('unsplash.com') &&
  79. !coverUrl.endsWith('.svg') &&
  80. !coverUrl.includes('/stage/requirements/') &&
  81. (coverUrl.includes('file-cloud.fmode.cn') || coverUrl.includes('storage'))
  82. );
  83. if (isRealUpload) return coverUrl;
  84. return '/assets/presets/家装图片.jpg';
  85. }
  86. closePanel() {
  87. this.close.emit();
  88. }
  89. onToggleFavorite() {
  90. this.toggleFavorite.emit(this.case);
  91. }
  92. onShare() {
  93. this.share.emit(this.case);
  94. }
  95. getFormattedDate(date: Date | string): string {
  96. const dateObj = typeof date === 'string' ? new Date(date) : date;
  97. return new Intl.DateTimeFormat('zh-CN', {
  98. year: 'numeric',
  99. month: 'long',
  100. day: 'numeric'
  101. }).format(dateObj);
  102. }
  103. }