优化若干性能问题
This commit is contained in:
@@ -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 "每行输入一个函数名";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user