1425 lines
54 KiB
HTML
1425 lines
54 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>模板字段关联管理</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif;
|
||
background: #f5f5f5;
|
||
padding: 20px;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1600px;
|
||
margin: 0 auto;
|
||
background: white;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
padding: 30px;
|
||
}
|
||
|
||
h1 {
|
||
color: #333;
|
||
margin-bottom: 10px;
|
||
font-size: 24px;
|
||
}
|
||
|
||
.subtitle {
|
||
color: #666;
|
||
margin-bottom: 30px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.section {
|
||
margin-bottom: 30px;
|
||
padding: 20px;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 6px;
|
||
background: #fafafa;
|
||
}
|
||
|
||
.section h2 {
|
||
font-size: 18px;
|
||
margin-bottom: 15px;
|
||
color: #333;
|
||
padding-bottom: 10px;
|
||
border-bottom: 2px solid #2196F3;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
font-weight: 500;
|
||
color: #333;
|
||
}
|
||
|
||
.form-group input,
|
||
.form-group select {
|
||
width: 100%;
|
||
padding: 8px 12px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.form-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 15px;
|
||
}
|
||
|
||
.btn {
|
||
padding: 10px 20px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: #4CAF50;
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: #45a049;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: #f5f5f5;
|
||
color: #333;
|
||
border: 1px solid #ddd;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: #e0e0e0;
|
||
}
|
||
|
||
.btn-danger {
|
||
background: #f44336;
|
||
color: white;
|
||
}
|
||
|
||
.btn-danger:hover {
|
||
background: #da190b;
|
||
}
|
||
|
||
.btn-info {
|
||
background: #2196F3;
|
||
color: white;
|
||
}
|
||
|
||
.btn-info:hover {
|
||
background: #0b7dda;
|
||
}
|
||
|
||
.btn:disabled {
|
||
background: #ccc;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.btn-group {
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.message {
|
||
padding: 12px;
|
||
border-radius: 4px;
|
||
margin-bottom: 20px;
|
||
display: none;
|
||
}
|
||
|
||
.message.success {
|
||
background: #d4edda;
|
||
color: #155724;
|
||
border: 1px solid #c3e6cb;
|
||
}
|
||
|
||
.message.error {
|
||
background: #f8d7da;
|
||
color: #721c24;
|
||
border: 1px solid #f5c6cb;
|
||
}
|
||
|
||
.message.show {
|
||
display: block;
|
||
}
|
||
|
||
.loading {
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: #999;
|
||
}
|
||
|
||
.fields-container {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 30px;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.field-section {
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 6px;
|
||
padding: 20px;
|
||
background: #fafafa;
|
||
}
|
||
|
||
.field-section h3 {
|
||
font-size: 16px;
|
||
margin-bottom: 15px;
|
||
color: #333;
|
||
padding-bottom: 10px;
|
||
border-bottom: 2px solid #4CAF50;
|
||
}
|
||
|
||
.field-section.output h3 {
|
||
border-bottom-color: #2196F3;
|
||
}
|
||
|
||
.field-list {
|
||
max-height: 500px;
|
||
overflow-y: auto;
|
||
padding: 10px 0;
|
||
}
|
||
|
||
.field-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 10px;
|
||
margin-bottom: 8px;
|
||
background: white;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 4px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.field-item:hover {
|
||
background: #f0f0f0;
|
||
border-color: #4CAF50;
|
||
}
|
||
|
||
.field-item.checked {
|
||
background: #e8f5e9;
|
||
border-color: #4CAF50;
|
||
}
|
||
|
||
.field-info {
|
||
flex: 1;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.field-name {
|
||
font-weight: 500;
|
||
color: #333;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.field-code {
|
||
font-size: 12px;
|
||
color: #999;
|
||
font-family: 'Courier New', monospace;
|
||
}
|
||
|
||
.field-actions {
|
||
display: flex;
|
||
gap: 5px;
|
||
}
|
||
|
||
.field-actions button {
|
||
padding: 5px 10px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.search-box {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.search-box input {
|
||
width: 100%;
|
||
padding: 8px 12px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: #999;
|
||
}
|
||
|
||
.stats {
|
||
display: flex;
|
||
gap: 20px;
|
||
margin-bottom: 20px;
|
||
padding: 15px;
|
||
background: #f0f7ff;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.stat-item {
|
||
flex: 1;
|
||
text-align: center;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #2196F3;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 12px;
|
||
color: #666;
|
||
margin-top: 5px;
|
||
}
|
||
|
||
.modal {
|
||
display: none;
|
||
position: fixed;
|
||
z-index: 1000;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: rgba(0,0,0,0.5);
|
||
}
|
||
|
||
.modal-content {
|
||
background-color: white;
|
||
margin: 5% auto;
|
||
padding: 30px;
|
||
border-radius: 8px;
|
||
width: 90%;
|
||
max-width: 600px;
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 1px solid #e0e0e0;
|
||
}
|
||
|
||
.modal-header h2 {
|
||
margin: 0;
|
||
font-size: 20px;
|
||
}
|
||
|
||
.close {
|
||
color: #aaa;
|
||
font-size: 28px;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.close:hover {
|
||
color: #000;
|
||
}
|
||
|
||
.tabs {
|
||
display: flex;
|
||
border-bottom: 2px solid #e0e0e0;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.tab {
|
||
padding: 10px 20px;
|
||
cursor: pointer;
|
||
border: none;
|
||
background: none;
|
||
font-size: 14px;
|
||
color: #666;
|
||
border-bottom: 2px solid transparent;
|
||
margin-bottom: -2px;
|
||
}
|
||
|
||
.tab.active {
|
||
color: #2196F3;
|
||
border-bottom-color: #2196F3;
|
||
}
|
||
|
||
.tab-content {
|
||
display: none;
|
||
}
|
||
|
||
.tab-content.active {
|
||
display: block;
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-top: 15px;
|
||
}
|
||
|
||
table th,
|
||
table td {
|
||
padding: 10px;
|
||
text-align: left;
|
||
border-bottom: 1px solid #e0e0e0;
|
||
}
|
||
|
||
table th {
|
||
background: #f5f5f5;
|
||
font-weight: 500;
|
||
}
|
||
|
||
table tr:hover {
|
||
background: #f9f9f9;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>模板字段关联管理</h1>
|
||
<p class="subtitle">维护模板与输入字段、输出字段的关联关系,支持字段的增删改查和数据库备份恢复</p>
|
||
|
||
<div id="message" class="message"></div>
|
||
|
||
<!-- Tenant ID 选择区域 -->
|
||
<div class="section">
|
||
<h2>租户选择</h2>
|
||
<div class="form-group">
|
||
<label for="tenantSelect">选择租户ID:</label>
|
||
<div class="btn-group">
|
||
<select id="tenantSelect" style="flex: 1; max-width: 300px;">
|
||
<option value="">请先查询租户ID...</option>
|
||
</select>
|
||
<button class="btn btn-info" onclick="loadTenantIds()">查询租户ID</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 字段管理区域 -->
|
||
<div class="section" id="fieldManagementSection" style="display: none;">
|
||
<h2>字段管理</h2>
|
||
<div class="btn-group" style="margin-bottom: 15px;">
|
||
<button class="btn btn-primary" onclick="showAddFieldModal()">新增字段</button>
|
||
<button class="btn btn-info" onclick="refreshFields()">刷新字段列表</button>
|
||
</div>
|
||
<div class="tabs">
|
||
<button class="tab active" onclick="switchTab('input')">输入字段</button>
|
||
<button class="tab" onclick="switchTab('output')">输出字段</button>
|
||
</div>
|
||
<div id="inputFieldsTab" class="tab-content active">
|
||
<div class="search-box">
|
||
<input type="text" id="inputFieldSearch" placeholder="搜索输入字段..." oninput="filterFields('input')">
|
||
</div>
|
||
<div id="inputFieldsTable"></div>
|
||
</div>
|
||
<div id="outputFieldsTab" class="tab-content">
|
||
<div class="search-box">
|
||
<input type="text" id="outputFieldSearch" placeholder="搜索输出字段..." oninput="filterFields('output')">
|
||
</div>
|
||
<div id="outputFieldsTable"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 模板字段关联区域 -->
|
||
<div class="section" id="templateFieldSection" style="display: none;">
|
||
<h2>模板字段关联</h2>
|
||
<div class="form-group">
|
||
<label for="templateSelect">选择模板:</label>
|
||
<select id="templateSelect">
|
||
<option value="">请选择模板...</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div id="stats" class="stats" style="display: none;">
|
||
<div class="stat-item">
|
||
<div class="stat-value" id="inputCount">0</div>
|
||
<div class="stat-label">已选输入字段</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="stat-value" id="outputCount">0</div>
|
||
<div class="stat-label">已选输出字段</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="stat-value" id="totalCount">0</div>
|
||
<div class="stat-label">总字段数</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="fields-container" id="fieldsContainer" style="display: none;">
|
||
<div class="field-section input">
|
||
<h3>
|
||
输入字段
|
||
<span class="field-count" id="inputFieldCount">(0)</span>
|
||
</h3>
|
||
<div class="search-box">
|
||
<input type="text" id="inputSearch" placeholder="搜索输入字段...">
|
||
</div>
|
||
<div class="field-list" id="inputFieldsList"></div>
|
||
</div>
|
||
|
||
<div class="field-section output">
|
||
<h3>
|
||
输出字段
|
||
<span class="field-count" id="outputFieldCount">(0)</span>
|
||
</h3>
|
||
<div class="search-box">
|
||
<input type="text" id="outputSearch" placeholder="搜索输出字段...">
|
||
</div>
|
||
<div class="field-list" id="outputFieldsList"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="btn-group" id="actions" style="display: none; margin-top: 20px;">
|
||
<button class="btn btn-secondary" onclick="resetSelection()">重置</button>
|
||
<button class="btn btn-primary" onclick="saveRelations()">保存关联关系</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 数据库备份恢复区域 -->
|
||
<div class="section" id="backupSection" style="display: none;">
|
||
<h2>数据库备份与恢复</h2>
|
||
<div class="btn-group">
|
||
<button class="btn btn-info" onclick="backupDatabase()">备份数据库</button>
|
||
<button class="btn btn-secondary" onclick="showRestoreModal()">恢复数据库</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="loading" class="loading">加载中...</div>
|
||
</div>
|
||
|
||
<!-- 新增/编辑字段模态框 -->
|
||
<div id="fieldModal" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2 id="fieldModalTitle">新增字段</h2>
|
||
<span class="close" onclick="closeFieldModal()">×</span>
|
||
</div>
|
||
<form id="fieldForm" onsubmit="saveField(event)">
|
||
<input type="hidden" id="fieldId">
|
||
<div class="form-group">
|
||
<label for="fieldName">字段名称:</label>
|
||
<input type="text" id="fieldName" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="fieldCode">字段编码:</label>
|
||
<input type="text" id="fieldCode" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="fieldType">字段类型:</label>
|
||
<select id="fieldType" required>
|
||
<option value="1">输入字段</option>
|
||
<option value="2">输出字段</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="fieldState">状态:</label>
|
||
<select id="fieldState">
|
||
<option value="1">启用</option>
|
||
<option value="0">未启用</option>
|
||
</select>
|
||
</div>
|
||
<div class="btn-group" style="margin-top: 20px;">
|
||
<button type="submit" class="btn btn-primary">保存</button>
|
||
<button type="button" class="btn btn-secondary" onclick="closeFieldModal()">取消</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 恢复数据库模态框 -->
|
||
<div id="restoreModal" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2>恢复数据库</h2>
|
||
<span class="close" onclick="closeRestoreModal()">×</span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="restoreFile">选择备份文件:</label>
|
||
<input type="file" id="restoreFile" accept=".json">
|
||
</div>
|
||
<div class="btn-group" style="margin-top: 20px;">
|
||
<button class="btn btn-primary" onclick="restoreDatabase()">恢复</button>
|
||
<button class="btn btn-secondary" onclick="closeRestoreModal()">取消</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let currentTenantId = null; // 存储为字符串,避免大整数精度问题
|
||
let templates = [];
|
||
let inputFields = [];
|
||
let outputFields = [];
|
||
let relations = {};
|
||
let currentTemplateId = null;
|
||
let selectedInputFields = new Set();
|
||
let selectedOutputFields = new Set();
|
||
let allFields = [];
|
||
|
||
// 页面加载时初始化
|
||
window.onload = function() {
|
||
// 确保 currentTenantId 初始化为 null
|
||
currentTenantId = null;
|
||
console.log('页面加载,初始化 currentTenantId 为 null');
|
||
|
||
// 清除可能存在的 localStorage 缓存
|
||
try {
|
||
localStorage.removeItem('currentTenantId');
|
||
sessionStorage.removeItem('currentTenantId');
|
||
} catch (e) {
|
||
console.warn('清除缓存失败:', e);
|
||
}
|
||
|
||
loadTenantIds();
|
||
};
|
||
|
||
// 加载租户ID列表
|
||
async function loadTenantIds() {
|
||
try {
|
||
console.log('开始加载租户ID列表...');
|
||
const response = await fetch('/api/tenant-ids');
|
||
console.log('API响应状态:', response.status);
|
||
|
||
const result = await response.json();
|
||
console.log('API返回结果:', result);
|
||
|
||
if (result.isSuccess) {
|
||
const tenantIds = result.data.tenant_ids || [];
|
||
console.log('获取到的租户ID列表:', tenantIds);
|
||
|
||
const select = document.getElementById('tenantSelect');
|
||
// 不保存当前选中的值,每次都重新选择
|
||
select.innerHTML = '<option value="">请选择租户ID...</option>';
|
||
|
||
if (tenantIds.length === 0) {
|
||
select.innerHTML = '<option value="">数据库中没有租户ID数据</option>';
|
||
showMessage('数据库中没有找到任何租户ID数据', 'error');
|
||
currentTenantId = null;
|
||
return;
|
||
}
|
||
|
||
tenantIds.forEach(tenantId => {
|
||
const option = document.createElement('option');
|
||
// tenantId 已经是字符串,直接使用
|
||
option.value = tenantId;
|
||
option.textContent = tenantId;
|
||
select.appendChild(option);
|
||
});
|
||
|
||
// 移除所有旧的事件监听器
|
||
const newSelect = select.cloneNode(true);
|
||
select.parentNode.replaceChild(newSelect, select);
|
||
const freshSelect = document.getElementById('tenantSelect');
|
||
|
||
// 设置新的事件监听器(使用 onchange 而不是 addEventListener,避免重复)
|
||
freshSelect.onchange = function() {
|
||
const tenantId = this.value;
|
||
console.log('租户ID选择变化:', tenantId, '之前的值:', currentTenantId);
|
||
if (tenantId) {
|
||
// 将 tenantId 转换为数字(用于数据库查询)
|
||
// 注意:大整数在 JavaScript 中可能会有精度问题,但这里只是用于显示和传递
|
||
const oldTenantId = currentTenantId;
|
||
currentTenantId = tenantId; // 保持为字符串,在发送请求时再转换
|
||
console.log('更新 currentTenantId:', oldTenantId, '->', currentTenantId);
|
||
console.log('准备调用 loadData,currentTenantId =', currentTenantId);
|
||
loadData();
|
||
} else {
|
||
currentTenantId = null;
|
||
hideAllSections();
|
||
}
|
||
};
|
||
|
||
showMessage(`成功加载 ${tenantIds.length} 个租户ID`, 'success');
|
||
} else {
|
||
console.error('API返回错误:', result);
|
||
showMessage('加载租户ID列表失败: ' + (result.errorMsg || '未知错误'), 'error');
|
||
}
|
||
} catch (error) {
|
||
console.error('加载租户ID列表异常:', error);
|
||
showMessage('加载租户ID列表失败: ' + error.message, 'error');
|
||
}
|
||
}
|
||
|
||
// 加载数据
|
||
async function loadData() {
|
||
console.log('========== loadData 被调用 ==========');
|
||
console.log('currentTenantId 的值:', currentTenantId);
|
||
console.log('currentTenantId 的类型:', typeof currentTenantId);
|
||
|
||
if (!currentTenantId) {
|
||
console.warn('currentTenantId 为空,无法加载数据');
|
||
return;
|
||
}
|
||
|
||
document.getElementById('loading').style.display = 'block';
|
||
hideAllSections();
|
||
|
||
try {
|
||
// 不要转换为数字!直接使用字符串,避免大整数精度丢失
|
||
// JavaScript 的 Number.MAX_SAFE_INTEGER 是 2^53 - 1 = 9007199254740991
|
||
// 615873064429507639 超过了这个值,parseInt 会丢失精度
|
||
const tenantId = currentTenantId; // 直接使用字符串
|
||
console.log('使用的 tenant_id (字符串):', tenantId);
|
||
console.log('tenant_id 的类型:', typeof tenantId);
|
||
console.log('准备加载数据,使用的 tenant_id:', tenantId);
|
||
console.log('API URL 1:', `/api/template-field-relations?tenant_id=${tenantId}`);
|
||
console.log('API URL 2:', `/api/field-management/fields?tenant_id=${tenantId}`);
|
||
|
||
// 同时加载模板字段关联数据和字段管理数据
|
||
const [relationsResponse, fieldsResponse] = await Promise.all([
|
||
fetch(`/api/template-field-relations?tenant_id=${tenantId}`),
|
||
fetch(`/api/field-management/fields?tenant_id=${tenantId}`)
|
||
]);
|
||
|
||
console.log('API 响应状态:', {
|
||
relations: relationsResponse.status,
|
||
fields: fieldsResponse.status
|
||
});
|
||
|
||
const relationsResult = await relationsResponse.json();
|
||
const fieldsResult = await fieldsResponse.json();
|
||
|
||
console.log('API 返回结果:', {
|
||
relations: relationsResult,
|
||
fields: fieldsResult
|
||
});
|
||
|
||
if (relationsResult.isSuccess && fieldsResult.isSuccess) {
|
||
// 处理模板字段关联数据
|
||
templates = relationsResult.data.templates || [];
|
||
inputFields = relationsResult.data.input_fields || [];
|
||
outputFields = relationsResult.data.output_fields || [];
|
||
let rawRelations = relationsResult.data.relations || {};
|
||
|
||
// 处理字段管理数据(包含所有字段,包括未启用的)
|
||
allFields = fieldsResult.data.fields || [];
|
||
|
||
// 确保 relations 对象的 key 是字符串类型(JSON 序列化后 key 是字符串)
|
||
relations = {};
|
||
for (const [key, value] of Object.entries(rawRelations)) {
|
||
// key 可能是字符串或数字,统一转换为字符串
|
||
const keyStr = String(key);
|
||
relations[keyStr] = value;
|
||
}
|
||
|
||
console.log('========== 加载数据 ==========');
|
||
console.log('数据统计:', {
|
||
tenant_id: tenantId,
|
||
templates: templates.length,
|
||
inputFields: inputFields.length,
|
||
outputFields: outputFields.length,
|
||
allFields: allFields.length,
|
||
relationsCount: Object.keys(relations).length
|
||
});
|
||
if (Object.keys(relations).length > 0) {
|
||
const firstKey = Object.keys(relations)[0];
|
||
console.log('relations 对象示例:', {
|
||
firstKey: firstKey,
|
||
firstKeyType: typeof firstKey,
|
||
firstValueCount: relations[firstKey].length,
|
||
firstValueSample: relations[firstKey].slice(0, 3)
|
||
});
|
||
}
|
||
console.log('==============================');
|
||
|
||
// 更新模板选择框
|
||
populateTemplateSelect();
|
||
|
||
// 显示所有区域
|
||
document.getElementById('fieldManagementSection').style.display = 'block';
|
||
document.getElementById('templateFieldSection').style.display = 'block';
|
||
document.getElementById('backupSection').style.display = 'block';
|
||
|
||
// 确保tab正确显示,然后渲染字段表格
|
||
switchTab('input');
|
||
|
||
document.getElementById('loading').style.display = 'none';
|
||
} else {
|
||
const errorMsg = relationsResult.isSuccess ? fieldsResult.errorMsg : relationsResult.errorMsg;
|
||
showMessage('加载数据失败: ' + errorMsg, 'error');
|
||
console.error('加载数据失败:', {
|
||
relationsResult,
|
||
fieldsResult
|
||
});
|
||
document.getElementById('loading').style.display = 'none';
|
||
}
|
||
} catch (error) {
|
||
showMessage('加载数据失败: ' + error.message, 'error');
|
||
document.getElementById('loading').style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// 隐藏所有区域
|
||
function hideAllSections() {
|
||
document.getElementById('fieldManagementSection').style.display = 'none';
|
||
document.getElementById('templateFieldSection').style.display = 'none';
|
||
document.getElementById('backupSection').style.display = 'none';
|
||
document.getElementById('fieldsContainer').style.display = 'none';
|
||
document.getElementById('actions').style.display = 'none';
|
||
document.getElementById('stats').style.display = 'none';
|
||
}
|
||
|
||
// 填充模板选择框
|
||
function populateTemplateSelect() {
|
||
const select = document.getElementById('templateSelect');
|
||
select.innerHTML = '<option value="">请选择模板...</option>';
|
||
|
||
templates.forEach(template => {
|
||
const option = document.createElement('option');
|
||
option.value = template.id;
|
||
option.textContent = template.name;
|
||
select.appendChild(option);
|
||
});
|
||
|
||
select.onchange = function() {
|
||
const templateId = parseInt(this.value);
|
||
if (templateId) {
|
||
loadTemplateFields(templateId);
|
||
} else {
|
||
hideFields();
|
||
}
|
||
};
|
||
}
|
||
|
||
// 加载模板字段
|
||
function loadTemplateFields(templateId) {
|
||
currentTemplateId = templateId;
|
||
const template = templates.find(t => t.id === templateId);
|
||
|
||
// 确保 templateId 是数字类型
|
||
const templateIdNum = parseInt(templateId);
|
||
|
||
// 获取该模板关联的所有字段ID
|
||
// relations 对象的 key 在 JSON 序列化后是字符串,所以使用字符串 key 查找
|
||
const templateIdStr = String(templateIdNum);
|
||
let relatedFieldIdsArray = relations[templateIdStr] || relations[templateIdNum] || [];
|
||
|
||
// 转换为 Set,并确保类型一致(都转换为数字进行比较)
|
||
const relatedFieldIds = new Set(relatedFieldIdsArray.map(id => {
|
||
const numId = parseInt(id);
|
||
return isNaN(numId) ? null : numId;
|
||
}).filter(id => id !== null));
|
||
|
||
console.log('========== 加载模板字段 ==========');
|
||
console.log('模板信息:', {
|
||
templateId: templateIdNum,
|
||
templateIdStr: templateIdStr,
|
||
templateName: template ? template.name : '未知',
|
||
relationsKeysCount: Object.keys(relations).length,
|
||
relationsKeysSample: Object.keys(relations).slice(0, 5)
|
||
});
|
||
console.log('关联关系查找:', {
|
||
'relations[templateIdStr]': relations[templateIdStr],
|
||
'relations[templateIdNum]': relations[templateIdNum],
|
||
relatedFieldIdsArray: relatedFieldIdsArray,
|
||
relatedFieldIds: Array.from(relatedFieldIds),
|
||
relatedFieldIdsCount: relatedFieldIds.size
|
||
});
|
||
console.log('字段列表:', {
|
||
inputFieldsCount: inputFields.length,
|
||
outputFieldsCount: outputFields.length,
|
||
inputFieldIds: inputFields.slice(0, 3).map(f => ({id: f.id, name: f.name})),
|
||
outputFieldIds: outputFields.slice(0, 3).map(f => ({id: f.id, name: f.name}))
|
||
});
|
||
|
||
selectedInputFields = new Set();
|
||
selectedOutputFields = new Set();
|
||
|
||
// 检查输入字段关联
|
||
let inputMatchCount = 0;
|
||
inputFields.forEach(field => {
|
||
const fieldId = parseInt(field.id);
|
||
if (!isNaN(fieldId) && relatedFieldIds.has(fieldId)) {
|
||
selectedInputFields.add(fieldId);
|
||
inputMatchCount++;
|
||
console.log(`[输入字段匹配 ${inputMatchCount}]`, field.name, `(ID: ${fieldId})`);
|
||
}
|
||
});
|
||
|
||
// 检查输出字段关联
|
||
let outputMatchCount = 0;
|
||
outputFields.forEach(field => {
|
||
const fieldId = parseInt(field.id);
|
||
// 确保 fieldId 是有效数字
|
||
if (!isNaN(fieldId)) {
|
||
// 检查是否在关联字段ID集合中
|
||
if (relatedFieldIds.has(fieldId)) {
|
||
selectedOutputFields.add(fieldId);
|
||
outputMatchCount++;
|
||
console.log(`[输出字段匹配 ${outputMatchCount}]`, field.name, `(ID: ${fieldId})`);
|
||
} else {
|
||
// 调试:检查为什么没有匹配
|
||
if (relatedFieldIds.size > 0 && outputMatchCount === 0) {
|
||
console.log(`[调试] 字段 ${field.name} (ID: ${fieldId}) 不在关联集合中`);
|
||
console.log(`[调试] 关联集合包含: ${Array.from(relatedFieldIds).slice(0, 5)}`);
|
||
}
|
||
}
|
||
} else {
|
||
console.warn(`[警告] 字段 ${field.name} 的ID无效: ${field.id}`);
|
||
}
|
||
});
|
||
|
||
console.log('最终选中的字段:', {
|
||
inputFields: Array.from(selectedInputFields),
|
||
outputFields: Array.from(selectedOutputFields),
|
||
inputFieldsSize: selectedInputFields.size,
|
||
outputFieldsSize: selectedOutputFields.size
|
||
});
|
||
console.log('====================================');
|
||
|
||
renderFields();
|
||
updateStats();
|
||
document.getElementById('fieldsContainer').style.display = 'grid';
|
||
document.getElementById('actions').style.display = 'flex';
|
||
document.getElementById('stats').style.display = 'flex';
|
||
}
|
||
|
||
// 渲染字段列表
|
||
function renderFields() {
|
||
renderFieldList('inputFieldsList', inputFields, selectedInputFields, 'input');
|
||
renderFieldList('outputFieldsList', outputFields, selectedOutputFields, 'output');
|
||
|
||
document.getElementById('inputFieldCount').textContent = `(${inputFields.length})`;
|
||
document.getElementById('outputFieldCount').textContent = `(${outputFields.length})`;
|
||
}
|
||
|
||
// 渲染单个字段列表
|
||
function renderFieldList(containerId, fields, selectedSet, type) {
|
||
const container = document.getElementById(containerId);
|
||
const searchTerm = type === 'input'
|
||
? document.getElementById('inputSearch').value.toLowerCase()
|
||
: document.getElementById('outputSearch').value.toLowerCase();
|
||
|
||
const filteredFields = fields.filter(field => {
|
||
return field.name.toLowerCase().includes(searchTerm) ||
|
||
field.filed_code.toLowerCase().includes(searchTerm);
|
||
});
|
||
|
||
if (filteredFields.length === 0) {
|
||
container.innerHTML = '<div class="empty-state">没有找到匹配的字段</div>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = filteredFields.map(field => {
|
||
// 确保 field.id 和 selectedSet 中的值类型一致(都转换为数字)
|
||
const fieldId = parseInt(field.id);
|
||
const isChecked = selectedSet.has(fieldId);
|
||
return `
|
||
<div class="field-item ${isChecked ? 'checked' : ''}" onclick="toggleField(${fieldId}, '${type}')">
|
||
<div class="field-info">
|
||
<div class="field-name">${escapeHtml(field.name)}</div>
|
||
<div class="field-code">${escapeHtml(field.filed_code)}</div>
|
||
</div>
|
||
<input type="checkbox" ${isChecked ? 'checked' : ''}
|
||
onclick="event.stopPropagation(); toggleField(${fieldId}, '${type}')">
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
// 切换字段选择
|
||
function toggleField(fieldId, type) {
|
||
// 确保 fieldId 是数字类型
|
||
fieldId = parseInt(fieldId);
|
||
|
||
if (type === 'input') {
|
||
if (selectedInputFields.has(fieldId)) {
|
||
selectedInputFields.delete(fieldId);
|
||
} else {
|
||
selectedInputFields.add(fieldId);
|
||
}
|
||
renderFieldList('inputFieldsList', inputFields, selectedInputFields, 'input');
|
||
} else {
|
||
if (selectedOutputFields.has(fieldId)) {
|
||
selectedOutputFields.delete(fieldId);
|
||
} else {
|
||
selectedOutputFields.add(fieldId);
|
||
}
|
||
renderFieldList('outputFieldsList', outputFields, selectedOutputFields, 'output');
|
||
}
|
||
updateStats();
|
||
}
|
||
|
||
// 更新统计信息
|
||
function updateStats() {
|
||
document.getElementById('inputCount').textContent = selectedInputFields.size;
|
||
document.getElementById('outputCount').textContent = selectedOutputFields.size;
|
||
document.getElementById('totalCount').textContent = selectedInputFields.size + selectedOutputFields.size;
|
||
}
|
||
|
||
// 设置搜索功能
|
||
function setupSearch() {
|
||
document.getElementById('inputSearch').oninput = function() {
|
||
renderFieldList('inputFieldsList', inputFields, selectedInputFields, 'input');
|
||
};
|
||
document.getElementById('outputSearch').oninput = function() {
|
||
renderFieldList('outputFieldsList', outputFields, selectedOutputFields, 'output');
|
||
};
|
||
}
|
||
|
||
// 重置选择
|
||
function resetSelection() {
|
||
if (confirm('确定要重置当前选择吗?')) {
|
||
selectedInputFields.clear();
|
||
selectedOutputFields.clear();
|
||
renderFields();
|
||
updateStats();
|
||
}
|
||
}
|
||
|
||
// 保存关联关系
|
||
async function saveRelations() {
|
||
if (!currentTemplateId || !currentTenantId) {
|
||
showMessage('请先选择租户和模板', 'error');
|
||
return;
|
||
}
|
||
|
||
const btn = event.target;
|
||
btn.disabled = true;
|
||
btn.textContent = '保存中...';
|
||
|
||
try {
|
||
// 不要转换为数字!直接使用字符串,避免大整数精度丢失
|
||
const tenantId = currentTenantId; // 直接使用字符串
|
||
|
||
const response = await fetch('/api/template-field-relations', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
tenant_id: tenantId, // 后端会处理字符串到数字的转换
|
||
template_id: currentTemplateId,
|
||
input_field_ids: Array.from(selectedInputFields),
|
||
output_field_ids: Array.from(selectedOutputFields)
|
||
})
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.isSuccess) {
|
||
showMessage('保存成功!', 'success');
|
||
relations[currentTemplateId] = [
|
||
...selectedInputFields,
|
||
...selectedOutputFields
|
||
];
|
||
} else {
|
||
showMessage('保存失败: ' + result.errorMsg, 'error');
|
||
}
|
||
} catch (error) {
|
||
showMessage('保存失败: ' + error.message, 'error');
|
||
} finally {
|
||
btn.disabled = false;
|
||
btn.textContent = '保存关联关系';
|
||
}
|
||
}
|
||
|
||
// 隐藏字段区域
|
||
function hideFields() {
|
||
document.getElementById('fieldsContainer').style.display = 'none';
|
||
document.getElementById('actions').style.display = 'none';
|
||
document.getElementById('stats').style.display = 'none';
|
||
}
|
||
|
||
// 显示消息
|
||
function showMessage(message, type) {
|
||
const msgDiv = document.getElementById('message');
|
||
msgDiv.textContent = message;
|
||
msgDiv.className = 'message ' + type + ' show';
|
||
setTimeout(() => {
|
||
msgDiv.classList.remove('show');
|
||
}, 3000);
|
||
}
|
||
|
||
// HTML转义
|
||
function escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
// 字段管理功能
|
||
function switchTab(type) {
|
||
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
|
||
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
||
|
||
if (type === 'input') {
|
||
const tabs = document.querySelectorAll('.tab');
|
||
if (tabs[0]) tabs[0].classList.add('active');
|
||
document.getElementById('inputFieldsTab').classList.add('active');
|
||
} else {
|
||
const tabs = document.querySelectorAll('.tab');
|
||
if (tabs[1]) tabs[1].classList.add('active');
|
||
document.getElementById('outputFieldsTab').classList.add('active');
|
||
}
|
||
renderFieldsTable();
|
||
}
|
||
|
||
async function refreshFields() {
|
||
if (!currentTenantId) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 不要转换为数字!直接使用字符串,避免大整数精度丢失
|
||
const tenantId = currentTenantId; // 直接使用字符串
|
||
|
||
// 同时刷新字段管理数据和模板字段关联数据
|
||
const [fieldsResponse, relationsResponse] = await Promise.all([
|
||
fetch(`/api/field-management/fields?tenant_id=${tenantId}`),
|
||
fetch(`/api/template-field-relations?tenant_id=${tenantId}`)
|
||
]);
|
||
|
||
const fieldsResult = await fieldsResponse.json();
|
||
const relationsResult = await relationsResponse.json();
|
||
|
||
if (fieldsResult.isSuccess && relationsResult.isSuccess) {
|
||
// 更新字段管理数据
|
||
allFields = fieldsResult.data.fields || [];
|
||
|
||
// 更新模板字段关联数据
|
||
templates = relationsResult.data.templates || [];
|
||
inputFields = relationsResult.data.input_fields || [];
|
||
outputFields = relationsResult.data.output_fields || [];
|
||
relations = relationsResult.data.relations || {};
|
||
|
||
// 更新模板选择框
|
||
populateTemplateSelect();
|
||
|
||
// 更新字段表格
|
||
renderFieldsTable();
|
||
|
||
// 如果当前选择了模板,重新加载模板字段
|
||
if (currentTemplateId) {
|
||
loadTemplateFields(currentTemplateId);
|
||
}
|
||
} else {
|
||
const errorMsg = fieldsResult.isSuccess ? relationsResult.errorMsg : fieldsResult.errorMsg;
|
||
showMessage('刷新字段列表失败: ' + errorMsg, 'error');
|
||
}
|
||
} catch (error) {
|
||
showMessage('刷新字段列表失败: ' + error.message, 'error');
|
||
}
|
||
}
|
||
|
||
function renderFieldsTable() {
|
||
const activeTab = document.querySelector('.tab-content.active');
|
||
if (!activeTab) {
|
||
// 如果没有活动的tab,默认显示输入字段tab
|
||
const inputTab = document.getElementById('inputFieldsTab');
|
||
if (inputTab) {
|
||
inputTab.classList.add('active');
|
||
const tabs = document.querySelectorAll('.tab');
|
||
if (tabs[0]) tabs[0].classList.add('active');
|
||
} else {
|
||
return;
|
||
}
|
||
}
|
||
|
||
const isInput = activeTab ? activeTab.id === 'inputFieldsTab' : true;
|
||
// 使用 allFields 而不是 inputFields/outputFields,因为 allFields 包含所有字段(包括未启用的)
|
||
const fields = allFields.filter(f => f.field_type === (isInput ? 1 : 2));
|
||
const searchTerm = (isInput ?
|
||
(document.getElementById('inputFieldSearch')?.value || '').toLowerCase() :
|
||
(document.getElementById('outputFieldSearch')?.value || '').toLowerCase());
|
||
|
||
const filteredFields = fields.filter(field => {
|
||
const name = (field.name || '').toLowerCase();
|
||
const code = (field.filed_code || '').toLowerCase();
|
||
return name.includes(searchTerm) || code.includes(searchTerm);
|
||
});
|
||
|
||
const tableId = isInput ? 'inputFieldsTable' : 'outputFieldsTable';
|
||
const container = document.getElementById(tableId);
|
||
|
||
if (!container) {
|
||
console.error('找不到容器:', tableId);
|
||
return;
|
||
}
|
||
|
||
console.log('渲染字段表格:', {
|
||
isInput,
|
||
allFieldsCount: allFields.length,
|
||
fieldsCount: fields.length,
|
||
filteredCount: filteredFields.length
|
||
});
|
||
|
||
if (filteredFields.length === 0) {
|
||
if (allFields.length === 0) {
|
||
container.innerHTML = '<div class="empty-state">该租户下暂无字段数据</div>';
|
||
} else {
|
||
container.innerHTML = '<div class="empty-state">没有找到匹配的字段</div>';
|
||
}
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = `
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>字段名称</th>
|
||
<th>字段编码</th>
|
||
<th>状态</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
${filteredFields.map(field => `
|
||
<tr>
|
||
<td>${escapeHtml(field.name)}</td>
|
||
<td><code>${escapeHtml(field.filed_code)}</code></td>
|
||
<td>${(field.state === 1 || field.state === '1') ? '<span style="color: green;">启用</span>' : '<span style="color: red;">未启用</span>'}</td>
|
||
<td>
|
||
<div class="field-actions">
|
||
<button class="btn btn-secondary" onclick="editField(${field.id})">编辑</button>
|
||
<button class="btn btn-danger" onclick="deleteField(${field.id})">删除</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`).join('')}
|
||
</tbody>
|
||
</table>
|
||
`;
|
||
}
|
||
|
||
function filterFields(type) {
|
||
renderFieldsTable();
|
||
}
|
||
|
||
function showAddFieldModal() {
|
||
if (!currentTenantId) {
|
||
showMessage('请先选择租户ID', 'error');
|
||
return;
|
||
}
|
||
document.getElementById('fieldModalTitle').textContent = '新增字段';
|
||
document.getElementById('fieldForm').reset();
|
||
document.getElementById('fieldId').value = '';
|
||
document.getElementById('fieldState').value = '1';
|
||
document.getElementById('fieldModal').style.display = 'block';
|
||
}
|
||
|
||
function editField(fieldId) {
|
||
const field = allFields.find(f => f.id === fieldId);
|
||
if (!field) {
|
||
showMessage('字段不存在', 'error');
|
||
return;
|
||
}
|
||
|
||
document.getElementById('fieldModalTitle').textContent = '编辑字段';
|
||
document.getElementById('fieldId').value = field.id;
|
||
document.getElementById('fieldName').value = field.name;
|
||
document.getElementById('fieldCode').value = field.filed_code;
|
||
document.getElementById('fieldType').value = field.field_type;
|
||
// 确保 state 值正确设置(可能是数字或字符串)
|
||
document.getElementById('fieldState').value = String(field.state || 1);
|
||
document.getElementById('fieldModal').style.display = 'block';
|
||
}
|
||
|
||
function closeFieldModal() {
|
||
document.getElementById('fieldModal').style.display = 'none';
|
||
}
|
||
|
||
async function saveField(event) {
|
||
event.preventDefault();
|
||
if (!currentTenantId) {
|
||
showMessage('请先选择租户ID', 'error');
|
||
return;
|
||
}
|
||
|
||
const fieldId = document.getElementById('fieldId').value;
|
||
const isEdit = !!fieldId;
|
||
const url = isEdit ? `/api/field-management/fields/${fieldId}` : '/api/field-management/fields';
|
||
const method = isEdit ? 'PUT' : 'POST';
|
||
|
||
// 不要转换为数字!直接使用字符串,避免大整数精度丢失
|
||
const tenantId = currentTenantId; // 直接使用字符串
|
||
|
||
const data = {
|
||
tenant_id: tenantId, // 后端会处理字符串到数字的转换
|
||
name: document.getElementById('fieldName').value,
|
||
filed_code: document.getElementById('fieldCode').value,
|
||
field_type: parseInt(document.getElementById('fieldType').value),
|
||
state: parseInt(document.getElementById('fieldState').value)
|
||
};
|
||
|
||
if (isEdit) {
|
||
data.tenant_id = tenantId;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(url, {
|
||
method: method,
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.isSuccess) {
|
||
showMessage(isEdit ? '字段更新成功!' : '字段创建成功!', 'success');
|
||
closeFieldModal();
|
||
refreshFields();
|
||
loadData(); // 重新加载关联数据
|
||
} else {
|
||
showMessage((isEdit ? '更新' : '创建') + '字段失败: ' + result.errorMsg, 'error');
|
||
}
|
||
} catch (error) {
|
||
showMessage((isEdit ? '更新' : '创建') + '字段失败: ' + error.message, 'error');
|
||
}
|
||
}
|
||
|
||
async function deleteField(fieldId) {
|
||
if (!confirm('确定要删除这个字段吗?')) {
|
||
return;
|
||
}
|
||
|
||
if (!currentTenantId) {
|
||
showMessage('请先选择租户ID', 'error');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 不要转换为数字!直接使用字符串,避免大整数精度丢失
|
||
const tenantId = currentTenantId; // 直接使用字符串
|
||
|
||
const response = await fetch(`/api/field-management/fields/${fieldId}?tenant_id=${tenantId}`, {
|
||
method: 'DELETE'
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.isSuccess) {
|
||
showMessage('字段删除成功!', 'success');
|
||
refreshFields();
|
||
loadData(); // 重新加载关联数据
|
||
} else {
|
||
showMessage('删除字段失败: ' + result.errorMsg, 'error');
|
||
}
|
||
} catch (error) {
|
||
showMessage('删除字段失败: ' + error.message, 'error');
|
||
}
|
||
}
|
||
|
||
// 数据库备份恢复功能
|
||
async function backupDatabase() {
|
||
if (!currentTenantId) {
|
||
showMessage('请先选择租户ID', 'error');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 不要转换为数字!直接使用字符串,避免大整数精度丢失
|
||
const tenantId = currentTenantId; // 直接使用字符串
|
||
|
||
const response = await fetch('/api/database/backup', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
tenant_id: tenantId // 后端会处理字符串到数字的转换
|
||
})
|
||
});
|
||
|
||
if (response.ok) {
|
||
const blob = await response.blob();
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `db_backup_${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
window.URL.revokeObjectURL(url);
|
||
document.body.removeChild(a);
|
||
showMessage('数据库备份成功!', 'success');
|
||
} else {
|
||
const result = await response.json();
|
||
showMessage('备份失败: ' + (result.errorMsg || '未知错误'), 'error');
|
||
}
|
||
} catch (error) {
|
||
showMessage('备份失败: ' + error.message, 'error');
|
||
}
|
||
}
|
||
|
||
function showRestoreModal() {
|
||
if (!currentTenantId) {
|
||
showMessage('请先选择租户ID', 'error');
|
||
return;
|
||
}
|
||
document.getElementById('restoreFile').value = '';
|
||
document.getElementById('restoreModal').style.display = 'block';
|
||
}
|
||
|
||
function closeRestoreModal() {
|
||
document.getElementById('restoreModal').style.display = 'none';
|
||
}
|
||
|
||
async function restoreDatabase() {
|
||
const fileInput = document.getElementById('restoreFile');
|
||
const file = fileInput.files[0];
|
||
|
||
if (!file) {
|
||
showMessage('请选择备份文件', 'error');
|
||
return;
|
||
}
|
||
|
||
if (!currentTenantId) {
|
||
showMessage('请先选择租户ID', 'error');
|
||
return;
|
||
}
|
||
|
||
if (!confirm('确定要恢复数据库吗?这将覆盖当前租户的所有数据!')) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 不要转换为数字!直接使用字符串,避免大整数精度丢失
|
||
const tenantId = currentTenantId; // 直接使用字符串
|
||
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
formData.append('tenant_id', tenantId);
|
||
|
||
const response = await fetch(`/api/database/restore?tenant_id=${tenantId}`, {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.isSuccess) {
|
||
showMessage('数据库恢复成功!', 'success');
|
||
closeRestoreModal();
|
||
loadData(); // 重新加载数据
|
||
} else {
|
||
showMessage('恢复失败: ' + result.errorMsg, 'error');
|
||
}
|
||
} catch (error) {
|
||
showMessage('恢复失败: ' + error.message, 'error');
|
||
}
|
||
}
|
||
|
||
// 初始化搜索功能
|
||
setupSearch();
|
||
</script>
|
||
</body>
|
||
</html>
|