import { Component, OnInit, signal, computed } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup } from '@angular/forms'; import { RouterModule, ActivatedRoute } from '@angular/router'; import * as QRCode from 'qrcode'; // 定义案例接口 interface CaseItem { id: string; name: string; category: string; style: string[]; houseType: string; property: string; designer: string; area: number; createdAt: Date; coverImage: string; detailImages: string[]; isFavorite: boolean; tags: string[]; views: number; description: string; // 新增字段 projectType: '工装' | '家装'; subType: '平层' | '复式' | '别墅' | '自建房' | '其他'; renderingLevel: '高端' | '中端'; shareCount: number; favoriteCount: number; likeCount: number; conversionRate: number; // 0-100 } @Component({ selector: 'app-case-library', standalone: true, imports: [CommonModule, FormsModule, ReactiveFormsModule, RouterModule], templateUrl: './case-library.html', styleUrls: ['./case-library.scss', '../customer-service-styles.scss'] }) export class CaseLibrary implements OnInit { // 当前日期 currentDate = new Date(); // 搜索关键词 searchTerm = signal(''); // 分享弹窗 showShareModal = signal(false); shareLink = signal(''); qrDataUrl = signal(''); sharedCaseId = signal(null); // 筛选表单 filterForm: FormGroup; // 案例列表 cases = signal([]); // 筛选后的案例 filteredCases = computed(() => { let result = [...this.cases()]; // 应用搜索筛选 if (this.searchTerm()) { const searchLower = this.searchTerm().toLowerCase(); result = result.filter(caseItem => caseItem.name.toLowerCase().includes(searchLower) || caseItem.designer.toLowerCase().includes(searchLower) || caseItem.description.toLowerCase().includes(searchLower) || caseItem.tags.some(tag => tag.toLowerCase().includes(searchLower)) ); } // 应用表单筛选 const filters = this.filterForm.value as any; if (filters.style && filters.style.length > 0) { result = result.filter(caseItem => caseItem.style.some((s: string) => filters.style.includes(s)) ); } if (filters.houseType) { result = result.filter(caseItem => caseItem.houseType === filters.houseType); } if (filters.property) { result = result.filter(caseItem => caseItem.property === filters.property); } if (filters.projectType) { result = result.filter(caseItem => caseItem.projectType === filters.projectType); } if (filters.subType) { result = result.filter(caseItem => caseItem.subType === filters.subType); } if (filters.renderingLevel) { result = result.filter(caseItem => caseItem.renderingLevel === filters.renderingLevel); } if (filters.minArea) { result = result.filter(caseItem => caseItem.area >= Number(filters.minArea)); } if (filters.maxArea) { result = result.filter(caseItem => caseItem.area <= Number(filters.maxArea)); } if (filters.favorite) { result = result.filter(caseItem => caseItem.isFavorite); } // 排序 if (filters.sortBy) { switch (filters.sortBy) { case 'views': result.sort((a, b) => b.views - a.views); break; case 'shares': result.sort((a, b) => b.shareCount - a.shareCount); break; case 'conversion': result.sort((a, b) => b.conversionRate - a.conversionRate); break; case 'createdAt': result.sort((a, b) => +b.createdAt - +a.createdAt); break; } } else { // 默认按创建时间倒序 result.sort((a, b) => +b.createdAt - +a.createdAt); } return result; }); // 显示筛选面板 showFilterPanel = signal(false); // 当前查看的案例详情 selectedCase = signal(null); // 分页信息 currentPage = signal(1); itemsPerPage = signal(12); // 分页后的案例 paginatedCases = computed(() => { const startIndex = (this.currentPage() - 1) * this.itemsPerPage(); return this.filteredCases().slice(startIndex, startIndex + this.itemsPerPage()); }); // 总页数 totalPages = computed(() => { return Math.ceil(this.filteredCases().length / this.itemsPerPage()); }); // 筛选选项 styleOptions = ['现代简约', '北欧风', '工业风', '新中式', '法式轻奢', '日式', '美式', '混搭']; houseTypeOptions = ['一室一厅', '两室一厅', '两室两厅', '三室一厅', '三室两厅', '四室两厅', '复式', '别墅', '其他']; propertyOptions = ['万科', '绿城', '保利', '龙湖', '融创', '中海', '碧桂园', '其他']; projectTypeOptions: Array = ['工装', '家装']; subTypeOptions: Array = ['平层', '复式', '别墅', '自建房', '其他']; renderingLevelOptions: Array = ['高端', '中端']; sortOptions = [ { label: '最新上传', value: 'createdAt' }, { label: '浏览最多', value: 'views' }, { label: '分享最多', value: 'shares' }, { label: '转化率最高', value: 'conversion' } ]; constructor(private fb: FormBuilder, private route: ActivatedRoute) { // 初始化筛选表单 this.filterForm = this.fb.group({ style: [[]], houseType: [''], property: [''], projectType: [''], subType: [''], renderingLevel: [''], minArea: [''], maxArea: [''], favorite: [false], sortBy: ['createdAt'] }); } ngOnInit(): void { // 加载模拟案例数据 this.loadCases(); // 读取分享链接参数并打开对应案例详情 this.route.queryParamMap.subscribe(params => { const caseId = params.get('case'); if (caseId) { const item = this.cases().find(c => c.id === caseId); if (item) { this.viewCaseDetails(item); } } }); } // 加载案例数据 loadCases(): void { // 本地占位图集合 const LOCAL_IMAGES = [ '/assets/images/portfolio-1.svg', '/assets/images/portfolio-2.svg', '/assets/images/portfolio-3.svg', '/assets/images/portfolio-4.svg' ]; const rand = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1)) + min; const pick = (arr: T[]): T => arr[Math.floor(Math.random() * arr.length)]; // 模拟API请求获取案例数据 const mockCases: CaseItem[] = Array.from({ length: 24 }, (_, i) => { const cover = LOCAL_IMAGES[i % LOCAL_IMAGES.length]; const details = Array.from({ length: 4 }, (_, j) => LOCAL_IMAGES[(i + j) % LOCAL_IMAGES.length]); const projectType = pick(this.projectTypeOptions); const subType = pick(this.subTypeOptions); const renderingLevel = pick(this.renderingLevelOptions); const createdAt = new Date(Date.now() - rand(0, 365) * 24 * 60 * 60 * 1000); const views = rand(100, 3000); const shareCount = rand(10, 500); const favoriteCount = rand(5, 400); const likeCount = rand(10, 800); const conversionRate = Number((Math.random() * 30 + 5).toFixed(1)); // 5% - 35% return { id: `case-${i + 1}`, name: `${pick(this.styleOptions)}风格 ${pick(this.houseTypeOptions)}设计`, category: pick(['客厅', '卧室', '厨房', '浴室', '书房', '餐厅']), style: [pick(this.styleOptions)], houseType: pick(this.houseTypeOptions), property: pick(this.propertyOptions), designer: pick(['张设计', '李设计', '王设计', '赵设计', '陈设计']), area: rand(50, 150), createdAt, coverImage: cover, detailImages: details, isFavorite: Math.random() > 0.7, tags: ['热门', '精选', '新上传', '高性价比', '业主好评'].filter(() => Math.random() > 0.5), views, description: '这是一个精美的' + pick(['现代简约', '北欧风', '新中式']) + '风格设计案例,融合了功能性与美学,为客户打造了舒适宜人的居住环境。', projectType, subType, renderingLevel, shareCount, favoriteCount, likeCount, conversionRate }; }); this.cases.set(mockCases); } // 切换收藏状态(同时更新收藏计数) toggleFavorite(caseId: string): void { this.cases.set( this.cases().map(caseItem => { if (caseItem.id === caseId) { const isFav = !caseItem.isFavorite; const favoriteCount = Math.max(0, caseItem.favoriteCount + (isFav ? 1 : -1)); return { ...caseItem, isFavorite: isFav, favoriteCount }; } return caseItem; }) ); } // 查看案例详情(增加浏览量) viewCaseDetails(caseItem: CaseItem): void { this.selectedCase.set(caseItem); // 增加浏览量 this.cases.set( this.cases().map(item => item.id === caseItem.id ? { ...item, views: item.views + 1 } : item ) ); } // 关闭案例详情 closeCaseDetails(): void { this.selectedCase.set(null); } // 分享案例:生成链接、复制并展示弹窗,同时更新分享计数 async shareCase(caseId: string): Promise { const link = this.getShareLink(caseId); this.shareLink.set(link); this.showShareModal.set(true); this.sharedCaseId.set(caseId); // 生成二维码 await this.generateQrCode(link); // 分享计数 +1 this.cases.set( this.cases().map(item => item.id === caseId ? { ...item, shareCount: item.shareCount + 1 } : item) ); // 尝试自动复制 try { await navigator.clipboard.writeText(link); } catch { // 忽略复制失败(例如非安全上下文),用户可手动复制 } } getShareLink(caseId: string): string { const base = window.location.origin; return `${base}/customer-service/case-library?case=${encodeURIComponent(caseId)}`; } async copyShareLink(): Promise { const link = this.shareLink(); try { await navigator.clipboard.writeText(link); alert('链接已复制到剪贴板'); } catch { alert('复制失败,请手动选择链接复制'); } } // 生成二维码 private async generateQrCode(text: string): Promise { try { const url = await QRCode.toDataURL(text, { width: 160, margin: 1 }); this.qrDataUrl.set(url); } catch (e) { console.error('生成二维码失败', e); this.qrDataUrl.set(''); } } downloadQrCode(): void { const dataUrl = this.qrDataUrl(); if (!dataUrl) { return; } const a = document.createElement('a'); const name = this.sharedCaseId() ? `${this.sharedCaseId()}-qr.png` : 'case-qr.png'; a.href = dataUrl; a.download = name; document.body.appendChild(a); a.click(); document.body.removeChild(a); } openShareLink(): void { const link = this.shareLink(); if (link) { window.open(link, '_blank', 'noopener'); } } closeShareModal(): void { this.showShareModal.set(false); this.qrDataUrl.set(''); this.sharedCaseId.set(null); } // 重置筛选条件 resetFilters(): void { this.filterForm.reset({ style: [], houseType: '', property: '', projectType: '', subType: '', renderingLevel: '', minArea: '', maxArea: '', favorite: false, sortBy: 'createdAt' }); this.searchTerm.set(''); this.currentPage.set(1); } // 切换筛选面板 toggleFilterPanel(): void { this.showFilterPanel.set(!this.showFilterPanel()); } // 分页导航 goToPage(page: number): void { if (page >= 1 && page <= this.totalPages()) { this.currentPage.set(page); } } // 上一页 prevPage(): void { this.goToPage(this.currentPage() - 1); } // 下一页 nextPage(): void { this.goToPage(this.currentPage() + 1); } // 格式化日期 formatDate(date: Date): string { return new Date(date).toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit', year: 'numeric' }); } // 智能页码生成 pageNumbers = computed(() => { const pages = [] as number[]; const total = this.totalPages(); const current = this.currentPage(); // 显示当前页及前后2页,加上第一页和最后一页 const start = Math.max(1, current - 2); const end = Math.min(total, current + 2); if (start > 1) { pages.push(1); if (start > 2) { pages.push(-1); // 用-1表示省略号 } } for (let i = start; i <= end; i++) { pages.push(i); } if (end < total) { if (end < total - 1) { pages.push(-1); // 用-1表示省略号 } pages.push(total); } return pages; }); // 格式化样式显示的辅助方法 getStyleDisplay(caseItem: CaseItem | null | undefined): string { if (!caseItem || !caseItem.style) { return ''; } return Array.isArray(caseItem.style) ? caseItem.style.join('、') : String(caseItem.style); } // 获取当前选中案例的样式显示 getSelectedCaseStyle(): string { return this.getStyleDisplay(this.selectedCase()); } // 修复 onStyleChange 方法中的类型安全问题 onStyleChange(style: string, isChecked: boolean): void { const currentStyles = (this.filterForm.get('style')?.value || []) as string[]; let updatedStyles: string[]; if (isChecked) { // 如果勾选,则添加风格(避免重复) updatedStyles = [...new Set([...currentStyles, style])]; } else { // 如果取消勾选,则移除风格 updatedStyles = currentStyles.filter(s => s !== style); } this.filterForm.patchValue({ style: updatedStyles }); } }