Compare commits

...

31 Commits

Author SHA1 Message Date
d7b4b48cc7 feat: 详情弹框内容添加Markdown显示开关 2026-04-24 12:58:09 +08:00
3f5284e067 feat: 统计卡片改一行显示+列表上方增加翻页 2026-04-24 12:24:54 +08:00
50d2e62694 feat: 添加置顶功能,置顶优先级最高 2026-04-24 12:16:39 +08:00
70dee494f3 fix: 点击未读数据后更新未读状态和类别计数 2026-04-22 23:49:49 +08:00
4b2a94002b feat: 添加未读提醒功能
- 数据列表中未读数据(views=0)显示特殊样式(黄色背景+红色边框+红点标记)
- 左侧类别显示未读数量(红色badge)
- API返回unread和unread_by_type统计数据
2026-04-22 23:44:22 +08:00
9eb872391e fix: 修复alert字符串中\n导致的JS语法错误 2026-04-22 23:33:32 +08:00
784830bd62 fix: 修复systemConfig变量定义顺序导致的JS错误 2026-04-22 23:24:08 +08:00
252bda696f feat: 添加系统配置功能
- AI自动添加的大模型接口配置(URL/Key/Model)
- 服务器连接状态刷新频率配置
- 数据列表每页显示条数配置
- 草稿自动保存间隔配置
- 显示统计/阅读次数开关
- 配置保存到localStorage
2026-04-22 23:09:39 +08:00
ef822d0eba feat: 编辑和新增弹框优化 - 导航按钮、固定底部、拖拽缩放、大尺寸 2026-04-22 22:47:59 +08:00
cc54fd52c6 feat: 导航按钮移到底部操作栏左侧 2026-04-22 22:00:22 +08:00
e4115b8baa fix: 详情弹框默认宽度改为800px 2026-04-22 21:56:32 +08:00
b99e3e88c9 fix: 详情弹框默认宽度改为600px,可拖拽放大到95vw 2026-04-22 21:53:38 +08:00
35198a8edb feat: 详情弹框添加JavaScript拖拽缩放功能 2026-04-22 18:52:26 +08:00
c9142c3f8a feat: 详情弹框尺寸优化 - 更宽、占满屏幕、支持拖拽缩放 2026-04-22 18:46:07 +08:00
9797ddf3f7 fix: 导航按钮改为absolute定位,显示在内容区域右上角 2026-04-22 18:40:56 +08:00
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
e92349e111 feat(v4.2.1): 文件夹按钮样式优化+新增数据到文件夹功能
- hover文件夹时按钮悬浮在右侧,高度一致
- 新增数据按钮(绿色+)直接添加数据到该文件夹
2026-04-22 16:59:27 +08:00
0912d658b8 feat(v4.2.0): 文件夹支持重命名和删除
- hover文件夹时显示编辑和删除按钮
- 编辑按钮打开重命名模态框
- 删除按钮确认后删除,数据移到未分类
2026-04-22 16:27:38 +08:00
8e63db4424 fix(v4.1.1): 待办事务功能优化
- 修复完成状态切换问题(可来回切换完成/未完成)
- 添加全部/未完成/已完成过滤按钮
- 翻页时保持弹窗滚动位置
2026-04-22 15:54:10 +08:00
0335937312 feat(v4.1.0): 文本类别添加待办事务功能
- 可在文本数据详情中追加待办事务
- 支持事务内容、剩余天数(默认1天)、完成状态
- 历史事务按时间倒序排列,显示创建/更新时间
- 分页显示,每页10条
2026-04-22 13:00:00 +08:00
6a0f8d7196 feat(v4.0.0): 快速插入支持选中位置插入
- 先选中部分文本,再双击可在选中位置前/后插入
- 支持替换选中内容
- 自动识别选中文本并在原内容中定位
2026-04-22 12:49:37 +08:00
407a63f3cf fix: 修复JS正则表达式在模板字符串中被破坏的问题 2026-04-22 12:22:07 +08:00
eec8b477be feat: 详情弹框内容支持Markdown格式显示 2026-04-22 12:06:42 +08:00
489e95d677 fix: 修复显示模式切换按钮无效问题(添加currentItems变量) 2026-04-22 12:02:39 +08:00
47cbcb25bc feat: 添加显示模式切换按钮(单行/双行) 2026-04-22 11:56:59 +08:00
527c411d87 fix: 卡片布局自适应,操作按钮不再被挤出可视区域 2026-04-22 11:35:13 +08:00
c8aecaeb03 fix: 修复文件夹过滤时分页总数计算错误 2026-04-22 11:18:51 +08:00
bf63610510 fix: 点击类别同时展开文件夹并过滤数据 2026-04-22 10:52:30 +08:00
7af3a7f21d feat: 侧边栏文件夹UI优化 - 折叠式设计
- 新建文件夹按钮移到类别右侧(文件夹+图标)
- 默认折叠状态,点击类别名称展开/折叠
- 投影箭头指示展开状态(▶折叠,▼展开)
- 按钮hover时显示绿色背景,更明显可见
2026-04-22 10:44:53 +08:00
4783e9d88e feat: 文件夹功能
- 数据库添加 folders 表和 items.folder_id 字段
- API 新增文件夹 CRUD 接口和条目移动接口
- 侧边栏每个类别下显示文件夹列表
- 新建文件夹按钮和模态框
- 条目卡片添加「移动到文件夹」按钮
- 点击文件夹过滤显示该文件夹下的数据
2026-04-21 22:23:20 +08:00
2 changed files with 2379 additions and 111 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -131,6 +131,19 @@ class Database:
) )
""") """)
# 文件夹表
cursor.execute("""
CREATE TABLE IF NOT EXISTS folders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT NOT NULL,
parent_id INTEGER DEFAULT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (parent_id) REFERENCES folders(id) ON DELETE CASCADE
)
""")
# 创建索引 # 创建索引
cursor.execute("CREATE INDEX IF NOT EXISTS idx_items_type ON items(type)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_items_type ON items(type)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_items_status ON items(status)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_items_status ON items(status)")
@@ -163,6 +176,42 @@ class Database:
except sqlite3.OperationalError: except sqlite3.OperationalError:
cursor.execute("ALTER TABLE items ADD COLUMN deleted_at TEXT") cursor.execute("ALTER TABLE items ADD COLUMN deleted_at TEXT")
# 检查并添加 folder_id 字段(兼容旧数据库)
try:
cursor.execute("SELECT folder_id FROM items LIMIT 1")
except sqlite3.OperationalError:
cursor.execute("ALTER TABLE items ADD COLUMN folder_id INTEGER DEFAULT NULL")
# 创建 folder 相关索引
cursor.execute("CREATE INDEX IF NOT EXISTS idx_items_folder ON items(folder_id)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_folders_type ON folders(type)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_folders_parent ON folders(parent_id)")
# 检查并添加 is_pinned 字段(置顶功能,兼容旧数据库)
try:
cursor.execute("SELECT is_pinned FROM items LIMIT 1")
except sqlite3.OperationalError:
cursor.execute("ALTER TABLE items ADD COLUMN is_pinned INTEGER DEFAULT 0")
# 创建 is_pinned 索引
cursor.execute("CREATE INDEX IF NOT EXISTS idx_items_pinned ON items(is_pinned)")
# 待办事务表关联到文本类别的items
cursor.execute("""
CREATE TABLE IF NOT EXISTS todo_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
item_id INTEGER NOT NULL,
content TEXT NOT NULL,
remaining_days INTEGER DEFAULT 1,
is_completed INTEGER DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE
)
""")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_todo_events_item ON todo_events(item_id)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_todo_events_created ON todo_events(created_at)")
conn.commit() conn.commit()
# ============ Item 操作 ============ # ============ Item 操作 ============
@@ -170,7 +219,8 @@ class Database:
def create_item(self, type: str = "text", title: str = None, content: str = None, def create_item(self, type: str = "text", title: str = None, content: str = None,
url: str = None, source: str = None, status: str = "pending", url: str = None, source: str = None, status: str = "pending",
priority: str = "medium", due_date: str = None, note: str = None, priority: str = "medium", due_date: str = None, note: str = None,
tags: List[str] = None, is_starred: bool = False) -> int: tags: List[str] = None, is_starred: bool = False,
folder_id: int = None) -> int:
"""创建新条目""" """创建新条目"""
self._ensure_init() self._ensure_init()
now = datetime.now().isoformat() now = datetime.now().isoformat()
@@ -184,9 +234,9 @@ class Database:
with self.get_conn() as conn: with self.get_conn() as conn:
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(""" cursor.execute("""
INSERT INTO items (type, title, content, url, source, status, priority, due_date, note, is_starred, created_at, updated_at) INSERT INTO items (type, title, content, url, source, status, priority, due_date, note, is_starred, folder_id, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (type, title, content, url, source, status, priority, due_date, note, 1 if is_starred else 0, now, now)) """, (type, title, content, url, source, status, priority, due_date, note, 1 if is_starred else 0, folder_id, now, now))
item_id = cursor.lastrowid item_id = cursor.lastrowid
# 添加标签 # 添加标签
@@ -210,12 +260,14 @@ class Database:
return item return item
def list_items(self, type: str = None, status: str = None, tag: str = None, def list_items(self, type: str = None, status: str = None, tag: str = None,
keyword: str = None, starred: bool = None, sort_by: str = None, keyword: str = None, starred: bool = None, folder_id: int = None,
sort_order: str = None, limit: int = 50, offset: int = 0) -> List[Dict[str, Any]]: sort_by: str = None, sort_order: str = None,
limit: int = 50, offset: int = 0) -> List[Dict[str, Any]]:
"""列出条目 """列出条目
sort_by: created_at, updated_at sort_by: created_at, updated_at
sort_order: desc, asc sort_order: desc, asc
folder_id: 文件夹IDNone表示不限制-1表示未分类folder_id为null
""" """
with self.get_conn() as conn: with self.get_conn() as conn:
cursor = conn.cursor() cursor = conn.cursor()
@@ -245,6 +297,15 @@ class Database:
conditions.append("i.is_starred = ?") conditions.append("i.is_starred = ?")
params.append(1 if starred else 0) params.append(1 if starred else 0)
# 文件夹过滤
if folder_id is not None:
if folder_id == -1:
# -1 表示未分类folder_id 为 null
conditions.append("i.folder_id IS NULL")
else:
conditions.append("i.folder_id = ?")
params.append(folder_id)
if keyword: if keyword:
conditions.append("(i.title LIKE ? OR i.content LIKE ? OR i.note LIKE ?)") conditions.append("(i.title LIKE ? OR i.content LIKE ? OR i.note LIKE ?)")
keyword_pattern = f"%{keyword}%" keyword_pattern = f"%{keyword}%"
@@ -253,18 +314,16 @@ class Database:
if conditions: if conditions:
query += " WHERE " + " AND ".join(conditions) query += " WHERE " + " AND ".join(conditions)
# 排序逻辑 # 排序逻辑:置顶 > 关注 > 创建时间
if sort_by == 'updated_at': if sort_by == 'updated_at':
order_field = 'i.updated_at' order_field = 'i.updated_at'
elif sort_by == 'created_at': elif sort_by == 'created_at':
order_field = 'i.created_at' order_field = 'i.created_at'
else: else:
# 默认:重点关注优先 + 创建时间降序 # 默认:置顶优先 + 关注优先 + 创建时间降序
order_field = 'i.created_at' order_field = 'i.created_at'
order_dir = 'DESC' if (sort_order == 'asc' or sort_order is None) else 'ASC' # 确定排序方向
# 这里反转逻辑:用户选择"降序"时用DESC选择"升序"时用ASC
if sort_order == 'asc': if sort_order == 'asc':
order_dir = 'ASC' order_dir = 'ASC'
elif sort_order == 'desc': elif sort_order == 'desc':
@@ -272,12 +331,12 @@ class Database:
else: else:
order_dir = 'DESC' # 默认降序 order_dir = 'DESC' # 默认降序
# 如果有指定排序字段,按该字段排序;否则默认重点关注优先 # 如果有指定排序字段,按该字段排序,但置顶始终优先
if sort_by: if sort_by:
query += f" ORDER BY {order_field} {order_dir} LIMIT ? OFFSET ?" query += f" ORDER BY i.is_pinned DESC, {order_field} {order_dir} LIMIT ? OFFSET ?"
else: else:
# 默认:重点关注优先,然后创建时间降序 # 默认:置顶 > 关注 > 创建时间降序
query += f" ORDER BY i.is_starred DESC, i.created_at DESC LIMIT ? OFFSET ?" query += f" ORDER BY i.is_pinned DESC, i.is_starred DESC, i.created_at DESC LIMIT ? OFFSET ?"
params.extend([limit, offset]) params.extend([limit, offset])
cursor.execute(query, params) cursor.execute(query, params)
@@ -290,7 +349,7 @@ class Database:
return items return items
def count_items(self, type: str = None, status: str = None, tag: str = None, 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: with self.get_conn() as conn:
cursor = conn.cursor() cursor = conn.cursor()
@@ -322,6 +381,15 @@ class Database:
keyword_pattern = f"%{keyword}%" keyword_pattern = f"%{keyword}%"
params.extend([keyword_pattern, keyword_pattern, keyword_pattern]) 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: if conditions:
query += " WHERE " + " AND ".join(conditions) query += " WHERE " + " AND ".join(conditions)
@@ -330,7 +398,7 @@ class Database:
def update_item(self, item_id: int, **kwargs) -> bool: 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} update_fields = {k: v for k, v in kwargs.items() if k in allowed_fields}
# 只有 tags 变化也算有效更新 # 只有 tags 变化也算有效更新
@@ -456,6 +524,31 @@ class Database:
conn.commit() conn.commit()
return cursor.rowcount > 0 return cursor.rowcount > 0
def toggle_pin(self, item_id: int) -> bool:
"""切换置顶状态"""
with self.get_conn() as conn:
cursor = conn.cursor()
# 先获取当前状态
cursor.execute("SELECT is_pinned FROM items WHERE id = ?", (item_id,))
row = cursor.fetchone()
if not row:
return False
new_status = 0 if row['is_pinned'] else 1
now = datetime.now().isoformat()
cursor.execute("UPDATE items SET is_pinned = ?, updated_at = ? WHERE id = ?", (new_status, now, item_id))
conn.commit()
return True
def set_pin(self, item_id: int, pinned: bool = True) -> bool:
"""设置置顶状态"""
with self.get_conn() as conn:
cursor = conn.cursor()
now = datetime.now().isoformat()
cursor.execute("UPDATE items SET is_pinned = ?, updated_at = ? WHERE id = ?", (1 if pinned else 0, now, item_id))
conn.commit()
return cursor.rowcount > 0
def increment_views(self, item_id: int) -> bool: def increment_views(self, item_id: int) -> bool:
"""增加阅读数""" """增加阅读数"""
with self.get_conn() as conn: with self.get_conn() as conn:
@@ -464,6 +557,116 @@ class Database:
conn.commit() conn.commit()
return cursor.rowcount > 0 return cursor.rowcount > 0
def move_item_to_folder(self, item_id: int, folder_id: int) -> bool:
"""将条目移动到文件夹"""
with self.get_conn() as conn:
cursor = conn.cursor()
now = datetime.now().isoformat()
cursor.execute("UPDATE items SET folder_id = ?, updated_at = ? WHERE id = ?", (folder_id, now, item_id))
conn.commit()
return cursor.rowcount > 0
# ============ Folder 文件夹操作 ============
def create_folder(self, name: str, type: str, parent_id: int = None) -> int:
"""创建文件夹
Args:
name: 文件夹名称
type: 类别类型text/link/column/todo
parent_id: 父文件夹ID目前不支持嵌套预留
"""
self._ensure_init()
now = datetime.now().isoformat()
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO folders (name, type, parent_id, created_at, updated_at)
VALUES (?, ?, ?, ?, ?)
""", (name, type, parent_id, now, now))
folder_id = cursor.lastrowid
conn.commit()
return folder_id
def get_folder(self, folder_id: int) -> Optional[Dict[str, Any]]:
"""获取文件夹信息"""
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM folders WHERE id = ?", (folder_id,))
row = cursor.fetchone()
if not row:
return None
folder = dict(row)
# 获取文件夹内条目数量
cursor.execute("SELECT COUNT(*) FROM items WHERE folder_id = ? AND is_deleted = 0", (folder_id,))
folder['item_count'] = cursor.fetchone()[0]
return folder
def list_folders(self, type: str = None) -> List[Dict[str, Any]]:
"""列出文件夹
Args:
type: 按类型过滤None表示列出所有
"""
with self.get_conn() as conn:
cursor = conn.cursor()
if type:
cursor.execute("SELECT * FROM folders WHERE type = ? ORDER BY created_at DESC", (type,))
else:
cursor.execute("SELECT * FROM folders ORDER BY created_at DESC")
folders = []
for row in cursor.fetchall():
folder = dict(row)
# 获取文件夹内条目数量
cursor.execute("SELECT COUNT(*) FROM items WHERE folder_id = ? AND is_deleted = 0", (folder['id'],))
folder['item_count'] = cursor.fetchone()[0]
folders.append(folder)
return folders
def update_folder(self, folder_id: int, name: str = None) -> bool:
"""更新文件夹"""
with self.get_conn() as conn:
cursor = conn.cursor()
now = datetime.now().isoformat()
if name:
cursor.execute("UPDATE folders SET name = ?, updated_at = ? WHERE id = ?", (name, now, folder_id))
conn.commit()
return cursor.rowcount > 0
return False
def delete_folder(self, folder_id: int, move_items_to_root: bool = True) -> bool:
"""删除文件夹
Args:
folder_id: 文件夹ID
move_items_to_root: 是否将条目移出文件夹到未分类True则移动False则一起删除
"""
with self.get_conn() as conn:
cursor = conn.cursor()
if move_items_to_root:
# 将条目移出文件夹folder_id设为null
cursor.execute("UPDATE items SET folder_id = NULL WHERE folder_id = ?", (folder_id,))
else:
# 删除文件夹内的所有条目
cursor.execute("DELETE FROM items WHERE folder_id = ?", (folder_id,))
# 删除文件夹
cursor.execute("DELETE FROM folders WHERE id = ?", (folder_id,))
conn.commit()
return cursor.rowcount > 0
def count_items_by_folder(self, folder_id: int) -> int:
"""统计文件夹内的条目数量"""
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM items WHERE folder_id = ? AND is_deleted = 0", (folder_id,))
return cursor.fetchone()[0]
# ============ Draft 草稿操作 ============ # ============ Draft 草稿操作 ============
def save_draft(self, type: str = "text", title: str = None, content: str = None, def save_draft(self, type: str = "text", title: str = None, content: str = None,
@@ -748,15 +951,23 @@ class Database:
stats = {} stats = {}
# 总数 # 总数
cursor.execute("SELECT COUNT(*) as count FROM items") cursor.execute("SELECT COUNT(*) as count FROM items WHERE is_deleted = 0")
stats['total'] = cursor.fetchone()['count'] stats['total'] = cursor.fetchone()['count']
# 按类型统计 # 按类型统计
cursor.execute("SELECT type, COUNT(*) as count FROM items GROUP BY type") cursor.execute("SELECT type, COUNT(*) as count FROM items WHERE is_deleted = 0 GROUP BY type")
stats['by_type'] = {row['type']: row['count'] for row in cursor.fetchall()} stats['by_type'] = {row['type']: row['count'] for row in cursor.fetchall()}
# 未读数量统计views = 0
cursor.execute("SELECT COUNT(*) as count FROM items WHERE is_deleted = 0 AND (views IS NULL OR views = 0)")
stats['unread'] = cursor.fetchone()['count']
# 按类型统计未读数量
cursor.execute("SELECT type, COUNT(*) as count FROM items WHERE is_deleted = 0 AND (views IS NULL OR views = 0) GROUP BY type")
stats['unread_by_type'] = {row['type']: row['count'] for row in cursor.fetchall()}
# 待办状态统计 # 待办状态统计
cursor.execute("SELECT status, COUNT(*) as count FROM items WHERE type = 'todo' GROUP BY status") cursor.execute("SELECT status, COUNT(*) as count FROM items WHERE type = 'todo' AND is_deleted = 0 GROUP BY status")
stats['todo_status'] = {row['status']: row['count'] for row in cursor.fetchall()} stats['todo_status'] = {row['status']: row['count'] for row in cursor.fetchall()}
# 标签数 # 标签数
@@ -1011,6 +1222,93 @@ class Database:
# 删除 # 删除
self.delete_backup(backup['name']) self.delete_backup(backup['name'])
# ============ Todo Events 待办事务操作 ============
def create_todo_event(self, item_id: int, content: str, remaining_days: int = 1) -> int:
"""创建待办事务"""
self._ensure_init()
now = datetime.now().isoformat()
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO todo_events (item_id, content, remaining_days, is_completed, created_at, updated_at)
VALUES (?, ?, ?, 0, ?, ?)
""", (item_id, content, remaining_days, now, now))
conn.commit()
return cursor.lastrowid
def list_todo_events(self, item_id: int, limit: int = 10, offset: int = 0) -> List[Dict[str, Any]]:
"""列出待办事务(按时间倒序)"""
self._ensure_init()
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT * FROM todo_events
WHERE item_id = ?
ORDER BY created_at DESC
LIMIT ? OFFSET ?
""", (item_id, limit, offset))
rows = cursor.fetchall()
return [dict(row) for row in rows]
def count_todo_events(self, item_id: int) -> int:
"""计算待办事务总数"""
self._ensure_init()
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) as count FROM todo_events WHERE item_id = ?", (item_id,))
return cursor.fetchone()['count']
def get_todo_event(self, event_id: int) -> Optional[Dict[str, Any]]:
"""获取单个待办事务"""
self._ensure_init()
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM todo_events WHERE id = ?", (event_id,))
row = cursor.fetchone()
return dict(row) if row else None
def update_todo_event(self, event_id: int, content: str = None, remaining_days: int = None, is_completed: bool = None) -> bool:
"""更新待办事务"""
self._ensure_init()
now = datetime.now().isoformat()
with self.get_conn() as conn:
cursor = conn.cursor()
# 获取当前数据
cursor.execute("SELECT * FROM todo_events WHERE id = ?", (event_id,))
row = cursor.fetchone()
if not row:
return False
# 更新字段
new_content = content if content is not None else row['content']
new_days = remaining_days if remaining_days is not None else row['remaining_days']
new_completed = 1 if is_completed is True else (0 if is_completed is False else row['is_completed'])
cursor.execute("""
UPDATE todo_events
SET content = ?, remaining_days = ?, is_completed = ?, updated_at = ?
WHERE id = ?
""", (new_content, new_days, new_completed, now, event_id))
conn.commit()
return True
def delete_todo_event(self, event_id: int) -> bool:
"""删除待办事务"""
self._ensure_init()
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM todo_events WHERE id = ?", (event_id,))
conn.commit()
return cursor.rowcount > 0
# 全局数据库实例 # 全局数据库实例
db = Database() db = Database()