支持全量转码和按需转码

This commit is contained in:
taocong
2026-09-10 16:01:54 +08:00
parent eab73a11ca
commit 644fa8b4ce
17 changed files with 1083 additions and 147 deletions
+24 -9
View File
@@ -45,7 +45,8 @@ bool validateLsFunctionNames(QWidget *parent, const QStringList &functionNames)
}
QMessageBox::warning(parent, "提示",
QString("不支持非LS业务转码:\n%1").arg(nonLsNames.join('\n')));
QString("\"全量转码\"仅支持 LS_ 开头的功能,以下功能无法加入:\n%1\n\n非LS功能请切到\"按需转码\"工作台添加。")
.arg(nonLsNames.join('\n')));
return false;
}
@@ -431,7 +432,7 @@ bool BatchConvertPage::saveToCustJson(const QStringList &funcList)
QFile file(jsonPath);
if (!file.open(QIODevice::ReadOnly)) {
LogManager::instance()->logWarning(
QString("业务转码 - 保存配置失败,无法打开: %1").arg(jsonPath));
QString("全量转码 - 保存配置失败,无法打开: %1").arg(jsonPath));
return false;
}
@@ -442,7 +443,7 @@ bool BatchConvertPage::saveToCustJson(const QStringList &funcList)
static QRegularExpression convertRe(R"("convert"\s*:)");
auto convertMatch = convertRe.match(content);
if (!convertMatch.hasMatch()) {
LogManager::instance()->logWarning("业务转码 - 保存配置失败,未找到 convert 节点");
LogManager::instance()->logWarning("全量转码 - 保存配置失败,未找到 convert 节点");
return false;
}
@@ -450,7 +451,7 @@ bool BatchConvertPage::saveToCustJson(const QStringList &funcList)
static QRegularExpression dirRe(R"("dir"\s*:\s*\[)");
auto dirMatch = dirRe.match(content, convertMatch.capturedEnd());
if (!dirMatch.hasMatch()) {
LogManager::instance()->logWarning("业务转码 - 保存配置失败,未找到 dir 数组");
LogManager::instance()->logWarning("全量转码 - 保存配置失败,未找到 dir 数组");
return false;
}
@@ -484,7 +485,7 @@ bool BatchConvertPage::saveToCustJson(const QStringList &funcList)
// 写回
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
LogManager::instance()->logWarning(
QString("业务转码 - 保存配置失败,无法写入: %1").arg(jsonPath));
QString("全量转码 - 保存配置失败,无法写入: %1").arg(jsonPath));
return false;
}
@@ -504,7 +505,7 @@ void BatchConvertPage::loadFromCustJson()
QFile file(jsonPath);
if (!file.open(QIODevice::ReadOnly)) {
LogManager::instance()->logWarning(
QString("业务转码 - 加载配置失败,无法打开: %1").arg(jsonPath));
QString("全量转码 - 加载配置失败,无法打开: %1").arg(jsonPath));
return;
}
@@ -515,7 +516,7 @@ void BatchConvertPage::loadFromCustJson()
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError) {
LogManager::instance()->logWarning(
QString("业务转码 - 加载配置 JSON 解析失败: %1").arg(parseError.errorString()));
QString("全量转码 - 加载配置 JSON 解析失败: %1").arg(parseError.errorString()));
return;
}
@@ -576,6 +577,20 @@ void BatchConvertPage::onConvertFinished()
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()) {
@@ -591,9 +606,9 @@ void BatchConvertPage::addFunction(const QString &funcName)
updateTable();
if (saveToCustJson(m_funcList)) {
QMessageBox::information(nullptr, "提示", QString("已将功能 [%1] 添加到业务转码").arg(funcName));
QMessageBox::information(nullptr, "提示", QString("已将功能 [%1] 添加到\"全量转码\"").arg(funcName));
} else {
QMessageBox::warning(nullptr, "警告", "添加到业务转码成功,但保存到 cust.json 失败");
QMessageBox::warning(nullptr, "警告", "添加到\"全量转码\"成功,但保存到 cust.json 失败");
}
}
+3 -1
View File
@@ -29,7 +29,9 @@ public slots:
void onConvertFinished();
void onConvertStarted();
void addFunction(const QString &funcName);
// 另一工作台任务运行时由 MainWindow 调用,禁用本页开始按钮
void setTaskBusy(bool busy);
int getFunctionCount() const { return m_funcList.size(); }
QStringList getFunctionList() const { return m_funcList; }
@@ -0,0 +1,55 @@
#include "businessconvertpage.h"
#include "ElaPivot.h"
#include "src/pages/batchconvert/batchconvertpage.h"
#include "src/pages/nonlsconvert/nonlsconvertpage.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QStackedWidget>
BusinessConvertPage::BusinessConvertPage(QWidget *parent)
: QWidget(parent)
, m_modePivot(nullptr)
, m_stackWidget(nullptr)
, m_lsPage(nullptr)
, m_nonLsPage(nullptr)
{
initUI();
}
void BusinessConvertPage::initUI()
{
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 12, 0, 0);
layout->setSpacing(8);
// 顶部模式切换(ElaPivot),与代码检索页风格一致
QHBoxLayout *modeLayout = new QHBoxLayout;
modeLayout->setContentsMargins(30, 0, 30, 0);
m_modePivot = new ElaPivot;
m_modePivot->appendPivot("全量转码");
m_modePivot->appendPivot("按需转码");
m_modePivot->setTextPixelSize(14);
m_modePivot->setCurrentIndex(0);
connect(m_modePivot, &ElaPivot::pivotClicked, this, &BusinessConvertPage::onModePivotClicked);
modeLayout->addWidget(m_modePivot);
modeLayout->addStretch();
layout->addLayout(modeLayout);
// 两套转码工作台放入堆叠,由 pivot 切换
m_lsPage = new BatchConvertPage;
m_nonLsPage = new NonLsConvertPage;
m_stackWidget = new QStackedWidget;
m_stackWidget->addWidget(m_lsPage);
m_stackWidget->addWidget(m_nonLsPage);
layout->addWidget(m_stackWidget, 1);
}
void BusinessConvertPage::onModePivotClicked(int index)
{
if (m_stackWidget) {
m_stackWidget->setCurrentIndex(index);
}
}
@@ -0,0 +1,36 @@
#ifndef BUSINESSCONVERTPAGE_H
#define BUSINESSCONVERTPAGE_H
#include <QWidget>
class ElaPivot;
class QStackedWidget;
class BatchConvertPage;
class NonLsConvertPage;
// 业务转码容器页:用 ElaPivot(类似代码检索的 m_modePivot)切换两种转码工作台
// Pivot1 全量转码 = BatchConvertPage(整体复用现有实现)
// Pivot2 按需转码 = NonLsConvertPage
class BusinessConvertPage : public QWidget
{
Q_OBJECT
public:
explicit BusinessConvertPage(QWidget *parent = nullptr);
BatchConvertPage *lsPage() const { return m_lsPage; }
NonLsConvertPage *nonLsPage() const { return m_nonLsPage; }
private slots:
void onModePivotClicked(int index);
private:
void initUI();
ElaPivot *m_modePivot;
QStackedWidget *m_stackWidget;
BatchConvertPage *m_lsPage;
NonLsConvertPage *m_nonLsPage;
};
#endif // BUSINESSCONVERTPAGE_H
@@ -60,7 +60,7 @@ void UF20FunctionSearchPage::initUI()
layout->addWidget(m_resultTable);
QHBoxLayout *tipLayout = new QHBoxLayout;
QLabel *tipLabel = new QLabel("💡 双击选中行添加转码业务,只支持LS功能添加");
QLabel *tipLabel = new QLabel("💡 双击选中行加入转码工作台:LS功能 → \"全量转码\";其它功能 → \"按需转码\"");
tipLabel->setStyleSheet("color: #666; font-size: 13px;");
tipLayout->addWidget(tipLabel);
tipLayout->addStretch();
@@ -113,11 +113,13 @@ void UF20FunctionSearchPage::onSearch()
void UF20FunctionSearchPage::onTableDoubleClicked(const QModelIndex &index)
{
if (index.isValid()) {
QString funcName = m_tableModel->data(m_tableModel->index(index.row(), 0)).toString();
if (!funcName.startsWith("LS_")) {
QMessageBox::information(this, "提示", "仅支持LS功能添加转码业务");
return;
const QString funcName =
m_tableModel->data(m_tableModel->index(index.row(), 0)).toString();
// LS_ 开头 → 全量转码;其余 → 按需转码(由 MainWindow 接到对应工作台)
if (funcName.startsWith("LS_")) {
emit addLsFunctionToConvert(funcName);
} else {
emit addNonLsFunctionToConvert(funcName);
}
emit addFunctionToConvert(funcName);
}
}
@@ -16,7 +16,9 @@ public:
~UF20FunctionSearchPage();
signals:
void addFunctionToConvert(const QString& funcName);
// 双击按功能类别分流:LS_ 开头 → 全量转码工作台;其余 → 按需转码工作台
void addLsFunctionToConvert(const QString& funcName);
void addNonLsFunctionToConvert(const QString& funcName);
private slots:
void onSearch();
+732 -77
View File
@@ -1,121 +1,776 @@
#include "nonlsconvertpage.h"
#include "src/utils/Constants.h"
#include "src/utils/uf2configreader.h"
#include "src/utils/logmanager.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QGroupBox>
#include <QComboBox>
#include <QFileDialog>
#include <QLabel>
#include <QSettings>
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QHeaderView>
#include <QMessageBox>
#include <QInputDialog>
#include <QDialog>
#include <QTextEdit>
#include <QStandardItem>
#include <QStyledItemDelegate>
#include <QPainter>
#include <QShowEvent>
#include <cstring>
#include "ElaPushButton.h"
#include "ElaComboBox.h"
namespace {
// 按需转码不做前缀白名单,仅对 LS_ 开头的函数给出“建议放全量转码 Tab”提示
void adviseAboutLsNames(QWidget *parent, const QStringList &names)
{
QStringList lsNames;
for (const QString &name : names) {
const QString trimmed = name.trimmed();
if (trimmed.startsWith("LS_") && !lsNames.contains(trimmed)) {
lsNames.append(trimmed);
}
}
if (!lsNames.isEmpty()) {
QMessageBox::information(parent, "提示",
QString("以下函数以 LS_ 开头,通常属于\"内存交易\"改造范围,\n建议放入\"全量转码\"工作台转码:\n\n%1")
.arg(lsNames.join('\n')));
}
}
// 清洗清单项:去首尾空白;若误带 .service_design 后缀则剥离(保持短名形态)
QString normalizeNonLsName(const QString &raw)
{
QString name = raw.trimmed();
const QString suffix = ".service_design";
if (name.endsWith(suffix)) {
name.chop(static_cast<int>(strlen(".service_design")));
name = name.trimmed();
}
return name;
}
// cust_nonls.json convert.dir 值语义:0=否(不递归)、-1=是(递归)
constexpr int RecursionNo = 0;
constexpr int RecursionYes = -1;
// convert.dir 中引擎兜底条目键(非清单功能,加载时跳过、保存时保留),默认值与既有配置样例一致
constexpr char WildcardDefaultKey[] = "*";
constexpr int WildcardDefaultValue = RecursionYes;
// 清单表格列定义
enum TableCol { ColName = 0, ColExists = 1, ColRecurse = 2, ColAction = 3 };
constexpr int ActionColumnWidth = 80;
constexpr int RecurseColumnWidth = 100;
} // namespace
class NonLsStatusDelegate : public QStyledItemDelegate
{
public:
explicit NonLsStatusDelegate(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);
}
};
NonLsConvertPage::NonLsConvertPage(QWidget *parent)
: QWidget(parent)
{
initUI();
loadFromCustNonLsJson();
m_reloadTimer = new QTimer(this);
m_reloadTimer->setSingleShot(true);
m_reloadTimer->setInterval(300);
connect(m_reloadTimer, &QTimer::timeout, this, &NonLsConvertPage::onReloadTimeout);
m_lastFileMtime = currentFileMtime();
setupConfigWatcher();
}
NonLsConvertPage::~NonLsConvertPage()
{
if (m_configWatcher) {
m_configWatcher->removePaths(m_configWatcher->files());
m_configWatcher->removePaths(m_configWatcher->directories());
}
}
void NonLsConvertPage::initUI()
{
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(30, 30, 30, 30);
layout->setSpacing(20);
layout->setContentsMargins(30, 20, 30, 30);
layout->setSpacing(14);
QGroupBox *inputBox = new QGroupBox("输入设置");
QVBoxLayout *inputLayout = new QVBoxLayout(inputBox);
inputLayout->setSpacing(15);
// 按需转码说明条:非 LS 功能,支持单文件/递归两种模式,不含核心文件与 module 模块文件
QLabel *modeTipLabel = new QLabel(
"<b>说明:</b>按需转码针对<b>非 LS 功能</b>转码,支持<b>单文件转码</b>与<b>递归转码</b>两种模式,"
"转码范围不包含核心文件、module 模块文件。"
"<ul style='margin-top:4px; margin-bottom:0px; padding-left:18px;'>"
"<li><b>非递归模式</b>:仅对目标文件本身执行转码,不会处理该文件内调用的函数;</li>"
"<li><b>递归模式</b>:转码目标文件的同时,自动对文件中所调用的关联函数一并完成转码。</li>"
"</ul>");
modeTipLabel->setTextFormat(Qt::RichText);
modeTipLabel->setStyleSheet("color: #666; font-size: 12px;");
modeTipLabel->setWordWrap(true);
layout->addWidget(modeTipLabel);
QHBoxLayout *srcLayout = new QHBoxLayout;
QLabel *srcLabel = new QLabel("源文件:");
m_srcFileEdit = new QLineEdit;
QPushButton *srcBtn = new QPushButton("浏览");
connect(srcBtn, &QPushButton::clicked, this, &NonLsConvertPage::onBrowseSrcFile);
srcLayout->addWidget(srcLabel);
srcLayout->addWidget(m_srcFileEdit);
srcLayout->addWidget(srcBtn);
inputLayout->addLayout(srcLayout);
QGroupBox *funcBox = new QGroupBox("转码功能");
QVBoxLayout *funcLayout = new QVBoxLayout(funcBox);
funcLayout->setSpacing(10);
QHBoxLayout *dstLayout = new QHBoxLayout;
QLabel *dstLabel = new QLabel("输出文件:");
m_dstFileEdit = new QLineEdit;
QPushButton *dstBtn = new QPushButton("浏览");
connect(dstBtn, &QPushButton::clicked, this, &NonLsConvertPage::onBrowseDstFile);
dstLayout->addWidget(dstLabel);
dstLayout->addWidget(m_dstFileEdit);
dstLayout->addWidget(dstBtn);
inputLayout->addLayout(dstLayout);
m_tableModel = new QStandardItemModel(this);
m_tableModel->setColumnCount(4);
m_tableModel->setHorizontalHeaderLabels({"UF20函数名", "是否存在", "是否递归", "操作"});
layout->addWidget(inputBox);
m_funcTable = new ElaTableView;
m_funcTable->setModel(m_tableModel);
m_funcTable->setMinimumHeight(180);
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()->setSectionResizeMode(3, QHeaderView::Fixed);
m_funcTable->horizontalHeader()->setStretchLastSection(false);
m_funcTable->setItemDelegateForColumn(ColExists, new NonLsStatusDelegate(this));
funcLayout->addWidget(m_funcTable);
QGroupBox *optionBox = new QGroupBox("转换选项");
QVBoxLayout *optionLayout = new QVBoxLayout(optionBox);
optionLayout->setSpacing(15);
QHBoxLayout *tableBtnLayout = new QHBoxLayout;
ElaPushButton *addBtn = new ElaPushButton("添加");
connect(addBtn, &QPushButton::clicked, this, &NonLsConvertPage::onAddFunction);
ElaPushButton *removeBtn = new ElaPushButton("删除选中");
connect(removeBtn, &QPushButton::clicked, this, &NonLsConvertPage::onRemoveFunction);
ElaPushButton *clearBtn = new ElaPushButton("清空列表");
connect(clearBtn, &QPushButton::clicked, this, &NonLsConvertPage::onClearTable);
tableBtnLayout->addWidget(addBtn);
tableBtnLayout->addStretch();
tableBtnLayout->addWidget(removeBtn);
tableBtnLayout->addWidget(clearBtn);
funcLayout->addLayout(tableBtnLayout);
QHBoxLayout *modeLayout = new QHBoxLayout;
QLabel *modeLabel = new QLabel("转换模式:");
QComboBox *modeCombo = new QComboBox;
modeCombo->addItems({"非LS转LS", "LS转非LS"});
modeLayout->addWidget(modeLabel);
modeLayout->addWidget(modeCombo);
optionLayout->addLayout(modeLayout);
layout->addWidget(funcBox);
QHBoxLayout *encodingLayout = new QHBoxLayout;
QLabel *encodingLabel = new QLabel("编码格式:");
QComboBox *encodingCombo = new QComboBox;
encodingCombo->addItems({"UTF-8", "GBK", "GB2312"});
encodingLayout->addWidget(encodingLabel);
encodingLayout->addWidget(encodingCombo);
optionLayout->addLayout(encodingLayout);
QGroupBox *progressBox = new QGroupBox("转换进度");
progressBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
QVBoxLayout *progressLayout = new QVBoxLayout(progressBox);
progressLayout->setSpacing(10);
layout->addWidget(optionBox);
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);
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;
QPushButton *startBtn = new QPushButton("开始转换");
startBtn->setStyleSheet("background-color: #3498db; color: white; padding: 10px 30px; font-size: 14px; border: none; border-radius: 4px;");
connect(startBtn, &QPushButton::clicked, this, &NonLsConvertPage::onStartConvert);
QPushButton *previewBtn = new QPushButton("预览");
connect(previewBtn, &QPushButton::clicked, this, &NonLsConvertPage::onPreview);
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, &NonLsConvertPage::onStartConvert);
btnLayout->addStretch();
btnLayout->addWidget(startBtn);
btnLayout->addWidget(previewBtn);
btnLayout->addWidget(m_startBtn);
layout->addLayout(btnLayout);
}
void NonLsConvertPage::onProgressUpdate(int progress, const QString &status)
{
m_progressBar->setValue(progress);
if (!status.isEmpty()) {
QString displayStatus = status;
if (displayStatus.length() > 80) {
displayStatus = displayStatus.left(80) + "...";
}
m_progressLabel->setText(displayStatus);
}
}
bool NonLsConvertPage::checkFunctionExists(const QString &funcName)
{
return UF2ConfigReader::checkFuncExistsStatic(funcName);
}
void NonLsConvertPage::updateTable()
{
// 不使用 blockSignals:QTableView 依赖 rowsRemoved/rowsInserted 信号更新内部布局缓存,
// 若屏蔽会导致新增行不可见。m_recursionMap 已预填充,值相等时无需写盘。
m_tableModel->removeRows(0, m_tableModel->rowCount());
for (int i = 0; i < m_funcList.size(); ++i) {
const QString funcName = m_funcList[i];
QStandardItem *nameItem = new QStandardItem(funcName);
nameItem->setFlags(nameItem->flags() & ~Qt::ItemIsEditable);
// LS_ 开头的函数给出黄色提示(不拦截)
if (funcName.startsWith("LS_")) {
nameItem->setData(QBrush(QColor(196, 126, 10)), Qt::ForegroundRole);
}
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);
}
// “是否递归”以行尾下拉呈现:否=0、是=-1(与 cust_nonls.json convert.dir 值一致)
QStandardItem *recItem = new QStandardItem("");
recItem->setFlags(recItem->flags() & ~Qt::ItemIsEditable);
recItem->setTextAlignment(Qt::AlignCenter);
QStandardItem *actionItem = new QStandardItem("");
actionItem->setFlags(actionItem->flags() & ~Qt::ItemIsEditable);
QList<QStandardItem*> rowItems;
rowItems.append(nameItem);
rowItems.append(statusItem);
rowItems.append(recItem);
rowItems.append(actionItem);
m_tableModel->appendRow(rowItems);
// “是否递归”下拉控件:否/是,切换即写回内存映射并持久化
ElaComboBox *recCombo = new ElaComboBox;
recCombo->addItem("否", RecursionNo);
recCombo->addItem("是", RecursionYes);
recCombo->setCurrentIndex(
(m_recursionMap.value(funcName, RecursionNo) == RecursionYes) ? 1 : 0);
connect(recCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, [this, funcName](int index) {
const int recursion = (index == 1) ? RecursionYes : RecursionNo;
if (m_recursionMap.value(funcName, RecursionNo) == recursion) {
return;
}
m_recursionMap.insert(funcName, recursion);
saveToCustNonLsJson(m_funcList);
});
m_funcTable->setIndexWidget(m_tableModel->index(i, ColRecurse), recCombo);
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("修改");
connect(editBtn, &QPushButton::clicked, this, [this, i]() {
onEditRow(i);
});
m_funcTable->setIndexWidget(m_tableModel->index(i, ColAction), editBtn);
}
int totalWidth = m_funcTable->viewport()->width();
const int fixedWidth = ActionColumnWidth + RecurseColumnWidth;
const int rest = totalWidth - fixedWidth;
m_funcTable->setColumnWidth(ColName, rest * 8 / 10);
m_funcTable->setColumnWidth(ColExists, rest * 2 / 10);
m_funcTable->setColumnWidth(ColRecurse, RecurseColumnWidth);
m_funcTable->setColumnWidth(ColAction, ActionColumnWidth);
// 视图刷新保险:强制重绘并滚动到顶部,确保新增行可见
m_funcTable->viewport()->update();
m_funcTable->resizeRowsToContents();
m_funcTable->scrollToTop();
m_funcTable->clearSelection();
}
void NonLsConvertPage::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
if (m_funcTable) {
int totalWidth = m_funcTable->width();
const int fixedWidth = ActionColumnWidth + RecurseColumnWidth;
const int rest = totalWidth - fixedWidth;
m_funcTable->setColumnWidth(ColName, rest * 8 / 10);
m_funcTable->setColumnWidth(ColExists, rest * 2 / 10);
m_funcTable->setColumnWidth(ColRecurse, RecurseColumnWidth);
m_funcTable->setColumnWidth(ColAction, ActionColumnWidth);
}
}
void NonLsConvertPage::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
updateTable();
}
void NonLsConvertPage::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 = normalizeNonLsName(dialog.textValue());
if (newName.isEmpty()) {
QMessageBox::warning(this, "提示", "函数名不能为空!");
continue;
}
if (newName == oldName) {
return;
}
if (m_funcList.contains(newName)) {
QMessageBox::warning(this, "提示", "该函数名已存在,请重新输入!");
continue;
}
break;
}
const int oldRecursion = m_recursionMap.value(oldName, RecursionNo);
m_recursionMap.remove(oldName);
m_recursionMap.insert(newName, oldRecursion);
m_funcList[row] = newName;
updateTable();
if (saveToCustNonLsJson(m_funcList)) {
loadFromCustNonLsJson();
}
adviseAboutLsNames(this, {newName});
}
void NonLsConvertPage::onAddFunction()
{
QDialog dialog(this);
dialog.setWindowTitle("添加函数");
dialog.resize(400, 250);
QVBoxLayout *layout = new QVBoxLayout(&dialog);
QTextEdit *textEdit = new QTextEdit;
textEdit->setPlaceholderText("每行输入一个函数名(短名,如 AS_xxx)");
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);
m_resultEdit = new QTextEdit;
m_resultEdit->setReadOnly(true);
m_resultEdit->setStyleSheet("background-color: #2c3e50; color: #ecf0f1;");
m_resultEdit->append("非LS转码工具已准备就绪...");
layout->addWidget(m_resultEdit);
}
connect(okBtn, &QPushButton::clicked, &dialog, &QDialog::accept);
connect(cancelBtn, &QPushButton::clicked, &dialog, &QDialog::reject);
void NonLsConvertPage::onBrowseSrcFile()
{
QString file = QFileDialog::getOpenFileName(this, "选择源文件");
if (!file.isEmpty()) {
m_srcFileEdit->setText(file);
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;
QStringList addedNames;
for (const QString &name : funcNames) {
const QString trimmedName = normalizeNonLsName(name);
if (trimmedName.isEmpty()) {
continue;
}
if (m_funcList.contains(trimmedName)) {
existCount++;
continue;
}
m_funcList.append(trimmedName);
m_recursionMap.insert(trimmedName, RecursionNo); // 新增功能默认“否”
addedNames.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));
}
adviseAboutLsNames(this, addedNames);
updateTable();
if (addedCount > 0) {
saveToCustNonLsJson(m_funcList);
}
}
void NonLsConvertPage::onBrowseDstFile()
void NonLsConvertPage::onRemoveFunction()
{
QString file = QFileDialog::getSaveFileName(this, "选择输出文件");
if (!file.isEmpty()) {
m_dstFileEdit->setText(file);
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_recursionMap.remove(m_funcList.at(row));
m_funcList.removeAt(row);
}
updateTable();
if (saveToCustNonLsJson(m_funcList)) {
loadFromCustNonLsJson();
}
}
void NonLsConvertPage::onClearTable()
{
if (m_funcList.isEmpty()) {
return;
}
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, "确认清空", "确定要清空所有函数吗?",
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
m_funcList.clear();
m_recursionMap.clear();
updateTable();
if (saveToCustNonLsJson(m_funcList)) {
loadFromCustNonLsJson();
}
}
}
bool NonLsConvertPage::saveToCustNonLsJson(const QStringList &funcList)
{
const QString jsonPath = Constants::custNonLsJson();
// 保留既有顶层节点,仅更新 convert 节点
QJsonObject root;
{
QFile file(jsonPath);
if (file.exists() && file.open(QIODevice::ReadOnly)) {
QByteArray data = file.readAll();
file.close();
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
if (parseError.error == QJsonParseError::NoError && doc.isObject()) {
root = doc.object();
} else {
LogManager::instance()->logWarning(
QString("按需转码 - 保存前读取既有配置失败(%1),将按当前清单重建: %2")
.arg(parseError.errorString()).arg(jsonPath));
}
}
}
// convert.dir 键值表:{功能名: 0|-1};0=否(不递归)、-1=是(递归),并保留/兜底引擎默认键 "*"
const QJsonObject oldDir = root.value("convert").toObject().value("dir").toObject();
QJsonObject dirObj;
for (const QString &name : funcList) {
dirObj.insert(name, m_recursionMap.value(name, RecursionNo));
}
if (oldDir.contains(WildcardDefaultKey)) {
dirObj.insert(WildcardDefaultKey, oldDir.value(WildcardDefaultKey));
} else {
dirObj.insert(WildcardDefaultKey, WildcardDefaultValue);
}
QJsonArray extArray;
for (const QString &ext : {QStringLiteral(".service_design"),
QStringLiteral(".function_design"),
QStringLiteral(".aservice_design"),
QStringLiteral(".procedure_design"),
QStringLiteral(".afunction_design")}) {
extArray.append(ext);
}
QJsonObject convertObj;
convertObj["dir"] = dirObj;
convertObj["ext"] = extArray;
root["convert"] = convertObj;
QFile file(jsonPath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
LogManager::instance()->logWarning(
QString("按需转码 - 保存配置失败,无法写入: %1").arg(jsonPath));
return false;
}
QJsonDocument doc(root);
file.write(doc.toJson(QJsonDocument::Indented));
file.close();
// 程序自身写入后更新 mtime,避免触发无谓的重载
m_lastFileMtime = currentFileMtime();
return true;
}
void NonLsConvertPage::loadFromCustNonLsJson()
{
const QString jsonPath = Constants::custNonLsJson();
if (!QFile::exists(jsonPath)) {
return; // 文件尚不存在(首次使用),保持空清单,不打扰
}
QFile file(jsonPath);
if (!file.open(QIODevice::ReadOnly)) {
LogManager::instance()->logWarning(
QString("按需转码 - 加载配置失败,无法打开: %1").arg(jsonPath));
return;
}
QByteArray data = file.readAll();
file.close();
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError) {
LogManager::instance()->logWarning(
QString("按需转码 - 加载配置 JSON 解析失败: %1").arg(parseError.errorString()));
return;
}
QJsonObject root = doc.object();
const QJsonValue dirVal = root["convert"].toObject()["dir"];
QStringList items;
m_recursionMap.clear();
if (dirVal.isObject()) {
// 新版:dir 为 {功能名: 0|-1},0=否(不递归)、-1=是(递归);"*" 为引擎默认键,不展示
const QJsonObject dirObj = dirVal.toObject();
for (auto it = dirObj.constBegin(); it != dirObj.constEnd(); ++it) {
if (it.key() == WildcardDefaultKey) {
continue;
}
const QString item = normalizeNonLsName(it.key());
if (item.isEmpty()) {
continue;
}
items.append(item);
const int v = it.value().toInt(RecursionNo);
m_recursionMap.insert(item, (v == RecursionYes) ? RecursionYes : RecursionNo);
}
} else {
// 兼容旧版:dir 为 ["功能名", ...],无递归标志,一律按“否”加载
for (const QJsonValue &val : dirVal.toArray()) {
const QString item = normalizeNonLsName(val.toString());
if (!item.isEmpty()) {
items.append(item);
m_recursionMap.insert(item, RecursionNo);
}
}
}
m_funcList = items;
updateTable();
}
void NonLsConvertPage::onStartConvert()
{
m_resultEdit->append("开始转换...");
m_resultEdit->append("源文件: " + m_srcFileEdit->text());
m_resultEdit->append("输出文件: " + m_dstFileEdit->text());
m_resultEdit->append("转换完成!");
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 (!saveToCustNonLsJson(m_funcList)) {
m_progressLabel->setText("配置保存失败");
return;
}
bool ok = false;
m_preparedArgs = buildArguments(&ok);
if (!ok) {
m_progressLabel->setText("参数生成失败");
return;
}
m_progressBar->setValue(20);
m_progressLabel->setText("配置加载成功,开始转换...");
emit startConvert();
}
void NonLsConvertPage::onPreview()
void NonLsConvertPage::onConvertStarted()
{
m_resultEdit->append("预览模式...");
m_resultEdit->append("源文件: " + m_srcFileEdit->text());
m_resultEdit->append("预览完成!");
}
m_startBtn->setText("停止转换");
m_startBtn->setStyleSheet("background-color: #e74c3c; color: white; padding: 10px 30px; font-size: 14px; border: none; border-radius: 4px;");
}
void NonLsConvertPage::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 NonLsConvertPage::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 NonLsConvertPage::addFunction(const QString &funcName)
{
if (funcName.isEmpty()) {
return;
}
const QString name = normalizeNonLsName(funcName);
if (m_funcList.contains(name)) {
QMessageBox::information(nullptr, "提示", QString("功能 [%1] 已在列表中").arg(name));
return;
}
m_funcList.append(name);
m_recursionMap.insert(name, RecursionNo); // 新增功能默认“否”
updateTable();
if (saveToCustNonLsJson(m_funcList)) {
QMessageBox::information(nullptr, "提示", QString("已将功能 [%1] 添加到\"按需转码\"").arg(name));
} else {
QMessageBox::warning(nullptr, "警告", "添加到\"按需转码\"成功,但保存到 cust_nonls.json 失败");
}
}
QStringList NonLsConvertPage::buildArguments(bool *ok)
{
if (ok) {
*ok = true;
}
// 引擎契约(新版):仅携带 -m 2 表示“按需转码”模式,
// 函数清单与“是否递归”开关由引擎直接读取 config/cust_nonls.json,无需再传 -t/-o 参数
return QStringList() << "-m" << "2";
}
void NonLsConvertPage::setupConfigWatcher()
{
const QString configPath = getConfigFilePath();
m_configWatcher = new QFileSystemWatcher(this);
if (QFile::exists(configPath)) {
m_configWatcher->addPath(configPath);
}
const QString dir = QFileInfo(configPath).absolutePath();
if (QDir(dir).exists()) {
m_configWatcher->addPath(dir);
}
connect(m_configWatcher, &QFileSystemWatcher::fileChanged,
this, &NonLsConvertPage::onConfigFileChanged);
connect(m_configWatcher, &QFileSystemWatcher::directoryChanged,
this, &NonLsConvertPage::onConfigDirChanged);
}
QDateTime NonLsConvertPage::currentFileMtime() const
{
QFileInfo info(getConfigFilePath());
return info.exists() ? info.lastModified() : QDateTime();
}
void NonLsConvertPage::onConfigFileChanged(const QString &path)
{
if (m_configWatcher && !m_configWatcher->files().contains(path)
&& QFile::exists(path)) {
m_configWatcher->addPath(path);
}
m_reloadTimer->start(); // 防抖
}
void NonLsConvertPage::onConfigDirChanged(const QString &dirPath)
{
Q_UNUSED(dirPath);
const QString configPath = getConfigFilePath();
if (m_configWatcher && QFile::exists(configPath)
&& !m_configWatcher->files().contains(configPath)) {
m_configWatcher->addPath(configPath);
}
m_reloadTimer->start(); // 防抖
}
void NonLsConvertPage::onReloadTimeout()
{
QDateTime now = currentFileMtime();
if (now.isValid() && now != m_lastFileMtime) {
m_lastFileMtime = now;
loadFromCustNonLsJson();
}
}
QString NonLsConvertPage::getConfigFilePath() const
{
return Constants::custNonLsJson();
}
+63 -9
View File
@@ -2,9 +2,23 @@
#define NONLSCONVERTPAGE_H
#include <QWidget>
#include <QLineEdit>
#include <QTextEdit>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QHash>
#include <QPushButton>
#include <QFileSystemWatcher>
#include <QTimer>
#include <QDateTime>
#include "ElaTableView.h"
#include "ElaProgressBar.h"
#include "ElaText.h"
// 按需转码工作台(业务转码页 Pivot2)
// 交互与全量转码页(BatchConvertPage)保持一致:
// 函数清单(短名)与“是否递归”开关 -> 持久化到 config/cust_nonls.json
// convert.dir 为 {功能名: 0| -1} 键值表:0=否(不递归)、-1=是(递归),另含引擎默认键 "*"
// 调用契约:仅携带 -m 2(按需转码模式);函数清单与递归开关由引擎直接读取 cust_nonls.json,
// 不再需要 -t/-o 等命令行参数
class NonLsConvertPage : public QWidget
{
Q_OBJECT
@@ -13,18 +27,58 @@ public:
explicit NonLsConvertPage(QWidget *parent = nullptr);
~NonLsConvertPage();
signals:
void startConvert();
void stopConvert();
public slots:
void onProgressUpdate(int progress, const QString &status);
void onConvertStarted();
void onConvertFinished();
void addFunction(const QString &funcName);
// 另一工作台任务运行时由 MainWindow 调用,禁用本页开始按钮
void setTaskBusy(bool busy);
int getFunctionCount() const { return m_funcList.size(); }
QStringList getFunctionList() const { return m_funcList; }
// 本次开始转换前已拼装好的引擎参数(由 onStartConvert 生成)
QStringList preparedArguments() const { return m_preparedArgs; }
private slots:
void onBrowseSrcFile();
void onBrowseDstFile();
void onStartConvert();
void onPreview();
void onAddFunction();
void onRemoveFunction();
void onClearTable();
void onEditRow(int row);
void onConfigFileChanged(const QString &path);
void onConfigDirChanged(const QString &path);
void onReloadTimeout();
private:
void initUI();
bool saveToCustNonLsJson(const QStringList &funcList);
void loadFromCustNonLsJson();
bool checkFunctionExists(const QString &funcName);
void updateTable();
void resizeEvent(QResizeEvent *event) override;
void showEvent(QShowEvent *event) override;
QString getConfigFilePath() const;
void setupConfigWatcher();
QDateTime currentFileMtime() const;
QStringList buildArguments(bool *ok);
QLineEdit *m_srcFileEdit;
QLineEdit *m_dstFileEdit;
QTextEdit *m_resultEdit;
ElaTableView *m_funcTable;
QStandardItemModel *m_tableModel;
QStringList m_funcList;
// 每个清单功能对应的“是否递归”标志:0=否、-1=是(与 cust_nonls.json convert.dir 值一致)
QHash<QString, int> m_recursionMap;
ElaProgressBar *m_progressBar;
ElaText *m_progressLabel;
QPushButton *m_startBtn;
QFileSystemWatcher *m_configWatcher;
QTimer *m_reloadTimer;
QDateTime m_lastFileMtime;
QStringList m_preparedArgs;
};
#endif // NONLSCONVERTPAGE_H
#endif // NONLSCONVERTPAGE_H