123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- 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<string | null>(null);
- // 筛选表单
- filterForm: FormGroup;
-
- // 案例列表
- cases = signal<CaseItem[]>([]);
-
- // 筛选后的案例
- 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<CaseItem | null>(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<CaseItem['projectType']> = ['工装', '家装'];
- subTypeOptions: Array<CaseItem['subType']> = ['平层', '复式', '别墅', '自建房', '其他'];
- renderingLevelOptions: Array<CaseItem['renderingLevel']> = ['高端', '中端'];
- 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 = <T,>(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<void> {
- 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<void> {
- const link = this.shareLink();
- try {
- await navigator.clipboard.writeText(link);
- alert('链接已复制到剪贴板');
- } catch {
- alert('复制失败,请手动选择链接复制');
- }
- }
- // 生成二维码
- private async generateQrCode(text: string): Promise<void> {
- 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 });
- }
- }
|