Files
recipe_tool/app/settings/export/page.tsx
T
hanhan 030fce2813 feat: add export settings for Word document generation
- Add export configuration (untranslated text color, page orientation)
- Add second page header option for multi-page exports
- Integrate export settings into main export flow
- Separate export settings page with visual preview
2026-04-12 23:54:00 +08:00

189 lines
6.8 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.
"use client";
import { useState, useEffect } from "react";
import { useToast } from "@/components/ui/toast";
import { Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
interface ExportConfig {
untranslatedColor: string;
secondPageHeader: boolean;
orientation: "portrait" | "landscape";
}
const defaultExportConfig: ExportConfig = {
untranslatedColor: "CC0000",
secondPageHeader: false,
orientation: "landscape",
};
export default function ExportPage() {
const [config, setConfig] = useState<ExportConfig>(defaultExportConfig);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const { addToast } = useToast();
useEffect(() => {
fetch("/api/config")
.then((res) => res.json())
.then((result) => {
if (result.success && result.data) {
setConfig({
untranslatedColor: result.data.export?.untranslatedColor || defaultExportConfig.untranslatedColor,
secondPageHeader: result.data.export?.secondPageHeader ?? defaultExportConfig.secondPageHeader,
orientation: result.data.export?.orientation || defaultExportConfig.orientation,
});
}
})
.catch(console.error)
.finally(() => setLoading(false));
}, []);
const handleSave = async () => {
setSaving(true);
try {
const response = await fetch("/api/config", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ export: config }),
});
const result = await response.json();
if (result.success) {
addToast({ type: "success", title: "导出设置已保存" });
} else {
addToast({ type: "error", title: "保存失败", description: result.error });
}
} catch {
addToast({ type: "error", title: "保存失败" });
} finally {
setSaving(false);
}
};
if (loading) {
return (
<div className="space-y-6">
<div>
<h2 className="text-lg font-semibold">导出设置</h2>
<p className="text-sm text-muted-foreground">配置导出 Word 文档的格式</p>
</div>
<div className="h-40 animate-pulse rounded-lg bg-muted" />
</div>
);
}
return (
<div className="space-y-6">
<div>
<h2 className="text-lg font-semibold">导出设置</h2>
<p className="text-sm text-muted-foreground">配置导出 Word 文档的格式</p>
</div>
{/* 未翻译文本颜色 */}
<div className="space-y-4">
<h3 className="text-sm font-medium">文本颜色</h3>
<div className="space-y-2">
<label className="text-sm font-medium">未翻译文本颜色</label>
<div className="flex items-center gap-3">
<input
type="color"
value={config.untranslatedColor}
onChange={(e) => setConfig({ ...config, untranslatedColor: e.target.value.toUpperCase() })}
className="h-10 w-20 cursor-pointer rounded-md border border-input"
/>
<input
type="text"
value={config.untranslatedColor}
onChange={(e) => {
const val = e.target.value.toUpperCase();
if (/^[0-9A-Fa-f]{0,6}$/.test(val)) {
setConfig({ ...config, untranslatedColor: val });
}
}}
className="w-24 rounded-md border border-input bg-background px-3 py-2 text-sm font-mono"
placeholder="CC0000"
/>
<span className="text-sm text-muted-foreground">HEX 颜色值(如 CC0000</span>
</div>
</div>
</div>
{/* 页面设置 */}
<div className="space-y-4">
<h3 className="text-sm font-medium">页面设置</h3>
<div className="space-y-2">
<label className="text-sm font-medium">页面方向</label>
<div className="flex gap-4">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="orientation"
checked={config.orientation === "landscape"}
onChange={() => setConfig({ ...config, orientation: "landscape" })}
className="cursor-pointer"
/>
<span className="text-sm">横向</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="orientation"
checked={config.orientation === "portrait"}
onChange={() => setConfig({ ...config, orientation: "portrait" })}
className="cursor-pointer"
/>
<span className="text-sm">纵向</span>
</label>
</div>
</div>
<div className="space-y-2">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={config.secondPageHeader}
onChange={(e) => setConfig({ ...config, secondPageHeader: e.target.checked })}
className="h-4 w-4 cursor-pointer rounded border-input"
/>
<span className="text-sm">第二页显示表头</span>
</label>
<p className="text-xs text-muted-foreground">启用后,如果内容超过一页,第二页及后续页面会在顶部显示表头行</p>
</div>
</div>
{/* 预览 */}
<div className="space-y-2">
<h3 className="text-sm font-medium">预览</h3>
<div className="rounded-lg border bg-card p-4">
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<span className="text-sm">未翻译示例:</span>
<span
className="rounded px-2 py-1 text-sm"
style={{ color: config.untranslatedColor, border: `1px solid ${config.untranslatedColor}40` }}
>
宫保鸡丁
</span>
</div>
<div className="flex items-center gap-2">
<span className="text-sm">页面方向:</span>
<span className="text-sm">{config.orientation === "landscape" ? "横向 (Landscape)" : "纵向 (Portrait)"}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-sm">第二页表头:</span>
<span className="text-sm">{config.secondPageHeader ? "启用" : "禁用"}</span>
</div>
</div>
</div>
</div>
<div className="flex gap-3">
<Button onClick={handleSave} disabled={saving}>
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{saving ? "保存中..." : "保存配置"}
</Button>
</div>
</div>
);
}