在員工信息管理系統中,數據校驗是保障數據準確性的核心環節,需要覆蓋必填項檢查、格式驗證、業務規則校驗等維度。以下是系統化的實現方案,結合代碼示例詳細説明如何添加和優化數據校驗功能:

一、數據校驗的核心維度

首先明確需要校驗的場景和規則:

校驗類型

校驗內容

必填項校驗

姓名、性別、身份證號、手機號等核心字段不能為空

格式校驗

身份證號(18位)、手機號(11位)、日期(YYYY-MM-DD)、數字(年齡/薪資)

業務規則校驗

年齡(18-65歲)、薪資(非負數)、入職日期(不能晚於當前日期)

唯一性校驗

身份證號、手機號在系統中唯一(可選)

二、分步實現數據校驗功能

步驟1:封裝校驗工具類(高內聚)

創建獨立的DataValidator類,集中管理所有校驗規則,便於維護和複用:

import re
from datetime import datetime

class DataValidator:
    """數據校驗工具類"""
    
    # ========== 基礎校驗 ==========
    @staticmethod
    def is_required(value):
        """檢查必填項(非空且非空白字符)"""
        return bool(value and str(value).strip())
    
    # ========== 格式校驗 ==========
    @staticmethod
    def validate_id_card(id_card):
        """驗證身份證號(18位,支持最後一位X/x)"""
        id_card = str(id_card).strip()
        # 18位身份證正則(含最後一位X/x)
        pattern = r'^[1-9]\d{5}(19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[\dXx]$'
        if not re.match(pattern, id_card):
            return False, "身份證號格式錯誤(需18位有效號碼)"
        # 可選:身份證號合法性校驗(校驗碼驗證)
        return True, ""
    
    @staticmethod
    def validate_phone(phone):
        """驗證手機號(11位,以13-9開頭)"""
        phone = str(phone).strip()
        pattern = r'^1[3-9]\d{9}$'
        if not re.match(pattern, phone):
            return False, "手機號格式錯誤(需11位有效號碼,如13800138000)"
        return True, ""
    
    @staticmethod
    def validate_date(date_str, allow_future=False):
        """驗證日期格式(YYYY-MM-DD),可選是否允許未來日期"""
        date_str = str(date_str).strip()
        try:
            date = datetime.strptime(date_str, "%Y-%m-%d")
            # 校驗入職日期不能晚於當前日期
            if not allow_future and date > datetime.now():
                return False, "日期不能晚於當前日期"
            return True, ""
        except ValueError:
            return False, "日期格式錯誤(需YYYY-MM-DD,如2024-01-01)"
    
    # ========== 業務規則校驗 ==========
    @staticmethod
    def validate_age(age):
        """驗證年齡(18-65歲)"""
        try:
            age = int(age)
            if age < 18 or age > 65:
                return False, "年齡需在18-65歲之間"
            return True, ""
        except ValueError:
            return False, "年齡需為有效數字"
    
    @staticmethod
    def validate_salary(salary):
        """驗證薪資(非負數)"""
        try:
            salary = float(salary)
            if salary < 0:
                return False, "薪資不能為負數"
            return True, ""
        except ValueError:
            return False, "薪資需為有效數字(如8000或8000.5)"
    
    # ========== 唯一性校驗(可選) ==========
    @staticmethod
    def validate_unique(field_value, field_name, employee_list, exclude_id=None):
        """
        校驗字段唯一性(如身份證號/手機號)
        :param field_value: 待校驗值
        :param field_name: 字段名(如id_card/phone)
        :param employee_list: 員工列表
        :param exclude_id: 編輯時排除當前員工ID
        :return: (是否唯一, 錯誤信息)
        """
        for emp in employee_list:
            if emp["id"] == exclude_id:
                continue
            if str(emp[field_name]).strip() == str(field_value).strip():
                field_cn = "身份證號" if field_name == "id_card" else "手機號"
                return False, f"{field_cn}已存在(員工ID:{emp['id']})"
        return True, ""
步驟2:在表單提交時添加校驗(新增/編輯場景)

在員工新增/編輯的表單窗口(EmployeeFormWindow)中,重寫submit_form方法,集成校驗邏輯:

class EmployeeFormWindow:
    def __init__(self, parent, title, db, refresh_callback, emp_data=None):
        self.parent = parent
        self.db = db
        self.refresh_callback = refresh_callback
        self.emp_data = emp_data  # 編輯時的員工數據
        self.is_edit = emp_data is not None
        # 其他初始化代碼...

    def submit_form(self):
        """提交表單(核心:先校驗,後保存)"""
        # 1. 收集表單數據
        form_data = {
            "name": self.form_vars["name"].get().strip(),
            "gender": self.form_vars["gender"].get().strip(),
            "age": self.form_vars["age"].get().strip(),
            "id_card": self.form_vars["id_card"].get().strip(),
            "phone": self.form_vars["phone"].get().strip(),
            "department": self.form_vars["department"].get().strip(),
            "position": self.form_vars["position"].get().strip(),
            "salary": self.form_vars["salary"].get().strip(),
            "hire_date": self.form_vars["hire_date"].get().strip()
        }

        # 2. 執行數據校驗
        errors = []
        
        # 2.1 必填項校驗
        required_fields = [
            ("姓名", form_data["name"]),
            ("性別", form_data["gender"]),
            ("年齡", form_data["age"]),
            ("身份證號", form_data["id_card"]),
            ("手機號", form_data["phone"]),
            ("部門", form_data["department"]),
            ("職位", form_data["position"]),
            ("薪資", form_data["salary"]),
            ("入職日期", form_data["hire_date"])
        ]
        for field_cn, value in required_fields:
            if not DataValidator.is_required(value):
                errors.append(f"{field_cn}為必填項,不能為空")
        
        # 2.2 格式/規則校驗
        # 年齡校驗
        if DataValidator.is_required(form_data["age"]):
            valid, msg = DataValidator.validate_age(form_data["age"])
            if not valid:
                errors.append(f"年齡:{msg}")
        
        # 身份證號校驗(格式+唯一性)
        if DataValidator.is_required(form_data["id_card"]):
            valid, msg = DataValidator.validate_id_card(form_data["id_card"])
            if not valid:
                errors.append(f"身份證號:{msg}")
            else:
                # 唯一性校驗(編輯時排除自身)
                exclude_id = self.emp_data["id"] if self.is_edit else None
                valid, msg = DataValidator.validate_unique(
                    form_data["id_card"], "id_card", self.db.get_all_employees(), exclude_id
                )
                if not valid:
                    errors.append(msg)
        
        # 手機號校驗(格式+唯一性)
        if DataValidator.is_required(form_data["phone"]):
            valid, msg = DataValidator.validate_phone(form_data["phone"])
            if not valid:
                errors.append(f"手機號:{msg}")
            else:
                exclude_id = self.emp_data["id"] if self.is_edit else None
                valid, msg = DataValidator.validate_unique(
                    form_data["phone"], "phone", self.db.get_all_employees(), exclude_id
                )
                if not valid:
                    errors.append(msg)
        
        # 薪資校驗
        if DataValidator.is_required(form_data["salary"]):
            valid, msg = DataValidator.validate_salary(form_data["salary"])
            if not valid:
                errors.append(f"薪資:{msg}")
        
        # 入職日期校驗(格式+不能晚於當前)
        if DataValidator.is_required(form_data["hire_date"]):
            valid, msg = DataValidator.validate_date(form_data["hire_date"], allow_future=False)
            if not valid:
                errors.append(f"入職日期:{msg}")

        # 3. 校驗失敗:提示錯誤
        if errors:
            error_msg = "表單校驗失敗:\n" + "\n".join(errors)
            messagebox.showerror("校驗失敗", error_msg)
            return

        # 4. 校驗通過:轉換數據類型並保存
        try:
            # 轉換數據類型(字符串→數字/日期)
            form_data["age"] = int(form_data["age"])
            form_data["salary"] = float(form_data["salary"])
            
            if self.is_edit:
                # 編輯模式
                self.db.update_employee(self.emp_data["id"], form_data)
                messagebox.showinfo("成功", "員工信息更新成功!")
            else:
                # 新增模式
                self.db.add_employee(form_data)
                messagebox.showinfo("成功", "員工信息新增成功!")
            
            # 刷新列表並關閉窗口
            self.refresh_callback()
            self.window.destroy()
        
        except Exception as e:
            messagebox.showerror("保存失敗", f"數據保存出錯:{str(e)}")
步驟3:批量導入時添加校驗(導入Excel/CSV場景)

在數據導入功能中,對每一行數據執行校驗,避免髒數據入庫:

class EmployeeManagementSystem:
    def import_data(self):
        """導入Excel/CSV數據(帶批量校驗)"""
        if self.user_role.get() != "管理員":
            messagebox.showwarning("權限不足", "僅管理員可導入數據")
            return
        
        # 選擇文件
        file_path = filedialog.askopenfilename(
            filetypes=[("Excel/CSV", "*.xlsx *.xls *.csv"), ("所有文件", "*.*")]
        )
        if not file_path:
            return
        
        try:
            # 讀取文件
            if file_path.endswith((".xlsx", ".xls")):
                df = pd.read_excel(file_path)
            elif file_path.endswith(".csv"):
                df = pd.read_csv(file_path, encoding="utf-8")
            else:
                messagebox.showerror("錯誤", "僅支持Excel/CSV格式")
                return
            
            # 校驗列名
            required_cols = ["name", "gender", "age", "id_card", "phone", "department", "position", "salary", "hire_date"]
            missing_cols = [col for col in required_cols if col not in df.columns]
            if missing_cols:
                messagebox.showerror("錯誤", f"缺少必填列:{', '.join(missing_cols)}")
                return
            
            # 批量校驗+導入
            success_count = 0
            fail_count = 0
            fail_details = []
            all_employees = self.db.get_all_employees()

            for row_idx, row in df.iterrows():
                row_num = row_idx + 1  # 行號(從1開始)
                row_data = row.to_dict()
                
                # 清理數據(去除空白字符)
                for key in row_data:
                    if isinstance(row_data[key], str):
                        row_data[key] = row_data[key].strip()
                
                # 執行校驗
                errors = []
                # 必填項校驗
                for col in required_cols:
                    if not DataValidator.is_required(row_data.get(col, "")):
                        errors.append(f"{self.col_cn_name(col)}不能為空")
                
                # 格式/規則校驗(同表單校驗邏輯)
                if DataValidator.is_required(row_data.get("age", "")):
                    valid, msg = DataValidator.validate_age(row_data["age"])
                    if not valid:
                        errors.append(f"年齡:{msg}")
                
                if DataValidator.is_required(row_data.get("id_card", "")):
                    valid, msg = DataValidator.validate_id_card(row_data["id_card"])
                    if not valid:
                        errors.append(f"身份證號:{msg}")
                    else:
                        valid, msg = DataValidator.validate_unique(row_data["id_card"], "id_card", all_employees)
                        if not valid:
                            errors.append(msg)
                
                # 其他字段校驗(手機號、薪資、日期...)
                # ...(同表單校驗邏輯)

                # 處理校驗結果
                if errors:
                    fail_count += 1
                    fail_details.append(f"第{row_num}行:{'; '.join(errors)}")
                    continue
                
                # 校驗通過:導入數據
                self.db.add_employee(row_data)
                success_count += 1
                all_employees = self.db.get_all_employees()  # 更新列表,保證唯一性校驗準確

            # 導入結果提示
            result_msg = f"導入完成!\n成功:{success_count}條 | 失敗:{fail_count}條"
            if fail_details:
                # 只顯示前10條失敗詳情,避免彈窗過長
                result_msg += "\n\n失敗詳情(前10條):\n" + "\n".join(fail_details[:10])
                if len(fail_details) > 10:
                    result_msg += f"\n... 還有{len(fail_details)-10}條失敗記錄未顯示"
            
            messagebox.showinfo("導入結果", result_msg)
            self.refresh_employee_list()

        except Exception as e:
            messagebox.showerror("導入失敗", f"文件處理出錯:{str(e)}")
    
    def col_cn_name(self, col):
        """列名轉中文"""
        col_map = {
            "name": "姓名", "gender": "性別", "age": "年齡", "id_card": "身份證號",
            "phone": "手機號", "department": "部門", "position": "職位",
            "salary": "薪資", "hire_date": "入職日期"
        }
        return col_map.get(col, col)
步驟4:實時校驗(提升用户體驗)

在表單輸入時添加實時校驗(如輸入手機號時即時提示格式錯誤),避免提交時才發現問題:

class EmployeeFormWindow:
    def create_form(self):
        """創建表單(添加實時校驗)"""
        # ... 原有代碼 ...
        
        # 為身份證號輸入框添加實時校驗
        id_card_entry = self.form_entries["id_card"]
        id_card_entry.bind("<FocusOut>", lambda e: self.real_time_validate("id_card"))
        
        # 為手機號輸入框添加實時校驗
        phone_entry = self.form_entries["phone"]
        phone_entry.bind("<FocusOut>", lambda e: self.real_time_validate("phone"))
        
        # 為日期輸入框添加實時校驗
        date_entry = self.form_entries["hire_date"]
        date_entry.bind("<FocusOut>", lambda e: self.real_time_validate("hire_date"))
    
    def real_time_validate(self, field):
        """實時校驗單個字段"""
        value = self.form_vars[field].get().strip()
        if not value:
            return
        
        # 根據字段類型執行校驗
        if field == "id_card":
            valid, msg = DataValidator.validate_id_card(value)
        elif field == "phone":
            valid, msg = DataValidator.validate_phone(value)
        elif field == "hire_date":
            valid, msg = DataValidator.validate_date(value)
        elif field == "age":
            valid, msg = DataValidator.validate_age(value)
        elif field == "salary":
            valid, msg = DataValidator.validate_salary(value)
        else:
            return
        
        # 實時提示(非阻塞,僅警告)
        if not valid:
            messagebox.showwarning("格式提示", f"{self.get_field_name(field)}:{msg}")

三、校驗功能的擴展與優化

  1. 自定義校驗規則
    可在DataValidator中添加更多業務規則,例如:
@staticmethod
def validate_salary_range(salary, department):
    """按部門校驗薪資範圍(如技術部薪資不低於8000)"""
    dept_salary_min = {
        "技術部": 8000,
        "人事部": 5000,
        "財務部": 6000
    }
    min_salary = dept_salary_min.get(department, 0)
    if float(salary) < min_salary:
        return False, f"{department}薪資不低於{min_salary}元"
    return True, ""
  1. 校驗結果可視化
    替代彈窗提示,可在表單字段旁添加紅色錯誤提示標籤:
# 在表單創建時添加錯誤標籤
self.error_labels = {}
for idx, (label, key, var, required) in enumerate(fields):
    # ... 原有輸入框創建代碼 ...
    # 添加錯誤提示標籤
    error_label = ttk.Label(form_frame, text="", foreground="red", font=("SimHei", 8))
    error_label.grid(row=idx, column=2, padx=5, pady=8, sticky=tk.W)
    self.error_labels[key] = error_label

# 實時校驗時更新錯誤標籤
def real_time_validate(self, field):
    # ... 原有校驗邏輯 ...
    error_label = self.error_labels.get(field)
    if error_label:
        if not valid:
            error_label.config(text=msg)
        else:
            error_label.config(text="")
  1. 批量校驗結果導出
    導入數據時,可將失敗記錄導出為Excel,方便用户修正後重新導入:
# 導入失敗時導出錯誤數據
if fail_details:
    fail_df = df.iloc[[idx for idx in range(len(fail_details))]]  # 失敗行數據
    fail_df["錯誤原因"] = fail_details
    fail_file = filedialog.asksaveasfilename(defaultextension=".xlsx", filetypes=[("Excel", "*.xlsx")])
    if fail_file:
        fail_df.to_excel(fail_file, index=False)
        messagebox.showinfo("提示", f"失敗數據已導出至:{fail_file}")

四、核心總結

數據校驗的實現遵循**「集中管理、分層校驗、實時提示、批量處理」** 原則:

  1. 集中管理:所有校驗規則封裝在DataValidator,便於維護;
  2. 分層校驗:表單提交前全量校驗,輸入時實時校驗,批量導入時逐行校驗;
  3. 用户體驗:實時提示替代阻塞式彈窗,錯誤信息具體到字段和原因;
  4. 業務適配:支持必填、格式、唯一性、業務規則等多維度校驗,可靈活擴展。

通過以上方案,可確保員工信息管理系統的數據準確性,同時兼顧用户操作體驗。