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

@@ -103,6 +103,11 @@ dependencies {
implementation 'me.friwi:jcefmaven:122.1.10'
implementation 'com.alphacephei:vosk:0.3.45'
implementation 'net.java.dev.jna:jna:5.13.0'
implementation 'com.h2database:h2:2.2.220'
implementation 'org.xerial:sqlite-jdbc:3.41.2.1'
implementation 'mysql:mysql-connector-java:8.0.33'
implementation 'org.postgresql:postgresql:42.6.0'
implementation 'net.java.dev.jna:jna-platform:5.13.0'
implementation 'org.apache.commons:commons-math3:3.6.1'
implementation 'com.google.guava:guava:31.1-jre'

View File

@@ -0,0 +1,793 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>数据库管理工具 - Axis Innovators Box</title>
<style>
/* ---------- 主题变量 & 基本样式 ---------- */
:root{
--primary-color: #2b8bef;
--accent-color: #49c28a;
--bg: #ffffff;
--surface: #f7fbff;
--text: #222;
--muted: #6b6b6b;
--border: #e6e9ee;
--shadow: 0 6px 20px rgba(34,34,34,0.06);
--hover: #f4f7fb;
}
[data-theme="dark"]{
--primary-color: #49a6ff;
--accent-color: #2ecc71;
--bg: #0f1115;
--surface: #121317;
--text: #e6edf3;
--muted: #9aa3ad;
--border: #24272b;
--shadow: 0 6px 20px rgba(0,0,0,0.6);
--hover: rgba(255,255,255,0.02);
}
[data-theme="light"]{
--primary-color: #2b8bef;
--accent-color: #49c28a;
--bg: #ffffff;
--surface: #f7fbff;
--text: #222;
--muted: #6b6b6b;
--border: #e6e9ee;
--shadow: 0 6px 20px rgba(34,34,34,0.06);
--hover: #f4f7fb;
}
*{box-sizing:border-box;margin:0;padding:0;transition:background-color .18s,color .18s,border-color .18s}
html,body{height:100%}
body{
font-family:Inter, 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background:var(--bg);
color:var(--text);
display:flex;
flex-direction:column;
height:100vh;
overflow:hidden;
}
/* ---------- Header ---------- */
header{
display:flex;
align-items:center;
justify-content:space-between;
padding:12px 18px;
background:var(--surface);
border-bottom:1px solid var(--border);
box-shadow:var(--shadow);
z-index:100;
}
.logo{display:flex;gap:12px;align-items:center}
.logo .mark{width:36px;height:36px;border-radius:10px;background:linear-gradient(180deg,var(--primary-color),#1f7ad8);display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;font-size:18px}
.logo .title{font-weight:700;font-size:18px}
.header-controls{display:flex;gap:12px;align-items:center}
.theme-toggle{background:transparent;border:1px solid var(--border);padding:6px 8px;border-radius:8px;cursor:pointer}
.user-profile{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:10px;background:transparent;border:1px solid transparent}
.avatar{width:34px;height:34px;border-radius:8px;background:var(--primary-color);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700}
/* ---------- 主区布局 ---------- */
.main-container{display:flex;flex:1;overflow:hidden}
/* 侧栏 */
.sidebar{width:280px;display:flex;flex-direction:column;background:var(--surface);border-right:1px solid var(--border);padding:12px;gap:12px}
.sidebar-section{background:transparent}
.sidebar-title{font-size:12px;color:var(--muted);text-transform:uppercase;padding:4px 8px;font-weight:600}
.sidebar-item{display:flex;align-items:center;gap:10px;padding:8px;border-radius:8px;color:var(--text);text-decoration:none;cursor:pointer}
.sidebar-item:hover{background:var(--hover)}
.sidebar-icon{width:20px;height:20px;display:inline-flex;align-items:center;justify-content:center}
/* 数据库/表 列表区域 */
.database-list{display:flex;flex-direction:column;gap:8px;padding:6px 4px;max-height:160px;overflow:auto}
.tables-list{flex:1;overflow:auto;padding:6px 4px;border-radius:8px}
.table-item{display:flex;align-items:center;justify-content:space-between;padding:8px;border-radius:8px}
.table-item .meta{display:flex;flex-direction:column}
.table-item .name{font-weight:600}
.table-item .sub{font-size:12px;color:var(--muted)}
/* 工具区域(可展开小图标) */
.tools-grid{display:grid;grid-template-columns:repeat(1,1fr);gap:8px}
.tool-btn{display:flex;align-items:center;gap:8px;padding:8px;border-radius:8px;background:transparent;border:1px solid transparent;cursor:pointer}
.tool-btn:hover{background:var(--hover)}
/* 内容区 */
.content-area{flex:1;display:flex;flex-direction:column;overflow:hidden}
.toolbar{display:flex;gap:10px;padding:12px;border-bottom:1px solid var(--border);background:var(--surface)}
.btn{padding:8px 12px;border-radius:10px;border:1px solid transparent;cursor:pointer;font-weight:600}
.btn-primary{background:linear-gradient(180deg,var(--primary-color),#1f7ad8);color:#fff;box-shadow:0 8px 20px rgba(33,120,210,0.12)}
.btn-outline{background:transparent;border:1px solid var(--border);color:var(--text)}
.query-editor-container{display:flex;gap:12px;padding:12px;flex:1;overflow:hidden}
.query-editor{width:45%;display:flex;flex-direction:column;border-radius:10px;border:1px solid var(--border);background:var(--bg);overflow:hidden}
.editor-toolbar{padding:10px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}
.editor-content{flex:1;padding:12px;font-family:monospace;font-size:13px;border:none;outline:none;background:transparent;resize:none;min-height:220px}
.results-container{flex:1;display:flex;flex-direction:column;border-radius:10px;border:1px solid var(--border);background:var(--bg);overflow:auto}
.results-header{padding:12px;border-bottom:1px solid var(--border);font-weight:700}
.results-content{padding:12px;overflow:auto}
/* 表格 */
table{width:100%;border-collapse:collapse}
th,td{padding:10px;border-bottom:1px solid var(--border);text-align:left;font-size:13px}
th{background:var(--surface);position:sticky;top:0}
.status-bar{padding:8px 12px;border-top:1px solid var(--border);background:var(--surface);display:flex;justify-content:space-between;font-size:13px;color:var(--muted)}
/* 弹窗(通用) */
.overlay{position:fixed;inset:0;background:rgba(0,0,0,0.45);display:none;z-index:2000}
.modal{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);background:var(--bg);border-radius:12px;padding:16px;box-shadow:var(--shadow);z-index:2001;min-width:360px;max-width:90%;display:none}
.modal .header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;font-weight:700}
.modal .body{max-height:60vh;overflow:auto;padding-top:8px}
.modal .footer{display:flex;justify-content:flex-end;gap:8px;margin-top:12px}
/* 响应式小屏 */
@media (max-width:900px){
.sidebar{display:none}
.query-editor{width:100%}
.query-editor-container{flex-direction:column}
}
/* 小图标样式SVG 作为图标) */
.svg-icon{width:18px;height:18px;display:inline-block;vertical-align:middle;fill:currentColor}
</style>
</head>
<body data-theme="light">
<header>
<div class="logo">
<div class="mark">AI</div>
<div>
<div class="title">Axis Innovators Box</div>
<div style="font-size:12px;color:var(--muted)">数据库管理工具</div>
</div>
</div>
<div class="header-controls">
<button id="themeToggle" class="theme-toggle" title="切换主题">☀️/🌙</button>
<div class="user-profile">
<div class="avatar">JD</div>
<div style="font-size:13px">开发者</div>
</div>
</div>
</header>
<div class="main-container">
<!-- 侧栏 -->
<aside class="sidebar" role="navigation">
<div class="sidebar-section">
<div class="sidebar-title">连接</div>
<div class="sidebar-item" id="openConnect">
<div class="sidebar-icon">
<!-- plug svg -->
<svg class="svg-icon" viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 1 7.07 0l1.41 1.41 1.41-1.41a3 3 0 0 0-4.24-4.24L14.24 9.17"/></svg>
</div>
<div>连接 / 创建 本地数据库</div>
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-title">数据库</div>
<div class="database-list" id="databaseList">
<!-- 动态填充 -->
</div>
</div>
<div class="sidebar-section" style="display:flex;flex-direction:column;flex:1;min-height:0">
<div class="sidebar-title"></div>
<div class="tables-list" id="tablesList">
<div style="padding:15px;text-align:center;color:var(--muted)">请连接数据库</div>
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-title">工具</div>
<div class="tools-grid">
<button class="tool-btn" data-action="queryAnalyzer" title="查询分析器">
<span class="sidebar-icon">
<svg class="svg-icon" viewBox="0 0 24 24"><path d="M3 3h18v4H3zM3 10h12v4H3zM3 17h6v4H3z"/></svg>
</span>
<span>查询分析器</span>
</button>
<button class="tool-btn" data-action="exportData" title="导出数据">
<span class="sidebar-icon">
<svg class="svg-icon" viewBox="0 0 24 24"><path d="M12 3v12M5 10l7-7 7 7M5 21h14"/></svg>
</span>
<span>导出数据</span>
</button>
<button class="tool-btn" data-action="importCsv" title="CSV 导入">
<span class="sidebar-icon">
<svg class="svg-icon" viewBox="0 0 24 24"><path d="M12 3v12M5 10l7-7 7 7M21 21H3"/></svg>
</span>
<span>CSV 导入</span>
</button>
<button class="tool-btn" data-action="erDiagram" title="ER 图">
<span class="sidebar-icon">
<svg class="svg-icon" viewBox="0 0 24 24"><path d="M4 6h16v12H4zM8 6v12M16 6v12"/></svg>
</span>
<span>ER 图</span>
</button>
<button class="tool-btn" data-action="performance" title="性能监控">
<span class="sidebar-icon">
<svg class="svg-icon" viewBox="0 0 24 24"><path d="M3 12h3l3-8 4 16 4-10 3 4h4"/></svg>
</span>
<span>性能监控</span>
</button>
<button class="tool-btn" data-action="userMgmt" title="用户管理">
<span class="sidebar-icon">
<svg class="svg-icon" viewBox="0 0 24 24"><path d="M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm-8 9a8 8 0 0 1 16 0"/></svg>
</span>
<span>用户管理</span>
</button>
</div>
</div>
</aside>
<!-- 内容区 -->
<main class="content-area">
<div class="toolbar">
<button id="executeQueryBtn" class="btn btn-primary"><svg class="svg-icon" viewBox="0 0 24 24"><path d="M5 3v18l15-9z"/></svg> 执行查询</button>
<button id="refreshTablesBtn" class="btn btn-outline"><svg class="svg-icon" viewBox="0 0 24 24"><path d="M21 12a9 9 0 1 0-2.6 6.12"/></svg> 刷新表</button>
<button id="newQueryBtn" class="btn btn-outline"><svg class="svg-icon" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg> 新建查询</button>
<button id="formatQueryBtn" class="btn btn-outline"><svg class="svg-icon" viewBox="0 0 24 24"><path d="M3 6h18M3 12h18M3 18h18"/></svg> 格式化</button>
<div style="flex:1"></div>
<div id="statusSmall" style="font-size:13px;color:var(--muted)">状态: 就绪</div>
</div>
<div class="query-editor-container">
<div class="query-editor">
<div class="editor-toolbar">
<div>查询编辑器</div>
<div style="display:flex;gap:8px">
<button class="btn btn-outline" id="saveQueryBtn">保存</button>
<button class="btn btn-outline" id="explainQueryBtn">解释</button>
</div>
</div>
<textarea id="queryEditor" class="editor-content" placeholder="输入SQL查询语句...">SELECT * FROM users;</textarea>
</div>
<div class="results-container">
<div class="results-header">
<div>查询结果</div>
<div id="resultsInfo" style="font-size:13px;color:var(--muted)"></div>
</div>
<div class="results-content" id="resultsContent">
<div style="text-align:center;padding:40px;color:var(--muted)">
<p>请连接数据库并执行查询以查看结果</p>
</div>
</div>
</div>
</div>
<div class="status-bar">
<div id="statusMessage">就绪</div>
<div id="rowColumnInfo">行: 0, 列: 0</div>
</div>
</main>
</div>
<!-- 事件日志按钮 -->
<button id="showEventsBtn" style="position:fixed;right:20px;bottom:20px;border-radius:50%;width:52px;height:52px;background:var(--primary-color);color:#fff;border:none;box-shadow:var(--shadow);cursor:pointer;z-index:1001">📋</button>
<!-- 弹窗遮罩 -->
<div id="overlay" class="overlay"></div>
<!-- 连接/创建对话框(原有功能,稍微美化) -->
<div id="connectionDialog" class="modal" style="min-width:520px">
<div class="header">数据库连接
<button onclick="hideModal('connectionDialog')" style="background:transparent;border:none;cursor:pointer"></button>
</div>
<div class="body">
<div style="display:flex;gap:12px">
<div style="flex:1">
<div style="margin-bottom:8px"><label>操作</label></div>
<select id="dialogAction" class="form-control" style="width:100%;padding:8px;border-radius:6px;border:1px solid var(--border)">
<option value="connect">连接数据库</option>
<option value="create">创建本地数据库</option>
</select>
<div id="connectFields" style="margin-top:12px">
<div style="margin-bottom:8px"><label>数据库类型</label></div>
<select id="dbDriver" style="width:100%;padding:8px;border-radius:6px;border:1px solid var(--border)">
<option value="mysql">MySQL</option>
<option value="postgresql">PostgreSQL</option>
<option value="sqlite">SQLite</option>
<option value="oracle">Oracle</option>
<option value="h2">H2</option>
</select>
<div style="display:flex;gap:8px;margin-top:8px">
<input id="dbHost" placeholder="主机" style="flex:1;padding:8px;border-radius:6px;border:1px solid var(--border)" value="localhost">
<input id="dbPort" placeholder="端口" style="width:120px;padding:8px;border-radius:6px;border:1px solid var(--border)" value="3306">
</div>
<div style="display:flex;gap:8px;margin-top:8px">
<input id="dbName" placeholder="数据库名" style="flex:1;padding:8px;border-radius:6px;border:1px solid var(--border)" value="test">
<input id="dbUsername" placeholder="用户名" style="width:160px;padding:8px;border-radius:6px;border:1px solid var(--border)" value="root">
</div>
<div style="margin-top:8px"><input id="dbPassword" placeholder="密码" type="password" style="width:100%;padding:8px;border-radius:6px;border:1px solid var(--border)"></div>
</div>
<div id="createFields" style="margin-top:12px;display:none">
<div style="margin-bottom:8px"><label>本地数据库类型</label></div>
<select id="localDbDriver" style="width:100%;padding:8px;border-radius:6px;border:1px solid var(--border)">
<option value="sqlite">SQLite</option>
<option value="h2">H2 Database</option>
</select>
<div style="margin-top:8px"><input id="localDbName" placeholder="数据库名称" style="width:100%;padding:8px;border-radius:6px;border:1px solid var(--border)" value="my_database"></div>
<div style="margin-top:8px"><label>存储路径</label><input id="localDbPath" readonly style="width:100%;padding:8px;border-radius:6px;border:1px solid var(--border);background:var(--hover)"></div>
<div style="margin-top:8px"><label><input type="checkbox" id="includeSampleData" checked> 包含示例数据</label></div>
</div>
</div>
</div>
</div>
<div class="footer">
<button class="btn btn-outline" onclick="hideModal('connectionDialog')">取消</button>
<button class="btn btn-primary" id="confirmConnectionBtn">确认</button>
</div>
</div>
<!-- 通用工具模态(动态使用) -->
<div id="toolModal" class="modal" style="min-width:520px">
<div class="header" id="toolModalTitle">工具</div>
<div class="body" id="toolModalBody"></div>
<div class="footer" id="toolModalFooter"><button class="btn btn-outline" onclick="hideModal('toolModal')">关闭</button></div>
</div>
<!-- 事件日志面板 -->
<div id="eventLog" class="modal" style="right:20px;bottom:20px;left:auto;top:auto;transform:none;display:none;min-width:320px">
<div class="header">事件日志 <button onclick="document.getElementById('eventLog').style.display='none'"></button></div>
<div class="body" id="eventLogContent" style="max-height:260px;overflow:auto"></div>
</div>
<script>
// Java 通信对象(与你的 Java 交互)
const JavaBridge = {
sendRequest: function(request, callback) {
if (window.cefQuery) {
// 保持原有行为
window.cefQuery({
request: JSON.stringify(request),
onSuccess: function(response) {
if (callback) {
try { callback(JSON.parse(response), null); } catch(e) { callback(response, null); }
}
},
onFailure: function(error_code, error_message) {
if (callback) callback(null, {code: error_code, message: error_message});
}
});
} else {
// 开发环境模拟
console.log("Java Request:", request);
// 模拟错误/成功
setTimeout(() => {
if (request.type === 'createLocalDatabase') {
callback({status:'success', connectionId:'mock_conn_' + Date.now(), database: request.dbName, driver: request.driver}, null);
} else if (request.type === 'connectDatabase') {
callback({status:'success', connectionId:'mock_conn_' + Date.now(), database: request.database, driver: request.driver}, null);
} else {
callback({status:'success', message:'模拟响应'}, null);
}
}, 350);
}
}
};
// UI 工具函数
function showModal(id){
document.getElementById('overlay').style.display='block';
document.getElementById(id).style.display='block';
}
function hideModal(id){
document.getElementById('overlay').style.display='none';
document.getElementById(id).style.display='none';
}
function addEventLog(title,desc){
const el = document.createElement('div');
const now = new Date();
el.innerHTML = `<div style="padding:8px;border-bottom:1px dashed var(--border)"><div style="font-weight:700">${title}</div><div style="font-size:12px;color:var(--muted)">${desc}</div><div style="font-size:11px;color:var(--muted)">${now.toLocaleString()}</div></div>`;
const content = document.getElementById('eventLogContent');
content.prepend(el);
// 保留最多 40 条
while (content.children.length > 40) content.removeChild(content.lastChild);
}
// 主题支持auto / light / dark
(function themeInit(){
const body = document.body;
let mode = localStorage.getItem('dbToolTheme') || 'light';
applyTheme(mode);
document.getElementById('themeToggle').addEventListener('click', () => {
mode = (mode === 'light') ? 'dark' : 'light';
localStorage.setItem('dbToolTheme', mode);
applyTheme(mode);
});
function applyTheme(m){
body.setAttribute('data-theme', m);
document.getElementById('themeToggle').textContent = m === 'light' ? '🌙' : '☀️';
}
})();
// 绑定打开连接对话框
document.getElementById('openConnect').addEventListener('click', () => {
showModal('connectionDialog');
// 默认显示 connect 区块
document.getElementById('dialogAction').value = 'connect';
toggleConnectionDialog();
});
// connection dialog toggle fields
document.getElementById('dialogAction').addEventListener('change', toggleConnectionDialog);
function toggleConnectionDialog(){
const action = document.getElementById('dialogAction').value;
const connectFields = document.getElementById('connectFields');
const createFields = document.getElementById('createFields');
if(action === 'connect'){
connectFields.style.display='block';
createFields.style.display='none';
} else {
connectFields.style.display='none';
createFields.style.display='block';
// 更新默认路径
const dbName = document.getElementById('localDbName').value || 'my_database';
const driver = document.getElementById('localDbDriver').value;
const extension = driver === 'h2' ? '' : '.db';
document.getElementById('localDbPath').value = `~/.axis_innovators_box/databases/${dbName}${extension}`;
}
}
// localDbName 和 localDbDriver 监听更新路径
document.getElementById('localDbName').addEventListener('input', () => {
const dbName = document.getElementById('localDbName').value || 'my_database';
const driver = document.getElementById('localDbDriver').value;
const extension = driver === 'h2' ? '' : '.db';
document.getElementById('localDbPath').value = `~/.axis_innovators_box/databases/${dbName}${extension}`;
});
document.getElementById('localDbDriver').addEventListener('change', () => {
const dbName = document.getElementById('localDbName').value || 'my_database';
const driver = document.getElementById('localDbDriver').value;
const extension = driver === 'h2' ? '' : '.db';
document.getElementById('localDbPath').value = `~/.axis_innovators_box/databases/${dbName}${extension}`;
});
// 确认连接 / 创建
document.getElementById('confirmConnectionBtn').addEventListener('click', () => {
const action = document.getElementById('dialogAction').value;
if(action === 'connect'){
const driver = document.getElementById('dbDriver').value;
const host = document.getElementById('dbHost').value;
const port = document.getElementById('dbPort').value;
const database = document.getElementById('dbName').value;
const username = document.getElementById('dbUsername').value;
const password = document.getElementById('dbPassword').value;
setStatus('正在连接数据库...');
JavaBridge.sendRequest({
type: 'connectDatabase',
driver, host, port, database, username, password
}, (resp, err) => {
if(err){ setStatus('连接失败'); addEventLog('连接失败', err.message); alert('连接失败: ' + err.message); return; }
if(resp && resp.status === 'success'){
setStatus('连接成功');
addEventLog('连接', `已连接 ${resp.database} (${resp.driver})`);
hideModal('connectionDialog');
// 更新列表
onConnected(resp.connectionId, resp.database, resp.driver);
} else {
setStatus('连接失败'); alert('连接失败: ' + (resp && resp.message || '未知错误'));
}
});
} else {
// 创建本地数据库
const driver = document.getElementById('localDbDriver').value;
const dbName = document.getElementById('localDbName').value || 'my_database';
const includeSampleData = document.getElementById('includeSampleData').checked;
setStatus('正在创建本地数据库...');
JavaBridge.sendRequest({
type: 'createLocalDatabase',
driver, dbName, includeSampleData
}, (resp, err) => {
if(err){ setStatus('创建失败'); addEventLog('创建失败', err.message); alert('创建失败: ' + err.message); return; }
if(resp && resp.status === 'success'){
setStatus('创建成功');
addEventLog('创建数据库', `${dbName} (${driver})`);
hideModal('connectionDialog');
onConnected(resp.connectionId, resp.database, resp.driver);
} else {
setStatus('创建失败'); alert('创建失败: ' + (resp && resp.message || '未知错误'));
}
});
}
});
// 当连接成功后更新 UI填充数据库与表占位
function onConnected(connectionId, database, driver){
window.isConnected = true;
window.currentConnectionId = connectionId;
// 更新数据库列表(这是简化示例:仅显示当前)
const dbList = document.getElementById('databaseList');
dbList.innerHTML = `<div style="padding:8px;border-radius:8px;background:var(--hover)"><div style="font-weight:700">${database}</div><div style="font-size:12px;color:var(--muted)">${driver}</div></div>`;
// 加载表(请求 Java
loadTables();
}
// 加载表
function loadTables(){
if(!window.currentConnectionId) return;
setStatus('加载表列表...');
JavaBridge.sendRequest({type:'getTables', connectionId: window.currentConnectionId}, (resp, err) => {
if(err){ setStatus('加载表失败'); addEventLog('加载表失败', err.message); return; }
if(resp && resp.status === 'success'){
renderTables(resp.tables || []);
setStatus('表列表已加载');
} else {
setStatus('加载表失败');
}
});
}
function renderTables(tables){
const list = document.getElementById('tablesList');
list.innerHTML = '';
if(!tables || tables.length===0){
list.innerHTML = '<div style="padding:15px;text-align:center;color:var(--muted)">数据库中没有表</div>';
return;
}
tables.forEach(t => {
const item = document.createElement('div');
item.className = 'table-item';
item.innerHTML = `<div class="meta"><div class="name">${t.name}</div><div class="sub">${t.type}${t.rows} 行</div></div>
<div style="display:flex;gap:6px">
<button class="btn btn-outline" data-action="view" data-table="${t.name}">查看</button>
<button class="btn btn-outline" data-action="structure" data-table="${t.name}">结构</button>
</div>`;
list.appendChild(item);
});
// 绑定内部按钮
list.querySelectorAll('button[data-action="view"]').forEach(b=>{
b.addEventListener('click',(e)=>{
e.stopPropagation();
const table = b.dataset.table;
loadTableData(table);
});
});
list.querySelectorAll('button[data-action="structure"]').forEach(b=>{
b.addEventListener('click',(e)=>{
e.stopPropagation();
const table = b.dataset.table;
showTableStructure(table);
});
});
}
// 加载表数据
function loadTableData(tableName){
if(!window.currentConnectionId) { alert('未连接数据库'); return; }
setStatus(`正在加载表 ${tableName} 数据...`);
JavaBridge.sendRequest({type:'getTableData', connectionId: window.currentConnectionId, tableName, limit:50, offset:0}, (resp, err) => {
if(err){ setStatus('加载表数据失败'); alert('加载表数据失败:' + err.message); return; }
if(resp && resp.status === 'success'){
renderTableData({tableName, columns: resp.columns, data: resp.data, total: resp.total, offset: resp.offset, limit: resp.limit});
setStatus('已加载数据');
document.getElementById('queryEditor').value = `SELECT * FROM ${tableName} LIMIT 50 OFFSET 0;`;
} else {
setStatus('加载表数据失败');
alert('加载表数据失败: ' + (resp && resp.message || '未知错误'));
}
});
}
function renderTableData(data){
const content = document.getElementById('resultsContent');
if(!data.columns || !data.data){
content.innerHTML = `<div style="padding:20px;color:var(--muted)">没有数据</div>`;
return;
}
let html = '<div class="table-container"><table><thead><tr>';
data.columns.forEach(c => html += `<th>${c}</th>`);
html += '</tr></thead><tbody>';
data.data.forEach(row=>{
html += '<tr>';
data.columns.forEach(col=>{
const v = row[col];
html += `<td>${v === null || v === undefined ? '<span style="color:var(--muted);font-style:italic">NULL</span>' : escapeHtml(String(v))}</td>`;
});
html += '</tr>';
});
html += '</tbody></table></div>';
content.innerHTML = html;
document.getElementById('rowColumnInfo').textContent = `行: ${data.data.length}, 列: ${data.columns.length}`;
}
// 查看表结构
function showTableStructure(tableName){
if(!window.currentConnectionId) return alert('未连接数据库');
setStatus('查询表结构...');
JavaBridge.sendRequest({type:'getTableStructure', connectionId:window.currentConnectionId, tableName}, (resp, err) => {
if(err){ setStatus('获取失败'); alert('获取表结构失败:'+err.message); return; }
if(resp && resp.status === 'success'){
openToolModal(`表结构: ${tableName}`, renderStructureHtml(resp));
} else {
alert('获取表结构失败: ' + (resp && resp.message || '未知错误'));
}
});
}
function renderStructureHtml(resp){
let html = `<div style="margin-bottom:8px"><strong>表名:</strong> ${escapeHtml(resp.tableName || '')}</div>`;
if(resp.columns && resp.columns.length>0){
html += '<table><thead><tr><th>列名</th><th>类型</th><th>大小</th><th>可空</th><th>默认值</th></tr></thead><tbody>';
resp.columns.forEach(c=>{
html += `<tr><td>${escapeHtml(c.name)}</td><td>${escapeHtml(c.type)}</td><td>${c.size||''}</td><td>${c.nullable ? '是' : '否'}</td><td>${escapeHtml(c.defaultValue||'')}</td></tr>`;
});
html += '</tbody></table>';
} else {
html += `<div style="padding:12px;color:var(--muted)">没有列信息</div>`;
}
return html;
}
// 工具按钮统一处理
document.querySelectorAll('.tool-btn').forEach(btn=>{
btn.addEventListener('click', ()=>{
const action = btn.dataset.action;
switch(action){
case 'queryAnalyzer':
openToolModal('查询分析器', `<div>查询分析器(本地模拟)<div style="margin-top:12px"><textarea id="analyzerSql" style="width:100%;min-height:120px;padding:8px;border:1px solid var(--border)">${document.getElementById('queryEditor').value}</textarea></div><div style="margin-top:8px"><button class="btn btn-primary" id="analyzeBtn">分析</button></div></div>`);
// 绑定分析按钮
setTimeout(()=>document.getElementById('analyzeBtn').addEventListener('click', ()=>{
const sql = document.getElementById('analyzerSql').value;
// 这里演示调用 Java 后端接口(实际需要后端支持)
JavaBridge.sendRequest({type:'analyzeQuery', connectionId: window.currentConnectionId, query: sql}, (resp,err)=>{
if(err) return alert('分析失败: ' + err.message);
openToolModal('查询分析结果', `<pre style="white-space:pre-wrap">${escapeHtml(JSON.stringify(resp, null, 2))}</pre>`);
});
}),60);
break;
case 'exportData':
openToolModal('导出数据', `<div>选择导出格式:<div style="margin-top:8px"><select id="exportFormat" style="padding:8px;border:1px solid var(--border)"><option value="csv">CSV</option><option value="json">JSON</option></select></div><div style="margin-top:8px">表名:<input id="exportTable" style="width:100%;padding:8px;border:1px solid var(--border)"></div><div style="margin-top:8px"><button class="btn btn-primary" id="doExport">开始导出</button></div></div>`);
setTimeout(()=>document.getElementById('doExport').addEventListener('click', ()=>{
const format = document.getElementById('exportFormat').value;
const table = document.getElementById('exportTable').value;
if(!table) return alert('请输入表名');
JavaBridge.sendRequest({type:'exportData', connectionId: window.currentConnectionId, table, format}, (resp,err)=>{
if(err) return alert('导出失败: ' + err.message);
alert('导出触发成功(请在 Java 层实现具体保存)');
addEventLog('导出', `${table} 导出为 ${format}`);
});
}),60);
break;
case 'importCsv':
openToolModal('CSV 导入', `<div>CSV 导入(请先确保文件已存在于应用可访问位置)<div style="margin-top:8px">目标表:<input id="importTable" style="width:100%;padding:8px;border:1px solid var(--border)"></div><div style="margin-top:8px">文件路径:<input id="importPath" placeholder="C:/path/file.csv 或 /home/.../file.csv" style="width:100%;padding:8px;border:1px solid var(--border)"></div><div style="margin-top:8px"><button class="btn btn-primary" id="doImport">开始导入</button></div></div>`);
setTimeout(()=>document.getElementById('doImport').addEventListener('click', ()=>{
const table = document.getElementById('importTable').value;
const path = document.getElementById('importPath').value;
if(!table || !path) return alert('请填写表名和文件路径');
JavaBridge.sendRequest({type:'importCsv', connectionId: window.currentConnectionId, table, path}, (resp,err)=>{
if(err) return alert('导入失败: ' + err.message);
alert('导入触发成功(请在 Java 层实现)');
addEventLog('导入', `${path}${table}`);
});
}),60);
break;
case 'erDiagram':
openToolModal('ER 图', `<div>生成 ER 图(演示)<div style="margin-top:12px;color:var(--muted)">请在后端实现 ER 图生成并返回 SVG/图片。</div><div style="margin-top:12px"><button class="btn btn-primary" id="genEr">生成 ER 图</button></div></div>`);
setTimeout(()=>document.getElementById('genEr').addEventListener('click', ()=>{
JavaBridge.sendRequest({type:'generateEr', connectionId: window.currentConnectionId}, (resp,err)=>{
if(err) return alert('生成失败: '+err.message);
openToolModal('ER 图', `<div>${escapeHtml(JSON.stringify(resp))}</div>`);
});
}),60);
break;
case 'performance':
openToolModal('性能监控', `<div>性能监控(实时)<div id="perfContent" style="margin-top:8px;color:var(--muted)">等待数据...</div><div style="margin-top:8px"><button class="btn btn-primary" id="refreshPerf">刷新</button></div></div>`);
setTimeout(()=>document.getElementById('refreshPerf').addEventListener('click', ()=>{
JavaBridge.sendRequest({type:'analyzePerformance', connectionId: window.currentConnectionId}, (resp,err)=>{
if(err) return alert('获取失败: ' + err.message);
document.getElementById('perfContent').innerHTML = `<pre style="white-space:pre-wrap">${escapeHtml(JSON.stringify(resp, null, 2))}</pre>`;
});
}),60);
break;
case 'userMgmt':
openToolModal('用户管理', `<div>用户管理(示例)<div style="margin-top:8px"><button class="btn btn-primary" id="listUsers">列出用户</button></div><div id="usersList" style="margin-top:8px"></div></div>`);
setTimeout(()=>document.getElementById('listUsers').addEventListener('click', ()=>{
JavaBridge.sendRequest({type:'listUsers', connectionId: window.currentConnectionId}, (resp,err)=>{
if(err) return alert('失败: '+err.message);
document.getElementById('usersList').innerHTML = `<pre style="white-space:pre-wrap">${escapeHtml(JSON.stringify(resp,null,2))}</pre>`;
});
}),60);
break;
default:
alert('未实现的工具: ' + action);
}
});
});
function openToolModal(title, bodyHtml){
document.getElementById('toolModalTitle').textContent = title;
document.getElementById('toolModalBody').innerHTML = bodyHtml;
document.getElementById('toolModalFooter').innerHTML = `<button class="btn btn-outline" onclick="hideModal('toolModal')">关闭</button>`;
showModal('toolModal');
}
// 查询相关按钮
document.getElementById('executeQueryBtn').addEventListener('click', ()=>{
const q = document.getElementById('queryEditor').value.trim();
if(!q) return alert('请输入 SQL');
if(!window.currentConnectionId) return alert('请先连接数据库');
setStatus('正在执行查询...');
JavaBridge.sendRequest({type:'executeQuery', connectionId:window.currentConnectionId, query:q}, (resp,err)=>{
if(err){ setStatus('查询失败'); alert('查询失败:'+err.message); return; }
if(resp && resp.status === 'success'){
renderQueryResults(resp);
setStatus('查询成功');
addEventLog('查询', q.slice(0,120));
} else {
setStatus('查询失败');
alert('查询失败: ' + (resp && resp.message || '未知错误'));
}
});
});
function renderQueryResults(resp){
if(resp.columns && resp.data){
// 重用 renderTableData 风格(临时对象)
renderTableData({tableName:'QueryResult', columns: resp.columns, data: resp.data, total: resp.rowCount || resp.data.length, offset:0, limit:resp.data.length});
document.getElementById('resultsInfo').textContent = `行: ${resp.rowCount || resp.data.length} 耗时: ${resp.executionTime || 'N/A'}`;
} else {
document.getElementById('resultsContent').innerHTML = `<div style="padding:12px">操作完成,影响行数:${resp.affectedRows || 0}</div>`;
}
}
// 格式化按钮(保留你原有简单逻辑)
document.getElementById('formatQueryBtn').addEventListener('click', ()=>{
let query = document.getElementById('queryEditor').value;
query = query.replace(/\bSELECT\b/gi, '\nSELECT').replace(/\bFROM\b/gi, '\nFROM')
.replace(/\bWHERE\b/gi, '\nWHERE').replace(/\bORDER BY\b/gi, '\nORDER BY')
.replace(/\bGROUP BY\b/gi, '\nGROUP BY').replace(/\bHAVING\b/gi, '\nHAVING')
.replace(/\bJOIN\b/gi, '\nJOIN').replace(/\bON\b/gi, '\nON').replace(/,/g, ',\n ');
document.getElementById('queryEditor').value = query.trim();
});
// 刷新表
document.getElementById('refreshTablesBtn').addEventListener('click', ()=> loadTables());
// 事件日志按钮
document.getElementById('showEventsBtn').addEventListener('click', ()=> {
const el = document.getElementById('eventLog');
el.style.display = el.style.display === 'block' ? 'none' : 'block';
});
// 状态函数
function setStatus(msg){
document.getElementById('statusMessage').textContent = msg;
document.getElementById('statusSmall').textContent = '状态: ' + msg;
}
// 工具:转义 HTML避免 XSS
function escapeHtml(str){
return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
// 初始化(模拟触发字体加载回调)
document.addEventListener('DOMContentLoaded', ()=>{
// 初始化 localDbPath
const dbName = document.getElementById('localDbName').value || 'my_database';
const driver = document.getElementById('localDbDriver').value;
const extension = driver === 'h2' ? '' : '.db';
document.getElementById('localDbPath').value = `~/.axis_innovators_box/databases/${dbName}${extension}`;
// 触发 fonts loaded兼容旧逻辑
setTimeout(()=> {
JavaBridge.sendRequest({type:'getFonts'}, (resp,err)=>{
if(!err && resp && resp.status === 'success') addEventLog('字体加载', `已加载 ${resp.fonts.length} 字体`);
});
}, 600);
});
</script>
</body>
</html>

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) {
// 写入原始控制台
originalStream.write(b);
buffer.write(b);
// 将内容同时写入对应的静态变量
if (isErrorStream) {
systemErrContent.write(b);
} else {
systemOutContent.write(b);
logger.info(String.valueOf((char) b));
}
// 遇到换行符时刷新缓冲区到日志
if (b == '\n') {
flush();
}
}
@Override
public void write(byte[] b, int off, int len) {
// 写入原始控制台
originalStream.write(b, off, len);
buffer.write(b, off, len);
// 将内容同时写入对应的静态变量
if (isErrorStream) {
systemErrContent.write(b, off, len);
} else {
systemOutContent.write(b, off, len);
String message = new String(b, off, len).trim();
}
// 检查是否包含换行符
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