2025-08-29 18:08:32 +08:00
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
|
<html lang="zh-CN">
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
|
<title>市区信息表结构</title>
|
|
|
|
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
|
|
|
|
<style>
|
|
|
|
|
|
* {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
body {
|
2025-08-30 15:26:27 +08:00
|
|
|
|
background: linear-gradient(135deg, #e6f7ff 0%, #f0f7ff 100%);
|
2025-08-29 18:08:32 +08:00
|
|
|
|
min-height: 100vh;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
padding: 10px; /* 核心:距离页面两边10px */
|
2025-08-29 18:08:32 +08:00
|
|
|
|
color: #2c3e50;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
overflow: hidden;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.container {
|
2025-08-30 15:26:27 +08:00
|
|
|
|
max-width: 100%;
|
|
|
|
|
|
margin: 0 auto; /* 水平居中,同时借助body的10px padding实现边缘距离 */
|
2025-08-29 18:08:32 +08:00
|
|
|
|
display: flex;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
gap: 15px;
|
|
|
|
|
|
height: calc(100vh - 20px); /* 减去body上下10px padding,占满垂直空间 */
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel {
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
border-radius: 16px;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
box-shadow: 0 4px 20px rgba(0, 120, 255, 0.08);
|
2025-08-29 18:08:32 +08:00
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
transition: transform 0.3s ease;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
height: 100%; /* 面板高度铺满容器,避免内容溢出 */
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel:hover {
|
2025-08-30 15:26:27 +08:00
|
|
|
|
transform: translateY(-3px);
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel-header {
|
2025-08-30 15:26:27 +08:00
|
|
|
|
background: linear-gradient(135deg, #4096ff 0%, #165dff 100%);
|
2025-08-29 18:08:32 +08:00
|
|
|
|
color: white;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
padding: 16px 20px;
|
|
|
|
|
|
font-size: 1.2rem;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
|
2025-08-30 15:26:27 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
flex-shrink: 0; /* 表头不压缩 */
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel-content {
|
2025-08-30 15:26:27 +08:00
|
|
|
|
padding: 20px;
|
|
|
|
|
|
flex: 1; /* 内容区占据剩余高度 */
|
2025-08-29 18:08:32 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
overflow: hidden; /* 关键:父容器隐藏溢出,确保子滚动容器生效 */
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.left-panel {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.right-panel {
|
|
|
|
|
|
flex: 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-card {
|
2025-08-30 15:26:27 +08:00
|
|
|
|
background: #eef7ff;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
border-radius: 12px;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
padding: 18px;
|
|
|
|
|
|
margin-bottom: 18px;
|
|
|
|
|
|
border-left: 4px solid #4096ff;
|
|
|
|
|
|
flex-shrink: 0; /* 信息卡片不压缩 */
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-row {
|
|
|
|
|
|
display: flex;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
margin-bottom: 12px;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
align-items: center;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
line-height: 1.5;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-label {
|
|
|
|
|
|
font-weight: 600;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
color: #165dff;
|
|
|
|
|
|
width: 110px;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-label::after {
|
|
|
|
|
|
content: ":";
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
right: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-value {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
color: #343a40;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.button-panel {
|
|
|
|
|
|
display: flex;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
gap: 10px;
|
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
|
border-bottom: 1px solid #e6f0ff;
|
|
|
|
|
|
padding-bottom: 12px;
|
|
|
|
|
|
flex-shrink: 0; /* 按钮区不压缩,固定高度 */
|
|
|
|
|
|
flex-wrap: wrap; /* 小屏幕按钮换行 */
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.button {
|
2025-08-30 15:26:27 +08:00
|
|
|
|
background: linear-gradient(135deg, #f0f7ff 0%, #e6f0ff 100%);
|
|
|
|
|
|
padding: 9px 18px;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
font-weight: 600;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
color: #165dff;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
cursor: pointer;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
border: 1px solid #d1e0ff;
|
|
|
|
|
|
transition: all 0.25s ease;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.button:hover {
|
2025-08-30 15:26:27 +08:00
|
|
|
|
background: linear-gradient(135deg, #e6f0ff 0%, #d1e0ff 100%);
|
|
|
|
|
|
box-shadow: 0 3px 6px rgba(64, 150, 255, 0.1);
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.button.active {
|
2025-08-30 15:26:27 +08:00
|
|
|
|
background: linear-gradient(135deg, #4096ff 0%, #165dff 100%);
|
2025-08-29 18:08:32 +08:00
|
|
|
|
color: white;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
border-color: #165dff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 1. 字段信息面板:滚动容器修复(确保滚动条必现) */
|
|
|
|
|
|
.table-scroll-container {
|
|
|
|
|
|
flex: 1; /* 占据内容区剩余高度 */
|
|
|
|
|
|
overflow-y: auto; /* 内容溢出时显示垂直滚动条 */
|
|
|
|
|
|
overflow-x: auto; /* 小屏幕列宽不足时显示水平滚动条 */
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
box-shadow: 0 3px 12px rgba(0, 120, 255, 0.05);
|
|
|
|
|
|
min-height: 100px; /* 最小高度,避免内容过少时容器塌陷 */
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
table {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
border-collapse: separate;
|
|
|
|
|
|
border-spacing: 0;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
background: white;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
table-layout: fixed; /* 固定列宽,避免表头错位 */
|
|
|
|
|
|
min-width: 600px; /* 表格最小宽度,确保小屏幕触发水平滚动 */
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
th {
|
2025-08-30 15:26:27 +08:00
|
|
|
|
background: linear-gradient(135deg, #4096ff 0%, #165dff 100%);
|
2025-08-29 18:08:32 +08:00
|
|
|
|
color: white;
|
|
|
|
|
|
font-weight: 600;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
padding: 14px 15px;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
text-align: left;
|
|
|
|
|
|
border-right: 1px solid rgba(255, 255, 255, 0.15);
|
|
|
|
|
|
position: sticky;
|
|
|
|
|
|
top: 0;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
z-index: 20;
|
|
|
|
|
|
background-clip: padding-box; /* 防止背景透色 */
|
|
|
|
|
|
/* 列宽分配:适配内容优先级 */
|
|
|
|
|
|
width: 100px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
th:nth-child(1) {
|
|
|
|
|
|
width: 80px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 序号 */
|
|
|
|
|
|
th:nth-child(2) {
|
|
|
|
|
|
width: 180px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 字段名称 */
|
|
|
|
|
|
th:nth-child(3) {
|
|
|
|
|
|
width: 150px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 字段类型 */
|
|
|
|
|
|
th:nth-child(4) {
|
|
|
|
|
|
width: 200px;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 15:26:27 +08:00
|
|
|
|
/* 描述(增加宽度,减少换行) */
|
|
|
|
|
|
th:nth-child(5) {
|
|
|
|
|
|
width: 120px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 键类型 */
|
|
|
|
|
|
|
2025-08-29 18:08:32 +08:00
|
|
|
|
th:last-child {
|
|
|
|
|
|
border-right: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
td {
|
2025-08-30 15:26:27 +08:00
|
|
|
|
padding: 12px 15px;
|
|
|
|
|
|
border-bottom: 1px solid #e6f0ff;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
color: #495057;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
width: 100px;
|
|
|
|
|
|
word-wrap: break-word; /* 长文本换行,避免列宽异常 */
|
|
|
|
|
|
word-break: break-all;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
td:nth-child(1) {
|
|
|
|
|
|
width: 80px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
td:nth-child(2) {
|
|
|
|
|
|
width: 180px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
td:nth-child(3) {
|
|
|
|
|
|
width: 150px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
td:nth-child(4) {
|
|
|
|
|
|
width: 200px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
td:nth-child(5) {
|
|
|
|
|
|
width: 120px;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tr:nth-child(even) {
|
2025-08-30 15:26:27 +08:00
|
|
|
|
background-color: #f7fbff;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tr:hover td {
|
2025-08-30 15:26:27 +08:00
|
|
|
|
background-color: rgba(64, 150, 255, 0.08);
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.primary-key {
|
2025-08-30 15:26:27 +08:00
|
|
|
|
background-color: #e6f4ff;
|
|
|
|
|
|
color: #165dff;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
font-weight: 600;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
padding: 2px 8px;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
border-radius: 50px;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
font-size: 0.8rem;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.index-key {
|
|
|
|
|
|
background-color: #e8f5e9;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
color: #00864e;
|
|
|
|
|
|
padding: 2px 8px;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
border-radius: 50px;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
font-size: 0.8rem;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.stats-row {
|
|
|
|
|
|
display: flex;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
gap: 15px;
|
|
|
|
|
|
margin-top: 12px;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
color: #6c757d;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
flex-shrink: 0; /* 统计行不压缩 */
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.stat-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
gap: 5px;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-ago {
|
2025-08-30 15:26:27 +08:00
|
|
|
|
color: #ff4d4f;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
font-weight: 500;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
font-size: 0.85rem;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 15:26:27 +08:00
|
|
|
|
/* 2. SQL面板:滚动容器修复(同字段面板逻辑) */
|
|
|
|
|
|
.sql-container {
|
|
|
|
|
|
flex: 1; /* 占据剩余高度 */
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
box-shadow: 0 3px 12px rgba(0, 120, 255, 0.05);
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
min-height: 100px; /* 最小高度,避免塌陷 */
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 15:26:27 +08:00
|
|
|
|
.sql-toolbar {
|
|
|
|
|
|
background: #f0f7ff;
|
|
|
|
|
|
padding: 8px 15px;
|
|
|
|
|
|
border-bottom: 1px solid #e6f0ff;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
flex-shrink: 0; /* 工具条不压缩 */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sql-copy-btn {
|
|
|
|
|
|
background: #4096ff;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 5px;
|
|
|
|
|
|
transition: background 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sql-copy-btn:hover {
|
|
|
|
|
|
background: #165dff;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 15:26:27 +08:00
|
|
|
|
/* SQL输入框:确保滚动条生效 */
|
|
|
|
|
|
.sql-input {
|
|
|
|
|
|
flex: 1; /* 占据SQL容器剩余高度 */
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
padding: 15px;
|
|
|
|
|
|
font-family: 'Consolas', 'Monaco', monospace;
|
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
overflow-y: auto; /* 垂直滚动 */
|
|
|
|
|
|
overflow-x: auto; /* 水平滚动(长SQL语句) */
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
contenteditable: true;
|
|
|
|
|
|
min-height: 80px; /* 输入框最小高度 */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 移除pre标签默认样式干扰(适配动态SQL) */
|
|
|
|
|
|
.sql-input pre {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
font-size: inherit;
|
|
|
|
|
|
line-height: inherit;
|
|
|
|
|
|
white-space: inherit; /* 继承sql-input的换行规则 */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* SQL高亮样式(不破坏格式) */
|
2025-08-30 15:38:11 +08:00
|
|
|
|
.sql-keyword {
|
|
|
|
|
|
color: #d73a49;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sql-table {
|
|
|
|
|
|
color: #005cc5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sql-column {
|
|
|
|
|
|
color: #6f42c1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sql-comment {
|
|
|
|
|
|
color: #6a737d;
|
|
|
|
|
|
}
|
2025-08-30 15:26:27 +08:00
|
|
|
|
|
|
|
|
|
|
/* 复制提示 */
|
|
|
|
|
|
.copy-toast {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
bottom: 30px;
|
|
|
|
|
|
right: 30px;
|
|
|
|
|
|
background: rgba(0, 120, 255, 0.9);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
transition: opacity 0.3s ease;
|
|
|
|
|
|
z-index: 100;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.copy-toast.show {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 统一滚动条样式(确保所有滚动容器外观一致) */
|
|
|
|
|
|
::-webkit-scrollbar {
|
2025-08-29 18:08:32 +08:00
|
|
|
|
width: 8px;
|
|
|
|
|
|
height: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 15:26:27 +08:00
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
|
|
|
|
background: #4096ff;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
border-radius: 4px;
|
2025-08-30 15:26:27 +08:00
|
|
|
|
transition: background 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
|
|
|
|
background: #165dff;
|
2025-08-29 18:08:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 15:26:27 +08:00
|
|
|
|
::-webkit-scrollbar-track {
|
|
|
|
|
|
background: rgba(64, 150, 255, 0.1);
|
2025-08-29 18:08:32 +08:00
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
}
|
2025-08-30 15:26:27 +08:00
|
|
|
|
|
|
|
|
|
|
/* 内容面板切换基础样式 */
|
|
|
|
|
|
.content-panel {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
overflow: hidden; /* 子容器溢出控制 */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.content-panel.active {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 3. 小屏幕/页面缩小适配(确保滚动条正常触发) */
|
|
|
|
|
|
@media (max-width: 1200px) {
|
|
|
|
|
|
.container {
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel-content {
|
|
|
|
|
|
padding: 15px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
th, td {
|
|
|
|
|
|
padding: 12px 10px;
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
th:nth-child(4) {
|
|
|
|
|
|
width: 180px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 小屏幕压缩描述列宽 */
|
|
|
|
|
|
td:nth-child(4) {
|
|
|
|
|
|
width: 180px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 1000px) {
|
|
|
|
|
|
.container {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
height: auto; /* 垂直布局时容器高度自适应 */
|
|
|
|
|
|
max-height: calc(100vh - 20px); /* 但不超过屏幕高度 */
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.table-scroll-container,
|
|
|
|
|
|
.sql-container {
|
|
|
|
|
|
max-height: 300px; /* 移动端限制最大高度,确保滚动 */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 移动端表格列宽进一步压缩 */
|
|
|
|
|
|
th:nth-child(2) {
|
|
|
|
|
|
width: 150px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
th:nth-child(3) {
|
|
|
|
|
|
width: 120px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
th:nth-child(4) {
|
|
|
|
|
|
width: 150px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
td:nth-child(2) {
|
|
|
|
|
|
width: 150px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
td:nth-child(3) {
|
|
|
|
|
|
width: 120px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
td:nth-child(4) {
|
|
|
|
|
|
width: 150px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 600px) {
|
|
|
|
|
|
.button {
|
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-label {
|
|
|
|
|
|
width: 90px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 超小屏幕表格列宽最小化 */
|
|
|
|
|
|
th {
|
|
|
|
|
|
padding: 10px 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
td {
|
|
|
|
|
|
padding: 10px 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
th:nth-child(2) {
|
|
|
|
|
|
width: 120px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
th:nth-child(4) {
|
|
|
|
|
|
width: 120px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
td:nth-child(2) {
|
|
|
|
|
|
width: 120px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
td:nth-child(4) {
|
|
|
|
|
|
width: 120px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
<div class="container">
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<!-- 左侧面板:表基础信息 -->
|
2025-08-29 18:08:32 +08:00
|
|
|
|
<div class="panel left-panel">
|
|
|
|
|
|
<div class="panel-header">
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<i class="fas fa-database"></i>
|
|
|
|
|
|
<span>表基础信息</span>
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</div>
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<div class="panel-content" th:each="t : ${data}">
|
2025-08-29 18:08:32 +08:00
|
|
|
|
<div class="info-card">
|
|
|
|
|
|
<div class="info-row">
|
|
|
|
|
|
<div class="info-label">数据库</div>
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<div class="info-value"><p th:text="${t.dbName}"></p></div>
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="info-row">
|
|
|
|
|
|
<div class="info-label">表名称</div>
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<div class="info-value" th:text="${t.tableName}"><strong></strong></div>
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="info-row">
|
|
|
|
|
|
<div class="info-label">表描述</div>
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<div class="info-value"><p th:text="${t.tableDesc}"></p></div>
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="info-row">
|
|
|
|
|
|
<div class="info-label">存储量</div>
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<div class="info-value"><p th:text="${t.dataLength}"></p></div>
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="info-card">
|
|
|
|
|
|
<div class="info-row">
|
|
|
|
|
|
<div class="info-label">创建时间</div>
|
|
|
|
|
|
<div class="info-value">
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<p th:text="${t.createTime}"></p>
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="info-row">
|
|
|
|
|
|
<div class="info-label">更新时间</div>
|
|
|
|
|
|
<div class="info-value">
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<p th:text="${t.updateTime}"></p>
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="stats-row">
|
|
|
|
|
|
<div class="stat-item">
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<i class="fas fa-key" style="color: #165dff;"></i>
|
2025-08-30 15:38:11 +08:00
|
|
|
|
<p>主键:<span th:text="${pkCnt}">0</span> 个</p>
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat-item">
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<i class="fas fa-sitemap" style="color: #00864e;"></i>
|
2025-08-30 15:38:11 +08:00
|
|
|
|
<p>索引:<span th:text="${idxCnt}">0</span> 个</p>
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat-item">
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<i class="fas fa-list" style="color: #4096ff;"></i>
|
2025-08-30 15:38:11 +08:00
|
|
|
|
<p>字段:<span th:text="${colCnt}">0</span> 个</p>
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<!-- 右侧面板:功能切换区 -->
|
2025-08-29 18:08:32 +08:00
|
|
|
|
<div class="panel right-panel">
|
|
|
|
|
|
<div class="panel-header">
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<i class="fas fa-columns"></i>
|
|
|
|
|
|
<span>表结构操作</span>
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="panel-content">
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<!-- 切换按钮 -->
|
2025-08-29 18:08:32 +08:00
|
|
|
|
<div class="button-panel">
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<div class="button active" data-target="field-info">
|
|
|
|
|
|
<i class="fas fa-list-ul"></i>
|
|
|
|
|
|
<span>字段信息</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="button" data-target="generate-select">
|
|
|
|
|
|
<i class="fas fa-code"></i>
|
|
|
|
|
|
<span>生成SELECT</span>
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</div>
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<div class="button" data-target="generate-ddl">
|
|
|
|
|
|
<i class="fas fa-database"></i>
|
|
|
|
|
|
<span>生成DDL</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 1. 字段信息面板(滚动条修复) -->
|
|
|
|
|
|
<div id="field-info" class="content-panel active">
|
|
|
|
|
|
<div class="table-scroll-container">
|
|
|
|
|
|
<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><strong th:text="${c.colName}"></strong></td>
|
|
|
|
|
|
<td th:text="${c.colType}"></td>
|
|
|
|
|
|
<td th:text="${c.colDesc}"></td>
|
|
|
|
|
|
<td><span class="primary-key" th:text="${c.keyType}"></span></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</div>
|
2025-08-30 15:26:27 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 2. 生成SELECT语句面板(滚动条修复) -->
|
|
|
|
|
|
<div id="generate-select" class="content-panel">
|
|
|
|
|
|
<div class="sql-container">
|
|
|
|
|
|
<div class="sql-toolbar">
|
|
|
|
|
|
<button class="sql-copy-btn" data-target="select-sql">
|
|
|
|
|
|
<i class="fas fa-copy"></i> 复制SQL
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2025-08-30 15:34:11 +08:00
|
|
|
|
<div id="select-sql" class="sql-input" th:text="${selectSql}">
|
2025-08-30 15:26:27 +08:00
|
|
|
|
</div>
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-08-30 15:26:27 +08:00
|
|
|
|
<!-- 3. 生成DDL语句面板(滚动条修复) -->
|
|
|
|
|
|
<div id="generate-ddl" class="content-panel">
|
|
|
|
|
|
<div class="sql-container">
|
|
|
|
|
|
<div class="sql-toolbar">
|
|
|
|
|
|
<button class="sql-copy-btn" data-target="ddl-sql">
|
|
|
|
|
|
<i class="fas fa-copy"></i> 复制SQL
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2025-08-30 15:34:11 +08:00
|
|
|
|
<div id="ddl-sql" class="sql-input" th:text="${ddlSql}">
|
2025-08-30 15:26:27 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-08-30 15:26:27 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 复制成功提示框 -->
|
|
|
|
|
|
<div class="copy-toast" id="copyToast">复制成功!</div>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
// 1. 功能切换逻辑
|
|
|
|
|
|
const buttons = document.querySelectorAll('.button');
|
|
|
|
|
|
const contentPanels = document.querySelectorAll('.content-panel');
|
|
|
|
|
|
|
|
|
|
|
|
buttons.forEach(button => {
|
|
|
|
|
|
button.addEventListener('click', () => {
|
|
|
|
|
|
buttons.forEach(btn => btn.classList.remove('active'));
|
|
|
|
|
|
button.classList.add('active');
|
|
|
|
|
|
|
|
|
|
|
|
const targetId = button.getAttribute('data-target');
|
|
|
|
|
|
contentPanels.forEach(panel => panel.classList.remove('active'));
|
|
|
|
|
|
const activePanel = document.getElementById(targetId);
|
|
|
|
|
|
activePanel.classList.add('active');
|
|
|
|
|
|
// 切换后重置滚动条位置(可选,提升体验)
|
|
|
|
|
|
const scrollContainer = activePanel.querySelector('.table-scroll-container, .sql-input');
|
|
|
|
|
|
if (scrollContainer) scrollContainer.scrollTop = 0;
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 2. SQL复制功能
|
|
|
|
|
|
const copyButtons = document.querySelectorAll('.sql-copy-btn');
|
|
|
|
|
|
const copyToast = document.getElementById('copyToast');
|
|
|
|
|
|
|
|
|
|
|
|
copyButtons.forEach(btn => {
|
|
|
|
|
|
btn.addEventListener('click', () => {
|
|
|
|
|
|
const targetId = btn.getAttribute('data-target');
|
|
|
|
|
|
const sqlInput = document.getElementById(targetId);
|
|
|
|
|
|
|
|
|
|
|
|
// 适配contenteditable复制
|
|
|
|
|
|
const range = document.createRange();
|
|
|
|
|
|
range.selectNodeContents(sqlInput);
|
|
|
|
|
|
const selection = window.getSelection();
|
|
|
|
|
|
selection.removeAllRanges();
|
|
|
|
|
|
selection.addRange(range);
|
|
|
|
|
|
document.execCommand('copy');
|
|
|
|
|
|
selection.removeAllRanges();
|
|
|
|
|
|
|
|
|
|
|
|
// 显示提示
|
|
|
|
|
|
copyToast.classList.add('show');
|
|
|
|
|
|
setTimeout(() => copyToast.classList.remove('show'), 2000);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 3. SQL编辑高亮逻辑
|
|
|
|
|
|
const sqlInputs = document.querySelectorAll('.sql-input');
|
|
|
|
|
|
const sqlKeywords = ['SELECT', 'FROM', 'WHERE', 'ORDER BY', 'GROUP BY', 'LIMIT', 'JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'ON', 'AND', 'OR', 'NOT', 'IN', 'NOT IN', 'BETWEEN', 'LIKE', 'CREATE TABLE', 'PRIMARY KEY', 'KEY', 'ENGINE', 'DEFAULT', 'CHARSET', 'COMMENT', 'NOT NULL', 'NULL', 'INT', 'VARCHAR', 'DATETIME', 'DECIMAL', 'TEXT', 'TINYINT', 'BIGINT'];
|
|
|
|
|
|
const sqlTables = ['work.biz_cities', 'idx_province_code', 'idx_f_flow_id', 'idx_tenant_delete'];
|
|
|
|
|
|
|
|
|
|
|
|
function highlightSQL(inputElement) {
|
|
|
|
|
|
let sqlText = inputElement.innerText;
|
|
|
|
|
|
|
|
|
|
|
|
// 高亮关键字(按长度排序,避免短关键字覆盖长关键字)
|
|
|
|
|
|
const sortedKeywords = [...sqlKeywords].sort((a, b) => b.length - a.length);
|
|
|
|
|
|
sortedKeywords.forEach(keyword => {
|
|
|
|
|
|
const regex = new RegExp(`\\b(${keyword})\\b`, 'gi');
|
|
|
|
|
|
sqlText = sqlText.replace(regex, '<span class="sql-keyword">$1</span>');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 高亮表名
|
|
|
|
|
|
sqlTables.forEach(table => {
|
|
|
|
|
|
const regex = new RegExp(`\\b(${table})\\b`, 'g');
|
|
|
|
|
|
sqlText = sqlText.replace(regex, '<span class="sql-table">$1</span>');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 高亮字段名(排除已高亮内容)
|
|
|
|
|
|
const originalText = inputElement.innerText;
|
|
|
|
|
|
const columnMatches = originalText.match(/\b([a-z_]+)\b/g) || [];
|
|
|
|
|
|
const validColumns = columnMatches.filter(col =>
|
|
|
|
|
|
!sqlKeywords.some(key => key.toUpperCase() === col.toUpperCase()) &&
|
|
|
|
|
|
!sqlTables.includes(col)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
validColumns.forEach(column => {
|
|
|
|
|
|
const regex = new RegExp(`(?<!<span class="[^"]+">)\\b(${column})\\b(?!<\/span>)`, 'g');
|
|
|
|
|
|
sqlText = sqlText.replace(regex, '<span class="sql-column">$1</span>');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 高亮注释
|
|
|
|
|
|
sqlText = sqlText.replace(/(-- .*)/g, '<span class="sql-comment">$1</span>');
|
|
|
|
|
|
|
|
|
|
|
|
// 保留光标位置
|
|
|
|
|
|
const selection = window.getSelection();
|
|
|
|
|
|
const range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
|
|
|
|
|
|
const startOffset = range ? range.startOffset : 0;
|
|
|
|
|
|
|
|
|
|
|
|
inputElement.innerHTML = sqlText;
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复光标
|
|
|
|
|
|
if (range && inputElement.firstChild) {
|
|
|
|
|
|
const newRange = document.createRange();
|
|
|
|
|
|
newRange.setStart(inputElement.firstChild, Math.min(startOffset, inputElement.firstChild.length));
|
|
|
|
|
|
newRange.setEnd(inputElement.firstChild, Math.min(startOffset, inputElement.firstChild.length));
|
|
|
|
|
|
selection.removeAllRanges();
|
|
|
|
|
|
selection.addRange(newRange);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化高亮
|
|
|
|
|
|
sqlInputs.forEach(input => highlightSQL(input));
|
|
|
|
|
|
|
|
|
|
|
|
// 编辑实时高亮
|
|
|
|
|
|
let highlightTimer;
|
|
|
|
|
|
sqlInputs.forEach(input => {
|
|
|
|
|
|
input.addEventListener('input', () => {
|
|
|
|
|
|
clearTimeout(highlightTimer);
|
|
|
|
|
|
highlightTimer = setTimeout(() => highlightSQL(input), 300);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
2025-08-29 18:08:32 +08:00
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|