From 40667da68282a736623b02fff2d8b5c7ec938bf7 Mon Sep 17 00:00:00 2001 From: xiaji Date: Sun, 25 Jan 2026 22:21:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=B1=87=E6=80=BB?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E6=8F=90=E4=BA=A4=E5=B7=A5=E5=85=B7GUI?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除core/views.py中的api_submit_summary视图函数 - 新增submit_summary_tool.py作为PySide6 GUI客户端 - 实现服务器连接配置、系统监控、手动/自动提交功能 - 添加系统托盘图标和状态栏功能 --- core/views.py | 54 ----- submit_summary_tool.py | 450 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 450 insertions(+), 54 deletions(-) create mode 100644 submit_summary_tool.py diff --git a/core/views.py b/core/views.py index 831bd8e..2c9da08 100644 --- a/core/views.py +++ b/core/views.py @@ -804,57 +804,3 @@ def pdf_list(request): } return render(request, 'core/pdf_list.html', context) - -def api_submit_summary(request): - """API提交汇总记录 - 仅接受指定分类和发言人的记录""" - logger.info("API: 收到汇总记录提交请求") - - if request.method != 'POST': - return JsonResponse({'success': False, 'message': '只支持POST请求'}, status=405) - - try: - import socket - import uuid - - content = request.POST.get('content', '').strip() - - if not content: - return JsonResponse({'success': False, 'message': '内容不能为空'}, status=400) - - category_name = "定期" - speaker_name = "机器人" - - try: - category = SummaryCategory.objects.get(name=category_name) - except SummaryCategory.DoesNotExist: - logger.error(f"API: 分类 '{category_name}' 不存在") - return JsonResponse({'success': False, 'message': f"分类 '{category_name}' 不存在"}, status=400) - - try: - speaker = FamilyMember.objects.get(name=speaker_name) - except FamilyMember.DoesNotExist: - logger.error(f"API: 发言人 '{speaker_name}' 不存在") - return JsonResponse({'success': False, 'message': f"发言人 '{speaker_name}' 不存在"}, status=400) - - hostname = socket.gethostname() - local_ip = socket.gethostbyname(hostname) - source = f"{hostname} ({local_ip})" - - summary = Summary.objects.create( - date=timezone.now().date(), - category=category, - speaker=speaker, - content=content, - source=source - ) - - logger.info(f"API: 汇总记录创建成功,ID={summary.id}") - return JsonResponse({ - 'success': True, - 'message': '提交成功', - 'id': summary.id - }) - - except Exception as e: - logger.error(f"API: 提交汇总记录失败: {str(e)}") - return JsonResponse({'success': False, 'message': f"提交失败: {str(e)}"}, status=500) diff --git a/submit_summary_tool.py b/submit_summary_tool.py new file mode 100644 index 0000000..2b500d0 --- /dev/null +++ b/submit_summary_tool.py @@ -0,0 +1,450 @@ +#!/usr/bin/env python3 +""" +汇总记录提交工具 - PySide6 GUI版本 +用于向家庭日报系统提交汇总记录 +""" + +import sys +import socket +import psutil +import requests +import json +import os +from datetime import datetime +from PySide6.QtWidgets import ( + QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, + QLabel, QLineEdit, QPushButton, QTextEdit, QGroupBox, QFormLayout, + QMessageBox, QSystemTrayIcon, QMenu, QStatusBar, QProgressBar, + QSpinBox, QComboBox +) +from PySide6.QtCore import QTimer, Qt, QSettings +from PySide6.QtGui import QIcon, QAction + + +class SubmitSummaryTool(QMainWindow): + """汇总记录提交工具主窗口""" + + def __init__(self): + super().__init__() + self.setWindowTitle("汇总记录提交工具") + self.setMinimumSize(600, 500) + self.resize(700, 550) + + self.settings = QSettings("DiaryFamily", "SubmitSummaryTool") + self.load_settings() + + self.init_ui() + self.setup_tray() + self.start_auto_refresh() + + def load_settings(self): + """加载保存的设置""" + self.server_url = self.settings.value("server_url", "http://localhost:8000") + self.auto_submit = self.settings.value("auto_submit", False, type=bool) + self.submit_interval = self.settings.value("submit_interval", 60, type=int) + self.auto_start = self.settings.value("auto_start", True, type=bool) + + def save_settings(self): + """保存设置""" + self.settings.setValue("server_url", self.server_url) + self.settings.setValue("auto_submit", self.auto_submit) + self.settings.setValue("submit_interval", self.submit_interval) + self.settings.setValue("auto_start", self.auto_start) + + def init_ui(self): + """初始化UI""" + central_widget = QWidget() + self.setCentralWidget(central_widget) + main_layout = QVBoxLayout(central_widget) + main_layout.setSpacing(10) + + self.create_server_config_group(main_layout) + self.create_system_info_group(main_layout) + self.create_manual_submit_group(main_layout) + self.create_log_group(main_layout) + self.create_status_bar() + self.create_actions() + + self.statusBar().showMessage("就绪") + self.refresh_system_info() + + def create_server_config_group(self, parent_layout): + """服务器配置组""" + group = QGroupBox("服务器配置") + layout = QFormLayout() + layout.setSpacing(10) + + self.server_url_edit = QLineEdit() + self.server_url_edit.setPlaceholderText("http://your-server:8000") + self.server_url_edit.setText(self.server_url) + self.server_url_edit.textChanged.connect(self.on_url_changed) + + self.auto_submit_check = QPushButton("自动提交") + self.auto_submit_check.setCheckable(True) + self.auto_submit_check.setChecked(self.auto_submit) + self.auto_submit_check.toggled.connect(self.on_auto_submit_toggled) + + self.interval_spin = QSpinBox() + self.interval_spin.setRange(10, 3600) + self.interval_spin.setValue(self.submit_interval) + self.interval_spin.setSuffix(" 秒") + self.interval_spin.valueChanged.connect(self.on_interval_changed) + + self.test_connection_btn = QPushButton("测试连接") + self.test_connection_btn.clicked.connect(self.test_connection) + + interval_layout = QHBoxLayout() + interval_layout.addWidget(self.auto_submit_check) + interval_layout.addWidget(self.interval_spin) + interval_layout.addStretch() + + layout.addRow("服务器地址:", self.server_url_edit) + layout.addRow("自动提交:", interval_layout) + layout.addRow("", self.test_connection_btn) + + group.setLayout(layout) + parent_layout.addWidget(group) + + def create_system_info_group(self, parent_layout): + """系统信息组""" + group = QGroupBox("系统监控信息") + layout = QFormLayout() + layout.setSpacing(8) + + self.cpu_label = QLabel("0%") + self.memory_label = QLabel("0%") + self.disk_label = QLabel("0%") + self.hostname_label = QLabel() + self.ip_label = QLabel() + self.timestamp_label = QLabel() + + self.cpu_progress = QProgressBar() + self.cpu_progress.setRange(0, 100) + self.memory_progress = QProgressBar() + self.memory_progress.setRange(0, 100) + self.disk_progress = QProgressBar() + self.disk_progress.setRange(0, 100) + + layout.addRow("CPU使用率:", self.cpu_progress) + layout.addRow("内存使用率:", self.memory_progress) + layout.addRow("磁盘使用率:", self.disk_progress) + layout.addRow("主机名:", self.hostname_label) + layout.addRow("IP地址:", self.ip_label) + layout.addRow("更新时间:", self.timestamp_label) + + refresh_btn = QPushButton("刷新") + refresh_btn.clicked.connect(self.refresh_system_info) + + btn_layout = QHBoxLayout() + btn_layout.addWidget(refresh_btn) + btn_layout.addStretch() + layout.addRow("", btn_layout) + + group.setLayout(layout) + parent_layout.addWidget(group) + + def create_manual_submit_group(self, parent_layout): + """手动提交组""" + group = QGroupBox("手动提交") + layout = QVBoxLayout() + layout.setSpacing(10) + + self.content_edit = QTextEdit() + self.content_edit.setPlaceholderText("输入要提交的内容...") + self.content_edit.setMaximumHeight(100) + + submit_btn = QPushButton("提交汇总记录") + submit_btn.clicked.connect(self.manual_submit) + + layout.addWidget(self.content_edit) + layout.addWidget(submit_btn) + + group.setLayout(layout) + parent_layout.addWidget(group) + + def create_log_group(self, parent_layout): + """日志组""" + group = QGroupBox("运行日志") + layout = QVBoxLayout() + layout.setSpacing(5) + + self.log_edit = QTextEdit() + self.log_edit.setReadOnly(True) + self.log_edit.setMaximumHeight(150) + + clear_btn = QPushButton("清除日志") + clear_btn.clicked.connect(self.log_edit.clear) + + layout.addWidget(self.log_edit) + layout.addWidget(clear_btn, alignment=Qt.AlignRight) + + group.setLayout(layout) + parent_layout.addWidget(group) + + def create_status_bar(self): + """创建状态栏""" + self.status_label = QLabel("就绪") + self.statusBar().addPermanentWidget(self.status_label) + + def create_actions(self): + """创建菜单动作""" + self.refresh_action = QAction("刷新", self) + self.refresh_action.triggered.connect(self.refresh_system_info) + + self.submit_action = QAction("提交", self) + self.submit_action.triggered.connect(self.manual_submit) + + self.quit_action = QAction("退出", self) + self.quit_action.triggered.connect(self.close) + + def setup_tray(self): + """设置系统托盘""" + if QSystemTrayIcon.isSystemTrayAvailable(): + self.tray_icon = QSystemTrayIcon(self) + self.tray_icon.setToolTip("汇总记录提交工具") + + tray_menu = QMenu() + tray_menu.addAction(self.refresh_action) + tray_menu.addAction(self.submit_action) + tray_menu.addSeparator() + tray_menu.addAction(self.quit_action) + + self.tray_icon.setContextMenu(tray_menu) + self.tray_icon.show() + + self.tray_icon.activated.connect(self.on_tray_activated) + + def on_tray_activated(self, reason): + """托盘图标激活处理""" + if reason == QSystemTrayIcon.DoubleClick: + self.show() + self.raise_() + self.activateWindow() + + def start_auto_refresh(self): + """启动自动刷新""" + self.timer = QTimer(self) + self.timer.timeout.connect(self.auto_refresh_cycle) + self.timer.start(self.submit_interval * 1000 if self.auto_submit else 5000) + + def auto_refresh_cycle(self): + """自动刷新周期""" + self.refresh_system_info() + if self.auto_submit: + self.auto_submit_content() + + def refresh_system_info(self): + """刷新系统信息""" + try: + cpu = psutil.cpu_percent(interval=None) + memory = psutil.virtual_memory() + disk = psutil.disk_usage('/') + hostname = socket.gethostname() + ip = socket.gethostbyname(hostname) + timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + + self.cpu_progress.setValue(int(cpu)) + self.memory_progress.setValue(int(memory.percent)) + self.disk_progress.setValue(int(disk.percent)) + + self.hostname_label.setText(hostname) + self.ip_label.setText(ip) + self.timestamp_label.setText(timestamp) + + self.log(f"系统信息已刷新 - CPU: {cpu}%, 内存: {memory.percent}%, 磁盘: {disk.percent}%") + except Exception as e: + self.log(f"获取系统信息失败: {str(e)}") + + def get_system_content(self): + """获取系统监控内容""" + info = self.get_system_info() + timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + return f"[系统监控 {timestamp}] CPU使用率: {info['cpu']}%, 内存使用率: {info['memory']}%, 磁盘使用率: {info['disk']}%" + + def auto_submit_content(self): + """自动提交系统监控内容""" + content = self.get_system_content() + self.submit_summary(content) + + def manual_submit(self): + """手动提交""" + content = self.content_edit.toPlainText().strip() + if not content: + QMessageBox.warning(self, "警告", "请输入要提交的内容") + return + self.submit_summary(content) + self.content_edit.clear() + + def get_csrf_token(self): + """获取CSRF token""" + try: + session = requests.Session() + get_url = f"{self.server_url}/" + self.log(f"正在访问首页获取CSRF token: {get_url}") + response = session.get(get_url, timeout=10) + self.log(f"首页响应状态码: {response.status_code}") + + cookies = session.cookies.get_dict() + self.log(f"收到的Cookies: {list(cookies.keys())}") + + csrf_token = cookies.get('csrftoken') + if csrf_token: + self.log(f"获取CSRF token成功: {csrf_token[:20]}...") + return session, csrf_token + else: + self.log("警告: 未获取到CSRF token,cookie中可能没有csrftoken") + self.log(f"所有cookies: {cookies}") + return session, None + except Exception as e: + self.log(f"获取CSRF token失败: {str(e)}") + return None, None + + def submit_summary(self, content): + """提交汇总记录到家庭日报系统""" + submit_url = f"{self.server_url}/api/v1/summary/submit/" + + try: + self.status_label.setText("正在提交...") + + session, csrf_token = self.get_csrf_token() + if session is None: + self.log("错误: 无法建立会话,连接可能失败") + self.status_label.setText("连接失败") + QMessageBox.critical(self, "错误", "无法连接到服务器,请检查URL是否正确") + return + + headers = { + 'Referer': self.server_url, + } + if csrf_token: + headers['X-CSRFToken'] = csrf_token + self.log(f"设置CSRF header: X-CSRFToken = {csrf_token[:20]}...") + + self.log(f"发送POST请求到: {submit_url}") + response = session.post(submit_url, data={'content': content}, headers=headers, timeout=10) + + self.log(f"服务器响应状态码: {response.status_code}") + self.log(f"响应Headers: {dict(response.headers)}") + self.log(f"响应内容: {response.text[:200]}...") + + if response.status_code != 200: + error_msg = f"服务器返回错误: HTTP {response.status_code}" + self.log(error_msg) + self.status_label.setText(f"错误: {response.status_code}") + QMessageBox.critical(self, "错误", f"{error_msg}\n\n响应内容:\n{response.text[:500]}") + return + + try: + result = response.json() + except json.JSONDecodeError as e: + error_msg = f"JSON解析失败: {str(e)}\n响应内容: {response.text[:200]}" + self.log(error_msg) + self.status_label.setText("JSON解析失败") + QMessageBox.critical(self, "错误", error_msg) + return + + if result['success']: + self.log(f"提交成功,记录ID: {result['id']}") + self.status_label.setText("提交成功") + QMessageBox.information(self, "成功", f"提交成功,记录ID: {result['id']}") + else: + self.log(f"提交失败: {result['message']}") + self.status_label.setText(f"失败: {result['message']}") + QMessageBox.warning(self, "失败", f"提交失败: {result['message']}") + except requests.exceptions.ConnectionError as e: + error_msg = f"连接失败: 无法连接到 {submit_url}" + self.log(error_msg) + self.status_label.setText("连接失败") + QMessageBox.critical(self, "错误", error_msg) + except requests.exceptions.Timeout: + error_msg = "请求超时,请检查服务器是否正常运行" + self.log(error_msg) + self.status_label.setText("连接超时") + QMessageBox.critical(self, "错误", error_msg) + except Exception as e: + error_msg = f"请求异常: {str(e)}" + self.log(error_msg) + self.status_label.setText("请求异常") + QMessageBox.critical(self, "错误", error_msg) + + def test_connection(self): + """测试服务器连接""" + url = self.server_url.rstrip('/') + "/api/v1/summary/submit/" + try: + self.status_label.setText("测试连接中...") + response = requests.get(url.replace('/submit/', '/'), timeout=5) + self.log(f"连接测试: 服务器响应状态码 {response.status_code}") + self.status_label.setText("连接成功") + QMessageBox.information(self, "成功", "服务器连接成功") + except requests.exceptions.ConnectionError: + self.log("连接测试: 无法连接到服务器") + self.status_label.setText("连接失败") + QMessageBox.warning(self, "失败", "无法连接到服务器,请检查URL是否正确") + except Exception as e: + self.log(f"连接测试: {str(e)}") + self.status_label.setText("测试异常") + QMessageBox.critical(self, "错误", f"测试失败: {str(e)}") + + def get_system_info(self): + """获取系统基本信息""" + try: + cpu_percent = psutil.cpu_percent(interval=1) + memory = psutil.virtual_memory() + disk = psutil.disk_usage('/') + return { + 'cpu': cpu_percent, + 'memory': memory.percent, + 'disk': disk.percent + } + except Exception as e: + self.log(f"获取系统信息失败: {str(e)}") + return {'cpu': 0, 'memory': 0, 'disk': 0} + + def log(self, message): + """记录日志""" + timestamp = datetime.now().strftime('%H:%M:%S') + log_message = f"[{timestamp}] {message}" + self.log_edit.append(log_message) + print(log_message) + + def on_url_changed(self, url): + """URL改变处理""" + self.server_url = url + + def on_auto_submit_toggled(self, checked): + """自动提交开关处理""" + self.auto_submit = checked + if checked: + self.log("已启用自动提交") + else: + self.log("已禁用自动提交") + + def on_interval_changed(self, value): + """刷新间隔改变处理""" + self.submit_interval = value + self.timer.setInterval(value * 1000 if self.auto_submit else 5000) + self.log(f"刷新间隔已设置为 {value} 秒") + + def closeEvent(self, event): + """关闭窗口事件""" + self.save_settings() + self.log("正在保存设置...") + event.accept() + + +def main(): + app = QApplication(sys.argv) + app.setStyle('Fusion') + + window = SubmitSummaryTool() + + if window.auto_start: + window.show() + else: + window.showMinimized() + + sys.exit(app.exec()) + + +if __name__ == "__main__": + main()