feat(lite): 发布多跳代理跳板 9999091814

This commit is contained in:
RoyirinRosmontis
2026-09-18 14:40:44 +08:00
parent aac616eed9
commit 8063b6d4e8
4 changed files with 702 additions and 24 deletions
+217 -1
View File
@@ -292,6 +292,205 @@ class WhitelistDialog(QDialog):
self.accept()
@dataclass(frozen=True)
class JumpServices:
"""代理跳板管理所需的节点摘要与配置回调。"""
load_icon: object
load_profiles: object
available_nodes: object
add_profile: object
remove_profile: object
class JumpDialog(QDialog):
"""配置最多三跳的节点链路;设置中只保存节点稳定标识。"""
def __init__(self, services: JumpServices, parent=None):
super().__init__(parent)
self._services = services
self.setWindowTitle("代理跳板")
self.setWindowIcon(services.load_icon())
self.setFixedSize(560, 520)
self.setStyleSheet(f"""
QDialog {{ background-color: {C_BG}; }}
QLabel {{ color: {C_TEXT}; font-family: "Microsoft YaHei", "Segoe UI", sans-serif; font-size: 12px; }}
QListWidget {{ background-color: {C_CARD}; border: 1px solid {C_BORDER}; border-radius: 6px;
padding: 4px; color: {C_TEXT}; font-size: 12px; }}
QListWidget::item {{ padding: 5px 7px; border-radius: 4px; }}
QListWidget::item:selected {{ background-color: #e3e8ee; color: {C_TEXT}; }}
QPushButton#btnOk {{ background-color: {C_GREEN}; color: #fff; border: none;
border-radius: 6px; padding: 5px 12px; font-size: 12px; font-weight: bold; }}
QPushButton#btnOk:hover {{ background-color: #5ed978; }}
QPushButton#btnDanger {{ background-color: {C_RED}; color: #fff; border: none;
border-radius: 6px; padding: 5px 12px; font-size: 12px; font-weight: bold; }}
QPushButton#btnSmall {{ background-color: {C_CARD}; color: {C_SUBTEXT}; border: 1px solid {C_BORDER};
border-radius: 6px; padding: 4px 10px; font-size: 11px; }}
QPushButton#btnSmall:hover {{ background-color: #e3e8ee; color: {C_TEXT}; }}
""")
layout = QVBoxLayout(self)
layout.setSpacing(8)
layout.setContentsMargins(18, 14, 18, 14)
title = QLabel("代理跳板")
title.setStyleSheet(f"color: {C_TITLE}; font-size: 15px; font-weight: bold;")
title.setAlignment(Qt.AlignCenter)
layout.addWidget(title)
hint = QLabel("按顺序加入节点,至少 2 个、最多 3 个;最后一个节点是出口")
hint.setStyleSheet(f"color: {C_SUBTEXT}; font-size: 11px;")
hint.setAlignment(Qt.AlignCenter)
layout.addWidget(hint)
saved_label = QLabel("已保存跳板")
saved_label.setStyleSheet(f"color: {C_SUBTEXT}; font-size: 11px;")
layout.addWidget(saved_label)
self.profiles = QListWidget()
self.profiles.setFixedHeight(68)
self.profiles.setToolTip("选择已保存跳板后可删除;新链路在下方配置")
layout.addWidget(self.profiles)
columns = QHBoxLayout()
columns.setSpacing(8)
left = QVBoxLayout()
left.addWidget(QLabel("当前可用节点"))
self.available = QListWidget()
self.available.setToolTip("选择节点后点击“加入链路”")
left.addWidget(self.available, 1)
columns.addLayout(left, 1)
middle = QVBoxLayout()
middle.addStretch()
self.btn_add_node = QPushButton("加入链路 →")
self.btn_add_node.setObjectName("btnSmall")
self.btn_add_node.clicked.connect(self._add_node)
middle.addWidget(self.btn_add_node)
self.btn_remove_node = QPushButton("← 移除")
self.btn_remove_node.setObjectName("btnSmall")
self.btn_remove_node.clicked.connect(self._remove_node)
middle.addWidget(self.btn_remove_node)
middle.addStretch()
columns.addLayout(middle)
right = QVBoxLayout()
right.addWidget(QLabel("新跳板链路"))
self.route = QListWidget()
self.route.setToolTip("顺序即连接顺序;可用上下按钮调整")
right.addWidget(self.route, 1)
reorder = QHBoxLayout()
self.btn_up = QPushButton("上移")
self.btn_up.setObjectName("btnSmall")
self.btn_up.clicked.connect(lambda: self._move_node(-1))
reorder.addWidget(self.btn_up)
self.btn_down = QPushButton("下移")
self.btn_down.setObjectName("btnSmall")
self.btn_down.clicked.connect(lambda: self._move_node(1))
reorder.addWidget(self.btn_down)
right.addLayout(reorder)
columns.addLayout(right, 1)
layout.addLayout(columns, 1)
self.lb_status = QLabel("")
self.lb_status.setStyleSheet(f"color: {C_SUBTEXT}; font-size: 11px;")
self.lb_status.setFixedHeight(16)
layout.addWidget(self.lb_status)
actions = QHBoxLayout()
self.btn_add_profile = QPushButton("添加")
self.btn_add_profile.setObjectName("btnOk")
self.btn_add_profile.clicked.connect(self._add_profile)
actions.addWidget(self.btn_add_profile)
self.btn_delete_profile = QPushButton("删除选中跳板")
self.btn_delete_profile.setObjectName("btnDanger")
self.btn_delete_profile.clicked.connect(self._delete_profile)
actions.addWidget(self.btn_delete_profile)
actions.addStretch()
self.btn_close = QPushButton("关闭")
self.btn_close.setObjectName("btnSmall")
self.btn_close.clicked.connect(self.accept)
actions.addWidget(self.btn_close)
layout.addLayout(actions)
self._reload()
def _reload(self):
self.available.clear()
for node in self._services.available_nodes():
item = QListWidgetItem(node["label"])
item.setData(Qt.UserRole, node["ref"])
self.available.addItem(item)
self.profiles.clear()
for profile in self._services.load_profiles():
state = "可用" if profile.get("available") else "节点不可用"
item = QListWidgetItem(f"{profile['name']} · {profile['route']} · {state}")
item.setData(Qt.UserRole, profile["id"])
self.profiles.addItem(item)
self.route.clear()
self.lb_status.setText("")
def _add_node(self):
item = self.available.currentItem()
if item is None:
self._status("请先选择一个节点", error=True)
return
ref = item.data(Qt.UserRole)
refs = {self.route.item(i).data(Qt.UserRole) for i in range(self.route.count())}
if ref in refs:
self._status("同一条链路不能重复使用节点", error=True)
return
if self.route.count() >= 3:
self._status("最多只能配置 3 个节点", error=True)
return
copy = QListWidgetItem(item.text())
copy.setData(Qt.UserRole, ref)
self.route.addItem(copy)
self._status(f"当前链路:{self.route.count()} 个节点")
def _remove_node(self):
item = self.route.currentItem()
if item is not None:
self.route.takeItem(self.route.row(item))
def _move_node(self, offset):
row = self.route.currentRow()
new_row = row + offset
if row < 0 or not 0 <= new_row < self.route.count():
return
item = self.route.takeItem(row)
self.route.insertItem(new_row, item)
self.route.setCurrentRow(new_row)
def _add_profile(self):
if not 2 <= self.route.count() <= 3:
self._status("请先配置 2-3 个节点的链路", error=True)
return
refs = [self.route.item(i).data(Qt.UserRole) for i in range(self.route.count())]
try:
self._services.add_profile(refs)
except Exception as error:
self._status(str(error), error=True)
return
self._reload()
self._status("跳板已添加")
def _delete_profile(self):
item = self.profiles.currentItem()
if item is None:
self._status("请先选择要删除的跳板", error=True)
return
try:
self._services.remove_profile(item.data(Qt.UserRole))
except Exception as error:
self._status(str(error), error=True)
return
self._reload()
self._status("跳板已删除")
def _status(self, message, error=False):
self.lb_status.setText(message)
color = C_RED if error else C_GREEN
self.lb_status.setStyleSheet(f"color: {color}; font-size: 11px;")
@dataclass(frozen=True)
class ThirdPartyServices:
"""第三方订阅管理框所需的显式业务服务。"""
@@ -475,6 +674,7 @@ class SettingsServices:
set_update_via_proxy: object
set_skip_update_check: object
open_whitelist: object
open_jump: object
open_third_party: object
has_login: object
open_login: object
@@ -489,7 +689,7 @@ class SettingsDialog(QDialog):
self._services = services
self.setWindowTitle("设置")
self.setWindowIcon(services.load_icon())
self.setFixedSize(300, 396)
self.setFixedSize(300, 430)
self.setStyleSheet(f"""
QDialog {{ background-color: {C_BG}; }}
QLabel {{ color: {C_TEXT}; font-family: "Microsoft YaHei", "Segoe UI", sans-serif; font-size: 12px; }}
@@ -544,6 +744,19 @@ class SettingsDialog(QDialog):
whitelist_row.addWidget(self.btn_whitelist)
layout.addLayout(whitelist_row)
jump_row = QHBoxLayout()
jump_label = QLabel("代理跳板")
jump_label.setStyleSheet(f"color: {C_SUBTEXT}; font-size: 12px;")
jump_label.setToolTip("把当前可用节点串成 2-3 跳链路;最后一个节点作为对外出口")
jump_row.addWidget(jump_label)
jump_row.addStretch()
self.btn_jump = QPushButton("添加")
self.btn_jump.setCursor(Qt.PointingHandCursor)
self.btn_jump.setStyleSheet(self.btn_whitelist.styleSheet())
self.btn_jump.clicked.connect(self._open_jump)
jump_row.addWidget(self.btn_jump)
layout.addLayout(jump_row)
third_party_row = QHBoxLayout()
third_party_label = QLabel("第三方")
third_party_label.setStyleSheet(f"color: {C_SUBTEXT}; font-size: 12px;")
@@ -636,6 +849,9 @@ class SettingsDialog(QDialog):
def _open_whitelist(self):
self._services.open_whitelist(self)
def _open_jump(self):
self._services.open_jump(self)
def _open_third_party(self):
self._services.open_third_party(self)