Compare commits

...

2 Commits

Author SHA1 Message Date
b92239fb1b feat(v4.4.0): 详情弹框添加导航按钮和固定底部操作栏
- 右侧固定显示向上/向下导航按钮
- 底部操作栏(转为待办、编辑、关闭)固定悬浮
2026-04-22 18:35:20 +08:00
105f4d5492 feat(v4.3.0): 新增/编辑弹框中添加文件夹选择
- 新增数据时可选择所属文件夹
- 编辑数据时可更改所属文件夹
- 切换类型时自动更新文件夹列表
2026-04-22 18:08:41 +08:00
2 changed files with 102 additions and 6 deletions

View File

@@ -1226,6 +1226,36 @@ INDEX_TEMPLATE = '''
.quick-insert-area:active {
background: #e8f4ff;
}
/* 详情弹框样式 */
.detail-modal-body {
max-height: 60vh;
overflow-y: auto;
padding-bottom: 20px;
}
.detail-nav-buttons {
position: fixed;
right: 20px;
top: 50%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
gap: 5px;
z-index: 10;
}
.detail-nav-buttons .btn {
padding: 8px 12px;
}
.detail-modal-footer {
position: sticky;
bottom: 0;
background: #fff;
border-top: 1px solid #dee2e6;
z-index: 10;
}
/* 解决底部按钮被遮挡的问题 */
.modal-dialog-scrollable .modal-body {
overflow-y: auto;
}
.star-btn { font-size: 11px; }
.status-pending { color: #ffc107; }
.status-in_progress { color: #17a2b8; }
@@ -1509,6 +1539,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">
@@ -1521,18 +1557,27 @@ INDEX_TEMPLATE = '''
<!-- 详情模态框 -->
<div class="modal fade" id="detailModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><span id="detailTypeIcon"></span> <span id="detailTitle"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="modal-body detail-modal-body">
<!-- 导航按钮 -->
<div class="detail-nav-buttons">
<button class="btn btn-sm btn-outline-secondary" onclick="scrollDetailToTop()" title="回到顶部">
<i class="bi bi-arrow-up"></i>
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="scrollDetailToBottom()" title="去到底部">
<i class="bi bi-arrow-down"></i>
</button>
</div>
<div id="detailContent">
<!-- 动态填充 -->
</div>
</div>
<div class="modal-footer">
<div class="modal-footer detail-modal-footer">
<button type="button" class="btn btn-outline-secondary" id="detailConvertBtn" onclick="showConvertModalFromDetail()" style="display:none;">
<i class="bi bi-arrow-repeat"></i> 转为待办
</button>
@@ -1623,6 +1668,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">
@@ -2213,6 +2264,8 @@ document.addEventListener('DOMContentLoaded', async () => {
// 编辑时类型切换
document.getElementById('editType').addEventListener('change', (e) => {
updateEditFieldsByType(e.target.value);
// 切换类型时更新文件夹列表
loadFolderSelect(e.target.value, 'editFolder');
});
// 搜索 - 直接绑定,不用 debounce
@@ -2716,6 +2769,9 @@ function showAddModal(type) {
// 打开弹窗
new bootstrap.Modal(document.getElementById('addModal')).show();
// 加载该类型下的文件夹列表
loadFolderSelect(type, 'addFolder', currentAddFolderId);
// 启动自动保存
startAutoSave();
@@ -2744,7 +2800,7 @@ async function addItem() {
note: document.getElementById('addNote').value,
tags: document.getElementById('addTags').value.split(',').map(t => t.trim()).filter(t => t),
is_starred: document.getElementById('addStarred').checked,
folder_id: currentAddFolderId // 如果从文件夹添加带上文件夹ID
folder_id: document.getElementById('addFolder').value ? parseInt(document.getElementById('addFolder').value) : currentAddFolderId
};
const res = await fetch(`${API_BASE}/items`, {
@@ -3242,6 +3298,21 @@ function showConvertModalFromDetail() {
setTimeout(() => showConvertModal(currentDetailId), 300);
}
// 详情弹框导航
function scrollDetailToTop() {
const modalBody = document.querySelector('.detail-modal-body');
if (modalBody) {
modalBody.scrollTop = 0;
}
}
function scrollDetailToBottom() {
const modalBody = document.querySelector('.detail-modal-body');
if (modalBody) {
modalBody.scrollTop = modalBody.scrollHeight;
}
}
// 从详情页打开编辑
function openEditModalFromDetail() {
bootstrap.Modal.getInstance(document.getElementById('detailModal')).hide();
@@ -3307,6 +3378,9 @@ async function openEditModal(id) {
// 设置重点关注状态
document.getElementById('editStarred').checked = item.is_starred === 1;
// 加载文件夹列表并设置当前文件夹
await loadFolderSelect(type, 'editFolder', item.folder_id);
// 保存原始数据用于比较
window.editOriginalData = {
type,
@@ -3395,7 +3469,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 {
@@ -4391,6 +4466,9 @@ function showAddToFolderModal(type, folderId) {
// 打开弹窗
new bootstrap.Modal(document.getElementById('addModal')).show();
// 加载该类型下的文件夹列表,并选中指定文件夹
loadFolderSelect(type, 'addFolder', folderId);
// 启动自动保存
startAutoSave();
@@ -4404,6 +4482,24 @@ function showAddToFolderModal(type, folderId) {
// 当前新增时的文件夹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 变化也算有效更新