Compare commits

...

4 Commits

2 changed files with 103 additions and 18 deletions

View File

@@ -55,7 +55,8 @@ def list_items():
status=request.args.get('status'),
tag=request.args.get('tag'),
keyword=request.args.get('keyword'),
starred=starred
starred=starred,
folder_id=folder_id
)
return jsonify({'success': True, 'data': items, 'total': total})
@@ -872,6 +873,8 @@ INDEX_TEMPLATE = '''
margin-left: 200px;
padding: 20px;
padding-top: 0; /* 顶部按钮栏有 sticky这里去掉顶部 padding */
width: -webkit-fill-available;
width: fill-available;
}
/* 顶部操作栏固定在主内容区顶部 */
@@ -1032,6 +1035,39 @@ INDEX_TEMPLATE = '''
.item-card h6 { font-size: 14px; margin-bottom: 2px; }
.item-card p { margin-bottom: 2px; }
.item-card .text-muted.small { font-size: 12px; }
/* 卡片内容自适应布局 */
.item-card .card-content {
display: flex;
align-items: flex-start;
gap: 8px;
}
.item-card .card-main {
flex: 1;
min-width: 0;
}
.item-card .card-actions {
display: flex;
align-items: center;
gap: 2px;
flex-shrink: 0;
}
.item-card .card-actions .btn {
white-space: nowrap;
}
/* 单行显示模式 */
.item-card.compact .card-content {
align-items: center;
}
.item-card.compact .card-main h6 {
margin-bottom: 0;
}
.item-card.compact .card-main p {
display: none;
}
/* 双行显示模式(默认) */
.item-card.double-line .card-main p {
display: block;
}
.type-text { border-left: 4px solid #17a2b8; }
.type-link { border-left: 4px solid #28a745; }
.type-column { border-left: 4px solid #6f42c1; }
@@ -1182,6 +1218,9 @@ INDEX_TEMPLATE = '''
<button class="btn btn-outline-info me-2" onclick="showAIAddModal()" title="AI自动添加">
<i class="bi bi-robot"></i> AI添加
</button>
<button class="btn btn-outline-secondary me-1" onclick="toggleDisplayMode()" title="切换显示模式" id="displayModeBtn">
<i class="bi bi-layout-text-sidebar"></i>
</button>
<button class="btn btn-outline-secondary me-1" onclick="showAddModal('text')" title="添加文本">
<i class="bi bi-file-text"></i>
</button>
@@ -1906,6 +1945,9 @@ document.addEventListener('DOMContentLoaded', async () => {
// 启动连接状态检测
startConnectionCheck();
// 初始化显示模式
updateDisplayMode();
// 确保初始状态清空
document.getElementById('searchInput').value = '';
document.getElementById('typeFilter').value = '';
@@ -2003,11 +2045,13 @@ function renderItems(items) {
return;
}
const modeClass = displayMode === 'single' ? 'compact' : 'double-line';
container.innerHTML = items.map(item => `
<div class="card type-${item.type} item-card ${item.is_starred ? 'is-starred' : ''}" style="cursor: pointer;" onclick="showDetail(${item.id})">
<div class="card type-${item.type} item-card ${modeClass} ${item.is_starred ? 'is-starred' : ''}" style="cursor: pointer;" onclick="showDetail(${item.id})">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div style="flex: 1; min-width: 0;">
<div class="card-content">
<div class="card-main">
<h6 class="card-title text-truncate mb-1 ${item.type === 'todo' && item.status === 'completed' ? 'text-muted' : ''}">
${item.is_starred ? '<i class="bi bi-star-fill" style="color:#ffc107;"></i>' : ''} ${getTypeIcon(item.type)} ${item.title || truncate(item.content || item.url, 30)}
${item.type === 'todo' && item.status === 'completed' ? '' : ''}
@@ -2023,7 +2067,7 @@ function renderItems(items) {
${item.type === 'todo' ? `${getStatusLabelShort(item.status)} ${getPriorityLabelShort(item.priority)} ${item.due_date ? '📅' + formatDueDate(item.due_date) : ''}` : ''}
</p>
</div>
<div class="d-flex align-items-center gap-1 flex-wrap ms-2" onclick="event.stopPropagation();">
<div class="card-actions" onclick="event.stopPropagation();">
${item.tags.slice(0, 2).map(t => `<span class="badge bg-secondary" style="font-size:10px;">${t}</span>`).join('')}
<button class="btn btn-sm btn-outline-warning py-0 px-1 star-btn" onclick="toggleStar(${item.id})" title="${item.is_starred ? '取消重点关注' : '设为重点关注'}">
<i class="bi bi-star${item.is_starred ? '-fill' : ''}" style="font-size:11px; ${item.is_starred ? 'color:#ffc107;' : ''}"></i>
@@ -2088,6 +2132,28 @@ function renderPagination(total, page) {
container.innerHTML = html;
}
// 显示模式single单行或 double双行
let displayMode = localStorage.getItem('displayMode') || 'double';
// 切换显示模式
function toggleDisplayMode() {
displayMode = displayMode === 'single' ? 'double' : 'single';
localStorage.setItem('displayMode', displayMode);
updateDisplayMode();
renderItems(currentItems);
}
// 更新显示模式按钮图标
function updateDisplayMode() {
const btn = document.getElementById('displayModeBtn');
if (btn) {
btn.innerHTML = displayMode === 'single'
? '<i class="bi bi-layout-text-sidebar-reverse"></i>'
: '<i class="bi bi-layout-text-sidebar"></i>';
btn.title = displayMode === 'single' ? '当前:单行(点击切换双行)' : '当前:双行(点击切换单行)';
}
}
// 加载统计
async function loadStats() {
const res = await fetch(`${API_BASE}/stats`);
@@ -2175,8 +2241,8 @@ function renderDrafts(drafts, total) {
container.innerHTML = header + drafts.map(draft => `
<div class="card type-${draft.type} item-card" style="opacity: 0.85;">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div style="flex: 1; min-width: 0;">
<div class="card-content">
<div class="card-main">
<h6 class="card-title text-truncate mb-1">
${getTypeIcon(draft.type)} ${draft.title || '(无标题)'}
</h6>
@@ -2185,14 +2251,14 @@ function renderDrafts(drafts, total) {
</p>
<small class="text-muted">保存于: ${formatShortDate(draft.updated_at)}</small>
</div>
<div class="d-flex gap-1">
<button class="btn btn-sm btn-outline-primary" onclick="editDraft(${draft.id})" title="编辑">
<div class="card-actions">
<button class="btn btn-sm btn-outline-primary py-0 px-1" onclick="editDraft(${draft.id})" title="编辑">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-outline-success" onclick="publishDraft(${draft.id})" title="发布">
<button class="btn btn-sm btn-outline-success py-0 px-1" onclick="publishDraft(${draft.id})" title="发布">
<i class="bi bi-send"></i>
</button>
<button class="btn btn-sm btn-outline-danger" onclick="deleteDraft(${draft.id})" title="删除">
<button class="btn btn-sm btn-outline-danger py-0 px-1" onclick="deleteDraft(${draft.id})" title="删除">
<i class="bi bi-trash"></i>
</button>
</div>
@@ -3334,8 +3400,8 @@ function renderTrash(items, total) {
container.innerHTML = header + items.map(item => `
<div class="card type-${item.type} item-card" style="opacity: 0.7;">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div style="flex: 1; min-width: 0;">
<div class="card-content">
<div class="card-main">
<h6 class="card-title text-truncate mb-1">
${getTypeIcon(item.type)} ${item.title || truncate(item.content || item.url, 30)}
</h6>
@@ -3343,11 +3409,11 @@ function renderTrash(items, total) {
删除时间: ${formatShortDate(item.deleted_at)}
</p>
</div>
<div class="d-flex gap-1">
<button class="btn btn-sm btn-outline-success" onclick="restoreItem(${item.id})" title="恢复">
<i class="bi bi-arrow-counterclockwise"></i> 恢复
<div class="card-actions">
<button class="btn btn-sm btn-outline-success py-0 px-1" onclick="restoreItem(${item.id})" title="恢复">
<i class="bi bi-arrow-counterclockwise"></i>
</button>
<button class="btn btn-sm btn-outline-danger" onclick="deletePermanently(${item.id})" title="彻底删除">
<button class="btn btn-sm btn-outline-danger py-0 px-1" onclick="deletePermanently(${item.id})" title="彻底删除">
<i class="bi bi-trash-fill"></i>
</button>
</div>
@@ -3462,11 +3528,21 @@ function renderFolderList(type) {
}
// 切换文件夹区域展开/折叠
// 切换文件夹区域展开/折叠,并过滤显示该类别数据
function toggleSection(type) {
const section = document.getElementById(`section-${type}`);
if (!section) return;
// 切换展开状态
section.classList.toggle('expanded');
// 更新侧边栏选中状态
document.querySelectorAll('.sidebar a').forEach(a => a.classList.remove('active'));
section.querySelector('.section-header').classList.add('active');
// 设置过滤条件:只过滤类型,不限制文件夹
currentFilter = { type, status: '', starred: null, folder_id: null };
loadItems(1);
}
async function showNewFolderModal(type) {

View File

@@ -326,7 +326,7 @@ class Database:
return items
def count_items(self, type: str = None, status: str = None, tag: str = None,
keyword: str = None, starred: bool = None) -> int:
keyword: str = None, starred: bool = None, folder_id: int = None) -> int:
"""计算符合条件的条目总数"""
with self.get_conn() as conn:
cursor = conn.cursor()
@@ -358,6 +358,15 @@ class Database:
keyword_pattern = f"%{keyword}%"
params.extend([keyword_pattern, keyword_pattern, keyword_pattern])
# 文件夹过滤
if folder_id is not None:
if folder_id == -1:
# 未分类folder_id为NULL
conditions.append("i.folder_id IS NULL")
else:
conditions.append("i.folder_id = ?")
params.append(folder_id)
if conditions:
query += " WHERE " + " AND ".join(conditions)