const REPORT_ENHANCED_STYLE = `:root { --primary: #1e3a5f; --primary-light: #2d5a8a; --accent: #e8b339; --accent2: #3b82f6; --success: #10b981; --danger: #ef4444; --warning: #f59e0b; --bg: #f0f4f8; --card-bg: #ffffff; --text: #1e293b; --text-secondary: #64748b; --border: #e2e8f0; --shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1); --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1); --radius: 16px; --radius-sm: 10px; } *{box-sizing:border-box;margin:0;padding:0} body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: var(--bg); color: var(--text); line-height: 1.6; } .wrap {max-width:1200px;margin:auto;padding:24px 16px 48px} .hero { background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%); border-radius: var(--radius); padding: 40px 32px; margin-bottom: 24px; color: white; position: relative; overflow: hidden; } .hero::before { content: ''; position: absolute; top: -50%; right: -20%; width: 400px; height: 400px; background: rgba(255,255,255,0.05); border-radius: 50%; } .hero h1 { font-size: 28px; font-weight: 700; margin-bottom: 8px; position: relative; } .hero .subtitle { font-size: 15px; opacity: 0.85; margin-bottom: 24px; position: relative; } .hero .source { font-size: 13px; opacity: 0.7; position: relative; } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; } .stat-card { background: var(--card-bg); border-radius: var(--radius); padding: 24px; box-shadow: var(--shadow); border: 1px solid var(--border); position: relative; overflow: hidden; transition: transform 0.2s, box-shadow 0.2s; } .stat-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); } .stat-card .icon { width: 44px; height: 44px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 20px; margin-bottom: 12px; } .stat-card.primary .icon {background: #dbeafe;} .stat-card.success .icon {background: #d1fae5;} .stat-card.warning .icon {background: #fef3c7;} .stat-card.info .icon {background: #e0e7ff;} .stat-card .label { font-size: 13px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; } .stat-card .value { font-size: 28px; font-weight: 700; color: var(--primary); } .stat-card .trend { font-size: 12px; margin-top: 6px; display: flex; align-items: center; gap: 4px; } .trend.up {color: var(--success);} .trend.down {color: var(--danger);} .section { background: var(--card-bg); border-radius: var(--radius); padding: 28px; margin-bottom: 20px; box-shadow: var(--shadow); border: 1px solid var(--border); } .section-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; flex-wrap: wrap; gap: 12px; } .section-title { font-size: 18px; font-weight: 700; color: var(--primary); display: flex; align-items: center; gap: 10px; } .section-title .badge { background: var(--accent); color: var(--primary); font-size: 11px; padding: 3px 10px; border-radius: 20px; font-weight: 600; } .section-subtitle { font-size: 13px; color: var(--text-secondary); } .chart-container { background: #fafbfc; border-radius: var(--radius-sm); padding: 20px; margin-bottom: 20px; border: 1px solid var(--border); } .chart-container img { width: 100%; height: auto; border-radius: 8px; } .chart-bar { display: flex; align-items: flex-end; gap: 8px; height: 200px; padding: 16px 8px 32px; position: relative; } .chart-bar::after { content: ''; position: absolute; bottom: 32px; left: 8px; right: 8px; height: 1px; background: var(--border); } .bar-item { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 0; } .bar-fill { width: 100%; max-width: 48px; border-radius: 6px 6px 0 0; transition: height 0.8s ease-out; position: relative; } .bar-fill:hover { filter: brightness(1.1); } .bar-fill .tooltip { position: absolute; bottom: calc(100% + 6px); left: 50%; transform: translateX(-50%); background: var(--primary); color: white; font-size: 11px; padding: 4px 8px; border-radius: 6px; white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 0.2s; } .bar-fill:hover .tooltip { opacity: 1; } .bar-label { font-size: 11px; color: var(--text-secondary); text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; } .table-wrap {overflow:auto;margin-top:12px} table {width:100%;border-collapse:separate;border-spacing:0;font-size:14px} thead th { background: var(--primary); color: white; font-weight: 600; padding: 12px 14px; text-align: right; white-space: nowrap; font-size: 13px; } thead th:first-child {text-align:left;border-radius:10px 0 0 0} thead th:last-child {border-radius:0 10px 0 0} tbody td { padding: 12px 14px; border-bottom: 1px solid var(--border); text-align: right; white-space: nowrap; } tbody td:first-child {text-align:left;font-weight:500} tbody tr:hover td {background:#f8fafc} tbody tr:last-child td:first-child {border-radius:0 0 0 10px} tbody tr:last-child td:last-child {border-radius:0 0 10px 0} .rank-1 {color: var(--accent); font-weight: 700;} .rank-2 {color: var(--text-secondary); font-weight: 600;} .rank-3 {color: #b45309; font-weight: 600;} .cell-bar { display: flex; align-items: center; gap: 8px; } .cell-bar .track { flex: 1; height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; min-width: 60px; } .cell-bar .fill { height: 100%; border-radius: 3px; transition: width 0.8s ease-out; } .insights { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin-top: 20px; } .insight-card { background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); border-radius: var(--radius-sm); padding: 20px; border-left: 4px solid var(--accent2); } .insight-card h4 { font-size: 14px; color: var(--primary); margin-bottom: 8px; display: flex; align-items: center; gap: 6px; } .insight-card p { font-size: 13px; color: var(--text-secondary); line-height: 1.7; } footer { text-align: center; padding: 32px 16px; color: var(--text-secondary); font-size: 13px; } @media (max-width: 640px) { .hero {padding: 28px 20px} .hero h1 {font-size: 22px} .stats-grid {grid-template-columns: repeat(2, 1fr)} .stat-card .value {font-size: 22px} .section {padding: 20px} .chart-bar {height: 160px} } @keyframes fadeInUp { from {opacity:0;transform:translateY(20px)} to {opacity:1;transform:translateY(0)} } .stat-card, .section, .insight-card { animation: fadeInUp 0.5s ease-out both; } .stat-card:nth-child(1) {animation-delay:0.05s} .stat-card:nth-child(2) {animation-delay:0.1s} .stat-card:nth-child(3) {animation-delay:0.15s} .stat-card:nth-child(4) {animation-delay:0.2s}`; const SECTION_LAYOUT = { 区域: { emoji: '🗺️', badge: (count) => `${count}大区域`, subtitle: '各区域销售额、订单量与单均表现', chartId: 'regionChart', tableBarColor(index) { return index <= 4 ? 'var(--accent2)' : 'var(--danger)'; }, chartColor(index) { if (index <= 3) return '#3b82f6'; if (index === 4) return '#f59e0b'; return '#ef4444'; }, }, 城市: { emoji: '🏙️', badge: (count) => `TOP ${count}`, subtitle: '各城市销售额排名与表现', chartId: 'cityChart', tableBarColor(index) { if (index <= 5) return 'var(--accent2)'; if (index <= 9) return 'var(--warning)'; return 'var(--danger)'; }, chartColor(index) { if (index <= 5) return '#3b82f6'; if (index <= 9) return '#f59e0b'; return '#ef4444'; }, }, 产品: { emoji: '🍜', badge: (count) => `${count}种产品`, subtitle: '各产品销售额、销量与单均表现', chartId: 'productChart', tableBarColor(index) { if (index <= 5) return 'var(--accent2)'; if (index <= 8) return 'var(--warning)'; return 'var(--danger)'; }, chartColor(index) { if (index <= 5) return '#3b82f6'; if (index <= 8) return '#f59e0b'; return '#ef4444'; }, }, }; function escapeXml(value) { return String(value ?? '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function safeJsonForScript(value) { return JSON.stringify(value).replace(/= 100 && Math.abs(rounded - Math.round(rounded)) <= 0.15) { return `${Math.round(rounded)}万`; } const text = rounded.toFixed(1); return text.endsWith('.0') ? `${Math.round(rounded)}万` : `${text}万`; } function formatWanPerOrder(value) { return formatWan(value); } function sectionLayout(dimension) { return SECTION_LAYOUT[dimension] ?? { emoji: '📊', badge: (count) => `${count} 项`, subtitle: `各${dimension}表现`, chartId: `chart${dimension}`, tableBarColor: () => 'var(--accent2)', chartColor: () => '#3b82f6', }; } function formatBarWidth(widthPct) { const raw = Number(widthPct) || 0; const nearest = Math.round(raw); if (Math.abs(raw - nearest) < 0.055) { return String(nearest); } const rounded = Math.round(raw * 10) / 10; return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1).replace(/\.0$/, ''); } function shareCell(value, topValue, grandTotal, barColor) { const sharePct = reportSharePercent(value, grandTotal); const widthPct = topValue ? (Number(value) / Number(topValue)) * 100 : 0; const widthText = formatBarWidth(widthPct); const shareText = sharePct.toFixed(1); return `
${shareText}%
`; } function rankMedalLabel(index, group) { if (index === 0) return `🥇 ${escapeXml(group)}`; if (index === 1) return `🥈 ${escapeXml(group)}`; if (index === 2) return `🥉 ${escapeXml(group)}`; return escapeXml(group); } function findSummaryMetric(model, pattern) { return (model.meta?.summaryMetrics ?? []).find((item) => pattern.test(item.name)); } function sectionByDimension(model, dimension) { return model.sections.find((section) => section.dimension === dimension) ?? null; } function buildHeroSource(model) { const parts = [ `数据来源:${model.source.path}`, `${model.source.rows}条记录`, ]; const region = sectionByDimension(model, '区域'); const city = sectionByDimension(model, '城市'); const product = sectionByDimension(model, '产品'); if (region) parts.push(`${region.items.length}大区域`); if (city) parts.push(`${city.items.length}个城市`); if (product) parts.push(`${product.items.length}种产品`); return parts.join(' · '); } function buildHeroSubtitle(model) { const dimensions = model.sections.map((section) => section.dimension).join(' · '); const period = model.meta?.periodLabel; if (period && dimensions) return `${period} · ${dimensions} 三维深度分析`; if (dimensions) return `${dimensions} 多维深度分析`; return 'Excel 数据分析报告'; } function buildStatCards(model) { const cards = []; const metricLabel = metricShortLabel(model.fields.metric); const metricPrefix = metricUsesCurrencyLabel(model.fields.metric) ? '¥' : ''; cards.push({ tone: 'primary', icon: '💰', label: `总${metricLabel}`, value: `${metricPrefix}${formatReportNumber(model.totals.metric)}`, trend: model.sections.some((section) => section.dimension === '区域') ? `↑ 覆盖${sectionByDimension(model, '区域')?.items.length ?? model.sections.length}大区域` : `↑ 覆盖${model.sections.length}个维度`, }); if (model.fields.quantityMetric) { const unit = metricUnit(model.fields.quantityMetric); const avgPerOrder = model.totals.orders ? model.totals.quantity / model.totals.orders : null; cards.push({ tone: 'success', icon: '📦', label: metricShortLabel(model.fields.quantityMetric), value: unit ? `${formatReportInteger(model.totals.quantity)} ${unit}` : formatReportInteger(model.totals.quantity), trend: avgPerOrder == null ? '↑ 已汇总' : `↑ 平均${formatReportInteger(avgPerOrder)}${unit ? `${unit}/单` : '/单'}`, }); } const dealerMetric = findSummaryMetric(model, /经销商/); if (dealerMetric) { const citySection = sectionByDimension(model, '城市'); const avgPerCity = citySection?.items.length ? dealerMetric.total / citySection.items.length : null; cards.push({ tone: 'warning', icon: '🏪', label: metricShortLabel(dealerMetric.name), value: `${formatReportInteger(dealerMetric.total)} 家`, trend: avgPerCity == null ? '↑ 已汇总' : `↑ 平均${Math.round(avgPerCity)}家/城市`, }); } else if (model.fields.profitMetric) { cards.push({ tone: 'warning', icon: '📈', label: metricShortLabel(model.fields.profitMetric), value: `${metricPrefix}${formatReportNumber(model.totals.profit)}`, trend: model.totals.margin == null ? '↑ 已汇总' : `↑ 比率 ${(model.totals.margin * 100).toFixed(1)}%`, }); } const citySection = sectionByDimension(model, '城市'); if (citySection) { cards.push({ tone: 'info', icon: '📍', label: '覆盖城市', value: `${citySection.items.length} 个`, trend: '↑ 全国布局', }); } else { cards.push({ tone: 'info', icon: '📍', label: model.fields.orderId ? '唯一订单数' : '记录数', value: `${formatReportInteger(model.totals.orders)} 个`, trend: `↑ ${model.source.rows} 行源数据`, }); } return cards.slice(0, 4).map((card) => ( `
${card.icon}
${escapeXml(card.label)}
${escapeXml(card.value)}
${escapeXml(card.trend)}
` )).join(''); } function buildRegionTable(section, model, layout) { const topValue = section.items[0]?.value ?? 0; const avgWinner = model.facts.topAverage[section.dimension]; const rows = section.items.map((item, index) => { const avgCell = avgWinner?.group === item.group ? `${escapeXml(formatReportInteger(item.averagePerOrder))}` : escapeXml(formatReportInteger(item.averagePerOrder)); return `${rankMedalLabel(index, item.group)}${escapeXml(formatReportInteger(item.value))}${shareCell(item.value, topValue, model.totals.metric, layout.tableBarColor(index))}${escapeXml(formatReportInteger(item.orders))}${avgCell}`; }).join(''); return `区域${escapeXml(model.fields.metric)}占比订单数单均(元)${rows}`; } function buildCityTable(section, model, layout) { const topValue = section.items[0]?.value ?? 0; const regionField = model.meta?.contextFields?.find((field) => field === '区域') ?? '区域'; const dealerField = model.meta?.contextFields?.find((field) => /经销商/.test(field)); const rows = section.items.map((item, index) => { const rankCell = index <= 2 ? `${index + 1}` : String(index + 1); const cityCell = index <= 2 ? `${escapeXml(item.group)}` : escapeXml(item.group); const regionValue = escapeXml(item.context?.[regionField] ?? ''); const dealerValue = dealerField ? escapeXml(formatReportInteger(item.context?.[dealerField])) : '—'; return `${rankCell}${cityCell}${regionValue}${escapeXml(formatReportInteger(item.value))}${shareCell(item.value, topValue, model.totals.metric, layout.tableBarColor(index))}${dealerValue}`; }).join(''); return `排名城市区域${escapeXml(model.fields.metric)}占比经销商${rows}`; } function buildProductTable(section, model, layout) { const topValue = section.items[0]?.value ?? 0; const avgWinner = model.facts.topAverage[section.dimension]; const joinFields = [...new Set(section.items.flatMap((item) => Object.keys(item.masterData ?? {})))]; const quantityLabel = model.fields.quantityMetric ? `总销量(${metricUnit(model.fields.quantityMetric) || '量'})` : null; const rows = section.items.map((item, index) => { const avgCell = avgWinner?.group === item.group ? `${escapeXml(formatReportInteger(item.averagePerOrder))}` : escapeXml(formatReportInteger(item.averagePerOrder)); const quantityCell = model.fields.quantityMetric ? `${escapeXml(formatReportInteger(item.quantity))}` : ''; const joinCells = joinFields.map((field) => `${escapeXml(item.masterData?.[field] ?? '')}`).join(''); return `${rankMedalLabel(index, item.group)}${joinCells}${escapeXml(formatReportInteger(item.value))}${shareCell(item.value, topValue, model.totals.metric, layout.tableBarColor(index))}${escapeXml(formatReportInteger(item.orders))}${avgCell}${quantityCell}`; }).join(''); const joinHeaders = joinFields.map((field) => `${escapeXml(field)}`).join(''); const quantityHeader = quantityLabel ? `${escapeXml(quantityLabel)}` : ''; return `产品${joinHeaders}${escapeXml(model.fields.metric)}占比订单数单均(元)${quantityHeader}${rows}`; } function buildGenericTable(section, model, layout) { const topValue = section.items[0]?.value ?? 0; const joinFields = [...new Set(section.items.flatMap((item) => Object.keys(item.masterData ?? {})))]; const header = [ section.dimension, ...joinFields, model.fields.metric, '占比', ...(model.fields.profitMetric ? [model.fields.profitMetric, '比率'] : []), ...(model.fields.quantityMetric ? [model.fields.quantityMetric] : []), '订单数', '单均', ]; const avgWinner = model.facts.topAverage[section.dimension]; const rows = section.items.map((item, index) => { const cells = [ rankMedalLabel(index, item.group), ...joinFields.map((field) => escapeXml(item.masterData?.[field] ?? '')), escapeXml(formatReportNumber(item.value)), shareCell(item.value, topValue, model.totals.metric, layout.tableBarColor(index)), ...(model.fields.profitMetric ? [escapeXml(formatReportNumber(item.profit)), escapeXml(item.margin == null ? '—' : `${(item.margin * 100).toFixed(1)}%`)] : []), ...(model.fields.quantityMetric ? [escapeXml(formatReportNumber(item.quantity))] : []), escapeXml(formatReportNumber(item.orders)), avgWinner?.group === item.group ? `${escapeXml(formatReportNumber(item.averagePerOrder))}` : escapeXml(formatReportNumber(item.averagePerOrder)), ]; return `${cells.map((cell) => `${cell}`).join('')}`; }).join(''); return `${header.map((cell) => `${escapeXml(cell)}`).join('')}${rows}`; } function buildSection(section, model, sectionIndex) { const layout = sectionLayout(section.dimension); const chartId = layout.chartId || `reportChart${sectionIndex}`; const chartData = section.items.map((item, index) => ({ label: item.group, value: item.value, color: layout.chartColor(index), })); let tableHtml; if (section.dimension === '区域') tableHtml = buildRegionTable(section, model, layout); else if (section.dimension === '城市') tableHtml = buildCityTable(section, model, layout); else if (section.dimension === '产品') tableHtml = buildProductTable(section, model, layout); else tableHtml = buildGenericTable(section, model, layout); return `
${layout.emoji} ${escapeXml(section.dimension)}分析 ${escapeXml(layout.badge(section.items.length))}
${escapeXml(layout.subtitle)}
${tableHtml}
` + `\n`; } function buildInsights(model) { const cards = []; const regionSection = sectionByDimension(model, '区域'); const citySection = sectionByDimension(model, '城市'); const productSection = sectionByDimension(model, '产品'); if (regionSection?.items.length) { const leader = regionSection.items[0]; const avgWinner = model.facts.topAverage.区域; const leaderCities = model.meta?.regionCityCounts?.[leader.group] ?? leader.orders; const winnerCities = avgWinner ? (model.meta?.regionCityCounts?.[avgWinner.group] ?? null) : null; let text = `${escapeXml(leader.group)}以${escapeXml(formatWan(leader.value))}领跑总量`; if (avgWinner && avgWinner.group !== leader.group) { text += `,但${escapeXml(avgWinner.group)}单均最高(${escapeXml(formatWanPerOrder(avgWinner.value))}/单)`; text += `。${escapeXml(leader.group)}靠${leaderCities}城堆量,${escapeXml(avgWinner.group)}${winnerCities ?? ''}城质量更优。建议:${escapeXml(avgWinner.group)}加大投入,${escapeXml(leader.group)}优化经销商效率。`; } else { text += '。'; } cards.push({ title: '区域格局', emoji: '🗺️', text }); } if (citySection?.items.length >= 3) { const top3 = citySection.items.slice(0, 3); const parts = top3.map((item, index) => { const label = index === 0 ? `${escapeXml(item.group)}(${escapeXml(formatWan(item.value))})` : `${escapeXml(item.group)}(${escapeXml(formatWan(item.value))})`; return label; }); let text = `${parts.join('> ')}位列前三。`; const northeastCities = citySection.items .filter((item) => String(item.context?.区域 ?? '').includes('东北')) .sort((a, b) => a.value - b.value); if (northeastCities.length >= 2) { text += `东北双城(${escapeXml(northeastCities[0].group)}${escapeXml(formatWan(northeastCities[0].value))}、${escapeXml(northeastCities[1].group)}${escapeXml(formatWan(northeastCities[1].value))})明显落后,是重点提升区域。`; } else { const tail = [...citySection.items.slice(-2)].sort((a, b) => a.value - b.value); if (tail.length >= 2) { text += `${escapeXml(tail[0].group)}(${escapeXml(formatWan(tail[0].value))})、${escapeXml(tail[1].group)}(${escapeXml(formatWan(tail[1].value))})明显落后,是重点提升区域。`; } } cards.push({ title: '城市表现', emoji: '🏙️', text }); } if (productSection?.items.length) { const leader = productSection.items[0]; const avgWinner = model.facts.topAverage.产品; const leaderShare = reportSharePercent(leader.value, model.totals.metric).toFixed(1); let text = `${escapeXml(leader.group)}是绝对主力(${escapeXml(formatWan(leader.value))},${leaderShare}%)`; const regionCount = model.meta?.productRegionCounts?.[leader.group]; if (regionCount) text += `,覆盖${regionCount}区域通用性强。`; if (avgWinner) { text += `${escapeXml(avgWinner.group)}单均最高(${escapeXml(formatWanPerOrder(avgWinner.value))})`; const onlyCity = model.meta?.productOnlyCity?.[avgWinner.group]; if (onlyCity) { text += `,但仅${escapeXml(onlyCity)}一城有售,可考虑向成都等城市扩展。`; } else { text += '。'; } } else { text += '。'; } cards.push({ title: '产品结构', emoji: '🍜', text }); } if (regionSection?.items.length >= 2) { const weakRegions = regionSection.items.filter((item) => /东北|西北/.test(item.group)); if (weakRegions.length) { const combined = weakRegions.reduce((sum, item) => sum + item.value, 0); const combinedShare = reportSharePercent(combined, model.totals.metric).toFixed(1); const names = weakRegions.map((item) => escapeXml(item.group)).join('、'); let text = `${names}市场薄弱(合计仅${escapeXml(formatWan(combined))},${combinedShare}%)。`; const northwest = weakRegions.find((item) => item.group.includes('西北')); const northeast = weakRegions.filter((item) => item.group.includes('东北')); if (northwest && citySection) { const northwestCities = citySection.items.filter((item) => item.context?.区域 === northwest.group); if (northwestCities.length === 1) { text += `西北仅${escapeXml(northwestCities[0].group)}一城,`; } } if (northeast.length && citySection) { const dealerField = model.meta?.contextFields?.find((field) => /经销商/.test(field)); const dealerCounts = citySection.items .filter((item) => northeast.some((region) => item.context?.区域 === region.group)) .map((item) => Number(item.context?.[dealerField] ?? NaN)) .filter((value) => Number.isFinite(value)); if (dealerCounts.length) { const minDealer = Math.min(...dealerCounts); const maxDealer = Math.max(...dealerCounts); text += `东北双城经销商数最少(${minDealer}${maxDealer !== minDealer ? `-${maxDealer}` : ''}家)。`; } } text += '建议增加经销商覆盖,或推出区域特色产品。'; cards.push({ title: '增长机会', emoji: '📈', text }); } } if (!cards.length) { for (const section of model.sections.slice(0, 4)) { const leader = section.items[0]; if (!leader) continue; cards.push({ title: `${section.dimension}概览`, emoji: '📊', text: `${escapeXml(leader.group)}在${escapeXml(section.dimension)}维度领先(${escapeXml(formatReportNumber(leader.value))})。`, }); } } return cards.slice(0, 4).map((item) => ( `

${item.emoji} ${escapeXml(item.title)}

${item.text}

` )).join(''); } const REPORT_CHART_SCRIPT = `function renderChart(containerId, data) { const container = document.getElementById(containerId); if (!container) return; const max = Math.max(...data.map(d=>d.value), 1); data.forEach((d,i) => { const pct = (d.value / max * 100).toFixed(1); const item = document.createElement('div'); item.className = 'bar-item'; item.innerHTML = \`
\${d.label}: \${d.value.toLocaleString()}元
\${d.label}
\`; container.appendChild(item); setTimeout(() => { item.querySelector('.bar-fill').style.height = pct + '%'; }, 100 + i * 50); }); } (window.__MINDSPACE_EXCEL_REPORT_CHARTS__||[]).forEach((entry)=>renderChart(entry.id,entry.data));`; export function reportHtml(model, { title = '' } = {}) { const safeTitle = escapeXml(title || `${model.source.sheet} 数据分析报告`); const dimensions = model.sections.map((section) => section.dimension).join(' · '); const description = model.meta?.periodLabel ? `${model.meta.periodLabel} ${model.source.sheet} 多维度分析,涵盖 ${model.sections.map((section) => section.dimension).join('、')} 三大维度` : `${model.source.sheet} 数据分析,涵盖 ${model.sections.length} 个维度`; const coverMeta = safeJsonForScript({ tag: '报告', emoji: '📊', accent: '#1e3a5f', accent2: '#f0f4f8', subtitle: dimensions ? dimensions.replace(/ · /g, '·') + '三维分析' : model.source.sheet, }); const sectionsHtml = model.sections.map((section, index) => buildSection(section, model, index)).join('\n\n'); const insightsHtml = buildInsights(model); return ` ${safeTitle}

📊 ${safeTitle}

${escapeXml(buildHeroSubtitle(model))}
${escapeXml(buildHeroSource(model))}
${model.source.downloadUrl ? `下载原始 Excel` : ''}
${buildStatCards(model)}
${sectionsHtml}
💡 核心洞察
基于数据的关键发现与建议
${insightsHtml}
`; }