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