import QtQuick import QtQuick.Controls import QtQuick.Layouts import FluentUI 1.0 // 单路无人机状态卡片(基于 FluentUI 的 FluFrame/FluText)。 FluFrame { id: card property var drone: ({}) property bool online: drone.online === true signal menuRequested(var drone, real x, real y) width: 300 height: 210 radius: 8 MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: (mouse) => { const p = card.mapToItem(null, mouse.x, mouse.y) card.menuRequested(card.drone, p.x, p.y) } } ColumnLayout { anchors.fill: parent anchors.margins: 14 spacing: 8 RowLayout { spacing: 8 Rectangle { width: 12 height: 12 radius: 6 color: card.online ? "#2ecc71" : "#e74c3c" SequentialAnimation on opacity { running: card.online loops: Animation.Infinite NumberAnimation { to: 0.35; duration: 600 } NumberAnimation { to: 1.0; duration: 600 } } } FluText { text: card.drone.name || "" font.bold: true font.pixelSize: 18 color: FluTheme.fontPrimaryColor Layout.fillWidth: true elide: Text.ElideRight } FluText { text: (card.drone.codec || "").toUpperCase() color: FluTheme.fontSecondaryColor font.pixelSize: 12 } FluText { visible: card.drone.recording === true text: "● REC" color: "#e74c3c" font.pixelSize: 12 font.bold: true } } GridLayout { columns: 2 columnSpacing: 24 rowSpacing: 4 FluText { text: "收包"; color: FluTheme.fontSecondaryColor; font.pixelSize: 13 } FluText { text: card.drone.recv || 0; color: FluTheme.fontPrimaryColor; font.bold: true; font.pixelSize: 13 } FluText { text: "转发"; color: FluTheme.fontSecondaryColor; font.pixelSize: 13 } FluText { text: card.drone.forwarded || 0; color: FluTheme.fontPrimaryColor; font.bold: true; font.pixelSize: 13 } FluText { text: "会话"; color: FluTheme.fontSecondaryColor; font.pixelSize: 13 } FluText { text: card.drone.session || 0; color: FluTheme.fontPrimaryColor; font.pixelSize: 13 } FluText { text: "解密错误"; color: FluTheme.fontSecondaryColor; font.pixelSize: 13 } FluText { text: card.drone.decErr || 0 color: (card.drone.decErr || 0) > 0 ? "#f39c12" : FluTheme.fontPrimaryColor font.pixelSize: 13 } FluText { text: "丢包"; color: FluTheme.fontSecondaryColor; font.pixelSize: 13 } FluText { text: card.drone.lost || 0 color: (card.drone.lost || 0) > 0 ? "#f39c12" : FluTheme.fontPrimaryColor font.pixelSize: 13 } FluText { text: "信号"; color: FluTheme.fontSecondaryColor; font.pixelSize: 13 } FluText { text: (card.drone.rssi || 0) === 0 ? "--" : (card.drone.rssi + " dBm") color: (card.drone.rssi || 0) === 0 ? FluTheme.fontSecondaryColor : ((card.drone.rssi < -70) ? "#f39c12" : "#2ecc71") font.pixelSize: 13 } } FluText { text: card.online ? "在线" : "离线 / 等待信号" color: card.online ? "#2ecc71" : FluTheme.fontSecondaryColor font.pixelSize: 12 } } }