- 移除重复的字体信息注入逻辑 - 添加 updateTheme 方法统一处理主题和字体更新 - 在 setVisible 方法中调用 updateTheme 确保显示时更新 -优化 JavaScript 中的主题应用逻辑,增强兼容性 - 增强 HTML 页面中的主题监听和字体应用功能 - 添加事件计数器和调试信息用于追踪主题变化
2015 lines
93 KiB
HTML
2015 lines
93 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||
<title>数据库管理工具 - Axis Innovators Box</title>
|
||
<style>
|
||
/* ---------- 主题变量 & 基本样式 ---------- */
|
||
:root{
|
||
--primary-color: #2b8bef;
|
||
--accent-color: #49c28a;
|
||
--bg: #ffffff;
|
||
--surface: #f7fbff;
|
||
--text: #222;
|
||
--muted: #6b6b6b;
|
||
--border: #e6e9ee;
|
||
--shadow: 0 6px 20px rgba(34,34,34,0.06);
|
||
--hover: #f4f7fb;
|
||
}
|
||
|
||
[data-theme="dark"]{
|
||
--primary-color: #49a6ff;
|
||
--accent-color: #2ecc71;
|
||
--bg: #0f1115;
|
||
--surface: #121317;
|
||
--text: #e6edf3;
|
||
--muted: #9aa3ad;
|
||
--border: #24272b;
|
||
--shadow: 0 6px 20px rgba(0,0,0,0.6);
|
||
--hover: rgba(255,255,255,0.02);
|
||
}
|
||
|
||
[data-theme="light"]{
|
||
--primary-color: #2b8bef;
|
||
--accent-color: #49c28a;
|
||
--bg: #ffffff;
|
||
--surface: #f7fbff;
|
||
--text: #222;
|
||
--muted: #6b6b6b;
|
||
--border: #e6e9ee;
|
||
--shadow: 0 6px 20px rgba(34,34,34,0.06);
|
||
--hover: #f4f7fb;
|
||
}
|
||
|
||
*{box-sizing:border-box;margin:0;padding:0;transition:background-color .18s,color .18s,border-color .18s}
|
||
html,body{height:100%}
|
||
body{
|
||
font-family:Inter, 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||
background:var(--bg);
|
||
color:var(--text);
|
||
display:flex;
|
||
flex-direction:column;
|
||
height:100vh;
|
||
overflow:hidden;
|
||
}
|
||
|
||
/* ---------- Header ---------- */
|
||
header{
|
||
display:flex;
|
||
align-items:center;
|
||
justify-content:space-between;
|
||
padding:12px 18px;
|
||
background:var(--surface);
|
||
border-bottom:1px solid var(--border);
|
||
box-shadow:var(--shadow);
|
||
z-index:100;
|
||
}
|
||
.logo{display:flex;gap:12px;align-items:center}
|
||
.logo .mark{width:36px;height:36px;border-radius:10px;background:linear-gradient(180deg,var(--primary-color),#1f7ad8);display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;font-size:18px}
|
||
.logo .title{font-weight:700;font-size:18px}
|
||
.header-controls{display:flex;gap:12px;align-items:center}
|
||
.theme-toggle{background:transparent;border:1px solid var(--border);padding:6px 8px;border-radius:8px;cursor:pointer}
|
||
.user-profile{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:10px;background:transparent;border:1px solid transparent}
|
||
.avatar{width:34px;height:34px;border-radius:8px;background:var(--primary-color);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700}
|
||
|
||
/* ---------- 主区布局 ---------- */
|
||
.main-container{display:flex;flex:1;overflow:hidden}
|
||
/* 侧栏 */
|
||
.sidebar{width:280px;display:flex;flex-direction:column;background:var(--surface);border-right:1px solid var(--border);padding:12px;gap:12px}
|
||
.sidebar-section{background:transparent}
|
||
.sidebar-title{font-size:12px;color:var(--muted);text-transform:uppercase;padding:4px 8px;font-weight:600}
|
||
.sidebar-item{display:flex;align-items:center;gap:10px;padding:8px;border-radius:8px;color:var(--text);text-decoration:none;cursor:pointer}
|
||
.sidebar-item:hover{background:var(--hover)}
|
||
.sidebar-icon{width:20px;height:20px;display:inline-flex;align-items:center;justify-content:center}
|
||
/* 数据库/表 列表区域 */
|
||
.database-list{display:flex;flex-direction:column;gap:8px;padding:6px 4px;max-height:160px;overflow:auto}
|
||
.tables-list{flex:1;overflow:auto;padding:6px 4px;border-radius:8px}
|
||
.table-item{display:flex;align-items:center;justify-content:space-between;padding:8px;border-radius:8px}
|
||
.table-item .meta{display:flex;flex-direction:column}
|
||
.table-item .name{font-weight:600}
|
||
.table-item .sub{font-size:12px;color:var(--muted)}
|
||
|
||
/* 工具区域(新设计) */
|
||
.tools-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>
|
||
<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>
|
||
|
||
window.eventCounter = {
|
||
javaFontsLoaded: 0,
|
||
javaThemeChanged: 0
|
||
};
|
||
|
||
document.addEventListener('javaThemeChanged', function(event) {
|
||
window.eventCounter.javaThemeChanged++;
|
||
console.log('事件详情:', event.detail);
|
||
|
||
if (typeof applyJavaTheme === 'function') {
|
||
applyJavaTheme(event.detail);
|
||
}
|
||
}, true);
|
||
|
||
// 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;
|
||
// const themeToggle = document.getElementById('themeToggle');
|
||
//
|
||
// let currentTheme = localStorage.getItem('dbToolTheme') || 'light';
|
||
//
|
||
// applyTheme(currentTheme);
|
||
//
|
||
// themeToggle.addEventListener('click', () => {
|
||
// const currentTheme = body.getAttribute('data-theme');
|
||
// const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
||
//
|
||
// localStorage.setItem('dbToolTheme', newTheme);
|
||
// applyTheme(newTheme);
|
||
// });
|
||
//
|
||
// function applyTheme(theme){
|
||
// body.setAttribute('data-theme', theme);
|
||
// themeToggle.textContent = theme === '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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||
}
|
||
|
||
// 搜索功能实现
|
||
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} 个匹配项` : '';
|
||
});
|
||
|
||
// 监听Java字体加载完成事件
|
||
document.addEventListener('javaFontsLoaded', function(event) {
|
||
const fontInfo = event.detail;
|
||
console.log('接收到Java字体信息:', fontInfo);
|
||
|
||
// 应用Java字体到编辑器
|
||
applyJavaFonts(fontInfo);
|
||
});
|
||
|
||
// 应用Java字体的函数 - 修复版本
|
||
function applyJavaFonts(fontInfo) {
|
||
const uiFonts = fontInfo.uiFonts || {};
|
||
const defaultFont = fontInfo.defaultFont || uiFonts['Label.font'] || {};
|
||
|
||
if (defaultFont && defaultFont.family) {
|
||
const fontFamily = defaultFont.family;
|
||
const fontSize = defaultFont.size || 14;
|
||
const fontWeight = defaultFont.bold ? 'bold' : 'normal';
|
||
const fontStyle = defaultFont.italic ? 'italic' : 'normal';
|
||
|
||
// 移除之前可能存在的字体样式
|
||
const existingStyle = document.getElementById('java-fonts-style');
|
||
if (existingStyle) {
|
||
existingStyle.remove();
|
||
}
|
||
|
||
// 创建字体样式 - 增加优先级和更全面的覆盖
|
||
const style = document.createElement('style');
|
||
style.id = 'java-fonts-style';
|
||
style.textContent = `
|
||
/* 强制应用Java字体到所有元素 */
|
||
* {
|
||
font-family: '${fontFamily}', 'Fira Code', 'JetBrains Mono', monospace !important;
|
||
}
|
||
|
||
/* 特定元素覆盖 */
|
||
body, html {
|
||
font-family: '${fontFamily}', 'Fira Code', 'JetBrains Mono', monospace !important;
|
||
font-size: ${fontSize}px !important;
|
||
}
|
||
|
||
.toolbar, button, select, input {
|
||
font-family: '${fontFamily}', 'Fira Code', 'JetBrains Mono', monospace !important;
|
||
font-size: ${fontSize}px !important;
|
||
}
|
||
|
||
.log-item {
|
||
font-family: '${fontFamily}', 'Fira Code', 'JetBrains Mono', monospace !important;
|
||
font-size: ${fontSize}px !important;
|
||
}
|
||
|
||
/* CodeMirror 编辑器字体 */
|
||
.CodeMirror, .CodeMirror * {
|
||
font-family: '${fontFamily}', 'Fira Code', 'JetBrains Mono', monospace !important;
|
||
font-size: ${fontSize}px !important;
|
||
}
|
||
|
||
.CodeMirror pre, .CodeMirror-code, .CodeMirror-line {
|
||
font-family: '${fontFamily}', 'Fira Code', 'JetBrains Mono', monospace !important;
|
||
font-size: ${fontSize}px !important;
|
||
}
|
||
|
||
.query-editor, .editor-content, textarea {
|
||
font-family: '${fontFamily}', 'Fira Code', 'JetBrains Mono', monospace !important;
|
||
font-size: ${fontSize}px !important;
|
||
}
|
||
|
||
table, th, td {
|
||
font-family: '${fontFamily}', 'Fira Code', 'JetBrains Mono', monospace !important;
|
||
}
|
||
|
||
.sidebar-item, .sidebar-title {
|
||
font-family: '${fontFamily}', 'Fira Code', 'JetBrains Mono', monospace !important;
|
||
}
|
||
|
||
.modal, .form-control {
|
||
font-family: '${fontFamily}', 'Fira Code', 'JetBrains Mono', monospace !important;
|
||
}
|
||
`;
|
||
|
||
// 添加到文档头
|
||
document.head.appendChild(style);
|
||
|
||
console.log('Java字体已应用到HTML界面:', fontFamily, fontSize + 'px');
|
||
|
||
// 强制刷新CodeMirror编辑器
|
||
if (window.editor) {
|
||
setTimeout(() => {
|
||
editor.refresh();
|
||
// 重新设置内容以触发完全重绘
|
||
const content = editor.getValue();
|
||
editor.setValue('');
|
||
editor.setValue(content);
|
||
|
||
// 额外的刷新确保字体应用
|
||
setTimeout(() => {
|
||
editor.refresh();
|
||
}, 100);
|
||
}, 50);
|
||
}
|
||
|
||
// 记录字体应用事件
|
||
addEventLog('字体应用', `已应用字体: ${fontFamily} ${fontSize}px`);
|
||
}
|
||
}
|
||
|
||
// 应用Java主题的函数
|
||
function applyJavaTheme(themeInfo) {
|
||
console.log('🎨 开始应用Java主题,原始数据:', themeInfo);
|
||
|
||
// 复用现有的主题处理逻辑
|
||
let isDarkTheme;
|
||
|
||
if (typeof themeInfo.isDarkTheme === 'boolean') {
|
||
isDarkTheme = themeInfo.isDarkTheme;
|
||
} else if (typeof themeInfo.isDarkTheme === 'string') {
|
||
// 处理字符串类型的布尔值
|
||
isDarkTheme = themeInfo.isDarkTheme === 'true' || themeInfo.isDarkTheme === '1';
|
||
console.log('🔄 转换字符串布尔值:', themeInfo.isDarkTheme, '->', isDarkTheme);
|
||
} else {
|
||
// 默认值
|
||
isDarkTheme = false;
|
||
console.warn('⚠️ 无法识别isDarkTheme值,使用默认值false');
|
||
}
|
||
|
||
console.log('🎯 最终isDarkTheme值:', isDarkTheme);
|
||
|
||
// 直接调用页面现有的主题切换函数
|
||
const theme = isDarkTheme ? 'dark' : 'light';
|
||
|
||
const body = document.body;
|
||
// 添加强制刷新CSS的函数
|
||
function forceThemeRefresh() {
|
||
// 强制重新计算CSS
|
||
const body = document.body;
|
||
const currentTheme = body.getAttribute('data-theme');
|
||
|
||
// 临时移除再重新添加data-theme属性
|
||
body.removeAttribute('data-theme');
|
||
setTimeout(() => {
|
||
body.setAttribute('data-theme', currentTheme);
|
||
console.log('强制刷新主题:', currentTheme);
|
||
}, 10);
|
||
}
|
||
function applyTheme(m){
|
||
body.setAttribute('data-theme', m);
|
||
document.getElementById('themeToggle').textContent = m === 'light' ? '🌙' : '☀️';
|
||
}
|
||
|
||
// 方法1: 调用现有的 applyTheme 函数
|
||
if (typeof applyTheme === 'function') {
|
||
applyTheme(theme);
|
||
forceThemeRefresh();
|
||
console.log('✅ 调用现有applyTheme函数:', theme);
|
||
}
|
||
const codeMirrorTheme = isDarkTheme ? 'material-darker' : 'nord';
|
||
if (window.editor) {
|
||
editor.setOption('theme', codeMirrorTheme);
|
||
}
|
||
const themeSelector = document.getElementById('theme-selector');
|
||
if (themeSelector) {
|
||
themeSelector.value = theme;
|
||
}
|
||
console.log('✅ Java主题已应用到HTML界面:', isDarkTheme ? '深色主题' : '浅色主题');
|
||
addEventLog('主题应用', `已应用${isDarkTheme ? '深色' : '浅色'}主题`);
|
||
}
|
||
|
||
// 辅助函数:添加事件日志
|
||
function addEventLog(type, message) {
|
||
const output = document.getElementById('output');
|
||
if (output) {
|
||
const timestamp = new Date().toLocaleTimeString();
|
||
const logItem = document.createElement('div');
|
||
logItem.className = `log-item ${type === 'error' ? 'error' : ''}`;
|
||
logItem.innerHTML = `
|
||
<i class="fas fa-info-circle"></i>
|
||
${timestamp}: ${message}
|
||
`;
|
||
output.appendChild(logItem);
|
||
output.scrollTop = output.scrollHeight;
|
||
}
|
||
}
|
||
|
||
// 初始化(模拟触发字体加载回调)
|
||
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);
|
||
});
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// 监听所有对data-theme的修改
|
||
const observer = new MutationObserver(function(mutations) {
|
||
mutations.forEach(function(mutation) {
|
||
if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') {
|
||
console.log('主题被修改:', mutation.oldValue, '->', document.body.getAttribute('data-theme'));
|
||
console.trace('调用堆栈');
|
||
}
|
||
});
|
||
});
|
||
|
||
observer.observe(document.body, {
|
||
attributes: true,
|
||
attributeFilter: ['data-theme'],
|
||
attributeOldValue: true
|
||
});
|
||
|
||
console.log('主题调试已启动');
|
||
});
|
||
|
||
</script>
|
||
</body>
|
||
</html> |