Compare commits

...

2 Commits

Author SHA1 Message Date
105f4d5492 feat(v4.3.0): 新增/编辑弹框中添加文件夹选择
- 新增数据时可选择所属文件夹
- 编辑数据时可更改所属文件夹
- 切换类型时自动更新文件夹列表
2026-04-22 18:08:41 +08:00
e92349e111 feat(v4.2.1): 文件夹按钮样式优化+新增数据到文件夹功能
- hover文件夹时按钮悬浮在右侧,高度一致
- 新增数据按钮(绿色+)直接添加数据到该文件夹
2026-04-22 16:59:27 +08:00
2 changed files with 126 additions and 7 deletions

View File

@@ -1087,18 +1087,38 @@ INDEX_TEMPLATE = '''
.folder-list a i { margin-right: 5px; }
.folder-list .folder-item {
display: flex;
align-items: center;
padding: 6px 10px;
position: relative;
}
.folder-list .folder-item a {
padding: 0 10px;
padding: 6px 10px;
flex-grow: 1;
display: flex;
align-items: center;
}
.folder-list .folder-actions {
position: absolute;
right: 10px;
display: none;
gap: 4px;
background: #343a40;
padding: 4px 8px;
border-radius: 4px;
}
.folder-list .folder-item:hover .folder-actions {
display: flex;
gap: 2px;
}
.folder-list .folder-actions .btn {
padding: 2px 6px;
font-size: 12px;
line-height: 1;
}
.folder-list .folder-item:hover {
background: #495057;
}
.folder-list .folder-item:hover a {
color: #fff;
}
.folder-action {
font-size: 12px;
@@ -1489,6 +1509,12 @@ INDEX_TEMPLATE = '''
</label>
</div>
</div>
<div class="mb-3">
<label class="form-label">所属文件夹</label>
<select id="addFolder" class="form-select">
<option value="">未分类(根目录)</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
@@ -1603,6 +1629,12 @@ INDEX_TEMPLATE = '''
</label>
</div>
</div>
<div class="mb-3">
<label class="form-label">所属文件夹</label>
<select id="editFolder" class="form-select">
<option value="">未分类(根目录)</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
@@ -2193,6 +2225,8 @@ document.addEventListener('DOMContentLoaded', async () => {
// 编辑时类型切换
document.getElementById('editType').addEventListener('change', (e) => {
updateEditFieldsByType(e.target.value);
// 切换类型时更新文件夹列表
loadFolderSelect(e.target.value, 'editFolder');
});
// 搜索 - 直接绑定,不用 debounce
@@ -2664,6 +2698,9 @@ function showAddModal(type) {
// 设置类型
document.getElementById('addType').value = type;
// 重置文件夹ID从顶部按钮添加时不指定文件夹
currentAddFolderId = null;
// 只有不是编辑草稿时才重置
// currentDraftId 在 editDraft 中已设置,不要覆盖
@@ -2693,6 +2730,9 @@ function showAddModal(type) {
// 打开弹窗
new bootstrap.Modal(document.getElementById('addModal')).show();
// 加载该类型下的文件夹列表
loadFolderSelect(type, 'addFolder', currentAddFolderId);
// 启动自动保存
startAutoSave();
@@ -2720,7 +2760,8 @@ async function addItem() {
due_date: type === 'todo' ? document.getElementById('addDueDate').value : null,
note: document.getElementById('addNote').value,
tags: document.getElementById('addTags').value.split(',').map(t => t.trim()).filter(t => t),
is_starred: document.getElementById('addStarred').checked
is_starred: document.getElementById('addStarred').checked,
folder_id: document.getElementById('addFolder').value ? parseInt(document.getElementById('addFolder').value) : currentAddFolderId
};
const res = await fetch(`${API_BASE}/items`, {
@@ -2741,6 +2782,7 @@ async function addItem() {
stopAutoSave();
hideDraftIndicator();
currentAddFolderId = null; // 重置文件夹ID
refreshData();
}
}
@@ -3282,6 +3324,9 @@ async function openEditModal(id) {
// 设置重点关注状态
document.getElementById('editStarred').checked = item.is_starred === 1;
// 加载文件夹列表并设置当前文件夹
await loadFolderSelect(type, 'editFolder', item.folder_id);
// 保存原始数据用于比较
window.editOriginalData = {
type,
@@ -3370,7 +3415,8 @@ async function saveEdit() {
due_date: type === 'todo' ? document.getElementById('editDueDate').value : null,
note: document.getElementById('editNote').value,
tags: document.getElementById('editTags').value.split(',').map(t => t.trim()).filter(t => t),
is_starred: document.getElementById('editStarred').checked ? 1 : 0
is_starred: document.getElementById('editStarred').checked ? 1 : 0,
folder_id: document.getElementById('editFolder').value ? parseInt(document.getElementById('editFolder').value) : null
};
try {
@@ -4194,10 +4240,13 @@ function renderFolderList(type) {
<i class="bi bi-folder"></i> ${f.name} <small class="text-muted">(${f.item_count || 0})</small>
</a>
<div class="folder-actions">
<button class="btn btn-sm btn-outline-secondary py-0 px-1" onclick="event.stopPropagation(); showEditFolderModal(${f.id}, '${f.name}'); return false;" title="重命名">
<button class="btn btn-outline-success" onclick="event.stopPropagation(); showAddToFolderModal('${type}', ${f.id}); return false;" title="新增数据到此文件夹">
<i class="bi bi-plus"></i>
</button>
<button class="btn btn-outline-secondary" onclick="event.stopPropagation(); showEditFolderModal(${f.id}, '${f.name}'); return false;" title="重命名">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-outline-danger py-0 px-1" onclick="event.stopPropagation(); deleteFolderConfirm(${f.id}, '${f.name}'); return false;" title="删除">
<button class="btn btn-outline-danger" onclick="event.stopPropagation(); deleteFolderConfirm(${f.id}, '${f.name}'); return false;" title="删除">
<i class="bi bi-trash"></i>
</button>
</div>
@@ -4327,6 +4376,76 @@ async function deleteFolderConfirm(folderId, folderName) {
}
}
// 显示新增数据到文件夹的模态框
function showAddToFolderModal(type, folderId) {
// 离线检查
if (!checkOnlineBeforeAction('添加数据')) return;
// 设置类型
document.getElementById('addType').value = type;
document.getElementById('editFolderId').value = ''; // 重置编辑ID
// 设置弹窗标题和图标
const typeInfo = {
text: { icon: '📝', title: '添加文本' },
link: { icon: '🔗', title: '添加链接' },
column: { icon: '📰', title: '添加专栏' },
todo: { icon: '', title: '添加待办' }
};
document.getElementById('addModalIcon').textContent = typeInfo[type].icon;
document.getElementById('addModalTitle').textContent = typeInfo[type].title;
// 显示/隐藏对应字段
document.getElementById('contentGroup').style.display = type === 'text' ? 'block' : 'none';
document.getElementById('urlGroup').style.display = ['link', 'column'].includes(type) ? 'block' : 'none';
document.getElementById('sourceGroup').style.display = type === 'column' ? 'block' : 'none';
document.getElementById('todoFields').style.display = type === 'todo' ? 'block' : 'none';
// 清空表单
document.getElementById('addForm').reset();
hideDraftIndicator();
// 设置默认文件夹(隐藏字段,需要在提交时带上)
currentAddFolderId = folderId;
// 打开弹窗
new bootstrap.Modal(document.getElementById('addModal')).show();
// 加载该类型下的文件夹列表,并选中指定文件夹
loadFolderSelect(type, 'addFolder', folderId);
// 启动自动保存
startAutoSave();
// 弹框关闭时停止自动保存并重置文件夹ID
document.getElementById('addModal').addEventListener('hidden.bs.modal', () => {
stopAutoSave();
currentAddFolderId = null;
}, { once: true });
}
// 当前新增时的文件夹ID
let currentAddFolderId = null;
// 加载文件夹选择列表
async function loadFolderSelect(type, selectId, selectedFolderId = null) {
const res = await fetch(`${API_BASE}/folders?type=${type}`);
const data = await res.json();
const select = document.getElementById(selectId);
if (!select) return;
select.innerHTML = '<option value="">未分类(根目录)</option>';
if (data.success && data.data.length > 0) {
data.data.forEach(f => {
const selected = f.id == selectedFolderId ? 'selected' : '';
select.innerHTML += `<option value="${f.id}" ${selected}>${f.name}</option>`;
});
}
}
function filterByFolder(type, folderId) {
// 更新侧边栏选中状态
document.querySelectorAll('.sidebar a').forEach(a => a.classList.remove('active'));

View File

@@ -391,7 +391,7 @@ class Database:
def update_item(self, item_id: int, **kwargs) -> bool:
"""更新条目"""
allowed_fields = ['type', 'title', 'content', 'url', 'source', 'status', 'priority', 'due_date', 'note', 'is_starred']
allowed_fields = ['type', 'title', 'content', 'url', 'source', 'status', 'priority', 'due_date', 'note', 'is_starred', 'folder_id']
update_fields = {k: v for k, v in kwargs.items() if k in allowed_fields}
# 只有 tags 变化也算有效更新