新增按需转码功能;支持批量查询和批量添加
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
#include <QHBoxLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QFile>
|
||||
#include <QSaveFile>
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QJsonDocument>
|
||||
@@ -25,6 +26,7 @@
|
||||
#include "ElaPushButton.h"
|
||||
#include "ElaProgressBar.h"
|
||||
#include "ElaText.h"
|
||||
#include "ElaTheme.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -93,6 +95,11 @@ BatchConvertPage::BatchConvertPage(QWidget *parent)
|
||||
m_lastFileMtime = currentFileMtime();
|
||||
|
||||
setupConfigWatcher();
|
||||
|
||||
// 灰字进度标签跟随主题明暗切换(上轮修改实际未落盘, 此处补全)
|
||||
connect(ElaTheme::getInstance(), &ElaTheme::themeModeChanged,
|
||||
this, &BatchConvertPage::updateTipStyle);
|
||||
updateTipStyle();
|
||||
}
|
||||
|
||||
BatchConvertPage::~BatchConvertPage()
|
||||
@@ -150,7 +157,6 @@ void BatchConvertPage::initUI()
|
||||
progressLayout->setSpacing(10);
|
||||
|
||||
m_progressLabel = new ElaText("等待开始...");
|
||||
m_progressLabel->setStyleSheet("color: #666; font-size: 12px;");
|
||||
m_progressLabel->setFixedHeight(40);
|
||||
m_progressLabel->setWordWrap(false);
|
||||
m_progressLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
@@ -232,9 +238,13 @@ void BatchConvertPage::updateTable()
|
||||
editBtn->setIconSize(QSize(20, 20));
|
||||
editBtn->setStyleSheet("padding: 2px; border: none; background-color: transparent;");
|
||||
editBtn->setToolTip("修改");
|
||||
connect(editBtn, &QPushButton::clicked, this, [this, i]() {
|
||||
onEditRow(i);
|
||||
});
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -263,6 +273,16 @@ void BatchConvertPage::showEvent(QShowEvent *event)
|
||||
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()) {
|
||||
@@ -310,6 +330,8 @@ void BatchConvertPage::onEditRow(int row)
|
||||
updateTable();
|
||||
if (saveToCustJson(m_funcList)) {
|
||||
loadFromCustJson();
|
||||
} else {
|
||||
QMessageBox::warning(this, "保存失败", "函数名已在内存中修改,但写入 cust.json 失败(文件被占用或权限不足?),重启后修改将丢失!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,6 +398,14 @@ void BatchConvertPage::onAddFunction()
|
||||
QString("成功添加 %1 个函数").arg(addedCount));
|
||||
}
|
||||
|
||||
// 落盘: 新增函数持久化到 cust.json, 否则只写内存重启后丢失(与 onEditRow 保持一致)
|
||||
if (addedCount > 0) {
|
||||
if (saveToCustJson(m_funcList)) {
|
||||
loadFromCustJson();
|
||||
} else {
|
||||
QMessageBox::warning(this, "保存失败", "新增函数已在内存中生效,但写入 cust.json 失败(文件被占用或权限不足?),重启后将丢失!");
|
||||
}
|
||||
}
|
||||
updateTable();
|
||||
}
|
||||
|
||||
@@ -403,6 +433,8 @@ void BatchConvertPage::onRemoveFunction()
|
||||
updateTable();
|
||||
if (saveToCustJson(m_funcList)) {
|
||||
loadFromCustJson();
|
||||
} else {
|
||||
QMessageBox::warning(this, "保存失败", "删除操作已在内存中生效,但写入 cust.json 失败(文件被占用或权限不足?),重启后将恢复原清单!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,10 +452,21 @@ void BatchConvertPage::onClearTable()
|
||||
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)
|
||||
{
|
||||
QString jsonPath = Constants::custJson();
|
||||
@@ -457,22 +500,42 @@ bool BatchConvertPage::saveToCustJson(const QStringList &funcList)
|
||||
|
||||
int bracketStart = dirMatch.capturedEnd() - 1; // 指向 '['
|
||||
|
||||
// 括号计数找到匹配的 ']'
|
||||
// 括号计数找到匹配的 ']'(必须跳过字符串字面量, 否则字符串内的 [ ] 会截断匹配改坏 JSON)
|
||||
int bracketCount = 1;
|
||||
int pos = bracketStart + 1;
|
||||
bool inString = false;
|
||||
bool escaped = false;
|
||||
while (pos < content.length() && bracketCount > 0) {
|
||||
QChar ch = content[pos];
|
||||
if (ch == '[') bracketCount++;
|
||||
else if (ch == ']') bracketCount--;
|
||||
if (inString) {
|
||||
if (escaped) { escaped = false; }
|
||||
else if (ch == '\\') { escaped = true; }
|
||||
else if (ch == '"') { inString = false; }
|
||||
} else {
|
||||
if (ch == '"') { inString = true; }
|
||||
else if (ch == '[') bracketCount++;
|
||||
else if (ch == ']') bracketCount--;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
if (bracketCount != 0) {
|
||||
LogManager::instance()->logWarning("全量转码 - 保存配置失败,dir 数组括号不匹配");
|
||||
return false;
|
||||
}
|
||||
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 \"" + funcList[i] + ".service_design\"";
|
||||
newArray += "\n \"" + jsonEscape(funcList[i]) + ".service_design\"";
|
||||
}
|
||||
if (!funcList.isEmpty()) {
|
||||
newArray += "\n ";
|
||||
@@ -482,15 +545,30 @@ bool BatchConvertPage::saveToCustJson(const QStringList &funcList)
|
||||
// 只替换 [bracketStart, bracketEnd) 区间,其余内容原样保留
|
||||
content.replace(bracketStart, bracketEnd - bracketStart, newArray);
|
||||
|
||||
// 写回
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
// 写回前校验替换结果仍是合法 JSON, 防止改坏的配置被原地写回无法恢复
|
||||
QJsonParseError parseErr;
|
||||
QJsonDocument::fromJson(content.toUtf8(), &parseErr);
|
||||
if (parseErr.error != QJsonParseError::NoError) {
|
||||
LogManager::instance()->logWarning(
|
||||
QString("全量转码 - 保存配置中止, 替换后 JSON 非法: %1").arg(parseErr.errorString()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// QSaveFile 原子写: 先写临时文件, commit 成功才替换目标文件,
|
||||
// 避免写入中途崩溃/断电损坏配置
|
||||
QSaveFile saveFile(jsonPath);
|
||||
if (!saveFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
LogManager::instance()->logWarning(
|
||||
QString("全量转码 - 保存配置失败,无法写入: %1").arg(jsonPath));
|
||||
return false;
|
||||
}
|
||||
|
||||
file.write(content.toUtf8());
|
||||
file.close();
|
||||
saveFile.write(content.toUtf8());
|
||||
if (!saveFile.commit()) {
|
||||
LogManager::instance()->logWarning(
|
||||
QString("全量转码 - 保存配置失败,写入提交异常: %1").arg(jsonPath));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 程序自身写入后更新 mtime,避免触发无谓的重载
|
||||
m_lastFileMtime = currentFileMtime();
|
||||
@@ -577,6 +655,14 @@ void BatchConvertPage::onConvertFinished()
|
||||
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) {
|
||||
@@ -612,6 +698,46 @@ void BatchConvertPage::addFunction(const QString &funcName)
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user