Files
window-axis-innovators-box/javascript/DatabaseTool.html

1789 lines
85 KiB
HTML
Raw Normal View History

<!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-panel {
position: fixed;
bottom: 20px;
left: 20px;
width: 250px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
box-shadow: var(--shadow);
z-index: 1000;
display: none;
padding: 12px;
flex-direction: column;
gap: 8px;
}
.tools-panel.active {
display: flex;
}
.tools-grid{display:grid;grid-template-columns:repeat(2,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)}
.tools-toggle {
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
border-radius: 8px;
background: transparent;
border: 1px solid var(--border);
cursor: pointer;
margin-top: 8px;
}
.tools-toggle: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}
/* 表格操作按钮 */
.table-actions{display:flex;gap:6px;margin-bottom:12px}
.table-actions .btn{font-size:12px;padding:6px 10px}
/* 编辑行样式 */
.editable-row{background:rgba(73,194,138,0.1) !important}
.editable-row td input{width:100%;padding:4px 6px;border:1px solid var(--border);border-radius:4px;background:var(--bg);color:var(--text)}
.editable-row td select{width:100%;padding:4px 6px;border:1px solid var(--border);border-radius:4px;background:var(--bg);color:var(--text)}
.action-cell{display:flex;gap:6px;justify-content:center}
.action-cell .btn{font-size:11px;padding:4px 8px}
/* 表设计器样式 */
.table-designer{display:flex;flex-direction:column;gap:12px}
.designer-section{background:var(--surface);border-radius:8px;padding:12px;border:1px solid var(--border)}
.designer-section h3{margin-bottom:12px;font-size:14px}
.columns-list{display:flex;flex-direction:column;gap:8px}
.column-item{display:grid;grid-template-columns:1fr 1fr 1fr auto;gap:8px;align-items:center;padding:8px;border-radius:6px;border:1px solid var(--border)}
.column-actions{display:flex;gap:4px}
.column-actions .btn{font-size:11px;padding:4px 8px}
.indexes-list{display:flex;flex-direction:column;gap:8px}
.index-item{display:grid;grid-template-columns:1fr 1fr 1fr auto;gap:8px;align-items:center;padding:8px;border-radius:6px;border:1px solid var(--border)}
.constraints-list{display:flex;flex-direction:column;gap:8px}
.constraint-item{display:grid;grid-template-columns:1fr 1fr 1fr auto;gap:8px;align-items:center;padding:8px;border-radius:6px;border:1px solid var(--border)}
.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}
/* 表单控件 */
.form-control{width:100%;padding:8px;border-radius:6px;border:1px solid var(--border);background:var(--bg);color:var(--text);margin-bottom:12px}
.form-label{display:block;margin-bottom:4px;font-weight:600;font-size:14px}
.form-row{display:flex;gap:12px;margin-bottom:12px}
.form-row .form-group{flex:1}
/* 搜索框 */
.search-box{position:sticky;top:0;background:var(--surface);padding:8px;border-bottom:1px solid var(--border);z-index:10}
.search-input{width:100%;padding:6px 8px;border-radius:6px;border:1px solid var(--border);background:var(--bg);color:var(--text)}
.search-results{font-size:12px;color:var(--muted);margin-top:4px}
/* 响应式小屏 */
@media (max-width:900px){
.sidebar{display:none}
.query-editor{width:100%}
.query-editor-container{flex-direction:column}
.column-item{grid-template-columns:1fr;gap:4px}
.index-item{grid-template-columns:1fr;gap:4px}
.constraint-item{grid-template-columns:1fr;gap:4px}
}
/* 小图标样式SVG 作为图标) */
.svg-icon{width:18px;height:18px;display:inline-block;vertical-align:middle;fill:currentColor}
/* 快速创建表按钮 */
.quick-create-table{background:var(--primary-color);color:white;border:none;padding:8px 12px;border-radius:8px;cursor:pointer;font-weight:600;margin-top:8px;width:100%}
.quick-create-table:hover{opacity:0.9}
</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>
<button class="quick-create-table" id="quickCreateTableBtn">+ 快速创建表</button>
<div class="search-box">
<input type="text" id="tableSearch" class="search-input" placeholder="搜索表...">
<div class="search-results" id="tableSearchResults"></div>
</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>
<button class="tools-toggle" id="toolsToggle">
<span>...</span>
</button>
</div>
</aside>
<!-- 工具面板 -->
<div class="tools-panel" id="toolsPanel">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<div style="font-weight: 600;">工具</div>
<button onclick="document.getElementById('toolsPanel').classList.remove('active')" style="background: transparent; border: none; cursor: pointer;"></button>
</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>
<!-- 内容区 -->
<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="search-box" id="resultsSearchBox" style="display:none">
<input type="text" id="resultsSearchInput" class="search-input" placeholder="在结果中搜索...">
<div class="search-results" id="resultsSearchInfo"></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="tableDesignerModal" class="modal" style="min-width:800px;max-width:95%">
<div class="header" id="tableDesignerTitle">表设计器</div>
<div class="body" id="tableDesignerBody">
<div class="table-designer">
<div class="designer-section">
<h3>基本信息</h3>
<div class="form-row">
<div class="form-group">
<label class="form-label">表名</label>
<input type="text" id="tableName" class="form-control" placeholder="输入表名">
</div>
<div class="form-group">
<label class="form-label">引擎</label>
<select id="tableEngine" class="form-control">
<option value="InnoDB">InnoDB</option>
<option value="MyISAM">MyISAM</option>
<option value="MEMORY">MEMORY</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">字符集</label>
<select id="tableCharset" class="form-control">
<option value="utf8mb4">utf8mb4</option>
<option value="utf8">utf8</option>
<option value="latin1">latin1</option>
</select>
</div>
<div class="form-group">
<label class="form-label">排序规则</label>
<select id="tableCollation" class="form-control">
<option value="utf8mb4_unicode_ci">utf8mb4_unicode_ci</option>
<option value="utf8mb4_general_ci">utf8mb4_general_ci</option>
<option value="utf8_general_ci">utf8_general_ci</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label">注释</label>
<textarea id="tableComment" class="form-control" placeholder="表注释" rows="2"></textarea>
</div>
</div>
<div class="designer-section">
<h3>列定义</h3>
<div class="columns-list" id="columnsList">
<!-- 动态生成的列 -->
</div>
<button class="btn btn-outline" id="addColumnBtn">+ 添加列</button>
</div>
<div class="designer-section">
<h3>索引</h3>
<div class="indexes-list" id="indexesList">
<!-- 动态生成的索引 -->
</div>
<button class="btn btn-outline" id="addIndexBtn">+ 添加索引</button>
</div>
<div class="designer-section">
<h3>约束</h3>
<div class="constraints-list" id="constraintsList">
<!-- 动态生成的约束 -->
</div>
<button class="btn btn-outline" id="addConstraintBtn">+ 添加约束</button>
</div>
</div>
</div>
<div class="footer" id="tableDesignerFooter">
<button class="btn btn-outline" onclick="hideModal('tableDesignerModal')">取消</button>
<button class="btn btn-primary" id="saveTableDesignBtn">保存表结构</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 {
// 开发环境模拟 - 更新模拟数据以匹配新的Java结构
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 if (request.type === 'getTables') {
callback({status:'success', tables:[
{name:'users', type:'TABLE', rows:5},
{name:'products', type:'TABLE', rows:12},
{name:'orders', type:'TABLE', rows:8}
]}, null);
} else if (request.type === 'getTableData') {
callback({status:'success', columns:['id','name','email','created_at'], data:[
{id:1, name:'张三', email:'zhang@example.com', created_at:'2023-01-15'},
{id:2, name:'李四', email:'li@example.com', created_at:'2023-02-20'},
{id:3, name:'王五', email:'wang@example.com', created_at:'2023-03-10'}
], total:3, offset:0, limit:50}, null);
} else if (request.type === 'insertRow') {
callback({status:'success', message:'插入成功', newId: Math.floor(Math.random()*1000)+3}, null);
} else if (request.type === 'updateRow') {
callback({status:'success', message:'更新成功'}, null);
} else if (request.type === 'deleteRow') {
callback({status:'success', message:'删除成功'}, null);
} else if (request.type === 'createTable') {
callback({status:'success', message:'表创建成功'}, null);
} else if (request.type === 'alterTable') {
callback({status:'success', message:'表结构修改成功'}, null);
} else if (request.type === 'getTableStructure') {
// 更新模拟数据以匹配新的Java结构
callback({status:'success',
tableName: request.tableName,
engine: 'InnoDB',
charset: 'utf8mb4',
collation: 'utf8mb4_unicode_ci',
comment: '用户表',
columns: [
{name: 'id', type: 'INT', size: 11, nullable: false, defaultValue: null, autoIncrement: true},
{name: 'name', type: 'VARCHAR(255)', size: 255, nullable: false, defaultValue: null, autoIncrement: false},
{name: 'email', type: 'VARCHAR(255)', size: 255, nullable: false, defaultValue: null, autoIncrement: false},
{name: 'created_at', type: 'DATETIME', size: null, nullable: true, defaultValue: 'CURRENT_TIMESTAMP', autoIncrement: false}
],
indexes: [
{name: 'idx_email', type: 'UNIQUE', columns: 'email'},
{name: 'idx_name', type: 'INDEX', columns: 'name'}
],
constraints: [
{name: 'PRIMARY', type: 'PRIMARY KEY', definition: 'PRIMARY KEY (id)'}
]
}, 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('toolsToggle').addEventListener('click', () => {
document.getElementById('toolsPanel').classList.toggle('active');
});
// 绑定打开连接对话框
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;
window.currentDatabase = database;
// 更新数据库列表(这是简化示例:仅显示当前)
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;
}
// 存储所有表数据用于搜索
window.allTables = tables;
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>
<button class="btn btn-outline" data-action="design" data-table="${t.name}" disabled>设计(维修中)</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);
});
});
list.querySelectorAll('button[data-action="design"]').forEach(b=>{
b.addEventListener('click',(e)=>{
e.stopPropagation();
const table = b.dataset.table;
openTableDesigner(table);
});
});
// 应用搜索过滤
applyTableSearch();
}
// 表搜索功能
document.getElementById('tableSearch').addEventListener('input', applyTableSearch);
function applyTableSearch() {
const searchTerm = document.getElementById('tableSearch').value.toLowerCase();
const tableItems = document.querySelectorAll('.table-item');
let visibleCount = 0;
tableItems.forEach(item => {
const tableName = item.querySelector('.name').textContent.toLowerCase();
if (tableName.includes(searchTerm)) {
item.style.display = 'flex';
visibleCount++;
} else {
item.style.display = 'none';
}
});
document.getElementById('tableSearchResults').textContent =
searchTerm ? `找到 ${visibleCount} 个表` : '';
}
// 加载表数据
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-actions">
<button class="btn btn-primary" id="addRowBtn">+ 添加行</button>
<button class="btn btn-outline" id="refreshTableBtn">刷新</button>
</div>`;
html += '<div class="table-container"><table><thead><tr>';
data.columns.forEach(c => html += `<th>${c}</th>`);
html += '<th>操作</th></tr></thead><tbody>';
data.data.forEach((row, index)=>{
html += `<tr data-row-index="${index}">`;
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 += `<td class="action-cell">
<button class="btn btn-outline edit-row" data-row-index="${index}">编辑</button>
<button class="btn btn-outline delete-row" data-row-index="${index}">删除</button>
</td>`;
html += '</tr>';
});
html += '</tbody></table></div>';
content.innerHTML = html;
document.getElementById('rowColumnInfo').textContent = `行: ${data.data.length}, 列: ${data.columns.length}`;
// 绑定增删改按钮事件
bindTableActions(data.tableName, data.columns, data.data);
}
// 绑定表操作按钮事件
function bindTableActions(tableName, columns, data){
// 添加行按钮
document.getElementById('addRowBtn').addEventListener('click', () => {
openAddRowModal(tableName, columns);
});
// 刷新按钮
document.getElementById('refreshTableBtn').addEventListener('click', () => {
loadTableData(tableName);
});
// 编辑按钮
document.querySelectorAll('.edit-row').forEach(btn => {
btn.addEventListener('click', (e) => {
const rowIndex = parseInt(e.target.dataset.rowIndex);
const rowData = data[rowIndex];
openEditRowModal(tableName, columns, rowData);
});
});
// 删除按钮
document.querySelectorAll('.delete-row').forEach(btn => {
btn.addEventListener('click', (e) => {
const rowIndex = parseInt(e.target.dataset.rowIndex);
const rowData = data[rowIndex];
deleteRow(tableName, columns, rowData);
});
});
}
// 打开添加行模态框
function openAddRowModal(tableName, columns){
let formHtml = `<form id="addRowForm">`;
columns.forEach(col => {
formHtml += `
<div style="margin-bottom:12px">
<label class="form-label">${col}</label>
<input type="text" class="form-control" name="${col}" placeholder="输入 ${col} 值">
</div>
`;
});
formHtml += `</form>`;
openToolModal(`添加行到 ${tableName}`, formHtml);
// 修改模态框底部按钮
document.getElementById('toolModalFooter').innerHTML = `
<button class="btn btn-outline" onclick="hideModal('toolModal')">取消</button>
<button class="btn btn-primary" id="confirmAddRow">确认添加</button>
`;
document.getElementById('confirmAddRow').addEventListener('click', () => {
const form = document.getElementById('addRowForm');
const formData = new FormData(form);
const rowData = {};
columns.forEach(col => {
rowData[col] = formData.get(col) || null;
});
insertRow(tableName, rowData);
});
}
// 打开编辑行模态框
function openEditRowModal(tableName, columns, rowData){
let formHtml = `<form id="editRowForm">`;
columns.forEach(col => {
const value = rowData[col] === null || rowData[col] === undefined ? '' : rowData[col];
formHtml += `
<div style="margin-bottom:12px">
<label class="form-label">${col}</label>
<input type="text" class="form-control" name="${col}" value="${escapeHtml(String(value))}">
</div>
`;
});
formHtml += `</form>`;
openToolModal(`编辑 ${tableName} 的行`, formHtml);
// 修改模态框底部按钮
document.getElementById('toolModalFooter').innerHTML = `
<button class="btn btn-outline" onclick="hideModal('toolModal')">取消</button>
<button class="btn btn-primary" id="confirmEditRow">确认更新</button>
`;
document.getElementById('confirmEditRow').addEventListener('click', () => {
const form = document.getElementById('editRowForm');
const formData = new FormData(form);
const updatedData = {};
columns.forEach(col => {
updatedData[col] = formData.get(col) || null;
});
updateRow(tableName, rowData, updatedData);
});
}
// 插入行
function insertRow(tableName, rowData){
if(!window.currentConnectionId) { alert('未连接数据库'); return; }
setStatus(`正在插入行到 ${tableName}...`);
JavaBridge.sendRequest({
type: 'insertRow',
connectionId: window.currentConnectionId,
tableName,
rowData
}, (resp, err) => {
if(err){
setStatus('插入失败');
addEventLog('插入失败', err.message);
alert('插入失败: ' + err.message);
return;
}
if(resp && resp.status === 'success'){
setStatus('插入成功');
addEventLog('插入行', `表 ${tableName}`);
hideModal('toolModal');
// 刷新表数据
loadTableData(tableName);
} else {
setStatus('插入失败');
alert('插入失败: ' + (resp && resp.message || '未知错误'));
}
});
}
// 更新行
function updateRow(tableName, originalData, updatedData){
if(!window.currentConnectionId) { alert('未连接数据库'); return; }
setStatus(`正在更新 ${tableName} 的行...`);
JavaBridge.sendRequest({
type: 'updateRow',
connectionId: window.currentConnectionId,
tableName,
originalData,
updatedData
}, (resp, err) => {
if(err){
setStatus('更新失败');
addEventLog('更新失败', err.message);
alert('更新失败: ' + err.message);
return;
}
if(resp && resp.status === 'success'){
setStatus('更新成功');
addEventLog('更新行', `表 ${tableName}`);
hideModal('toolModal');
// 刷新表数据
loadTableData(tableName);
} else {
setStatus('更新失败');
alert('更新失败: ' + (resp && resp.message || '未知错误'));
}
});
}
// 删除行
function deleteRow(tableName, columns, rowData){
if(!window.currentConnectionId) { alert('未连接数据库'); return; }
if(!confirm('确定要删除这行数据吗?此操作不可撤销。')) {
return;
}
setStatus(`正在从 ${tableName} 删除行...`);
JavaBridge.sendRequest({
type: 'deleteRow',
connectionId: window.currentConnectionId,
tableName,
rowData
}, (resp, err) => {
if(err){
setStatus('删除失败');
addEventLog('删除失败', err.message);
alert('删除失败: ' + err.message);
return;
}
if(resp && resp.status === 'success'){
setStatus('删除成功');
addEventLog('删除行', `表 ${tableName}`);
// 刷新表数据
loadTableData(tableName);
} else {
setStatus('删除失败');
alert('删除失败: ' + (resp && resp.message || '未知错误'));
}
});
}
// 打开表设计器 - 修复:只在新建表时重置表单
function openTableDesigner(tableName = null) {
if(!window.currentConnectionId) { alert('未连接数据库'); return; }
const isNewTable = !tableName;
document.getElementById('tableDesignerTitle').textContent =
isNewTable ? '创建新表' : `设计表: ${tableName}`;
// 只有在新建表时才重置表单
if (isNewTable) {
resetTableDesignerForm();
}
// 如果是编辑现有表,加载表结构
if (!isNewTable) {
setStatus(`正在加载表 ${tableName} 结构...`);
JavaBridge.sendRequest({
type: 'getTableStructure',
connectionId: window.currentConnectionId,
tableName
}, (resp, err) => {
if (err) {
setStatus('加载表结构失败');
alert('加载表结构失败: ' + err.message);
return;
}
if (resp && resp.status === 'success') {
// 填充表结构到设计器
populateTableDesigner(resp);
setStatus('表结构已加载');
} else {
setStatus('加载表结构失败');
alert('加载表结构失败');
}
});
}
showModal('tableDesignerModal');
// 绑定保存按钮事件
document.getElementById('saveTableDesignBtn').onclick = () => {
saveTableDesign(isNewTable ? null : tableName);
};
}
// 重置表设计器表单(只在新建表时调用)
function resetTableDesignerForm() {
document.getElementById('tableName').value = '';
document.getElementById('tableEngine').value = 'InnoDB';
document.getElementById('tableCharset').value = 'utf8mb4';
document.getElementById('tableCollation').value = 'utf8mb4_unicode_ci';
document.getElementById('tableComment').value = '';
// 清空列列表
document.getElementById('columnsList').innerHTML = '';
document.getElementById('indexesList').innerHTML = '';
document.getElementById('constraintsList').innerHTML = '';
// 添加默认列
addColumnRow();
}
// 添加列行 - 修改根据新的Java数据结构处理列属性
function addColumnRow(columnData = null) {
const columnsList = document.getElementById('columnsList');
const columnId = 'column_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5);
// 获取列数据,如果没有数据则使用默认值
const typeValue = columnData ? columnData.type : 'VARCHAR(255)';
const nullableValue = columnData ? columnData.nullable : true;
const autoIncrementValue = columnData ? columnData.autoIncrement : false;
// 根据nullable和autoIncrement构建attributes值
let attributesValue = '';
if (columnData) {
if (columnData.autoIncrement) {
attributesValue = 'AUTO_INCREMENT';
}
if (!columnData.nullable) {
if (attributesValue) attributesValue += ' NOT NULL';
else attributesValue = 'NOT NULL';
}
}
// 定义所有可用的列类型
const columnTypes = [
'INT', 'VARCHAR(255)', 'TEXT', 'DATE', 'DATETIME', 'DECIMAL(10,2)', 'BOOLEAN',
'BIGINT', 'SMALLINT', 'TINYINT', 'FLOAT', 'DOUBLE', 'CHAR(1)', 'BLOB', 'LONGTEXT'
];
// 生成类型选项
let typeOptions = '';
columnTypes.forEach(type => {
const selected = type === typeValue ? 'selected' : '';
typeOptions += `<option value="${type}" ${selected}>${type}</option>`;
});
// 如果类型不在预定义列表中,添加自定义选项
if (columnData && !columnTypes.includes(typeValue)) {
typeOptions += `<option value="${typeValue}" selected>${typeValue}</option>`;
}
const columnHtml = `
<div class="column-item" id="${columnId}">
<div>
<input type="text" class="form-control column-name" placeholder="列名" value="${columnData ? columnData.name : ''}">
</div>
<div>
<select class="form-control column-type">
${typeOptions}
</select>
</div>
<div>
<select class="form-control column-attributes">
<option value=""></option>
<option value="NOT NULL" ${attributesValue.includes('NOT NULL') ? 'selected' : ''}>非空</option>
<option value="AUTO_INCREMENT" ${attributesValue.includes('AUTO_INCREMENT') ? 'selected' : ''}>自增</option>
<option value="PRIMARY KEY" ${attributesValue.includes('PRIMARY KEY') ? 'selected' : ''}>主键</option>
<option value="UNIQUE" ${attributesValue.includes('UNIQUE') ? 'selected' : ''}>唯一</option>
<option value="NOT NULL AUTO_INCREMENT" ${attributesValue.includes('NOT NULL') && attributesValue.includes('AUTO_INCREMENT') ? 'selected' : ''}>非空+自增</option>
</select>
</div>
<div class="column-actions">
<button class="btn btn-outline move-up" type="button"></button>
<button class="btn btn-outline move-down" type="button"></button>
<button class="btn btn-outline remove-column" type="button">删除</button>
</div>
</div>
`;
columnsList.insertAdjacentHTML('beforeend', columnHtml);
// 绑定事件
const columnElement = document.getElementById(columnId);
columnElement.querySelector('.remove-column').addEventListener('click', () => {
columnElement.remove();
});
columnElement.querySelector('.move-up').addEventListener('click', () => {
const prev = columnElement.previousElementSibling;
if (prev) {
columnsList.insertBefore(columnElement, prev);
}
});
columnElement.querySelector('.move-down').addEventListener('click', () => {
const next = columnElement.nextElementSibling;
if (next) {
columnsList.insertBefore(next, columnElement);
}
});
}
// 添加索引行
function addIndexRow(indexData = null) {
const indexesList = document.getElementById('indexesList');
const indexId = 'index_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5);
const indexHtml = `
<div class="index-item" id="${indexId}">
<div>
<input type="text" class="form-control index-name" placeholder="索引名" value="${indexData ? indexData.name : ''}">
</div>
<div>
<select class="form-control index-type">
<option value="INDEX" ${(!indexData || indexData.type === 'INDEX') ? 'selected' : ''}>普通索引</option>
<option value="UNIQUE" ${indexData && indexData.type === 'UNIQUE' ? 'selected' : ''}>唯一索引</option>
<option value="FULLTEXT" ${indexData && indexData.type === 'FULLTEXT' ? 'selected' : ''}>全文索引</option>
</select>
</div>
<div>
<input type="text" class="form-control index-columns" placeholder="列名(逗号分隔)" value="${indexData ? indexData.columns : ''}">
</div>
<div class="column-actions">
<button class="btn btn-outline remove-index" type="button">删除</button>
</div>
</div>
`;
indexesList.insertAdjacentHTML('beforeend', indexHtml);
// 绑定事件
const indexElement = document.getElementById(indexId);
indexElement.querySelector('.remove-index').addEventListener('click', () => {
indexElement.remove();
});
}
// 添加约束行
function addConstraintRow(constraintData = null) {
const constraintsList = document.getElementById('constraintsList');
const constraintId = 'constraint_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5);
const constraintHtml = `
<div class="constraint-item" id="${constraintId}">
<div>
<input type="text" class="form-control constraint-name" placeholder="约束名" value="${constraintData ? constraintData.name : ''}">
</div>
<div>
<select class="form-control constraint-type">
<option value="FOREIGN KEY" ${(!constraintData || constraintData.type === 'FOREIGN KEY') ? 'selected' : ''}>外键</option>
<option value="PRIMARY KEY" ${constraintData && constraintData.type === 'PRIMARY KEY' ? 'selected' : ''}>主键</option>
<option value="CHECK" ${constraintData && constraintData.type === 'CHECK' ? 'selected' : ''}>检查约束</option>
</select>
</div>
<div>
<input type="text" class="form-control constraint-definition" placeholder="约束定义" value="${constraintData ? constraintData.definition : ''}">
</div>
<div class="column-actions">
<button class="btn btn-outline remove-constraint" type="button">删除</button>
</div>
</div>
`;
constraintsList.insertAdjacentHTML('beforeend', constraintHtml);
// 绑定事件
const constraintElement = document.getElementById(constraintId);
constraintElement.querySelector('.remove-constraint').addEventListener('click', () => {
constraintElement.remove();
});
}
// 填充表设计器 - 修改根据新的Java数据结构处理所有字段
function populateTableDesigner(tableStructure) {
// 设置表基本信息 - 处理可能的JSONObject.NULL值
document.getElementById('tableName').value = tableStructure.tableName || '';
document.getElementById('tableEngine').value = (tableStructure.engine && tableStructure.engine !== null && tableStructure.engine !== 'null') ? tableStructure.engine : 'InnoDB';
document.getElementById('tableCharset').value = (tableStructure.charset && tableStructure.charset !== null && tableStructure.charset !== 'null') ? tableStructure.charset : 'utf8mb4';
document.getElementById('tableCollation').value = (tableStructure.collation && tableStructure.collation !== null && tableStructure.collation !== 'null') ? tableStructure.collation : 'utf8mb4_unicode_ci';
document.getElementById('tableComment').value = (tableStructure.comment && tableStructure.comment !== null && tableStructure.comment !== 'null') ? tableStructure.comment : '';
// 清空列列表
document.getElementById('columnsList').innerHTML = '';
document.getElementById('indexesList').innerHTML = '';
document.getElementById('constraintsList').innerHTML = '';
// 添加列
if (tableStructure.columns && tableStructure.columns.length > 0) {
tableStructure.columns.forEach(column => {
addColumnRow(column);
});
} else {
addColumnRow();
}
// 添加索引
if (tableStructure.indexes && tableStructure.indexes.length > 0) {
tableStructure.indexes.forEach(index => {
addIndexRow(index);
});
}
// 添加约束
if (tableStructure.constraints && tableStructure.constraints.length > 0) {
tableStructure.constraints.forEach(constraint => {
addConstraintRow(constraint);
});
}
}
// 保存表设计
function saveTableDesign(existingTableName) {
const tableName = document.getElementById('tableName').value.trim();
if (!tableName) {
alert('请输入表名');
return;
}
// 收集列信息
const columns = [];
const columnElements = document.querySelectorAll('.column-item');
columnElements.forEach(colElement => {
const name = colElement.querySelector('.column-name').value.trim();
const type = colElement.querySelector('.column-type').value;
const attributes = colElement.querySelector('.column-attributes').value;
if (name) {
columns.push({
name: name,
type: type,
attributes: attributes
});
}
});
if (columns.length === 0) {
alert('请至少定义一个列');
return;
}
// 收集索引信息
const indexes = [];
const indexElements = document.querySelectorAll('.index-item');
indexElements.forEach(indexElement => {
const name = indexElement.querySelector('.index-name').value.trim();
const type = indexElement.querySelector('.index-type').value;
const columns = indexElement.querySelector('.index-columns').value.trim();
if (name && columns) {
indexes.push({
name: name,
type: type,
columns: columns
});
}
});
// 收集约束信息
const constraints = [];
const constraintElements = document.querySelectorAll('.constraint-item');
constraintElements.forEach(constraintElement => {
const name = constraintElement.querySelector('.constraint-name').value.trim();
const type = constraintElement.querySelector('.constraint-type').value;
const definition = constraintElement.querySelector('.constraint-definition').value.trim();
if (name && definition) {
constraints.push({
name: name,
type: type,
definition: definition
});
}
});
// 收集表选项
const tableOptions = {
engine: document.getElementById('tableEngine').value,
charset: document.getElementById('tableCharset').value,
collation: document.getElementById('tableCollation').value,
comment: document.getElementById('tableComment').value
};
setStatus(existingTableName ? `正在修改表 ${tableName}...` : `正在创建表 ${tableName}...`);
JavaBridge.sendRequest({
type: existingTableName ? 'alterTable' : 'createTable',
connectionId: window.currentConnectionId,
tableName: existingTableName || tableName,
newTableName: existingTableName ? tableName : null,
columns: columns,
indexes: indexes,
constraints: constraints,
tableOptions: tableOptions
}, (resp, err) => {
if (err) {
setStatus('操作失败');
alert('操作失败: ' + err.message);
return;
}
if (resp && resp.status === 'success') {
setStatus('操作成功');
addEventLog(existingTableName ? '修改表' : '创建表',
existingTableName ? `表 ${existingTableName} 已更新为 ${tableName}` : `表 ${tableName} 已创建`);
hideModal('tableDesignerModal');
// 刷新表列表
loadTables();
} else {
setStatus('操作失败');
alert('操作失败: ' + (resp && resp.message || '未知错误'));
}
});
}
// 查看表结构 - 修改:显示更详细的结构信息
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:16px">
<div><strong>表名:</strong> ${escapeHtml(resp.tableName || '')}</div>
<div><strong>引擎:</strong> ${escapeHtml(resp.engine || '')}</div>
<div><strong>字符集:</strong> ${escapeHtml(resp.charset || '')}</div>
<div><strong>排序规则:</strong> ${escapeHtml(resp.collation || '')}</div>
<div><strong>注释:</strong> ${escapeHtml(resp.comment || '')}</div>
</div>`;
if(resp.columns && resp.columns.length>0){
html += '<h3 style="margin:16px 0 8px 0">列信息</h3>';
html += '<table><thead><tr><th>列名</th><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>
<td>${c.autoIncrement ? '是' : '否'}</td>
</tr>`;
});
html += '</tbody></table>';
} else {
html += `<div style="padding:12px;color:var(--muted)">没有列信息</div>`;
}
if(resp.indexes && resp.indexes.length>0){
html += '<h3 style="margin:16px 0 8px 0">索引信息</h3>';
html += '<table><thead><tr><th>索引名</th><th>类型</th><th></th></tr></thead><tbody>';
resp.indexes.forEach(idx=>{
html += `<tr>
<td>${escapeHtml(idx.name)}</td>
<td>${escapeHtml(idx.type)}</td>
<td>${escapeHtml(idx.columns)}</td>
</tr>`;
});
html += '</tbody></table>';
}
if(resp.constraints && resp.constraints.length>0){
html += '<h3 style="margin:16px 0 8px 0">约束信息</h3>';
html += '<table><thead><tr><th>约束名</th><th>类型</th><th>定义</th></tr></thead><tbody>';
resp.constraints.forEach(con=>{
html += `<tr>
<td>${escapeHtml(con.name)}</td>
<td>${escapeHtml(con.type)}</td>
<td>${escapeHtml(con.definition)}</td>
</tr>`;
});
html += '</tbody></table>';
}
return html;
}
function renderErDiagram(erData) {
if(!erData.tables || !Array.isArray(erData.tables)) {
return '<div>无效的ER数据</div>';
}
let html = '<div class="er-diagram" style="display: flex; flex-wrap: wrap; gap: 20px;">';
erData.tables.forEach(table => {
html += `
<div class="er-table" style="border: 2px solid #333; border-radius: 8px; min-width: 200px;">
<div class="table-header" style="background: #f0f0f0; padding: 8px; font-weight: bold; border-bottom: 1px solid #ccc;">
${escapeHtml(table.name)}
</div>
<div class="table-columns" style="padding: 8px;">
`;
if(table.columns && Array.isArray(table.columns)) {
table.columns.forEach(column => {
const type = column.type || 'VARCHAR';
const size = column.size ? `(${column.size})` : '';
const nullable = column.nullable ? 'NULL' : 'NOT NULL';
html += `
<div style="padding: 4px 0; border-bottom: 1px dashed #eee;">
<span style="font-weight: bold;">${escapeHtml(column.name)}</span>
<br>
<small style="color: #666;">${type}${size} ${nullable}</small>
</div>
`;
});
}
html += `
</div>
</div>
`;
});
html += '</div>';
return html;
}
// 工具按钮统一处理
document.querySelectorAll('.tool-btn').forEach(btn=>{
btn.addEventListener('click', ()=>{
const action = btn.dataset.action;
switch(action){
case 'tableDesigner':
openTableDesigner();
break;
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 图', renderErDiagram(resp.er));
});
}),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);
}
// 关闭工具面板
document.getElementById('toolsPanel').classList.remove('active');
});
});
// 绑定添加列按钮
document.getElementById('addColumnBtn').addEventListener('click', () => {
addColumnRow();
});
// 绑定添加索引按钮
document.getElementById('addIndexBtn').addEventListener('click', () => {
addIndexRow();
});
// 绑定添加约束按钮
document.getElementById('addConstraintBtn').addEventListener('click', () => {
addConstraintRow();
});
// 快速创建表按钮
document.getElementById('quickCreateTableBtn').addEventListener('click', () => {
openTableDesigner();
});
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('keydown', (e) => {
// Ctrl+F 在结果区域搜索
if (e.ctrlKey && e.key === 'f') {
e.preventDefault();
const searchBox = document.getElementById('resultsSearchBox');
const searchInput = document.getElementById('resultsSearchInput');
if (searchBox.style.display === 'none') {
searchBox.style.display = 'block';
searchInput.focus();
} else {
searchBox.style.display = 'none';
}
}
});
// 结果搜索功能
document.getElementById('resultsSearchInput').addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
const table = document.querySelector('.results-content table');
if (!table) return;
const rows = table.querySelectorAll('tbody tr');
let matchCount = 0;
rows.forEach(row => {
const text = row.textContent.toLowerCase();
if (text.includes(searchTerm)) {
row.style.display = '';
matchCount++;
// 高亮匹配的文本
if (searchTerm) {
const cells = row.querySelectorAll('td');
cells.forEach(cell => {
const originalText = cell.textContent;
const highlightedText = originalText.replace(
new RegExp(searchTerm, 'gi'),
match => `<mark style="background-color: yellow; color: black;">${match}</mark>`
);
cell.innerHTML = highlightedText;
});
}
} else {
row.style.display = 'none';
}
});
document.getElementById('resultsSearchInfo').textContent =
searchTerm ? `找到 ${matchCount} 个匹配项` : '';
});
// 初始化(模拟触发字体加载回调)
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>