- 添加了数据库连接管理器,支持多种数据库类型(MySQL、PostgreSQL、SQLite、Oracle、H2) - 开发了数据库管理工具的前端界面,包含连接配置、查询编辑器和结果展示 - 支持本地数据库创建与示例数据初始化 - 提供了数据库表结构管理和基础SQL执行功能- 增加了暗色主题切换和响应式布局设计 - 集成了事件日志面板用于调试和状态跟踪
794 lines
43 KiB
HTML
794 lines
43 KiB
HTML
<!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>
|