QT-自定义参数设计框架软件

QT-自定义参数设计框架软件


前言

常用本地数据参数通常使用的是xml等文本的格式,进行本地的数据参数的存储。这种参数的保存方式有个致命的一点,就是可以存在参数的丢失。特别是在软件异常退出的情况下发生。

针对此等情况,我们现在使用的是sqllite的本地存储方式进行设计,并且尽量将参数的格式通用化。

一、演示效果

二、使用步骤

1.应用进行参数注册

代码如下:

c 复制代码
void QSettingTools::registerParams()
{
	if (1)
	{
		QString strGroupName = u8"用户参数";
		cParamOperator::getInstance()->registerQString(strGroupName, "User", "sa", QString(u8"用户名称"));
		cParamOperator::getInstance()->registerQString(strGroupName, "Passord", "123456", QString(u8"用户密码"));
		cParamOperator::getInstance()->registerInt(strGroupName, "Index", 0, 0, 10000, QString(u8"序号"));
		cParamOperator::getInstance()->registerFloat(strGroupName, "Time", 1.5, 0, 10000, QString(u8"时间,单位秒"));
	}

	if (1)
	{
		QString strGroupName = u8"系统参数";
		cParamOperator::getInstance()->registerQString(strGroupName, "FactoryId", "123433ad", QString(u8"工厂ID"));
	}


	cParamOperator::getInstance()->autoRemoveInvialParams();

}

2.数据库操作单例对象

代码如下:

c 复制代码
#include "ParamOperator.h"
#include "SqlLiteDatabase.h"
#include <QFile>
#include <QMutex>
#include <QXmlStreamWriter>
#include <QDebug>
#include <QCoreApplication>
#include <QDomDocument>
#include <QApplication>

cParamOperator g_userParam;     // 用户参数

struct cAppBaserData
{
	QHash<QString, QString> strScrTranHash;
};

cParamOperator::cParamOperator()
	: d_ptr(new cParamManagerData)
{
	m_strConfig = "Setting.db";
}

cParamOperator::~cParamOperator()
{
	delete d_ptr;
	d_ptr = nullptr;
}

// 获取一个实例
cParamOperator *cParamOperator::getInstance()
{
	static cParamOperator manager;
	return &manager;
}

// 设置app安装目录
void cParamOperator::setAppExePath(QString strPath)
{
	m_strAppExePath = strPath;
	cSqlLiteDatabase::getInstance()->setAppExePath(strPath);
	initialParams();
}

// 注册整形参数
void cParamOperator::registerInt(QString strGroup, QString strName, int nMin, int nSet, int nMax, QString strDescribe)
{
	sParamItem_t paramItem;                                           
	paramItem.strGroupName = strGroup;
	paramItem.strParamType = PARAM_TYPE_INT;      
	paramItem.strName = strName;
	paramItem.strDescribe = strDescribe;
	paramItem.minVal = nMin;
	paramItem.setVal = nSet;
	paramItem.maxVal = nMax;

	registerParam(paramItem);
}

// 注册浮点型参数
void cParamOperator::registerFloat(QString strGroup, QString strName, float fMin, float fSet, float fMax, QString strDescribe)
{
	sParamItem_t paramItem;
	paramItem.strGroupName = strGroup;
	paramItem.strParamType = PARAM_TYPE_FLOAT;
	paramItem.strName = strName;
	paramItem.strDescribe = strDescribe;
	paramItem.minVal = fMin;
	paramItem.setVal = fSet;
	paramItem.maxVal = fMax;

	registerParam(paramItem);
}

// 注册字符串参数
void cParamOperator::registerQString(QString strGroup, QString strName, QString strSet, QString strDescribe)
{
	sParamItem_t paramItem;
	paramItem.strGroupName = strGroup;
	paramItem.strParamType = PARAM_TYPE_STRING;
	paramItem.strName = strName;
	paramItem.strDescribe = strDescribe;
	paramItem.minVal = "";
	paramItem.setVal = strSet;
	paramItem.maxVal = "";

	registerParam(paramItem);
}

// 修改参数值
void cParamOperator::setParam(QString strName, QVariant var)
{
	auto pParam = getParam(strName);
	if (pParam != nullptr)
	{
		pParam->setVal = var;
		g_userParam.updateParam(*pParam);
	}
}

// 获取整数
int cParamOperator::getParamInt(QString strName)
{
	auto param = getParam(strName);
	if (param != nullptr)
	{
		return param->setVal.toInt();
	}

	return -12345;
}

// 获取浮点数
float cParamOperator::getParamFloat(QString strName)
{
	auto param = getParam(strName);
	if (param != nullptr)
	{
		return param->setVal.toFloat();
	}

	return -12345;
}

// 获取字符串
QString cParamOperator::getParamString(QString strName)
{
	auto param = getParam(strName);
	if (param != nullptr)
	{
		return param->setVal.toString();
	}

	return "Error";
}

// 获取参数组名链表
QStringList cParamOperator::getGroupNameList()
{
	QStringList strNameList;
	auto paramList = params();
	for (size_t i = 0; i < paramList->size(); i++)
	{
		auto item = paramList->at(i);
		if (!strNameList.contains(item.strGroupName))
		{
			strNameList << item.strGroupName;
		}
	}

	return strNameList;
}

// 获取某个组的所有参数
QVector<sParamItem_t> cParamOperator::getGroupParam(QString strGroupName)
{
	QVector<sParamItem_t> list;
	auto paramList = params();
	for (size_t i = 0; i < paramList->size(); i++)
	{
		auto item = paramList->at(i);
		if (item.strGroupName == strGroupName)
		{
			list << item;
		}
	}

	return list;
}

// 初始化一个参数,插入内存中
int cParamOperator::initialParamItem(sParamItem_t &param)
{
	// 注册过的就直接返回就行了
	if (d_ptr->mapName.contains(param.strName))
		return d_ptr->mapName.value(param.strName);

	// 链表没有,插入链表记录
	auto index = d_ptr->vecParam.count();
	d_ptr->vecParam.append(param);
	d_ptr->mapName.insert(param.strName, index);

	// 如果有组名,直接插入到组名对应得链表里面
	if (d_ptr->mapGroup.contains(param.strGroupName))
	{
		for (auto iter = d_ptr->mapGroup.begin(); iter != d_ptr->mapGroup.end(); iter++)
		{
			if (iter.key() == param.strGroupName)
			{
				iter.value().insert(param.strName, index);
				break;
			}
		}
	}
	else
	{
		// 重新建立一个组名链表
		QHash<QString, int> groupItem;
		groupItem.insert(param.strName, index);
		d_ptr->mapGroup.insert(param.strGroupName, groupItem);

	}

	return index;
}

// 刷新参数
bool cParamOperator::updateParam(sParamItem_t &param, bool bUpdateAll)
{
	bool bRet = false;
	if (!param.strName.isEmpty())
	{
		if (bUpdateAll)
		{
			// 整个项都更新
			cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagSetVal, param.setVal.toString());
			cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagGroupName, param.strGroupName);
			cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagType, param.strParamType);
			cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagMinVal, param.minVal.toString());
			cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagMaxVal, param.maxVal.toString());


		}
		else
		{
			cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagSetVal, param.setVal.toString());
		}
	}

	return bRet;
}

// 通过参数名获取参数
sParamItem_t *cParamOperator::getParam(QString strName)
{
	return getParam(d_ptr->mapName.value(strName, -1));
}

// 通过索引获取参数
sParamItem_t *cParamOperator::getParam(int nIndex)
{
	if (nIndex >= 0 && nIndex < d_ptr->vecParam.count())
		return &d_ptr->vecParam[nIndex];

	return nullptr;
}

// 所有的参数
QVector<sParamItem_t>* cParamOperator::params()
{
	return &d_ptr->vecParam;
}

// 删除参数
bool cParamOperator::removeParam(QString strName)
{
	for (int i = 0; i < d_ptr->vecParam.size(); i++)
	{
		if (d_ptr->vecParam[i].strName == strName)
		{
			d_ptr->vecParam.removeAt(i);
			break;
		}
	}
	return cSqlLiteDatabase::getInstance()->remove(m_strTableName, g_strTagName, strName);
}

// 注册参数
int cParamOperator::registerParam(sParamItem_t &param)
{
	// 缓存起来
	if (!m_strResisterNameList.contains(param.strName))
		m_strResisterNameList << param.strName;

	// 注册过,组名不同得话就重新修改所属得组名,如果没有就直接返回
	if (d_ptr->mapName.contains(param.strName))
	{
		auto paramTemp = getParam(param.strName);

		// 如果组名不同,就换新注册得组名
		if (paramTemp->strGroupName != param.strGroupName)
		{
			auto itGroup = d_ptr->mapGroup.find(paramTemp->strGroupName);
			if (itGroup != d_ptr->mapGroup.end())
			{
				// 删掉旧的,再插入新得
				auto qTempHash = itGroup.value();
				d_ptr->mapGroup.erase(itGroup);
				
				d_ptr->mapGroup.insert(param.strGroupName, qTempHash);
			}

			// 更新为最新注册得
			paramTemp->strGroupName = param.strGroupName;
			cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagGroupName, param.strGroupName);
		}

		
		cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagMinVal, param.minVal.toString());
		cSqlLiteDatabase::getInstance()->update(d_ptr->strTableName, g_strTagName, param.strName, g_strTagMaxVal, param.maxVal.toString());

		auto pItem = getParam(param.strName);
		if (pItem != nullptr)
		{

			pItem->strDescribe = param.strDescribe;
			pItem->minVal = param.minVal;
			pItem->maxVal = param.maxVal;
			if (pItem->strDescribe.indexOf("《****")!= -1)
			{
				param.strRemarks = "";
			}
			pItem->strRemarks = param.strRemarks;
		}

		return d_ptr->mapName.value(param.strName);
	}

	// 链表没有,插入链表记录
	auto index = d_ptr->vecParam.count();
	d_ptr->vecParam.append(param);
	d_ptr->mapName.insert(param.strName, index);
	QHash<QString, QString> itemHash;

	// 同时插入数据表
	itemHash.insert(g_strTagGroupName, param.strGroupName);
	itemHash.insert(g_strTagType, param.strParamType);
	itemHash.insert(g_strTagName, param.strName);
	itemHash.insert(g_strTagSetVal, param.setVal.toString());
	itemHash.insert(g_strTagMinVal, param.minVal.toString());
	itemHash.insert(g_strTagMaxVal, param.maxVal.toString());
	itemHash.insert(g_strTagDescribe, param.strDescribe);
	cSqlLiteDatabase::getInstance()->insert(d_ptr->strTableName,itemHash);

	// 如果有组名,直接插入到组名对应得链表里面
	if (d_ptr->mapGroup.contains(param.strGroupName))
	{
		for (auto iter = d_ptr->mapGroup.begin(); iter != d_ptr->mapGroup.end(); iter++)
		{
			if (iter.key() == param.strGroupName)
			{
				iter.value().insert(param.strName, index);
				break;
			}
		}

	}
	else
	{
		// 重新建立一个组名链表
		QHash<QString, int> groupItem;
		groupItem.insert(param.strName, index);
		d_ptr->mapGroup.insert(param.strGroupName, groupItem);
	}



	return index;
}

// 全部注册后,删除无效参数(注意:所有的参数要全部注册完成后调用)
void cParamOperator::autoRemoveInvialParams()
{

	for (auto itSqlName:m_strSqlParamNameList)
	{
		// 如果程序注册的链表不存在就需要删除数据库的参数
		if (!m_strResisterNameList.contains(itSqlName))
		{
			removeParam(itSqlName);
		}
	}

	// 重新排序,按注册顺序显示
	QVector<sParamItem_t> tempAllParam = d_ptr->vecParam;
	d_ptr->vecParam.clear();
	d_ptr->mapName.clear();
	d_ptr->mapGroup.clear();
	
	for (auto itRegister:m_strResisterNameList)
	{
		for (auto& itFind : tempAllParam)
		{
			if (itFind.strName == itRegister)
			{
				initialParamItem(itFind);
				break;
			}
		}
	}


	
}

// 初始化数据参数
int cParamOperator::initialParams(QString strTableName)
{

	if (strTableName.isEmpty())
		return -1;

	// 先打开数据库
	cSqlLiteDatabase::getInstance()->openDb(m_strConfig);


	m_strTableName = strTableName;

	static QMutex mutex;
	mutex.lock();

	// 创建数据库表
	QStringList strList;
	strList << g_strTagGroupName << g_strTagType
		<< g_strTagName << g_strTagSetVal
		<< g_strTagMinVal << g_strTagMaxVal
		<< g_strTagDescribe  << g_strTagIsTemp;

	d_ptr->strTableName = strTableName;
	cSqlLiteDatabase::getInstance()->createTable(strTableName, strList);


	// 查找数据库表的所有参数
	auto dataHashList = cSqlLiteDatabase::getInstance()->select(strTableName);
	for (int i = 0; i < dataHashList.size(); i++)
	{
		auto hash = dataHashList[i];
		sParamItem_t paramItem;
		paramItem.strGroupName = hash.find(g_strTagGroupName) != hash.end() ? hash.find(g_strTagGroupName).value() : "";
		paramItem.strParamType = hash.find(g_strTagType) != hash.end() ? hash.find(g_strTagType).value() : "";
		paramItem.strName = hash.find(g_strTagName) != hash.end() ? hash.find(g_strTagName).value() : "";
		paramItem.setVal = hash.find(g_strTagSetVal) != hash.end() ? hash.find(g_strTagSetVal).value() : "";
		paramItem.minVal = hash.find(g_strTagMinVal) != hash.end() ? hash.find(g_strTagMinVal).value() : "";
		paramItem.maxVal = hash.find(g_strTagMaxVal) != hash.end() ? hash.find(g_strTagMaxVal).value() : "";
		paramItem.strDescribe = hash.find(g_strTagDescribe) != hash.end() ? hash.find(g_strTagDescribe).value() : "";

		// 缓存数据库的参数名称
		if (!m_strSqlParamNameList.contains(paramItem.strName))
			m_strSqlParamNameList << paramItem.strName;


		initialParamItem(paramItem);
	}

	mutex.unlock();

	return 0;
}

3.参数操作单例对象

代码如下:

cpp 复制代码
#include "SqlLiteDatabase.h"
#include <QDir>
#include <QDateTime>
#include <QApplication>

cSqlLiteDatabase::cSqlLiteDatabase(QObject *parent)
	: QObject(parent)
{

}

cSqlLiteDatabase::~cSqlLiteDatabase()
{
	closeDb();
}

// 设置appExe安装目录
void cSqlLiteDatabase::setAppExePath(QString strPath)
{
	m_strExePath = strPath;
	createDir();
}

// 获取实例
cSqlLiteDatabase *cSqlLiteDatabase::getInstance()
{
	static cSqlLiteDatabase obj;
	return &obj;
}

// 创建配置文件夹
void cSqlLiteDatabase::createDir()
{
	QDir dir;

	m_strConfigPath = m_strExePath + QString("/Config");
	if (!dir.exists(m_strConfigPath))
		dir.mkpath(m_strConfigPath);

	m_strDbDirPath = m_strConfigPath;
}

// 打开数据库
QSqlDatabase cSqlLiteDatabase::openDb(QString strDatabaseName, QString strDbDirPath)
{
	if (!strDbDirPath.isEmpty())
		m_strDbDirPath = strDbDirPath;

	m_strDatabaseName = strDatabaseName;

	QSqlDatabase db;
	if (QSqlDatabase::contains(m_strDatabaseName))
		db = QSqlDatabase::database(m_strDatabaseName);
	else
	{
		QString strTempName = m_strDbDirPath + "/"+strDatabaseName;
		db = QSqlDatabase::addDatabase("QSQLITE", m_strDatabaseName);
		db.setDatabaseName(strTempName);
		db.setPassword("8888");
		db.setHostName("root");
		db.setUserName("root");
	}

	if (!db.open())
		qDebug() << db.lastError().text();
	else
		m_bConnected = true;

	return db;

}

// 关闭数据库
bool cSqlLiteDatabase::closeDb()
{
	QSqlDatabase::removeDatabase(m_strDatabaseName);
	return true;
}

// 创建表
bool cSqlLiteDatabase::createTable(QString strTableName, QStringList strHeaderNameList)
{
	bool bRet = true;
	if (true)
	{
		if (strTableName.isEmpty() || strHeaderNameList.size() <= 0)
		{
			bRet = false;
			return bRet;
		}

		auto findItem = m_strTableNameHeaderHash.find(strTableName);
		if (findItem == m_strTableNameHeaderHash.end())
			m_strTableNameHeaderHash.insert(strTableName, strHeaderNameList);

		QString strCreateTable = QString(u8"CREATE TABLE %1(").arg(strTableName);
		for (int i = 0; i < strHeaderNameList.size(); i++)
		{
			if (i < (strHeaderNameList.size() - 1))
				strCreateTable = strCreateTable + strHeaderNameList[i] + QString(u8" VARCHAR(256)") + QString(",");
			else
				strCreateTable = strCreateTable + strHeaderNameList[i] + QString(u8" VARCHAR(256)") + QString(")");
		}
		bRet = excute(strCreateTable);
	}

	return bRet;
}

// 删除表
bool cSqlLiteDatabase::dropTable(QString strTableName)
{
	if (strTableName.isEmpty())
		return false;

	QString strDrop = QString("DROP TABLE %1").arg(strTableName);
	return excute(strDrop);
}

// 执行sql语句
bool cSqlLiteDatabase::excute(QString strSql)
{
	static QMutex mutex;
	mutex.lock();
	bool bRet = true;
	QSqlDatabase db = openDb(m_strDatabaseName);
	QString strConnectionName = db.connectionName();

	QSqlQuery query(db);
	query.prepare(strSql);
	bool success = query.exec(strSql);
	if (!success)
	{
		qDebug() << "Error:" << query.lastError();
		bRet = false;
	}


	closeDb();
	mutex.unlock();
	return bRet;
}

// 查找
QList<QStringList> cSqlLiteDatabase::seach(QString strTableName, QString strSql)
{
	QList<QStringList> temp;

	QSqlDatabase db = openDb(m_strDatabaseName);
	QString strConnectionName = db.connectionName();

	db.transaction(); // 开启事务查询
	QSqlQuery query("", db);
	query.exec(strSql);
	db.commit();  // 提交事务

	while (query.next())
	{
		QStringList keyValueHash;
		int nCount = query.record().count();
		for (size_t i = 0; i < nCount; i++)
			keyValueHash << query.record().value(i).toString();

		if (keyValueHash.size() > 0)
			temp << keyValueHash;
	}

	closeDb();
	return temp;
}

// 查找
QList< QHash<QString/*name*/, QString/*value*/> > cSqlLiteDatabase::select(QString strTableName, QString strName, QString strValue)
{
	QList< QHash<QString, QString> > temp;

	if (strTableName.isEmpty() || strTableName.isEmpty())
		return temp;

	QSqlDatabase db = openDb(m_strDatabaseName);
	QString strConnectionName = db.connectionName();

	QString strSelect("");
	if (!strValue.isEmpty()&& !strName.isEmpty())
		strSelect = QString("SELECT * FROM %1 WHERE %2 = '%3';").arg(strTableName).arg(strName).arg(strValue);
	else
		strSelect = QString("SELECT * FROM  %1;").arg(strTableName);
	
	db.transaction(); // 开启事务查询
	QSqlQuery query("", db);
	query.exec(strSelect);
	db.commit();  // 提交事务

	while (query.next())
	{
		QHash<QString, QString> keyValueHash;
		int nCount = query.record().count();
		for (size_t i = 0; i < nCount; i++)
			keyValueHash.insert(query.record().fieldName(i), query.record().value(i).toString());

		if (keyValueHash.size() > 0)
			temp << keyValueHash;

	}
	
	closeDb();
	return temp;
}

// 插入数据
bool cSqlLiteDatabase::insert(QString strTableName, QHash<QString/*name*/, QString/*value*/> dataHash)
{
	bool bRet = false;
	if (strTableName.isEmpty())
		return bRet;

	QStringList strHeaderList;
	auto findItem = m_strTableNameHeaderHash.find(strTableName);
	if (findItem == m_strTableNameHeaderHash.end())
		return bRet;
	else
		strHeaderList = m_strTableNameHeaderHash[strTableName];

	if (true)
	{
		QString strInsert = QString(u8"INSERT INTO %1 VALUES(").arg(strTableName);
		for (size_t j = 0; j < strHeaderList.size(); j++)
		{
			QString strName = strHeaderList[j];
			QString strValue = "";
			auto findName = dataHash.find(strName);
			if (findName != dataHash.end())
				strValue = findName.value();

			if (j < (strHeaderList.size() - 1))
				strInsert = strInsert + QString(u8"'%1'").arg(strValue) + QString(",");
			else
				strInsert = strInsert + QString(u8"'%1'").arg(strValue) + QString(")");
		}

		bRet = excute(strInsert);
	} 

	return bRet;
}

// 更新数据
bool cSqlLiteDatabase::update(QString strTableName, QString strWhereName, QString strWhereValue, QString strUpdateName, QString strUpdateValue)
{
	bool bRet = false;
	if (strTableName.isEmpty())
		return bRet;

	QStringList strHeaderList;
	auto findItem = m_strTableNameHeaderHash.find(strTableName);
	if (findItem == m_strTableNameHeaderHash.end())
		return bRet;

	if (true)
	{

		QString strUpdate = QString(u8"UPDATE %1 SET %2='%3' WHERE %4='%5';")
			.arg(strTableName)
			.arg(strUpdateName)
			.arg(strUpdateValue)
			.arg(strWhereName)
			.arg(strWhereValue);

		bRet = excute(strUpdate);
	}

	return bRet;
}

// 删除数据
bool cSqlLiteDatabase::remove(QString strTableName, QString strWhereName, QString strWhereValue)
{
	bool bRet = false;
	if (strTableName.isEmpty())
		return bRet;

	QStringList strHeaderList;
	auto findItem = m_strTableNameHeaderHash.find(strTableName);
	if (findItem == m_strTableNameHeaderHash.end())
		return bRet;

	if (true)
	{

		QString strUpdate = QString(u8"DELETE FROM %1 WHERE %2='%3';")
			.arg(strTableName)
			.arg(strWhereName)
			.arg(strWhereValue);

		bRet = excute(strUpdate);
	}

	return bRet;
}

// 模糊查询
QStringList cSqlLiteDatabase::fuzzySearch(QString strTableName, QString strName, QString strLike)
{
	QStringList temp;

	if (strTableName.isEmpty() || strTableName.isEmpty())
		return temp;

	QSqlDatabase db = openDb(m_strDatabaseName);
	QString strConnectionName = db.connectionName();

	QString strSelect("");
	if (!strLike.isEmpty())
		strSelect = QString("SELECT %1 FROM %2 WHERE %3 LIKE '%%4%' LIMIT 0,10;").arg(strName).arg(strTableName).arg(strName).arg(strLike);
	else
		return temp;

	db.transaction(); // 开启事务查询
	QSqlQuery query("", db);
	query.exec(strSelect);
	while (query.next())
	{
		QHash<QString, QString> keyValueHash;
		for (size_t i = 0; i < query.record().count(); i++)
		{
			QString strName = query.record().fieldName(i);
			QString strValue = query.record().value(i).toString();
			if (!temp.contains(strValue) && (strLike != strValue))
				temp << strValue;

		}
	}

	db.commit();  // 提交事务
	closeDb();
	return temp;
}

三、下载链接

https://download.csdn.net/download/u013083044/89053778

相关推荐
用户805533698034 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner4 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz9 天前
QML Hello World 入门示例
qt
xcyxiner12 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner13 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner13 天前
DicomViewer (添加模型类)3
qt
xcyxiner14 天前
DicomViewer (目录调整) 2
qt
xcyxiner14 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00616 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术16 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript