上传文件至 /
This commit is contained in:
commit
609a2d8254
|
@ -0,0 +1,498 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import requests
|
||||||
|
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||||
|
QLabel, QLineEdit, QComboBox, QPushButton, QFileDialog,
|
||||||
|
QGroupBox, QDateTimeEdit, QDoubleSpinBox, QSpinBox, QTextEdit,
|
||||||
|
QTabWidget, QScrollArea, QMessageBox, QCalendarWidget,QGridLayout)
|
||||||
|
from PyQt5.QtCore import Qt, QDateTime, QTimer
|
||||||
|
from PyQt5.QtGui import QFont, QIcon
|
||||||
|
|
||||||
|
|
||||||
|
class NoWheelComboBox(QComboBox):
|
||||||
|
def wheelEvent(self, event):
|
||||||
|
event.ignore()
|
||||||
|
|
||||||
|
|
||||||
|
class NoWheelSpinBox(QSpinBox):
|
||||||
|
def wheelEvent(self, event):
|
||||||
|
event.ignore()
|
||||||
|
|
||||||
|
|
||||||
|
class NoWheelDoubleSpinBox(QDoubleSpinBox):
|
||||||
|
def wheelEvent(self, event):
|
||||||
|
event.ignore()
|
||||||
|
|
||||||
|
|
||||||
|
class ImageUploaderApp(QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("图片批量上传工具")
|
||||||
|
self.setWindowIcon(QIcon("upload_icon.png")) # 替换为您自己的图标或删除此行
|
||||||
|
self.setGeometry(100, 100, 850, 650)
|
||||||
|
|
||||||
|
# 初始化参数
|
||||||
|
self.base_url = "http://pms.dtyx.net:9158/image"
|
||||||
|
self.default_part_id = "a3b4b81c5973a1895a4d95d50dd8351c"
|
||||||
|
self.file_paths = []
|
||||||
|
self.upload_in_progress = False
|
||||||
|
|
||||||
|
# 初始化UI
|
||||||
|
self.init_ui()
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
# 创建主窗口部件
|
||||||
|
main_widget = QWidget()
|
||||||
|
self.setCentralWidget(main_widget)
|
||||||
|
|
||||||
|
# 主布局
|
||||||
|
main_layout = QVBoxLayout()
|
||||||
|
main_widget.setLayout(main_layout)
|
||||||
|
|
||||||
|
# 创建标签页
|
||||||
|
tab_widget = QTabWidget()
|
||||||
|
main_layout.addWidget(tab_widget)
|
||||||
|
|
||||||
|
# 基本参数标签页
|
||||||
|
basic_tab = QWidget()
|
||||||
|
tab_widget.addTab(basic_tab, "基本参数")
|
||||||
|
self.setup_basic_tab(basic_tab)
|
||||||
|
|
||||||
|
# 高级参数标签页
|
||||||
|
advanced_tab = QWidget()
|
||||||
|
tab_widget.addTab(advanced_tab, "高级参数")
|
||||||
|
self.setup_advanced_tab(advanced_tab)
|
||||||
|
|
||||||
|
# 底部按钮区域
|
||||||
|
button_layout = QHBoxLayout()
|
||||||
|
|
||||||
|
self.upload_btn = QPushButton("开始上传")
|
||||||
|
self.upload_btn.setFont(QFont("Arial", 12, QFont.Bold))
|
||||||
|
self.upload_btn.setStyleSheet("background-color: #4CAF50; color: white; padding: 10px;")
|
||||||
|
self.upload_btn.clicked.connect(self.start_upload)
|
||||||
|
|
||||||
|
self.clear_btn = QPushButton("清空选择")
|
||||||
|
self.clear_btn.setFont(QFont("Arial", 12))
|
||||||
|
self.clear_btn.setStyleSheet("background-color: #f44336; color: white; padding: 10px;")
|
||||||
|
self.clear_btn.clicked.connect(self.clear_selection)
|
||||||
|
|
||||||
|
button_layout.addWidget(self.upload_btn)
|
||||||
|
button_layout.addWidget(self.clear_btn)
|
||||||
|
main_layout.addLayout(button_layout)
|
||||||
|
|
||||||
|
# 日志输出
|
||||||
|
self.log_text = QTextEdit()
|
||||||
|
self.log_text.setReadOnly(True)
|
||||||
|
self.log_text.setFont(QFont("Consolas", 10))
|
||||||
|
self.log_text.setStyleSheet("background-color: #f5f5f5;")
|
||||||
|
main_layout.addWidget(self.log_text)
|
||||||
|
|
||||||
|
def setup_basic_tab(self, tab):
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
tab.setLayout(layout)
|
||||||
|
|
||||||
|
# Part ID设置
|
||||||
|
part_id_group = QGroupBox("部件ID设置")
|
||||||
|
part_id_layout = QVBoxLayout()
|
||||||
|
|
||||||
|
self.part_id_input = QLineEdit(self.default_part_id)
|
||||||
|
part_id_layout.addWidget(QLabel("部件ID:"))
|
||||||
|
part_id_layout.addWidget(self.part_id_input)
|
||||||
|
|
||||||
|
part_id_group.setLayout(part_id_layout)
|
||||||
|
layout.addWidget(part_id_group)
|
||||||
|
|
||||||
|
# 图片源选择
|
||||||
|
source_group = QGroupBox("图片源设置")
|
||||||
|
source_layout = QVBoxLayout()
|
||||||
|
|
||||||
|
self.source_combo = NoWheelComboBox()
|
||||||
|
self.source_combo.addItems(["图像采集", "外部工作", "内部工作", "防雷工作"])
|
||||||
|
self.source_combo.setCurrentText("内部工作")
|
||||||
|
source_layout.addWidget(QLabel("图片源类型:"))
|
||||||
|
source_layout.addWidget(self.source_combo)
|
||||||
|
|
||||||
|
source_group.setLayout(source_layout)
|
||||||
|
layout.addWidget(source_group)
|
||||||
|
|
||||||
|
# 图片类型选择
|
||||||
|
type_group = QGroupBox("图片类型设置")
|
||||||
|
type_layout = QVBoxLayout()
|
||||||
|
|
||||||
|
self.type_combo = NoWheelComboBox()
|
||||||
|
self.type_combo.addItems(["", "缺陷影像", "典型影像", "其他影像"])
|
||||||
|
type_layout.addWidget(QLabel("图片类型:"))
|
||||||
|
type_layout.addWidget(self.type_combo)
|
||||||
|
|
||||||
|
type_group.setLayout(type_layout)
|
||||||
|
layout.addWidget(type_group)
|
||||||
|
|
||||||
|
# 拍摄方式选择
|
||||||
|
method_group = QGroupBox("拍摄方式设置")
|
||||||
|
method_layout = QVBoxLayout()
|
||||||
|
|
||||||
|
self.method_combo = NoWheelComboBox()
|
||||||
|
self.method_combo.addItems(["", "无人机航拍", "手持相机拍摄"])
|
||||||
|
method_layout.addWidget(QLabel("拍摄方式:"))
|
||||||
|
method_layout.addWidget(self.method_combo)
|
||||||
|
|
||||||
|
method_group.setLayout(method_layout)
|
||||||
|
layout.addWidget(method_group)
|
||||||
|
|
||||||
|
# 天气选择
|
||||||
|
weather_group = QGroupBox("天气设置")
|
||||||
|
weather_layout = QVBoxLayout()
|
||||||
|
|
||||||
|
self.weather_combo = NoWheelComboBox()
|
||||||
|
weather_options = [
|
||||||
|
"", "晴天", "多云", "阴天", "小雨", "中雨", "大雨", "暴雨",
|
||||||
|
"阵雨", "雷阵雨", "雷电", "冰雹", "轻雾", "雾", "浓雾",
|
||||||
|
"霾", "雨夹雪", "小雪", "中雪", "大雪", "暴雪", "冻雨"
|
||||||
|
]
|
||||||
|
self.weather_combo.addItems(weather_options)
|
||||||
|
weather_layout.addWidget(QLabel("天气情况:"))
|
||||||
|
weather_layout.addWidget(self.weather_combo)
|
||||||
|
|
||||||
|
weather_group.setLayout(weather_layout)
|
||||||
|
layout.addWidget(weather_group)
|
||||||
|
|
||||||
|
# 文件选择按钮
|
||||||
|
self.select_files_btn = QPushButton("选择图片文件")
|
||||||
|
self.select_files_btn.setFont(QFont("Arial", 12))
|
||||||
|
self.select_files_btn.setStyleSheet("background-color: #2196F3; color: white; padding: 8px;")
|
||||||
|
self.select_files_btn.clicked.connect(self.select_files)
|
||||||
|
layout.addWidget(self.select_files_btn)
|
||||||
|
|
||||||
|
# 选中的文件列表
|
||||||
|
self.file_list_label = QLabel("已选择 0 个文件")
|
||||||
|
self.file_list_label.setFont(QFont("Arial", 10))
|
||||||
|
layout.addWidget(self.file_list_label)
|
||||||
|
|
||||||
|
# 添加弹性空间
|
||||||
|
layout.addStretch()
|
||||||
|
|
||||||
|
def setup_advanced_tab(self, tab):
|
||||||
|
scroll = QScrollArea()
|
||||||
|
scroll.setWidgetResizable(True)
|
||||||
|
tab.layout = QVBoxLayout(tab)
|
||||||
|
tab.layout.addWidget(scroll)
|
||||||
|
|
||||||
|
container = QWidget()
|
||||||
|
scroll.setWidget(container)
|
||||||
|
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
container.setLayout(layout)
|
||||||
|
|
||||||
|
# 采集员信息
|
||||||
|
collector_group = QGroupBox("采集员信息")
|
||||||
|
collector_layout = QVBoxLayout()
|
||||||
|
|
||||||
|
self.collector_id_input = QLineEdit()
|
||||||
|
self.collector_name_input = QLineEdit()
|
||||||
|
collector_layout.addWidget(QLabel("采集员ID:"))
|
||||||
|
collector_layout.addWidget(self.collector_id_input)
|
||||||
|
collector_layout.addWidget(QLabel("采集员姓名:"))
|
||||||
|
collector_layout.addWidget(self.collector_name_input)
|
||||||
|
|
||||||
|
collector_group.setLayout(collector_layout)
|
||||||
|
layout.addWidget(collector_group)
|
||||||
|
|
||||||
|
# 拍摄时间
|
||||||
|
time_group = QGroupBox("拍摄时间")
|
||||||
|
time_layout = QVBoxLayout()
|
||||||
|
|
||||||
|
# 开始时间
|
||||||
|
start_time_group = QGroupBox("开始时间")
|
||||||
|
start_time_layout = QVBoxLayout()
|
||||||
|
|
||||||
|
self.start_calendar = QCalendarWidget()
|
||||||
|
self.start_time_edit = QDateTimeEdit()
|
||||||
|
self.start_time_edit.setDisplayFormat("HH:mm:ss")
|
||||||
|
self.start_time_edit.setTime(QDateTime.currentDateTime().time())
|
||||||
|
|
||||||
|
start_time_layout.addWidget(self.start_calendar)
|
||||||
|
start_time_layout.addWidget(self.start_time_edit)
|
||||||
|
start_time_group.setLayout(start_time_layout)
|
||||||
|
|
||||||
|
# 结束时间
|
||||||
|
end_time_group = QGroupBox("结束时间")
|
||||||
|
end_time_layout = QVBoxLayout()
|
||||||
|
|
||||||
|
self.end_calendar = QCalendarWidget()
|
||||||
|
self.end_time_edit = QDateTimeEdit()
|
||||||
|
self.end_time_edit.setDisplayFormat("HH:mm:ss")
|
||||||
|
self.end_time_edit.setTime(QDateTime.currentDateTime().time())
|
||||||
|
|
||||||
|
end_time_layout.addWidget(self.end_calendar)
|
||||||
|
end_time_layout.addWidget(self.end_time_edit)
|
||||||
|
end_time_group.setLayout(end_time_layout)
|
||||||
|
|
||||||
|
time_layout.addWidget(start_time_group)
|
||||||
|
time_layout.addWidget(end_time_group)
|
||||||
|
time_group.setLayout(time_layout)
|
||||||
|
layout.addWidget(time_group)
|
||||||
|
|
||||||
|
# 环境参数
|
||||||
|
env_group = QGroupBox("环境参数")
|
||||||
|
env_layout = QGridLayout()
|
||||||
|
|
||||||
|
self.humidity_spin = NoWheelSpinBox()
|
||||||
|
self.humidity_spin.setRange(0, 100)
|
||||||
|
self.humidity_spin.setSpecialValueText("未设置")
|
||||||
|
self.humidity_spin.setValue(0)
|
||||||
|
|
||||||
|
self.temp_min_spin = NoWheelDoubleSpinBox()
|
||||||
|
self.temp_min_spin.setRange(-50, 50)
|
||||||
|
self.temp_min_spin.setSpecialValueText("未设置")
|
||||||
|
self.temp_min_spin.setValue(-50)
|
||||||
|
|
||||||
|
self.temp_max_spin = NoWheelDoubleSpinBox()
|
||||||
|
self.temp_max_spin.setRange(-50, 50)
|
||||||
|
self.temp_max_spin.setSpecialValueText("未设置")
|
||||||
|
self.temp_max_spin.setValue(-50)
|
||||||
|
|
||||||
|
self.wind_level_spin = NoWheelSpinBox()
|
||||||
|
self.wind_level_spin.setRange(0, 12)
|
||||||
|
self.wind_level_spin.setSpecialValueText("未设置")
|
||||||
|
self.wind_level_spin.setValue(0)
|
||||||
|
|
||||||
|
self.distance_spin = NoWheelSpinBox()
|
||||||
|
self.distance_spin.setRange(0, 1000)
|
||||||
|
self.distance_spin.setSpecialValueText("未设置")
|
||||||
|
self.distance_spin.setValue(0)
|
||||||
|
|
||||||
|
env_layout.addWidget(QLabel("湿度(%):"), 0, 0)
|
||||||
|
env_layout.addWidget(self.humidity_spin, 0, 1)
|
||||||
|
env_layout.addWidget(QLabel("最低温度(℃):"), 1, 0)
|
||||||
|
env_layout.addWidget(self.temp_min_spin, 1, 1)
|
||||||
|
env_layout.addWidget(QLabel("最高温度(℃):"), 2, 0)
|
||||||
|
env_layout.addWidget(self.temp_max_spin, 2, 1)
|
||||||
|
env_layout.addWidget(QLabel("风力等级:"), 3, 0)
|
||||||
|
env_layout.addWidget(self.wind_level_spin, 3, 1)
|
||||||
|
env_layout.addWidget(QLabel("拍摄距离(m):"), 4, 0)
|
||||||
|
env_layout.addWidget(self.distance_spin, 4, 1)
|
||||||
|
|
||||||
|
env_group.setLayout(env_layout)
|
||||||
|
layout.addWidget(env_group)
|
||||||
|
|
||||||
|
# 添加弹性空间
|
||||||
|
layout.addStretch()
|
||||||
|
|
||||||
|
def select_files(self):
|
||||||
|
options = QFileDialog.Options()
|
||||||
|
files, _ = QFileDialog.getOpenFileNames(
|
||||||
|
self, "选择图片文件", "",
|
||||||
|
"图片文件 (*.jpg *.jpeg *.png *.bmp *.gif)",
|
||||||
|
options=options
|
||||||
|
)
|
||||||
|
|
||||||
|
if files:
|
||||||
|
self.file_paths = files
|
||||||
|
self.file_list_label.setText(f"已选择 {len(files)} 个文件")
|
||||||
|
self.log_message(f"已选择 {len(files)} 个图片文件")
|
||||||
|
|
||||||
|
def clear_selection(self):
|
||||||
|
self.file_paths = []
|
||||||
|
self.file_list_label.setText("已选择 0 个文件")
|
||||||
|
self.log_message("已清空文件选择")
|
||||||
|
|
||||||
|
def log_message(self, message):
|
||||||
|
self.log_text.append(f"[{QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss')}] {message}")
|
||||||
|
|
||||||
|
def start_upload(self):
|
||||||
|
if self.upload_in_progress:
|
||||||
|
QMessageBox.warning(self, "警告", "已有上传任务在进行中!")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.file_paths:
|
||||||
|
QMessageBox.warning(self, "警告", "请先选择要上传的图片文件!")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.upload_in_progress = True
|
||||||
|
self.upload_btn.setEnabled(False)
|
||||||
|
self.clear_btn.setEnabled(False)
|
||||||
|
|
||||||
|
# 使用QTimer在事件循环中启动上传,避免界面卡住
|
||||||
|
QTimer.singleShot(100, self.upload_images)
|
||||||
|
|
||||||
|
def upload_images(self):
|
||||||
|
try:
|
||||||
|
# 准备参数
|
||||||
|
part_id = self.part_id_input.text().strip() or self.default_part_id
|
||||||
|
|
||||||
|
# 从日历和时间控件获取时间
|
||||||
|
start_date = self.start_calendar.selectedDate()
|
||||||
|
start_time = self.start_time_edit.time()
|
||||||
|
start_datetime = QDateTime(start_date, start_time)
|
||||||
|
|
||||||
|
end_date = self.end_calendar.selectedDate()
|
||||||
|
end_time = self.end_time_edit.time()
|
||||||
|
end_datetime = QDateTime(end_date, end_time)
|
||||||
|
|
||||||
|
# 构建参数字典,只包含有值的参数
|
||||||
|
params = {
|
||||||
|
"imageSource": ["collect", "out-work", "in-work", "lightning-protection-work"][
|
||||||
|
self.source_combo.currentIndex()],
|
||||||
|
}
|
||||||
|
|
||||||
|
# 添加可选参数
|
||||||
|
if self.type_combo.currentIndex() > 0:
|
||||||
|
params["imageType"] = ["", "DEFECT", "TYPICAL", "OTHER"][self.type_combo.currentIndex()]
|
||||||
|
|
||||||
|
if self.method_combo.currentIndex() > 0:
|
||||||
|
params["shootingMethod"] = ["", "UAV", "HANDHELD_CAMERA"][self.method_combo.currentIndex()]
|
||||||
|
|
||||||
|
if self.weather_combo.currentIndex() > 0:
|
||||||
|
weather_options = [
|
||||||
|
"", "SUNNY", "CLOUDY", "OVERCAST", "LIGHT_RAIN", "MODERATE_RAIN", "HEAVY_RAIN",
|
||||||
|
"CLOUDBURST", "SHOWER", "THUNDERSHOWER", "THUNDER", "HAIL", "LIGHT_FOG",
|
||||||
|
"FOG", "THICK_FOG", "HAZE", "SLEET", "LIGHT_SNOW", "MODERATE_SNOW",
|
||||||
|
"HEAVY_SNOW", "BLIZZARD", "FREEZING_RAIN"
|
||||||
|
]
|
||||||
|
params["weather"] = weather_options[self.weather_combo.currentIndex()]
|
||||||
|
|
||||||
|
collector_id = self.collector_id_input.text().strip()
|
||||||
|
if collector_id:
|
||||||
|
params["collectorId"] = collector_id
|
||||||
|
|
||||||
|
collector_name = self.collector_name_input.text().strip()
|
||||||
|
if collector_name:
|
||||||
|
params["collectorName"] = collector_name
|
||||||
|
|
||||||
|
params["shootingTimeBegin"] = start_datetime.toString("yyyy-MM-dd hh:mm:ss")
|
||||||
|
params["shootingTimeEnd"] = end_datetime.toString("yyyy-MM-dd hh:mm:ss")
|
||||||
|
|
||||||
|
# 环境参数
|
||||||
|
if self.humidity_spin.value() > 0:
|
||||||
|
params["humidness"] = self.humidity_spin.value()
|
||||||
|
|
||||||
|
if self.temp_min_spin.value() > -50:
|
||||||
|
params["temperatureMin"] = self.temp_min_spin.value()
|
||||||
|
|
||||||
|
if self.temp_max_spin.value() > -50:
|
||||||
|
params["temperatureMax"] = self.temp_max_spin.value()
|
||||||
|
|
||||||
|
if self.wind_level_spin.value() > 0:
|
||||||
|
params["windLevel"] = self.wind_level_spin.value()
|
||||||
|
|
||||||
|
if self.distance_spin.value() > 0:
|
||||||
|
params["shootingDistance"] = self.distance_spin.value()
|
||||||
|
|
||||||
|
# 构建URL
|
||||||
|
url = f"{self.base_url}/{params['imageSource']}/upload-batch/{part_id}"
|
||||||
|
|
||||||
|
# 准备图片列表参数
|
||||||
|
for i, file_path in enumerate(self.file_paths):
|
||||||
|
file_name = os.path.basename(file_path)
|
||||||
|
params[f"imageList[{i}].imageName"] = file_name
|
||||||
|
params[f"imageList[{i}].imagePath"] = file_path
|
||||||
|
|
||||||
|
if "imageType" in params:
|
||||||
|
params[f"imageList[{i}].imageType"] = params["imageType"]
|
||||||
|
if "shootingMethod" in params:
|
||||||
|
params[f"imageList[{i}].shootingMethod"] = params["shootingMethod"]
|
||||||
|
if "weather" in params:
|
||||||
|
params[f"imageList[{i}].weather"] = params["weather"]
|
||||||
|
|
||||||
|
# 准备文件数据
|
||||||
|
files = []
|
||||||
|
for file_path in self.file_paths:
|
||||||
|
file_name = os.path.basename(file_path)
|
||||||
|
files.append(('files', (file_name, open(file_path, 'rb'), 'image/jpeg')))
|
||||||
|
|
||||||
|
self.log_message(f"开始上传 {len(self.file_paths)} 张图片...")
|
||||||
|
self.log_message(f"请求URL: {url}")
|
||||||
|
self.log_message(f"请求参数: {params}")
|
||||||
|
|
||||||
|
# 显示上传进度对话框
|
||||||
|
self.progress = QMessageBox(self)
|
||||||
|
self.progress.setWindowTitle("上传中")
|
||||||
|
self.progress.setText(f"正在上传 {len(self.file_paths)} 张图片,请稍候...")
|
||||||
|
self.progress.setStandardButtons(QMessageBox.Cancel)
|
||||||
|
self.progress.buttonClicked.connect(self.cancel_upload)
|
||||||
|
self.progress.show()
|
||||||
|
|
||||||
|
# 确保UI更新
|
||||||
|
QApplication.processEvents()
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
url,
|
||||||
|
headers={"Authorization": "null"},
|
||||||
|
params=params,
|
||||||
|
files=files
|
||||||
|
)
|
||||||
|
|
||||||
|
self.progress.close()
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
self.log_message("上传成功!")
|
||||||
|
self.log_message(f"响应数据: {response.text}")
|
||||||
|
QMessageBox.information(self, "成功", "图片上传成功!")
|
||||||
|
else:
|
||||||
|
self.log_message(f"上传失败,状态码: {response.status_code}")
|
||||||
|
self.log_message(f"响应内容: {response.text}")
|
||||||
|
QMessageBox.critical(self, "错误", f"上传失败: {response.text}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log_message(f"上传过程中发生错误: {str(e)}")
|
||||||
|
QMessageBox.critical(self, "错误", f"上传过程中发生错误: {str(e)}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# 确保所有文件都被关闭
|
||||||
|
if 'files' in locals():
|
||||||
|
for file in files:
|
||||||
|
file[1][1].close()
|
||||||
|
|
||||||
|
self.upload_in_progress = False
|
||||||
|
self.upload_btn.setEnabled(True)
|
||||||
|
self.clear_btn.setEnabled(True)
|
||||||
|
|
||||||
|
def cancel_upload(self):
|
||||||
|
self.upload_in_progress = False
|
||||||
|
self.log_message("用户取消了上传操作")
|
||||||
|
self.progress.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
|
# 设置全局样式
|
||||||
|
app.setStyle("Fusion")
|
||||||
|
app.setStyleSheet("""
|
||||||
|
QMainWindow {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
QGroupBox {
|
||||||
|
font-weight: bold;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
QGroupBox::title {
|
||||||
|
subcontrol-origin: margin;
|
||||||
|
left: 10px;
|
||||||
|
padding: 0 3px;
|
||||||
|
}
|
||||||
|
QLabel {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
QComboBox, QLineEdit, QDateTimeEdit, QSpinBox, QDoubleSpinBox {
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
QTextEdit {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
QCalendarWidget {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
|
window = ImageUploaderApp()
|
||||||
|
window.show()
|
||||||
|
sys.exit(app.exec_())
|
|
@ -0,0 +1,302 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
from datetime import datetime
|
||||||
|
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||||
|
QLabel, QLineEdit, QPushButton, QListWidget, QMessageBox,
|
||||||
|
QProgressBar, QFileDialog, QGroupBox, QFormLayout)
|
||||||
|
from PyQt5.QtCore import Qt, QThread, pyqtSignal
|
||||||
|
|
||||||
|
|
||||||
|
class VideoUploadThread(QThread):
|
||||||
|
progress_updated = pyqtSignal(int, str)
|
||||||
|
upload_finished = pyqtSignal(int, int)
|
||||||
|
|
||||||
|
def __init__(self, files, params, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.files = files
|
||||||
|
self.params = params
|
||||||
|
self.base_url = "http://pms.dtyx.net:9158"
|
||||||
|
self.upload_endpoint = "/video-file-info/batch-upload"
|
||||||
|
self.running = True
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
successful_uploads = 0
|
||||||
|
failed_uploads = 0
|
||||||
|
|
||||||
|
for index, file_path in enumerate(self.files):
|
||||||
|
if not self.running:
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 从文件名中提取拍摄时间
|
||||||
|
shooting_time = self.extract_time_from_filename(file_path)
|
||||||
|
params = self.params.copy()
|
||||||
|
if shooting_time:
|
||||||
|
params["shootingTime"] = shooting_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
else:
|
||||||
|
# 如果没有提取到时间,使用当前时间
|
||||||
|
params["shootingTime"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
# 读取文件内容
|
||||||
|
with open(file_path, 'rb') as f:
|
||||||
|
files = {'file': (os.path.basename(file_path), f, 'video/mp4')}
|
||||||
|
|
||||||
|
# 发送请求
|
||||||
|
response = requests.post(
|
||||||
|
url=f"{self.base_url}{self.upload_endpoint}",
|
||||||
|
params=params,
|
||||||
|
files=files,
|
||||||
|
headers={"Authorization": None} # token为null
|
||||||
|
)
|
||||||
|
|
||||||
|
# 处理响应
|
||||||
|
if response.status_code == 200:
|
||||||
|
result = response.json()
|
||||||
|
if result.get("success"):
|
||||||
|
self.progress_updated.emit(index + 1, f"上传成功: {os.path.basename(file_path)}")
|
||||||
|
successful_uploads += 1
|
||||||
|
else:
|
||||||
|
self.progress_updated.emit(index + 1,
|
||||||
|
f"上传失败: {os.path.basename(file_path)} - {result.get('msg', '未知错误')}")
|
||||||
|
failed_uploads += 1
|
||||||
|
else:
|
||||||
|
self.progress_updated.emit(index + 1,
|
||||||
|
f"上传失败: {os.path.basename(file_path)} - HTTP状态码: {response.status_code}")
|
||||||
|
failed_uploads += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.progress_updated.emit(index + 1, f"上传异常: {os.path.basename(file_path)} - {str(e)}")
|
||||||
|
failed_uploads += 1
|
||||||
|
|
||||||
|
self.upload_finished.emit(successful_uploads, failed_uploads)
|
||||||
|
|
||||||
|
def extract_time_from_filename(self, file_path):
|
||||||
|
"""从文件名中提取时间信息"""
|
||||||
|
filename = os.path.basename(file_path)
|
||||||
|
|
||||||
|
# 匹配类似 VID_20250611_144614 的格式
|
||||||
|
match = re.search(r'(?:VID|VIDEO|视频)_?(\d{4})(\d{2})(\d{2})_?(\d{2})(\d{2})(\d{2})', filename, re.IGNORECASE)
|
||||||
|
if match:
|
||||||
|
year, month, day, hour, minute, second = match.groups()
|
||||||
|
try:
|
||||||
|
return datetime(
|
||||||
|
year=int(year),
|
||||||
|
month=int(month),
|
||||||
|
day=int(day),
|
||||||
|
hour=int(hour),
|
||||||
|
minute=int(minute),
|
||||||
|
second=int(second)
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 匹配其他常见日期格式
|
||||||
|
date_patterns = [
|
||||||
|
r'(\d{4})-(\d{2})-(\d{2})[ _](\d{2})-(\d{2})-(\d{2})', # 2025-06-11 14-46-14
|
||||||
|
r'(\d{4})(\d{2})(\d{2})[ _](\d{2})(\d{2})(\d{2})', # 20250611 144614
|
||||||
|
r'(\d{4})_(\d{2})_(\d{2})[ _](\d{2})_(\d{2})_(\d{2})', # 2025_06_11 14_46_14
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern in date_patterns:
|
||||||
|
match = re.search(pattern, filename)
|
||||||
|
if match:
|
||||||
|
year, month, day, hour, minute, second = match.groups()
|
||||||
|
try:
|
||||||
|
return datetime(
|
||||||
|
year=int(year),
|
||||||
|
month=int(month),
|
||||||
|
day=int(day),
|
||||||
|
hour=int(hour),
|
||||||
|
minute=int(minute),
|
||||||
|
second=int(second)
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
|
||||||
|
class VideoUploaderApp(QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("防雷工作视频批量上传工具")
|
||||||
|
self.setGeometry(100, 100, 800, 600)
|
||||||
|
|
||||||
|
self.upload_thread = None
|
||||||
|
self.init_ui()
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
# 主窗口布局
|
||||||
|
main_widget = QWidget()
|
||||||
|
main_layout = QVBoxLayout()
|
||||||
|
|
||||||
|
# 参数设置区域
|
||||||
|
params_group = QGroupBox("上传参数设置")
|
||||||
|
params_layout = QFormLayout()
|
||||||
|
|
||||||
|
self.part_id_input = QLineEdit("81d2bcdf0db9b0e986b61f986c5d520d")
|
||||||
|
self.location_input = QLineEdit()
|
||||||
|
self.test_point_input = QLineEdit()
|
||||||
|
self.worker_id_input = QLineEdit()
|
||||||
|
|
||||||
|
params_layout.addRow(QLabel("部件ID:"), self.part_id_input)
|
||||||
|
params_layout.addRow(QLabel("拍摄地点:"), self.location_input)
|
||||||
|
params_layout.addRow(QLabel("测试点:"), self.test_point_input)
|
||||||
|
params_layout.addRow(QLabel("作业人员ID:"), self.worker_id_input)
|
||||||
|
|
||||||
|
params_group.setLayout(params_layout)
|
||||||
|
|
||||||
|
# 文件选择区域
|
||||||
|
file_group = QGroupBox("视频文件选择")
|
||||||
|
file_layout = QVBoxLayout()
|
||||||
|
|
||||||
|
self.file_list = QListWidget()
|
||||||
|
self.select_btn = QPushButton("选择视频文件")
|
||||||
|
self.select_btn.clicked.connect(self.select_videos)
|
||||||
|
self.clear_btn = QPushButton("清空列表")
|
||||||
|
self.clear_btn.clicked.connect(self.clear_file_list)
|
||||||
|
|
||||||
|
file_layout.addWidget(self.file_list)
|
||||||
|
file_btn_layout = QHBoxLayout()
|
||||||
|
file_btn_layout.addWidget(self.select_btn)
|
||||||
|
file_btn_layout.addWidget(self.clear_btn)
|
||||||
|
file_layout.addLayout(file_btn_layout)
|
||||||
|
|
||||||
|
file_group.setLayout(file_layout)
|
||||||
|
|
||||||
|
# 上传控制区域
|
||||||
|
control_layout = QHBoxLayout()
|
||||||
|
self.upload_btn = QPushButton("开始上传")
|
||||||
|
self.upload_btn.clicked.connect(self.start_upload)
|
||||||
|
self.stop_btn = QPushButton("停止上传")
|
||||||
|
self.stop_btn.clicked.connect(self.stop_upload)
|
||||||
|
self.stop_btn.setEnabled(False)
|
||||||
|
|
||||||
|
control_layout.addWidget(self.upload_btn)
|
||||||
|
control_layout.addWidget(self.stop_btn)
|
||||||
|
|
||||||
|
# 进度显示区域
|
||||||
|
self.progress_bar = QProgressBar()
|
||||||
|
self.progress_bar.setAlignment(Qt.AlignCenter)
|
||||||
|
self.status_label = QLabel("准备就绪")
|
||||||
|
self.status_label.setAlignment(Qt.AlignCenter)
|
||||||
|
|
||||||
|
# 组装主布局
|
||||||
|
main_layout.addWidget(params_group)
|
||||||
|
main_layout.addWidget(file_group)
|
||||||
|
main_layout.addLayout(control_layout)
|
||||||
|
main_layout.addWidget(self.progress_bar)
|
||||||
|
main_layout.addWidget(self.status_label)
|
||||||
|
|
||||||
|
main_widget.setLayout(main_layout)
|
||||||
|
self.setCentralWidget(main_widget)
|
||||||
|
|
||||||
|
def select_videos(self):
|
||||||
|
"""选择视频文件"""
|
||||||
|
file_dialog = QFileDialog()
|
||||||
|
file_dialog.setFileMode(QFileDialog.ExistingFiles)
|
||||||
|
file_dialog.setNameFilter("视频文件 (*.mp4 *.avi *.mov *.mkv)")
|
||||||
|
|
||||||
|
if file_dialog.exec_():
|
||||||
|
file_paths = file_dialog.selectedFiles()
|
||||||
|
for file_path in file_paths:
|
||||||
|
self.file_list.addItem(file_path)
|
||||||
|
|
||||||
|
self.status_label.setText(f"已选择 {len(file_paths)} 个视频文件")
|
||||||
|
|
||||||
|
def clear_file_list(self):
|
||||||
|
"""清空文件列表"""
|
||||||
|
self.file_list.clear()
|
||||||
|
self.status_label.setText("文件列表已清空")
|
||||||
|
|
||||||
|
def start_upload(self):
|
||||||
|
"""开始上传视频"""
|
||||||
|
if self.file_list.count() == 0:
|
||||||
|
QMessageBox.warning(self, "警告", "请先选择要上传的视频文件!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 获取参数
|
||||||
|
params = {
|
||||||
|
"partId": self.part_id_input.text().strip(),
|
||||||
|
"qualified": 1, # 默认合格
|
||||||
|
"location": self.location_input.text().strip() or None,
|
||||||
|
"testPoint": self.test_point_input.text().strip() or None,
|
||||||
|
"workerUserId": self.worker_id_input.text().strip() or None
|
||||||
|
}
|
||||||
|
|
||||||
|
# 验证部件ID
|
||||||
|
if not params["partId"]:
|
||||||
|
QMessageBox.warning(self, "警告", "部件ID不能为空!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 获取文件列表
|
||||||
|
file_paths = [self.file_list.item(i).text() for i in range(self.file_list.count())]
|
||||||
|
|
||||||
|
# 设置UI状态
|
||||||
|
self.upload_btn.setEnabled(False)
|
||||||
|
self.stop_btn.setEnabled(True)
|
||||||
|
self.progress_bar.setMaximum(len(file_paths))
|
||||||
|
self.progress_bar.setValue(0)
|
||||||
|
self.status_label.setText("开始上传...")
|
||||||
|
|
||||||
|
# 创建并启动上传线程
|
||||||
|
self.upload_thread = VideoUploadThread(file_paths, params)
|
||||||
|
self.upload_thread.progress_updated.connect(self.update_progress)
|
||||||
|
self.upload_thread.upload_finished.connect(self.upload_finished)
|
||||||
|
self.upload_thread.start()
|
||||||
|
|
||||||
|
def stop_upload(self):
|
||||||
|
"""停止上传"""
|
||||||
|
if self.upload_thread and self.upload_thread.isRunning():
|
||||||
|
self.upload_thread.stop()
|
||||||
|
self.status_label.setText("上传已停止")
|
||||||
|
self.upload_btn.setEnabled(True)
|
||||||
|
self.stop_btn.setEnabled(False)
|
||||||
|
|
||||||
|
def update_progress(self, value, message):
|
||||||
|
"""更新上传进度"""
|
||||||
|
self.progress_bar.setValue(value)
|
||||||
|
self.status_label.setText(message)
|
||||||
|
|
||||||
|
def upload_finished(self, success_count, fail_count):
|
||||||
|
"""上传完成处理"""
|
||||||
|
self.upload_btn.setEnabled(True)
|
||||||
|
self.stop_btn.setEnabled(False)
|
||||||
|
|
||||||
|
msg = f"上传完成! 成功: {success_count} 个, 失败: {fail_count} 个"
|
||||||
|
self.status_label.setText(msg)
|
||||||
|
QMessageBox.information(self, "上传完成", msg)
|
||||||
|
|
||||||
|
# 重置进度条
|
||||||
|
self.progress_bar.reset()
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
"""窗口关闭事件"""
|
||||||
|
if self.upload_thread and self.upload_thread.isRunning():
|
||||||
|
reply = QMessageBox.question(
|
||||||
|
self, '确认退出',
|
||||||
|
'上传正在进行中,确定要退出吗?',
|
||||||
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
|
QMessageBox.No
|
||||||
|
)
|
||||||
|
|
||||||
|
if reply == QMessageBox.Yes:
|
||||||
|
self.upload_thread.stop()
|
||||||
|
self.upload_thread.wait()
|
||||||
|
event.accept()
|
||||||
|
else:
|
||||||
|
event.ignore()
|
||||||
|
else:
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
window = VideoUploaderApp()
|
||||||
|
window.show()
|
||||||
|
sys.exit(app.exec_())
|
Loading…
Reference in New Issue