[Python] 纯文本查看 复制代码
import tkinter as tk
from tkinter import ttk,Listbox, Button, font, filedialog, messagebox
import pandas as pd
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
import unicodedata
import re
import configparser
import os
file_path=None
refresh_time=None
remaining_days=None
def zengze(value_str):
number = re.search(r'\d+(\.\d+)?', str(value_str))
if number:#如果找到了匹配项,提取它的值
extracted_number = float(number.group())
return extracted_number
else:
return value_str
def pad_to_width(text, width):
text_width = 0
for char in text:
char_width = unicodedata.east_asian_width(char)
if char_width in ('F', 'W'):
text_width += 2 #全角和宽字符占用两个字符宽度
else:
text_width += 1 #其他字符占用一个字符宽度
padding_needed = width - text_width
if padding_needed > 0:
return text + ' ' * padding_needed
else:
return text #如果不需要填充,则直接返回原字符串
def read_excel_file():
global file_path
x=int(entry_1.get())
file_path = filedialog.askopenfilename(filetypes=[("Excel files", "*.xlsx;*.xls")])#打开文件对话框以选择Excel文件
if file_path:
try:#使用pandas读取Excel文件
read_excel_to_dict(file_path,remaining_days)
xieru(file_path,x)
except Exception as e:print(f"Error reading Excel file: {e}")#如果发生错误,打印错误信息
def auto_excel_file(file_path):
x=int(entry_1.get())
if file_path is not None:
read_excel_to_dict(file_path,x)
xieru(file_path,x)
else:print(f'file_path:{file_path}')
def read_excel_to_dict(file_path, x):
today = datetime.today().date()#获取当前日期
df = pd.read_excel(file_path, engine='openpyxl')#使用pandas读取Excel文件
dict_list = []#初始化一个空列表来存储每行的数据字典
below_dict_list = []#初始化一个空列表来存储满足条件的数据字典
for index, row in df.iterrows():#遍历DataFrame的每一行
credential_name = row['证件名称']
credential_name = pad_to_width(credential_name,20)
valid_period_years_1 = row['证件有效期']#证件有效期以年为单位
valid_period_years = zengze(valid_period_years_1)
valid_period_years_1 = pad_to_width(f" {valid_period_years_1}", 11)
issue_Date = row['发证日期']
date_str = "{:08d}".format(issue_Date)
year = int(date_str[:4])
month = int(date_str[4:6])
day = int(date_str[6:])
issue_Date = datetime(year, month, day).date()
expiry_Date_1 = issue_Date + relativedelta(years=valid_period_years)
youxiaoqi_1 = (expiry_Date_1 - today).days
days_remaining = pad_to_width(f' {(expiry_Date_1 - today).days}天', 15)
expiry_Date_1 = pad_to_width(f" {expiry_Date_1.strftime('%Y-%m-%d')}", 11)
issue_Date_1 = pad_to_width(issue_Date.strftime('%Y-%m-%d'), 11)
Personnel_Qualification_Date = row['人员资质日期']
date_str_1 = "{:08d}".format(Personnel_Qualification_Date)
year_1 = int(date_str_1[:4])
month_1 = int(date_str_1[4:6])
day_1 = int(date_str_1[6:])
Personnel_Qualification_Date = datetime(year_1, month_1, day_1).date()
Validity_period_of_personnel_qualifications_1 = row['人员资质有效期']
Validity_period_of_personnel_qualifications = zengze(Validity_period_of_personnel_qualifications_1)
Expiration_date_of_personnel_qualification = Personnel_Qualification_Date + relativedelta(
years=Validity_period_of_personnel_qualifications)
youxiaoqi_2 = (Expiration_date_of_personnel_qualification - today).days
days_remaining_1 = (Expiration_date_of_personnel_qualification - today).days
Personnel_Qualification_Date_1 = pad_to_width(Personnel_Qualification_Date.strftime('%Y-%m-%d'), 13)
Validity_period_of_personnel_qualifications_1 = pad_to_width(f" {Validity_period_of_personnel_qualifications_1}",11)
Expiration_date_of_personnel_qualification_1 = pad_to_width(
f" {Expiration_date_of_personnel_qualification}", 14)
days_remaining_1_1 = pad_to_width(f' {days_remaining_1}天', 13)
Test_report_date = row['检测报告日期']
date_str_2 = "{:08d}".format(Test_report_date)
year_2 = int(date_str_2[:4])
month_2 = int(date_str_2[4:6])
day_2 = int(date_str_2[6:])
Test_report_date = datetime(year_2, month_2, day_2).date()
Validity_period_of_testing_report_1 = row['检测报告有效期']
Validity_period_of_testing_report = zengze(Validity_period_of_testing_report_1)
Expiration_date_of_testing_report = Test_report_date + relativedelta(years=Validity_period_of_testing_report)
youxiaoqi_3 = (Expiration_date_of_testing_report - today).days
days_remaining_2 = (Expiration_date_of_testing_report - today).days
Test_report_date_1 = pad_to_width(Test_report_date.strftime('%Y-%m-%d'), 10)
Validity_period_of_testing_report_2 = pad_to_width(f" {Validity_period_of_personnel_qualifications_1}", 13)
Expiration_date_of_testing_report_1 = pad_to_width(f"{Expiration_date_of_testing_report.strftime('%Y-%m-%d')}",13)
days_remaining_2_1 = pad_to_width(f' {days_remaining_2}天', 13)
#判断有效期是否小于指定值
if youxiaoqi_1 < x or youxiaoqi_2 < x or youxiaoqi_3 < x:
#将满足条件的数据添加到新的字典中
below_dict = {
'证件名称': credential_name,
'发证日期': issue_Date_1,
'有效期_1': youxiaoqi_1,
'证件有效期': valid_period_years_1,
'证件到期日期': expiry_Date_1,
'证件到期天数': days_remaining,
'人员资质日期': Personnel_Qualification_Date_1,
'有效期_2': youxiaoqi_2,
'人员资质有效期': Validity_period_of_personnel_qualifications_1,
'人员资质到期日期': Expiration_date_of_personnel_qualification_1,
'人员资质到期天数': days_remaining_1_1,
'检测报告日期': Test_report_date_1,
'有效期_3': youxiaoqi_3,
'检测报告有效期': Validity_period_of_testing_report_2,
'检测报告到期日期': Expiration_date_of_testing_report_1,
'检测报告到期天数': days_remaining_2_1
}
below_dict_list.append(below_dict)
else:
#将不满足条件的数据添加到原有的字典列表中
credentials_dict = {
'证件名称': credential_name,
'发证日期': issue_Date_1,
'有效期_1': youxiaoqi_1,
'证件有效期': valid_period_years_1,
'证件到期日期': expiry_Date_1,
'证件到期天数': days_remaining,
'人员资质日期': Personnel_Qualification_Date_1,
'有效期_2': youxiaoqi_2,
'人员资质有效期': Validity_period_of_personnel_qualifications_1,
'人员资质到期日期': Expiration_date_of_personnel_qualification_1,
'人员资质到期天数': days_remaining_1_1,
'检测报告日期': Test_report_date_1,
'有效期_3': youxiaoqi_3,
'检测报告有效期': Validity_period_of_testing_report_2,
'检测报告到期日期': Expiration_date_of_testing_report_1,
'检测报告到期天数': days_remaining_2_1
}
dict_list.append(credentials_dict)
return below_dict_list, dict_list
def xieru_biaoti():
upper_listbox.insert(tk.END, f"{pad_to_width('证件名称',20)}{pad_to_width('发证日期', 12)}{pad_to_width('证件有效期', 12)}{pad_to_width('证件到期日期', 14)}{pad_to_width('证件到期天数', 13)}{pad_to_width('人员资质日期', 14)}{pad_to_width('资质有效期', 12)}{pad_to_width('资质到期日期', 14)}{pad_to_width('资质到期天数', 14)}{pad_to_width('检测报告日期', 14)}{pad_to_width('报告有效期', 11)}{pad_to_width('报告到期日期', 14)}报告到期天数")
below_listbox.insert(tk.END, f"{pad_to_width('证件名称',20)}{pad_to_width('发证日期', 12)}{pad_to_width('证件有效期', 12)}{pad_to_width('证件到期日期', 14)}{pad_to_width('证件到期天数', 13)}{pad_to_width('人员资质日期', 14)}{pad_to_width('资质有效期', 12)}{pad_to_width('资质到期日期', 14)}{pad_to_width('资质到期天数', 14)}{pad_to_width('检测报告日期', 14)}{pad_to_width('报告有效期', 11)}{pad_to_width('报告到期日期', 14)}报告到期天数")
def xieru(file_path,x):
cert_name = "已到期证件:\n"#初始化cert_name为空字符串
count = 0 # 初始化计数器
max_per_line = 5 # 每行最多显示的证件名称数量
below_dict_list, dict_list = read_excel_to_dict(file_path, x)
upper_listbox.delete(0, 'end')
below_listbox.delete(0, 'end')
xieru_biaoti()
for item in dict_list:#在列表框中显示不满足条件的字典数据
upper_listbox.insert(tk.END, f"{item['证件名称']}\n{item['发证日期']}\n{item['证件有效期']}\n{item['证件到期日期']}\n{item['证件到期天数']}\n{item['人员资质日期']}\n{item['人员资质有效期']}\n{item['人员资质到期日期']}\n{item['人员资质到期天数']}\n{item['检测报告日期']}\n{item['检测报告有效期']}\n{item['检测报告到期日期']}\n{item['检测报告到期天数']}")
# messagebox.showinfo("到期提示",below_dict_list['证件名称'])
for dict_item in below_dict_list:
if '证件名称' in dict_item:
cert_name = cert_name+pad_to_width(dict_item['证件名称'] ,20)
count+=1
if count % max_per_line == 0:# 如果当前行已达到最大数量,则添加换行符,并重置计数器
cert_name += "\n"
count = 0
#在列表框中显示满足条件的字典数据
for item in below_dict_list:
below_listbox.insert(tk.END, f"{item['证件名称']}\n{item['发证日期']}\n{item['证件有效期']}\n{item['证件到期日期']}\n{item['证件到期天数']}\n{item['人员资质日期']}\n{item['人员资质有效期']}\n{item['人员资质到期日期']}\n{item['人员资质到期天数']}\n{item['检测报告日期']}\n{item['检测报告有效期']}\n{item['检测报告到期日期']}\n{item['检测报告到期天数']}")
messagebox.showinfo("到期提示", cert_name)
def read_ini_file(section, option):
global file_path, refresh_time, remaining_days
ini_file_path = 'settings.ini'
config = configparser.ConfigParser()
if os.path.exists(ini_file_path):
config.read(ini_file_path)
if section in config and option in config[section]:
value = config[section][option]
#根据选项类型进行转换
if option == 'refresh_time':
try:
value = int(value)
except ValueError:
print(f"读取的刷新时间不是有效的整数: {value}")
return
elif option == 'remaining_days':#新添加的剩余天数读取功能
try:
value = int(value)
except ValueError:
print(f"读取的剩余天数不是有效的整数: {value}")
return
#根据选项更新全局变量
if option == 'file_path':
file_path = value
print(f"从settings.ini中读取到文件路径: {file_path}")
elif option == 'refresh_time':
refresh_time = value
print(f"从settings.ini中读取到刷新时间: {refresh_time}秒")
elif option == 'remaining_days': #更新剩余天数的全局变量
remaining_days = value
print(f"从settings.ini中读取到剩余天数: {remaining_days}天")
else:
print(f"从settings.ini中读取到{option}的值: {value}")
else:
print(f"settings.ini中没有找到[{section}]下的{option}")
else:
print("没有找到settings.ini文件")
def on_checkbox_change(*args):
global file_path
is_checked = checkbox_var.get()
if is_checked:
if not file_path:
messagebox.showerror("错误", "请先读取一个Excel文件")
checkbox_var.set(0)
else:
config = configparser.ConfigParser()
config.read('settings.ini')
config['FILE_SETTINGS'] = {'file_path': file_path}#使用[FILE_SETTINGS]部分
with open('settings.ini', 'w') as configfile:config.write(configfile)
print("文件路径已保存到settings.ini中")
checkbox.config(text="是否记住文件路径:是\n(下次启动程序时将自动读取文件)")
else:
config = configparser.ConfigParser()
config.read('settings.ini')
if 'FILE_SETTINGS' in config and 'file_path' in config['FILE_SETTINGS']:
del config['FILE_SETTINGS']['file_path'] #只删除 [FILE_SETTINGS]下的file_path项
with open('settings.ini', 'w') as configfile:config.write(configfile)
print("文件路径已从settings.ini中删除")
checkbox.config(text="是否记住文件路径:否\n(下次启动程序时不会自动读取文件)")
def remaining_time_format(seconds):
minutes, seconds = divmod(seconds, 60)# 将秒数转换为分钟和秒的格式
return f"剩余时间: {int(minutes):02d}:{int(seconds):02d}"
def auto_up(*args):
if checkbox_var_1.get():
auto_excel_file(file_path)
Update_time(int(entry.get()))
if checkbox_var_1:
time_up=int(int(entry.get())*1000)
window.after(time_up, auto_up)
def Update_time(remaining_time):
time_label.config(text=remaining_time_format(remaining_time))
if remaining_time > 0:window.after(1000,lambda: Update_time(remaining_time-1))
def on_save_config():
global is_remember_refresh_time
refresh_time = entry.get()
is_remember_refresh_time = checkbox_var_1.get()#获取复选框状态
if is_remember_refresh_time and not refresh_time:
messagebox.showerror("错误", "请先输入刷新时间")
return
try:
if is_remember_refresh_time:
refresh_time_int = int(refresh_time)
config = configparser.ConfigParser()#读取现有的配置文件
config.read('settings.ini')
if 'REFRESH_SETTINGS' not in config:#如果配置文件不存在REFRESH_SETTINGS部分,则创建它
config['REFRESH_SETTINGS'] = {}
config['REFRESH_SETTINGS']['refresh_time'] = str(refresh_time_int)#更新或写入刷新时间到REFRESH_SETTINGS部分
checkbox_1.config(text="是否记住刷新时间:是")
else:#如果复选框未选中,则检查并删除REFRESH_SETTINGS部分(如果存在)
checkbox_1.config(text="是否记住刷新时间:否")
if os.path.exists('settings.ini'):
config = configparser.ConfigParser()
config.read('settings.ini')
if 'REFRESH_SETTINGS' in config:#如果存在REFRESH_SETTINGS部分,则删除它
del config['REFRESH_SETTINGS']
with open('settings.ini', 'w') as configfile:#将修改后的配置写回文件
config.write(configfile)
messagebox.showinfo("成功", "已删除刷新时间设置")
print('刷新时间设置已从settings.ini中删除')
if is_remember_refresh_time:#如果复选框选中并且有有效输入,将修改后的配置写回文件
with open('settings.ini', 'w') as configfile:
config.write(configfile)
messagebox.showinfo("成功", "刷新时间已保存")
print('刷新时间已保存到settings.ini中')
auto_up(None)
except ValueError:
checkbox_var_1.set(0)
messagebox.showerror("错误", "请输入有效的刷新时间(整数)")
def on_text_change(P):
try:
int(P)#尝试将文本框内容转换为整数
return True#验证成功,但这里不需要做任何操作,因为验证本身不会改变标签文本
except ValueError:
return False#验证失败,返回False阻止字符被输入
#创建一个函数来更新标签文本,当文本框失去焦点时调用
def update_label_text(event):#添加event参数
try:
number = int(entry.get())#获取文本框内容并转换为整数
#更新标签文本
if number < 10:
label.config(text=f"秒数已设置为: {number}秒(数据过大时不建议低于10秒)")
else:
label.config(text=f"秒数已设置为: {number}秒")
except ValueError:
#如果不是数字,则重置标签文本
label.config(text="请输入有效的数字")
def update_label_1_text(event):#添加event参数
try:
number = int(entry_1.get())#获取文本框内容并转换为整数
label_1.config(text=f"提醒天数已设置为: {number}天")#更新标签文本
except ValueError:
#如果不是数字,则重置标签文本
label.config(text="请输入有效的数字")
def on_save_remaining_days():
global is_remember_remaining_days
remaining_days = entry_1.get()
is_remember_remaining_days = checkbox_var_2.get()
if is_remember_remaining_days and not remaining_days:
messagebox.showerror("错误", "请先输入剩余天数")
return
try:
if is_remember_remaining_days:
remaining_days_int = int(remaining_days)
config = configparser.ConfigParser()
config.read('settings.ini')
if 'REMAINING_DAYS_SETTINGS' not in config:
config['REMAINING_DAYS_SETTINGS'] = {}
config['REMAINING_DAYS_SETTINGS']['remaining_days'] = str(remaining_days_int)
checkbox_2.config(text="是否记住剩余天数:是")
else:
checkbox_2.config(text="是否记住剩余天数:否")
if os.path.exists('settings.ini'):
config = configparser.ConfigParser()
config.read('settings.ini')
if 'REMAINING_DAYS_SETTINGS' in config:
del config['REMAINING_DAYS_SETTINGS']
with open('settings.ini', 'w') as configfile:
config.write(configfile)
messagebox.showinfo("成功", "已删除剩余天数设置")
print('剩余天数设置已从settings.ini中删除')
if is_remember_remaining_days:
with open('settings.ini', 'w') as configfile:
config.write(configfile)
messagebox.showinfo("成功", "剩余天数已保存")
print('剩余天数已保存到settings.ini中')
except ValueError:
checkbox_var_2.set(0)
messagebox.showerror("错误", "请输入有效的剩余天数(整数)")
#创建主窗口
window = tk.Tk()
window.title("证件信息")
#获取屏幕宽度和高度
screen_width = window.winfo_screenwidth()
screen_height = window.winfo_screenheight()
#计算窗口的宽度和高度
window_width = 1100
window_height = 500
#计算窗口左上角应该放置的位置,使其居中
x_coordinate = (screen_width // 2) - (window_width // 2)
y_coordinate = (screen_height // 2) - (window_height // 2)
#设置窗口的位置
window.geometry(f"{window_width}x{window_height}+{int(x_coordinate)}+{int(y_coordinate)}")
#创建变量来跟踪复选框的状态
checkbox_var = tk.IntVar(value=0)#初始值设为0,表示未选中状态
checkbox_var_1 = tk.IntVar(value=0)#初始值设为0,表示未选中状态
checkbox_var_2 = tk.IntVar(value=0)#初始值设为0,表示未选中状态
read_ini_file('FILE_SETTINGS', 'file_path')#读取文件路径
read_ini_file('REFRESH_SETTINGS', 'refresh_time')#读取刷新时间
read_ini_file('REMAINING_DAYS_SETTINGS', 'remaining_days')#读取剩余天数
if file_path:checkbox_var.set(1)
if refresh_time:checkbox_var_1.set(1)
if remaining_days:checkbox_var_2.set(1)
if refresh_time is None:refresh_time=60
if remaining_days is None:remaining_days=30
refresh_time_var = tk.StringVar()
refresh_time_var.set(str(refresh_time))
remaining_days_var = tk.StringVar()
remaining_days_var.set(str(remaining_days))
#创建父容器
frame = ttk.Frame(window)
frame_1=ttk.Frame(window)
frame.grid(row=0, column=0, pady=0, sticky="news")#'news'代表北、东、南、西,意味着frame将扩展到父容器的所有可用空间
frame_1.grid(row=1, column=0, pady=0, sticky="news")
window.rowconfigure(0, pad=0)#控制第一行之间的间隔
window.rowconfigure(1, pad=0)#控制第二行之间的间隔
window.rowconfigure(2, pad=0)#控制第三行之间的间隔
#创建水平滚动条
xscrollbar = ttk.Scrollbar(frame, orient=tk.HORIZONTAL)
xscrollbar.grid(row=2,column=1,sticky="ew")
#创建垂直滚动条
yscrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL)
yscrollbar.grid(row=0,column=2,sticky="ns")
#设置字体
font_name = "Consolas"#你可以换成任何你想要的等宽字体名称
font_size = 8
label_1=tk.Label(frame,text="证件列表:")
label_1.grid(row=0,column=0)
#创建无需弹窗列表框
upper_listbox = tk.Listbox(frame, width=168, height=12, xscrollcommand=xscrollbar.set, yscrollcommand=yscrollbar.set)
upper_listbox.grid(row=0,column=1)
upper_listbox_listbox_font = tk.font.Font(family=font_name, size=font_size)
upper_listbox.config(font=upper_listbox_listbox_font) #应用字体到列表框
label_2=tk.Label(frame,text="弹窗列表:")
label_2.grid(row=1,column=0)
#创建需要弹窗列表框
below_listbox = tk.Listbox(frame, width=168, height=12, xscrollcommand=xscrollbar.set, yscrollcommand=yscrollbar.set)
below_listbox.grid(row=1,column=1)
below_listbox_listbox_font = tk.font.Font(family=font_name, size=font_size)
below_listbox.config(font=below_listbox_listbox_font)#应用字体到列表框
#设置滚动条与列表框的关联
xscrollbar.config(command=upper_listbox.xview)
yscrollbar.config(command=upper_listbox.yview)
xscrollbar.config(command=below_listbox.xview)
yscrollbar.config(command=below_listbox.yview)
#创建复选框
checkbox_1 = tk.Checkbutton(frame_1, text="是否记住刷新秒数:否",variable=checkbox_var_1, command=on_save_config)
checkbox_1.grid(row=0, column=3, pady=0, sticky="news")
#创建标签
label = tk.Label(frame_1, text="请输入秒数:秒")
label.grid(row=0, column=4, pady=0, sticky="news")
time_label = tk.Label(frame_1, text="剩余时间: 00:00")
time_label.grid(row=1, column=5, pady=0, sticky="news")
#创建文本框,并设置validate和validatecommand选项来限制输入为数字
entry = tk.Entry(frame_1, textvariable=refresh_time_var,validate="key", validatecommand=(frame_1.register(on_text_change), '%P'))
entry.grid(row=0, column=5, pady=0, sticky="news")
checkbox_2 = tk.Checkbutton(frame_1, text="是否记住剩余天数:否",variable=checkbox_var_2, command=on_save_remaining_days)
checkbox_2.grid(row=0, column=0, pady=0,padx=40, sticky="news")
#创建标签
label_1 = tk.Label(frame_1, text="请输入天数阈值:天")#Remaining_time
label_1.grid(row=0, column=1, pady=0, sticky="news")
#创建文本框,并设置validate和validatecommand选项来限制输入为数字
entry_1 = tk.Entry(frame_1, textvariable=remaining_days_var,validate="key", validatecommand=(frame_1.register(on_text_change), '%P'))
entry_1.grid(row=0, column=2, pady=0, sticky="news")
update_label_text(None)
update_label_1_text(None)
#绑定entry的失焦事件到update_label_text函数
entry.bind('<FocusOut>', update_label_text)
entry_1.bind('<FocusOut>', update_label_1_text)
#创建按钮
read_button = tk.Button(frame_1, text="读取Excel文件", command=read_excel_file)
read_button.grid(row=1, column=2, pady=0, sticky="news")
checkbox = tk.Checkbutton(frame_1, text="是否记住文件路径:否\n(下次启动程序时不会自动读取文件)", variable=checkbox_var,command=on_checkbox_change)
checkbox.grid(row=1, column=3, pady=0, sticky="news")
if checkbox_var :checkbox.config(text="是否记住文件路径:")
if checkbox_var_1 :
checkbox_1.config(text="是否记住刷新时间:")
auto_up(None)
if checkbox_var_2 :checkbox_2.config(text="是否记住剩余天数:")
auto_excel_file(file_path)
#进入消息循环
window.mainloop()