這是一個基于Flask和PyQt的排班系統,可以將Web界面嵌入到桌面應用程序中。
系統界面:
功能特點:
- 讀取員工信息和現有排班表
- 自動生成排班表
- 美觀的Web界面
- 獨立的桌面應用程序
整體架構:
系統采用前后端分離的架構設計,通過 PyQt5 的?WebEngine 組件將?Web 界面嵌入到桌面應用中。
├──?桌面應用層?(PyQt5)
│???└──?WebEngine?視圖
├──?Web?層?(Flask)
│???├──?路由控制
│???└──?業務邏輯
└──?數據層
????├──?CSV?數據文件
????└──?Excel?導出
核心模塊:
主程序模塊?(main.py)
- 負責初始化 PyQt5 應用
- 集成 Flask 服務器
- 管理主窗口和?Web 視圖
后端服務模塊?(app.py)
- 提供 RESTful API
- 處理排班算法
- 管理數據導入導出
前端界面模塊 (templates/index.html)
- 員工列表管理
- 排班表顯示
- 用戶交互處理
核心代碼:main.py
import sys
import time
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtCore import QUrl
from flask import Flask
import threading
import osclass MainWindow(QMainWindow):def __init__(self):super().__init__()self.setWindowTitle("排班系統")self.setGeometry(100, 100, 1200, 800)# 創建中心部件central_widget = QWidget()self.setCentralWidget(central_widget)layout = QVBoxLayout(central_widget)# 創建Web視圖self.web_view = QWebEngineView()layout.addWidget(self.web_view)# 啟動Flask服務器self.start_flask_server()# 等待服務器啟動后加載頁面time.sleep(1) # 給服務器一點啟動時間self.web_view.setUrl(QUrl("http://127.0.0.1:3863"))def start_flask_server(self):# 在新線程中啟動Flask服務器threading.Thread(target=self.run_flask, daemon=True).start()def run_flask(self):from app import appapp.run(host='127.0.0.1', port=3863)def main():app = QApplication(sys.argv)window = MainWindow()window.show()sys.exit(app.exec_())if __name__ == '__main__':main()
核心代碼:app.py
from flask import Flask, render_template, request, jsonify, send_file
import pandas as pd
from datetime import datetime, timedelta
import calendar
import json
import numpy as np
import osapp = Flask(__name__)# 班次定義
SHIFTS = {'白班': 'D','晚班': 'N','休息': 'R'
}# 讀取員工數據
def load_employee_data():try:df = pd.read_csv('Employee.csv', encoding='utf-8')# 只返回員工姓名列return pd.DataFrame({'name': df["Employee'sName"]})except Exception as e:print(f"Error loading employee data: {e}")return pd.DataFrame({'name': []})# 讀取排班表
def load_schedule():try:df = pd.read_excel('客戶服務部排班表20250301-20250331.xls')return dfexcept Exception as e:print(f"Error loading schedule: {e}")return pd.DataFrame()def get_month_calendar(year, month):cal = calendar.monthcalendar(year, month)return caldef generate_monthly_schedule(employees, year, month):num_days = calendar.monthrange(year, month)[1]num_employees = len(employees)# 將employees列表轉換為numpy數組employees_array = np.array(employees)# 創建排班表schedule = pd.DataFrame(index=employees, columns=range(1, num_days + 1))schedule.fillna('R', inplace=True) # 默認全部休息# 為每一天分配班次for day in range(1, num_days + 1):# 確保每天有足夠的白班和晚班day_employees = employees_array.copy()np.random.shuffle(day_employees)# 分配白班(約40%的員工)day_shifts = int(num_employees * 0.4)schedule.loc[day_employees[:day_shifts], day] = 'D'# 分配晚班(約30%的員工)night_shifts = int(num_employees * 0.3)schedule.loc[day_employees[day_shifts:day_shifts+night_shifts], day] = 'N'# 確保每周至少休息兩天for employee in employees:for week in range(0, num_days, 7):week_schedule = schedule.loc[employee, week+1:min(week+7, num_days)]rest_days = (week_schedule == 'R').sum()if rest_days < 2:work_days = list(week_schedule[week_schedule != 'R'].index)if work_days: # 確保有工作日可以調整np.random.shuffle(work_days)for i in range(min(2-rest_days, len(work_days))):schedule.loc[employee, work_days[i]] = 'R'return schedule@app.route('/')
def index():return render_template('index.html')@app.route('/api/employees')
def get_employees():df = load_employee_data()return jsonify(df.to_dict('records'))@app.route('/api/calendar/<int:year>/<int:month>')
def get_calendar(year, month):cal = get_month_calendar(year, month)return jsonify(cal)@app.route('/api/generate_schedule', methods=['POST'])
def generate_schedule():try:data = request.get_json()year = data.get('year', 2025)month = data.get('month', 1)selected_employees = data.get('employees', [])if not selected_employees:return jsonify({"status": "error", "message": "請選擇員工"})schedule = generate_monthly_schedule(selected_employees, year, month)# 將DataFrame轉換為字典格式schedule_dict = {}for employee in selected_employees:schedule_dict[employee] = schedule.loc[employee].to_dict()return jsonify({"status": "success","schedule": schedule_dict,"message": "排班表生成成功"})except Exception as e:return jsonify({"status": "error", "message": str(e)})@app.route('/api/export_schedule', methods=['POST'])
def export_schedule():try:data = request.get_json()year = data.get('year', 2025)month = data.get('month', 1)schedule_data = data.get('schedule', {})# 創建新的排班表df = pd.DataFrame.from_dict(schedule_data, orient='index')# 設置列名為日期df.columns = [str(i) for i in range(1, len(df.columns) + 1)]# 重置索引,將員工名稱作為一列df.reset_index(inplace=True)df.rename(columns={'index': '姓名'}, inplace=True)# 保存文件output_file = f'客戶服務部排班表{year}{month:02d}01-{year}{month:02d}{calendar.monthrange(year, month)[1]}.xlsx'# 使用 openpyxl 引擎保存為 xlsx 格式df.to_excel(output_file, index=False, engine='openpyxl')# 返回文件下載路徑return send_file(output_file,as_attachment=True,download_name=output_file,mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')except Exception as e:print(f"Export error: {str(e)}") # 添加錯誤日志return jsonify({"status": "error", "message": f"導出失敗: {str(e)}"})if __name__ == '__main__':app.run(host='127.0.0.1', port=3863, debug=True)