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
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { getConfig, saveDBConfig, saveAIConfig, type DBConfig, type AIConfig } from "@/lib/config";
|
import { getConfig, saveDBConfig, saveAIConfig, saveExportConfig, type DBConfig, type AIConfig, type ExportConfig } from "@/lib/config";
|
||||||
|
|
||||||
// GET /api/config - 获取完整配置
|
// GET /api/config - 获取完整配置
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
@@ -18,11 +18,11 @@ export async function GET() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// POST /api/config - 保存配置
|
// POST /api/config - 保存配置
|
||||||
// Body 格式: { db?: DBConfig, ai?: AIConfig }
|
// Body 格式: { db?: DBConfig, ai?: AIConfig, export?: ExportConfig }
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
try {
|
try {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { db, ai } = body;
|
const { db, ai, export: exportConfig } = body;
|
||||||
|
|
||||||
if (db) {
|
if (db) {
|
||||||
saveDBConfig(db);
|
saveDBConfig(db);
|
||||||
@@ -32,8 +32,12 @@ export async function POST(request: Request) {
|
|||||||
saveAIConfig(ai);
|
saveAIConfig(ai);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (exportConfig) {
|
||||||
|
saveExportConfig(exportConfig);
|
||||||
|
}
|
||||||
|
|
||||||
// 如果同时发送了完整配置
|
// 如果同时发送了完整配置
|
||||||
if (!db && !ai && body.db === undefined && body.ai === undefined) {
|
if (!db && !ai && !exportConfig && body.db === undefined && body.ai === undefined && body.export === undefined) {
|
||||||
// 可能是旧的扁平格式,尝试兼容
|
// 可能是旧的扁平格式,尝试兼容
|
||||||
if (body.apiKey !== undefined || body.baseUrl !== undefined || body.model !== undefined) {
|
if (body.apiKey !== undefined || body.baseUrl !== undefined || body.model !== undefined) {
|
||||||
saveAIConfig({
|
saveAIConfig({
|
||||||
|
|||||||
+40
-8
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {useState, useCallback} from "react";
|
import {useState, useCallback, useEffect} from "react";
|
||||||
import {
|
import {
|
||||||
Upload,
|
Upload,
|
||||||
Sun,
|
Sun,
|
||||||
@@ -116,6 +116,31 @@ export default function RecipeTranslationPage() {
|
|||||||
const [data, setData] = useState<WeeklyMenu | null>(null);
|
const [data, setData] = useState<WeeklyMenu | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [fileName, setFileName] = useState<string>("");
|
const [fileName, setFileName] = useState<string>("");
|
||||||
|
const [exportConfig, setExportConfig] = useState<{
|
||||||
|
untranslatedColor: string;
|
||||||
|
secondPageHeader: boolean;
|
||||||
|
orientation: "portrait" | "landscape";
|
||||||
|
}>({
|
||||||
|
untranslatedColor: "CC0000",
|
||||||
|
secondPageHeader: false,
|
||||||
|
orientation: "landscape",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载导出配置
|
||||||
|
useEffect(() => {
|
||||||
|
fetch("/api/config")
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((result) => {
|
||||||
|
if (result.success && result.data?.export) {
|
||||||
|
setExportConfig({
|
||||||
|
untranslatedColor: result.data.export.untranslatedColor || "CC0000",
|
||||||
|
secondPageHeader: result.data.export.secondPageHeader || false,
|
||||||
|
orientation: result.data.export.orientation || "landscape",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 收集所有未翻译的菜品名称
|
// 收集所有未翻译的菜品名称
|
||||||
const getAllDishNames = useCallback((menu: WeeklyMenu): string[] => {
|
const getAllDishNames = useCallback((menu: WeeklyMenu): string[] => {
|
||||||
@@ -195,7 +220,10 @@ export default function RecipeTranslationPage() {
|
|||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. 收集所有需要翻译的内容(星期、餐食类型、菜品)
|
// 1. 获取导出设置
|
||||||
|
const { untranslatedColor, secondPageHeader, orientation } = exportConfig;
|
||||||
|
|
||||||
|
// 2. 收集所有需要翻译的内容(星期、餐食类型、菜品)
|
||||||
const allNames: string[] = [];
|
const allNames: string[] = [];
|
||||||
|
|
||||||
// 收集星期名称(使用实际数据中的星期)
|
// 收集星期名称(使用实际数据中的星期)
|
||||||
@@ -321,7 +349,7 @@ export default function RecipeTranslationPage() {
|
|||||||
} else {
|
} else {
|
||||||
cellContent.push(
|
cellContent.push(
|
||||||
new Paragraph({
|
new Paragraph({
|
||||||
children: [new TextRun({text: zhText, color: "CC0000"})],
|
children: [new TextRun({text: zhText, color: untranslatedColor})],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -340,16 +368,20 @@ export default function RecipeTranslationPage() {
|
|||||||
tableRows.push(new TableRow({children: dayCells}));
|
tableRows.push(new TableRow({children: dayCells}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 创建文档 - 横向 A4
|
// 5. 创建文档 - 根据设置选择页面方向
|
||||||
|
const isLandscape = orientation === "landscape";
|
||||||
|
const pageWidth = isLandscape ? 11906 : 16838;
|
||||||
|
const pageHeight = isLandscape ? 16838 : 11906;
|
||||||
|
|
||||||
const doc = new Document({
|
const doc = new Document({
|
||||||
sections: [
|
sections: [
|
||||||
{
|
{
|
||||||
properties: {
|
properties: {
|
||||||
page: {
|
page: {
|
||||||
size: {
|
size: {
|
||||||
orientation: PageOrientation.LANDSCAPE,
|
orientation: isLandscape ? PageOrientation.LANDSCAPE : PageOrientation.PORTRAIT,
|
||||||
width: 11906,
|
width: pageWidth,
|
||||||
height: 16838,
|
height: pageHeight,
|
||||||
},
|
},
|
||||||
margin: {
|
margin: {
|
||||||
top: 700, // 约1.27厘米
|
top: 700, // 约1.27厘米
|
||||||
@@ -395,7 +427,7 @@ export default function RecipeTranslationPage() {
|
|||||||
console.error("Export error:", err);
|
console.error("Export error:", err);
|
||||||
alert("导出失败,请重试");
|
alert("导出失败,请重试");
|
||||||
}
|
}
|
||||||
}, [data, fileName]);
|
}, [data, fileName, exportConfig]);
|
||||||
|
|
||||||
const handleFileUpload = useCallback(
|
const handleFileUpload = useCallback(
|
||||||
async (file: File) => {
|
async (file: File) => {
|
||||||
|
|||||||
+182
-12
@@ -1,18 +1,188 @@
|
|||||||
import {FileText} from "lucide-react";
|
"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() {
|
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 (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold">导出设置</h2>
|
<h2 className="text-lg font-semibold">导出设置</h2>
|
||||||
<p className="text-sm text-muted-foreground">配置导出 Word 文档的格式</p>
|
<p className="text-sm text-muted-foreground">配置导出 Word 文档的格式</p>
|
||||||
</div>
|
|
||||||
<div className="rounded-lg border bg-card p-8 text-center">
|
|
||||||
<FileText className="mx-auto h-12 w-12 text-muted-foreground"/>
|
|
||||||
<p className="mt-4 text-sm text-muted-foreground">
|
|
||||||
导出设置功能开发中...
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,5 +10,10 @@
|
|||||||
"apiKey": "6a81b361620a44fd86aa3c8fcd833800.PjPyiU8kSzCTviYa",
|
"apiKey": "6a81b361620a44fd86aa3c8fcd833800.PjPyiU8kSzCTviYa",
|
||||||
"baseUrl": "https://open.bigmodel.cn/api/paas/v4/",
|
"baseUrl": "https://open.bigmodel.cn/api/paas/v4/",
|
||||||
"model": "GLM-4.7-Flash"
|
"model": "GLM-4.7-Flash"
|
||||||
|
},
|
||||||
|
"export": {
|
||||||
|
"untranslatedColor": "CC0000",
|
||||||
|
"secondPageHeader": false,
|
||||||
|
"orientation": "landscape"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,9 +15,19 @@ export interface AIConfig {
|
|||||||
model: string;
|
model: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExportConfig {
|
||||||
|
// 未翻译文本的颜色 (hex)
|
||||||
|
untranslatedColor: string;
|
||||||
|
// 第二页是否显示表头
|
||||||
|
secondPageHeader: boolean;
|
||||||
|
// 页面方向: portrait | landscape
|
||||||
|
orientation: "portrait" | "landscape";
|
||||||
|
}
|
||||||
|
|
||||||
export interface AppConfig {
|
export interface AppConfig {
|
||||||
db: DBConfig;
|
db: DBConfig;
|
||||||
ai: AIConfig;
|
ai: AIConfig;
|
||||||
|
export: ExportConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 配置文件路径(单一文件)
|
// 配置文件路径(单一文件)
|
||||||
@@ -101,6 +111,29 @@ export function getAIConfig(): AIConfig {
|
|||||||
return { apiKey, baseUrl, model };
|
return { apiKey, baseUrl, model };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取导出配置
|
||||||
|
* 优先级:config.json(没有环境变量)
|
||||||
|
*/
|
||||||
|
export function getExportConfig(): ExportConfig {
|
||||||
|
const defaultConfig: ExportConfig = {
|
||||||
|
untranslatedColor: "CC0000",
|
||||||
|
secondPageHeader: false,
|
||||||
|
orientation: "landscape",
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileConfig = readConfigFile();
|
||||||
|
if (fileConfig.export) {
|
||||||
|
return {
|
||||||
|
untranslatedColor: fileConfig.export.untranslatedColor || defaultConfig.untranslatedColor,
|
||||||
|
secondPageHeader: fileConfig.export.secondPageHeader ?? defaultConfig.secondPageHeader,
|
||||||
|
orientation: fileConfig.export.orientation || defaultConfig.orientation,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultConfig;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取完整配置(兼容旧接口)
|
* 获取完整配置(兼容旧接口)
|
||||||
*/
|
*/
|
||||||
@@ -108,6 +141,7 @@ export function getConfig(): AppConfig {
|
|||||||
return {
|
return {
|
||||||
db: getDBConfig(),
|
db: getDBConfig(),
|
||||||
ai: getAIConfig(),
|
ai: getAIConfig(),
|
||||||
|
export: getExportConfig(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +163,7 @@ export function saveDBConfig(config: Partial<DBConfig>): DBConfig {
|
|||||||
const fileConfig = readConfigFile();
|
const fileConfig = readConfigFile();
|
||||||
const updatedConfig: AppConfig = {
|
const updatedConfig: AppConfig = {
|
||||||
ai: fileConfig.ai ?? { apiKey: "", baseUrl: "https://api.openai.com/v1", model: "gpt-3.5-turbo" },
|
ai: fileConfig.ai ?? { apiKey: "", baseUrl: "https://api.openai.com/v1", model: "gpt-3.5-turbo" },
|
||||||
|
export: fileConfig.export ?? { untranslatedColor: "CC0000", secondPageHeader: false, orientation: "landscape" },
|
||||||
db: newConfig,
|
db: newConfig,
|
||||||
};
|
};
|
||||||
writeConfigFile(updatedConfig);
|
writeConfigFile(updatedConfig);
|
||||||
@@ -152,6 +187,7 @@ export function saveAIConfig(config: Partial<AIConfig>): AIConfig {
|
|||||||
const fileConfig = readConfigFile();
|
const fileConfig = readConfigFile();
|
||||||
const updatedConfig: AppConfig = {
|
const updatedConfig: AppConfig = {
|
||||||
db: fileConfig.db ?? { host: "127.0.0.1", port: 3306, user: "root", password: "", database: "recipe_tools" },
|
db: fileConfig.db ?? { host: "127.0.0.1", port: 3306, user: "root", password: "", database: "recipe_tools" },
|
||||||
|
export: fileConfig.export ?? { untranslatedColor: "CC0000", secondPageHeader: false, orientation: "landscape" },
|
||||||
ai: newConfig,
|
ai: newConfig,
|
||||||
};
|
};
|
||||||
writeConfigFile(updatedConfig);
|
writeConfigFile(updatedConfig);
|
||||||
@@ -159,6 +195,30 @@ export function saveAIConfig(config: Partial<AIConfig>): AIConfig {
|
|||||||
return newConfig;
|
return newConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存导出配置
|
||||||
|
*/
|
||||||
|
export function saveExportConfig(config: Partial<ExportConfig>): ExportConfig {
|
||||||
|
const current = getExportConfig();
|
||||||
|
|
||||||
|
const newConfig: ExportConfig = {
|
||||||
|
untranslatedColor: config.untranslatedColor ?? current.untranslatedColor,
|
||||||
|
secondPageHeader: config.secondPageHeader ?? current.secondPageHeader,
|
||||||
|
orientation: config.orientation ?? current.orientation,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 读取现有配置,更新 export 部分
|
||||||
|
const fileConfig = readConfigFile();
|
||||||
|
const updatedConfig: AppConfig = {
|
||||||
|
db: fileConfig.db ?? { host: "127.0.0.1", port: 3306, user: "root", password: "", database: "recipe_tools" },
|
||||||
|
ai: fileConfig.ai ?? { apiKey: "", baseUrl: "https://api.openai.com/v1", model: "gpt-3.5-turbo" },
|
||||||
|
export: newConfig,
|
||||||
|
};
|
||||||
|
writeConfigFile(updatedConfig);
|
||||||
|
|
||||||
|
return newConfig;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存完整配置(兼容旧接口)
|
* 保存完整配置(兼容旧接口)
|
||||||
*/
|
*/
|
||||||
@@ -169,5 +229,6 @@ export function saveConfig(config: Partial<AppConfig>): AppConfig {
|
|||||||
return {
|
return {
|
||||||
db: config.db ? saveDBConfig(config.db) : current.db,
|
db: config.db ? saveDBConfig(config.db) : current.db,
|
||||||
ai: config.ai ? saveAIConfig(config.ai) : current.ai,
|
ai: config.ai ? saveAIConfig(config.ai) : current.ai,
|
||||||
|
export: config.export ? saveExportConfig(config.export) : current.export,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user