Initial clean project import

This commit is contained in:
cat-shark
2026-06-19 18:41:41 +08:00
commit a13b804c7a
1306 changed files with 220568 additions and 0 deletions
+985
View File
@@ -0,0 +1,985 @@
/**
* 字幕模板管理 - 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
};