Files
2026-06-19 18:45:55 +08:00

986 lines
36 KiB
TypeScript
Raw Permalink 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.
/**
* 字幕模板管理 - IPC Handlers
* 提供字幕模板的CRUD操作
* 用于替代 localStorage,将字幕模板持久化到数据库
*/
import { ipcMain, dialog } from "electron";
import { Log } from "../log/main";
import DB from "../db/main";
import * as path from "path";
import * as fs from "fs";
import { AppEnv } from "../env";
import { getCommonResourcePath } from "../../lib/resource-path";
// UUID生成函数
const generateUUID = (): string => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
const cloneJson = <T>(value: T): T => {
if (value === null || value === undefined) {
return value;
}
return JSON.parse(JSON.stringify(value));
};
const isSystemTemplateRecord = (template: any): boolean => (
template?.isSystem === true || template?.is_system === 1
);
const parseTemplateConfig = (config: any): any => {
if (!config) {
return {};
}
if (typeof config === "string") {
return JSON.parse(config);
}
return cloneJson(config);
};
const mergeTemplateConfig = (baseConfig: any, overrideConfig: any): any => {
const base = baseConfig || {};
const override = overrideConfig || {};
return {
...base,
...override,
subtitleStyle: override.subtitleStyle ?? base.subtitleStyle,
titleSubtitleConfig: override.titleSubtitleConfig ?? base.titleSubtitleConfig,
keywordGroupsStyles: override.keywordGroupsStyles ?? base.keywordGroupsStyles,
previewConfig: {
...(base.previewConfig || {}),
...(override.previewConfig || {}),
},
};
};
const dedupeTemplatesById = (templates: any[]): any[] => {
const deduped = new Map<string, any>();
for (const template of templates) {
if (!template?.id) {
continue;
}
deduped.set(template.id, template);
}
return Array.from(deduped.values());
};
const loadExistingExportConfig = (configPath: string): any => {
const possiblePaths = Array.from(new Set([
configPath,
path.join(AppEnv.appRoot || process.cwd(), "electron", "config", "system-templates.json"),
path.join(process.cwd(), "electron", "config", "system-templates.json"),
]));
for (const candidatePath of possiblePaths) {
if (!candidatePath || !fs.existsSync(candidatePath)) {
continue;
}
try {
const content = fs.readFileSync(candidatePath, "utf-8");
const parsed = JSON.parse(content);
return {
version: parsed.version || "2.0.0",
description: parsed.description || "系统默认模板配置 - 应用启动时自动初始化",
subtitleTemplates: Array.isArray(parsed.subtitleTemplates) ? parsed.subtitleTemplates : [],
subtitleStyles: Array.isArray(parsed.subtitleStyles) ? parsed.subtitleStyles : [],
coverTemplates: Array.isArray(parsed.coverTemplates) ? parsed.coverTemplates : [],
};
} catch (error) {
Log.warn("ipAgent.exportSubtitleTemplates.loadExistingConfigFailed", {
configPath: candidatePath,
error,
});
}
}
return {
version: "2.0.0",
description: "系统默认模板配置 - 应用启动时自动初始化",
subtitleTemplates: [],
subtitleStyles: [],
coverTemplates: [],
};
};
const buildBundledSystemTemplateMap = (config: any): Map<string, any> => {
const map = new Map<string, any>();
for (const template of config?.subtitleTemplates || []) {
if (!template?.id || !isSystemTemplateRecord(template)) {
continue;
}
map.set(template.id, template);
}
return map;
};
const normalizeSubtitleTemplateRecord = (template: any, bundledSystemTemplateMap: Map<string, any>): any => {
const dbConfig = parseTemplateConfig(template.config);
const bundledTemplate = template.is_system === 1 ? bundledSystemTemplateMap.get(template.id) : null;
const bundledConfig = bundledTemplate?.config ? parseTemplateConfig(bundledTemplate.config) : {};
const mergedConfig = template.is_system === 1
? mergeTemplateConfig(bundledConfig, dbConfig)
: dbConfig;
return {
id: template.id,
name: template.name,
description: template.description,
createdAt: template.created_at,
isSystem: template.is_system === 1,
config: mergedConfig,
};
};
// 注册字幕模板相关的IPC handlers
export const registerSubtitleTemplateHandlers = () => {
// 保存字幕模板
ipcMain.handle("ipAgent:saveSubtitleTemplate", async (event, params: {
name: string;
description?: string;
config: any;
id?: string;
isDev?: boolean;
}) => {
try {
const { name, description, config, id, isDev } = params;
Log.info("ipAgent.saveSubtitleTemplate - START", {
name,
id,
haConfig: !!config,
isDev
});
console.log('[SubtitleTemplate saveSubtitleTemplate] 接收到参数:', {
name,
id,
description,
isDev
});
// 验证参数
if (!name || !config) {
console.error('[SubtitleTemplate saveSubtitleTemplate] ❌ 参数验证失败:', {
nameEmpty: !name,
configEmpty: !config
});
return {
success: false,
message: "模板名称和配置不能为空"
};
}
const now = Date.now();
const configJson = JSON.stringify(config);
// 如果提供了id,先检查该id是否存在
if (id) {
const existing = await DB.first(
`SELECT id, name, is_system FROM subtitle_templates WHERE id = ?`,
[id]
);
if (existing) {
// ✅ 修复:仅在非开发模式下保护系统模板,开发模式下允许修改
if (existing.is_system === 1 && !isDev) {
Log.warn("ipAgent.saveSubtitleTemplate.systemTemplate", { id, name, isDev });
return {
success: false,
message: "系统默认模板不能修改"
};
}
// 如果修改了名称,检查新名称是否与其他模板冲突
if (existing.name !== name) {
const nameConflict = await DB.first(
`SELECT id FROM subtitle_templates WHERE name = ? AND id != ?`,
[name, id]
);
if (nameConflict) {
return {
success: false,
message: "模板名称已存在,请使用其他名称"
};
}
}
// 更新现有模板
await DB.execute(
`UPDATE subtitle_templates
SET name = ?, description = ?, config = ?, updated_at = ?
WHERE id = ?`,
[name, description || null, configJson, now, id]
);
Log.info("ipAgent.saveSubtitleTemplate.updated", { name, id });
return {
success: true,
id,
message: "模板更新成功"
};
}
}
// 🔧 防止误用已存在的ID进行创建操作
if (id && typeof id === 'string' && id.trim()) {
Log.warn("ipAgent.saveSubtitleTemplate.idProvisionedForCreation", { id, name });
return {
success: false,
message: "创建新模板不应该指定ID,请不指定ID让系统自动生成,或使用更新接口修改现有模板"
};
}
// 检查是否已存在同名模板
const existingByName = await DB.first(
`SELECT id FROM subtitle_templates WHERE name = ?`,
[name]
);
if (existingByName) {
return {
success: false,
message: "模板名称已存在,请使用其他名称"
};
}
// 创建新模板 - 生成新UUID
const newId = generateUUID();
await DB.execute(
`INSERT INTO subtitle_templates (id, name, description, config, is_system, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[newId, name, description || null, configJson, 0, now, now]
);
Log.info("ipAgent.saveSubtitleTemplate.created", { name, id: newId });
return {
success: true,
id: newId,
message: "模板保存成功"
};
} catch (error) {
Log.error("ipAgent.saveSubtitleTemplate.error", error);
return {
success: false,
message: "保存模板失败: " + (error?.message || error)
};
}
});
// 获取所有字幕模板
ipcMain.handle("ipAgent:getSubtitleTemplates", async (event, params?: { metadataOnly?: boolean }) => {
try {
const metadataOnly = params?.metadataOnly === true;
Log.info("ipAgent.getSubtitleTemplates - START", { metadataOnly });
console.log(`[SubtitleTemplate] 开始获取字幕模板列表 (metadataOnly=${metadataOnly})`);
const selectFields = metadataOnly
? `id, name, description, created_at, updated_at, is_system`
: `id, name, description, config, created_at, updated_at, is_system`;
const templates = await DB.select(
`SELECT ${selectFields}
FROM subtitle_templates
ORDER BY is_system DESC, created_at DESC`
);
console.log(`[SubtitleTemplate] 查询到 ${templates.length} 个模板`);
Log.info("ipAgent.getSubtitleTemplates.queryResult", { count: templates.length, metadataOnly });
// 仅元数据模式,直接返回
if (metadataOnly) {
const metadataTemplates = templates.map(t => ({
id: t.id,
name: t.name,
description: t.description,
createdAt: t.created_at,
updatedAt: t.updated_at,
is_system: t.is_system || 0
}));
console.log(`[SubtitleTemplate] 返回 ${metadataTemplates.length} 个模板(仅元数据)`);
return {
success: true,
templates: metadataTemplates
};
}
// 解析config JSON并返回完整模板
const parsedTemplates = templates.map(t => {
try {
const config = JSON.parse(t.config);
const template = {
id: t.id,
name: t.name,
description: t.description,
createdAt: t.created_at,
updatedAt: t.updated_at,
is_system: t.is_system || 0,
config: config
};
console.log(`[SubtitleTemplate] 解析模板: ${t.name}, is_system: ${t.is_system}`);
return template;
} catch (error) {
Log.error("ipAgent.getSubtitleTemplates.parseError", {
templateId: t.id,
error: error
});
console.error(`[SubtitleTemplate] 解析模板失败: ${t.id}`, error);
// 返回基本信息,避免整个列表加载失败
return {
id: t.id,
name: t.name,
description: t.description,
createdAt: t.created_at,
updatedAt: t.updated_at,
is_system: t.is_system || 0,
config: {}
};
}
});
Log.info("ipAgent.getSubtitleTemplates.success", {
count: parsedTemplates.length,
systemCount: parsedTemplates.filter(t => t.is_system === 1).length
});
console.log(`[SubtitleTemplate] 成功返回 ${parsedTemplates.length} 个模板`);
return {
success: true,
templates: parsedTemplates
};
} catch (error) {
console.error("[SubtitleTemplate] 获取模板列表失败:", error);
Log.error("ipAgent.getSubtitleTemplates.error", error);
return {
success: false,
message: "获取模板列表失败: " + (error?.message || error),
templates: []
};
}
});
// 获取单个字幕模板
ipcMain.handle("ipAgent:getSubtitleTemplate", async (event, params: { id: string }) => {
try {
const { id } = params;
Log.info("ipAgent.getSubtitleTemplate", { id });
if (!id) {
return {
success: false,
message: "模板ID不能为空"
};
}
const template = await DB.first(
`SELECT id, name, description, config, created_at, updated_at, is_system
FROM subtitle_templates
WHERE id = ?`,
[id]
);
if (!template) {
return {
success: false,
message: "模板不存在"
};
}
try {
const config = JSON.parse(template.config);
const parsedTemplate = {
id: template.id,
name: template.name,
description: template.description,
createdAt: template.created_at,
updatedAt: template.updated_at,
is_system: template.is_system || 0,
config: config
};
Log.info("ipAgent.getSubtitleTemplate.success", { id });
return {
success: true,
template: parsedTemplate
};
} catch (error) {
Log.error("ipAgent.getSubtitleTemplate.parseError", {
id,
error: error
});
return {
success: false,
message: "模板数据解析失败"
};
}
} catch (error) {
Log.error("ipAgent.getSubtitleTemplate.error", error);
return {
success: false,
message: "获取模板失败: " + (error?.message || error)
};
}
});
// 删除字幕模板
ipcMain.handle("ipAgent:deleteSubtitleTemplate", async (event, params: {
id: string;
isDev?: boolean;
}) => {
try {
const { id, isDev } = params;
Log.info("ipAgent.deleteSubtitleTemplate", { id });
if (!id) {
return {
success: false,
message: "模板ID不能为空"
};
}
// 检查是否为系统模板
const template = await DB.first(
`SELECT is_system FROM subtitle_templates WHERE id = ?`,
[id]
);
// ✅ 修复:仅在非开发模式下保护系统模板,开发模式下允许删除
if (template && template.is_system === 1 && !isDev) {
Log.warn("ipAgent.deleteSubtitleTemplate.systemTemplate", { id, isDev });
return {
success: false,
message: "系统默认模板不能删除"
};
}
await DB.execute(
`DELETE FROM subtitle_templates WHERE id = ?`,
[id]
);
Log.info("ipAgent.deleteSubtitleTemplate.success", { id });
return {
success: true,
message: "模板删除成功"
};
} catch (error) {
Log.error("ipAgent.deleteSubtitleTemplate.error", error);
return {
success: false,
message: "删除模板失败: " + (error?.message || error)
};
}
});
// 更新字幕模板
ipcMain.handle("ipAgent:updateSubtitleTemplate", async (event, params: {
id: string;
name?: string;
description?: string;
config?: any;
is_system?: number;
isDev?: boolean;
}) => {
try {
const { id, name, description, config, is_system, isDev } = params;
Log.info("ipAgent.updateSubtitleTemplate", { id, hasName: !!name, hasConfig: !!config });
if (!id) {
return {
success: false,
message: "模板ID不能为空"
};
}
// ✅ 修复:仅在非开发模式下保护系统模板,开发模式下允许修改
let template;
try {
template = await DB.first(
`SELECT is_system FROM subtitle_templates WHERE id = ?`,
[id]
);
} catch (columnError) {
console.warn("[SubtitleTemplate] is_system 列不存在,跳过系统模板检查");
template = null;
}
if (template && template.is_system === 1 && !isDev) {
Log.warn("ipAgent.updateSubtitleTemplate.systemTemplate", { id, isDev });
return {
success: false,
message: "系统默认模板不能修改"
};
}
const updates: string[] = [];
const values: any[] = [];
if (name !== undefined) {
updates.push('name = ?');
values.push(name);
}
if (description !== undefined) {
updates.push('description = ?');
values.push(description);
}
if (config !== undefined) {
updates.push('config = ?');
values.push(JSON.stringify(config));
}
if (is_system !== undefined) {
updates.push('is_system = ?');
values.push(is_system);
}
if (updates.length === 0) {
return {
success: false,
message: "没有要更新的内容"
};
}
updates.push('updated_at = ?');
values.push(Date.now());
values.push(id);
await DB.execute(
`UPDATE subtitle_templates
SET ${updates.join(', ')}
WHERE id = ?`,
values
);
Log.info("ipAgent.updateSubtitleTemplate.success", { id });
return {
success: true,
message: "模板更新成功"
};
} catch (error) {
Log.error("ipAgent.updateSubtitleTemplate.error", error);
return {
success: false,
message: "更新模板失败: " + (error?.message || error)
};
}
});
// 导出所有字幕模板为JSON配置文件
ipcMain.handle("ipAgent:exportSubtitleTemplates", async (event, params?: {
includeSystemOnly?: boolean;
}) => {
try {
const includeSystemOnly = params?.includeSystemOnly === true;
Log.info("ipAgent.exportSubtitleTemplates - START", { includeSystemOnly });
console.log('[SubtitleTemplate] 开始导出字幕模板 (includeSystemOnly=' + includeSystemOnly + ')');
// 查询数据库中的所有模板
const sqlQuery = includeSystemOnly
? `SELECT id, name, description, config, created_at, is_system FROM subtitle_templates WHERE is_system = 1 ORDER BY created_at ASC`
: `SELECT id, name, description, config, created_at, is_system FROM subtitle_templates ORDER BY is_system DESC, created_at ASC`;
const dbTemplates = await DB.select(sqlQuery);
console.log(`[SubtitleTemplate] 查询到 ${dbTemplates.length} 个模板`);
// 转换为JSON导出格式
const subtitleTemplates = dbTemplates.map(t => {
try {
return {
id: t.id,
name: t.name,
description: t.description,
createdAt: t.created_at,
isSystem: t.is_system === 1,
config: typeof t.config === 'string' ? JSON.parse(t.config) : t.config
};
} catch (error) {
Log.warn("ipAgent.exportSubtitleTemplates.parseError", {
templateId: t.id,
error: error
});
return null;
}
}).filter(t => t !== null);
// 获取系统模板配置文件路径(使用统一的打包配置文件)
const configPath = getCommonResourcePath('config/system-templates.json');
const existingConfig = loadExistingExportConfig(configPath);
const bundledSystemTemplateMap = buildBundledSystemTemplateMap(existingConfig);
const normalizedTemplates = dedupeTemplatesById(
dbTemplates.map((template) => normalizeSubtitleTemplateRecord(template, bundledSystemTemplateMap))
);
console.log(`[SubtitleTemplate] 配置文件路径: ${configPath}`);
let config: any = {
version: "2.0.0",
description: "系统默认模板配置 - 应用启动时自动初始化",
subtitleTemplates: [],
subtitleStyles: [],
coverTemplates: []
};
// 读取现有配置文件(保留覆盖模板)
if (fs.existsSync(configPath)) {
try {
const content = fs.readFileSync(configPath, 'utf-8');
config = JSON.parse(content);
} catch (error) {
console.warn(`[SubtitleTemplate] 读取现有配置失败,使用默认结构`);
}
}
// 获取系统模板ID列表(用于过滤)
config = {
...existingConfig,
...config,
version: existingConfig.version || config.version || "2.0.0",
description: existingConfig.description || config.description || "系统默认模板配置 - 应用启动时自动初始化",
};
const systemTemplateIds = normalizedTemplates.filter(t => t.isSystem).map(t => t.id);
// 直接查询所有系统字幕样式(is_system = 1
// 这样可以确保导出所有系统样式,不依赖模板引用
let subtitleStyles: any[] = [];
try {
subtitleStyles = await DB.select(
`SELECT id, name, description, fontName, fontSize, fontColor, outlineColor, outlineWidth,
shadowOffset, shadowColor, shadowBlur, backgroundColor, backgroundOpacity,
position, alignment, is_system, preview
FROM subtitle_styles
WHERE is_system = 1
ORDER BY id ASC`
);
console.log(`[SubtitleTemplate] ✅ 查询到 ${subtitleStyles.length} 个系统字幕样式`);
} catch (error) {
console.warn(`[SubtitleTemplate] ⚠️ 查询字幕样式失败,继续不导出样式:`, error);
}
// 更新字幕模板:移除旧的系统模板,添加新的
if (includeSystemOnly) {
const existingNonSystemTemplates = (existingConfig.subtitleTemplates || []).filter((template: any) => !isSystemTemplateRecord(template));
config.subtitleTemplates = dedupeTemplatesById([
...normalizedTemplates,
...existingNonSystemTemplates,
]);
} else {
config.subtitleTemplates = normalizedTemplates;
}
// 更新字幕样式:移除旧的系统样式,添加新的
const systemStyleIds = new Set(subtitleStyles.map(s => s.id));
config.subtitleStyles = [
...subtitleStyles.map(s => ({
id: s.id,
name: s.name,
description: s.description,
preview: s.preview || "示例文字",
is_system: 1,
fontName: s.fontName,
fontSize: s.fontSize,
fontColor: s.fontColor,
outlineColor: s.outlineColor,
outlineWidth: s.outlineWidth,
shadowOffset: s.shadowOffset,
shadowColor: s.shadowColor,
shadowBlur: s.shadowBlur,
backgroundColor: s.backgroundColor,
backgroundOpacity: s.backgroundOpacity,
position: s.position,
alignment: s.alignment
})),
...(config.subtitleStyles || []).filter((s: any) => !systemStyleIds.has(s.id))
];
// 写回配置文件
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
console.log(`[SubtitleTemplate] ✅ 已导出 ${config.subtitleTemplates.length} 个模板和 ${subtitleStyles.length} 个样式到配置文件`);
console.log(`[SubtitleTemplate] 配置文件已更新: ${configPath}`);
Log.info("ipAgent.exportSubtitleTemplates.success", {
count: config.subtitleTemplates.length,
styleCount: subtitleStyles.length,
configPath: configPath,
templates: config.subtitleTemplates.map((t: any) => ({ id: t.id, name: t.name }))
});
return {
success: true,
count: config.subtitleTemplates.length,
styleCount: subtitleStyles.length,
message: `已将 ${config.subtitleTemplates.length} 个模板和 ${subtitleStyles.length} 个样式导出到配置文件`,
configPath: configPath,
templates: config.subtitleTemplates.map((t: any) => ({ id: t.id, name: t.name }))
};
} catch (error) {
console.error("[SubtitleTemplate] 导出模板失败:", error);
Log.error("ipAgent.exportSubtitleTemplates.error", error);
return {
success: false,
message: "导出失败: " + (error?.message || error),
data: null
};
}
});
// 导出单个字幕模板为JSON
ipcMain.handle("ipAgent:exportSubtitleTemplateById", async (event, params: {
id: string;
}) => {
try {
const { id } = params;
if (!id) {
return {
success: false,
message: "模板ID不能为空"
};
}
Log.info("ipAgent.exportSubtitleTemplateById", { id });
console.log('[SubtitleTemplate] 开始导出单个字幕模板:', id);
const template = await DB.first(
`SELECT id, name, description, config, created_at, is_system FROM subtitle_templates WHERE id = ?`,
[id]
);
if (!template) {
return {
success: false,
message: "模板不存在"
};
}
const bundledSystemTemplateMap = buildBundledSystemTemplateMap(
loadExistingExportConfig(getCommonResourcePath('config/system-templates.json'))
);
const normalizedTemplate = normalizeSubtitleTemplateRecord(template, bundledSystemTemplateMap);
const exportData = {
version: "2.0.0",
description: "单个字幕模板导出",
timestamp: new Date().toISOString(),
subtitleTemplate: {
id: normalizedTemplate.id,
name: normalizedTemplate.name,
description: normalizedTemplate.description,
createdAt: normalizedTemplate.createdAt,
isSystem: normalizedTemplate.isSystem,
config: normalizedTemplate.config
}
};
console.log(`[SubtitleTemplate] 导出单个模板成功: ${template.name}`);
Log.info("ipAgent.exportSubtitleTemplateById.success", { id, name: template.name });
return {
success: true,
data: exportData,
message: "导出成功"
};
} catch (error) {
console.error("[SubtitleTemplate] 导出单个模板失败:", error);
Log.error("ipAgent.exportSubtitleTemplateById.error", error);
return {
success: false,
message: "导出失败: " + (error?.message || error),
data: null
};
}
});
// 复制字幕模板
ipcMain.handle("ipAgent:duplicateSubtitleTemplate", async (event, params: {
id: string;
newName?: string;
}) => {
try {
const { id, newName } = params;
Log.info("ipAgent.duplicateSubtitleTemplate", { id, newName });
console.log('[SubtitleTemplate duplicateSubtitleTemplate] 开始复制模板:', id);
if (!id) {
return {
success: false,
message: "模板ID不能为空"
};
}
// 获取原模板
const sourceTemplate = await DB.first(
`SELECT id, name, description, config, is_system FROM subtitle_templates WHERE id = ?`,
[id]
);
if (!sourceTemplate) {
return {
success: false,
message: "源模板不存在"
};
}
// 生成新模板名称
let duplicateName = newName || `${sourceTemplate.name} - 副本`;
// 检查名称是否已存在,如果存在则添加数字后缀
let counter = 1;
let finalName = duplicateName;
while (true) {
const existing = await DB.first(
`SELECT id FROM subtitle_templates WHERE name = ?`,
[finalName]
);
if (!existing) break;
finalName = `${duplicateName} (${counter})`;
counter++;
}
// 创建新模板
const newId = generateUUID();
const now = Date.now();
const bundledSystemTemplateMap = buildBundledSystemTemplateMap(
loadExistingExportConfig(getCommonResourcePath('config/system-templates.json'))
);
const normalizedSourceTemplate = normalizeSubtitleTemplateRecord(sourceTemplate, bundledSystemTemplateMap);
await DB.execute(
`INSERT INTO subtitle_templates (id, name, description, config, is_system, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[newId, finalName, normalizedSourceTemplate.description, JSON.stringify(normalizedSourceTemplate.config), 0, now, now]
);
Log.info("ipAgent.duplicateSubtitleTemplate.success", {
sourceId: id,
newId,
sourceName: sourceTemplate.name,
newName: finalName
});
console.log(`[SubtitleTemplate] ✅ 复制成功: ${sourceTemplate.name}${finalName}`);
return {
success: true,
id: newId,
name: finalName,
message: "模板复制成功"
};
} catch (error) {
Log.error("ipAgent.duplicateSubtitleTemplate.error", error);
console.error("[SubtitleTemplate] 复制模板失败:", error);
return {
success: false,
message: "复制模板失败: " + (error?.message || error)
};
}
});
ipcMain.handle("ipAgent:importSubtitleTemplate", async (event) => {
try {
const result = await dialog.showOpenDialog({
title: '导入字幕模板',
filters: [
{ name: 'JSON文件', extensions: ['json'] }
],
properties: ['openFile']
});
if (result.canceled || !result.filePaths || result.filePaths.length === 0) {
return { success: false, message: '用户取消导入' };
}
const filePath = result.filePaths[0];
const content = fs.readFileSync(filePath, 'utf-8');
const data = JSON.parse(content);
let templateData: any = null;
if (data.subtitleTemplate) {
templateData = data.subtitleTemplate;
} else if (data.name && data.config) {
templateData = data;
} else {
return { success: false, message: '无法识别的模板文件格式' };
}
if (!templateData.config) {
return { success: false, message: '模板文件缺少配置数据' };
}
const templateName = templateData.name || '导入的模板';
const configJson = JSON.stringify(templateData.config);
const now = Date.now();
const count = await DB.first(
`SELECT COUNT(*) as count FROM subtitle_templates WHERE is_system = 0 OR is_system IS NULL`
);
if (count && count.count >= 24) {
return { success: false, message: '最多只能保存24个自定义模板,请先删除一些模板' };
}
const existingByName = await DB.first(
`SELECT id FROM subtitle_templates WHERE name = ?`,
[templateName]
);
let finalName = templateName;
if (existingByName) {
finalName = templateName + ' (' + new Date().toLocaleString() + ')';
}
const newId = generateUUID();
await DB.execute(
`INSERT INTO subtitle_templates (id, name, description, config, created_at, updated_at, is_system)
VALUES (?, ?, ?, ?, ?, ?, 0)`,
[newId, finalName, templateData.description || '', configJson, now, now]
);
console.log(`[SubtitleTemplate] 导入模板成功: ${finalName}`);
Log.info("ipAgent.importSubtitleTemplate.success", { id: newId, name: finalName });
return {
success: true,
id: newId,
name: finalName,
message: `模板 "${finalName}" 导入成功`
};
} catch (error) {
console.error("[SubtitleTemplate] 导入模板失败:", error);
Log.error("ipAgent.importSubtitleTemplate.error", error);
return {
success: false,
message: "导入失败: " + (error?.message || error)
};
}
});
Log.info("ipAgent.subtitleTemplate.handlers.registered");
};
export default {
registerSubtitleTemplateHandlers
};