新增转码模式支持转到核心以及sql语法转换
This commit is contained in:
@@ -10,33 +10,59 @@
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
#include <QJsonParseError>
|
||||
#include <QRegularExpression>
|
||||
#include <QMessageBox>
|
||||
#include <QRegularExpression>
|
||||
#include <QLabel>
|
||||
#include <QStandardItem>
|
||||
#include <cstring>
|
||||
#include "ElaComboBox.h"
|
||||
#include "ElaTheme.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// V2 默认值(契约 docs/转码配置格式契约.md): core 默认 ucbp, 柜面接口默认否;
|
||||
// dialect 不落 UI, 保存时由 core 派生(ucbp/uses→mdb, uarg/usms→db)
|
||||
|
||||
bool validateLsFunctionNames(QWidget *parent, const QStringList &functionNames)
|
||||
{
|
||||
QStringList nonLsNames;
|
||||
// 只允许 LS_ 开头的功能进入全量转码清单
|
||||
QStringList invalidNames;
|
||||
for (const QString &name : functionNames) {
|
||||
const QString trimmedName = name.trimmed();
|
||||
if (!trimmedName.isEmpty()
|
||||
&& !trimmedName.startsWith(Constants::FuncName::LsPrefix)
|
||||
&& !nonLsNames.contains(trimmedName)) {
|
||||
nonLsNames.append(trimmedName);
|
||||
if (!name.startsWith("LS_")) {
|
||||
invalidNames.append(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (nonLsNames.isEmpty()) {
|
||||
return true;
|
||||
if (!invalidNames.isEmpty()) {
|
||||
QMessageBox::warning(
|
||||
parent,
|
||||
QStringLiteral("全量转码"),
|
||||
QStringLiteral("以下功能不是 LS_ 开头,无法加入全量转码清单:\n")
|
||||
+ invalidNames.join("\n"),
|
||||
QMessageBox::Ok);
|
||||
LogManager::instance()->logWarning(
|
||||
QString("全量转码 - 存在非 LS_ 前缀功能名被拦截: %1")
|
||||
.arg(invalidNames.join(", ")));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QMessageBox::warning(parent, "提示",
|
||||
QString("\"全量转码\"仅支持 LS_ 开头的功能,以下功能无法加入:\n%1\n\n非LS功能请切到\"按需转码\"工作台添加。")
|
||||
.arg(nonLsNames.join('\n')));
|
||||
return false;
|
||||
// 清单表格列定义
|
||||
enum TableCol {
|
||||
ColName = 0, // UF20函数名
|
||||
ColExists = 1, // 是否存在
|
||||
ColCore = 2, // 核心(ucbp/uses/uarg/usms)
|
||||
ColViaUmgr = 3 // 是否柜面接口
|
||||
};
|
||||
|
||||
// 核心取值表(顺序即下拉顺序, 首项为默认值)
|
||||
const QStringList kCoreList = {Constants::DirEntry::CoreUcbp, Constants::DirEntry::CoreUses,
|
||||
Constants::DirEntry::CoreUarg, Constants::DirEntry::CoreUsms};
|
||||
|
||||
int coreIndexOf(const QString &core)
|
||||
{
|
||||
const int index = kCoreList.indexOf(core);
|
||||
return index >= 0 ? index : 0; // 未知核心回退默认首项(ucbp)
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -48,139 +74,300 @@ BatchConvertPage::BatchConvertPage(QWidget *parent)
|
||||
initialize();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 界面骨架
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void BatchConvertPage::applyPageLayout(QVBoxLayout *layout)
|
||||
{
|
||||
layout->setContentsMargins(30, 30, 30, 30);
|
||||
layout->setSpacing(20);
|
||||
// 页首说明条: 无"操作"列, 双击函数名所在行即可修改函数名
|
||||
m_tipLabel = new QLabel(
|
||||
"<b>说明:</b>双击函数名所在行,可在弹窗中修改函数名。");
|
||||
m_tipLabel->setTextFormat(Qt::RichText);
|
||||
m_tipLabel->setWordWrap(true);
|
||||
layout->addWidget(m_tipLabel);
|
||||
}
|
||||
|
||||
void BatchConvertPage::applyTipStyle(const QString &textColor)
|
||||
{
|
||||
if (m_tipLabel) {
|
||||
m_tipLabel->setStyleSheet(QString("color: %1; font-size: 12px;").arg(textColor));
|
||||
}
|
||||
}
|
||||
|
||||
int BatchConvertPage::tableColumnCount() const
|
||||
{
|
||||
return 3;
|
||||
return 4;
|
||||
}
|
||||
|
||||
QStringList BatchConvertPage::tableHeaderLabels() const
|
||||
{
|
||||
return {"UF20函数名", "是否存在", "操作"};
|
||||
return {QStringLiteral("UF20函数名"), QStringLiteral("是否存在"),
|
||||
QStringLiteral("核心"), QStringLiteral("是否柜面接口")};
|
||||
}
|
||||
|
||||
void BatchConvertPage::applyColumnLayout()
|
||||
{
|
||||
// 4 列按 8:2:2:2 比例分摊、恰好铺满表格:
|
||||
// "核心"/"是否柜面接口"各占 2 份, 保证四/六字表头与行内下拉完整显示不截断;
|
||||
// 取整余数并入函数名列, 确保总宽精确铺满、无横向滚动条
|
||||
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);
|
||||
const int unit = totalWidth / 14;
|
||||
m_funcTable->setColumnWidth(ColName, totalWidth - unit * 6);
|
||||
m_funcTable->setColumnWidth(ColExists, unit * 2);
|
||||
m_funcTable->setColumnWidth(ColCore, unit * 2);
|
||||
m_funcTable->setColumnWidth(ColViaUmgr, unit * 2);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 名称校验
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool BatchConvertPage::validateFunctionNames(const QStringList &names)
|
||||
{
|
||||
return validateLsFunctionNames(this, names);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 表格行: 核心/柜面接口两列
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void BatchConvertPage::attachRowItems(QList<QStandardItem *> &rowItems, const QString &funcName)
|
||||
{
|
||||
Q_UNUSED(funcName)
|
||||
// "核心"/"是否柜面接口"以行内下拉呈现, 占位列仅承载 setIndexWidget 控件
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
QStandardItem *item = new QStandardItem("");
|
||||
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
|
||||
item->setTextAlignment(Qt::AlignCenter);
|
||||
rowItems.append(item);
|
||||
}
|
||||
}
|
||||
|
||||
void BatchConvertPage::attachRowWidgets(int row, const QString &funcName)
|
||||
{
|
||||
// "核心"下拉: ucbp/uses/uarg/usms, 切换即写回并持久化(dialect 由 core 派生, 保存时写入)
|
||||
ElaComboBox *coreCombo = new ElaComboBox;
|
||||
for (const QString &core : kCoreList) {
|
||||
coreCombo->addItem(core);
|
||||
}
|
||||
const QString curCore = m_coreMap.value(funcName, Constants::DirEntry::CoreUcbp);
|
||||
coreCombo->setCurrentIndex(coreIndexOf(curCore));
|
||||
connect(coreCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
[this, funcName, coreCombo](int index) {
|
||||
const QString core = kCoreList.value(index, Constants::DirEntry::CoreUcbp);
|
||||
const QString prev = m_coreMap.value(funcName, Constants::DirEntry::CoreUcbp);
|
||||
if (prev == core) {
|
||||
return;
|
||||
}
|
||||
m_coreMap.insert(funcName, core);
|
||||
// 值变化才落盘, 不重建表格; 失败回滚内存与下拉选中项
|
||||
// (否则界面显示新值而磁盘仍是旧值, 重启后配置与界面矛盾)
|
||||
if (!saveConfig(m_funcList)) {
|
||||
m_coreMap.insert(funcName, prev);
|
||||
coreCombo->blockSignals(true);
|
||||
coreCombo->setCurrentIndex(coreIndexOf(prev));
|
||||
coreCombo->blockSignals(false);
|
||||
}
|
||||
});
|
||||
m_funcTable->setIndexWidget(m_tableModel->index(row, ColCore), coreCombo);
|
||||
|
||||
// "是否柜面接口"下拉: 否/是(与 viaUmgr 布尔一致), 交互与"核心"列一致
|
||||
ElaComboBox *umgrCombo = new ElaComboBox;
|
||||
umgrCombo->addItem(QStringLiteral("否"));
|
||||
umgrCombo->addItem(QStringLiteral("是"));
|
||||
umgrCombo->setCurrentIndex(m_viaUmgrMap.value(funcName, false) ? 1 : 0);
|
||||
connect(umgrCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
[this, funcName, umgrCombo](int index) {
|
||||
const bool viaUmgr = (index == 1);
|
||||
const bool prev = m_viaUmgrMap.value(funcName, false);
|
||||
if (prev == viaUmgr) {
|
||||
return;
|
||||
}
|
||||
m_viaUmgrMap.insert(funcName, viaUmgr);
|
||||
if (!saveConfig(m_funcList)) {
|
||||
m_viaUmgrMap.insert(funcName, prev);
|
||||
umgrCombo->blockSignals(true);
|
||||
umgrCombo->setCurrentIndex(prev ? 1 : 0);
|
||||
umgrCombo->blockSignals(false);
|
||||
}
|
||||
});
|
||||
m_funcTable->setIndexWidget(m_tableModel->index(row, ColViaUmgr), umgrCombo);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 附加属性映射维护
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void BatchConvertPage::onFunctionAdded(const QString &name)
|
||||
{
|
||||
m_coreMap.insert(name, Constants::DirEntry::CoreUcbp); // 新增功能默认 ucbp
|
||||
m_viaUmgrMap.insert(name, false); // 新增功能默认非柜面接口
|
||||
}
|
||||
|
||||
void BatchConvertPage::onFunctionsRemoved(const QStringList &names)
|
||||
{
|
||||
for (const QString &name : names) {
|
||||
m_coreMap.remove(name);
|
||||
m_viaUmgrMap.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
void BatchConvertPage::onFunctionsRenamed(const QString &oldName, const QString &newName)
|
||||
{
|
||||
const QString core = m_coreMap.value(oldName, Constants::DirEntry::CoreUcbp);
|
||||
const bool viaUmgr = m_viaUmgrMap.value(oldName, false);
|
||||
m_coreMap.remove(oldName);
|
||||
m_viaUmgrMap.remove(oldName);
|
||||
m_coreMap.insert(newName, core);
|
||||
m_viaUmgrMap.insert(newName, viaUmgr);
|
||||
}
|
||||
|
||||
void BatchConvertPage::onFunctionListCleared()
|
||||
{
|
||||
m_coreMap.clear();
|
||||
m_viaUmgrMap.clear();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 持久化: cust.json
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool BatchConvertPage::saveConfig(const QStringList &funcList)
|
||||
{
|
||||
QString jsonPath = Constants::custJson();
|
||||
|
||||
// 读取原始文件内容(保留全部格式不重序列化)
|
||||
QFile file(jsonPath);
|
||||
// 1. 读原文件并解析
|
||||
QFile file(configFilePath());
|
||||
QByteArray original;
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
LogManager::instance()->logWarning(
|
||||
QString("全量转码 - 保存配置失败,无法打开: %1").arg(jsonPath));
|
||||
LogManager::instance()->logError(
|
||||
QString("全量转码 - 保存配置失败,无法打开原文件: %1").arg(configFilePath()));
|
||||
return false;
|
||||
}
|
||||
|
||||
QString content = QString::fromUtf8(file.readAll());
|
||||
original = file.readAll();
|
||||
file.close();
|
||||
|
||||
// 用正则定位 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 节点");
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(original, &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
LogManager::instance()->logError(
|
||||
QString("全量转码 - 保存配置失败,原文件 JSON 解析失败: %1")
|
||||
.arg(parseError.errorString()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 在 convert 之后定位 "dir" 数组起始 [
|
||||
static QRegularExpression dirRe(R"("dir"\s*:\s*\[)");
|
||||
auto dirMatch = dirRe.match(content, convertMatch.capturedEnd());
|
||||
// 2. 定位 convert.dir 的 [ ] 区间
|
||||
QString content = QString::fromUtf8(original);
|
||||
QRegularExpression dirRe(
|
||||
QStringLiteral("\"%1\"\\s*:\\s*\\[").arg(Constants::CustKeys::ConvertDirSub));
|
||||
QRegularExpressionMatch dirMatch = dirRe.match(content);
|
||||
if (!dirMatch.hasMatch()) {
|
||||
LogManager::instance()->logWarning("全量转码 - 保存配置失败,未找到 dir 数组");
|
||||
LogManager::instance()->logError(
|
||||
QString("全量转码 - 保存配置失败,未找到 convert.%1 节点").arg(
|
||||
Constants::CustKeys::ConvertDirSub));
|
||||
return false;
|
||||
}
|
||||
|
||||
int bracketStart = dirMatch.capturedEnd() - 1; // 指向 '['
|
||||
|
||||
// 括号计数找到匹配的 ']'(必须跳过字符串字面量, 否则字符串内的 [ ] 会截断匹配改坏 JSON)
|
||||
int bracketCount = 1;
|
||||
int pos = bracketStart + 1;
|
||||
const int bracketStart = dirMatch.capturedEnd() - 1; // '[' 位置
|
||||
int bracketEnd = -1;
|
||||
int depth = 0;
|
||||
bool inString = false;
|
||||
bool escaped = false;
|
||||
while (pos < content.length() && bracketCount > 0) {
|
||||
QChar ch = content[pos];
|
||||
for (int i = bracketStart; i < content.size(); ++i) {
|
||||
const QChar c = content[i];
|
||||
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--;
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
} else if (c == '\\') {
|
||||
escaped = true;
|
||||
} else if (c == '"') {
|
||||
inString = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (c == '"') {
|
||||
inString = true;
|
||||
} else if (c == '[') {
|
||||
++depth;
|
||||
} else if (c == ']') {
|
||||
--depth;
|
||||
if (depth == 0) {
|
||||
bracketEnd = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
if (bracketCount != 0) {
|
||||
LogManager::instance()->logWarning("全量转码 - 保存配置失败,dir 数组括号不匹配");
|
||||
if (bracketEnd < 0) {
|
||||
LogManager::instance()->logError(
|
||||
QString("全量转码 - 保存配置失败,convert.%1 数组括号不匹配").arg(
|
||||
Constants::CustKeys::ConvertDirSub));
|
||||
return false;
|
||||
}
|
||||
int bracketEnd = pos; // 指向 ']' 之后一位
|
||||
|
||||
// 构建新数组(保持原始缩进风格:项 12 空格,闭合 ] 8 空格)
|
||||
// 3. 构建新数组(V2 对象格式, dialect 由 core 派生)
|
||||
// 紧凑格式: 每个功能一行对象(与需求示例一致), 避免逐字段展开占用大量缩进
|
||||
QString newArray = "[";
|
||||
for (int i = 0; i < funcList.size(); ++i) {
|
||||
if (i > 0) newArray += ",";
|
||||
newArray += "\n \"" + CustJsonIo::escapeJsonString(funcList[i]) + Constants::DesignExts::Service + "\"";
|
||||
const QString &name = funcList[i];
|
||||
const QString core = m_coreMap.value(name, Constants::DirEntry::CoreUcbp);
|
||||
const bool viaUmgr = m_viaUmgrMap.value(name, false);
|
||||
const QString dialect = Constants::DirEntry::dialectForCore(core);
|
||||
if (i > 0) {
|
||||
newArray += ",";
|
||||
}
|
||||
newArray += "\n { \"" + Constants::DirEntry::Name + "\": \""
|
||||
+ CustJsonIo::escapeJsonString(name + Constants::DesignExts::Service)
|
||||
+ "\", \"" + Constants::DirEntry::Core + "\": \"" + core
|
||||
+ "\", \"" + Constants::DirEntry::ViaUmgr + "\": ";
|
||||
newArray += viaUmgr ? "true" : "false";
|
||||
newArray += ", \"" + Constants::DirEntry::Dialect + "\": \"" + dialect + "\" }";
|
||||
}
|
||||
if (!funcList.isEmpty()) {
|
||||
newArray += "\n ";
|
||||
}
|
||||
newArray += "]";
|
||||
|
||||
// 只替换 [bracketStart, bracketEnd) 区间,其余内容原样保留
|
||||
content.replace(bracketStart, bracketEnd - bracketStart, newArray);
|
||||
// 换行风格跟随原文件: 原文件为 CRLF 时, 新数组文本的 \n 同步为 \r\n。
|
||||
// 写出必须用二进制模式(不开 QIODevice::Text)——Text 模式会把 \n 再转成 \r\n,
|
||||
// 而保留区域(fromUtf8 原样保留)已含 \r\n, 每保存一次就多累积一个 \r,
|
||||
// 最终文件里出现成串回车符(表现为编辑器中大量空白)。
|
||||
if (original.contains("\r\n")) {
|
||||
newArray.replace("\n", "\r\n");
|
||||
}
|
||||
|
||||
// 写回前校验替换结果仍是合法 JSON, 防止改坏的配置被原地写回无法恢复
|
||||
QJsonParseError parseErr;
|
||||
QJsonDocument::fromJson(content.toUtf8(), &parseErr);
|
||||
if (parseErr.error != QJsonParseError::NoError) {
|
||||
LogManager::instance()->logWarning(
|
||||
QString("全量转码 - 保存配置中止, 替换后 JSON 非法: %1").arg(parseErr.errorString()));
|
||||
// 4. 拼装并做整体 JSON 合法性校验
|
||||
QString newContent = content.left(bracketStart) + newArray + content.mid(bracketEnd + 1);
|
||||
QJsonDocument newDoc = QJsonDocument::fromJson(newContent.toUtf8(), &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
LogManager::instance()->logError(
|
||||
QString("全量转码 - 保存配置失败,新文件 JSON 校验未通过: %1")
|
||||
.arg(parseError.errorString()));
|
||||
return false;
|
||||
}
|
||||
QJsonObject newRoot = newDoc.object();
|
||||
const QJsonArray verifyDir =
|
||||
newRoot[Constants::CustKeys::ConvertDir].toObject()[Constants::CustKeys::ConvertDirSub].toArray();
|
||||
if (verifyDir.size() != funcList.size()) {
|
||||
LogManager::instance()->logError(
|
||||
QString("全量转码 - 保存配置失败,新文件数组元素数(%1)与清单(%2)不一致")
|
||||
.arg(verifyDir.size())
|
||||
.arg(funcList.size()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// QSaveFile 原子写: 先写临时文件, commit 成功才替换目标文件,
|
||||
// 避免写入中途崩溃/断电损坏配置。
|
||||
// 保存窗口: 写入前开启, 窗口期内 QSaveFile 替换目标文件引发的
|
||||
// fileChanged/directoryChanged 监控事件直接忽略, 防止自身保存触发重载;
|
||||
// 失败路径(打开/提交失败)由组件 2s 兜底自动关窗
|
||||
m_configMonitor->beginSaveWindow();
|
||||
QSaveFile saveFile(jsonPath);
|
||||
if (!saveFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
LogManager::instance()->logWarning(
|
||||
QString("全量转码 - 保存配置失败,无法写入: %1").arg(jsonPath));
|
||||
// 5. 原子写回(二进制模式: 内容字节原样落盘, 不做任何换行转换)
|
||||
QSaveFile saveFile(configFilePath());
|
||||
if (!saveFile.open(QIODevice::WriteOnly)) {
|
||||
LogManager::instance()->logError(
|
||||
QString("全量转码 - 保存配置失败,无法写入: %1").arg(configFilePath()));
|
||||
return false;
|
||||
}
|
||||
|
||||
saveFile.write(content.toUtf8());
|
||||
saveFile.write(newContent.toUtf8());
|
||||
if (!saveFile.commit()) {
|
||||
LogManager::instance()->logWarning(
|
||||
QString("全量转码 - 保存配置失败,写入提交异常: %1").arg(jsonPath));
|
||||
LogManager::instance()->logError(
|
||||
QString("全量转码 - 保存配置失败,写入提交失败: %1").arg(configFilePath()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 程序自身写入后更新监控 mtime 基准, 避免触发无谓的重载
|
||||
m_configMonitor->endSave();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -207,15 +394,36 @@ void BatchConvertPage::loadConfig()
|
||||
}
|
||||
|
||||
QJsonObject root = doc.object();
|
||||
QJsonArray dir = root[Constants::CustKeys::ConvertDir].toObject()[Constants::CustKeys::ConvertDirSub].toArray();
|
||||
const QJsonArray dir = root[Constants::CustKeys::ConvertDir].toObject()[Constants::CustKeys::ConvertDirSub].toArray();
|
||||
|
||||
QStringList items;
|
||||
m_coreMap.clear();
|
||||
m_viaUmgrMap.clear();
|
||||
for (const QJsonValue &val : dir) {
|
||||
QString item = val.toString();
|
||||
// V1 字符串元素 / V2 对象元素双格式兼容: 缺失字段按契约默认值兜底
|
||||
QString item;
|
||||
QString core = Constants::DirEntry::CoreUcbp;
|
||||
bool viaUmgr = false;
|
||||
if (val.isObject()) {
|
||||
const QJsonObject entry = val.toObject();
|
||||
item = entry.value(Constants::DirEntry::Name).toString();
|
||||
const QString c = entry.value(Constants::DirEntry::Core).toString();
|
||||
if (kCoreList.contains(c)) {
|
||||
core = c;
|
||||
}
|
||||
viaUmgr = entry.value(Constants::DirEntry::ViaUmgr).toBool(false);
|
||||
} else {
|
||||
item = val.toString();
|
||||
}
|
||||
if (item.endsWith(Constants::DesignExts::Service)) {
|
||||
item.chop(Constants::DesignExts::Service.size());
|
||||
}
|
||||
if (item.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
items.append(item);
|
||||
m_coreMap.insert(item, core);
|
||||
m_viaUmgrMap.insert(item, viaUmgr);
|
||||
}
|
||||
|
||||
m_funcList = items;
|
||||
@@ -229,20 +437,20 @@ QString BatchConvertPage::configFilePath() const
|
||||
|
||||
QString BatchConvertPage::workbenchTitle() const
|
||||
{
|
||||
return "全量转码";
|
||||
return QStringLiteral("全量转码");
|
||||
}
|
||||
|
||||
QString BatchConvertPage::configDisplayName() const
|
||||
{
|
||||
return "cust.json";
|
||||
return QStringLiteral("cust.json");
|
||||
}
|
||||
|
||||
QString BatchConvertPage::addDialogTitle() const
|
||||
{
|
||||
return "添加UF20函数";
|
||||
return QStringLiteral("添加功能");
|
||||
}
|
||||
|
||||
QString BatchConvertPage::addDialogPlaceholder() const
|
||||
{
|
||||
return "每行输入一个函数名";
|
||||
return QStringLiteral("输入 LS_ 开头的功能名,如 LS_证券参数_股票名单增加");
|
||||
}
|
||||
|
||||
@@ -2,13 +2,19 @@
|
||||
#define BATCHCONVERTPAGE_H
|
||||
|
||||
#include "src/pages/common/convertworkbenchbase.h"
|
||||
#include <QHash>
|
||||
|
||||
class QVBoxLayout;
|
||||
class QLabel;
|
||||
class QStandardItem;
|
||||
|
||||
// 全量转码工作台(业务转码页 LS pivot,仅支持 LS_ 开头的功能)
|
||||
// 函数清单(短名)持久化到 config/cust.json,convert.dir 为
|
||||
// ["功能名.service_design", ...] 数组,保存时对原文件做文本级区间替换
|
||||
// (保留 cust.json 其余全部格式不重序列化)
|
||||
// 函数清单(短名)持久化到 config/cust.json,convert.dir 为对象数组
|
||||
// [{name, core, viaUmgr, dialect}, ...](V2 格式,兼容读旧字符串数组 V1),
|
||||
// 保存时对原文件做文本级区间替换(保留 cust.json 其余全部格式不重序列化)。
|
||||
// core 决定目标核心(ucbp/uses/uarg/usms);dialect 由 core 派生、不提供独立 UI
|
||||
// (ucbp/uses→mdb,uarg/usms→db);viaUmgr 为是否柜面接口。
|
||||
// 格式契约见 docs/转码配置格式契约.md
|
||||
class BatchConvertPage : public ConvertWorkbenchBase
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -19,6 +25,7 @@ public:
|
||||
protected:
|
||||
// 界面骨架钩子
|
||||
void applyPageLayout(QVBoxLayout *layout) override;
|
||||
void applyTipStyle(const QString &textColor) override;
|
||||
int tableColumnCount() const override;
|
||||
QStringList tableHeaderLabels() const override;
|
||||
void applyColumnLayout() override;
|
||||
@@ -26,6 +33,16 @@ protected:
|
||||
// 名称校验钩子: 非 LS_ 前缀弹窗拦截
|
||||
bool validateFunctionNames(const QStringList &names) override;
|
||||
|
||||
// 表格行钩子: 核心/柜面接口两列行内下拉
|
||||
void attachRowItems(QList<QStandardItem *> &rowItems, const QString &funcName) override;
|
||||
void attachRowWidgets(int row, const QString &funcName) override;
|
||||
|
||||
// 附加属性映射维护钩子(新增默认值/删除清理/改名随迁)
|
||||
void onFunctionAdded(const QString &name) override;
|
||||
void onFunctionsRemoved(const QStringList &names) override;
|
||||
void onFunctionsRenamed(const QString &oldName, const QString &newName) override;
|
||||
void onFunctionListCleared() override;
|
||||
|
||||
// 持久化钩子
|
||||
bool saveConfig(const QStringList &funcList) override;
|
||||
void loadConfig() override;
|
||||
@@ -34,6 +51,12 @@ protected:
|
||||
QString configDisplayName() const override;
|
||||
QString addDialogTitle() const override;
|
||||
QString addDialogPlaceholder() const override;
|
||||
|
||||
private:
|
||||
QLabel *m_tipLabel = nullptr; // 页首灰字说明条(需主题联动)
|
||||
// 每个清单功能的核心/柜面接口属性(与 cust.json convert.dir 条目字段一致)
|
||||
QHash<QString, QString> m_coreMap; // 功能名 → "ucbp"/"uses"/"uarg"/"usms"
|
||||
QHash<QString, bool> m_viaUmgrMap; // 功能名 → 是否柜面接口
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif // BATCHCONVERTPAGE_H
|
||||
|
||||
@@ -62,7 +62,11 @@ namespace {
|
||||
}
|
||||
|
||||
for (const QJsonValue &v : dirArray) {
|
||||
QString name = v.toString();
|
||||
// V1 字符串元素 / V2 对象元素(取 name 字段)双格式兼容
|
||||
// (全量转码 convert.dir 格式升级, 见 docs/转码配置格式契约.md)
|
||||
QString name = v.isObject()
|
||||
? v.toObject().value(Constants::DirEntry::Name).toString()
|
||||
: v.toString();
|
||||
for (const QString &ext : extList) {
|
||||
if (name.endsWith(ext)) {
|
||||
name.chop(ext.length());
|
||||
|
||||
@@ -22,9 +22,8 @@
|
||||
|
||||
namespace {
|
||||
|
||||
// "是否存在"列(两工作台一致)与操作列固定宽度
|
||||
// "是否存在"列(两工作台一致)
|
||||
constexpr int ExistsColumn = 1;
|
||||
constexpr int ActionColumnWidth = 80;
|
||||
// 开始/停止按钮样式: 状态切换仅换底色, 其余属性一致; 常量化防多处字面量漂移
|
||||
constexpr const char *kStartBtnStyle =
|
||||
"background-color: #1abc9c; color: white; padding: 10px 30px; font-size: 14px; border: none; border-radius: 4px;";
|
||||
@@ -129,6 +128,21 @@ void ConvertWorkbenchBase::initUI()
|
||||
}
|
||||
m_funcTable->horizontalHeader()->setStretchLastSection(false);
|
||||
m_funcTable->setItemDelegateForColumn(ExistsColumn, new StatusDelegate(this));
|
||||
|
||||
// 双击函数名所在行触发编辑(替代原"操作"列编辑按钮)。
|
||||
// 两段式投递: 直连段在信号发出瞬间(行号仍有效)按行取出函数名(稳定键),
|
||||
// 再排队执行编辑——onEditRow 会重建表格并弹模态对话框, 不在视图双击事件
|
||||
// 处理栈内同步执行; 接收者绑定 m_funcTable, 表格析构时过期排队请求随之失效
|
||||
connect(m_funcTable, &QAbstractItemView::doubleClicked, m_funcTable,
|
||||
[this](const QModelIndex &index) {
|
||||
if (index.row() < 0 || index.row() >= m_funcList.size()) {
|
||||
return;
|
||||
}
|
||||
const QString funcName = m_funcList.at(index.row());
|
||||
QMetaObject::invokeMethod(m_funcTable, [this, funcName]() {
|
||||
onEditRowByName(funcName);
|
||||
}, Qt::QueuedConnection);
|
||||
});
|
||||
funcLayout->addWidget(m_funcTable);
|
||||
|
||||
QHBoxLayout *tableBtnLayout = new QHBoxLayout;
|
||||
@@ -206,31 +220,13 @@ void ConvertWorkbenchBase::refreshTable()
|
||||
styleStatusItem(statusItem, batchExistsReady ? allCnames.contains(funcName)
|
||||
: checkFunctionExists(funcName));
|
||||
|
||||
QStandardItem *actionItem = new QStandardItem("");
|
||||
actionItem->setFlags(actionItem->flags() & ~Qt::ItemIsEditable);
|
||||
|
||||
QList<QStandardItem*> rowItems;
|
||||
rowItems.append(nameItem);
|
||||
rowItems.append(statusItem);
|
||||
attachRowItems(rowItems, funcName);
|
||||
rowItems.append(actionItem);
|
||||
m_tableModel->appendRow(rowItems);
|
||||
|
||||
attachRowWidgets(i, funcName);
|
||||
|
||||
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 内 refreshTable 会重建表格析构 editBtn 自身,
|
||||
// 同步回调返回到按钮内部事件处理时构成 use-after-free, 故延迟到事件循环执行。
|
||||
// 捕获函数名而非行号且以按钮为接收者: 排队前表格可能被重建, 行号会指向错误行;
|
||||
// 函数名是稳定键, 且按钮析构时过期的排队请求随之失效
|
||||
connect(editBtn, &QPushButton::clicked, editBtn, [this, funcName]() {
|
||||
onEditRowByName(funcName);
|
||||
}, Qt::QueuedConnection);
|
||||
m_funcTable->setIndexWidget(m_tableModel->index(i, m_tableModel->columnCount() - 1), editBtn);
|
||||
}
|
||||
|
||||
applyColumnLayout();
|
||||
|
||||
@@ -83,10 +83,10 @@ protected:
|
||||
// ---- 表格行虚钩子 ----
|
||||
// 函数名列样式(子类可对特殊前缀着色等)
|
||||
virtual QStandardItem *createNameItem(const QString &funcName);
|
||||
// 在"是否存在"与"操作"列之间插入额外占位项(如"是否递归"列, 默认无)
|
||||
// 在"是否存在"列之后插入额外占位项(如"是否递归"列, 默认无)
|
||||
virtual void attachRowItems(QList<QStandardItem *> &rowItems, const QString &funcName)
|
||||
{ Q_UNUSED(rowItems) Q_UNUSED(funcName) }
|
||||
// 嵌入额外列控件(如"是否递归"下拉, 默认无); 操作列编辑按钮由基类统一嵌入
|
||||
// 嵌入额外列控件(如"是否递归"下拉, 默认无); 行编辑入口为双击函数名所在行(基类统一接线)
|
||||
virtual void attachRowWidgets(int row, const QString &funcName)
|
||||
{ Q_UNUSED(row) Q_UNUSED(funcName) }
|
||||
|
||||
|
||||
@@ -490,7 +490,7 @@ void MetadataUpdatePage::onWorkerFinished()
|
||||
}
|
||||
|
||||
// 提示文案按结果区分: 失败时不得谎报"更新完成", 取消时不报"失败"。
|
||||
// 具体失败原因由各 process* 写日志, 弹窗引导用户查看日志
|
||||
// 具体失败原因由各 process* 写日志, 结果仅显示在页面底部提示条, 不弹窗
|
||||
QString tip;
|
||||
if (r.cancelled) {
|
||||
tip = QString("%1更新已取消(总耗时: %2)").arg(m_currentTaskName, elapsedStr);
|
||||
@@ -521,10 +521,6 @@ void MetadataUpdatePage::onUpdateFinished(bool success, const QString& message)
|
||||
_uft3AllBtn->setEnabled(true);
|
||||
_uft3CancelBtn->setEnabled(true);
|
||||
_forceFullCheckBox->setEnabled(true);
|
||||
|
||||
if (success) {
|
||||
QMessageBox::information(this, "提示", message);
|
||||
} else {
|
||||
QMessageBox::warning(this, "提示", message);
|
||||
}
|
||||
|
||||
// 结果仅在页面底部提示条展示, 不再弹窗打断用户
|
||||
}
|
||||
|
||||
@@ -47,15 +47,20 @@ QString normalizeNonLsName(const QString &raw)
|
||||
return name;
|
||||
}
|
||||
|
||||
// cust_nonls.json convert.dir 值语义:0=否(不递归)、-1=是(递归)
|
||||
// cust_nonls.json convert.dir 条目 depth 值语义:0=否(不递归)、-1=是(递归)
|
||||
constexpr int RecursionNo = 0;
|
||||
constexpr int RecursionYes = -1;
|
||||
// convert.dir 中引擎兜底条目键(非清单功能,加载时跳过、保存时保留),默认值与既有配置样例一致
|
||||
// convert.dir 中引擎兜底条目键(非清单功能,加载时跳过、保存时原样透传),默认值与既有配置样例一致
|
||||
constexpr char WildcardDefaultKey[] = "*";
|
||||
constexpr int WildcardDefaultValue = RecursionYes;
|
||||
|
||||
// 清单表格列定义
|
||||
enum TableCol { ColName = 0, ColExists = 1, ColRecurse = 2, ColAction = 3 };
|
||||
enum TableCol {
|
||||
ColName = 0, // UF20函数名
|
||||
ColExists = 1, // 是否存在
|
||||
ColRecurse = 2, // 是否递归
|
||||
ColDbMode = 3 // db模式(是=db、否=mdb)
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -77,7 +82,9 @@ void NonLsConvertPage::applyPageLayout(QVBoxLayout *layout)
|
||||
"转码范围不包含核心文件、module 模块文件。"
|
||||
"<ul style='margin-top:4px; margin-bottom:0px; padding-left:18px;'>"
|
||||
"<li><b>非递归模式</b>:仅对目标文件本身执行转码,不会处理该文件内调用的函数;</li>"
|
||||
"<li><b>递归模式</b>:转码目标文件的同时,自动对文件中所调用的关联函数一并完成转码。</li>"
|
||||
"<li><b>递归模式</b>:转码目标文件的同时,自动对文件中所调用的关联函数一并完成转码;</li>"
|
||||
"<li><b>db模式</b>:决定 SQL 语法转换方言,是=db、否=mdb(默认);</li>"
|
||||
"<li><b>双击函数名所在行</b>可在弹窗中修改函数名。</li>"
|
||||
"</ul>");
|
||||
m_tipLabel->setTextFormat(Qt::RichText);
|
||||
m_tipLabel->setWordWrap(true);
|
||||
@@ -98,7 +105,7 @@ int NonLsConvertPage::tableColumnCount() const
|
||||
|
||||
QStringList NonLsConvertPage::tableHeaderLabels() const
|
||||
{
|
||||
return {"UF20函数名", "是否存在", "是否递归", "操作"};
|
||||
return {"UF20函数名", "是否存在", "是否递归", "db模式"};
|
||||
}
|
||||
|
||||
int NonLsConvertPage::tableMinimumHeight() const
|
||||
@@ -108,15 +115,15 @@ int NonLsConvertPage::tableMinimumHeight() const
|
||||
|
||||
void NonLsConvertPage::applyColumnLayout()
|
||||
{
|
||||
// 4 列按 11:3:2:2 比例分摊、恰好铺满表格(约 61%/17%/11%/11%):
|
||||
// "是否递归"占 2 份,保证表头四字与"否/是"下拉完整显示不截断;
|
||||
// 4 列按 8:2:2:2 比例分摊、恰好铺满表格(约 57%/14.3%*3):
|
||||
// "是否递归"/"db模式"各占 2 份,保证表头四字与"否/是"下拉完整显示不截断;
|
||||
// 取整余数并入函数名列,确保总宽精确铺满、无横向滚动条
|
||||
const int totalWidth = m_funcTable->viewport()->width();
|
||||
const int unit = totalWidth / 18;
|
||||
m_funcTable->setColumnWidth(ColName, totalWidth - unit * 7);
|
||||
m_funcTable->setColumnWidth(ColExists, unit * 3);
|
||||
const int unit = totalWidth / 14;
|
||||
m_funcTable->setColumnWidth(ColName, totalWidth - unit * 6);
|
||||
m_funcTable->setColumnWidth(ColExists, unit * 2);
|
||||
m_funcTable->setColumnWidth(ColRecurse, unit * 2);
|
||||
m_funcTable->setColumnWidth(ColAction, unit * 2);
|
||||
m_funcTable->setColumnWidth(ColDbMode, unit * 2);
|
||||
}
|
||||
|
||||
QStandardItem *NonLsConvertPage::createNameItem(const QString &funcName)
|
||||
@@ -132,16 +139,19 @@ QStandardItem *NonLsConvertPage::createNameItem(const QString &funcName)
|
||||
void NonLsConvertPage::attachRowItems(QList<QStandardItem *> &rowItems, const QString &funcName)
|
||||
{
|
||||
Q_UNUSED(funcName)
|
||||
// “是否递归”以行尾下拉呈现:否=0、是=-1(与 cust_nonls.json convert.dir 值一致)
|
||||
QStandardItem *recItem = new QStandardItem("");
|
||||
recItem->setFlags(recItem->flags() & ~Qt::ItemIsEditable);
|
||||
recItem->setTextAlignment(Qt::AlignCenter);
|
||||
rowItems.append(recItem);
|
||||
// "是否递归"/"db模式"以行内下拉呈现:递归 否=0、是=-1(即 depth),
|
||||
// 方言 否=mdb、是=db(即 dialect),占位列仅承载 setIndexWidget 控件
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
QStandardItem *item = new QStandardItem("");
|
||||
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
|
||||
item->setTextAlignment(Qt::AlignCenter);
|
||||
rowItems.append(item);
|
||||
}
|
||||
}
|
||||
|
||||
void NonLsConvertPage::attachRowWidgets(int row, const QString &funcName)
|
||||
{
|
||||
// “是否递归”下拉控件:否/是,切换即写回内存映射并持久化
|
||||
// "是否递归"下拉控件:否/是,切换即写回内存映射并持久化
|
||||
ElaComboBox *recCombo = new ElaComboBox;
|
||||
recCombo->addItem("否", RecursionNo);
|
||||
recCombo->addItem("是", RecursionYes);
|
||||
@@ -166,6 +176,31 @@ void NonLsConvertPage::attachRowWidgets(int row, const QString &funcName)
|
||||
}
|
||||
});
|
||||
m_funcTable->setIndexWidget(m_tableModel->index(row, ColRecurse), recCombo);
|
||||
|
||||
// "db模式"下拉控件:否/是 → dialect mdb/db(契约:是=db、否=mdb),交互与"是否递归"列一致
|
||||
ElaComboBox *dbCombo = new ElaComboBox;
|
||||
dbCombo->addItem("否", Constants::DirEntry::DialectMdb);
|
||||
dbCombo->addItem("是", Constants::DirEntry::DialectDb);
|
||||
dbCombo->setCurrentIndex(
|
||||
(m_dialectMap.value(funcName, Constants::DirEntry::DialectMdb)
|
||||
== Constants::DirEntry::DialectDb) ? 1 : 0);
|
||||
connect(dbCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
this, [this, funcName, dbCombo](int index) {
|
||||
const QString dialect = (index == 1) ? Constants::DirEntry::DialectDb
|
||||
: Constants::DirEntry::DialectMdb;
|
||||
const QString prev = m_dialectMap.value(funcName, Constants::DirEntry::DialectMdb);
|
||||
if (prev == dialect) {
|
||||
return;
|
||||
}
|
||||
m_dialectMap.insert(funcName, dialect);
|
||||
if (!saveConfig(m_funcList)) {
|
||||
m_dialectMap.insert(funcName, prev);
|
||||
dbCombo->blockSignals(true);
|
||||
dbCombo->setCurrentIndex(prev == Constants::DirEntry::DialectDb ? 1 : 0);
|
||||
dbCombo->blockSignals(false);
|
||||
}
|
||||
});
|
||||
m_funcTable->setIndexWidget(m_tableModel->index(row, ColDbMode), dbCombo);
|
||||
}
|
||||
|
||||
QString NonLsConvertPage::normalizeFunctionName(const QString &raw) const
|
||||
@@ -176,12 +211,14 @@ QString NonLsConvertPage::normalizeFunctionName(const QString &raw) const
|
||||
void NonLsConvertPage::onFunctionAdded(const QString &name)
|
||||
{
|
||||
m_recursionMap.insert(name, RecursionNo); // 新增功能默认“否”
|
||||
m_dialectMap.insert(name, Constants::DirEntry::DialectMdb); // 新增功能默认 mdb
|
||||
}
|
||||
|
||||
void NonLsConvertPage::onFunctionsRemoved(const QStringList &names)
|
||||
{
|
||||
for (const QString &name : names) {
|
||||
m_recursionMap.remove(name);
|
||||
m_dialectMap.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,11 +227,15 @@ void NonLsConvertPage::onFunctionsRenamed(const QString &oldName, const QString
|
||||
const int oldRecursion = m_recursionMap.value(oldName, RecursionNo);
|
||||
m_recursionMap.remove(oldName);
|
||||
m_recursionMap.insert(newName, oldRecursion);
|
||||
const QString oldDialect = m_dialectMap.value(oldName, Constants::DirEntry::DialectMdb);
|
||||
m_dialectMap.remove(oldName);
|
||||
m_dialectMap.insert(newName, oldDialect);
|
||||
}
|
||||
|
||||
void NonLsConvertPage::onFunctionListCleared()
|
||||
{
|
||||
m_recursionMap.clear();
|
||||
m_dialectMap.clear();
|
||||
}
|
||||
|
||||
void NonLsConvertPage::adviseAddedFunctions(const QStringList &names)
|
||||
@@ -234,12 +275,17 @@ bool NonLsConvertPage::saveConfig(const QStringList &funcList)
|
||||
}
|
||||
}
|
||||
|
||||
// convert.dir 键值表:{功能名: 0|-1};0=否(不递归)、-1=是(递归),并保留/兜底引擎默认键 "*"
|
||||
// convert.dir 键值表(V2):{功能名: {depth, dialect}};depth 0=否(不递归)、-1=是(递归),
|
||||
// dialect 是=db、否=mdb;引擎兜底键 "*" 保持数值原样透传(不升级为对象、不带 dialect)
|
||||
const QJsonObject oldDir = root.value(Constants::CustKeys::ConvertDir).toObject()
|
||||
.value(Constants::CustKeys::ConvertDirSub).toObject();
|
||||
QJsonObject dirObj;
|
||||
for (const QString &name : funcList) {
|
||||
dirObj.insert(name, m_recursionMap.value(name, RecursionNo));
|
||||
QJsonObject entry;
|
||||
entry.insert(Constants::DirEntry::Depth, m_recursionMap.value(name, RecursionNo));
|
||||
entry.insert(Constants::DirEntry::Dialect,
|
||||
m_dialectMap.value(name, Constants::DirEntry::DialectMdb));
|
||||
dirObj.insert(name, entry);
|
||||
}
|
||||
if (oldDir.contains(WildcardDefaultKey)) {
|
||||
dirObj.insert(WildcardDefaultKey, oldDir.value(WildcardDefaultKey));
|
||||
@@ -315,8 +361,10 @@ void NonLsConvertPage::loadConfig()
|
||||
|
||||
QStringList items;
|
||||
m_recursionMap.clear();
|
||||
m_dialectMap.clear();
|
||||
if (dirVal.isObject()) {
|
||||
// 新版:dir 为 {功能名: 0|-1},0=否(不递归)、-1=是(递归);"*" 为引擎默认键,不展示
|
||||
// V2:dir 为 {功能名: {depth, dialect}},depth 0=否、-1=是,dialect 是=db、否=mdb;
|
||||
// 兼容 V1 数值条目(无 dialect,按 mdb 兜底);"*" 为引擎默认键,不展示、保存时原样透传
|
||||
const QJsonObject dirObj = dirVal.toObject();
|
||||
for (auto it = dirObj.constBegin(); it != dirObj.constEnd(); ++it) {
|
||||
if (it.key() == WildcardDefaultKey) {
|
||||
@@ -326,17 +374,31 @@ void NonLsConvertPage::loadConfig()
|
||||
if (item.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
// V1 数值 / V2 对象条目双格式兼容: 缺失字段按契约默认值兜底
|
||||
int depth = RecursionNo;
|
||||
QString dialect = Constants::DirEntry::DialectMdb;
|
||||
if (it.value().isObject()) {
|
||||
const QJsonObject entry = it.value().toObject();
|
||||
depth = entry.value(Constants::DirEntry::Depth).toInt(RecursionNo);
|
||||
const QString d =
|
||||
entry.value(Constants::DirEntry::Dialect).toString(Constants::DirEntry::DialectMdb);
|
||||
dialect = (d == Constants::DirEntry::DialectDb) ? Constants::DirEntry::DialectDb
|
||||
: Constants::DirEntry::DialectMdb;
|
||||
} else {
|
||||
depth = it.value().toInt(RecursionNo);
|
||||
}
|
||||
items.append(item);
|
||||
const int v = it.value().toInt(RecursionNo);
|
||||
m_recursionMap.insert(item, (v == RecursionYes) ? RecursionYes : RecursionNo);
|
||||
m_recursionMap.insert(item, (depth == RecursionYes) ? RecursionYes : RecursionNo);
|
||||
m_dialectMap.insert(item, dialect);
|
||||
}
|
||||
} else {
|
||||
// 兼容旧版:dir 为 ["功能名", ...],无递归标志,一律按“否”加载
|
||||
// 兼容更早期版本:dir 为 ["功能名", ...],无递归标志,一律按"否/mdb"加载
|
||||
for (const QJsonValue &val : dirVal.toArray()) {
|
||||
const QString item = normalizeNonLsName(val.toString());
|
||||
if (!item.isEmpty()) {
|
||||
items.append(item);
|
||||
m_recursionMap.insert(item, RecursionNo);
|
||||
m_dialectMap.insert(item, Constants::DirEntry::DialectMdb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,14 @@ class QVBoxLayout;
|
||||
class QStandardItem;
|
||||
|
||||
// 按需转码工作台(业务转码页非 LS pivot,针对非 LS 功能转码)
|
||||
// 函数清单(短名)与"是否递归"开关持久化到 config/cust_nonls.json,
|
||||
// convert.dir 为 {功能名: 0| -1} 键值表:0=否(不递归)、-1=是(递归),另含引擎默认键 "*"
|
||||
// 函数清单(短名)与"是否递归"开关、"db模式"方言持久化到 config/cust_nonls.json,
|
||||
// convert.dir 为 {功能名: {depth: 0|-1, dialect: "db"|"mdb"}} 键值表(V2 格式):
|
||||
// depth 0=否(不递归)、-1=是(递归),dialect 是=db、否=mdb(默认),
|
||||
// 另含引擎默认键 "*"(保持数值原样透传,不展示不修改)
|
||||
// 调用契约:仅携带 -m 2(按需转码模式);函数清单与递归开关由引擎直接读取 cust_nonls.json,
|
||||
// 不再需要 -t/-o 等命令行参数
|
||||
// 历史格式兼容:旧版条目为数值(无 dialect)按"否/mdb"兜底;更早期字符串数组(无递归
|
||||
// 标志)统一按"否/mdb"兜底。格式契约见 docs/转码配置格式契约.md
|
||||
class NonLsConvertPage : public ConvertWorkbenchBase
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -55,8 +59,10 @@ protected:
|
||||
|
||||
private:
|
||||
QLabel *m_tipLabel = nullptr; // 页首灰字说明条(需主题联动)
|
||||
// 每个清单功能对应的“是否递归”标志:0=否、-1=是(与 cust_nonls.json convert.dir 值一致)
|
||||
// 每个清单功能对应的"是否递归"标志:0=否、-1=是(与 cust_nonls.json convert.dir 条目 depth 字段一致)
|
||||
QHash<QString, int> m_recursionMap;
|
||||
// 每个清单功能对应的 SQL 方言:"db"/"mdb"(db模式列 是=db、否=mdb,默认 mdb)
|
||||
QHash<QString, QString> m_dialectMap;
|
||||
};
|
||||
|
||||
#endif // NONLSCONVERTPAGE_H
|
||||
|
||||
Reference in New Issue
Block a user