feat(browser): 添加数据库管理工具和JS对话框处理- 实现了浏览器窗口中的JavaScript alert弹窗拦截与处理

- 添加了数据库连接管理器,支持多种数据库类型(MySQL、PostgreSQL、SQLite、Oracle、H2)
- 开发了数据库管理工具的前端界面,包含连接配置、查询编辑器和结果展示
- 支持本地数据库创建与示例数据初始化
- 提供了数据库表结构管理和基础SQL执行功能- 增加了暗色主题切换和响应式布局设计
- 集成了事件日志面板用于调试和状态跟踪
This commit is contained in:
tzdwindows 7
2025-10-07 12:38:53 +08:00
parent 167bf6405f
commit 8f40542ab0
13 changed files with 2404 additions and 38 deletions

View File

@@ -6,33 +6,121 @@ import org.apache.logging.log4j.Logger;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
/**
* 将输出传递给 Log4j2 的日志记录器
* 将输出传递给 Log4j2 的日志记录器,同时保持控制台输出
* 修复问题控制台输出被Log4j2覆盖
* @author tzdwindows 7
*/
public class Log4j2OutputStream extends OutputStream {
private static final Logger logger = LogManager.getLogger();
// 恢复静态变量
public static final ByteArrayOutputStream systemOutContent = new ByteArrayOutputStream();
public static final ByteArrayOutputStream systemErrContent = new ByteArrayOutputStream();
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
private final boolean isErrorStream;
private final PrintStream originalStream; // 保存原始的控制台流
public Log4j2OutputStream(boolean isErrorStream, PrintStream originalStream) {
this.isErrorStream = isErrorStream;
this.originalStream = originalStream;
}
@Override
public void write(int b) {
systemOutContent.write(b);
logger.info(String.valueOf((char) b));
// 写入原始控制台
originalStream.write(b);
buffer.write(b);
// 将内容同时写入对应的静态变量
if (isErrorStream) {
systemErrContent.write(b);
} else {
systemOutContent.write(b);
}
// 遇到换行符时刷新缓冲区到日志
if (b == '\n') {
flush();
}
}
@Override
public void write(byte[] b, int off, int len) {
systemOutContent.write(b, off, len);
String message = new String(b, off, len).trim();
logger.info(message);
// 写入原始控制台
originalStream.write(b, off, len);
buffer.write(b, off, len);
// 将内容同时写入对应的静态变量
if (isErrorStream) {
systemErrContent.write(b, off, len);
} else {
systemOutContent.write(b, off, len);
}
// 检查是否包含换行符
for (int i = off; i < off + len; i++) {
if (b[i] == '\n') {
flush();
break;
}
}
}
@Override
public void flush() {
originalStream.flush();
String message = buffer.toString(StandardCharsets.UTF_8).trim();
if (!message.isEmpty()) {
if (isErrorStream) {
logger.error(message);
} else {
logger.info(message);
}
}
buffer.reset(); // 清空缓冲区
}
@Override
public void close() {
flush();
originalStream.close();
}
/**
* 重定向 System.out 和 System.err 到 Log4j2
* 重定向 System.out 和 System.err 到 Log4j2,同时保持控制台输出
*/
public static void redirectSystemStreams() {
System.setOut(new PrintStream(new Log4j2OutputStream(), true));
System.setErr(new PrintStream(new Log4j2OutputStream(), true));
// 保存原始流
PrintStream originalOut = System.out;
PrintStream originalErr = System.err;
// System.out 使用 INFO 级别,同时输出到原始控制台
System.setOut(new PrintStream(new Log4j2OutputStream(false, originalOut), true, StandardCharsets.UTF_8));
// System.err 使用 ERROR 级别,同时输出到原始控制台
System.setErr(new PrintStream(new Log4j2OutputStream(true, originalErr), true, StandardCharsets.UTF_8));
}
/**
* 清空静态缓冲区内容
*/
public static void clearBuffers() {
systemOutContent.reset();
systemErrContent.reset();
}
/**
* 获取输出内容
*/
public static String getSystemOutContent() {
return systemOutContent.toString(StandardCharsets.UTF_8);
}
public static String getSystemErrContent() {
return systemErrContent.toString(StandardCharsets.UTF_8);
}
}

View File

@@ -11,9 +11,11 @@ import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.browser.CefMessageRouter;
import org.cef.callback.CefContextMenuParams;
import org.cef.callback.CefJSDialogCallback;
import org.cef.callback.CefMenuModel;
import org.cef.callback.CefQueryCallback;
import org.cef.handler.*;
import org.cef.misc.BoolRef;
import org.cef.network.CefRequest;
import javax.swing.*;
@@ -425,6 +427,24 @@ public class BrowserWindow extends JFrame {
}
});
client.addJSDialogHandler(new CefJSDialogHandlerAdapter() {
@Override
public boolean onJSDialog(CefBrowser browser, String origin_url, CefJSDialogHandler.JSDialogType dialog_type, String message_text, String default_prompt_text, CefJSDialogCallback callback, BoolRef suppress_message) {
if (dialog_type == CefJSDialogHandler.JSDialogType.JSDIALOGTYPE_ALERT) {
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(
BrowserWindow.this,
message_text,
"警告",
JOptionPane.INFORMATION_MESSAGE
);
});
callback.Continue(true, "");
return true;
}
return false;
}
});
// 3. 拦截所有新窗口(关键修复点!)

View File

@@ -11,9 +11,11 @@ import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.browser.CefMessageRouter;
import org.cef.callback.CefContextMenuParams;
import org.cef.callback.CefJSDialogCallback;
import org.cef.callback.CefMenuModel;
import org.cef.callback.CefQueryCallback;
import org.cef.handler.*;
import org.cef.misc.BoolRef;
import org.cef.network.CefRequest;
import org.json.JSONObject;
@@ -432,6 +434,26 @@ public class BrowserWindowJDialog extends JDialog {
}
});
// 添加 alert 弹窗监控处理
client.addJSDialogHandler(new CefJSDialogHandlerAdapter() {
@Override
public boolean onJSDialog(CefBrowser browser, String origin_url, CefJSDialogHandler.JSDialogType dialog_type, String message_text, String default_prompt_text, CefJSDialogCallback callback, BoolRef suppress_message) {
if (dialog_type == CefJSDialogHandler.JSDialogType.JSDIALOGTYPE_ALERT) {
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(
BrowserWindowJDialog.this,
message_text,
"警告",
JOptionPane.INFORMATION_MESSAGE
);
});
callback.Continue(true, "");
return true;
}
return false;
}
});
// 3. 拦截所有新窗口(关键修复点!)

View File

@@ -0,0 +1,397 @@
package com.axis.innovators.box.browser.util;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* 数据库连接管理器
* @author tzdwindows 7
*/
public class DatabaseConnectionManager {
private static final java.util.Map<String, Connection> connections = new java.util.concurrent.ConcurrentHashMap<>();
private static final java.util.Map<String, DatabaseInfo> connectionInfo = new java.util.concurrent.ConcurrentHashMap<>();
public static class DatabaseInfo {
public String driver;
public String url;
public String host;
public String port;
public String database;
public String username;
public DatabaseInfo(String driver, String url, String host, String port, String database, String username) {
this.driver = driver;
this.url = url;
this.host = host;
this.port = port;
this.database = database;
this.username = username;
}
}
public static String connect(String driver, String host, String port,
String database, String username, String password) throws SQLException {
String connectionId = "conn_" + System.currentTimeMillis();
String drv = driver == null ? "" : driver.toLowerCase();
// 规范化 database 路径(特别是 Windows 反斜杠问题)
if (database != null) {
database = database.replace("\\", "/");
} else {
database = "";
}
// 先显式加载驱动,避免因为 classloader 问题找不到驱动
try {
switch (drv) {
case "mysql":
Class.forName("com.mysql.cj.jdbc.Driver");
break;
case "postgresql":
Class.forName("org.postgresql.Driver");
break;
case "sqlite":
Class.forName("org.sqlite.JDBC");
break;
case "oracle":
Class.forName("oracle.jdbc.OracleDriver");
break;
case "h2":
Class.forName("org.h2.Driver");
break;
default:
// 不抛出,使后续 URL 构造仍可检查类型
}
} catch (ClassNotFoundException e) {
throw new SQLException("JDBC 驱动未找到,请确认对应驱动已加入 classpath: " + e.getMessage(), e);
}
String url = buildConnectionUrl(driver, host, port, database);
Connection connection;
Properties props = new Properties();
if (username != null && !username.isEmpty()) props.setProperty("user", username);
if (password != null && !password.isEmpty()) props.setProperty("password", password);
switch (drv) {
case "mysql":
props.setProperty("useSSL", "false");
props.setProperty("serverTimezone", "UTC");
connection = DriverManager.getConnection(url, props);
break;
case "postgresql":
connection = DriverManager.getConnection(url, props);
break;
case "sqlite":
// sqlite 不需要 propsURL 已经是文件路径(已做过替换)
connection = DriverManager.getConnection(url);
break;
case "oracle":
connection = DriverManager.getConnection(url, props);
break;
case "h2":
// H2 使用默认用户 sa / 空密码(如果需要可调整)
connection = DriverManager.getConnection(url, "sa", "");
break;
default:
throw new SQLException("不支持的数据库类型: " + driver);
}
connections.put(connectionId, connection);
connectionInfo.put(connectionId, new DatabaseInfo(driver, url, host, port, database, username));
return connectionId;
}
public static void disconnect(String connectionId) throws SQLException {
Connection connection = connections.get(connectionId);
if (connection != null && !connection.isClosed()) {
connection.close();
}
connections.remove(connectionId);
connectionInfo.remove(connectionId);
}
public static Connection getConnection(String connectionId) {
return connections.get(connectionId);
}
public static DatabaseInfo getConnectionInfo(String connectionId) {
return connectionInfo.get(connectionId);
}
private static String buildConnectionUrl(String driver, String host, String port, String database) {
String drv = driver == null ? "" : driver.toLowerCase();
switch (drv) {
case "mysql":
return "jdbc:mysql://" + host + ":" + port + "/" + database;
case "postgresql":
return "jdbc:postgresql://" + host + ":" + port + "/" + database;
case "sqlite":
// 对于 SQLitedatabase 可能是绝对路径或相对文件名,先把反斜杠替成正斜杠
if (database == null || database.isEmpty()) {
return "jdbc:sqlite::memory:";
}
String normalized = database.replace("\\", "/");
// 如果看起来像相对文件名(不含冒号也不以 / 开头),则当作相对于用户目录的路径
if (!normalized.contains(":") && !normalized.startsWith("/")) {
String userHome = System.getProperty("user.home").replace("\\", "/");
normalized = userHome + "/" + normalized;
}
return "jdbc:sqlite:" + normalized;
case "oracle":
return "jdbc:oracle:thin:@" + host + ":" + port + ":" + database;
case "h2":
// H2 文件路径同样做反斜杠处理
if (database == null || database.isEmpty()) {
String userHome = System.getProperty("user.home").replace("\\", "/");
return "jdbc:h2:file:" + userHome + "/.axis_innovators_box/databases/h2db";
} else {
String norm = database.replace("\\", "/");
// 如果传入仅是名字(无斜杠或冒号),则存到用户目录下
if (!norm.contains("/") && !norm.contains(":")) {
String userHome = System.getProperty("user.home").replace("\\", "/");
norm = userHome + "/.axis_innovators_box/databases/" + norm;
}
return "jdbc:h2:file:" + norm;
}
default:
throw new IllegalArgumentException("不支持的数据库类型: " + driver);
}
}
/**
* 在服务器上创建数据库MySQL / PostgreSQL / Oracle示例
* @param driver mysql | postgresql | oracle
* @param host 数据库主机
* @param port 端口
* @param dbName 要创建的数据库名(或 schema 名)
* @param adminUser 管理员用户名(用于创建数据库)
* @param adminPassword 管理员密码
* @return 如果创建成功返回一个简短消息,否则抛出 SQLException
* @throws SQLException
*/
public static String createDatabaseOnServer(String driver, String host, String port,
String dbName, String adminUser, String adminPassword) throws SQLException {
if (driver == null) throw new SQLException("driver 不能为空");
String drv = driver.toLowerCase().trim();
// 简单校验 dbName避免注入——只允许字母数字下划线
if (dbName == null || !dbName.matches("[A-Za-z0-9_]+")) {
throw new SQLException("不合法的数据库名: " + dbName);
}
try {
switch (drv) {
case "mysql":
// 加载驱动(如果尚未加载)
try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) {
throw new SQLException("MySQL 驱动未找到,请加入 mysql-connector-java 到 classpath", e);
}
// 连接到服务器的默认库(不指定数据库)以执行 CREATE DATABASE
String mysqlUrl = "jdbc:mysql://" + host + ":" + port + "/?useSSL=false&serverTimezone=UTC";
try (Connection conn = DriverManager.getConnection(mysqlUrl, adminUser, adminPassword);
Statement st = conn.createStatement()) {
String sql = "CREATE DATABASE `" + dbName + "` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci";
st.executeUpdate(sql);
}
return "MySQL 数据库创建成功: " + dbName;
case "postgresql":
case "postgres":
try { Class.forName("org.postgresql.Driver"); } catch (ClassNotFoundException e) {
throw new SQLException("Postgres 驱动未找到,请加入 postgresql 到 classpath", e);
}
// 连接到默认 postgres 数据库以创建新数据库
String pgUrl = "jdbc:postgresql://" + host + ":" + port + "/postgres";
try (Connection conn = DriverManager.getConnection(pgUrl, adminUser, adminPassword);
Statement st = conn.createStatement()) {
String sql = "CREATE DATABASE " + dbName + " WITH ENCODING 'UTF8'";
st.executeUpdate(sql);
}
return "PostgreSQL 数据库创建成功: " + dbName;
case "oracle":
// Oracle 数据库“创建数据库”通常由 DBA 完成(复杂),这里示例创建用户/模式(更常见)
try { Class.forName("oracle.jdbc.OracleDriver"); } catch (ClassNotFoundException e) {
throw new SQLException("Oracle 驱动未找到,请把 ojdbc.jar 加入 classpath", e);
}
// 需使用具有足够权限的账户(通常为 sys or system并且 URL 需要正确SID / ServiceName
// 下面示例假设通过 SID 连接: jdbc:oracle:thin:@host:port:SID
String oracleUrl = "jdbc:oracle:thin:@" + host + ":" + port + ":" + "ORCL"; // 把 ORCL 换成实际 SID
try (Connection conn = DriverManager.getConnection(oracleUrl, adminUser, adminPassword);
Statement st = conn.createStatement()) {
// 创建 userschema示例
String pwd = adminPassword; // 实际应使用独立密码,不推荐用 adminPassword
String createUser = "CREATE USER " + dbName + " IDENTIFIED BY \"" + pwd + "\"";
String grant = "GRANT CONNECT, RESOURCE TO " + dbName;
st.executeUpdate(createUser);
st.executeUpdate(grant);
} catch (SQLException ex) {
// Oracle 操作更容易失败,给出提示
throw new SQLException("Oracle: 无法创建用户/模式,请检查权限和 URL通常需由 DBA 操作): " + ex.getMessage(), ex);
}
return "Oracle 用户/模式创建成功(注意:真正的 DB 实例通常由 DBA 管理): " + dbName;
default:
throw new SQLException("不支持的数据库类型: " + driver);
}
} catch (SQLException se) {
// 透传 SQLException调用方会拿到 message 并反馈给前端
throw se;
} catch (Exception e) {
throw new SQLException("创建数据库时发生异常: " + e.getMessage(), e);
}
}
public static String createLocalDatabase(String driver, String dbName) throws SQLException {
switch (driver.toLowerCase()) {
case "sqlite":
// 创建目录并构造规范化路径(确保路径使用正斜杠)
String dbFileName = dbName.endsWith(".db") ? dbName : (dbName + ".db");
java.nio.file.Path dbDir = java.nio.file.Paths.get(System.getProperty("user.home"), ".axis_innovators_box", "databases");
try {
java.nio.file.Files.createDirectories(dbDir);
} catch (Exception e) {
throw new SQLException("无法创建数据库目录: " + e.getMessage(), e);
}
String dbPath = dbDir.resolve(dbFileName).toAbsolutePath().toString().replace("\\", "/");
// 显式加载 sqlite 驱动(避免 No suitable driver
try {
Class.forName("org.sqlite.JDBC");
} catch (ClassNotFoundException e) {
throw new SQLException("未找到 sqlite 驱动,请确认 sqlite-jdbc 已加入 classpath", e);
}
// 直接使用 connect 构建连接connect 中会通过 buildConnectionUrl 处理 path
String connectionId = connect("sqlite", "", "", dbPath, "", "");
// 创建示例表
createSampleTables(connectionId);
return connectionId;
case "h2":
java.nio.file.Path h2Dir = java.nio.file.Paths.get(System.getProperty("user.home"), ".axis_innovators_box", "databases");
try {
java.nio.file.Files.createDirectories(h2Dir);
} catch (Exception e) {
throw new SQLException("无法创建数据库目录: " + e.getMessage(), e);
}
String h2Path = h2Dir.resolve(dbName).toAbsolutePath().toString().replace("\\", "/");
String h2Url = "jdbc:h2:file:" + h2Path;
try {
Class.forName("org.h2.Driver");
} catch (ClassNotFoundException e) {
throw new SQLException("未找到 H2 驱动,请确认 h2.jar 已加入 classpath", e);
}
Connection h2Conn = DriverManager.getConnection(h2Url, "sa", "");
String h2ConnectionId = "conn_" + System.currentTimeMillis();
connections.put(h2ConnectionId, h2Conn);
connectionInfo.put(h2ConnectionId, new DatabaseInfo("h2", h2Url, "localhost", "", dbName, "sa"));
createSampleTables(h2ConnectionId);
return h2ConnectionId;
default:
throw new SQLException("不支持创建本地数据库类型: " + driver);
}
}
private static void createSampleTables(String connectionId) throws SQLException {
Connection conn = getConnection(connectionId);
DatabaseInfo info = getConnectionInfo(connectionId);
if ("sqlite".equals(info.driver) || "h2".equals(info.driver)) {
try (Statement stmt = conn.createStatement()) {
// 创建用户表
stmt.execute("CREATE TABLE IF NOT EXISTS users (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"username VARCHAR(50) NOT NULL UNIQUE, " +
"email VARCHAR(100) NOT NULL, " +
"password VARCHAR(100) NOT NULL, " +
"status VARCHAR(20) DEFAULT 'active', " +
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
")");
// 创建产品表
stmt.execute("CREATE TABLE IF NOT EXISTS products (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"name VARCHAR(100) NOT NULL, " +
"description TEXT, " +
"price DECIMAL(10,2) NOT NULL, " +
"stock INTEGER DEFAULT 0, " +
"category VARCHAR(50), " +
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
")");
// 创建订单表
stmt.execute("CREATE TABLE IF NOT EXISTS orders (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"user_id INTEGER, " +
"product_id INTEGER, " +
"quantity INTEGER NOT NULL, " +
"total_price DECIMAL(10,2) NOT NULL, " +
"status VARCHAR(20) DEFAULT 'pending', " +
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
"FOREIGN KEY (user_id) REFERENCES users(id), " +
"FOREIGN KEY (product_id) REFERENCES products(id)" +
")");
// 插入示例数据
insertSampleData(conn);
}
}
}
private static void insertSampleData(Connection conn) throws SQLException {
// 检查是否已有数据
try (Statement checkStmt = conn.createStatement();
ResultSet rs = checkStmt.executeQuery("SELECT COUNT(*) FROM users")) {
if (rs.next() && rs.getInt(1) == 0) {
// 插入用户数据
try (PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO users (username, email, password) VALUES (?, ?, ?)")) {
String[][] users = {
{"admin", "admin@example.com", "password123"},
{"user1", "user1@example.com", "password123"},
{"user2", "user2@example.com", "password123"}
};
for (String[] user : users) {
pstmt.setString(1, user[0]);
pstmt.setString(2, user[1]);
pstmt.setString(3, user[2]);
pstmt.executeUpdate();
}
}
// 插入产品数据
try (PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO products (name, description, price, stock, category) VALUES (?, ?, ?, ?, ?)")) {
Object[][] products = {
{"笔记本电脑", "高性能笔记本电脑", 5999.99, 50, "电子"},
{"智能手机", "最新款智能手机", 3999.99, 100, "电子"},
{"办公椅", "舒适办公椅", 299.99, 30, "家居"},
{"咖啡机", "全自动咖啡机", 899.99, 20, "家电"}
};
for (Object[] product : products) {
pstmt.setString(1, (String) product[0]);
pstmt.setString(2, (String) product[1]);
pstmt.setDouble(3, (Double) product[2]);
pstmt.setInt(4, (Integer) product[3]);
pstmt.setString(5, (String) product[4]);
pstmt.executeUpdate();
}
}
}
}
}
}

View File

@@ -43,32 +43,33 @@ public class RegistrationSettingsItem extends WindowsJDialog {
private final AxisInnovatorsBox mainWindow;
static {
RegistrationSettingsItem registrationSettingsItem = new RegistrationSettingsItem();
JPanel pluginPanel = createPluginSettingsPanel();
JPanel generalPanel = createGeneralSettingsPanel();
JPanel aboutPanel = createAboutPanel();
JPanel themePanel = createThemePanel();
registrationSettingsItem.addSettings(
generalPanel, language.getText("settings.2.title"),
null, language.getText("settings.2.tip"), "system:settings_appearance_item"
);
registrationSettingsItem.addSettings(
pluginPanel, language.getText("settings.1.title"),
null, language.getText("settings.1.tip"), "system:settings_plugins_item"
);
registrationSettingsItem.addSettings(
themePanel, language.getText("settings.4.title"),
null, language.getText("settings.4.tip"), "system:settings_theme_item"
);
registrationSettingsItem.addSettings(
aboutPanel, language.getText("settings.3.title"),
null, language.getText("settings.3.tip"), "system:settings_information_item"
);
registrationSettingsItemList.add(
registrationSettingsItem
);
//RegistrationSettingsItem registrationSettingsItem = new RegistrationSettingsItem();
//JPanel pluginPanel = createPluginSettingsPanel();
//JPanel generalPanel = createGeneralSettingsPanel();
//JPanel aboutPanel = createAboutPanel();
//JPanel themePanel = createThemePanel();
//
//registrationSettingsItem.addSettings(
// generalPanel, language.getText("settings.2.title"),
// null, language.getText("settings.2.tip"), "system:settings_appearance_item"
//);
//registrationSettingsItem.addSettings(
// pluginPanel, language.getText("settings.1.title"),
// null, language.getText("settings.1.tip"), "system:settings_plugins_item"
//);
//registrationSettingsItem.addSettings(
// themePanel, language.getText("settings.4.title"),
// null, language.getText("settings.4.tip"), "system:settings_theme_item"
//);
//registrationSettingsItem.addSettings(
// aboutPanel, language.getText("settings.3.title"),
// null, language.getText("settings.3.tip"), "system:settings_information_item"
//);
//
//registrationSettingsItemList.add(
// registrationSettingsItem
//);
overloading();
}
public static void overloading() {
@@ -624,7 +625,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
MainWindow mainWindow = getMainWindow();
if (mainWindow != null) {
mainWindow.setBackgroundWithGlassEffect(bgImage, blurAmount,1.0f);
logger.info("图片背景已应用,模糊度: " + blurAmount);
logger.info("图片背景已应用,模糊度: {}", blurAmount);
// 保存设置到 StateManager
String backgroundPath = (String) backgroundPreview.getClientProperty("backgroundPath");
@@ -957,7 +958,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
settingsManager.saveState("background.path", backgroundPath);
settingsManager.saveState("background.blur", blurAmount);
settingsManager.saveState("background.enabled", true);
logger.info("背景设置已保存: " + backgroundPath + ", 模糊度: " + blurAmount);
logger.info("背景设置已保存: {}, 模糊度: {}", backgroundPath, blurAmount);
}
}

View File

@@ -82,6 +82,16 @@ public class RegistrationTool {
}
}));
programmingToolsCategory.addTool(new MainWindow.ToolItem("数据库管理工具", "programming/programming_dark.png",
"用于管理数据库" +
"\n作者tzdwindows 7", ++id, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
// Window owner = SwingUtilities.windowForComponent((Component) e.getSource());
MainApplication.popupDataBaseWindow();
}
}));
MainWindow.ToolCategory aICategory = new MainWindow.ToolCategory("AI工具",
"ai/ai.png",
"人工智能/大语言模型");

View File

@@ -938,7 +938,15 @@ public class MainWindow extends JFrame {
// ---------- 工具卡/面板 ----------
private JPanel createToolsPanel(ToolCategory category) {
JPanel panel = new JPanel(new WrapLayout(FlowLayout.LEFT, 16, 16));
JPanel panel = new JPanel(new WrapLayout(FlowLayout.LEFT, 16, 16)) {
@Override
public Dimension getPreferredSize() {
// 计算容器宽度以恰好容纳3个卡片和间隙
int cardWidth = 240; // 卡片宽度
int gap = 16;
return new Dimension(cardWidth * 3 + gap * 2, super.getPreferredSize().height);
}
};
panel.setOpaque(false);
panel.setBorder(null);

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB