Python制作排班小工具【三】
  MnuOoTXPgNLZ 2023年12月06日 15 0

一、背景

Python制作排班小工具【二】中,我们实现了GUI界面元素的展示。本文将介绍按钮对应的事件及文本框的展示。以下是界面事件和按钮流程图的详细描述:

1.打开程序时显示上一次的输入记录。如果没有记录,输入框为空。

2.姓名输入以顿号“、”隔开。

3.姓名排序和序号相对应,即第一个姓名序号为1。

4.每日值班人数和值班组数量不能为空且不能为非整数。

5.排班表展示框将展示生成的排班表。

6.开启【删除人员配置开关】后,方可点击【删除人员配置】按钮。

7.【查看记录】标签页将展示所有生成的排班表。

主要按钮的流程图如下:

Python制作排班小工具【三】_值班排班

二、完整代码

import datetime
import os.path
import re
import time
from tkinter.font import Font
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from ttkbootstrap.dialogs import Messagebox
from tkinter import DISABLED, NORMAL
from Scheduling import yaml_read, scheduling, yaml_write, yaml_clear


class GUI:

    def __init__(self, master):
        """排排班GUI"""
        self.root = master
        self.name = ttk.StringVar()  # 生成StringVar对象,以保存输入框中的内容--参与排班人员姓名
        self.each_group_num = ttk.StringVar()  # 每日值班人数
        self.group_num = ttk.StringVar()  # 值班组数量
        self.variable = ttk.BooleanVar()  # 删除人员配置开关设置--布尔值
        self.create_page()  # 显示页面元素
        self.update_time()  # 更新时间
        self.show_last_input_record()  # 展示最近一次输入记录
        self.tab_idx = ttk.IntVar()  # tab标签页的下标
        self.bind_notebook_event()  # 绑定tab标签页切换事件

    def create_page(self):
        """创建页面元素"""
        # # ------------------------------------标签页控件------------------------------------#
        # 创建标签页控件
        self.tabcontrol = ttk.Notebook(self.root)
        # 【生成排班】标签
        tab1 = ttk.Frame(self.tabcontrol)
        self.tabcontrol.add(tab1, text='生成排班')
        # 【查看记录】标签
        tab2 = ttk.Frame(self.tabcontrol)
        self.tabcontrol.add(tab2, text="查看记录")
        # 打包标签控件
        self.tabcontrol.pack(expand=1, fill=BOTH)
        # # ------------------------------------标签页控件------------------------------------#

        # # ------------------------------------生成排班------------------------------------#
        # 参与排班人员姓名标签
        name_label = ttk.Label(tab1, text="参与排班人员姓名:", bootstyle=PRIMARY)
        name_label.grid(row=0, column=0, pady=5)  # 使用grid布局(网格布局方式)
        # 参与排班人员姓名文本框
        self.name_input = ttk.Text(tab1, width=23, height=2, wrap=WORD)
        self.name_input.grid(row=0, column=1, columnspan=3, pady=5)

        # 每日值班人数标签
        each_group_num_label = ttk.Label(tab1, text="每日值班人数:", bootstyle=PRIMARY)
        each_group_num_label.grid(row=1, column=0, pady=5, sticky=E)
        # 每日值班人数输入框
        each_group_num_input = ttk.Entry(tab1, textvariable=self.each_group_num, width=5)
        each_group_num_input.grid(row=1, column=1, pady=5)

        # 值班组数量标签
        group_num_label = ttk.Label(tab1, text="值班组数量:", bootstyle=PRIMARY)
        group_num_label.grid(row=1, column=2, pady=5, padx=5, sticky=E)
        # 值班组数量输入框
        group_num_input = ttk.Entry(tab1, textvariable=self.group_num, width=5)
        group_num_input.grid(row=1, column=3, pady=5)

        # 生成排班按钮
        self.generate_button = ttk.Button(tab1, text="生成排班", bootstyle=OUTLINE, command=self.generate)
        self.generate_button.grid(row=2, column=0, pady=5)
        # 删除人员配置开关设置按钮
        switch_button = ttk.Checkbutton(tab1, variable=self.variable, offvalue=False, onvalue=True,
                                        command=self.delete_switch, bootstyle="round-toggle")
        switch_button.grid(row=2, column=1, pady=5)
        # 删除人员配置按钮
        self.del_button = ttk.Button(tab1, text="删除人员配置", bootstyle="danger-outline", command=self.delete,
                                     state=DISABLED)  # 默认不可点击
        self.del_button.grid(row=2, column=2, columnspan=2, pady=5)

        # 生成的排班表展示文本框
        self.show_text = ttk.Text(tab1, wrap=WORD, width=39, height=10, state=DISABLED)  # 默认text文本框不可编辑
        self.show_text.grid(row=3, column=0, columnspan=4)
        self.show_text_style(self.show_text)  # 设置展示样式
        # # ------------------------------------生成排班------------------------------------#

        # # ------------------------------------查看记录------------------------------------#
        # 生成的排班记录展示文本框
        self.show_record_text = ttk.Text(tab2, wrap=WORD, width=39, height=16, state=DISABLED)
        self.show_record_text.pack(expand=1, fill=BOTH)
        self.show_text_style(self.show_record_text)  # 设置展示样式
        # # ------------------------------------查看记录------------------------------------#

        # # ------------------------------------当前时间------------------------------------#
        # 当前时间标签
        self.current_time_label = ttk.Label(self.root, bootstyle=PRIMARY)
        self.current_time_label.pack()
        # # ------------------------------------当前时间------------------------------------#

    def generate(self):
        """
        点击【生成排班】按钮时对应的事件
        """
        # 获取UI界面输入的内容
        people_str = self.name_input.get(1.0, END)  # 获取输入的参与排班人员列表
        each_group_num_str = self.each_group_num.get()  # 获取输入的每日值班人数
        group_num_str = self.group_num.get()  # 获取输入的值班组数量
        people_list = [i for i in (re.split(r"、|\n", people_str)) if i != '']  # 将输入的姓名字符串分割为列表
        people_num = len(people_list)  # 获取参与排班人员数量
        # 有输入记录
        if os.path.exists("last_input_record.yaml"):
            input_record = yaml_read("last_input_record.yaml")  # 读取输入记录Yaml文件
            # 获取参与排班人员列表记录
            old_people_str = input_record['people']
            old_people_list = [i for i in (re.split(r"、|\n", old_people_str)) if i != '']
            old_people_num = len(old_people_list)
            if people_num != old_people_num:  # 若修改了参与排班人员的数量
                Messagebox.show_info(title="没整对!", message='参与排班人员发生变化,请【删除人员配置】后再重新排班!')
            else:  # 未修改参与排班人员的数量
                # 输入异常时提示,无异常时执行
                self.execute(people_str, people_list, people_num, each_group_num_str, group_num_str)
        # 无输入记录
        else:
            # 输入异常时提示,无异常时执行
            self.execute(people_str, people_list, people_num, each_group_num_str, group_num_str)

    def delete_switch(self):
        """
        点击【删除人员配置开关设置】按钮时对应的事件
        """
        switch = self.variable.get()  # 获取开关值
        if switch is True:  # 开关打开时,【删除人员配置】按钮设置为可点击
            self.del_button.config(state=NORMAL)
        else:  # 开关关闭时,【删除人员配置】按钮设置为不可点击
            self.del_button.config(state=DISABLED)

    def delete(self):
        """
        点击【删除人员配置】按钮时对应的事件
        """
        msg = Messagebox.okcancel("删除人员配置后将不再根据历史记录生成排班!")
        if msg == '确定':  # 点击确定后执行
            if os.path.exists('random_list.yaml'):  # 若文件存在则删除并提示
                os.remove('random_list.yaml')  # 删除文件
                Messagebox.show_info("删除成功!")  # 提示
                self.variable.set(False)  # 【删除人员配置开关】置为关闭
                self.del_button.config(state=DISABLED)  # 【删除人员配置】按钮设置为不可点击
                self.root.focus()  # 移除焦点
            else:  # 若文件不存在则提示
                Messagebox.show_info("文件不存在,请勿重复操作!")

    def update_time(self):
        """
        更新当前时间和本年第几周
        """
        current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        week_num = int(time.strftime("%W")) + 1
        self.current_time_label.config(text=f"{current_time}  第{week_num}周")
        self.current_time_label.after(1000, self.update_time)

    def execute(self, people_str, people_list, people_num, each_group_num_str, group_num_str):
        """
        输入异常时提示,无异常时执行
        """
        # 输入异常时提示,无异常时执行
        if self.exception_input(people_str, people_num, each_group_num_str, group_num_str):
            # 生成排班序号列表并格式化处理
            head, body = self.format_content(people_list, people_num, each_group_num_str, group_num_str)
            # 保存排班记录并更新输入记录
            self.save_and_update_record(head, body, people_str, each_group_num_str, group_num_str)
            # 将排班记录插入到展示框中
            self.show_scheduling(head, body)

    def exception_input(self, people_str, people_num, each_group_num_str, group_num_str):
        """
        输入异常时弹窗提醒,无异常时返回True
        """
        # 输入异常
        if (people_str == '\n') or not self.is_separated_by_pause(people_str):  # 未输入参与排班人员姓名或不是以”、“分隔
            Messagebox.show_info(title="没整对!", message='请输入参与排班人员姓名,以"、"隔开!')
        elif each_group_num_str == '' or group_num_str == '':  # 未输入每日值班人数或值班组数量
            Messagebox.show_info(title="没整对!", message='请输入每日值班人数和值班组数量!')
        elif not each_group_num_str.isdigit() or not group_num_str.isdigit():  # 输入的每日值班人数或值班组数量不是整数
            Messagebox.show_info(title="没整对!", message='每日值班人数和值班组数量请输入整数!')
        elif each_group_num_str.isdigit() and people_num < int(each_group_num_str):  # 输入的参与排班人员数量小于每日值班人数
            Messagebox.show_info(title="没整对!", message='参与排班人员数量不能小于每日值班人数')
        elif group_num_str.isdigit() and int(group_num_str) > 7:  # 输入的值班组数量大于7
            Messagebox.show_info(title="没整对!", message='值班组数量不能大于7!')
        # 输入无异常
        else:
            return True

    @staticmethod
    def is_separated_by_pause(target):
        """
        判断字符串是否以顿号“、”分隔
        """
        return target.find('、') != -1 and target.find('、') != 0 and target.rfind('、') != len(target) - 1

    def format_content(self, people_list, people_num, each_group_num_str, group_num_str):
        """
        调用scheduling方法生成排班序号列表并格式化处理展示内容
        :return: head--时间和本年第几周;body--星期几和值班人员姓名
        """
        each_group_num, group_num = int(each_group_num_str), int(group_num_str)  # 将每日值班人数和值班组数量转换为整型
        scheduling_list = scheduling(people_num, each_group_num, group_num)  # 调用scheduling方法生成排班序号列表
        cur_time, week_num, today_week_num = self.get_time_and_week()  # 获取当前时间、周数和星期
        week_list = [i for i in self.get_week_list(today_week_num - 1, group_num)]  # 以今天为起点生成连续的星期列表
        watchkeepers = ["、".join([people_list[j - 1] for j in i]) for i in scheduling_list]  # 拼接值班人员姓名列表
        head = f"{cur_time} 【第{week_num}周】"  # 拼接时间和本年第几周
        body = "\n".join([f"{i}:  {j}" for i, j in zip(week_list, watchkeepers)])  # 将星期列表和值班人员姓名列表组合成字符串
        return head, body

    @staticmethod
    def get_time_and_week():
        """
        获取当前时间、周数和星期
        :return: cur_time--当前时间;week_num--本年周数;today_week_num--今天星期数
        """
        # 当前时间
        cur_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        # 本年的第几周
        week_num = int(time.strftime("%W")) + 1
        # 今天的日期
        today = datetime.date.today()
        # 今天的星期数(周一:1,周日:7)
        today_week_num = datetime.date.isoweekday(today)
        return cur_time, week_num, today_week_num

    @staticmethod
    def get_week_list(start_idx, days):
        """
        星期循环迭代生成器
        根据起始下标生成目标天数个星期
        :param start_idx: 起始下标
        :param days: 目标天数
        :return:
        """
        week_list = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日']
        for i in range(days):
            idx = (start_idx + i) % 7  # (起始下标+第几个)%星期列表长度,取余得下标
            yield week_list[idx]  # 迭代生成器:记住上次返回的位置,下次迭代就从这个位置之后开始

    @staticmethod
    def save_and_update_record(head, body, people_str, each_group_num_str, group_num_str):
        """
        保存排班记录并更新输入记录
        """
        # 保存排班记录
        yaml_write('scheduling_record.yaml', {head: body})
        # 更新输入记录
        if os.path.exists('last_input_record.yaml'):  # 若已经存在则先清空
            yaml_clear('last_input_record.yaml')
        data = {'people': people_str, 'each_group_num': each_group_num_str, 'group_num': group_num_str}
        yaml_write('last_input_record.yaml', data)

    @staticmethod
    def show_text_style(show_test):
        """
        设置展示框的样式
        """
        bold_font = Font(weight="bold")  # 定义加粗字体
        show_test.tag_add('bold_center', 1.0, '1.end')  # 添加名称为align_center的标签
        show_test.tag_config('bold_center', font=bold_font, justify=CENTER)  # 将bold_center标签设置为加粗并居中对齐
        show_test.tag_add('center', 2.0, END)  # 添加名称为center的标签
        show_test.tag_config('center', justify=CENTER)  # 将center标签设置为居中对齐

    def show_scheduling(self, head, body):
        """
        展示生成的排班表
        """
        self.show_text.config(state=NORMAL)  # 设置文本框可编辑
        self.show_text.delete(1.0, END)  # 清空文本框
        self.show_text.insert(END, head, 'bold_center')  # 将时间和本年第几周插入bold_center标签
        self.show_text.insert(END, '\n', 'center')  # 插入换行符
        self.show_text.insert(END, body, 'center')  # 将排班表插入center标签
        self.show_text.config(state=DISABLED)  #
        self.generate_button.config(text='已生成排班', state=DISABLED)  # 修改“生成排班”按钮的文字并设置为不可编辑
        self.root.focus()  # 移除焦点

    def show_last_input_record(self):
        """
        输入框显示之前的输入记录
        :return:
        """
        # 打开程序时显示上一次的输入记录,若无则输入框为空
        if os.path.exists("last_input_record.yaml"):
            input_record = yaml_read("last_input_record.yaml")  # 读取输入记录Yaml文件
            # 显示输入记录
            self.name_input.insert(END, input_record['people'])
            self.each_group_num.set(input_record['each_group_num'])
            self.group_num.set(input_record['group_num'])

    def bind_notebook_event(self):
        """
        绑定tab标签页切换事件
        """
        self.tabcontrol.bind("<<NotebookTabChanged>>",
                             lambda event: self.show_scheduling_record(self.tabcontrol, self.tab_idx,
                                                                       self.show_record_text))

    @staticmethod
    def show_scheduling_record(tabcontrol, tab_idx, textbox):
        """
        展示排班记录
        """
        idx = tabcontrol.index(tabcontrol.select())  # 获取当前tab标签页的下标
        tab_idx.set(idx)  # 将当前tab标签页的下标赋值给tab_idx
        # 切换到“查看记录”标签页时读取排班记录文件
        if tab_idx.get() == 1:
            # 排班记录文件存在则展示
            if os.path.exists('scheduling_record.yaml'):
                scheduling_record = yaml_read('scheduling_record.yaml')
                textbox.config(state=NORMAL)  # 设置文本框可编辑
                textbox.delete(1.0, END)  # 清空文本框
                for i, k in enumerate(scheduling_record):
                    textbox.insert(END, k, 'bold_center')  # 将时间和本年第几周插入bold_center标签
                    textbox.insert(END, '\n', 'center')  # 插入换行符
                    textbox.insert(END, scheduling_record[k], 'center')  # 将排班表插入center标签
                    textbox.insert(END, '\n', 'center')  # 插入换行符
                textbox.see(END)  # 文本框内容超出显示范围时自动滚动到底部
                textbox.config(state=DISABLED)  # 设置文本框不可编辑
            # 排班记录文件不存在时,展示提示
            else:
                textbox.config(state=NORMAL)  # 设置文本框可编辑
                textbox.delete(1.0, END)  # 清空文本框
                textbox.insert(END, "暂无排班记录", 'bold_center')  # 将默认提示语插入bold_center标签
                textbox.config(state=DISABLED)  # 设置文本框不可编辑


if __name__ == "__main__":
    root = ttk.Window(
        title="排排班",  # 窗口标题
        themename="solar"  # 主题
    )
    root.place_window_center()  # # 设置窗口居中(左上角坐标在屏幕中间?)
    root.iconbitmap('icon.ico')  # 更改GUI图标
    root.resizable(width=False, height=False)  # 设置窗口大小不可变
    GUI(root)  # 展示GUI界面
    root.mainloop()  # 进入消息循环

运行结果:

Python制作排班小工具【三】_Python_02

三、结语

注意:

 1.根据当前的星期为起点生成指定个值班组,应用场景为每周第一个工作日生成一次

2.需要导入在Python制作排班小工具【一】中编写好的Scheduling.py文件

3.对输入框为空、未按要求等异常输入情况添加了提示弹窗

4.使用添加标签tag_add以及tag_config的方式,优化了文本框Text的文本内容展示;同时将文本框设置为不可编辑的状态,避免误修改文本框内容

5.切换到【查看记录】标签页,绑定事件时不能返回方法的结果,故使用到了lambda函数

问题:

1.重新打开软件再次点击【生成排班】按钮会直接生成排班记录,从而产生脏数据

2.输入的“参与排班人员姓名”只能使用“、”分隔,方式比较局限

3.实现主要功能正常使用,UI展示不美观

4.未做不同操作系统、屏幕尺寸等的兼容

5.其他未知的问题,需要根据使用情况解决和优化



【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年12月06日 0

暂无评论

推荐阅读
MnuOoTXPgNLZ
最新推荐 更多