1. 重点内容
- 配置项说明
- 建立多数据源
- 加密配置项
2. 运行展示
3. 程序示例
DB模块主要是对数据库的操作,它是纯Qt上的封装,不涉及第三方的库,Qt操作数据库主要是QSqlDatabase,QSqlQuery,QSqlRecord对象,在此基础上将这些对象的协作简化成API来完成,请看以下API说明:
//打开数据库
bool open();
//数据库是否打开
bool isOpen();
//type指定的支持驱动类型是否存在
bool isDriverSupport(const QString &type);
//关闭数据库
void close();
//取指定select语句中列名的列号
int queryGetColumnNo(const QSqlQuery &query, const QString &column);
//取指定select语句中列号的列名
QString queryGetColumnName(const QSqlQuery &query, const int &columnNo);
//取指定select语句中所有字段名称
QStringList queryGetColumnNames(const QSqlQuery &query);
//取记录数
int queryRowCount(const QSqlQuery &query);
int queryColumnCount(const QSqlQuery &query);
//取指定select语句的值
QVariant queryValue(const QSqlQuery &query, const QString &column);
//取指定select语句指定列名的值,该值是整型
int queryIntValue(const QSqlQuery &query, const QString &column);
//取指定select语句指定列名的值,该值是长整型
long queryLongValue(const QSqlQuery &query, const QString &column);
//取指定select语句指定列名的值,该值是单精度型
float queryFloatValue(const QSqlQuery &query, const QString &column);
//取指定select语句指定列名的值,该值是双精度型
double queryDoubleValue(const QSqlQuery &query, const QString &column);
//取指定select语句指定列名的值,该值是布尔型
bool queryBoolValue(const QSqlQuery &query, const QString &column);
//取指定select语句指定列名的值,该值是字符串型
QString queryStringValue(const QSqlQuery &query, const QString &column);
//取指定select语句指定列名的值,该值是日期型
QDate queryDateValue(const QSqlQuery &query, const QString &column);
//取指定select语句指定列名的值,该值是日期时间型
QDateTime queryDateTimeValue(const QSqlQuery &query, const QString &column);
//取指定select语句指定列名的值,该值是字节型
QByteArray queryByteArrayValue(const QSqlQuery &query, const QString &column);
QSqlDatabase db() const;
QSqlQuery sqlQuery() const;
void queryExec(QSqlQuery &query, bool &isSuccess, QString &errorText);
void queryExec(QSqlQuery &query, const QString &sqlText, bool &isSuccess, QString &errorText);
我们操作数据库可以用上述方法就可以完成。
为了支持Qt对不同数据源及不同数据库操作,灵活配置在peanut_appconfig.xml中。
为了支持在代码层,SQL语句与代码业务逻辑不混在一起,支持它们之间的分离,支持不同数据库的切换,借鉴并简化了另一种方式SQLMapper,后面将重点介绍。
3.1. 配置项说明
DB模块的BeanConfig类名PlDBBeanConfig,该类目前实现的Bean类名PlDBController,结构如下:
BeanConfig:PlDBBeanConfig
Bean: PlDBController
PlDBBeanConfig配置项在peanut_appconfig.xml中,参考如下配置:
<beanConfig id="dbBeanConfigSample" chooseBeanId="" className="peanut::PlDBBeanConfig" name="DBModule" type="DB">
<bean id="sqliteDBBeanSample" connectOptions="" userName="" port="" className="peanut::PlDBController" hostName="" description="" isEncrypt="false" name="sqliteDBBeanSample" databaseName="./db/peanutsample.db" password="" type="QSQLITE">
</bean>
</beanConfig>
说明:
- BeanConfig: id="dbBeanConfigSample"的BeanConfig,对应的类名className="peanut::PlDBBeanConfig"
- Bean:在BeanConfig下配置一个id="sqliteDBBeanSample",类名className="peanut::PlDBController"
属性type:Qt的数据库驱动名称,有如下值可选,但是在Qt的版本中某些驱动要单独编译
QDB2 IBM DB2
QIBASE Borland InterBase Driver
QMYSQL MySQL Driver
QOCI Oracle Call Interface Driver
QODBC ODBC Driver (includes Microsoft SQL Server)
QPSQL PostgreSQL Driver
QSQLITE SQLite version 3 or above
QSQLITE2 SQLite version 2
QTDS Sybase Adaptive Server
目前peanutlib1.5.10版本支持的驱动如下:
QSQLITE QMYSQL QMYSQL3 QODBC QODBC3 QPSQL QPSQL7
本例中配置的Bean的数据库驱动type=”QSQLITE”
属性name:name属性值在DB模块中具有唯一性,当Bean加载时,如果name值不存在,会将name的值使用QSqlDatabase::addDatabase(...)方法,如果name存在,会直接引用该数据库的QSqlDatabase对象。
属性databaseName:数据库名称,对应QSqlDababase对象的setDatabaseName方法参数值。本例中,databaseName="./db/peanutsample.db",表示使用的sqlite数据库,它的数据库文件放在当前工作目录的db目录下的peanutsample.db文件中。
属性hostName:主机名称,对应QSqlDababase对象的setHostName方法参数值。
属性userName:用户名称,对应QSqlDababase对象的setUserName方法参数值。
属性password:密码,对应QSqlDababase对象的setPassword方法参数值。
属性port:端口号,对应QSqlDababase对象的sePort方法参数值。
属性isEncrypt:是否当前Bean的数据库配置项加密保护(通常在系统上线运行后,可使用该开关将配置数据库的数据加密),true表示加密,设置后系统运行时会自动加密字符串;false表示不加密,使用当前配置项的值。
3.2 建立多数据源
多数据源指能得到一至多个数据库的连接,例如,得到一个连接sqlite的数据库连接,得到一个连接MYSQL的数据库连接,利用连接对象就可以对数据库操作(注:peanutlib的数据库连接与操作都由PlDBController完成)。下面是示例及运行界面。
- 在peanut_appconfig.xml中设置DB模块
<beanConfig id="dbBeanConfigSample" name="DBModule" type="DB" className="peanut::PlDBBeanConfig" chooseBeanId="" >
<bean id="sqliteDBBeanSample" name="sqliteDBBeanSample" className="peanut::PlDBController" databaseName="./db/peanutsample.db" type="QSQLITE" userName="" port="" hostName="" description="" isEncrypt="false" password="" connectOptions="" >
</bean>
<bean id="mysqlDBBeanSample" name="mysql-peanutsample" className="peanut::PlDBController" hostName="127.0.0.1" userName="root" password="123456" port="3306" databaseName="peanutsample" type="QMYSQL" isEncrypt="false" connectOptions="">
</bean>
</beanConfig>
注:id="dbBeanConfigSample"的DB模块,配置两个Bean,分别是id="sqliteDBBeanSample"的连接sqlite数据库(该数据库放在本地当前工作目录/db/peanutsample.db中),id="mysqlDBBeanSample"连接mysql数据库(本机安装mysql,建立名为peanutsample的数据库)。
- .h文件ps_conndb_mainwindow.h
/***************************************************************************
* This file is part of the Peanut Library project *
* Copyright (C) 2022 by Wang Ren Qian *
* author:积木虎 154318869@qq.com *
* Peanut Software Studio *
****************************************************************************/
#ifndef PS_CONNDB_MAINWINDOW_H
#define PS_CONNDB_MAINWINDOW_H
#include <QMainWindow>
#include <pl_db.h>
#include <grid/pl_grid_info.h>
namespace peanut {
namespace Ui {
class PsConndbMainWindow;
}
class PsConndbMainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit PsConndbMainWindow(QWidget *parent = nullptr);
~PsConndbMainWindow();
protected:
//初始化
void init();
//将Bean的属性值转换到TableWidget中
void insertTableWidget(const PlBeanInfo &beanInfo, int row=0);
//将Bean的属性值转换到表单界面中
void setDBParamForm(const PlBeanInfo &beanInfo);
//从界面中取已设定的参数值
PlSequenceMap getMapFromDialog(bool &isSuccess, QString &errMsg);
private slots:
//当前TableWidget的行发生改变后触发
void on_currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous);
//单击测试连接
void on_tbTest_clicked();
private:
Ui::PsConndbMainWindow *ui;
//DB模块的BeanConfig对象
PlDBBeanConfig *m_dbBeanConfig=nullptr;
//DB模块的BeanCoinfig的ID值
QString m_dbBeanConfigId="dbBeanConfigSample";
//设置一个Grid对象(注:tableWidget 属于Grid)
PlGridInfo m_gridInfo;
//图标颜色
QColor m_iconColor = QColor(Qt::red);
};
}
#endif // PS_CONNDB_MAINWINDOW_H
- .cpp文件ps_conndb_mainwindow.cpp
/***************************************************************************
* This file is part of the Peanut Library project *
* Copyright (C) 2022 by Wang Ren Qian *
* author:积木虎 154318869@qq.com *
* Peanut Software Studio *
****************************************************************************/
#include "ps_application.h"
#include "ps_conndb_mainwindow.h"
#include "ui_ps_conndb_mainwindow.h"
#include <pl_utils.h>
namespace peanut {
PsConndbMainWindow::PsConndbMainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::PsConndbMainWindow)
{
ui->setupUi(this);
bool isSuccess;
QString errMsg;
//设置测试连接的图标
QVariantMap options;
options.insert( "color" , m_iconColor);
QIcon icon = PlQIconUtils::getIcon("repeat", isSuccess, errMsg, options);
ui->tbTest->setIcon(icon);
//设置连接类型的初始值
ui->cbType->clear();
QStringList list = QSqlDatabase::drivers();
foreach(QString type, list) {
ui->cbType->addItem(type);
}
//初始化
init();
}
PsConndbMainWindow::~PsConndbMainWindow()
{
delete ui;
}
void PsConndbMainWindow::init()
{
PsApplication *app = PsApplication::getPsApplication();
Q_ASSERT(app!=nullptr);
//得到DB模块的BeanConfig对象
m_dbBeanConfig = app->getDBBeanConfig(m_dbBeanConfigId);
if(m_dbBeanConfig==nullptr) {
PlMessageBox::warning(this, tr("异常警告信息"), tr("找不到[id=%1]的DBBeanConfig").arg(m_dbBeanConfigId));
return;
}
//设置Grid的表格头
m_gridInfo.insertHearderColumnInfo(PlGridHearderColumnInfo("beanId", tr("连接Id"), Qt::Horizontal));
m_gridInfo.insertHearderColumnInfo(PlGridHearderColumnInfo("beanName", tr("连接名称"), Qt::Horizontal));
m_gridInfo.insertHearderColumnInfo(PlGridHearderColumnInfo("beanType", tr("连接类型"), Qt::Horizontal));
m_gridInfo.insertHearderColumnInfo(PlGridHearderColumnInfo("beanConfigId", tr("配置项Id"), Qt::Horizontal));
m_gridInfo.insertHearderColumnInfo(PlGridHearderColumnInfo("beanConfigName", tr("配置项名称"), Qt::Horizontal));
//初始化TableWidget的表格头
ui->tableWidget->setColumnCount(m_gridInfo.countColumn());
foreach(PlGridHearderColumnInfo info, m_gridInfo.getHearderColumnInfos()) {
QTableWidgetItem *headerItem = new QTableWidgetItem();
headerItem->setText(info.getColumnValue());
QFont font = headerItem->font(); //获取原有字体设置
font.setBold(true);//设置为粗体
headerItem->setFont(font);//设置字体
headerItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableWidget->setHorizontalHeaderItem(info.getColumnNo(), headerItem); //设置表头单元格的Item
}
//得到DB模块的BeanConfig的Info对象
PlBeanConfigInfo beanConfig = m_dbBeanConfig->getBeanConfigInfo();
//取DB模块的BeanConfig的所有Bean的Info对象
QList<PlBeanInfo> beanList = beanConfig.getBeanInfos();
int curRow = 0;
foreach(PlBeanInfo info, beanList) {
if(info.type()=="SQLMapper") {
continue;
}
curRow = ui->tableWidget->rowCount();
if(curRow<0) curRow = 0;
ui->tableWidget->insertRow(curRow);
//将当前Bean的Info对象转换到TableWidget中
insertTableWidget(info, curRow);
}
ui->tableWidget->setSelectionBehavior( QAbstractItemView::SelectionBehavior::SelectRows );
ui->tableWidget->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
ui->tableWidget->setAlternatingRowColors(true);
ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
ui->tableWidget->horizontalHeader()->setStretchLastSection(true);
//绑定tableWidget对象的当前行发生改变信号与槽
connect(ui->tableWidget->selectionModel(),SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
this,SLOT(on_currentRowChanged(QModelIndex,QModelIndex)));
if(ui->tableWidget->rowCount()>0) {
QTableWidgetItem *item = ui->tableWidget->item(0,0);
ui->tableWidget->setCurrentItem(item); //第一行为当前行
}
}
void PsConndbMainWindow::insertTableWidget(const PlBeanInfo &beanInfo, int row)
{
Q_ASSERT(m_dbBeanConfig!=nullptr);
QTableWidgetItem *item = new QTableWidgetItem();
item->setFlags(item->flags() & (~Qt::ItemIsEditable));//设置不可编辑
item->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
item->setText(beanInfo.id());
ui->tableWidget->setItem(row, 0, item);
item = new QTableWidgetItem();
item->setFlags(item->flags() & (~Qt::ItemIsEditable));//设置不可编辑
item->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
item->setText(beanInfo.name());
ui->tableWidget->setItem(row, 1, item);
item = new QTableWidgetItem();
item->setFlags(item->flags() & (~Qt::ItemIsEditable));//设置不可编辑
item->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
item->setText(beanInfo.type());
ui->tableWidget->setItem(row, 2, item);
item = new QTableWidgetItem();
item->setFlags(item->flags() & (~Qt::ItemIsEditable));//设置不可编辑
item->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
item->setText(m_dbBeanConfig->getBeanConfigInfo().id());
ui->tableWidget->setItem(row, 3, item);
item = new QTableWidgetItem();
item->setFlags(item->flags() & (~Qt::ItemIsEditable));//设置不可编辑
item->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
item->setText(m_dbBeanConfig->getBeanConfigInfo().name());
ui->tableWidget->setItem(row, 4, item);
}
void PsConndbMainWindow::setDBParamForm(const PlBeanInfo &beanInfo)
{
//得到Bean的Info对象的所有属性与值
QMap<QString,QString> map = beanInfo.attrMap();
QString isEncrypt = map.value("isEncrypt","");
if(isEncrypt.toLower()!="true") {
isEncrypt = "false";
}
else {
isEncrypt = "true";
}
QString value;
if(isEncrypt=="true") {
value = map.value("hostName");
value = PlBaseDBController::decrypt(value);
map.insert("hostName", value);
value = map.value("userName");
value = PlBaseDBController::decrypt(value);
map.insert("userName", value);
value = map.value("password");
value = PlBaseDBController::decrypt(value);
map.insert("password", value);
value = map.value("port");
value = PlBaseDBController::decrypt(value);
map.insert("port", value);
value = map.value("databaseName");
value = PlBaseDBController::decrypt(value);
map.insert("databaseName", value);
}
ui->leId->setText(beanInfo.id());
ui->leName->setText(beanInfo.name());
ui->leUserName->setText(map.value("userName"));
ui->leIP->setText(map.value("hostName"));
ui->lePassword->setText(map.value("password"));
ui->lePort->setText(map.value("port"));
ui->leDatabaseName->setText(map.value("databaseName"));
ui->leConnectOptions->setText(map.value("connectOptions"));
if(isEncrypt=="true") {
ui->rbIsEncryptY->setChecked(true);
ui->rbIsEncryptN->setChecked(false);
}
else {
ui->rbIsEncryptY->setChecked(false);
ui->rbIsEncryptN->setChecked(true);
}
value = map.value("type");
ui->cbType->setCurrentText(value);
}
PlSequenceMap PsConndbMainWindow::getMapFromDialog(bool &isSuccess, QString &errMsg)
{
PlSequenceMap map; //按先进序列排序的Map
PlCheckInputStateForm input; //检查界面中输入型控件的状态
try {
input.inputTextAndIsNull(ui->leId, "id", tr("输入框[%1]不能为空").arg("连接ID"),true);
input.inputTextAndIsNull(ui->leName, "name", tr("输入框[%1]不能为空").arg("连接名称"),true);
input.inputTextAndIsNull(ui->leIP, "hostName", "", false);
input.inputTextAndIsNull(ui->leUserName, "userName", "",true);
input.inputTextAndIsNull(ui->lePassword, "password", "",true);
input.inputTextAndIsNull(ui->lePort, "port", "",true);
input.inputTextAndIsNull(ui->leDatabaseName, "databaseName", "",true);
input.inputTextAndIsNull(ui->leConnectOptions, "connectOptions", "",true);
input.inputTextAndIsNull(ui->cbType, "type", "",true,false);
if(ui->rbIsEncryptY->isChecked()) {
input.inputValue("isEncrypt", "true");
}
else {
input.inputValue("isEncrypt", "false");
}
input.inputValue("className", "peanut::PlDBController");
} catch (PeanutError err) {
isSuccess = false;
errMsg = err.what();
return PlSequenceMap();
}
isSuccess = true;
return input.sequenceMap();
}
void PsConndbMainWindow::on_currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous)
{
Q_UNUSED(previous);
if (!current.isValid())
{
return;
}
ui->leId->setReadOnly(true);
//得到当前tableWidget的Id列值
QString beanId = ui->tableWidget->item(current.row(), 0)->text();
//得到DB模块BeanConfig对象
PlBeanConfigInfo beanConfig = m_dbBeanConfig->getBeanConfigInfo();
QList<PlBeanInfo> beanList = beanConfig.getBeanInfos();
//遍历BeanConfig对象所有Bean
foreach(PlBeanInfo info, beanList) {
if(info.type()=="SQLMapper") {
continue;
}
//如果当前tableWidget的Id等于Bean的ID
if(info.id()==beanId) {
//将Bean的Info转换到界面中
setDBParamForm(info);
break;
}
}
}
void peanut::PsConndbMainWindow::on_tbTest_clicked()
{
Q_ASSERT(m_dbBeanConfig!=nullptr);
bool isSuccess;
QString errMsg;
try {
//得到当前界面的参数值
PlSequenceMap seqMap = getMapFromDialog(isSuccess, errMsg);
if(!isSuccess) {
throw PeanutError(errMsg);
}
//转换成QMap
QMap<QString,QString> map = seqMap.toMap();
//使用参数值集创建一个PlDBController对象
PlDBController dbCtrl(map);
QString name = map.value("name");
//打开并连接数据库
isSuccess = dbCtrl.open();
//如果打开失败,拋出异常
if(!isSuccess) {
throw PeanutError(dbCtrl.lastError());
}
//关闭数据库
dbCtrl.close();
PlMessageBox::information(this, tr("信息提示"), tr("连接[%1]成功").arg(name),QMessageBox::Ok);
} catch (PeanutError err) {
PlMessageBox::warning(this, tr("异常警告信息"), err.what());
}
}
}
- .ui文件ps_conndb_mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>peanut::PsConndbMainWindow</class>
<widget class="QMainWindow" name="peanut::PsConndbMainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>DB模块-多数据库连接</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="tbTest">
<property name="toolTip">
<string>测试当前连接是否正常</string>
</property>
<property name="text">
<string>测试连接</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QSplitter" name="splitter">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>7</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QTableWidget" name="tableWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>4</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>6</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string/>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="9" column="1">
<widget class="QLabel" name="label_9">
<property name="text">
<string>是否存储加密:</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QLineEdit" name="lePort"/>
</item>
<item row="7" column="1">
<widget class="QLabel" name="label_8">
<property name="text">
<string>数据库名称(databaseName):</string>
</property>
</widget>
</item>
<item row="10" column="2">
<widget class="QRadioButton" name="rbIsEncryptN">
<property name="text">
<string>否</string>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QLineEdit" name="lePassword">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="label_6">
<property name="text">
<string>用户密码(password):</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="label_5">
<property name="text">
<string>用户名称(userName):</string>
</property>
</widget>
</item>
<item row="7" column="2">
<widget class="QLineEdit" name="leDatabaseName"/>
</item>
<item row="9" column="2">
<widget class="QRadioButton" name="rbIsEncryptY">
<property name="text">
<string>是</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="leId">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>连接名称:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string>服务器地址(hostName):</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QLineEdit" name="leUserName"/>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>连接ID:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="label_7">
<property name="text">
<string>端口号(port):</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_3">
<property name="text">
<string>连接类型:</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QComboBox" name="cbType"/>
</item>
<item row="1" column="2">
<widget class="QLineEdit" name="leName"/>
</item>
<item row="3" column="2">
<widget class="QLineEdit" name="leIP"/>
</item>
<item row="8" column="1">
<widget class="QLabel" name="label_10">
<property name="text">
<string>连接选项(connectOptions):</string>
</property>
</widget>
</item>
<item row="8" column="2">
<widget class="QLineEdit" name="leConnectOptions"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>23</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
4. 代码下载
代码下载链接:https://pan.baidu.com/s/1VMrSKbQq6xL_haLPw3qVeg?pwd=zmp3 提取码: zmp3
Peanutlib项目演示程序下载:peanut_pwdis: pwdis是QT应用系统开发框架(C++),采用分层模块化设计,底层peanutlib按模块封装方便易用的类库及API(xml,db,appconfig,log,grid,json,bean等十几个),应用层提供部门人员权限及报表等,还提供了开发中常用的组件使用。