1.1加载界面:
界面要素:
成员信息
变更位置申请
接受消息列表
根据角色加载对应界面。
1.2发起变更申请:
- 用户点击“发起变更申请”按钮。
- 变更申请对话框可编辑,
- 用户填写申请信息:
申请方(自动填充)。
目的方(选择老师或组长)。
若为老师角色 则可选其他组(除副班长) 若为组长则固定老师 - 提交申请:
- 流程显示
- 查看对应未读消息提醒
- 处理未读信息
界面效果展示
- 用户点击“提交”按钮,将申请信息发送至老师
1.3目标方(老师或组长)查看或待处理申请列表。
- 选择某个申请查看详细信息同意或驳回申请:
驳回 内容清空
目标方选择操作:
同意: - 更新申请状态 为“已同意”。
- 更新流程操作1\2\3
更新申请方的组别信息(如果目的方是老师)。
通知申请方其申请已被同意。
驳回:
更新申请状态为“已驳回”。
通知申请方其申请未被批准。
记录变更历史:
记录所有变更申请的历史信息。
返回申请列表:
目标方返回变更申请列表,继续处理其他申请。
2 源代码展示
2.1 mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTreeWidget>
#include <QComboBox>
#include <QPushButton>
#include <QVBoxLayout>
#include <QLabel>
#include <QHBoxLayout>
#include <QWidget>
#include <QTabWidget>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QStackedWidget>
#include "processprogressdisplaywidget.h"
#include "BubblelTipButton.h"
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
void setupMembersListTab(); // 设置成员列表标签页
void setupDynamicInfoTab(); // 设置动态信息标签页
void fillTargetComboBox(); // 填充目标方ComboBox
private slots:
void onMemberSelected(QTreeWidgetItem *item); // 响应成员选择
void onInitiateChangeClicked(); // 响应发起变更按钮点击
void onSendRequestClicked(); // 响应发送请求按钮点击
void OnPage1ButtonAddClicked();//列表按钮槽函数
void OnPage2ButtonAddClicked();//消息按钮槽函数
void confirmMessage(); // 确认消息的槽函数
void rejectMessage(); // 驳回消息的槽函数
private:
QStackedWidget *stackedWidget; // 堆叠窗口,用于切换不同页面
QWidget *page1;
QWidget *page2;
QPushButton *page1Button; // 发起变更按钮
BubblelTipButton *page2Button; // 发起变更按钮
QPushButton *page3Button; // 发起变更按钮
QTreeWidget *membersTreeWidget; // 成员树形控件
QComboBox *applicantComboBox; // 申请方ComboBox
QComboBox *targetComboBox; // 目标方ComboBox
QPushButton *initiateChangeButton; // 发起变更按钮
QPushButton *sendButton; // 发送按钮
QTabWidget *tabWidget; // 标签控件
QTableWidget *dynamicInfoTable; // 动态信息显示表格
ProcessProgressDisplayWidget* DisplayW;
int messageCount; // 记录当前消息数量
};
#endif // MAINWINDOW_H
2.2 mainwindow.cpp
#include "mainwindow.h"
#include <QApplication>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QComboBox>
#include <QPushButton>
#include <QTreeWidget>
#include <QTableWidget>
#include <QHeaderView>
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),messageCount(0)
{
setWindowTitle("人员分配管理系统");
QVBoxLayout *verticalLayout = new QVBoxLayout();
verticalLayout->setSpacing(0);
verticalLayout->setObjectName(QString::fromUtf8("verticalLayout"));
verticalLayout->setContentsMargins(0, 0, 0, 0);
page1Button = new QPushButton(this);
page1Button->setStyleSheet("QPushButton { "
"background-image: url(../Data/list.png); "
"border: none; "
"text-align: center; "
"color: white; "
"padding: 0px; "
"}");
//page1Button->setText("链表");
page1Button->setMinimumSize(QSize(30, 30));
page1Button->setMaximumSize(QSize(30, 30));
page2Button = new BubblelTipButton(this);
page2Button->setText("消息");
page2Button->setMinimumSize(QSize(40, 30));
page2Button->setMaximumSize(QSize(40, 30));
// page3Button = new QPushButton(this);
QSpacerItem *verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
verticalLayout->addWidget(page1Button);
verticalLayout->addWidget(page2Button);
// verticalLayout->addWidget(page3Button);
verticalLayout->addItem(verticalSpacer);
verticalLayout->setSpacing(2);
QVBoxLayout* Vlayout = new QVBoxLayout();
QHBoxLayout *layout = new QHBoxLayout();
layout->addLayout(verticalLayout);
layout->addLayout(Vlayout);
layout->setStretch(1, 10);
QWidget* CurrentWidget = new QWidget(this);
CurrentWidget->setLayout(layout);
setCentralWidget(CurrentWidget);
stackedWidget = new QStackedWidget(this);
page1 = new QWidget();
page2 = new QWidget();
setupMembersListTab();
setupDynamicInfoTab();
stackedWidget->addWidget(page1);
stackedWidget->addWidget(page2);
Vlayout->addWidget(stackedWidget);
connect(page1Button, SIGNAL(clicked()), this, SLOT(OnPage1ButtonAddClicked()));
connect(page2Button, SIGNAL(clicked()), this, SLOT(OnPage2ButtonAddClicked()));
}
MainWindow::~MainWindow() {
// 析构函数
}
void MainWindow::setupMembersListTab() {
QHBoxLayout *layout = new QHBoxLayout();
// 左侧树形控件显示成员信息
membersTreeWidget = new QTreeWidget(this);
membersTreeWidget->setHeaderLabels(QStringList() << "成员信息");
// 添加成员信息
QTreeWidgetItem *teacherA = new QTreeWidgetItem(membersTreeWidget, QStringList() << "老师A");
QTreeWidgetItem *groupLeaderB = new QTreeWidgetItem(membersTreeWidget, QStringList() << "组长B");
QTreeWidgetItem *deputyB1 = new QTreeWidgetItem(groupLeaderB, QStringList() << "副组长B1");
new QTreeWidgetItem(groupLeaderB, QStringList() << "成员H2");
new QTreeWidgetItem(groupLeaderB, QStringList() << "成员H3");
new QTreeWidgetItem(groupLeaderB, QStringList() << "成员H4");
QTreeWidgetItem *groupLeaderC = new QTreeWidgetItem(membersTreeWidget, QStringList() << "组长C");
QTreeWidgetItem *deputyC1 = new QTreeWidgetItem(groupLeaderC, QStringList() << "副组长C1");
new QTreeWidgetItem(groupLeaderC, QStringList() << "成员H7");
new QTreeWidgetItem(groupLeaderC, QStringList() << "成员H8");
new QTreeWidgetItem(groupLeaderC, QStringList() << "成员H9");
QTreeWidgetItem *groupLeaderD = new QTreeWidgetItem(membersTreeWidget, QStringList() << "组长D");
QTreeWidgetItem *deputyD1 = new QTreeWidgetItem(groupLeaderD, QStringList() << "副组长D1");
new QTreeWidgetItem(groupLeaderD, QStringList() << "成员H10");
new QTreeWidgetItem(groupLeaderD, QStringList() << "成员H11");
new QTreeWidgetItem(groupLeaderD, QStringList() << "成员H12");
layout->addWidget(membersTreeWidget, 1);
// 右侧变更申请信息区域
QWidget *requestWidget = new QWidget(this);
QVBoxLayout *requestLayout = new QVBoxLayout(requestWidget);
applicantComboBox = new QComboBox(this);
applicantComboBox->setEditable(true);
targetComboBox = new QComboBox(this);
sendButton = new QPushButton("发送申请", this);
initiateChangeButton = new QPushButton("发起变更", this);
initiateChangeButton->setEnabled(false); // 禁用发起变更按钮
applicantComboBox->setEnabled(false);
targetComboBox->setEnabled(false);
sendButton->setEnabled(false); // 初始禁用发送按钮
// 添加布局
QHBoxLayout *initiateLayout = new QHBoxLayout();
initiateLayout->addWidget(new QLabel("变更位置申请信息", this));
initiateLayout->addStretch();
initiateLayout->addWidget(initiateChangeButton);
requestLayout->addLayout(initiateLayout);
DisplayW = new ProcessProgressDisplayWidget(this);
DisplayW->setProcessList(QStringList()<<"编辑信息"<<"选择源"<<"待反馈");
DisplayW->setCurrentStep(0);
requestLayout->addWidget(DisplayW);
requestLayout->addWidget(applicantComboBox);
requestLayout->addWidget(targetComboBox);
requestLayout->addWidget(sendButton);
layout->addWidget(requestWidget, 3);
connect(membersTreeWidget, &QTreeWidget::itemClicked, this, &MainWindow::onMemberSelected);
connect(initiateChangeButton, &QPushButton::clicked, this, &MainWindow::onInitiateChangeClicked);
connect(sendButton, &QPushButton::clicked, this, &MainWindow::onSendRequestClicked);
page1->setLayout(layout);
}
void MainWindow::setupDynamicInfoTab() {
QVBoxLayout *layout = new QVBoxLayout();
dynamicInfoTable = new QTableWidget(this);
dynamicInfoTable->setColumnCount(4);
dynamicInfoTable->setHorizontalHeaderLabels(QStringList() << "申请方" << "目标方" << "申请信息" << "状态");
// 设置最后一列自动填充
dynamicInfoTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
layout->addWidget(dynamicInfoTable);
page2->setLayout(layout);
}
void MainWindow::fillTargetComboBox() {
targetComboBox->clear();
for (int i = 0; i < membersTreeWidget->topLevelItemCount(); ++i) {
QTreeWidgetItem *item = membersTreeWidget->topLevelItem(i);
targetComboBox->addItem(item->text(0));
for (int j = 0; j < item->childCount(); ++j) {
QTreeWidgetItem *child = item->child(j);
QString memberName = child->text(0);
// 检查成员是否为副组长
if (!memberName.contains("副组长")) { // 过滤副组长
targetComboBox->addItem(memberName); // 添加到目标组合框
}
}
}
}
void MainWindow::onMemberSelected(QTreeWidgetItem *item) {
if (item) {
applicantComboBox->setCurrentText(item->text(0)); // 设置申请方
}
DisplayW->setCurrentStep(0);
applicantComboBox->setEnabled(false);
targetComboBox->setEnabled(false);
sendButton->setEnabled(false); // 启用发送按钮
// 判断是否选择了副组长
QString selectedItemText = item->text(0);
if (selectedItemText.contains("副组长")) { // 假设副组长的名称包含“副组长”
initiateChangeButton->setEnabled(false); // 禁用发起变更按钮
} else {
initiateChangeButton->setEnabled(true); // 启用发起变更按钮
}
}
void MainWindow::onInitiateChangeClicked() {
applicantComboBox->setEnabled(true);
targetComboBox->setEnabled(true);
sendButton->setEnabled(true); // 启用发送按钮
fillTargetComboBox(); // 填充目标方下拉框
DisplayW->setCurrentStep(1);
}
void MainWindow::onSendRequestClicked() {
int rowCount = dynamicInfoTable->rowCount();
QString sender = "H2";
QString destination = "D";
QString reason = "位置变更申请";
// Update Table Widget
int row = dynamicInfoTable->rowCount();
dynamicInfoTable->insertRow(row);
dynamicInfoTable->setItem(row, 0, new QTableWidgetItem(sender));
dynamicInfoTable->setItem(row, 1, new QTableWidgetItem(destination));
dynamicInfoTable->setItem(row, 2, new QTableWidgetItem(reason));
dynamicInfoTable->setRowHeight(row, 50); // 设置其他行高度为50
// Status with buttons
QWidget *statusWidget = new QWidget(this);
QHBoxLayout *statusLayout = new QHBoxLayout(statusWidget);
QPushButton *confirmButton = new QPushButton("确认");
QPushButton *rejectButton = new QPushButton("驳回");
// Set row and column data using properties
confirmButton->setProperty("row", row);
confirmButton->setProperty("col", 3);
rejectButton->setProperty("row", row);
rejectButton->setProperty("col", 3);
// Add buttons to the layout
statusLayout->addWidget(confirmButton);
statusLayout->addWidget(rejectButton);
statusWidget->setLayout(statusLayout);
dynamicInfoTable->setCellWidget(row, 3, statusWidget);
connect(confirmButton, SIGNAL(clicked()), this, SLOT(confirmMessage()));
connect(rejectButton, SIGNAL(clicked()), this, SLOT(rejectMessage()));
// Update message count
messageCount++;
page2Button->setMsgNumber(messageCount);
applicantComboBox->clear();
targetComboBox->clear();
sendButton->setEnabled(false); // 发送后禁用按钮
DisplayW->setCurrentStep(2);
}
void MainWindow::OnPage1ButtonAddClicked()
{
stackedWidget->setCurrentIndex(0);
}
void MainWindow::OnPage2ButtonAddClicked()
{
stackedWidget->setCurrentIndex(1);
}
void MainWindow::confirmMessage()
{
QPushButton *button = qobject_cast<QPushButton *>(sender());
int row = button->property("row").toInt(); // 获取行索引
int col = 3; // 状态列
dynamicInfoTable->removeCellWidget(row, 3); // 删除旧的按钮 widget
dynamicInfoTable->setItem(row, 3, new QTableWidgetItem("已确认"));
messageCount--;
page2Button->setMsgNumber(messageCount);
stackedWidget->setCurrentIndex(0);
}
void MainWindow::rejectMessage()
{
QPushButton *button = qobject_cast<QPushButton *>(sender());
int row = button->property("row").toInt(); // 获取行索引
int col = 3; // 状态列
dynamicInfoTable->removeCellWidget(row, 3); // 删除旧的按钮 widget
dynamicInfoTable->setItem(row, 3, new QTableWidgetItem("已驳回"));
messageCount--;
page2Button->setMsgNumber(messageCount);
stackedWidget->setCurrentIndex(1);
}
2.3 processprogressdisplaywidget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
//流程进度展示控件
class ProcessProgressDisplayWidget : public QWidget
{
Q_OBJECT
public:
ProcessProgressDisplayWidget(QWidget *parent = nullptr);
~ProcessProgressDisplayWidget()override;
void setProcessList(const QStringList &value);
void setCurrentStep(int value);
protected:
void paintEvent(QPaintEvent *event)override;
private:
QStringList processList;
int currentStep{1};
QColor lineColor{"#C0C4CC"};
QColor stepsCompletedColor{"#41CD52"};//已完成步骤的颜色
QColor stepUncompleteColor{"#333333"};//未完成步骤的颜色
QColor currentStepColor{"#FC5531"};//当前步骤颜色
};
#endif // WIDGET_H
2.4 processprogressdisplaywidget.cpp
实现了一个自定义的小部件,用于展示一个过程进度条,并显示各步骤的圆形节点
- drawSmallCircle 是一个辅助函数,用于绘制带渐变颜色的圆形,以及圆形内居中的文本
函数通过 QPainter::save() 和 QPainter::restore() 来确保绘制时的状态不影响外部环境。
使用了 QLinearGradient 创建圆的渐变效果,增加视觉层次感
根据控件的宽高、步骤数量,计算出每个节点圆圈的半径、间距和位置。- 循环遍历每个步骤节点,根据 currentStep 来设置不同状态(已完成、当前步骤、未完成)的颜色,并绘制连接线和文本。
每个步骤使用 drawSmallCircle 函数绘制,并在圆圈下方显示步骤名称
#include "processprogressdisplaywidget.h"
#include <QPainter>
#include <QPaintEvent>
#include <QDebug>
ProcessProgressDisplayWidget::ProcessProgressDisplayWidget(QWidget *parent)
: QWidget(parent)
{
setPalette(Qt::white); // 设置背景为白色
}
ProcessProgressDisplayWidget::~ProcessProgressDisplayWidget()
{
}
//drawSmallCircle 是一个辅助函数,用于绘制带渐变颜色的圆形,以及圆形内居中的文本
void drawSmallCircle(QPainter * painter, int x, int y,int radius,const QColor & color,const QString & text)
{
QRect boundingRect = QRect(-radius,-radius,radius * 2,radius * 2);// 定义圆的边界矩形
painter->save();// 保存当前的 QPainter 状态
painter->translate(x,y);// 将绘制中心平移到 (x, y)
// 创建线性渐变效果,从矩形的左上角到右下角
QLinearGradient linearGradient(boundingRect.topLeft(),boundingRect.bottomRight());
linearGradient.setColorAt(0.0,color); // 渐变起始颜色
linearGradient.setColorAt(1.0,color.lighter()); // 渐变结束颜色
painter->setBrush(linearGradient);// 设置画刷为线性渐变
painter->setPen(QPen(QBrush(linearGradient), 3,Qt::SolidLine,Qt::RoundCap,Qt::RoundJoin));// 设置画笔,使用渐变的笔刷和圆角
painter->drawEllipse(boundingRect);// 绘制圆形
painter->setBrush(Qt::white);// 设置画刷为白色,用于绘制圆内部
painter->drawEllipse(boundingRect.adjusted(1,1,-1,-1));// 绘制稍微缩小的白色圆,制造圆边效果
painter->setPen(color);// 设置画笔颜色
painter->drawText(boundingRect,Qt::AlignCenter,text);// 在圆形内居中绘制文字
painter->restore();// 恢复 QPainter 状态
}
void ProcessProgressDisplayWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);// 创建 QPainter 对象,用于绘制控件
painter.setRenderHint(QPainter::Antialiasing,true);// 开启反锯齿
auto font = painter.font();
font.setPixelSize(20);// 设置字体大小为20像素
font.setBold(true);// 设置字体为粗体
painter.setFont(font);
auto rect = event->rect(); // 获取控件的矩形区域
auto width = rect.width(); // 获取控件的宽度
auto height = rect.height();// 获取控件的高度
int lineY = static_cast<int>(height * 0.3) ;// 进度条的纵坐标位置
int startX = 80;// 起始的横坐标
int size = processList.size();// 获取步骤数量
int radius = 20;// 圆的半径
int spacerLenth = (width - 2 * startX - (radius * 2 * size)) / (size - 1);// 计算每两个圆之间的间距
for(int i = 0;i < size;++i)
{
// 计算每个圆心的横坐标
int x = startX + radius + (spacerLenth * i) + (radius * 2 * i);
QColor * color;
if(i < (size - 1))
{
painter.save();
if(i < currentStep)
{
color = &stepsCompletedColor;// 已完成步骤的颜色
}
else
{
color = &lineColor;// 还未完成步骤的颜色
}
painter.setPen(QPen(*color,3));// 设置画笔颜色
// 绘制连接两个圆的线条
painter.drawLine(QPoint(x + radius,lineY),QPoint(x + radius + spacerLenth,lineY));
painter.restore();
}
// 设置当前步骤、已完成步骤和未完成步骤的颜色
if(i < currentStep)
{
color = &stepsCompletedColor; //已完成步骤
}
else if(i == currentStep)
{
color = ¤tStepColor; //当前步骤
}
else
{
color = &stepUncompleteColor; //未完成步骤
}
// 绘制每个步骤的圆圈和数字
drawSmallCircle(&painter,x,lineY+1,radius,*color,QString::number(i+1));
// 计算文本框的宽度
int textRectW;
if(i == 0 || i == (size - 1))
{
textRectW = 2 * (startX - 20) + radius * 2;
}
else
{
textRectW = spacerLenth + radius * 2;
}
// 计算文本框的起始横坐标
int rextRectX;
if(i == 0)
{
rextRectX = 20;
}
else if(i == (size - 1))
{
rextRectX = startX + i * (radius * 2) + spacerLenth * (i - 1) + (spacerLenth - (textRectW / 2) + radius);
}
else
{
rextRectX = startX + spacerLenth/2 + spacerLenth * (i - 1) + i * 2 * radius;
}
// 计算文本框的位置和大小
QRect textRect = QRect(rextRectX,
lineY + radius * 2,
textRectW,
height - lineY - radius * 2);
// 设置画笔颜色
painter.setPen(*color);
// 绘制步骤名称
painter.drawText(textRect,Qt::AlignTop | Qt::AlignHCenter,processList[i]);
}
}
//设置当前步骤
void ProcessProgressDisplayWidget::setCurrentStep(int value)
{
currentStep = value;
update();
}
void ProcessProgressDisplayWidget::setProcessList(const QStringList &value)
{
processList = value;
currentStep = 0;
update();
}
消息提示红点BubblelTipButton.h
#pragma once
#include <QPushButton>
class BubblelTipButton : public QPushButton
{
Q_OBJECT
public:
BubblelTipButton(QWidget *parent);
~BubblelTipButton();
void setMsgNumber(int number);
protected:
virtual void paintEvent(QPaintEvent * event);
private:
int bubbleWidth;
int m_msgNumber;
};
消息提示红点BubblelTipButton.cpp
#include <QPainter>
#include <QIcon>
#include "BubblelTipButton.h"
BubblelTipButton::BubblelTipButton(QWidget *parent)
: QPushButton(parent)
, bubbleWidth(20)
, m_msgNumber(0)
{
}
BubblelTipButton::~BubblelTipButton()
{
}
void BubblelTipButton::setMsgNumber(int number)
{
m_msgNumber = number;
update();
}
void BubblelTipButton::paintEvent(QPaintEvent * event)
{
QPainter painter(this);
QRect rt = rect();
painter.setRenderHint(QPainter::Antialiasing, true);
QPoint pointMsg(0, 15);
QPixmap pixmapMsg(QString::fromLocal8Bit("../Data/TestImage/message.png"));
//painter.drawPixmap(pointMsg, pixmapMsg);
QRect rt2 = QRect(rt.left(), rt.top(), rt.right() - 10, rt.bottom());
painter.drawPixmap(rt2, pixmapMsg); // 在按钮矩形区域内绘制图片
if (m_msgNumber > 0)
{
if (m_msgNumber > 9)
{
bubbleWidth = 23;
}
if (m_msgNumber > 99)
{
bubbleWidth = 27;
}
QRect rt1 = QRect(rt.right() - bubbleWidth, rt.top(), bubbleWidth, bubbleWidth);
painter.setPen(Qt::red);
painter.setBrush(QBrush(Qt::red));
painter.drawEllipse(rt1);
painter.setPen(Qt::white);
painter.drawText(rt1, Qt::AlignCenter, QString::number(m_msgNumber));
}
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
2疑难点
int spacerLenth = (width - 2 * startX - (radius * 2 * size)) / (size - 1); 这行用于计算进度条上两个圆形之间的间距
2.1参数解释:
- width: 窗口的总宽度。
- startX: 第一个圆距离窗口左侧的偏移量。
- radius: 圆的半径。
- size: 总共的步骤数,也就是圆的数量
2.2 公式解释:
1.width - 2 * startX:这是去掉两边空白区域后,绘制所有圆和它们之间间距的总可用宽度。
2.radius * 2 * size:这是所有圆形的总宽度。因为每个圆的直径是 2 * radius,
所以总共有 size 个圆,它们的总宽度为 radius * 2 * size。
3.width - 2 * startX - (radius * 2 * size):这是除去圆形本身所占的宽度后,剩余的可用宽度(用于间距的总宽度)。
4.(size - 1):总共 size 个圆,那么圆与圆之间的间距总共会有 size - 1 个。
2.3 示例说明:
假设:
width = 600 (窗口宽度为600像素)
startX = 80 (圆形距离窗口左边和右边各80像素的空白)
radius = 20 (每个圆的半径为20像素)
size = 5 (总共有5个步骤/圆)
2.3.1计算可用绘制区域宽度
width−2∗startX=600−2∗80=440
去掉两边的空白区域后,可用宽度为440像素
2.3.2计算所有圆形所占的总宽度
radius∗2∗size=20∗2∗5=200
5个圆形的总宽度为200像素
2.3.3计算用于间距的总宽度
440−200=240
在所有圆形绘制完后,剩下240像素的空间用于圆之间的间距
2.3.4计算圆之间的间距
spacerLenth= 240/(5-1) = 240/4 = 60
所以,两个圆形之间的间距为60像素