/** * pages/view-report.js —— 报告阅读页(保留 v3 能力)。 * * - 吸顶章节导航 + 滚动高亮(IntersectionObserver)+ 平滑跳转,当前章写回 ?ch=,刷新还原 * - 每章开头一句「这章在回答什么问题」 * - 数据管线可见(2,727 采集 → 1,190 去噪 → 730 精准,过滤率 73%) * - KPI、交叉分析表、敢下判断的结论 * - 结论旁的证据锚点 → 证据抽屉(N 人提到 / N 条原文 / N 条支撑成果,点名到人) * - 附录:所有结论 × 证据对照总表;「关联成果」区显示引用了本报告的归纳(反向) * * 所有 section.kind 都有渲染器;未知 kind 原样列出键值,不静默吞。 */ import { LitElement, css, html } from 'lit'; import { useTokens } from '../components/base.js'; import { reportOverview, findingsOf, fmtDate, chapterDigests, reportTypeOf } from '../core/index.js'; import { reportCategoryOf } from '../core/report-delivery.js'; import { reportAttachments } from '../core/report-attachments.js'; import { evidenceExcerpts } from '../core/report-evidence.js'; import { navigate, href, safeReturnTo } from '../utils/router.js'; import { t, VIEW_CSS } from './shared.js'; import { icon } from '../components/icons.js'; export class ViewReport extends LitElement { static properties = { db: { type: Object }, artifactId: { type: String }, routeQuery: { type: Object }, _active: { state: true }, }; #r = null; #type = null; #resize = null; #chapterFrame = null; #smoothJump = false; #lastChapterTarget = ''; #chapterVersion = 0; constructor() { super(); this.db = null; this.artifactId = ''; this.routeQuery = {}; this._active = ''; } connectedCallback() { super.connectedCallback(); useTokens(this); } disconnectedCallback() { super.disconnectedCallback(); this.#io?.disconnect(); this.#resize?.disconnect(); cancelAnimationFrame(this.#chapterFrame); this.#chapterVersion++; } #io = null; willUpdate(changed) { if (changed.has('artifactId')) this._active = ''; } updated(changed) { if (!this.db) return; if (!this.#resize && typeof ResizeObserver !== 'undefined') { this.#resize = new ResizeObserver(() => this.#measureOffsets()); const toc = this.renderRoot.querySelector('[data-toc]'); if (toc) this.#resize.observe(toc); } if (changed.has('artifactId') || changed.has('db')) this.#measureOffsets(); const chapter = String(this.routeQuery?.ch || '').replace(/^ch-/, ''); const target = chapter ? `${this.artifactId}:${chapter}` : ''; if (chapter && target !== this.#lastChapterTarget) { const smooth = this.#smoothJump; this.#smoothJump = false; this.#restoreChapter(`ch-${chapter}`, smooth ? 'smooth' : 'instant'); } else if (target !== this.#lastChapterTarget) { this.#chapterVersion++; cancelAnimationFrame(this.#chapterFrame); } this.#lastChapterTarget = target; } #measureOffsets() { const top = window.matchMedia('(max-width: 767px)').matches ? 0 : 12; const tocHeight = Math.ceil(this.renderRoot.querySelector('[data-toc]')?.getBoundingClientRect().height || 44); this.style.setProperty('--report-topbar', `${top}px`); this.style.setProperty('--report-anchor', `${top + tocHeight + 16}px`); this.#observeChapters(top + tocHeight + 8); } async #restoreChapter(id, behavior) { const version = ++this.#chapterVersion; // 卡片的 Shadow DOM 和字体都会改变章节位置,等待实际布局稳定后再恢复锚点。 await Promise.all([...this.renderRoot.querySelectorAll('*')].map(el => el.updateComplete)); if (document.fonts) await document.fonts.ready; if (!this.isConnected || version !== this.#chapterVersion) return; cancelAnimationFrame(this.#chapterFrame); this.#chapterFrame = requestAnimationFrame(() => { this.#measureOffsets(); this.#chapterFrame = requestAnimationFrame(() => { if (version === this.#chapterVersion) this.#scrollChapter(id, behavior); }); }); } #observeChapters(offset) { this.#io?.disconnect(); // 滚动高亮是增强能力:环境不支持 IntersectionObserver 时整页照常可读,只是不自动高亮 if (typeof IntersectionObserver === 'undefined') return; const root = this.renderRoot; const chapters = [...root.querySelectorAll('article.ch')]; this.#io = new IntersectionObserver((entries) => { const visible = entries.filter((e) => e.isIntersecting).sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top); if (visible[0]) this._active = visible[0].target.id; }, { rootMargin: `-${offset}px 0px -45% 0px`, threshold: 0 }); chapters.forEach((c) => this.#io.observe(c)); } static styles = [VIEW_CSS, css` .report-image{display:block;max-width:100%;height:auto;border:1px solid var(--kf-line);border-radius:6px}.file-preview{display:block;width:100%;height:600px;max-height:75vh;border:1px solid var(--kf-line);margin-top:12px;background:white}.file-entry{padding:12px 0;border-bottom:1px solid var(--kf-line);overflow-wrap:anywhere}.file-entry details summary{cursor:pointer} /* kf-glass-card 的插槽内容必须包一层块容器:自定义元素宿主是 display:block,直接排多个块级子元素会和卡片的行盒互相叠 */ .cover { --kf-card-pad: 24px; height: auto; margin-bottom: 20px; } .cover-in { display: grid; gap: 16px; min-width: 0; } .cover:has(.dgrid) .cover-in { gap: 12px; } .rail .r-in { display: grid; gap: 8px; min-width: 0; } .cover .top { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; } .cover h1 { margin: 4px 0 0; font-size: 26px; line-height: 1.45; font-weight: 600; color: var(--kf-ink); letter-spacing: -0.01em; overflow-wrap: anywhere; } .cover .sub { font-size: 14px; line-height: 1.6; color: var(--kf-ink-2); } .cover .meta { font-size: 12px; line-height: 1.5; color: var(--kf-ink-3); display: flex; gap: 16px; flex-wrap: wrap; font-variant-numeric: tabular-nums; } .cover .pipe { padding: 16px 0 0; border-top: 1px solid var(--kf-line); } .exec { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; counter-reset: e; } .exec li { display: grid; grid-template-columns: 34px 1fr; gap: 10px; font-size: 14px; color: var(--kf-ink); line-height: 1.6; } .exec li::before { counter-increment: e; content: counter(e, decimal-leading-zero); font-family: var(--kf-font-num); font-size: 12px; color: var(--kf-accent-ink); padding-top: 4px; } /* 章节目录 = 上层玻璃(.72 + blur 32),和正文卡片拉开层次 */ .toc[data-toc] { position: sticky; top: var(--report-topbar, 12px); z-index: 3; display: flex; gap: 4px; overflow-x: auto; padding: 6px; margin: 0 0 20px; border-radius: var(--kf-radius); background: var(--kf-surface); border: 1px solid var(--kf-line); box-shadow: var(--elev-2); scrollbar-width: thin; scrollbar-color: var(--kf-line-strong) transparent; } .dgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr)); gap: 10px; } a.d { display: grid; grid-template-columns: 32px minmax(0,1fr); gap: 2px 8px; padding: 12px; border-radius: var(--r-sm); text-decoration: none; background: rgba(255,255,255,0.4); border: 1px solid var(--kf-line); color: inherit; transition: var(--kf-t); } a.d:hover { background: rgba(255,255,255,0.7); box-shadow: var(--kf-shadow-chip); } a.d .dn { font: 600 12px/1.4 var(--kf-font-num); color: var(--kf-accent-ink); grid-row: span 2; padding-top: 2px; font-variant-numeric: tabular-nums; } a.d .dt { font-size: 14px; font-weight: 600; color: var(--kf-ink); } a.d .ds { font-size: 12px; color: var(--kf-ink-2); grid-column: 2; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .toc a.jump, aside.rail a.jump { flex: none; font-size: 14px; color: var(--kf-ink-2); text-decoration: none; padding: 10px 12px; border-radius: var(--r-sm); display: inline-block; line-height: 1.4; transition: background var(--kf-dur) var(--kf-ease); } .toc a.jump { white-space: nowrap; font-weight: 500; } .toc a.jump:hover, aside.rail a.jump:hover { background: var(--kf-surface-btn); color: var(--kf-accent-ink); } .toc a.jump[aria-current="true"] { background: var(--kf-accent-soft); color: var(--kf-accent-ink); } aside.rail ol li + li { margin-top: 2px; } aside.rail ol { list-style: none; padding-left: 0; margin: 6px 0 0; } .layout { display: grid; grid-template-columns: minmax(0, 1fr) 264px; gap: 16px; align-items: start; } /* 列容器用块布局:kf-glass-card 是自定义元素,在 grid/flex 里会被当成 flex item 参与拉伸 */ /* 视图宿主与列容器显式 block:否则自定义元素被排成行盒,块级子元素互相叠、互相撑高 */ :host { display: block; } @media (prefers-reduced-motion: no-preference) { :host { animation: report-appear 160ms var(--kf-ease-out); } @keyframes report-appear { from { opacity: .65; } to { opacity: 1; } } } .col, .main-col { display: block; min-width: 0; } article.ch { padding: 24px; margin-bottom: 20px; scroll-margin-top: var(--report-anchor, 84px); min-width: 0; box-sizing: border-box; overflow-wrap: anywhere; } .ch-h { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 8px 12px; align-items: baseline; padding-bottom: 14px; margin-bottom: 4px; border-bottom: 1px solid var(--kf-line); } .ch-h .en { grid-column: 1 / -1; } .ch-h .en:empty { display: none; } .ch-no { font-family: var(--kf-font-num); font-size: 12px; line-height: 20px; color: var(--kf-accent-ink); border: 1px solid rgba(91, 91, 214, 0.30); border-radius: var(--r-sm); padding: 0 6px; font-variant-numeric: tabular-nums; } .ch-h h2 { margin: 0; font-size: 20px; line-height: 1.5; font-weight: 600; color: var(--kf-ink); letter-spacing: -0.01em; } .en { font-size: 12px; letter-spacing: var(--ls-cn-small); color: var(--kf-ink-3); font-weight: 600; text-transform: uppercase; } /* 摘要块:6% 染色底 + 1px 同色半透明描边(v11 反例 #2:不用 3px 彩色左边栏) */ .lead { margin: 12px 0 16px; padding: 12px 16px; background: color-mix(in srgb, var(--kf-accent) 6%, transparent); font-size: 14px; color: var(--kf-ink-2); line-height: var(--lh-body); border-radius: var(--r-md); box-shadow: 0 0 0 1px color-mix(in srgb, var(--kf-accent) 16%, transparent); } .lead b { color: var(--kf-accent-ink); font-weight: 600; } .sec { padding: 12px 0; border-top: 1px solid var(--kf-line); } .sec:first-of-type { border-top: 0; } .sec h4 { margin: 0 0 8px; font-size: 14px; line-height: 1.5; font-weight: 600; color: var(--kf-ink); display: flex; gap: 8px; align-items: baseline; } .sec h4 .no { font-family: var(--kf-font-num); font-size: 12px; color: var(--kf-ink-3); } p.para { margin: 0 0 12px; font-size: 14px; line-height: 1.85; color: var(--kf-ink-2); max-width: 72ch; } .kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(152px, 1fr)); gap: 12px; } .tbl-wrap { overflow-x: auto; border: 1px solid var(--kf-line); border-radius: var(--r-md); } table.rt { width: 100%; border-collapse: collapse; font-size: 14px; } .rt th { background: var(--kf-surface-btn); text-align: left; padding: 10px 12px; color: var(--kf-ink-3); font-weight: 600; font-size: 12px; letter-spacing: var(--ls-cn-small); border-bottom: 1px solid var(--kf-line); white-space: nowrap; } .rt td { padding: 8px 12px; border-bottom: 1px solid var(--kf-line); vertical-align: middle; color: var(--kf-ink-2); font-variant-numeric: tabular-nums; } .rt tr:last-child td { border-bottom: 0; } .rt .k { color: var(--kf-ink); font-weight: 600; } .funnel { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; } .funnel li { display: grid; grid-template-columns: 44px minmax(0, 1fr) 120px; gap: 10px; align-items: center; font-size: 14px; } .bar { height: 8px; border-radius: var(--r-sm); background: rgba(20,36,70,0.07); overflow: hidden; } .bar i { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--kf-s1), var(--kf-s2)); } .finding { padding: 12px 16px; border-radius: var(--r-sm); border: 1px solid var(--kf-line); background: rgba(255,255,255,0.45); margin-bottom: 12px; } .finding h5 { margin: 0; font-size: 14px; line-height: 1.5; font-weight: 600; color: var(--kf-ink); } .finding p { margin: 4px 0 8px; font-size: 14px; line-height: 1.6; color: var(--kf-ink-2); } .evidence-excerpts { display:grid; gap:10px; margin:12px 0; min-width:0; } .evidence-excerpt { margin:0; padding:12px; border:1px solid var(--kf-line); border-radius:var(--r-sm); background:var(--kf-surface); min-width:0; overflow-wrap:anywhere; } .evidence-excerpt blockquote { margin:0 0 8px; white-space:pre-wrap; font-size:14px; line-height:1.85; color:var(--kf-ink); } .evidence-caption { display:flex; gap:6px 12px; flex-wrap:wrap; align-items:center; font-size:12px; line-height:1.6; color:var(--kf-ink-2); } .evidence-caption a { color:var(--kf-accent-ink); } .evidence-source { font-weight:600; } .evidence-note { margin:8px 0; font-size:12px; line-height:1.7; color:var(--kf-ink-3); } .evidence-group { padding:14px 0; border-top:1px solid var(--kf-line); } .evidence-group:first-of-type { border-top:0; } .evidence-group h3 { margin:0 0 8px; font-size:15px; line-height:1.7; } .evidence-more summary { cursor:pointer; font-size:13px; color:var(--kf-accent-ink); padding:6px 0; } @media print { .evidence-excerpt { break-inside:avoid; } } .ev { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; font-size: 12px; color: var(--kf-ink-3); } .ev b { color: var(--kf-ink); font-variant-numeric: tabular-nums; } .anchor { display: inline-flex; gap: 4px; align-items: center; height: 26px; padding: 0 10px; border-radius: var(--r-sm); border: 1px solid rgba(91, 91, 214, 0.22); background: rgba(91, 91, 214, 0.10); color: var(--kf-accent-ink); font: 600 12px/1 var(--kf-font); cursor: pointer; transition: var(--kf-t); } .anchor:hover { background: rgba(91, 91, 214, 0.10); border-color: rgba(91, 91, 214, 0.45); } .two { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); gap: 12px; } .two > div { padding: 12px; border-radius: var(--r-sm); border: 1px solid var(--kf-line); font-size: 14px; line-height: 1.6; } .two .yes { background: rgba(31,107,69,0.06); border-color: rgba(31,107,69,0.25); } .two .no { background: rgba(164,36,28,0.05); border-color: rgba(164,36,28,0.22); } .two ul { margin: 6px 0 0; padding-left: 18px; } aside.rail { position: sticky; top: var(--report-anchor, 84px); display: grid; gap: 12px; min-width: 0; } .rail .r { --kf-card-pad: 16px; height: auto; } .reading-nav { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 20px; margin: 0 0 16px; font-size: 13px; } .reading-nav .report-back { margin-right: auto; font-weight: 600; } .reading-nav a { text-decoration: none; padding: 7px 10px; border-radius: var(--r-sm); color: var(--kf-ink-2); transition: var(--kf-t-colors); } .reading-nav .report-back { background: var(--kf-surface); border: 1px solid var(--kf-line); } .reading-nav a:hover { color: var(--kf-accent-ink); background: var(--kf-accent-soft); } .section-label { font-size: 14px; font-weight: 600; color: var(--kf-ink); } .cover.digest .kf-kicker { color: var(--kf-ink-3); } .rail .r .k { font-size: 12px; color: var(--kf-ink-3); letter-spacing: var(--ls-cn-small); font-weight: 600; } .rail ol { margin: 6px 0 0; padding-left: 18px; font-size: 14px; color: var(--kf-ink-2); display: grid; gap: 4px; } .rail ol a { color: var(--kf-ink); text-decoration: none; border-bottom: 1px dashed var(--kf-line-strong); } .rail .stat { display: flex; justify-content: space-between; font-size: 14px; padding: 3px 0; color: var(--kf-ink-2); } .chain { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; } .chain div { display: grid; justify-items: center; gap: 4px; padding: 8px 4px; border-radius: var(--r-sm); background: rgba(255,255,255,0.45); border: 1px solid var(--kf-line); } .chain b { font: 600 14px/1.1 var(--kf-font-num); color: var(--kf-ink); font-variant-numeric: tabular-nums; } .chain i { font-style: normal; font-size: 12px; line-height: 1.3; color: var(--kf-accent-ink); text-align: center; } .rail .stat b { color: var(--kf-ink); font-variant-numeric: tabular-nums; } .muted2 { font-size: 12px; color: var(--kf-ink-3); } @media (max-width: 1100px) { .layout { grid-template-columns: 1fr; } aside.rail { position: static; } } /* 手机(任务书 1.3 / 2.4):阅读稿单栏;章节导航吸在视口上方、左右通到屏幕边;封面标题 26→20;来源收成两行 */ @media (max-width: 767px) { .toc[data-toc] { margin: 0 0 16px; padding: 4px; scrollbar-width: none; } .toc::-webkit-scrollbar { display: none; } .toc a.jump { font-size: 14px; padding: 10px 12px; } .cover { --kf-card-pad: 16px; margin-bottom: 16px; } article.ch { padding: 18px 16px; margin-bottom: 16px; } .ch-h h2 { font-size: 18px; } .reading-nav { gap: 4px 16px; } .reading-nav .report-back { flex: 1 1 100%; } .funnel li { grid-template-columns: 36px minmax(0, 1fr); } .funnel li > :last-child { grid-column: 2; } .cover .top { row-gap: 6px; } .cover .top > span:last-child { margin-left: 0 !important; width: 100%; justify-content: flex-start; } /* 类型名已在顶栏下方标题区,封面不再重复(任务书 0.5:同一信息在 tab / 标题 / 数据卡各出现一次) */ .cover .typeb { display: none; } .cover h1 { font-size: 20px; line-height: 1.35; font-weight: 600; } .cover .sub { font-size: 14px; line-height: 1.55; } .cover .meta { font-size: 14px; gap: 12px; } .ch-no, .ch-h h2 { line-height: 1.4; } .art p, .ch-body p, .toc ~ * p { font-size: 14px; line-height: 1.7; } } `]; #jump(id) { // 应用内路径 + 相对章节 id(ch3 而不是 ch-ch3) const key = id.replace(/^ch-/, ''); if (String(this.routeQuery?.ch || '').replace(/^ch-/, '') === key) return this.#scrollChapter(id, 'smooth'); this.#smoothJump = true; // 当前章写回 URL(?ch=3),刷新时还原到同一章;只改查询串,不新增历史 const q = new URLSearchParams(this.routeQuery || {}); q.set('ch', key); navigate(`/report/${encodeURIComponent(this.artifactId)}?${q.toString()}`, { replace: true }); } #scrollChapter(id, behavior) { const el = this.renderRoot.querySelector(`#${CSS.escape(id)}`); if (!el) return; this._active = id; el.scrollIntoView({ behavior: matchMedia('(prefers-reduced-motion: reduce)').matches ? 'instant' : behavior, block: 'start' }); } get typeInfo() { return this.#type; } #digest(d) { return html` { e.preventDefault(); this.#jump(`ch-${d.id}`); }}> ${String(d.no).padStart(2, '0')} ${d.title} ${d.summary || '这一章没有可抽取的句子,点到章内看原文。'} `; } #openEv(evId) { this.dispatchEvent(new CustomEvent('evidence-open', { detail: { reportId: this.artifactId, evId }, bubbles: true, composed: true })); } #artifact(id) { this.dispatchEvent(new CustomEvent('artifact-open', { detail: { id }, bubbles: true, composed: true })); } #anchor(ev, label) { if (!ev) return ''; const e = this.#r?.report.evidence?.[ev]; if (!e) return ''; const n = (e.quotes || []).length; return html``; } #excerpts(evidenceId, limit = Infinity) { const all = evidenceExcerpts(this.#r?.report, evidenceId, this.db?.sourceById); if (!all.length) return html`

未保存可展示的原句,请核对正文依据与来源;不根据摘要补造引文。

`; return html`
${all.slice(0, limit).map(quote => html`
${quote.text}
${quote.sourceTitle || quote.ref || '来源名称未记录'} ${quote.sourceTitle && quote.ref && quote.ref !== quote.sourceTitle ? html`${quote.ref}` : ''} ${quote.author ? html`作者:${quote.author}` : ''} ${quote.url ? html`打开原始来源 ↗` : ''} ${quote.sourceAvailable ? html`` : ''} ${quote.sourceId && !quote.sourceAvailable ? html`来源编号:${quote.sourceId}(本页未载入原件)` : ''}
`)} ${all.length > limit ? html`另有 ${all.length - limit} 条已保存引文,可在下方“判断与原始依据”或证据链查看。` : ''}
`; } render() { const db = this.db; if (!db) return html``; const r = reportOverview(db, this.artifactId); this.#r = r; if (!r) { return html` navigate(safeReturnTo(this.routeQuery?.returnTo, '/reports'))}>`; } const findings = findingsOf(db, this.artifactId); const report = r.report; const attachments = reportAttachments(r.artifact, ['https://kangshifu.fmode.cn', location.origin]); const digests = chapterDigests(db, this.artifactId); this.#type = reportTypeOf(reportCategoryOf(r.artifact)); if (!r.executive?.length) { // 执行摘要缺失时:用章节摘要替身,但明确标注来源,不冒充 r.executive = digests.slice(0, 3).map((d) => `〔${d.title}〕${d.summary || ''}`); } const active = this._active || (this.routeQuery?.ch ? `ch-${String(this.routeQuery.ch).replace(/^ch-/, '')}` : ''); const back = safeReturnTo(this.routeQuery?.returnTo, '/reports'); return html`
${r.artifact.upload.agentName ? html`` : ''} ${this.typeInfo ? html`` : ''}

${r.title}

${r.subtitle}
${fmtDate(r.generatedAt, true)}${t(`${r.quoteCount} 原文`)}${t(`${r.refsInLib}/${r.refCount} 关联成果`)}
${r.pipeline ? html`
` : ''}
${r.chapters.map((c) => html` { e.preventDefault(); this.#jump(`ch-${c.id}`); }}>${c.no}. ${c.title}`)}
${r.executive.length ? html`
${report.executive?.length ? '报告摘要' : '章节摘要'}
    ${r.executive.map((x, i) => html`
  1. ${x}
  2. `)}
` : ''}
${digests.map((d) => this.#digest(d))}
${r.chapters.map((c) => html`
${c.en}${c.no}

${c.title}

${c.subtitle ? html`
${c.subtitle}
` : ''} ${c.lead ? html`
${c.lead}
` : ''} ${c.stage ? html`
${t(`对应阶段 ${c.stage.code} ${c.stage.name}`)}
` : ''} ${c.sections.map((s) => html`
${this.#section(s, db)}
`)}
`)} ${attachments.length ? html`

报告文件与示意图

${attachments.map(file => html`

${file.name}

${Math.ceil(file.sizeBytes / 1024)} KB

${file.image ? html`${file.name}` : ''} ${file.document ? html`
预览文件
` : ''}

下载文件

`)}
` : ''}

以下展示报告已保存的判断与引文。原句用于核对依据;引文或关联成果的数量不代表结论已验证。对象、时间、样本范围及相反材料应结合正文阅读。

${findings.map(f => html`

${f.conclusion}

${this.#excerpts(f.evId)} ${this.#anchor(f.evId, '查看引用关系')}
`)} ${!findings.length ? html`

本报告尚未保存可展开的证据关联;请阅读正文中的依据说明。

` : ''}
查看引用数量与关联成果
${findings.map((f) => html``)}
结论人原文成果
${f.conclusion} ${f.ownerCount ?? '—'}${f.quoteCount}${f.derivedCount} ${this.#anchor(f.evId, '证据链')}
`; } /** section.kind → 渲染(未知 kind 原样列出,不吞) */ #section(s) { const title = html`${s.no ? html`${s.no}` : ''}${s.title || ''}`; switch (s.kind) { case 'para': return html`

${title}

${s.text}

`; case 'longpara': return html`

${title}

${String(s.text || '').split(/\n{2,}/).filter(Boolean).map((p) => html`

${p}

`)}`; case 'funnel': return html`

${title}

    ${(s.data || []).map((x) => { const max = Math.max(...(s.data || []).map((d) => Number(d.value) || 0), 1); const w = Math.max(12, Math.round((Math.log10((Number(x.value) || 1) + 1) / Math.log10(max + 1)) * 100)); return html`
  1. ${x.stage} ${x.name} ${x.scope}
    ${Number(x.value).toLocaleString('zh-CN')} ${x.unit || ''}
  2. `; })}
`; case 'findings': return html`

${title}

${(s.data || []).map((f) => { const e = this.#r.report.evidence?.[f.evidence] || {}; return html`
${f.conclusion}

${f.attitude}

${this.#excerpts(f.evidence, 2)}
${e.ownerCount != null ? html`报告记录人数 ${e.ownerCount}` : ''} ${(e.quotes || []).length} 条原文 ${(e.derivedFrom || []).filter(Boolean).length} 条关联成果 ${this.#anchor(f.evidence, '看证据链')}
`; })}`; case 'matrix': return html`

${title}

${(s.data?.cols || []).map((c) => html``)}${(s.data?.rows || []).map((row) => html`${row.map((cell, i) => html``)}`)}
${c}
${this.#cell(cell)}
`; case 'table': return html`

${title}

${(s.data?.cols || []).map((c) => html``)}${(s.data?.rows || []).map((row) => html`${row.map((cell, i) => html``)}`)}
${c}
${this.#cell(cell)}
`; case 'two': return html`

${title}

能做到的
    ${(s.data?.can || []).map((x) => html`
  • ${x}
  • `)}
做不到的(红线)
    ${(s.data?.cannot || []).map((x) => html`
  • ${x}
  • `)}
`; case 'fit': return html`

${title}

${(s.data?.transferCols || []).map((c) => html``)}${(s.data?.transfer || []).map((row) => html`${row.map((c) => html``)}`)}
${c}
${c}
${(s.data?.redline || []).length ? html`
${(s.data.redlineCols || []).map((c) => html``)}${s.data.redline.map((row) => html`${row.map((c) => html``)}`)}
${c}
${c}
` : ''}`; case 'score': return html`

${title}

${(s.data || []).map(([n, a, b, c, t, v]) => html``)}
机会需求竞争我方能力总分结论
${n}${a}${b}${c}
${t}
${v}
`; case 'ladder': { const d = s.data || {}; return html`

${title}

    ${(d.ladder || []).map(([no, name, note]) => html`
  1. ${no}${name} ${note}
  2. `)}
${d.statement ? html`

一句话定位:「${d.statement}」

` : ''} ${d.for_who ? html`
${t(`写给谁:${d.for_who}`)}
` : ''}`; } case 'statement': return html`${(s.data?.alt || []).length ? html`

${title}

` : ''}`; case 'milestones': return html`

${title}

${(s.data || []).map(([m, name, checks, rule]) => html``)}
${m}${name}
    ${(checks || []).map((c) => html`
  • ${c}
  • `)}
决策规则:${rule}
`; default: { const flat = (v) => (v && typeof v === 'object' ? JSON.stringify(v) : String(v ?? '')); return html`

${title}

未知的结构类型「${s.kind}」,原始数据:
${Object.entries(s.data && typeof s.data === 'object' ? s.data : s).map(([k, v]) => `${k}: ${flat(v)}`).join('\n')}
`; } } } /** 单元格:字符串若是 evidence 键就渲染为结论 + 锚点;数组按行展示 */ #cell(cell) { const ev = this.#r?.report?.evidence || {}; if (Array.isArray(cell) && cell.every((x) => typeof x === 'string')) return cell.map((x) => html`
${x}
`); if (Array.isArray(cell)) return cell.map(([name, pct]) => html`
${name} ${pct}%
`); if (typeof cell === 'string' && ev[cell]) return html`
${ev[cell].claim || cell}
${this.#anchor(cell, '原文')}`; if (typeof cell === 'number') return html`${cell.toLocaleString('zh-CN')}`; return cell ?? '—'; } } if (!customElements.get('view-report')) customElements.define('view-report', ViewReport);