import type { NormalizedCandidate } from './recommendation.service.ts'; function escapeHtml(value: string): string { return value .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"'); } /** * 生成媒体资源推荐表 Excel (HTML table format compatible with MS Excel) * 按模板生成:账号名称、ID、账号类型、地区、链接、粉丝数、赞藏数、 * 图文/视频报价、推荐等级、推荐理由等字段 */ export function generateExcelBuffer(candidates: NormalizedCandidate[], taskFileName?: string): Buffer { const title = taskFileName ? taskFileName.replace(/\.[^.]+$/, '') : '达人推荐表'; const now = new Date().toLocaleDateString('zh-CN'); const headerRow = ` 序号 平台 账号名称 账号ID 地区 主页链接 粉丝数 图文报价 视频报价 最低报价 内容标签 综合评分 风格匹配 推荐等级 推荐理由 风险提示 数据来源 昵称 性别 粉丝数(万) 赞藏数(万) 内容类型 城市 地理位置 小红书主页 合作方式 图文笔记报价(含平台服务费) 视频笔记报价(含平台服务费) `; const rows = candidates.map((c, index) => ` ${index + 1} ${escapeHtml(mapPlatformDisplay(c.platform))} ${escapeHtml(c.displayName)} ${escapeHtml(c.platformUserId)} ${escapeHtml(c.location || '-')} ${escapeHtml(c.profileUrl)} ${formatFans(c.fansCount)} ${c.imagePrice > 0 ? `¥${c.imagePrice}` : '-'} ${c.videoPrice > 0 ? `¥${c.videoPrice}` : '-'} ${c.minPrice > 0 ? `¥${c.minPrice}` : '-'} ${escapeHtml(c.contentTags.join('、') || '-')} ${c.score} ${c.styleMatch}% ${escapeHtml(c.recommendStatus)} ${escapeHtml(c.recommendReason || '-')} ${escapeHtml(c.riskNote || '-')} ${escapeHtml(c.sourceProvider)} ${escapeHtml(c.displayName)} ${escapeHtml(c.gender || '-')} ${formatWan(c.fansCount)} ${formatWan(c.likedCollectCount || 0)} ${escapeHtml(c.contentType || c.contentTags.join('、') || '-')} ${escapeHtml(c.city || c.location || '-')} ${escapeHtml(c.geoLocation || c.location || '-')} ${escapeHtml(c.xiaohongshuUrl || (c.platform === 'xiaohongshu' ? c.profileUrl : '-'))} ${escapeHtml(c.cooperationMethod || '-')} ${c.imagePrice > 0 ? `¥${c.imagePrice}` : '-'} ${c.videoPrice > 0 ? `¥${c.videoPrice}` : '-'} ` ).join(''); const html = ` ${headerRow} ${rows}
${escapeHtml(title)} - 媒体资源推荐表
生成时间:${now} | 候选总数:${candidates.length} | 强推荐:${candidates.filter(c => c.recommendStatus === '强推荐').length} | 备选:${candidates.filter(c => c.recommendStatus === '备选').length}
`; return Buffer.from(html, 'utf-8'); } function mapPlatformDisplay(platform: string): string { const map: Record = { xiaohongshu: '小红书', douyin: '抖音', bilibili: 'B站', weibo: '微博', weixin: '微信/公众号', }; return map[platform] || platform; } function formatFans(count: number): string { if (count === 0) return '-'; if (count >= 10000) return `${(count / 10000).toFixed(1)}万`; return String(count); } function formatWan(count: number): string { if (!count) return '-'; const value = count >= 10000 ? count / 10000 : count; return Number.isInteger(value) ? String(value) : value.toFixed(2).replace(/0+$/, '').replace(/\.$/, ''); }