• Python桌面应用之XX学院水卡报表查询系统(Tkinter+cx_Oracle)


    一、功能样式

    Python桌面应用之XX学院水卡报表查询系统功能:

    连接Oracle数据库,查询XX学院水卡操作总明细报表,汇总数据报表,个人明细报表,进行预览并且支持导出报表

    1.总明细报表样式
    明细
    2.汇总明细样式
    汇总明细

    3.个人明细样式
    个人明细
    4.导出报表样式
    导出
    5.错误提示样式
    tip
    tip2

    二、核心点

    1. 安装cx_Oracle:使用cx_Oracle三方库连接Oracle,该库使用的python版本略低,可以在[https://cx-oracle.readthedocs.io/en/latest/](https://cx-oracle.readthedocs.io/en/latest/进行查询,安装前先确定:python版本、Orale客户端版本(要不都是64位,要不都是32位),安装cx_Oracle的版本位数是跟python的位数相关的。
    使用代码进行测试

    import cx_Oracle
    # 账户  密码  ip:1521/实例名
    conn = cx_Oracle.connect('system','Yxq123456','127.0.0.1:1521/ecard'
    # 挂载数据库连接游标
    self.cursor = conn.cursor()
    print('连接数据库成功!')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2. 多参数查询Sql: sql语句使用:参数名来定义参数,多参数使用cursor.execute(sql,(参数1,参数2)).fetchall()来查询

    sql = "select a.outid ,a.name ,b.opfare,b.opdt,b.dscrp from base_customers a,rec_cust_acc b where a.customerid = b. customerid and b.opdt >= to_date(:preopdt,'yyyy-MM-dd HH24:mi:ss') and b.opdt <= to_date(:nextopdt,'yyyy-MM-dd HH24:mi:ss') order by b.opdt desc"
    preopdt=self.pretimeInput.get()
    nextopdt=self.nexttimeInput.get()
    data = self.cursor.execute(sql,(preopdt,nextopdt)).fetchall()
    
    • 1
    • 2
    • 3
    • 4

    **3. Treeview表格组件的使用:**这里使用了三个报表,其实可以将打开的Treeview做成一个表格class类,要使用的时候直接生成要使用的对象,传入该对象的大小,heading标题,data数据即可。

    # 明细查询
        def Consumedetail(self):
            self.consumedetail = tk.Tk()
            self.consumedetail.title('XX学院明细查询')
            self.consumedetail.geometry("1000x600")
            # 加载滚动条
            scrollBar = tk.Scrollbar(self.consumedetail)
            scrollBar.pack(side = tk.RIGHT,fill = tk.Y)
            self.tree = ttk.Treeview(self.consumedetail, columns=('outid', 'name', 'opfare', 'opdt','dscrp'), show="headings", displaycolumns="#all",yscrollcommand = scrollBar.set)
            self.tree.pack()
            self.tree.heading('outid', text="学号", anchor=tk.W)
            self.tree.heading('name', text="姓名", anchor=tk.W)
            self.tree.heading('opfare', text="交易金额", anchor=tk.W)
            self.tree.heading('opdt', text="交易日期", anchor=tk.W)
            self.tree.heading('dscrp', text="交易类型", anchor=tk.W)
            # 设置关联
            scrollBar.config(command = self.tree.yview)
            # 每次打开清空页面
            for item in self.tree.get_children():
                self.consumedetail.tree.delete(item)
            sql = "select a.outid ,a.name ,b.opfare,b.opdt,b.dscrp from base_customers a,rec_cust_acc b where a.customerid = b. customerid and b.opdt >= to_date(:preopdt,'yyyy-MM-dd HH24:mi:ss') and b.opdt <= to_date(:nextopdt,'yyyy-MM-dd HH24:mi:ss') order by b.opdt desc"
            preopdt=self.pretimeInput.get()
            nextopdt=self.nexttimeInput.get()
            data = self.cursor.execute(sql,(preopdt,nextopdt)).fetchall()
            # print(data)
            # data = [['2013090101','张三','100','2023-10-19','PC存款']]
            for itm in data:
                self.tree.insert("",tk.END,values=itm)
                self.tree.pack(padx=10,pady=10, fill=tk.BOTH,expand=1)
            exportbtn = tk.Button(self.consumedetail,text='导出',width=8,command=self.export).pack()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    4. 导出数据自定义文件名:报表里面导出数据,其实使用遍历treeview组件数据,在进行整理后写入csv文件,自定义文件名是使用filedialog.asksaveasfilename来打开文件框,里面的文件类型使用参数filetypes ,输入文件名后获取名称生成文件。这里导出的文件就只是csv文件,如果需要其他文件类型,可以自行导入其他三方库。

         def export(self):
            # 导出export        
             # 打开文件夹选择对话框
            
            # 更新标签文本
            # print(folder_path)
            list = []
            columns = []
            # 获取表格内容id
            for row_id in self.tree.get_children():
                list.append(self.tree.item(row_id)['values'])
            print(len(self.tree.get_children()))   
            # 通过第一行获取列数生成标题
            # print(self.tree.item)
            if len(self.tree.get_children()) != 0:
                print('ok')
                folder_path = filedialog.asksaveasfilename(title='请选择一个文件',filetypes=[("CSV", ".csv")]) 
                for i in range(0,len(self.tree.item('I001')['values'])):
                    columns.append(self.tree.heading(column=i)['text'])
            # 导出csv
                with open(f'{folder_path}.csv','w',newline='') as csvfile:
                    fieldnames = columns
                    writer = csv.writer(csvfile)
                    writer.writerow(fieldnames)
                    writer.writerows(list)
            else:
                messagebox.showwarning("提示", "没有数据,无法导出")
                return
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    5.遍历Treeview表格数据与标题:获取Treeview里面的数据与标题,这里现获取id值,然后通过item获取[‘values’]值,获取标题这里先遍历了第一行有多少数据,然后使用self.tree.heading(column=i)['text']来获取标题。

     # 获取表格内容id
    for row_id in self.tree.get_children():
        list.append(self.tree.item(row_id)['values'])
    
    • 1
    • 2
    • 3
     # 通过第一行获取列数生成标题
    for i in range(0,len(self.tree.item('I001')['values'])):
        columns.append(self.tree.heading(column=i)['text'])
    
    • 1
    • 2
    • 3

    三、完整代码

    import tkinter as tk
    from tkinter import ttk
    from tkinter import messagebox
    import cx_Oracle
    import time
    import csv
    from tkinter import filedialog
    
    # mainapp
    class mainApp(object):
        def __init__(self,parent,**kwargs):
            self.root = parent
            current_timestamp = time.time()
             # 将时间戳转换为本地时间的struct_time对象
            local_time = time.localtime(current_timestamp)
             # 使用strftime()方法将struct_time对象格式化为指定的时间字符串  
            # 挂在self时间
            self.pretime = time.strftime("%Y-%m-%d 00:00:00", local_time)
            self.nexttime = time.strftime("%Y-%m-%d %H:%M:%S", local_time)
    
            conn = cx_Oracle.connect('system','Yxq123456','127.0.0.1:1521/ecard')
            # conn = cx_Oracle.connect('ccense','XCXY123*','127.0.0.1:1521/ecard')
            # 挂载数据库连接游标
            self.cursor = conn.cursor()
            print('连接数据库成功!')
    
            self.root.config(**kwargs)
            self.root.title('XX学院')
            self.root.resizable(False, False)
            self.create_widgets()
            # 获取屏幕尺寸
            screen_width = self.root.winfo_screenwidth()
            screen_height = self.root.winfo_screenheight()
    
            # 确定窗口位置,并设置大小
            x_coordinate = (screen_width / 2) - 300 # 300是窗口的宽度
            y_coordinate = (screen_height / 2) - 200 # 200是窗口的高度
            self.root.geometry('650x400+{}+{}'.format(int(x_coordinate), int(y_coordinate)))
            # self.root.geometry("600x400")
            
        # 创建UI
        def create_widgets(self):
            self.titleLab = tk.Label(self.root,text='XX学院水卡报表管理系统',font=("kaiti",18)).place(x=190,y=30)
            self.outidLab = tk.Label(self.root,text='学号:').place(x=80,y=100)
            self.outidInput = tk.Entry(self.root, width=20)
            self.outidInput.place(x=130,y=100)
            # 姓名
            # 学号
            self.nameLab = tk.Label(self.root,text='姓名:').place(x=380,y=100)
            self.nameInput = tk.Entry(self.root,width=20)
            self.nameInput.place(x=430,y=100)
            # 起始时间
            self.mustLabel1 = tk.Label(self.root,text='*',font=('Arial', 16),fg = 'red').place(x=45,y=160)
    
            self.pretimeLab = tk.Label(self.root,text='起始时间:').place(x=55,y=160)
            self.pretimeInput = tk.Entry(self.root, width=20)
            self.pretimeInput.place(x=130,y=160)
            self.pretimeInput.insert(0,self.pretime)
            # 终始时间
            self.mustLabel2 = tk.Label(self.root,text='*',font=('Arial', 16),fg = 'red').place(x=350,y=160)
            
            self.nexttimeLab = tk.Label(self.root,text='终止时间:').place(x=360,y=160)
            self.nexttimeInput = tk.Entry(self.root,width=20)
            self.nexttimeInput.place(x=430,y=160)
            self.nexttimeInput.insert(0,self.nexttime)
            self.consumeBtn = tk.Button(self.root,text='明细查询',command=self.Consumedetail,width=10).place(x=130,y=260)
            self.sumBtn = tk.Button(root,text='汇总查询',command=self.sumDetail,width=10).place(x=300,y=260)
            self.personBtn = tk.Button(root,text='个人查询',command=self.personDetail,width=10).place(x=480,y=260)
    
            
    
        # 明细查询
        def Consumedetail(self):
            self.consumedetail = tk.Tk()
            self.consumedetail.title('XX学院明细查询')
            self.consumedetail.geometry("1000x600")
        
            # 加载滚动条
            scrollBar = tk.Scrollbar(self.consumedetail)
            scrollBar.pack(side = tk.RIGHT,fill = tk.Y)
            self.tree = ttk.Treeview(self.consumedetail, columns=('outid', 'name', 'opfare', 'opdt','dscrp'), show="headings", displaycolumns="#all",yscrollcommand = scrollBar.set)
            self.tree.pack()
            self.tree.heading('outid', text="学号", anchor=tk.W)
            self.tree.heading('name', text="姓名", anchor=tk.W)
            self.tree.heading('opfare', text="交易金额", anchor=tk.W)
            self.tree.heading('opdt', text="交易日期", anchor=tk.W)
            self.tree.heading('dscrp', text="交易类型", anchor=tk.W)
            # 设置关联
            scrollBar.config(command = self.tree.yview)
            # 每次打开清空页面
            for item in self.tree.get_children():
                self.consumedetail.tree.delete(item)
            sql = "select a.outid ,a.name ,b.opfare,b.opdt,b.dscrp from base_customers a,rec_cust_acc b where a.customerid = b. customerid and b.opdt >= to_date(:preopdt,'yyyy-MM-dd HH24:mi:ss') and b.opdt <= to_date(:nextopdt,'yyyy-MM-dd HH24:mi:ss') order by b.opdt desc"
            preopdt=self.pretimeInput.get()
            nextopdt=self.nexttimeInput.get()
            data = self.cursor.execute(sql,(preopdt,nextopdt)).fetchall()
            # print(data)
            # data = [['2013090101','张三','100','2023-10-19','PC存款']]
            for itm in data:
                self.tree.insert("",tk.END,values=itm)
                self.tree.pack(padx=10,pady=10, fill=tk.BOTH,expand=1)
            exportbtn = tk.Button(self.consumedetail,text='导出',width=8,command=self.export).pack()
    
        # 汇总查询
        def sumDetail(self):
            sql = "select sum(opfare),count(acccode),dscrp from rec_cust_acc where opdt >= to_date(:preopdt,'yyyy-MM-dd HH24:mi:ss') and opdt <= to_date(:nextopdt,'yyyy-MM-dd HH24:mi:ss') group by dscrp"
            self.sumtail = tk.Tk()
            self.sumtail.title('XX学院汇总查询')
            self.sumtail.geometry("800x600")
       
            # 加载滚动条
            # exportbtn = Button(sumtail,text='导出',width=8,command=export).pack()
            scrollBar = tk.Scrollbar(self.sumtail)
            scrollBar.pack(side = tk.RIGHT,fill = tk.Y)
            self.tree = ttk.Treeview(self.sumtail, columns=('sum', 'count', 'dscrp'), show="headings", displaycolumns="#all",yscrollcommand = scrollBar.set)
            self.tree.pack()
            self.tree.heading('sum', text="总金额", anchor=tk.W)
            self.tree.heading('count', text="总次数", anchor=tk.W)
            self.tree.heading('dscrp', text="交易类型", anchor=tk.W)
            # 设置关联
            scrollBar.config(command = self.tree.yview)
            # 每次打开清空页面
            for item in self.tree.get_children():
                self.tree.delete(item)
            # sql = "select a.outid ,a.name ,b.opfare,b.opdt,b.dscrp from base_customers a,rec_cust_acc b where a.customerid = b. customerid and b.opdt >= to_date(:preopdt,'yyyy-MM-dd HH24:mi:ss') and b.opdt <= to_date(:nextopdt,'yyyy-MM-dd HH24:mi:ss') order by b.opdt desc"
            preopdt=self.pretimeInput.get()
            nextopdt=self.nexttimeInput.get()
            data = self.cursor.execute(sql,(preopdt,nextopdt)).fetchall()
            # print(data)
            for itm in data:
                self.tree.insert("",tk.END,values=itm)
                self.tree.pack(padx=10,pady=10, fill=tk.BOTH,expand=1)
    
            exportbtn = tk.Button(self.sumtail,text='导出',width=8,command=self.export).pack()
    
        # 个人明细
        def personDetail(self):
            if(self.outidInput.get()):
                print('outid not is null')
                sql="select a.outid ,a.name ,b.opfare,b.oddfare,b.opdt,b.dscrp from base_customers a,rec_cust_acc b where a.customerid = b. customerid and b.opdt >= to_date(:preopdt,'yyyy-MM-dd HH24:mi:ss') and b.opdt <= to_date(:nextopdt,'yyyy-MM-dd HH24:mi:ss') and a.outid = :outid order by b.opdt desc"
                outidname = self.outidInput.get()
            elif(self.nameInput.get()):
                sql="select a.outid ,a.name ,b.opfare,b.oddfare,b.opdt,b.dscrp from base_customers a,rec_cust_acc b where a.customerid = b. customerid and b.opdt >= to_date(:preopdt,'yyyy-MM-dd HH24:mi:ss') and b.opdt <= to_date(:nextopdt,'yyyy-MM-dd HH24:mi:ss') and a.name like :name order by b.opdt desc"
                outidname = self.nameInput.get()
            else:
                messagebox.showwarning("提示", "请输入学号或者姓名!")
                return
            self.persontail = tk.Tk()
            self.persontail.title('XX学院个人查询')
            self.persontail.geometry("1200x600")
        
            # 加载滚动条
            # exportbtn = Button(persontail,text='导出',width=8,command=export).pack()
            scrollBar = tk.Scrollbar(self.persontail)
            scrollBar.pack(side = tk.RIGHT,fill = tk.Y)
            self.tree = ttk.Treeview(self.persontail, columns=('outid', 'name', 'opfare','oddfare', 'opdt','dscrp'), show="headings", displaycolumns="#all",yscrollcommand = scrollBar.set)
            self.tree.pack()
            self.tree.heading('outid', text="学号", anchor=tk.W)
            self.tree.heading('name', text="姓名", anchor=tk.W)
            self.tree.heading('opfare', text="交易金额", anchor=tk.W)
            self.tree.heading('oddfare', text="账户余额", anchor=tk.W)
            self.tree.heading('opdt', text="交易日期", anchor=tk.W)
            self.tree.heading('dscrp', text="交易类型", anchor=tk.W)
            # 设置关联
            scrollBar.config(command = self.tree.yview)
            # 每次打开清空页面
            for item in self.tree.get_children():
                self.tree.delete(item)
            # sql = "select a.outid ,a.name ,b.opfare,b.opdt,b.dscrp from base_customers a,rec_cust_acc b where a.customerid = b. customerid and b.opdt >= to_date(:preopdt,'yyyy-MM-dd HH24:mi:ss') and b.opdt <= to_date(:nextopdt,'yyyy-MM-dd HH24:mi:ss') order by b.opdt desc"
            preopdt=self.pretimeInput.get()
            nextopdt=self.nexttimeInput.get()
            # print(outidname)
            data = self.cursor.execute(sql,(preopdt,nextopdt,outidname)).fetchall()
            # print(data)
            for itm in data:
                self.tree.insert("",tk.END,values=itm)
                self.tree.pack(padx=10,pady=10, fill=tk.BOTH,expand=1)
            def export():
            # 导出export        
                 # 打开文件夹选择对话框
                folder_path = filedialog.asksaveasfilename(title='请选择一个文件',filetypes=[("CSV", ".csv")]) 
                # 更新标签文本
                print(folder_path)
                list = []
                for row_id in self.tree.get_children():
                    list.append(self.tree.item(row_id)['values'])
                with open(f'{folder_path}.csv','w',newline='') as csvfile:
                    fieldnames = ['学号', '姓名', '交易金额','账户余额','交易日期','交易类型']
                    writer = csv.writer(csvfile)
                    writer.writerow(fieldnames)
                    writer.writerows(list)
    
            exportbtn = tk.Button(self.persontail,text='导出',width=8,command=self.export).pack()
    
    
        def export(self):
            # 导出export        
             # 打开文件夹选择对话框
            
            # 更新标签文本
            # print(folder_path)
            list = []
            columns = []
            # 获取表格内容id
            for row_id in self.tree.get_children():
                list.append(self.tree.item(row_id)['values'])
            print(len(self.tree.get_children()))   
            # 通过第一行获取列数生成标题
            # print(self.tree.item)
            if len(self.tree.get_children()) != 0:
                print('ok')
                folder_path = filedialog.asksaveasfilename(title='请选择一个文件',filetypes=[("CSV", ".csv")]) 
                for i in range(0,len(self.tree.item('I001')['values'])):
                    columns.append(self.tree.heading(column=i)['text'])
            # 导出csv
                with open(f'{folder_path}.csv','w',newline='') as csvfile:
                    fieldnames = columns
                    writer = csv.writer(csvfile)
                    writer.writerow(fieldnames)
                    writer.writerows(list)
            else:
                messagebox.showwarning("提示", "没有数据,无法导出")
                return
    
    if __name__ == "__main__":
        root = tk.Tk()
        app =  mainApp(root)
        root.mainloop()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
  • 相关阅读:
    Dextran-PEG-HSA 葡聚糖-聚乙二醇-人血清白蛋白简介
    初识数据库Mysql
    23111708[含文档+PPT+源码等]计算机毕业设计基于javaweb的旅游网站前台与后台旅景点
    Kotlin语法入门-密封类和密封接口(11)
    带讲解的自行车租赁系统,可做毕设/课设
    高性能分布式限流:Redis+Lua真香
    【Hadoop】序列化、反序列化、序列化案例实操(包括Windows本地运行,hadoop集群运行)
    【计算机网络基础实验】实验二(补充内容)路由器的配置和静态路由
    Java之线程详解(三)——多线程常用API、七种状态、优先级、Lock锁
    [Mac软件]Adobe Substance 3D Stager 2.1.4 3D场景搭建工具
  • 原文地址:https://blog.csdn.net/weixin_43205774/article/details/133985811