easysql/dist/main/main.js
2025-12-29 18:35:04 +08:00

533 lines
19 KiB
JavaScript

"use strict";
const electron = require("electron");
const path = require("path");
let mainWindow = null;
let dbConnections = /* @__PURE__ */ new Map();
const isDev = !electron.app.isPackaged;
const gotTheLock = electron.app.requestSingleInstanceLock();
if (!gotTheLock) {
electron.app.quit();
} else {
electron.app.on("second-instance", () => {
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
}
function createWindow() {
mainWindow = new electron.BrowserWindow({
width: 1400,
height: 900,
minWidth: 1100,
minHeight: 700,
frame: false,
backgroundColor: "#1f1f1f",
webPreferences: {
preload: path.join(__dirname, "../preload/preload.js"),
contextIsolation: true,
nodeIntegration: false
}
});
if (isDev) {
mainWindow.loadURL("http://localhost:5173");
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"));
}
}
electron.app.whenReady().then(createWindow);
electron.app.on("window-all-closed", () => {
if (process.platform !== "darwin") electron.app.quit();
});
electron.app.on("activate", () => {
if (electron.BrowserWindow.getAllWindows().length === 0) createWindow();
});
electron.ipcMain.handle("window:minimize", () => mainWindow == null ? void 0 : mainWindow.minimize());
electron.ipcMain.handle("window:maximize", () => {
(mainWindow == null ? void 0 : mainWindow.isMaximized()) ? mainWindow.unmaximize() : mainWindow == null ? void 0 : mainWindow.maximize();
});
electron.ipcMain.handle("window:close", () => mainWindow == null ? void 0 : mainWindow.close());
function resolveHost(host) {
return host === "localhost" ? "127.0.0.1" : host;
}
electron.ipcMain.handle("db:test", async (_, config) => {
const host = resolveHost(config.host);
try {
if (config.type === "mysql" || config.type === "mariadb") {
const mysql = require("mysql2/promise");
const conn = await mysql.createConnection({
host,
port: config.port,
user: config.username,
password: config.password,
database: config.database || void 0,
connectTimeout: 1e4
});
await conn.ping();
await conn.end();
return { success: true, message: "连接成功" };
} else if (config.type === "postgres") {
const { Client } = require("pg");
const client = new Client({
host,
port: config.port,
user: config.username,
password: config.password,
database: config.database || "postgres",
connectionTimeoutMillis: 1e4
});
await client.connect();
await client.end();
return { success: true, message: "连接成功" };
} else if (config.type === "mongodb") {
const { MongoClient } = require("mongodb");
const uri = config.username ? `mongodb://${config.username}:${config.password}@${host}:${config.port}/${config.database || "admin"}` : `mongodb://${host}:${config.port}/${config.database || "admin"}`;
const client = new MongoClient(uri, { serverSelectionTimeoutMS: 1e4 });
await client.connect();
await client.close();
return { success: true, message: "连接成功" };
} else if (config.type === "redis") {
const Redis = require("ioredis");
const client = new Redis({
host,
port: config.port,
password: config.password || void 0,
connectTimeout: 1e4,
lazyConnect: true
});
await client.connect();
await client.ping();
await client.quit();
return { success: true, message: "连接成功" };
} else if (config.type === "sqlserver") {
const sql = require("mssql");
const poolConfig = {
server: host,
port: config.port,
user: config.username,
password: config.password,
database: config.database || "master",
options: { encrypt: false, trustServerCertificate: true },
connectionTimeout: 1e4
};
const pool = await sql.connect(poolConfig);
await pool.close();
return { success: true, message: "连接成功" };
}
return { success: false, message: `暂不支持 ${config.type}` };
} catch (err) {
return { success: false, message: err.message };
}
});
electron.ipcMain.handle("db:connect", async (_, config) => {
const host = resolveHost(config.host);
try {
if (config.type === "mysql" || config.type === "mariadb") {
const mysql = require("mysql2/promise");
const conn = await mysql.createConnection({
host,
port: config.port,
user: config.username,
password: config.password,
database: config.database || void 0
});
dbConnections.set(config.id, { type: "mysql", conn });
return { success: true, message: "连接成功" };
} else if (config.type === "postgres") {
const { Client } = require("pg");
const client = new Client({
host,
port: config.port,
user: config.username,
password: config.password,
database: config.database || "postgres"
});
await client.connect();
dbConnections.set(config.id, { type: "postgres", conn: client });
return { success: true, message: "连接成功" };
} else if (config.type === "mongodb") {
const { MongoClient } = require("mongodb");
const uri = config.username ? `mongodb://${config.username}:${config.password}@${host}:${config.port}/${config.database || "admin"}` : `mongodb://${host}:${config.port}/${config.database || "admin"}`;
const client = new MongoClient(uri);
await client.connect();
dbConnections.set(config.id, { type: "mongodb", conn: client });
return { success: true, message: "连接成功" };
} else if (config.type === "redis") {
const Redis = require("ioredis");
const client = new Redis({
host,
port: config.port,
password: config.password || void 0,
lazyConnect: true
});
await client.connect();
dbConnections.set(config.id, { type: "redis", conn: client });
return { success: true, message: "连接成功" };
} else if (config.type === "sqlserver") {
const sql = require("mssql");
const poolConfig = {
server: host,
port: config.port,
user: config.username,
password: config.password,
database: config.database || "master",
options: { encrypt: false, trustServerCertificate: true }
};
const pool = await sql.connect(poolConfig);
dbConnections.set(config.id, { type: "sqlserver", conn: pool });
return { success: true, message: "连接成功" };
}
return { success: false, message: `暂不支持 ${config.type}` };
} catch (err) {
return { success: false, message: err.message };
}
});
electron.ipcMain.handle("db:disconnect", async (_, id) => {
const db = dbConnections.get(id);
if (db) {
try {
await db.conn.end();
} catch {
}
dbConnections.delete(id);
}
});
electron.ipcMain.handle("db:query", async (_, id, sql) => {
var _a;
const db = dbConnections.get(id);
if (!db) return { columns: [], rows: [], error: "未连接" };
try {
if (db.type === "mysql") {
const [rows, fields] = await db.conn.query(sql);
const columns = (fields == null ? void 0 : fields.map((f) => f.name)) || [];
return { columns, rows: Array.isArray(rows) ? rows : [] };
} else if (db.type === "postgres") {
const result = await db.conn.query(sql);
const columns = ((_a = result.fields) == null ? void 0 : _a.map((f) => f.name)) || [];
return { columns, rows: result.rows };
}
return { columns: [], rows: [], error: "不支持的类型" };
} catch (err) {
return { columns: [], rows: [], error: err.message };
}
});
electron.ipcMain.handle("db:getDatabases", async (_, id) => {
const db = dbConnections.get(id);
if (!db) return [];
try {
if (db.type === "mysql") {
const [rows] = await db.conn.query("SHOW DATABASES");
return rows.map((r) => r.Database);
} else if (db.type === "postgres") {
const result = await db.conn.query("SELECT datname FROM pg_database WHERE datistemplate = false");
return result.rows.map((r) => r.datname);
}
} catch {
}
return [];
});
electron.ipcMain.handle("db:getTables", async (_, id, database) => {
const db = dbConnections.get(id);
if (!db) return [];
try {
if (db.type === "mysql") {
await db.conn.query(`USE \`${database}\``);
const [rows] = await db.conn.query(`
SELECT TABLE_NAME as name, TABLE_ROWS as \`rows\`
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = ?
`, [database]);
return rows.map((r) => ({ name: r.name, rows: r.rows || 0 }));
} else if (db.type === "postgres") {
const result = await db.conn.query(`
SELECT tablename as name,
(SELECT reltuples::bigint FROM pg_class WHERE relname = tablename) as rows
FROM pg_tables WHERE schemaname = 'public'
`);
return result.rows.map((r) => ({ name: r.name, rows: parseInt(r.rows) || 0 }));
}
} catch (err) {
console.error("getTables error:", err);
}
return [];
});
electron.ipcMain.handle("db:getColumns", async (_, id, database, table) => {
const db = dbConnections.get(id);
if (!db) return [];
try {
if (db.type === "mysql") {
const [rows] = await db.conn.query(`
SELECT COLUMN_NAME as name, DATA_TYPE as type, IS_NULLABLE as nullable,
COLUMN_KEY as \`key\`, COLUMN_COMMENT as comment
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
ORDER BY ORDINAL_POSITION
`, [database, table]);
return rows.map((r) => ({
name: r.name,
type: r.type,
nullable: r.nullable === "YES",
key: r.key || void 0,
comment: r.comment || void 0
}));
} else if (db.type === "postgres") {
const result = await db.conn.query(`
SELECT c.column_name as name, c.data_type as type, c.is_nullable as nullable,
pgd.description as comment
FROM information_schema.columns c
LEFT JOIN pg_catalog.pg_statio_all_tables st ON c.table_schema = st.schemaname AND c.table_name = st.relname
LEFT JOIN pg_catalog.pg_description pgd ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
WHERE c.table_schema = 'public' AND c.table_name = $1
ORDER BY c.ordinal_position
`, [table]);
return result.rows.map((r) => ({
name: r.name,
type: r.type,
nullable: r.nullable === "YES",
comment: r.comment || void 0
}));
}
} catch (err) {
console.error("getColumns error:", err);
}
return [];
});
electron.ipcMain.handle("db:getTableData", async (_, id, database, table, page = 1, pageSize = 100) => {
var _a, _b;
const db = dbConnections.get(id);
if (!db) return { data: [], total: 0 };
try {
const offset = (page - 1) * pageSize;
if (db.type === "mysql") {
const [countResult] = await db.conn.query(`SELECT COUNT(*) as total FROM \`${database}\`.\`${table}\``);
const total = ((_a = countResult[0]) == null ? void 0 : _a.total) || 0;
const [rows] = await db.conn.query(`SELECT * FROM \`${database}\`.\`${table}\` LIMIT ? OFFSET ?`, [pageSize, offset]);
return { data: rows, total };
} else if (db.type === "postgres") {
const countResult = await db.conn.query(`SELECT COUNT(*) as total FROM "${table}"`);
const total = parseInt((_b = countResult.rows[0]) == null ? void 0 : _b.total) || 0;
const result = await db.conn.query(`SELECT * FROM "${table}" LIMIT $1 OFFSET $2`, [pageSize, offset]);
return { data: result.rows, total };
}
} catch (err) {
console.error("getTableData error:", err);
}
return { data: [], total: 0 };
});
const fs = require("fs");
const configPath = path.join(electron.app.getPath("userData"), "connections.json");
electron.ipcMain.handle("config:save", async (_, connections) => {
fs.writeFileSync(configPath, JSON.stringify(connections, null, 2));
});
electron.ipcMain.handle("config:load", async () => {
try {
if (fs.existsSync(configPath)) {
return JSON.parse(fs.readFileSync(configPath, "utf-8"));
}
} catch {
}
return [];
});
electron.ipcMain.handle("file:open", async () => {
const result = await electron.dialog.showOpenDialog(mainWindow, {
title: "打开 SQL 文件",
filters: [
{ name: "SQL 文件", extensions: ["sql"] },
{ name: "所有文件", extensions: ["*"] }
],
properties: ["openFile"]
});
if (result.canceled || result.filePaths.length === 0) {
return null;
}
const filePath = result.filePaths[0];
try {
const content = fs.readFileSync(filePath, "utf-8");
return { path: filePath, content, name: path.basename(filePath) };
} catch (err) {
return { error: err.message };
}
});
electron.ipcMain.handle("file:save", async (_, filePath, content) => {
let targetPath = filePath;
if (!targetPath) {
const result = await electron.dialog.showSaveDialog(mainWindow, {
title: "保存 SQL 文件",
defaultPath: "query.sql",
filters: [
{ name: "SQL 文件", extensions: ["sql"] },
{ name: "所有文件", extensions: ["*"] }
]
});
if (result.canceled || !result.filePath) {
return null;
}
targetPath = result.filePath;
}
try {
fs.writeFileSync(targetPath, content, "utf-8");
return { path: targetPath, name: path.basename(targetPath) };
} catch (err) {
return { error: err.message };
}
});
electron.ipcMain.handle("db:backup", async (_, id, database) => {
const db = dbConnections.get(id);
if (!db) return { error: "未连接数据库" };
const result = await electron.dialog.showSaveDialog(mainWindow, {
title: "备份数据库",
defaultPath: `${database}_backup_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.sql`,
filters: [
{ name: "SQL 文件", extensions: ["sql"] },
{ name: "所有文件", extensions: ["*"] }
]
});
if (result.canceled || !result.filePath) {
return { cancelled: true };
}
try {
let sqlContent = "";
sqlContent += `-- Database Backup: ${database}
`;
sqlContent += `-- Generated: ${(/* @__PURE__ */ new Date()).toLocaleString()}
`;
sqlContent += `-- Tool: EasySQL
`;
if (db.type === "mysql" || db.type === "mariadb") {
await db.conn.query(`USE \`${database}\``);
const [tables] = await db.conn.query(`SHOW TABLES`);
const tableKey = `Tables_in_${database}`;
sqlContent += `SET FOREIGN_KEY_CHECKS = 0;
`;
for (const tableRow of tables) {
const tableName = tableRow[tableKey];
const [createResult] = await db.conn.query(`SHOW CREATE TABLE \`${tableName}\``);
const createStatement = createResult[0]["Create Table"];
sqlContent += `-- Table: ${tableName}
`;
sqlContent += `DROP TABLE IF EXISTS \`${tableName}\`;
`;
sqlContent += `${createStatement};
`;
const [rows] = await db.conn.query(`SELECT * FROM \`${tableName}\``);
if (rows.length > 0) {
const columns = Object.keys(rows[0]);
for (const row of rows) {
const values = columns.map((col) => {
const val = row[col];
if (val === null) return "NULL";
if (typeof val === "number") return val;
if (val instanceof Date) return `'${val.toISOString().slice(0, 19).replace("T", " ")}'`;
return `'${String(val).replace(/'/g, "''").replace(/\\/g, "\\\\")}'`;
}).join(", ");
sqlContent += `INSERT INTO \`${tableName}\` (\`${columns.join("`, `")}\`) VALUES (${values});
`;
}
sqlContent += "\n";
}
}
sqlContent += `SET FOREIGN_KEY_CHECKS = 1;
`;
} else if (db.type === "postgres") {
const tablesResult = await db.conn.query(`
SELECT tablename FROM pg_tables WHERE schemaname = 'public'
`);
for (const tableRow of tablesResult.rows) {
const tableName = tableRow.tablename;
const dataResult = await db.conn.query(`SELECT * FROM "${tableName}"`);
if (dataResult.rows.length > 0) {
const columns = Object.keys(dataResult.rows[0]);
sqlContent += `-- Table: ${tableName}
`;
for (const row of dataResult.rows) {
const values = columns.map((col) => {
const val = row[col];
if (val === null) return "NULL";
if (typeof val === "number") return val;
if (val instanceof Date) return `'${val.toISOString().slice(0, 19).replace("T", " ")}'`;
return `'${String(val).replace(/'/g, "''")}'`;
}).join(", ");
sqlContent += `INSERT INTO "${tableName}" ("${columns.join('", "')}") VALUES (${values});
`;
}
sqlContent += "\n";
}
}
}
fs.writeFileSync(result.filePath, sqlContent, "utf-8");
return { success: true, path: result.filePath };
} catch (err) {
return { error: err.message };
}
});
electron.ipcMain.handle("db:exportTable", async (_, id, database, tableName, format) => {
const db = dbConnections.get(id);
if (!db) return { error: "未连接数据库" };
const ext = format === "excel" ? "xlsx" : format;
const result = await electron.dialog.showSaveDialog(mainWindow, {
title: `导出表 ${tableName}`,
defaultPath: `${tableName}_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.${ext}`,
filters: [
{ name: format === "excel" ? "Excel 文件" : format === "sql" ? "SQL 文件" : "CSV 文件", extensions: [ext] },
{ name: "所有文件", extensions: ["*"] }
]
});
if (result.canceled || !result.filePath) {
return { cancelled: true };
}
try {
let rows = [];
let columns = [];
if (db.type === "mysql" || db.type === "mariadb") {
await db.conn.query(`USE \`${database}\``);
const [data] = await db.conn.query(`SELECT * FROM \`${tableName}\``);
rows = data;
if (rows.length > 0) columns = Object.keys(rows[0]);
} else if (db.type === "postgres") {
const data = await db.conn.query(`SELECT * FROM "${tableName}"`);
rows = data.rows;
if (rows.length > 0) columns = Object.keys(rows[0]);
}
if (format === "sql") {
let content = `-- Table: ${tableName}
`;
content += `-- Exported: ${(/* @__PURE__ */ new Date()).toLocaleString()}
`;
for (const row of rows) {
const values = columns.map((col) => {
const val = row[col];
if (val === null) return "NULL";
if (typeof val === "number") return val;
return `'${String(val).replace(/'/g, "''")}'`;
}).join(", ");
content += `INSERT INTO \`${tableName}\` (\`${columns.join("`, `")}\`) VALUES (${values});
`;
}
fs.writeFileSync(result.filePath, content, "utf-8");
} else if (format === "csv") {
let content = columns.join(",") + "\n";
for (const row of rows) {
const values = columns.map((col) => {
const val = row[col];
if (val === null) return "";
const str = String(val);
if (str.includes(",") || str.includes('"') || str.includes("\n")) {
return `"${str.replace(/"/g, '""')}"`;
}
return str;
});
content += values.join(",") + "\n";
}
fs.writeFileSync(result.filePath, content, "utf-8");
}
return { success: true, path: result.filePath };
} catch (err) {
return { error: err.message };
}
});