Files
c-api/src/main/resources/templates/views/data/detail.html
2025-08-30 23:52:58 +08:00

573 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">&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>
<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. 优先使用现代剪贴板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");
});
// 绑定DDL复制按钮事件传入DDL代码块容器ID
document.getElementById("copy-ddl").addEventListener("click", () => {
copyToClipboard("ddl-content");
});
// 导出功能提示优化(原功能待实现,增强用户体验)
document.getElementById("export-field").addEventListener("click", () => {
showModal("提示", "导出功能暂未实现,您可先复制字段信息表格内容");
});
</script>
</body>
</html>