398 lines
13 KiB
C++
398 lines
13 KiB
C++
#include "qbigorderviewer.h"
|
||
#include "qbigordermanager.h"
|
||
#include "qhistoryorderdialog.h"
|
||
#include <QHeaderView>
|
||
#include <QVBoxLayout>
|
||
#include <QHBoxLayout>
|
||
#include <QLabel>
|
||
#include <QPainter>
|
||
#include <QFileDialog>
|
||
#include <QTextStream>
|
||
#include <QLineEdit>
|
||
#include <QComboBox>
|
||
#include <QTableView>
|
||
#include <QPushButton>
|
||
#include <QWidget>
|
||
#include <QCheckBox>
|
||
|
||
|
||
// ==================== QBigOrderViewer ====================
|
||
QBigOrderViewer::QBigOrderViewer(QWidget *parent)
|
||
: QWidget(parent),
|
||
m_model(new QStandardItemModel(0, 8, this)),
|
||
m_proxyModel(new QSortFilterProxyModel(this)),
|
||
m_typeDelegate(new OrderTypeDelegate(this)),
|
||
m_lastOrderDelegate(new HighlightDelegate(this))
|
||
{
|
||
initUI();
|
||
initConnections();
|
||
|
||
QBigOrderManager* manager = QBigOrderManager::instance();
|
||
connect(manager, &QBigOrderManager::bigOrderAdded,
|
||
this, &QBigOrderViewer::onBigOrderAdded);
|
||
|
||
connect(manager, &QBigOrderManager::saveOverRefreshView,
|
||
this, [this]() {
|
||
m_model->removeRows(0, m_model->rowCount());
|
||
}, Qt::QueuedConnection // 必须使用跨线程连接
|
||
);
|
||
}
|
||
|
||
QBigOrderViewer::~QBigOrderViewer()
|
||
{
|
||
delete m_model;
|
||
delete m_proxyModel;
|
||
delete m_typeDelegate;
|
||
}
|
||
|
||
void QBigOrderViewer::initUI()
|
||
{
|
||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||
|
||
// 过滤控件区域
|
||
QHBoxLayout *filterLayout = new QHBoxLayout();
|
||
|
||
QCheckBox *cboAutoSave = new QCheckBox("自动保存数据");
|
||
cboAutoSave->setChecked(true);
|
||
filterLayout->addWidget(cboAutoSave);
|
||
|
||
filterLayout->addWidget(new QLabel("股票代码:"));
|
||
m_stockCodeFilter = new QLineEdit();
|
||
m_stockCodeFilter->setPlaceholderText("支持模糊搜索");
|
||
filterLayout->addWidget(m_stockCodeFilter);
|
||
|
||
filterLayout->addWidget(new QLabel("订单类型:"));
|
||
m_orderTypeFilter = new QComboBox();
|
||
m_orderTypeFilter->addItem("全部", "");
|
||
m_orderTypeFilter->addItem("买入", "BID");
|
||
m_orderTypeFilter->addItem("卖出", "ASK");
|
||
filterLayout->addWidget(m_orderTypeFilter);
|
||
|
||
filterLayout->addStretch();
|
||
// 添加历史查询按钮
|
||
QPushButton *historyButton = new QPushButton("历史查询");
|
||
filterLayout->addWidget(historyButton);
|
||
|
||
m_exportButton = new QPushButton("导出数据");
|
||
filterLayout->addWidget(m_exportButton);
|
||
mainLayout->addLayout(filterLayout);
|
||
|
||
// 表格视图配置
|
||
m_tableView = new QTableView();
|
||
m_proxyModel->setSourceModel(m_model);
|
||
m_tableView->setModel(m_proxyModel);
|
||
m_tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||
|
||
// 设置表头(股票代码、名称、类型、数量、价格、订单数、时间、时间戳)
|
||
QStringList headers;
|
||
headers << "股票代码" << "股票名称" << "订单类型"
|
||
<< "股票数量" << "股票价格" << "订单档位"
|
||
<< "时间"<< "Timestamp";
|
||
m_model->setHorizontalHeaderLabels(headers);
|
||
m_tableView->setColumnHidden(7, true);
|
||
|
||
|
||
// 设置表头文字居中
|
||
for (int col = 0; col < m_model->columnCount(); ++col) {
|
||
m_model->setHeaderData(col, Qt::Horizontal, Qt::AlignCenter, Qt::TextAlignmentRole);
|
||
}
|
||
|
||
// 表格属性
|
||
m_tableView->setItemDelegateForColumn(2, m_typeDelegate);
|
||
m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||
m_tableView->setSortingEnabled(true);
|
||
// m_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
|
||
m_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||
|
||
// 设置高亮委托到所有列
|
||
m_tableView->setItemDelegate(m_lastOrderDelegate);
|
||
|
||
// 默认按时间列(第7列,索引6)升序排序 -> 新插入的行放在最顶上,故修改为降序排序 @2025.08.30 stone
|
||
const int timeColumnIndex = 6;
|
||
m_proxyModel->sort(timeColumnIndex, Qt::DescendingOrder);
|
||
|
||
mainLayout->addWidget(m_tableView);
|
||
|
||
// 连接历史查询按钮
|
||
connect(historyButton, &QPushButton::clicked, this, [this] {
|
||
QHistoryOrderDialog *dialog = new QHistoryOrderDialog(this);
|
||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||
dialog->show();
|
||
});
|
||
|
||
connect(cboAutoSave, &QCheckBox::stateChanged, [](int state) {
|
||
if (state == Qt::Checked) {
|
||
QBigOrderManager::instance()->setAutoSave(true);
|
||
// 复选框被选中
|
||
}
|
||
else if (state == Qt::Unchecked) {
|
||
QBigOrderManager::instance()->setAutoSave(false);
|
||
// 复选框被取消选中
|
||
}
|
||
});
|
||
}
|
||
|
||
void QBigOrderViewer::initConnections()
|
||
{
|
||
connect(m_stockCodeFilter, &QLineEdit::textChanged,
|
||
this, &QBigOrderViewer::onFilterChanged);
|
||
connect(m_orderTypeFilter, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||
this, &QBigOrderViewer::onFilterChanged);
|
||
connect(m_exportButton, &QPushButton::clicked,
|
||
this, &QBigOrderViewer::onExportClicked);
|
||
// connect(m_tableView->horizontalHeader(), &QHeaderView::sectionClicked,
|
||
// m_proxyModel, &QSortFilterProxyModel::sort);
|
||
// // 表头点击排序(使用lambda适配参数)
|
||
connect(m_tableView->horizontalHeader(), &QHeaderView::sectionClicked,
|
||
[this](int logicalIndex) {
|
||
Qt::SortOrder currentOrder = m_proxyModel->sortOrder();
|
||
m_proxyModel->sort(logicalIndex,
|
||
currentOrder == Qt::AscendingOrder
|
||
? Qt::AscendingOrder
|
||
: Qt::DescendingOrder);
|
||
});
|
||
|
||
QObject::connect(m_tableView->selectionModel(), &QItemSelectionModel::selectionChanged,
|
||
|
||
[&](const QItemSelection &selected, const QItemSelection &deselected) {
|
||
|
||
Q_UNUSED(deselected); // 未使用deselected,避免警告
|
||
|
||
// 获取当前选中的所有行
|
||
QModelIndexList selectedIndexes = m_tableView->selectionModel()->selectedIndexes();
|
||
|
||
// 如果没有选中任何项
|
||
if (selectedIndexes.isEmpty()) {
|
||
return;
|
||
}
|
||
emit sendStockCodeToMainForm(m_proxyModel->index(selectedIndexes.at(0).row(), selectedIndexes.at(0).column()).data().toString());
|
||
});
|
||
}
|
||
|
||
void QBigOrderViewer::updateView()
|
||
{
|
||
m_model->removeRows(0, m_model->rowCount());
|
||
for (const auto& order : m_currentOrders) {
|
||
QList<QStandardItem*> rowItems;
|
||
|
||
// 创建项并设置文本居中
|
||
QStandardItem* codeItem = new QStandardItem(order->code);
|
||
codeItem->setTextAlignment(Qt::AlignCenter);
|
||
rowItems << codeItem;
|
||
|
||
QStandardItem* nameItem = new QStandardItem(order->name);
|
||
nameItem->setTextAlignment(Qt::AlignCenter);
|
||
rowItems << nameItem;
|
||
|
||
int nType = order->nBigOrderType;
|
||
QStandardItem* typeItem = new QStandardItem(nType ? "多单" : "空单");
|
||
typeItem->setTextAlignment(Qt::AlignCenter);
|
||
rowItems << typeItem;
|
||
|
||
QStandardItem* volumeItem = new QStandardItem(QString::number(std::fabs(order->volume / 1000)) + "K");
|
||
volumeItem->setTextAlignment(Qt::AlignCenter);
|
||
rowItems << volumeItem;
|
||
|
||
QStandardItem* priceItem = new QStandardItem(QString::number(order->price, 'f', 2));
|
||
priceItem->setTextAlignment(Qt::AlignCenter);
|
||
rowItems << priceItem;
|
||
|
||
QStandardItem* levelItem = new QStandardItem(QString::number(order->level));
|
||
levelItem->setTextAlignment(Qt::AlignCenter);
|
||
rowItems << levelItem;
|
||
|
||
QString str = order->svrRecvTime.mid(11);
|
||
if (str == nullptr)
|
||
{
|
||
QDateTime dateTime = QDateTime::currentDateTime();
|
||
str = dateTime.toString("hh:mm:ss");
|
||
}
|
||
QStandardItem* timeItem = new QStandardItem(str);
|
||
timeItem->setTextAlignment(Qt::AlignCenter);
|
||
rowItems << timeItem;
|
||
|
||
// 添加时间戳列
|
||
QStandardItem* timestampItem = new QStandardItem(order->svrRecvTime);
|
||
timestampItem->setTextAlignment(Qt::AlignCenter);
|
||
rowItems << timestampItem;
|
||
|
||
m_model->appendRow(rowItems);
|
||
}
|
||
}
|
||
|
||
void QBigOrderViewer::applyFilters()
|
||
{
|
||
QString stockCode = m_stockCodeFilter->text().trimmed();
|
||
QString orderType = m_orderTypeFilter->currentData().toString();
|
||
|
||
m_currentOrders.clear();
|
||
for (const auto& order : m_allOrders) {
|
||
bool match = true;
|
||
if (!stockCode.isEmpty() && !order->code.contains(stockCode, Qt::CaseInsensitive)) {
|
||
match = false;
|
||
}
|
||
if (!orderType.isEmpty() && order->nBigOrderType != orderType) {
|
||
match = false;
|
||
}
|
||
if (match) m_currentOrders.append(order);
|
||
}
|
||
}
|
||
|
||
//void QBigOrderViewer::onBigOrderAdded(const BigOrderInfo &order)
|
||
//{
|
||
// auto newOrder = QSharedPointer<BigOrderInfo>::create(order);
|
||
// m_allOrders.append(newOrder);
|
||
// if (matchesFilter(newOrder)) {
|
||
// m_currentOrders.append(newOrder);
|
||
//
|
||
// // 高效插入单行
|
||
// int row = m_model->rowCount();
|
||
// m_model->insertRow(row);
|
||
// setRowData(row, newOrder);
|
||
// }
|
||
//}
|
||
|
||
void QBigOrderViewer::onBigOrderAdded(const BigOrderInfo &order)
|
||
{
|
||
auto newOrder = QSharedPointer<BigOrderInfo>::create(order);
|
||
m_allOrders.append(newOrder);
|
||
|
||
// 应用过滤条件
|
||
if (matchesFilter(newOrder)) {
|
||
m_currentOrders.append(newOrder);
|
||
|
||
// 在源模型的首行插入
|
||
int row = 0 ;
|
||
m_model->insertRow(row);
|
||
setRowData(row, newOrder);
|
||
|
||
// 为订单生成唯一标识
|
||
QString orderId = QString("%1_%2_%3")
|
||
.arg(order.code)
|
||
.arg(QString::number(order.price, 'f', 2))
|
||
.arg(QDateTime::currentMSecsSinceEpoch());
|
||
|
||
// 设置高亮标记
|
||
for (int col = 0; col < m_model->columnCount(); ++col) {
|
||
QModelIndex index = m_model->index(row, col);
|
||
m_model->setData(index, true, IsHighlightedRole);
|
||
m_model->setData(index, orderId, Qt::UserRole + 102); // 存储唯一标识
|
||
}
|
||
|
||
// 通知视图更新
|
||
QModelIndex topLeft = m_model->index(row, 0);
|
||
QModelIndex bottomRight = m_model->index(row, m_model->columnCount() - 1);
|
||
emit m_model->dataChanged(topLeft, bottomRight);
|
||
|
||
// 2秒后取消高亮
|
||
QTimer::singleShot(2000, this, [this, orderId]() {
|
||
for (int row = 0; row < m_model->rowCount(); ++row) {
|
||
QModelIndex index = m_model->index(row, 0);
|
||
m_model->setData(index, false, IsHighlightedRole);
|
||
QString currentOrderId = index.data(Qt::UserRole + 102).toString();
|
||
|
||
if (currentOrderId == orderId) {
|
||
|
||
for (int col = 0; col < m_model->columnCount(); ++col) {
|
||
QModelIndex cellIndex = m_model->index(row, col);
|
||
m_model->setData(cellIndex, false, IsHighlightedRole);
|
||
}
|
||
|
||
// 通知视图更新
|
||
QModelIndex topLeft = m_model->index(row, 0);
|
||
QModelIndex bottomRight = m_model->index(row, m_model->columnCount() - 1);
|
||
emit m_model->dataChanged(topLeft, bottomRight);
|
||
|
||
break; // 找到后退出循环
|
||
}
|
||
}
|
||
});
|
||
|
||
// 确保代理模型按时间降序排序,新数据在顶部
|
||
m_proxyModel->sort(6, Qt::DescendingOrder);
|
||
}
|
||
}
|
||
|
||
// 新增辅助函数
|
||
void QBigOrderViewer::setRowData(int row, QSharedPointer<BigOrderInfo> order)
|
||
{
|
||
m_model->setData(m_model->index(row, 0), order->code);
|
||
m_model->setData(m_model->index(row, 1), order->name);
|
||
m_model->setData(m_model->index(row, 2),
|
||
order->nBigOrderType == 0 ? "卖出" : "买入");
|
||
m_model->setData(m_model->index(row, 3),
|
||
QString::number(std::fabs(order->volume / 1000)) + "K");
|
||
m_model->setData(m_model->index(row, 4),
|
||
QString::number(order->price, 'f', 2));
|
||
m_model->setData(m_model->index(row, 5),
|
||
QString::number(order->level));
|
||
// 确保时间格式正确,用于排序
|
||
QString timeStr;
|
||
if (order->svrRecvTime.length() >= 11) {
|
||
timeStr = order->svrRecvTime.mid(11);
|
||
}
|
||
else {
|
||
QDateTime dateTime = QDateTime::currentDateTime();
|
||
timeStr = dateTime.toString("hh:mm:ss");
|
||
}
|
||
m_model->setData(m_model->index(row, 6), timeStr);
|
||
|
||
// 添加时间戳列(第7列)
|
||
m_model->setData(m_model->index(row, 7), order->svrRecvTime);
|
||
|
||
// 添加一个隐藏的时间戳列用于精确排序
|
||
QDateTime fullDateTime;
|
||
if (order->svrRecvTime.length() >= 19) {
|
||
fullDateTime = QDateTime::fromString(order->svrRecvTime.left(19), "yyyy-MM-dd hh:mm:ss");
|
||
}
|
||
else {
|
||
fullDateTime = QDateTime::currentDateTime();
|
||
}
|
||
m_model->setData(m_model->index(row, 6), fullDateTime, Qt::UserRole + 101); // 使用自定义角色存储完整时间戳
|
||
}
|
||
|
||
bool QBigOrderViewer::matchesFilter(QSharedPointer<BigOrderInfo> order)
|
||
{
|
||
QString stockCode = m_stockCodeFilter->text().trimmed();
|
||
QString orderType = m_orderTypeFilter->currentData().toString();
|
||
|
||
return (stockCode.isEmpty() || order->code.contains(stockCode, Qt::CaseInsensitive))
|
||
&& (orderType.isEmpty() || order->nBigOrderType == orderType);
|
||
}
|
||
|
||
|
||
void QBigOrderViewer::onFilterChanged()
|
||
{
|
||
applyFilters();
|
||
updateView();
|
||
}
|
||
|
||
void QBigOrderViewer::onExportClicked()
|
||
{
|
||
QString fileName = QFileDialog::getSaveFileName(this, "导出CSV", "", "CSV文件 (*.csv)");
|
||
if (fileName.isEmpty()) return;
|
||
|
||
QFile file(fileName);
|
||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return;
|
||
|
||
QTextStream out(&file);
|
||
out.setCodec("GBK");
|
||
|
||
// 写入表头
|
||
for (int col = 0; col < m_model->columnCount(); ++col) {
|
||
out << m_model->headerData(col, Qt::Horizontal).toString();
|
||
if (col < m_model->columnCount() - 1) out << ",";
|
||
}
|
||
out << "\n";
|
||
|
||
// 写入数据
|
||
for (int row = 0; row < m_proxyModel->rowCount(); ++row) {
|
||
for (int col = 0; col < m_proxyModel->columnCount(); ++col) {
|
||
out << m_proxyModel->index(row, col).data().toString();
|
||
if (col < m_proxyModel->columnCount() - 1) out << ",";
|
||
}
|
||
out << "\n";
|
||
}
|
||
file.close();
|
||
}
|