PySimpleGUI图形界面实例|PDF表格转换Excel文件

实例要求:

使用PySimpleGUI做一个把单位考勤系统导出的pdf文件合并输出Excel的应用,故事出自:https://hannyang.blog.csdn.net/article/details/135395946

当时时间紧,没有好好做界面且输出csv文件了事。今天趁周六休息,把代码做一下升级处理,使用库pdfplumber直接遍历多个pdf文件,得到数据后输出Excel文件即可。之前先合并pdf文件再取数,纯粹有点多余。

原始文件:

输出文件:

运行界面:

完整代码: 

import os, xlwt, pyperclip, pdfplumber
import datetime as dt
import PySimpleGUI as sg# 全局变量
table_head = '姓名,部门,应到,实到,出勤率,迟到次数,早退次数,加班(分钟)'
path, font = '', ('宋体',12)
date, data = [], []
DateFormat = '    .  . -    .  .  '
ErrMessage = '错误'
SortedType = ["出勤率排序","加班时长排序","迟到次数排序","早退次数排序"]# 定义布局
layout = [[sg.Text("昆山分行考勤表",font=('',16)),sg.Text(pad=(132,10)),sg.Text("请选择考勤文件:",font=font),sg.Input(key="-FOLDER-", enable_events=True, readonly=True,font=font,size=18),sg.FolderBrowse(button_text='...', enable_events=True, initial_folder='./')],[sg.Text("考勤日期:",font=font),sg.Text(DateFormat,key='-DATE-',font=font)],[sg.Table(values='',headings=table_head.split(','),key='-TABLE-',auto_size_columns=False,justification='left',num_rows=10)],[sg.Button("输出Excel文件",size=(12,1),pad=(15,30)),sg.Button(SortedType[0], enable_events=True,size=(10,1),pad=(15,30)),sg.Button(SortedType[1], enable_events=True,size=(10,1),pad=(15,30)),sg.Button(SortedType[2], enable_events=True,size=(10,1),pad=(15,30)),sg.Button(SortedType[3], enable_events=True,size=(10,1),pad=(15,30)),sg.Button("退出",size=(10,1),pad=(15,30))],[sg.StatusBar('',key="-BAR-",font=font,size=92)]
]# 读取pdf表格
def read_table(file):dct = dict()with pdfplumber.open(file) as pdf:for page in pdf.pages:tables = page.extract_tables(table_settings = {})for table in tables:for lst in table:tmp = lst[1:]if not any(tmp): continuetmp = [tmp[0]]+tmp[3:8]+[tmp[-1]]tmp[0] = tmp[0].replace('\n','')tmp[0] = tmp[0].split('/')tmp[0] = tmp[0][-1]if lst[0]=='时间':dct[lst[0]] = tmp[0]else:dct[','.join([lst[0],tmp[0]])] = ','.join(tmp[1:])return dct# 写入xls文件
def write_sheet():global data, date, table_head, ErrMessageif ErrMessage[:2] in ('错误','输出'): returnmyxl = xlwt.Workbook()style = xlwt.easyxf('align: wrap yes; align: horiz center; font: bold yes;') sheet = myxl.add_sheet('考勤表')wcol = [20,40,60,30,30,40,40,40,60]for i,w in enumerate(wcol):sheet.col(i).width = w * 80sheet.write_merge(0,0,0,8,'出勤统计报表',style)style = xlwt.easyxf('borders:top thin; borders:bottom thin; borders:left thin; borders:right thin;') sheet.write_merge(1,1,0,2,'考勤日期:'+date[0])for i,head in enumerate(['序号']+table_head.split(',')):sheet.write(2,i,head,style)for i,row in enumerate(data):for j,col in enumerate([str(i+1)]+row):sheet.write(3+i,j,col,style)for i,t in enumerate(SortedType):if t in ErrMessage:tmp = SortedType[i]breakelse: tmp = ""excel_file = f'昆山分行考勤表{date[0]}({tmp}{strDateTime()}).xls'ErrMessage = f'输出文件为:{excel_file}'try:myxl.save(excel_file)except:ErrMessage = '写入excel文件失败!'finally:pyperclip.copy('\\'.join((os.getcwd(),excel_file)))window['-BAR-'].update(ErrMessage)# 获取当前时间
def strDateTime(diff=0):now = dt.datetime.now()time = now + dt.timedelta(days=diff)    return f'{time.year}{time.month:02}{time.day:02}{time.hour:02}{time.minute:02}{time.second:02}'# 选择并处理文件
def on_text_changed(event, values):global date, data, path, ErrMessagenew_path = values["-FOLDER-"]window["-FOLDER-"].update(new_path.split('/')[-1])if path==new_path: returnelse: path = new_pathpdfs = [f for f in os.listdir(path) if f.endswith('.pdf') and not f.startswith('PDFmerged')]if len(pdfs)==0:ErrMessage = '错误:所选文件夹中没有PDF文件!'window['-BAR-'].update(ErrMessage)window['-DATE-'].update(DateFormat)window['-TABLE-'].update(values=[])returndate, data, sheet = [], [], dict()for pdf in pdfs:dct = read_table('/'.join([path,pdf]))date.append(dct['时间'])sheet.update(dct)if date:window['-DATE-'].update(date[-1])for k,v in sheet.items():if k in ('时间','姓名,所属组织','普通班个人出勤统计报表,'): continuedata.append(','.join([k,v]).split(','))window['-TABLE-'].update(values=data)persons = len(data)departments = len(set([d[1] for d in data]))if len(set(date))!=1:data = []ErrMessage = f'错误:请检查所选文件存在多个时间段:{",".join(set(date))}'else:ErrMessage = f'考勤人数:{persons} / 部门数:{departments}'window['-BAR-'].update(ErrMessage)# 表格排序
def on_table_sorted(event, data):global ErrMessageif not data: returnslist = ['x[-4][:-1]', 'x[-1]', 'x[-3]', 'x[-2]']style = slist[SortedType.index(event)]data = sorted(data, key=lambda x: float(eval(style)), reverse=True)window['-TABLE-'].update(values=data)ErrMessage = f'已按{event}更新!'window['-BAR-'].update(ErrMessage)# 创建窗口
window = sg.Window("汇总考勤表", layout, finalize=True)# 事件循环
while True:event, values = window.read()if event == sg.WINDOW_CLOSED or event == "退出":breakelif event == "-FOLDER-":on_text_changed(event, values)elif event in SortedType:on_table_sorted(event, data)elif event == "输出Excel文件":write_sheet()# 关闭窗口
window.close()

代码分析:

重点代码都用彩色字体加粗标注了:

遍历表格

读取代码如下:

import pdfplumber

def read_table(file):
    dct = dict()
    with pdfplumber.open(file) as pdf:
        for page in pdf.pages:
            tables = page.extract_tables(table_settings = {})
            for table in tables:
                for lst in table:
                    # 根据表格实际情况来清洗数据
    return dct

布局界面

import PySimpleGUI as pg

layout = [
    [sg.Text("昆山分行考勤表",font=('',16)),
     sg.Text(pad=(132,10)),
     sg.Text("请选择考勤文件:",font=font),
     sg.Input(key="-FOLDER-", enable_events=True, readonly=True,font=font,size=18),
     sg.FolderBrowse(button_text='...', enable_events=True, initial_folder='./')
     ],
    [sg.Text("考勤日期:",font=font),
     sg.Text(DateFormat,key='-DATE-',font=font)
     ],
    [sg.Table(values='',
              headings=table_head.split(','),
              key='-TABLE-',
              auto_size_columns=False,
              justification='left',
              num_rows=10)],
    [sg.Button("输出Excel文件",size=(12,1),pad=(15,30)),
     sg.Button(SortedType[0], enable_events=True,size=(10,1),pad=(15,30)),
     sg.Button(SortedType[1], enable_events=True,size=(10,1),pad=(15,30)),
     sg.Button(SortedType[2], enable_events=True,size=(10,1),pad=(15,30)),
     sg.Button(SortedType[3], enable_events=True,size=(10,1),pad=(15,30)),
     sg.Button("退出",size=(10,1),pad=(15,30))],
    [sg.StatusBar('',key="-BAR-",font=font,size=92)]
]

控件简介

除了最常用的Text, Input, Button,使用了 FolderBrowse、Table、StatsBar 三个不是最常用的控件,分别是文件夹打开框、表格和状态栏。

表格最重要的三个参数: values, headings, auto_size_columns

sg.Table(values='', headings=table_head.split(','), auto_size_columns=False)

表格数据values和表头headings都列表(分别是二维和一维的),auto_size_columns=False建议不要缺省,否则列宽不可控,各列都自动缩进紧靠在一起。

表格更新数据的方法:window['-TABLE-'].update(values=data)

写入表格

import xlwt

def write_sheet():
    global data, date, table_head, ErrMessage
    if ErrMessage[:2] in ('错误','输出'): return
    myxl = xlwt.Workbook()
    style = xlwt.easyxf('align: wrap yes; align: horiz center; font: bold yes;') 
    sheet = myxl.add_sheet('考勤表')
    wcol = [20,40,60,30,30,40,40,40,60]
    for i,w in enumerate(wcol):
        sheet.col(i).width = w * 80
    sheet.write_merge(0,0,0,8,'出勤统计报表',style)
    style = xlwt.easyxf('borders:top thin; borders:bottom thin; borders:left thin; borders:right thin;') 
    sheet.write_merge(1,1,0,2,'考勤日期:'+date[0])
    for i,head in enumerate(['序号']+table_head.split(',')):
        sheet.write(2,i,head,style)
    for i,row in enumerate(data):
        for j,col in enumerate([str(i+1)]+row):
            sheet.write(3+i,j,col,style)
    for i,t in enumerate(SortedType):
        if t in ErrMessage:
            tmp = SortedType[i]
            break
    else: tmp = ""
    excel_file = f'昆山分行考勤表{date[0]}({tmp}{strDateTime()}).xls'
    ErrMessage = f'输出文件为:{excel_file}'
    try:
        myxl.save(excel_file)
    except:
        ErrMessage = '写入excel文件失败!'

注意单格和多个的写入区别: sheet.write() sheet.write_merge()

事件循环

while True:
    event, values = window.read()
    if event == sg.WINDOW_CLOSED or event == "退出":
        break
    elif event == "-FOLDER-":
        on_text_changed(event, values)
    elif event in SortedType:
        on_table_sorted(event, data)
    elif event == "输出Excel文件":
        write_sheet()

# 表格排序

SortedType = ["出勤率排序","加班时长排序","迟到次数排序","早退次数排序"]
def on_table_sorted(event, data):
    global ErrMessage
    if not data: return
    slist = ['x[-4][:-1]', 'x[-1]', 'x[-3]', 'x[-2]']
    style = slist[SortedType.index(event)]
    data = sorted(data, key=lambda x: float(eval(style)), reverse=True)
    window['-TABLE-'].update(values=data)
    ErrMessage = f'已按{event}更新!'
    window['-BAR-'].update(ErrMessage)

使用eval()简化了表格排序事件的代码行数。


源码和2个例表已绑定上传资源,欢迎下载测试。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/326365.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

初中数学:几何题的相关解题原则总结

一、多问类型的几何题 我们做题,应该都遇到过这类几何题目,就是,三个小问,每个小问对应一个几何图像,而且,渐渐复杂。这种题目,大多数有一个变化的条件,比如,动点、角度…

从零开始:教你如何规划和开发一款优质的教育网校APP

本篇文章,笔者将从规划和开发的角度,详细介绍如何从零开始打造一款优质的教育网校APP。 第一步:明确需求和目标 在开始规划之前,我们首先需要明确教育网校APP的目标受众是谁,提供哪些课程,以及期望达到的…

基于SpringBoot+Vue人力资源管理系统(前后端分离)

该项目完全免费 系统介绍 基于 SpringBootVue 实现的人力资源管理系统是为了提高企业人力资源管理水平而开发的。主要目标是通过对员工 及人力资源活动信息(考勤、工资 ) 等的编制来提高企业效率。 系统一共分为五大菜单项,分别是首页、薪资管理、权…

【总线接口】1.以Xilinx开发板为例,直观的认识硬件板卡和接口

初接触硬件,五花八门的总线、接口一定会让你有些疑惑,我尝试用一系列文章来解开你的疑惑 系列文章 【总线接口】1.以Xilinx开发板为例,直观的认识硬件接口 【总线接口】2.学习硬件这些年接触过的硬件接口、总线 大汇总 【总线接口】…

Qt/QML编程学习之心得:QSocketNotifier(二十一)

QSocketNotifier在Qt中怎么使用? QSocketNotifier使Qt的事件循环与其他基于文件描述符的事件循环集成成为可能。在Qt的主事件循环(QCoreApplication::exec())中检测到文件描述符操作。 使用低级(通常是特定于平台的)API打开设备后,可以创建一个套接字通知程序来监视文…

【自学笔记】01Java基础-09Java关键字详解

介绍java(基于java11)中所有关键字,以及主要重要的关键字详解。 1 Java 11中的关键字: 1.1 类型声明与变量定义 boolean:声明布尔类型变量,只有两个可能值 true 或 false。byte:声明一个8位有…

软件测试|SQL中的null值,该如何理解?

深入理解SQL中的Null值:处理缺失数据的重要概念 简介 Null值在SQL中是用于表示缺失或未知数据的特殊值。本文将深入探讨Null值的概念、处理方法和注意事项,以帮助读者更好地理解和处理SQL中的缺失数据。 在SQL数据库中,Null值是一种特殊的…

第九节HarmonyOS 常用基础组件10-TextClock

1、描述 TextClock组件通过文本将当前系统时间显示在设备上。支持不同时区的时间显示,最高精度到秒级。 2、接口 TextClock(options?: {timeZoneOffset?: number, controller?: TextClockController}) 3、参数 参数名称 参数类型 必填 描述 timeZoneOffs…

同步检查继电器 DT-1/200 100V 板后接线 面板安装 JOSEF约瑟

系列型号 DT-1/200同步检查继电器; DT-1/160同步检查继电器; DT-1/130同步检查继电器; DT-1/120同步检查继电器; DT-1/90同步检查继电器; DT-1/254同步检查继电器; 一、用途 DT-1型同步检查继电器用于两端供电线路的自动重合闸线路中,其作用在于检查线路上电压的存…

【数据分享】2023年我国土壤数据集HWSD2.0(免费获取/7个土壤深度/40多种土壤指标)

土壤数据,包括土壤类型、土壤酸碱度、土壤沙含量等指标,对于农业、林业、园艺、环境保护等领域都非常重要。 本次我们为大家带来的是2023年1月份发布的我国土壤数据库,包括国界范围的数据和分省份的数据。该数据的核心信息如下: …

labelme的json转mask,实测有效

1、创建一个conda的虚拟环境 conda creat -n labelme python3.82、转到你的标注文件夹(包括json和图片) cd C:/Users/Administrator/Desktop/json3、你需要在标注文件夹下用txt写下以下代码,并保存bat文件。 放在最后一个就可以了 echo of…

法一(auto-py-to-exe):Pyinstaller将yolov5的detect.py封装成detect.exe

pip install pyinstaller # 安装最新版本的pyinstaller指令# 在dist目录下只生成一个较大xxx.exe文件,所有依赖库全打包到exe中,打包后的exe可单独使用 pyinstaller -F xxx.py # 在dist目录下生成较小的exe文件,其他依赖库全都在dist文件夹下…