完成主要交互、高性能组件、国际化和A型传感器数据包接收
This commit is contained in:
421
qml/content/LeftPanel.qml
Normal file
421
qml/content/LeftPanel.qml
Normal file
@@ -0,0 +1,421 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import "."
|
||||
import TactileIPC 1.0
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
width: 350
|
||||
color: Backend.lightMode ? "#F5F5F5" : "#2C2C2C"
|
||||
radius: 8
|
||||
border.color: Qt.rgba(0, 0, 0, 0.08)
|
||||
property color textColor: Backend.lightMode ? "#424242" : "#E0E0E0"
|
||||
|
||||
function formatHexByte(value) {
|
||||
const hex = Number(value).toString(16).toUpperCase()
|
||||
return "0x" + ("00" + hex).slice(-2)
|
||||
}
|
||||
|
||||
function formatHexValue(value) {
|
||||
const hex = Number(value).toString(16).toUpperCase()
|
||||
return "0x" + hex
|
||||
}
|
||||
|
||||
function parseHexValue(text, maxValue) {
|
||||
let trimmed = String(text).trim()
|
||||
if (trimmed.startsWith("0x") || trimmed.startsWith("0X"))
|
||||
trimmed = trimmed.slice(2)
|
||||
if (trimmed.length === 0)
|
||||
return NaN
|
||||
const value = parseInt(trimmed, 16)
|
||||
if (isNaN(value) || value < 0 || value > maxValue)
|
||||
return NaN
|
||||
return value
|
||||
}
|
||||
|
||||
function tr(text) {
|
||||
I18n.retranslateToken
|
||||
return qsTr(text)
|
||||
}
|
||||
|
||||
Material.theme: Backend.lightMode ? Material.Light : Material.Dark
|
||||
Material.accent: Material.Green
|
||||
Material.primary: Material.Green
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 12
|
||||
|
||||
CollapsiblePanel {
|
||||
title: root.tr("连接设置")
|
||||
expanded: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Label {
|
||||
text: root.tr("COM Port")
|
||||
Layout.preferredWidth: 90
|
||||
color: root.textColor
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: portBox
|
||||
Layout.fillWidth: true
|
||||
model: Backend.serial.availablePorts
|
||||
Component.onCompleted: {
|
||||
for (let i = 0; i < portBox.count; i++) {
|
||||
if (portBox.textAt(i) === Backend.serial.portName) {
|
||||
currentIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
onActivated: Backend.serial.portName = currentText
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
text: "\u21bb"
|
||||
onClicked: Backend.serial.refreshPorts()
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Label {
|
||||
text: root.tr("Baud")
|
||||
Layout.preferredWidth: 90
|
||||
color: root.textColor
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
Layout.fillWidth: true
|
||||
model: ["9600", "57600", "115200", "230400", "912600"]
|
||||
Component.onCompleted: {
|
||||
const idx = model.indexOf(String(Backend.serial.baudRate))
|
||||
if (idx >= 0)
|
||||
currentIndex = idx
|
||||
}
|
||||
onActivated: Backend.serial.baudRate = parseInt(currentText)
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Label {
|
||||
text: root.tr("模式")
|
||||
Layout.preferredWidth: 90
|
||||
color: root.textColor
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
Layout.fillWidth: true
|
||||
textRole: "text"
|
||||
model: [
|
||||
{ text: root.tr("从站"), value: "slave" },
|
||||
{ text: root.tr("主站"), value: "master" }
|
||||
]
|
||||
function syncModeIndex() {
|
||||
for (let i = 0; i < model.length; i++) {
|
||||
if (model[i].value === Backend.serial.mode) {
|
||||
currentIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
Component.onCompleted: syncModeIndex()
|
||||
onModelChanged: syncModeIndex()
|
||||
Connections {
|
||||
target: Backend.serial
|
||||
function onModeChanged() {
|
||||
syncModeIndex()
|
||||
}
|
||||
}
|
||||
onActivated: Backend.serial.mode = model[currentIndex].value
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Label {
|
||||
text: root.tr("设备地址")
|
||||
Layout.preferredWidth: 90
|
||||
color: root.textColor
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: addrField
|
||||
Layout.fillWidth: true
|
||||
text: root.formatHexByte(Backend.serial.deviceAddress)
|
||||
placeholderText: "0x01"
|
||||
inputMethodHints: Qt.ImhPreferUppercase
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /^(0x|0X)?[0-9a-fA-F]{1,2}$/
|
||||
}
|
||||
onEditingFinished: {
|
||||
const value = root.parseHexValue(text, 255)
|
||||
if (isNaN(value)) {
|
||||
text = root.formatHexByte(Backend.serial.deviceAddress)
|
||||
return
|
||||
}
|
||||
Backend.serial.deviceAddress = value
|
||||
text = root.formatHexByte(Backend.serial.deviceAddress)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Backend.serial
|
||||
function onDeviceAddressChanged() {
|
||||
addrField.text = root.formatHexByte(Backend.serial.deviceAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Label {
|
||||
text: root.tr("采样周期")
|
||||
Layout.preferredWidth: 90
|
||||
color: root.textColor
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
Layout.fillWidth: true
|
||||
from: 1
|
||||
to: 2000
|
||||
value: Backend.serial.pollIntervalMs
|
||||
enabled: Backend.serial.mode === "slave"
|
||||
onValueModified: Backend.serial.pollIntervalMs = value
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
text: root.tr("连接")
|
||||
highlighted: true
|
||||
enabled: !Backend.serial.connected
|
||||
onClicked: Backend.serial.open()
|
||||
}
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
text: root.tr("断开")
|
||||
enabled: Backend.serial.connected
|
||||
onClicked: Backend.serial.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CollapsiblePanel {
|
||||
title: root.tr("采样参数")
|
||||
expanded: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
Label {
|
||||
text: root.tr("功能码")
|
||||
Layout.preferredWidth: 90
|
||||
color: root.textColor
|
||||
}
|
||||
SpinBox {
|
||||
id: requestFunctionBox
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 255
|
||||
editable: true
|
||||
value: Backend.serial.requestFunction
|
||||
textFromValue: function(value, locale) {
|
||||
return root.formatHexByte(value)
|
||||
}
|
||||
valueFromText: function(text, locale) {
|
||||
const parsed = root.parseHexValue(text, requestFunctionBox.to)
|
||||
return isNaN(parsed) ? requestFunctionBox.value : parsed
|
||||
}
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /^(0x|0X)?[0-9a-fA-F]{1,2}$/
|
||||
}
|
||||
onValueModified: Backend.serial.requestFunction = value
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
Label {
|
||||
text: root.tr("起始地址")
|
||||
Layout.preferredWidth: 90
|
||||
color: root.textColor
|
||||
}
|
||||
SpinBox {
|
||||
id: requestStartAddressBox
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 1000000
|
||||
editable: true
|
||||
value: Backend.serial.requestStartAddress
|
||||
textFromValue: function(value, locale) {
|
||||
return root.formatHexValue(value)
|
||||
}
|
||||
valueFromText: function(text, locale) {
|
||||
const parsed = root.parseHexValue(text, requestStartAddressBox.to)
|
||||
return isNaN(parsed) ? requestStartAddressBox.value : parsed
|
||||
}
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /^(0x|0X)?[0-9a-fA-F]{1,8}$/
|
||||
}
|
||||
onValueModified: Backend.serial.requestStartAddress = value
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
Label {
|
||||
text: root.tr("读取长度")
|
||||
Layout.preferredWidth: 90
|
||||
color: root.textColor
|
||||
}
|
||||
SpinBox {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 65535
|
||||
editable: true
|
||||
value: Backend.serial.requestLength
|
||||
onValueModified: Backend.serial.requestLength = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CollapsiblePanel {
|
||||
title: root.tr("传感器规格")
|
||||
expanded: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
Label {
|
||||
text: root.tr("协议")
|
||||
Layout.preferredWidth: 90
|
||||
color: root.textColor
|
||||
}
|
||||
Label {
|
||||
text: Backend.serial.protocol
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignRight
|
||||
color: root.textColor
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
Label {
|
||||
text: root.tr("型号")
|
||||
Layout.preferredWidth: 90
|
||||
color: root.textColor
|
||||
}
|
||||
Label {
|
||||
text: Backend.serial.sensorModel
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignRight
|
||||
color: root.textColor
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
Label {
|
||||
text: root.tr("规格")
|
||||
Layout.preferredWidth: 90
|
||||
color: root.textColor
|
||||
}
|
||||
Label {
|
||||
text: Backend.serial.sensorGrid
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignRight
|
||||
color: root.textColor
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
text: root.tr("重新识别")
|
||||
highlighted: true
|
||||
}
|
||||
}
|
||||
|
||||
CollapsiblePanel {
|
||||
title: root.tr("显示控制")
|
||||
expanded: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
CheckBox {
|
||||
text: root.tr("显示网络")
|
||||
checked: Backend.showGrid
|
||||
onToggled: Backend.showGrid = checked
|
||||
}
|
||||
CheckBox {
|
||||
text: root.tr("显示坐标轴")
|
||||
checked: false
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
text: root.tr("回放数据")
|
||||
highlighted: true
|
||||
}
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
text: root.tr("导出数据")
|
||||
onClicked: exportDlg.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
}
|
||||
}
|
||||
SaveAsExportDialog {
|
||||
id: exportDlg
|
||||
/* onSaveTo: (folder, filename, format, method) => {
|
||||
console.log("保存目录:", folder)
|
||||
console.log("文件名:", filename)
|
||||
console.log("格式:", format, "方式:", method)
|
||||
} */
|
||||
onSaveTo: (folder, filename, format, method) => {
|
||||
Backend.data.exportHandler(folder, filename, format, method)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user