openpyxl处理excel表格文件;python窗体应用程序tkinter的使用;python进度条ProgressBar
该需求背景是有一个应收逾期表格,里面有很多部门的数据,要把表格按部门拆分成每个部门单独一个EXCEL表格文件,并把拆分出来的各部门文件邮件发送给各部门领导,涉及到的python知识点想当多,大致包括:
# python 图形界面程序tkinter的使用
# python messagebox的使用
# python 进度条ProgressBar的使用
# openpyxl 对Excel表格的操作
# python 正则表达式的使用
# python 发送带附件的邮件
# openpyxl 知道表格列号,获取列标字母的方法get_column_letter(int)
# openpyxl 获取表格有数据的区域的最大行号的方法
# openpyxl 遍历表格指定区域
# openpyxl 合并单元格
# openpyxl 新建、保存表格
# openpyxl 设置单元格数值格式
# openpyxl 遍历表格所有有数据的区域,并调整字体、字号、边框、列宽
# python 将小数格式调整为保留两位小数的百分数形式
# 这个程序是用于周钊铌每周拆分应收逾期表格文件,并发送邮件给各部门 # 营销中心各二级部门的数据不拆分成单独文件 # 大客户部专用 # v2022.10.19 import datetime import smtplib import re import tkinter.filedialog from email.header import Header from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from tkinter import * from tkinter import messagebox, ttk from openpyxl import load_workbook, Workbook from openpyxl.styles import Alignment, PatternFill, Font, Border, Side from openpyxl.utils import get_column_letter all_dept_arr = ["营销中心-华南区", "营销中心-西部区","海外事业部","金融及大客户部"] sales_dept_arr = ["营销中心-华南区", "营销中心-西部区"] key_customer_dept = ["战略大客户部","华东区大客户部","金融大客户部","金融及大客户部"] to_list = { "营销中心-华南区": ["abc@123.com"], "营销中心-西部区": ["abc@123.com"],"金融及大客户部": ["abc@123.com"],"海外事业部": ["abc@123.com"], } name_list = { "营销中心-华南区": "周总", "营销中心-西部区": "胡总","金融及大客户部": "邓总","海外事业部": "王洋总", "营销中心": "陈阳总" } cc_list = { "营销中心-华南区": ["abc@123.com", "def@123.com"], "营销中心-西部区": ["abc@123.com", "def@123.com"],"金融及大客户部": ["abc@123.com", "def@123"],"营销中心": ["abc@123.com", "abc@def.com"] } dept_arr = [] current_sales_dept = [] dept_arr_mail = [] addr_dict = dict() ar_balance = dict() overdue_amount = dict() overdue_percent = dict() overdue_date = "" root = Tk() root.title("Python小助手@lidi v1.0") root.wm_geometry('500x450+500+250') root.resizable(False, False) l_frame_1 = LabelFrame(root, text="拆分文件", padx=10, pady=10) l_frame_1.pack(pady=20) label_1 = Label(l_frame_1, text="请选择待拆分文件:") text_box = Entry(l_frame_1, bd=2) label_1.grid(row=0, column=0, padx=5, pady=10) text_box.grid(row=0, column=1, padx=5, pady=10) def btn_view_click(): text_box.delete(0, END) text_box.insert(0, tkinter.filedialog.askopenfilename()) btn_view = Button(l_frame_1, text='浏览...', command=btn_view_click) btn_view.grid(row=0, column=2, padx=5, pady=10) pgrs_bar = ttk.Progressbar(root) l_frame_2 = LabelFrame(root, text="发送邮件", padx=30, pady=10) label_name = Label(l_frame_2, text="发件人邮箱") label_pwd = Label(l_frame_2, text='发件人密码') label_addr_list = Label(l_frame_2, text='收件人地址簿') entry_name = Entry(l_frame_2, bd=2, state='readonly') entry_pwd = Entry(l_frame_2, bd=2, show='*', state='readonly') entry_addr_list = Entry(l_frame_2, bd=2, state='readonly') def btn_view2_click(): entry_addr_list.delete(0, END) entry_addr_list.insert(0, tkinter.filedialog.askopenfilename()) def getLastMonday(): monday = datetime.date.today() # monday = datetime.date(2022, 6, 27) one_day = datetime.timedelta(days=1) while monday.weekday() != 0: monday -= one_day return datetime.datetime.strftime(monday, "%Y-%m-%d") def btn_send_click(): sender_account = "abc@456.com" sender_pwd = "123456" sender_name = "ABC<abc@456.com>" smtp = smtplib.SMTP_SSL(host="smtp.exmail.qq.com", port=465) smtp.login(sender_account, sender_pwd) pgrs_bar['value'] = 5 root.update() global overdue_date try: pgrs_step = 0 for dept in dept_arr_mail: pgrs_step = pgrs_step + 1 msg = MIMEMultipart() msg['From'] = sender_name msg['To'] = ";".join(to_list[dept]) msg['Cc'] = ";".join(cc_list[dept]) msg['Subject'] = '逾期货款统计月报' + overdue_date message_tips = "<hr /><p>温馨提示:</p><p>本邮件由系统自动发出,有可能遇到附件名称出现乱码的情况,此时双击附件,会提示选择用什么程序打开,此时选择wps或者Office " \ "Excel即可正常打开。</p>" \ "<p>建议将Foxmail升级到最新版客户端,或者使用网页邮箱查看邮件,附件名称都会正常显示</p>" \ "<p>最新版Foxmail下载地址:<a href='https://www.foxmail.com/'>https://www.foxmail.com/</a></p>" \ "<p>网页版邮箱登录地址:<a href='https://exmail.qq.com/'>https://exmail.qq.com/</a></p>" message_sign = '<hr />Best regards<br/>' \ '李堤 营销财管部 财务BP' \ ' <br/>' \ '深圳市优博讯科技股份有限公司(股票代码:300531)<br/>' \ 'UROVO TECHNOLOGY CO., LTD. (Stock Code: 300531.SZ)<br/>' \ '深圳市南山区学府路63号高新区联合总部大厦36-37楼<br/>' \ 'Floor 36-37, Hi-tech Zone Union Tower,No.63 Xuefu Road, Nanshan District, Shenzhen,' \ 'Guangdong,<br/>' \ 'ChinaTel:+86-755-86186300,<br/>' \ 'Web:http://www.urovo.com<br/>' \ '------------------------------------<br/>' \ '本邮件包含信息归优博讯所有,优博讯对该邮件拥有所有权利。请收件人注意保密,未经发件人书面许可,不得向任何第三方组织和个人透露本邮件所含全部或部分信息。<br/>' \ 'CONFIDENTIALITY NOTICE. This message is intended exclusively for the named addressee and ' \ 'may contain confidential information. Unless you are the named addressee (or authorised ' \ 'to receive for the addressee) you may not copy, use or disclose this message. If this ' \ 'e-mail was sent to you by mistake please notify the sender immediately and delete this ' \ 'e-mail. ' message = name_list[dept] + ':<br/><p>您好!截至' + overdue_date + ', ' + dept + '的应收账款为' + str( format(ar_balance[ dept], ",")) + '元,逾期金额为' + str(format(overdue_amount[dept], ",")) + '元,逾期率为' + str( overdue_percent[ dept]) + '</p><p>附件为应收款和逾期款明细,请查收,谢谢!</p><p>后续若有疑问,请及时与财务中心相关同事联系。</p><p>海外--李媛媛</p><p' \ '>国内--王博、段春雪、李堤。</p>' + message_sign msg.attach(MIMEText(message, 'html', 'utf-8')) # print(message) xlsx_file = MIMEApplication(open(dept + '.xlsx', 'rb').read()) xlsx_file['Content-type'] = 'application/octet-stream' xlsx_file.add_header('Content-Disposition', 'attachment', filename=Header(dept + '.xlsx', 'utf-8').encode()) # 添加到header信息,此处filename必须用Header编码,不然会出现乱码 msg.attach(xlsx_file) # 正式上线时修改为正式的收件人和抄送人 # 此处sender_name参数的值必须包含发件人地址,否则会报错 smtp.sendmail(sender_name, to_list[dept] + cc_list[dept], msg.as_string()) pgrs_bar['value'] = 5 + pgrs_step / len(dept_arr) * 95 root.update() pgrs_bar['value'] = 5 + pgrs_step / len(dept_arr_mail) * 95 root.update() smtp.quit() messagebox.showinfo('发送成功', str(len(dept_arr_mail)) + '封邮件发送成功,请登录邮箱查看已发送邮件') except Exception as e: # smtp.quit() messagebox.showerror('错误', e) btn_view2 = Button(l_frame_2, text='从文件导入...') btn_send = Button(l_frame_2, text='发送邮件', command=btn_send_click) def show_mail_area(): l_frame_2.pack(pady=20) label_name.grid(row=2, column=0, padx=10, pady=10) label_pwd.grid(row=3, column=0, padx=10, pady=10) label_addr_list.grid(row=4, column=0, padx=10, pady=10) entry_name.grid(row=2, column=1, padx=10, pady=10) entry_pwd.grid(row=3, column=1, padx=10, pady=10) entry_addr_list.grid(row=4, column=1, padx=10, pady=10) btn_view2.grid(row=4, column=2, padx=10, pady=10) btn_send.grid(row=5, column=1, padx=10, pady=10) def btn_split_click(): file_name = text_box.get() if file_name != "": #try: # 显示进度条 pgrs_bar.pack(padx=100, pady=10) pgrs_bar['length'] = 300 pgrs_bar['maximum'] = 100 pgrs_bar['value'] = 3 root.update() wb = load_workbook(file_name, data_only=True) # 读取工作簿,只读模式 # print(wb.sheetnames) sheet1 = wb["应收汇总"] # 获取工作表 global overdue_date overdue_date = re.search("[0-9]+\.[0-9]+\.[0-9]+", sheet1.cell(1, 1).value).group() pgrs_bar['value'] = 5 root.update() # print(sheet1.title) max_row_B = max(bb.row for bb in sheet1['B'] if bb.value) # 获取B列有数据最大行号 pgrs_bar['value'] = 8 root.update() # print(max_row_B) # 遍历工作表数据 dept_dict = dict() for row in sheet1["B4:B" + str(max_row_B)]: for cell in row: if cell.value in all_dept_arr: dept_dict[cell.value] = "" for key in dept_dict.keys(): dept_arr.append(key) if key in sales_dept_arr: current_sales_dept.append(key) elif key in key_customer_dept: dept_arr_mail.append(key) del dept_dict #dept_arr_mail.append("营销中心") # print(dept_arr) # print(current_sales_dept) # print(dept_arr_mail) pgrs_bar['value'] = 10 root.update() pgrs_step = 0 for dept in dept_arr_mail: pgrs_step = pgrs_step + 1 new_wb = Workbook() # 新建工作簿 new_ws = new_wb.active # 获取当前工作表 new_ws.title = dept header1 = ["合并客户", "部门", "业务员", "年销售目标(否-不是代理商;有数字则是销售目标量)","应收金额(RMB)", "逾期(含发货超过90天", "逾期1:发货超过90天", "逾期2:超账期", "逾期期间", "", "", "", "", "9-10月份回款额(含售后)", "9月份回款额(含售后)", "10月W1", "10月W2", "10月W3","10月回款合计"] header2 = ["", "", "", "", "", "", "","", "1-30天", "31-90天", "91-180天", "181-365天", "1年以上"] new_ws.append(header1) # 往表格中追加内容 new_ws.append(header2) new_ws.merge_cells("A1:A2") # 合并单元格 new_ws.merge_cells("B1:B2") new_ws.merge_cells("C1:C2") new_ws.merge_cells("D1:D2") new_ws.merge_cells("E1:E2") new_ws.merge_cells("F1:F2") new_ws.merge_cells("G1:G2") new_ws.merge_cells("H1:H2") new_ws.merge_cells("I1:M1") new_ws.merge_cells("N1:N2") new_ws.merge_cells("O1:O2") new_ws.merge_cells("P1:P2") new_ws.merge_cells("Q1:Q2") new_ws.merge_cells("R1:R2") new_ws.merge_cells("S1:S2") for row3 in new_ws["A1:S2"]: for cell4 in row3: cell4.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True) # 居中 cell4.fill = PatternFill(start_color='C0C0C0', fill_type='solid') # 字体颜色 if dept != "营销中心": for row in sheet1["B4:B" + str(max_row_B)]: for cell in row: if cell.value == dept: data_arr = [] for row_temp in sheet1["A" + str(cell.row) + ":S" + str(cell.row)]: for cell2 in row_temp: data_arr.append(cell2.value) new_ws.append(data_arr) else: for row in sheet1["B4:B" + str(max_row_B)]: for cell in row: if cell.value in current_sales_dept: data_arr = [] for cell2 in sheet1[cell.row]: data_arr.append(cell2.value) new_ws.append(data_arr) max_row_B2 = max(bb.row for bb in new_ws['B'] if bb.value) for row4 in new_ws["E" + str(max_row_B2 + 1) + ":S" + str(max_row_B2 + 1)]: for cell5 in row4: # get_column_letter,已知列号,获取列标字母, cell5.value = '=sum(' + get_column_letter(cell5.column) + '3:' + get_column_letter( cell5.column) + str(max_row_B2) + ')' new_ws.cell(max_row_B2 + 1, 1).value = '合计' # 计算部门的应收余额和逾期金额 ar_balance[dept] = 0 overdue_amount[dept] = 0 # 累加D列的应收金额 for row5 in new_ws["E3:E" + str(max_row_B2)]: for cell6 in row5: # 有些单元格为空,Value不能直接做加法,空值做加法会报错,所以做个判断 if cell6.value: print(dept) ar_balance[dept] += cell6.value # 累加E列的逾期金额 for row6 in new_ws["F3:F" + str(max_row_B2)]: for cell7 in row6: # 有些单元格为空,Value不能直接做加法,空值做加法会报错,所以做个判断 if cell7.value: overdue_amount[dept] += cell7.value ar_balance[dept] = round(ar_balance[dept], 2) overdue_amount[dept] = round(overdue_amount[dept], 2) print(dept) print(ar_balance[dept]) # 计算逾期率,并将结果转化为保留两位小数的百分数 overdue_percent[dept] = "%.2f%%" % round(overdue_amount[dept] / ar_balance[dept] * 100, 2) for row2 in new_ws[new_ws.dimensions]: for cell3 in row2: cell3.font = Font(size=9) # 字体 cell3.number_format = "#,##0.00" # 千分位格式 cell3.border = Border(left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin')) # 边框 new_ws.column_dimensions[get_column_letter(cell3.column)].width = 15 #列宽 new_ws.delete_cols(18) # 删除列 new_ws.cell(max_row_B2 + 2, 6).value = '=F' + str(max_row_B2 + 1) + '/E' + str(max_row_B2 + 1) new_ws.cell(max_row_B2 + 2, 6).number_format = "0.00%" # 百分号格式 new_ws.cell(max_row_B2 + 2, 6).font = Font(size=9) # 字号 new_wb.save(dept + ".xlsx") pgrs_bar['value'] = 10 + pgrs_step / len(dept_arr_mail) * 90 root.update() messagebox.showinfo('拆分成功', '文件已拆分成功,请查看程序所在文件夹') # print(ar_balance) # print(overdue_amount) # print(overdue_percent) show_mail_area() # pgrs_bar.destroy() #except Exception as e: #messagebox.showerror('错误', e) #pgrs_bar.destroy() else: messagebox.showerror('错误', '请先选择需要拆分的文件') btn_split = Button(l_frame_1, text='开始拆分', command=btn_split_click) btn_split.grid(row=0, column=3, padx=5, pady=10) root.mainloop()
程序运行效果: