Files
c-api/src/main/resources/templates/views/data/detail.html

573 lines
18 KiB
HTML
Raw Normal View History

2025-08-29 18:08:32 +08:00
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
2025-08-30 23:39:22 +08:00
<title>表结构信息</title>
2025-08-29 18:08:32 +08:00
<style>
2025-08-30 23:39:22 +08:00
/* ===== 全局 & 布局(沿用原样式,略) ===== */
2025-08-29 18:08:32 +08:00
* {
margin: 0;
padding: 0;
box-sizing: border-box;
2025-08-30 23:39:22 +08:00
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
2025-08-29 18:08:32 +08:00
}
body {
2025-08-30 23:39:22 +08:00
padding-bottom: 10px;
2025-08-29 18:08:32 +08:00
min-height: 100vh;
}
.container {
display: flex;
2025-08-30 23:39:22 +08:00
padding: 16px;
gap: 16px;
min-height: calc(100vh - 10px);
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.left-column {
flex: 0 0 520px;
2025-08-29 18:08:32 +08:00
display: flex;
flex-direction: column;
2025-08-30 23:39:22 +08:00
gap: 15px;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.card {
border: 1px solid #ccc;
border-radius: 4px;
padding: 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, .1);
background: #fff;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.card-header {
font-weight: bold;
margin-bottom: 12px;
font-size: 16px;
color: #333;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.detail {
margin-bottom: 8px;
color: #555;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.time-info p {
margin-bottom: 4px;
color: #555;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.stats-row {
display: flex;
gap: 15px;
margin-top: 12px;
color: #6c757d;
font-size: .85rem;
flex-shrink: 0;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.stat-item {
2025-08-29 18:08:32 +08:00
display: flex;
align-items: center;
2025-08-30 23:39:22 +08:00
gap: 5px;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.right-column {
2025-08-29 18:08:32 +08:00
flex: 1;
display: flex;
2025-08-30 23:39:22 +08:00
flex-direction: column;
gap: 15px;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
/* ===== 标签栏 ===== */
.tabs {
2025-08-30 15:26:27 +08:00
display: flex;
2025-08-30 23:39:22 +08:00
border-bottom: 1px solid #dadce0;
padding: 0 8px;
background: #f8f9fa;
border-radius: 8px 8px 0 0;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.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;
2025-08-30 15:26:27 +08:00
}
2025-08-30 23:39:22 +08:00
.tab:hover {
background: rgba(60, 64, 67, .08);
color: #202124;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.tab.active {
background: #e8f0fe;
color: #1967d2;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.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;
2025-08-30 15:26:27 +08:00
}
2025-08-30 23:39:22 +08:00
.tab-content.active {
display: flex;
2025-08-30 15:26:27 +08:00
}
2025-08-30 23:39:22 +08:00
.content-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #eee;
2025-08-30 15:26:27 +08:00
}
2025-08-30 23:39:22 +08:00
.content-title {
font-weight: 500;
font-size: 16px;
color: #202124;
2025-08-30 15:26:27 +08:00
}
2025-08-30 23:39:22 +08:00
.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;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.action-btn:hover {
background: #1557b0;
2025-08-30 15:26:27 +08:00
}
2025-08-30 23:39:22 +08:00
.content-body {
flex: 1;
overflow: hidden;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
/* ===== 字段表格:行边框、无列边框 ===== */
.table-container {
width: 100%;
height: 100%;
overflow: auto;
2025-08-30 15:26:27 +08:00
}
2025-08-30 23:39:22 +08:00
.field-table {
width: 100%;
border-collapse: collapse;
min-width: 600px;
2025-08-30 15:26:27 +08:00
}
2025-08-30 23:39:22 +08:00
.field-table thead {
position: sticky;
top: 0;
z-index: 1;
2025-08-30 15:26:27 +08:00
}
2025-08-30 23:39:22 +08:00
.field-table th, .field-table td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
border-left: none;
border-right: none;
2025-08-30 15:26:27 +08:00
}
2025-08-30 23:39:22 +08:00
.field-table thead tr:first-child th {
border-top: none;
2025-08-30 15:26:27 +08:00
}
2025-08-30 23:39:22 +08:00
.field-table th {
background: #e3f2fd;
color: #1565c0;
font-weight: 500;
white-space: nowrap;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.field-table tr:nth-child(even) {
background: #f8f9fa;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.field-table tr:hover {
background: #e8f5e9;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:52:58 +08:00
.col-name {
2025-08-30 23:39:22 +08:00
display: inline-block;
2025-08-30 23:52:58 +08:00
font-family: "JetBrains Mono", SFMono-Regular, Consolas, monospace;
2025-08-29 18:08:32 +08:00
font-weight: 600;
2025-08-30 23:39:22 +08:00
font-size: 0.875rem;
2025-08-30 23:52:58 +08:00
color: #0c4a6e; /* 深蓝字 */
2025-08-30 23:39:22 +08:00
padding: 3px 8px 4px;
border-radius: 4px;
background: linear-gradient(90deg, #e0f2fe 0%, #bae6fd 100%); /* 浅蓝渐变 */
2025-08-30 23:52:58 +08:00
box-shadow: 0 1px 2px rgba(14, 165, 233, .15);
2025-08-30 23:39:22 +08:00
transition: box-shadow .2s;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:52:58 +08:00
.col-name:hover {
box-shadow: 0 2px 6px rgba(14, 165, 233, .25);
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
/* ===== 代码块 ===== */
.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;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
/* ===== 对话框 ===== */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, .5);
2025-08-29 18:08:32 +08:00
display: flex;
align-items: center;
2025-08-30 23:39:22 +08:00
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: .3s;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.modal-overlay.active {
opacity: 1;
visibility: visible;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.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;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.modal-overlay.active .modal {
transform: translateY(0);
2025-08-30 15:26:27 +08:00
}
2025-08-30 23:39:22 +08:00
.modal-header {
2025-08-30 15:26:27 +08:00
display: flex;
2025-08-30 23:39:22 +08:00
justify-content: space-between;
2025-08-30 15:26:27 +08:00
align-items: center;
2025-08-30 23:39:22 +08:00
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid #eee;
2025-08-29 18:08:32 +08:00
}
2025-08-30 23:39:22 +08:00
.modal-title {
font-size: 18px;
font-weight: 500;
color: #202124;
2025-08-30 15:26:27 +08:00
}
2025-08-30 23:39:22 +08:00
.modal-close {
background: none;
2025-08-30 15:26:27 +08:00
border: none;
2025-08-30 23:39:22 +08:00
font-size: 20px;
cursor: pointer;
color: #5f6368;
transition: .2s;
2025-08-30 15:38:11 +08:00
}
2025-08-30 23:39:22 +08:00
.modal-close:hover {
color: #202124;
2025-08-30 15:38:11 +08:00
}
2025-08-30 23:39:22 +08:00
.modal-body {
margin-bottom: 24px;
color: #3c4043;
font-size: 14px;
line-height: 1.5;
2025-08-30 15:38:11 +08:00
}
2025-08-30 23:39:22 +08:00
.modal-footer {
display: flex;
justify-content: flex-end;
2025-08-30 15:38:11 +08:00
}
2025-08-30 15:26:27 +08:00
2025-08-30 23:39:22 +08:00
.modal-btn {
2025-08-30 15:26:27 +08:00
padding: 8px 16px;
2025-08-30 23:39:22 +08:00
background: #1967d2;
color: #fff;
border: none;
2025-08-29 18:08:32 +08:00
border-radius: 4px;
2025-08-30 23:39:22 +08:00
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: .2s;
2025-08-29 18:08:32 +08:00
}
2025-08-30 15:26:27 +08:00
2025-08-30 23:39:22 +08:00
.modal-btn:hover {
background: #1557b0;
2025-08-30 15:26:27 +08:00
}
2025-08-29 18:08:32 +08:00
</style>
</head>
<body>
<div class="container">
2025-08-30 23:39:22 +08:00
<!-- 左侧 -->
<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>
2025-08-29 18:08:32 +08:00
</div>
2025-08-30 23:39:22 +08:00
<div class="card time-info">
2025-08-30 23:41:22 +08:00
<div>创建时间: <span th:text="${t.createTime}"></span></div>
<div>更新时间: <span th:text="${t.updateTime}"></span></div>
2025-08-30 23:39:22 +08:00
</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>
2025-08-29 18:08:32 +08:00
</div>
</div>
2025-08-30 23:39:22 +08:00
<!-- 右侧 -->
<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>
2025-08-29 18:08:32 +08:00
</div>
2025-08-30 15:26:27 +08:00
2025-08-30 23:39:22 +08:00
<!-- 字段信息 -->
<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">
2025-08-30 15:26:27 +08:00
<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>
2025-08-30 23:39:22 +08:00
<td><span class="col-name" th:text="${c.colName}">f_user_name</span></td>
2025-08-30 15:26:27 +08:00
<td th:text="${c.colType}"></td>
<td th:text="${c.colDesc}"></td>
2025-08-30 23:39:22 +08:00
<td th:text="${c.keyType}"></td>
2025-08-30 15:26:27 +08:00
</tr>
2025-08-30 23:39:22 +08:00
2025-08-30 15:26:27 +08:00
</tbody>
</table>
2025-08-29 18:08:32 +08:00
</div>
2025-08-30 15:26:27 +08:00
</div>
2025-08-30 23:39:22 +08:00
</div>
2025-08-30 15:26:27 +08:00
2025-08-30 23:39:22 +08:00
<!-- 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>
2025-08-29 18:08:32 +08:00
</div>
2025-08-30 23:39:22 +08:00
<div class="content-body">
<pre class="code-block" th:text="${selectSql}"></pre>
</div>
</div>
2025-08-29 18:08:32 +08:00
2025-08-30 23:39:22 +08:00
<!-- 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>
2025-08-29 18:08:32 +08:00
</div>
</div>
</div>
</div>
2025-08-30 15:26:27 +08:00
2025-08-30 23:39:22 +08:00
<!-- 对话框 -->
<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">&times;</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>
2025-08-30 15:26:27 +08:00
<script>
2025-08-30 23:39:22 +08:00
/* 对话框 */
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';
}
2025-08-30 15:26:27 +08:00
2025-08-30 23:39:22 +08:00
function hideModal() {
modal.classList.remove('active');
document.body.style.overflow = '';
2025-08-30 15:26:27 +08:00
}
2025-08-30 23:39:22 +08:00
modalClose.addEventListener('click', hideModal);
modalConfirm.addEventListener('click', hideModal);
modal.addEventListener('click', e => {
if (e.target === modal) hideModal();
});
2025-08-30 15:26:27 +08:00
2025-08-30 23:39:22 +08:00
/* 标签切换 */
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');
2025-08-30 15:26:27 +08:00
});
});
2025-08-30 23:39:22 +08:00
/* 复制/导出 */
2025-08-30 23:52:58 +08:00
/* 优化后的复制功能支持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. 优先使用现代剪贴板APIHTTPS环境降级使用execCommandHTTP/旧浏览器)
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");
2025-08-30 23:39:22 +08:00
});
2025-08-30 23:52:58 +08:00
// 绑定DDL复制按钮事件传入DDL代码块容器ID
document.getElementById("copy-ddl").addEventListener("click", () => {
copyToClipboard("ddl-content");
});
// 导出功能提示优化(原功能待实现,增强用户体验)
document.getElementById("export-field").addEventListener("click", () => {
showModal("提示", "导出功能暂未实现,您可先复制字段信息表格内容");
2025-08-30 23:39:22 +08:00
});
2025-08-30 15:26:27 +08:00
</script>
2025-08-29 18:08:32 +08:00
</body>
</html>