ai-business-write/static/template_field_manager.html

1165 lines
40 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">
<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()">&times;</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()">&times;</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() {
loadTenantIds();
};
// 加载租户ID列表
async function loadTenantIds() {
try {
const response = await fetch('/api/tenant-ids');
const result = await response.json();
if (result.isSuccess) {
const select = document.getElementById('tenantSelect');
select.innerHTML = '<option value="">请选择租户ID...</option>';
result.data.tenant_ids.forEach(tenantId => {
const option = document.createElement('option');
option.value = tenantId;
option.textContent = tenantId;
select.appendChild(option);
});
select.onchange = function() {
const tenantId = this.value;
if (tenantId) {
currentTenantId = parseInt(tenantId);
loadData();
} else {
hideAllSections();
}
};
} else {
showMessage('加载租户ID列表失败: ' + result.errorMsg, 'error');
}
} catch (error) {
showMessage('加载租户ID列表失败: ' + error.message, 'error');
}
}
// 加载数据
async function loadData() {
if (!currentTenantId) {
return;
}
document.getElementById('loading').style.display = 'block';
hideAllSections();
try {
const response = await fetch(`/api/template-field-relations?tenant_id=${currentTenantId}`);
const result = await response.json();
if (result.isSuccess) {
templates = result.data.templates || [];
inputFields = result.data.input_fields || [];
outputFields = result.data.output_fields || [];
relations = result.data.relations || {};
allFields = [...inputFields, ...outputFields];
populateTemplateSelect();
refreshFields();
document.getElementById('fieldManagementSection').style.display = 'block';
document.getElementById('templateFieldSection').style.display = 'block';
document.getElementById('backupSection').style.display = 'block';
document.getElementById('loading').style.display = 'none';
} else {
showMessage('加载数据失败: ' + result.errorMsg, 'error');
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);
const relatedFieldIds = new Set(relations[templateId] || []);
selectedInputFields = new Set();
selectedOutputFields = new Set();
inputFields.forEach(field => {
if (relatedFieldIds.has(field.id)) {
selectedInputFields.add(field.id);
}
});
outputFields.forEach(field => {
if (relatedFieldIds.has(field.id)) {
selectedOutputFields.add(field.id);
}
});
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 => {
const isChecked = selectedSet.has(field.id);
return `
<div class="field-item ${isChecked ? 'checked' : ''}" onclick="toggleField(${field.id}, '${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(${field.id}, '${type}')">
</div>
`;
}).join('');
}
// 切换字段选择
function toggleField(fieldId, type) {
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 response = await fetch('/api/template-field-relations', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
tenant_id: currentTenantId,
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') {
document.querySelectorAll('.tab')[0].classList.add('active');
document.getElementById('inputFieldsTab').classList.add('active');
} else {
document.querySelectorAll('.tab')[1].classList.add('active');
document.getElementById('outputFieldsTab').classList.add('active');
}
refreshFields();
}
async function refreshFields() {
if (!currentTenantId) {
return;
}
try {
const response = await fetch(`/api/fields?tenant_id=${currentTenantId}`);
const result = await response.json();
if (result.isSuccess) {
allFields = result.data.fields || [];
inputFields = allFields.filter(f => f.field_type === 1);
outputFields = allFields.filter(f => f.field_type === 2);
renderFieldsTable();
} else {
showMessage('刷新字段列表失败: ' + result.errorMsg, 'error');
}
} catch (error) {
showMessage('刷新字段列表失败: ' + error.message, 'error');
}
}
function renderFieldsTable() {
const activeTab = document.querySelector('.tab-content.active');
const isInput = activeTab.id === 'inputFieldsTab';
const fields = isInput ? inputFields : outputFields;
const searchTerm = (isInput ?
document.getElementById('inputFieldSearch').value.toLowerCase() :
document.getElementById('outputFieldSearch').value.toLowerCase());
const filteredFields = fields.filter(field => {
return field.name.toLowerCase().includes(searchTerm) ||
field.filed_code.toLowerCase().includes(searchTerm);
});
const tableId = isInput ? 'inputFieldsTable' : 'outputFieldsTable';
const container = document.getElementById(tableId);
if (filteredFields.length === 0) {
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 ? '<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;
document.getElementById('fieldState').value = field.state;
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/fields/${fieldId}` : '/api/fields';
const method = isEdit ? 'PUT' : 'POST';
const data = {
tenant_id: currentTenantId,
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 = currentTenantId;
}
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 response = await fetch(`/api/fields/${fieldId}?tenant_id=${currentTenantId}`, {
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 response = await fetch('/api/database/backup', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
tenant_id: currentTenantId
})
});
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 formData = new FormData();
formData.append('file', file);
formData.append('tenant_id', currentTenantId);
const response = await fetch(`/api/database/restore?tenant_id=${currentTenantId}`, {
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>