123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526 |
- import { Component, OnInit } from '@angular/core';
- import { CommonModule } from '@angular/common';
- import { FormsModule, ReactiveFormsModule } from '@angular/forms';
- import { FormControl } from '@angular/forms';
- import { Router } from '@angular/router';
- import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
- import { CaseDetailPanelComponent } from './case-detail-panel.component';
- interface Case {
- id: string;
- name: string;
- coverImage: string;
- projectType: '工装' | '家装';
- spaceType: '平层' | '复式' | '别墅' | '自建房';
- renderingLevel: '高端' | '中端' | '低端';
- designer: string;
- team: string;
- area: number;
- styleTags: string[];
- customerReview?: string;
- viewCount: number;
- shareCount: number;
- favoriteCount: number;
- isFavorite: boolean;
- isExcellent: boolean;
- createdAt: Date;
- }
- interface StatItem {
- id: string;
- name: string;
- shareCount: number;
- }
- interface StyleStat {
- style: string;
- count: number;
- }
- interface DesignerStat {
- designer: string;
- rate: number;
- }
- @Component({
- selector: 'app-case-library',
- standalone: true,
- imports: [CommonModule, FormsModule, ReactiveFormsModule, CaseDetailPanelComponent],
- templateUrl: './case-library.html',
- styleUrls: ['./case-library.scss']
- })
- export class CaseLibrary implements OnInit {
- // 表单控件
- searchControl = new FormControl('');
- projectTypeControl = new FormControl('');
- spaceTypeControl = new FormControl('');
- renderingLevelControl = new FormControl('');
- styleControl = new FormControl('');
- areaRangeControl = new FormControl('');
- // 数据
- cases: Case[] = [];
- filteredCases: Case[] = [];
-
- // 统计数据
- topSharedCases: StatItem[] = [];
- favoriteStyles: StyleStat[] = [];
- designerRecommendations: DesignerStat[] = [];
-
- // 状态
- showStatsPanel = false;
- selectedCase: Case | null = null; // 用于详情面板
- selectedCaseForShare: Case | null = null; // 用于分享模态框
- currentPage = 1;
- itemsPerPage = 10; // 每页显示10个案例
- totalPages = 1;
-
- // 用户类型(模拟)
- isInternalUser = true; // 可根据实际用户权限设置
-
- // 行为追踪
- private pageStartTime = Date.now();
- private caseViewStartTimes = new Map<string, number>();
- constructor(private router: Router) {}
- ngOnInit() {
- this.initializeData();
- this.setupFilterListeners();
- this.setupBehaviorTracking();
- }
-
- private setupBehaviorTracking() {
- // 记录页面访问
- this.recordBehavior('page_view', 'case-library', {
- timestamp: new Date().toISOString()
- });
-
- // 页面卸载时记录停留时长
- window.addEventListener('beforeunload', () => {
- const stayDuration = Date.now() - this.pageStartTime;
- this.recordBehavior('page_stay', 'case-library', {
- duration: stayDuration,
- durationMinutes: Math.round(stayDuration / 60000 * 100) / 100
- });
- });
- }
- private initializeData() {
- // 模拟案例数据
- this.cases = this.generateMockCases();
- this.filteredCases = [...this.cases];
- this.updatePagination();
-
- // 初始化统计数据
- this.initializeStats();
- }
- private setupFilterListeners() {
- // 搜索框防抖
- this.searchControl.valueChanges.pipe(
- debounceTime(300),
- distinctUntilChanged()
- ).subscribe(() => this.applyFilters());
- // 其他筛选条件变化
- [
- this.projectTypeControl,
- this.spaceTypeControl,
- this.renderingLevelControl,
- this.styleControl,
- this.areaRangeControl
- ].forEach(control => {
- control.valueChanges.subscribe(() => this.applyFilters());
- });
- }
- private generateMockCases(): Case[] {
- const mockCases: Case[] = [];
- const projectTypes: ('工装' | '家装')[] = ['工装', '家装'];
- const spaceTypes: ('平层' | '复式' | '别墅' | '自建房')[] = ['平层', '复式', '别墅', '自建房'];
- const renderingLevels: ('高端' | '中端' | '低端')[] = ['高端', '中端', '低端'];
- const styles = ['现代', '中式', '欧式', '美式', '日式', '工业风', '极简风', '轻奢风'];
- const designers = ['张三', '李四', '王五', '赵六', '钱七'];
- const teams = ['设计一组', '设计二组', '设计三组', '设计四组'];
- for (let i = 1; i <= 50; i++) {
- const projectType = projectTypes[Math.floor(Math.random() * projectTypes.length)];
- const spaceType = spaceTypes[Math.floor(Math.random() * spaceTypes.length)];
- const renderingLevel = renderingLevels[Math.floor(Math.random() * renderingLevels.length)];
- const styleCount = Math.floor(Math.random() * 3) + 1;
- const styleTags = Array.from({ length: styleCount }, () =>
- styles[Math.floor(Math.random() * styles.length)]
- );
- mockCases.push({
- id: `case-${i}`,
- name: `${projectType}${spaceType}设计案例 ${i}`,
- coverImage: this.generatePlaceholderImage(400, 300, i),
- projectType,
- spaceType,
- renderingLevel,
- designer: designers[Math.floor(Math.random() * designers.length)],
- team: teams[Math.floor(Math.random() * teams.length)],
- area: Math.floor(Math.random() * 200) + 50,
- styleTags: [...new Set(styleTags)], // 去重
- customerReview: Math.random() > 0.3 ? `客户非常满意这次${projectType}设计,${spaceType}空间利用得很合理` : undefined,
- viewCount: Math.floor(Math.random() * 1000),
- shareCount: Math.floor(Math.random() * 100),
- favoriteCount: Math.floor(Math.random() * 50),
- isFavorite: Math.random() > 0.7,
- isExcellent: Math.random() > 0.5,
- createdAt: new Date(Date.now() - Math.floor(Math.random() * 365 * 24 * 60 * 60 * 1000))
- });
- }
- return mockCases;
- }
- private generatePlaceholderImage(width: number, height: number, seed: number): string {
- return `https://picsum.photos/seed/${seed}/${width}/${height}`;
- }
- private initializeStats() {
- // Top5 分享案例
- this.topSharedCases = this.cases
- .sort((a, b) => b.shareCount - a.shareCount)
- .slice(0, 5)
- .map(caseItem => ({
- id: caseItem.id,
- name: caseItem.name,
- shareCount: caseItem.shareCount
- }));
- // 客户最喜欢案例风格
- const styleCounts = new Map<string, number>();
- this.cases.forEach(caseItem => {
- caseItem.styleTags.forEach(style => {
- styleCounts.set(style, (styleCounts.get(style) || 0) + caseItem.favoriteCount);
- });
- });
-
- this.favoriteStyles = Array.from(styleCounts.entries())
- .sort(([, a], [, b]) => b - a)
- .slice(0, 5)
- .map(([style, count]) => ({ style, count }));
- // 设计师作品推荐率
- const designerStats = new Map<string, { total: number; excellent: number }>();
- this.cases.forEach(caseItem => {
- const stats = designerStats.get(caseItem.designer) || { total: 0, excellent: 0 };
- stats.total++;
- if (caseItem.isExcellent) stats.excellent++;
- designerStats.set(caseItem.designer, stats);
- });
- this.designerRecommendations = Array.from(designerStats.entries())
- .map(([designer, stats]) => ({
- designer,
- rate: Math.round((stats.excellent / stats.total) * 100)
- }))
- .sort((a, b) => b.rate - a.rate)
- .slice(0, 5);
- }
- applyFilters() {
- const searchTerm = this.searchControl.value?.toLowerCase() || '';
- const projectType = this.projectTypeControl.value;
- const spaceType = this.spaceTypeControl.value;
- const renderingLevel = this.renderingLevelControl.value;
- const style = this.styleControl.value;
- const areaRange = this.areaRangeControl.value;
- this.filteredCases = this.cases.filter(caseItem => {
- // 搜索条件
- if (searchTerm && !(
- caseItem.name.toLowerCase().includes(searchTerm) ||
- caseItem.designer.toLowerCase().includes(searchTerm) ||
- caseItem.styleTags.some(tag => tag.toLowerCase().includes(searchTerm))
- )) {
- return false;
- }
- // 项目类型筛选
- if (projectType && caseItem.projectType !== projectType) {
- return false;
- }
- // 空间类型筛选
- if (spaceType && caseItem.spaceType !== spaceType) {
- return false;
- }
- // 渲染水平筛选
- if (renderingLevel && caseItem.renderingLevel !== renderingLevel) {
- return false;
- }
- // 风格筛选
- if (style && !caseItem.styleTags.includes(style)) {
- return false;
- }
- // 面积范围筛选
- if (areaRange) {
- const [min, max] = areaRange.split('-').map(Number);
- if (max === undefined) {
- if (caseItem.area < min) return false;
- } else if (caseItem.area < min || caseItem.area > max) {
- return false;
- }
- }
- return true;
- });
- this.currentPage = 1;
- this.updatePagination();
- }
- resetFilters() {
- this.searchControl.setValue('');
- this.projectTypeControl.setValue('');
- this.spaceTypeControl.setValue('');
- this.renderingLevelControl.setValue('');
- this.styleControl.setValue('');
- this.areaRangeControl.setValue('');
-
- this.filteredCases = [...this.cases];
- this.currentPage = 1;
- this.updatePagination();
- }
- updatePagination() {
- this.totalPages = Math.ceil(this.filteredCases.length / this.itemsPerPage);
- if (this.currentPage > this.totalPages) {
- this.currentPage = this.totalPages || 1;
- }
- }
- get paginatedCases(): Case[] {
- const startIndex = (this.currentPage - 1) * this.itemsPerPage;
- return this.filteredCases.slice(startIndex, startIndex + this.itemsPerPage);
- }
- nextPage() {
- if (this.currentPage < this.totalPages) {
- this.currentPage++;
- }
- }
- previousPage() {
- if (this.currentPage > 1) {
- this.currentPage--;
- }
- }
- showStatistics() {
- this.showStatsPanel = !this.showStatsPanel;
- }
- viewCaseDetail(caseItem: Case) {
- // 记录案例查看开始时间
- this.caseViewStartTimes.set(caseItem.id, Date.now());
-
- // 增加浏览次数
- caseItem.viewCount++;
-
- // 记录浏览行为
- this.recordBehavior('case_view', caseItem.id, {
- caseName: caseItem.name,
- designer: caseItem.designer,
- projectType: caseItem.projectType,
- spaceType: caseItem.spaceType
- });
-
- // 设置当前选中的案例以显示详情面板
- this.selectedCase = caseItem;
- }
- // 跳转到独立的案例详情页面
- navigateToCaseDetail(caseItem: Case) {
- // 记录案例查看开始时间
- this.caseViewStartTimes.set(caseItem.id, Date.now());
-
- // 增加浏览次数
- caseItem.viewCount++;
-
- // 记录浏览行为
- this.recordBehavior('case_view', caseItem.id, {
- caseName: caseItem.name,
- designer: caseItem.designer,
- projectType: caseItem.projectType,
- spaceType: caseItem.spaceType
- });
-
- // 跳转到独立的案例详情页面
- this.router.navigate(['/customer-service/case-detail', caseItem.id]);
- }
- closeCaseDetail() {
- // 记录案例查看时长
- if (this.selectedCase) {
- const viewStartTime = this.caseViewStartTimes.get(this.selectedCase.id);
- if (viewStartTime) {
- const viewDuration = Date.now() - viewStartTime;
- this.recordBehavior('case_view_duration', this.selectedCase.id, {
- duration: viewDuration,
- durationSeconds: Math.round(viewDuration / 1000)
- });
- this.caseViewStartTimes.delete(this.selectedCase.id);
- }
- }
-
- this.selectedCase = null;
- }
- toggleFavorite(caseItem: Case) {
- const wasLiked = caseItem.isFavorite;
- caseItem.isFavorite = !caseItem.isFavorite;
-
- if (caseItem.isFavorite) {
- caseItem.favoriteCount++;
- this.showToast('已收藏该案例', 'success');
-
- // 记录收藏行为
- this.recordBehavior('case_favorite', caseItem.id, {
- action: 'add',
- caseName: caseItem.name,
- designer: caseItem.designer
- });
- } else {
- caseItem.favoriteCount = Math.max(0, caseItem.favoriteCount - 1);
- this.showToast('已取消收藏', 'info');
-
- // 记录取消收藏行为
- this.recordBehavior('case_favorite', caseItem.id, {
- action: 'remove',
- caseName: caseItem.name,
- designer: caseItem.designer
- });
- }
- }
- shareCase(caseItem: Case) {
- this.selectedCaseForShare = caseItem;
- caseItem.shareCount++;
-
- // 记录分享行为数据
- this.recordBehavior('share', caseItem.id, {
- caseName: caseItem.name,
- designer: caseItem.designer,
- projectType: caseItem.projectType
- });
- }
- closeShareModal() {
- this.selectedCaseForShare = null;
- }
- generateQRCode(caseItem: Case): string {
- // 实际项目中应使用二维码生成库,如 qrcode.js
- const qrData = this.generateShareLink(caseItem);
- // 这里返回一个模拟的二维码图片
- const svgContent = `
- <svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
- <rect width="200" height="200" fill="white"/>
- <rect x="20" y="20" width="160" height="160" fill="black"/>
- <rect x="30" y="30" width="140" height="140" fill="white"/>
- <text x="100" y="105" text-anchor="middle" font-size="12" fill="black">案例二维码</text>
- <text x="100" y="125" text-anchor="middle" font-size="10" fill="gray">${caseItem.name}</text>
- </svg>
- `;
- // 使用 encodeURIComponent 来正确处理SVG内容
- const encodedSVG = encodeURIComponent(svgContent);
- return `data:image/svg+xml;charset=utf-8,${encodedSVG}`;
- }
- generateShareLink(caseItem: Case): string {
- return `${window.location.origin}/customer-service/case-detail/${caseItem.id}?from=share&designer=${encodeURIComponent(caseItem.designer)}`;
- }
- copyShareLink() {
- if (this.selectedCase) {
- const link = this.generateShareLink(this.selectedCase);
- navigator.clipboard.writeText(link).then(() => {
- this.showToast('链接已复制到剪贴板,可直接分享给客户!', 'success');
-
- // 记录复制行为
- this.recordBehavior('copy_link', this.selectedCase!.id, {
- link: link
- });
- }).catch(err => {
- this.showToast('复制失败,请手动复制链接', 'error');
- console.error('复制链接失败:', err);
- });
- }
- }
- shareToWeCom() {
- if (this.selectedCase) {
- // 实际项目中应集成企业微信分享SDK
- const shareData = {
- title: `${this.selectedCase.name} - ${this.selectedCase.designer}设计作品`,
- description: `${this.selectedCase.projectType} | ${this.selectedCase.spaceType} | ${this.selectedCase.area}㎡`,
- link: this.generateShareLink(this.selectedCase),
- imgUrl: this.selectedCase.coverImage
- };
-
- // 模拟企业微信分享
- console.log('分享到企业微信:', shareData);
- this.showToast('已调用企业微信分享', 'success');
-
- // 记录企业微信分享行为
- this.recordBehavior('share_wecom', this.selectedCase.id, shareData);
-
- this.closeShareModal();
- }
- }
- // 新增:行为数据记录方法
- private recordBehavior(action: string, caseId: string, data?: any) {
- const behaviorData = {
- action,
- caseId,
- timestamp: new Date().toISOString(),
- userAgent: navigator.userAgent,
- data: data || {}
- };
-
- // 实际项目中应发送到后端API
- console.log('记录用户行为:', behaviorData);
-
- // 模拟存储到本地(实际应发送到服务器)
- const behaviors = JSON.parse(localStorage.getItem('caseBehaviors') || '[]');
- behaviors.push(behaviorData);
- localStorage.setItem('caseBehaviors', JSON.stringify(behaviors));
- }
- // 新增:显示提示消息
- private showToast(message: string, type: 'success' | 'error' | 'info' = 'info') {
- // 实际项目中应使用专业的Toast组件
- const toast = document.createElement('div');
- toast.className = `toast toast-${type}`;
- toast.textContent = message;
- toast.style.cssText = `
- position: fixed;
- top: 20px;
- right: 20px;
- padding: 12px 20px;
- border-radius: 8px;
- color: white;
- font-weight: 500;
- z-index: 10000;
- animation: slideIn 0.3s ease;
- background: ${type === 'success' ? '#10b981' : type === 'error' ? '#ef4444' : '#3b82f6'};
- `;
-
- document.body.appendChild(toast);
-
- setTimeout(() => {
- toast.style.animation = 'slideOut 0.3s ease';
- setTimeout(() => document.body.removeChild(toast), 300);
- }, 3000);
- }
- }
|