吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 221|回复: 5
上一主题 下一主题
收起左侧

[原创工具] 最近玩偷菜做的倒计时闹铃小工具

[复制链接]
跳转到指定楼层
楼主
abouttutu 发表于 2026-4-17 14:22 回帖奖励
任务条可以拖拽,看得到进度,
可以透明
https://wwbvp.lanzouu.com/iXP483ngisnc
密码:buon




代码如下:

import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import threading
import time
import json
import os
import sys
import winsound

# --- 解决打包后资源路径问题 ---
def resource_path(relative_path):
    """获取打包后资源的绝对路径"""
    base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, relative_path)

# --- 配置文件路径 (隐藏到系统AppData目录) ---
APP_DATA_DIR = os.path.join(os.environ['APPDATA'], "涂涂牌倒计时闹钟")
os.makedirs(APP_DATA_DIR, exist_ok=True)

CONFIG_FILE = os.path.join(APP_DATA_DIR, "countdown_tasks.json")
WINDOW_CONFIG_FILE = os.path.join(APP_DATA_DIR, "window_config.json")
ALARM_SOUND = resource_path("alarm.wav") # 闹钟声音文件路径

class CountdownTask:
    def __init__(self, name, total_seconds, remind_before=0):
        self.name = name
        self.total_seconds = total_seconds
        self.remaining = total_seconds
        self.remind_before = remind_before
        self.is_running = False
        self.is_finished = False
        self.reminded = False
        self.is_alarming = False

    def to_dict(self):
        return {
            "name": self.name,
            "total_seconds": self.total_seconds,
            "remaining": self.remaining,
            "remind_before": self.remind_before,
            "is_finished": self.is_finished
        }

    @staticmethod
    def from_dict(data):
        task = CountdownTask(data["name"], data["total_seconds"], data.get("remind_before", 0))
        task.remaining = data["remaining"]
        task.is_finished = data["is_finished"]
        return task

class App:
    def __init__(self, root):
        self.root = root
        self.root.title("涂涂牌倒计时闹钟")
        
        self.load_window_config()
        self.root.minsize(380, 300)
        
        self.tasks = []
        self.task_widgets = []
        self.breathing_state = 0
        self.breathing_colors = ["#ff3333", "#ff9999"]
        self.dragging_index = None
        
        self.load_data()
        self.setup_ui()
        
        self.running = True
        self.update_thread = threading.Thread(target=self.tick_loop, daemon=True)
        self.update_thread.start()
        
        self.breathing_loop()
        self.update_clock()
        
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

    def setup_ui(self):
        frame_top = tk.Frame(self.root, pady=8, bg="#f0f0f0")
        frame_top.pack(fill='x', side='top')
        
        frame_top.grid_columnconfigure(2, weight=1)
        
        btn_group = tk.Frame(frame_top, bg="#f0f0f0")
        btn_group.grid(row=0, column=0, sticky='w', padx=10)
        
        btn_add = tk.Button(btn_group, text="➕ 新建", command=self.add_task_dialog,
                           bg="#4CAF50", fg="white", font=("Arial", 9, "bold"))
        btn_add.pack(side='left', padx=2)
        
        btn_pause_all = tk.Button(btn_group, text="⏸️ 全停", command=self.pause_all)
        btn_pause_all.pack(side='left', padx=2)
        
        btn_reset_all = tk.Button(btn_group, text="🔄 全重置", command=self.reset_all)
        btn_reset_all.pack(side='left', padx=2)
        
        btn_stop_all_alarm = tk.Button(btn_group, text="🔇 全静音", bg="red", fg="white",
                                      command=self.stop_all_alarm)
        btn_stop_all_alarm.pack(side='left', padx=2)

        setting_group = tk.Frame(frame_top, bg="#f0f0f0")
        setting_group.grid(row=0, column=3, sticky='e', padx=10)
        
        tk.Label(setting_group, text="透:", bg="#f0f0f0", font=("Arial", 9)).pack(side='left')
        self.alpha_slider = tk.Scale(setting_group, from_=20, to=100, orient='horizontal',
                                      length=60, showvalue=0, command=self.change_alpha)
        self.alpha_slider.set(100)
        self.alpha_slider.pack(side='left', padx=3)
        
        self.clock_label = tk.Label(setting_group, text="00:00:00",
                                   font=("Arial", 12, "bold"), fg="#2196F3", bg="#f0f0f0")
        self.clock_label.pack(side='left', padx=8)

        self.canvas = tk.Canvas(self.root, bg="white")
        self.scrollbar = ttk.Scrollbar(self.root, orient="vertical", command=self.canvas.yview)
        self.scrollable_frame = ttk.Frame(self.canvas)

        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))
        )

        self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw", tags="frame")
        self.canvas.configure(yscrollcommand=self.scrollbar.set)
        
        self.canvas.bind("<Configure>", self.on_canvas_configure)
        self.canvas.bind_all("<MouseWheel>", self.on_mousewheel)

        self.canvas.pack(side="left", fill="both", expand=True)
        self.scrollbar.pack(side="right", fill="y")
        
        self.refresh_task_list()

    def on_mousewheel(self, event):
        self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")

    def on_canvas_configure(self, event):
        self.canvas.itemconfig("frame", width=event.width)

    def change_alpha(self, value):
        alpha_val = int(value) / 100.0
        self.root.attributes("-alpha", alpha_val)

    def update_clock(self):
        current_time = time.strftime("%H:%M:%S")
        self.clock_label.config(text=current_time)
        self.root.after(1000, self.update_clock)

    def add_task_dialog(self):
        win = tk.Toplevel(self.root)
        win.title("新建任务")
        win.geometry("350x300")
        win.resizable(False, False)
        win.transient(self.root)
        
        tk.Label(win, text="任务名称:", font=("Arial", 10)).pack(pady=5)
        name_entry = tk.Entry(win, font=("Arial", 12), width=25)
        name_entry.pack()
        name_entry.insert(0, "新任务")
        name_entry.focus_set()
        
        time_frame = tk.Frame(win)
        time_frame.pack(pady=10)
        
        tk.Label(time_frame, text="时:").grid(row=0, column=0)
        hour_entry = tk.Entry(time_frame, width=5)
        hour_entry.grid(row=0, column=1, padx=5)
        hour_entry.insert(0, "0")
        
        tk.Label(time_frame, text="分:").grid(row=0, column=2)
        min_entry = tk.Entry(time_frame, width=5)
        min_entry.grid(row=0, column=3, padx=5)
        min_entry.insert(0, "5")
        
        tk.Label(time_frame, text="秒:").grid(row=0, column=4)
        sec_entry = tk.Entry(time_frame, width=5)
        sec_entry.grid(row=0, column=5, padx=5)
        sec_entry.insert(0, "0")
        
        tk.Label(win, text="提前多少秒提醒:", font=("Arial", 10)).pack(pady=5)
        remind_entry = tk.Entry(win, font=("Arial", 12), width=10)
        remind_entry.pack()
        remind_entry.insert(0, "10")
        
        def confirm(event=None):
            try:
                name = name_entry.get()
                h = int(hour_entry.get() or 0)
                m = int(min_entry.get() or 0)
                s = int(sec_entry.get() or 0)
                remind_s = int(remind_entry.get() or 0)
               
                total_secs = h * 3600 + m * 60 + s
               
                if total_secs <= 0:
                    messagebox.showerror("错误", "时长必须大于0")
                    return
                if not name: name = "未命名任务"
               
                task = CountdownTask(name, total_secs, remind_s)
                self.tasks.append(task)
                self.save_data()
                self.refresh_task_list()
                win.destroy()
            except ValueError:
                messagebox.showerror("错误", "请输入数字")
        
        win.bind("<Return>", confirm)
        
        tk.Button(win, text="确定", command=confirm, bg="blue", fg="white", height=2, width=15).pack(pady=15)

    def refresh_task_list(self):
        for widget in self.scrollable_frame.winfo_children():
            widget.destroy()
        self.task_widgets = []
        
        if not self.tasks:
            empty_label = tk.Label(self.scrollable_frame, text="暂无任务\n点击左上角「新建」添加倒计时",
                                  font=("Arial", 14), fg="#999999", pady=50)
            empty_label.pack()
            return
        
        for i, task in enumerate(self.tasks):
            frame = tk.Frame(self.scrollable_frame, bd=2, relief=tk.RIDGE, pady=8)
            frame.pack(fill='x', pady=4, padx=8)
            
            frame.grid_columnconfigure(2, weight=1)
            
            drag_handle = tk.Label(frame, text="&#9776;", cursor="hand2",
                                  bg="#d0d0d0", fg="#666666",
                                  font=("Arial", 14, "bold"),
                                  padx=3, pady=2)
            drag_handle.grid(row=0, column=0, sticky='ns', padx=3)
            drag_handle.bind("<ButtonPress-1>", lambda e, idx=i: self.start_drag(e, idx))
            drag_handle.bind("<B1-Motion>", self.do_drag)
            drag_handle.bind("<ButtonRelease-1>", self.end_drag)
            
            def show_tooltip(event):
                tooltip = tk.Toplevel(frame)
                tooltip.wm_overrideredirect(True)
                tooltip.geometry(f"+{event.x_root+10}+{event.y_root+10}")
                tk.Label(tooltip, text="按住拖动排序", bg="#ffffe0", padx=5, pady=2).pack()
                tooltip.after(1000, tooltip.destroy)
            drag_handle.bind("<Enter>", show_tooltip)
            
            lbl_name = tk.Label(frame, text=task.name, font=("Arial", 11, "bold"), fg="blue", cursor="hand2")
            lbl_name.grid(row=0, column=1, sticky='w', padx=5)
            
            def edit_name(event, t=task, lbl=lbl_name):
                new_name = simpledialog.askstring("修改", "输入新名称:", initialvalue=t.name)
                if new_name:
                    t.name = new_name
                    lbl.config(text=new_name)
                    self.save_data()
            lbl_name.bind("<Button-1>", edit_name)
            
            lbl_time = tk.Label(frame, text="00:00:00", font=("Arial", 16, "bold"))
            lbl_time.grid(row=0, column=2, padx=8)
            
            progress_bg = tk.Frame(frame, bg="#e0e0e0", height=8)
            progress_bg.grid(row=1, column=1, columnspan=2, sticky='ew', padx=8, pady=4)
            progress_fg = tk.Frame(progress_bg, bg="#4CAF50", width=0)
            progress_fg.place(x=0, y=0, relheight=1)
            
            btn_frame = tk.Frame(frame)
            btn_frame.grid(row=0, column=3, rowspan=2, padx=8)
            
            self.task_widgets.append({
                'task': task,
                'label': lbl_time,
                'progress_bg': progress_bg,
                'progress_fg': progress_fg,
                'frame': frame
            })
            
            if task.is_finished:
                frame.config(bg="#e8f5e9")
                mins, secs = divmod(task.total_seconds, 60)
                hours, mins = divmod(mins, 60)
                total_time_str = f"{hours:02d}:{mins:02d}:{secs:02d}"
                lbl_time.config(text=total_time_str, fg="green")
               
                if task.is_alarming:
                    btn_stop_alarm = tk.Button(btn_frame, text="&#128263;", bg="red", fg="white", width=2,
                                               command=lambda t=task: self.stop_alarm(t))
                    btn_stop_alarm.pack(side='left', padx=1)
               
                btn_repeat = tk.Button(btn_frame, text="&#128257;", width=2, command=lambda t=task: self.reset_task(t))
                btn_repeat.pack(side='left', padx=1)
               
                btn_del = tk.Button(btn_frame, text="&#128465;&#65039;", fg="red", width=2, command=lambda t=task: self.delete_task(t))
                btn_del.pack(side='left', padx=1)
            else:
                if task.is_running:
                    frame.config(bd=3, relief=tk.SOLID, highlightbackground="#2196F3", highlightthickness=2)
               
                if task.is_alarming:
                    frame.config(bg="#ffcdd2")
                    btn_stop_alarm = tk.Button(btn_frame, text="&#128263;", bg="red", fg="white", width=2,
                                               command=lambda t=task: self.stop_alarm(t))
                    btn_stop_alarm.pack(side='left', padx=1)
               
                btn_text = "&#9208;&#65039;" if task.is_running else "&#9654;&#65039;"
                btn_start = tk.Button(btn_frame, text=btn_text, width=2,
                                     command=lambda t=task: self.toggle_task(t))
                btn_start.pack(side='left', padx=1)
               
                btn_reset = tk.Button(btn_frame, text="&#128260;", width=2, command=lambda t=task: self.reset_task(t))
                btn_reset.pack(side='left', padx=1)
               
                btn_del = tk.Button(btn_frame, text="&#128465;&#65039;", fg="red", width=2, command=lambda t=task: self.delete_task(t))
                btn_del.pack(side='left', padx=1)

    def start_drag(self, event, index):
        self.dragging_index = index
        self.task_widgets[index]['frame'].config(bg="#ffffcc")

    def do_drag(self, event):
        if self.dragging_index is None:
            return
        
        y = event.y_root
        for i, widget in enumerate(self.task_widgets):
            if i == self.dragging_index:
                continue
            frame = widget['frame']
            if frame.winfo_rooty() < y < frame.winfo_rooty() + frame.winfo_height():
                self.tasks.insert(i, self.tasks.pop(self.dragging_index))
                self.dragging_index = i
                self.refresh_task_list()
                break

    def end_drag(self, event):
        if self.dragging_index is not None:
            self.save_data()
            self.dragging_index = None

    def toggle_task(self, task):
        task.is_running = not task.is_running
        self.refresh_task_list()

    def reset_task(self, task):
        task.remaining = task.total_seconds
        task.is_finished = False
        task.is_running = False
        task.reminded = False
        self.stop_alarm(task)
        self.save_data()
        self.refresh_task_list()

    def delete_task(self, task):
        if messagebox.askyesno("确认删除", f"确定要删除任务「{task.name}」吗?"):
            self.stop_alarm(task)
            self.tasks.remove(task)
            self.save_data()
            self.refresh_task_list()

    def stop_alarm(self, task):
        task.is_alarming = False
        # 立即停止所有声音播放
        winsound.PlaySound(None, winsound.SND_ASYNC)
        for widget in self.task_widgets:
            if widget['task'] == task:
                widget['progress_fg'].config(bg="#4CAF50")
                break
        self.refresh_task_list()

    def pause_all(self):
        for task in self.tasks:
            if task.is_running:
                task.is_running = False
        self.refresh_task_list()

    def reset_all(self):
        if messagebox.askyesno("确认", "确定要重置所有任务吗?"):
            for task in self.tasks:
                task.remaining = task.total_seconds
                task.is_finished = False
                task.is_running = False
                task.reminded = False
                task.is_alarming = False
            # 停止所有声音
            winsound.PlaySound(None, winsound.SND_ASYNC)
            self.save_data()
            self.refresh_task_list()

    def stop_all_alarm(self):
        for task in self.tasks:
            task.is_alarming = False
        # 立即停止所有声音
        winsound.PlaySound(None, winsound.SND_ASYNC)
        for widget in self.task_widgets:
            widget['progress_fg'].config(bg="#4CAF50")
        self.refresh_task_list()

    def tick_loop(self):
        while self.running:
            time.sleep(0.1)
            self.root.after(0, self.update_ui)

    def update_ui(self):
        for widget in self.task_widgets:
            task = widget['task']
            lbl = widget['label']
            progress_fg = widget['progress_fg']
            progress_bg = widget['progress_bg']
            
            if task.is_running and not task.is_finished:
                task.remaining -= 0.1
               
                if not task.reminded and 0 < task.remaining <= task.remind_before:
                    task.reminded = True
                    if not task.is_alarming:
                        task.is_alarming = True
                        self.start_alarm_thread(task)
               
                if task.remaining <= 0:
                    task.remaining = 0
                    task.is_running = False
                    task.is_finished = True
                    if not task.is_alarming:
                        task.is_alarming = True
                        self.start_alarm_thread(task)
                    self.save_data()
                    self.refresh_task_list()
                    self.canvas.yview_moveto(0)
            
            mins, secs = divmod(int(task.remaining), 60)
            hours, mins = divmod(mins, 60)
            time_str = f"{hours:02d}:{mins:02d}:{secs:02d}"
            
            if not task.is_finished:
                lbl.config(text=time_str)
                if task.total_seconds > 0:
                    progress = (task.total_seconds - task.remaining) / task.total_seconds
                    progress_fg.config(width=int(progress_bg.winfo_width() * progress))

    def breathing_loop(self):
        self.breathing_state = 1 - self.breathing_state
        current_color = self.breathing_colors[self.breathing_state]
        
        for widget in self.task_widgets:
            if widget['task'].is_alarming:
                widget['progress_fg'].config(bg=current_color)
        
        self.root.after(300, self.breathing_loop)

    def start_alarm_thread(self, task):
        def alarm_sound_loop():
            # 使用 SND_LOOP 标志实现无缝循环播放
            try:
                winsound.PlaySound(ALARM_SOUND, winsound.SND_FILENAME | winsound.SND_ASYNC | winsound.SND_LOOP)
            except:
                # 如果WAV文件播放失败,回退到蜂鸣器循环
                while task.is_alarming:
                    try:
                        winsound.Beep(1200, 300)
                        time.sleep(0.1)
                        winsound.Beep(1000, 300)
                        time.sleep(0.3)
                    except:
                        break
        
        threading.Thread(target=alarm_sound_loop, daemon=True).start()
        self.refresh_task_list()

    def save_data(self):
        data = [t.to_dict() for t in self.tasks]
        with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
            json.dump(data, f)

    def load_data(self):
        if os.path.exists(CONFIG_FILE):
            try:
                with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                    self.tasks = [CountdownTask.from_dict(d) for d in data]
            except:
                pass

    def save_window_config(self):
        config = {
            "geometry": self.root.geometry(),
            "alpha": self.alpha_slider.get()
        }
        with open(WINDOW_CONFIG_FILE, 'w', encoding='utf-8') as f:
            json.dump(config, f)

    def load_window_config(self):
        if os.path.exists(WINDOW_CONFIG_FILE):
            try:
                with open(WINDOW_CONFIG_FILE, 'r', encoding='utf-8') as f:
                    config = json.load(f)
                    self.root.geometry(config.get("geometry", "600x700"))
            except:
                self.root.geometry("600x700")
        else:
            self.root.geometry("600x700")

    def on_closing(self):
        # 关闭软件时停止所有声音
        winsound.PlaySound(None, winsound.SND_ASYNC)
        self.save_window_config()
        self.root.destroy()

if __name__ == "__main__":
    root = tk.Tk()
    app = App(root)
    root.mainloop()







免费评分

参与人数 2吾爱币 +7 热心值 +2 收起 理由
confiant + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
风之暇想 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
HuaGdao1 发表于 2026-4-19 19:09
你说的偷菜跟我理解的是同一个吗
3#
kevinw 发表于 2026-4-20 11:19
呵呵,感觉又回到二十年前全民偷菜的时候。我现在播种了随缘,想起来就玩一下。如果定闹钟那估计又该晚上睡不着觉了
4#
法国巴黎和 发表于 2026-4-20 14:49
5#
rockshuai 发表于 2026-4-20 15:38
这个跟我用手机设闹钟有啥区别
6#
aero 发表于 2026-4-20 15:45
虽然用不到,也要点赞,感谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-4-20 15:49

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表