573 lines
18 KiB
HTML
573 lines
18 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>表结构信息</title>
|
||
<style>
|
||
/* ===== 全局 & 布局(沿用原样式,略) ===== */
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||
}
|
||
|
||
body {
|
||
padding-bottom: 10px;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.container {
|
||
display: flex;
|
||
padding: 16px;
|
||
gap: 16px;
|
||
min-height: calc(100vh - 10px);
|
||
}
|
||
|
||
.left-column {
|
||
flex: 0 0 520px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
}
|
||
|
||
.card {
|
||
border: 1px solid #ccc;
|
||
border-radius: 4px;
|
||
padding: 15px;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, .1);
|
||
background: #fff;
|
||
}
|
||
|
||
.card-header {
|
||
font-weight: bold;
|
||
margin-bottom: 12px;
|
||
font-size: 16px;
|
||
color: #333;
|
||
}
|
||
|
||
.detail {
|
||
margin-bottom: 8px;
|
||
color: #555;
|
||
}
|
||
|
||
.time-info p {
|
||
margin-bottom: 4px;
|
||
color: #555;
|
||
}
|
||
|
||
.stats-row {
|
||
display: flex;
|
||
gap: 15px;
|
||
margin-top: 12px;
|
||
color: #6c757d;
|
||
font-size: .85rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.stat-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
}
|
||
|
||
.right-column {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
}
|
||
|
||
/* ===== 标签栏 ===== */
|
||
.tabs {
|
||
display: flex;
|
||
border-bottom: 1px solid #dadce0;
|
||
padding: 0 8px;
|
||
background: #f8f9fa;
|
||
border-radius: 8px 8px 0 0;
|
||
}
|
||
|
||
.tab {
|
||
padding: 10px 24px;
|
||
cursor: pointer;
|
||
border: none;
|
||
background: none;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: #5f6368;
|
||
position: relative;
|
||
transition: .2s;
|
||
border-radius: 4px;
|
||
margin: 0 4px;
|
||
}
|
||
|
||
.tab:hover {
|
||
background: rgba(60, 64, 67, .08);
|
||
color: #202124;
|
||
}
|
||
|
||
.tab.active {
|
||
background: #e8f0fe;
|
||
color: #1967d2;
|
||
}
|
||
|
||
.tab.active::after {
|
||
content: "";
|
||
position: absolute;
|
||
bottom: -1px;
|
||
left: 0;
|
||
right: 0;
|
||
height: 3px;
|
||
background: #1967d2;
|
||
border-radius: 3px 3px 0 0;
|
||
}
|
||
|
||
/* ===== 内容区 ===== */
|
||
.tab-content {
|
||
border: 1px solid #dadce0;
|
||
border-radius: 0 0 8px 8px;
|
||
padding: 16px;
|
||
height: calc(100vh - 120px);
|
||
display: none;
|
||
flex-direction: column;
|
||
background: #fff;
|
||
}
|
||
|
||
.tab-content.active {
|
||
display: flex;
|
||
}
|
||
|
||
.content-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
padding-bottom: 8px;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
|
||
.content-title {
|
||
font-weight: 500;
|
||
font-size: 16px;
|
||
color: #202124;
|
||
}
|
||
|
||
.action-btn {
|
||
padding: 6px 12px;
|
||
background: #1967d2;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
transition: .2s;
|
||
}
|
||
|
||
.action-btn:hover {
|
||
background: #1557b0;
|
||
}
|
||
|
||
.content-body {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* ===== 字段表格:行边框、无列边框 ===== */
|
||
.table-container {
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow: auto;
|
||
}
|
||
|
||
.field-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
min-width: 600px;
|
||
}
|
||
|
||
.field-table thead {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 1;
|
||
}
|
||
|
||
.field-table th, .field-table td {
|
||
padding: 12px 16px;
|
||
text-align: left;
|
||
border-bottom: 1px solid #e0e0e0;
|
||
border-left: none;
|
||
border-right: none;
|
||
}
|
||
|
||
.field-table thead tr:first-child th {
|
||
border-top: none;
|
||
}
|
||
|
||
.field-table th {
|
||
background: #e3f2fd;
|
||
color: #1565c0;
|
||
font-weight: 500;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.field-table tr:nth-child(even) {
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
.field-table tr:hover {
|
||
background: #e8f5e9;
|
||
}
|
||
|
||
.col-name {
|
||
display: inline-block;
|
||
font-family: "JetBrains Mono", SFMono-Regular, Consolas, monospace;
|
||
font-weight: 600;
|
||
font-size: 0.875rem;
|
||
color: #0c4a6e; /* 深蓝字 */
|
||
padding: 3px 8px 4px;
|
||
border-radius: 4px;
|
||
background: linear-gradient(90deg, #e0f2fe 0%, #bae6fd 100%); /* 浅蓝渐变 */
|
||
box-shadow: 0 1px 2px rgba(14, 165, 233, .15);
|
||
transition: box-shadow .2s;
|
||
}
|
||
|
||
.col-name:hover {
|
||
box-shadow: 0 2px 6px rgba(14, 165, 233, .25);
|
||
}
|
||
|
||
|
||
/* ===== 代码块 ===== */
|
||
.code-block {
|
||
background: #f8f9fa;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 4px;
|
||
padding: 16px;
|
||
font-family: monospace;
|
||
white-space: pre-wrap;
|
||
height: 100%;
|
||
overflow: auto;
|
||
color: #202124;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* ===== 对话框 ===== */
|
||
.modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, .5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
transition: .3s;
|
||
}
|
||
|
||
.modal-overlay.active {
|
||
opacity: 1;
|
||
visibility: visible;
|
||
}
|
||
|
||
.modal {
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 10px rgba(0, 0, 0, .1);
|
||
width: 350px;
|
||
max-width: 90%;
|
||
padding: 24px;
|
||
transform: translateY(-20px);
|
||
transition: .3s;
|
||
}
|
||
|
||
.modal-overlay.active .modal {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
padding-bottom: 8px;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
|
||
.modal-title {
|
||
font-size: 18px;
|
||
font-weight: 500;
|
||
color: #202124;
|
||
}
|
||
|
||
.modal-close {
|
||
background: none;
|
||
border: none;
|
||
font-size: 20px;
|
||
cursor: pointer;
|
||
color: #5f6368;
|
||
transition: .2s;
|
||
}
|
||
|
||
.modal-close:hover {
|
||
color: #202124;
|
||
}
|
||
|
||
.modal-body {
|
||
margin-bottom: 24px;
|
||
color: #3c4043;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.modal-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.modal-btn {
|
||
padding: 8px 16px;
|
||
background: #1967d2;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
transition: .2s;
|
||
}
|
||
|
||
.modal-btn:hover {
|
||
background: #1557b0;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<!-- 左侧 -->
|
||
<div class="left-column" th:each="t : ${data}">
|
||
<div class="card table-info">
|
||
<div class="card-header">表基础信息</div>
|
||
<div class="detail">数据库: <span th:text="${t.dbName}"></span></div>
|
||
<div class="detail">表名称: <strong th:text="${t.tableName}"></strong></div>
|
||
<div class="detail">表描述: <span th:text="${t.tableDesc}"></span></div>
|
||
<div class="detail">存储量: <span th:text="${t.dataLength}"></span></div>
|
||
</div>
|
||
<div class="card time-info">
|
||
<div>创建时间: <span th:text="${t.createTime}"></span></div>
|
||
<div>更新时间: <span th:text="${t.updateTime}"></span></div>
|
||
</div>
|
||
<div class="stats-row">
|
||
<div class="stat-item"><i class="fas fa-key" style="color:#165dff;"></i>
|
||
<p>主键:<span th:text="${pkCnt}">0</span> 个</p></div>
|
||
<div class="stat-item"><i class="fas fa-sitemap" style="color:#00864e;"></i>
|
||
<p>索引:<span th:text="${idxCnt}">0</span> 个</p></div>
|
||
<div class="stat-item"><i class="fas fa-list" style="color:#4096ff;"></i>
|
||
<p>字段:<span th:text="${colCnt}">0</span> 个</p></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧 -->
|
||
<div class="right-column">
|
||
<div class="tabs">
|
||
<div class="tab active" data-tab="field">字段信息</div>
|
||
<div class="tab" data-tab="select">生成SELECT</div>
|
||
<div class="tab" data-tab="ddl">生成DDL</div>
|
||
</div>
|
||
|
||
<!-- 字段信息 -->
|
||
<div class="tab-content active" id="field-content">
|
||
<div class="content-header">
|
||
<div class="content-title">字段信息</div>
|
||
<button class="action-btn" id="export-field">导出 ↓</button>
|
||
</div>
|
||
<div class="content-body">
|
||
<div class="table-container">
|
||
<table class="field-table">
|
||
<thead>
|
||
<tr>
|
||
<th>序号</th>
|
||
<th>字段名称</th>
|
||
<th>字段类型</th>
|
||
<th>描述</th>
|
||
<th>键类型</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody th:each="c : ${columns}">
|
||
<tr>
|
||
<td th:text="${c.sort}"></td>
|
||
<td><span class="col-name" th:text="${c.colName}">f_user_name</span></td>
|
||
<td th:text="${c.colType}"></td>
|
||
<td th:text="${c.colDesc}"></td>
|
||
<td th:text="${c.keyType}"></td>
|
||
</tr>
|
||
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SELECT -->
|
||
<div class="tab-content" id="select-content">
|
||
<div class="content-header">
|
||
<div class="content-title">SELECT语句</div>
|
||
<button class="action-btn" id="copy-select">复制SQL</button>
|
||
</div>
|
||
<div class="content-body">
|
||
<pre class="code-block" th:text="${selectSql}"></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- DDL -->
|
||
<div class="tab-content" id="ddl-content">
|
||
<div class="content-header">
|
||
<div class="content-title">DDL语句</div>
|
||
<button class="action-btn" id="copy-ddl">复制DDL</button>
|
||
</div>
|
||
<div class="content-body">
|
||
<pre class="code-block" th:text="${ddlSql}"></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 对话框 -->
|
||
<div class="modal-overlay" id="modal">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<div class="modal-title" id="modal-title">提示</div>
|
||
<button class="modal-close" id="modal-close">×</button>
|
||
</div>
|
||
<div class="modal-body" id="modal-message">这是一条提示信息</div>
|
||
<div class="modal-footer">
|
||
<button class="modal-btn" id="modal-confirm">确定</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
/* 对话框 */
|
||
const modal = document.getElementById('modal');
|
||
const modalTitle = document.getElementById('modal-title');
|
||
const modalMessage = document.getElementById('modal-message');
|
||
const modalClose = document.getElementById('modal-close');
|
||
const modalConfirm = document.getElementById('modal-confirm');
|
||
|
||
function showModal(title, message) {
|
||
modalTitle.textContent = title;
|
||
modalMessage.textContent = message;
|
||
modal.classList.add('active');
|
||
document.body.style.overflow = 'hidden';
|
||
}
|
||
|
||
function hideModal() {
|
||
modal.classList.remove('active');
|
||
document.body.style.overflow = '';
|
||
}
|
||
|
||
modalClose.addEventListener('click', hideModal);
|
||
modalConfirm.addEventListener('click', hideModal);
|
||
modal.addEventListener('click', e => {
|
||
if (e.target === modal) hideModal();
|
||
});
|
||
|
||
/* 标签切换 */
|
||
const tabs = document.querySelectorAll('.tab');
|
||
const tabContents = document.querySelectorAll('.tab-content');
|
||
tabs.forEach(tab => {
|
||
tab.addEventListener('click', () => {
|
||
tabs.forEach(t => t.classList.remove('active'));
|
||
tabContents.forEach(c => c.classList.remove('active'));
|
||
tab.classList.add('active');
|
||
document.getElementById(`${tab.dataset.tab}-content`).classList.add('active');
|
||
});
|
||
});
|
||
|
||
/* 复制/导出 */
|
||
/* 优化后的复制功能:支持HTTPS/HTTP、兼容旧浏览器、增强错误处理 */
|
||
// 通用复制函数:接收代码块容器ID,返回复制结果
|
||
async function copyToClipboard(codeBlockContainerId) {
|
||
try {
|
||
// 1. 安全获取代码块元素(避免选择器失效)
|
||
const container = document.getElementById(codeBlockContainerId);
|
||
if (!container) {
|
||
throw new Error("复制失败:未找到代码块容器,请检查页面结构");
|
||
}
|
||
const codeBlock = container.querySelector(".code-block");
|
||
if (!codeBlock) {
|
||
throw new Error("复制失败:代码块元素不存在");
|
||
}
|
||
|
||
// 2. 获取纯净文本(排除HTML标签,处理换行格式)
|
||
const codeText = codeBlock.textContent.trim();
|
||
if (!codeText) {
|
||
throw new Error("复制失败:代码块内容为空");
|
||
}
|
||
|
||
// 3. 优先使用现代剪贴板API(HTTPS环境),降级使用execCommand(HTTP/旧浏览器)
|
||
if (navigator.clipboard && window.isSecureContext) {
|
||
// 现代浏览器:HTTPS环境下使用clipboard API
|
||
await navigator.clipboard.writeText(codeText);
|
||
} else {
|
||
// 降级方案:创建临时文本框,用execCommand复制(兼容HTTP/旧浏览器)
|
||
const tempTextarea = document.createElement("textarea");
|
||
// 隐藏临时文本框(避免影响页面布局)
|
||
tempTextarea.style.position = "fixed";
|
||
tempTextarea.style.top = "-999px";
|
||
tempTextarea.style.left = "-999px";
|
||
tempTextarea.value = codeText;
|
||
document.body.appendChild(tempTextarea);
|
||
|
||
// 选中文本并复制
|
||
tempTextarea.select();
|
||
const copySuccess = document.execCommand("copy");
|
||
document.body.removeChild(tempTextarea); // 复制后删除临时元素
|
||
|
||
if (!copySuccess) {
|
||
throw new Error("复制失败:浏览器不支持传统复制方法,请升级浏览器或切换HTTPS环境");
|
||
}
|
||
}
|
||
|
||
// 4. 复制成功提示
|
||
showModal("复制成功", "代码已成功复制到剪贴板,可直接粘贴使用");
|
||
} catch (err) {
|
||
// 5. 区分错误类型,给出明确提示(方便生产环境排查)
|
||
let errorMsg = "复制失败:";
|
||
if (err.message.includes("HTTPS")) {
|
||
errorMsg += "当前为HTTP环境,建议切换到HTTPS以使用更稳定的复制功能";
|
||
} else if (err.message.includes("不存在") || err.message.includes("容器")) {
|
||
errorMsg += "页面元素异常,请刷新页面重试";
|
||
} else if (err.message.includes("空")) {
|
||
errorMsg += "代码块无内容,无需复制";
|
||
} else {
|
||
errorMsg += err.message || "未知错误,请检查浏览器权限";
|
||
}
|
||
showModal("复制失败", errorMsg);
|
||
console.error("复制功能错误详情:", err); // 生产环境可上报日志
|
||
}
|
||
}
|
||
|
||
// 绑定SELECT复制按钮事件(传入SELECT代码块容器ID)
|
||
document.getElementById("copy-select").addEventListener("click", () => {
|
||
copyToClipboard("select-content");
|
||
});
|
||
|
||
// 绑定DDL复制按钮事件(传入DDL代码块容器ID)
|
||
document.getElementById("copy-ddl").addEventListener("click", () => {
|
||
copyToClipboard("ddl-content");
|
||
});
|
||
|
||
// 导出功能提示优化(原功能待实现,增强用户体验)
|
||
document.getElementById("export-field").addEventListener("click", () => {
|
||
showModal("提示", "导出功能暂未实现,您可先复制字段信息表格内容");
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |