You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

590 lines
18 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "mainwindow.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QInputDialog>
#include <QMessageBox>
#include <QHeaderView>
#include <QMenu>
#include <QMenuBar>
#include <QFile>
#include <QTextStream>
#include <QFileDialog>
#include <QFileInfo>
#include <QToolBar>
#include <QToolButton>
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
setupUI();
setWindowTitle("学生信息管理系统");
}
MainWindow::~MainWindow() {}
void MainWindow::setupUI()
{
QWidget *central = new QWidget(this);
setCentralWidget(central);
// Action 定义
QAction *actImport = new QAction("导入", this);
QAction *actSave = new QAction("保存", this);
QAction *actAddCls = new QAction("新增班级", this);
QAction *actDelCls = new QAction("删除班级", this);
QAction *actRenCls = new QAction("重命名班级", this);
// 工具栏(唯一一行)
QToolBar *toolBar = addToolBar("主工具栏");
toolBar->setMovable(false);
toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
// 默认显示“文件”相关
toolBar->addAction(actImport);
toolBar->addAction(actSave);
// 伪菜单栏按钮
QToolButton *fileBtn = new QToolButton(this);
fileBtn->setText("文件");
fileBtn->setToolButtonStyle(Qt::ToolButtonTextOnly);
fileBtn->setAutoRaise(true); // 看起来像菜单
QToolButton *editBtn = new QToolButton(this);
editBtn->setText("编辑");
editBtn->setToolButtonStyle(Qt::ToolButtonTextOnly);
editBtn->setAutoRaise(true);
// 放到菜单栏位置
QWidget *menuWidget = new QWidget(this);
QHBoxLayout *menuLayout = new QHBoxLayout(menuWidget);
menuLayout->setContentsMargins(6, 0, 0, 0);
menuLayout->setSpacing(10);
menuLayout->addWidget(fileBtn);
menuLayout->addWidget(editBtn);
menuLayout->addStretch();
menuBar()->setCornerWidget(menuWidget, Qt::TopLeftCorner);
// 按钮点击 → 切换工具栏
connect(fileBtn, &QToolButton::clicked, this, [=]() {
toolBar->clear();
toolBar->addAction(actImport);
toolBar->addAction(actSave);
});
connect(editBtn, &QToolButton::clicked, this, [=]() {
toolBar->clear();
toolBar->addAction(actAddCls);
toolBar->addAction(actDelCls);
toolBar->addAction(actRenCls);
});
// 核心控件初始化
tree = new QTreeWidget;
tree->setHeaderLabel("班级列表");
table = new QTableWidget(0, 5);
table->setHorizontalHeaderLabels({"姓名", "性别", "年龄", "学号", "电话"});
table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
table->setSelectionBehavior(QAbstractItemView::SelectRows);
// 右侧编辑面板
nameEdit = new QLineEdit;
genderEdit = new QLineEdit;
ageEdit = new QLineEdit;
idEdit = new QLineEdit;
phoneEdit = new QLineEdit;
addBtn = new QPushButton("新增");
delBtn = new QPushButton("删除");
updateBtn = new QPushButton("修改");
searchBtn = new QPushButton("查询");
refreshBtn = new QPushButton("刷新/清空查询");
QVBoxLayout *rightLayout = new QVBoxLayout;
rightLayout->addWidget(new QLabel("姓名:"));
rightLayout->addWidget(nameEdit);
rightLayout->addWidget(new QLabel("性别:"));
rightLayout->addWidget(genderEdit);
rightLayout->addWidget(new QLabel("年龄:"));
rightLayout->addWidget(ageEdit);
rightLayout->addWidget(new QLabel("学号:"));
rightLayout->addWidget(idEdit);
rightLayout->addWidget(new QLabel("电话:"));
rightLayout->addWidget(phoneEdit);
rightLayout->addWidget(addBtn);
rightLayout->addWidget(updateBtn);
rightLayout->addWidget(delBtn);
rightLayout->addWidget(searchBtn);
rightLayout->addWidget(refreshBtn);
rightLayout->addStretch();
// 分页控件
btnPrev = new QPushButton("上一页");
btnNext = new QPushButton("下一页");
pageLabel = new QLabel("第 1/1 页");
QHBoxLayout *pageLayout = new QHBoxLayout;
pageLayout->addStretch();
pageLayout->addWidget(btnPrev);
pageLayout->addWidget(pageLabel);
pageLayout->addWidget(btnNext);
pageLayout->addStretch();
// 布局组合
QVBoxLayout *centerLayout = new QVBoxLayout;
centerLayout->addWidget(table);
centerLayout->addLayout(pageLayout);
QHBoxLayout *mainLayout = new QHBoxLayout(central);
mainLayout->addWidget(tree, 1);
mainLayout->addLayout(centerLayout, 3);
mainLayout->addLayout(rightLayout, 1);
// 信号槽连接
connect(actAddCls, &QAction::triggered, this, &MainWindow::addClass);
connect(actDelCls, &QAction::triggered, this, &MainWindow::deleteClass);
connect(actRenCls, &QAction::triggered, this, &MainWindow::renameClass);
connect(actImport, &QAction::triggered, this, &MainWindow::importData);
connect(actSave, &QAction::triggered, this, &MainWindow::saveData);
connect(tree, &QTreeWidget::itemClicked, this, &MainWindow::onClassClicked);
connect(addBtn, &QPushButton::clicked, this, &MainWindow::addStudent);
connect(delBtn, &QPushButton::clicked, this, &MainWindow::deleteStudent);
connect(updateBtn, &QPushButton::clicked, this, &MainWindow::updateStudent);
connect(searchBtn, &QPushButton::clicked, this, &MainWindow::searchStudent);
connect(refreshBtn, &QPushButton::clicked, this, &MainWindow::refreshAll);
connect(table, &QTableWidget::cellClicked, this, &MainWindow::tableItemClicked);
connect(btnPrev, &QPushButton::clicked, this, &MainWindow::prevPage);
connect(btnNext, &QPushButton::clicked, this, &MainWindow::nextPage);
resize(1000, 600);
}
// 业务逻辑实现
QString MainWindow::currentClass() const
{
auto item = tree->currentItem();
return item ? item->text(0) : "";
}
void MainWindow::refreshTable()
{
table->setRowCount(0);
int total = displayList.size();
int maxPage = qMax(1, (total + pageSize - 1) / pageSize);
if (currentPage > maxPage)
currentPage = maxPage;
pageLabel->setText(QString("第 %1/%2 页").arg(currentPage).arg(maxPage));
int start = (currentPage - 1) * pageSize;
int end = qMin(start + pageSize, total);
for (int i = start; i < end; ++i)
{
int r = table->rowCount();
table->insertRow(r);
table->setItem(r, 0, new QTableWidgetItem(displayList[i].name));
table->setItem(r, 1, new QTableWidgetItem(displayList[i].gender));
table->setItem(r, 2, new QTableWidgetItem(QString::number(displayList[i].age)));
table->setItem(r, 3, new QTableWidgetItem(displayList[i].id));
table->setItem(r, 4, new QTableWidgetItem(displayList[i].phone));
}
}
void MainWindow::onClassClicked(QTreeWidgetItem *item)
{
displayList = classData[item->text(0)];
currentPage = 1;
refreshTable();
}
void MainWindow::addStudent()
{
QString cls = currentClass();
if (cls.isEmpty())
return;
Student s{nameEdit->text(), genderEdit->text(), ageEdit->text().toInt(), idEdit->text(), phoneEdit->text()};
if (s.name.isEmpty() || s.id.isEmpty())
{
QMessageBox::warning(this, "错误", "姓名和学号不能为空"); //
return;
}
// 查重
for (const auto &st : classData[cls])
{
if (st.id == s.id)
{
QMessageBox::warning(this, "错误", "学号重复");
return;
}
}
classData[cls].append(s);
saveClassToFile(cls); // 自动保存
// 刷新输入框内容
nameEdit->clear();
genderEdit->clear();
ageEdit->clear();
idEdit->clear();
phoneEdit->clear();
refreshAll();
}
void MainWindow::updateStudent()
{
QString cls = currentClass();
int row = table->currentRow();
if (cls.isEmpty() || row < 0)
return;
int realIdx = (currentPage - 1) * pageSize + row;
Student &s = classData[cls][realIdx];
s.name = nameEdit->text();
s.gender = genderEdit->text();
s.age = ageEdit->text().toInt();
s.phone = phoneEdit->text();
saveClassToFile(cls);
refreshAll();
}
void MainWindow::deleteStudent()
{
QString cls = currentClass();
int row = table->currentRow();
if (row < 0 || QMessageBox::question(this, "确认", "确定删除?") != QMessageBox::Yes)
return;
int realIdx = (currentPage - 1) * pageSize + row;
classData[cls].removeAt(realIdx);
saveClassToFile(cls);
refreshAll();
}
void MainWindow::searchStudent()
{
QString cls = currentClass();
if (cls.isEmpty())
{
QMessageBox::warning(this, "提示", "请先在左侧选择一个班级再进行查询");
return;
}
// 1. 获取所有输入框的关键字,并去掉首尾空格
QString nameKw = nameEdit->text().trimmed();
QString genderKw = genderEdit->text().trimmed();
QString ageKw = ageEdit->text().trimmed();
QString idKw = idEdit->text().trimmed();
QString phoneKw = phoneEdit->text().trimmed();
displayList.clear();
// 2. 遍历当前班级的所有学生
for (const auto &s : classData[cls])
{
// 初始假设该学生符合条件
bool isMatch = true;
// 3. 逐个字段进行条件校验(逻辑:如果输入框不为空,则必须匹配该条件)
// 校验姓名
if (!nameKw.isEmpty() && !s.name.contains(nameKw, Qt::CaseInsensitive))
{
isMatch = false;
}
// 校验性别
if (isMatch && !genderKw.isEmpty() && !s.gender.contains(genderKw, Qt::CaseInsensitive))
{
isMatch = false;
}
// 校验年龄
if (isMatch && !ageKw.isEmpty() && !QString::number(s.age).contains(ageKw))
{
isMatch = false;
}
// 校验学号
if (isMatch && !idKw.isEmpty() && !s.id.contains(idKw, Qt::CaseInsensitive))
{
isMatch = false;
}
// 校验电话
if (isMatch && !phoneKw.isEmpty() && !s.phone.contains(phoneKw, Qt::CaseInsensitive))
{
isMatch = false;
}
// 4. 如果所有非空条件都通过,则加入显示列表
if (isMatch)
{
displayList.append(s);
}
}
currentPage = 1; // 重置到第一页
refreshTable(); // 刷新表格显示
}
void MainWindow::refreshAll()
{
QString cls = currentClass();
if (!cls.isEmpty())
displayList = classData[cls]; // 恢复初始状态
currentPage = 1;
// 刷新输入框内容
nameEdit->clear();
genderEdit->clear();
ageEdit->clear();
idEdit->clear();
phoneEdit->clear();
refreshTable();
}
// --- 文件与分页 ---
void MainWindow::saveClassToFile(const QString &className)
{
QFile file(className + ".txt"); // 按班级名保存
if (file.open(QIODevice::WriteOnly | QIODevice::Text))
{
QTextStream out(&file);
for (const auto &s : classData[className])
{
out << s.name << "," << s.gender << "," << s.age << "," << s.id << "," << s.phone << "\n";
}
}
}
void MainWindow::saveData()
{
for (auto it = classData.begin(); it != classData.end(); ++it)
saveClassToFile(it.key());
QMessageBox::information(this, "提示", "所有班级已保存");
}
void MainWindow::importData()
{
// 1. 直接弹出文件选择框,不需要预先选中班级
QString path = QFileDialog::getOpenFileName(this, "导入班级文件", "", "文本文件 (*.txt);;所有文件 (*.*)");
if (path.isEmpty())
return;
// 2. 提取文件名作为班级名
QFileInfo fileInfo(path);
QString className = fileInfo.baseName(); // 例如 "高三二班"
// 3. 检查班级是否已存在,不存在则新建
if (!classData.contains(className))
{
classData[className] = QList<Student>();
QTreeWidgetItem *newItem = new QTreeWidgetItem({className});
tree->addTopLevelItem(newItem);
tree->setCurrentItem(newItem); // 自动选中这个新创建的班级
}
else
{
// 如果班级已存在,询问是追加还是清空
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, "班级已存在",
QString("班级 '%1' 已存在,是否追加数据?\n(选“否”将清空原数据并覆盖)").arg(className),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
if (reply == QMessageBox::Cancel)
return;
if (reply == QMessageBox::No)
{
classData[className].clear(); // 清空原数据实现“覆盖”
}
// 选中已有的班级节点
QList<QTreeWidgetItem *> items = tree->findItems(className, Qt::MatchExactly);
if (!items.isEmpty())
tree->setCurrentItem(items[0]);
}
// 4. 读取文件内容
QFile file(path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QMessageBox::critical(this, "错误", "无法打开文件");
return;
}
QTextStream in(&file);
int count = 0;
while (!in.atEnd())
{
QString line = in.readLine();
QStringList fields = line.split(",");
if (fields.size() >= 5)
{
Student s;
s.name = fields[0].trimmed();
s.gender = fields[1].trimmed();
s.age = fields[2].trimmed().toInt();
s.id = fields[3].trimmed();
s.phone = fields[4].trimmed();
classData[className].append(s);
count++;
}
}
file.close();
// 5. 更新显示
displayList = classData[className];
currentPage = 1;
refreshTable();
QMessageBox::information(this, "导入成功", QString("已成功创建/更新班级 '%1',并导入 %2 条学生记录。").arg(className).arg(count));
}
void MainWindow::prevPage()
{
if (currentPage > 1)
{
currentPage--;
refreshTable();
}
}
void MainWindow::nextPage()
{
if (currentPage * pageSize < displayList.size())
{
currentPage++;
refreshTable();
}
}
void MainWindow::tableItemClicked(int row, int)
{
int realIdx = (currentPage - 1) * pageSize + row;
if (realIdx < displayList.size())
{
const auto &s = displayList[realIdx];
nameEdit->setText(s.name);
genderEdit->setText(s.gender);
ageEdit->setText(QString::number(s.age));
idEdit->setText(s.id);
phoneEdit->setText(s.phone);
}
}
// 班级管理槽函数(逻辑同前,移至菜单触发)
void MainWindow::addClass()
{
while (true)
{
bool ok;
// 弹出输入框
QString name = QInputDialog::getText(this, "新建班级", "请输入班级名称:", QLineEdit::Normal, "", &ok);
// 如果用户点击了“取消”或直接关闭窗口,则退出函数
if (!ok)
return;
name = name.trimmed(); // 去除首尾空格
// 逻辑判断 1不能为空
if (name.isEmpty())
{
QMessageBox::warning(this, "错误", "班级名称不能为空,请重新输入!");
continue; // 跳回循环开始,重新弹出输入框
}
// 逻辑判断 2检查是否已存在
if (classData.contains(name))
{
QMessageBox::warning(this, "错误", QString("班级“%1”已存在请换一个名称").arg(name));
continue; // 跳回循环开始,重新弹出输入框
}
// 校验通过:保存数据并更新界面
classData[name] = QList<Student>();
QTreeWidgetItem *newItem = new QTreeWidgetItem({name});
tree->addTopLevelItem(newItem);
tree->setCurrentItem(newItem); // 自动选中新创建的班级
break; // 成功创建后跳出循环
}
}
void MainWindow::deleteClass()
{
auto item = tree->currentItem();
if (!item)
{
QMessageBox::warning(this, "提示", "请先选择要删除的班级");
return;
}
if (item && QMessageBox::question(this, "确认", "删除班级及其信息?") == QMessageBox::Yes)
{
classData.remove(item->text(0));
delete item;
displayList.clear();
refreshTable();
}
}
void MainWindow::renameClass()
{
// 1. 获取当前选中的班级节点
auto item = tree->currentItem();
if (!item)
{
QMessageBox::warning(this, "提示", "请先选择要重命名的班级");
return;
}
QString oldName = item->text(0);
// 2. 使用循环处理重复或为空的情况
while (true)
{
bool ok;
// 弹出输入框,默认显示旧名称
QString newName = QInputDialog::getText(this, "重命名班级", "请输入新的班级名称:",
QLineEdit::Normal, oldName, &ok);
// 如果用户点击了“取消”,则直接退出
if (!ok)
return;
newName = newName.trimmed(); // 去除多余空格
// 情况 A名称没变直接退出
if (newName == oldName)
return;
// 情况 B名称为空
if (newName.isEmpty())
{
QMessageBox::warning(this, "错误", "班级名称不能为空,请重新输入!");
continue; // 跳回循环开始,再次弹出对话框
}
// 情况 C名称与现有其他班级重复
if (classData.contains(newName))
{
QMessageBox::warning(this, "错误", QString("班级“%1”已存在请换一个名称").arg(newName));
continue; // 跳回循环开始
}
// 3. 校验通过,更新底层数据结构 QMap
// 使用 take() 移除旧键并返回对应的数据列表,再赋值给新键
classData[newName] = classData.take(oldName);
// 4. 更新界面显示的名称
item->setText(0, newName);
break; // 完成重命名,跳出循环
}
}