feat: 优化设置/关于窗口与交互体验

- 设置窗口:frameless 自定义标题栏,网卡接口选择,卸载插件功能
- 卸载流程:复制命令到剪贴板,按钮文字临时显示提示并禁用点击
- 关于窗口:独立 Window 在桌面居中,展示作者信息
- 右键菜单:删除刷新项,精简为设置/关于
- 网卡列表:活动接口优先排序,超出宽度水平滚动
- 弹出面板:网卡名+IP 卡片背景,双列速度居中
- 修复 tooltip 数值变 0、右键菜单不展示等问题
This commit is contained in:
2026-07-18 19:25:07 +08:00
parent 009dd9578f
commit 6142dc4769
+224 -74
View File
@@ -34,6 +34,31 @@ AppletItem {
readonly property var networkInterfaces: applet ? (applet.networkInterfaces || []) : [] readonly property var networkInterfaces: applet ? (applet.networkInterfaces || []) : []
readonly property var interfaceStats: applet ? (applet.interfaceStats || []) : [] readonly property var interfaceStats: applet ? (applet.interfaceStats || []) : []
// 判断是否为物理网卡(QML 侧排序用,与 C++ isPhysicalInterface 逻辑一致)
function isPhysicalIf(name) {
return name.startsWith("wlp") || name.startsWith("wlan")
|| name.startsWith("enp") || name.startsWith("eth")
}
// 排序后的接口列表:活动接口最前,其次物理网卡,最后虚拟网卡
// 设计原因:用户希望启动监测的网卡在最左边,方便查看和切换
readonly property var sortedInterfaces: {
if (!root.ready || root.networkInterfaces.length === 0) return []
var physical = []
var virtual = []
for (var i = 0; i < root.networkInterfaces.length; i++) {
var name = root.networkInterfaces[i]
if (name === root.activeInterface) continue
if (root.isPhysicalIf(name)) physical.push(name)
else virtual.push(name)
}
physical.sort()
virtual.sort()
var result = []
if (root.activeInterface) result.push(root.activeInterface)
return result.concat(physical).concat(virtual)
}
// 紧凑格式化速度(用于任务栏图标和 tooltip,不带单位后缀,节省空间) // 紧凑格式化速度(用于任务栏图标和 tooltip,不带单位后缀,节省空间)
// 设计原因:任务栏 ~48px 空间有限,"1.2M" 比 "1.2 MB/s" 节省约一半宽度 // 设计原因:任务栏 ~48px 空间有限,"1.2M" 比 "1.2 MB/s" 节省约一半宽度
// 最小单位为 KB:B 级别也转换为 KB 显示(如 512 B/s -> "0.50K"), // 最小单位为 KB:B 级别也转换为 KB 显示(如 512 B/s -> "0.50K"),
@@ -428,20 +453,35 @@ AppletItem {
} }
} }
// 接口切换 chip 列表(仅多接口时显示,一行等宽分散显示) // 接口切换 chip 列表:水平滚动,活动接口在最左,物理网卡次之,虚拟网卡在后
Row { // chip 少时分散撑满一行;chip 多超出宽度时固定宽度 + 水平滚动
Flickable {
id: chipFlickable
Layout.fillWidth: true Layout.fillWidth: true
spacing: 6 Layout.preferredHeight: 32
visible: root.ready && root.networkInterfaces.length > 1 visible: root.ready && root.networkInterfaces.length > 1
contentWidth: Math.max(chipRow.width, width)
contentHeight: chipRow.height
flickableDirection: Flickable.HorizontalFlick
clip: true
// 等宽分配宽度:假设所有 chip 等宽撑满时的单个宽度
readonly property real equalChipWidth: root.sortedInterfaces.length > 0
? (width - 6 * (root.sortedInterfaces.length - 1)) / root.sortedInterfaces.length
: 0
Row {
id: chipRow
spacing: 6
Repeater { Repeater {
model: root.networkInterfaces model: root.sortedInterfaces
Rectangle { Rectangle {
// 每个 chip 等宽分配,撑满一行分散显示 // 宽度取等宽和自然宽度的较大值:
width: root.networkInterfaces.length > 0 // chip 少时 equalChipWidth > 自然宽度 -> 分散撑满一行
? (parent.width - 6 * (root.networkInterfaces.length - 1)) / root.networkInterfaces.length // chip 多时 自然宽度 > equalChipWidth -> 固定宽度 + 水平滚动
: 0 width: Math.max(chipFlickable.equalChipWidth, chipText.implicitWidth + 24)
height: 28 height: 28
radius: 8 radius: 8
color: modelData === root.activeInterface color: modelData === root.activeInterface
@@ -474,6 +514,11 @@ AppletItem {
} }
} }
ScrollBar.horizontal: ScrollBar {
policy: ScrollBar.AsNeeded
}
}
// 未检测到接口占位 // 未检测到接口占位
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
@@ -524,77 +569,177 @@ AppletItem {
Platform.Menu { Platform.Menu {
id: contextMenu id: contextMenu
Platform.MenuItem {
text: qsTr("刷新")
onTriggered: {
if (root.applet) root.applet.refresh()
}
}
Platform.MenuSeparator {}
Platform.MenuItem { Platform.MenuItem {
text: qsTr("设置") text: qsTr("设置")
onTriggered: settingsWindow.show() onTriggered: {
settingsWindow.show()
settingsWindow.raise()
settingsWindow.requestActivate()
}
} }
Platform.MenuSeparator {} Platform.MenuSeparator {}
Platform.MenuItem { Platform.MenuItem {
text: qsTr("关于") text: qsTr("关于")
onTriggered: aboutDialog.open() onTriggered: {
aboutWindow.show()
aboutWindow.raise()
aboutWindow.requestActivate()
}
} }
} }
// 关于对话框:显示插件信息 // 关于窗口:独立顶层窗口,在桌面中间弹出,展示作者信息
Dialog { // 与设置窗口同样的 frameless + 自定义标题栏模式
id: aboutDialog Window {
anchors.centerIn: parent id: aboutWindow
modal: true width: 320
width: 280 height: 230
height: 160 visible: false
flags: Qt.FramelessWindowHint | Qt.Window
modality: Qt.NonModal
color: "transparent"
background: Rectangle { onVisibleChanged: {
color: root.cardBackground if (visible) {
x = (Screen.width - width) / 2
y = (Screen.height - height) / 2
}
}
Rectangle {
anchors.fill: parent
color: "#f5f5f5"
radius: 12 radius: 12
border.width: 1 border.width: 1
border.color: root.cardBorder border.color: "#e0e0e0"
ColumnLayout {
anchors.fill: parent
anchors.margins: 0
spacing: 0
// 自定义标题栏(可拖动)
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 44
color: "transparent"
MouseArea {
anchors.fill: parent
drag.target: aboutWindow
} }
contentItem: ColumnLayout { Text {
spacing: 8 anchors.left: parent.left
anchors.leftMargin: 16
anchors.verticalCenter: parent.verticalCenter
text: qsTr("关于")
font.pixelSize: 15
font.weight: Font.Bold
color: "#333333"
}
Rectangle {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
width: 28
height: 28
radius: 14
color: aboutCloseMouse.containsMouse ? Qt.rgba(220/255, 38/255, 38/255, 0.15) : "transparent"
Text {
anchors.centerIn: parent
text: "×"
font.pixelSize: 20
font.weight: Font.Bold
color: aboutCloseMouse.containsMouse ? root.accentRed : "#666666"
}
MouseArea {
id: aboutCloseMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: aboutWindow.hide()
}
}
}
// 内容区
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
anchors.margins: 0
spacing: 10
Layout.leftMargin: 24
Layout.rightMargin: 24
Layout.topMargin: 4
// 标题组:插件名 + 英文名紧凑排列
ColumnLayout {
spacing: 2
Layout.alignment: Qt.AlignHCenter
Text { Text {
text: qsTr("网络速度监控") text: qsTr("网络速度监控")
font.pixelSize: 16 font.pixelSize: 18
font.weight: Font.Bold font.weight: Font.Bold
color: root.primaryText color: "#333333"
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
} }
Text {
text: "JNetApplet"
font.pixelSize: 12
color: "#999999"
Layout.alignment: Qt.AlignHCenter
}
}
// 分隔线
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: "#e0e0e0"
}
// 信息行
ColumnLayout {
spacing: 6
Layout.fillWidth: true
Text { Text {
text: qsTr("版本:1.0") text: qsTr("版本:1.0")
font.pixelSize: 12 font.pixelSize: 12
color: root.secondaryText color: "#666666"
Layout.alignment: Qt.AlignHCenter
} }
Text { Text {
text: qsTr("监控网络速度和流量") text: qsTr("作者:Jokul")
font.pixelSize: 11 font.pixelSize: 12
color: root.tertiaryText color: "#666666"
Layout.alignment: Qt.AlignHCenter }
Layout.preferredWidth: 240
horizontalAlignment: Text.AlignHCenter Text {
text: qsTr("描述:监控网络速度和流量")
font.pixelSize: 12
color: "#666666"
Layout.fillWidth: true
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Item { Layout.fillHeight: true } Text {
text: qsTr("仓库:git.jokul.space/Jokul/JNetApplet")
Button { font.pixelSize: 11
text: qsTr("确定") color: "#999999"
Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true
onClicked: aboutDialog.close() wrapMode: Text.WordWrap
}
}
}
} }
} }
} }
@@ -610,6 +755,8 @@ AppletItem {
flags: Qt.FramelessWindowHint | Qt.Window flags: Qt.FramelessWindowHint | Qt.Window
// NonModal:不阻塞桌面其他区域,用户可同时操作任务栏 // NonModal:不阻塞桌面其他区域,用户可同时操作任务栏
modality: Qt.NonModal modality: Qt.NonModal
// 卸载按钮文字:复制命令后临时显示提示,定时器到期后还原
property string uninstallButtonText: qsTr("卸载插件")
// 窗口透明:让圆角外的区域不显示,由内部 Rectangle 提供可见背景 // 窗口透明:让圆角外的区域不显示,由内部 Rectangle 提供可见背景
color: "transparent" color: "transparent"
@@ -760,22 +907,33 @@ AppletItem {
Item { Layout.fillHeight: true } Item { Layout.fillHeight: true }
// 卸载插件按钮 // 卸载插件按钮:文字可动态切换(复制命令后显示提示)
// 提示状态期间按钮不可点击,文字变绿色
Rectangle { Rectangle {
id: uninstallButton id: uninstallButton
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 40 Layout.preferredHeight: 40
color: uninstallMouse.containsMouse ? Qt.rgba(220/255, 38/255, 38/255, 0.15) : Qt.rgba(220/255, 38/255, 38/255, 0.08) // 提示状态时背景和边框变绿
readonly property bool isStatus: settingsWindow.uninstallButtonText !== qsTr("卸载插件")
color: isStatus
? Qt.rgba(22/255, 163/255, 74/255, 0.08)
: (uninstallMouse.containsMouse ? Qt.rgba(220/255, 38/255, 38/255, 0.15) : Qt.rgba(220/255, 38/255, 38/255, 0.08))
radius: 10 radius: 10
border.width: 1 border.width: 1
border.color: root.accentRed border.color: isStatus ? Qt.rgba(22/255, 163/255, 74/255, 1) : root.accentRed
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: qsTr("卸载插件") anchors.left: parent.left
font.pixelSize: 13 anchors.right: parent.right
anchors.margins: 8
text: settingsWindow.uninstallButtonText
font.pixelSize: uninstallButton.isStatus ? 11 : 13
font.weight: Font.Medium font.weight: Font.Medium
color: root.accentRed // 提示状态时文字绿色,否则红色
color: uninstallButton.isStatus ? Qt.rgba(22/255, 163/255, 74/255, 1) : root.accentRed
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
} }
MouseArea { MouseArea {
@@ -783,23 +941,12 @@ AppletItem {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
// 提示状态期间禁用点击
enabled: !uninstallButton.isStatus
onClicked: uninstallConfirmDialog.open() onClicked: uninstallConfirmDialog.open()
} }
} }
// 卸载状态提示(确认后显示,提示用户去终端执行)
Text {
id: uninstallStatus
visible: false
text: qsTr("卸载命令已复制到剪贴板,请在终端中粘贴执行")
font.pixelSize: 11
color: "#16a34a"
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
// 关闭按钮 // 关闭按钮
Rectangle { Rectangle {
id: bottomCloseButton id: bottomCloseButton
@@ -831,13 +978,16 @@ AppletItem {
} }
} }
// 卸载确认对话框 // 卸载确认对话框:在设置窗口内水平居中
Dialog { Dialog {
id: uninstallConfirmDialog id: uninstallConfirmDialog
width: 320 width: 320
height: 260 height: 260
modal: true modal: true
visible: false visible: false
// 在设置窗口中心显示
x: (settingsWindow.width - width) / 2
y: (settingsWindow.height - height) / 2
background: Rectangle { background: Rectangle {
color: "#f5f5f5" color: "#f5f5f5"
@@ -860,7 +1010,7 @@ AppletItem {
} }
Text { Text {
text: qsTr("确定要卸载插件吗?此操作将删除插件文件并重启 dde-shell") text: qsTr("以下命令用于卸载插件并重启 dde-shell,请复制到终端中执行:")
font.pixelSize: 12 font.pixelSize: 12
color: "#333333" color: "#333333"
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@@ -919,7 +1069,7 @@ AppletItem {
} }
} }
// 确认卸载按钮 // 复制命令按钮
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 36 Layout.preferredHeight: 36
@@ -928,7 +1078,7 @@ AppletItem {
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: qsTr("确认卸载") text: qsTr("复制命令")
font.pixelSize: 13 font.pixelSize: 13
font.weight: Font.Medium font.weight: Font.Medium
color: "white" color: "white"
@@ -944,8 +1094,8 @@ AppletItem {
clipboardHelper.selectAll() clipboardHelper.selectAll()
clipboardHelper.copy() clipboardHelper.copy()
uninstallConfirmDialog.close() uninstallConfirmDialog.close()
// 显示状态提示,5 秒后自动隐藏 // 卸载按钮文字临时替换为复制成功提示,5 秒后还原
uninstallStatus.visible = true settingsWindow.uninstallButtonText = qsTr("卸载命令已复制到剪贴板,请在终端中粘贴执行")
statusHideTimer.start() statusHideTimer.start()
} }
} }
@@ -963,11 +1113,11 @@ AppletItem {
text: "sudo rm -rf /usr/share/dde-shell/space.jokul.JNetApplet/ && systemctl --user restart dde-shell@DDE" text: "sudo rm -rf /usr/share/dde-shell/space.jokul.JNetApplet/ && systemctl --user restart dde-shell@DDE"
} }
// 卸载状态提示自动隐藏定时器 // 卸载按钮文字还原定时器:复制命令后 5 秒将按钮文字从提示还原为"卸载插件"
Timer { Timer {
id: statusHideTimer id: statusHideTimer
interval: 5000 interval: 5000
onTriggered: uninstallStatus.visible = false onTriggered: settingsWindow.uninstallButtonText = qsTr("卸载插件")
} }
// 点击处理 // 点击处理