Files
window-axis-innovators-box/javascript/DatabaseTool.html
tzdwindows 7 8f40542ab0 feat(browser): 添加数据库管理工具和JS对话框处理- 实现了浏览器窗口中的JavaScript alert弹窗拦截与处理
- 添加了数据库连接管理器,支持多种数据库类型(MySQL、PostgreSQL、SQLite、Oracle、H2)
- 开发了数据库管理工具的前端界面,包含连接配置、查询编辑器和结果展示
- 支持本地数据库创建与示例数据初始化
- 提供了数据库表结构管理和基础SQL执行功能- 增加了暗色主题切换和响应式布局设计
- 集成了事件日志面板用于调试和状态跟踪
2025-10-07 12:38:53 +08:00

794 lines
43 KiB
HTML
Raw Blame History

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