570 lines
18 KiB
HTML
570 lines
18 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: 1400px;
|
|
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;
|
|
}
|
|
|
|
.template-selector {
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.template-selector label {
|
|
display: block;
|
|
margin-bottom: 10px;
|
|
font-weight: 500;
|
|
color: #333;
|
|
}
|
|
|
|
.template-selector select {
|
|
width: 100%;
|
|
padding: 10px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
background: white;
|
|
}
|
|
|
|
.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 h2 {
|
|
font-size: 18px;
|
|
margin-bottom: 15px;
|
|
color: #333;
|
|
padding-bottom: 10px;
|
|
border-bottom: 2px solid #4CAF50;
|
|
}
|
|
|
|
.field-section.output h2 {
|
|
border-bottom-color: #2196F3;
|
|
}
|
|
|
|
.field-count {
|
|
font-size: 12px;
|
|
color: #999;
|
|
font-weight: normal;
|
|
margin-left: 10px;
|
|
}
|
|
|
|
.field-list {
|
|
max-height: 500px;
|
|
overflow-y: auto;
|
|
padding: 10px 0;
|
|
}
|
|
|
|
.field-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 10px;
|
|
margin-bottom: 8px;
|
|
background: white;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.field-item:hover {
|
|
background: #f0f0f0;
|
|
border-color: #4CAF50;
|
|
}
|
|
|
|
.field-item.checked {
|
|
background: #e8f5e9;
|
|
border-color: #4CAF50;
|
|
}
|
|
|
|
.field-item input[type="checkbox"] {
|
|
margin-right: 10px;
|
|
width: 18px;
|
|
height: 18px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.field-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.field-name {
|
|
font-weight: 500;
|
|
color: #333;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.field-code {
|
|
font-size: 12px;
|
|
color: #999;
|
|
font-family: 'Courier New', monospace;
|
|
}
|
|
|
|
.actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 10px;
|
|
padding-top: 20px;
|
|
border-top: 1px solid #e0e0e0;
|
|
}
|
|
|
|
.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-primary:disabled {
|
|
background: #ccc;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: #f5f5f5;
|
|
color: #333;
|
|
border: 1px solid #ddd;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: #e0e0e0;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>模板字段关联管理</h1>
|
|
<p class="subtitle">维护模板与输入字段、输出字段的关联关系</p>
|
|
|
|
<div id="message" class="message"></div>
|
|
|
|
<div class="template-selector">
|
|
<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">
|
|
<h2>
|
|
输入字段
|
|
<span class="field-count" id="inputFieldCount">(0)</span>
|
|
</h2>
|
|
<div class="search-box">
|
|
<input type="text" id="inputSearch" placeholder="搜索输入字段...">
|
|
</div>
|
|
<div class="field-list" id="inputFieldsList"></div>
|
|
</div>
|
|
|
|
<div class="field-section output">
|
|
<h2>
|
|
输出字段
|
|
<span class="field-count" id="outputFieldCount">(0)</span>
|
|
</h2>
|
|
<div class="search-box">
|
|
<input type="text" id="outputSearch" placeholder="搜索输出字段...">
|
|
</div>
|
|
<div class="field-list" id="outputFieldsList"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="actions" id="actions" style="display: none;">
|
|
<button class="btn btn-secondary" onclick="resetSelection()">重置</button>
|
|
<button class="btn btn-primary" onclick="saveRelations()">保存关联关系</button>
|
|
</div>
|
|
|
|
<div id="loading" class="loading">加载中...</div>
|
|
</div>
|
|
|
|
<script>
|
|
let templates = [];
|
|
let inputFields = [];
|
|
let outputFields = [];
|
|
let relations = {};
|
|
let currentTemplateId = null;
|
|
let selectedInputFields = new Set();
|
|
let selectedOutputFields = new Set();
|
|
|
|
// 页面加载时初始化
|
|
window.onload = function() {
|
|
loadData();
|
|
setupSearch();
|
|
};
|
|
|
|
// 加载数据
|
|
async function loadData() {
|
|
try {
|
|
const response = await fetch('/api/template-field-relations');
|
|
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 || {};
|
|
|
|
populateTemplateSelect();
|
|
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 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}')">
|
|
<input type="checkbox" ${isChecked ? 'checked' : ''}
|
|
onclick="event.stopPropagation(); 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>
|
|
</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) {
|
|
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({
|
|
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 showMessage(message, type) {
|
|
const msgDiv = document.getElementById('message');
|
|
msgDiv.textContent = message;
|
|
msgDiv.className = 'message ' + type + ' show';
|
|
setTimeout(() => {
|
|
msgDiv.classList.remove('show');
|
|
}, 3000);
|
|
}
|
|
|
|
// 隐藏字段区域
|
|
function hideFields() {
|
|
document.getElementById('fieldsContainer').style.display = 'none';
|
|
document.getElementById('actions').style.display = 'none';
|
|
document.getElementById('stats').style.display = 'none';
|
|
}
|
|
|
|
// HTML转义
|
|
function escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|