diff --git a/README.md b/README.md index a3e66b4..580267d 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,127 @@ pyinstaller --onefile --windowed --name TaskManager app.py ``` +当前python环境的conda listL +``` +# Name Version Build Channel +altgraph 0.17.5 pypi_0 pypi +anaconda-anon-usage 0.7.4 pyhb46e38b_100 +anaconda-auth 0.10.0 py313haa95532_1 +anaconda-cli-base 0.6.0 py313haa95532_0 +anaconda_powershell_prompt 1.1.0 haa95532_1 +anaconda_prompt 1.1.0 haa95532_1 +annotated-types 0.6.0 py313haa95532_1 +archspec 0.2.5 pyhd3eb1b0_0 +babel 2.17.0 pypi_0 pypi +boltons 25.0.0 py313haa95532_0 +brotlicffi 1.0.9.2 py313h885b0b7_2 +bzip2 1.0.8 h2bbff1b_6 +ca-certificates 2025.11.4 haa95532_0 +certifi 2025.10.5 py313haa95532_0 +cffi 2.0.0 py313h02ab6af_1 +charset-normalizer 3.4.4 py313haa95532_0 +click 8.1.8 py313haa95532_0 +colorama 0.4.6 py313haa95532_0 +conda 25.9.1 py313haa95532_0 +conda-anaconda-telemetry 0.3.0 pyhd3eb1b0_1 +conda-anaconda-tos 0.2.2 py313haa95532_1 +conda-content-trust 0.2.0 py313haa95532_1 +conda-libmamba-solver 25.4.0 pyhdf14ebd_1 +conda-package-handling 2.4.0 py313haa95532_1 +conda-package-streaming 0.12.0 py313haa95532_1 +cpp-expected 1.1.0 h214f63a_0 +cryptography 46.0.3 py313habbc9f9_0 +customtkinter 5.2.2 pypi_0 pypi +darkdetect 0.8.0 pypi_0 pypi +distro 1.9.0 py313haa95532_0 +expat 2.7.3 h9214b88_0 +fmt 11.2.0 h58b7f6e_0 +frozendict 2.4.6 py313h02ab6af_0 +idna 3.11 py313haa95532_0 +jaraco.classes 3.4.0 py313haa95532_0 +jaraco.context 6.0.0 py313haa95532_0 +jaraco.functools 4.1.0 py313haa95532_0 +jsonpatch 1.33 py313haa95532_1 +jsonpointer 3.0.0 py313haa95532_0 +keyring 25.6.0 py313haa95532_0 +libarchive 3.8.2 h6c023e8_0 +libcurl 8.16.0 h97e0424_0 +libffi 3.4.4 hd77b12b_1 +libiconv 1.16 h2bbff1b_3 +libmamba 2.3.2 h7d9f7df_0 +libmambapy 2.3.2 py313h5078c03_0 +libmpdec 4.0.0 h827c3e9_0 +libsolv 0.7.30 h23a355e_2 +libssh2 1.11.1 h2addb87_0 +libxml2 2.13.9 h6201b9f_0 +libzlib 1.3.1 h02ab6af_0 +lz4-c 1.9.4 h2bbff1b_1 +markdown-it-py 4.0.0 py313haa95532_0 +mdurl 0.1.2 py313haa95532_0 +menuinst 2.4.1 py313h885b0b7_1 +more-itertools 10.8.0 py313haa95532_0 +nlohmann_json 3.11.2 h6c2663c_0 +openssl 3.0.18 h543e019_0 +packaging 25.0 py313haa95532_1 +pcre2 10.46 h5740b90_0 +pefile 2024.8.26 pypi_0 pypi +pillow 12.0.0 pypi_0 pypi +pip 25.2 pyhc872135_1 +pkce 1.0.3 py313haa95532_0 +platformdirs 4.5.0 py313haa95532_0 +pluggy 1.5.0 py313haa95532_0 +pybind11-abi 5 hd3eb1b0_0 +pycosat 0.6.6 py313h827c3e9_2 +pycparser 2.23 py313haa95532_0 +pydantic 2.12.3 py313haa95532_1 +pydantic-core 2.41.4 py313h114bc41_0 +pydantic-settings 2.10.1 py313haa95532_0 +pygments 2.19.2 py313haa95532_0 +pyinstaller 6.17.0 pypi_0 pypi +pyinstaller-hooks-contrib 2025.10 pypi_0 pypi +pyjwt 2.10.1 py313haa95532_0 +pysocks 1.7.1 py313haa95532_1 +python 3.13.9 h260b955_100_cp313 +python-dotenv 1.1.0 py313haa95532_0 +python_abi 3.13 1_cp313 +pywin32-ctypes 0.2.2 py313haa95532_0 +readchar 4.2.1 py313haa95532_0 +reproc 14.2.4 hd77b12b_2 +reproc-cpp 14.2.4 hd77b12b_2 +requests 2.32.5 py313haa95532_1 +rich 14.2.0 py313haa95532_0 +ruamel.yaml 0.18.16 py313hb9a58be_0 +ruamel.yaml.clib 0.2.14 py313hb9a58be_0 +semver 3.0.4 py313haa95532_0 +setuptools 80.9.0 py313haa95532_0 +shellingham 1.5.4 py313haa95532_0 +simdjson 3.10.1 h214f63a_0 +sqlite 3.51.0 hda9a48d_0 +tk 8.6.15 hf199647_0 +tkcalendar 1.6.1 pypi_0 pypi +tksheet 7.5.19 pypi_0 pypi +tomli 2.2.1 py313haa95532_0 +tqdm 4.67.1 py313h4442805_1 +truststore 0.10.1 py313haa95532_1 +typer 0.17.4 py313haa95532_0 +typing-extensions 4.15.0 py313haa95532_0 +typing-inspection 0.4.2 py313haa95532_0 +typing_extensions 4.15.0 py313haa95532_0 +tzdata 2025b h04d1e81_0 +ucrt 10.0.22621.0 haa95532_0 +urllib3 2.5.0 py313haa95532_0 +vc 14.3 h2df5915_10 +vc14_runtime 14.44.35208 h4927774_10 +vs2015_runtime 14.44.35208 ha6b5a95_10 +wheel 0.45.1 py313haa95532_0 +win_inet_pton 1.1.0 py313haa95532_1 +xz 5.6.4 h4754444_1 +yaml-cpp 0.8.0 hd77b12b_1 +zlib 1.3.1 h02ab6af_0 +zstandard 0.24.0 py313he335c29_0 +zstd 1.5.7 h56299aa_0 +``` + **主要功能:** 1. 添加任务(可设置 类型、优先级、状态、链接、备注等信息) 2. 可筛选、排序,并且可以保存为模板快速应用 diff --git a/app.py b/app.py index c22f911..76aad61 100644 --- a/app.py +++ b/app.py @@ -1,5 +1,6 @@ import os import json +import math import sqlite3 import webbrowser from datetime import datetime, date, timezone @@ -12,7 +13,7 @@ from tkcalendar import Calendar # ----------------------- CONFIG ----------------------- # region 配置 -VERSION = "v1.0.0" +VERSION = "v1.0.1" DB_PATH = "tasks.db" # 数据库文件路径 TEMPLATES_PATH = "templates.json" # 检索模板文件路径 @@ -91,6 +92,7 @@ COLUMNS = [ ("type", "类型"), ("status", "状态"), ("priority", "优先级"), + ("deadline", "截止日期"), ("composite", "综合优先级"), ("title", "标题"), ("brief", "简介"), @@ -115,6 +117,19 @@ COL_START_DATE = COL_INDEX.get("start_date") COL_LINKS_COUNT = COL_INDEX.get("links_count") COL_NOTES = COL_INDEX.get("notes") COL_COMPOSITE = COL_INDEX.get("composite") +COL_DEADLINE = COL_INDEX.get("deadline") + +# 截止日期颜色,距离截止日期的时间小于哪个值就是哪个颜色 +DEADLINE_COLOR = [ + (0, "#fb0000"), + (1, "#ff6f22"), + (2, "#ff904b"), + (3, "#b2ff77"), + (4, "#73bc75"), + (10, "#83d6ff"), + (15, "#bdd8e9"), + (30, "#ffffff"), +] # endregion @@ -141,6 +156,7 @@ class TaskDB: type TEXT, status TEXT, start_date TEXT, + deadline TEXT, links TEXT, notes TEXT, updated_at TEXT @@ -178,6 +194,8 @@ class TaskDB: adds.append(("status", "TEXT")) if "start_date" not in cols: adds.append(("start_date", "TEXT")) + if "deadline" not in cols: + adds.append(("deadline", "TEXT")) if "links" not in cols: adds.append(("links", "TEXT")) if "notes" not in cols: @@ -224,8 +242,8 @@ class TaskDB: now = datetime.now(timezone.utc).isoformat() cur = self.conn.cursor() cur.execute(""" - INSERT INTO tasks (title, brief, description, priority, type, status, start_date, links, notes, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + INSERT INTO tasks (title, brief, description, priority, type, status, start_date, deadline, links, notes, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( task.get("title", ""), task.get("brief", ""), @@ -234,6 +252,7 @@ class TaskDB: task.get("type"), task.get("status"), task.get("start_date"), + task.get("deadline"), json.dumps(task.get("links", []), ensure_ascii=False), task.get("notes", ""), now @@ -244,7 +263,7 @@ class TaskDB: def update_task(self, tid: int, task: dict): now = datetime.now(timezone.utc).isoformat() self.conn.execute(""" - UPDATE tasks SET title=?, brief=?, description=?, priority=?, type=?, status=?, start_date=?, links=?, notes=?, updated_at=? + UPDATE tasks SET title=?, brief=?, description=?, priority=?, type=?, status=?, start_date=?, deadline=?, links=?, notes=?, updated_at=? WHERE sid=? """, ( task.get("title", ""), @@ -254,6 +273,7 @@ class TaskDB: task.get("type"), task.get("status"), task.get("start_date"), + task.get("deadline"), json.dumps(task.get("links", []), ensure_ascii=False), task.get("notes", ""), now, @@ -363,24 +383,37 @@ def compute_composite_score(task_row: dict) -> float: Compute composite priority score using COMPOSITE_WEIGHTS. Higher number => more urgent. """ - # 读取数据 + # 任务开始以来的天数 start_date = task_row.get("start_date") - days = 0 + days_from_start = 0 try: if start_date: dt = datetime.fromisoformat(start_date) # convert to local naive days - days = (datetime.now(dt.tzinfo or timezone.utc).date() - dt.date()).days - if days < 0: - days = 0 + days_from_start = (datetime.now(dt.tzinfo or timezone.utc).date() - dt.date()).days + if days_from_start < 0: + days_from_start = 0 + except Exception: + days_from_start = 0 + # 距离截止日期的时间 + deadline = task_row.get("deadline") + have_deadline = False + days_to_deadline = 0 + try: + if deadline: + dt_deadline = datetime.fromisoformat(deadline) + days_to_deadline = (dt_deadline.date() - datetime.now(dt_deadline.tzinfo or timezone.utc).date()).days + have_deadline = True except Exception: - days = 0 - d_score = COMPOSITE_WEIGHTS.get("age_factor", 0.1) * days + 1 + have_deadline = False + # 各种因素分数 + ddl_score = 1 + math.exp(-(0.3 + days_to_deadline / 3.0)) if have_deadline else 0.8 + d_score = COMPOSITE_WEIGHTS.get("age_factor", 0.1) * days_from_start + 1 p_score = COMPOSITE_WEIGHTS["priority"].get(task_row.get("priority"), 1.0) t_score = COMPOSITE_WEIGHTS["type"].get(task_row.get("type"), 1.0) s_score = COMPOSITE_WEIGHTS["status"].get(task_row.get("status"), 1.0) # 计算综合优先级分数 - score = p_score * t_score * s_score * d_score + score = p_score * t_score * s_score * d_score * ddl_score return round(float(score), 6) @@ -401,6 +434,7 @@ class TaskEditor(ctk.CTkToplevel): self.title_var = ctk.StringVar() self.brief_var = ctk.StringVar() self.start_var = ctk.StringVar(value=date.today().isoformat()) + self.deadline_var = ctk.StringVar(value="") self.priority_var = ctk.StringVar() self.type_var = ctk.StringVar() self.status_var = ctk.StringVar() @@ -446,33 +480,37 @@ class TaskEditor(ctk.CTkToplevel): ctk.CTkLabel(frm, text="Start Date (YYYY-MM-DD)").grid(row=8, column=0, columnspan=3, sticky="w", padx=pad, pady=(10,0)) ctk.CTkEntry(frm, textvariable=self.start_var).grid(row=9, column=0, columnspan=3, sticky="ew", padx=pad) + # deadline + ctk.CTkLabel(frm, text="Deadline (YYYY-MM-DD)").grid(row=10, column=0, columnspan=3, sticky="w", padx=pad, pady=(10,0)) + ctk.CTkEntry(frm, textvariable=self.deadline_var).grid(row=11, column=0, columnspan=3, sticky="ew", padx=pad) + # links - ctk.CTkLabel(frm, text="Links (one per line)").grid(row=10, column=0, columnspan=3, sticky="w", padx=pad, pady=(10,0)) + ctk.CTkLabel(frm, text="Links (one per line)").grid(row=12, column=0, columnspan=3, sticky="w", padx=pad, pady=(10,0)) self.links_text = ctk.CTkTextbox(frm, height=80) - self.links_text.grid(row=11, column=0, columnspan=3, sticky="nsew", padx=pad) + self.links_text.grid(row=13, column=0, columnspan=3, sticky="nsew", padx=pad) # logs (processed dates) - ctk.CTkLabel(frm, text="Processed Dates (handling records)").grid(row=12, column=0, columnspan=3, sticky="w", padx=pad, pady=(10,0)) + ctk.CTkLabel(frm, text="Processed Dates (handling records)").grid(row=14, column=0, columnspan=3, sticky="w", padx=pad, pady=(10,0)) logs_frame = ctk.CTkFrame(frm) - logs_frame.grid(row=13, column=0, columnspan=3, sticky="ew", padx=pad, pady=(4,0)) + logs_frame.grid(row=15, column=0, columnspan=3, sticky="ew", padx=pad, pady=(4,0)) ctk.CTkButton(logs_frame, text="Mark Today", width=120, command=self._mark_today).pack(side="left", padx=6, pady=6) ctk.CTkButton(logs_frame, text="Add Date...", width=120, command=self._add_date_dialog).pack(side="left", padx=6, pady=6) ctk.CTkButton(logs_frame, text="Remove Selected", fg_color="#cc4444", hover_color="#aa3333", command=self._remove_selected_date).pack(side="right", padx=6, pady=6) # list widget for logs (tk Listbox embedded) self.logs_listbox = tk.Listbox(frm, height=5) - self.logs_listbox.grid(row=14, column=0, columnspan=3, sticky="nsew", padx=pad, pady=(4,0)) + self.logs_listbox.grid(row=16, column=0, columnspan=3, sticky="nsew", padx=pad, pady=(4,0)) # buttons btn_row = ctk.CTkFrame(frm) - btn_row.grid(row=15, column=0, columnspan=3, pady=(12,6)) + btn_row.grid(row=17, column=0, columnspan=3, pady=(12,6)) ctk.CTkButton(btn_row, text="Save", command=self._save).pack(side="left", padx=8) ctk.CTkButton(btn_row, text="Cancel", fg_color="#888", hover_color="#666", command=self.destroy).pack(side="left", padx=8) # grid weights frm.grid_rowconfigure(5, weight=1) - frm.grid_rowconfigure(11, weight=0) - frm.grid_rowconfigure(14, weight=0) + frm.grid_rowconfigure(13, weight=0) + frm.grid_rowconfigure(16, weight=0) frm.grid_columnconfigure(0, weight=1) frm.grid_columnconfigure(1, weight=1) frm.grid_columnconfigure(2, weight=1) @@ -489,6 +527,7 @@ class TaskEditor(ctk.CTkToplevel): self.type_cb.set(row.get("type") or "") self.status_cb.set(row.get("status") or "") self.start_var.set(row.get("start_date") or "") + self.deadline_var.set(row.get("deadline") or "") links = json.loads(row.get("links") or "[]") self.links_text.delete("1.0", "end"); self.links_text.insert("1.0", "\n".join(links)) # logs @@ -556,6 +595,7 @@ class TaskEditor(ctk.CTkToplevel): "type": self.type_cb.get() or None, "status": self.status_cb.get() or None, "start_date": self.start_var.get().strip() or date.today().isoformat(), + "deadline": self.deadline_var.get().strip() or "", "links": links, "processed_dates": getattr(self, "_logs", []) } @@ -575,6 +615,7 @@ class TaskEditor(ctk.CTkToplevel): "type": data["type"], "status": data["status"], "start_date": data["start_date"], + "deadline": data["deadline"], "links": data["links"], "notes": data["notes"] }) @@ -595,6 +636,7 @@ class TaskEditor(ctk.CTkToplevel): "type": data["type"], "status": data["status"], "start_date": data["start_date"], + "deadline": data["deadline"], "links": data["links"], "notes": data["notes"], }) @@ -725,7 +767,7 @@ class TaskManagerApp(ctk.CTk): # 指定列居中 align_center_cols = [] for i, (key, _) in enumerate(COLUMNS): - if key in ('sid', 'priority', 'type', 'status', 'start_date', 'processed_today', 'last_processed', 'links_count', 'composite'): + if key in ('sid', 'priority', 'type', 'status', 'start_date', 'processed_today', 'last_processed', 'links_count', 'composite', 'deadline'): align_center_cols.append(i) self.sheet.align_columns(columns=align_center_cols, align="center") @@ -758,6 +800,20 @@ class TaskManagerApp(ctk.CTk): elif col == "processed_today": today_iso = date.today().isoformat() rows.sort(key=lambda r: (today_iso in self.db.get_logs_for_task(r["sid"])), reverse=not self.order_asc) + elif col == "deadline": + # Treat empty/null deadlines as greater than any date so they appear last when sorting ascending + def _deadline_key(r): + d = r.get("deadline") + if not d: + # missing deadline -> mark as 'empty' (1) and None for tie + return (1, None) + try: + dt = datetime.fromisoformat(d) + return (0, dt) + except Exception: + # fallback to string compare (ISO-like strings sort correctly) + return (0, d) + rows.sort(key=_deadline_key, reverse=not self.order_asc) else: rows.sort(key=lambda r: r.get(col) or "", reverse=not self.order_asc) @@ -801,6 +857,8 @@ class TaskManagerApp(ctk.CTk): row.append(r.get("last_processed") or "") elif key == "start_date": row.append(r.get("start_date") or "") + elif key == "deadline": + row.append(r.get("deadline") or "") elif key == "links_count": row.append(str(links_count)) elif key == "notes": @@ -857,6 +915,7 @@ class TaskManagerApp(ctk.CTk): for r_idx, tid in enumerate(self.displayed_sids): task = self.db.get_task(tid) pr = task.get("priority"); ty = task.get("type"); st = task.get("status") + ddl = task.get("deadline") # check if processed today using processed_map processed = processed_map.get(tid, False) if pr and pr in PRIORITY_DISPLAY: @@ -877,6 +936,27 @@ class TaskManagerApp(ctk.CTk): except Exception: try: self.sheet.set_cell_bg(r_idx,COL_STATUS,bg) except Exception: pass + if ddl: + try: + dt_deadline = datetime.fromisoformat(ddl) + days_to_deadline = (dt_deadline.date() - datetime.now(dt_deadline.tzinfo or timezone.utc).date()).days + for (d, c) in DEADLINE_COLOR: + if days_to_deadline <= d: + bg = c + try: self.sheet.highlight_cells(row=r_idx, column=COL_DEADLINE, bg=bg) + except Exception: + try: self.sheet.set_cell_bg(r_idx,COL_DEADLINE,bg) + except Exception: pass + break + except Exception: + pass + else: + # 取最后一个颜色 + bg = DEADLINE_COLOR[-1][1] + try: self.sheet.highlight_cells(row=r_idx, column=COL_DEADLINE, bg=bg) + except Exception: + try: self.sheet.set_cell_bg(r_idx,COL_DEADLINE,bg) + except Exception: pass # processed today column: use COL_PROCESSED_TODAY constant if processed: bg = PROCESSED_DISPLAY["yes"]["bg"]