#pragma execution_character_set("utf-8") #include "QMainwindow.h" #include #include #include #include //#include #include QMainwindow::QMainwindow(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); // 初始化日志管理器 QLogManager::Instance()->setLogLevel(QLogManager::SQ_INFO);; m_nReplyCount = 0; m_dataAcquisition = new QDataAcquisition; m_netCheckTimer = new QTimer(this); m_netCheckTimer->start(1000); // 启动定时器,间隔1000毫秒(1秒) connect(m_netCheckTimer, &QTimer::timeout, this, [=]() { if (m_dataAcquisition->m_InterfaceFutu->bConnected) ui.lblNetStatu->setStyleSheet("background-color: green;"); else ui.lblNetStatu->setStyleSheet("background-color: red;"); }); initWidget(); initReplyManage(); // 日志查看器标签页 logViewer = new QLogViewer; //tabWidget->addTab(logViewer, "System Logs"); ui.verticalLayout_9->addWidget(logViewer); LOG_INFO("Start Programm!"); // 创建查看器 QBigOrderViewer* viewer = new QBigOrderViewer(); ui.layoutBigorderViewer->addWidget(viewer); QObject::connect( viewer, &QBigOrderViewer::sendStockCodeToMainForm, this, [this](const QString& code) { int nIdex = 0; for (const auto& item : m_replyCodes) { if (item->stockCode == code) break; nIdex++; } if (nIdex < 0 || nIdex >= m_model->rowCount()) return; // 清除当前选择 ui.tableReplylist->clearSelection(); // 创建选择范围(整行) QModelIndex topLeft = m_model->index(nIdex, 0); QModelIndex bottomRight = m_model->index(nIdex, m_model->columnCount() - 1); QItemSelection selection(topLeft, bottomRight); // 应用选择 ui.tableReplylist->selectionModel()->select(selection, QItemSelectionModel::Select); // 滚动到选中行 ui.tableReplylist->scrollTo(m_model->index(nIdex, 0), QAbstractItemView::PositionAtTop); //ui.tableReplylist->scrollTo(topLeft, QAbstractItemView::PositionAtCenter); ui.tableReplylist->update(); }, Qt::QueuedConnection // 必须使用跨线程连接 ); auto manager = QBigOrderManager::instance(); QObject::connect( manager, &QBigOrderManager::markBigOrderSignal, this, [this]() { m_lightWidget->triggerSignal(); }, Qt::QueuedConnection // 必须使用跨线程连接 ); // 检查对象有效性 if (!m_dataAcquisition || !m_dataAcquisition->m_InterfaceFutu) { qCritical() << "Invalid data acquisition or interface"; return; } // 获取处理器指针 auto* processor = &m_dataAcquisition->m_InterfaceFutu->processor; // 连接 maxAskOrderReady 信号 bool askConnected = QObject::connect( processor, &QOrderProcessor::maxOrderReady, this, [this](BigOrderInfo bigOrderInfo) { if (auto manager = QBigOrderManager::instance()) { if (!manager->addBigOrder(bigOrderInfo)) ;// LOG_WARNING("Failed to add order"); } },Qt::QueuedConnection // 必须使用跨线程连接 ); //bool bidConnected = QObject::connect( // processor, // &QOrderProcessor::maxBidOrderReady, // this, // [this](BigOrderInfo bigOrderInfo) { // BigOrderItem order; // order.stockCode = bigOrderInfo.code; // order.stockName = bigOrderInfo.name; // order.orderPrice = bigOrderInfo.price; // order.stockQuantity = bigOrderInfo.volume; // order.orderCount = 1; // order.orderType = "BID"; // order.orderTime = QDateTime::currentDateTime(); // 这里时间还不准确,应该是接收到的系统时间 // if (auto manager = QBigOrderManager::instance()) { // if (!manager->addBigOrder(order)) // LOG_WARNING("Failed to add order"); // } // }, Qt::QueuedConnection // 必须使用跨线程连接 //); /* 执行订阅 - 发送订阅信号 */ QObject::connect(ui.btnDoReply, &QPushButton::clicked, [this]() { m_nReplyCount = m_setReplayCode.size(); LOG_INFO("Start Reply!"); ui.btnDoReply->setText("正在检测 ..."); // 后面封装起来,通过信号的方式进行交互 // 筛选参数,这个放到外面去,修改之后及时更新 m_dataAcquisition->m_InterfaceFutu->processor.setreplyCodeQuantity(m_replyCodeQuantity); m_dataAcquisition->m_InterfaceFutu->subStock(); }); /* 手动连接 OpenD */ QObject::connect(ui.btnConnOpend, &QPushButton::clicked, [this]() { if (!m_dataAcquisition->m_InterfaceFutu->bConnected) m_dataAcquisition->m_InterfaceFutu->InitConn(); }); } QMainwindow::~QMainwindow() { if (m_dataAcquisition) { delete m_dataAcquisition; m_dataAcquisition = NULL; } } void QMainwindow::initWidget() { m_model = new QStandardItemModel(0, 2, this); //m_model->setEditable(true); m_proxyModel = new QSortFilterProxyModel(this); // 表格视图配置 m_proxyModel->setSourceModel(m_model); ui.tableReplylist->setModel(m_proxyModel); //ui.tableReplylist->setEditTriggers(QAbstractItemView::NoEditTriggers); ui.tableReplylist->setSelectionBehavior(QAbstractItemView::SelectRows); ui.tableReplylist->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); //ui.tableReplylist->setEditTriggers(QAbstractItemView::EditTriggers); // 设置表头 m_model->setHorizontalHeaderLabels( QStringList() << "股票代码" << "检测阈值"); /* 用户更改数量阈值时,同时修改列表 */ QObject::connect(m_model, &QStandardItemModel::dataChanged, [&](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { // 遍历变化区域 for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { for (int col = topLeft.column(); col <= bottomRight.column(); ++col) { QModelIndex index = m_model->index(row, col); QString newData = m_model->data(index, Qt::DisplayRole).toString(); m_replyCodes.at(row)->stockQuantityTh = newData.mid(0, newData.size() - 2).toFloat() * 1000; } } }); QObject::connect(ui.tableReplylist->selectionModel(), &QItemSelectionModel::selectionChanged, [&](const QItemSelection &selected, const QItemSelection &deselected) { Q_UNUSED(deselected); // 未使用deselected,避免警告 // 获取当前选中的所有行 QModelIndexList selectedIndexes = ui.tableReplylist->selectionModel()->selectedIndexes(); // 如果没有选中任何项 if (selectedIndexes.isEmpty()) { return; } // 提取选中的行号(去重) int selectedRow = selectedIndexes.at(0).row(); ui.editInputCode->setText(m_replyCodes.at(selectedRow)->stockCode); ui.editQuantityTh->setText(QString::number(m_replyCodes.at(selectedRow)->stockQuantityTh / 1000,'f',1)); }); //ui.listAsk->setAlternatingRowColors(true); //ui.listAsk->setStyleSheet( // "QListWidget::item[latest=true] {" // " background-color: #ffcccc;" // 最新行的颜色 // "}" // "QListWidget::item {" // " background-color: white;" // 默认行的颜色 // "}" //); //ui.listBid->setAlternatingRowColors(true); //ui.listBid->setStyleSheet( // "QListWidget::item[latest=true] {" // " background-color: #ffcccc;" // 最新行的颜色 // "}" // "QListWidget::item {" // " background-color: white;" // 默认行的颜色 // "}" //); //// 添加项目后更新属性 //connect(ui.listAsk, &QListWidget::itemChanged, [=]() { // if (ui.listAsk->count() > 0) { // // 清除之前最新项的标记 // for (int i = 0; i < ui.listAsk->count(); ++i) { // ui.listAsk->item(i)->setData(Qt::UserRole, QVariant()); // } // // 标记最新项 // ui.listAsk->item(ui.listAsk->count() - 1)->setData(Qt::UserRole, "latest"); // } //}); //connect(ui.listBid, &QListWidget::itemChanged, [=]() { // if (ui.listAsk->count() > 0) { // // 清除之前最新项的标记 // for (int i = 0; i < ui.listAsk->count(); ++i) { // ui.listBid->item(i)->setData(Qt::UserRole, QVariant()); // } // // 标记最新项 // ui.listAsk->item(ui.listBid->count() - 1)->setData(Qt::UserRole, "latest"); // } //}); // 创建呼吸灯控件 m_lightWidget = new QBreathingLight(); ui.layoutBreathing->addWidget(m_lightWidget, 0, Qt::AlignCenter); //// 创建自动测试定时器 //QTimer *autoTestTimer = new QTimer(this); //connect(autoTestTimer, &QTimer::timeout, m_lightWidget, &QBreathingLight::triggerSignal); //autoTestTimer->start(2000); // 每2秒自动触发一次 //connect(ui.btnConnOpend, &QPushButton::clicked, m_lightWidget, &QBreathingLight::triggerSignal); } void QMainwindow::initReplyManage() { // 读取默认订阅股票代码列表 readReplyCodefile(m_replyCodes); LOG_INFO("订阅列表加载成功!"); m_dataAcquisition->m_InterfaceFutu->setReplyList(m_replyCodes); // 设置代码输入规则 QIntValidator *intValidator = new QIntValidator(1, 99999, this); ui.editInputCode->setValidator(intValidator); // 添加/修改订阅项 QObject::connect(ui.btnAddCode, &QPushButton::clicked, [=]() { ReplyCodeItem order; order.stockCode = toFixedDigitNumber(ui.editInputCode->text()); order.stockQuantityTh = ui.editQuantityTh->text().toFloat() * 1000; bool bFinded = false; for (const auto& item : m_replyCodes) { if (item->stockCode == order.stockCode) { item->stockQuantityTh = order.stockQuantityTh; m_replyCodeQuantity[item->stockCode] = order.stockQuantityTh; bFinded = true; LOG_INFO("更新完成,代码:" + order.stockCode + " ,阈值:"+QString::number(order.stockQuantityTh)); break; } } if (!bFinded) { if (m_replyCodes.size() + 1 > 50) { LOG_WARNING("订阅列表添加失败,订阅数超过最大订阅数量!"); return; } auto newOrder = QSharedPointer::create(order); m_replyCodes.append(newOrder); m_replyCodeQuantity.insert(order.stockCode, order.stockQuantityTh); LOG_INFO("添加完成,代码:" + order.stockCode + " ,阈值:" + QString::number(order.stockQuantityTh)); } updateCodeTable(); }); // 删除选订阅项 QObject::connect(ui.btnDeleteCode, &QPushButton::clicked, [=]() { // 获取所有选中的行 QModelIndexList selected = ui.tableReplylist->selectionModel()->selectedRows(); // 删除选中行 for (int i = selected.size() - 1; i >= 0; --i) { m_replyCodes.removeAt(selected[i].row()); m_replyCodeQuantity.remove(toFixedDigitNumber(ui.editInputCode->text())); } updateCodeTable(); }); /* 保存文件 */ QObject::connect(ui.btnSaveCodefile, &QPushButton::clicked, [=]() { saveReplyCodefile(); // 更新检测阈值 m_dataAcquisition->m_InterfaceFutu->processor.setreplyCodeQuantity(m_replyCodeQuantity); }); } void QMainwindow::updateCodeTable() { m_model->removeRows(0, m_model->rowCount()); for (const auto& order : m_replyCodes) { QList rowItems; QStandardItem* codeItem = new QStandardItem(order->stockCode); codeItem->setTextAlignment(Qt::AlignCenter); rowItems << codeItem; //rowItems << new QStandardItem(order->stockCode); QStandardItem* quanityItem = new QStandardItem(QString::number(order->stockQuantityTh / 1000, 'f', 1) + "K"); quanityItem->setTextAlignment(Qt::AlignCenter); rowItems << quanityItem; m_model->appendRow(rowItems); } } // 检查并格式化为5位数字 QString QMainwindow::toFixedDigitNumber(const QString& input) { // 检查是否全为0-9数字 QRegularExpression re("^\\d+$"); if (!re.match(input).hasMatch()) { return QString(); // 弹出提示框,code 输入错误,暂时先返回空 } // 长度检查和处理 if (input.length() > 5) { return input.right(5); // 超过5位:截取后5位 } else if (input.length() < 5) { return QString("%1").arg(input, 5, '0'); // 不足5位:左侧补0 } return input; // 正好5位:直接返回 } /* 加载订阅列表 */ void QMainwindow::readReplyCodefile(QList>& replyList) { QString appDir = QCoreApplication::applicationDirPath(); QString fileName = QDir(appDir).absoluteFilePath("../../config/replyCodeList.csv"); QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { LOG_FATAL("无法打开文件。"); } QTextStream in(&file); in.setCodec("UTF-8"); // 设置编码,确保中文正常 replyList.clear(); // 清空原有数据 m_replyCodeQuantity.clear(); bool isFirstLine = true; while (!in.atEnd()) { QString line = in.readLine(); // 跳过空行 if (line.trimmed().isEmpty()) { continue; } // 跳过标题行(假设第一行是标题) if (isFirstLine) { isFirstLine = false; continue; } // 解析CSV行 QStringList fields = line.split(','); // 确保有足够的字段(根据你的CSV结构调整) if (/*fields.size() >= 3*/1) { QString code = fields[0].trimmed(); QString description = fields[1].trimmed(); //bool isActive = fields[2].trimmed().toLower() == "true" || fields[2].trimmed() == "1"; ReplyCodeItem replyItem; replyItem.stockCode = toFixedDigitNumber(code); replyItem.stockQuantityTh = description.toFloat(); // 创建ReplyCodeItem并添加到列表 auto item = QSharedPointer::create(replyItem); m_replyCodes.append(item); // 保存到映射表 m_replyCodeQuantity.insert(replyItem.stockCode, replyItem.stockQuantityTh); } else { //qWarning() << "忽略格式不正确的行:" << line; } } file.close(); updateCodeTable(); } /* 保存订阅列表 */ void QMainwindow::saveReplyCodefile() { //// 配置文件名 QString appDir = QCoreApplication::applicationDirPath(); QString fileName = QDir(appDir).absoluteFilePath("../../config/replyCodeList.csv"); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { LOG_FATAL("无法创建订阅文件。" ); return; } QTextStream out(&file); out.setCodec("GBK"); // 确保使用UTF-8编码 // 写入表头 for (int col = 0; col < m_model->columnCount(); ++col) { out << escapeCsv(m_model->headerData(col, Qt::Horizontal).toString()); if (col < m_model->columnCount() - 1) out << ","; } out << "\n"; // 写入数据 for (const auto &value : m_replyCodes) { out << value->stockCode<<","<< value->stockQuantityTh << "\n"; } file.close(); LOG_INFO("订阅列表保存成功!"); } QString QMainwindow::escapeCsv(const QString &field) { if (field.contains(',') || field.contains('"') || field.contains('\n')) { QString escaped = field; escaped.replace("\"", "\"\""); return "\"" + escaped + "\""; } return field; }