初始化

This commit is contained in:
2026-05-01 10:51:39 +08:00
commit fe6e4e428a
25 changed files with 5763 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
#ifndef ATTENDANCERECORD_H
#define ATTENDANCERECORD_H
#include <QString>
#include <QDateTime>
/**
* @brief 考勤记录数据模型
*
* 对应CSV文件格式:Timestamp,Student_ID,Name,Confidence,Status,Class_ID
*/
class AttendanceRecord
{
public:
AttendanceRecord();
AttendanceRecord(const QString &timestamp, const QString &studentId,
const QString &name, float confidence, const QString &status,
const QString &classId = "0");
// Getters
QString getTimestamp() const { return m_timestamp; }
QString getStudentId() const { return m_studentId; }
QString getName() const { return m_name; }
float getConfidence() const { return m_confidence; }
QString getStatus() const { return m_status; }
QString getClassId() const { return m_classId; }
QDateTime getDateTime() const { return m_dateTime; }
// Setters
void setTimestamp(const QString &timestamp);
void setStudentId(const QString &studentId) { m_studentId = studentId; }
void setName(const QString &name) { m_name = name; }
void setConfidence(float confidence) { m_confidence = confidence; }
void setStatus(const QString &status) { m_status = status; }
void setClassId(const QString &classId) { m_classId = classId; }
// 从CSV行解析
static AttendanceRecord fromCsvLine(const QString &line);
// 转换为显示字符串
QString toString() const;
private:
QString m_timestamp; // 时间戳字符串
QString m_studentId; // 学号
QString m_name; // 姓名
float m_confidence; // 置信度 (0.0-1.0)
QString m_status; // 状态 (success/fail)
QString m_classId; // 班级编号
QDateTime m_dateTime; // 解析后的日期时间对象
};
#endif // ATTENDANCERECORD_H
+98
View File
@@ -0,0 +1,98 @@
#ifndef FACEMANAGEDIALOG_H
#define FACEMANAGEDIALOG_H
#include <QDialog>
#include <QGridLayout>
#include <QScrollArea>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QMap>
#include <QNetworkAccessManager>
#include <QNetworkReply>
/**
* @brief 人脸照片项组件
* 显示单个人脸照片及其学号映射
*/
class FacePhotoItem : public QWidget
{
Q_OBJECT
public:
explicit FacePhotoItem(const QString &faceId, bool hasPhoto = true, QWidget *parent = nullptr);
QString getFaceId() const { return m_faceId; }
QString getStudentId() const { return m_studentIdEdit->text().trimmed(); }
void setStudentId(const QString &studentId) { m_studentIdEdit->setText(studentId); }
void setPhoto(const QPixmap &pixmap);
bool hasPhoto() const { return m_hasPhoto; }
signals:
void studentIdChanged(const QString &faceId, const QString &studentId);
void deleteRequested(const QString &faceId);
private:
QString m_faceId;
bool m_hasPhoto;
QLabel *m_photoLabel;
QLabel *m_idLabel;
QLineEdit *m_studentIdEdit;
QPushButton *m_deleteButton;
};
/**
* @brief 人脸管理对话框
* 显示K210上传的人脸照片,允许配置学号映射
*/
class FaceManageDialog : public QDialog
{
Q_OBJECT
public:
explicit FaceManageDialog(QWidget *parent = nullptr);
~FaceManageDialog();
void setServerUrl(const QString &url) { m_serverUrl = url; }
signals:
void mappingSaved();
public slots:
void refreshPhotos();
private slots:
void onPhotoListReceived(QNetworkReply *reply);
void onPhotoReceived(QNetworkReply *reply);
void onMappingReceived(QNetworkReply *reply);
void onMappingSaved(QNetworkReply *reply);
void onAllStudentIdsReceived(QNetworkReply *reply);
void onFaceDeleted(QNetworkReply *reply);
void saveMapping();
void onDeleteRequested(const QString &studentId);
private:
void setupUI();
void fetchPhotoList();
void fetchPhoto(const QString &faceId);
void fetchMapping();
void fetchAllStudentIds();
void uploadMapping();
void deleteFace(const QString &studentId, bool deleteRecords);
void clearPhotoItems();
QString m_serverUrl;
QNetworkAccessManager *m_networkManager;
QScrollArea *m_scrollArea;
QWidget *m_contentWidget;
QGridLayout *m_gridLayout;
QPushButton *m_refreshButton;
QPushButton *m_saveButton;
QLabel *m_statusLabel;
QMap<QString, FacePhotoItem*> m_photoItems; // faceId -> item
QMap<QString, QString> m_idMapping; // faceId -> studentId
int m_pendingPhotoCount;
};
#endif // FACEMANAGEDIALOG_H
+135
View File
@@ -0,0 +1,135 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTableWidget>
#include <QPushButton>
#include <QLabel>
#include <QTimer>
#include <QComboBox>
#include <QLineEdit>
#include <QCheckBox>
#include <QDateEdit>
#include <QVector>
#include <QEvent>
#include <QDialog>
#include "AttendanceRecord.h"
#include "NetworkApiManager.h"
class MappingConfigDialog;
/**
* @brief 对话框位置限制过滤器
* 限制对话框只能在父窗口范围内移动
*/
class DialogPositionFilter : public QObject
{
Q_OBJECT
public:
explicit DialogPositionFilter(QWidget *parent = nullptr) : QObject(parent) {}
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
};
/**
* @brief 考勤记录管理端主窗口(网络版)
*
* 功能:
* - 通过HTTP API从服务器获取数据
* - 显示考勤记录表格
* - 自动刷新数据(30秒)
* - 按学号、姓名、日期筛选
* - 统计信息显示
* - 服务器配置管理
*/
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
// 数据操作
void loadAttendanceData(); // 从服务器加载考勤数据
void refreshData(); // 刷新数据
void filterData(); // 筛选数据
void exportToExcel(); // 导出为CSV
void exportSelectedRecords(); // 导出选中的记录
void clearFilter(); // 清除筛选条件
void deleteSelectedRecord(); // 删除选中的记录
void deleteSelectedRecords(); // 批量删除选中的记录
void toggleSelectAll(); // 全选/取消全选
void unselectAll(); // 取消选中
// 管理功能
void onClearAllRecords(); // 清空所有签到记录(调用API)
void onOpenSettings(); // 打开时间段设置
void onOpenMappingConfig(); // 打开映射配置
void onOpenFaceManage(); // 打开人脸管理
void onShowStatistics(); // 显示统计报表
void onAbout(); // 关于对话框
// 网络响应处理
void onRecordsFetched(const QVector<AttendanceRecord> &records);
void onStatisticsFetched(int total, int today, const QString &serverTime);
void onRecordsCleared(bool success);
void onRecordDeleted(bool success); // 新增:删除记录响应
void onNetworkError(const QString &error);
void onMappingConfigFetched(const QMap<QString, QString> &studentNameMap,
const QMap<QString, QString> &classNameMap);
void onMappingConfigUploaded(bool success);
private:
void setupUI(); // 设置UI界面
void setupMenuBar(); // 设置菜单栏
void setupConnections(); // 设置信号槽连接
void updateTable(const QVector<AttendanceRecord> &records); // 更新表格
void updateStatistics(int total, int today); // 更新统计信息
void showConnectionStatus(bool connected); // 显示连接状态
// UI组件
QTableWidget *m_tableWidget;
QPushButton *m_refreshButton;
QPushButton *m_exportButton;
QPushButton *m_exportSelectedButton; // 导出选中
QPushButton *m_deleteSelectedButton; // 删除选中
QPushButton *m_clearFilterButton;
QPushButton *m_settingsButton;
QPushButton *m_statsButton;
QPushButton *m_clearRecordsButton;
QCheckBox *m_selectAllCheckBox; // 全选复选框
QPushButton *m_unselectAllButton; // 取消选中按钮
QLabel *m_totalLabel;
QLabel *m_todayLabel;
QLabel *m_statusLabel;
QLabel *m_connectionLabel;
QLabel *m_selectedCountLabel; // 显示选中数量
QComboBox *m_classFilterCombo; // 班级筛选下拉框
QComboBox *m_studentFilterCombo;
QLineEdit *m_nameFilterEdit;
QComboBox *m_dateFilterCombo;
QDateEdit *m_specificDateEdit; // 特定日期选择器
QCheckBox *m_enableSpecificDateCheckBox; // 启用特定日期筛选
// 数据
QVector<AttendanceRecord> m_allRecords; // 所有记录
QVector<AttendanceRecord> m_filteredRecords; // 筛选后的记录
QSet<QString> m_selectedRecordKeys; // 保存选中记录的唯一键(timestamp+studentId)
QMap<QString, QString> m_studentNameMap; // 学号 -> 真实姓名 映射
QMap<QString, QString> m_classNameMap; // 班级编号 -> 班级名称 映射
// 辅助函数
QString getMappedStudentName(const QString &studentId, const QString &originalName) const;
QString getMappedClassName(const QString &classId) const;
void loadMappingConfigFromSettings();
void saveMappingConfigToSettings();
// 网络
NetworkApiManager *m_apiManager; // 网络API管理器
QTimer *m_autoRefreshTimer; // 自动刷新定时器
};
#endif // MAINWINDOW_H
+89
View File
@@ -0,0 +1,89 @@
#ifndef MAPPINGCONFIGDIALOG_H
#define MAPPINGCONFIGDIALOG_H
#include <QDialog>
#include <QTableWidget>
#include <QPushButton>
#include <QLineEdit>
#include <QMap>
#include <QTabWidget>
#include <QLabel>
/**
* @brief 映射配置对话框
*
* 用于配置:
* 1. 学号 -> 真实姓名 的映射
* 2. 班级编号 -> 真实班级名称 的映射
*
* 特性:
* - 双击单元格可直接编辑(类似Excel)
* - 自动从服务器加载已有映射
* - 一键保存到服务器
*/
class MappingConfigDialog : public QDialog
{
Q_OBJECT
public:
explicit MappingConfigDialog(QWidget *parent = nullptr);
~MappingConfigDialog();
// 获取映射数据
QMap<QString, QString> getStudentNameMap() const { return m_studentNameMap; }
QMap<QString, QString> getClassNameMap() const { return m_classNameMap; }
// 设置映射数据(用于从服务器加载后更新)
void setStudentNameMap(const QMap<QString, QString> &map);
void setClassNameMap(const QMap<QString, QString> &map);
// 从表格收集数据到映射
void collectDataFromTables();
// 设置状态文本
void setStatus(const QString &status);
signals:
void mappingChanged(); // 映射数据已修改,需要保存
void requestSyncFromServer(); // 请求从服务器加载数据
private slots:
void onAddStudent();
void onDeleteStudent();
void onAddClass();
void onDeleteClass();
void onSave();
void onSyncToServer();
void onSyncFromServer();
private:
void setupUI();
void updateStudentTable();
void updateClassTable();
// UI组件
QTabWidget *m_tabWidget;
QLabel *m_statusLabel;
// 学号-姓名映射表
QTableWidget *m_studentTable;
QPushButton *m_addStudentBtn;
QPushButton *m_deleteStudentBtn;
// 班级编号-名称映射表
QTableWidget *m_classTable;
QPushButton *m_addClassBtn;
QPushButton *m_deleteClassBtn;
// 底部按钮
QPushButton *m_syncFromServerBtn;
QPushButton *m_syncToServerBtn;
QPushButton *m_saveBtn;
QPushButton *m_cancelBtn;
// 数据
QMap<QString, QString> m_studentNameMap; // 学号 -> 姓名
QMap<QString, QString> m_classNameMap; // 班级编号 -> 班级名称
};
#endif // MAPPINGCONFIGDIALOG_H
+89
View File
@@ -0,0 +1,89 @@
#ifndef NETWORKAPIMANAGER_H
#define NETWORKAPIMANAGER_H
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QVector>
#include "AttendanceRecord.h"
#include "TimeSlotConfig.h"
/**
* @brief 网络API管理类
*
* 负责与服务器端HTTP API通信(支持AES-128-CBC加密)
* API端点:http://server_ip:8082/api/
*/
class NetworkApiManager : public QObject
{
Q_OBJECT
public:
explicit NetworkApiManager(QObject *parent = nullptr);
~NetworkApiManager();
// 设置服务器地址
void setServerUrl(const QString &url);
QString getServerUrl() const { return m_serverUrl; }
// 设置是否启用加密通信
void setEncryptionEnabled(bool enabled) { m_encryptionEnabled = enabled; }
bool isEncryptionEnabled() const { return m_encryptionEnabled; }
// API调用
void fetchRecords(int limit = 100, int offset = 0);
void fetchStatistics();
void clearAllRecords();
void deleteRecord(const QString &timestamp, const QString &studentId);
void uploadTimeSlots(const QVector<TimeSlot> &timeSlots);
void checkHealth();
// 映射配置API
void uploadMappingConfig(const QMap<QString, QString> &studentNameMap,
const QMap<QString, QString> &classNameMap);
void fetchMappingConfig();
// 人脸照片管理API
void fetchFacePhotos();
void fetchFacePhoto(const QString &faceId);
void uploadFaceIdMapping(const QMap<QString, QString> &mapping);
void fetchFaceIdMapping();
signals:
// 响应信号
void recordsFetched(const QVector<AttendanceRecord> &records);
void statisticsFetched(int total, int today, const QString &serverTime);
void recordsCleared(bool success);
void recordDeleted(bool success);
void timeSlotsUploaded(bool success);
void healthChecked(bool healthy);
void errorOccurred(const QString &error);
// 映射配置信号
void mappingConfigUploaded(bool success);
void mappingConfigFetched(const QMap<QString, QString> &studentNameMap,
const QMap<QString, QString> &classNameMap);
// 人脸照片管理信号
void facePhotosFetched(const QJsonArray &photos);
void facePhotoFetched(const QString &faceId, const QByteArray &photoData);
void faceIdMappingUploaded(bool success);
void faceIdMappingFetched(const QMap<QString, QString> &mapping);
private slots:
void onReplyFinished(QNetworkReply *reply);
private:
QNetworkAccessManager *m_networkManager;
QString m_serverUrl;
bool m_encryptionEnabled; // 是否启用加密通信
QString buildApiUrl(const QString &endpoint, const QMap<QString, QString> &params = QMap<QString, QString>());
QVector<AttendanceRecord> parseRecordsJson(const QByteArray &json);
// AES加密/解密功能
QByteArray aesDecrypt(const QByteArray &encryptedData);
QByteArray removePKCS7Padding(const QByteArray &data);
};
#endif // NETWORKAPIMANAGER_H
+33
View File
@@ -0,0 +1,33 @@
#ifndef SERVERCONFIGDIALOG_H
#define SERVERCONFIGDIALOG_H
#include <QDialog>
#include <QLineEdit>
#include <QPushButton>
#include <QLabel>
/**
* @brief 服务器配置对话框
*/
class ServerConfigDialog : public QDialog
{
Q_OBJECT
public:
explicit ServerConfigDialog(const QString &currentUrl, QWidget *parent = nullptr);
QString getServerUrl() const;
private slots:
void onTestConnection();
void onSave();
private:
void setupUI();
QLineEdit *m_urlEdit;
QPushButton *m_testButton;
QLabel *m_statusLabel;
QString m_serverUrl;
};
#endif // SERVERCONFIGDIALOG_H
+75
View File
@@ -0,0 +1,75 @@
#ifndef SETTINGSDIALOG_H
#define SETTINGSDIALOG_H
#include <QDialog>
#include <QTableWidget>
#include <QPushButton>
#include <QTimeEdit>
#include <QLineEdit>
#include <QCheckBox>
#include "TimeSlotConfig.h"
#include "NetworkApiManager.h"
/**
* @brief 设置对话框 - 管理签到时间段
*/
class SettingsDialog : public QDialog
{
Q_OBJECT
public:
explicit SettingsDialog(QWidget *parent = nullptr);
~SettingsDialog();
private slots:
void onAddTimeSlot();
void onEditTimeSlot();
void onDeleteTimeSlot();
void onResetToDefault();
void onSave();
void onCancel();
void onTimeSlotsUploaded(bool success); // 新增:处理上传结果
private:
void setupUI();
void loadTimeSlots();
void saveTimeSlots();
void refreshTimeSlots(); // 新增:只刷新表格显示,不重新加载数据
// UI组件
QTableWidget *m_tableWidget;
QPushButton *m_addButton;
QPushButton *m_editButton;
QPushButton *m_deleteButton;
QPushButton *m_resetButton;
QPushButton *m_saveButton;
QPushButton *m_cancelButton;
// 临时存储
QVector<TimeSlot> m_tempTimeSlots;
// 网络API管理器
NetworkApiManager *m_apiManager;
};
/**
* @brief 时间段编辑对话框
*/
class TimeSlotEditDialog : public QDialog
{
Q_OBJECT
public:
explicit TimeSlotEditDialog(QWidget *parent = nullptr, const TimeSlot *slot = nullptr);
TimeSlot getTimeSlot() const;
private:
void setupUI();
QLineEdit *m_nameEdit;
QTimeEdit *m_startTimeEdit;
QTimeEdit *m_endTimeEdit;
QCheckBox *m_enabledCheckBox;
};
#endif // SETTINGSDIALOG_H
+58
View File
@@ -0,0 +1,58 @@
#ifndef STATISTICSDIALOG_H
#define STATISTICSDIALOG_H
#include <QDialog>
#include <QTableWidget>
#include <QPushButton>
#include <QLabel>
#include <QDateEdit>
#include <QComboBox>
#include <QMap>
#include "AttendanceRecord.h"
/**
* @brief 统计报表对话框
*/
class StatisticsDialog : public QDialog
{
Q_OBJECT
public:
explicit StatisticsDialog(const QVector<AttendanceRecord> &records, QWidget *parent = nullptr);
~StatisticsDialog();
private slots:
void onDateRangeChanged();
void onExportReport();
void onRefresh();
private:
void setupUI();
void calculateStatistics();
void updateTable();
// 统计数据结构
struct StudentStats {
QString studentId;
QString name;
int totalCount; // 总签到次数
int todayCount; // 今日签到次数
QDateTime lastCheckIn; // 最后签到时间
double avgConfidence; // 平均置信度
};
// UI组件
QDateEdit *m_startDateEdit;
QDateEdit *m_endDateEdit;
QComboBox *m_quickSelectCombo;
QTableWidget *m_tableWidget;
QPushButton *m_exportButton;
QPushButton *m_refreshButton;
QLabel *m_summaryLabel;
// 数据
QVector<AttendanceRecord> m_records;
QMap<QString, StudentStats> m_statsMap;
};
#endif // STATISTICSDIALOG_H
+92
View File
@@ -0,0 +1,92 @@
#ifndef TIMESLOTCONFIG_H
#define TIMESLOTCONFIG_H
#include <QString>
#include <QTime>
#include <QVector>
#include <QSettings>
/**
* @brief 签到时间段结构
*/
struct TimeSlot
{
QString name; // 时段名称(如:上午签到)
QTime startTime; // 开始时间
QTime endTime; // 结束时间
bool enabled; // 是否启用
TimeSlot()
: enabled(true)
{}
TimeSlot(const QString &n, const QTime &start, const QTime &end, bool en = true)
: name(n), startTime(start), endTime(end), enabled(en)
{}
// 检查给定时间是否在此时段内
bool isInRange(const QTime &time) const {
if (!enabled) return false;
return time >= startTime && time <= endTime;
}
QString toString() const {
return QString("%1: %2 - %3 %4")
.arg(name)
.arg(startTime.toString("HH:mm"))
.arg(endTime.toString("HH:mm"))
.arg(enabled ? "(启用)" : "(禁用)");
}
};
/**
* @brief 时间段配置管理类
*/
class TimeSlotConfig
{
public:
static TimeSlotConfig& instance();
// 获取所有时间段
QVector<TimeSlot> getTimeSlots() const { return m_timeSlots; }
// 设置时间段
void setTimeSlots(const QVector<TimeSlot> &timeSlots);
// 添加时间段
void addTimeSlot(const TimeSlot &timeSlot);
// 删除时间段
void removeTimeSlot(int index);
// 更新时间段
void updateTimeSlot(int index, const TimeSlot &timeSlot);
// 检查时间是否在任何启用的时段内
bool isValidTime(const QTime &time) const;
// 获取当前时段名称
QString getCurrentTimeSlotName(const QTime &time) const;
// 保存配置到文件
void saveToFile();
// 从文件加载配置
void loadFromFile();
// 重置为默认配置
void resetToDefault();
private:
TimeSlotConfig();
~TimeSlotConfig();
TimeSlotConfig(const TimeSlotConfig&) = delete;
TimeSlotConfig& operator=(const TimeSlotConfig&) = delete;
QVector<TimeSlot> m_timeSlots;
QString m_configFilePath;
void initDefaultTimeSlots();
};
#endif // TIMESLOTCONFIG_H