新增数据映射页面,表映射、函数映射;新增版本更新提示功能

This commit is contained in:
taocong
2026-07-02 19:54:22 +08:00
parent d4d607dc6a
commit 1f33f0fbaa
31 changed files with 1506 additions and 214 deletions
+149 -111
View File
@@ -1,9 +1,16 @@
#include "batchconvertpage.h"
#include "src/utils/Constants.h"
#include "src/utils/uf2configreader.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QRegularExpression>
#include <QCoreApplication>
#include <QHeaderView>
#include <QMessageBox>
@@ -48,19 +55,24 @@ BatchConvertPage::BatchConvertPage(QWidget *parent)
{
initUI();
loadFromCustJson();
m_configWatcher = new QFileSystemWatcher(this);
QString configPath = getConfigFilePath();
if (!configPath.isEmpty() && QFile::exists(configPath)) {
m_configWatcher->addPath(configPath);
connect(m_configWatcher, &QFileSystemWatcher::fileChanged, this, &BatchConvertPage::onConfigFileChanged);
}
// 防抖定时器:编辑器保存可能触发多次信号,合并为一次重载
m_reloadTimer = new QTimer(this);
m_reloadTimer->setSingleShot(true);
m_reloadTimer->setInterval(300);
connect(m_reloadTimer, &QTimer::timeout, this, &BatchConvertPage::onReloadTimeout);
// 记录初始 mtime
m_lastFileMtime = currentFileMtime();
setupConfigWatcher();
}
BatchConvertPage::~BatchConvertPage()
{
if (m_configWatcher) {
m_configWatcher->removePaths(m_configWatcher->files());
m_configWatcher->removePaths(m_configWatcher->directories());
}
}
@@ -366,140 +378,112 @@ void BatchConvertPage::onClearTable()
bool BatchConvertPage::saveToCustJson(const QStringList &funcList)
{
QString jsonPath = QCoreApplication::applicationDirPath() + "/uf2touft3/cust.json";
QString jsonPath = Constants::custJson();
// 读取原始文件内容(保留全部格式不重序列化)
QFile file(jsonPath);
if (!file.open(QIODevice::ReadOnly)) {
LogManager::instance()->logWarning(
QString("业务转码 - 保存配置失败,无法打开: %1").arg(jsonPath));
return false;
}
QByteArray data = file.readAll();
QString content = QString::fromUtf8(file.readAll());
file.close();
QString content = QString::fromUtf8(data);
int convertStart = content.indexOf("\"convert\":");
if (convertStart < 0) {
convertStart = content.indexOf("\"convert\" :");
}
if (convertStart < 0) {
// 用正则定位 "convert" 节点(兼容任意空格)
static QRegularExpression convertRe(R"("convert"\s*:)");
auto convertMatch = convertRe.match(content);
if (!convertMatch.hasMatch()) {
LogManager::instance()->logWarning("业务转码 - 保存配置失败,未找到 convert 节点");
return false;
}
int dirStart = content.indexOf("\"dir\":[", convertStart);
if (dirStart < 0) {
dirStart = content.indexOf("\"dir\" : [", convertStart);
}
if (dirStart < 0) {
// 在 convert 之后定位 "dir" 数组起始 [
static QRegularExpression dirRe(R"("dir"\s*:\s*\[)");
auto dirMatch = dirRe.match(content, convertMatch.capturedEnd());
if (!dirMatch.hasMatch()) {
LogManager::instance()->logWarning("业务转码 - 保存配置失败,未找到 dir 数组");
return false;
}
int arrayStart = content.indexOf('[', dirStart);
int bracketStart = dirMatch.capturedEnd() - 1; // 指向 '['
// 括号计数找到匹配的 ']'
int bracketCount = 1;
int pos = arrayStart + 1;
int pos = bracketStart + 1;
while (pos < content.length() && bracketCount > 0) {
if (content[pos] == '[') bracketCount++;
else if (content[pos] == ']') bracketCount--;
QChar ch = content[pos];
if (ch == '[') bracketCount++;
else if (ch == ']') bracketCount--;
pos++;
}
int arrayEnd = pos;
QString newDirArray = "[";
int bracketEnd = pos; // 指向 ']' 之后一位
// 构建新数组(保持原始缩进风格:项 12 空格,闭合 ] 8 空格)
QString newArray = "[";
for (int i = 0; i < funcList.size(); ++i) {
if (i > 0) newDirArray += ",";
newDirArray += "\n \"" + funcList[i] + ".service_design\"";
if (i > 0) newArray += ",";
newArray += "\n \"" + funcList[i] + ".service_design\"";
}
newDirArray += "\n ]";
content.replace(arrayStart, arrayEnd - arrayStart, newDirArray);
if (!funcList.isEmpty()) {
newArray += "\n ";
}
newArray += "]";
// 只替换 [bracketStart, bracketEnd) 区间,其余内容原样保留
content.replace(bracketStart, bracketEnd - bracketStart, newArray);
// 写回
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
LogManager::instance()->logWarning(
QString("业务转码 - 保存配置失败,无法写入: %1").arg(jsonPath));
return false;
}
file.write(content.toUtf8());
file.close();
// 程序自身写入后更新 mtime,避免触发无谓的重载
m_lastFileMtime = currentFileMtime();
return true;
}
void BatchConvertPage::loadFromCustJson()
{
QString jsonPath = QCoreApplication::applicationDirPath() + "/uf2touft3/cust.json";
QString jsonPath = Constants::custJson();
QFile file(jsonPath);
int retryCount = 0;
const int maxRetries = 3;
const int retryDelayMs = 100;
while (!file.open(QIODevice::ReadOnly) && retryCount < maxRetries) {
retryCount++;
QThread::msleep(retryDelayMs);
}
if (!file.isOpen()) {
if (!file.open(QIODevice::ReadOnly)) {
LogManager::instance()->logWarning(
QString("业务转码 - 加载配置失败,无法打开: %1").arg(jsonPath));
return;
}
QByteArray data = file.readAll();
file.close();
QString content = QString::fromUtf8(data);
int convertStart = content.indexOf("\"convert\":");
if (convertStart < 0) {
convertStart = content.indexOf("\"convert\" :");
}
if (convertStart < 0) {
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError) {
LogManager::instance()->logWarning(
QString("业务转码 - 加载配置 JSON 解析失败: %1").arg(parseError.errorString()));
return;
}
int dirStart = content.indexOf("\"dir\":[", convertStart);
if (dirStart < 0) {
dirStart = content.indexOf("\"dir\" : [", convertStart);
}
if (dirStart < 0) {
return;
}
int arrayStart = content.indexOf('[', dirStart);
int bracketCount = 1;
int pos = arrayStart + 1;
while (pos < content.length() && bracketCount > 0) {
if (content[pos] == '[') bracketCount++;
else if (content[pos] == ']') bracketCount--;
pos++;
}
int arrayEnd = pos;
QString arrayContent = content.mid(arrayStart + 1, arrayEnd - arrayStart - 1).trimmed();
if (arrayContent.isEmpty() || arrayContent == "null") {
return;
}
QJsonObject root = doc.object();
QJsonArray dir = root["convert"].toObject()["dir"].toArray();
QStringList items;
int itemPos = 0;
while (itemPos < arrayContent.length()) {
int itemStart = arrayContent.indexOf('"', itemPos);
if (itemStart < 0) break;
int itemEnd = arrayContent.indexOf('"', itemStart + 1);
if (itemEnd < 0) break;
QString item = arrayContent.mid(itemStart + 1, itemEnd - itemStart - 1);
items.append(item);
itemPos = itemEnd + 1;
}
for (QString &item : items) {
for (const QJsonValue &val : dir) {
QString item = val.toString();
if (item.endsWith(".service_design")) {
item = item.left(item.length() - static_cast<int>(strlen(".service_design")));
item.chop(static_cast<int>(strlen(".service_design")));
}
items.append(item);
}
m_funcList = items;
updateTable();
}
@@ -566,17 +550,71 @@ void BatchConvertPage::addFunction(const QString &funcName)
}
}
void BatchConvertPage::setupConfigWatcher()
{
QString configPath = getConfigFilePath();
if (configPath.isEmpty()) {
return;
}
m_configWatcher = new QFileSystemWatcher(this);
// 1. 监视文件本身(覆盖直接编辑场景)
if (QFile::exists(configPath)) {
m_configWatcher->addPath(configPath);
}
// 2. 监视文件所在目录(覆盖编辑器原子替换:写临时文件→删除→重命名)
// Windows 上 fileChanged 在文件被替换后常失效,directoryChanged 更可靠
QString dir = QFileInfo(configPath).absolutePath();
if (QDir(dir).exists()) {
m_configWatcher->addPath(dir);
}
connect(m_configWatcher, &QFileSystemWatcher::fileChanged,
this, &BatchConvertPage::onConfigFileChanged);
connect(m_configWatcher, &QFileSystemWatcher::directoryChanged,
this, &BatchConvertPage::onConfigDirChanged);
}
QDateTime BatchConvertPage::currentFileMtime() const
{
QFileInfo info(getConfigFilePath());
return info.exists() ? info.lastModified() : QDateTime();
}
void BatchConvertPage::onConfigFileChanged(const QString &path)
{
QStringList oldList = m_funcList;
loadFromCustJson();
if (m_configWatcher && !m_configWatcher->files().contains(path)) {
{
// 文件可能被编辑器替换导致监视失效,尝试重新添加
if (m_configWatcher && !m_configWatcher->files().contains(path)
&& QFile::exists(path)) {
m_configWatcher->addPath(path);
}
m_reloadTimer->start(); // 防抖
}
void BatchConvertPage::onConfigDirChanged(const QString & /*dirPath*/)
{
// 目录变化时重新监视文件(可能之前文件不存在/被替换)
QString configPath = getConfigFilePath();
if (m_configWatcher && QFile::exists(configPath)
&& !m_configWatcher->files().contains(configPath)) {
m_configWatcher->addPath(configPath);
}
m_reloadTimer->start(); // 防抖
}
void BatchConvertPage::onReloadTimeout()
{
// 比较 mtime,避免无谓重载
QDateTime now = currentFileMtime();
if (now.isValid() && now != m_lastFileMtime) {
m_lastFileMtime = now;
loadFromCustJson();
}
}
QString BatchConvertPage::getConfigFilePath() const
{
return QCoreApplication::applicationDirPath() + "/uf2touft3/cust.json";
return Constants::custJson();
}