最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于Python開發(fā)的學(xué)習(xí)通簽到數(shù)據(jù)統(tǒng)計(jì)工具

 更新時(shí)間:2026年01月26日 09:55:11   作者:.笑對(duì)人生.  
本文介紹的項(xiàng)目是一款面向?qū)W習(xí)通簽到數(shù)據(jù)的可視化統(tǒng)計(jì)工具,核心目標(biāo)是解決學(xué)習(xí)通導(dǎo)出的簽到表格手動(dòng)統(tǒng)計(jì)效率低、易出錯(cuò)的問題,下面將詳細(xì)講解學(xué)習(xí)通簽到統(tǒng)計(jì)工具開發(fā)過程以及Python代碼完整實(shí)現(xiàn),需要的朋友可以參考下

一、引言

本文介紹的項(xiàng)目是一款面向?qū)W習(xí)通簽到數(shù)據(jù)的可視化統(tǒng)計(jì)工具,核心目標(biāo)是解決學(xué)習(xí)通導(dǎo)出的簽到表格手動(dòng)統(tǒng)計(jì)效率低、易出錯(cuò)的問題。下面將詳細(xì)講解學(xué)習(xí)通簽到統(tǒng)計(jì)工具開發(fā)過程以及Python代碼完整實(shí)現(xiàn)。

二、第一階段:需求分析與功能規(guī)劃

開發(fā)前首先明確核心需求,確保工具貼合實(shí)際使用場景:

1. 核心業(yè)務(wù)需求

  • 支持導(dǎo)入單文件、多文件、整個(gè)文件夾的學(xué)習(xí)通簽到文件(Excel/CSV 格式);
  • 自動(dòng)識(shí)別文件中 “簽到狀態(tài)” 相關(guān)表頭(解決學(xué)習(xí)通導(dǎo)出文件表頭行不固定的問題);
  • 多維度統(tǒng)計(jì)簽到狀態(tài):區(qū)分 “已簽、教師代簽、遲到、未參與”,并計(jì)算 “最終簽到統(tǒng)計(jì)”(前三者算有效簽到,未參與算無效);
  • 生成單個(gè)文件的統(tǒng)計(jì)結(jié)果(帶新增統(tǒng)計(jì)列),并支持多文件匯總(按學(xué)生維度統(tǒng)計(jì)總次數(shù));
  • 處理過程中給出可視化反饋(進(jìn)度條、狀態(tài)提示),并記錄操作日志(便于排查問題)。

2. 非功能需求

  • 易用性:GUI 界面操作,無需命令行,適配普通用戶;
  • 容錯(cuò)性:處理文件時(shí)捕獲權(quán)限錯(cuò)誤、列缺失、格式錯(cuò)誤等異常,給出友好提示;
  • 兼容性:支持 Excel(.xlsx)和 CSV 格式,過濾臨時(shí)文件(如~$ 開頭的 Excel 臨時(shí)文件);
  • 可回溯:日志記錄所有操作,支持清空日志、重置所有數(shù)據(jù)。

三、第二階段:技術(shù)選型

結(jié)合需求與開發(fā)成本,選擇輕量、易部署的技術(shù)棧:

技術(shù) / 庫選型理由
Python 3.8+生態(tài)豐富,數(shù)據(jù)處理庫成熟,跨平臺(tái),打包成 exe 后無需安裝運(yùn)行環(huán)境
tkinter + ttkPython 內(nèi)置 GUI 庫,無需額外安裝,足夠支撐輕量桌面應(yīng)用;ttk 提升界面美觀度
pandas高效處理表格數(shù)據(jù),支持 Excel/CSV 讀寫、分組統(tǒng)計(jì),是 Python 表格處理的首選庫
openpyxlpandas 寫入 Excel 的依賴庫,支持.xlsx 格式,處理大數(shù)據(jù)量更穩(wěn)定
os/pathlib處理文件路徑、遍歷文件夾,跨平臺(tái)兼容
datetime生成日志時(shí)間戳,記錄操作時(shí)間
warnings過濾 openpyxl 的樣式警告,提升運(yùn)行體驗(yàn)
pyinstaller(后續(xù)打包)將代碼打包成 exe,方便非技術(shù)用戶使用

四、第三階段:架構(gòu)設(shè)計(jì)

采用面向?qū)ο螅∣OP) 設(shè)計(jì),將功能封裝為AttendanceApp類,核心優(yōu)勢是 “數(shù)據(jù)與邏輯封裝、代碼復(fù)用、便于維護(hù)”。類的核心結(jié)構(gòu)如下:

核心設(shè)計(jì)思路:

  • 全局變量file_paths存儲(chǔ)導(dǎo)入的文件路徑,summary_data緩存匯總數(shù)據(jù),避免重復(fù)計(jì)算;
  • UI 與邏輯分離create_widgets()只負(fù)責(zé) UI 渲染,業(yè)務(wù)邏輯封裝在獨(dú)立方法中;
  • 異常分層處理:批量處理時(shí)捕獲單個(gè)文件的異常,不影響其他文件處理;
  • 用戶反饋閉環(huán):操作→進(jìn)度條更新→狀態(tài)提示→日志記錄,全程可視化。

五、第四階段:分模塊詳細(xì)開發(fā)

1. 初始化與基礎(chǔ)配置(__init__方法)

def __init__(self, root):
    self.root = root
    self.root.title("學(xué)習(xí)通簽到統(tǒng)計(jì)工具")
    self.root.geometry("900x700")  # 固定窗口大小,適配主流屏幕
    # 全局變量初始化:存儲(chǔ)文件路徑和匯總數(shù)據(jù)
    self.file_paths = []
    self.summary_data = []
    # 過濾openpyxl的樣式警告(提升用戶體驗(yàn))
    warnings.filterwarnings('ignore', category=UserWarning, module='openpyxl')
    # 初始化UI
    self.create_widgets()
  • 初始化主窗口的標(biāo)題、尺寸,定義核心全局變量;
  • 過濾第三方庫的無關(guān)警告,避免日志 / 控制臺(tái)出現(xiàn)冗余信息。

2. UI 組件開發(fā)(create_widgets 方法)

按 “功能分區(qū)” 構(gòu)建 UI,確保布局清晰、操作符合用戶習(xí)慣:

組件區(qū)域核心控件與作用
頂部說明區(qū)Label 控件:顯示工具名稱、功能說明,字體放大提升辨識(shí)度
按鈕操作區(qū)Button 控件:導(dǎo)入文件(單 / 多 / 文件夾)、重置、處理文件、生成匯總表,按 “操作流程” 排列
文件列表區(qū)Listbox + Scrollbar:顯示已導(dǎo)入文件,支持滾動(dòng)查看,適配多文件場景
進(jìn)度反饋區(qū)Progressbar + Label:顯示處理進(jìn)度、當(dāng)前狀態(tài)(如 “等待導(dǎo)入文件”)
日志記錄區(qū)ScrolledText + Button:滾動(dòng)日志框(禁用手動(dòng)編輯)、清空日志按鈕

關(guān)鍵實(shí)現(xiàn)細(xì)節(jié):

  • 按鈕區(qū)使用ttk.Frame包裹,通過side=tk.LEFT/RIGHT排列,保證布局整齊;
  • 日志框設(shè)置為tk.DISABLED狀態(tài),避免用戶誤編輯,僅通過log_message方法寫入;
  • 所有控件的pack方法添加padx/pady(內(nèi)邊距),提升界面美觀度。

3. 日志系統(tǒng)開發(fā)(log_message/clear_log 方法)

日志是工具的 “調(diào)試與追溯” 核心,設(shè)計(jì)要點(diǎn):

def log_message(self, message, level="INFO"):
    timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")  # 精確到秒的時(shí)間戳
    log_entry = f"[{timestamp}] [{level}] {message}\n"  # 標(biāo)準(zhǔn)化日志格式
    # 臨時(shí)啟用日志框,寫入內(nèi)容后禁用
    self.log_text.config(state=tk.NORMAL)
    self.log_text.insert(tk.END, log_entry)
    self.log_text.see(tk.END)  # 自動(dòng)滾動(dòng)到最新日志
    self.log_text.config(state=tk.DISABLED)
  • 日志包含 “時(shí)間戳 + 級(jí)別 + 內(nèi)容”,級(jí)別分為 INFO/WARNING/ERROR,便于區(qū)分操作類型;
  • 自動(dòng)滾動(dòng)到最新日志,無需用戶手動(dòng)翻頁;
  • clear_log方法提供日志清空功能,同時(shí)記錄 “日志已清空” 的操作,保證日志鏈完整。

4. 文件導(dǎo)入模塊開發(fā)

解決 “如何便捷導(dǎo)入文件并過濾無效文件” 的問題,核心是 3 個(gè)導(dǎo)入方法 + 1 個(gè)輔助方法:

(1)臨時(shí)文件過濾(is_temp_file)

學(xué)習(xí)通導(dǎo)出文件時(shí)可能生成臨時(shí)文件(如~$xxx.xlsx),需過濾避免處理錯(cuò)誤:

def is_temp_file(self, file_path):
    filename = os.path.basename(file_path)
    return filename.startswith('~$') or filename.startswith('.')  # 過濾Excel臨時(shí)文件/隱藏文件

(2)單文件 / 批量 / 文件夾導(dǎo)入

  • import_single_file:調(diào)用filedialog.askopenfilename選擇單個(gè)文件,校驗(yàn)后加入列表;
  • import_multiple_files:調(diào)用askopenfilenames選擇多文件,批量校驗(yàn)、統(tǒng)計(jì)導(dǎo)入 / 跳過數(shù)量;
  • import_folder:調(diào)用askdirectory選擇文件夾,通過os.walk遍歷所有 Excel/CSV 文件,批量導(dǎo)入;
  • 所有導(dǎo)入方法最終調(diào)用update_file_list更新 Listbox 顯示,并通過日志反饋導(dǎo)入結(jié)果。

關(guān)鍵設(shè)計(jì):

  • 校驗(yàn)文件是否已導(dǎo)入(避免重復(fù))、是否為臨時(shí)文件(避免無效處理);
  • 統(tǒng)計(jì) “成功導(dǎo)入 / 跳過” 數(shù)量,通過日志和狀態(tài)標(biāo)簽反饋,提升透明度。

5. 核心數(shù)據(jù)處理模塊開發(fā)

這是工具的 “業(yè)務(wù)核心”,解決 “如何解析學(xué)習(xí)通文件并統(tǒng)計(jì)數(shù)據(jù)” 的問題,分為 3 個(gè)核心方法:

(1)動(dòng)態(tài)查找表頭行(find_header_row)

學(xué)習(xí)通導(dǎo)出的簽到文件表頭行不固定(可能第 1 行是標(biāo)題,第 2 行是表頭),需動(dòng)態(tài)定位包含 “簽到狀態(tài)” 的行:

def find_header_row(self, df):
    for i, row in df.iterrows():
        if '簽到狀態(tài)' in str(row.values):  # 遍歷每行,查找包含目標(biāo)字段的行
            return i
    return None  # 未找到則返回None,后續(xù)拋出異常
  • 先以header=None讀取整個(gè)文件(無表頭),遍歷所有行查找 “簽到狀態(tài)”;
  • 找到后再以該行為表頭重新讀取文件,確保列名正確。

(2)單個(gè)文件處理(process_single_file)

這是數(shù)據(jù)處理的核心,步驟拆解:

  1. 讀取文件:根據(jù)后綴(xlsx/csv)選擇pd.read_excel/read_csv,先讀取全量數(shù)據(jù)找表頭;
  2. 校驗(yàn)表頭:若未找到 “簽到狀態(tài)” 行,拋出ValueError;
  3. 重新讀取 + 列名清理:以找到的表頭行重新讀取,清理列名空格(避免 “姓名” 和 “姓名” 被識(shí)別為不同列);
  4. 關(guān)鍵列校驗(yàn):檢查是否包含['姓名', '學(xué)號(hào)/工號(hào)', '簽到狀態(tài)']等必填列,缺失則拋出KeyError;
  5. 多維度統(tǒng)計(jì)
# 已簽統(tǒng)計(jì):僅“已簽”算1,其他狀態(tài)算0
df['已簽統(tǒng)計(jì)'] = df['簽到狀態(tài)'].apply(lambda x: 1 if str(x).strip() == '已簽' else 0 if str(x).strip() in ['教師代簽', '遲到', '未參與'] else None)
# 最終簽到統(tǒng)計(jì):已簽/教師代簽/遲到算1,未參與算0(核心統(tǒng)計(jì)維度)
df['最終簽到統(tǒng)計(jì)'] = df['簽到狀態(tài)'].apply(lambda x: 1 if str(x).strip() in ['已簽', '教師代簽', '遲到'] else 0 if str(x).strip() == '未參與' else None)
  • 每個(gè)統(tǒng)計(jì)列僅針對(duì)目標(biāo)狀態(tài)標(biāo) 1,其他有效狀態(tài)標(biāo) 0,未知狀態(tài)標(biāo) None(便于后續(xù)排查異常);
  1. 保存統(tǒng)計(jì)結(jié)果
    • 優(yōu)先保存到原文件同目錄(命名為 “原文件名_統(tǒng)計(jì)結(jié)果.xlsx”);
    • 若原路徑無權(quán)限(如文件在只讀文件夾),彈出保存對(duì)話框讓用戶選擇路徑;
  2. 緩存匯總數(shù)據(jù):將當(dāng)前文件的統(tǒng)計(jì)結(jié)果存入summary_data,為后續(xù)匯總表做準(zhǔn)備。

(3)批量處理文件(process_files)

實(shí)現(xiàn) “批量處理 + 異常隔離 + 進(jìn)度反饋”:

  1. 校驗(yàn)是否有導(dǎo)入文件,無則提示并返回;
  2. 初始化進(jìn)度條(最大值為文件數(shù)量),重置匯總數(shù)據(jù);
  3. 遍歷所有文件,逐個(gè)調(diào)用process_single_file;
  4. 異常處理:捕獲PermissionError(文件被占用)、KeyError(列缺失)、通用Exception,記錄失敗數(shù),不中斷批量處理;
  5. 實(shí)時(shí)更新進(jìn)度條(self.root.update_idletasks()強(qiáng)制刷新 UI);
  6. 處理完成后,通過彈窗 + 日志反饋 “成功 / 失敗數(shù)量”。

6. 匯總表生成模塊(generate_summary_button)

解決 “多文件按學(xué)生維度匯總” 的需求,核心邏輯:

  1. 校驗(yàn)summary_data是否為空(無處理結(jié)果則提示);
  2. 將匯總數(shù)據(jù)轉(zhuǎn)為 DataFrame,按['姓名', '學(xué)號(hào)/工號(hào)', '學(xué)校', '院系', '專業(yè)', '行政班級(jí)']分組(確保學(xué)生唯一);
  3. 對(duì)所有統(tǒng)計(jì)列求和(agg({'已簽統(tǒng)計(jì)':'sum', ...})),并重命名為 “總 XXX 統(tǒng)計(jì)”;
  4. 彈出保存對(duì)話框,將匯總結(jié)果保存為 Excel 文件;
  5. 反饋匯總結(jié)果(統(tǒng)計(jì)學(xué)生數(shù)量、保存路徑)。

7. 重置功能(reset_all)

支持用戶 “清空所有數(shù)據(jù)重新操作”,設(shè)計(jì)要點(diǎn):

  • 彈出確認(rèn)對(duì)話框(避免誤操作);
  • 清空全局變量(file_paths/summary_data)、文件列表、進(jìn)度條、狀態(tài)標(biāo)簽;
  • 記錄重置日志,反饋重置完成。

六、第五階段:測試與優(yōu)化

開發(fā)完成后,需覆蓋多場景測試,修復(fù)問題并優(yōu)化體驗(yàn):

1. 核心測試場景

測試場景測試目的測試結(jié)果
導(dǎo)入單個(gè) Excel/CSV 文件驗(yàn)證文件讀取、表頭查找、統(tǒng)計(jì)是否正確統(tǒng)計(jì)列生成正常,文件保存成功
導(dǎo)入臨時(shí)文件(~$xxx.xlsx)驗(yàn)證臨時(shí)文件過濾邏輯自動(dòng)跳過,日志提示 “跳過臨時(shí)文件”
導(dǎo)入缺失 “簽到狀態(tài)” 列的文件驗(yàn)證列校驗(yàn)、異常提示彈出錯(cuò)誤提示,日志記錄 KeyError
導(dǎo)入被占用的文件驗(yàn)證權(quán)限錯(cuò)誤處理、保存對(duì)話框彈出提示權(quán)限錯(cuò)誤,彈出保存對(duì)話框
批量導(dǎo)入 10 + 文件驗(yàn)證進(jìn)度條、批量處理、異常隔離進(jìn)度條實(shí)時(shí)更新,失敗文件不影響其他
生成匯總表驗(yàn)證分組求和、列重命名、保存匯總數(shù)據(jù)正確,列名符合預(yù)期
重置所有數(shù)據(jù)驗(yàn)證數(shù)據(jù)清空、UI 重置所有數(shù)據(jù) / UI 恢復(fù)初始狀態(tài)

2. 關(guān)鍵優(yōu)化點(diǎn)

  • 過濾 openpyxl 的樣式警告(避免日志冗余);
  • 日志自動(dòng)滾動(dòng)到最新條目(無需手動(dòng)翻頁);
  • 進(jìn)度條實(shí)時(shí)刷新(update_idletasks強(qiáng)制 UI 更新);
  • 列名清理(col.strip()):解決學(xué)習(xí)通文件列名帶空格的問題;
  • 多維度統(tǒng)計(jì):滿足用戶對(duì)不同簽到狀態(tài)的細(xì)分統(tǒng)計(jì)需求。

七、學(xué)習(xí)通簽到統(tǒng)計(jì)工具的Python代碼完整實(shí)現(xiàn)

import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import pandas as pd
import os
from pathlib import Path
import datetime
import warnings

class AttendanceApp:
    def __init__(self, root):
        self.root = root
        self.root.title("學(xué)習(xí)通簽到統(tǒng)計(jì)工具")
        self.root.geometry("900x700")

        # 全局變量
        self.file_paths = []
        self.summary_data = []

        # 過濾openpyxl的樣式警告
        warnings.filterwarnings('ignore', category=UserWarning, module='openpyxl')

        # 創(chuàng)建UI組件
        self.create_widgets()

    def create_widgets(self):
        # 頂部說明
        ttk.Label(self.root, text="學(xué)習(xí)通簽到統(tǒng)計(jì)工具", font=("微軟雅黑", 16)).pack(pady=10)
        ttk.Label(self.root, text="支持CSV/Excel文件導(dǎo)入,自動(dòng)統(tǒng)計(jì)簽到次數(shù)").pack(pady=5)

        # 按鈕區(qū)域
        button_frame = ttk.Frame(self.root)
        button_frame.pack(pady=10, fill=tk.X, padx=20)

        ttk.Button(button_frame, text="導(dǎo)入單個(gè)文件", command=self.import_single_file).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="批量導(dǎo)入文件", command=self.import_multiple_files).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="導(dǎo)入文件夾", command=self.import_folder).pack(side=tk.LEFT, padx=5)
        # 重置按鈕
        ttk.Button(button_frame, text="重置所有", command=self.reset_all).pack(side=tk.RIGHT, padx=5)
        ttk.Button(button_frame, text="處理選中文件", command=self.process_files).pack(side=tk.RIGHT, padx=5)
        # padx參數(shù)移到pack方法中(ttk.Button初始化不支持padx)
        ttk.Button(button_frame, text="生成匯總表", command=self.generate_summary_button).pack(side=tk.RIGHT, padx=10)

        # 文件列表顯示
        file_frame = ttk.Frame(self.root)
        file_frame.pack(pady=5, fill=tk.BOTH, expand=True, padx=20)

        ttk.Label(file_frame, text="已導(dǎo)入文件列表:").pack(anchor=tk.W)
        self.file_listbox = tk.Listbox(file_frame, height=8)
        self.file_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 滾動(dòng)條
        file_scroll = ttk.Scrollbar(file_frame, orient=tk.VERTICAL, command=self.file_listbox.yview)
        file_scroll.pack(side=tk.RIGHT, fill=tk.Y)
        self.file_listbox.config(yscrollcommand=file_scroll.set)

        # 進(jìn)度條
        self.progress = ttk.Progressbar(self.root, orient=tk.HORIZONTAL, length=400, mode='determinate')
        self.progress.pack(pady=5)

        # 狀態(tài)標(biāo)簽
        self.status_label = ttk.Label(self.root, text="等待導(dǎo)入文件...")
        self.status_label.pack(pady=5)

        # 日志區(qū)域
        log_frame = ttk.Frame(self.root)
        log_frame.pack(pady=5, fill=tk.BOTH, expand=True, padx=20)

        ttk.Label(log_frame, text="操作日志:").pack(anchor=tk.W)
        self.log_text = scrolledtext.ScrolledText(log_frame, height=15, state=tk.DISABLED, wrap=tk.WORD)
        self.log_text.pack(fill=tk.BOTH, expand=True)

        # 清空日志按鈕
        ttk.Button(log_frame, text="清空日志", command=self.clear_log).pack(side=tk.RIGHT, pady=5)

    def log_message(self, message, level="INFO"):
        """添加日志信息"""
        # 生成時(shí)間戳
        timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        # 格式化日志內(nèi)容
        log_entry = f"[{timestamp}] [{level}] {message}\n"

        # 啟用文本框,添加內(nèi)容,然后禁用
        self.log_text.config(state=tk.NORMAL)
        self.log_text.insert(tk.END, log_entry)
        # 自動(dòng)滾動(dòng)到最后
        self.log_text.see(tk.END)
        self.log_text.config(state=tk.DISABLED)

    def clear_log(self):
        """清空日志"""
        self.log_text.config(state=tk.NORMAL)
        self.log_text.delete(1.0, tk.END)
        self.log_text.config(state=tk.DISABLED)
        self.log_message("日志已清空", "INFO")

    def reset_all(self):
        """重置所有狀態(tài)(重新統(tǒng)計(jì))"""
        # 確認(rèn)重置操作
        if messagebox.askyesno("確認(rèn)重置", "是否確定重置所有數(shù)據(jù)?這將清空已導(dǎo)入的文件和統(tǒng)計(jì)數(shù)據(jù)!"):
            # 清空全局變量
            self.file_paths = []
            self.summary_data = []
            # 清空文件列表
            self.file_listbox.delete(0, tk.END)
            # 重置進(jìn)度條
            self.progress['value'] = 0
            # 重置狀態(tài)標(biāo)簽
            self.status_label.config(text="等待導(dǎo)入文件...")
            # 日志記錄
            self.log_message("已重置所有數(shù)據(jù):清空文件列表和匯總數(shù)據(jù)", "WARNING")
            messagebox.showinfo("重置完成", "所有數(shù)據(jù)已重置,可重新導(dǎo)入文件進(jìn)行統(tǒng)計(jì)")

    def import_single_file(self):
        """導(dǎo)入單個(gè)文件"""
        file_path = filedialog.askopenfilename(
            filetypes=[("Excel文件", "*.xlsx"), ("CSV文件", "*.csv")]
        )
        if file_path and file_path not in self.file_paths and not self.is_temp_file(file_path):
            self.file_paths.append(file_path)
            self.update_file_list()
            self.log_message(f"成功導(dǎo)入單個(gè)文件:{os.path.basename(file_path)}")
        elif self.is_temp_file(file_path):
            self.log_message(f"跳過臨時(shí)文件:{os.path.basename(file_path)}", "WARNING")

    def import_multiple_files(self):
        """批量導(dǎo)入多個(gè)文件"""
        files = filedialog.askopenfilenames(
            filetypes=[("Excel文件", "*.xlsx"), ("CSV文件", "*.csv")]
        )
        imported_count = 0
        skipped_count = 0
        for file in files:
            if file not in self.file_paths and not self.is_temp_file(file):
                self.file_paths.append(file)
                imported_count += 1
            elif self.is_temp_file(file):
                skipped_count += 1
        self.update_file_list()
        self.log_message(f"批量導(dǎo)入完成:成功導(dǎo)入{imported_count}個(gè)文件,跳過{skipped_count}個(gè)臨時(shí)文件")

    def import_folder(self):
        """導(dǎo)入整個(gè)文件夾"""
        folder_path = filedialog.askdirectory()
        if folder_path:
            imported_count = 0
            skipped_count = 0
            for root, _, files in os.walk(folder_path):
                for file in files:
                    if file.endswith(('.xlsx', '.csv')):
                        file_path = os.path.join(root, file)
                        if file_path not in self.file_paths and not self.is_temp_file(file_path):
                            self.file_paths.append(file_path)
                            imported_count += 1
                        elif self.is_temp_file(file_path):
                            skipped_count += 1
            self.update_file_list()
            self.log_message(f"文件夾導(dǎo)入完成:從{folder_path}導(dǎo)入{imported_count}個(gè)文件,跳過{skipped_count}個(gè)臨時(shí)文件")

    def is_temp_file(self, file_path):
        """判斷是否是臨時(shí)文件(包含~或隱藏文件)"""
        filename = os.path.basename(file_path)
        return filename.startswith('~$') or filename.startswith('.')

    def update_file_list(self):
        """更新文件列表顯示"""
        self.file_listbox.delete(0, tk.END)
        for file in self.file_paths:
            self.file_listbox.insert(tk.END, os.path.basename(file))
        self.status_label.config(text=f"已導(dǎo)入 {len(self.file_paths)} 個(gè)文件")

    def process_files(self):
        """處理所有導(dǎo)入的文件(僅生成單個(gè)文件統(tǒng)計(jì)結(jié)果,不匯總)"""
        if not self.file_paths:
            messagebox.showwarning("警告", "請(qǐng)先導(dǎo)入文件!")
            self.log_message("處理文件失?。何磳?dǎo)入任何文件", "ERROR")
            return

        self.progress['value'] = 0
        self.progress['maximum'] = len(self.file_paths)
        self.summary_data = []  # 重置匯總數(shù)據(jù)

        self.log_message(f"開始處理{len(self.file_paths)}個(gè)文件...")

        success_count = 0
        fail_count = 0

        for i, file_path in enumerate(self.file_paths):
            try:
                self.log_message(f"正在處理文件:{os.path.basename(file_path)}")
                self.process_single_file(file_path)
                self.progress['value'] = i + 1
                self.root.update_idletasks()
                success_count += 1
                self.log_message(f"文件處理成功:{os.path.basename(file_path)}")
            except PermissionError:
                fail_count += 1
                error_msg = f"權(quán)限錯(cuò)誤:無法訪問文件{os.path.basename(file_path)},請(qǐng)確保文件未被打開且有讀寫權(quán)限"
                messagebox.showerror("權(quán)限錯(cuò)誤", error_msg)
                self.log_message(error_msg, "ERROR")
            except KeyError as e:
                fail_count += 1
                error_msg = f"列名錯(cuò)誤:文件{os.path.basename(file_path)}缺少關(guān)鍵列{str(e)}"
                messagebox.showerror("列名錯(cuò)誤", error_msg)
                self.log_message(error_msg, "ERROR")
            except Exception as e:
                fail_count += 1
                error_msg = f"處理文件{os.path.basename(file_path)}時(shí)出錯(cuò):{str(e)}"
                messagebox.showerror("錯(cuò)誤", error_msg)
                self.log_message(error_msg, "ERROR")

        self.progress['value'] = 0
        self.status_label.config(text=f"文件處理完成:成功{success_count}個(gè),失敗{fail_count}個(gè)")
        self.log_message(f"文件處理結(jié)束:成功{success_count}個(gè),失敗{fail_count}個(gè)")

        if success_count > 0:
            messagebox.showinfo("完成",
                                f"文件處理完成!成功{success_count}個(gè),失敗{fail_count}個(gè)\n可點(diǎn)擊'生成匯總表'按鈕創(chuàng)建匯總文件")

    def find_header_row(self, df):
        """動(dòng)態(tài)查找包含'簽到狀態(tài)'的表頭行"""
        for i, row in df.iterrows():
            if '簽到狀態(tài)' in str(row.values):
                return i
        return None

    def process_single_file(self, file_path):
        """處理單個(gè)簽到文件"""
        # 讀取整個(gè)文件,用于動(dòng)態(tài)檢測表頭
        if file_path.endswith('.xlsx'):
            full_df = pd.read_excel(file_path, header=None)
        elif file_path.endswith('.csv'):
            full_df = pd.read_csv(file_path, header=None)

        # 動(dòng)態(tài)查找表頭行
        header_row = self.find_header_row(full_df)
        if header_row is None:
            raise ValueError("未找到包含'簽到狀態(tài)'的表頭行,請(qǐng)確認(rèn)文件格式")

        # 用找到的表頭行重新讀取文件
        if file_path.endswith('.xlsx'):
            df = pd.read_excel(file_path, header=header_row)
        elif file_path.endswith('.csv'):
            df = pd.read_csv(file_path, header=header_row)

        # 清理列名中的空格和特殊字符
        df.columns = [col.strip() for col in df.columns]

        # 驗(yàn)證關(guān)鍵列是否存在
        required_columns = ['姓名', '學(xué)號(hào)/工號(hào)', '學(xué)校', '院系', '專業(yè)', '行政班級(jí)', '簽到狀態(tài)']
        for col in required_columns:
            if col not in df.columns:
                raise KeyError(col)

        # 生成已簽統(tǒng)計(jì)
        df['已簽統(tǒng)計(jì)'] = df['簽到狀態(tài)'].apply(
            lambda x: 1 if str(x).strip() == '已簽' else 0 if str(x).strip() in ['教師代簽', '遲到', '未參與'] else None
        )
        # 生成教師代簽統(tǒng)計(jì)
        df['教師代簽統(tǒng)計(jì)'] = df['簽到狀態(tài)'].apply(
            lambda x: 1 if str(x).strip() == '教師代簽' else 0 if str(x).strip() in ['已簽', '遲到', '未參與'] else None
        )
        # 生成遲到統(tǒng)計(jì)
        df['遲到統(tǒng)計(jì)'] = df['簽到狀態(tài)'].apply(
            lambda x: 1 if str(x).strip() == '遲到' else 0 if str(x).strip() in ['已簽', '教師代簽', '未參與'] else None
        )
        # 生成未參與統(tǒng)計(jì)
        df['未參與統(tǒng)計(jì)'] = df['簽到狀態(tài)'].apply(
            lambda x: 1 if str(x).strip() == '未參與' else 0 if str(x).strip() in ['已簽', '教師代簽', '遲到'] else None
        )
        # 生成最終簽到統(tǒng)計(jì)(已簽/教師代簽/遲到都算1,未參與算0)
        df['最終簽到統(tǒng)計(jì)'] = df['簽到狀態(tài)'].apply(
            lambda x: 1 if str(x).strip() in ['已簽', '教師代簽', '遲到'] else 0 if str(x).strip() == '未參與' else None
        )

        # 保存處理后的文件
        try:
            output_path = f"{os.path.splitext(file_path)[0]}_統(tǒng)計(jì)結(jié)果.xlsx"
            df.to_excel(output_path, index=False)
            self.log_message(f"統(tǒng)計(jì)文件已保存:{os.path.basename(output_path)}")
        except PermissionError:
            # 如果原路徑無法寫入,讓用戶選擇保存位置
            self.log_message(f"原路徑無寫入權(quán)限,彈出保存對(duì)話框:{os.path.basename(file_path)}", "WARNING")
            output_path = filedialog.asksaveasfilename(
                defaultextension=".xlsx",
                initialfile=f"{os.path.splitext(os.path.basename(file_path))[0]}_統(tǒng)計(jì)結(jié)果.xlsx",
                filetypes=[("Excel文件", "*.xlsx")]
            )
            if output_path:
                df.to_excel(output_path, index=False)
                self.log_message(f"統(tǒng)計(jì)文件已保存到指定位置:{os.path.basename(output_path)}")
            else:
                raise PermissionError("用戶取消了文件保存操作")

        # 收集匯總數(shù)據(jù)(包含所有需要的字段)
        file_name = os.path.basename(file_path)
        for _, row in df.iterrows():
            self.summary_data.append({
                '姓名': row['姓名'],
                '學(xué)號(hào)/工號(hào)': row['學(xué)號(hào)/工號(hào)'],
                '學(xué)校': row['學(xué)校'],
                '院系': row['院系'],
                '專業(yè)': row['專業(yè)'],
                '行政班級(jí)': row['行政班級(jí)'],
                '文件名': file_name,
                '已簽統(tǒng)計(jì)': row['已簽統(tǒng)計(jì)'],
                '教師代簽統(tǒng)計(jì)': row['教師代簽統(tǒng)計(jì)'],
                '遲到統(tǒng)計(jì)': row['遲到統(tǒng)計(jì)'],
                '未參與統(tǒng)計(jì)': row['未參與統(tǒng)計(jì)'],
                '最終簽到統(tǒng)計(jì)': row['最終簽到統(tǒng)計(jì)']
            })

    def generate_summary_button(self):
        """獨(dú)立的匯總表生成按鈕處理函數(shù)"""
        if not self.summary_data:
            messagebox.showwarning("警告", "暫無匯總數(shù)據(jù)!請(qǐng)先處理文件")
            self.log_message("生成匯總表失敗:暫無匯總數(shù)據(jù)", "WARNING")
            return

        self.log_message("開始生成匯總表...")

        try:
            summary_df = pd.DataFrame(self.summary_data)
            # 按分組對(duì)所有統(tǒng)計(jì)列求和
            final_summary = summary_df.groupby(
                ['姓名', '學(xué)號(hào)/工號(hào)', '學(xué)校', '院系', '專業(yè)', '行政班級(jí)']
            ).agg({
                '已簽統(tǒng)計(jì)': 'sum',
                '教師代簽統(tǒng)計(jì)': 'sum',
                '遲到統(tǒng)計(jì)': 'sum',
                '未參與統(tǒng)計(jì)': 'sum',
                '最終簽到統(tǒng)計(jì)': 'sum'
            }).reset_index()

            # 重命名列名為"總XXX統(tǒng)計(jì)"
            final_summary.rename(columns={
                '已簽統(tǒng)計(jì)': '總已簽統(tǒng)計(jì)',
                '教師代簽統(tǒng)計(jì)': '總教師代簽統(tǒng)計(jì)',
                '遲到統(tǒng)計(jì)': '總遲到統(tǒng)計(jì)',
                '未參與統(tǒng)計(jì)': '總未參與統(tǒng)計(jì)',
                '最終簽到統(tǒng)計(jì)': '總最終簽到統(tǒng)計(jì)'
            }, inplace=True)

            # 保存匯總表
            save_path = filedialog.asksaveasfilename(
                defaultextension=".xlsx",
                initialfile="學(xué)習(xí)通簽到匯總表.xlsx",
                filetypes=[("Excel文件", "*.xlsx")]
            )

            if save_path:
                final_summary.to_excel(save_path, index=False)
                self.log_message(f"匯總表生成成功:{os.path.basename(save_path)}")
                self.log_message(f"匯總數(shù)據(jù):共統(tǒng)計(jì){len(final_summary)}名學(xué)生的簽到情況,包含多維度簽到統(tǒng)計(jì)")
                messagebox.showinfo("成功",
                                    f"匯總表已保存!\n文件路徑:{save_path}\n共統(tǒng)計(jì){len(final_summary)}名學(xué)生\n包含:總已簽、總教師代簽、總遲到、總未參與、總最終簽到統(tǒng)計(jì)")
            else:
                self.log_message("用戶取消了匯總表保存操作", "WARNING")

        except Exception as e:
            error_msg = f"生成匯總表時(shí)出錯(cuò):{str(e)}"
            messagebox.showerror("錯(cuò)誤", error_msg)
            self.log_message(error_msg, "ERROR")

if __name__ == "__main__":
    root = tk.Tk()
    app = AttendanceApp(root)
    root.mainloop()

八、程序運(yùn)行部分截圖展示

九、打包項(xiàng)目

步驟 1:創(chuàng)建并激活純凈虛擬環(huán)境打開 Anaconda Prompt,執(zhí)行以下命令(復(fù)制粘貼即可)

# 1. 創(chuàng)建僅包含Python 3.10的純凈環(huán)境(兼容性最佳,適配tkinter/pandas)
conda create -n attendance_tool python=3.10 -y
 
# 2. 激活該環(huán)境
conda activate attendance_tool
 
# 3. 僅安裝必需依賴(無任何冗余,清華源加速)
pip install pandas openpyxl pyinstaller -i https://pypi.tuna.tsinghua.edu.cn/simple/

步驟 2:切換目錄并快速打包

# 切換到你的學(xué)習(xí)通簽到工具代碼所在文件夾(替換為實(shí)際路徑)
cd C:\Users\ABC\PycharmProjects\AttendanceTool
 
# 用目錄模式打包(-D),速度快、體積小、穩(wěn)定性高;-w隱藏控制臺(tái)(GUI工具必備)
pyinstaller -w -n 學(xué)習(xí)通簽到統(tǒng)計(jì)工具 -D 學(xué)習(xí)通簽到統(tǒng)計(jì)工具.py

步驟 3:驗(yàn)證結(jié)果

打包時(shí)間:僅需 1-2 分鐘即可完成;
exe 位置:dist / 學(xué)習(xí)通簽到統(tǒng)計(jì)工具 / 目錄下的學(xué)習(xí)通簽到統(tǒng)計(jì)工具.exe;
體積:約 60-90MB(對(duì)比全局環(huán)境打包的 1GB+,大幅精簡);
功能:雙擊 exe,測試文件導(dǎo)入、簽到統(tǒng)計(jì)、匯總表生成等核心功能,和原代碼完全一致。

十、總結(jié)

本文介紹了一款基于Python開發(fā)的學(xué)習(xí)通簽到數(shù)據(jù)統(tǒng)計(jì)工具,該工具通過GUI界面實(shí)現(xiàn)高效自動(dòng)化處理,主要解決手動(dòng)統(tǒng)計(jì)Excel/CSV簽到表格效率低、易出錯(cuò)的問題。工具核心功能包括:支持單文件/多文件/文件夾導(dǎo)入;自動(dòng)識(shí)別動(dòng)態(tài)表頭;多維度統(tǒng)計(jì)簽到狀態(tài)(已簽、教師代簽、遲到、未參與);生成帶統(tǒng)計(jì)列的單個(gè)文件和匯總表。采用tkinter+ttk構(gòu)建界面,pandas處理數(shù)據(jù),具有異常處理、日志記錄和進(jìn)度反饋機(jī)制。經(jīng)過測試驗(yàn)證,工具能準(zhǔn)確處理學(xué)習(xí)通導(dǎo)出的各類簽到文件,并通過pyinstaller打包為60-90MB的獨(dú)立exe程序,方便非技術(shù)用戶使用。

以上就是基于Python開發(fā)的學(xué)習(xí)通簽到數(shù)據(jù)統(tǒng)計(jì)工具的詳細(xì)內(nèi)容,更多關(guān)于Python學(xué)習(xí)通簽到統(tǒng)計(jì)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論

基隆市| 锡林郭勒盟| 喀什市| 万源市| 南投县| 天等县| 昔阳县| 尚义县| 家居| 通辽市| 凤城市| 江陵县| 汾西县| 宜兴市| 津南区| 乌拉特前旗| 威海市| 波密县| 昭通市| 高阳县| 高尔夫| 平凉市| 芦山县| 通城县| 琼海市| 武汉市| 临清市| 东阿县| 龙泉市| 汽车| 巴南区| 上思县| 岫岩| 龙山县| 双辽市| 伊通| 龙海市| 古蔺县| 台中市| 金坛市| 明溪县|