优化若干性能问题

This commit is contained in:
taocong
2026-09-22 15:15:22 +08:00
committed by taocong
parent afb8161648
commit 43e184bffb
118 changed files with 5518 additions and 6372 deletions
+57 -617
View File
@@ -1,31 +1,18 @@
#include "batchconvertpage.h"
#include "src/utils/Constants.h"
#include "src/utils/uf2configreader.h"
#include "src/utils/logmanager.h"
#include "src/utils/custjsonio.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
#include <QFile>
#include <QSaveFile>
#include <QFileInfo>
#include <QDir>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include <QJsonParseError>
#include <QRegularExpression>
#include <QCoreApplication>
#include <QHeaderView>
#include <QMessageBox>
#include <QInputDialog>
#include <QDialog>
#include <QTextEdit>
#include <QStandardItem>
#include <QStyledItemDelegate>
#include <QPainter>
#include <QShowEvent>
#include "src/utils/logmanager.h"
#include "ElaPushButton.h"
#include "ElaProgressBar.h"
#include "ElaText.h"
#include <cstring>
#include "ElaTheme.h"
namespace {
@@ -36,7 +23,7 @@ bool validateLsFunctionNames(QWidget *parent, const QStringList &functionNames)
for (const QString &name : functionNames) {
const QString trimmedName = name.trimmed();
if (!trimmedName.isEmpty()
&& !trimmedName.startsWith("LS_")
&& !trimmedName.startsWith(Constants::FuncName::LsPrefix)
&& !nonLsNames.contains(trimmedName)) {
nonLsNames.append(trimmedName);
}
@@ -52,422 +39,45 @@ bool validateLsFunctionNames(QWidget *parent, const QStringList &functionNames)
return false;
}
}
class StatusDelegate : public QStyledItemDelegate
{
public:
explicit StatusDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
QVariant bgColor = index.data(Qt::BackgroundRole);
QVariant fgColor = index.data(Qt::ForegroundRole);
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
if (bgColor.canConvert<QBrush>()) {
painter->fillRect(opt.rect, bgColor.value<QBrush>());
}
if (fgColor.canConvert<QBrush>()) {
painter->setPen(fgColor.value<QBrush>().color());
}
painter->drawText(opt.rect, opt.displayAlignment, opt.text);
}
};
} // namespace
BatchConvertPage::BatchConvertPage(QWidget *parent)
: QWidget(parent)
: ConvertWorkbenchBase(parent)
{
initUI();
loadFromCustJson();
// 防抖定时器:编辑器保存可能触发多次信号,合并为一次重载
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();
// 灰字进度标签跟随主题明暗切换(上轮修改实际未落盘, 此处补全)
connect(ElaTheme::getInstance(), &ElaTheme::themeModeChanged,
this, &BatchConvertPage::updateTipStyle);
updateTipStyle();
// 两阶段初始化: 构造体内调用, 确保界面钩子(loadConfig/applyPageLayout 等)派发到本类
initialize();
}
BatchConvertPage::~BatchConvertPage()
void BatchConvertPage::applyPageLayout(QVBoxLayout *layout)
{
if (m_configWatcher) {
m_configWatcher->removePaths(m_configWatcher->files());
m_configWatcher->removePaths(m_configWatcher->directories());
}
}
void BatchConvertPage::initUI()
{
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(30, 30, 30, 30);
layout->setSpacing(20);
QGroupBox *funcBox = new QGroupBox("转码功能");
QVBoxLayout *funcLayout = new QVBoxLayout(funcBox);
funcLayout->setSpacing(10);
m_tableModel = new QStandardItemModel(this);
m_tableModel->setColumnCount(3);
m_tableModel->setHorizontalHeaderLabels({"UF20函数名", "是否存在", "操作"});
m_funcTable = new ElaTableView;
m_funcTable->setModel(m_tableModel);
m_funcTable->setMinimumHeight(200);
m_funcTable->setSelectionBehavior(QAbstractItemView::SelectRows);
m_funcTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
m_funcTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);
m_funcTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed);
m_funcTable->horizontalHeader()->setStretchLastSection(false);
m_funcTable->setColumnWidth(2, 80);
m_funcTable->setItemDelegateForColumn(1, new StatusDelegate(this));
funcLayout->addWidget(m_funcTable);
QHBoxLayout *tableBtnLayout = new QHBoxLayout;
ElaPushButton *addBtn = new ElaPushButton("添加");
connect(addBtn, &QPushButton::clicked, this, &BatchConvertPage::onAddFunction);
ElaPushButton *removeBtn = new ElaPushButton("删除选中");
connect(removeBtn, &QPushButton::clicked, this, &BatchConvertPage::onRemoveFunction);
ElaPushButton *clearBtn = new ElaPushButton("清空列表");
connect(clearBtn, &QPushButton::clicked, this, &BatchConvertPage::onClearTable);
tableBtnLayout->addWidget(addBtn);
tableBtnLayout->addStretch();
tableBtnLayout->addWidget(removeBtn);
tableBtnLayout->addWidget(clearBtn);
funcLayout->addLayout(tableBtnLayout);
layout->addWidget(funcBox);
QGroupBox *progressBox = new QGroupBox("转换进度");
progressBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
QVBoxLayout *progressLayout = new QVBoxLayout(progressBox);
progressLayout->setSpacing(10);
m_progressLabel = new ElaText("等待开始...");
m_progressLabel->setFixedHeight(40);
m_progressLabel->setWordWrap(false);
m_progressLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
progressLayout->addWidget(m_progressLabel);
m_progressBar = new ElaProgressBar;
m_progressBar->setRange(0, 100);
m_progressBar->setValue(0);
m_progressBar->setFixedHeight(25);
m_progressBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
progressLayout->addWidget(m_progressBar);
layout->addWidget(progressBox);
QHBoxLayout *btnLayout = new QHBoxLayout;
m_startBtn = new QPushButton("开始转换");
m_startBtn->setStyleSheet("background-color: #1abc9c; color: white; padding: 10px 30px; font-size: 14px; border: none; border-radius: 4px;");
connect(m_startBtn, &QPushButton::clicked, this, &BatchConvertPage::onStartConvert);
btnLayout->addStretch();
btnLayout->addWidget(m_startBtn);
layout->addLayout(btnLayout);
}
void BatchConvertPage::onProgressUpdate(int progress, const QString& status)
int BatchConvertPage::tableColumnCount() const
{
m_progressBar->setValue(progress);
// 仅当传入有效状态文字时才更新标签,避免无意义的日志行覆盖当前处理状态
if (!status.isEmpty()) {
QString displayStatus = status;
if (displayStatus.length() > 80) {
displayStatus = displayStatus.left(80) + "...";
}
m_progressLabel->setText(displayStatus);
}
return 3;
}
bool BatchConvertPage::checkFunctionExists(const QString &funcName)
QStringList BatchConvertPage::tableHeaderLabels() const
{
return UF2ConfigReader::checkFuncExistsStatic(funcName);
return {"UF20函数名", "是否存在", "操作"};
}
void BatchConvertPage::updateTable()
void BatchConvertPage::applyColumnLayout()
{
m_tableModel->removeRows(0, m_tableModel->rowCount());
for (int i = 0; i < m_funcList.size(); ++i) {
QString funcName = m_funcList[i];
QStandardItem *nameItem = new QStandardItem(funcName);
nameItem->setFlags(nameItem->flags() & ~Qt::ItemIsEditable);
bool exists = checkFunctionExists(funcName);
QString statusText = exists ? "是" : "否";
QStandardItem *statusItem = new QStandardItem(statusText);
statusItem->setFlags(statusItem->flags() & ~Qt::ItemIsEditable);
statusItem->setTextAlignment(Qt::AlignCenter);
if (exists) {
statusItem->setData(QBrush(QColor(144, 238, 144)), Qt::BackgroundRole);
statusItem->setData(QBrush(QColor(0, 100, 0)), Qt::ForegroundRole);
} else {
statusItem->setData(QBrush(QColor(255, 182, 193)), Qt::BackgroundRole);
statusItem->setData(QBrush(QColor(139, 0, 0)), Qt::ForegroundRole);
}
QStandardItem *actionItem = new QStandardItem("");
actionItem->setFlags(actionItem->flags() & ~Qt::ItemIsEditable);
QList<QStandardItem*> rowItems;
rowItems.append(nameItem);
rowItems.append(statusItem);
rowItems.append(actionItem);
m_tableModel->appendRow(rowItems);
QPushButton *editBtn = new QPushButton;
editBtn->setIcon(QIcon(":/resources/images/bianji.png"));
editBtn->setIconSize(QSize(20, 20));
editBtn->setStyleSheet("padding: 2px; border: none; background-color: transparent;");
editBtn->setToolTip("修改");
// QueuedConnection: onEditRow 经 loadFromCustJson 重建表格会析构 editBtn 自身,
// 同步回调返回到按钮内部事件处理时构成 use-after-free, 故延迟到事件循环执行。
// 捕获函数名而非行号且以按钮为接收者: 排队前表格可能被重建, 行号会指向错误行;
// 函数名是稳定键, 且按钮析构时过期的排队请求随之失效
connect(editBtn, &QPushButton::clicked, editBtn, [this, funcName]() {
onEditRowByName(funcName);
}, Qt::QueuedConnection);
m_funcTable->setIndexWidget(m_tableModel->index(i, 2), editBtn);
}
int totalWidth = m_funcTable->viewport()->width();
const int totalWidth = m_funcTable->viewport()->width();
m_funcTable->setColumnWidth(0, (totalWidth - 80) * 8 / 10);
m_funcTable->setColumnWidth(1, (totalWidth - 80) * 2 / 10);
m_funcTable->setColumnWidth(2, 80);
}
void BatchConvertPage::resizeEvent(QResizeEvent *event)
bool BatchConvertPage::validateFunctionNames(const QStringList &names)
{
QWidget::resizeEvent(event);
if (m_funcTable) {
int totalWidth = m_funcTable->width();
m_funcTable->setColumnWidth(0, (totalWidth - 80) * 8 / 10);
m_funcTable->setColumnWidth(1, (totalWidth - 80) * 2 / 10);
m_funcTable->setColumnWidth(2, 80);
}
return validateLsFunctionNames(this, names);
}
void BatchConvertPage::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
// 每次显示页面时刷新"是否存在"列
// (元数据更新后 DataCache 变化,需要重新检查函数存在状态)
updateTable();
}
// 按函数名定位行再编辑: 表格可能在外部文件监控/切页时被全量重建,
// 排队请求中的旧行号会失效指向错误行, 函数名是唯一稳定键
void BatchConvertPage::onEditRowByName(const QString &funcName)
{
const int row = m_funcList.indexOf(funcName);
if (row >= 0) {
onEditRow(row);
}
}
void BatchConvertPage::onEditRow(int row)
{
if (row < 0 || row >= m_funcList.size()) {
return;
}
QString oldName = m_funcList[row];
QString newName = oldName;
while (true) {
QInputDialog dialog(this);
dialog.setWindowTitle("修改函数名");
dialog.setLabelText("请输入新的函数名:");
dialog.setTextValue(oldName);
dialog.resize(300, 150);
if (dialog.exec() != QDialog::Accepted) {
return;
}
newName = dialog.textValue().trimmed();
if (newName.isEmpty()) {
QMessageBox::warning(this, "提示", "函数名不能为空!");
continue;
}
if (!validateLsFunctionNames(this, QStringList{newName})) {
continue;
}
if (newName == oldName) {
return;
}
if (m_funcList.contains(newName)) {
QMessageBox::warning(this, "提示", "该函数名已存在,请重新输入!");
continue;
}
break;
}
m_funcList[row] = newName;
updateTable();
if (saveToCustJson(m_funcList)) {
loadFromCustJson();
} else {
QMessageBox::warning(this, "保存失败", "函数名已在内存中修改,但写入 cust.json 失败(文件被占用或权限不足?),重启后修改将丢失!");
}
}
void BatchConvertPage::onAddFunction()
{
QDialog dialog(this);
dialog.setWindowTitle("添加UF20函数");
dialog.resize(400, 250);
QVBoxLayout *layout = new QVBoxLayout(&dialog);
QTextEdit *textEdit = new QTextEdit;
textEdit->setPlaceholderText("每行输入一个函数名");
layout->addWidget(textEdit);
QHBoxLayout *btnLayout = new QHBoxLayout;
QPushButton *okBtn = new QPushButton("确定");
QPushButton *cancelBtn = new QPushButton("取消");
btnLayout->addStretch();
btnLayout->addWidget(okBtn);
btnLayout->addWidget(cancelBtn);
layout->addLayout(btnLayout);
connect(okBtn, &QPushButton::clicked, &dialog, [this, &dialog, textEdit]() {
const QStringList funcNames =
textEdit->toPlainText().split('\n', Qt::SkipEmptyParts);
if (validateLsFunctionNames(this, funcNames)) {
dialog.accept();
}
});
connect(cancelBtn, &QPushButton::clicked, &dialog, &QDialog::reject);
if (dialog.exec() != QDialog::Accepted) {
return;
}
QString text = textEdit->toPlainText().trimmed();
if (text.isEmpty()) {
return;
}
QStringList funcNames = text.split('\n', Qt::SkipEmptyParts);
int addedCount = 0;
int existCount = 0;
for (const QString &name : funcNames) {
QString trimmedName = name.trimmed();
if (trimmedName.isEmpty()) {
continue;
}
if (m_funcList.contains(trimmedName)) {
existCount++;
continue;
}
m_funcList.append(trimmedName);
addedCount++;
}
if (existCount > 0) {
QMessageBox::information(this, "提示",
QString("成功添加 %1 个函数,%2 个函数已存在").arg(addedCount).arg(existCount));
} else if (addedCount > 0) {
QMessageBox::information(this, "提示",
QString("成功添加 %1 个函数").arg(addedCount));
}
// 落盘: 新增函数持久化到 cust.json, 否则只写内存重启后丢失(与 onEditRow 保持一致)
if (addedCount > 0) {
if (saveToCustJson(m_funcList)) {
loadFromCustJson();
} else {
QMessageBox::warning(this, "保存失败", "新增函数已在内存中生效,但写入 cust.json 失败(文件被占用或权限不足?),重启后将丢失!");
}
}
updateTable();
}
void BatchConvertPage::onRemoveFunction()
{
QModelIndexList selectedIndexes = m_funcTable->selectionModel()->selectedRows();
if (selectedIndexes.isEmpty()) {
QMessageBox::warning(this, "提示", "请先选中要删除的行!");
return;
}
QList<int> rowsToRemove;
for (const QModelIndex &index : selectedIndexes) {
int row = index.row();
if (!rowsToRemove.contains(row)) {
rowsToRemove.append(row);
}
}
std::sort(rowsToRemove.begin(), rowsToRemove.end(), std::greater<int>());
for (int row : rowsToRemove) {
m_funcList.removeAt(row);
}
updateTable();
if (saveToCustJson(m_funcList)) {
loadFromCustJson();
} else {
QMessageBox::warning(this, "保存失败", "删除操作已在内存中生效,但写入 cust.json 失败(文件被占用或权限不足?),重启后将恢复原清单!");
}
}
void BatchConvertPage::onClearTable()
{
if (m_funcList.isEmpty()) {
return;
}
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, "确认清空", "确定要清空所有函数吗?",
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
m_funcList.clear();
updateTable();
if (saveToCustJson(m_funcList)) {
loadFromCustJson();
} else {
QMessageBox::warning(this, "保存失败", "清空操作已在内存中生效,但写入 cust.json 失败(文件被占用或权限不足?),重启后将恢复原清单!");
}
}
}
void BatchConvertPage::updateTipStyle()
{
if (!m_progressLabel) return;
const ElaThemeType::ThemeMode mode = ElaTheme::getInstance()->getThemeMode();
const QString textColor = ElaThemeColor(mode, BasicText).name();
// 仅联动颜色: ElaText 保持默认字号(与"按需转码"页一致), 强设 12px 过小(用户反馈)
m_progressLabel->setStyleSheet(QString("color: %1;").arg(textColor));
}
bool BatchConvertPage::saveToCustJson(const QStringList &funcList)
bool BatchConvertPage::saveConfig(const QStringList &funcList)
{
QString jsonPath = Constants::custJson();
@@ -482,8 +92,9 @@ bool BatchConvertPage::saveToCustJson(const QStringList &funcList)
QString content = QString::fromUtf8(file.readAll());
file.close();
// 用正则定位 "convert" 节点(兼容任意空格)
static QRegularExpression convertRe(R"("convert"\s*:)");
// 用正则定位 convert 顶层键(兼容任意空格)
static const QRegularExpression convertRe(
QString(R"("%1"\s*:)").arg(Constants::CustKeys::ConvertDir));
auto convertMatch = convertRe.match(content);
if (!convertMatch.hasMatch()) {
LogManager::instance()->logWarning("全量转码 - 保存配置失败,未找到 convert 节点");
@@ -525,17 +136,10 @@ bool BatchConvertPage::saveToCustJson(const QStringList &funcList)
int bracketEnd = pos; // 指向 ']' 之后一位
// 构建新数组(保持原始缩进风格:项 12 空格,闭合 ] 8 空格)
// 函数名做 JSON 字符串转义, 防止含引号/反斜杠时产生非法 JSON
auto jsonEscape = [](const QString &s) -> QString {
QString out = s;
out.replace('\\', "\\\\");
out.replace('"', "\\\"");
return out;
};
QString newArray = "[";
for (int i = 0; i < funcList.size(); ++i) {
if (i > 0) newArray += ",";
newArray += "\n \"" + jsonEscape(funcList[i]) + ".service_design\"";
newArray += "\n \"" + CustJsonIo::escapeJsonString(funcList[i]) + Constants::DesignExts::Service + "\"";
}
if (!funcList.isEmpty()) {
newArray += "\n ";
@@ -555,7 +159,11 @@ bool BatchConvertPage::saveToCustJson(const QStringList &funcList)
}
// QSaveFile 原子写: 先写临时文件, commit 成功才替换目标文件,
// 避免写入中途崩溃/断电损坏配置
// 避免写入中途崩溃/断电损坏配置。
// 保存窗口: 写入前开启, 窗口期内 QSaveFile 替换目标文件引发的
// fileChanged/directoryChanged 监控事件直接忽略, 防止自身保存触发重载;
// 失败路径(打开/提交失败)由组件 2s 兜底自动关窗
m_configMonitor->beginSaveWindow();
QSaveFile saveFile(jsonPath);
if (!saveFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
LogManager::instance()->logWarning(
@@ -570,13 +178,13 @@ bool BatchConvertPage::saveToCustJson(const QStringList &funcList)
return false;
}
// 程序自身写入后更新 mtime,避免触发无谓的重载
m_lastFileMtime = currentFileMtime();
// 程序自身写入后更新监控 mtime 基准, 避免触发无谓的重载
m_configMonitor->endSave();
return true;
}
void BatchConvertPage::loadFromCustJson()
void BatchConvertPage::loadConfig()
{
QString jsonPath = Constants::custJson();
@@ -599,210 +207,42 @@ void BatchConvertPage::loadFromCustJson()
}
QJsonObject root = doc.object();
QJsonArray dir = root["convert"].toObject()["dir"].toArray();
QJsonArray dir = root[Constants::CustKeys::ConvertDir].toObject()[Constants::CustKeys::ConvertDirSub].toArray();
QStringList items;
for (const QJsonValue &val : dir) {
QString item = val.toString();
if (item.endsWith(".service_design")) {
item.chop(static_cast<int>(strlen(".service_design")));
if (item.endsWith(Constants::DesignExts::Service)) {
item.chop(Constants::DesignExts::Service.size());
}
items.append(item);
}
m_funcList = items;
updateTable();
refreshTable();
}
void BatchConvertPage::onStartConvert()
{
if (m_startBtn->text() == "停止转换") {
m_progressLabel->setText("正在停止转换...");
emit stopConvert();
return;
}
if (m_funcList.isEmpty()) {
QMessageBox::warning(this, "提示", "请先添加需要转码的函数!");
return;
}
m_progressBar->setValue(0);
m_progressLabel->setText("正在准备转换...");
if (saveToCustJson(m_funcList)) {
m_progressLabel->setText("配置加载成功,开始转换...");
m_progressBar->setValue(20);
} else {
m_progressLabel->setText("配置加载失败");
return;
}
emit startConvert();
}
void BatchConvertPage::onConvertStarted()
{
m_startBtn->setText("停止转换");
m_startBtn->setStyleSheet("background-color: #e74c3c; color: white; padding: 10px 30px; font-size: 14px; border: none; border-radius: 4px;");
}
void BatchConvertPage::onConvertFinished()
{
m_progressBar->setValue(100);
m_progressLabel->setText("转换完成");
m_startBtn->setText("开始转换");
m_startBtn->setStyleSheet("background-color: #1abc9c; color: white; padding: 10px 30px; font-size: 14px; border: none; border-radius: 4px;");
}
void BatchConvertPage::onConvertFailed(const QString &detail)
{
// 失败收尾: 进度条保持当前进度(不复位 100%, 与成功完成区分), 文字提示转换失败
m_progressLabel->setText(detail.isEmpty() ? "转换失败" : detail);
m_startBtn->setText("开始转换");
m_startBtn->setStyleSheet("background-color: #1abc9c; color: white; padding: 10px 30px; font-size: 14px; border: none; border-radius: 4px;");
}
void BatchConvertPage::setTaskBusy(bool busy)
{
if (!m_startBtn) {
return;
}
m_startBtn->setEnabled(!busy);
if (busy) {
m_progressBar->setValue(0);
m_progressLabel->setText("有其他转换任务进行中,请等待完成...");
} else {
m_progressLabel->setText("等待开始...");
}
}
void BatchConvertPage::addFunction(const QString &funcName)
{
if (funcName.isEmpty()) {
return;
}
if (m_funcList.contains(funcName)) {
QMessageBox::information(nullptr, "提示", QString("功能 [%1] 已在列表中").arg(funcName));
return;
}
m_funcList.append(funcName);
updateTable();
if (saveToCustJson(m_funcList)) {
QMessageBox::information(nullptr, "提示", QString("已将功能 [%1] 添加到\"全量转码\"").arg(funcName));
} else {
QMessageBox::warning(nullptr, "警告", "添加到\"全量转码\"成功,但保存到 cust.json 失败");
}
}
QPair<int, int> BatchConvertPage::addFunctionsBatch(const QStringList &funcNames, bool notify)
{
// 批量添加(功能查询页多选双击): 不逐条弹窗;
// notify=true 时结束后一次汇总提示, notify=false 静默(由调用方统一汇总), 返回 {新增数, 跳过数}
QStringList added;
int skipped = 0;
for (const QString &funcName : funcNames) {
if (funcName.isEmpty()) {
continue;
}
if (m_funcList.contains(funcName)) {
++skipped;
continue;
}
m_funcList.append(funcName);
added.append(funcName);
}
if (added.isEmpty()) {
if (notify && skipped > 0) {
QMessageBox::information(nullptr, "提示",
QString("所选 %1 个功能均已在\"全量转码\"列表中").arg(skipped));
}
return qMakePair(0, skipped);
}
updateTable();
if (!saveToCustJson(m_funcList)) {
QMessageBox::warning(nullptr, "警告",
QString("添加成功,但保存到 cust.json 失败(%1 个功能)").arg(added.size()));
return qMakePair(-1, -1);
}
if (notify) {
QMessageBox::information(nullptr, "提示",
QString("已将 %1 个功能加入\"全量转码\"").arg(added.size()) +
(skipped > 0 ? QString("(另有 %1 个已存在,已跳过)").arg(skipped) : QString()));
}
return qMakePair(added.size(), skipped);
}
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)
{
// 文件可能被编辑器替换导致监视失效,尝试重新添加
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
QString BatchConvertPage::configFilePath() const
{
return Constants::custJson();
}
QString BatchConvertPage::workbenchTitle() const
{
return "全量转码";
}
QString BatchConvertPage::configDisplayName() const
{
return "cust.json";
}
QString BatchConvertPage::addDialogTitle() const
{
return "添加UF20函数";
}
QString BatchConvertPage::addDialogPlaceholder() const
{
return "每行输入一个函数名";
}