feat(browser): 添加数据库管理工具和JS对话框处理- 实现了浏览器窗口中的JavaScript alert弹窗拦截与处理
- 添加了数据库连接管理器,支持多种数据库类型(MySQL、PostgreSQL、SQLite、Oracle、H2) - 开发了数据库管理工具的前端界面,包含连接配置、查询编辑器和结果展示 - 支持本地数据库创建与示例数据初始化 - 提供了数据库表结构管理和基础SQL执行功能- 增加了暗色主题切换和响应式布局设计 - 集成了事件日志面板用于调试和状态跟踪
This commit is contained in:
@@ -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'
|
||||
|
||||
793
javascript/DatabaseTool.html
Normal file
793
javascript/DatabaseTool.html
Normal 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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
// 初始化(模拟触发字体加载回调)
|
||||
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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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. 拦截所有新窗口(关键修复点!)
|
||||
|
||||
@@ -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. 拦截所有新窗口(关键修复点!)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 不需要 props,URL 已经是文件路径(已做过替换)
|
||||
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":
|
||||
// 对于 SQLite,database 可能是绝对路径或相对文件名,先把反斜杠替成正斜杠
|
||||
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()) {
|
||||
// 创建 user(schema)示例
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
"人工智能/大语言模型");
|
||||
|
||||
@@ -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 |
BIN
src/main/resources/icons/programming/database.png
Normal file
BIN
src/main/resources/icons/programming/database.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
src/main/resources/icons/programming/database_dark.png
Normal file
BIN
src/main/resources/icons/programming/database_dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Reference in New Issue
Block a user