Files
MorphDoc/apps/web/src/docx-export.ts
T

145 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
DOCX_MIME_TYPE,
type DocxCapability,
type DocxExportRequestInput
} from "@md-to-pdf/core";
import { parseContentDispositionFileName } from "./pdf-export";
export interface DocxExportResult {
blob: Blob;
fileName: string;
warningCount: number;
echartsErrorCount: number;
mermaidErrorCount: number;
}
export interface DesktopDocxExportResult {
saved: boolean;
fileName: string;
warningCount: number;
echartsErrorCount: number;
mermaidErrorCount: number;
}
function parseNumericHeader(value: string | null) {
if (value === null) {
return 0;
}
const parsed = Number.parseInt(value, 10);
return Number.isInteger(parsed) && parsed >= 0 ? parsed : 0;
}
async function readErrorMessage(response: Response) {
try {
const payload = (await response.json()) as {
message?: unknown;
};
if (typeof payload.message === "string") {
return payload.message;
}
} catch {
// 非 JSON 错误响应使用通用提示。
}
return "DOCX 导出失败";
}
export async function getDocxCapability(
fetcher: typeof fetch = fetch
): Promise<DocxCapability> {
const bridge = window.mdToPdfDesktop;
if (bridge) {
return bridge.getDocxCapability();
}
const response = await fetcher("/api/docx/capability");
if (!response.ok) {
throw new Error("无法检测 DOCX 导出能力");
}
return (await response.json()) as DocxCapability;
}
export async function requestDocxExport(
request: DocxExportRequestInput,
fetcher: typeof fetch = fetch
): Promise<DocxExportResult> {
const response = await fetcher("/api/docx", {
method: "POST",
headers: {
"content-type": "application/json"
},
body: JSON.stringify(request)
});
if (!response.ok) {
throw new Error(await readErrorMessage(response));
}
const contentType =
response.headers.get("content-type")?.split(";")[0]?.trim();
if (contentType !== DOCX_MIME_TYPE) {
throw new Error("DOCX 服务返回了无效文件类型");
}
return {
blob: await response.blob(),
fileName: parseContentDispositionFileName(
response.headers.get("content-disposition"),
"document.docx"
),
warningCount: parseNumericHeader(
response.headers.get("x-docx-warning-count")
),
echartsErrorCount: parseNumericHeader(
response.headers.get("x-echarts-error-count")
),
mermaidErrorCount: parseNumericHeader(
response.headers.get("x-mermaid-error-count")
)
};
}
export async function requestDesktopDocxExport(
request: DocxExportRequestInput
): Promise<DesktopDocxExportResult> {
const bridge = window.mdToPdfDesktop;
if (!bridge) {
throw new Error("桌面 DOCX 引擎不可用");
}
const outcome = await bridge.exportDocx(request);
if (!outcome.ok) {
throw new Error(outcome.message);
}
return {
saved: outcome.saved,
fileName: outcome.fileName,
warningCount: outcome.diagnostics.warnings.length,
echartsErrorCount: outcome.diagnostics.echartsErrors.length,
mermaidErrorCount: outcome.diagnostics.mermaidErrors.length
};
}
export function downloadDocx(result: DocxExportResult) {
const url = URL.createObjectURL(result.blob);
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = result.fileName;
anchor.click();
window.setTimeout(() => URL.revokeObjectURL(url), 0);
}
export function createDocxDiagnosticsMessage(result: {
warningCount: number;
echartsErrorCount: number;
mermaidErrorCount: number;
}) {
const messages = [];
if (result.warningCount > 0) {
messages.push(`${result.warningCount} 条提示`);
}
if (result.echartsErrorCount > 0) {
messages.push(`${result.echartsErrorCount} 个 ECharts 图表失败`);
}
if (result.mermaidErrorCount > 0) {
messages.push(`${result.mermaidErrorCount} 个 Mermaid 图表失败`);
}
return messages.length > 0
? messages.join("")
: "纸张、样式和可编辑结构已写入文档";
}