From 8b5146338772599b3310cbd0fa27a3861827966d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=B9=8F?= Date: Thu, 9 Jul 2026 11:57:15 +0800 Subject: [PATCH] refactor: use tabs for selected instrument tables --- src/grid_trading/ui/main_window.py | 52 ++++++++---------------------- tests/test_ui.py | 33 ++++++++++++++++--- 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/src/grid_trading/ui/main_window.py b/src/grid_trading/ui/main_window.py index f9a5e21..a0643d0 100644 --- a/src/grid_trading/ui/main_window.py +++ b/src/grid_trading/ui/main_window.py @@ -9,7 +9,6 @@ from PySide6.QtWidgets import ( QDialog, QFrame, QGridLayout, - QGroupBox, QHBoxLayout, QHeaderView, QLabel, @@ -19,6 +18,7 @@ from PySide6.QtWidgets import ( QPushButton, QSplitter, QSpinBox, + QTabWidget, QTableWidget, QTableWidgetItem, QVBoxLayout, @@ -128,26 +128,10 @@ class MainWindow(QMainWindow): self.holdings_table.itemSelectionChanged.connect(self._on_holding_selected) splitter.addWidget(self.holdings_table) - details = QSplitter(Qt.Orientation.Horizontal) - self.detail_box = QGroupBox("选中标的详情") - self.detail_layout = QGridLayout(self.detail_box) - self.detail_labels = { - "price_source": QLabel("-"), - "cost": QLabel("-"), - "breakeven": QLabel("-"), - "profit": QLabel("-"), - } - self.detail_layout.addWidget(QLabel("价格来源"), 0, 0) - self.detail_layout.addWidget(self.detail_labels["price_source"], 0, 1) - self.detail_layout.addWidget(QLabel("剩余成本"), 1, 0) - self.detail_layout.addWidget(self.detail_labels["cost"], 1, 1) - self.detail_layout.addWidget(QLabel("回本价"), 2, 0) - self.detail_layout.addWidget(self.detail_labels["breakeven"], 2, 1) - self.detail_layout.addWidget(QLabel("利润"), 3, 0) - self.detail_layout.addWidget(self.detail_labels["profit"], 3, 1) + self.details_tabs = QTabWidget() - grid_group = QGroupBox("网格档位") - grid_layout = QVBoxLayout(grid_group) + grid_tab = QWidget() + grid_layout = QVBoxLayout(grid_tab) grid_controls = QHBoxLayout() self.grid_levels_count_edit = QSpinBox() self.grid_levels_count_edit.setRange(1, 100) @@ -166,8 +150,8 @@ class MainWindow(QMainWindow): grid_layout.addLayout(grid_controls) grid_layout.addWidget(self.grid_levels_table) - open_grid_group = QGroupBox("待卖网格") - open_grid_layout = QVBoxLayout(open_grid_group) + open_grid_tab = QWidget() + open_grid_layout = QVBoxLayout(open_grid_tab) self.open_grid_lots_table = QTableWidget(0, len(self.OPEN_GRID_LOT_COLUMNS)) self.open_grid_lots_table.setHorizontalHeaderLabels(self.OPEN_GRID_LOT_COLUMNS) self.open_grid_lots_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents) @@ -175,8 +159,8 @@ class MainWindow(QMainWindow): self.open_grid_lots_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) open_grid_layout.addWidget(self.open_grid_lots_table) - trade_group = QGroupBox("最近成交") - trade_layout = QVBoxLayout(trade_group) + trade_tab = QWidget() + trade_layout = QVBoxLayout(trade_tab) trade_buttons = QHBoxLayout() edit_trade_button = QPushButton("编辑成交") edit_trade_button.clicked.connect(self._edit_selected_trade) @@ -193,11 +177,11 @@ class MainWindow(QMainWindow): trade_layout.addLayout(trade_buttons) trade_layout.addWidget(self.trades_table) - details.addWidget(self.detail_box) - details.addWidget(grid_group) - details.addWidget(open_grid_group) - details.addWidget(trade_group) - splitter.addWidget(details) + self.details_tabs.addTab(grid_tab, "网格档位") + self.details_tabs.addTab(open_grid_tab, "待卖网格") + self.details_tabs.addTab(trade_tab, "最近成交") + self.details_tabs.setCurrentIndex(1) + splitter.addWidget(self.details_tabs) splitter.setSizes([470, 230]) content_layout.addWidget(splitter) @@ -341,20 +325,10 @@ class MainWindow(QMainWindow): def _refresh_details(self) -> None: position = self._selected_position() if position is None: - for label in self.detail_labels.values(): - label.setText("-") self._refresh_grid_levels(None) self._refresh_open_grid_lots(None) self._fill_trades_table([]) return - self.detail_labels["price_source"].setText(position.price_source) - self.detail_labels["cost"].setText(format_money(position.remaining_cost)) - self.detail_labels["breakeven"].setText( - f"持仓 {format_price(position.position_breakeven_price)} / 账户 {format_price(position.account_breakeven_price)}" - ) - self.detail_labels["profit"].setText( - f"已实现 {format_money(position.realized_pnl)} / 网格 {format_money(position.grid_profit)}" - ) self._refresh_grid_levels(position) self._refresh_open_grid_lots(position) self._fill_trades_table(self.service.list_trades(instrument_id=position.instrument_id)) diff --git a/tests/test_ui.py b/tests/test_ui.py index 0dcea4a..386fd1a 100644 --- a/tests/test_ui.py +++ b/tests/test_ui.py @@ -38,7 +38,7 @@ def test_main_window_can_be_constructed_offscreen(tmp_path, monkeypatch): def test_main_window_contains_grid_level_table(tmp_path, monkeypatch): monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen") - from PySide6.QtWidgets import QApplication, QGroupBox + from PySide6.QtWidgets import QApplication, QTabWidget from grid_trading.services.trading_service import TradingService from grid_trading.ui.main_window import MainWindow @@ -48,7 +48,9 @@ def test_main_window_contains_grid_level_table(tmp_path, monkeypatch): window = MainWindow(service) assert window.grid_levels_table.columnCount() == 7 - assert any(group.title() == "网格档位" for group in window.findChildren(QGroupBox)) + tab_titles = _tab_titles(window.details_tabs) + assert tab_titles == ["网格档位", "待卖网格", "最近成交"] + assert isinstance(window.details_tabs, QTabWidget) window.close() service.close() @@ -58,6 +60,26 @@ def test_main_window_contains_grid_level_table(tmp_path, monkeypatch): def test_main_window_contains_open_grid_lots_table(tmp_path, monkeypatch): monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen") + from PySide6.QtWidgets import QApplication + + from grid_trading.services.trading_service import TradingService + from grid_trading.ui.main_window import MainWindow + + app = QApplication.instance() or QApplication([]) + service = TradingService(tmp_path / "grid.db") + window = MainWindow(service) + + assert window.open_grid_lots_table.columnCount() == 8 + assert "待卖网格" in _tab_titles(window.details_tabs) + + window.close() + service.close() + app.processEvents() + + +def test_main_window_removes_selected_instrument_detail_group(tmp_path, monkeypatch): + monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen") + from PySide6.QtWidgets import QApplication, QGroupBox from grid_trading.services.trading_service import TradingService @@ -67,8 +89,7 @@ def test_main_window_contains_open_grid_lots_table(tmp_path, monkeypatch): service = TradingService(tmp_path / "grid.db") window = MainWindow(service) - assert window.open_grid_lots_table.columnCount() == 8 - assert any(group.title() == "待卖网格" for group in window.findChildren(QGroupBox)) + assert not any(group.title() == "选中标的详情" for group in window.findChildren(QGroupBox)) window.close() service.close() @@ -116,6 +137,10 @@ def test_trade_dialog_labels_price_as_trade_price(tmp_path, monkeypatch): app.processEvents() +def _tab_titles(tabs): + return [tabs.tabText(index) for index in range(tabs.count())] + + def test_quote_refresh_does_not_block_main_window(tmp_path, monkeypatch): monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen")