费用计算及确认系统上线
This commit is contained in:
		
							
								
								
									
										142
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										142
									
								
								main.py
									
									
									
									
									
								
							@@ -3,10 +3,11 @@ import os
 | 
				
			|||||||
import cv2
 | 
					import cv2
 | 
				
			||||||
import numpy as np
 | 
					import numpy as np
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
from collections import defaultdict, deque
 | 
					from collections import defaultdict, deque
 | 
				
			||||||
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, \
 | 
					from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, \
 | 
				
			||||||
    QFileDialog, QFrame, QScrollArea, QComboBox, QLineEdit, QListWidget, QListWidgetItem
 | 
					    QFileDialog, QFrame, QScrollArea, QComboBox, QLineEdit, QListWidget, QListWidgetItem, QDialog, QMessageBox
 | 
				
			||||||
from PyQt5.QtCore import QTimer, Qt, pyqtSignal, QThread
 | 
					from PyQt5.QtCore import QTimer, Qt, pyqtSignal, QThread
 | 
				
			||||||
from PyQt5.QtGui import QImage, QPixmap, QFont, QPainter, QPen, QColor
 | 
					from PyQt5.QtGui import QImage, QPixmap, QFont, QPainter, QPen, QColor
 | 
				
			||||||
from yolopart.detector import LicensePlateYOLO
 | 
					from yolopart.detector import LicensePlateYOLO
 | 
				
			||||||
@@ -345,6 +346,79 @@ class VideoThread(QThread):
 | 
				
			|||||||
                    break
 | 
					                    break
 | 
				
			||||||
            self.msleep(30)  # 约30fps
 | 
					            self.msleep(30)  # 约30fps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ParkingFeeDialog(QDialog):
 | 
				
			||||||
 | 
					    """停车费用确认对话框"""
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def __init__(self, plate_number, parking_duration, fee_amount, parent=None):
 | 
				
			||||||
 | 
					        super().__init__(parent)
 | 
				
			||||||
 | 
					        self.plate_number = plate_number
 | 
				
			||||||
 | 
					        self.parking_duration = parking_duration
 | 
				
			||||||
 | 
					        self.fee_amount = fee_amount
 | 
				
			||||||
 | 
					        self.payment_confirmed = False
 | 
				
			||||||
 | 
					        self.init_ui()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def init_ui(self):
 | 
				
			||||||
 | 
					        self.setWindowTitle("停车费用确认")
 | 
				
			||||||
 | 
					        self.setModal(True)
 | 
				
			||||||
 | 
					        self.resize(400, 300)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        layout = QVBoxLayout()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # 车牌号信息
 | 
				
			||||||
 | 
					        plate_label = QLabel(f"车牌号: {self.plate_number}")
 | 
				
			||||||
 | 
					        plate_label.setFont(QFont("Arial", 14, QFont.Bold))
 | 
				
			||||||
 | 
					        layout.addWidget(plate_label)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # 停车时长信息
 | 
				
			||||||
 | 
					        hours = self.parking_duration // 3600
 | 
				
			||||||
 | 
					        minutes = (self.parking_duration % 3600) // 60
 | 
				
			||||||
 | 
					        seconds = self.parking_duration % 60
 | 
				
			||||||
 | 
					        duration_text = f"停车时长: {hours}小时{minutes}分钟{seconds}秒"
 | 
				
			||||||
 | 
					        duration_label = QLabel(duration_text)
 | 
				
			||||||
 | 
					        duration_label.setFont(QFont("Arial", 12))
 | 
				
			||||||
 | 
					        layout.addWidget(duration_label)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # 费用信息
 | 
				
			||||||
 | 
					        fee_label = QLabel(f"应缴费用: ¥{self.fee_amount:.2f}")
 | 
				
			||||||
 | 
					        fee_label.setFont(QFont("Arial", 14, QFont.Bold))
 | 
				
			||||||
 | 
					        fee_label.setStyleSheet("color: red;")
 | 
				
			||||||
 | 
					        layout.addWidget(fee_label)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # 提示信息
 | 
				
			||||||
 | 
					        tip_label = QLabel("请确认该车辆是否已经缴费:")
 | 
				
			||||||
 | 
					        tip_label.setFont(QFont("Arial", 12))
 | 
				
			||||||
 | 
					        layout.addWidget(tip_label)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # 按钮布局
 | 
				
			||||||
 | 
					        button_layout = QHBoxLayout()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # 已缴费按钮
 | 
				
			||||||
 | 
					        paid_button = QPushButton("已缴费 - 开闸放行")
 | 
				
			||||||
 | 
					        paid_button.setFont(QFont("Arial", 12))
 | 
				
			||||||
 | 
					        paid_button.setStyleSheet("QPushButton { background-color: #4CAF50; color: white; padding: 10px; }")
 | 
				
			||||||
 | 
					        paid_button.clicked.connect(self.confirm_payment)
 | 
				
			||||||
 | 
					        button_layout.addWidget(paid_button)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # 未缴费按钮
 | 
				
			||||||
 | 
					        unpaid_button = QPushButton("未缴费 - 拒绝放行")
 | 
				
			||||||
 | 
					        unpaid_button.setFont(QFont("Arial", 12))
 | 
				
			||||||
 | 
					        unpaid_button.setStyleSheet("QPushButton { background-color: #f44336; color: white; padding: 10px; }")
 | 
				
			||||||
 | 
					        unpaid_button.clicked.connect(self.reject_payment)
 | 
				
			||||||
 | 
					        button_layout.addWidget(unpaid_button)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        layout.addLayout(button_layout)
 | 
				
			||||||
 | 
					        self.setLayout(layout)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def confirm_payment(self):
 | 
				
			||||||
 | 
					        """确认已缴费"""
 | 
				
			||||||
 | 
					        self.payment_confirmed = True
 | 
				
			||||||
 | 
					        self.accept()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def reject_payment(self):
 | 
				
			||||||
 | 
					        """拒绝放行"""
 | 
				
			||||||
 | 
					        self.payment_confirmed = False
 | 
				
			||||||
 | 
					        self.reject()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LicensePlateWidget(QWidget):
 | 
					class LicensePlateWidget(QWidget):
 | 
				
			||||||
    """单个车牌结果显示组件"""
 | 
					    """单个车牌结果显示组件"""
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@@ -505,6 +579,9 @@ class MainWindow(QMainWindow):
 | 
				
			|||||||
        # 车牌记录存储 - 用于道闸控制
 | 
					        # 车牌记录存储 - 用于道闸控制
 | 
				
			||||||
        self.plate_records = {}  # 格式: {plate_number: {'first_time': datetime, 'sent': bool}}
 | 
					        self.plate_records = {}  # 格式: {plate_number: {'first_time': datetime, 'sent': bool}}
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        # 加载停车费用配置
 | 
				
			||||||
 | 
					        self.parking_config = self.load_parking_config()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        # 识别框命令发送记录 - 防止同一识别框重复发送命令
 | 
					        # 识别框命令发送记录 - 防止同一识别框重复发送命令
 | 
				
			||||||
        self.frame_command_sent = {}  # 格式: {plate_id: {'plate_number': str, 'command_sent': bool}}
 | 
					        self.frame_command_sent = {}  # 格式: {plate_id: {'plate_number': str, 'command_sent': bool}}
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
@@ -1448,7 +1525,9 @@ class MainWindow(QMainWindow):
 | 
				
			|||||||
                # 计算时间间隔
 | 
					                # 计算时间间隔
 | 
				
			||||||
                time_diff = (current_time - record['first_time']).total_seconds()
 | 
					                time_diff = (current_time - record['first_time']).total_seconds()
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                # 发送时间间隔命令
 | 
					                # 显示费用确认对话框
 | 
				
			||||||
 | 
					                if self.show_parking_fee_dialog(plate_number, time_diff):
 | 
				
			||||||
 | 
					                    # 用户确认缴费,发送时间间隔命令
 | 
				
			||||||
                    message = f"{plate_number} {int(time_diff)}sec"
 | 
					                    message = f"{plate_number} {int(time_diff)}sec"
 | 
				
			||||||
                    try:
 | 
					                    try:
 | 
				
			||||||
                        send_command(1, message)
 | 
					                        send_command(1, message)
 | 
				
			||||||
@@ -1465,8 +1544,17 @@ class MainWindow(QMainWindow):
 | 
				
			|||||||
                        
 | 
					                        
 | 
				
			||||||
                    except Exception as e:
 | 
					                    except Exception as e:
 | 
				
			||||||
                        print(f"发送道闸命令失败: {e}")
 | 
					                        print(f"发送道闸命令失败: {e}")
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    # 用户拒绝缴费或未缴费,不发送开闸命令
 | 
				
			||||||
 | 
					                    print(f"车牌 {plate_number} 未缴费,拒绝放行")
 | 
				
			||||||
 | 
					                    # 标记该识别框ID已处理,避免重复弹窗
 | 
				
			||||||
 | 
					                    self.frame_command_sent[plate_id] = {
 | 
				
			||||||
 | 
					                        'plate_number': plate_number,
 | 
				
			||||||
 | 
					                        'command_sent': False
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
            else: 
 | 
					            else: 
 | 
				
			||||||
                # 首次识别到车牌号(入库)
 | 
					                # 首次识别到车牌号(入库)
 | 
				
			||||||
 | 
					                # 入库时不收费,直接放行
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    message = f"{plate_number} 通行"
 | 
					                    message = f"{plate_number} 通行"
 | 
				
			||||||
                    send_command(1, message)
 | 
					                    send_command(1, message)
 | 
				
			||||||
@@ -1527,6 +1615,56 @@ class MainWindow(QMainWindow):
 | 
				
			|||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            print(f"发送关闸命令失败: {e}")
 | 
					            print(f"发送关闸命令失败: {e}")
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    def load_parking_config(self):
 | 
				
			||||||
 | 
					        """加载停车费用配置"""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            config_path = os.path.join(os.path.dirname(__file__), 'parking_config.json')
 | 
				
			||||||
 | 
					            with open(config_path, 'r', encoding='utf-8') as f:
 | 
				
			||||||
 | 
					                config = json.load(f)
 | 
				
			||||||
 | 
					            print(f"停车费用配置加载成功: {config}")
 | 
				
			||||||
 | 
					            return config
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            print(f"加载停车费用配置失败: {e}")
 | 
				
			||||||
 | 
					            # 返回默认配置
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					                "free_parking_duration": 1800,  # 30分钟免费停车
 | 
				
			||||||
 | 
					                "billing_cycle": 3600,  # 1小时计费周期
 | 
				
			||||||
 | 
					                "price_per_cycle": 5.0  # 每小时5元
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def calculate_parking_fee(self, parking_duration):
 | 
				
			||||||
 | 
					        """计算停车费用"""
 | 
				
			||||||
 | 
					        free_duration = self.parking_config.get("free_parking_duration", 1800)
 | 
				
			||||||
 | 
					        billing_cycle = self.parking_config.get("billing_cycle", 3600)
 | 
				
			||||||
 | 
					        price_per_cycle = self.parking_config.get("price_per_cycle", 5.0)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # 如果停车时长在免费时间内,费用为0
 | 
				
			||||||
 | 
					        if parking_duration <= free_duration:
 | 
				
			||||||
 | 
					            return 0.0
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # 计算超出免费时间的部分
 | 
				
			||||||
 | 
					        chargeable_duration = parking_duration - free_duration
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # 计算需要收费的周期数(向上取整)
 | 
				
			||||||
 | 
					        cycles = (chargeable_duration + billing_cycle - 1) // billing_cycle
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # 计算总费用
 | 
				
			||||||
 | 
					        total_fee = cycles * price_per_cycle
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return total_fee
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def show_parking_fee_dialog(self, plate_number, parking_duration):
 | 
				
			||||||
 | 
					        """显示停车费用确认对话框"""
 | 
				
			||||||
 | 
					        fee_amount = self.calculate_parking_fee(parking_duration)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        dialog = ParkingFeeDialog(plate_number, parking_duration, fee_amount, self)
 | 
				
			||||||
 | 
					        result = dialog.exec_()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if result == QDialog.Accepted and dialog.payment_confirmed:
 | 
				
			||||||
 | 
					            return True  # 确认已缴费,允许开闸
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return False  # 未缴费或取消,拒绝开闸
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    def closeEvent(self, event):
 | 
					    def closeEvent(self, event):
 | 
				
			||||||
        """窗口关闭事件"""
 | 
					        """窗口关闭事件"""
 | 
				
			||||||
        if self.camera_thread and self.camera_thread.running:
 | 
					        if self.camera_thread and self.camera_thread.running:
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user