feat(report): ship full-mode longform appendix beside the five-chapter report
Web export now calls the same full pack as the long skill report and caches an owner-only Markdown download. Appendix failure stays unavailable and does not change the main report status. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { GeneratePersonalReportButton } from "./generate-personal-report-button";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useVisibilityAwarePoll } from "@/hooks/use-visibility-aware-poll";
|
||||
import { downloadMarkdownReport } from "@/lib/consultation-report-export";
|
||||
import { downloadPersonalReportLongformAppendix } from "@/lib/personal-report-longform-download";
|
||||
|
||||
const LIST_POLL_INTERVAL_MS = 3000;
|
||||
|
||||
@@ -143,21 +143,11 @@ export function PersonalReportCenter() {
|
||||
setExportingReportId(reportId);
|
||||
setExportError(null);
|
||||
try {
|
||||
const response = await fetch(`/api/reports/${encodeURIComponent(reportId)}/professional-reference`, {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: { Accept: "application/json" },
|
||||
});
|
||||
const result: unknown = await response.json().catch(() => null);
|
||||
const payload = result && typeof result === "object" ? result as Record<string, unknown> : {};
|
||||
if (!response.ok || payload.format !== "markdown" || typeof payload.markdown !== "string") {
|
||||
throw new Error(typeof payload.error === "string" ? payload.error : "专业参考版暂时无法生成");
|
||||
}
|
||||
downloadMarkdownReport("个人专业参考版", payload.markdown);
|
||||
await downloadPersonalReportLongformAppendix(reportId);
|
||||
} catch (error) {
|
||||
setExportError({
|
||||
reportId,
|
||||
message: error instanceof Error ? error.message : "专业参考版暂时无法生成",
|
||||
message: error instanceof Error ? error.message : "全量数据附录暂不可用",
|
||||
});
|
||||
} finally {
|
||||
setExportingReportId(null);
|
||||
@@ -240,7 +230,7 @@ export function PersonalReportCenter() {
|
||||
onClick={() => void downloadProfessionalReference(report.id)}
|
||||
>
|
||||
{exportingReportId === report.id ? <InlineSpinner size={14} /> : null}
|
||||
专业参考版(导出)
|
||||
全量数据附录
|
||||
</Button>
|
||||
{exportError?.reportId === report.id ? (
|
||||
<span className="report-center-export-error" role="alert">
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useState, useSyncExternalStore } from "react";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
import { ArrowLeft, Printer, TriangleAlert } from "lucide-react";
|
||||
import { ArrowLeft, FileText, Printer, TriangleAlert } from "lucide-react";
|
||||
|
||||
import {
|
||||
detectPrintRestriction,
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
printPersonalReport,
|
||||
safeReportFilename,
|
||||
} from "@/lib/client-report-export";
|
||||
import { downloadPersonalReportLongformAppendix } from "@/lib/personal-report-longform-download";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface ReportActionsProps {
|
||||
@@ -27,23 +28,37 @@ function subscribePrintCapability(onStoreChange: () => void): () => void {
|
||||
}
|
||||
|
||||
export function ReportActions({ reportId, reportTitle }: ReportActionsProps) {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [printBusy, setPrintBusy] = useState(false);
|
||||
const [appendixBusy, setAppendixBusy] = useState(false);
|
||||
const [notice, setNotice] = useState<string | null>(null);
|
||||
const printSupported = useSyncExternalStore(subscribePrintCapability, isPrintSupported, () => false);
|
||||
|
||||
async function handlePrint() {
|
||||
if (busy || !printSupported) return;
|
||||
if (printBusy || appendixBusy || !printSupported) return;
|
||||
const restriction = detectPrintRestriction();
|
||||
if (restriction.restricted) {
|
||||
setNotice(restriction.message ?? "当前浏览器无法可靠打印,请在系统浏览器中打开本页。");
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
setPrintBusy(true);
|
||||
setNotice(null);
|
||||
try {
|
||||
await printPersonalReport({ title: reportTitle?.trim() || safeReportFilename(reportId) });
|
||||
} finally {
|
||||
setBusy(false);
|
||||
setPrintBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAppendix() {
|
||||
if (printBusy || appendixBusy) return;
|
||||
setAppendixBusy(true);
|
||||
setNotice(null);
|
||||
try {
|
||||
await downloadPersonalReportLongformAppendix(reportId);
|
||||
} catch (error) {
|
||||
setNotice(error instanceof Error ? error.message : "全量数据附录暂不可用");
|
||||
} finally {
|
||||
setAppendixBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,11 +70,14 @@ export function ReportActions({ reportId, reportTitle }: ReportActionsProps) {
|
||||
</Link>
|
||||
<div className="personal-report-action-end">
|
||||
{notice && <span role="status"><TriangleAlert aria-hidden="true" />{notice}</span>}
|
||||
<Button type="button" size="sm" onClick={() => void handlePrint()} disabled={busy || !printSupported}>
|
||||
<Printer aria-hidden="true" />{busy ? "正在准备…" : "打印 / 保存为 PDF"}
|
||||
<Button type="button" size="sm" variant="ghost" onClick={() => void handleAppendix()} disabled={printBusy || appendixBusy}>
|
||||
<FileText aria-hidden="true" />{appendixBusy ? "正在准备附录…" : "全量数据附录"}
|
||||
</Button>
|
||||
<Button type="button" size="sm" onClick={() => void handlePrint()} disabled={printBusy || appendixBusy || !printSupported}>
|
||||
<Printer aria-hidden="true" />{printBusy ? "正在准备…" : "打印 / 保存为 PDF"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user