深入浅出 Qt 中 QListView 的设计思想,并掌握大规模、高性能列表的实现方法

在大规模列表控件的显示需求中,必须解决2个问题才能获得较好的性能:

  1. 第一就是数据存在哪里, 避免出现数据的副本。
  2. 第二就是如何展示Item,如何复用或避免创建大量的Item控件。

在QListView体系里,QAbstractListModel解决的是“数据存哪”,解决的是第一个问题,而QAbstractItemDelegate解决的是数据“如何展示”,解决的是第二个问题。

因此,在大规模列表的编写代码中,高效的数据存储和高效的数据UI展示,需要用到这两个类。接下来,我们通过三个例子,循序渐进地介绍QListView,使读者掌握QListView的使用技巧和设计思想。

示例 1: 使用 QListWidget 的基本用法

QListWidget 是一个方便的控件,它内部管理了一个项目列表,并提供了一些简单的接口来添加、删除和修改这些项目。但没有对数据存储和数据展示进行过多的优化,这种方式适合于简单的应用场景,其中列表的大小不会很大,因为每个项目都会被存储为一个 QListWidgetItem 对象。

#include <QApplication>
#include <QListWidget>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);    
    QListWidget* listWidget = new QListWidget;

    for (int i = 0; i < 100; ++i) {
        listWidget->addItem(QString("项目 %1").arg(i));
    }

    listWidget->show();    
    return app.exec();
}

尽管这种方式使用起来非常简单,但它并不适合处理大量数据。因为 QListWidget 会为每个项目创建一个 QListWidgetItem 对象,这将导致大量的内存消耗。

示例 2: 使用 QListView 和 QAbstractItemDelegate(解决数据存哪的问题)

在这个示例中,我们将直接从 QAbstractListModel 派生一个Model类,而不是使用 addItem构造大量的ItemData。这样,我们就无需存储这些数据。这个方法在处理具有可预知数据模式的大量数据时特别有用,因为它避免了冗余的数据存储和内存开销。

首先,我们定义一个 SyntheticListModel 类,它继承自 QAbstractListModel。这个模型将根据索引动态生成数据项:

#include <QAbstractListModel>
#include <QVariant>
#include <QModelIndex>

class SyntheticListModel : public QAbstractListModel {
    Q_OBJECT

public:
    SyntheticListModel(int numItems, QObject *parent = nullptr)
        : QAbstractListModel(parent), m_numItems(numItems) {}

    int rowCount(const QModelIndex &parent = QModelIndex()) const override {
        // 在顶层,返回项的数量;否则返回0,因为这是一个列表,没有子项
        return parent.isValid() ? 0 : m_numItems;
    }

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
        if (index.isValid() && index.row() < m_numItems) {
            if (role == Qt::DisplayRole) {
                // 根据行号动态生成数据
                return QString("项目 %1").arg(index.row());
            }
            // 可以根据需要添加其他角色的处理
        }
        return QVariant(); // 无效索引或角色时返回无效的QVariant
    }

private:
    int m_numItems; // 列表中项目的数量
};

然后,我们创建一个简单的 QStyledItemDelegate,这个委托可以根据需要自定义项的显示方式:

#include <QStyledItemDelegate>
#include <QPainter>

class SimpleDelegate : public QStyledItemDelegate {
    Q_OBJECT

public:
    using QStyledItemDelegate::QStyledItemDelegate; // 继承构造函数

    // 重写 paint 方法以自定义项目显示方式
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
        // 调用基类的 paint 方法进行默认的绘制
        QStyledItemDelegate::paint(painter, option, index);
        
        // 可以添加额外的绘制代码,如绘制边框、背景等
    }

    // 如果需要,可以重写 createEditor、setEditorData、setModelData 等方法以自定义编辑行为
};

最后,我们在 main 函数中创建 QListView,并将其与我们的自定义模型和委托相连接:

#include <QApplication>
#include <QListView>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    // 创建列表视图
    QListView listView;
    // 创建模型,这里我们创建了 100000 个假数据项
    SyntheticListModel model(100000);
    // 创建委托
    SimpleDelegate delegate;

    // 将模型和委托设置到列表视图
    listView.setModel(&model);
    listView.setItemDelegate(&delegate);

    listView.show();

    return app.exec();
}

在这个示例中,我们展示了如何使用 QListView 和自定义的 QAbstractListModel 来动态生成数据,而不需要在内存中维护一份数据存储的副本。在实际业务中,数据可以直接从业务模块中获取,这样避免出现数据的两个副本SimpleDelegate 负责定制列表项的视觉呈现,但在这个简化的例子中,我们仅使用了默认的绘制逻辑。如果需要更复杂的项显示或编辑功能,可以在委托中进一步扩展 paint 和其他相关方法。这些内容我们在示例3中展现。

示例 3: 使用 QListView 和自定义 QAbstractListModel(解决数据如何展示问题)

实例2中没有展开SimpleDelegate 的实现,在实际开发场景中,界面展示的需求往往更加复杂,特别是QListView的View模型采用的是paint函数来呈现,和其他图形界面框架(如AndroidFramework)构造一个QWidget*控件的形式不同,paint的形式用起来更复杂,但性能天花板更高

下面是一个使用自定义模型 LargeListModel 和委托 SimpleDelegate 的例子。在这个示例中,我们将创建一个自定义的 QAbstractListModel 类,名为 LargeListModel,它将处理大量数据项。此外,我们还将扩展 SimpleDelegate 类来自定义 QListView 中项的视觉呈现。这个委托将负责绘制项的背景、文本和一些装饰元素,从而提供更丰富的用户界面。

首先,我们定义 LargeListModel 类,该类派生自 QAbstractListModel

#include <QAbstractListModel>

class LargeListModel : public QAbstractListModel {
    Q_OBJECT
public:
    explicit LargeListModel(int numItems, QObject *parent = nullptr)
        : QAbstractListModel(parent), m_numItems(numItems) {}

    int rowCount(const QModelIndex &parent = QModelIndex()) const override {
        return parent.isValid() ? 0 : m_numItems;
    }

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
        if (!index.isValid() || index.row() >= m_numItems)
            return QVariant();

        switch (role) {
        case Qt::DisplayRole:
            // 动态生成显示的数据
            return QStringLiteral("Item %1").arg(index.row());
        // 这里可以根据需要添加对其他角色的处理
        default:
            return QVariant();
        }
    }

private:
    int m_numItems; // 数据项的数量
};

接下来,我们将自定义 SimpleDelegate 类,以便在 QListView 中渲染更复杂的项:

#include <QStyledItemDelegate>
#include <QPainter>

class SimpleDelegate : public QStyledItemDelegate {
public:
    using QStyledItemDelegate::QStyledItemDelegate; // 继承构造函数

    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
        // 保存当前的绘图状态
        painter->save();

        // 绘制项的背景
        if (option.state & QStyle::State_Selected) {
            painter->fillRect(option.rect, option.palette.highlight());
        } else {
            painter->fillRect(option.rect, option.palette.base());
        }
        
        // 绘制自定义内容,例如文本
        QString text = index.data(Qt::DisplayRole).toString();
        painter->drawText(option.rect, Qt::AlignLeft | Qt::AlignVCenter, text);

        // 还原绘图状态
        painter->restore();
    }

    // 如果需要项的大小不是默认值,可以重写 sizeHint 方法
    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override {
        return QSize(option.rect.width(), 50); // 设置项的高度为50
    }
};

最后,我们在 main 函数中创建 QListView,设置自定义的模型和委托,并显示它们:

#include <QApplication>
#include <QListView>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    // 创建自定义模型和委托
    LargeListModel *model = new LargeListModel(100000); // 假设有 10 万个数据项
    SimpleDelegate *delegate = new SimpleDelegate();

    // 创建并设置 QListView
    QListView listView;
    listView.setModel(model);
    listView.setItemDelegate(delegate);

    // 设置其他视图属性,如果需要
    listView.setSelectionMode(QAbstractItemView::SingleSelection);
    listView.setAlternatingRowColors(true); // 设置交替行颜色

    listView.show();

    return app.exec();
}

在这个示例中,LargeListModel 负责提供数据项,而 SimpleDelegate 负责自定义每个项的绘制。这种方法使得处理大量数据项时能够保持高性能,同时提供了丰富的视觉呈现和用户交互能力。通过委托的 paint 方法,我们可以绘制文本、图标、背景或任何其他图形元素来增强用户界面的视觉效果。通过重写 sizeHint 方法,我们可以为每个项定制大小,以适应不同的内容和设计需求。

这种模型-视图-委托的方法为高效地处理和展示大型数据集提供了灵活的解决方案,使得开发者可以在保持应用程序性能的同时,实现复杂且具有吸引力的用户界面。

进一步优化:处理大量动态数据

以上示例已经展示了如何使用 QListView 和自定义模型以及委托来处理静态数据。但在实际应用中,我们可能需要处理动态变化的数据集,其中项目可能会被添加、移除或更新。为了保持UI的响应性和数据的一致性,我们需要在模型中正确地处理这些变化。

数据添加示例

假设我们的 LargeListModel 需要动态添加数据,我们可以在模型中实现添加数据的逻辑,并通知视图更新:

class LargeListModel : public QAbstractListModel {
    // ...(省略已有代码)

public:
    // ...(省略已有代码)

    // 添加新项的方法
    void addItem(const QString &title) {
        const int index = itemCount;
        beginInsertRows(QModelIndex(), index, index);
        titles.append(title);
        checkedItems.insert(index, false);
        itemCount++;
        endInsertRows();
    }

    // ...(省略已有代码)
};

// 使用示例:
int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    // ...(省略已有代码)

    LargeListModel* model = new LargeListModel(0); // 初始时没有数据
    // ...(省略已有代码)

    // 动态添加数据
    for (int i = 0; i < 100000; ++i) {
        model->addItem(QString("动态项目 %1").arg(i));
    }

    // ...(省略已有代码)

    return app.exec();
}

在这个例子中,我们通过 addItem 方法在模型中添加新的数据项。在添加数据之前和之后,我们分别调用 beginInsertRowsendInsertRows 方法,这样 QListView 就会自动更新显示新添加的数据。

数据更新示例

如果我们需要更新现有数据,我们同样需要确保视图能够得到通知:

class LargeListModel : public QAbstractListModel {
    // ...(省略已有代码)

public:
    // ...(省略已有代码)

    // 更新某项的方法
    void updateItem(int index, const QString &newTitle) {
        if(index >= 0 && index < itemCount) {
            titles[index] = newTitle;
            QModelIndex modelIndex = createIndex(index, 0);
            emit dataChanged(modelIndex, modelIndex);
        }
    }

    // ...(省略已有代码)
};

在这里,我们通过 updateItem 方法更新一条数据,并通过 dataChanged 信号告知视图特定项的数据已经改变。createIndex 方法用来创建一个指向已更新项的 QModelIndex 对象,这是发出 dataChanged 信号所必需的。

性能注意事项

处理大量数据时,以下是一些提高性能的常见做法:

  • 使用 beginInsertRowsendInsertRows(或对应的删除和更新版本)时,请确保避免同时进行大量的单独插入或删除操作,因为这会导致视图频繁更新,从而降低性能。相反,应该批量插入或删除。
  • 避免在 data 方法中执行耗时的计算。如果需要,可以将数据缓存或使用后台线程来准备数据。
  • 如果列表项的大小是固定的,使用 setUniformItemSizes(true) 可以提高滚动和渲染的性能。
  • 如果数据的读取是昂贵的操作,考虑实现延迟加载或数据分页,这样只有当数据真正需要显示时才读取。

一个更复杂的完整例子

刚刚我们循序渐进地了解了QListView高性能大规模列表的设计思想和实现步骤,接下来我们实现一个稍微复杂的例子,这个例子在列表的每一项中增加了一个复选框和一个按钮,表示这是一个复杂列表项的呈现。

#include <QApplication>
#include <QCheckBox>
#include <QListView>
#include <QPainter>
#include <QPushButton>
#include <QStandardItemModel>
#include <QStyledItemDelegate>
#include <QAbstractListModel>
#include <QHash>
#include <QVariant>

class LargeListModel : public QAbstractListModel {
public:
	LargeListModel(int numItems, QObject* parent = nullptr)
		: QAbstractListModel(parent), itemCount(numItems) {
		titles.resize(itemCount); // 初始化标题数组
		for (int i = 0; i < itemCount; ++i) {
			titles[i] = QString("标题文本 %1").arg(i); // 生成初始标题文本
		}
	}

	int rowCount(const QModelIndex& parent = QModelIndex()) const override {
		if (parent.isValid()) {
			return 0;
		}
		return itemCount;
	}

	QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override {
		if (!index.isValid() || index.row() >= itemCount || index.row() < 0) {
			return QVariant();
		}
		switch (role) {
		case Qt::DisplayRole:
			return titles.at(index.row());
		case Qt::CheckStateRole:
		{
			auto it = checkedItems.find(index.row());
			if (it != checkedItems.end()) {
				return QVariant(it.value() ? Qt::Checked : Qt::Unchecked);
			}
			return QVariant(Qt::Unchecked);
		}
		case Qt::EditRole: // 处理编辑角色
			return titles[index.row()];
		default:
			return QVariant();
		}
	}

	bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override {
		if (!index.isValid() || index.row() >= itemCount || index.row() < 0)
			return false;

		switch (role) {
		case Qt::CheckStateRole:
			// 更新 checkedItems,记录复选框的状态
			checkedItems[index.row()] = (value.toBool());
			emit dataChanged(index, index, { role });
			return true;
		case Qt::EditRole:
			// 更新标题文本
			titles[index.row()] = value.toString();
			emit dataChanged(index, index, { role });
			return true;
		default:
			return false;
		}
	}

	Qt::ItemFlags flags(const QModelIndex& index) const override {
		if (!index.isValid())
			return Qt::NoItemFlags;
		// 添加 Qt::ItemIsEditable 以支持编辑
		return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEditable;
	}

private:
	int itemCount;
	mutable QHash<int, bool> checkedItems; // 存储复选框的状态
	QVector<QString> titles; // 存储标题文本
};


// 自定义委托来绘制和处理列表项
class CustomDelegate : public QStyledItemDelegate {
public:
	CustomDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {}

	// 绘制列表项
	void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override {
		QStyleOptionButton buttonOption;
		QRect buttonRect = QRect(option.rect.right() - 80, option.rect.y() + 1, 78, option.rect.height() - 2);
		buttonOption.rect = buttonRect;

		QStyleOptionButton checkboxOption;
		QRect checkboxRect = QRect(option.rect.x() + 5, option.rect.y() + 5, option.rect.height() - 10, option.rect.height() - 10);
		checkboxOption.rect = checkboxRect;
		checkboxOption.state |= QStyle::State_Enabled;
		if (index.data(Qt::CheckStateRole).toBool()) {
			checkboxOption.state |= QStyle::State_On;
		} else {
			checkboxOption.state |= QStyle::State_Off;
		}

		painter->save();

		if (option.state & QStyle::State_MouseOver) {
			painter->fillRect(option.rect, option.palette.light());
		}
		// 根据是否有鼠标悬停,绘制高亮背景
		if (option.state & QStyle::State_MouseOver) {
			QRect highlightRect = option.rect;
			painter->fillRect(highlightRect, option.palette.highlight());
		}

		// 绘制复选框
		QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkboxOption, painter);

		// 绘制按钮
		buttonOption.text = "按钮";
		QApplication::style()->drawControl(QStyle::CE_PushButton, &buttonOption, painter);

		// 绘制文本
		QRect textRect = option.rect.adjusted(checkboxRect.width() + 10, 0, -buttonRect.width() - 10, 0);
		painter->drawText(textRect, Qt::AlignVCenter, index.data().toString());

		painter->restore();
	}

	// 处理事件,如复选框的点击
	bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) override {
		if (event->type() == QEvent::MouseButtonRelease) {
			QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
			QRect buttonRect = QRect(option.rect.right() - 80, option.rect.y() + 5, 70, option.rect.height() - 10);
			QRect checkboxRect = QRect(option.rect.x() + 5, option.rect.y() + 5, option.rect.height() - 10, option.rect.height() - 10);

			if (buttonRect.contains(mouseEvent->pos())) {
				// 按钮被点击
				qDebug() << "按钮点击,项:" << index.row();
			} else if (checkboxRect.contains(mouseEvent->pos())) {
				// 切换复选框状态
				bool checked = !index.data(Qt::CheckStateRole).toBool();
				model->setData(index, checked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);
				qDebug() << "勾选checkbox,项:" << index.row();
			}
			return true; // 事件已处理
		}
		return QStyledItemDelegate::editorEvent(event, model, option, index);
	}

	// 提供项的大小提示
	QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override {
		return QSize(option.rect.width(), 34); // 调整为所需的大小
	}
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QListView* listView = new QListView;
	auto insertCount = 100000;// 构造展示10w条数据
	CustomDelegate* delegate = new CustomDelegate(listView);

	listView->setModel(new LargeListModel(insertCount));
	listView->setItemDelegate(delegate);

	// 优化性能
	listView->setUniformItemSizes(true);
	listView->setSelectionMode(QAbstractItemView::SingleSelection);
    listView->show();
    return app.exec();
}

展示效果:
在这里插入图片描述

结语

通过遵循上述模式和性能最佳实践,可以更好地创建强大的、响应迅速的 Qt 应用程序。

简而言之,在QListView体系里,QAbstractListModel解决的是“数据存哪”,而QAbstractItemDelegate解决的是数据“如何展示”。这种模型/视图和委托的架构是 Qt 高效数据处理的基石,使得复杂的UI设计和数据操作变得可管理和可扩展。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/700170.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

1.Anaconda-创建虚拟环境的手把手教程

文章目录 介绍&#xff08;必看&#xff09;正文版本信息模块安装流程1.创建虚拟环境2.激活环境3.退出虚拟环境4.安装python(激活虚拟环境)5.安装tensorflow(激活虚拟环境)6.安装matplotlib7.protobuf版本太高会有问题(激活虚拟环境) 常用的指令&#xff08;一定会用到&#xf…

基于C#开发web网页管理系统模板流程-主界面统计功能完善

前言 紧接上篇->基于C#开发web网页管理系统模板流程-主界面管理员入库和出库功能完善_c#web程序设计-CSDN博客 统计功能是管理系统很常见的功能&#xff0c;例如仓库管理系统要统计某时间段的出入库以整合利润情况&#xff0c;再例如论文管理系统要统计男女生的分数情况等等…

day35|1005.K次取反后最大化的数组和 134. 加油站135. 分发糖果

文章目录 python语法记录 sort格式 1005.K次取反后最大化的数组和思路方法一方法二 按照绝对值排序 教程&#x1f388;✨ 背住 按照绝对值进行降序排序的语法是&#xff1a; 134. 加油站思路方法一 教程解法方法二 暴力求解 135. 分发糖果思路方法一 总结 python语法记录 sort …

那些年我看过的技术书(持续更新,大佬的成长之路)

作为一个技术人啊&#xff0c;要学会多看书&#xff0c;发展自己。哦也&#xff01;你可以不关注&#xff0c;就把文章点个收藏吧&#xff0c;万一以后想看书了呢&#xff1f; 网络安全 CTF篇 入门篇 《极限黑客攻防&#xff1a;CTF赛题揭秘》 Web篇 Reserve篇 《IDApro…

Nature | 百年未变?博士评定机制该改改了!

19世纪初&#xff0c;德国和法国先后开始授予现代科研博士学位。时至今日&#xff0c;大学的科研与教学早已不同于往昔。但惊人的是&#xff0c;获得和评定博士学位的流程却几乎没变。但改革势在必行。 博士生导师可以从其他教育阶段的创新中学到很多东西。 Innovation in PhD…

TinyHttpd源码精读(三)

在上一章中我们一起看了如何实现静态的网页&#xff0c;在这里我们一起看Tinyhttpd最后的一部分&#xff0c;动态网页的实现&#xff1a;在这里首先声明下因为cgi脚本的支持问题&#xff0c;所以我会新建一个简单的cgi脚本然后将路径导向到这个脚本&#xff1a; 0.perl的配置&…

3、SLAM算法中的运动模型和观测模型和常用传感器类型

SLAM算法中的运动模型和观测模型 常用传感器类型&#xff1a;局部传感器&#xff08;相机、IMU、激光雷达等&#xff09;在小区域内提供了精确的位姿&#xff0c;而全局传感器&#xff08;GPS、磁力计、气压计等&#xff09;在大尺度环境中提供了有噪声但是全局无漂移的定位。 …

【three.js】光源对物体表面影响

目录 一、受光照影响材质 二、光源简介 2.1 点光源 光源位置 点光源辅助观察 完整代码,粘贴即用 2.2 环境光 2.3 平行光 平行光辅助观察 实际生活中物体表面的明暗效果是会受到光照的影响,比如晚上不开灯,你就看不到物体,灯光比较暗,物体也比较暗。在threejs中,…

金融数据中心布线运维管理解决方案

金融行业的核心业务&#xff0c;如交易、支付、结算等&#xff0c;对网络的依赖程度极高。布线作为网络基础设施的重要组成部分&#xff0c;其稳定性和可靠性直接关系到业务的连续运行。因此&#xff0c;良好的布线管理能够确保网络系统的稳定运行&#xff0c;减少因网络故障导…

安装sqlserver2022 express

1、下载 SQL Server 下载 | Microsoft 双击sql2022-ssei-expr 2、安装 下载完成以后&#xff0c;将会出现以下对话框 &#xff1a; 点击【全新SQL Server独立安装或向现有安全添加功能】 下一步&#xff0c;下一步&#xff1a; 下一步&#xff1a; 下一步&#xff0c;这里我…

encoding Token和embedding 傻傻分不清楚?

encoding 编码 “encoding” 是一个在计算机科学和人工智能领域广泛使用的术语&#xff0c;它可以指代多种不同的过程和方法。核心就是编码&#xff1a;用某些数字来表示特定的信息。当然你或许会说字符集(Unicode)更理解这种概念&#xff0c;编码更强调这种动态的过程。而字符…

Rejetto HTTP文件服务器 未授权RCE漏洞复现(CVE-2024-23692)

0x01 产品简介 Rejetto HTTP File Server(HFS)是一个基于HTTP协议的文件服务器软件,旨在为用户提供简单、轻量级且易于使用的文件共享解决方案。功能强大、易于使用的文件服务器软件,无论是个人使用还是团队协作,HFS都能满足用户的需求,提高工作效率。 0x02 漏洞概述 …

vue3根据按钮切换更新echarts对应的数据

效果图 初始化注意 setOption的函数定义&#xff0c;option是指图表的配置项和数据&#xff0c;notMerge是指是否不跟之前设置的 option 进行合并。默认为 false。即表示合并。如果为 true&#xff0c;表示所有组件都会被删除&#xff0c;然后根据新option 创建所有新组件 //…

springboot+vue前后端分离项目中使用jwt实现登录认证

文章目录 一、后端代码1.响应工具类2.jwt工具类3.登录用户实体类4.登录接口5.测试接口6.过滤器7.启动类 二、前端代码1.登录页index 页面 三、效果展示 一、后端代码 1.响应工具类 package com.etime.util;import com.etime.vo.ResponseModel; import com.fasterxml.jackson.…

采用java+springboot+vue+uniapp自主研发的智慧城管源码,城管综合执法平台源代码

智慧城管执法平台源码&#xff0c;PCAPP端全套源码&#xff0c;城管综合执法系统源码。 智慧城管系统拥有自主版权&#xff0c;项目落地案例&#xff0c;有演示&#xff0c;适合二次开发项目使用。 智慧城管执法系统旨在提高城市管理效率&#xff0c;涵盖了城市管理中的很多业务…

Linux kernel本地权限提升漏洞(CentOS8升级内核的解决方案)

一、CentOS8升级kernel内核的必要性 1、增强系统的安全性。 升级CentOS内核可以提供更好的安全性保障。新的内核版本通常包含了的安全补丁和漏洞修复&#xff0c;可以有效防止系统遭受恶意攻击&#xff0c;提高系统的稳定性和安全性。 2、优化硬件兼容性。 CentOS升级内核可以…

深度解析地推效果,Xinstall助您精准提升推广成效

在移动互联网时代&#xff0c;App的推广方式日趋多样化&#xff0c;其中地推作为线下推广的重要一环&#xff0c;因其成本相对较低、互动性强等特点&#xff0c;受到越来越多App厂商的青睐。然而&#xff0c;地推过程中往往面临着数据收集困难、获客率低、业务员费用结算标准难…

HTML5+CSS3小实例:粘性文字的滚动效果

实例:粘性文字的滚动效果 技术栈:HTML+CSS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-sca…

web刷题记录(5)

[羊城杯 2020]easycon 进来以后就是一个默认测试页面&#xff0c; 在这种默认界面里&#xff0c;我觉得一般不会有什么注入点之类的&#xff0c;所以这里先选择用御剑扫扫目录看看有没有什么存在关键信息的页面 扫了一半发现&#xff0c;很多都是和index.php文件有关&#xff0…

运维系列.在Docker中使用Grafana

运维专题 在Docker中使用Grafana - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_2855026…