import tkinter as tk
from tkinter import ttk, messagebox
from PIL import Image, ImageTk, ImageDraw, ImageFont
import vlc
import os
import time
import threading
import queue
import random
class EmployeeClockSystem:
def __init__(self, root):
self.root = root
self.root.title("员工工牌识别打卡系统")
self.root.geometry("1200x700")
self.root.configure(bg="#f0f0f0")
# 创建样式
self.style = ttk.Style()
self.style.configure("Title.TLabel", font=("微软雅黑", 18, "bold"), foreground="#2c3e50")
self.style.configure("Subtitle.TLabel", font=("微软雅黑", 14), foreground="#34495e")
self.style.configure("Info.TLabel", font=("微软雅黑", 12), foreground="#2c3e50")
self.style.configure("Card.TFrame", background="#ffffff", borderwidth=1, relief="raised", padding=10)
self.style.configure("Control.TFrame", background="#e0e0e0", borderwidth=1, relief="sunken", padding=10)
# 主布局框架 - 使用PanedWindow实现可调整的分割
main_paned = tk.PanedWindow(root, orient=tk.HORIZONTAL, sashrelief=tk.RAISED, sashwidth=4)
main_paned.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 左侧视频区域 (50%)
left_frame = ttk.Frame(main_paned)
main_paned.add(left_frame, stretch="always")
# 右侧员工信息区域 (50%)
right_frame = ttk.Frame(main_paned)
main_paned.add(right_frame, stretch="always")
# 视频流标题
ttk.Label(left_frame, text="实时视频监控", style="Title.TLabel").pack(pady=(0, 10), anchor=tk.W, padx=10)
# 视频显示区域
video_card = ttk.Frame(left_frame, style="Card.TFrame")
video_card.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
self.video_container = ttk.Frame(video_card)
self.video_container.pack(fill=tk.BOTH, expand=True)
# 视频控制面板
control_frame = ttk.Frame(left_frame, style="Control.TFrame")
control_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
# URL输入框
ttk.Label(control_frame, text="RTSP地址:").pack(side=tk.LEFT, padx=(0, 5))
self.url_entry = ttk.Entry(control_frame, width=40)
self.url_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
self.url_entry.insert(0, "rtsp://tapocxy:123456@192.168.137.100/stream1")
# 连接按钮
self.connect_button = ttk.Button(control_frame, text="启动监控",
command=self.toggle_stream, width=12)
self.connect_button.pack(side=tk.LEFT, padx=(0, 5))
# 截图按钮
self.snapshot_button = ttk.Button(control_frame, text="抓拍",
command=self.take_snapshot, width=8, state=tk.DISABLED)
self.snapshot_button.pack(side=tk.LEFT)
# 员工信息标题
ttk.Label(right_frame, text="员工信息识别", style="Title.TLabel").pack(pady=(0, 10), anchor=tk.W, padx=10)
# 员工信息卡片
info_card = ttk.Frame(right_frame, style="Card.TFrame")
info_card.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
# 员工照片和基本信息
info_frame = ttk.Frame(info_card)
info_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 左侧员工照片区域
avatar_frame = ttk.Frame(info_frame, width=180, height=200)
avatar_frame.pack(side=tk.LEFT, padx=(0, 20), fill=tk.Y)
self.avatar_label = ttk.Label(avatar_frame)
self.avatar_label.pack(fill=tk.BOTH, expand=True)
# 默认头像
self.show_default_avatar()
# 右侧员工详细信息
detail_frame = ttk.Frame(info_frame)
detail_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
ttk.Label(detail_frame, text="员工基本信息", style="Subtitle.TLabel").pack(anchor=tk.W, pady=(0, 10))
# 信息标签 - 使用Grid布局更精确控制
label_frame = ttk.Frame(detail_frame)
label_frame.pack(fill=tk.X, pady=5)
ttk.Label(label_frame, text="姓名:", width=8, anchor=tk.E, style="Info.TLabel").grid(row=0, column=0, sticky="e", padx=5, pady=5)
self.name_value = ttk.Label(label_frame, text="", width=20, anchor=tk.W, style="Info.TLabel")
self.name_value.grid(row=0, column=1, sticky="w", padx=5, pady=5)
ttk.Label(label_frame, text="工号:", width=8, anchor=tk.E, style="Info.TLabel").grid(row=1, column=0, sticky="e", padx=5, pady=5)
self.id_value = ttk.Label(label_frame, text="", width=20, anchor=tk.W, style="Info.TLabel")
self.id_value.grid(row=1, column=1, sticky="w", padx=5, pady=5)
ttk.Label(label_frame, text="部门:", width=8, anchor=tk.E, style="Info.TLabel").grid(row=2, column=0, sticky="e", padx=5, pady=5)
self.dept_value = ttk.Label(label_frame, text="", width=20, anchor=tk.W, style="Info.TLabel")
self.dept_value.grid(row=2, column=1, sticky="w", padx=5, pady=5)
ttk.Label(label_frame, text="职位:", width=8, anchor=tk.E, style="Info.TLabel").grid(row=3, column=0, sticky="e", padx=5, pady=5)
self.position_value = ttk.Label(label_frame, text="", width=20, anchor=tk.W, style="Info.TLabel")
self.position_value.grid(row=3, column=1, sticky="w", padx=5, pady=5)
ttk.Label(label_frame, text="打卡状态:", width=8, anchor=tk.E, style="Info.TLabel").grid(row=4, column=0, sticky="e", padx=5, pady=5)
self.status_value = ttk.Label(label_frame, text="未识别", width=20, anchor=tk.W, style="Info.TLabel")
self.status_value.grid(row=4, column=1, sticky="w", padx=5, pady=5)
# 打卡按钮
button_frame = ttk.Frame(detail_frame)
button_frame.pack(fill=tk.X, pady=10)
self.clock_button = ttk.Button(button_frame, text="打卡",
command=self.clock_in, width=15, state=tk.DISABLED)
self.clock_button.pack(side=tk.RIGHT, padx=(0, 10))
# 考勤记录标题
ttk.Label(right_frame, text="今日考勤记录", style="Title.TLabel").pack(pady=(10, 10), anchor=tk.W, padx=10)
# 考勤记录表格
record_card = ttk.Frame(right_frame, style="Card.TFrame")
record_card.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
# 创建表格
columns = ("time", "id", "name", "dept", "status")
self.record_tree = ttk.Treeview(record_card, columns=columns, show="headings", height=8)
# 设置列标题
self.record_tree.heading("time", text="时间", anchor=tk.W)
self.record_tree.heading("id", text="工号", anchor=tk.W)
self.record_tree.heading("name", text="姓名", anchor=tk.W)
self.record_tree.heading("dept", text="部门", anchor=tk.W)
self.record_tree.heading("status", text="状态", anchor=tk.W)
# 设置列宽
self.record_tree.column("time", width=150, anchor=tk.W)
self.record_tree.column("id", width=100, anchor=tk.W)
self.record_tree.column("name", width=100, anchor=tk.W)
self.record_tree.column("dept", width=120, anchor=tk.W)
self.record_tree.column("status", width=80, anchor=tk.W)
# 添加滚动条
scrollbar = ttk.Scrollbar(record_card, orient="vertical", command=self.record_tree.yview)
self.record_tree.configure(yscrollcommand=scrollbar.set)
# 布局
self.record_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 状态栏
self.status_var = tk.StringVar(value="系统就绪")
status_bar = ttk.Label(root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# 初始化变量
self.stream_active = False
self.instance = vlc.Instance("--no-xlib")
self.player = self.instance.media_player_new()
# 模拟员工数据库
self.employees = {
"1001": {"name": "张明", "dept": "技术部", "position": "高级工程师", "avatar": "avatar1.jpg"},
"1002": {"name": "李华", "dept": "市场部", "position": "市场经理", "avatar": "avatar2.jpg"},
"1003": {"name": "王芳", "dept": "财务部", "position": "会计", "avatar": "avatar3.jpg"},
"1004": {"name": "赵刚", "dept": "人力资源", "position": "招聘主管", "avatar": "avatar4.jpg"},
"1005": {"name": "陈晓", "dept": "产品部", "position": "产品经理", "avatar": "avatar5.jpg"},
}
# 考勤记录
self.attendance_records = []
# 添加示例记录
self.add_sample_records()
def show_default_avatar(self):
"""显示默认头像"""
# 创建默认头像
default_img = Image.new('RGB', (180, 200), color='#3498db')
draw = ImageDraw.Draw(default_img)
# 使用更大的字体
try:
font = ImageFont.truetype("arial.ttf", 24)
except:
font = ImageFont.load_default()
draw.text((40, 85), "无数据", fill="white", font=font)
default_photo = ImageTk.PhotoImage(default_img)
self.avatar_label.config(image=default_photo)
self.avatar_label.image = default_photo
def add_sample_records(self):
"""添加示例考勤记录"""
records = [
("08:45:22", "1001", "张明", "技术部", "正常"),
("09:01:35", "1002", "李华", "市场部", "迟到"),
("09:05:47", "1003", "王芳", "财务部", "正常"),
("12:01:15", "1001", "张明", "技术部", "外出"),
("13:30:08", "1004", "赵刚", "人力资源", "正常"),
]
for record in records:
self.attendance_records.append(record)
self.record_tree.insert("", tk.END, values=record)
def toggle_stream(self):
"""切换视频流状态"""
if self.stream_active:
self.stop_stream()
else:
self.start_stream()
def start_stream(self):
"""启动视频流"""
url = self.url_entry.get().strip()
if not url:
messagebox.showerror("错误", "请输入有效的视频流URL")
return
try:
# 设置媒体
media = self.instance.media_new(url)
self.player.set_media(media)
# 获取并设置视频窗口 - 直接使用video_container的ID
win_id = self.video_container.winfo_id()
# 在Windows平台上需要特殊处理
if os.name == 'nt':
# Windows平台需要将窗口ID转换为长整型
win_id = int(win_id)
self.player.set_hwnd(win_id)
else:
# Linux/macOS平台
self.player.set_xwindow(win_id)
# 开始播放
self.player.play()
# 更新状态
self.stream_active = True
self.connect_button.config(text="停止监控")
self.snapshot_button.config(state=tk.NORMAL)
self.status_var.set(f"正在播放: {url}")
# 模拟识别过程
self.simulate_recognition()
except Exception as e:
messagebox.showerror("连接错误", f"无法连接到视频流: {str(e)}")
self.status_var.set("连接失败")
def stop_stream(self):
"""停止视频流"""
if self.player:
self.player.stop()
self.stream_active = False
self.connect_button.config(text="启动监控")
self.snapshot_button.config(state=tk.DISABLED)
self.clock_button.config(state=tk.DISABLED)
self.status_var.set("已停止视频流")
# 清空员工信息
self.name_value.config(text="")
self.id_value.config(text="")
self.dept_value.config(text="")
self.position_value.config(text="")
self.status_value.config(text="未识别")
self.show_default_avatar()
def take_snapshot(self):
"""抓拍当前帧"""
if self.stream_active:
timestamp = time.strftime("%Y%m%d_%H%M%S")
filename = f"snapshot_{timestamp}.png"
# 保存截图
self.player.video_take_snapshot(0, filename, 0, 0)
messagebox.showinfo("抓拍成功", f"已保存截图: {filename}")
self.status_var.set(f"截图已保存: {filename}")
def simulate_recognition(self):
"""模拟工牌识别过程"""
if not self.stream_active:
return
# 随机选择一名员工
emp_id, emp_info = random.choice(list(self.employees.items()))
# 更新员工信息
self.name_value.config(text=emp_info['name'])
self.id_value.config(text=emp_id)
self.dept_value.config(text=emp_info['dept'])
self.position_value.config(text=emp_info['position'])
self.status_value.config(text="待打卡")
# 显示员工照片
self.show_employee_avatar(emp_info['avatar'])
# 启用打卡按钮
self.clock_button.config(state=tk.NORMAL)
# 更新状态
self.status_var.set(f"已识别到员工: {emp_info['name']} ({emp_id})")
# 3秒后再次模拟识别
self.root.after(3000, self.simulate_recognition)
def show_employee_avatar(self, avatar_path):
"""显示员工头像"""
try:
# 在实际应用中,这里应该加载真实的员工照片
# 这里我们使用随机生成的头像
colors = ["#3498db", "#2ecc71", "#e74c3c", "#f39c12", "#9b59b6"]
color = random.choice(colors)
img = Image.new('RGB', (180, 200), color=color)
# 添加文字标识
draw = ImageDraw.Draw(img)
try:
font = ImageFont.truetype("arial.ttf", 20)
except:
font = ImageFont.load_default()
draw.text((40, 85), "员工照片", fill="white", font=font)
photo = ImageTk.PhotoImage(img)
self.avatar_label.config(image=photo)
self.avatar_label.image = photo
except Exception as e:
print(f"头像加载错误: {e}")
def clock_in(self):
"""员工打卡"""
# 获取当前时间
current_time = time.strftime("%H:%M:%S")
# 获取员工信息
emp_id = self.id_value.cget("text")
emp_name = self.name_value.cget("text")
emp_dept = self.dept_value.cget("text")
# 判断是否迟到
hour = int(time.strftime("%H"))
minute = int(time.strftime("%M"))
status = "迟到" if (hour > 9 or (hour == 9 and minute > 0)) else "正常"
# 添加考勤记录
record = (current_time, emp_id, emp_name, emp_dept, status)
self.attendance_records.append(record)
self.record_tree.insert("", tk.END, values=record)
# 更新状态
self.status_value.config(text=f"已打卡 ({status})")
self.status_var.set(f"{emp_name} 打卡成功! 时间: {current_time}")
# 禁用打卡按钮
self.clock_button.config(state=tk.DISABLED)
# 滚动到最新记录
self.record_tree.see(self.record_tree.get_children()[-1])
# 3秒后自动识别下一位员工
self.root.after(3000, self.simulate_recognition)
if __name__ == "__main__":
root = tk.Tk()
app = EmployeeClockSystem(root)
root.mainloop()
帮我把上面的代码分成两个线程,并输出最后的完整的py代码