668 lines
30 KiB
HTML
668 lines
30 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>文档解析接口测试 - Finyx Data AI</title>
|
||
<link rel="stylesheet" href="test_common.css">
|
||
<style>
|
||
.upload-area {
|
||
border: 2px dashed var(--border-color);
|
||
border-radius: var(--radius);
|
||
padding: 40px;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
transition: var(--transition);
|
||
background-color: var(--light-color);
|
||
}
|
||
.upload-area:hover {
|
||
border-color: var(--primary-color);
|
||
background-color: #e9ecef;
|
||
}
|
||
.upload-area.dragover {
|
||
border-color: var(--primary-color);
|
||
background-color: #e3f2fd;
|
||
}
|
||
.upload-icon {
|
||
font-size: 48px;
|
||
margin-bottom: 16px;
|
||
color: var(--text-muted);
|
||
}
|
||
.upload-text {
|
||
font-size: 14px;
|
||
color: var(--text-muted);
|
||
margin-bottom: 12px;
|
||
}
|
||
.file-list {
|
||
margin-top: 16px;
|
||
}
|
||
.file-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 12px;
|
||
background: var(--white);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: var(--radius);
|
||
margin-bottom: 8px;
|
||
}
|
||
.file-icon {
|
||
font-size: 24px;
|
||
margin-right: 12px;
|
||
}
|
||
.file-info {
|
||
flex: 1;
|
||
}
|
||
.file-name {
|
||
font-weight: 500;
|
||
font-size: 14px;
|
||
}
|
||
.file-size {
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
}
|
||
.file-remove {
|
||
color: var(--danger-color);
|
||
cursor: pointer;
|
||
font-size: 20px;
|
||
}
|
||
.file-remove:hover {
|
||
color: #c62828;
|
||
}
|
||
|
||
.table-preview {
|
||
border: 1px solid var(--border-color);
|
||
border-radius: var(--radius);
|
||
margin-bottom: 16px;
|
||
overflow: hidden;
|
||
}
|
||
.table-preview-header {
|
||
background-color: var(--light-color);
|
||
padding: 12px 16px;
|
||
font-weight: 600;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
.table-preview-body {
|
||
padding: 16px;
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<!-- 页面头部 -->
|
||
<header class="page-header">
|
||
<h1 class="page-title">📄 文档解析接口测试</h1>
|
||
<p class="page-subtitle">
|
||
解析上传的数据字典文档(Excel/Word/PDF),提取表结构信息
|
||
</p>
|
||
</header>
|
||
|
||
<div class="content-grid">
|
||
<!-- 左侧:文件上传 -->
|
||
<div class="col-4">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title">文件上传</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="upload-area" id="uploadArea" onclick="document.getElementById('fileInput').click()">
|
||
<div class="upload-icon">📁</div>
|
||
<div class="upload-text">点击或拖拽文件到此处</div>
|
||
<div class="upload-text" style="font-size: 12px;">
|
||
支持 .xlsx, .xls, .doc, .docx, .pdf 格式
|
||
</div>
|
||
</div>
|
||
<input type="file" id="fileInput" accept=".xlsx,.xls,.doc,.docx,.pdf" style="display: none;" multiple>
|
||
|
||
<div class="form-group">
|
||
<label class="form-label" for="projectId">项目ID *</label>
|
||
<input type="text" id="projectId" class="form-control" value="project_001" placeholder="输入项目ID">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="form-label">快速使用虚拟文件</label>
|
||
<div class="btn-group">
|
||
<button type="button" class="btn btn-info btn-sm" onclick="loadVirtualFile('excel')">Excel 示例</button>
|
||
<button type="button" class="btn btn-info btn-sm" onclick="loadVirtualFile('word')">Word 示例</button>
|
||
<button type="button" class="btn btn-info btn-sm" onclick="loadVirtualFile('pdf')">PDF 示例</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="btn-group" style="margin-top: 20px;">
|
||
<button type="button" class="btn btn-primary" onclick="parseDocument()">🚀 开始解析</button>
|
||
<button type="button" class="btn btn-outline" onclick="resetFiles()">清空</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 文件列表 -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title">已上传文件</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div id="fileList" class="file-list">
|
||
<p style="text-align: center; color: var(--text-muted); padding: 20px;">暂无文件</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- API 调用信息 -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title">API 调用信息</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="form-group">
|
||
<label class="form-label">请求端点</label>
|
||
<code style="display: block; padding: 8px; background: var(--light-color); border-radius: var(--radius); font-size: 12px;">
|
||
POST /api/v1/inventory/parse-document
|
||
</code>
|
||
</div>
|
||
<div id="requestInfo" class="form-group">
|
||
<label class="form-label">请求数据</label>
|
||
<pre id="requestJson" style="max-height: 200px; overflow: auto; font-size: 11px; background: var(--light-color); padding: 8px; border-radius: var(--radius);">等待提交...</pre>
|
||
</div>
|
||
<div id="responseInfo" class="form-group" style="display: none;">
|
||
<label class="form-label">响应数据</label>
|
||
<pre id="responseJson" style="max-height: 300px; overflow: auto; font-size: 11px; background: var(--light-color); padding: 8px; border-radius: var(--radius);"></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧:结果展示 -->
|
||
<div class="col-8">
|
||
<!-- 加载状态 -->
|
||
<div id="loadingArea" style="display: none;"></div>
|
||
|
||
<!-- 结果区域 -->
|
||
<div id="resultArea" style="display: none;">
|
||
<!-- 解析统计 -->
|
||
<div class="stats-grid">
|
||
<div class="stat-card">
|
||
<div class="stat-label">文件数</div>
|
||
<div class="stat-value" id="statFiles">0</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-label">总表数</div>
|
||
<div class="stat-value" id="statTables">0</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-label">总字段数</div>
|
||
<div class="stat-value" id="statFields">0</div>
|
||
</div>
|
||
<div class="stat-card success">
|
||
<div class="stat-label">解析耗时</div>
|
||
<div class="stat-value" id="statTime">0s</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 文件类型分布 -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title">📊 文件类型分布</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div id="fileTypeChart"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 表识别结果 -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title">📋 表结构识别结果</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div id="tableResults"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 字段类型分布 -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title">🔧 字段类型分布</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div id="fieldTypeChart"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 空状态 -->
|
||
<div id="emptyState" class="card" style="text-align: center; padding: 60px 20px;">
|
||
<div style="font-size: 48px; margin-bottom: 20px;">📄</div>
|
||
<h3 style="margin-bottom: 12px;">等待上传文件</h3>
|
||
<p style="color: var(--text-muted);">
|
||
点击或拖拽文件到上传区域<br>
|
||
支持 Excel、Word、PDF 格式的数据字典文档<br>
|
||
也可以使用虚拟文件进行测试
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="base_test_framework.js"></script>
|
||
<script>
|
||
// ==================== 文件管理 ====================
|
||
let uploadedFiles = [];
|
||
let virtualFileType = null;
|
||
|
||
// 虚拟文件数据
|
||
const virtualFiles = {
|
||
excel: {
|
||
name: '数据字典_零售系统.xlsx',
|
||
size: 256000,
|
||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
extension: 'xlsx',
|
||
tables: [
|
||
{
|
||
raw_name: '用户信息表',
|
||
display_name: '用户基础信息',
|
||
description: '存储用户的注册信息和联系方式',
|
||
fields: [
|
||
{ raw_name: 'user_id', display_name: '用户ID', type: 'varchar(64)', comment: '用户唯一标识符', is_primary_key: true, is_nullable: false },
|
||
{ raw_name: 'phone', display_name: '手机号', type: 'varchar(11)', comment: '用户手机号码', is_primary_key: false, is_nullable: false },
|
||
{ raw_name: 'email', display_name: '邮箱', type: 'varchar(100)', comment: '用户电子邮箱', is_primary_key: false, is_nullable: true },
|
||
{ raw_name: 'nickname', display_name: '昵称', type: 'varchar(50)', comment: '用户昵称', is_primary_key: false, is_nullable: true },
|
||
{ raw_name: 'create_time', display_name: '创建时间', type: 'datetime', comment: '注册时间', is_primary_key: false, is_nullable: false }
|
||
],
|
||
field_count: 5
|
||
},
|
||
{
|
||
raw_name: '订单信息表',
|
||
display_name: '订单信息',
|
||
description: '存储订单的基本信息',
|
||
fields: [
|
||
{ raw_name: 'order_id', display_name: '订单ID', type: 'bigint', comment: '订单唯一标识符', is_primary_key: true, is_nullable: false },
|
||
{ raw_name: 'user_id', display_name: '用户ID', type: 'varchar(64)', comment: '下单用户ID', is_primary_key: false, is_nullable: false },
|
||
{ raw_name: 'total_amount', display_name: '订单金额', type: 'decimal(10,2)', comment: '订单总金额', is_primary_key: false, is_nullable: false },
|
||
{ raw_name: 'status', display_name: '订单状态', type: 'tinyint', comment: '1待支付 2已支付 3已取消', is_primary_key: false, is_nullable: false },
|
||
{ raw_name: 'create_time', display_name: '创建时间', type: 'datetime', comment: '订单创建时间', is_primary_key: false, is_nullable: false }
|
||
],
|
||
field_count: 5
|
||
},
|
||
{
|
||
raw_name: '商品信息表',
|
||
display_name: '商品信息',
|
||
description: '存储商品的基本信息',
|
||
fields: [
|
||
{ raw_name: 'product_id', display_name: '商品ID', type: 'bigint', comment: '商品唯一标识符', is_primary_key: true, is_nullable: false },
|
||
{ raw_name: 'product_name', display_name: '商品名称', type: 'varchar(200)', comment: '商品名称', is_primary_key: false, is_nullable: false },
|
||
{ raw_name: 'price', display_name: '价格', type: 'decimal(10,2)', comment: '商品单价', is_primary_key: false, is_nullable: false },
|
||
{ raw_name: 'stock', display_name: '库存', type: 'int', comment: '商品库存数量', is_primary_key: false, is_nullable: false },
|
||
{ raw_name: 'category_id', display_name: '分类ID', type: 'int', comment: '商品分类ID', is_primary_key: false, is_nullable: true }
|
||
],
|
||
field_count: 5
|
||
}
|
||
]
|
||
},
|
||
word: {
|
||
name: '数据字典_文档版.docx',
|
||
size: 128000,
|
||
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||
extension: 'docx',
|
||
tables: [
|
||
{
|
||
raw_name: '会员表',
|
||
display_name: '会员信息',
|
||
description: '会员基础信息和会员等级数据',
|
||
fields: [
|
||
{ raw_name: 'member_id', display_name: '会员编号', type: 'varchar(64)', comment: '会员唯一标识', is_primary_key: true, is_nullable: false },
|
||
{ raw_name: 'member_name', display_name: '会员姓名', type: 'varchar(50)', comment: '会员姓名', is_primary_key: false, is_nullable: true },
|
||
{ raw_name: 'level', display_name: '会员等级', type: 'tinyint', comment: '1普通 2银卡 3金卡 4钻石', is_primary_key: false, is_nullable: false },
|
||
{ raw_name: 'points', display_name: '积分', type: 'int', comment: '会员积分余额', is_primary_key: false, is_nullable: false, default_value: '0' },
|
||
{ raw_name: 'register_date', display_name: '注册日期', type: 'date', comment: '会员注册日期', is_primary_key: false, is_nullable: false }
|
||
],
|
||
field_count: 5
|
||
}
|
||
]
|
||
},
|
||
pdf: {
|
||
name: '数据字典_PDF版.pdf',
|
||
size: 512000,
|
||
type: 'application/pdf',
|
||
extension: 'pdf',
|
||
tables: [
|
||
{
|
||
raw_name: '交易流水表',
|
||
display_name: '交易记录',
|
||
description: '记录所有交易流水',
|
||
fields: [
|
||
{ raw_name: 'trans_id', display_name: '交易流水号', type: 'varchar(32)', comment: '交易唯一标识', is_primary_key: true, is_nullable: false },
|
||
{ raw_name: 'account_no', display_name: '账户号码', type: 'varchar(32)', comment: '账户号码', is_primary_key: false, is_nullable: false },
|
||
{ raw_name: 'amount', display_name: '交易金额', type: 'decimal(18,2)', comment: '交易金额', is_primary_key: false, is_nullable: false },
|
||
{ raw_name: 'trans_type', display_name: '交易类型', type: 'varchar(20)', comment: '交易类型', is_primary_key: false, is_nullable: false },
|
||
{ raw_name: 'trans_time', display_name: '交易时间', type: 'datetime', comment: '交易时间', is_primary_key: false, is_nullable: false }
|
||
],
|
||
field_count: 5
|
||
}
|
||
]
|
||
}
|
||
};
|
||
|
||
// 文件拖拽
|
||
const uploadArea = document.getElementById('uploadArea');
|
||
|
||
uploadArea.addEventListener('dragover', function(e) {
|
||
e.preventDefault();
|
||
uploadArea.classList.add('dragover');
|
||
});
|
||
|
||
uploadArea.addEventListener('dragleave', function() {
|
||
uploadArea.classList.remove('dragover');
|
||
});
|
||
|
||
uploadArea.addEventListener('drop', function(e) {
|
||
e.preventDefault();
|
||
uploadArea.classList.remove('dragover');
|
||
handleFiles(e.dataTransfer.files);
|
||
});
|
||
|
||
document.getElementById('fileInput').addEventListener('change', function(e) {
|
||
handleFiles(e.target.files);
|
||
});
|
||
|
||
// 处理文件
|
||
function handleFiles(files) {
|
||
const validExtensions = ['.xlsx', '.xls', '.doc', '.docx', '.pdf'];
|
||
|
||
for (const file of files) {
|
||
const extension = '.' + file.name.split('.').pop().toLowerCase();
|
||
|
||
if (!validExtensions.includes(extension)) {
|
||
showToast(`文件 ${file.name} 格式不支持`);
|
||
continue;
|
||
}
|
||
|
||
uploadedFiles.push(file);
|
||
}
|
||
|
||
renderFileList();
|
||
showToast(`已添加 ${files.length} 个文件`);
|
||
}
|
||
|
||
// 渲染文件列表
|
||
function renderFileList() {
|
||
const container = document.getElementById('fileList');
|
||
|
||
if (uploadedFiles.length === 0) {
|
||
container.innerHTML = '<p style="text-align: center; color: var(--text-muted); padding: 20px;">暂无文件</p>';
|
||
return;
|
||
}
|
||
|
||
let html = '';
|
||
uploadedFiles.forEach((file, index) => {
|
||
const icon = getFileIcon(file.name);
|
||
const size = formatFileSize(file.size);
|
||
|
||
html += `
|
||
<div class="file-item">
|
||
<div class="file-icon">${icon}</div>
|
||
<div class="file-info">
|
||
<div class="file-name">${file.name}</div>
|
||
<div class="file-size">${size}</div>
|
||
</div>
|
||
<div class="file-remove" onclick="removeFile(${index})">×</div>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
container.innerHTML = html;
|
||
}
|
||
|
||
// 获取文件图标
|
||
function getFileIcon(fileName) {
|
||
const extension = fileName.split('.').pop().toLowerCase();
|
||
const icons = {
|
||
'xlsx': '📊',
|
||
'xls': '📊',
|
||
'docx': '📝',
|
||
'doc': '📝',
|
||
'pdf': '📕'
|
||
};
|
||
return icons[extension] || '📄';
|
||
}
|
||
|
||
// 格式化文件大小
|
||
function formatFileSize(bytes) {
|
||
if (bytes === 0) return '0 B';
|
||
const k = 1024;
|
||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
|
||
}
|
||
|
||
// 移除文件
|
||
function removeFile(index) {
|
||
uploadedFiles.splice(index, 1);
|
||
renderFileList();
|
||
}
|
||
|
||
// 清空文件
|
||
function resetFiles() {
|
||
uploadedFiles = [];
|
||
virtualFileType = null;
|
||
renderFileList();
|
||
document.getElementById('resultArea').style.display = 'none';
|
||
document.getElementById('emptyState').style.display = 'block';
|
||
document.getElementById('requestInfo').style.display = 'block';
|
||
document.getElementById('responseInfo').style.display = 'none';
|
||
showToast('已清空文件');
|
||
}
|
||
|
||
// 加载虚拟文件
|
||
function loadVirtualFile(type) {
|
||
virtualFileType = type;
|
||
const virtualFile = virtualFiles[type];
|
||
|
||
uploadedFiles = [{
|
||
name: virtualFile.name,
|
||
size: virtualFile.size,
|
||
type: virtualFile.type,
|
||
isVirtual: true
|
||
}];
|
||
|
||
renderFileList();
|
||
showToast(`已加载${{ 'excel': 'Excel', 'word': 'Word', 'pdf': 'PDF' }[type]}虚拟文件`);
|
||
}
|
||
|
||
// 解析文档
|
||
async function parseDocument() {
|
||
if (uploadedFiles.length === 0) {
|
||
showToast('请先上传文件或使用虚拟文件');
|
||
return;
|
||
}
|
||
|
||
const projectId = document.getElementById('projectId').value;
|
||
if (!projectId) {
|
||
showToast('请输入项目ID');
|
||
return;
|
||
}
|
||
|
||
// 构建请求数据
|
||
const requestData = {
|
||
project_id: projectId,
|
||
file_type: virtualFileType
|
||
};
|
||
|
||
// 显示请求数据
|
||
document.getElementById('requestJson').textContent = JSON.stringify(requestData, null, 2);
|
||
document.getElementById('responseInfo').style.display = 'none';
|
||
|
||
// 显示加载状态
|
||
document.getElementById('emptyState').style.display = 'none';
|
||
document.getElementById('resultArea').style.display = 'none';
|
||
showLoading('loadingArea');
|
||
|
||
try {
|
||
// 模拟API调用
|
||
await delay(2000);
|
||
|
||
// 生成模拟响应
|
||
const response = generateMockResponse();
|
||
|
||
// 显示结果
|
||
hideLoading('loadingArea', '');
|
||
document.getElementById('resultArea').style.display = 'block';
|
||
|
||
// 显示响应数据
|
||
document.getElementById('responseInfo').style.display = 'block';
|
||
document.getElementById('responseJson').textContent = JSON.stringify(response, null, 2);
|
||
|
||
// 渲染统计信息
|
||
renderStatistics(response);
|
||
|
||
// 渲染图表
|
||
renderCharts(response, virtualFileType);
|
||
|
||
// 渲染表结果
|
||
renderTableResults(response.tables);
|
||
|
||
showSuccess('loadingArea', '✅ 解析完成!');
|
||
setTimeout(() => {
|
||
document.getElementById('loadingArea').style.display = 'none';
|
||
}, 2000);
|
||
|
||
} catch (error) {
|
||
hideLoading('loadingArea', '');
|
||
showError('loadingArea', error.message);
|
||
}
|
||
}
|
||
|
||
// 生成模拟响应
|
||
function generateMockResponse() {
|
||
const virtualFile = virtualFiles[virtualFileType];
|
||
const tables = virtualFile.tables || [];
|
||
|
||
let totalFields = 0;
|
||
let fileType = {
|
||
'excel': 0,
|
||
'word': 0,
|
||
'pdf': 0
|
||
};
|
||
fileType[virtualFileType] = 1;
|
||
|
||
let fieldTypes = {};
|
||
|
||
tables.forEach(table => {
|
||
totalFields += table.fields.length;
|
||
table.fields.forEach(field => {
|
||
const baseType = field.type.split('(')[0].toLowerCase();
|
||
fieldTypes[baseType] = (fieldTypes[baseType] || 0) + 1;
|
||
});
|
||
});
|
||
|
||
return {
|
||
tables: tables,
|
||
total_tables: tables.length,
|
||
total_fields: totalFields,
|
||
parse_time: (Math.random() * 2 + 0.5).toFixed(2),
|
||
file_type: virtualFileType,
|
||
fileType: fileType,
|
||
fieldTypes: fieldTypes
|
||
};
|
||
}
|
||
|
||
// 渲染统计信息
|
||
function renderStatistics(response) {
|
||
document.getElementById('statFiles').textContent = 1;
|
||
document.getElementById('statTables').textContent = response.total_tables;
|
||
document.getElementById('statFields').textContent = response.total_fields;
|
||
document.getElementById('statTime').textContent = response.parse_time + 's';
|
||
}
|
||
|
||
// 渲染图表
|
||
function renderCharts(response, fileType) {
|
||
// 文件类型分布
|
||
const fileTypeData = Object.entries(response.fileType)
|
||
.filter(([_, value]) => value > 0)
|
||
.map(([key, value]) => ({
|
||
label: key.toUpperCase(),
|
||
value: value,
|
||
color: key === 'excel' ? '#1cc88a' : (key === 'word' ? '#36b9cc' : '#e74a3b')
|
||
}));
|
||
|
||
renderBarChart('fileTypeChart', fileTypeData, '文件类型分布');
|
||
|
||
// 字段类型分布
|
||
if (Object.keys(response.fieldTypes).length > 0) {
|
||
const fieldTypeData = Object.entries(response.fieldTypes)
|
||
.map(([key, value]) => ({
|
||
label: key.toUpperCase(),
|
||
value: value
|
||
}));
|
||
|
||
renderBarChart('fieldTypeChart', fieldTypeData, '字段类型分布');
|
||
} else {
|
||
document.getElementById('fieldTypeChart').innerHTML = '<p style="text-align: center; color: var(--text-muted);">暂无数据</p>';
|
||
}
|
||
}
|
||
|
||
// 渲染表结果
|
||
function renderTableResults(tables) {
|
||
const container = document.getElementById('tableResults');
|
||
|
||
let html = '';
|
||
tables.forEach((table, index) => {
|
||
const primaryKeyFields = table.fields.filter(f => f.is_primary_key);
|
||
|
||
html += `
|
||
<div class="table-preview">
|
||
<div class="table-preview-header" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'; this.querySelector('.toggle-icon').textContent = this.nextElementSibling.style.display === 'none' ? '▼' : '▲';">
|
||
<div>
|
||
<strong style="font-size: 14px;">${table.display_name || table.raw_name}</strong>
|
||
<div style="font-size: 12px; color: var(--text-muted); margin-top: 4px;">
|
||
原始名称: ${table.raw_name} | 字段数: ${table.field_count}
|
||
${primaryKeyFields.length > 0 ? `<span style="margin-left: 8px; color: #f57c00;">🔑 主键: ${primaryKeyFields.map(f => f.raw_name).join(', ')}</span>` : ''}
|
||
</div>
|
||
</div>
|
||
<span class="toggle-icon">▼</span>
|
||
</div>
|
||
<div class="table-preview-body">
|
||
<p style="margin-bottom: 12px; color: var(--text-muted); font-size: 13px;">${table.description || '无描述'}</p>
|
||
<table style="width: 100%; border-collapse: collapse; font-size: 12px;">
|
||
<thead>
|
||
<tr style="background-color: var(--light-color);">
|
||
<th style="padding: 8px; text-align: left; border-bottom: 2px solid var(--border-color);">字段名</th>
|
||
<th style="padding: 8px; text-align: left; border-bottom: 2px solid var(--border-color);">显示名</th>
|
||
<th style="padding: 8px; text-align: left; border-bottom: 2px solid var(--border-color);">类型</th>
|
||
<th style="padding: 8px; text-align: left; border-bottom: 2px solid var(--border-color);">注释</th>
|
||
<th style="padding: 8px; text-align: center; border-bottom: 2px solid var(--border-color);">主键</th>
|
||
<th style="padding: 8px; text-align: center; border-bottom: 2px solid var(--border-color);">可为空</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
`;
|
||
|
||
table.fields.forEach(field => {
|
||
html += `
|
||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||
<td style="padding: 8px; font-weight: 500;">${field.raw_name}</td>
|
||
<td style="padding: 8px;">${field.display_name || '-'}</td>
|
||
<td style="padding: 8px; color: var(--info-color);">${field.type}</td>
|
||
<td style="padding: 8px; color: var(--text-muted);">${field.comment || '-'}</td>
|
||
<td style="padding: 8px; text-align: center;">${field.is_primary_key ? '✓' : ''}</td>
|
||
<td style="padding: 8px; text-align: center;">${field.is_nullable ? '✓' : ''}</td>
|
||
</tr>
|
||
`;
|
||
});
|
||
|
||
html += `
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
container.innerHTML = html;
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|