|
|
|
|
@@ -132,6 +132,23 @@ def create_tag():
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/api/tags/<int:tag_id>', methods=['PUT'])
|
|
|
|
|
def update_tag(tag_id):
|
|
|
|
|
"""更新标签"""
|
|
|
|
|
data = request.get_json()
|
|
|
|
|
name = data.get('name', '').strip()
|
|
|
|
|
|
|
|
|
|
if not name:
|
|
|
|
|
return jsonify({'success': False, 'error': '标签名不能为空'}), 400
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
if db.update_tag(tag_id, name):
|
|
|
|
|
return jsonify({'success': True, 'data': {'id': tag_id, 'name': name}})
|
|
|
|
|
return jsonify({'success': False, 'error': '标签不存在或名称已存在'}), 404
|
|
|
|
|
except Exception as e:
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/api/tags/<int:tag_id>', methods=['DELETE'])
|
|
|
|
|
def delete_tag(tag_id):
|
|
|
|
|
"""删除标签"""
|
|
|
|
|
@@ -486,10 +503,18 @@ INDEX_TEMPLATE = '''
|
|
|
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-body">
|
|
|
|
|
<div class="mb-3">
|
|
|
|
|
<div class="input-group">
|
|
|
|
|
<input type="text" id="newTagName" class="form-control" placeholder="新标签名称">
|
|
|
|
|
<button class="btn btn-primary" onclick="createTag()"><i class="bi bi-plus"></i> 创建</button>
|
|
|
|
|
<div class="row mb-3">
|
|
|
|
|
<div class="col">
|
|
|
|
|
<div class="input-group">
|
|
|
|
|
<input type="text" id="tagSearch" class="form-control" placeholder="搜索标签...">
|
|
|
|
|
<button class="btn btn-outline-secondary" onclick="loadTagManagerList()"><i class="bi bi-search"></i></button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col">
|
|
|
|
|
<div class="input-group">
|
|
|
|
|
<input type="text" id="newTagName" class="form-control" placeholder="新标签名称">
|
|
|
|
|
<button class="btn btn-primary" onclick="createTag()"><i class="bi bi-plus"></i> 创建</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="tagListContainer">
|
|
|
|
|
@@ -518,6 +543,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
|
|
document.getElementById('addTags').addEventListener('input', showTagSuggestions);
|
|
|
|
|
document.getElementById('editTags').addEventListener('input', showTagSuggestionsEdit);
|
|
|
|
|
|
|
|
|
|
// 标签搜索实时过滤
|
|
|
|
|
document.getElementById('tagSearch')?.addEventListener('input', debounce(loadTagManagerList, 300));
|
|
|
|
|
|
|
|
|
|
// 类型切换时显示/隐藏字段
|
|
|
|
|
document.getElementById('addType').addEventListener('change', (e) => {
|
|
|
|
|
const type = e.target.value;
|
|
|
|
|
@@ -964,20 +992,42 @@ async function loadTagManagerList() {
|
|
|
|
|
if (!data.success) return;
|
|
|
|
|
|
|
|
|
|
const container = document.getElementById('tagListContainer');
|
|
|
|
|
if (!data.data.length) {
|
|
|
|
|
|
|
|
|
|
// 搜索过滤
|
|
|
|
|
const searchKeyword = document.getElementById('tagSearch').value.trim().toLowerCase();
|
|
|
|
|
let tags = data.data;
|
|
|
|
|
if (searchKeyword) {
|
|
|
|
|
tags = tags.filter(t => t.name.toLowerCase().includes(searchKeyword));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!tags.length) {
|
|
|
|
|
container.innerHTML = '<div class="text-center text-muted py-3">暂无标签</div>';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
container.innerHTML = data.data.map(tag => `
|
|
|
|
|
<div class="d-flex justify-content-between align-items-center p-2 border-bottom">
|
|
|
|
|
<div>
|
|
|
|
|
container.innerHTML = tags.map(tag => `
|
|
|
|
|
<div class="d-flex justify-content-between align-items-center p-2 border-bottom" id="tag-row-${tag.id}">
|
|
|
|
|
<div id="tag-display-${tag.id}">
|
|
|
|
|
<span class="badge bg-secondary">${tag.name}</span>
|
|
|
|
|
<span class="text-muted small ms-2">${tag.item_count || 0} 个条目</span>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="btn btn-sm btn-outline-danger" onclick="deleteTagManager(${tag.id}, '${tag.name}')">
|
|
|
|
|
<i class="bi bi-trash"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<div id="tag-edit-${tag.id}" style="display:none;">
|
|
|
|
|
<input type="text" class="form-control form-control-sm" id="edit-tag-name-${tag.id}" value="${tag.name}" style="width:150px;">
|
|
|
|
|
</div>
|
|
|
|
|
<div class="btn-group btn-group-sm">
|
|
|
|
|
<button class="btn btn-outline-primary" id="tag-edit-btn-${tag.id}" onclick="showEditTag(${tag.id})" title="编辑">
|
|
|
|
|
<i class="bi bi-pencil"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn btn-outline-success" id="tag-save-btn-${tag.id}" style="display:none;" onclick="saveEditTag(${tag.id})" title="保存">
|
|
|
|
|
<i class="bi bi-check"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn btn-outline-secondary" id="tag-cancel-btn-${tag.id}" style="display:none;" onclick="cancelEditTag(${tag.id}, '${tag.name}')" title="取消">
|
|
|
|
|
<i class="bi bi-x"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn btn-outline-danger" onclick="deleteTagManager(${tag.id}, '${tag.name}')" title="删除">
|
|
|
|
|
<i class="bi bi-trash"></i>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`).join('');
|
|
|
|
|
}
|
|
|
|
|
@@ -1008,6 +1058,45 @@ async function deleteTagManager(id, name) {
|
|
|
|
|
loadItems();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 编辑标签
|
|
|
|
|
function showEditTag(id) {
|
|
|
|
|
document.getElementById(`tag-display-${id}`).style.display = 'none';
|
|
|
|
|
document.getElementById(`tag-edit-${id}`).style.display = 'block';
|
|
|
|
|
document.getElementById(`tag-edit-btn-${id}`).style.display = 'none';
|
|
|
|
|
document.getElementById(`tag-save-btn-${id}`).style.display = 'inline-block';
|
|
|
|
|
document.getElementById(`tag-cancel-btn-${id}`).style.display = 'inline-block';
|
|
|
|
|
document.getElementById(`edit-tag-name-${id}`).focus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function cancelEditTag(id, oldName) {
|
|
|
|
|
document.getElementById(`edit-tag-name-${id}`).value = oldName;
|
|
|
|
|
document.getElementById(`tag-display-${id}`).style.display = 'block';
|
|
|
|
|
document.getElementById(`tag-edit-${id}`).style.display = 'none';
|
|
|
|
|
document.getElementById(`tag-edit-btn-${id}`).style.display = 'inline-block';
|
|
|
|
|
document.getElementById(`tag-save-btn-${id}`).style.display = 'none';
|
|
|
|
|
document.getElementById(`tag-cancel-btn-${id}`).style.display = 'none';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function saveEditTag(id) {
|
|
|
|
|
const newName = document.getElementById(`edit-tag-name-${id}`).value.trim();
|
|
|
|
|
if (!newName) return;
|
|
|
|
|
|
|
|
|
|
const res = await fetch(`${API_BASE}/tags/${id}`, {
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body: JSON.stringify({ name: newName })
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (res.ok) {
|
|
|
|
|
loadTagManagerList();
|
|
|
|
|
loadTags();
|
|
|
|
|
loadItems();
|
|
|
|
|
} else {
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
alert(data.error || '更新失败');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 导出数据
|
|
|
|
|
async function exportData() {
|
|
|
|
|
const res = await fetch(`${API_BASE}/items?limit=1000`);
|
|
|
|
|
|