#include "financialwidget.h"
#include "ui_financialwidget.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QChart>
#include <QChartView>
#include <QLabel>
#include <QComboBox>
#include <QDate>
#include <QDateEdit>
#include <QPushButton>
#include <QStringList>
#include <QTableWidget>
#include <QSqlQuery>
#include <QHeaderView>
#include <QDialog>
#include <QFormLayout>
#include <QLineEdit>
#include <QDialogButtonBox>
#include <QSqlError>
#include <QPieSeries>
#include <QPieSlice>
#include <QLineSeries>
#include <QDateTimeAxis>
#include <QValueAxis>
#include <QMessageBox>
#include <QScatterSeries>
#include <QGraphicsTextItem>
#include <QDebug>
QT_CHARTS_USE_NAMESPACE
FinancialWidget::FinancialWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::FinancialWidget)
{
ui->setupUi(this);
setupUI();
populateStudentComboBox();
loadFinancialRecords(); // 在 UI 初始化完成后再加载数据
}
FinancialWidget::~FinancialWidget()
{
delete ui;
}
void FinancialWidget::setupUI()
{
QVBoxLayout* mainLayout = new QVBoxLayout(this);
QHBoxLayout* topLayout = new QHBoxLayout();
QHBoxLayout* middleLayout = new QHBoxLayout();
// 创建图表视图(延迟创建确保非空)
chartView = new QChartView();
pieChartView = new QChartView();
// 设置抗锯齿
chartView->setRenderHint(QPainter::Antialiasing);
pieChartView->setRenderHint(QPainter::Antialiasing);
// 布局分配
mainLayout->addLayout(topLayout);
mainLayout->addLayout(middleLayout, 60); // 主体占60%
mainLayout->addWidget(chartView, 40); // 图表占40%
// =============== 顶部筛选条件 ===============
topLayout->addWidget(new QLabel("学生姓名:", this));
studentComboBox = new QComboBox(this);
topLayout->addWidget(studentComboBox);
topLayout->addWidget(new QLabel("起始日期:", this));
startDateEdit = new QDateEdit(QDate::currentDate().addMonths(-1));
startDateEdit->setCalendarPopup(true);
topLayout->addWidget(startDateEdit);
topLayout->addWidget(new QLabel("结束日期:", this));
endDateEdit = new QDateEdit(QDate::currentDate());
endDateEdit->setCalendarPopup(true);
topLayout->addWidget(endDateEdit);
addButton = new QPushButton("添加", this);
deleteButton = new QPushButton("删除", this);
editButton = new QPushButton("修改", this);
topLayout->addWidget(addButton);
topLayout->addWidget(deleteButton);
topLayout->addWidget(editButton);
topLayout->addStretch();
// =============== 主内容布局 ===============
tableWidget = new QTableWidget();
tableWidget->setFixedWidth(550);
tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
tableWidget->setAlternatingRowColors(true);
QStringList header = {"ID", "学生名字", "缴费日期", "金额", "支付类型", "备注"};
tableWidget->setColumnCount(header.count());
tableWidget->setHorizontalHeaderLabels(header);
tableWidget->setColumnHidden(0, true); // 隐藏 ID 列
tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
middleLayout->addWidget(tableWidget);
middleLayout->addWidget(pieChartView);
// 连接信号槽
connect(addButton, &QPushButton::clicked, this, &FinancialWidget::addRecord);
connect(deleteButton, &QPushButton::clicked, this, &FinancialWidget::deleteRecord);
connect(editButton, &QPushButton::clicked, this, &FinancialWidget::editRecord);
connect(studentComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &FinancialWidget::loadFinancialRecords);
connect(startDateEdit, &QDateEdit::dateChanged, this, &FinancialWidget::loadFinancialRecords);
connect(endDateEdit, &QDateEdit::dateChanged, this, &FinancialWidget::loadFinancialRecords);
// 调试打印(可选)
qDebug() << "Controls initialized:"
<< "addButton:" << addButton
<< "deleteButton:" << deleteButton
<< "editButton:" << editButton
<< "studentComboBox:" << studentComboBox
<< "startDateEdit:" << startDateEdit
<< "endDateEdit:" << endDateEdit
<< "tableWidget:" << tableWidget
<< "chartView:" << chartView
<< "pieChartView:" << pieChartView;
}
void FinancialWidget::loadFinancialRecords()
{
// 安全检查
if (!tableWidget || !chartView || !pieChartView) {
qWarning() << "UI components not fully initialized!";
return;
}
tableWidget->setRowCount(0);
QString studentId = studentComboBox->currentData().toString();
QDate startDate = startDateEdit->date();
QDate endDate = endDateEdit->date();
if (!startDate.isValid() || !endDate.isValid()) {
qWarning() << "Invalid date range.";
return;
}
// 参数化查询,防止SQL注入和格式错误
QSqlQuery query;
query.prepare(
"SELECT fr.id, s.name, fr.payment_date, fr.amount, fr.payment_type, fr.notes "
"FROM financialRecords fr "
"JOIN studentInfo s ON fr.student_id = s.id "
"WHERE fr.payment_date BETWEEN :startDate AND :endDate "
"AND (:studentId = '-1' OR fr.student_id = :studentId)"
);
query.bindValue(":startDate", startDate.toString("yyyy-MM-dd"));
query.bindValue(":endDate", endDate.toString("yyyy-MM-dd"));
query.bindValue(":studentId", studentId);
if (!query.exec()) {
qWarning() << "Failed to execute financial records query:" << query.lastError().text();
QMessageBox::warning(this, "数据库错误", "无法加载财务数据,请检查数据库连接或表结构。");
return;
}
while (query.next()) {
int row = tableWidget->rowCount();
tableWidget->insertRow(row);
for (int col = 0; col < 6; ++col) {
QTableWidgetItem *item = new QTableWidgetItem(query.value(col).toString());
item->setTextAlignment(Qt::AlignCenter);
tableWidget->setItem(row, col, item);
}
}
updateChart();
updatePieChart();
}
void FinancialWidget::populateStudentComboBox()
{
studentComboBox->clear();
studentComboBox->addItem("所有学生", "-1");
QSqlQuery query("SELECT id, name FROM studentInfo ORDER BY name");
while (query.next()) {
QString id = query.value(0).toString();
QString name = query.value(1).toString();
studentComboBox->addItem(name, id);
}
}
void FinancialWidget::addRecord()
{
QDialog dialog(this);
dialog.setWindowTitle("添加缴费记录");
QFormLayout form(&dialog);
QComboBox* studentNameComboBox = new QComboBox(&dialog);
QSqlQuery query("SELECT id, name FROM studentInfo ORDER BY name");
while (query.next()) {
QString id = query.value(0).toString();
QString name = query.value(1).toString();
studentNameComboBox->addItem(name, id);
}
QDateEdit* paymentDateEdit = new QDateEdit(QDate::currentDate(), &dialog);
paymentDateEdit->setCalendarPopup(true);
QLineEdit* amountEdit = new QLineEdit(&dialog);
QLineEdit* feeTypeEdit = new QLineEdit(&dialog);
QLineEdit* remarkEdit = new QLineEdit(&dialog);
form.addRow("学生名称:", studentNameComboBox);
form.addRow("缴费日期:", paymentDateEdit);
form.addRow("金额:", amountEdit);
form.addRow("支付类型:", feeTypeEdit);
form.addRow("备注:", remarkEdit);
QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog);
buttonBox.button(QDialogButtonBox::Ok)->setText("确定");
buttonBox.button(QDialogButtonBox::Cancel)->setText("取消");
form.addRow(&buttonBox);
connect(&buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
connect(&buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
if (dialog.exec() == QDialog::Accepted) {
QString studentId = studentNameComboBox->currentData().toString();
QString paymentDate = paymentDateEdit->date().toString("yyyy-MM-dd");
bool ok;
double amount = amountEdit->text().toDouble(&ok);
if (!ok || amount <= 0) {
QMessageBox::warning(this, "输入错误", "请输入有效的金额!");
return;
}
QString feeType = feeTypeEdit->text().trimmed();
QString remark = remarkEdit->text().trimmed();
QSqlQuery insertQuery;
insertQuery.prepare(
"INSERT INTO financialRecords (student_id, payment_date, amount, payment_type, notes) "
"VALUES (:student_id, :payment_date, :amount, :payment_type, :notes)"
);
insertQuery.bindValue(":student_id", studentId);
insertQuery.bindValue(":payment_date", paymentDate);
insertQuery.bindValue(":amount", amount);
insertQuery.bindValue(":payment_type", feeType);
insertQuery.bindValue(":notes", remark);
if (insertQuery.exec()) {
qDebug() << "新增记录成功:" << amount << "元";
loadFinancialRecords();
} else {
qCritical() << "插入失败:" << insertQuery.lastError().text();
QMessageBox::critical(this, "错误", "添加记录失败:" + insertQuery.lastError().text());
}
}
}
void FinancialWidget::updatePieChart()
{
if (!pieChartView) return;
QString studentId = studentComboBox->currentData().toString();
QDate startDate = startDateEdit->date();
QDate endDate = endDateEdit->date();
QSqlQuery query;
query.prepare(
"SELECT payment_type, SUM(amount) "
"FROM financialRecords "
"WHERE payment_date BETWEEN :startDate AND :endDate "
"AND (:studentId = '-1' OR student_id = :studentId) "
"GROUP BY payment_type"
);
query.bindValue(":startDate", startDate.toString("yyyy-MM-dd"));
query.bindValue(":endDate", endDate.toString("yyyy-MM-dd"));
query.bindValue(":studentId", studentId);
if (!query.exec()) {
qWarning() << "Pie chart query error:" << query.lastError().text();
return;
}
QChart* newChart = new QChart();
newChart->setTitle("支付类型分布");
QPieSeries* series = new QPieSeries();
series->setPieSize(0.75);
bool hasData = false;
double totalAmount = 0.0;
while (query.next()) {
QString type = query.value(0).toString();
qreal value = query.value(1).toDouble();
if (value > 0) {
hasData = true;
totalAmount += value;
QPieSlice* slice = series->append(QString("%1\n%2元").arg(type).arg(value), value);
slice->setLabelVisible(true);
slice->setLabelPosition(QPieSlice::LabelInsideTangential);
slice->setLabelColor(Qt::white);
slice->setLabel(QString("%1\n%2元\n%3%")
.arg(type)
.arg(value)
.arg(QString::number(value/totalAmount*100, 'f', 1)));
}
}
if (hasData) {
newChart->setTitle(QString("支付类型分布 (总金额: %1元)").arg(totalAmount));
newChart->addSeries(series);
} else {
newChart->setTitle("支付类型分布 (无数据)");
QPieSeries* emptySeries = new QPieSeries();
emptySeries->append("暂无数据", 1)->setLabel("暂无支付记录");
newChart->addSeries(emptySeries);
}
newChart->legend()->setVisible(true);
newChart->legend()->setAlignment(Qt::AlignBottom);
newChart->legend()->setBackgroundVisible(true);
newChart->legend()->setBrush(QBrush(Qt::white));
newChart->legend()->setLabelColor(Qt::black);
newChart->setAnimationOptions(QChart::AllAnimations);
QChart* oldChart = pieChartView->chart();
pieChartView->setChart(newChart);
if (oldChart) oldChart->deleteLater();
}
void FinancialWidget::createEmptyChart(const QString& message)
{
QChart* newChart = new QChart();
newChart->setTitle("支付趋势图");
QLineSeries* series = new QLineSeries();
series->append(0, 0);
newChart->addSeries(series);
QGraphicsTextItem* textItem = new QGraphicsTextItem(message);
textItem->setDefaultTextColor(Qt::gray);
textItem->setFont(QFont("Arial", 12, QFont::Bold));
newChart->scene()->addItem(textItem);
textItem->setPos(100, 100);
QValueAxis* axisX = new QValueAxis();
axisX->setRange(0, 1);
axisX->setVisible(false);
newChart->addAxis(axisX, Qt::AlignBottom);
QValueAxis* axisY = new QValueAxis();
axisY->setRange(0, 1);
axisY->setVisible(false);
newChart->addAxis(axisY, Qt::AlignLeft);
QChart* oldChart = chartView->chart();
chartView->setChart(newChart);
if (oldChart) oldChart->deleteLater();
}
void FinancialWidget::addDataMarkers(QChart* chart, const QMap<QDate, qreal>& dayData)
{
QScatterSeries* markers = new QScatterSeries();
markers->setName("数据点");
markers->setMarkerSize(8);
markers->setColor(QColor("#FF6B6B"));
markers->setBorderColor(Qt::white);
for (auto it = dayData.constBegin(); it != dayData.constEnd(); ++it) {
if (it.value() > 0) {
markers->append(it.key().startOfDay().toMSecsSinceEpoch(), it.value());
}
}
chart->addSeries(markers);
if (!chart->axes(Qt::Horizontal).isEmpty())
markers->attachAxis(chart->axes(Qt::Horizontal).first());
if (!chart->axes(Qt::Vertical).isEmpty())
markers->attachAxis(chart->axes(Qt::Vertical).first());
}
void FinancialWidget::updateChart()
{
if (!chartView || !startDateEdit || !endDateEdit) {
qWarning() << "Chart view or date edits not initialized!";
return;
}
QDate startDate = startDateEdit->date();
QDate endDate = endDateEdit->date();
if (!startDate.isValid() || !endDate.isValid() || startDate > endDate) {
createEmptyChart("日期范围无效");
return;
}
QString studentId = studentComboBox->currentData().toString();
QSqlQuery query;
query.prepare(
"SELECT DATE(payment_date), SUM(amount) "
"FROM financialRecords "
"WHERE payment_date BETWEEN :startDate AND :endDate "
"AND (:studentId = '-1' OR student_id = :studentId) "
"GROUP BY DATE(payment_date) ORDER BY DATE(payment_date)"
);
query.bindValue(":startDate", startDate.toString("yyyy-MM-dd"));
query.bindValue(":endDate", endDate.toString("yyyy-MM-dd"));
query.bindValue(":studentId", studentId);
if (!query.exec()) {
qCritical() << "Chart data query failed:" << query.lastError().text();
createEmptyChart("数据查询失败");
return;
}
QMap<QDate, qreal> dayData;
qreal maxAmount = 0;
bool hasData = false;
while (query.next()) {
QDate day = QDate::fromString(query.value(0).toString(), "yyyy-MM-dd");
if (!day.isValid()) continue;
qreal amount = query.value(1).toDouble();
if (amount > 0) {
dayData[day] = amount;
maxAmount = qMax(maxAmount, amount);
hasData = true;
}
}
QChart* newChart = new QChart();
newChart->setTitle(QString("每日支付趋势 (%1 - %2)")
.arg(startDate.toString("M/d"))
.arg(endDate.toString("M/d")));
if (hasData) {
QLineSeries* series = new QLineSeries();
series->setName("销售额");
QPen pen(QColor("#4A90E2"), 2);
pen.setCapStyle(Qt::RoundCap);
series->setPen(pen);
QDate current = startDate;
while (current <= endDate) {
qreal val = dayData.value(current, 0.0);
series->append(current.startOfDay().toMSecsSinceEpoch(), val);
current = current.addDays(1);
}
newChart->addSeries(series);
QDateTimeAxis* axisX = new QDateTimeAxis;
axisX->setFormat("M/d");
axisX->setTickCount(qMin(startDate.daysTo(endDate) + 1, 15));
axisX->setLabelsAngle(startDate.daysTo(endDate) > 7 ? -45 : 0);
axisX->setTitleText("日期");
axisX->setRange(startDate.startOfDay(), endDate.addDays(1).startOfDay());
newChart->addAxis(axisX, Qt::AlignBottom);
series->attachAxis(axisX);
QValueAxis* axisY = new QValueAxis;
axisY->setLabelFormat("%.0f");
axisY->setTitleText("金额 (元)");
axisY->setRange(0, maxAmount * 1.1);
newChart->addAxis(axisY, Qt::AlignLeft);
series->attachAxis(axisY);
addDataMarkers(newChart, dayData);
} else {
createEmptyChart("所选范围内无数据");
}
newChart->setMargins(QMargins(10, 10, 10, 30)); // 底部留白给标签
newChart->legend()->setVisible(true);
newChart->setAnimationOptions(QChart::AllAnimations);
QChart* oldChart = chartView->chart();
chartView->setChart(newChart);
chartView->repaint(); // 强制刷新
if (oldChart) {
oldChart->deleteLater();
}
}
void FinancialWidget::editRecord()
{
int currentRow = tableWidget->currentRow();
if (currentRow < 0) {
QMessageBox::warning(this, "警告", "请选择要修改的记录!");
return;
}
QTableWidgetItem *idItem = tableWidget->item(currentRow, 0);
QTableWidgetItem *nameItem = tableWidget->item(currentRow, 1);
QTableWidgetItem *dateItem = tableWidget->item(currentRow, 2);
QTableWidgetItem *amountItem = tableWidget->item(currentRow, 3);
QTableWidgetItem *typeItem = tableWidget->item(currentRow, 4);
QTableWidgetItem *noteItem = tableWidget->item(currentRow, 5);
if (!idItem || !nameItem || !dateItem || !amountItem || !typeItem) {
QMessageBox::warning(this, "错误", "数据不完整,无法编辑!");
return;
}
QString id = idItem->text();
QString originalName = nameItem->text();
QString dateStr = dateItem->text();
QString amountStr = amountItem->text();
QString typeStr = typeItem->text();
QString noteStr = noteItem ? noteItem->text() : "";
QDialog dialog(this);
dialog.setWindowTitle("修改缴费记录");
QFormLayout form(&dialog);
QComboBox* nameCombo = new QComboBox(&dialog);
QSqlQuery query("SELECT id, name FROM studentInfo ORDER BY name");
QString selectedStudentId;
while (query.next()) {
QString sid = query.value(0).toString();
QString sname = query.value(1).toString();
nameCombo->addItem(sname, sid);
if (sname == originalName) {
nameCombo->setCurrentIndex(nameCombo->count() - 1);
selectedStudentId = sid;
}
}
QLineEdit* dateEdit = new QLineEdit(dateStr, &dialog);
QLineEdit* amountEdit = new QLineEdit(amountStr, &dialog);
QLineEdit* typeEdit = new QLineEdit(typeStr, &dialog);
QLineEdit* noteEdit = new QLineEdit(noteStr, &dialog);
form.addRow("学生名称:", nameCombo);
form.addRow("缴费日期:", dateEdit);
form.addRow("金额:", amountEdit);
form.addRow("支付类型:", typeEdit);
form.addRow("备注:", noteEdit);
QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog);
buttonBox.button(QDialogButtonBox::Ok)->setText("确定");
buttonBox.button(QDialogButtonBox::Cancel)->setText("取消");
form.addRow(&buttonBox);
connect(&buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
connect(&buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
if (dialog.exec() == QDialog::Accepted) {
QString newStudentId = nameCombo->currentData().toString();
QString newDate = dateEdit->text();
bool ok;
double newAmount = amountEdit->text().toDouble(&ok);
if (!ok || newAmount < 0) {
QMessageBox::warning(this, "输入错误", "请输入有效金额!");
return;
}
QString newType = typeEdit->text();
QString newNote = noteEdit->text();
QSqlQuery updateQuery;
updateQuery.prepare(
"UPDATE financialRecords SET student_id = :sid, payment_date = :date, "
"amount = :amt, payment_type = :ptype, notes = :note WHERE id = :id"
);
updateQuery.bindValue(":sid", newStudentId);
updateQuery.bindValue(":date", newDate);
updateQuery.bindValue(":amt", newAmount);
updateQuery.bindValue(":ptype", newType);
updateQuery.bindValue(":note", newNote);
updateQuery.bindValue(":id", id);
if (updateQuery.exec()) {
qDebug() << "记录更新成功 ID:" << id;
loadFinancialRecords();
} else {
qWarning() << "更新失败:" << updateQuery.lastError().text();
QMessageBox::warning(this, "错误", "更新失败:" + updateQuery.lastError().text());
}
}
}
void FinancialWidget::deleteRecord()
{
int currentRow = tableWidget->currentRow();
if (currentRow < 0) {
QMessageBox::warning(this, "警告", "请先选择一条记录!");
return;
}
QTableWidgetItem *idItem = tableWidget->item(currentRow, 0);
if (!idItem) {
QMessageBox::warning(this, "错误", "无法获取记录ID!");
return;
}
bool ok;
int recordId = idItem->text().toInt(&ok);
if (!ok) {
QMessageBox::warning(this, "错误", "记录ID无效!");
return;
}
QMessageBox::StandardButton reply = QMessageBox::question(
this,
"确认删除",
"确定要删除这条记录吗?\n此操作不可恢复。",
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No
);
if (reply == QMessageBox::Yes) {
QSqlQuery query;
query.prepare("DELETE FROM financialRecords WHERE id = :id");
query.bindValue(":id", recordId);
if (query.exec() && query.numRowsAffected() > 0) {
qDebug() << "删除成功 ID:" << recordId;
loadFinancialRecords();
} else {
QMessageBox::warning(this, "删除失败", "未找到该记录或数据库错误。");
}
}
}
分析这串代码的问题并修改
最新发布