ai-business-write/static/template_field_manager.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>