Files
JNetApplet/docs/superpowers/plans/2026-07-18-ui-redesign.md
T
Jokul 009dd9578f feat: 重新设计插件 UI 样式与交互
- 任务栏图标:双行数值显示(下载蓝/上传绿),固定宽度避免抖动
- 左键弹出面板:360×320 居中布局,双列速度卡片,网卡名+IP 卡片背景
- 右键菜单:deepin 风格 Platform.Menu + MenuHelper(刷新/设置/关于)
- 设置窗口:frameless 自定义标题栏,网卡接口选择,卸载插件功能
- Tooltip:网卡名 + IP 地址单行展示
- C++ 后端:新增 ipAddress 属性(QNetworkInterface),优先选择物理网卡
- 速度格式化:最小单位 KB,2 位小数
- 移除 QML 侧多余 refresh 调用,修复 tooltip 数值变 0
- 新增设计文档与实现计划
2026-07-18 18:44:12 +08:00

842 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# JNetApplet UI 重新设计实现计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 重写 `package/networkview.qml`,实现 deepin 原生简约风格的任务栏图标、左键弹出面板、右键菜单和 Tooltip。
**Architecture:** 单文件 QML 重写,不动 C++ 后端。颜色派生自 `DockPalette.iconTextPalette`,深/浅色自适应。新增 `formatSpeedShort`、右键 `Menu`、chip 接口切换器。
**Tech Stack:** QML 2.15、QtQuick.Controls 2.15、QtQuick.Layouts 1.15、org.deepin.ds 1.0、org.deepin.ds.dock 1.0、org.deepin.dtk 1.0。
## Global Constraints
- 仅修改 `package/networkview.qml`,不改 C++、`metadata.json``CMakeLists.txt`
- 保留 SPDX 文件头与所有 imports
- Root 元素为 `AppletItem`(来自 `import org.deepin.ds 1.0`
- QML 属性类型用 `double`/`real`,不用 `qint64`
- 注释统一中文
- 项目无测试/lint/CI,验证方式:`cmake --build build` + 手动安装重启 dde-shell
- Commit 中文 conventional commits,仅在被显式请求时提交
**Spec 引用:** `docs/superpowers/specs/2026-07-18-ui-redesign-design.md`
---
## File Structure
仅修改 `package/networkview.qml`(当前 521 行)。无新文件、无 C++ 改动。
重写后内部结构:
```
AppletItem (root)
├── 属性区:applet 绑定、颜色派生、formatSpeedShort
├── 任务栏图标区:Column { Row(↓值), Row(↑值) }
├── PanelToolTip
├── Timer × 2
├── HoverHandler
├── PanelPopup
│ └── Control > ColumnLayout
│ ├── 标题栏(标题+副标题+刷新按钮)
│ ├── 实时速度卡片(双列)
│ ├── 总量统计(单行)
│ ├── 接口切换 chip 列表
│ └── 未检测到接口占位
├── Menu (contextMenu) ← 新增
├── Dialog (aboutDialog) ← 新增
├── TapHandler (左键)
└── TapHandler (右键) ← 新增
```
---
## Task 1: 属性区与格式化函数(基础层)
**Files:** Modify `package/networkview.qml:32-72`
**Interfaces:**
- Produces: `formatSpeedShort()``accentGreen``accentGreenLight``accentOrange``accentRed``downloadValueColor``uploadValueColor` - 后续任务全部依赖
**说明:** 替换 root 的函数块和颜色属性块。保留所有 `applet` 绑定属性不变。
- [ ] **Step 1: 替换函数区(原 32-56 行)**
`// 格式化速度显示` 注释到 `formatTotal` 函数结束替换为:
```qml
// 紧凑格式化速度(用于任务栏图标和 tooltip,不带单位后缀,节省空间)
// 设计原因:任务栏 ~48px 空间有限,"1.2M" 比 "1.2 MB/s" 节省约一半宽度
function formatSpeedShort(bytesPerSec) {
if (bytesPerSec < 1024) {
return bytesPerSec.toFixed(0)
} else if (bytesPerSec < 1024 * 1024) {
return (bytesPerSec / 1024).toFixed(1) + "K"
} else if (bytesPerSec < 1024 * 1024 * 1024) {
return (bytesPerSec / (1024 * 1024)).toFixed(1) + "M"
} else {
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + "G"
}
}
// 格式化速度显示(带单位,用于弹出面板,信息更完整)
function formatSpeed(bytesPerSec) {
if (bytesPerSec < 1024) {
return bytesPerSec.toFixed(0) + " B/s"
} else if (bytesPerSec < 1024 * 1024) {
return (bytesPerSec / 1024).toFixed(1) + " KB/s"
} else if (bytesPerSec < 1024 * 1024 * 1024) {
return (bytesPerSec / (1024 * 1024)).toFixed(2) + " MB/s"
} else {
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(2) + " GB/s"
}
}
// 格式化总量显示(用于弹出面板的累计统计)
function formatTotal(bytes) {
if (bytes < 1024) {
return bytes.toFixed(0) + " B"
} else if (bytes < 1024 * 1024) {
return (bytes / 1024).toFixed(1) + " KB"
} else if (bytes < 1024 * 1024 * 1024) {
return (bytes / (1024 * 1024)).toFixed(1) + " MB"
} else {
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB"
}
}
```
- [ ] **Step 2: 替换颜色属性区(原 58-72 行)**
`// 根据速度计算图标颜色` 注释到 `accentBlueLight` 属性结束替换为:
```qml
// 任务栏与面板的颜色全部派生自 DockPalette.iconTextPalette
// 这样在深色/浅色任务栏下自动适配,无需手动判断主题
property Palette basePalette: DockPalette.iconTextPalette
readonly property color primaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.95)
readonly property color secondaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.80)
readonly property color tertiaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.65)
readonly property color cardBackground: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.06)
readonly property color cardBorder: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.10)
// 强调色:下载蓝、上传绿,是面板与任务栏的主视觉区分
readonly property color accentBlue: Qt.rgba(20 / 255, 80 / 255, 160 / 255, 1)
readonly property color accentBlueLight: Qt.rgba(20 / 255, 80 / 255, 160 / 255, 0.12)
readonly property color accentGreen: Qt.rgba(22 / 255, 163 / 255, 74 / 255, 1)
readonly property color accentGreenLight: Qt.rgba(22 / 255, 163 / 255, 74 / 255, 0.12)
// 高速警示色:下载速度超过阈值时由蓝转橙再转红
readonly property color accentOrange: Qt.rgba(245 / 255, 158 / 255, 11 / 255, 1)
readonly property color accentRed: Qt.rgba(220 / 255, 38 / 255, 38 / 255, 1)
// 下载值颜色:保留原有阈值逻辑(>10MB/s 红、>1MB/s 橙、否则蓝),仅作用于下载值
readonly property color downloadValueColor: {
if (downloadSpeed > 10 * 1024 * 1024) return accentRed
if (downloadSpeed > 1 * 1024 * 1024) return accentOrange
return accentBlue
}
// 上传值颜色:固定绿色,不做高速警示
readonly property color uploadValueColor: accentGreen
```
- [ ] **Step 3: 验证标识符已定义**
Run: `grep -n "formatSpeedShort\|downloadValueColor\|uploadValueColor\|accentGreen " /home/Jokul/Documents/projects/JNetApplet/package/networkview.qml`
Expected: 每个标识符至少出现一次。
- [ ] **Step 4: 验证 C++ 仍可构建**
Run: `cmake --build /home/Jokul/Documents/projects/JNetApplet/build 2>&1 | tail -5`
Expected: 构建成功,无 error。
---
## Task 2: 任务栏图标区重写
**Files:** Modify `package/networkview.qml`(原 74-120 行的圆圈 `Rectangle`
**Interfaces:**
- Consumes: `formatSpeedShort()``downloadValueColor``uploadValueColor``secondaryText``dockSize`
- Produces: 任务栏双行数值显示
- [ ] **Step 1: 替换图标区域**
`// 图标区域` 注释到对应 `Rectangle` 闭合 `}` 替换为:
```qml
// 任务栏图标区:双行紧凑数值,移除原圆圈边框
// 上行下载、下行上传,箭头为次级色、数值为强调色
// 设计原因:圆圈边框在 ~48px dock 内压缩可用空间,去掉后数值字号可放大提升可读性
Column {
anchors.centerIn: parent
spacing: 1
// 下载速度行
Row {
spacing: 2
anchors.horizontalCenter: parent.horizontalCenter
Text {
text: "↓"
font.pixelSize: root.dockSize * 0.16
color: root.secondaryText
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: root.formatSpeedShort(root.downloadSpeed)
font.pixelSize: root.dockSize * 0.22
font.weight: Font.Medium
color: root.downloadValueColor
anchors.verticalCenter: parent.verticalCenter
}
}
// 上传速度行
Row {
spacing: 2
anchors.horizontalCenter: parent.horizontalCenter
Text {
text: "↑"
font.pixelSize: root.dockSize * 0.16
color: root.secondaryText
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: root.formatSpeedShort(root.uploadSpeed)
font.pixelSize: root.dockSize * 0.22
font.weight: Font.Medium
color: root.uploadValueColor
anchors.verticalCenter: parent.verticalCenter
}
}
}
```
- [ ] **Step 2: 确认旧 `iconColor` 已无引用**
Run: `grep -n "iconColor" /home/Jokul/Documents/projects/JNetApplet/package/networkview.qml`
Expected: 无输出。
- [ ] **Step 3: 安装并目视验证**
Run: `sudo cmake --install /home/Jokul/Documents/projects/JNetApplet/build && systemctl --user restart dde-shell@DDE`
Expected: 任务栏图标显示双行「↓数值 / ↑数值」,下载蓝、上传绿,无圆圈边框。
---
## Task 3: Tooltip 文本更新
**Files:** Modify `package/networkview.qml``buildToolTipText` 函数)
**Interfaces:**
- Consumes: `formatSpeedShort()``activeInterface``ready`
**说明:** Tooltip 改用 `formatSpeedShort` 与任务栏一致。`PanelToolTip.text` 是纯文本,不支持富文本着色,箭头无法单独着色。
- [ ] **Step 1: 替换 buildToolTipText 函数体**
```qml
// 构建 tooltip 文本:单行紧凑展示下载/上传速度和活动接口
// 设计原因:tooltip 空间有限,用 formatSpeedShort 与任务栏保持视觉一致
// 用 "·" 分隔速度区与接口名,比 " | " 更轻量
function buildToolTipText() {
if (!root.ready) {
return qsTr("未检测到网络接口")
}
var lines = []
lines.push("↓ " + root.formatSpeedShort(root.downloadSpeed))
lines.push("↑ " + root.formatSpeedShort(root.uploadSpeed))
lines.push(root.activeInterface)
return lines.join(" · ")
}
```
- [ ] **Step 2: 确认 PanelToolTip 未就绪文案为中文**
确认 `PanelToolTip``text` 属性为:
```qml
PanelToolTip {
id: toolTip
text: root.ready ? root.buildToolTipText() : qsTr("未检测到网络接口")
toolTipX: DockPanelPositioner.x
toolTipY: DockPanelPositioner.y
}
```
若为英文 `"No network interface detected"`,替换为上述中文。
- [ ] **Step 3: 安装并目视验证**
Run: `sudo cmake --install /home/Jokul/Documents/projects/JNetApplet/build && systemctl --user restart dde-shell@DDE`
Expected: 悬停时 tooltip 显示形如 `↓ 1.2M · ↑ 256K · eth0`
---
## Task 4: 左键弹出面板重写
**Files:** Modify `package/networkview.qml``PanelPopup` 内部,原 173-489 行)
**Interfaces:**
- Consumes: `formatSpeed``formatTotal``primaryText``secondaryText``tertiaryText``cardBackground``cardBorder``accentBlue``accentBlueLight``accentGreen``accentGreenLight``downloadValueColor``uploadValueColor``ready``activeInterface``downloadSpeed``uploadSpeed``totalDownload``totalUpload``networkInterfaces``applet`
- Produces: 360×320 重构面板
**说明:** 尺寸从 400×350 改为 360×320。结构:标题栏(含副标题)+ 实时速度双列卡片 + 总量单行 + chip 接口切换 + 未就绪占位。
- [ ] **Step 1: 调整 PanelPopup 尺寸**
`PanelPopup``width: 400` / `height: 350` 改为 `width: 360` / `height: 320`。保留 `popupX`/`popupY`/`onPopupVisibleChanged` 不变。
- [ ] **Step 2: 替换 Control 及其 contentItem 全部内容**
`PanelPopup` 内的 `Control { id: popupContainer ... }` 整体替换为:
```qml
Control {
id: popupContainer
anchors.fill: parent
padding: 16
contentItem: ColumnLayout {
spacing: 12
// 标题栏:左标题+副标题,右刷新按钮
RowLayout {
Layout.fillWidth: true
spacing: 8
ColumnLayout {
spacing: 0
Text {
text: qsTr("网络速度监控")
font.pixelSize: 15
font.weight: Font.Bold
color: root.primaryText
}
Text {
text: root.ready ? root.activeInterface : qsTr("未连接")
font.pixelSize: 11
color: root.secondaryText
visible: root.ready
}
}
Item { Layout.fillWidth: true }
// 刷新按钮:hover 高亮 + 旋转动画
Rectangle {
width: 28
height: 28
radius: 6
color: refreshIconArea.containsMouse ? root.accentBlueLight : "transparent"
Text {
anchors.centerIn: parent
text: "⟳"
font.pixelSize: 16
color: refreshIconArea.containsMouse ? root.accentBlue : root.secondaryText
}
MouseArea {
id: refreshIconArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.applet) root.applet.refresh()
refreshSpin.start()
}
}
RotationAnimator on rotation {
id: refreshSpin
running: false
from: 0
to: 360
duration: 500
}
}
}
// 实时速度卡片:双列布局,主视觉
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 110
color: root.cardBackground
radius: 12
border.width: 1
border.color: root.cardBorder
RowLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 0
// 下载列
ColumnLayout {
Layout.fillWidth: true
spacing: 4
anchors.verticalCenter: parent.verticalCenter
Rectangle {
width: 32
height: 32
radius: 16
color: root.accentBlueLight
Layout.alignment: Qt.AlignHCenter
Text {
anchors.centerIn: parent
text: "↓"
font.pixelSize: 18
font.weight: Font.Bold
color: root.accentBlue
}
}
Text {
text: qsTr("下载")
font.pixelSize: 11
color: root.secondaryText
Layout.alignment: Qt.AlignHCenter
}
Text {
text: root.formatSpeed(root.downloadSpeed)
font.pixelSize: 18
font.weight: Font.Bold
color: root.downloadValueColor
Layout.alignment: Qt.AlignHCenter
}
}
// 分隔线
Rectangle {
width: 1
Layout.fillHeight: true
color: root.cardBorder
}
// 上传列
ColumnLayout {
Layout.fillWidth: true
spacing: 4
anchors.verticalCenter: parent.verticalCenter
Rectangle {
width: 32
height: 32
radius: 16
color: root.accentGreenLight
Layout.alignment: Qt.AlignHCenter
Text {
anchors.centerIn: parent
text: "↑"
font.pixelSize: 18
font.weight: Font.Bold
color: root.accentGreen
}
}
Text {
text: qsTr("上传")
font.pixelSize: 11
color: root.secondaryText
Layout.alignment: Qt.AlignHCenter
}
Text {
text: root.formatSpeed(root.uploadSpeed)
font.pixelSize: 18
font.weight: Font.Bold
color: root.uploadValueColor
Layout.alignment: Qt.AlignHCenter
}
}
}
}
// 总量统计:单行紧凑
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 44
color: root.cardBackground
radius: 12
border.width: 1
border.color: root.cardBorder
visible: root.ready
RowLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 8
Text {
text: qsTr("总计")
font.pixelSize: 11
font.weight: Font.Bold
color: root.secondaryText
}
Text {
text: "↓ " + root.formatTotal(root.totalDownload)
font.pixelSize: 12
color: root.primaryText
}
Item { Layout.fillWidth: true }
Text {
text: "↑ " + root.formatTotal(root.totalUpload)
font.pixelSize: 12
color: root.primaryText
}
}
}
// 接口切换 chip 列表(仅多接口时显示)
Flow {
Layout.fillWidth: true
spacing: 6
visible: root.ready && root.networkInterfaces.length > 1
Repeater {
model: root.networkInterfaces
Rectangle {
width: chipText.implicitWidth + 20
height: 28
radius: 8
color: modelData === root.activeInterface
? root.accentBlue
: (chipMouse.containsMouse ? root.accentBlueLight : root.cardBackground)
border.width: 1
border.color: modelData === root.activeInterface
? root.accentBlue
: root.cardBorder
Text {
id: chipText
anchors.centerIn: parent
text: modelData
font.pixelSize: 11
font.weight: modelData === root.activeInterface ? Font.Bold : Font.Normal
color: modelData === root.activeInterface ? "white" : root.primaryText
}
MouseArea {
id: chipMouse
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
if (root.applet) root.applet.setActiveInterface(modelData)
}
}
}
}
}
// 未检测到接口占位
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 80
color: root.cardBackground
radius: 12
border.width: 1
border.color: root.cardBorder
visible: !root.ready
ColumnLayout {
anchors.centerIn: parent
spacing: 6
Text {
text: "⚠"
font.pixelSize: 24
color: root.secondaryText
Layout.alignment: Qt.AlignHCenter
}
Text {
text: qsTr("未检测到网络接口")
font.pixelSize: 12
color: root.secondaryText
Layout.alignment: Qt.AlignHCenter
}
}
}
Item { Layout.fillHeight: true }
}
}
```
- [ ] **Step 3: 确认 `Component.onCompleted` 中 `DockPanelPositioner.bounding` 绑定保留**
`PanelPopup` 末尾的 `Component.onCompleted` 块保留不变(用于定位 popup 位置)。
- [ ] **Step 4: 安装并目视验证**
Run: `sudo cmake --install /home/Jokul/Documents/projects/JNetApplet/build && systemctl --user restart dde-shell@DDE`
Expected:
- 左键点击任务栏图标,弹出 360×320 面板
- 标题栏显示「网络速度监控」+ 接口名副标题
- 实时速度卡片双列:下载蓝、上传绿,各带圆形图标底
- 总量统计单行显示「总计 ↓ X · ↑ Y」
- 多接口时显示 chip 列表,当前接口高亮蓝色
- 未检测到接口时显示占位卡片
---
## Task 5: 右键菜单与关于对话框
**Files:** Modify `package/networkview.qml`(新增 `Menu``Dialog`、右键 `TapHandler`,替换原左键 `TapHandler`
**Interfaces:**
- Consumes: `applet.refresh()``applet.setActiveInterface()``networkInterfaces``activeInterface``primaryText``secondaryText``accentBlue`
- Produces: 右键弹出 deepin 风格菜单
**说明:**`PanelPopup` 之后、原左键 `TapHandler` 之前,新增 `Menu``Dialog`。在原左键 `TapHandler` 之后新增右键 `TapHandler`
- [ ] **Step 1: 新增 contextMenu 和 aboutDialog**
`PanelPopup` 闭合 `}` 之后,原 `// 构建 tooltip 文本` 注释之前,插入:
```qml
// 右键菜单:deepin 风格 Menu,提供刷新/切换接口/关于快捷操作
Menu {
id: contextMenu
MenuItem {
text: qsTr("刷新")
onTriggered: {
if (root.applet) root.applet.refresh()
}
}
MenuSeparator {}
// 切换接口子菜单:动态构建可用接口列表
Menu {
title: qsTr("切换接口")
enabled: root.networkInterfaces.length > 1
Instantiator {
model: root.networkInterfaces
delegate: MenuItem {
text: modelData
checkable: true
checked: modelData === root.activeInterface
onTriggered: {
if (root.applet) root.applet.setActiveInterface(modelData)
}
}
onObjectAdded: (index, object) => contextMenu.children[2].insertItem(index, object)
onObjectRemoved: (index, object) => contextMenu.children[2].removeItem(object)
}
}
MenuSeparator {}
MenuItem {
text: qsTr("关于")
onTriggered: aboutDialog.open()
}
}
// 关于对话框:显示插件信息
Dialog {
id: aboutDialog
anchors.centerIn: parent
modal: true
width: 280
height: 160
background: Rectangle {
color: root.cardBackground
radius: 12
border.width: 1
border.color: root.cardBorder
}
contentItem: ColumnLayout {
spacing: 8
Text {
text: qsTr("网络速度监控")
font.pixelSize: 16
font.weight: Font.Bold
color: root.primaryText
Layout.alignment: Qt.AlignHCenter
}
Text {
text: qsTr("版本:1.0")
font.pixelSize: 12
color: root.secondaryText
Layout.alignment: Qt.AlignHCenter
}
Text {
text: qsTr("监控网络速度和流量")
font.pixelSize: 11
color: root.tertiaryText
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: 240
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
Item { Layout.fillHeight: true }
Button {
text: qsTr("确定")
Layout.alignment: Qt.AlignHCenter
onClicked: aboutDialog.close()
}
}
}
```
- [ ] **Step 2: 新增右键 TapHandler**
在原左键 `TapHandler``acceptedButtons: Qt.LeftButton`)之后,文件末尾的 root 闭合 `}` 之前,插入:
```qml
// 右键点击:弹出上下文菜单
TapHandler {
acceptedButtons: Qt.RightButton
gesturePolicy: TapHandler.ReleaseWithinBounds
onTapped: {
// 关闭可能打开的 popup,避免视觉冲突
if (networkPopup.popupVisible) networkPopup.close()
toolTip.close()
// 使用 root 中心点定位菜单
contextMenu.popup()
}
}
```
- [ ] **Step 3: 确认 Instantiator 子菜单挂载点正确**
Run: `grep -n "Instantiator\|onObjectAdded" /home/Jokul/Documents/projects/JNetApplet/package/networkview.qml`
Expected: 出现一次 Instantiator 块。
> 注:Instantiator 的 `onObjectAdded`/`onObjectRemoved` 中 `contextMenu.children[2]` 是切换接口子菜单(菜单顺序:刷新 MenuItem / MenuSeparator / 切换接口 Menu / MenuSeparator / 关于 MenuItem)。若 QtQuick.Controls 版本不支持 `insertItem`/`removeItem`,可改用 `Menu` 的 `MenuItem` 直接列出(无 Instantiator),通过 `Instantiator` 的 `onObjectAdded` 调用 `parent.addItem(object)` 或直接在子 Menu 内用 `Repeater`+`MenuItem` 写法。验证时如报错,将子菜单改为:
```qml
Menu {
title: qsTr("切换接口")
enabled: root.networkInterfaces.length > 1
Repeater {
model: root.networkInterfaces
delegate: MenuItem {
text: modelData
checkable: true
checked: modelData === root.activeInterface
onTriggered: {
if (root.applet) root.applet.setActiveInterface(modelData)
}
}
}
}
```
(两种写法二选一,优先用 Repeater 版本更稳。)
- [ ] **Step 4: 安装并目视验证**
Run: `sudo cmake --install /home/Jokul/Documents/projects/JNetApplet/build && systemctl --user restart dde-shell@DDE`
Expected:
- 右键点击任务栏图标,弹出 deepin 风格菜单
- 菜单项:刷新 / 切换接口(子菜单)/ 关于
- 切换接口子菜单列出所有接口,当前接口打勾
- 接口数 ≤ 1 时「切换接口」项禁用(灰色)
- 点击「关于」弹出对话框显示版本信息
---
## Task 6: 最终验证清单
**Files:** 无改动,仅验证
**说明:** 按 spec 验证清单逐项检查。
- [ ] **Step 1: 重新构建并安装**
Run: `cmake --build /home/Jokul/Documents/projects/JNetApplet/build && sudo cmake --install /home/Jokul/Documents/projects/JNetApplet/build && systemctl --user restart dde-shell@DDE`
Expected: 构建成功、安装成功、dde-shell 重启无报错。
- [ ] **Step 2: 任务栏图标验证**
逐项检查:
- [ ] 双行数值显示,下载在上、上传在下
- [ ] 颜色正确(下载蓝、上传绿)
- [ ] 高速时下载值变橙(>1MB/s/红(>10MB/s
- [ ] 深色任务栏下文字可读
- [ ] 浅色任务栏下文字可读
- [ ] 无圆圈边框
- [ ] **Step 3: Tooltip 验证**
悬停任务栏图标:
- [ ] 单行显示 `↓ X · ↑ Y · 接口名`
- [ ] 未检测到接口时显示「未检测到网络接口」
- [ ] **Step 4: 左键弹出面板验证**
左键点击任务栏图标:
- [ ] 尺寸 360×320
- [ ] 标题栏:「网络速度监控」+ 接口名副标题
- [ ] 实时速度双列卡片:下载蓝、上传绿,圆形图标底
- [ ] 总量统计单行:「总计 ↓ X · ↑ Y」
- [ ] 多接口时显示 chip 列表,当前接口高亮
- [ ] 刷新按钮 hover 高亮 + 旋转动画
- [ ] 未检测到接口时显示占位卡片
- [ ] **Step 5: 右键菜单验证**
右键点击任务栏图标:
- [ ] 弹出 deepin 风格菜单
- [ ] 刷新项可用,点击后数据更新
- [ ] 切换接口子菜单列出接口、当前项打勾
- [ ] 接口数 ≤ 1 时切换项禁用
- [ ] 关于对话框显示版本信息
- [ ] **Step 6: 检查无 QML 运行时错误**
Run: `journalctl --user -u dde-shell@DDE -n 50 --no-pager | grep -i "qml\|error" | head -20`
Expected: 无 QML 加载或运行时错误。
---
## Self-Review
**Spec 覆盖检查:**
- §1 任务栏图标区 → Task 1 (颜色/函数) + Task 2 (布局) ✓
- §2 左键弹出面板 → Task 4 ✓
- §3 右键菜单 → Task 5 ✓
- §4 Tooltip → Task 3 ✓
- §5 视觉规范 → Task 1 (颜色表) + 各任务内尺寸 ✓
- §6 排除项 → Global Constraints 明确 ✓
**占位符扫描:** 无 TBD/TODO,所有代码块完整。
**类型一致性:**
- `formatSpeedShort` 在 Task 1 定义,Task 2/3 使用 ✓
- `downloadValueColor`/`uploadValueColor` 在 Task 1 定义,Task 2/4 使用 ✓
- `accentGreen`/`accentGreenLight` 在 Task 1 定义,Task 4 使用 ✓
- 颜色派生自 `basePalette` 全局一致 ✓
**已知风险:**
- Task 5 Step 1 的 `Instantiator`+`insertItem`/`removeItem` 写法在不同 QtQuick.Controls 版本可能有差异,提供了 Repeater 备选方案。
- 右键菜单的 `contextMenu.popup()` 默认在鼠标位置弹出,dde-shell 环境下应正常工作;如定位异常可改用 `contextMenu.popup(mouseX, mouseY)`