| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- /**
- * 卡片渲染组件
- */
- class CardRenderer {
- /**
- * 渲染帖子卡片列表
- * @param {Array} notes - 帖子数组
- * @param {HTMLElement} container - 容器元素
- */
- static renderNotes(notes, container) {
- if (!notes || notes.length === 0) {
- container.innerHTML = `
- <div class="empty-state">
- <div class="empty-icon">📭</div>
- <p class="empty-text">该特征暂无搜索结果</p>
- </div>
- `;
- return;
- }
- container.innerHTML = notes.map(note => this.renderNoteCard(note)).join('');
- // 初始化轮播图
- this.initCarousels(container);
- }
- /**
- * 渲染单个帖子卡片
- * @param {Object} noteData - 帖子数据
- * @returns {string} HTML字符串
- */
- static renderNoteCard(noteData) {
- const note = noteData.note_card || noteData;
- // 提取数据
- const title = note.display_title || '无标题';
- const desc = note.desc || '';
- const user = note.user || {};
- const interactInfo = note.interact_info || {};
- const images = note.image_list || [];
- const timestamp = note.publish_timestamp;
- // 格式化时间
- const timeStr = this.formatTime(timestamp);
- // 格式化数字
- const likeCount = this.formatNumber(interactInfo.liked_count);
- const commentCount = this.formatNumber(interactInfo.comment_count);
- const collectCount = this.formatNumber(interactInfo.collected_count);
- const shareCount = this.formatNumber(interactInfo.shared_count);
- return `
- <div class="note-card">
- <!-- 卡片头部 -->
- <div class="card-header">
- <div class="card-user">
- <img class="card-avatar"
- src="${user.avatar || 'https://via.placeholder.com/40'}"
- alt="${user.nickname}"
- onerror="this.src='https://via.placeholder.com/40'">
- <div class="card-user-info">
- <div class="card-username">${user.nickname || '匿名用户'}</div>
- <div class="card-time">${timeStr}</div>
- </div>
- </div>
- </div>
- <!-- 图片轮播 -->
- ${images.length > 0 ? `
- <div class="card-carousel" data-carousel-container></div>
- ` : ''}
- <!-- 卡片内容 -->
- <div class="card-body">
- <h3 class="card-title">${this.escapeHtml(title)}</h3>
- <div class="card-desc" data-desc>
- ${this.escapeHtml(desc)}
- </div>
- ${desc.length > 150 ? `
- <span class="card-desc-toggle" data-desc-toggle>展开全文</span>
- ` : ''}
- </div>
- <!-- 卡片底部交互数据 -->
- <div class="card-stats">
- <div class="card-stat">
- <span class="card-stat-icon">❤️</span>
- <span>${likeCount}</span>
- </div>
- <div class="card-stat">
- <span class="card-stat-icon">💬</span>
- <span>${commentCount}</span>
- </div>
- <div class="card-stat">
- <span class="card-stat-icon">⭐</span>
- <span>${collectCount}</span>
- </div>
- <div class="card-stat">
- <span class="card-stat-icon">🔗</span>
- <span>${shareCount}</span>
- </div>
- </div>
- </div>
- `;
- }
- /**
- * 初始化所有轮播图
- * @param {HTMLElement} container - 容器元素
- */
- static initCarousels(container) {
- const carouselContainers = container.querySelectorAll('[data-carousel-container]');
- carouselContainers.forEach((carouselContainer, index) => {
- const card = carouselContainer.closest('.note-card');
- // 从容器中找到对应的图片数据
- // 这里需要从原始数据中获取,所以我们先存储在 data 属性中
- const carouselIndex = Array.from(container.querySelectorAll('[data-carousel-container]')).indexOf(carouselContainer);
- // 临时方案:从全局变量获取(在 renderNotes 时设置)
- if (window.__currentNotes && window.__currentNotes[carouselIndex]) {
- const note = window.__currentNotes[carouselIndex].note_card || window.__currentNotes[carouselIndex];
- const images = note.image_list || [];
- if (images.length > 0) {
- new Carousel(carouselContainer, images);
- }
- }
- });
- // 绑定展开/折叠事件
- this.bindDescToggle(container);
- }
- /**
- * 绑定描述文本展开/折叠
- * @param {HTMLElement} container - 容器元素
- */
- static bindDescToggle(container) {
- const toggles = container.querySelectorAll('[data-desc-toggle]');
- toggles.forEach(toggle => {
- toggle.addEventListener('click', () => {
- const desc = toggle.previousElementSibling;
- const isExpanded = desc.classList.contains('expanded');
- if (isExpanded) {
- desc.classList.remove('expanded');
- toggle.textContent = '展开全文';
- } else {
- desc.classList.add('expanded');
- toggle.textContent = '收起';
- }
- });
- });
- }
- /**
- * 格式化时间戳
- * @param {number} timestamp - Unix时间戳(秒)
- * @returns {string} 格式化的时间字符串
- */
- static formatTime(timestamp) {
- if (!timestamp) return '未知时间';
- const date = new Date(timestamp * 1000);
- const now = new Date();
- const diff = now - date;
- // 小于1分钟
- if (diff < 60 * 1000) {
- return '刚刚';
- }
- // 小于1小时
- if (diff < 60 * 60 * 1000) {
- const minutes = Math.floor(diff / (60 * 1000));
- return `${minutes}分钟前`;
- }
- // 小于24小时
- if (diff < 24 * 60 * 60 * 1000) {
- const hours = Math.floor(diff / (60 * 60 * 1000));
- return `${hours}小时前`;
- }
- // 小于7天
- if (diff < 7 * 24 * 60 * 60 * 1000) {
- const days = Math.floor(diff / (24 * 60 * 60 * 1000));
- return `${days}天前`;
- }
- // 显示具体日期
- const year = date.getFullYear();
- const month = String(date.getMonth() + 1).padStart(2, '0');
- const day = String(date.getDate()).padStart(2, '0');
- if (year === now.getFullYear()) {
- return `${month}-${day}`;
- }
- return `${year}-${month}-${day}`;
- }
- /**
- * 格式化数字
- * @param {number|string} num - 数字
- * @returns {string} 格式化的数字字符串
- */
- static formatNumber(num) {
- if (!num) return '0';
- const n = parseInt(num);
- if (n >= 10000) {
- return (n / 10000).toFixed(1) + 'w';
- }
- if (n >= 1000) {
- return (n / 1000).toFixed(1) + 'k';
- }
- return n.toString();
- }
- /**
- * HTML 转义
- * @param {string} text - 原始文本
- * @returns {string} 转义后的文本
- */
- static escapeHtml(text) {
- if (!text) return '';
- const div = document.createElement('div');
- div.textContent = text;
- return div.innerHTML;
- }
- /**
- * 显示加载状态
- * @param {HTMLElement} container - 容器元素
- */
- static showLoading(container) {
- container.innerHTML = '<div class="loading">加载中...</div>';
- }
- /**
- * 显示错误状态
- * @param {HTMLElement} container - 容器元素
- * @param {string} message - 错误消息
- */
- static showError(container, message) {
- container.innerHTML = `
- <div class="empty-state">
- <div class="empty-icon">❌</div>
- <p class="empty-text">${message}</p>
- </div>
- `;
- }
- }
|